| /* |
| * 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 <cassert> |
| #include <cstring> |
| #include <cstdio> |
| |
| #include <algorithm> |
| #include <list> |
| #include <map> |
| #include <numeric> |
| #include <stack> |
| #include <string> |
| |
| #define MAXLENGTH_FORMATTED_DATE 10 |
| #define MAXLENGTH_FORMATTED_TIME 19 |
| #define MAXLENGTH_CALENDAR_DATE 21 |
| #define MAXLENGTH_FORMATTED_DATETIME 30 |
| |
| #pragma GCC diagnostic push |
| #pragma GCC diagnostic ignored "-Wmissing-field-initializers" |
| |
| extern YYLTYPE yylloc; |
| |
| extern int yylineno, yyleng, yychar; |
| extern char *yytext; |
| |
| bool need_nume_set( bool tf = true ); |
| |
| bool max_errors_exceeded( int nerr ); |
| |
| extern cbl_label_t *next_sentence; |
| void next_sentence_label(cbl_label_t* label) { |
| parser_label_label(label); |
| next_sentence = NULL; |
| // release codegen label structure, so it can be reused. |
| assert(label->structs.goto_trees || mode_syntax_only()); |
| free(label->structs.goto_trees); |
| label->structs.goto_trees = NULL; |
| } |
| |
| void apply_declaratives(); |
| const char * keyword_str( int token ); |
| void labels_dump(); |
| |
| unsigned int cbl_dialects; |
| size_t cbl_gcobol_features; |
| |
| static enum cbl_division_t current_division; |
| static size_t nparse_error = 0; |
| |
| size_t parse_error_inc() { |
| mode_syntax_only(current_division); |
| return ++nparse_error; |
| } |
| size_t parse_error_count() { return nparse_error; } |
| void input_file_status_notify(); |
| |
| #define YYLLOC_DEFAULT(Current, Rhs, N) \ |
| do { \ |
| if (N) \ |
| { \ |
| (Current).first_line = YYRHSLOC (Rhs, 1).first_line; \ |
| (Current).first_column = YYRHSLOC (Rhs, 1).first_column; \ |
| (Current).last_line = YYRHSLOC (Rhs, N).last_line; \ |
| (Current).last_column = YYRHSLOC (Rhs, N).last_column; \ |
| location_dump("parse.c", N, \ |
| "rhs N ", YYRHSLOC (Rhs, N)); \ |
| } \ |
| else \ |
| { \ |
| (Current).first_line = \ |
| (Current).last_line = YYRHSLOC (Rhs, 0).last_line; \ |
| (Current).first_column = \ |
| (Current).last_column = YYRHSLOC (Rhs, 0).last_column; \ |
| } \ |
| location_dump("parse.c", __LINE__, "current", (Current)); \ |
| input_file_status_notify(); \ |
| location_set(Current); \ |
| } while (0) |
| |
| int yylex(void); |
| extern int yydebug; |
| |
| #include <cstdarg> |
| |
| const char * |
| consistent_encoding_check( const YYLTYPE& loc, const char input[] ) { |
| cbl_field_t faux = {}; |
| faux.type = FldAlphanumeric; |
| faux.data.capacity = capacity_cast(strlen(input)); |
| faux.data.initial = input; |
| |
| auto s = faux.internalize(); |
| if( !s ) { |
| error_msg(loc, "inconsistent string literal encoding for '%s'", input); |
| } else { |
| if( s != input ) return s; |
| } |
| return NULL; |
| } |
| |
| const char * original_picture(); |
| char * original_number( char input[] = NULL ); |
| |
| static const relop_t invalid_relop = static_cast<relop_t>(-1); |
| |
| static cbl_refer_t null_reference; |
| static cbl_field_t *literally_one, *literally_zero; |
| |
| cbl_field_t * |
| literal_of( size_t value ) { |
| switch(value) { |
| case 0: return literally_zero; |
| case 1: return literally_one; |
| } |
| cbl_err("logic error: %s: " HOST_SIZE_T_PRINT_UNSIGNED " not supported", |
| __func__, (fmt_size_t)value); |
| return NULL; |
| } |
| |
| enum data_section_t { // values reflect mandatory order |
| not_data_datasect_e, |
| file_datasect_e, |
| working_storage_datasect_e, |
| local_storage_datasect_e, |
| linkage_datasect_e, |
| } current_data_section; |
| |
| static bool current_data_section_set( const YYLTYPE& loc, enum data_section_t ); |
| |
| enum data_clause_t { |
| picture_clause_e = 0x0001, |
| usage_clause_e = 0x0002, |
| value_clause_e = 0x0004, |
| occurs_clause_e = 0x0008, |
| global_clause_e = 0x0010, |
| external_clause_e = 0x0020, |
| justified_clause_e = 0x0040, |
| redefines_clause_e = 0x0080, |
| blank_zero_clause_e = 0x0100, |
| synched_clause_e = 0x0200, |
| sign_clause_e = 0x0400, |
| based_clause_e = 0x0800, |
| same_clause_e = 0x1000, |
| volatile_clause_e = 0x2000, |
| type_clause_e = 0x4000, |
| typedef_clause_e = 0x8000, |
| }; |
| |
| static inline bool |
| has_clause( int data_clauses, data_clause_t clause ) { |
| return clause == (data_clauses & clause); |
| } |
| |
| |
| static bool |
| is_cobol_charset( const char name[] ) { |
| auto eoname = name + strlen(name); |
| auto ok = std::all_of( name, eoname, |
| []( char ch ) { |
| switch(ch) { |
| case '-': |
| case '_': |
| return true; |
| case '$': // maybe one day (IBM allows) |
| return false; |
| break; |
| } |
| return 0 != ISALNUM(ch); |
| } ); |
| return ok; |
| } |
| |
| bool |
| in_procedure_division(void) { |
| return current_division == procedure_div_e; |
| } |
| |
| static inline bool |
| in_file_section(void) { return current_data_section == file_datasect_e; } |
| |
| static cbl_refer_t * |
| intrinsic_inconsistent_parameter( size_t n, cbl_refer_t *args ); |
| |
| static int |
| intrinsic_token_of( const char name[] ); |
| |
| static inline bool |
| namcpy(const YYLTYPE& loc, cbl_name_t tgt, const char *src ) { |
| // snprintf(3): writes at most size bytes (including the terminating NUL byte) |
| auto len = snprintf(tgt, sizeof(cbl_name_t), "%s", src); |
| if( ! (0 < len && len < int(sizeof(cbl_name_t))) ) { |
| error_msg(loc, "name truncated to '%s' (max %zu characters)", |
| tgt, sizeof(cbl_name_t)-1); |
| return false; |
| } |
| return true; |
| } |
| |
| cbl_field_t * |
| new_alphanumeric( size_t capacity = MAXIMUM_ALPHA_LENGTH, |
| const cbl_name_t name = nullptr ); |
| |
| static inline cbl_field_t * |
| new_alphanumeric( const cbl_name_t name ) { |
| return new_alphanumeric(MAXIMUM_ALPHA_LENGTH, name); |
| } |
| |
| static inline cbl_refer_t * |
| new_reference( enum cbl_field_type_t type, const char *initial ) { |
| return new cbl_refer_t( new_temporary(type, initial) ); |
| } |
| static inline cbl_refer_t * |
| new_reference( cbl_field_t *field ) { |
| return new cbl_refer_t(field); |
| } |
| static inline cbl_refer_t * |
| new_reference_like( const cbl_field_t& skel ) { |
| return new cbl_refer_t( new_temporary_like(skel) ); |
| } |
| |
| static void reject_refmod( YYLTYPE loc, const cbl_refer_t& ); |
| static bool require_pointer( YYLTYPE loc, const cbl_refer_t& ); |
| static bool require_integer( YYLTYPE loc, const cbl_refer_t& ); |
| |
| struct cbl_field_t * constant_of( size_t isym ); |
| |
| static const struct cbl_occurs_t nonarray = cbl_occurs_t(); |
| |
| using std::list; |
| |
| static inline bool isquote( char ch ) { |
| return ch == '\'' || ch == '"'; |
| } |
| |
| static inline char * dequote( char input[] ) { |
| char *pend = input + strlen(input) - 1; |
| assert(isquote(*input)); |
| assert(isquote(*pend)); |
| assert(*input == *pend); |
| *input = *pend = '\0'; |
| return ++input; |
| } |
| |
| static const char * |
| name_of( cbl_field_t *field ) { |
| assert(field); |
| return field->name[0] == '_' && field->data.initial? |
| field->data.initial : field->name; |
| } |
| |
| static const char * |
| nice_name_of( cbl_field_t *field ) { |
| auto name = name_of(field); |
| return name[0] == '_'? "" : name; |
| } |
| |
| struct evaluate_elem_t { |
| size_t nother; |
| struct cbl_label_t label; |
| struct cbl_field_t *result; |
| struct case_t { |
| private: |
| relop_t oper; |
| public: |
| cbl_field_t *subject, *object, *cond; |
| explicit case_t( cbl_field_t * subject ) |
| : oper(eq_op) |
| , subject(subject) |
| , object(NULL) |
| , cond( keep_temporary(FldConditional) ) |
| {} |
| |
| cbl_field_t * object_set( cbl_field_t *obj, relop_t op ) { |
| oper = op; |
| return object = obj; |
| } |
| |
| inline relop_t op() const { return oper; } |
| |
| void dump() const { |
| dbgmsg( " cond is '%s'\n\t" |
| "subject is '%s'\n\t" |
| " oper is %s \n\t" |
| " object is '%s'", |
| cond? xstrdup(field_str(cond)) : "none", |
| subject? xstrdup(field_str(subject)) : "none", |
| relop_str(oper), |
| object? xstrdup(field_str(object)) : "none"); |
| } |
| static void Dump( const case_t& c ) { c.dump(); } |
| }; |
| list<case_t> cases; |
| typedef list<case_t>::iterator case_iter; |
| case_iter pcase; |
| |
| void dump() const { |
| dbgmsg( "nother=" HOST_SIZE_T_PRINT_UNSIGNED " label '%s', " |
| HOST_SIZE_T_PRINT_UNSIGNED " cases", |
| (fmt_size_t)nother, label.name, (fmt_size_t)cases.size() ); |
| std::for_each( cases.begin(), cases.end(), case_t::Dump ); |
| } |
| |
| explicit evaluate_elem_t( const char skel[] ) |
| : nother(0) |
| , label{LblEvaluate} |
| , result( keep_temporary(FldConditional) ) |
| , pcase( cases.end() ) |
| { |
| label.line = yylineno; |
| if( -1 == snprintf(label.name, sizeof(label.name), |
| "%.*s_%d", (int)sizeof(label.name)-6, skel, yylineno) ) { |
| yyerror("could not create unique label %<%s_%d%> because it is too long", |
| skel, yylineno); |
| } |
| } |
| |
| size_t ncolumn() const { return cases.size(); } |
| size_t nready() const { |
| size_t n=0; |
| for( const auto& c : cases ) { |
| if( c.object == NULL ) break; |
| n++; |
| } |
| return n; |
| } |
| }; |
| |
| /* |
| * The file_X_args variables hold the arguments to parser_file_X. The |
| * X_body nonterminal collects the arguments, but we defer calling |
| * parser_file_X until either: |
| * 1. end of statement, implying sequentiality, or |
| * 2. ON ERROR, implying random access |
| * In the 2nd case, the call to parser_file_X is made at the top of |
| * the io_error nonterminal, before any statements are parsed. The |
| * effect is to delay the call only until we've parsed ON ERROR. |
| * Because there are no intervening statements, there's no need for a |
| * stack of arguments. One global does the trick. |
| */ |
| static class file_delete_args_t { |
| cbl_file_t *file; |
| public: |
| file_delete_args_t() : file(nullptr) {} |
| void init( cbl_file_t *file ) { |
| this->file = file; |
| } |
| bool ready() const { return file != nullptr; } |
| void call_parser_file_delete( bool sequentially ) { |
| parser_file_delete(file, sequentially); |
| file = nullptr; |
| } |
| } file_delete_args; |
| |
| cbl_round_t current_rounded_mode(); |
| |
| static struct file_read_args_t { |
| cbl_file_t *file; |
| cbl_refer_t record, *read_into; |
| int where; |
| enum { where_unknown = 0 }; |
| |
| file_read_args_t() : file(NULL), read_into(NULL), where(where_unknown) {} |
| |
| void |
| init( struct cbl_file_t *file, |
| const cbl_refer_t& record, |
| cbl_refer_t *read_into, |
| int where ) { |
| this->file = file; |
| this->record = record; |
| this->read_into = read_into; |
| this->where = where; |
| } |
| |
| bool ready() const { return file != NULL; } |
| void default_march( bool sequential ) { |
| if( where == where_unknown ) { |
| where = sequential? -1 : 1; |
| } |
| } |
| |
| void |
| call_parser_file_read( int w = where_unknown) { |
| if( w != where_unknown ) where = w; |
| if( where == where_unknown) { |
| switch( file->access ) { |
| case file_inaccessible_e: |
| case file_access_seq_e: |
| where = -1; |
| break; |
| case file_access_rnd_e: |
| where = 1; |
| break; |
| case file_access_dyn_e: |
| where = 1; |
| break; |
| } |
| } |
| parser_file_read(file, record, where); |
| if( read_into ) { |
| parser_move( *read_into, record, current_rounded_mode() ); |
| } |
| *this = file_read_args_t(); |
| } |
| } file_read_args; |
| |
| static class file_return_args_t { |
| cbl_file_t *file; |
| public: |
| file_return_args_t() : file(NULL) {} |
| void init( cbl_file_t *file ) { |
| this->file = file; |
| } |
| bool ready() const { return file != NULL; } |
| void call_parser_return_start(const cbl_refer_t& into = cbl_refer_t() ) { |
| parser_return_start(file, into); |
| file = NULL; |
| } |
| } file_return_args; |
| |
| static class file_rewrite_args_t { |
| cbl_file_t *file; |
| cbl_field_t *record; |
| public: |
| file_rewrite_args_t() : file(nullptr), record(nullptr) {} |
| void init( cbl_file_t *file, cbl_field_t *record ) { |
| this->file = file; |
| this->record = record; |
| } |
| bool ready() const { return file != nullptr; } |
| void call_parser_file_rewrite( bool sequentially ) { |
| sequentially = sequentially || file->access == file_access_seq_e; |
| if( file->access == file_access_rnd_e ) sequentially = false; |
| parser_file_rewrite(file, record, sequentially); |
| file = nullptr; |
| record = nullptr; |
| } |
| } file_rewrite_args; |
| |
| static class file_start_args_t { |
| cbl_file_t *file; |
| public: |
| file_start_args_t() : file(NULL) {} |
| cbl_file_t * init( YYLTYPE loc, cbl_file_t *file ) { |
| this->file = file; |
| if( is_sequential(file) ) { |
| error_msg(loc, "START invalid with sequential file %s", file->name); |
| } |
| return file; |
| } |
| bool ready() const { return file != NULL; } |
| void call_parser_file_start() { |
| // not needed: parser_file_start(file, sequentially); |
| file = NULL; |
| } |
| } file_start_args; |
| |
| static class file_write_args_t { |
| cbl_file_t *file; |
| cbl_field_t *data_source; |
| bool after; |
| cbl_refer_t *advance; |
| public: |
| file_write_args_t() |
| : file(nullptr) |
| , data_source(nullptr) |
| , after(false) |
| , advance(nullptr) |
| {} |
| cbl_file_t * init( cbl_file_t *file, |
| cbl_field_t *data_source, |
| bool after, |
| const cbl_refer_t *advance ) { |
| this->file = file; |
| this->data_source = data_source; |
| this->after = after; |
| this->advance = new cbl_refer_t(*advance); |
| return this->file; |
| } |
| bool ready() const { return file != nullptr; } |
| void call_parser_file_write( bool sequentially ) { |
| sequentially = sequentially || file->access == file_access_seq_e; |
| parser_file_write(file, data_source, after, *advance, sequentially); |
| *this = file_write_args_t(); |
| } |
| } file_write_args; |
| |
| /* |
| * Fields |
| */ |
| struct group_attr_t { |
| cbl_field_type_t default_usage; // for COMP-5 etc. |
| int encoding; // for ASCII, National, etc. |
| cbl_field_t *field; |
| |
| group_attr_t( cbl_field_t *field, |
| cbl_field_type_t default_usage, |
| int encoding ) |
| : default_usage(default_usage) |
| , encoding(encoding) |
| , field(field) |
| {} |
| }; |
| |
| struct refer_list_t; |
| |
| struct arith_t { |
| cbl_arith_format_t format; |
| list<cbl_num_result_t> tgts; |
| list<cbl_refer_t> A, B; |
| cbl_refer_t remainder; |
| cbl_label_t *on_error, *not_error; |
| |
| explicit arith_t( cbl_arith_format_t format ) |
| : format(format), on_error(NULL), not_error(NULL) |
| {} |
| arith_t( cbl_arith_format_t format, refer_list_t * refers ); |
| |
| bool corresponding() const { return format == corresponding_e; } |
| |
| void another_pair( size_t src, size_t tgt ) { |
| assert(src > 0 && tgt > 0); |
| |
| cbl_refer_t a(A.front()); |
| a.field = cbl_field_of(symbol_at(src)); |
| A.push_back( a ); |
| |
| cbl_num_result_t res = tgts.front(); |
| res.refer.field = cbl_field_of(symbol_at(tgt)); |
| tgts.push_back( res ); |
| |
| dbgmsg("%s:%d: SRC: %3" GCC_PRISZ "u %s", |
| __func__, __LINE__, (fmt_size_t)src, a.str()); |
| dbgmsg("%s:%d: to %3" GCC_PRISZ "u %s", |
| __func__, __LINE__, (fmt_size_t)tgt, res.refer.str()); |
| } |
| void operator()( const corresponding_fields_t::const_reference elem ) { |
| another_pair( elem.first, elem.second ); |
| } |
| |
| const char * format_str() const { |
| switch(format) { |
| case not_expected_e: return "not_expected_e"; |
| case no_giving_e: return "no_giving_e"; |
| case giving_e: return "giving_e"; |
| case corresponding_e: return "corresponding_e"; |
| } |
| return "???"; |
| } |
| }; |
| |
| static cbl_refer_t * ast_op( cbl_refer_t *lhs, char op, cbl_refer_t *rhs ); |
| |
| static void ast_add( arith_t *arith ); |
| static bool ast_subtract( arith_t *arith ); |
| static bool ast_multiply( arith_t *arith ); |
| static bool ast_divide( arith_t *arith ); |
| |
| static cbl_field_type_t intrinsic_return_type( int token ); |
| |
| template <typename T> |
| static T* use_any( list<T>& src, T *tgt) { |
| if( src.empty() ) return NULL; |
| |
| std::copy(src.begin(), src.end(), tgt); |
| src.clear(); |
| |
| return tgt; |
| } |
| template <typename T> |
| static T* use_any( list<T>& src, std::vector<T>& tgt) { |
| if( src.empty() ) return NULL; |
| |
| std::copy(src.begin(), src.end(), tgt.begin()); |
| src.clear(); |
| |
| return tgt.data(); |
| } |
| |
| class evaluate_t; |
| /* |
| * Evaluate |
| */ |
| class eval_subject_t { |
| friend evaluate_t; |
| struct { cbl_label_t *done, *yeah, *when; } labels; |
| cbl_field_t *result; |
| relop_t abbr_relop; |
| typedef std::list<cbl_refer_t> column_list_t; |
| column_list_t columns; |
| column_list_t::iterator pcol; |
| |
| static cbl_label_t * label( const char skel[] ); |
| |
| void new_object_labels(); |
| public: |
| eval_subject_t(); |
| void append( const cbl_refer_t& field ) { |
| columns.push_back(field); |
| pcol = columns.begin(); |
| } |
| cbl_label_t *yeah() { return labels.yeah; } |
| cbl_label_t *when() { return labels.when; } |
| cbl_label_t *done() { return labels.done; } |
| |
| cbl_field_t *subject() const { |
| if( pcol == columns.end() ) return nullptr; |
| return pcol->field; |
| } |
| size_t subject_count() const { return columns.size(); } |
| size_t object_count() { return std::distance(columns.begin(), pcol); } |
| |
| void object_relop( relop_t op ) { abbr_relop = op; } |
| relop_t object_relop() const { return abbr_relop; } |
| |
| void rewind() { pcol = columns.begin(); } |
| |
| bool compatible( const cbl_field_t *object ) const; |
| |
| // compare sets result |
| cbl_field_t * compare( int token ); |
| cbl_field_t * compare( relop_t op, |
| const cbl_refer_t& object, bool deciding = false); |
| cbl_field_t * compare( const cbl_refer_t& object, |
| const cbl_refer_t& object2 = nullptr); |
| |
| void write_when_label() { |
| parser_label_label(labels.when); |
| labels.when = label("when"); |
| } |
| void write_yeah_label() { |
| parser_label_label(labels.yeah); |
| labels.yeah = label("yeah"); |
| } |
| |
| // decide() calls codegen with the result and increments the subject column. |
| // On FALSE, skip past <statements> and fall into next WHEN. |
| bool decided( cbl_field_t *result ) { |
| this->result = result; |
| parser_if( result ); |
| parser_else(); |
| parser_label_goto( labels.when ); |
| parser_fi(); |
| pcol++; |
| return true; |
| } |
| bool decide( int token ) { |
| if( pcol == columns.end() ) return false; |
| if( compare( token ) ) { |
| parser_if( result ); |
| parser_else(); |
| parser_label_goto( labels.when ); |
| parser_fi(); |
| } |
| pcol++; |
| return true; |
| } |
| bool decide( const cbl_refer_t& object, bool invert ) { |
| if( pcol == columns.end() ) return false; |
| if( compare( object ) ) { |
| if( invert ) { |
| parser_logop( result, NULL, not_op, result ); |
| } |
| parser_if( result ); |
| parser_else(); |
| parser_label_goto( labels.when ); |
| parser_fi(); |
| } |
| pcol++; |
| return true; |
| } |
| bool decide( relop_t op, const cbl_refer_t& object, bool invert ) { |
| if( pcol == columns.end() ) return false; |
| dbgmsg("%s() if not %s goto %s", __func__, result->name, when()->name); |
| |
| if( compare(op, object, true) ) { |
| if( invert ) { |
| parser_logop( result, NULL, not_op, result ); |
| } |
| parser_if( result ); |
| parser_else(); |
| parser_label_goto( labels.when ); |
| parser_fi(); |
| } |
| pcol++; |
| return true; |
| } |
| bool decide( const cbl_refer_t& object, const cbl_refer_t& object2, bool invert ) { |
| if( pcol == columns.end() ) return false; |
| if( compare(object, object2) ) { |
| if( invert ) { |
| parser_logop( result, NULL, not_op, result ); |
| } |
| parser_if( result ); |
| parser_else(); |
| parser_label_goto( labels.when ); |
| parser_fi(); |
| } |
| pcol++; |
| return true; |
| } |
| }; |
| |
| class evaluate_t : private std::stack<eval_subject_t> { |
| public: |
| size_t depth() const { return size(); } |
| |
| void alloc() { |
| push(eval_subject_t()); |
| } |
| void free() { assert(!empty()); pop(); } |
| |
| eval_subject_t& current() { |
| assert(!empty()); |
| if( yydebug ) { |
| auto& ev( top() ); |
| dbgmsg("eval_subject: res: %s, When %s, Yeah %s, Done %s", |
| ev.result->name, |
| ev.when()->name, ev.yeah()->name, ev.done()->name); |
| } |
| return top(); |
| } |
| |
| } eval_stack; |
| |
| |
| |
| static void dump_inspect( const cbl_inspect_t& i ); |
| void dump_inspect_match( const cbl_inspect_match_t& M ); |
| |
| struct perform_t { |
| struct cbl_perform_tgt_t tgt; |
| bool before; |
| list<cbl_perform_vary_t> varys; |
| list<cbl_declarative_t> dcls; |
| |
| struct ec_labels_t { |
| cbl_label_t |
| *init, // Format 3, code that installs handlers |
| *fini, // Format 3, code that reverts handlers |
| *top, // Format 3, above imperative-statement-1 |
| *from, // Format 3, imperative-statement-1 |
| *finally, |
| *other, *common; |
| ec_labels_t() |
| : init(NULL), fini(NULL), |
| top(NULL), from(NULL), finally(NULL), |
| other(NULL), common(NULL) |
| {} |
| void generate() { |
| init = new_label( LblLoop, "init" ); |
| fini = new_label( LblLoop, "fini" ); |
| top = new_label( LblLoop, "top" ); |
| from = new_label( LblLoop, "from" ); |
| other = new_label( LblLoop, "other" ); |
| common = new_label( LblLoop, "common" ); |
| finally = new_label( LblLoop, "finally" ); |
| } |
| static cbl_label_t * |
| new_label( cbl_label_type_t type, const cbl_name_t role ); |
| } ec_labels; |
| |
| struct { |
| cbl_label_t *start, *end; |
| cbl_field_t *unsatisfied, *size; |
| cbl_refer_t table; |
| } search; |
| |
| explicit perform_t( cbl_label_t *from, cbl_label_t *to = NULL ) |
| : tgt( from, to ), before(true) |
| , search() |
| {} |
| ~perform_t() { varys.clear(); } |
| cbl_field_t * until() { |
| assert(!varys.empty()); |
| cbl_field_t *f = varys.front().until; |
| assert(f->type == FldConditional); |
| return f; |
| } |
| }; |
| |
| static list<perform_t> performs; |
| |
| static inline perform_t * |
| perform_alloc() { |
| performs.push_back(perform_t(NULL)); |
| return &performs.back(); |
| } |
| |
| static inline void |
| perform_free(void) { |
| assert(performs.size() > 0); |
| performs.pop_back(); |
| } |
| |
| static inline perform_t * |
| perform_current(void) { |
| assert(performs.size() > 0); |
| return &performs.back(); |
| } |
| |
| static inline perform_t * |
| perform_tgt_set( cbl_label_t *from, cbl_label_t *to = NULL ) { |
| struct perform_t *perf = perform_current(); |
| perf->tgt = cbl_perform_tgt_t(from, to); |
| return perf; |
| } |
| |
| #define PERFORM_EXCEPT 1 |
| static void |
| perform_ec_setup() { |
| struct perform_t *perf = perform_current(); |
| perf->ec_labels.generate(); |
| perf->tgt.from( perf->ec_labels.from ); |
| |
| #if PERFORM_EXCEPT |
| parser_label_goto(perf->ec_labels.init); |
| parser_label_label(perf->ec_labels.top); |
| #endif |
| parser_perform_start(&perf->tgt); |
| } |
| |
| static void |
| perform_ec_cleanup() { |
| struct perform_t *perf = perform_current(); |
| #if PERFORM_EXCEPT |
| parser_label_goto(perf->ec_labels.fini); |
| parser_label_label(perf->ec_labels.init); |
| /* ... empty init block ... */ |
| parser_label_goto(perf->ec_labels.top); |
| parser_label_label(perf->ec_labels.fini); |
| #endif |
| } |
| |
| static list<cbl_label_t*> searches; |
| |
| static inline cbl_label_t * |
| search_alloc( cbl_label_t *name ) { |
| searches.push_back(name); |
| return searches.back(); |
| } |
| |
| static inline void |
| search_free(void) { |
| assert(searches.size() > 0); |
| searches.pop_back(); |
| } |
| |
| static inline cbl_label_t * |
| search_current(void) { |
| assert(searches.size() > 0); |
| return searches.back(); |
| } |
| |
| static list<cbl_num_result_t> rhs; |
| typedef list<cbl_num_result_t>::iterator rhs_iter; |
| |
| struct tgt_list_t { |
| list<cbl_num_result_t> targets; |
| }; |
| |
| static struct cbl_label_t * |
| label_add( const YYLTYPE& loc, enum cbl_label_type_t type, const char name[] ); |
| static struct cbl_label_t * |
| label_add( enum cbl_label_type_t type, const char name[], int line ); |
| |
| static struct cbl_label_t * |
| paragraph_reference( const char name[], size_t section ); |
| |
| static inline void |
| list_add( list<cbl_num_result_t>& list, const cbl_refer_t& refer, int round ) { |
| struct cbl_num_result_t arg = { static_cast<cbl_round_t>(round), refer }; |
| list.push_back(arg); |
| } |
| |
| static list<cbl_domain_t> domains; |
| typedef list<cbl_domain_t>::iterator domain_iter; |
| |
| /* |
| * The name queue is a queue of lists of data-item names recognized by the |
| * lexer, but not returned to the parser. These lists are "teed up" by the |
| * lexer until no more qualifiers are found. At that point, the last name is |
| * returned as a NAME or NAME88 token. NAME88 is returned only if a correctly, |
| * uniquely specified Level 88 data item is found in the symbol table (because |
| * else we can't know). |
| * |
| * When the parser gets a NAME or NAME88 token, it retrieves the pending list |
| * of qualifiers, if any, from the name queue. It adds the returned name to |
| * the list and calls symbol_find() to search the name map. For correctly |
| * specified names, the lexer has already done that work, which is now |
| * unfortunately repeated. For incorrect names, the parser emits a most useful |
| * diagnostic. |
| */ |
| static name_queue_t name_queue; |
| |
| void |
| tee_up_empty() { |
| name_queue.allocate(); |
| } |
| void |
| tee_up_name( const YYLTYPE& loc, const char name[] ) { |
| name_queue.push(loc, name); |
| } |
| cbl_namelist_t |
| teed_up_names() { |
| return name_queue_t::namelist_of( name_queue.peek() ); |
| } |
| |
| #define cdf_tokens cdf_current_tokens() |
| |
| int |
| redefined_token( const cbl_name_t name ) { |
| return cdf_tokens.redefined_as(name); |
| } |
| |
| struct file_list_t { |
| list<cbl_file_t*> files; |
| file_list_t() {} |
| explicit file_list_t( cbl_file_t* file ) { |
| files.push_back(file); |
| } |
| file_list_t( file_list_t& that ) : files(that.files.size()) { |
| std::copy( that.files.begin(), that.files.end(), files.begin() ); |
| } |
| |
| static size_t symbol_index( cbl_file_t* file ) { |
| return ::symbol_index( symbol_elem_of(file) ); |
| } |
| }; |
| |
| struct field_list_t { |
| list<cbl_field_t*> fields; |
| field_list_t() {} |
| explicit field_list_t( cbl_field_t *field ) { |
| fields.push_back(field); |
| } |
| std::vector<const cbl_field_t*> |
| as_vector() const { |
| std::vector<const cbl_field_t*> output( fields.begin(), fields.end() ); |
| return output; |
| } |
| }; |
| |
| cbl_field_t ** |
| use_list( field_list_t *src, cbl_field_t *tgt[] ) { |
| assert(src); |
| std::copy(src->fields.begin(), src->fields.end(), tgt); |
| src->fields.clear(); |
| delete src; |
| |
| return tgt; |
| } |
| |
| cbl_file_t ** |
| use_list( list<cbl_file_t*>& src, bool clear = true ) { |
| if( src.empty() ) return NULL; |
| auto tgt = new cbl_file_t*[ src.size() ]; |
| std::copy(src.begin(), src.end(), tgt); |
| |
| if( clear ) |
| src.clear(); |
| |
| return tgt; |
| } |
| |
| struct refer_list_t { |
| list<cbl_refer_t> refers; |
| explicit refer_list_t( cbl_refer_t *refer ) { |
| if( refer ) { |
| refers.push_back(*refer); |
| delete refer; |
| } |
| } |
| refer_list_t * push_back( cbl_refer_t *refer ) { |
| refers.push_back(*refer); |
| delete refer; |
| return this; |
| } |
| inline list<cbl_refer_t>& items() { return refers; } |
| inline list<cbl_refer_t>::iterator begin() { return refers.begin(); } |
| inline list<cbl_refer_t>::iterator end() { return refers.end(); } |
| inline size_t size() const { return refers.size(); } |
| |
| cbl_refer_t * |
| use_list( cbl_refer_t tgt[] ) { |
| std::copy(refers.begin(), refers.end(), tgt); |
| refers.clear(); |
| return tgt; |
| } |
| std::vector<cbl_refer_t> |
| vectorize() { |
| std::vector<cbl_refer_t> tgt(refers.size()); |
| std::copy(refers.begin(), refers.end(), tgt.begin()); |
| refers.clear(); |
| return tgt; |
| } |
| }; |
| |
| struct refer_marked_list_t : public refer_list_t { |
| cbl_refer_t *marker; |
| |
| refer_marked_list_t() : refer_list_t(NULL), marker(NULL) {} |
| refer_marked_list_t( cbl_refer_t *marker, const refer_list_t *refers ) |
| : refer_list_t(*refers), marker(marker) {} |
| refer_marked_list_t( cbl_refer_t *marker, cbl_refer_t *input ) |
| : refer_list_t(input) |
| , marker(marker) {} |
| |
| refer_marked_list_t * push_back( refer_list_t *refers ) { |
| push_back(refers); |
| return this; |
| } |
| refer_marked_list_t * push_on( cbl_refer_t *marker, cbl_refer_t *input ) { |
| refers.push_back(*input); |
| this->marker = marker; |
| return this; |
| } |
| }; |
| |
| struct refer_collection_t { |
| list<refer_marked_list_t> lists; |
| |
| explicit refer_collection_t( const refer_marked_list_t& marked_list ) |
| { |
| lists.push_back( marked_list ); |
| } |
| refer_collection_t * push_back( const refer_marked_list_t& marked_list ) |
| { |
| lists.push_back( marked_list ); |
| return this; |
| } |
| |
| const cbl_refer_t* last_delimiter() const { |
| return lists.back().marker; |
| } |
| cbl_refer_t* last_delimiter( cbl_refer_t* marker) { |
| return lists.back().marker = marker; |
| } |
| |
| size_t total_size() const { |
| size_t n = 0; |
| for( auto p=lists.begin(); p != lists.end(); p++ ) { |
| n += p->refers.size(); |
| } |
| return n; |
| } |
| }; |
| |
| void ast_inspect( YYLTYPE loc, cbl_refer_t& input, bool backward, |
| cbl_inspect_opers_t& inspects ); |
| |
| template <typename E> |
| struct elem_list_t { |
| list<E*> elems; |
| explicit elem_list_t( E *elem ) { |
| elems.push_back(elem); |
| } |
| void clear() { |
| for( auto p = elems.begin(); p != elems.add(); p++ ) { |
| delete *p; |
| } |
| elems.clear(); |
| } |
| }; |
| |
| typedef elem_list_t<cbl_label_t> label_list_t; |
| |
| template <typename L, typename E> |
| E use_list( L *src, E tgt ) { |
| assert(src); |
| std::copy(src->elems.begin(), src->elems.end(), tgt); |
| src->elems.clear(); |
| delete src; |
| |
| return tgt; |
| } |
| |
| struct unstring_tgt_t { |
| cbl_refer_t *tgt, *delimiter, *count; |
| explicit unstring_tgt_t( cbl_refer_t *tgt, |
| cbl_refer_t *delimiter = NULL, |
| cbl_refer_t *count = NULL ) |
| : tgt(tgt), delimiter(delimiter), count(count) |
| {} |
| |
| static cbl_refer_t tgt_of( const unstring_tgt_t& that ) { |
| return maybe_empty(that.tgt); |
| } |
| static cbl_refer_t delimiter_of( const unstring_tgt_t& that ) { |
| return maybe_empty(that.delimiter); |
| } |
| static cbl_refer_t count_of( const unstring_tgt_t& that ) { |
| return maybe_empty(that.count); |
| } |
| private: |
| static cbl_refer_t maybe_empty( cbl_refer_t *p ) { |
| return p? *p : cbl_refer_t(); |
| } |
| }; |
| |
| struct unstring_tgt_list_t { |
| list<unstring_tgt_t> unstring_tgts; |
| |
| explicit unstring_tgt_list_t( unstring_tgt_t *unstring_tgt ) { |
| unstring_tgts.push_back(*unstring_tgt); |
| delete unstring_tgt; |
| } |
| unstring_tgt_list_t * push_back( unstring_tgt_t *unstring_tgt ) { |
| unstring_tgts.push_back(*unstring_tgt); |
| delete unstring_tgt; |
| return this; |
| } |
| |
| size_t size() const { return unstring_tgts.size(); } |
| |
| typedef cbl_refer_t xform_t( const unstring_tgt_t& that ); |
| void use_list( std::vector<cbl_refer_t>& output, xform_t func ) { |
| std::transform( unstring_tgts.begin(), |
| unstring_tgts.end(), |
| output.begin(), func ); |
| } |
| }; |
| |
| struct unstring_into_t : public unstring_tgt_list_t { |
| cbl_refer_t pointer, tally; |
| explicit unstring_into_t( unstring_tgt_list_t *tgt_list, |
| cbl_refer_t *pointer = NULL, |
| cbl_refer_t *tally = NULL ) |
| : unstring_tgt_list_t(*tgt_list) |
| , pointer( pointer? *pointer : cbl_refer_t() ) |
| , tally( tally? *tally : cbl_refer_t() ) |
| { |
| delete tgt_list; |
| if( pointer ) delete pointer; |
| if( tally ) delete tally; |
| } |
| }; |
| |
| struct ffi_args_t { |
| list<cbl_ffi_arg_t> elems; |
| |
| explicit ffi_args_t( cbl_ffi_arg_t *arg ) { |
| this->push_back(arg); |
| } |
| |
| ffi_args_t( size_t narg, cbl_ffi_arg_t *args ) { |
| std::copy(args, args+narg, std::back_inserter(elems)); |
| } |
| |
| // set explicitly, or assume |
| ffi_args_t * push_back( cbl_ffi_arg_t *arg ) { |
| if( arg->crv == by_default_e ) { |
| arg->crv = elems.empty()? by_reference_e : elems.back().crv; |
| } |
| elems.push_back(*arg); |
| delete arg; |
| return this; |
| } |
| |
| // infer reference/content/value from previous |
| ffi_args_t * push_back( cbl_refer_t* refer, |
| cbl_ffi_arg_attr_t attr = none_of_e ) { |
| cbl_ffi_crv_t crv = elems.empty()? by_reference_e : elems.back().crv; |
| cbl_ffi_arg_t arg( crv, refer, attr ); |
| elems.push_back(arg); |
| return this; |
| } |
| void dump() const { |
| int i=0; |
| for( const auto& arg : elems ) { |
| dbgmsg( "%8d) %-10s %-16s %s", i++, |
| cbl_ffi_crv_str(arg.crv), |
| 3 + cbl_field_type_str(arg.refer.field->type), |
| arg.refer.field->pretty_name() ); |
| } |
| } |
| |
| const char * |
| parameter_types() const { |
| auto output = new char[ 1 + elems.size() ]; |
| auto p = std::transform( elems.begin(), elems.end(), output, |
| []( auto arg ) { |
| return function_descr_t::parameter_type(*arg.field()); |
| } ); |
| assert(output < p); |
| p[-1] = '\0'; |
| return output; |
| } |
| }; |
| |
| struct relop_abbr_t { |
| relop_t relop; |
| cbl_refer_t *rhs; |
| }; |
| |
| typedef struct elem_list_t<relop_abbr_t> relop_abbr__list_t; |
| |
| #pragma GCC diagnostic push |
| #pragma GCC diagnostic ignored "-Wreorder" |
| |
| struct sort_key_t : public field_list_t { |
| bool ascending; |
| sort_key_t( bool ascending, field_list_t key ) |
| : ascending(ascending), field_list_t(key) |
| {} |
| }; |
| |
| #pragma GCC diagnostic pop |
| |
| struct sort_keys_t { |
| list<sort_key_t> key_list; |
| }; |
| |
| struct file_sort_io_t { |
| file_list_t file_list; |
| cbl_perform_tgt_t tgt; |
| |
| explicit file_sort_io_t( file_list_t& files ) : file_list(files) {} |
| explicit file_sort_io_t( cbl_perform_tgt_t& tgt ) : tgt(tgt.from(), tgt.to()) {} |
| size_t nfile() const { return file_list.files.size(); } |
| }; |
| |
| |
| struct merge_t { |
| cbl_file_t *master; |
| list<cbl_file_t*> updates; |
| // collation missing |
| enum output_type_t { output_unknown_e, |
| output_proc_e, |
| output_file_e } type; |
| cbl_perform_tgt_t tgt; |
| list<cbl_file_t*> outputs; |
| |
| explicit merge_t( cbl_file_t *input ) : master(input), type(output_unknown_e) {} |
| }; |
| |
| static list<merge_t> merges; |
| |
| static inline merge_t& |
| merge_alloc( cbl_file_t *file ) { |
| merges.push_back(merge_t(file)); |
| return merges.back(); |
| } |
| |
| static inline void |
| merge_free(void) { |
| assert(merges.size() > 0); |
| merges.pop_back(); |
| } |
| |
| static inline merge_t& |
| merge_current(void) { |
| assert(merges.size() > 0); |
| return merges.back(); |
| } |
| |
| static list<cbl_refer_t> lhs; |
| |
| struct vargs_t { |
| std::list<cbl_refer_t> args; |
| vargs_t() {} |
| explicit vargs_t( struct cbl_refer_t *p ) { args.push_back(*p); delete p; } |
| void push_back( cbl_refer_t *p ) { args.push_back(*p); delete p; } |
| }; |
| |
| static const char intermediate[] = ":intermediate"; |
| |
| #include <set> |
| |
| std::set<const char *> pristine_values; |
| |
| // key is a name after DEBUGGING/ERROR/EXCEPTION |
| // value is the list of sections invoked |
| std::map<std::string, std::list<std::string>> |
| debugging_clients, error_clients, exception_clients; |
| |
| class prog_descr_t { |
| std::set<std::string> call_targets, subprograms; |
| public: |
| std::set<function_descr_t> function_repository; |
| size_t program_index; |
| cbl_label_t *declaratives_eval, *paragraph, *section; |
| const char *collating_sequence; |
| struct locale_t { |
| cbl_name_t name; const char *os_name; |
| locale_t() : name(""), os_name(nullptr) {} |
| locale_t(const cbl_name_t name, const char *os_name) |
| : name(""), os_name(os_name) { |
| if( name ) { |
| bool ok = namcpy(YYLTYPE(), this->name, name); |
| gcc_assert(ok); |
| } |
| } |
| } locale; |
| cbl_options_t options; |
| |
| explicit prog_descr_t( size_t isymbol ) |
| : program_index(isymbol) |
| , declaratives_eval(NULL) |
| , paragraph(NULL) |
| , section(NULL) |
| , collating_sequence(NULL) |
| {} |
| |
| std::set<std::string> external_targets() { |
| std::set<std::string> externals; |
| std::set_difference( call_targets.begin(), call_targets.end(), |
| subprograms.begin(), subprograms.end(), |
| std::inserter(externals, externals.begin()) ); |
| return externals; |
| } |
| }; |
| |
| static char * |
| uniq_label_impl( const char stem[], int line ) { |
| char *name = xasprintf("%s_%d_%d", stem, yylineno, line); |
| return name; |
| } |
| #define uniq_label(S) uniq_label_impl( (S), __LINE__ ) |
| |
| /* |
| * One of these days, paragraph and section will have to move into |
| * prog_descr_t, because the current section and paragraph depend on the |
| * current program, which may be nested and "pop back" into existence at END |
| * PROGRAM. |
| */ |
| struct error_labels_t { |
| cbl_label_t *on_error, *not_error, *compute_error; |
| error_labels_t() : on_error(NULL), not_error(NULL), compute_error(NULL) {} |
| void clear() { on_error = not_error = compute_error = NULL; } |
| error_labels_t& generate() { |
| on_error = label_add(LblArith, uniq_label("arith"), yylineno); |
| not_error = label_add(LblArith, uniq_label("arith"), yylineno); |
| compute_error = label_add(LblCompute, uniq_label("compute"), yylineno); |
| return *this; |
| } |
| }; |
| |
| struct cbl_typedef_less { |
| bool operator()( const cbl_field_t *a, const cbl_field_t *b ) const { |
| auto result = strcasecmp(a->name, b->name); |
| if( result < 0 ) return true; |
| if( result > 0 ) return false; |
| |
| // Names that match are different if they're in different programs |
| // and neither is external. |
| auto lhs = field_index(a); |
| auto rhs = field_index(b); |
| if( lhs != rhs ) { |
| if( !a->has_attr(external_e) && !b->has_attr(external_e) ) { |
| return lhs < rhs; |
| } |
| } |
| return false; |
| } |
| }; |
| |
| static bool |
| is_conditional( const cbl_field_t *field ) { |
| return FldConditional == field->type; |
| } |
| static bool |
| is_conditional( const cbl_refer_t *refer ) { |
| return is_conditional(refer->field); |
| } |
| |
| typedef std::set< const cbl_field_t*, cbl_typedef_less > unique_typedefs_t; |
| |
| static cbl_label_t * implicit_paragraph(); |
| static cbl_label_t * implicit_section(); |
| |
| /* |
| * Incomplete because not needed at this time: we do not attempt to |
| * set used/lain for labels used by these functions: |
| * parser_lsearch_start( cbl_label_t *name, |
| * parser_lsearch_conditional(cbl_label_t * name) |
| * parser_lsearch_when( cbl_label_t *name, cbl_field_t *conditional ) |
| * parser_lsearch_end( cbl_label_t *name ) |
| * parser_bsearch_start( cbl_label_t* name, |
| * parser_bsearch_conditional( cbl_label_t* name ) |
| * parser_bsearch_when(cbl_label_t* name, |
| * parser_bsearch_end( cbl_label_t* name ) |
| * parser_string_overflow( cbl_label_t *name ) |
| * parser_string_overflow_end( cbl_label_t *name ) |
| * parser_call_exception( cbl_label_t *name ) |
| * parser_call_exception_end( cbl_label_t *name ) |
| * parser_entry_activate( size_t iprog, const cbl_label_t *declarative ) |
| */ |
| |
| class program_stack_t : protected std::stack<prog_descr_t> { |
| struct pending_t { |
| bool initial; |
| pending_t() : initial(false) {} |
| } pending; |
| public: |
| bool pending_initial() { return pending.initial = true; } |
| |
| void push( prog_descr_t descr ) { |
| std::stack<prog_descr_t>& me(*this); |
| me.push(descr); |
| } |
| inline void pop() { |
| std::stack<prog_descr_t>& me(*this); |
| me.pop(); |
| } |
| inline prog_descr_t& top() { |
| std::stack<prog_descr_t>& me(*this); |
| return me.top(); |
| } |
| inline const prog_descr_t& top() const { |
| const std::stack<prog_descr_t>& me(*this); |
| return me.top(); |
| } |
| inline size_t size() const { |
| const std::stack<prog_descr_t>& me(*this); |
| return me.size(); |
| } |
| inline bool empty() const { |
| const std::stack<prog_descr_t>& me(*this); |
| return me.empty(); |
| } |
| |
| void apply_pending() { |
| if( pending.initial ) { |
| auto e = symbol_at(top().program_index); |
| auto prog(cbl_label_of(e)); |
| prog->initial = pending.initial; |
| } |
| } |
| |
| // cppcheck-suppress-begin useStlAlgorithm |
| cbl_label_t *first_declarative() { |
| auto eval = top().declaratives_eval; |
| if( eval ) return eval; |
| // scan stack container for declaratives |
| for( const auto& prog : c ) { |
| if( prog.declaratives_eval ) { |
| eval = prog.declaratives_eval; |
| break; |
| } |
| } |
| return eval; |
| } |
| // cppcheck-suppress-end useStlAlgorithm |
| }; |
| |
| struct rel_part_t { |
| cbl_refer_t *operand; // lhs |
| bool has_relop, invert; |
| relop_t relop; |
| |
| rel_part_t() |
| : operand(nullptr), |
| has_relop(false), |
| invert(false), |
| relop(relop_t(-1)) |
| {} |
| rel_part_t( cbl_refer_t *operand, relop_t relop, bool invert ) |
| : operand(operand), |
| has_relop(relop != -1), |
| invert(invert), |
| relop(relop) |
| {} |
| rel_part_t& relop_set( relop_t op ) { |
| has_relop = true; |
| relop = op; |
| return *this; |
| } |
| |
| bool is_value() const { return operand && is_elementary(operand->field->type); } |
| }; |
| |
| /* |
| * Evaluation of OR is deferred in case it's followed by AND. As each |
| * logical operand is encountered, it's first assigned to the |
| * "andable" member. As ANDs are encountered, they're ANDed to |
| * andable. When OR is first encountered, we've reached the end of a |
| * string of ANDs (possibly empty): we move andable to orable, and |
| * assign the rhs to andable (because it could be followed by AND). |
| * Successive ORs produce (orable = orable OR andable), followed by |
| * assigning the rhs to andable. |
| * |
| * At the end of the AND/OR evaluation, there is always an andable |
| * value, because that's where we began. If there is a orable, that |
| * indicates that the final OR remains unevaluated. In the resolve() |
| * method, we OR the two, and return that orable. If there's no |
| * orable, we simply return the andable. |
| */ |
| class log_expr_t { |
| cbl_field_t *orable, *andable; |
| public: |
| explicit log_expr_t( cbl_field_t *init ) : orable(NULL), andable(init) { |
| if( ! is_conditional(init) ) { |
| dbgmsg("%s:%d: logic error: %s is not a truth value", |
| __func__, __LINE__, name_of(init)); |
| } |
| } |
| |
| cbl_field_t * and_term() { |
| return andable; |
| } |
| log_expr_t * and_term( cbl_field_t *rhs ) { |
| if( ! is_conditional(rhs) ) { |
| dbgmsg("%s:%d: logic error: %s is not a truth value", |
| __func__, __LINE__, name_of(rhs)); |
| } else { |
| parser_logop( andable, andable, and_op, rhs ); |
| } |
| return this; |
| } |
| log_expr_t * or_term( cbl_field_t *rhs ) { |
| if( ! is_conditional(rhs) ) { |
| dbgmsg("%s:%d: logic error: %s is not a truth value", |
| __func__, __LINE__, name_of(rhs)); |
| return this; |
| } |
| if( ! orable ) { |
| orable = andable; |
| } else { |
| parser_logop( orable, orable, or_op, andable ); |
| } |
| andable = rhs; |
| return this; |
| } |
| cbl_field_t * resolve() { |
| assert(andable); |
| if( orable ) { |
| parser_logop( andable, orable, or_op, andable ); |
| orable = NULL; |
| } |
| assert(!orable); |
| return andable; // leave in (initial) ANDable state |
| } |
| bool unresolved() const { |
| return orable != NULL; |
| } |
| }; |
| |
| static void ast_enter_section( cbl_label_t * ); |
| static void ast_enter_paragraph( cbl_label_t * ); |
| |
| static class current_t { |
| friend cbl_options_t current_options(); |
| cbl_options_t options_paragraph; |
| program_stack_t programs; |
| unique_typedefs_t typedefs; |
| std::set<function_descr_t> udfs; |
| int first_statement; |
| bool in_declaratives; |
| // from command line or early TURN |
| std::list<exception_turn_t> exception_turns; |
| |
| error_labels_t error_labels; |
| |
| static void declarative_execute( cbl_label_t *eval ) { |
| assert(eval); |
| auto iprog = symbol_elem_of(eval)->program; |
| if( iprog == current_program_index() ) { |
| parser_perform(eval); |
| } else { |
| parser_entry_activate( iprog, eval ); |
| auto name = cbl_label_of(symbol_at(iprog))->name; |
| cbl_unimplemented("Global declarative %s for %s", |
| eval->name, name); |
| parser_call( new_literal(strlen(name), name, quoted_e), |
| cbl_refer_t(), 0, NULL, NULL, NULL, false ); |
| } |
| } |
| |
| rel_part_t antecedent_cache; |
| |
| public: |
| current_t() |
| : first_statement(0) |
| , in_declaratives(false) |
| {} |
| |
| bool option( cbl_options_t::arith_t option ) { |
| if( programs.size() == 1 ) { |
| options_paragraph.arith = option; |
| return true; |
| } |
| return false; |
| } |
| bool option_binary( cbl_options_t::float_endidanism_t option ) { |
| if( programs.size() == 1 ) { |
| options_paragraph.binary_endidanism = option; |
| return true; |
| } |
| return false; |
| } |
| bool option_decimal( cbl_options_t::float_endidanism_t option ) { |
| if( programs.size() == 1 ) { |
| options_paragraph.decimal_endidanism = option; |
| return true; |
| } |
| return false; |
| } |
| bool option( cbl_options_t::float_encoding_t option ) { |
| if( programs.size() == 1 ) { |
| options_paragraph.float_encoding = option; |
| return true; |
| } |
| return false; |
| } |
| bool default_round( cbl_round_t option ) { |
| if( programs.size() == 1 ) { |
| options_paragraph.default_round = option; |
| return true; |
| } |
| return false; |
| } |
| bool intermediate_round( cbl_round_t option ) { |
| if( programs.size() == 1 ) { |
| options_paragraph.intermediate_round = option; |
| return true; |
| } |
| return false; |
| } |
| |
| template <typename T> |
| bool initial_option( cbl_section_type_t section, T value ) { |
| if( programs.size() == 1 ) { |
| switch( section ) { |
| case file_sect_e: |
| case linkage_sect_e: |
| break; |
| case working_sect_e: |
| options_paragraph.initial_value.working = value; |
| return true; |
| break; |
| case local_sect_e: |
| options_paragraph.initial_value.local = value; |
| return true; |
| break; |
| } |
| } |
| return false; |
| } |
| |
| bool initial_value( cbl_section_type_t section, size_t isym ) { |
| return initial_option( section, isym ); |
| } |
| |
| cbl_enabled_exceptions_t enabled_exception_cache; |
| |
| typedef std::list<cbl_declarative_t> declaratives_list_t; |
| class declaratives_t : protected declaratives_list_t { |
| struct file_exception_t { |
| ec_type_t type; uint32_t file; |
| file_exception_t() : type(ec_none_e), file(0) {} |
| file_exception_t(ec_type_t type, uint32_t file) |
| : type(type), file(file) |
| {} |
| bool operator<( const file_exception_t& that ) const { |
| if( type == that.type ) return file < that.file; |
| return type < that.type; |
| } |
| }; |
| std::set<file_exception_t> file_exceptions; |
| public: |
| declaratives_t() {} |
| // current compiled data for enabled ECs and Declaratives, used by library. |
| struct runtime_t { |
| tree ena, dcl; |
| runtime_t() : ena(nullptr), dcl(nullptr) {} |
| } runtime; |
| |
| bool empty() const { |
| return declaratives_list_t::empty(); |
| } |
| inline const declaratives_list_t& as_list() const { return *this; } |
| |
| bool add( const_reference declarative ) { |
| auto d = std::find_if( begin(), end(), |
| [sect = declarative.section]( const_reference decl ) { |
| return decl.section == sect; |
| } ); |
| if( d != end() ) { |
| auto label = cbl_label_of(symbol_at(d->section)); |
| yyerror("USE already defined for %s", label->name); |
| return false; |
| } |
| for( auto f = declarative.files; |
| f && f < declarative.files + declarative.nfile; f++ ) { |
| file_exception_t ex( declarative.type, *f ); |
| auto result = file_exceptions.insert(ex); |
| if( ! result.second ) { |
| yyerror("%s defined twice for %s", |
| ec_type_str(declarative.type), |
| cbl_file_of(symbol_at(*f))->name); |
| return false; |
| } |
| } |
| declaratives_list_t::push_back(declarative); |
| return true; |
| } |
| |
| // cppcheck-suppress-begin useStlAlgorithm |
| uint32_t status() const { |
| uint32_t status_word = 0; |
| for( auto dcl : *this ) { |
| status_word |= (EC_ALL_E & dcl.type ); |
| } |
| return status_word; |
| } |
| // cppcheck-suppress-end useStlAlgorithm |
| |
| bool has_format_1() const { |
| return std::any_of( begin(), end(), |
| []( const cbl_declarative_t& dcl ) { |
| return dcl.is_format_1(); |
| } ); |
| } |
| |
| std::vector<uint64_t> |
| encode() const { |
| std::vector<uint64_t> encoded; |
| auto p = std::back_inserter(encoded); |
| for( const auto& dcl : *this ) { |
| *p++ = dcl.section; |
| *p++ = dcl.global; |
| *p++ = dcl.type; |
| *p++ = dcl.nfile; |
| p = std::copy(dcl.files, std::end(dcl.files), p); |
| *p++ = dcl.mode; |
| } |
| return encoded; |
| } |
| |
| } declaratives; |
| |
| void exception_add( ec_type_t ec, bool enabled = true) { |
| exception_turns.push_back(exception_turn_t(ec, enabled)); |
| } |
| std::list<exception_turn_t>& pending_exceptions() { |
| return exception_turns; |
| } |
| |
| bool typedef_add( const cbl_field_t *field ) { |
| auto result = typedefs.insert(field); |
| return result.second; |
| } |
| const cbl_field_t * has_typedef( const cbl_field_t *field ) { |
| auto found = typedefs.find(field); |
| return found == typedefs.end()? NULL : *found; |
| } |
| |
| void udf_add( size_t isym ) { |
| auto udf = function_descr_t::init(isym); |
| auto p = udfs.insert(udf); |
| assert(p.second); |
| } |
| const function_descr_t * udf_in( const char name[] ) { |
| auto udf = function_descr_t::init(name); |
| auto p = udfs.find(udf); |
| const function_descr_t *output = NULL; |
| if( p != udfs.end() ) output = &*p; |
| return output; |
| } |
| void udf_update( const ffi_args_t *ffi_args ); |
| bool udf_args_valid( const cbl_label_t *func, |
| const std::list<cbl_refer_t>& args, |
| std::vector<function_descr_arg_t>& params /*out*/ ); |
| |
| void udf_dump() const { |
| if( yydebug ) { |
| int i=0; |
| for( auto udf : udfs ) { |
| dbgmsg("%4d %-30s %-30s", i++, keyword_str(udf.token), udf.name); |
| } |
| } |
| } |
| |
| void repository_add_all(); |
| bool repository_add( const char name[] ); |
| int repository_in( const char name[] ); |
| |
| bool repository_add( size_t isym ) { |
| auto udf = function_descr_t::init(isym); |
| auto p = udfs.find(udf); // previously defined functions in "udfs" |
| assert(p != udfs.end()); // If it's a symbol, it must be in udfs. |
| auto result = programs.top().function_repository.insert(*p); |
| if( yydebug ) { |
| for( auto descr : programs.top().function_repository ) { |
| dbgmsg("%s:%d: %-20s %-20s %-20s", __func__, __LINE__, |
| keyword_str(descr.token), descr.name, descr.cname); |
| } |
| } |
| return result.second; |
| } |
| |
| size_t declarative_section() const { |
| return symbol_index(symbol_elem_of(programs.top().section)); |
| } |
| const char * declarative_section_name() const { |
| return in_declaratives? programs.top().section->name : NULL; |
| } |
| |
| std::list<std::string>& debugging_declaratives(bool all) const { |
| const char *para = programs.top().paragraph->name; |
| auto client = debugging_clients.find(all? ":all:" : para); |
| if( client == debugging_clients.end() ) { |
| static std::list<std::string> empty; |
| return empty; |
| } |
| return client->second; |
| } |
| |
| bool |
| collating_sequence( const cbl_name_t name ) { |
| assert(name); |
| assert(!programs.empty()); |
| prog_descr_t& program = programs.top(); |
| if( program.collating_sequence ) return false; // already defined |
| program.collating_sequence = name; |
| return true; |
| } |
| const char * |
| collating_sequence() const { |
| assert(!programs.empty()); |
| return programs.top().collating_sequence; |
| } |
| |
| cbl_round_t rounded_mode() const { return programs.top().options.default_round; } |
| cbl_round_t rounded_mode( cbl_round_t mode ) { |
| return programs.top().options.default_round = mode; |
| } |
| |
| const char * |
| locale() { |
| return programs.empty()? NULL : programs.top().locale.os_name; |
| } |
| const char * |
| locale( const cbl_name_t name ) { |
| if( programs.empty() ) return NULL; |
| const prog_descr_t::locale_t& locale = programs.top().locale; |
| return 0 == strcmp(name, locale.name)? locale.name : NULL; |
| } |
| const prog_descr_t::locale_t& |
| locale( const cbl_name_t name, const char os_name[] ) { |
| if( programs.empty() ) { |
| static prog_descr_t::locale_t empty; |
| return empty; |
| } |
| return programs.top().locale = prog_descr_t::locale_t(name, os_name); |
| } |
| |
| bool new_program ( const YYLTYPE& loc, cbl_label_type_t type, |
| const char name[], const char os_name[], |
| bool common, bool initial ) |
| { |
| size_t parent = programs.empty()? 0 : programs.top().program_index; |
| cbl_label_t label = {}; |
| label.type = type; |
| label.parent = parent; |
| label.line = yylineno; |
| label.common = common; |
| label.initial = initial; |
| label.os_name = os_name; |
| if( !namcpy(loc, label.name, name) ) { gcc_unreachable(); } |
| |
| const cbl_label_t *L; |
| if( (L = symbol_program_add(parent, &label)) == NULL ) return false; |
| programs.push( prog_descr_t(symbol_index(symbol_elem_of(L))) ); |
| programs.apply_pending(); |
| |
| bool fOK = symbol_at(programs.top().program_index) + 1 == symbols_end(); |
| assert(fOK); |
| |
| if( (L = symbol_program_local(name)) != NULL ) { |
| error_msg(loc, "program '%s' already defined on line %d", |
| L->name, L->line); |
| return false; |
| } |
| |
| options_paragraph = cbl_options_t(); |
| first_statement = 0; |
| |
| return fOK; |
| } |
| |
| void program_needs_initial() { programs.pending_initial(); } |
| |
| size_t program_index(void) const { |
| assert(!programs.empty()); |
| return programs.top().program_index; |
| } |
| const cbl_label_t * program(void) { |
| return programs.empty()? |
| NULL : cbl_label_of(symbol_at(programs.top().program_index)); |
| } |
| cbl_label_t * section(void) { |
| return programs.empty()? NULL : programs.top().section; |
| } |
| cbl_label_t * paragraph(void) { |
| return programs.empty()? NULL : programs.top().paragraph; |
| } |
| |
| bool is_first_statement( const YYLTYPE& loc ) { |
| if( ! in_declaratives && first_statement == 0 ) { |
| auto eval = programs.top().declaratives_eval; |
| if( eval ) { |
| size_t ilabel = symbol_index(symbol_elem_of(eval)); |
| if( ! symbol_label_section_exists(ilabel) ) { |
| if( ! dialect_ibm() ) { |
| error_msg(loc, |
| "Per ISO a program with DECLARATIVES must begin with a SECTION, " |
| "requires %<-dialect ibm%>"); |
| } |
| } |
| } |
| first_statement = loc.first_line; |
| return true; |
| } |
| return false; |
| } |
| |
| /* |
| * At the end of each program, ensure there are no uses of an ambiguous |
| * procedure (SECTION or PARAGRAPH) name. At the end of a top-level program, |
| * adjust any CALL targets to use the mangled name of the internal (contained |
| * or COMMON ) program. We ensure there are no duplicate program names, per |
| * ISO, in new_program. |
| */ |
| std::set<std::string> end_program() { |
| cbl_enabled_exceptions_t& enabled_exceptions( cdf_enabled_exceptions() ); |
| if( enabled_exceptions.size() ) { |
| declaratives_evaluate(); |
| } |
| |
| assert(!programs.empty()); |
| |
| const procref_t *ref = ambiguous_reference(program_index()); |
| std::set<std::string> externals = programs.top().external_targets(); |
| |
| /* |
| * For each called local program, replace the original undecorated |
| * target with the mangled name. |
| * |
| * At END-PROGRAM for the top-level program, we know all |
| * subprograms, and whether or not they are COMMON. PROGRAM may be |
| * the caller, or a subprogram could call COMMON sibling. |
| */ |
| |
| static std::unordered_set<size_t> callers_we_have_seen; |
| if( programs.size() == 1 ) { |
| if( yydebug ) parser_call_targets_dump(); |
| for( size_t caller : symbol_program_programs() ) { |
| // We are running through the entire growing list of called programs |
| // at the point of each END PROGRAM. This confuses the name changing |
| // routines, so we use a std::set to avoid doing callers more than |
| // once. |
| if( callers_we_have_seen.find(caller) != callers_we_have_seen.end() ) |
| { |
| continue; |
| } |
| const char *caller_name = cbl_label_of(symbol_at(caller))->name; |
| for( auto callable : symbol_program_callables(caller) ) { |
| auto called = cbl_label_of(symbol_at(callable)); |
| auto mangled_name = |
| called->mangled_name? called->mangled_name : called->name; |
| |
| size_t n = |
| parser_call_target_update(caller, |
| called->name, |
| mangled_name); |
| // Zero is not an error |
| dbgmsg("updated " HOST_SIZE_T_PRINT_UNSIGNED |
| " calls from #%-3" GCC_PRISZ "u (%s) s/%s/%s/", |
| (fmt_size_t)n, (fmt_size_t)caller, caller_name, |
| called->name, mangled_name); |
| } |
| callers_we_have_seen.insert(caller); |
| } |
| if( yydebug ) parser_call_targets_dump(); |
| } |
| |
| parser_leave_paragraph( programs.top().paragraph ); |
| parser_leave_section( programs.top().section ); |
| programs.pop(); |
| |
| debugging_clients.clear(); |
| error_clients.clear(); |
| exception_clients.clear(); |
| |
| if( ref ) { |
| yywarn("could not resolve paragraph (or section) '%s' at line %d", |
| ref->paragraph(), ref->line_number()); |
| // add string to indicate ambiguity error |
| externals.insert(":ambiguous:"); |
| } |
| return externals; |
| } |
| |
| size_t program_level() const { return programs.size(); } |
| |
| size_t program_section() const { |
| if( programs.empty() || programs.top().section == NULL ) return 0; |
| auto section = programs.top().section; |
| return symbol_index(symbol_elem_of(section)); |
| } |
| |
| cbl_label_t * doing_declaratives( bool begin ) { |
| if( begin ) { |
| in_declaratives = true; |
| return NULL; |
| } |
| assert( !begin ); |
| in_declaratives = false; |
| if( declaratives.empty() ) return NULL; |
| assert(!declaratives.empty()); |
| |
| declaratives.runtime.dcl = parser_compile_dcls(declaratives.encode()); |
| |
| // Create section to evaluate declaratives. Given them unique names so |
| // that we can figure out what is going on in a trace or looking at the |
| // assembly language. |
| static int eval_count = 1; |
| char eval[32], lave[32]; |
| |
| sprintf(eval, "_DECLARATIVES_EVAL%d", eval_count); |
| sprintf(lave, "_DECLARATIVES_LAVE%d", eval_count++); |
| |
| struct cbl_label_t*& eval_label = programs.top().declaratives_eval; |
| eval_label = label_add(LblSection, eval, yylineno); |
| struct cbl_label_t * lave_label = label_add(LblSection, lave, yylineno); |
| |
| ast_enter_section(eval_label); |
| |
| declarative_runtime_match(declaratives.as_list(), lave_label); |
| |
| parser_label_label(lave_label); |
| |
| return lave_label; |
| } |
| |
| cbl_label_t * new_section( cbl_label_t * section ) { |
| std::swap( programs.top().section, section ); |
| return section; |
| } |
| |
| ec_type_t ec_type_of( file_status_t status ) { |
| static 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_io_imp_e, // unused, not defined by ISO |
| /* 9 */ ec_io_imp_e, |
| }; |
| int status10 = static_cast<unsigned int>(status) / 10; |
| gcc_assert(ec_by_status.size() == 10); |
| gcc_assert(0 <= status10 && status10 < 10 && status10 != 8); |
| return ec_by_status[status10]; |
| } |
| |
| /* |
| * END DECLARATIVES causes: |
| * 1. Create section _DECLARATIVES_EVAL |
| * and exit label _DECLARATIVES_LAVE |
| * 2. declarative_runtime_match generates runtime evaluation "ladder". |
| * 3. After a declarative is executed, control branches to the exit label. |
| * |
| * After each verb, we call declaratives_evaluate, |
| * which PERFORMs _DECLARATIVES_EVAL. |
| * |
| * If the matched declarative is defined by a superior program as |
| * GLOBAL, it cannot be PERFORMed. Instead, it is CALLed with an |
| * alternative entry point (TODO). |
| */ |
| void |
| declaratives_evaluate( cbl_file_t *file ) { |
| gcc_assert(file); |
| parser_file_stash(file); |
| |
| cbl_label_t *eval = programs.first_declarative(); |
| if( eval ) { |
| auto iprog = symbol_elem_of(eval)->program; |
| if( iprog == current_program_index() ) { |
| parser_perform(eval); |
| } else { |
| parser_entry_activate( iprog, eval ); |
| auto name = cbl_label_of(symbol_at(iprog))->name; |
| parser_call( new_literal(strlen(name), name, quoted_e), |
| cbl_refer_t(), 0, NULL, NULL, NULL, false ); |
| } |
| } |
| } |
| |
| void |
| declaratives_evaluate( std::list<cbl_file_t*>& files ) { |
| for( auto& file : files ) { |
| declaratives_evaluate(file); |
| } |
| } |
| |
| /* |
| * To indicate to the runtime-match function that we want to evaluate |
| * only the exception condition, unrelated to a file, we set the |
| * file register to 0 and the handled-exception register to the |
| * handled exception condition. |
| * |
| * declaratives_execute performs the "declarative ladder" produced |
| * by declaratives_runtime_match. That section CALLs the |
| * runtime-match procedure __gg__match_exception, passing it the |
| * values of those two registers. When that function sees there's |
| * no file involved, it interprets the "handled" parameter as |
| * ec_type_t, and returns the matching declarative symbol-table |
| * index, per usual. |
| */ |
| void |
| declaratives_evaluate() { |
| cbl_label_t *eval = programs.first_declarative(); |
| if( eval ) declarative_execute(eval); |
| } |
| |
| cbl_label_t * new_paragraph( cbl_label_t *para ) { |
| auto& prog( programs.top() ); |
| auto old(prog.paragraph); |
| prog.paragraph = para; |
| return old; |
| } |
| |
| void antecedent_dump() const { |
| if( ! yydebug ) return; |
| if( ! antecedent_cache.operand ) { |
| yywarn( "Antecedent: none" ); |
| } else { |
| yywarn( "Antecedent: %c %s %s %c", |
| antecedent_cache.invert? '!':' ', |
| name_of(antecedent_cache.operand->field), |
| relop_str(antecedent_cache.relop), |
| antecedent_cache.has_relop? 'T' : 'F' ); |
| } |
| } |
| void antecedent( const rel_part_t& ante ) { antecedent_cache = ante; antecedent_dump(); } |
| void antecedent_reset() { antecedent_cache = rel_part_t(); antecedent_dump(); } |
| rel_part_t& antecedent() { return antecedent_cache; } |
| rel_part_t& antecedent( relop_t op ) { |
| antecedent_cache.relop_set(op); |
| antecedent_dump(); |
| return antecedent_cache; |
| } |
| rel_part_t& antecedent_invert( bool invert=true ) { |
| antecedent_cache.invert = invert; |
| antecedent_dump(); |
| return antecedent_cache; |
| } |
| |
| void compute_begin() { error_labels.generate(); } |
| bool in_compute() { return error_labels.on_error != NULL; } |
| void compute_end() { error_labels.clear(); } |
| cbl_label_t * compute_on_error() { return error_labels.on_error; } |
| cbl_label_t * compute_not_error() { return error_labels.not_error; } |
| cbl_label_t * compute_label() { return error_labels.compute_error; } |
| } current; |
| |
| void current_enabled_ecs( tree ena ) { |
| current.declaratives.runtime.ena = ena; |
| } |
| |
| #define PROGRAM current.program_index() |
| |
| static void |
| add_debugging_declarative( const cbl_label_t * label ) { |
| // cppcheck-suppress [unreadVariable] obviously not true |
| const char *section = current.declarative_section_name(); |
| if( section ) { |
| debugging_clients[label->name].push_back(section); |
| } |
| } |
| |
| cbl_options_t current_options() { |
| return current.options_paragraph; |
| } |
| |
| size_t current_program_index() { |
| return current.program()? current.program_index() : 0; |
| } |
| |
| cbl_label_t * current_section() { |
| return current.section(); |
| } |
| cbl_label_t * current_paragraph() { |
| return current.paragraph(); |
| } |
| |
| const char * |
| current_declarative_section_name() { |
| return current.declarative_section_name(); |
| } |
| |
| void |
| add_cobol_exception( ec_type_t type, bool enabled ) { |
| current.exception_add( type, enabled ); |
| } |
| |
| static cbl_round_t rounded_of( int token ); |
| |
| cbl_round_t |
| current_rounded_mode() { |
| return current.rounded_mode(); |
| } |
| |
| #if needed |
| static cbl_round_t |
| current_rounded_mode( cbl_round_t rounded) { |
| return current.rounded_mode(rounded); |
| } |
| #endif |
| static cbl_round_t current_rounded_mode( int token ); |
| |
| size_t program_level() { return current.program_level(); } |
| |
| static size_t constant_index( int token ); |
| |
| static relop_t relop_of(int); |
| static relop_t relop_invert(relop_t op); |
| |
| static enum classify_t classify_of( int token ); |
| |
| static void subscript_dimension_error( YYLTYPE loc, size_t, const cbl_refer_t *name ); |
| |
| /* |
| * Utility functions |
| */ |
| |
| char * |
| normalize_picture( char picture[] ); |
| |
| static inline cbl_field_t * |
| new_tempnumeric(const cbl_name_t name = nullptr) { |
| return new_temporary(FldNumericBin5, name); |
| } |
| |
| static inline cbl_field_t * |
| new_tempnumeric_float(const cbl_name_t name = nullptr) { |
| return new_temporary(FldFloat, name); |
| } |
| |
| uint32_t |
| type_capacity( enum cbl_field_type_t type, uint32_t digits ); |
| |
| bool |
| valid_picture( enum cbl_field_type_t type, const char picture[] ); |
| |
| bool |
| move_corresponding( cbl_refer_t& tgt, cbl_refer_t& src ); |
| |
| static bool |
| literal_subscripts_valid( YYLTYPE loc, const cbl_refer_t& name ); |
| static bool |
| literal_refmod_valid( YYLTYPE loc, const cbl_refer_t& r ); |
| |
| static bool |
| is_integer_literal( const cbl_field_t *field ) { |
| if( field->type == FldLiteralN ) { |
| const char *initial = field->data.initial; |
| |
| switch( *initial ) { |
| case '-': case '+': ++initial; |
| } |
| |
| const char *eos = initial + strlen(initial); |
| auto p = std::find_if_not( initial, eos, fisdigit ); |
| if( p == eos ) return true; |
| |
| if( *p++ == symbol_decimal_point() ) { |
| switch( *p++ ) { |
| case 'E': case 'e': |
| switch( *p++ ) { |
| case '+': case '-': |
| return std::all_of(p, eos, []( char ch ) { return ch == '0'; } ); |
| break; |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| static inline bool |
| is_string_literal( const cbl_field_t *field ) { |
| return is_literal(field) && is_quoted(field); |
| } |
| |
| static inline bool |
| needs_picture( cbl_field_type_t type ) { |
| switch(type) { |
| case FldDisplay: |
| case FldInvalid: |
| gcc_unreachable(); |
| return false; // not a valid question |
| |
| case FldAlphaEdited: |
| case FldAlphanumeric: |
| case FldNumericBinary: |
| case FldNumericDisplay: |
| case FldNumericEdited: |
| case FldPacked: |
| return true; |
| |
| case FldFloat: |
| case FldNumericBin5: |
| return false; |
| |
| case FldBlob: |
| case FldClass: |
| case FldConditional: |
| case FldForward: |
| case FldGroup: |
| case FldIndex: |
| case FldLiteralA: |
| case FldLiteralN: |
| case FldPointer: |
| case FldSwitch: |
| return false; |
| } |
| |
| dbgmsg("%s:%d: unknown cbl_field_type_t %u", __func__, __LINE__, type); |
| gcc_unreachable(); |
| return false; |
| } |
| |
| static bool |
| is_callable( const cbl_field_t *field ) { |
| switch ( field->type ) { |
| case FldInvalid: |
| case FldNumericEdited: |
| case FldAlphaEdited: |
| case FldClass: |
| case FldConditional: |
| case FldForward: |
| case FldSwitch: |
| case FldDisplay: |
| case FldBlob: |
| case FldNumericDisplay: |
| case FldNumericBinary: |
| case FldFloat: |
| case FldPacked: |
| case FldNumericBin5: |
| case FldLiteralN: |
| case FldIndex: |
| return false; |
| case FldGroup: |
| case FldLiteralA: |
| case FldAlphanumeric: |
| case FldPointer: |
| return true; |
| } |
| cbl_internal_error( "%s:%d: invalid %<symbol_type_t%> %d", |
| __func__, __LINE__, field->type ); |
| return false; |
| } |
| |
| /* |
| * intrinsic calls |
| */ |
| struct cbl_fieldloc_t { |
| YYLTYPE loc; |
| cbl_field_t *field; |
| |
| cbl_fieldloc_t() : loc{ 1,1, 1,1 }, field(NULL) {} |
| cbl_fieldloc_t( const YYLTYPE& loc, cbl_field_t *field ) |
| : loc(loc), field(field) |
| {} |
| }; |
| |
| static size_t |
| intrinsic_invalid_parameter( int token, const std::vector<cbl_refer_t>& args ); |
| |
| static const char * |
| intrinsic_cname( int token ); |
| |
| static bool |
| intrinsic_call_0( cbl_field_t *output, int token ) { |
| const char *name = intrinsic_cname(token); |
| if( !name ) return false; |
| parser_intrinsic_call_0( output, name ); |
| return true; |
| } |
| |
| static bool |
| intrinsic_call_1( cbl_field_t *output, int token, |
| cbl_refer_t *r1, const YYLTYPE& loc ) { |
| std::vector<cbl_refer_t> args { *r1 }; |
| if( 0 == intrinsic_invalid_parameter(token, args) ) { |
| error_msg(loc, "invalid parameter '%s'", r1->field->name); |
| return false; |
| } |
| |
| const char *func = intrinsic_cname(token); |
| if( !func ) return false; |
| parser_intrinsic_call_1( output, func, *r1 ); |
| return true; |
| } |
| |
| static bool |
| intrinsic_call_2( cbl_field_t *tgt, int token, const cbl_refer_t *r1, cbl_refer_t *r2 ) { |
| std::vector<cbl_refer_t> args { *r1, r2? *r2 : cbl_refer_t() }; |
| size_t n = intrinsic_invalid_parameter(token, args); |
| if( n < args.size() ) { |
| error_msg(args[n].loc, "invalid parameter '%s'", args[n].field->name); |
| return false; |
| } |
| const char *fund = intrinsic_cname(token); |
| if( !fund ) return false; |
| parser_intrinsic_call_2( tgt, fund, args[0], args[1] ); |
| return true; |
| } |
| |
| static bool |
| intrinsic_call_3( cbl_field_t *tgt, int token, |
| cbl_refer_t *r1, cbl_refer_t *r2, cbl_refer_t *r3 ) { |
| std::vector<cbl_refer_t> args { *r1, *r2, *r3 }; |
| size_t n = intrinsic_invalid_parameter(token, args); |
| if( n < args.size() ) { |
| error_msg(args[n].loc, "invalid parameter '%s'", args[n].field->name); |
| return false; |
| } |
| const char *func = intrinsic_cname(token); |
| if( !func ) return false; |
| parser_intrinsic_call_3( tgt, func, *r1, *r2, *r3 ); |
| return true; |
| } |
| |
| static bool |
| intrinsic_call_4( cbl_field_t *tgt, int token, |
| cbl_refer_t *r1, cbl_refer_t *r2, |
| cbl_refer_t *r3, cbl_refer_t *r4 ) { |
| std::vector<cbl_refer_t> args { *r1, *r2, *r3, *r4 }; |
| size_t n = intrinsic_invalid_parameter(token, args); |
| if( n < args.size() ) { |
| error_msg(args[n].loc, "invalid parameter '%s'", args[n].field->name); |
| return false; |
| } |
| const char *func = intrinsic_cname(token); |
| if( !func ) return false; |
| parser_intrinsic_call_4( tgt, func, *r1, *r2, *r3, *r4 ); |
| return true; |
| } |
| |
| /* |
| * Local functions |
| */ |
| |
| static inline cbl_field_t * |
| new_literal( const char initial[] ) { |
| return new_literal( strlen(initial), initial ); |
| } |
| |
| cbl_refer_t * |
| negate( cbl_refer_t * refer, bool neg = true ) { |
| if( ! neg ) return refer; |
| assert( is_numeric(refer->field) ); |
| auto output = new_reference(new_tempnumeric()); |
| parser_subtract( *output, literally_zero, *refer, current_rounded_mode() ); |
| return output; |
| } |
| |
| cbl_field_t * |
| conditional_set( cbl_field_t *tgt, bool tf ) { |
| static cbl_field_t *one = new_literal("1"); |
| |
| enum relop_t op = tf? eq_op : ne_op; |
| parser_relop( tgt, one, op, one ); |
| return tgt; |
| } |
| |
| static inline cbl_field_t * |
| table_primary_index( cbl_field_t *table ) { |
| assert(table); |
| return 0 == table->occurs.indexes.nfield? |
| NULL : cbl_field_of(symbol_at(table->occurs.indexes.fields[0])); |
| } |
| |
| static inline const cbl_refer_t // return copy, not element reference |
| invalid_key( const cbl_refer_t& ref ) { |
| assert(ref.field); |
| auto p = std::find_if( ref.subscripts.begin(), ref.subscripts.end(), |
| [parent = ref.field->parent]( const auto &sub ) { |
| return sub.field->parent == parent; |
| } ); |
| return p != ref.subscripts.end() ? *p : nullptr; |
| } |
| |
| static inline symbol_elem_t * |
| symbol_find( const std::list<const char *>& names ) { |
| auto found = symbol_find(PROGRAM, names); |
| if( found.first && !found.second ) { |
| auto field = cbl_field_of(found.first); |
| yyerror( "%s is not unique, first defined on line %d", |
| field->name, field->line ); |
| return NULL; |
| } |
| return found.first; |
| } |
| |
| static inline cbl_field_t * |
| field_find( const std::list<const char *>& names ) { |
| if( names.size() == 1 ) { |
| auto value = cdf_value(names.front()); |
| if( value ) { |
| cbl_field_t * field; |
| if( value->is_numeric() ) { |
| field = new_tempnumeric(); |
| parser_set_numeric(field, value->as_number()); |
| } else { |
| field = new_literal(value->string); |
| } |
| return field; |
| } |
| } |
| symbol_elem_t *e = symbol_find(names); |
| return e? cbl_field_of(e) : NULL; |
| } |
| |
| static inline symbol_elem_t * |
| symbol_find( const YYLTYPE& loc, const char *name ) { |
| cbl_namelist_t names; |
| if( ! name_queue.empty() ) { |
| auto names = name_queue.pop_as_names(); |
| } |
| names.push_front(name); |
| auto found = symbol_find( PROGRAM, names ); |
| if( found.first && !found.second ) { |
| auto field = cbl_field_of(found.first); |
| error_msg(loc, "'%s' is not unique, first defined on line %d", |
| field->name, field->line); |
| return NULL; |
| } |
| return found.first; |
| } |
| |
| static inline cbl_field_t * |
| register_find( const char *name ) { |
| return cbl_field_of(symbol_register(name)); |
| } |
| |
| static bool |
| valid_redefine( const YYLTYPE& loc, |
| const cbl_field_t *field, const cbl_field_t *orig ) { |
| // Must have same level. |
| if( field->level != orig->level ) { |
| error_msg(loc, "cannot redefine %s %s as %s %s " |
| "because they have different levels", |
| orig->level_str(), orig->name, |
| field->level_str(), field->name); |
| return false; |
| } |
| |
| // no higher level intervenes |
| /* |
| * No entry having a level-number numerically lower than the |
| * level-number of data-name-2 may occur between the data |
| * description entries of data-name-2 and the subject of the entry. |
| */ |
| struct { symbol_elem_t *field, *orig; } sym = { |
| symbol_at(field_index(field)), |
| symbol_at(field_index(orig)) }; |
| |
| auto e = std::find_if( sym.orig + 1, sym.field, |
| [lowest = field->level]( auto& elem ) { |
| if( elem.type != SymField ) return false; |
| auto f = cbl_field_of(&elem); |
| return 0 < f->level && f->level < lowest; |
| } ); |
| if( e != sym.field ) { |
| auto wrong = cbl_field_of(e); |
| error_msg(loc, "%s %s on line %d lies between %s and %s", |
| wrong->level_str(), wrong->name, wrong->line, |
| orig->name, field->name); |
| return false; |
| } |
| |
| // cannot redefine a table |
| if( orig->occurs.ntimes() ) { |
| error_msg(loc, "cannot redefine table %s %s", |
| orig->level_str(), orig->name); |
| return false; |
| } |
| |
| // redefined field cannot be ODO |
| if( orig->occurs.depending_on ) { |
| error_msg(loc, "redefined data item %s %s has OCCURS DEPENDING ON", |
| orig->level_str(), orig->name); |
| return false; |
| } |
| // redefiner cannot have ODO |
| if( field->occurs.depending_on ) { |
| error_msg(loc, "data item %s %s cannot use REDEFINES and OCCURS DEPENDING ON", |
| field->level_str(), field->name); |
| return false; |
| } |
| |
| if( is_variable_length(orig) ) { |
| error_msg(loc, "redefined data item %s %s has OCCURS DEPENDING ON", |
| orig->level_str(), orig->name); |
| return false; |
| } |
| // We don't know about the redefining group until it's completely defined. |
| |
| /* |
| * 8) The storage area required for the subject of the entry |
| * shall not be larger than the storage area required for the |
| * data item referenced by data-name-2, unless the data item |
| * referenced by data- name-2 has been specified with level |
| * number 1 and without the EXTERNAL clause. |
| */ |
| if( field->type != FldGroup && orig->type != FldGroup ) { |
| if( orig->size() < field->size() ) { |
| if( orig->level > 1 || orig->has_attr(external_e) ) { |
| dbgmsg( "size error orig: %s", field_str(orig) ); |
| dbgmsg( "size error redef: %s", field_str(field) ); |
| error_msg(loc, "%s (%s size %u) larger than REDEFINES %s (%s size %u)", |
| field->name, |
| 3 + cbl_field_type_str(field->type), field->size(), |
| orig->name, |
| 3 + cbl_field_type_str(orig->type), orig->size() ); |
| } |
| } |
| } |
| |
| /* |
| * 4) No entry having a level-number numerically lower than the |
| * level-number of data-name-2 may occur between the data |
| * description entries of data-name-2 and the subject of the entry. |
| */ |
| bool same_group = std::none_of( symbol_at(field_index(orig)), |
| symbol_at(field_index(field)), |
| [level = field->level]( const auto& elem ) { |
| if( elem.type == SymField ) { |
| auto f = cbl_field_of(&elem); |
| return 0 < f->level && f->level < level; |
| } |
| return false; |
| } ); |
| if( ! same_group ) { |
| error_msg(loc, "cannot redefine %s %s as %s %s " |
| "because they belong to different groups", |
| orig->level_str(), orig->name, |
| field->level_str(), field->name); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static void |
| field_value_all(struct cbl_field_t * field ) { |
| // Expand initial by repeating its contents until it is of length capacity: |
| assert(field->data.initial != NULL); |
| size_t initial_length = strlen(field->data.initial); |
| char *new_initial = static_cast<char*>(xmalloc(field->data.capacity + 1)); |
| size_t i = 0; |
| while(i < field->data.capacity) { |
| new_initial[i] = field->data.initial[i%initial_length]; |
| i += 1; |
| } |
| new_initial[field->data.capacity] = '\0'; |
| free(const_cast<char *>(field->data.initial)); |
| field->data.initial = new_initial; |
| } |
| |
| static cbl_field_t * |
| parent_has_value( cbl_field_t *field ) { |
| while( (field = parent_of(field)) != NULL ) { |
| if( field->data.initial ) break; |
| } |
| return field; |
| } |
| |
| static uint32_t |
| group_attr( const cbl_field_t * field ) { |
| if( field->parent == 0 ) return 0; |
| |
| const symbol_elem_t *e = symbol_at(field->parent); |
| if( SymField != e->type ) return 0; |
| |
| const cbl_field_t *p = cbl_field_of(e); |
| if( p->type != FldGroup ) return 0; |
| |
| return p->attr; |
| } |
| |
| static struct cbl_field_t * |
| field_add( const YYLTYPE& loc, cbl_field_t *field ) { |
| switch(current_data_section) { |
| case not_data_datasect_e: |
| case file_datasect_e: |
| case working_storage_datasect_e: |
| break; |
| case local_storage_datasect_e: |
| field->attr |= local_e; |
| break; |
| case linkage_datasect_e: |
| field->attr |= linkage_e; |
| break; |
| } |
| |
| // Use isym 0 to indicate the location of the field under construction. |
| symbol_field_location(0, loc); |
| |
| struct symbol_elem_t *e = symbol_field_add(PROGRAM, field); |
| if( !e ) return NULL; |
| symbol_field_location(symbol_index(e), loc); |
| field = cbl_field_of(e); |
| assert(field->type != FldDisplay); |
| |
| if( field->parent == 0 ) { |
| switch(field->level) { |
| case 0: case 1: case 77: case 78: |
| break; |
| default: |
| error_msg(loc, "%s %s is not part of an 01 record", |
| field->level_str(), field->name ); |
| return NULL; |
| break; |
| } |
| } |
| return field; |
| } |
| |
| static const char * |
| field_attr_str( const cbl_field_t *field ) { |
| static const std::vector<cbl_field_attr_t> attrs { |
| figconst_1_e, figconst_2_e, figconst_4_e, rjust_e, ljust_e, |
| zeros_e, signable_e, constant_e, function_e, quoted_e, filler_e, |
| intermediate_e, embiggened_e, all_alpha_e, all_x_e, |
| all_ax_e, prog_ptr_e, scaled_e, refmod_e, based_e, any_length_e, |
| global_e, external_e, blank_zero_e, linkage_e, local_e, leading_e, |
| separate_e, envar_e, dnu_1_e, bool_encoded_e, hex_encoded_e, |
| depends_on_e, initialized_e, has_value_e, ieeedec_e, big_endian_e, |
| same_as_e, record_key_e, typedef_e, strongdef_e, |
| }; |
| return field->attr_str(attrs); |
| } |
| |
| static bool |
| uniform_picture( const char *picture, char model ) { |
| const char *eopicture( picture + strlen(picture) ); |
| model = TOLOWER(model); |
| return std::all_of(picture, eopicture, |
| [model]( char ch ) { |
| return model == TOLOWER(ch); |
| } ); |
| } |
| |
| static enum cbl_field_attr_t |
| uniform_picture( const char *picture ) { |
| static char ch[] = { 'A', 'X' }; |
| for( auto p = ch; p < ch + sizeof(ch); p++ ) { |
| if( uniform_picture(picture, *p) ) { |
| switch(*p) { |
| case 'A': return all_alpha_e; |
| case 'X': return all_x_e; |
| } |
| } |
| } |
| return none_e; |
| } |
| |
| static bool |
| field_type_update( cbl_field_t *field, cbl_field_type_t type, |
| YYLTYPE loc, |
| bool is_usage = false) |
| { |
| // preserve NumericEdited if already established |
| if( !is_usage && field->has_attr(blank_zero_e) ) { |
| if( type == FldNumericDisplay && field->type == FldNumericEdited ) { |
| return true; |
| } |
| } |
| |
| // disallow USAGE if inherited from parent (all members must be of same type) |
| if( is_usage && field->usage != type ) { |
| switch( field->usage ) { |
| case FldInvalid: |
| case FldDisplay: |
| break; // ok |
| default: |
| error_msg(loc, "cannot set %s to USAGE %s " |
| "because the group is restricted to USAGE %s", |
| field->name, cbl_field_type_str(type), |
| cbl_field_type_str(field->usage)); |
| return false; |
| } |
| } |
| |
| if( ! symbol_field_type_update(field, type, is_usage) ) { |
| error_msg(loc, "cannot set USAGE of %s to %s (from %s)", field->name, |
| cbl_field_type_str(type) + 3, cbl_field_type_str(field->type) + 3); |
| return false; |
| } |
| |
| dbgmsg( "%s:%d: %s became %s based on %s", __func__, __LINE__, field->name, |
| cbl_field_type_str(field->type), cbl_field_type_str(type) ); |
| |
| return true; |
| } |
| |
| static bool |
| field_capacity_error( const YYLTYPE& loc, const cbl_field_t *field ) { |
| uint32_t parent_capacity = 0; |
| if( field->parent ) { |
| auto e = symbol_at(field->parent); |
| if( e->type == SymField ) parent_capacity = cbl_field_of(e)->data.capacity; |
| } |
| /* |
| * Field may become a table whose capacity was inherited from a parent with |
| * data. If so, the field's capacity will be overwritten by its |
| * PICTURE-defined size. |
| */ |
| if( parent_capacity < field->data.capacity && !symbol_redefines(field) ) { |
| dbgmsg( "%s: %s", __func__, field_str(field) ); |
| error_msg(loc, "%s has USAGE incompatible with PICTURE", |
| field->name ); |
| return true; |
| } |
| return false; |
| } |
| #define ERROR_IF_CAPACITY(L, F) \ |
| do { if( field_capacity_error(L, F) ) YYERROR; } while(0) |
| |
| static const char * |
| blank_pad_initial( const char initial[], size_t capacity, size_t new_size ) { |
| assert(capacity < new_size); |
| assert(initial != NULL); |
| |
| if( normal_value_e != cbl_figconst_of(initial) ) return initial; |
| |
| auto p = reinterpret_cast<char *>( xmalloc(2 + new_size) ); |
| memset(p, 0x20, new_size); |
| memcpy(p, initial, capacity); |
| p[new_size] = '\0'; // for debugging |
| p[++new_size] = '\0'; // for debugging |
| return p; |
| } |
| |
| static bool |
| value_encoding_check( const YYLTYPE& loc, cbl_field_t *field ) { |
| if( ! field->internalize() ) { |
| error_msg(loc, "inconsistent string literal encoding for '%s'", |
| field->data.initial); |
| return false; |
| } |
| return true; |
| } |
| |
| |
| #pragma GCC diagnostic push |
| #pragma GCC diagnostic ignored "-Wmissing-field-initializers" |
| |
| static struct cbl_field_t * |
| field_alloc( const YYLTYPE& loc, cbl_field_type_t type, size_t parent, const char name[] ) { |
| cbl_field_t *f, field = {}; |
| field.type = type; |
| field.usage = FldInvalid; |
| field.parent = parent; |
| field.line = yylineno; |
| |
| if( !namcpy(loc, field.name, name) ) return NULL; |
| f = field_add(loc, &field); |
| assert(f); |
| return f; |
| } |
| |
| static const cbl_file_t protofile; |
| |
| // Add a file to the symbol table with its record area field. |
| // The default organization is sequential. |
| #pragma GCC diagnostic push |
| #pragma GCC diagnostic ignored "-Wformat-truncation" |
| static cbl_file_t * |
| file_add( YYLTYPE loc, cbl_file_t *file ) { |
| gcc_assert(file); |
| enum { level = 1 }; |
| struct cbl_field_t area = { 0, FldAlphanumeric, FldInvalid, 0, 0,0, level, {}, yylineno }, |
| *field = field_add(loc, &area); |
| file->default_record = field_index(field); |
| |
| // install file, and set record area's name |
| auto e = symbol_file_add(PROGRAM, file); |
| if( !e ) { |
| error_msg(loc, "%s was defined previously on line %d", file->name, file->line); |
| return NULL; |
| } |
| file = cbl_file_of(e); |
| snprintf(field->name, sizeof(field->name), |
| "%s" HOST_SIZE_T_PRINT_UNSIGNED "_%s", |
| record_area_name_stem, (fmt_size_t)symbol_index(e), file->name); |
| if( file->attr & external_e ) { |
| snprintf(field->name, sizeof(field->name), |
| "%s%s", record_area_name_stem, file->name); |
| } |
| field->file = field->parent = symbol_index(e); |
| |
| return file; |
| } |
| #pragma GCC diagnostic pop |
| #pragma GCC diagnostic pop |
| |
| |
| static cbl_alphabet_t * |
| alphabet_add( const YYLTYPE& loc, cbl_encoding_t encoding ) { |
| cbl_alphabet_t alphabet(loc, encoding); |
| symbol_elem_t *e = symbol_alphabet_add(PROGRAM, &alphabet); |
| assert(e); |
| return cbl_alphabet_of(e); |
| } |
| |
| // The current field always exists in the symbol table, even if it's incomplete. |
| static cbl_field_t * |
| current_field(cbl_field_t * field = NULL) { |
| static cbl_field_t *local; |
| if( field ) local = field; |
| gcc_assert(field_index(local)); |
| return local; |
| } |
| |
| static void |
| set_real_from_capacity( const YYLTYPE& loc, |
| cbl_field_t *field, |
| REAL_VALUE_TYPE *r ) { |
| if( field == current_field() ) { |
| error_msg(loc, "cannot define %s via self-reference", field->name); |
| return; |
| } |
| field->data.set_real_from_capacity(r); |
| } |
| |
| static struct cbl_special_name_t * |
| special_of( const char F[], int L, const char name[] ) { |
| struct symbol_elem_t *e = symbol_special(PROGRAM, name); |
| if( !e ) { |
| dbgmsg("%s:%d: no special symbol '%s' found", F, L, name); |
| return NULL; |
| } |
| return cbl_special_name_of(e); |
| } |
| #define special_of( F ) special_of(__func__, __LINE__, (F)) |
| |
| static const special_name_t * |
| cmd_or_env_special_of( std::string name ) { |
| static const std::map< std::string, special_name_t > fujitsus |
| { // Fujitsu calls these "function names", not device names |
| { "ARGUMENT-NUMBER", ARG_NUM_e }, |
| { "ARGUMENT-VALUE", ARG_VALUE_e } , |
| { "ENVIRONMENT-NAME", ENV_NAME_e }, |
| { "ENVIRONMENT-VALUE", ENV_VALUE_e }, |
| }; |
| |
| std::transform(name.begin(), name.end(), name.begin(), ::toupper); |
| auto p = fujitsus.find(name.c_str()); |
| return p != fujitsus.end()? &p->second : nullptr; |
| } |
| |
| static inline void |
| parser_add2( const cbl_num_result_t& to, |
| const cbl_refer_t& from ) { |
| parser_add(to.refer, to.refer, from, to.rounded); |
| } |
| |
| static inline void |
| parser_subtract2( const cbl_num_result_t& to, |
| const cbl_refer_t& from ) { |
| parser_subtract(to.refer, to.refer, from, to.rounded); |
| } |
| |
| static bool |
| parser_move_carefully( const char */*F*/, int /*L*/, |
| tgt_list_t *tgt_list, |
| const cbl_refer_t& src, |
| bool is_index ) |
| { |
| for( const auto& num_result : tgt_list->targets ) { |
| const cbl_refer_t& tgt = num_result.refer; |
| |
| if( is_index ) { |
| if( tgt.field->type != FldIndex && src.field->type != FldIndex) { |
| error_msg(src.loc, "invalid SET %s (%s) TO %s (%s): not a field index", |
| tgt.field->name, cbl_field_type_str(tgt.field->type), |
| src.field->name, cbl_field_type_str(src.field->type)); |
| delete tgt_list; |
| return false; |
| } |
| } else { |
| if( ! valid_move( tgt.field, src.field ) ) { |
| if( src.field->type == FldPointer && |
| tgt.field->type == FldPointer ) { |
| if( dialect_mf() || dialect_gnu() ) return true; |
| dialect_error(src.loc, "MOVE POINTER", "mf"); |
| } |
| if( ! is_index ) { |
| char ach[16]; |
| char stype[32]; |
| char dtype[32]; |
| strcpy(stype, cbl_field_type_str(src.field->type)); |
| strcpy(dtype, cbl_field_type_str(tgt.field->type)); |
| |
| if( src.field->attr & all_alpha_e ) |
| { |
| strcpy(stype, "FldAlphabetic"); |
| } |
| if( tgt.field->attr & all_alpha_e ) |
| { |
| strcpy(dtype, "FldAlphabetic"); |
| } |
| if( !(src.field->attr & scaled_e) && src.field->data.rdigits ) |
| { |
| sprintf(ach, ".%d", src.field->data.rdigits); |
| strcat(stype, ach); |
| } |
| if( !(tgt.field->attr & scaled_e) && tgt.field->data.rdigits ) |
| { |
| sprintf(ach, ".%d", tgt.field->data.rdigits); |
| strcat(dtype, ach); |
| } |
| error_msg(src.loc, "cannot MOVE '%s' (%s) to '%s' (%s)", |
| name_of(src.field), stype, |
| name_of(tgt.field), dtype); |
| delete tgt_list; |
| return false; |
| } |
| } |
| } |
| } |
| size_t ntgt = tgt_list->targets.size(); |
| std::vector <cbl_refer_t> tgts(ntgt); |
| std::transform( tgt_list->targets.begin(), tgt_list->targets.end(), tgts.begin(), |
| []( const cbl_num_result_t& res ) { return res.refer; } ); |
| parser_move(ntgt, tgts.data(), src); |
| delete tgt_list; |
| return true; |
| } |
| #define parser_move2(P, S) \ |
| parser_move_carefully(__func__, __LINE__, (P), (S), false) |
| #define parser_index(P, S) \ |
| parser_move_carefully(__func__, __LINE__, (P), (S), true) |
| |
| static void |
| ast_set_pointers( const list<cbl_num_result_t>& tgts, cbl_refer_t src ) { |
| assert(!tgts.empty()); |
| assert(src.field || src.prog_func); |
| size_t nptr = tgts.size(); |
| std::vector <cbl_refer_t> ptrs(nptr); |
| |
| std::transform( tgts.begin(), tgts.end(), ptrs.begin(), cbl_num_result_t::refer_of ); |
| parser_set_pointers(nptr, ptrs.data(), src); |
| } |
| |
| void |
| stringify( refer_collection_t *inputs, |
| const cbl_refer_t& into, const cbl_refer_t& pointer, |
| cbl_label_t *on_error = NULL, |
| cbl_label_t *not_error = NULL); |
| |
| void unstringify( const cbl_refer_t& src, refer_list_t *delimited, |
| unstring_into_t * into, |
| cbl_label_t *on_error = NULL, |
| cbl_label_t *not_error = NULL ); |
| |
| static cbl_label_t * |
| implicit_paragraph() |
| { |
| cbl_name_t name; |
| sprintf(name, "_implicit_paragraph_" HOST_SIZE_T_PRINT_UNSIGNED, |
| (fmt_size_t)symbol_index()); |
| // Programs have to start with an implicit paragraph |
| return label_add(LblParagraph, name, yylineno); |
| } |
| static cbl_label_t * |
| implicit_section() |
| { |
| cbl_name_t name; |
| sprintf(name, "_implicit_section_" HOST_SIZE_T_PRINT_UNSIGNED, |
| (fmt_size_t)symbol_index()); |
| // Programs have to start with an implicit section |
| return label_add(LblSection, name, yylineno); |
| } |
| |
| static void |
| // cppcheck-suppress constParameterPointer |
| ast_enter_exit_section( cbl_label_t * section ) { |
| auto implicit = section? implicit_paragraph() : NULL; |
| |
| struct { cbl_label_t *para, *sect; |
| inline bool exists() const { return sect != NULL && para != NULL; } |
| } prior = { |
| current.new_paragraph(implicit), |
| current.new_section(section) |
| }; |
| if( false && yydebug ) { |
| fprintf(stderr, "( %d ) %s:%d: leaving section %s paragraph %s\n", |
| yylineno, __func__, __LINE__, |
| prior.sect? prior.sect->name : "''", |
| prior.para? prior.para->name : "''"); |
| } |
| if( prior.exists() ) { |
| parser_leave_paragraph(prior.para); |
| parser_leave_section(prior.sect); |
| } |
| if( section ) { |
| parser_enter_section(section); |
| parser_enter_paragraph(implicit); |
| } |
| } |
| |
| static inline void |
| ast_enter_section( cbl_label_t * section ) { |
| assert(section); |
| section->lain = yylineno; |
| ast_enter_exit_section( section ); |
| } |
| |
| static inline void |
| ast_exit_section() { |
| ast_enter_exit_section( NULL ); |
| } |
| |
| static void |
| ast_enter_paragraph( cbl_label_t * para ) { |
| para->lain = yylineno; |
| cbl_label_t *prior = current.new_paragraph(para); |
| if( prior ) { |
| parser_leave_paragraph(prior); |
| } |
| parser_enter_paragraph(para); |
| } |
| |
| static bool |
| data_division_ready() { |
| // Install and use any alphabets. |
| if( nparse_error == 0 ) { // error might have stemmed from the alphabet itself |
| const char *name = current.collating_sequence(); |
| |
| if( ! symbols_alphabet_set(PROGRAM, name) ) { |
| error_msg(yylloc, "no alphabet '%s' defined", name); |
| return false; |
| } |
| } |
| |
| // Tell codegen about symbols. |
| static size_t nsymbol = 0; |
| if( (nsymbol = symbols_update(nsymbol, nparse_error == 0)) > 0 ) { |
| if( ! literally_one ) { |
| literally_one = new_literal("1"); |
| literally_zero = new_literal("0"); |
| } |
| } |
| |
| if( nsymbol == 0 || nparse_error > 0 ) { |
| dbgmsg( HOST_SIZE_T_PRINT_DEC " errors in DATA DIVISION, compilation ceases", |
| (fmt_size_t)nparse_error ); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static |
| bool |
| anybody_redefines( const cbl_field_t *tree ) |
| { |
| bool retval = false; |
| while(tree) |
| { |
| if( symbol_redefines(tree) ) |
| { |
| retval = true; |
| break; |
| } |
| // cppcheck-suppress [unreadVariable] obviously not true |
| tree = parent_of(tree); |
| } |
| return retval; |
| } |
| |
| static bool |
| procedure_division_ready( YYLTYPE loc, cbl_field_t *returning, ffi_args_t *ffi_args ) { |
| auto prog = cbl_label_of(symbols_begin(current.program_index())); |
| |
| if( prog->type == LblFunction ) { |
| if( ! returning ) { |
| error_msg(loc, "FUNCTION %s requires RETURNING", prog->name); |
| return false; |
| } else { |
| prog->returning = field_index(returning); |
| } |
| current.udf_update(ffi_args); |
| } |
| |
| if( returning ) { |
| if( ! (returning->level == 1 || returning->level == 77) ) { |
| error_msg(loc, "RETURNING %s must be level 01 or 77", returning->name); |
| } |
| if( symbol_redefines(returning) ) { |
| error_msg(loc, "RETURNING %s cannot REDFINE anything", returning->name); |
| } |
| } |
| if( ffi_args ) { |
| size_t i=0; |
| for( const auto& arg : ffi_args->elems ) { |
| auto field = arg.refer.field; |
| i++; |
| if( returning == field ) { |
| error_msg(loc, "RETURNING %s duplicates USING parameter %zu", |
| returning->name, i); |
| } |
| if( ! (field->level == 1 || field->level == 77) ) { |
| error_msg(loc, "USING %s must be level 01 or 77", |
| field->name); |
| } |
| if( symbol_redefines(field) ) { |
| error_msg(loc, "USING %s cannot REDEFINE anything", |
| field->name ); |
| } |
| } |
| } |
| |
| // Apply ECs from the command line |
| std::list<exception_turn_t>& exception_turns = current.pending_exceptions(); |
| for( const auto& exception_turn : exception_turns) { |
| apply_cdf_turn(exception_turn); |
| } |
| exception_turns.clear(); |
| |
| // Start the Procedure Division. |
| size_t narg = ffi_args? ffi_args->elems.size() : 0; |
| std::vector <cbl_ffi_arg_t> args(narg); |
| cbl_ffi_arg_t*pargs = NULL; |
| if( narg > 0 ) { |
| std::copy(ffi_args->elems.begin(), ffi_args->elems.end(), args.begin()); |
| pargs = args.data(); |
| } |
| |
| // Create program initialization section. We build it on an island, |
| // that gets executed only if the program is IS INITIAL, or when the |
| // program is the subject of a CANCEL statement. |
| |
| static const char init[] = "_INITIALIZE_PROGRAM"; |
| static const char tini[] = "_INITIALIZE_DONE"; |
| |
| struct cbl_label_t * init_label = label_add(LblSection, init, yylineno); |
| struct cbl_label_t * tini_label = label_add(LblSection, tini, yylineno); |
| |
| // parser_division(procedure_div_e) needs initial_section: |
| prog->initial_section = symbol_index(symbol_elem_of(init_label)); |
| |
| if( current.program_index() > 1 ) { |
| ast_exit_section(); |
| } |
| parser_division( procedure_div_e, returning, narg, pargs ); |
| |
| std::for_each( symbols_begin(current.program_index()), symbols_end(), |
| []( auto& elem ) { |
| if( elem.type == SymField ) { |
| auto f = cbl_field_of(&elem); |
| if( f->has_attr(local_e) ) { |
| parser_local_add(f); |
| } |
| } |
| } ); |
| |
| // At this point we count up the number of variables that will need to be |
| // initialized in _INITIALIZE_PROGRAM: |
| int count_of_variables = 0; |
| for( symbol_elem_t *e = |
| symbols_begin(1 + current.program_index()); |
| e < symbols_end(); e++ ) { |
| if( is_program(*e) ) break; |
| if( e->type != SymField ) continue; |
| cbl_field_t *f = cbl_field_of(e); |
| if( !f->var_decl_node ) |
| { |
| // This can happen when there was an error parsing the data division |
| continue; |
| } |
| if( f->type == FldForward ) continue; |
| if( f->type == FldLiteralA ) continue; |
| if( anybody_redefines(f) ) continue; |
| if( f->has_attr(linkage_e) ) continue; |
| if( f->has_attr(local_e) ) continue; |
| if( f->is_typedef() ) { |
| auto isym = end_of_group( symbol_index(e) ); |
| e = symbol_at(--isym); |
| continue; |
| } |
| count_of_variables += 1; |
| } |
| // Allocate space for the static table of variables |
| parser_init_list_size(count_of_variables); |
| |
| // Do a second pass: |
| // Initialize the static table with the variables: |
| for( symbol_elem_t *e = |
| symbols_begin(1 + current.program_index()); |
| e < symbols_end(); e++ ) { |
| if( is_program(*e) ) break; |
| if( e->type != SymField ) continue; |
| cbl_field_t *f = cbl_field_of(e); |
| if( !f->var_decl_node ) |
| { |
| // This can happen when there was an error parsing the data division |
| continue; |
| } |
| if( f->type == FldForward ) continue; |
| if( f->type == FldLiteralA ) continue; |
| if( anybody_redefines(f) ) continue; |
| if( f->has_attr(linkage_e) ) continue; |
| if( f->has_attr(local_e) ) continue; |
| if( f->is_typedef() ) { |
| auto isym = end_of_group( symbol_index(e) ); |
| e = symbol_at(--isym); |
| continue; |
| } |
| parser_init_list_element(f); |
| } |
| |
| // This is where we jump over the island |
| parser_label_goto(tini_label); |
| |
| // And here we create the initialization section: |
| ast_enter_section(init_label); // _INITIALIZE_PROGRAM section. |
| |
| parser_init_list(); |
| |
| // Lay down an implicit section to end the init_label |
| ast_enter_section(implicit_section()); |
| |
| // This is the end of the island |
| parser_label_label(tini_label); |
| |
| if( current.program()->initial ) { |
| // We perform the section we just layed down when IS INITIAL |
| parser_perform(init_label); |
| } |
| return true; |
| } |
| |
| static size_t file_section_fd; |
| static size_t current_sort_file; |
| |
| static bool |
| file_section_fd_set( file_entry_type_t type, char name[], const YYLTYPE& loc ) { |
| static std::set<size_t> has_fd; |
| |
| // File must have been uniquely created by SELECT. |
| // FD names are also unique within a program. |
| auto e = symbol_file(PROGRAM, name); |
| if( !e ) { |
| error_msg(loc, "file name not found"); |
| return false; |
| } |
| |
| file_section_fd = symbol_index(e); |
| auto result = has_fd.insert(file_section_fd); |
| if( !result.second ) { |
| auto f = cbl_file_of(e); |
| const char *type_str = "???"; |
| switch(type) { |
| case fd_e: type_str = "FD"; break; |
| case sd_e: type_str = "SD"; break; |
| } |
| error_msg(loc, "%s %s previously defined on line %d", |
| type_str, f->name, f->line); |
| return false; |
| } |
| |
| auto& file(*cbl_file_of(e)); |
| file.entry_type = type; |
| |
| if( file.org == file_disorganized_e ) { |
| file.org = file_sequential_e; |
| } |
| |
| return file_section_fd > 0; |
| } |
| |
| /* |
| * While in the File Section, set the parent of each 01 to be the FD |
| * default_record, and its file member to the file's symbol index. |
| */ |
| static bool |
| file_section_parent_set( cbl_field_t *field ) { |
| if( symbol_at(file_section_fd)->type == SymFile ) { |
| auto file = cbl_file_of(symbol_at(file_section_fd)); |
| auto record_area = cbl_field_of(symbol_at(file->default_record)); |
| |
| record_area->data.capacity = std::max(record_area->data.capacity, |
| field->data.capacity); |
| |
| field->file = file_section_fd; |
| const auto redefined = symbol_redefines(record_area); |
| field->parent = redefined? record_area->parent : file->default_record; |
| } |
| return file_section_fd > 0; |
| } |
| |
| void ast_call(const YYLTYPE& loc, cbl_refer_t name, |
| const cbl_refer_t& returning, |
| size_t narg, cbl_ffi_arg_t args[], |
| cbl_label_t *except, |
| cbl_label_t *not_except, |
| bool is_function ); |
| |
| cbl_field_t * |
| ast_file_status_between( file_status_t lower, file_status_t upper ); |
| |
| void internal_ebcdic_lock(); |
| void internal_ebcdic_unlock(); |
| |
| void |
| ast_end_program(const char name[] ) { |
| std::for_each( symbols_begin(), symbols_end(), |
| []( const auto& elem ) { |
| if( elem.type == SymLabel ) { |
| auto& L( *cbl_label_of(&elem) ); |
| if( L.used ) { |
| if( ! L.lain ) { |
| YYLTYPE loc { L.line, 1, L.line, 1 }; |
| error_msg(loc, "line %d: %s " |
| "is used on line %d and never defined", |
| L.line, L.name, L.used ); |
| } |
| dbgmsg("label: %.20s: %d/%d/%d", |
| L.name, L.line, L.lain, L.used); |
| } |
| } |
| } ); |
| if( current_program_index() == 0 ) { |
| parser_program_hierarchy( cbl_prog_hier_t() ); |
| } else { |
| ast_exit_section(); |
| } |
| parser_end_program(name); |
| internal_ebcdic_unlock(); |
| } |
| |
| static bool |
| goodnight_gracie() { |
| const cbl_label_t *prog = current.program(); |
| assert(prog); |
| |
| std::set<std::string> externals = current.end_program(); |
| |
| if( !externals.empty() ) { |
| for( const auto& name : externals ) { |
| yywarn("%s calls external symbol '%s'", |
| prog->name, name.c_str()); |
| } |
| return false; |
| } |
| |
| // pointer still valid because name is in symbol table |
| ast_end_program(prog->name); |
| return true; |
| } |
| |
| // false after USE statement, to enter Declarative with EC intact. |
| static bool statement_cleanup = true; |
| static YYLTYPE current_location; |
| |
| static void statement_epilog( int token ); |
| |
| const char * keyword_str( int token ); |
| |
| const YYLTYPE& cobol_location() { return current_location; } |
| |
| static inline void |
| location_set( const YYLTYPE& loc ) { |
| current_location = loc; |
| gcc_location_set(loc); |
| } |
| |
| static void statement_begin( const YYLTYPE& loc, int token ); |
| |
| static void ast_first_statement( const YYLTYPE& loc ) { |
| if( current.is_first_statement( loc ) ) { |
| parser_first_statement(loc.first_line); |
| } |
| } |
| |
| #pragma GCC diagnostic push |