/* * 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 <assert.h> #include <string.h> #include <stdio.h> #include <algorithm> #include <list> #include <map> #include <numeric> #include <stack> #include <string> #define MAXLENGTH_FORMATTED_DATE 10 #define MAXLENGTH_FORMATTED_TIME 19 #define MAXLENGTH_FORMATTED_DATETIME 30 #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmissing-field-initializers" extern void declarative_runtime_match(cbl_field_t *declaratives, cbl_label_t *lave ); 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(); cbl_dialect_t cbl_dialect; size_t cbl_gcobol_features; static size_t nparse_error = 0; size_t parse_error_inc() { 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)); \ gcc_location_set( location_set(Current) ); \ input_file_status_notify(); \ } while (0) int yylex(void); extern int yydebug; #include <stdarg.h> 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 enum cbl_division_t current_division; 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: %zu not supported", __func__, 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_word( const char name[] ) { auto eoname = name + strlen(name); auto p = std::find_if( name, eoname, []( char ch ) { switch(ch) { case '-': case '_': return false; case '$': // maybe one day (IBM allows) break; } return !ISALNUM(ch); } ); return p == eoname; } 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 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 ); 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, cbl_refer_t ); static bool require_pointer( YYLTYPE loc, cbl_refer_t ); static bool require_numeric( YYLTYPE loc, 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; 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=%zu label '%s', %zu cases", nother, label.name, cases.size() ); std::for_each( cases.begin(), cases.end(), case_t::Dump ); } explicit evaluate_elem_t( const char skel[] ) : nother(0) , result( keep_temporary(FldConditional) ) , pcase( cases.end() ) { static const cbl_label_t protolabel = { LblEvaluate }; label = protolabel; 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: void init( cbl_file_t *file ) { this->file = file; } bool ready() const { return file != NULL; } void call_parser_file_delete( bool sequentially ) { parser_file_delete(file, sequentially); file = NULL; } } 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, 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(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: void init( cbl_file_t *file, cbl_field_t *record ) { this->file = file; this->record = record; } bool ready() const { return file != NULL; } 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 = NULL; record = NULL; } } file_rewrite_args; static class file_start_args_t { cbl_file_t *file; public: file_start_args_t() : file(NULL) {} void 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); } } 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(NULL) , after(false) , advance(NULL) {} cbl_file_t * init( cbl_file_t *file, cbl_field_t *data_source, bool after, 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 != NULL; } 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; 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: %3zu %s", __func__, __LINE__, src, a.str()); dbgmsg("%s:%d: to %3zu %s", __func__, __LINE__, 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( 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 ); 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; 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, 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() ); } class tokenset_t { std::vector<const char *>token_names; std::map <std::string, int> tokens; std::set<std::string> cobol_words; static std::string lowercase( const cbl_name_t name ) { cbl_name_t lname; std::transform(name, name + strlen(name) + 1, lname, ftolower); return lname; } public: tokenset_t(); int find( const cbl_name_t name, bool include_intrinsics ); bool equate( const YYLTYPE& loc, int token, const cbl_name_t name ) { auto lname( lowercase(name) ); auto cw = cobol_words.insert(lname); if( ! cw.second ) { error_msg(loc, "COBOL-WORDS EQUATE: %s may appear but once", name); return false; } auto p = tokens.find(lowercase(name)); bool fOK = p == tokens.end(); if( fOK ) { // name not already in use tokens[lname] = token; } else { error_msg(loc, "EQUATE: %s already defined as a token", name); } return fOK; } bool undefine( const YYLTYPE& loc, const cbl_name_t name ) { auto lname( lowercase(name) ); auto cw = cobol_words.insert(lname); if( ! cw.second ) { error_msg(loc, "COBOL-WORDS UNDEFINE: %s may appear but once", name); return false; } auto p = tokens.find(lname); bool fOK = p != tokens.end(); if( fOK ) { // name in use tokens.erase(p); } else { error_msg(loc, "UNDEFINE: %s not defined as a token", name); } return fOK; } bool substitute( const YYLTYPE& loc, const cbl_name_t extant, int token, const cbl_name_t name ) { return equate( loc, token, name ) && undefine( loc, extant ); } bool reserve( const YYLTYPE& loc, const cbl_name_t name ) { auto lname( lowercase(name) ); auto cw = cobol_words.insert(lname); if( ! cw.second ) { error_msg(loc, "COBOL-WORDS RESERVE: %s may appear but once", name); return false; } tokens[lname] = -42; return true; } int redefined_as( const cbl_name_t name ) { auto lname( lowercase(name) ); if( cobol_words.find(lname) != cobol_words.end() ) { auto p = tokens.find(lname); if( p != tokens.end() ) { return p->second; } } return 0; } const char * name_of( int tok ) const { tok -= (255 + 3); gcc_assert(0 <= tok && size_t(tok) < token_names.size()); return token_names[tok]; } }; class current_tokens_t { tokenset_t tokens; public: current_tokens_t() {} int find( const cbl_name_t name, bool include_intrinsics ) { return tokens.find(name, include_intrinsics); } bool equate( const YYLTYPE& loc, cbl_name_t keyword, const cbl_name_t name ) { int token = keyword_tok(keyword); if( 0 == token ) { error_msg(loc, "EQUATE %s: not a valid token", keyword); return false; } return tokens.equate(loc, token, name); } bool undefine( const YYLTYPE& loc, cbl_name_t keyword ) { return tokens.undefine(loc, keyword); } bool substitute( const YYLTYPE& loc, cbl_name_t keyword, const cbl_name_t name ) { int token = keyword_tok(keyword); if( 0 == token ) { error_msg(loc, "SUBSTITUTE %s: not a valid token", keyword); return false; } return tokens.substitute(loc, keyword, token, name); } bool reserve( const YYLTYPE& loc, const cbl_name_t name ) { return tokens.reserve(loc, name); } int redefined_as( const cbl_name_t name ) { return tokens.redefined_as(name); } const char * name_of( int tok ) const { return tokens.name_of(tok); } } tokens; int redefined_token( const cbl_name_t name ) { return tokens.redefined_as(name); } struct file_list_t { list<cbl_file_t*> files; file_list_t() {} 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( cbl_field_t *field ) { fields.push_back(field); } explicit field_list_t() {} }; 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; 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; } }; 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, 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; 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; } }; struct ast_inspect_oper_t { cbl_inspect_bound_t bound; // CHARACTERS/ALL/LEADING/FIRST std::list<cbl_inspect_match_t> matches; std::list<cbl_inspect_replace_t> replaces; ast_inspect_oper_t( const cbl_inspect_match_t& match, cbl_inspect_bound_t bound = bound_characters_e ) : bound(bound) { matches.push_back(match); } ast_inspect_oper_t( const cbl_inspect_replace_t& replace, cbl_inspect_bound_t bound = bound_characters_e ) : bound(bound) { replaces.push_back(replace); } }; struct ast_inspect_t : public std::list<cbl_inspect_oper_t> { cbl_refer_t tally; // field is NULL for REPLACING const std::list<cbl_inspect_oper_t>& opers() const { return *this; } }; struct ast_inspect_list_t : public std::list<cbl_inspect_t> { ast_inspect_list_t( const cbl_inspect_t& insp ) { push_back(insp); } cbl_inspect_t * as_array() { cbl_inspect_t *output = new cbl_inspect_t[ size() ]; std::copy( begin(), end(), output ); return output; } }; void ast_inspect( cbl_refer_t& input, bool backward, ast_inspect_list_t& inspects ); template <typename E> struct elem_list_t { list<E*> elems; 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; 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; 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; 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; 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; file_sort_io_t( file_list_t& files ) : file_list(files) {} 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; 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(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() {} 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, declaratives_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(const cbl_name_t name = NULL, const char *os_name = NULL) : name(""), os_name(os_name) { if( name ) { bool ok = namcpy(YYLTYPE(), this->name, name); gcc_assert(ok); } } } locale; cbl_call_convention_t call_convention; cbl_options_t options; prog_descr_t( size_t isymbol ) : program_index(isymbol) , declaratives_index(0) , declaratives_eval(NULL) , paragraph(NULL) , section(NULL) , collating_sequence(NULL) { call_convention = current_call_convention(); } 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 { cbl_call_convention_t call_convention; bool initial; pending_t() : call_convention(cbl_call_convention_t(0)) , initial(false) {} } pending; public: cbl_call_convention_t pending_call_convention( cbl_call_convention_t convention ) { return pending.call_convention = convention; } bool pending_initial() { return pending.initial = true; } void push( prog_descr_t descr ) { cbl_call_convention_t current_call_convention = cbl_call_cobol_e; if( !empty() ) current_call_convention = top().call_convention; descr.call_convention = current_call_convention; 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( size() == 1 && 0 != pending.call_convention ) { top().call_convention = pending.call_convention; } if( pending.initial ) { auto e = symbol_at(top().program_index); auto prog(cbl_label_of(e)); prog->initial = pending.initial; } } cbl_label_t *first_declarative() { auto eval = top().declaratives_eval; if( eval ) return eval; // scan stack container for declaratives for( auto& prog : c ) { if( prog.declaratives_eval ) { eval = prog.declaratives_eval; break; } } return eval; } }; struct rel_part_t { cbl_refer_t *operand; // lhs bool has_relop, invert; relop_t relop; rel_part_t( cbl_refer_t *operand = NULL, relop_t relop = relop_t(-1), bool invert = false ) : 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: 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<cbl_exception_files_t> cobol_exceptions; error_labels_t error_labels; static void declarative_execute( cbl_label_t *eval ) { if( !eval ) { if( !enabled_exceptions.empty() ) { auto index = new_temporary(FldNumericBin5); parser_match_exception(index, NULL); } return; } 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; 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: 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; } } declaratives; void exception_add( ec_type_t ec, bool enabled = true) { std::set<size_t> files; enabled_exceptions.turn_on_off(enabled, false, // for now ec, files); if( yydebug) enabled_exceptions.dump(); } 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; 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 declaratives = debugging_clients.find(all? ":all:" : para); if( declaratives == debugging_clients.end() ) { static std::list<std::string> empty; return empty; } return declaratives->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; } cbl_call_convention_t call_convention() { return programs.empty()? cbl_call_cobol_e : programs.top().call_convention; } cbl_call_convention_t call_convention( cbl_call_convention_t convention) { if( programs.empty() ) { return programs.pending_call_convention(convention); } auto& prog( programs.top() ); return prog.call_convention = convention; } 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( 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; } size_t program_declaratives(void) const { if( programs.empty() ) return 0; return programs.top().declaratives_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 ) { if( ! symbol_label_section_exists(program_index()) ) { 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() { if( enabled_exceptions.size() ) { declaratives_evaluate(ec_none_e); } assert(!programs.empty()); 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. */ if( programs.size() == 1 ) { if( yydebug ) parser_call_targets_dump(); for( size_t caller : symbol_program_programs() ) { 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 %zu calls from #%-3zu (%s) s/%s/%s/", n, caller, caller_name, called->name, mangled_name); } } 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()); size_t idcl = symbol_declaratives_add(program_index(), declaratives.as_list()); programs.top().declaratives_index = idcl; // 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]; char lave[32]; sprintf(eval, "_DECLARATIVES_EVAL%d", eval_count); sprintf(lave, "_DECLARATIVES_LAVE%d", eval_count); eval_count +=1 ; 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(cbl_field_of(symbol_at(idcl)), lave_label); return lave_label; } cbl_label_t * new_section( cbl_label_t * section ) { std::swap( programs.top().section, section ); return section; } /* * END DECLARATIVES causes: * 1. Add DECLARATIVES symbol, containing criteria blob. * 2. Create section _DECLARATIVES_EVAL * and exit label _DECLARATIVES_LAVE * 3. declarative_runtime_match generates runtime evaluation "ladder". * 4. 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, file_status_t status = FsSuccess ) { // The exception file number is assumed to be zero at all times unless // it has been set to non-zero, at which point whoever picks it up and takes // action on it is charged with setting it back to zero. if( file ) { parser_set_file_number((int)symbol_index(symbol_elem_of(file))); } // parser_set_file_number(file ? (int)symbol_index(symbol_elem_of(file)) : 0); parser_set_handled((ec_type_t)status); 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 (not file status). * * 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( ec_type_t handled = ec_none_e ) { // The exception file number is assumed to be zero unless it has been // changed to a non-zero value. The program picking it up and referencing // it is charged with setting it back to zero. // parser_set_file_number(0); parser_set_handled(handled); cbl_label_t *eval = programs.first_declarative(); 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; #define PROGRAM current.program_index() static void add_debugging_declarative( const cbl_label_t * label ) { 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 ); cbl_call_convention_t current_call_convention() { return current.call_convention(); } cbl_call_convention_t current_call_convention( cbl_call_convention_t convention) { return current.call_convention(convention); } 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(void) { return new_temporary(FldNumericBin5); } static inline cbl_field_t * new_tempnumeric_float(void) { return new_temporary(FldFloat); } 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( is_literal(field) ) { int v, n; const char *initial = field->data.initial; return 1 == sscanf(initial, "%d%n", &v, &n) && n == (int)strlen(initial); } 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, cbl_refer_t *r1, cbl_refer_t *r2 ) { std::vector<cbl_refer_t> args { *r1, *r2 }; 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 // & // Removed the '&' to stop a weird compiler error invalid_key( const cbl_refer_t& ref ) { assert(ref.field); if( ref.nsubscript == 0 ) return ref; for( size_t i=0; i < ref.nsubscript; i++ ) { if( ref.subscripts[i].field->parent != ref.field->parent ) { return ref.subscripts[i]; } } return NULL; } 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 symbol_elem_t * field_of( const char F[], int L, const char name[] ) { struct symbol_elem_t *e = symbol_field(PROGRAM, 0, name); if( !e ) { cbl_internal_error("%s:%d: no symbol '%s' found", F, L, name); } assert( procedure_div_e != current_division ); return e; } #define field_of( F ) field_of(__func__, __LINE__, (F)) 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%zu_%s", record_area_name_stem, 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 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 inline void parser_add2( struct cbl_num_result_t& to, struct cbl_refer_t from ) { parser_add(to.refer, to.refer, from, to.rounded); } static inline void parser_subtract2( struct cbl_num_result_t to, struct 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( ! 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, cbl_refer_t into, cbl_refer_t pointer, cbl_label_t *on_error = NULL, cbl_label_t *not_error = NULL); void unstringify( 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_%zu", 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_%zu", symbol_index()); // Programs have to start with an implicit section return label_add(LblSection, name, yylineno); } static void 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( "%d errors in DATA DIVISION, compilation ceases", nparse_error ); return false; } return true; } static bool anybody_redefines(cbl_field_t *tree) { bool retval = false; while(tree) { if( symbol_redefines(tree) ) { retval = true; break; } 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 ); } } } // 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; 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, 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; } const char * keyword_str( int token ); static YYLTYPE current_location; const YYLTYPE& cobol_location() { return current_location; } static inline YYLTYPE location_set( const YYLTYPE& loc ) { return current_location = loc; } static int prior_statement; static size_t 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