aboutsummaryrefslogtreecommitdiff
path: root/gcc/cobol/parse.y
diff options
context:
space:
mode:
authorJames K. Lowden <jklowden@symas.com>2025-03-06 16:25:09 -0500
committerRichard Biener <rguenth@gcc.gnu.org>2025-03-11 07:48:21 +0100
commit3c5ed996ac94a15bc2929155f2c69cc85eef89f7 (patch)
treec365f6e25814ca3e88ae3fed34ca7a327a016540 /gcc/cobol/parse.y
parenta0754187274a36443707eab5506ae53ab1d71ad2 (diff)
downloadgcc-3c5ed996ac94a15bc2929155f2c69cc85eef89f7.zip
gcc-3c5ed996ac94a15bc2929155f2c69cc85eef89f7.tar.gz
gcc-3c5ed996ac94a15bc2929155f2c69cc85eef89f7.tar.bz2
COBOL: Frontend
gcc/cobol/ * LICENSE: New file. * Make-lang.in: New file. * config-lang.in: New file. * lang.opt: New file. * lang.opt.urls: New file. * cbldiag.h: New file. * cdfval.h: New file. * cobol-system.h: New file. * copybook.h: New file. * dts.h: New file. * exceptg.h: New file. * gengen.h: New file. * genmath.h: New file. * genutil.h: New file. * inspect.h: New file. * lang-specs.h: New file. * lexio.h: New file. * parse_ante.h: New file. * parse_util.h: New file. * scan_ante.h: New file. * scan_post.h: New file. * show_parse.h: New file. * structs.h: New file. * symbols.h: New file. * token_names.h: New file. * util.h: New file. * cdf-copy.cc: New file. * lexio.cc: New file. * scan.l: New file. * parse.y: New file. * genapi.cc: New file. * genapi.h: New file. * gengen.cc: New file. * genmath.cc: New file. * genutil.cc: New file. * cdf.y: New file. * cobol1.cc: New file. * convert.cc: New file. * except.cc: New file. * gcobolspec.cc: New file. * structs.cc: New file. * symbols.cc: New file. * symfind.cc: New file. * util.cc: New file. * gcobc: New file. * gcobol.1: New file. * gcobol.3: New file. * help.gen: New file. * udf/stored-char-length.cbl: New file.
Diffstat (limited to 'gcc/cobol/parse.y')
-rw-r--r--gcc/cobol/parse.y13107
1 files changed, 13107 insertions, 0 deletions
diff --git a/gcc/cobol/parse.y b/gcc/cobol/parse.y
new file mode 100644
index 0000000..15dbd1c
--- /dev/null
+++ b/gcc/cobol/parse.y
@@ -0,0 +1,13107 @@
+/*
+ * 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.
+ */
+%code requires {
+ #include <fstream> // Before cobol-system because it uses poisoned functions
+ #include "cobol-system.h"
+ #include <cmath>
+ #include <algorithm>
+ #include <map>
+ #include "io.h"
+ #include "ec.h"
+
+#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
+
+ enum radix_t {
+ decimal_e = 10,
+ hexadecimal_e = 16,
+ boolean_e = 2,
+ };
+
+ enum accept_func_t {
+ accept_done_e,
+ accept_command_line_e,
+ accept_envar_e,
+ };
+
+ class literal_t {
+ size_t isym;
+ public:
+ char prefix[3];
+ size_t len;
+ char *data;
+
+ bool empty() const { return data == NULL; }
+ size_t isymbol() const { return isym; }
+ const char * symbol_name() const {
+ return isym? cbl_field_of(symbol_at(isym))->name : "";
+ }
+
+ literal_t&
+ set( size_t len, char *data, const char prefix[] ) {
+ set_prefix(prefix, strlen(prefix));
+ set_data(len, data);
+ return *this;
+ }
+
+ literal_t&
+ set( const cbl_field_t * field ) {
+ assert(field->has_attr(constant_e));
+ assert(is_literal(field));
+
+ set_prefix( "", 0 );
+ set_data( field->data.capacity,
+ const_cast<char*>(field->data.initial),
+ field_index(field) );
+ return *this;
+ }
+ literal_t&
+ set_data( size_t len, char *data, size_t isym = 0 ) {
+ this->isym = isym;
+ this->len = len;
+ this->data = data;
+ if( this->prefix[0] == 'Z' ) {
+ this->data = new char[++this->len];
+ auto p = std::copy(data, data + len, this->data);
+ *p = '\0';
+ }
+ return *this;
+ }
+ literal_t&
+ set_prefix( const char *input, size_t len ) {
+ assert(len < sizeof(prefix));
+ std::fill(prefix, prefix + sizeof(prefix), '\0');
+ std::transform(input, input + len, prefix, toupper);
+ return *this;
+ }
+ bool
+ compatible_prefix( const literal_t& that ) const {
+ if( prefix[0] != that.prefix[0] ) {
+ return prefix[0] != 'N' && that.prefix[0] != 'N';
+ }
+ return true;
+ }
+ };
+
+ struct acrc_t { // Abbreviated combined relation condition
+ cbl_refer_t *term;
+ relop_t op;
+ bool invert;
+ acrc_t& init( cbl_refer_t *term = NULL,
+ relop_t op = relop_t(-1),
+ bool invert = false )
+ {
+ this->term = term;
+ this->op = op;
+ this->invert = invert;
+ return *this;
+ }
+ static acrc_t make( cbl_refer_t *term = NULL,
+ relop_t op = relop_t(-1),
+ bool invert = false )
+ {
+ acrc_t output;
+ return output.init( term, op, invert );
+ }
+ relop_t relop_from( relop_t ante_op ) const {
+ assert(ante_op != -1);
+ return op != -1? op : ante_op;
+ }
+ bool is_relation_condition() const { return term && term->field; }
+ };
+ typedef std::list<acrc_t> acrcs_t;
+
+ enum data_category_t { data_category_none,
+ data_category_all,
+ data_alphabetic_e,
+ data_alphanumeric_e,
+ data_alphanumeric_edited_e,
+ data_boolean_e,
+ data_data_pointer_e,
+ data_function_pointer_e,
+ data_msg_tag_e,
+ data_dbcs_e,
+ data_egcs_e,
+ data_national_e,
+ data_national_edited_e,
+ data_numeric_e,
+ data_numeric_edited_e,
+ data_object_referenc_e,
+ data_program_pointer_e,
+ };
+
+ const char * data_category_str( data_category_t category );
+
+ typedef std::map<data_category_t, struct cbl_refer_t*> category_map_t;
+
+ struct substitution_t {
+ enum subst_fl_t { subst_all_e, subst_first_e = 'F', subst_last_e = 'L' };
+ bool anycase;
+ subst_fl_t first_last;
+ cbl_refer_t *orig, *replacement;
+
+ substitution_t& init( bool anycase, char first_last,
+ cbl_refer_t *orig, cbl_refer_t *replacement ) {
+ this->anycase = anycase;
+ switch(first_last) {
+ case 'F': this->first_last = subst_first_e; break;
+ case 'L': this->first_last = subst_last_e; break;
+ default:
+ this->first_last = subst_all_e;
+ break;
+ }
+ this->orig = orig;
+ this->replacement = replacement;
+ return *this;
+ }
+ };
+ typedef std::list<substitution_t> substitutions_t;
+
+ struct init_statement_t {
+ bool to_value;
+ data_category_t category;
+ category_map_t replacement;
+
+ init_statement_t( category_map_t replacement )
+ : to_value(false)
+ , category(data_category_none)
+ , replacement(replacement)
+
+ {}
+
+ init_statement_t( bool to_value = false )
+ : to_value(to_value)
+ , category(data_category_none)
+ , replacement(category_map_t())
+ {}
+
+ };
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-function"
+ static data_category_t
+ data_category_of( const cbl_refer_t& refer );
+
+ static _Float128
+ numstr2i( const char input[], radix_t radix );
+
+ struct cbl_field_t;
+ static inline cbl_field_t *
+ new_literal( const char initial[], enum radix_t radix );
+#pragma GCC diagnostic pop
+
+
+ #include <list>
+
+ enum select_clause_t {
+ access_clause_e = 0x0001,
+ alt_key_clause_e = 0x0002,
+ assign_clause_e = 0x0004,
+ collating_clause_e = 0x0008,
+ file_status_clause_e = 0x0010,
+ lock_mode_clause_e = 0x0020,
+ organization_clause_e = 0x0040,
+ padding_clause_e = 0x0080,
+ record_delim_clause_e = 0x0100,
+ record_key_clause_e = 0x0200,
+ relative_key_clause_e = 0x0400,
+ reserve_clause_e = 0x0800,
+ sharing_clause_e = 0x1000,
+ };
+
+ struct symbol_elem_t;
+ struct symbol_elem_t * symbols_begin( size_t first );
+ struct symbol_elem_t * symbols_end();
+
+ void field_done();
+
+ template <typename E>
+ struct Elem_list_t {
+ std::list<E> elems;
+ Elem_list_t() {}
+ Elem_list_t( E elem ) {
+ elems.push_back(elem);
+ }
+ Elem_list_t * push_back( E elem ) {
+ elems.push_back(elem);
+ return this;
+ }
+ void clear() {
+ for( auto p = elems.begin(); p != elems.end(); p++ ) {
+ assert( !(symbols_begin(0) <= *p && *p < symbols_end()) );
+ delete *p;
+ }
+ elems.clear();
+ }
+ };
+
+ struct file_list_t;
+ struct cbl_label_t;
+ typedef struct Elem_list_t<cbl_label_t*> Label_list_t;
+
+ struct cbl_file_key_t;
+ typedef struct Elem_list_t<cbl_file_key_t*> key_list_t;
+
+ struct cbl_declarative_t;
+ typedef struct Elem_list_t<cbl_declarative_t*> declarative_list_t;
+ typedef struct Elem_list_t<ec_type_t> ec_list_t;
+ typedef struct Elem_list_t<size_t> isym_list_t;
+
+ struct rel_part_t;
+
+ bool set_debug(bool);
+
+#include "ec.h"
+#include "common-defs.h"
+#include "inspect.h"
+}
+
+%{
+#include <fstream> // Before cobol-system because it uses poisoned functions
+#include "cobol-system.h"
+#include "cdfval.h"
+#include "ec.h"
+#include "common-defs.h"
+#include "util.h"
+#include "cbldiag.h"
+#include "symbols.h"
+#include "inspect.h"
+#include "io.h"
+#include "genapi.h"
+#include "exceptl.h"
+#include "exceptg.h"
+#include "parse_ante.h"
+%}
+
+%token IDENTIFICATION_DIV "IDENTIFICATION DIVISION"
+ ENVIRONMENT_DIV "ENVIRONMENT DIVISION"
+ PROCEDURE_DIV "PROCEDURE DIVISION"
+ DATA_DIV "DATA DIVISION"
+ FILE_SECT "FILE SECTION"
+ INPUT_OUTPUT_SECT "INPUT-OUTPUT SECTION"
+ LINKAGE_SECT "LINKAGE SECTION"
+ LOCAL_STORAGE_SECT "LOCAL-STORAGE SECTION"
+ WORKING_STORAGE_SECT "WORKING-STORAGE SECTION"
+
+%token OBJECT_COMPUTER "OBJECT COMPUTER"
+
+%token DISPLAY_OF "DISPLAY OF"
+ END_FUNCTION "END FUNCTION"
+ END_PROGRAM "END PROGRAM"
+ END_SUBPROGRAM "END PROGRAM <contained program>"
+
+%token JUSTIFIED RETURNING NO_CONDITION "invalid token"
+
+%token <string> ALNUM ALPHED
+%token <number> ERROR EXCEPTION SIZE_ERROR "SIZE ERROR"
+%token <ec_type> EXCEPTION_NAME "EXCEPTION NAME"
+%token <number> LEVEL LEVEL66 "66" LEVEL78 "78" LEVEL88 "88"
+%token <string> CLASS_NAME "class name"
+ NAME
+ NAME88 "Level 88 NAME"
+ NUME "Name"
+ NUMED "NUMERIC-EDITED picture"
+ NUMED_CR "NUMERIC-EDITED CR picture"
+ NUMED_DB "NUMERIC-EDITED DB picture"
+%token <number> NINEDOT NINES NINEV PIC_P
+%token <string> SPACES
+%token <literal> LITERAL
+%token <number> END EOP
+%token <string> FILENAME
+%token <number> INVALID
+%token <number> NUMBER NEGATIVE
+%token <numstr> NUMSTR "numeric literal"
+%token <number> OVERFLOW
+%token <computational> COMPUTATIONAL
+
+%token <boolean> PERFORM BACKWARD
+%token <number> POSITIVE
+%token <field_attr> POINTER
+%token <string> SECTION
+%token <number> STANDARD_ALPHABET "STANDARD ALPHABET"
+%token <string> SWITCH
+%token <string> UPSI
+%token <number> ZERO
+
+ /* environment names */
+%token <number> SYSIN SYSIPT SYSOUT SYSLIST SYSLST SYSPUNCH SYSPCH CONSOLE
+%token <number> C01 C02 C03 C04 C05 C06 C07 C08 C09 C10 C11 C12 CSP
+%token <number> S01 S02 S03 S04 S05 AFP_5A "AFP 5A"
+%token <number> STDIN STDOUT STDERR
+
+ /* intrinsics */
+%token <string> LIST MAP NOLIST NOMAP NOSOURCE
+%token <number> MIGHT_BE "IS or IS NOT"
+ FUNCTION_UDF "UDF name"
+ FUNCTION_UDF_0 "UDF"
+
+%token <string> DATE_FMT "date format"
+ TIME_FMT "time format"
+ DATETIME_FMT "datetime format"
+
+ /* tokens without semantic value */
+ /* CDF (COPY and >> defined here but used in cdf.y) */
+%token BASIS CBL CONSTANT COPY
+ DEFINED ENTER FEATURE INSERTT
+ LSUB "("
+ PARAMETER_kw "PARAMETER"
+ OVERRIDE READY RESET
+ RSUB ")"
+ SERVICE_RELOAD "SERVICE RELOAD" STAR_CBL "*CBL"
+ SUBSCRIPT SUPPRESS TITLE TRACE USE
+
+ COBOL_WORDS ">>COBOL-WORDS" EQUATE UNDEFINE
+ CDF_DEFINE ">>DEFINE" CDF_DISPLAY ">>DISPLAY"
+ CDF_IF ">>IF" CDF_ELSE ">>ELSE" CDF_END_IF ">>END-IF"
+ CDF_EVALUATE ">>EVALUATE"
+ CDF_WHEN ">>WHEN"
+ CDF_END_EVALUATE ">>END-EVALUATE"
+ CALL_COBOL "CALL" CALL_VERBATIM "CALL (as C)"
+
+ IF THEN ELSE
+ SENTENCE
+ ACCEPT ADD ALTER CALL CANCEL CLOSE COMPUTE CONTINUE
+ DELETE DISPLAY DIVIDE EVALUATE EXIT FILLER_kw "FILLER"
+ GOBACK GOTO
+ INITIALIZE INSPECT
+ MERGE MOVE MULTIPLY OPEN PARAGRAPH
+ READ RELEASE RETURN REWRITE
+ SEARCH SET SELECT SORT SORT_MERGE "SORT-MERGE"
+ STRING_kw "STRING" STOP SUBTRACT START
+ UNSTRING WRITE WHEN
+
+ ABS ACCESS ACOS ACTUAL ADVANCING AFTER ALL
+ ALLOCATE
+ ALPHABET ALPHABETIC ALPHABETIC_LOWER "ALPHABETIC-LOWER"
+ ALPHABETIC_UPPER "ALPHABETIC-UPPER"
+ ALPHANUMERIC
+ ALPHANUMERIC_EDITED "ALPHANUMERIC-EDITED"
+ ALSO ALTERNATE ANNUITY ANUM ANY ANYCASE APPLY ARE
+ AREA AREAS AS
+ ASCENDING ACTIVATING ASIN ASSIGN AT ATAN
+
+ BASED BASECONVERT
+ BEFORE BINARY BIT BIT_OF "BIT-OF" BIT_TO_CHAR "BIT-TO-CHAR"
+ BLANK BLOCK
+ BOOLEAN_OF_INTEGER "BOOLEAN-OF-INTEGER"
+ BOTTOM BY
+ BYTE BYTE_LENGTH "BYTE-LENGTH"
+
+ CF CH
+ CHANGED CHAR CHAR_NATIONAL "CHAR-NATIONAL"
+ CHARACTER CHARACTERS CHECKING CLASS
+ COBOL CODE CODESET COLLATING
+ COLUMN COMBINED_DATETIME "COMBINED-DATETIME"
+ COMMA COMMAND_LINE "COMMAND-LINE"
+ COMMAND_LINE_COUNT "COMMAND-LINE-COUNT"
+ COMMIT COMMON
+
+ CONCAT CONDITION CONFIGURATION_SECT "CONFIGURATION SECTION"
+ CONTAINS
+ CONTENT CONTROL CONTROLS CONVERT CONVERTING CORRESPONDING COS
+ COUNT CURRENCY CURRENT CURRENT_DATE
+
+ DATA DATE DATE_COMPILED
+ DATE_OF_INTEGER "DATE-OF-INTEGER"
+ DATE_TO_YYYYMMDD "DATE-TO-YYYYMMDD"
+ DATE_WRITTEN "DATE-WRITTEN"
+ DAY DAY_OF_INTEGER "DAY-OF-INTEGER"
+ DAY_OF_WEEK "DAY-OF-WEEK"
+ DAY_TO_YYYYDDD "DAY-TO-YYYYDDD"
+ DBCS DE DEBUGGING DECIMAL_POINT
+ DECLARATIVES DEFAULT DELIMITED DELIMITER DEPENDING
+ DESCENDING DETAIL DIRECT
+ DIRECT_ACCESS "DIRECT-ACCESS"
+ DOWN DUPLICATES
+ DYNAMIC
+
+ E EBCDIC EC EGCS ENTRY ENVIRONMENT EQUAL EVERY
+ EXAMINE EXHIBIT EXP EXP10 EXTEND EXTERNAL
+
+ EXCEPTION_FILE "EXCEPTION-FILE"
+ EXCEPTION_FILE_N "EXCEPTION-FILE-N"
+ EXCEPTION_LOCATION "EXCEPTION-LOCATION"
+ EXCEPTION_LOCATION_N "EXCEPTION-LOCATION-N"
+ EXCEPTION_STATEMENT "EXCEPTION-STATEMENT"
+ EXCEPTION_STATUS "EXCEPTION-STATUS"
+
+ FACTORIAL FALSE_kw "False" FD
+ FILE_CONTROL "FILE-CONTROL"
+ FILE_KW "File"
+ FILE_LIMIT "FILE-LIMIT"
+ FINAL FINALLY
+ FIND_STRING "FIND-STRING"
+ FIRST FIXED FOOTING FOR
+ FORMATTED_CURRENT_DATE "FORMATTED-CURRENT-DATE"
+ FORMATTED_DATE "FORMATTED-DATE"
+ FORMATTED_DATETIME "FORMATTED-DATETIME"
+ FORMATTED_TIME "FORMATTED-TIME"
+ FORM_OVERFLOW "FORM-OVERFLOW"
+ FREE
+ FRACTION_PART "FRACTION-PART"
+ FROM FUNCTION
+
+ GENERATE GIVING GLOBAL GO GROUP
+
+ HEADING HEX
+ HEX_OF "HEX-OF"
+ HEX_TO_CHAR "HEX-TO-CHAR"
+ HIGH_VALUES "HIGH-VALUES"
+ HIGHEST_ALGEBRAIC "HIGHEST-ALGEBRAIC"
+ HOLD
+
+ IBM_360 IN INCLUDE INDEX INDEXED INDICATE INITIAL_kw "INITIAL"
+ INITIATE INPUT INSTALLATION INTERFACE
+ INTEGER
+ INTEGER_OF_BOOLEAN "INTEGER-OF-BOOLEAN"
+ INTEGER_OF_DATE "INTEGER-OF-DATE"
+ INTEGER_OF_DAY "INTEGER-OF-DAY"
+ INTEGER_OF_FORMATTED_DATE "INTEGER-OF-FORMATTED-DATE"
+ INTEGER_PART "INTEGER-PART"
+ INTO INTRINSIC INVOKE IO IO_CONTROL "IO-CONTROL"
+ IS ISNT "IS NOT"
+
+ KANJI KEY
+
+ LABEL LAST LEADING LEFT LENGTH
+ LENGTH_OF "LENGTH-OF"
+ LIMIT LIMITS LINE LINES
+ LINE_COUNTER "LINE-COUNTER"
+ LINAGE LINKAGE LOCALE LOCALE_COMPARE "LOCALE-COMPARE"
+ LOCALE_DATE "LOCALE-DATE"
+ LOCALE_TIME "LOCALE-TIME"
+ LOCALE_TIME_FROM_SECONDS "LOCALE-TIME-FROM-SECONDS"
+ LOCAL_STORAGE "LOCAL-STORAGE"
+ LOCATION
+ LOCK LOCK_ON LOG LOG10
+ LOWER_CASE "LOWER-CASE"
+ LOW_VALUES "LOW-VALUES"
+ LOWEST_ALGEBRAIC "LOWEST-ALGEBRAIC"
+ LPAREN " )"
+
+ MANUAL MAXX "Max" MEAN MEDIAN MIDRANGE
+ MINN "Min" MULTIPLE MOD MODE
+ MODULE_NAME "MODULE-NAME "
+
+ NAMED NAT NATIONAL
+ NATIONAL_EDITED "NATIONAL-EDITED"
+ NATIONAL_OF "NATIONAL-OF"
+ NATIVE NESTED NEXT
+ NO NOTE
+ NULLS NULLPTR
+ NUMERIC
+ NUMERIC_EDITED NUMVAL
+ NUMVAL_C "NUMVAL-C"
+ NUMVAL_F "NUMVAL-F"
+
+ OCCURS OF OFF OMITTED ON ONLY OPTIONAL OPTIONS ORD ORDER
+ ORD_MAX "ORD-MAX"
+ ORD_MIN "ORD-MIN"
+ ORGANIZATION OTHER OTHERWISE OUTPUT
+
+ PACKED_DECIMAL PADDING PAGE
+ PAGE_COUNTER "PAGE-COUNTER"
+ PF PH PI PIC PICTURE
+ PLUS PRESENT_VALUE PRINT_SWITCH
+ PROCEDURE PROCEDURES PROCEED PROCESS
+ PROGRAM_ID "PROGRAM-ID"
+ PROGRAM_kw "Program" PROPERTY PROTOTYPE PSEUDOTEXT
+
+ QUOTES "QUOTE"
+
+ RANDOM RANDOM_SEED RANGE RAISE RAISING
+ RD RECORD RECORDING RECORDS RECURSIVE
+ REDEFINES REEL REFERENCE RELATIVE REM REMAINDER REMARKS
+ REMOVAL RENAMES REPLACE REPLACING REPORT REPORTING REPORTS
+ REPOSITORY RERUN RESERVE RESTRICTED RESUME
+ REVERSE REVERSED REWIND RF RH RIGHT ROUNDED RUN
+
+ SAME SCREEN SD
+ SECONDS_FROM_FORMATTED_TIME "SECONDS-FROM-FORMATTED-TIME"
+ SECONDS_PAST_MIDNIGHT "SECONDS-PAST-MIDNIGHT"
+ SECURITY
+ SEPARATE SEQUENCE SEQUENTIAL SHARING
+ SIMPLE_EXIT "(simple) EXIT"
+ SIGN SIN SIZE
+ SMALLEST_ALGEBRAIC "SMALLEST-ALGEBRAIC"
+ SOURCE
+ SOURCE_COMPUTER "SOURCE-COMPUTER"
+ SPECIAL_NAMES SQRT STACK
+ STANDARD
+ STANDARD_1 "STANDARD-1"
+ STANDARD_DEVIATION "STANDARD-DEVIATION "
+ STANDARD_COMPARE "STANDARD-COMPARE"
+ STATUS STRONG
+ SUBSTITUTE SUM SYMBOL SYMBOLIC SYNCHRONIZED
+
+ TALLY TALLYING TAN TERMINATE TEST
+ TEST_DATE_YYYYMMDD "TEST-DATE-YYYYMMDD"
+ TEST_DAY_YYYYDDD "TEST-DAY-YYYYDDD"
+ TEST_FORMATTED_DATETIME "TEST-FORMATTED-DATETIME"
+ TEST_NUMVAL "TEST-NUMVAL"
+ TEST_NUMVAL_C "TEST-NUMVAL-C"
+ TEST_NUMVAL_F "TEST-NUMVAL-F"
+ THAN TIME TIMES
+ TO TOP
+ TOP_LEVEL
+ TRACKS TRACK_AREA TRAILING TRANSFORM TRIM TRUE_kw "True" TRY
+ TURN TYPE TYPEDEF
+
+ ULENGTH UNBOUNDED UNIT UNITS UNIT_RECORD UNTIL UP UPON
+ UPOS UPPER_CASE USAGE USING USUBSTR USUPPLEMENTARY
+ UTILITY UUID4 UVALID UWIDTH
+
+ VALUE VARIANCE VARYING VOLATILE
+
+ WHEN_COMPILED WITH WORKING_STORAGE
+ XML XMLGENERATE XMLPARSE
+ YEAR_TO_YYYY YYYYDDD YYYYMMDD
+
+ /* unused Context Words */
+ ARITHMETIC ATTRIBUTE AUTO AUTOMATIC
+ AWAY_FROM_ZERO "AWAY-FROM-ZERO"
+ BACKGROUND_COLOR "BACKGROUND-COLOR"
+ BELL
+ BINARY_ENCODING "BINARY-ENCODING"
+ BLINK
+ CAPACITY CENTER CLASSIFICATION CYCLE
+ DECIMAL_ENCODING "DECIMAL-ENCODING"
+ ENTRY_CONVENTION EOL EOS ERASE EXPANDS
+ FLOAT_BINARY "FLOAT-BINARY"
+ FLOAT_DECIMAL "FLOAT-DECIMAL"
+ FOREGROUND_COLOR FOREVER FULL
+ HIGHLIGHT
+ HIGH_ORDER_LEFT "HIGH-ORDER-LEFT"
+ HIGH_ORDER_RIGHT "HIGH-ORDER-RIGHT"
+ IGNORING IMPLEMENTS INITIALIZED INTERMEDIATE
+ LC_ALL_kw "LC-ALL"
+ LC_COLLATE_kw "LC-COLLATE"
+ LC_CTYPE_kw "LC-CTYPE"
+ LC_MESSAGES_kw "LC-MESSAGES"
+ LC_MONETARY_kw "LC-MONETARY"
+ LC_NUMERIC_kw "LC-NUMERIC"
+ LC_TIME_kw "LC-TIME"
+ LOWLIGHT
+ NEAREST_AWAY_FROM_ZERO "NEAREST-AWAY-FROM-ZERO"
+ NEAREST_EVEN NEAREST_TOWARD_ZERO "NEAREST-EVEN NEAREST-TOWARD-ZERO"
+ NONE NORMAL NUMBERS
+ PREFIXED PREVIOUS PROHIBITED RELATION REQUIRED
+ REVERSE_VIDEO ROUNDING
+ SECONDS SECURE SHORT SIGNED
+ STANDARD_BINARY "STANDARD-BINARY"
+ STANDARD_DECIMAL "STANDARD-DECIMAL"
+ STATEMENT STEP STRUCTURE
+ TOWARD_GREATER "TOWARD-GREATER"
+ TOWARD_LESSER "TOWARD-LESSER"
+ TRUNCATION
+ UCS_4 "UCS-4"
+ UNDERLINE UNSIGNED
+ UTF_16 "UTF-16"
+ UTF_8 "UTF-8"
+
+ ADDRESS
+ END_ACCEPT "END-ACCEPT"
+ END_ADD "END-ADD"
+ END_CALL "END-CALL"
+ END_COMPUTE "END-COMPUTE"
+ END_DELETE "END-DELETE"
+ END_DISPLAY "END-DISPLAY"
+ END_DIVIDE "END-DIVIDE"
+ END_EVALUATE "END-EVALUATE"
+ END_MULTIPLY "END-MULTIPLY"
+ END_PERFORM "END-PERFORM"
+ END_READ "END-READ"
+ END_RETURN "END-RETURN"
+ END_REWRITE "END-REWRITE"
+ END_SEARCH "END-SEARCH"
+ END_START "END-START"
+ END_STRING "END-STRING"
+ END_SUBTRACT "END-SUBTRACT"
+ END_UNSTRING "END-UNSTRING"
+ END_WRITE "END-WRITE"
+ END_IF "END-IF"
+ /* end tokens without semantic value */
+
+ // YYEOF added for compatibility with Bison 3.5
+ // https://savannah.gnu.org/forum/forum.php?forum_id=9735
+%token YYEOF 0 "end of file"
+
+%type <number> sentence statements statement
+%type <number> star_cbl_opt close_how
+
+%type <number> test_before usage_clause1 might_be
+%type <boolean> all optional sign_leading on_off initialized strong
+%type <number> count data_clauses data_clause
+%type <number> nine nines nps relop spaces_etc reserved_value signed
+%type <number> variable_type
+%type <number> true_false posneg eval_posneg
+%type <number> open_io alphabet_etc
+%type <special_type> device_name
+%type <string> numed collating_sequence context_word ctx_name locale_spec
+%type <literal> namestr alphabet_lit program_as repo_as
+%type <field> perform_cond kind_of_name
+%type <refer> alloc_ret
+
+%type <field> log_term rel_expr rel_abbr eval_abbr
+%type <refer> num_value num_term value factor
+%type <refer> simple_cond bool_expr
+%type <log_expr_t> log_expr rel_abbrs eval_abbrs
+%type <rel_term_t> rel_term rel_term1
+
+%type <field_data> value78
+%type <field> literal name nume typename
+%type <field> num_literal signed_literal
+
+%type <number> perform_start
+%type <refer> perform_times
+%type <perf> perform_verb
+ perform_inline perform_except
+
+%type <refer> eval_subject1
+%type <vargs> vargs disp_vargs;
+%type <field> level_name
+%type <string> fd_name picture_sym name66 paragraph_name
+%type <literal> literalism
+%type <number> bound advance_when org_clause1 read_next
+%type <number> access_mode multiple lock_how lock_mode
+%type <select_clauses> select_clauses
+%type <select_clause> select_clause access_clause alt_key_clause
+ assign_clause collate_clause status_clause
+ lock_mode_clause org_clause padding_clause
+ record_delim_clause record_key_clause
+ relative_key_clause reserve_clause sharing_clause
+
+%type <file> filename read_body write_body delete_body
+%type <rewrite_t> rewrite_body
+%type <min_max> record_vary rec_contains from_to record_desc
+%type <file_op> read_file rewrite1 write_file
+%type <field> data_descr data_descr1 write_what file_record
+%type <field> name88
+%type <refer> advancing advance_by
+%type <refer> alphaval alpha_val numeref scalar scalar88
+%type <refer> tableref tableish
+%type <refer> varg varg1 varg1a
+%type <refer> expr expr_term compute_expr free_tgt by_value_arg
+%type <refer> move_tgt selected_name read_key read_into vary_by
+%type <refer> accept_refer num_operand envar search_expr any_arg
+%type <accept_func> accept_body
+%type <refers> expr_list subscripts arg_list free_tgts
+%type <targets> move_tgts set_tgts
+%type <field> search_varying
+%type <field> search_term search_terms
+%type <label> label_name
+%type <tgt> sort_target
+%type <files> filenames cdf_use_files
+%type <field> one_switch
+%type <fields> field_list switches key_sources key_source
+%type <sort_keys> sort_keys
+%type <sort_key> sort_key
+%type <sort_io> sort_input sort_output
+%type <boolean> sort_dup forward_order unique_key sign_separate
+%type <number> befter cardinal initial first_leading
+
+%type <refer> inspected
+%type <insp_qual> insp_qual
+%type <insp_match> insp_quals insp_mtquals tally_match
+%type <insp_replace> x_by_y
+%type <insp_oper> replace_oper x_by_ys
+%type <insp_oper> tally_forth tally_matches
+%type <inspect> tally
+%type <insp_one> replacement tally_fors
+%type <insp_all> tallies replacements
+
+%type <arith> add_body subtract_body multiply_body divide_body
+%type <arith> add_impl subtract_impl multiply_impl divide_impl
+%type <arith> add_cond subtract_cond multiply_cond divide_cond
+%type <arith> divide_into divide_by
+
+%type <refer> intrinsic_call
+%type <field> intrinsic intrinsic_locale
+
+%type <field> intrinsic0
+%type <number> intrinsic_v intrinsic_I intrinsic_N intrinsic_X
+%type <number> intrinsic_I2 intrinsic_N2 intrinsic_X2
+%type <number> lopper_case
+%type <number> return_body return_file
+%type <field> trim_trailing function_udf
+
+%type <refer> str_input str_size
+%type <refer2> str_into
+
+%type <refers> sum scalar88s ffi_names
+%type <delimited_1> str_delimited
+%type <delimiteds> str_delimiteds
+%type <str_body> string_body
+
+%type <refmod_parts> refmod
+
+%type <uns_body> unstring_body
+%type <refers> uns_delimiters uns_delimited
+%type <refer> uns_delimiter
+%type <uns_into> uns_into
+%type <uns_tgts> uns_tgts
+%type <uns_tgt> uns_tgt
+
+%type <error> on_overflow on_overflows
+%type <error> arith_err arith_errs
+%type <error> accept_except accept_excepts call_except call_excepts
+%type <compute_body_t> compute_body
+
+%type <refer> ffi_name set_operand set_tgt scalar_arg unstring_src
+%type <number> /* addr_len_of */ alphanum_pic
+%type <pic_part> alphanum_part
+
+%type <ffi_arg> parameter ffi_by_ref ffi_by_con ffi_by_val
+%type <ffi_args> parameters
+%type <ffi_impl> call_body call_impl
+
+%type <ffi_arg> procedure_use
+%type <ffi_args> procedure_uses
+
+%type <comminit> comminit comminits program_attrs
+
+%type <error_clauses> io_invalids read_eofs write_eops
+%type <boolean> io_invalid read_eof write_eop
+ global is_global anycase backward
+%type <number> mistake globally first_last
+%type <io_mode> io_mode
+
+%type <labels> labels
+%type <label> label_1 section_name
+
+%type <switches> upsi_entry
+
+%type <special> acceptable disp_target
+%type <display> disp_body
+
+%type <false_domain> domains domain
+%type <colseq> alphabet_seq
+%type <alphasym> alphabet_name alphabet_seqs sort_seq
+
+%type <init_stmt> init_clause init_value
+%type <data_category> init_categora init_category
+%type <replacement> init_by
+%type <replacements> init_bys init_replace
+%type <refer> init_data exit_with stop_status
+%type <float128> cce_expr cce_factor const_value
+%type <prog_end> end_program1
+%type <substitution> subst_input
+%type <substitutions> subst_inputs
+%type <numval_locale_t> numval_locale
+
+%type <ec_type> except_name exit_raising
+%type <ec_list> except_names
+%type <isym_list> except_files
+%type <dcl_list_t> perform_ec
+
+%type <opt_init_sects> opt_init_sects
+%type <opt_init_sect> opt_init_sect
+%type <number> opt_init_value
+%type <opt_round> rounded round_between rounded_type rounded_mode
+%type <opt_arith> opt_arith_type
+%type <module_type> module_type
+
+%union {
+ bool boolean;
+ int number;
+ char *string;
+ _Float128 float128; // Hope springs eternal: 28 Mar 2023
+ literal_t literal;
+ cbl_field_attr_t field_attr;
+ ec_type_t ec_type;
+ ec_list_t* ec_list;
+ declarative_list_t* dcl_list_t;
+ isym_list_t* isym_list;
+ struct { radix_t radix; char *string; } numstr;
+ struct { int token; literal_t name; } prog_end;
+ struct { int token; special_name_t id; } special_type;
+ struct { cbl_field_type_t type;
+ uint32_t capacity; bool signable; } computational;
+ struct cbl_special_name_t *special;
+ struct cbl_alphabet_t *alphasym;
+ struct tgt_list_t *targets;
+ struct cbl_file_t *file;
+ struct { bool varying; size_t min, max; } min_max;
+ struct { cbl_file_t *file; cbl_field_t *buffer; } rewrite_t;
+ struct { cbl_file_t *file; file_status_t handled; } file_op;
+ struct cbl_label_t *label;
+ struct { cbl_label_t *label; int token; } exception;
+ struct cbl_field_data_t *field_data;
+ struct cbl_field_t *field;
+ struct { bool tf; cbl_field_t *field; } bool_field;
+ struct { int token; cbl_field_t *cond; } cond_field;
+ struct cbl_refer_t *refer;
+
+ struct rel_term_type { bool invert; cbl_refer_t *term; } rel_term_t;
+ struct log_expr_t *log_expr_t;
+ struct vargs_t* vargs;
+ struct perform_t *perf;
+ struct cbl_perform_tgt_t *tgt;
+ Label_list_t *labels;
+ key_list_t *file_keys;
+ cbl_file_mode_t io_mode;
+ struct cbl_file_key_t *file_key;
+ struct file_list_t *files;
+ struct field_list_t *fields;
+ struct refer_list_t *refers;
+ struct sort_key_t *sort_key;
+ struct sort_keys_t *sort_keys;
+ struct file_sort_io_t *sort_io;
+ struct arith_t *arith;
+ struct { size_t ntgt; cbl_num_result_t *tgts;
+ cbl_refer_t *expr; } compute_body_t;
+ struct ast_inspect_t *insp_one;
+ struct ast_inspect_list_t *insp_all;
+ struct ast_inspect_oper_t *insp_oper;
+ struct { bool before; cbl_inspect_qual_t *qual; } insp_qual;
+ cbl_inspect_t *inspect;
+ cbl_inspect_match_t *insp_match;
+ cbl_inspect_replace_t *insp_replace;
+
+ struct { cbl_refer_t *delimited; refer_list_t *inputs; } delimited;
+ struct { cbl_refer_t *input, *delimiter; } delimited_1;
+ struct { cbl_refer_t *from, *len; } refmod_parts;
+ struct refer_collection_t *delimiteds;
+ struct { cbl_label_t *on_error, *not_error; } error;
+ struct { unsigned int nclause; bool tf; } error_clauses;
+ struct refer_pair_t { cbl_refer_t *first, *second; } refer2;
+ struct { refer_collection_t *inputs; refer_pair_t into; } str_body;
+
+ struct { accept_func_t func; cbl_refer_t *into, *from; } accept_func;
+ struct unstring_into_t *uns_into;
+ struct unstring_tgt_list_t *uns_tgts;
+ struct unstring_tgt_t *uns_tgt;
+ struct { cbl_refer_t *input;
+ refer_list_t *delimited; unstring_into_t *into; } uns_body;
+
+ struct cbl_ffi_arg_t *ffi_arg;
+ struct ffi_args_t *ffi_args;
+ struct { YYLTYPE loc; cbl_refer_t *ffi_name, *ffi_returning;
+ ffi_args_t *using_params; } ffi_impl;
+
+ struct { bool common, initial, recursive; } comminit;
+ struct { enum select_clause_t clause; cbl_file_t *file; } select_clause;
+ struct { size_t clauses; cbl_file_t *file; } select_clauses;
+ struct { YYLTYPE loc; char *on, *off; } switches;
+ struct cbl_domain_t *false_domain;
+ struct { size_t also; unsigned char *low, *high; } colseq;
+ struct { cbl_field_attr_t attr; int nbyte; } pic_part;
+
+ data_category_t data_category;
+ struct { data_category_t category; cbl_refer_t* replacement; } replacement;
+ category_map_t *replacements;
+ init_statement_t *init_stmt;
+ struct { cbl_special_name_t *special; vargs_t *vargs; } display;
+ substitution_t substitution;
+ substitutions_t *substitutions;
+ struct { bool is_locale; cbl_refer_t *arg2; } numval_locale_t;
+
+ cbl_options_t::arith_t opt_arith;
+ cbl_round_t opt_round;
+ cbl_section_type_t opt_init_sect;
+ struct { bool local, working; } opt_init_sects;
+ module_type_t module_type;
+}
+
+%printer { fprintf(yyo, "clauses: 0x%04x", $$); } data_clauses
+%printer { fprintf(yyo, "%s %s", refer_type_str($$), $$? $$->name() : "<none>"); } <refer>
+%printer { fprintf(yyo, "%s", $$->field? name_of($$->field) : "[omitted]"); } alloc_ret
+%printer { fprintf(yyo, "%s %s '%s' (%s)",
+ $$? cbl_field_type_str($$->type) : "<%empty>",
+ $$? name_of($$) : "",
+ $$? $$->data.initial? $$->data.initial : "<nil>" : "",
+ $$? $$->value_str() : "" ); } <field>
+
+%printer { fprintf(yyo, "%c %s",
+ $$.invert? '!' : ' ',
+ $$.term? name_of($$.term->field) : "<none>"); } <rel_term_t>
+
+%printer { fprintf(yyo, "%s (token %d)", keyword_str($$), $$ ); } relop
+%printer { fprintf(yyo, "'%s'", $$? $$ : "" ); } NAME <string>
+%printer { fprintf(yyo, "%s'%.*s'{%zu} %s", $$.prefix, int($$.len), $$.data, $$.len,
+ $$.symbol_name()); } <literal>
+%printer { fprintf(yyo, "%s (1st of %zu)",
+ $$->targets.empty()? "" : $$->targets.front().refer.field->name,
+ $$->targets.size() ); } <targets>
+%printer { fprintf(yyo, "#%zu: %s",
+ is_temporary($$)? 0 : field_index($$),
+ $$? name_of($$) : "<nil>" ); } name
+%printer { fprintf(yyo, "{%zu-%zu}", $$.min, $$.max ); } <min_max>
+%printer { fprintf(yyo, "{%s}", $$? "+/-" : "" ); } signed
+%printer { fprintf(yyo, "{%s of %zu}",
+ teed_up_names().front(), teed_up_names().size() ); } qname
+%printer { fprintf(yyo, "{%d}", $$ ); } <number>
+%printer { fprintf(yyo, "'%s'", $$.string ); } <numstr>
+%printer { const char *s = string_of($$);
+ fprintf(yyo, "{%s}", s? s : "??" ); } <float128>
+%printer { fprintf(yyo, "{%s %c%u}", cbl_field_type_str($$.type),
+ $$.signable? '+' : ' ',
+ $$.capacity ); } <computational>
+%printer { fprintf(yyo, "{'%s'-'%s'%s}",
+ $$.low? (const char*) $$.low : "",
+ $$.high? (const char*) $$.high : "",
+ $$.also? "+" : "" ); } <colseq>
+%printer { fprintf(yyo, "{%s, %zu parameters}",
+ name_of($$.ffi_name->field), !$$.using_params? 0 :
+ $$.using_params->elems.size()); } call_body
+%printer { fprintf(yyo, "%s <- %s", data_category_str($$.category),
+ name_of($$.replacement->field)); } init_by
+
+ /* CDF (COPY and >> defined here but used in cdf.y) */
+%left BASIS CBL CONSTANT COPY
+ DEFINED ENTER FEATURE INSERTT
+ LIST LSUB MAP NOLIST NOMAP NOSOURCE
+ PARAMETER_kw OVERRIDE READY RESET RSUB
+ SERVICE_RELOAD STAR_CBL
+ SUBSCRIPT SUPPRESS TITLE TRACE USE
+
+ COBOL_WORDS EQUATE UNDEFINE
+
+ CDF_DEFINE CDF_DISPLAY
+ CDF_IF CDF_ELSE CDF_END_IF
+ CDF_EVALUATE
+ CDF_WHEN
+ CDF_END_EVALUATE
+ CALL_COBOL CALL_VERBATIM
+
+%right IF THEN ELSE
+ SENTENCE
+ ACCEPT ADD ALTER CALL CANCEL CLOSE COMPUTE CONTINUE
+ DELETE DISPLAY DIVIDE EVALUATE END EOP EXIT FILLER_kw
+ GOBACK GOTO
+ INITIALIZE INSPECT
+ MERGE MOVE MULTIPLY OPEN OVERFLOW PARAGRAPH PERFORM
+ READ RELEASE RETURN REWRITE
+ SEARCH SET SELECT SORT SORT_MERGE
+ STRING_kw STOP SUBTRACT START
+ UNSTRING WRITE WHEN INVALID
+
+%left ABS ACCESS ACOS ACTUAL ADVANCING AFP_5A AFTER ALL
+ ALLOCATE
+ ALPHABET ALPHABETIC ALPHABETIC_LOWER
+ ALPHABETIC_UPPER
+ ALPHANUMERIC
+ ALPHANUMERIC_EDITED
+ ALPHED ALSO ALTERNATE ANNUITY ANUM ANY ANYCASE APPLY ARE
+ AREA AREAS AS
+ ASCENDING ACTIVATING ASIN ASSIGN AT ATAN
+
+ BACKWARD BASED BASECONVERT
+ BEFORE BINARY BIT BIT_OF BIT_TO_CHAR
+ BLANK BLOCK
+ BOOLEAN_OF_INTEGER
+ BOTTOM BY
+ BYTE BYTE_LENGTH
+
+ C01 C02 C03 C04 C05 C06 C07 C08 C09 C10 C11 C12 CF CH
+ CHANGED CHAR CHAR_NATIONAL
+ CHARACTER CHARACTERS CHECKING CLASS
+ COBOL CODE CODESET COLLATING
+ COLUMN COMBINED_DATETIME
+ COMMA COMMAND_LINE
+ COMMAND_LINE_COUNT
+ COMMIT COMMON COMPUTATIONAL
+
+ CONCAT CONDITION CONFIGURATION_SECT
+ CONSOLE CONTAINS
+ CONTENT CONTROL CONTROLS CONVERT CONVERTING CORRESPONDING COS
+ COUNT CSP CURRENCY CURRENT CURRENT_DATE
+
+ DATA DATE DATE_COMPILED
+ DATE_OF_INTEGER
+ DATE_TO_YYYYMMDD
+ DATE_FMT
+ TIME_FMT
+ DATETIME_FMT
+ DATE_WRITTEN
+ DAY DAY_OF_INTEGER
+ DAY_OF_WEEK
+ DAY_TO_YYYYDDD
+ DBCS DE DEBUGGING DECIMAL_POINT
+ DECLARATIVES DEFAULT DELIMITED DELIMITER DEPENDING
+ DESCENDING DETAIL DIRECT
+ DIRECT_ACCESS
+ DOWN DUPLICATES
+ DYNAMIC
+
+ E EBCDIC EC EGCS ENTRY ENVIRONMENT EQUAL ERROR EVERY
+ EXAMINE EXCEPTION EXHIBIT EXP EXP10 EXTEND EXTERNAL
+
+ EXCEPTION_FILE
+ EXCEPTION_FILE_N
+ EXCEPTION_LOCATION
+ EXCEPTION_LOCATION_N
+ EXCEPTION_NAME
+ EXCEPTION_STATEMENT
+ EXCEPTION_STATUS
+
+ FACTORIAL FALSE_kw FD FILENAME
+ FILE_CONTROL
+ FILE_KW
+ FILE_LIMIT
+ FINAL FINALLY
+ FIND_STRING
+ FIRST FIXED FOOTING FOR
+ FORMATTED_CURRENT_DATE
+ FORMATTED_DATE
+ FORMATTED_DATETIME
+ FORMATTED_TIME
+ FORM_OVERFLOW
+ FREE
+ FRACTION_PART
+ FROM FUNCTION
+ FUNCTION_UDF
+
+ GENERATE GIVING GLOBAL GO GROUP
+
+ HEADING HEX
+ HEX_OF
+ HEX_TO_CHAR
+ HIGH_VALUES
+ HIGHEST_ALGEBRAIC
+ HOLD
+
+ IBM_360 IN INCLUDE INDEX INDEXED INDICATE INITIAL_kw
+ INITIATE INPUT INSTALLATION INTERFACE
+ INTEGER
+ INTEGER_OF_BOOLEAN
+ INTEGER_OF_DATE
+ INTEGER_OF_DAY
+ INTEGER_OF_FORMATTED_DATE
+ INTEGER_PART
+ INTO INTRINSIC INVOKE IO IO_CONTROL
+ IS ISNT
+
+ KANJI KEY
+
+ LABEL LAST LEADING LEFT LENGTH
+ LENGTH_OF
+ LEVEL LEVEL66
+ LEVEL88 LIMIT LIMITS LINE LINES
+ LINE_COUNTER
+ LINAGE LINKAGE LOCALE LOCALE_COMPARE
+ LOCALE_DATE
+ LOCALE_TIME
+ LOCALE_TIME_FROM_SECONDS
+ LOCAL_STORAGE
+ LOCATION
+ LOCK LOCK_ON LOG LOG10
+ LOWER_CASE
+ LOW_VALUES
+ LOWEST_ALGEBRAIC
+ LPAREN
+
+ MANUAL MAXX MEAN MEDIAN MIDRANGE
+ MIGHT_BE MINN MULTIPLE MOD MODE
+ MODULE_NAME
+
+ NAMED NAT NATIONAL
+ NATIONAL_EDITED
+ NATIONAL_OF
+ NATIVE NEGATIVE NESTED NEXT
+ NINEDOT NINES NINEV NO NOTE NO_CONDITION
+ NULLS NULLPTR NUMBER
+ NUME NUMED NUMED_CR NUMED_DB NUMERIC
+ NUMERIC_EDITED NUMSTR NUMVAL
+ NUMVAL_C
+ NUMVAL_F
+
+ OCCURS OF OFF OMITTED ON ONLY OPTIONAL OPTIONS ORD ORDER
+ ORD_MAX
+ ORD_MIN
+ ORGANIZATION OTHER OTHERWISE OUTPUT
+
+ PACKED_DECIMAL PADDING PAGE
+ PAGE_COUNTER
+ PF PH PI PIC PICTURE PIC_P
+ PLUS POINTER POSITIVE PRESENT_VALUE PRINT_SWITCH
+ PROCEDURE PROCEDURES PROCEED PROCESS
+ PROGRAM_ID
+ PROGRAM_kw PROPERTY PROTOTYPE PSEUDOTEXT
+
+ QUOTES
+
+ RANDOM RANDOM_SEED RANGE RAISE RAISING
+ RD RECORD RECORDING RECORDS RECURSIVE
+ REDEFINES REEL REFERENCE RELATIVE REM REMAINDER REMARKS
+ REMOVAL RENAMES REPLACE REPLACING REPORT REPORTING REPORTS
+ REPOSITORY RERUN RESERVE RESTRICTED RESUME
+ REVERSE REVERSED REWIND RF RH RIGHT ROUNDED RUN
+
+ S01 S02 S03 S04 S05 SAME SCREEN SD
+ SECONDS_FROM_FORMATTED_TIME
+ SECONDS_PAST_MIDNIGHT
+ SECTION SECURITY
+ SEPARATE SEQUENCE SEQUENTIAL SHARING
+ SIMPLE_EXIT
+ SIGN SIN SIZE SIZE_ERROR
+ SMALLEST_ALGEBRAIC
+ SOURCE
+ SOURCE_COMPUTER
+ SPACES SPECIAL_NAMES SQRT STACK
+ STANDARD
+ STANDARD_ALPHABET
+ STANDARD_1
+ STANDARD_DEVIATION
+ STANDARD_COMPARE
+ STATUS STRONG STDERR STDIN STDOUT
+ LITERAL SUBSTITUTE SUM SWITCH SYMBOL SYMBOLIC SYNCHRONIZED
+ SYSIN SYSIPT SYSLST SYSOUT SYSPCH SYSPUNCH
+
+ TALLY TALLYING TAN TERMINATE TEST
+ TEST_DATE_YYYYMMDD
+ TEST_DAY_YYYYDDD
+ TEST_FORMATTED_DATETIME
+ TEST_NUMVAL
+ TEST_NUMVAL_C
+ TEST_NUMVAL_F
+ THAN TIME TIMES
+ TO TOP
+ TOP_LEVEL
+ TRACKS TRACK_AREA TRAILING TRANSFORM TRIM TRUE_kw TRY
+ TURN TYPE TYPEDEF
+
+ ULENGTH UNBOUNDED UNIT UNITS UNIT_RECORD UNTIL UP UPON
+ UPOS UPPER_CASE UPSI USAGE USING USUBSTR USUPPLEMENTARY
+ UTILITY UUID4 UVALID UWIDTH
+
+ VALUE VARIANCE VARYING VOLATILE
+
+ WHEN_COMPILED WITH WORKING_STORAGE
+ XML XMLGENERATE XMLPARSE
+ YEAR_TO_YYYY YYYYDDD YYYYMMDD
+ ZERO
+
+ /* unused Context Words */
+ ARITHMETIC ATTRIBUTE AUTO AUTOMATIC
+ AWAY_FROM_ZERO
+ BACKGROUND_COLOR
+ BELL
+ BINARY_ENCODING
+ BLINK
+ CAPACITY CENTER CLASSIFICATION CYCLE
+ DECIMAL_ENCODING
+ ENTRY_CONVENTION EOL EOS ERASE EXPANDS
+ FLOAT_BINARY
+ FLOAT_DECIMAL
+ FOREGROUND_COLOR FOREVER FULL
+ HIGHLIGHT
+ HIGH_ORDER_LEFT
+ HIGH_ORDER_RIGHT
+ IGNORING IMPLEMENTS INITIALIZED INTERMEDIATE
+ LC_ALL_kw
+ LC_COLLATE_kw
+ LC_CTYPE_kw
+ LC_MESSAGES_kw
+ LC_MONETARY_kw
+ LC_NUMERIC_kw
+ LC_TIME_kw
+ LOWLIGHT
+ NEAREST_AWAY_FROM_ZERO
+ NEAREST_EVEN NEAREST_TOWARD_ZERO
+ NONE NORMAL NUMBERS
+ PREFIXED PREVIOUS PROHIBITED RELATION REQUIRED
+ REVERSE_VIDEO ROUNDING
+ SECONDS SECURE SHORT SIGNED
+ STANDARD_BINARY
+ STANDARD_DECIMAL
+ STATEMENT STEP STRUCTURE
+ TOWARD_GREATER
+ TOWARD_LESSER
+ TRUNCATION
+ UCS_4
+ UNDERLINE UNSIGNED
+ UTF_16
+ UTF_8
+
+%left CLASS_NAME NAME NAME88
+%left ADDRESS
+%left END_ACCEPT END_ADD END_CALL END_COMPUTE
+ END_DELETE END_DISPLAY END_DIVIDE
+ END_EVALUATE END_MULTIPLY END_PERFORM
+ END_READ END_RETURN END_REWRITE
+ END_SEARCH END_START END_STRING END_SUBTRACT
+ END_UNSTRING END_WRITE
+ error
+ END_IF
+
+%left THRU
+%left OR
+%left AND
+%right NOT
+%left '<' '>' '=' NE LE GE
+%left '-' '+'
+%left '*' '/'
+%right POW
+%precedence NEG
+
+
+
+%{
+ static cbl_field_type_t
+ set_operand_type(const cbl_refer_t& refer) {
+ if( refer.field == NULL ) return FldInvalid;
+ return refer.addr_of? FldPointer : refer.field->type;
+ }
+
+ static bool
+ refer_pointer( const cbl_num_result_t& elem ) {
+ assert(elem.refer.field);
+ return elem.refer.is_pointer();
+ }
+ static bool
+ valid_set_targets( const tgt_list_t& tgts, bool want_pointers ) {
+ bool ok = true;
+ // The only targets that can have addr_of are BASED or in Linkage Section.
+ auto baddie = std::find_if( tgts.targets.begin(),
+ tgts.targets.end(),
+ []( const auto& num_result ) {
+ if( num_result.refer.addr_of ) {
+ auto f = num_result.refer.field;
+ if( ! (f->has_attr(based_e) || f->has_attr(linkage_e)) ) {
+ return true;
+ }
+ }
+ return false;
+ } );
+ if( baddie != tgts.targets.end() ) {
+ auto loc = symbol_field_location(field_index(baddie->refer.field));
+ error_msg(loc,"target %s must be BASED or in LINKAGE SECTION",
+ baddie->refer.name() );
+ return false;
+ }
+
+ for( const auto& num_result : tgts.targets ) {
+ auto loc = symbol_field_location(field_index(num_result.refer.field));
+ if( refer_pointer(num_result) ) {
+ if( !want_pointers ) {
+ ok = false;
+ error_msg( loc, "%s is a pointer", num_result.refer.name() );
+ }
+ } else {
+ if( want_pointers ) {
+ ok = false;
+ error_msg( loc, "%s is not a pointer", num_result.refer.name() );
+ }
+ }
+ }
+ return ok;
+ }
+
+ static void initialize_allocated( cbl_refer_t input );
+ static void
+ initialize_statement( std::list<cbl_num_result_t>& tgts,
+ bool with_filler,
+ data_category_t category,
+ const category_map_t& replacement = category_map_t());
+
+
+ unsigned char cbl_alphabet_t::nul_string[2] = ""; // 2 NULs lets us use one
+ unsigned char *nul_string() { return cbl_alphabet_t::nul_string; }
+
+ static inline literal_t literal_of( char *s ) {
+ literal_t output;
+ return output.set( strlen(s), s, "" );
+ }
+ static inline char * string_of( const literal_t& lit ) {
+ return strlen(lit.data) == lit.len? lit.data : NULL;
+ }
+
+ static inline char * string_of( _Float128 cce ) {
+ static const char empty[] = "", format[] = "%.32E";
+ char output[64];
+ int len = strfromf128 (output, sizeof(output), format, cce);
+ if( sizeof(output) < size_t(len) ) {
+ dbgmsg("string_of: value requires %d digits (of %zu)",
+ len, sizeof(output));
+ return xstrdup(empty);
+ }
+
+ char decimal = symbol_decimal_point();
+ std::replace(output, output + strlen(output), '.', decimal);
+ return xstrdup(output);
+ }
+
+ cbl_field_t *
+ new_literal( const literal_t& lit, enum cbl_field_attr_t attr );
+
+ static YYLTYPE first_line_of( YYLTYPE loc );
+%}
+
+%locations
+%token-table
+%define parse.error verbose // custom
+%expect 6
+%require "3.5.1" // 3.8.2 also works, but not 3.8.0
+%%
+
+top: programs
+ {
+ if( ! goodnight_gracie() ) {
+ YYABORT;
+ }
+ if( nparse_error > 0 ) YYABORT;
+ }
+ | programs end_program
+ {
+ if( nparse_error > 0 ) YYABORT;
+ }
+ ;
+programs: program
+ | programs end_program program
+ ;
+program: id_div options_para env_div data_div
+ {
+ if( ! data_division_ready() ) {
+ mode_syntax_only(procedure_div_e);
+ }
+ current_division = procedure_div_e;
+ }
+ procedure_div
+ {
+ if( yydebug ) labels_dump();
+ }
+ ;
+
+id_div: cdf_words IDENTIFICATION_DIV '.' program_id
+ | cdf_words program_id
+ | cdf_words IDENTIFICATION_DIV '.' function_id
+ ;
+
+cdf_words: %empty
+ | cobol_words
+ ;
+cobol_words: cobol_words1
+ | cobol_words cobol_words1
+ ;
+cobol_words1: COBOL_WORDS EQUATE NAME[keyword] WITH NAME[name] {
+ if( ! tokens.equate(@keyword, $keyword, $name) ) { YYERROR; }
+ }
+ | COBOL_WORDS UNDEFINE NAME[keyword] {
+ if( ! tokens.undefine(@keyword, $keyword) ) { YYERROR; }
+ }
+ | COBOL_WORDS SUBSTITUTE NAME[keyword] BY NAME[name] {
+ if( ! tokens.substitute(@keyword, $keyword, $name) ) { YYERROR; }
+ }
+ | COBOL_WORDS RESERVE NAME[name] {
+ if( ! tokens.reserve(@name, $name) ) { YYERROR; }
+ }
+ ;
+
+program_id: PROGRAM_ID dot namestr[name] program_as program_attrs[attr] dot
+ {
+ internal_ebcdic_lock();
+ current_division = identification_div_e;
+ parser_division( identification_div_e, NULL, 0, NULL );
+ location_set(@1);
+ int main_error=0;
+ const char *name = string_of($name);
+ parser_enter_program( name, false, &main_error );
+ if( main_error ) {
+ error_msg(@name, "PROGRAM-ID 'main' is invalid with -main option");
+ YYERROR;
+ }
+
+ if( symbols_begin() == symbols_end() ) {
+ symbol_table_init();
+ }
+ if( !current.new_program(@name, LblProgram, name,
+ $program_as.data,
+ $attr.common, $attr.initial) ) {
+ auto L = symbol_program(current_program_index(), name);
+ assert(L);
+ error_msg(@name, "PROGRAM-ID %s already defined on line %d",
+ name, L->line);
+ YYERROR;
+ }
+ if( nparse_error > 0 ) YYABORT;
+ }
+ ;
+dot: %empty
+ | '.'
+ ;
+program_as: %empty { $$ = (literal_t){}; }
+ | AS LITERAL { $$ = $2; }
+ ;
+
+function_id: FUNCTION '.' NAME program_as program_attrs[attr] '.'
+ {
+ internal_ebcdic_lock();
+ current_division = identification_div_e;
+ parser_division( identification_div_e, NULL, 0, NULL );
+ location_set(@1);
+
+ int main_error = 0;
+ parser_enter_program( $NAME, true, &main_error );
+ if( main_error ) {
+ error_msg(@NAME, "FUNCTION-ID 'main' is invalid with -main option");
+ YYERROR;
+ }
+ if( symbols_begin() == symbols_end() ) {
+ symbol_table_init();
+ }
+ if( !current.new_program(@NAME, LblFunction, $NAME,
+ $program_as.data,
+ $attr.common, $attr.initial) ) {
+ auto L = symbol_program(current_program_index(), $NAME);
+ assert(L);
+ error_msg(@NAME, "FUNCTION %s already defined on line %d",
+ $NAME, L->line);
+ YYERROR;
+ }
+ if( keyword_tok($NAME, true) ) {
+ error_msg(@NAME, "FUNCTION %s is an intrinsic function",
+ $NAME);
+ YYERROR;
+ }
+ current.udf_add(current_program_index());
+ if( nparse_error > 0 ) YYABORT;
+ }
+ | FUNCTION '.' NAME program_as is PROTOTYPE '.'
+ {
+ cbl_unimplemented("FUNCTION PROTOTYPE");
+ }
+ ;
+
+options_para: %empty
+ | OPTIONS opt_clauses '.'
+ | OPTIONS
+ ;
+
+opt_clauses: opt_clause
+ | opt_clauses opt_clause
+ ;
+opt_clause: opt_arith
+ | opt_round
+ | opt_entry
+ | opt_binary
+ | opt_decimal {
+ cbl_unimplementedw("type FLOAT-DECIMAL was ignored");
+ }
+ | opt_intermediate
+ | opt_init
+ ;
+
+opt_arith: ARITHMETIC is opt_arith_type {
+ if( ! current.option($opt_arith_type) ) {
+ error_msg(@3, "unable to set ARITHMETIC option");
+ }
+ }
+ ;
+opt_arith_type: NATIVE { $$ = cbl_options_t::native_e; }
+ | STANDARD { $$ = cbl_options_t::standard_e; }
+ | STANDARD_BINARY { $$ = cbl_options_t::standard_binary_e; }
+ | STANDARD_DECIMAL { $$ = cbl_options_t::standard_decimal_e; }
+ ;
+opt_round: DEFAULT ROUNDED mode is rounded_type[type] {
+ current_rounded_mode($type);
+ }
+ ;
+opt_entry: ENTRY_CONVENTION is COBOL {
+ yywarn("ENTRY-CONVENTION IS COBOL, check");
+ }
+ ;
+opt_binary: FLOAT_BINARY default_kw is HIGH_ORDER_LEFT
+ {
+ cbl_unimplementedw("HIGH-ORDER-LEFT was ignored");
+ if( ! current.option_binary(cbl_options_t::high_order_left_e) ) {
+ error_msg(@3, "unable to set HIGH_ORDER_LEFT");
+ }
+ }
+ | FLOAT_BINARY default_kw is HIGH_ORDER_RIGHT[opt]
+ {
+ cbl_unimplementedw("HIGH-ORDER-RIGHT was ignored");
+ if( ! current.option_binary(cbl_options_t::high_order_right_e) ) {
+ error_msg(@opt, "unable to set HIGH-ORDER-RIGHT");
+ }
+ }
+ ;
+default_kw: %empty
+ | DEFAULT
+ ;
+opt_decimal: FLOAT_DECIMAL default_kw is HIGH_ORDER_LEFT[opt]
+ {
+ cbl_unimplementedw("HIGH-ORDER-LEFT was ignored");
+ if( ! current.option_decimal(cbl_options_t::high_order_left_e) ) {
+ error_msg(@opt, "unable to set HIGH-ORDER-LEFT");
+ }
+ }
+ | FLOAT_DECIMAL default_kw is HIGH_ORDER_RIGHT[opt]
+ {
+ cbl_unimplementedw("HIGH-ORDER-RIGHT was ignored");
+ if( ! current.option_decimal(cbl_options_t::high_order_right_e) ) {
+ error_msg(@opt, "unable to set HIGH-ORDER-RIGHT");
+ }
+ }
+ | FLOAT_DECIMAL default_kw is BINARY_ENCODING[opt]
+ {
+ cbl_unimplementedw("BINARY-ENCODING was ignored");
+ if( ! current.option(cbl_options_t::binary_encoding_e) ) {
+ error_msg(@opt, "unable to set BINARY-ENCODING option");
+ }
+ }
+ | FLOAT_DECIMAL default_kw is DECIMAL_ENCODING[opt]
+ {
+ cbl_unimplementedw("DECIMAL-ENCODING was ignored");
+ if( ! current.option(cbl_options_t::decimal_encoding_e) ) {
+ error_msg(@opt, "unable to set DECIMAL-ENCODING option");
+ }
+ }
+ ;
+opt_intermediate:
+ INTERMEDIATE ROUNDING is round_between[round] {
+ current.intermediate_round($round);
+ }
+ ;
+
+opt_init: INITIALIZE opt_init_sects[sect] opt_section to opt_init_value[init]
+ {
+ if( $sect.local ) {
+ current.initial_value(local_sect_e, $init);
+ }
+ if( $sect.working ) {
+ current.initial_value(working_sect_e, $init);
+ }
+ }
+ ;
+opt_section: %empty
+ | SECTION
+ ;
+opt_init_sects: ALL { $$.local = $$.working = true; }
+ | opt_init_sect {
+ $$.local = $$.working = false;
+ switch($1) {
+ case local_sect_e:
+ $$.local = true; break;
+ case working_sect_e:
+ $$.working = true; break;
+ default: gcc_unreachable();
+ }
+ }
+ | opt_init_sects opt_init_sect {
+ $$ = $1;
+ switch($2) {
+ case local_sect_e:
+ if( $$.local ) {
+ error_msg(@2, "LOCAL-STORAGE repeated");
+ }
+ $$.local = true; break;
+ case working_sect_e:
+ if( $$.working ) {
+ error_msg(@2, "WORKING-STORAGE repeated");
+ }
+ $$.working = true; break;
+ default: gcc_unreachable();
+ }
+ }
+ ;
+opt_init_sect: LOCAL_STORAGE { $$ = local_sect_e; }
+ | SCREEN { cbl_unimplemented("SCREEN SECTION"); }
+ | WORKING_STORAGE { $$ = working_sect_e; }
+ ;
+opt_init_value: BINARY ZERO { $$ = constant_index(NULLS); }
+ | HIGH_VALUES { $$ = constant_index(HIGH_VALUES); }
+ | LITERAL
+ {
+ if( $1.prefix[0] != 'X' ) {
+ error_msg(@1, "hexadecimal literal required");
+ }
+ if( $1.len != 1 ) {
+ error_msg(@1, "1-byte hexadecimal literal required");
+ }
+ char ach[16];
+ sprintf(ach, "%d", (int)($1.data[0]));
+ //auto f = new_literal($1.data);
+ auto f = new_literal(ach);
+ f = field_add(@1, f);
+ $$ = field_index(f);
+ }
+ | LOW_VALUES { $$ = constant_index(LOW_VALUES); }
+ | SPACES { $$ = constant_index(SPACES); }
+ ;
+
+namestr: ctx_name {
+ $$ = literal_of($1);
+ if( ! string_of($$) ) {
+ error_msg(@1, "'%s' has embedded NUL", $$.data);
+ YYERROR;
+ }
+ }
+ | LITERAL {
+ if( $$.prefix[0] != '\0' ) {
+ error_msg(@1, "literal cannot use %s prefix in this context",
+ $$.prefix);
+ YYERROR;
+ }
+ if( !is_cobol_word($$.data) ) {
+ error_msg(@1, "literal '%s' must be a COBOL or C identifier",
+ $$.data);
+ }
+ }
+ ;
+
+program_attrs: %empty { $$.common = $$.initial = $$.recursive = false; }
+ | is comminits program_kw { $$ = $2; }
+ ;
+comminits: comminit
+ | comminits comminit {
+ if( ($1.initial && $2.recursive) ||
+ ($2.initial && $1.recursive) ) {
+ auto loc = $1.initial? @1 : @2;
+ error_msg(loc, "INITIAL cannot be used with RECURSIVE");
+ }
+ $$ = $1;
+ if( $2.common ) {
+ if( $1.common ) {
+ error_msg(@2, "COMMON repeated");
+ }
+ $$.common = $2.common;
+ }
+ if( $2.initial ) {
+ if( $1.initial ) {
+ error_msg(@2, "INITIAL repeated");
+ }
+ $$.initial = $2.initial;
+ }
+ if( $2.recursive ) {
+ if( $1.recursive ) {
+ error_msg(@2, "RECURSIVE repeated");
+ }
+ $$.recursive = $2.recursive;
+ }
+ }
+ ;
+comminit: COMMON {
+ if( program_level() == 0 ) { // PROGRAM-ID being parsed not added yet.
+ error_msg(@1, "COMMON may be used only in a contained program");
+ }
+ $$.common = true;
+ $$.initial = $$.recursive = false;
+ }
+ | INITIAL_kw { $$.initial = true; $$.common = $$.recursive = false;}
+ | RECURSIVE {
+ $$.recursive = true; $$.common = $$.initial = false;
+ }
+ ;
+
+
+env_div: %empty { current_division = environment_div_e; }
+ | ENVIRONMENT_DIV '.' { current_division = environment_div_e; }
+ | ENVIRONMENT_DIV '.' {
+ current_division = environment_div_e;
+ } env_sections
+ ;
+
+env_sections: env_section
+ | env_sections env_section
+ ;
+
+env_section: INPUT_OUTPUT_SECT '.'
+ | INPUT_OUTPUT_SECT '.' io_sections
+ | INPUT_OUTPUT_SECT '.' selects { /* IBM requires FILE CONTROL. */ }
+ | CONFIGURATION_SECT '.'
+ | CONFIGURATION_SECT '.' config_paragraphs
+ | cdf
+ ;
+
+io_sections: io_section
+ | io_sections io_section
+ ;
+
+io_section: FILE_CONTROL '.'
+ | FILE_CONTROL '.' selects
+ | IO_CONTROL '.'
+ | IO_CONTROL '.' io_control_clauses '.'
+ ;
+
+io_control_clauses: io_control_clause
+ | io_control_clauses io_control_clause
+ ;
+io_control_clause:
+ SAME record area for_kw filenames
+ {
+ symbol_file_same_record_area( $filenames->files );
+ }
+ | SAME smerge area for_kw filenames
+ {
+ symbol_file_same_record_area( $filenames->files );
+ }
+ | APPLY COMMIT on field_list
+ {
+ cbl_unimplementedw("I-O-CONTROL APPLY COMMIT");
+ }
+ ;
+area: %empty
+ | AREA
+ ;
+smerge: SORT
+ | SORT_MERGE
+ ;
+
+selects: select
+ | selects select
+ ;
+
+select: SELECT optional NAME[name] select_clauses[clauses] '.'
+ {
+ assert($clauses.file);
+ cbl_file_t *file = $clauses.file;
+
+ file->optional = $optional;
+ file->line = yylineno;
+ if( !namcpy(@clauses, file->name, $name) ) YYERROR;
+
+ if( ! ($clauses.clauses & assign_clause_e) ) {
+ error_msg(@name, "ASSIGN clause missing for %s", file->name);
+ }
+
+ // key check
+ switch(file->org) {
+ case file_indexed_e:
+ // indexed file cannot have relative key
+ if( ($clauses.clauses & relative_key_clause_e) != 0) {
+ assert(file->keys);
+ auto ikey = file->nkey - 1;
+ assert(file->keys[ikey].fields);
+ auto f = cbl_field_of(symbol_at(file->keys[ikey].fields[0]));
+ error_msg(@name, "INDEXED file %s cannot have RELATIVE key %s",
+ file->name, f->name);
+ break; // because next message would be redundant
+ }
+ if( ($clauses.clauses & record_key_clause_e) == 0 ) {
+ error_msg(@name, "INDEXED file %s has no RECORD KEY",
+ file->name);
+ }
+ break;
+ case file_disorganized_e:
+ file->org = file_sequential_e;
+ __attribute__((fallthrough));
+ default:
+ if( ($clauses.clauses & record_key_clause_e) != 0 ) {
+ assert(file->keys);
+ auto ikey = file->nkey - 1;
+ assert(file->keys[ikey].fields);
+ auto f = cbl_field_of(symbol_at(file->keys[ikey].fields[0]));
+ error_msg(@name, "%s file %s cannot have RECORD key %s",
+ file_org_str(file->org), file->name, f->name);
+ }
+ break;
+ }
+
+ // access check
+ switch(file->access) {
+ case file_access_rnd_e:
+ case file_access_dyn_e:
+ if( is_sequential(file) ) {
+ error_msg(@name, "%s file %s cannot have ACCESS %s",
+ file_org_str(file->org), file->name,
+ file_access_str(file->access));
+ }
+ break;
+ default:
+ break;
+ }
+
+ // install file, and set record area's name
+ if( (file = file_add(@name, file)) == NULL ) YYERROR;
+ auto ifile = symbol_index(symbol_elem_of(file));
+ // update keys
+ for( auto p = file->keys;
+ p && p < file->keys + file->nkey; p++ )
+ {
+ if( p->name[0] == '\0' ) continue;
+ auto f = symbol_field(PROGRAM, 0, p->name);
+ cbl_field_of(f)->parent = ifile;
+ size_t isym = field_index(cbl_field_of(f));
+ update_symbol_map(symbol_at(isym));
+ }
+ }
+ | SELECT optional NAME[name] '.'
+ {
+ cbl_file_t file = protofile;
+
+ file.optional = $optional;
+ file.line = yylineno;
+ if( !namcpy(@name, file.name, $name) ) YYERROR;
+
+ if( file_add(@name, &file) == NULL ) YYERROR;
+ }
+ ;
+selected_name: external scalar { $$ = $2; }
+ | external LITERAL[name]
+ {
+ const char *name = string_of($name);
+ if( ! name ) {
+ error_msg(@name, "'%s' has embedded NUL", $name.data);
+ YYERROR;
+ }
+ uint32_t len = $name.len;
+ cbl_field_t field = {
+ 0, FldLiteralA, FldInvalid, quoted_e | constant_e,
+ 0, 0, 0, nonarray, 0, "", 0, cbl_field_t::linkage_t(),
+ {len,len,0,0, $name.data, NULL, {NULL}, {NULL}}, NULL };
+ field.attr |= literal_attr($name.prefix);
+ $$ = new cbl_refer_t( field_add(@name, &field) );
+ }
+ ;
+external: %empty /* GnuCOBOL uses EXTERNAL to control name resolution. */
+ | EXTERNAL
+ ;
+
+select_clauses: select_clause { $$.clauses = $1.clause; $$.file = $1.file; }
+ | select_clauses[total] select_clause[part]
+ {
+ $$ = $total;
+ // The default organization is sequential.
+ if( ($$.clauses & organization_clause_e) == 0 ) {
+ $$.file->org = file_sequential_e;
+ }
+ const bool exists = ($$.clauses & $part.clause);
+ $$.clauses |= $part.clause;
+
+ switch($part.clause) {
+ case alt_key_clause_e:
+ assert( $part.file->nkey == 1 );
+ if( $$.file->nkey++ == 0 ) {
+ // If no key yet exists, create room for it and the
+ // present alternate.
+ assert($$.file->keys == &no_key);
+ $$.file->keys = new cbl_file_key_t[++$$.file->nkey];
+ }
+ {
+ auto keys = new cbl_file_key_t[$$.file->nkey];
+ auto alt = std::copy($$.file->keys,
+ $$.file->keys +
+ $$.file->nkey - 1,
+ keys);
+ // Assign the alternate key to the last element,
+ // and update the pointer.
+ *alt = $part.file->keys[0];
+ delete[] $$.file->keys;
+ $$.file->keys = keys;
+ }
+ break;
+ case assign_clause_e:
+ if( exists ) {
+ error_msg(@part, "clause is repeated");
+ YYERROR;
+ }
+ $$.file->filename = $part.file->filename;
+ break;
+ case collating_clause_e:
+ if( exists ) {
+ error_msg(@part, "clause is repeated");
+ YYERROR;
+ }
+ break;
+ case lock_mode_clause_e:
+ if( exists ) {
+ error_msg(@part, "clause is repeated");
+ YYERROR;
+ }
+ $$.file->lock = $part.file->lock;
+ break;
+ case organization_clause_e:
+ if( exists ) {
+ error_msg(@part, "clause is repeated");
+ YYERROR;
+ }
+ $$.file->org = $part.file->org;
+ break;
+ case padding_clause_e:
+ case reserve_clause_e:
+ case sharing_clause_e:
+ case record_delim_clause_e:
+ if( exists ) {
+ error_msg(@part, "clause is repeated");
+ YYERROR;
+ }
+ break;
+ case access_clause_e:
+ if( exists ) {
+ error_msg(@part, "clause is repeated");
+ YYERROR;
+ }
+ $$.file->access = $part.file->access;
+ break;
+ case relative_key_clause_e:
+ if( exists ) {
+ error_msg(@part, "clause is repeated");
+ YYERROR;
+ }
+ if( $$.clauses & record_key_clause_e ) {
+ error_msg(@part, "FILE %s is INDEXED, has no RELATIVE key",
+ $$.file->name);
+ YYERROR;
+ }
+ // fall thru
+ case record_key_clause_e:
+ if( exists ) {
+ error_msg(@part, "clause is repeated");
+ YYERROR;
+ }
+ if( ($$.clauses & relative_key_clause_e) &&
+ $part.clause == record_key_clause_e ) {
+ error_msg(@part, "FILE %s is RELATIVE, has no RECORD key",
+ $$.file->name);
+ YYERROR;
+ }
+ if( $$.file->nkey == 0 ) {
+ $$.file->nkey = $part.file->nkey;
+ $$.file->keys = $part.file->keys;
+ } else {
+ $$.file->keys[0] = $part.file->keys[0];
+ }
+ break;
+ /* case password_clause_e: */
+ case file_status_clause_e:
+ if( exists ) {
+ error_msg(@part, "clause is repeated");
+ YYERROR;
+ }
+ $$.file->user_status = $part.file->user_status;
+ $$.file->vsam_status = $part.file->vsam_status;
+ break;
+ }
+ if( $$.file->lock.locked() ) {
+ if( $$.file->org == file_sequential_e &&
+ $$.file->lock.multiple ) {
+ error_msg(@part, "SEQUENTIAL file cannot lock MULTIPLE records");
+ }
+ }
+
+ delete $part.file;
+ }
+ ;
+
+select_clause: access_clause
+ | alt_key_clause[alts]
+ | assign_clause[alts]
+ | collate_clause
+ | /* file */ status_clause
+ | lock_mode_clause
+ | org_clause
+ | padding_clause
+ | record_delim_clause
+ | record_key_clause
+ | relative_key_clause
+ | reserve_clause
+ | sharing_clause
+ ;
+
+access_clause: ACCESS mode is access_mode[acc]
+ {
+ $$.clause = access_clause_e;
+ $$.file = new cbl_file_t(protofile);
+ $$.file->access = static_cast<cbl_file_access_t>($acc);
+ }
+ ;
+access_mode: RANDOM { $$ = file_access_rnd_e; }
+ | DYNAMIC { $$ = file_access_dyn_e; }
+ | SEQUENTIAL { $$ = file_access_seq_e; }
+ ;
+
+alt_key_clause: ALTERNATE record key is name key_source[fields] unique_key
+ {
+ $$.clause = alt_key_clause_e;
+ $$.file = new cbl_file_t(protofile);
+ $$.file->nkey = 1;
+ if( $fields == NULL ) {
+ $$.file->keys = new cbl_file_key_t(field_index($name),
+ $unique_key);
+ } else {
+ $name->type = FldLiteralA;
+ $name->data.initial = $name->name;
+ $name->attr |= record_key_e;
+ auto& name = *$name;
+ $$.file->keys = new cbl_file_key_t(name.name,
+ $fields->fields,
+ $unique_key);
+ }
+ }
+ ;
+key_source: %empty { $$ = NULL; }
+ | SOURCE is key_sources[fields] { $$ = $fields; }
+ ;
+key_sources: name { $$ = new field_list_t($1); }
+ | key_sources name { $$ = $1; $$->fields.push_back($2); }
+ ;
+unique_key: %empty { $$ = true; }
+ | with DUPLICATES { $$ = false; }
+ ;
+
+assign_clause: ASSIGN to selected_name[selected] {
+ $$.clause = assign_clause_e;
+ $$.file = new cbl_file_t(protofile);
+ $$.file->filename = field_index($selected->field);
+ }
+ | ASSIGN to device_name USING name {
+ $$.clause = assign_clause_e;
+ cbl_unimplemented("ASSIGN TO DEVICE");
+ YYERROR;
+ }
+ | ASSIGN to device_name {
+ $$.clause = assign_clause_e;
+ cbl_unimplemented("ASSIGN TO DEVICE");
+ YYERROR;
+ }
+ | ASSIGN USING name {
+ $$.clause = assign_clause_e;
+ $$.file = new cbl_file_t(protofile);
+ $$.file->filename = field_index($name);
+ }
+ ;
+
+collate_clause: collate_claus1 {
+ $$.clause = collating_clause_e;
+ $$.file = new cbl_file_t(protofile);
+ }
+ ;
+collate_claus1: collating SEQUENCE NAME /* SEQUENCE swallows IS/FOR */
+ | collating SEQUENCE ALPHANUMERIC is NAME
+ | collating SEQUENCE NATIONAL is NAME
+ ;
+
+status_clause: file STATUS is name[user]
+ {
+ $$.clause = file_status_clause_e;
+ $$.file = new cbl_file_t(protofile);
+ $$.file->user_status = field_index($user);
+ }
+ | file STATUS is name[user] name[vsam]
+ {
+ $$.clause = file_status_clause_e;
+ $$.file = new cbl_file_t(protofile);
+ $$.file->user_status = field_index($user);
+ $$.file->vsam_status = field_index($vsam);
+ }
+ ;
+
+lock_mode_clause: // ISO only
+ LOCK mode is lock_mode lock_how[how]
+ {
+ $$.clause = lock_mode_clause_e;
+ $$.file = new cbl_file_t(protofile);
+ $$.file->lock.multiple = $how > 0;
+ if( ! $$.file->lock.mode_set($lock_mode) ) {
+ error_msg(@lock_mode, "logic error: %s is not a file lock mode",
+ keyword_str($lock_mode) );
+ }
+ }
+lock_how: %empty { $$ = 0; }
+ | with LOCK_ON multiple records { $$ = $multiple; }
+ ;
+lock_mode: MANUAL { $$ = MANUAL; }
+ | RECORD { $$ = RECORD; }
+ | AUTOMATIC { $$ = AUTOMATIC; }
+ ;
+multiple: %empty { $$ = 0; }
+ | MULTIPLE { $$ = MULTIPLE; }
+ ;
+records: RECORD
+ | RECORDS
+ ;
+
+org_clause: org_clause1[org]
+ {
+ $$.clause = organization_clause_e;
+ $$.file = new cbl_file_t(protofile);
+ $$.file->org = static_cast<cbl_file_org_t>($org);
+ }
+ ;
+org_is: %empty
+ | ORGANIZATION is
+ ;
+ // file_sequential is the proper default
+org_clause1: org_is SEQUENTIAL { $$ = file_sequential_e; }
+ | org_is LINE SEQUENTIAL { $$ = file_line_sequential_e; }
+ | org_is RELATIVE { $$ = file_relative_e; }
+ | org_is INDEXED { $$ = file_indexed_e; }
+ ;
+
+ /*
+ * "The PADDING CHARACTER clause is syntax checked, but has no
+ * effect on the execution of the program."
+ */
+padding_clause: PADDING character is padding_char
+ {
+ $$.clause = padding_clause_e;
+ $$.file = new cbl_file_t(protofile);
+ }
+ ;
+character: %empty
+ | CHARACTER
+ ;
+padding_char: NAME
+ | LITERAL
+ | NUMSTR
+ ;
+
+record_delim_clause: RECORD DELIMITER is STANDARD_ALPHABET
+ {
+ $$.clause = record_delim_clause_e;
+ $$.file = new cbl_file_t(protofile);
+ }
+ ;
+
+record_key_clause: RECORD key is name key_source[fields]
+ {
+ $$.clause = record_key_clause_e;
+ $$.file = new cbl_file_t(protofile);
+ $$.file->nkey = 1;
+ if( $fields == NULL ) {
+ $$.file->keys = new cbl_file_key_t(field_index($name));
+ } else { // "special" not-literal literal: a key name
+ $name->type = FldLiteralA;
+ $name->data.initial = $name->name;
+ $name->attr |= record_key_e;
+ $$.file->keys = new cbl_file_key_t($name->name,
+ $fields->fields, true);
+ }
+ }
+ ;
+
+relative_key_clause: /* RELATIVE */ KEY is name
+ { // lexer returns KEY for RELATIVE ... NAME
+ $$.clause = relative_key_clause_e;
+ $$.file = new cbl_file_t(protofile);
+ $$.file->nkey = 1;
+ $$.file->keys = new cbl_file_key_t(field_index($name));
+ }
+ ;
+
+reserve_clause: RESERVE NUMSTR reserve_area
+ {
+ $$.clause = reserve_clause_e;
+ $$.file = new cbl_file_t(protofile);
+ }
+ ;
+reserve_area: %empty
+ | AREA
+ | AREAS
+ ;
+
+sharing_clause: SHARING with sharing_who
+ {
+ $$.clause = sharing_clause_e;
+ $$.file = new cbl_file_t(protofile);
+ }
+ ;
+sharing_who: ALL other
+ | NO other
+ | READ ONLY
+ ;
+other: %empty
+ | OTHER
+ ;
+
+config_paragraphs: config_paragraph
+ | config_paragraphs config_paragraph
+ ;
+
+config_paragraph:
+ SPECIAL_NAMES '.'
+ | SPECIAL_NAMES '.' specials '.'
+ | SOURCE_COMPUTER '.' NAME with_debug '.'
+ | OBJECT_COMPUTER '.' NAME collating_sequence[name] '.'
+ {
+ if( $name ) {
+ if( !current.collating_sequence($name) ) {
+ error_msg(@name, "collating sequence already defined as '%s'",
+ current.collating_sequence());
+ YYERROR;
+ }
+ }
+ }
+ | REPOSITORY '.'
+ | REPOSITORY '.' repo_members '.'
+ ;
+
+repo_members: repo_member
+ | repo_members repo_member
+ ;
+repo_member: repo_class
+ { cbl_unimplemented("CLASS"); }
+ | repo_interface
+ { cbl_unimplemented("INTERFACE"); }
+ | repo_func
+ | repo_program
+ | repo_property
+ { cbl_unimplemented("PROPERTY"); }
+ ;
+
+repo_class: CLASS NAME repo_as repo_expands
+ ;
+repo_as: %empty { $$ = literal_t(); }
+ | AS LITERAL { $$ = $2; }
+ ;
+repo_expands: %empty
+ | EXPANDS NAME USING NAME
+ ;
+
+repo_interface: INTERFACE NAME repo_as repo_expands
+ ;
+
+repo_func: FUNCTION repo_func_names INTRINSIC
+ {
+ auto namelocs( name_queue.pop() );
+ for( const auto& nameloc : namelocs ) {
+ current.repository_add(nameloc.name);
+ }
+ }
+ | FUNCTION ALL INTRINSIC
+ {
+ current.repository_add_all();
+ }
+ | FUNCTION repo_func_names
+ ;
+repo_func_names:
+ repo_func_name
+ | repo_func_names repo_func_name
+ ;
+repo_func_name: NAME {
+ if( ! current.repository_add($NAME) ) { // add intrinsic by name
+ auto token = current.udf_in($NAME);
+ if( !token ) {
+ error_msg(@NAME, "%s is not defined here as a user-defined function",
+ $NAME);
+ current.udf_dump();
+ YYERROR;
+ }
+ auto e = symbol_function(0, $NAME);
+ assert(e);
+ current.repository_add(symbol_index(e)); // add UDF to repository
+ }
+ }
+ ;
+
+repo_program: PROGRAM_kw NAME repo_as
+ {
+ size_t parent = 0;
+ auto program = symbol_label( PROGRAM, LblProgram, 0, $NAME );
+ if( ! program ) {
+ if( $repo_as.empty() ) {
+ error_msg(@repo_as, "'%s' does not name an earlier program", $NAME);
+ YYERROR;
+ }
+ program = symbol_label( PROGRAM, LblProgram, 0,
+ "", $repo_as.data );
+ }
+ if( ! program ) {
+ error_msg(@repo_as, "'%s' does not name an earlier program",
+ $repo_as.data);
+ YYERROR;
+ }
+ assert(program);
+ parent = symbol_index(symbol_elem_of(program));
+ // Literal field whose parent is the the aliased program.
+ cbl_field_t prog = { .type = FldLiteralA,
+ .attr = quoted_e,
+ .parent = parent,
+ .data = {.initial = $repo_as.data} };
+ namcpy(@NAME, prog.name, $NAME);
+ if( ! prog.data.initial ) {
+ assert(program);
+ prog.data.initial = program->name;
+ }
+ auto e = symbol_field_add(PROGRAM, &prog);
+ symbol_field_location(symbol_index(e), @NAME);
+ }
+ ;
+
+repo_property: PROPERTY NAME repo_as
+ ;
+
+with_debug: %empty
+ | with DEBUGGING MODE {
+ if( ! set_debug(true) ) {
+ error_msg(@2, "DEBUGGING MODE valid only in fixed format");
+ }
+ }
+ ;
+
+collating_sequence: %empty { $$ = NULL; }
+ | PROGRAM_kw COLLATING SEQUENCE is NAME[name] { $$ = $name; }
+ | PROGRAM_kw SEQUENCE is NAME[name] { $$ = $name; }
+ | COLLATING SEQUENCE is NAME[name] { $$ = $name; }
+ | SEQUENCE is NAME[name] { $$ = $name; }
+ ;
+
+specials: special_names
+ ;
+special_names: special_name
+ | special_names special_name
+ ;
+
+special_name: dev_mnemonic
+ | ALPHABET NAME[name] is alphabet_name[abc]
+ {
+ if( !$abc ) YYERROR;
+ assert($abc); // already in symbol table
+ if( !namcpy(@name, $abc->name, $name) ) YYERROR;
+ if( yydebug ) $abc->dump();
+ }
+ | CLASS NAME is domains
+ {
+ struct cbl_field_t field = { 0,
+ FldClass, FldInvalid, 0, 0, 0, 0, nonarray, yylineno, "",
+ 0, cbl_field_t::linkage_t(),
+ { 0,0,0,0, NULL, NULL, { NULL }, { NULL } }, NULL };
+ if( !namcpy(@NAME, field.name, $2) ) YYERROR;
+
+ struct cbl_domain_t *domain =
+ new cbl_domain_t[ domains.size() + 1 ] ;
+
+ std::copy(domains.begin(), domains.end(), domain);
+
+ field.data.false_value = $domains;
+ field.data.domain = domain;
+ domains.clear();
+
+ if( field_add(@2, &field) == NULL ) {
+ dbgmsg("failed class");
+ YYERROR;
+ }
+ }
+ | CURRENCY sign is LITERAL[lit] with picture_sym
+ {
+ // The COBOL is "CURRENCY sign SYMBOL PICTURE symbol"
+ // In our processing, we flip the order, and refer to
+ // symbol_currency_add (symbol, sign-string). 'symbol' is the
+ // character in the PICTURE string, and 'sign' is the substitution
+ // that gets made in memory.
+ if( ! string_of($lit) ) {
+ error_msg(@lit, "'%s' has embedded NUL", $lit.data);
+ YYERROR;
+ }
+ symbol_currency_add( $picture_sym, $lit.data );
+ }
+ | DECIMAL_POINT is COMMA
+ {
+ symbol_decimal_point_set(',');
+ }
+ | LOCALE NAME is locale_spec
+ {
+ current.locale($NAME, $locale_spec);
+ cbl_unimplemented("LOCALE syntax");
+ }
+ ;
+ | upsi
+ | SYMBOLIC characters symbolic is_alphabet
+ {
+ cbl_unimplemented("SYMBOLIC syntax");
+ }
+ ;
+locale_spec: NAME { $$ = $1; }
+ | LITERAL { $$ = string_of($1); }
+
+ ;
+symbolic: NAME
+ | NUMSTR
+ ;
+is_alphabet: ARE NUMSTR
+ | is NUMSTR
+ ;
+
+dev_mnemonic: device_name is NAME
+ {
+ cbl_special_name_t special = { .token = $1.token,
+ .id = $1.id };
+ if( !namcpy(@NAME, special.name, $NAME) ) YYERROR;
+
+ const char *filename;
+
+ switch( special.id ) {
+ case STDIN_e: case SYSIN_e: case SYSIPT_e:
+ filename = "/dev/stdin";
+ break;
+ case STDOUT_e: case SYSOUT_e:
+ case SYSLIST_e: case SYSLST_e: case CONSOLE_e:
+ filename ="/dev/stdout";
+ break;
+ case STDERR_e: case SYSPUNCH_e: case SYSPCH_e: case SYSERR_e:
+ filename ="/dev/stderr";
+ break;
+ default:
+ filename ="/dev/null";
+ break;
+ }
+
+ special.filename = symbol_index(symbol_literalA(0, filename));
+
+ symbol_special_add(PROGRAM, &special);
+ }
+ | NAME[device] is NAME[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 },
+ };
+ char device[ 1 + strlen($device) ];
+ std::transform($device, $device + strlen($device) + 1,
+ device, toupper);
+ auto p = fujitsus.find(device);
+ if( p == fujitsus.end() ) {
+ error_msg(@device, "%s is not a device name");
+ }
+
+ cbl_special_name_t special = { .id = p->second };
+ if( !namcpy(@name, special.name, $name) ) YYERROR;
+
+ symbol_special_add(PROGRAM, &special);
+ }
+ ;
+
+device_name: SYSIN { $$.token = SYSIN; $$.id = SYSIN_e; }
+ | SYSIPT { $$.token = SYSIPT; $$.id = SYSIPT_e; }
+ | SYSOUT { $$.token = SYSOUT; $$.id = SYSOUT_e; }
+ | SYSLIST { $$.token = SYSLIST; $$.id = SYSLIST_e; }
+ | SYSLST { $$.token = SYSLST; $$.id = SYSLST_e; }
+ | SYSPUNCH { $$.token = SYSPUNCH; $$.id = SYSPUNCH_e; }
+ | SYSPCH { $$.token = SYSPCH; $$.id = SYSPCH_e; }
+ | CONSOLE { $$.token = CONSOLE; $$.id = CONSOLE_e; }
+ | C01 { $$.token = C01; $$.id = C01_e; }
+ | C02 { $$.token = C02; $$.id = C02_e; }
+ | C03 { $$.token = C03; $$.id = C03_e; }
+ | C04 { $$.token = C04; $$.id = C04_e; }
+ | C05 { $$.token = C05; $$.id = C05_e; }
+ | C06 { $$.token = C06; $$.id = C06_e; }
+ | C07 { $$.token = C07; $$.id = C07_e; }
+ | C08 { $$.token = C08; $$.id = C08_e; }
+ | C09 { $$.token = C09; $$.id = C09_e; }
+ | C10 { $$.token = C10; $$.id = C10_e; }
+ | C11 { $$.token = C11; $$.id = C11_e; }
+ | C12 { $$.token = C12; $$.id = C12_e; }
+ | CSP { $$.token = CSP; $$.id = CSP_e; }
+ | S01 { $$.token = S01; $$.id = S01_e; }
+ | S02 { $$.token = S02; $$.id = S02_e; }
+ | S03 { $$.token = S03; $$.id = S03_e; }
+ | S04 { $$.token = S04; $$.id = S04_e; }
+ | S05 { $$.token = S05; $$.id = S05_e; }
+ | AFP_5A { $$.token = AFP_5A; $$.id = AFP_5A_e; }
+ | STDIN { $$.token = STDIN; $$.id = STDIN_e; }
+ | STDOUT { $$.token = STDOUT; $$.id = STDOUT_e; }
+ | STDERR { $$.token = STDERR; $$.id = STDERR_e; }
+ ;
+
+alphabet_name: STANDARD_ALPHABET { $$ = alphabet_add(@1, ASCII_e); }
+ | NATIVE { $$ = alphabet_add(@1, EBCDIC_e); }
+ | EBCDIC { $$ = alphabet_add(@1, EBCDIC_e); }
+ | alphabet_seqs
+ {
+ $$ = cbl_alphabet_of(symbol_alphabet_add(PROGRAM, $1));
+ }
+ | error
+ {
+ error_msg(@1, "code-name-1 may be STANDARD-1, STANDARD-2, "
+ "NATIVE, OR EBCDIC");
+ $$ = NULL;
+ }
+ ;
+alphabet_seqs: alphabet_seq[seq]
+ /*
+ * The 1st element of the 1st sequence represents the
+ * low-value; its index becomes cbl_alphabet_t::low_index. The
+ * high_index belongs to the last element of the last sequence
+ * that is not an ALSO.
+ */
+ {
+ $$ = new cbl_alphabet_t(@seq, custom_encoding_e);
+
+ if( !$seq.low || $seq.also ) {
+ error_msg(@1, "syntax error at ALSO");
+ YYERROR;
+ }
+ $$->add_sequence(@seq, $seq.low);
+ size_t len = $seq.low == nul_string()? 1 : strlen((const char*)$seq.low);
+ assert(len > 0);
+ $$->add_interval(@seq, $seq.low[--len], $seq.high[0]);
+ $$->add_sequence(@seq, $seq.high);
+ }
+ | alphabet_seqs alphabet_seq[seq]
+ {
+ // ALSO x'00' is valid, but in that case the low pointer is NULL
+ if( !$seq.low ) {
+ $$->also(@seq, $seq.also);
+ } else {
+ $$->add_sequence(@seq, $seq.low);
+ size_t len = $seq.low == nul_string()? 1 : strlen((const char*)$seq.low);
+ assert(len > 0);
+ $$->add_interval(@seq, $seq.low[--len], $seq.high[0]);
+ $$->add_sequence(@seq, $seq.high);
+ }
+ }
+ ;
+alphabet_seq: alphabet_lit[low]
+ {
+ $$.also = 0;
+ if( $low.len == 1 && $low.data[0] == '\0' ) {
+ $$.high = $$.low = nul_string();
+ } else {
+ size_t size = 1 + $low.len;
+ $$.low = new unsigned char[size];
+ memcpy($$.low, $low.data, size);
+ $$.high = $$.low + size - 1;
+ assert($$.high[0] == '\0');
+ }
+ }
+ | alphabet_lit[low] THRU alphabet_lit[high]
+ {
+ $$.also = 0;
+ size_t size = 1 + $low.len;
+ if( $low.len == 1 && $low.data[0] == '\0' ) {
+ $$.low = nul_string();
+ } else {
+ $$.low = new unsigned char[size];
+ memcpy($$.low, $low.data, size);
+ }
+ assert($high.len > 0);
+ assert($high.data[0] != '\0');
+ size = 1 + $high.len;
+ $$.high = new unsigned char[size];
+ memcpy($$.high, $high.data, size);
+ }
+ | ALSO alphabet_etc { $$ = {}; $$.also = $2; }
+ ;
+alphabet_etc: alphabet_lit
+ {
+ if( $1.len > 1 ) {
+ error_msg(@1, "'%c' can be only a single letter", $1.data);
+ YYERROR;
+ }
+ $$ = (unsigned char)$1.data[0];
+ }
+ | spaces_etc {
+ // For figurative constants, pass the synmbol table index,
+ // marked with the high bit.
+ static const auto bits = sizeof($$) * 8 - 1;
+ $$ = 1;
+ $$ = $$ << bits;
+ $$ |= constant_index($1);
+ }
+ ;
+alphabet_lit: LITERAL { $$ = $1; assert($$.len > 0); }
+ | NUMSTR {
+ assert( $1.radix == decimal_e);
+ $$ = literal_of($1.string);
+ }
+ ;
+
+upsi: UPSI is NAME
+ {
+ assert($UPSI);
+ size_t parent = symbol_index(symbol_field(0,0,"UPSI-0"));
+ cbl_field_t *field = field_alloc(@NAME, FldSwitch, parent, $NAME);
+ if( !field ) YYERROR;
+ field->attr = constant_e;
+ field->data.initial = $UPSI;
+ }
+ | UPSI is NAME upsi_entry[entry]
+ {
+ assert($UPSI);
+ size_t parent = symbol_index(symbol_field(0,0,"UPSI-0"));
+ cbl_field_t *field = field_alloc(@NAME, FldSwitch, parent, $NAME);
+ if( !field ) YYERROR;
+ field->attr = constant_e;
+ field->data.initial = $UPSI;
+
+ assert('0' <= $UPSI[0] && $UPSI[0] < '8');
+ const uint32_t bitn = $UPSI[0] - '0', value = (1 << bitn);
+
+ if( $entry.on ) {
+ cbl_field_t *on = field_alloc(@NAME, FldSwitch, parent, $entry.on);
+ if( !on ) YYERROR;
+ on->data.upsi_mask = new cbl_upsi_mask_t(true, value);
+ }
+ if( $entry.off ) {
+ cbl_field_t *off = field_alloc(@NAME, FldSwitch, parent, $entry.off);
+ if( !off ) YYERROR;
+ off->data.upsi_mask = new cbl_upsi_mask_t(false, value);
+ }
+ }
+ | UPSI upsi_entry[entry]
+ {
+ size_t parent = symbol_index(symbol_field(0,0,"UPSI-0"));
+ assert('0' <= $UPSI[0] && $UPSI[0] < '8');
+ const uint32_t bitn = $UPSI[0] - '0', value = (1 << bitn);
+
+ if( $entry.on ) {
+ cbl_field_t *on = field_alloc($entry.loc, FldSwitch, parent, $entry.on);
+ if( !on ) YYERROR;
+ on->data.upsi_mask = new cbl_upsi_mask_t(true, value);
+ }
+ if( $entry.off ) {
+ cbl_field_t *off = field_alloc($entry.loc, FldSwitch, parent, $entry.off);
+ if( !off ) YYERROR;
+ off->data.upsi_mask = new cbl_upsi_mask_t(false, value);
+ }
+ }
+ ;
+upsi_entry: ON status is NAME
+ {
+ $$.loc = @NAME;
+ $$.on = $NAME;
+ $$.off = NULL;
+ }
+ | OFF status is NAME
+ {
+ $$.loc = @NAME;
+ $$.on = NULL;
+ $$.off = $NAME;
+ }
+ | OFF status is NAME[off] ON status is NAME[on]
+ {
+ $$.loc = @off;
+ $$.on = $on;
+ $$.off = $off;
+ }
+ | ON status is NAME[on] OFF status is NAME[off]
+ {
+ $$.loc = @on;
+ $$.on = $on;
+ $$.off = $off;
+ }
+ ;
+
+picture_sym: %empty { $$ = NULL; }
+ | PICTURE SYMBOL LITERAL[lit] {
+ if( ! string_of($lit) ) {
+ error_msg(@lit, "'%s' has embedded NUL", $lit.data);
+ YYERROR;
+ }
+ $$ = string_of($lit);
+ }
+ ;
+
+ /*
+ * The domains nonterminal ($domain) carries the FALSE value,
+ * if any. The domains variable (global std::list) carries the
+ * variable's DOMAIN, ending in a NULL. See the action for
+ * "CLASS NAME is domains".
+ */
+domains: domain
+ | domains domain { $$ = $1? $1 : $2; }
+ ;
+
+domain: all LITERAL[a]
+ {
+ if( ! string_of($a) ) {
+ gcc_location_set(@a);
+ yywarn("'%s' has embedded NUL", $a.data);
+ }
+ $$ = NULL;
+ cbl_domain_t domain(@a, $all, $a.len, $a.data);
+ domains.push_back(domain);
+ }
+ | all[a_all] LITERAL[a] THRU all[z_all] LITERAL[z]
+ {
+ if( ! string_of($a) ) {
+ yywarn("'%s' has embedded NUL", $a.data);
+ }
+ if( ! string_of($z) ) {
+ yywarn("'%s' has embedded NUL", $z.data);
+ }
+ $$ = NULL;
+ cbl_domain_elem_t first(@a, $a_all, $a.len, $a.data),
+ last(@z, $z_all, $z.len, $z.data);
+ domains.push_back(cbl_domain_t(first, last));
+ }
+ | all NUMSTR[n]
+ {
+ $$ = NULL;
+ cbl_domain_t dom(@n, $all, strlen($n.string), $n.string, true);
+ domains.push_back(dom);
+ }
+ | all[n_all] NUMSTR[n] THRU all[m_all] NUMSTR[m]
+ {
+ $$ = NULL;
+ cbl_domain_elem_t first(@n, $n_all, strlen($n.string), $n.string, true),
+ last(@m, $m_all, strlen($m.string), $m.string, true);
+ domains.push_back(cbl_domain_t(first, last));
+ }
+ | all reserved_value {
+ $$ = NULL;
+ if( $2 == NULLS ) YYERROR;
+ auto value = constant_of(constant_index($2))->data.initial;
+ struct cbl_domain_t domain( @2, $all, strlen(value), value );
+ domains.push_back(domain);
+ }
+ | all[a_all] reserved_value[a] THRU all[z_all] LITERAL[z] {
+ if( ! string_of($z) ) {
+ yywarn("'%s' has embedded NUL", $z.data);
+ }
+ $$ = NULL;
+ if( $a == NULLS ) YYERROR;
+ auto value = constant_of(constant_index($a))->data.initial;
+ cbl_domain_elem_t first(@a, $a_all, strlen(value), value),
+ last(@z, $z_all, $z.len, $z.data);
+ domains.push_back(cbl_domain_t(first, last));
+ }
+ | all[a_all] reserved_value[a] THRU all[z_all] NUMSTR[z] {
+ $$ = NULL;
+ if( $a == NULLS ) YYERROR;
+ auto value = constant_of(constant_index($a))->data.initial;
+ cbl_domain_elem_t first(@a, $a_all, strlen(value), value, true),
+ last(@z, $z_all, strlen($z.string), $z.string, true);
+ domains.push_back(cbl_domain_t(first, last));
+ }
+ | when_set_to FALSE_kw is LITERAL[value]
+ {
+ if( ! string_of($value) ) {
+ yywarn("'%s' has embedded NUL", $value.data);
+ }
+ char *dom = $value.data;
+ $$ = new cbl_domain_t(@value, false, $value.len, dom);
+ }
+ | when_set_to FALSE_kw is reserved_value
+ {
+ if( $4 == NULLS ) YYERROR;
+ auto value = constant_of(constant_index($4))->data.initial;
+ $$ = new cbl_domain_t(@4, false, strlen(value), value );
+ }
+ | when_set_to FALSE_kw is NUMSTR[n]
+ {
+ $$ = new cbl_domain_t(@n, false, strlen($n.string), $n.string, true);
+ }
+ ;
+when_set_to: %empty
+ | WHEN
+ | SET
+ | TO
+ | WHEN SET
+ | SET TO
+ | WHEN TO
+ | WHEN SET TO
+ ;
+
+data_div: %empty
+ | DATA_DIV
+ | DATA_DIV { current_division = data_div_e; } data_sections
+ {
+ current_data_section = not_data_datasect_e;
+ parser_division( data_div_e, NULL, 0, NULL );
+ }
+ ;
+
+data_sections: data_section
+ | data_sections data_section
+ ;
+
+data_section: FILE_SECT '.'
+ | FILE_SECT '.' {
+ current_data_section_set(@1, file_datasect_e);
+ } file_descrs
+ | WORKING_STORAGE_SECT '.' {
+ current_data_section_set(@1, working_storage_datasect_e);
+ } fields_maybe
+ | LOCAL_STORAGE_SECT '.' {
+ current_data_section_set(@1, local_storage_datasect_e);
+ } fields_maybe
+ | LINKAGE_SECT '.' {
+ current_data_section_set(@1, linkage_datasect_e);
+ } fields_maybe
+ | SCREEN SECTION '.' {
+ cbl_unimplemented("SCREEN SECTION");
+ }
+ ;
+
+file_descrs: file_descr
+ | file_descrs file_descr
+ ;
+file_descr: fd_name '.' { field_done(); } fields
+ | fd_name fd_clauses '.' { field_done(); } fields
+ ;
+
+fd_name: FD NAME { $$ = $2; file_section_fd_set(fd_e, $2, @2); }
+ | SD NAME { $$ = $2; file_section_fd_set(sd_e, $2, @2); }
+ ;
+
+fd_clauses: fd_clause
+ | fd_clauses fd_clause
+ ;
+fd_clause: record_desc
+ {
+ auto f = cbl_file_of(symbol_at(file_section_fd));
+ f->varying_size.min = $1.min;
+ f->varying_size.max = $1.max;
+ auto& cap = cbl_field_of(symbol_at(f->default_record))->data.capacity;
+ cap = std::max(cap, uint32_t(f->varying_size.max));
+ // If min != max now, we know varying is explicitly defined.
+ f->varying_size.explicitly = f->varies();
+ if( f->varying_size.max != 0 ) {
+ if( !(f->varying_size.min <= f->varying_size.max) ) {
+ error_msg(@1, "%zu must be <= %zu",
+ f->varying_size.min, f->varying_size.max);
+ YYERROR;
+ }
+ }
+ }
+ | block_desc
+ | label_desc
+ | DATA record_is field_list
+ | RECORDING mode is NAME
+ {
+ switch( $NAME[0] ) {
+ case 'F':
+ case 'V':
+ case 'U':
+ case 'S':
+ break;
+ default:
+ error_msg(@NAME, "invalid RECORDING MODE '%s'", $NAME);
+ YYERROR;
+ }
+ cbl_unimplementedw("RECORDING MODE was ignored, not defined by ISO 2023");
+ }
+ | VALUE OF fd_values
+ | CODESET is NAME
+ | is GLOBAL
+ {
+ auto f = cbl_file_of(symbol_at(file_section_fd));
+ f->attr |= global_e;
+ }
+ | is EXTERNAL
+ {
+ auto f = cbl_file_of(symbol_at(file_section_fd));
+ f->attr |= external_e;
+ }
+ | is EXTERNAL as LITERAL
+ {
+ auto f = cbl_file_of(symbol_at(file_section_fd));
+ f->attr |= external_e;
+ cbl_unimplemented("AS LITERAL ");
+ }
+ | fd_linage
+ | fd_report {
+ cbl_unimplemented("REPORT WRITER");
+ YYERROR;
+ }
+ ;
+
+block_desc: BLOCK contains rec_contains chars_recs
+ ;
+rec_contains: NUMSTR[min] {
+ ssize_t n;
+ if( (n = numstr2i($min.string, $min.radix)) < 0 ) {
+ error_msg(@min, "size %s cannot be negative", $min.string);
+ YYERROR;
+ }
+ $$.min = $$.max = n; // fixed length
+ }
+ | NUMSTR[min] TO NUMSTR[max] {
+ ssize_t n;
+ if( (n = numstr2i($min.string, $min.radix)) < 0 ) {
+ error_msg(@min, "size %s cannot be negative", $min.string);
+ YYERROR;
+ }
+ $$.min = n;
+
+ if( (n = numstr2i($max.string, $max.radix)) < 0 ) {
+ error_msg(@max, "size %s cannot be negative", $max.string);
+ YYERROR;
+ }
+ $$.max = n;
+ if( !($$.min < $$.max) ) {
+ error_msg(@max, "FROM (%xz) must be less than TO (%zu)",
+ $$.min, $$.max);
+ YYERROR;
+ }
+ }
+ ;
+chars_recs: %empty
+ | CHARACTERS
+ | RECORDS
+ ;
+
+label_desc: LABEL record_is STANDARD
+ | LABEL record_is OMITTED
+ | LABEL record_is fd_labels
+ ;
+
+record_is: RECORD /* lexer swallows IS/ARE */
+ | RECORDS
+ ;
+
+fd_values: fd_value
+ | fd_values fd_value
+ ;
+ /* "The VALUE OF clause is syntax checked, but has
+ no effect on the execution of the program." */
+fd_value: NAME is alpha_val
+ ;
+alpha_val: alphaval
+ | scalar
+ ;
+
+fd_labels: fd_label
+ | fd_labels fd_label
+ ;
+fd_label: NAME
+ ;
+
+record_desc: RECORD is record_vary[r] depending { $$ = $r; }
+ | RECORD contains rec_contains[r] characters { $$ = $r; }
+ ;
+
+record_vary: VARYING in_size from_to { $$ = $from_to; }
+ | VARYING from_to { $$ = $from_to; }
+ | VARYING in_size { $$.min = 0; $$.max = 0; }
+ | VARYING { $$.min = 0; $$.max = 0; }
+ ;
+
+in_size: IN SIZE
+ | IN
+ | SIZE
+ ;
+
+from_to: FROM NUMSTR[min] TO NUMSTR[max] characters {
+ ssize_t n;
+ if( (n = numstr2i($min.string, $min.radix)) < 0 ) {
+ error_msg(@min, "size %s cannot be negative", $min.string);
+ YYERROR;
+ }
+ $$.min = n;
+ if( (n = numstr2i($max.string, $max.radix)) < 0 ) {
+ error_msg(@min, "size %s cannot be negative", $max.string);
+ YYERROR;
+ }
+ $$.max = n;
+ }
+ | NUMSTR[min] TO NUMSTR[max] characters {
+ ssize_t n;
+ if( (n = numstr2i($min.string, $min.radix)) < 0 ) {
+ error_msg(@min, "size %s cannot be negative", $min.string);
+ YYERROR;
+ }
+ $$.min = n;
+ if( (n = numstr2i($max.string, $max.radix)) < 0 ) {
+ error_msg(@max, "size %s cannot be negative", $max.string);
+ YYERROR;
+ }
+ $$.max = n;
+ }
+
+ | TO NUMSTR[max] characters {
+ ssize_t n;
+ if( (n = numstr2i($max.string, $max.radix)) < 0 ) {
+ error_msg(@max, "size %s cannot be negative", $max.string);
+ YYERROR;
+ }
+ $$.min = 0;
+ $$.max = n;
+ }
+
+ | FROM NUMSTR[min] characters {
+ ssize_t n;
+ if( (n = numstr2i($min.string, $min.radix)) < 0 ) {
+ error_msg(@min, "size %s cannot be negative", $min.string);
+ YYERROR;
+ }
+ $$.min = n;
+ $$.max = size_t(-1);
+ }
+ | NUMSTR[min] characters {
+ ssize_t n;
+ if( (n = numstr2i($min.string, $min.radix)) < 0 ) {
+ error_msg(@min, "size %s cannot be negative", $min.string);
+ YYERROR;
+ }
+ $$.min = n;
+ $$.max = size_t(-1);
+ }
+
+ | CHARACTERS { $$.min = 0; $$.max = size_t(-1); }
+ ;
+
+depending: %empty
+ | DEPENDING on NAME
+ {
+ assert(file_section_fd > 0);
+ symbol_elem_t *e = symbol_at(file_section_fd);
+ assert(e);
+ auto file = cbl_file_of(e);
+ size_t odo;
+
+ if( (e = symbol_field(PROGRAM, 0, $3)) != NULL ) {
+ assert(e->type == SymField);
+ odo = symbol_index(e);
+ } else {
+ e = symbol_field_forward_add(PROGRAM, 0, $NAME, yylineno);
+ if( !e ) YYERROR;
+ symbol_field_location( symbol_index(e), @NAME );
+ odo = field_index(cbl_field_of(e));
+ }
+
+ file->record_length = odo;
+ assert( file->record_length > 0 );
+ }
+ ;
+
+fd_linage: LINAGE is num_value with_footings
+ | LINAGE is num_value lines
+ ;
+with_footings: with_footing
+ | with_footings with_footing
+ ;
+with_footing: lines with FOOTING at num_value
+ | lines at top_bot num_value
+ ;
+top_bot: TOP
+ | BOTTOM
+ ;
+
+fd_report: REPORT
+ | REPORTS
+ ;
+
+fields_maybe: %empty
+ | fields
+ ;
+fields: field
+ | fields field
+ ;
+
+field: cdf
+ | data_descr '.'
+ {
+ if( in_file_section() && $data_descr->level == 1 ) {
+ if( !file_section_parent_set($data_descr) ) {
+ YYERROR;
+ }
+ }
+ field_done();
+
+ const auto& field(*$data_descr);
+
+ // Format data.initial per picture
+ if( 0 == pristine_values.count(field.data.initial) ) {
+ if( field.data.digits > 0 &&
+ field.data.value != 0.0 )
+ {
+ char *initial;
+ int rdigits = field.data.rdigits < 0?
+ 1 : field.data.rdigits + 1;
+
+ if( field.has_attr(scaled_e) ) {
+ if( field.data.rdigits > 0 ) {
+ rdigits = field.data.digits + field.data.rdigits;
+ } else {
+ rdigits = 0;
+ }
+ }
+ initial = string_of(field.data.value);
+ if( !initial ) {
+ error_msg(@1, xstrerror(errno));
+ YYERROR;
+ }
+ char decimal = symbol_decimal_point();
+ std::replace(initial, initial + strlen(initial), '.', decimal);
+ free(const_cast<char*>($data_descr->data.initial));
+ $data_descr->data.initial = initial;
+ if( yydebug ) {
+ const char *value_str = string_of(field.data.value);
+ dbgmsg("%s::data.initial is (%%%d.%d) %s ==> '%s'",
+ field.name,
+ field.data.digits,
+ rdigits,
+ value_str? value_str : "",
+ field.data.initial);
+ }
+ }
+ }
+ }
+ ;
+
+occurs_clause: OCCURS cardinal_lb indexed
+ | OCCURS cardinal_lb key_descrs indexed
+ | OCCURS depending_on key_descrs indexed
+ | OCCURS depending_on indexed
+ | OCCURS name indexed
+ {
+ if( ! (is_constant($name) && $name->type == FldLiteralN) ) {
+ error_msg(@name, "%s is not CONSTANT", $name->name);
+ YYERROR;
+ }
+ cbl_occurs_t *occurs = &current_field()->occurs;
+ occurs->bounds.lower =
+ occurs->bounds.upper = $name->data.value;
+ }
+ ;
+cardinal_lb: cardinal times {
+ current_field()->occurs.bounds.lower = $cardinal;
+ current_field()->occurs.bounds.upper = $cardinal;
+ }
+ ;
+
+cardinal: NUMSTR[input]
+ {
+ $$ = numstr2i( $input.string, $input.radix );
+ }
+ ;
+
+depending_on: cardinal[lower] TO bound DEPENDING on name
+ {
+ cbl_occurs_t *occurs = &current_field()->occurs;
+ occurs->bounds.lower = (size_t)$lower;
+ occurs->bounds.upper = (size_t)$bound;
+ occurs->depending_on = field_index($name);
+ }
+ | bound DEPENDING on name
+ {
+ cbl_occurs_t *occurs = &current_field()->occurs;
+ occurs->bounds.lower = 1;
+ occurs->bounds.upper = (size_t)$bound;
+ occurs->depending_on = field_index($name);
+ }
+ ;
+bound: cardinal times
+ | UNBOUNDED times { $$ = -1; }
+ ;
+
+key_descrs: key_descr
+ | key_descrs key_descr
+ ;
+key_descr: ordering key is key_fields
+ ;
+ordering: ASCENDING
+ {
+ current_field()->occurs.key_alloc(true);
+ }
+ | DESCENDING
+ {
+ current_field()->occurs.key_alloc(false);
+ }
+ ;
+key_fields: key_field1
+ | key_fields key_field1
+ ;
+key_field1: name
+ {
+ current_field()->occurs.key_field_add($1);
+ }
+ ;
+
+indexed: %empty
+ | INDEXED by index_fields
+ ;
+index_fields: index_field1
+ | index_fields index_field1
+ ;
+index_field1: ctx_name[name]
+ {
+ static const cbl_field_data_t data { .capacity = 8, .digits = 0 };
+ cbl_field_t field = { .type = FldIndex,
+ .parent = field_index(current_field()),
+ .data = data };
+ if( !namcpy(@name, field.name, $name) ) YYERROR;
+
+ auto symbol = symbol_field(PROGRAM, 0, $name);
+ if( symbol ) {
+ auto field( cbl_field_of(symbol) );
+ error_msg(@name, "'%s' already defined on line %d",
+ field->name, field->line );
+ YYERROR;
+ }
+
+ auto index = field_add(@name, &field);
+ if( !index ) {
+ YYERROR;
+ }
+
+ current_field()->occurs.index_add(index);
+ }
+ ;
+
+level_name: LEVEL ctx_name
+ {
+ switch($LEVEL) {
+ case 1 ... 49:
+ case 66:
+ case 77:
+ case 88:
+ break;
+ default:
+ error_msg(@LEVEL, "LEVEL %d not supported", $LEVEL);
+ YYERROR;
+ }
+ struct cbl_field_t field = { 0,
+ FldInvalid, FldInvalid, 0, 0, 0, capacity_cast($1),
+ nonarray, yylineno, "",
+ 0, cbl_field_t::linkage_t(),
+ { 0,0,0,0, NULL, NULL, { NULL }, { NULL } }, NULL };
+ if( !namcpy(@ctx_name, field.name, $2) ) YYERROR;
+
+ $$ = field_add(@$, &field);
+ if( !$$ ) {
+ YYERROR;
+ }
+ current_field($$); // make available for data_clauses
+ }
+ | LEVEL
+ {
+ switch($LEVEL) {
+ case 1 ... 49:
+ case 66:
+ case 77:
+ case 88:
+ break;
+ default:
+ error_msg(@LEVEL, "LEVEL %d not supported", $LEVEL);
+ YYERROR;
+ }
+ struct cbl_field_t field = { 0,
+ FldInvalid, FldInvalid, 0, 0, 0, capacity_cast($1),
+ nonarray, yylineno, "",
+ 0, {}, { 0,0,0,0, NULL, NULL, { NULL }, { NULL } }, NULL };
+
+ $$ = field_add(@1, &field);
+ if( !$$ ) {
+ YYERROR;
+ }
+ current_field($$); // make available for data_clauses
+ }
+ ;
+
+data_descr: data_descr1
+ {
+ $$ = current_field($1); // make available for occurs, etc.
+ char *env = getenv("symbols_update");
+ if( env && env[0] == 'P' ) {
+ dbgmsg("parse.y:%d: %-15s %s (%s)", __LINE__,
+ cbl_field_type_str($$->type) + 3,
+ field_str($$),
+ cbl_field_type_str($$->usage) + 3);
+ }
+ }
+ | error { static cbl_field_t none = {}; $$ = &none; }
+ ;
+
+const_value: cce_expr
+ | BYTE_LENGTH of name { $$ = $name->data.capacity; }
+ | LENGTH of name { $$ = $name->data.capacity; }
+ | LENGTH_OF of name { $$ = $name->data.capacity; }
+ ;
+
+value78: literalism
+ {
+ cbl_field_data_t
+ data = { .capacity = capacity_cast(strlen($1.data)),
+ .initial = $1.data };
+ $$ = new cbl_field_data_t(data);
+ }
+ | const_value
+ {
+ cbl_field_data_t data = { .value = $1 };
+ $$ = new cbl_field_data_t(data);
+ }
+ | true_false
+ {
+ cbl_unimplemented("Boolean constant");
+ YYERROR;
+ }
+ ;
+
+data_descr1: level_name
+ {
+ assert($1 == current_field());
+ if( $1->usage == FldIndex ) {
+ field_type_update($1, $1->usage, @1, true);
+ }
+ }
+
+ | level_name CONSTANT is_global as const_value
+ {
+ cbl_field_t& field = *$1;
+ if( field.level != 1 ) {
+ error_msg(@1, "%s must be an 01-level data item", field.name);
+ YYERROR;
+ }
+
+ field.attr |= constant_e;
+ if( $is_global ) field.attr |= global_e;
+ field.type = FldLiteralN;
+ field.data.value = $const_value;
+ field.data.initial = string_of($const_value);
+
+ if( !cdf_value(field.name, static_cast<int64_t>($const_value)) ) {
+ error_msg(@1, "%s was defined by CDF", field.name);
+ }
+ }
+ | level_name CONSTANT is_global as literalism[lit]
+ {
+ cbl_field_t& field = *$1;
+ field.attr |= constant_e;
+ if( $is_global ) field.attr |= global_e;
+ field.type = FldLiteralA;
+ field.data.capacity = $lit.len;
+ field.data.initial = $lit.data;
+ field.attr |= literal_attr($lit.prefix);
+ if( field.level != 1 ) {
+ error_msg(@lit, "%s must be an 01-level data item", field.name);
+ YYERROR;
+ }
+ if( !cdf_value(field.name, $lit.data) ) {
+ error_msg(@1, "%s was defined by CDF", field.name);
+ }
+ value_encoding_check(@lit, $1);
+ }
+ | level_name CONSTANT is_global FROM NAME
+ {
+ assert($1 == current_field());
+ const cdfval_t *cdfval = cdf_value($NAME);
+ if( !cdfval ) {
+ error_msg(@1, "%s was defined by CDF", $NAME);
+ YYERROR;
+ }
+ cbl_field_t& field = *$1;
+ field.attr |= ($is_global | constant_e);
+ field.data.capacity = cdfval->string ? strlen(cdfval->string)
+ : sizeof(field.data.value);
+ field.data.initial = cdfval->string;
+ field.data.value = cdfval->number;
+ if( !cdf_value(field.name, *cdfval) ) {
+ error_msg(@1, "%s was defined by CDF", field.name);
+ }
+ }
+
+ | LEVEL78 NAME[name] VALUE is value78[data]
+ {
+ if( ! dialect_mf() ) {
+ dialect_error(@1, "level 78", "mf");
+ YYERROR;
+ }
+ struct cbl_field_t field = { 0, FldLiteralA, FldInvalid,
+ constant_e, 0, 0, 78, nonarray,
+ yylineno, "", 0, {}, *$data, NULL };
+ if( !namcpy(@name, field.name, $name) ) YYERROR;
+ if( field.data.initial ) {
+ field.attr |= quoted_e;
+ if( !cdf_value(field.name, field.data.initial) ) {
+ yywarn("%s was defined by CDF", field.name);
+ }
+ } else {
+ field.type = FldLiteralN;
+ field.data.initial = string_of(field.data.value);
+ if( !cdf_value(field.name,
+ static_cast<int64_t>(field.data.value)) ) {
+ yywarn("%s was defined by CDF", field.name);
+ }
+ }
+ if( ($$ = field_add(@name, &field)) == NULL ) {
+ error_msg(@name, "failed level 78");
+ YYERROR;
+ }
+ }
+
+ | LEVEL88 NAME /* VALUE */ NULLPTR
+ {
+ struct cbl_field_t field = { 0,
+ FldClass, FldInvalid, 0, 0, 0, 88, nonarray, yylineno, "",
+ 0, cbl_field_t::linkage_t(),
+ { 0,0,0,0, NULL, NULL, { NULL }, { NULL } }, NULL };
+ if( !namcpy(@NAME, field.name, $2) ) YYERROR;
+
+ auto fig = constant_of(constant_index(NULLS))->data.initial;
+ struct cbl_domain_t *domain = new cbl_domain_t[2];
+
+ domain[0] = cbl_domain_t(@NAME, false, strlen(fig), fig);
+
+ field.data.domain = domain;
+
+ if( ($$ = field_add(@2, &field)) == NULL ) {
+ error_msg(@NAME, "failed level 88");
+ YYERROR;
+ }
+ auto parent = cbl_field_of(symbol_at($$->parent));
+ if( parent->type != FldPointer ) {
+ error_msg(@NAME, "LEVEL 88 %s VALUE NULLS invalid for "
+ "%s %s, which is not a POINTER",
+ $$->name, parent->level_str(), parent->name);
+ }
+ }
+ | LEVEL88 NAME VALUE domains
+ {
+ struct cbl_field_t field = { 0,
+ FldClass, FldInvalid, 0, 0, 0, 88, nonarray, yylineno, "",
+ 0, cbl_field_t::linkage_t(),
+ { 0,0,0,0, NULL, NULL, { NULL }, { NULL } }, NULL };
+ if( !namcpy(@NAME, field.name, $2) ) YYERROR;
+
+ struct cbl_domain_t *domain =
+ new cbl_domain_t[ domains.size() + 1];
+
+ std::copy(domains.begin(), domains.end(), domain);
+
+ field.data.domain = domain;
+ field.data.false_value = $domains;
+ domains.clear();
+
+ if( ($$ = field_add(@2, &field)) == NULL ) {
+ error_msg(@NAME, "failed level 88");
+ YYERROR;
+ }
+ }
+
+ | name66[alias] RENAMES name[orig]
+ {
+ symbol_field_alias_end();
+ if( is_literal($orig) ) {
+ error_msg(@orig, "cannot RENAME '%s'", name_of($orig));
+ YYERROR;
+ }
+ if( !immediately_follows($orig) ) {
+ error_msg(@orig, "%s must immediately follow %s to RENAME it",
+ $alias, name_of($orig));
+ YYERROR;
+ }
+ if( $orig->occurs.ntimes() ) {
+ error_msg(@orig, "cannot RENAME table %s %s",
+ $orig->level_str(), name_of($orig));
+ YYERROR;
+ }
+ auto table = occurs_in($orig);
+ if( table ) {
+ error_msg(@orig, "cannot RENAME '%s' OF %s",
+ name_of($orig), table->name);
+ YYERROR;
+ }
+ if( ! $orig->rename_level_ok() ) {
+ error_msg(@orig, "cannot RENAME %s %s",
+ $orig->level_str(), name_of($orig));
+ YYERROR;
+ }
+ symbol_elem_t *orig = symbol_at(field_index($orig));
+ $$ = cbl_field_of(symbol_field_alias(orig, $alias));
+ symbol_field_location(field_index($$), @alias);
+ }
+
+ | name66[alias] RENAMES name[orig] THRU name[thru]
+ {
+ symbol_field_alias_end();
+ if( !immediately_follows($orig) ) {
+ error_msg(@orig, "RENAMES: %s must immediately follow %s",
+ $alias, name_of($orig));
+ YYERROR;
+ }
+ if( is_literal($orig) ) {
+ error_msg(@orig, "cannot RENAME '%s'", name_of($orig));
+ YYERROR;
+ }
+ if( is_literal($thru) ) {
+ error_msg(@thru, "cannot RENAME '%s'", name_of($thru));
+ YYERROR;
+ }
+ auto table = occurs_in($orig);
+ if( table ) {
+ error_msg(@orig, "cannot RENAME '%s' OF %s",
+ name_of($orig), table->name);
+ YYERROR;
+ }
+ table = occurs_in($thru);
+ if( table ) {
+ error_msg(@thru, "cannot RENAME '%s' OF %s",
+ name_of($thru), table->name);
+ YYERROR;
+ }
+ if( ! $orig->rename_level_ok() ) {
+ error_msg(@orig, "cannot RENAME %s %s",
+ $orig->level_str(), name_of($orig));
+ YYERROR;
+ }
+ if( $orig->has_subordinate($thru) ) {
+ error_msg(@orig, "cannot RENAME %s %s THRU %s %s "
+ "because %s is subordinate to %s",
+ $orig->level_str(), name_of($orig),
+ $thru->level_str(), name_of($thru),
+ name_of($thru), name_of($orig));
+ YYERROR;
+ }
+ auto not_ok = rename_not_ok($orig, $thru);
+ if( not_ok ) {
+ error_msg(@orig, "cannot RENAME %s %s THRU %s %s "
+ "because %s %s cannot be renamed",
+ $orig->level_str(), name_of($orig),
+ $thru->level_str(), name_of($thru),
+ not_ok->level_str(), name_of(not_ok));
+ YYERROR;
+ }
+ if( field_index($thru) <= field_index($orig) ) {
+ error_msg(@orig, "cannot RENAME %s %s THRU %s %s "
+ "because they're in the wrong order",
+ $orig->level_str(), name_of($orig),
+ $thru->level_str(), name_of($thru));
+ YYERROR;
+ }
+ symbol_elem_t *orig = symbol_at(field_index($orig));
+ symbol_elem_t *last = symbol_at(field_index($thru));
+ $$ = cbl_field_of(symbol_field_alias2(orig, last, $alias));
+ symbol_field_location(field_index($$), @alias);
+ }
+
+ | level_name[field] data_clauses
+ {
+ gcc_assert($field == current_field());
+ if( $data_clauses == value_clause_e ) { // only VALUE, no PIC
+ // Error unless VALUE is a figurative constant or (quoted) string.
+ if( $field->type != FldPointer &&
+ ! $field->has_attr(quoted_e) &&
+ normal_value_e == cbl_figconst_of($field->data.initial) )
+ {
+ error_msg(@field, "%s numeric VALUE %s requires PICTURE",
+ $field->name, $field->data.initial);
+ }
+ if( null_value_e == cbl_figconst_of($field->data.initial) ) {
+ // don't change the type
+ assert(FldPointer == $field->type);
+ } else {
+ // alphanumeric VALUE by itself implies alphanumeric type
+ assert(FldPointer != $field->type);
+ $field->type = FldAlphanumeric;
+ if( $field->data.initial ) {
+ $field->data.capacity = strlen($field->data.initial);
+ }
+ }
+ }
+
+ // Verify BLANK WHEN ZERO
+ if( $field->has_attr(blank_zero_e) ) {
+ switch($field->type) {
+ case FldNumericEdited:
+ if( $field->has_attr(signable_e) ) {
+ error_msg(@2, "%s has 'S' in PICTURE, cannot be BLANK WHEN ZERO",
+ $field->name, cbl_field_type_str($field->type) );
+ }
+ break;
+ default:
+ error_msg(@2, "%s must be "
+ "NUMERIC DISPLAY or NUMERIC-EDITED, not %s",
+ $field->name, cbl_field_type_str($field->type) );
+ }
+ $field->data.picture = original_picture();
+ }
+
+ // SIGN clause valid only with "S" in picture
+ if( $field->type == FldNumericDisplay && !is_signable($field) ) {
+ static const size_t sign_attrs = leading_e | separate_e;
+ static_assert(sizeof(sign_attrs) == sizeof($field->attr),
+ "size matters");
+
+ // remove inapplicable inherited sign attributes
+ size_t group_sign = group_attr($field) & sign_attrs;
+ $field->attr &= ~group_sign;
+
+ if( $field->attr & sign_attrs ) {
+ dbgmsg("%s:%d: %s", __func__, __LINE__, field_str($field));
+ error_msg(@field, "%s must be signed for SIGN IS",
+ $field->name );
+ YYERROR;
+ }
+ }
+
+ // Increase numeric display capacity by 1 for SIGN SEPARATE.
+ if( $field->type == FldNumericDisplay &&
+ is_signable($field) &&
+ $field->has_attr(separate_e) ){
+ $field->data.capacity++;
+ }
+
+ // Set Packed-Decimal capacity
+ if( $field->type == FldPacked ) {
+ $field->data.capacity = type_capacity($field->type,
+ $field->data.digits);
+ if( $field->attr & separate_e )
+ {
+ // This is a gentle kludge required by the the belated
+ // introduction of COMP-6, which is like COMP-3 but with no
+ // sign nybble. The code in type_capacity assumes a sign
+ // nybble.
+ $field->data.capacity = ($field->data.digits+1)/2;
+ }
+ }
+
+ // Check COMP-5 capacity
+ // No capacity means no PICTURE, valid only for a (potential) group
+ if( $field->type == FldNumericBin5 && $field->data.capacity == 0 ) {
+ if( has_clause ($data_clauses, usage_clause_e) &&
+ !has_clause ($data_clauses, picture_clause_e) ) {
+ // invalidate until a child is born
+ $field->type = FldInvalid;
+ }
+ }
+
+ // Ensure signed initial VALUE is for signed numeric type
+ if( is_numeric($field) &&
+ $field->data.initial &&
+ $field->type != FldFloat )
+ {
+ switch( $field->data.initial[0] ) {
+ case '-':
+ if( !$field->has_attr(signable_e) ) {
+ error_msg(@field, "%s is unsigned but has signed VALUE '%s'",
+ $field->name, $field->data.initial);
+ }
+ }
+ }
+
+ // Verify VALUE
+ $field->report_invalid_initial_value(@data_clauses);
+
+ // verify REDEFINES
+ auto parent = parent_of($field);
+ if( parent && $field->level == parent->level ) {
+ valid_redefine(@field, $field, parent); // calls yyerror
+ }
+ }
+ ;
+
+literalism: LITERAL { $$ = $1; }
+ | literalism[first] '&' LITERAL[second]
+ {
+ $$ = $first;
+ literal_t& output($$);
+
+ output.len += $second.len;
+ output.data = reinterpret_cast<char*>(xrealloc(output.data,
+ output.len + 1));
+ memcpy( output.data + $first.len, $second.data, $second.len );
+ output.data[output.len] = '\0';
+
+ if( $second.prefix[0] ) { strcpy(output.prefix, $second.prefix); }
+ if( ! $first.compatible_prefix($second) ) {
+ yywarn("dissimilar literals, '%s' prevails",
+ output.prefix);
+ }
+ }
+ ;
+
+name66: LEVEL66 NAME[alias]
+ {
+ build_symbol_map();
+ if( ! symbol_field_alias_begin() ) {
+ error_msg(@alias, "no Level 01 record exists "
+ "for %s to redefine", $alias);
+ }
+ $$ = $alias;
+ }
+ ;
+
+data_clauses: data_clause
+ {
+ if( $data_clause == redefines_clause_e ) {
+ auto parent = parent_of(current_field());
+ if( !parent ) {
+ error_msg(@1, "%s invalid REDEFINES",
+ current_field()->name);
+ YYERROR;
+ }
+ if( parent->occurs.ntimes() > 0 ) {
+ error_msg(@1, "%s cannot REDEFINE table %s",
+ current_field()->name,
+ parent->name);
+ YYERROR;
+ }
+ }
+ }
+ | data_clauses data_clause {
+ const char *clause = "data";
+ switch($2) {
+ case occurs_clause_e: clause = "OCCURS"; break;
+ case picture_clause_e: clause = "PIC"; break;
+ case usage_clause_e: clause = "USAGE"; break;
+ case value_clause_e: clause = "VALUE"; break;
+ case global_clause_e: clause = "GLOBAL"; break;
+ case external_clause_e: clause = "EXTERNAL"; break;
+ case justified_clause_e: clause = "JUSTIFIED"; break;
+ case redefines_clause_e: clause = "REDEFINES"; break;
+ case blank_zero_clause_e: clause = "BLANK WHEN ZERO"; break;
+ case synched_clause_e: clause = "SYNCHRONIZED"; break;
+ case sign_clause_e: clause = "SIGN"; break;
+ case based_clause_e: clause = "BASED"; break;
+ case same_clause_e: clause = "SAME AS"; break;
+ case volatile_clause_e: clause = "VOLATILE"; break;
+ case type_clause_e: clause = "TYPE"; break;
+ case typedef_clause_e: clause = "TYPEDEF"; break;
+ }
+ if( ($$ & $2) == $2 ) {
+ error_msg(@2, "%s clause repeated", clause);
+ YYERROR;
+ }
+
+ if( $data_clause == redefines_clause_e ) {
+ error_msg(@2, "REDEFINES must appear "
+ "immediately after LEVEL and NAME");
+ YYERROR;
+ }
+ cbl_field_t *field = current_field();
+ const int globex = (global_e | external_e);
+ if( (($$ | $2) & globex) == globex ) {
+ error_msg(@2, "GLOBAL and EXTERNAL specified");
+ YYERROR;
+ }
+
+ $$ |= $2;
+
+ // If any implied TYPE bits are on in addition to
+ // type_clause_e, they're in conflict.
+ static const size_t type_implies =
+ // ALIGNED clause not implemented
+ blank_zero_clause_e | justified_clause_e | picture_clause_e
+ | sign_clause_e | synched_clause_e | usage_clause_e;
+
+ if( type_clause_e < ($$ & (type_clause_e | type_implies)) ) {
+ if( $2 == type_clause_e ) {
+ error_msg(@2, "TYPE TO incompatible with ALIGNED, "
+ "BLANK WHEN ZERO, JUSTIFIED, PICTURE, SIGN, "
+ "SYNCHRONIZED, and USAGE");
+ } else {
+ error_msg(@2, "%s incompatible with TYPE TO", clause);
+ }
+ YYERROR;
+ }
+
+ if( ($$ & same_clause_e) == same_clause_e ) {
+ if( 0 < ($$ & ~same_clause_e) ) {
+ error_msg(@2, "%s %s SAME AS "
+ "precludes other DATA DIVISION clauses",
+ field->level_str(), field->name);
+ YYERROR;
+ }
+ }
+
+ if( is_numeric(field->type) && field->type != FldNumericDisplay ) {
+ if( $$ & sign_clause_e ) {
+ error_msg(@2, "%s is binary NUMERIC type, "
+ "incompatible with SIGN IS", field->name);
+ }
+ }
+
+ if( gcobol_feature_embiggen() ) {
+ if( field->is_binary_integer() && field->data.capacity == 4) {
+ auto redefined = symbol_redefines(field);
+ if( redefined && redefined->type == FldPointer ) {
+ if( yydebug ) {
+ yywarn("expanding %s size from %u bytes to %zu "
+ "because it redefines %s with USAGE POINTER",
+ field->name, field->size(), sizeof(void*),
+ redefined->name);
+ }
+ field->embiggen();
+ }
+ }
+ }
+
+ switch( field->type ) {
+ case FldFloat:
+ if( ($$ & picture_clause_e) == picture_clause_e ) {
+ error_msg(@2, "%s: FLOAT types do not allow PICTURE",
+ field->name);
+ }
+ break;
+ default:
+ break;
+ }
+
+ if( ! field->is_justifiable() ) {
+ error_msg(@2, "%s: %s is incompatible with JUSTIFIED",
+ field->name, 3 + cbl_field_type_str(field->type));
+ }
+ }
+ ;
+
+data_clause: any_length { $$ = any_length_e; }
+ | based_clause { $$ = based_clause_e; }
+ | blank_zero_clause { $$ = blank_zero_clause_e; }
+ | external_clause { $$ = external_clause_e; }
+ | global_clause { $$ = global_clause_e; }
+ | justified_clause { $$ = justified_clause_e; }
+ | occurs_clause { $$ = occurs_clause_e;
+ cbl_field_t *field = current_field();
+ switch( field->level ) {
+ case 1:
+ if( dialect_mf() ) break;
+ __attribute__((fallthrough));
+ case 77:
+ case 88:
+ error_msg(@$, "%s %s: invalid LEVEL for OCCURS",
+ field->level_str(), field->name );
+ break;
+ default:
+ assert( field->parent > 0 );
+ }
+ }
+ | picture_clause { $$ = picture_clause_e; }
+ | redefines_clause { $$ = redefines_clause_e; }
+ | same_clause { $$ = same_clause_e; }
+ | sign_clause { $$ = sign_clause_e; }
+ | synched_clause { $$ = synched_clause_e; }
+ | type_clause { $$ = type_clause_e; }
+ | typedef_clause { $$ = typedef_clause_e; }
+ | usage_clause { $$ = usage_clause_e; }
+ | value_clause { $$ = value_clause_e;
+ cbl_field_t *field = current_field();
+
+ if( field->type != FldAlphanumeric &&
+ field->data.initial && field->data.initial[0] )
+ {
+ // Embedded NULs are valid only in FldAlphanumeric, and are
+ // already handled.
+ if( strlen(field->data.initial) < field->data.capacity ) {
+ auto p = blank_pad_initial( field->data.initial,
+ strlen(field->data.initial),
+ field->data.capacity );
+ if( !p ) YYERROR;
+ field->data.initial = p;
+ }
+ }
+ const cbl_field_t *parent;
+ if( (parent = parent_has_value(field)) != NULL ) {
+ error_msg(@1, "VALUE invalid because group %s has VALUE clause",
+ parent->name);
+ }
+ }
+ | volatile_clause { $$ = volatile_clause_e; }
+ ;
+
+picture_clause: PIC signed nps[fore] nines nps[aft]
+ {
+ cbl_field_t *field = current_field();
+ if( !field_type_update(field, FldNumericDisplay, @$) ) {
+ YYERROR;
+ }
+ ERROR_IF_CAPACITY(@PIC, field);
+ field->attr |= $signed;
+ field->data.capacity = type_capacity(field->type, $4);
+ field->data.digits = $4;
+ if( long(field->data.digits) != $4 ) {
+ error_msg(@2, "indicated size would be %ld bytes, "
+ "maximum data item size is %u",
+ $4, UINT32_MAX);
+ }
+
+ if( $fore && $aft ) { // leading and trailing P's
+ error_msg(@2, "PIC cannot have both leading and trailing P");
+ YYERROR;
+ }
+ if( $fore || $aft ) {
+ field->attr |= scaled_e;
+ field->data.rdigits = $fore? $fore : -$aft;
+ }
+ if( ! field->reasonable_capacity() ) {
+ error_msg(@2, "%s limited to capacity of %d (would need %u)",
+ field->name, MAX_FIXED_POINT_DIGITS, field->data.capacity);
+ }
+ }
+
+ | PIC signed NINEV[left] nine[rdigits]
+ {
+ cbl_field_t *field = current_field();
+ field->data.digits = $left + $rdigits;
+
+ if( field->is_binary_integer() ) {
+ field->data.capacity = type_capacity(field->type,
+ field->data.digits);
+ } else {
+ if( !field_type_update(field, FldNumericDisplay, @$) ) {
+ YYERROR;
+ }
+ ERROR_IF_CAPACITY(@PIC, field);
+ field->attr |= $signed;
+ field->data.capacity = field->data.digits;
+ field->data.rdigits = $rdigits;
+ }
+ if( ! field->reasonable_capacity() ) {
+ error_msg(@2, "%s limited to capacity of %d (would need %u)",
+ field->name, MAX_FIXED_POINT_DIGITS, field->data.capacity);
+ }
+ }
+ | PIC signed NINEDOT[left] nine[rdigits]
+ {
+ uint32_t size = $left + $rdigits;
+
+ cbl_field_t *field = current_field();
+ if( !field_type_update(field, FldNumericEdited, @$) ) {
+ YYERROR;
+ }
+ ERROR_IF_CAPACITY(@PIC, field);
+ field->attr |= $signed;
+ field->data.digits = size;
+ field->data.capacity = ++size;
+ field->data.rdigits = $rdigits;
+
+ if( ! field->reasonable_capacity() ) {
+ error_msg(@2, "%s limited to capacity of %d (would need %u)",
+ field->name, MAX_FIXED_POINT_DIGITS, field->data.capacity);
+ }
+ }
+
+ | PIC alphanum_pic[size]
+ {
+ cbl_field_t *field = current_field();
+
+ if( field->type == FldNumericBin5 &&
+ field->data.capacity == 0 &&
+ dialect_mf() )
+ { // PIC X COMP-X or COMP-9
+ if( ! field->has_attr(all_x_e) ) {
+ error_msg(@2, "COMP PICTURE requires all X's or all 9's");
+ YYERROR;
+ }
+ } else {
+ if( !field_type_update(field, FldAlphanumeric, @$) ) {
+ YYERROR;
+ }
+ }
+ assert(0 < $size);
+ if( field->data.initial != NULL ) {
+ if( 0 < field->data.capacity &&
+ field->data.capacity < uint32_t($size) ) {
+ auto p = blank_pad_initial( field->data.initial,
+ field->data.capacity, $size );
+ if( !p ) YYERROR;
+ field->data.initial = p;
+ }
+ }
+
+ field->data.capacity = $size;
+ field->data.picture = NULL;
+
+ if( false ) dbgmsg("PIC alphanum_pic[size]:%d: %s",
+ field->line, field_str(field));
+ }
+
+ | PIC numed[picture]
+ {
+ cbl_field_t *field = current_field();
+ if( !field_type_update(field, FldNumericEdited, @$) ) {
+ YYERROR;
+ }
+ ERROR_IF_CAPACITY(@PIC, field);
+ if( !is_numeric_edited($picture) ) {
+ error_msg(@picture, numed_message);
+ YYERROR;
+ }
+ field->data.picture = $picture;
+ field->data.capacity = length_of_picture($picture);
+ field->data.digits = digits_of_picture($picture, false);
+ field->data.rdigits = rdigits_of_picture($picture);
+ if( is_picture_scaled($picture) ) field->attr |= scaled_e;
+ }
+
+ | PIC ALPHED[picture]
+ {
+ bool is_alpha_edited( const char picture[] );
+
+ cbl_field_t *field = current_field();
+ ERROR_IF_CAPACITY(@PIC, field);
+ field->data.capacity = length_of_picture($picture);
+ field->data.picture = $picture;
+
+ // In case the lexer guesses wrong.
+ cbl_field_type_t type = is_numeric_edited($picture)?
+ FldNumericEdited : FldAlphaEdited;
+ if( !field_type_update(field, type, @$) ) {
+ YYERROR;
+ }
+
+ switch( type ) {
+ case FldNumericEdited:
+ field->data.digits = digits_of_picture($picture, false);
+ field->data.rdigits = rdigits_of_picture($picture);
+ if( is_picture_scaled($picture) ) field->attr |= scaled_e;
+ break;
+ case FldAlphaEdited:
+ if( !is_alpha_edited(field->data.picture) ) {
+ error_msg(@picture, "invalid picture for Alphanumeric-edited");
+ YYERROR;
+ }
+ break;
+ default:
+ gcc_unreachable();
+ }
+ }
+ ;
+
+alphanum_pic: alphanum_part {
+ current_field()->set_attr($1.attr);
+ $$ = $1.nbyte;
+ }
+ | alphanum_pic alphanum_part
+ {
+ auto field = current_field();
+ dbgmsg("%s has %s against %s",
+ field->name, field_attr_str(field),
+ cbl_field_attr_str($2.attr));
+
+ if( ! field->has_attr($2.attr) ) {
+ field->clear_attr(all_ax_e); // clears 2 bits
+ }
+ $$ += $2.nbyte;
+
+ dbgmsg("%s attrs: %s", field->name, field_attr_str(field));
+ }
+ ;
+alphanum_part: ALNUM[picture] count
+ {
+ $$.attr = uniform_picture($picture);
+ $$.nbyte = strlen($picture);
+ auto count($count);
+ if( count > 0 ) {
+ --count;
+ $$.nbyte += count; // AX9(3) has count 5
+ }
+ if( count < 0 ) {
+ error_msg(@2, "PICTURE count '(%d)' is negative", count );
+ YYERROR;
+ }
+ }
+ ;
+
+signed: %empty { $$ = 0; }
+ | 'S' { $$ = signable_e; }
+ ;
+
+nps: %empty { $$ = 0; }
+ | PIC_P { $$ = $1; }
+ ;
+
+nine: %empty { $$ = 0; }
+ | nines
+ {
+ $$ = $1;
+ if( $$ == 0 ) {
+ error_msg(@1, "'(0)' invalid in PICTURE (ISO 2023 13.18.40.3)");
+ }
+ }
+ ;
+nines: NINES
+ | nines NINES { $$ = $1 + $2; }
+ ;
+
+count: %empty { $$ = 0; }
+ | '(' NUMSTR ')'
+ {
+ $$ = numstr2i( $NUMSTR.string, $NUMSTR.radix );
+ if( $$ == 0 ) {
+ error_msg(@2, "'(0)' invalid in PICTURE (ISO 2023 13.18.40.3)");
+ }
+ }
+ | '(' NAME ')'
+ {
+ auto value = cdf_value($NAME);
+ if( ! (value && value->is_numeric()) ) {
+ error_msg(@NAME, "PICTURE '(%s)' requires a CONSTANT value", $NAME );
+ YYERROR;
+ }
+ int nmsg = 0;
+ auto e = symbol_field(PROGRAM, 0, $NAME);
+ if( e ) { // verify not floating point with nonzero fraction
+ auto field = cbl_field_of(e);
+ assert(is_literal(field));
+ if( field->data.value != size_t(field->data.value) ) {
+ nmsg++;
+ error_msg(@NAME, "invalid PICTURE count '(%s)'",
+ field->data.initial );
+ }
+ }
+ $$ = value->as_number();
+ if( $$ <= 0 && !nmsg) {
+ error_msg(@NAME, "invalid PICTURE count '(%s)'", $NAME );
+ }
+ }
+ ;
+
+numed: NUMED
+ | NUMED_CR
+ | NUMED_DB
+ ;
+
+usage_clause: usage_clause1[type]
+ {
+ cbl_field_t *field = current_field();
+ cbl_field_type_t type = static_cast<cbl_field_type_t>($type);
+ if( ! field_type_update(field, type, @$, true) ) {
+ YYERROR;
+ }
+ }
+ ;
+usage_clause1: usage COMPUTATIONAL[comp] native
+ {
+ bool infer = true;
+ cbl_field_t *field = current_field();
+
+ // Some binary types have defined capacity;
+ switch($comp.type) {
+ // COMPUTATIONAL and COMP-5 rely on PICTURE.
+ case FldNumericBinary:
+ field->attr |= big_endian_e;
+ __attribute__((fallthrough));
+ case FldNumericBin5:
+ // If no capacity yet, then no picture, infer $comp.capacity.
+ // If field has capacity, ensure USAGE is compatible.
+ if( field->data.capacity > 0 ) { // PICTURE before USAGE
+ infer = false;
+ switch( field->type ) {
+ case FldAlphanumeric: // PIC X COMP-5 or COMP-X
+ assert( field->data.digits == 0 );
+ assert( field->data.rdigits == 0 );
+ if( dialect_mf() ) {
+ field->type = $comp.type;
+ field->clear_attr(signable_e);
+ } else {
+ error_msg(@comp, "numeric USAGE invalid "
+ "with Alpnanumeric PICTURE");
+ YYERROR;
+ }
+ break;
+ case FldNumericDisplay: // PIC 9 COMP-5 or COMP-X
+ if( $comp.capacity == 0xFF ) { // comp-x is a bit like comp-5
+ assert( field->data.digits == field->data.capacity );
+ if( ! dialect_mf() ) {
+ dialect_error(@1, "COMP-X", "mf");
+ }
+ }
+ field->type = $comp.type;
+ field->data.capacity = type_capacity(field->type,
+ field->data.digits);
+ break;
+ default: break;
+ }
+ }
+ break;
+ case FldPacked: // comp-6 is unsigned comp-3
+ assert(! $comp.signable); // else PACKED_DECIMAL from scanner
+ field->attr |= separate_e;
+ if( ! dialect_mf() ) {
+ dialect_error(@1, "COMP-6", "mf");
+ }
+ if( field->type == FldNumericDisplay ) {// PICTURE before USAGE
+ infer = false;
+ assert(field->data.capacity > 0);
+ field->type = $comp.type;
+ field->data.capacity = type_capacity(field->type,
+ field->data.digits);
+ }
+ break;
+ default:
+ break;
+ }
+
+ if( infer ) {
+ if( $comp.capacity > 0 ) {
+ if( field->data.capacity > 0 ) {
+ error_msg(@comp, "%s is BINARY type, incompatible with PICTURE",
+ field->name);
+ YYERROR;
+ }
+ field->data.capacity = $comp.capacity;
+ field->type = $comp.type;
+ if( $comp.signable ) {
+ field->attr = (field->attr | signable_e);
+ }
+ }
+ }
+ $$ = $comp.type;
+ }
+ | usage DISPLAY native { $$ = FldDisplay; }
+ | usage PACKED_DECIMAL native { $$ = FldPacked; }
+ | usage PACKED_DECIMAL with NO SIGN
+ {
+ cbl_field_t *field = current_field();
+ if( field->data.capacity > 0 &&
+ field->type != FldNumericDisplay) {
+ error_msg(@2, "%s PICTURE is incompatible with USAGE PACKED DECIMAL",
+ field->name);
+ YYERROR;
+ }
+ field->clear_attr(separate_e);
+ field->clear_attr(signable_e);
+ if( field->type == FldNumericDisplay ) {// PICTURE before USAGE
+ assert(field->data.capacity > 0);
+ field->data.capacity = type_capacity(FldPacked,
+ field->data.digits);
+ }
+ $$ = field->type = FldPacked;
+ }
+ | usage INDEX {
+ $$ = symbol_field_index_set( current_field() )->type;
+ }
+ // We should enforce data/code pointers with a different type.
+ | usage POINTER
+ {
+ $$ = FldPointer;
+ auto field = current_field();
+ auto redefined = symbol_redefines(field);
+
+ if( $POINTER ) {
+ field->set_attr($POINTER);
+ }
+ if( gcobol_feature_embiggen() && redefined &&
+ is_numeric(redefined->type) && redefined->size() == 4) {
+ // For now, we allow POINTER to expand a 32-bit item to 64 bits.
+ field->data.capacity = sizeof(void *);
+ dbgmsg("%s: expanding #%zu %s capacity %u => %u", __func__,
+ field_index(redefined), redefined->name,
+ redefined->data.capacity, field->data.capacity);
+
+ redefined->embiggen();
+
+ if( redefined->data.initial ) {
+ auto s = xasprintf( "%s ", redefined->data.initial);
+ std::replace(s, s + strlen(s), '!', char(0x20));
+ redefined->data.initial = s;
+ }
+ }
+ }
+ | usage POINTER TO error
+ {
+ cbl_unimplemented("POINTER TO");
+ $$ = FldPointer;
+ }
+ ;
+
+value_clause: VALUE all LITERAL[lit] {
+ cbl_field_t *field = current_field();
+ field->data.initial = $lit.data;
+ field->attr |= literal_attr($lit.prefix);
+ // The __gg__initialize_data routine needs to know that VALUE is a
+ // quoted literal. This is critical for NumericEdited variables
+ field->attr |= quoted_e;
+
+ if( field->data.capacity == 0 ) {
+ field->data.capacity = $lit.len;
+ } else {
+ if( $all ) {
+ field_value_all(field);
+ } else {
+ if( $lit.len < field->data.capacity ) {
+ auto p = blank_pad_initial( $lit.data, $lit.len,
+ field->data.capacity );
+ if( !p ) YYERROR;
+ field->data.initial = p;
+ }
+ }
+ }
+ value_encoding_check(@lit, field);
+ }
+ | VALUE all cce_expr[value] {
+ cbl_field_t *field = current_field();
+ auto orig_str = original_number();
+ auto orig_val = numstr2i(orig_str, decimal_e);
+ char *initial = NULL;
+
+ if( orig_val == $value ) {
+ initial = orig_str;
+ pristine_values.insert(initial);
+ } else {
+ initial = string_of($value);
+ gcc_assert(initial);
+ }
+
+ char decimal = symbol_decimal_point();
+ std::replace(initial, initial + strlen(initial), '.', decimal);
+
+ field->data.initial = initial;
+ field->data.value = $value;
+
+ if( $all ) field_value_all(field);
+ }
+ | VALUE all reserved_value[value]
+ {
+ if( $value != NULLS ) {
+ auto fig = constant_of(constant_index($value));
+ current_field()->data.initial = fig->data.initial;
+ }
+ }
+ | /* VALUE is */ NULLPTR
+ {
+ auto fig = constant_of(constant_index(NULLS));
+ current_field()->data.initial = fig->data.initial;
+ }
+ | VALUE error
+ {
+ error_msg(@2, "no valid VALUE supplied");
+ }
+ ;
+
+global_clause: is GLOBAL
+ {
+ cbl_field_t *field = current_field();
+ field->attr |= (field->attr | global_e);
+ }
+ ;
+external_clause: is EXTERNAL
+ {
+ cbl_field_t *field = current_field();
+ field->attr |= (field->attr | external_e);
+ }
+ ;
+
+justified_clause: is JUSTIFIED
+ {
+ cbl_field_t *field = current_field();
+ field->attr |= rjust_e;
+ }
+ ;
+
+redefines_clause: REDEFINES NAME[orig]
+ {
+ struct symbol_elem_t *e = field_of($orig);
+ if( !e ) {
+ error_msg(@2, "REDEFINES target not defined");
+ YYERROR;
+ }
+ cbl_field_t *field = current_field();
+ cbl_field_t *orig = cbl_field_of(e);
+ if( orig->has_attr(filler_e) ) {
+ error_msg(@2, "%s may not REDEFINE %s",
+ field->name, orig->name);
+ }
+ cbl_field_t *super = symbol_redefines(orig);
+ if( super ) {
+ error_msg(@2, "%s may not REDEFINE %s, "
+ "which redefines %s",
+ field->name, orig->name, super->name);
+ }
+ if( field->level != orig->level ) {
+ error_msg(@2, "cannot redefine %s %s as %s %s "
+ "because they have different levels",
+ orig->level_str(), name_of(orig),
+ field->level_str(), name_of(field));
+ }
+ // ISO 13.18.44.3
+ auto parent( symbol_index(e) );
+ auto p = std::find_if( symbol_elem_of(orig) + 1,
+ symbol_elem_of(field),
+ [parent, level = field->level]( const auto& elem ) {
+ if( elem.type == SymField ) {
+ auto f = cbl_field_of(&elem);
+ return
+ f->level == level &&
+ f->parent != parent;
+ }
+ return false;
+ } );
+ if( p != symbol_elem_of(field) ) {
+ auto mid( cbl_field_of(p) );
+ error_msg(@2, "cannot redefine %s %s as %s %s "
+ "because %s %s intervenes",
+ orig->level_str(), name_of(orig),
+ field->level_str(), name_of(field),
+ mid->level_str(), name_of(mid));
+ }
+
+ if( valid_redefine(@2, field, orig) ) {
+ /*
+ * Defer "inheriting" the parent's description until the
+ * redefine is complete.
+ */
+ current_field()->parent = symbol_index(e);
+ }
+ }
+ ;
+
+any_length: ANY LENGTH
+ { cbl_field_t *field = current_field();
+ if( field->attr & any_length_e ) {
+ error_msg(@1, "ANY LENGTH already set");
+ }
+ if( ! (field->level == 1 &&
+ current_data_section == linkage_datasect_e &&
+ (1 < current.program_level() ||
+ current.program()->is_function())) ) {
+ error_msg(@1, "ANY LENGTH valid only for 01 "
+ "in LINKAGE SECTION of a function or contained program");
+ YYERROR;
+ }
+ field->attr |= any_length_e;
+ }
+ ;
+
+based_clause: BASED
+ { cbl_field_t *field = current_field();
+ if( field->attr & based_e ) {
+ error_msg(@1, "BASED already set");
+ }
+ field->attr |= based_e;
+ }
+ ;
+
+blank_zero_clause: blank_when_zero
+ { cbl_field_t *field = current_field();
+ // the BLANK WHEN ZERO clause defines the item as numeric-edited.
+ if( !field_type_update(field, FldNumericEdited, @1) ) {
+ YYERROR;
+ }
+ field->attr |= blank_zero_e;
+ }
+ ;
+blank_when_zero:
+ BLANK WHEN ZERO
+ | BLANK ZERO
+ ;
+
+synched_clause: SYNCHRONIZED
+ | SYNCHRONIZED LEFT
+ | SYNCHRONIZED RIGHT
+ ;
+
+same_clause: SAME AS name
+ {
+ cbl_field_t *field = current_field(), *other = $name;
+ if( other->occurs.ntimes() > 0 ) {
+ error_msg(@name, "SAME AS %s: cannot have OCCURS",
+ other->name); // 13.18.49.2,P5
+ YYERROR;
+ }
+ if( field->level == 77 and !is_elementary(other->type) ) {
+ // ISO 2023 13.18.49.2,P8
+ error_msg(@name, "%s %s SAME AS %s: must be elementary",
+ field->level_str(), field->name, other->name);
+ YYERROR;
+ }
+
+ if( (other->attr & (sign_clause_e | usage_clause_e)) > 0 ) {
+ error_msg(@name, "%s: source of SAME AS cannot have "
+ "SIGN or USAGE clause", other->name);
+ YYERROR;
+ }
+ if( other->usage == FldGroup ) {
+ error_msg(@name, "%s: source of SAME AS cannot have "
+ "GROUP-USAGE clause", other->name);
+ YYERROR;
+ }
+ if( other->has_attr(constant_e ) ) {
+ error_msg(@name, "%s: source of SAME AS cannot "
+ "be constant", other->name);
+ YYERROR;
+ }
+ if( field->parent == field_index(other) ) {
+ error_msg(@name, "%s: SAME AS uses "
+ "its own parent %s", field->name, other->name);
+ YYERROR;
+ }
+
+ auto e = symbol_field_same_as( field, other );
+ symbol_field_location( symbol_index(e), @name );
+ }
+ ;
+
+sign_clause: sign_is sign_leading sign_separate
+ {
+ cbl_field_t *field = current_field();
+ if( $sign_leading ) {
+ field->attr |= leading_e;
+ } else {
+ field->attr &= ~size_t(leading_e); // turn off in case inherited
+ field->attr |= signable_e;
+ }
+ if( $sign_separate ) field->attr |= separate_e;
+ }
+ ;
+sign_is: %empty
+ | SIGN is
+ ;
+sign_leading: LEADING { $$ = true; }
+ | TRAILING { $$ = false; }
+ ;
+sign_separate: %empty { $$ = false; }
+ | SEPARATE CHARACTER { $$ = true; }
+ | SEPARATE { $$ = true; }
+ ;
+
+/*
+ * "The effect of the TYPE clause is as though the data description identified
+ * by type-name-1 had been coded in place of the TYPE clause, excluding the
+ * level-number, name, alignment, and the GLOBAL, SELECT WHEN, and TYPEDEF
+ * clauses specified for type-name-1;"
+ *
+ * The essential characteristics of a type, which is identified by its
+ * type-name, are the:
+ * — relative positions and lengths of the elementary items
+ * — ALIGNED clause
+ * — BLANK WHEN ZERO clause
+ * — JUSTIFIED clause
+ * — PICTURE clause
+ * — SIGN clause
+ * — SYNCHRONIZED clause
+ * — USAGE clause
+ */
+type_clause: TYPE to typename
+ {
+ cbl_field_t *field = current_field();
+ if( $typename ) {
+ auto e = symbol_field_same_as(field, $typename);
+ symbol_field_location( symbol_index(e), @typename );
+ }
+ }
+ | USAGE is typename
+ {
+ if( ! dialect_mf() ) {
+ dialect_error(@typename, "USAGE TYPENAME", "mf");
+ YYERROR;
+ }
+ cbl_field_t *field = current_field();
+ if( $typename ) {
+ auto e = symbol_field_same_as(field, $typename);
+ symbol_field_location( symbol_index(e), @typename );
+ }
+ }
+ ;
+
+typedef_clause: is TYPEDEF strong
+ {
+ cbl_field_t *field = current_field();
+ switch( field->level ) {
+ case 1: case 77: break;
+ default:
+ error_msg(@2, "%s %s IS TYPEDEF must be level 01",
+ field->level_str(), field->name);
+ }
+ field->attr |= typedef_e;
+ if( $strong ) field->attr |= strongdef_e;
+ if( ! current.typedef_add(field) ) {
+ auto prior = current.has_typedef(field);
+ assert(prior);
+ error_msg(@2, "%s %s IS TYPEDEF is not unique "
+ "(see %s, line %d)",
+ field->level_str(), field->name,
+ prior->name, prior->line);
+ }
+ }
+ ;
+
+volatile_clause:
+ VOLATILE
+ {
+ if( dialect_ibm() ) {
+ yywarn("VOLATILE has no effect");
+ } else {
+ dialect_error(@1, "VOLATILE", "ibm");
+ }
+ }
+ ;
+
+procedure_div: %empty {
+ if( !procedure_division_ready(@$, NULL, NULL) ) YYABORT;
+ }
+ | PROCEDURE_DIV '.' {
+ if( !procedure_division_ready(@$, NULL, NULL) ) YYABORT;
+ } declaratives sentences
+ | PROCEDURE_DIV procedure_args '.' declaratives sentences
+ | PROCEDURE_DIV procedure_args '.'
+ ;
+
+procedure_args: USING procedure_uses[args]
+ {
+ if( !procedure_division_ready(@args, NULL, $args) ) YYABORT;
+ }
+ | USING procedure_uses[args] RETURNING name[ret]
+ {
+ if( !procedure_division_ready(@ret, $ret, $args) ) YYABORT;
+ if( ! $ret->has_attr(linkage_e) ) {
+ error_msg(@ret, "RETURNING %s is not defined in LINKAGE SECTION",
+ $ret->name);
+ }
+ }
+ | RETURNING name[ret]
+ {
+ if( !procedure_division_ready(@ret, $ret, NULL) ) YYABORT;
+ if( ! $ret->has_attr(linkage_e) ) {
+ error_msg(@ret, "RETURNING %s is not defined in LINKAGE SECTION",
+ $ret->name);
+ }
+ }
+ ;
+procedure_uses: procedure_use { $$ = new ffi_args_t($1); }
+ | procedure_uses procedure_use { $$->push_back($2); }
+ ;
+procedure_use: optional scalar {
+ $$ = new cbl_ffi_arg_t(by_default_e, $scalar);
+ $$->optional = $optional;
+ $$->validate(); // produces message
+ }
+ | by REFERENCE optional scalar {
+ $$ = new cbl_ffi_arg_t(by_reference_e, $scalar);
+ $$->optional = $optional;
+ $$->validate(); // produces message
+ }
+ | by CONTENT error { // no "by content" in procedure definition
+ $$ = new cbl_ffi_arg_t(by_content_e,
+ new_reference(literally_zero));
+ }
+ | by VALUE by_value_arg[arg] {
+ $$ = new cbl_ffi_arg_t(by_value_e, $arg);
+ $$->validate(); // produces message
+ }
+ ;
+by_value_arg: scalar
+ | LITERAL { $$ = new_reference(new_literal($1, quoted_e)); }
+ | reserved_value
+ {
+ $$ = new_reference(constant_of(constant_index($1)));
+ }
+ ;
+
+declaratives: %empty
+ | DECLARATIVES '.'
+ <label>{
+ current.enabled_exception_cache = enabled_exceptions;
+ enabled_exceptions.clear();
+ current.doing_declaratives(true);
+ $$ = label_add(LblString, "_end_declaratives", 0);
+ assert($$);
+ parser_label_goto($$);
+ } [label]
+ sentences END DECLARATIVES '.'
+ {
+ size_t ndecl = current.declaratives.as_list().size();
+ cbl_declarative_t decls[ ndecl ];
+ auto decl_list = current.declaratives.as_list();
+ std::copy( decl_list.begin(), decl_list.end(), decls );
+ std::sort( decls, decls + ndecl );
+ current.doing_declaratives(false);
+ /* TODO: if( intradeclarative_reference() ) yyerror;
+ * Test also at paragraph_reference, for non-forward
+ * reference with good line numbers. See
+ * utilcc::procedures_t and ambiguous_reference(). At this
+ * point, no reference should pick up anything except a
+ * forward reference, because we haven't yet begun to parse
+ * nondeclarative procedures.
+ */
+ parser_label_label($label);
+ enabled_exceptions = current.enabled_exception_cache;
+ current.enabled_exception_cache.clear();
+ ast_enter_section(implicit_section());
+ }
+ ;
+
+sentences: sentence {
+ ast_first_statement(@1);
+ symbol_temporaries_free();
+ }
+ | section_name
+ | paragraph_name[para] '.'
+ {
+ location_set(@para);
+ cbl_label_t *label = label_add(@para, LblParagraph, $para);
+ if( !label ) {
+ YYERROR;
+ }
+ ast_enter_paragraph(label);
+ current.new_paragraph(label);
+ apply_declaratives();
+ }
+ | sentences sentence
+ { // sentences might not be sentence
+ ast_first_statement(@2);
+ symbol_temporaries_free();
+ }
+ | sentences section_name
+ | sentences paragraph_name[para] '.'
+ {
+ location_set(@para);
+ cbl_label_t *label = label_add(@para, LblParagraph, $para);
+ if( !label ) {
+ YYERROR;
+ }
+ ast_enter_paragraph(label);
+ current.new_paragraph(label);
+ apply_declaratives();
+ }
+ ;
+paragraph_name: NAME
+ | NUMSTR { $$ = $1.string; }
+ ;
+
+sentence: statements '.'
+ | statements YYEOF
+ {
+ if( ! goodnight_gracie() ) {
+ YYABORT;
+ }
+ if( nparse_error > 0 ) YYABORT;
+ YYACCEPT;
+ }
+ | program END_SUBPROGRAM namestr[name] '.'
+ { // a contained program (no prior END PROGRAM) is a "sentence"
+ const cbl_label_t *prog = current.program();
+ assert(prog);
+ const char *name = string_of($name);
+ if( !name || 0 != strcasecmp(prog->name, name) ) {
+ error_msg(@name, "END PROGRAM '%s' does not match PROGRAM-ID '%s'",
+ name? name : $name.data, prog->name);
+ YYERROR;
+ }
+
+ 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());
+ }
+ YYERROR;
+ }
+ // pointer still valid because name is in symbol table
+ ast_end_program(prog->name);
+ }
+ | program YYEOF
+ { // a contained program (no prior END PROGRAM) is a "sentence"
+ if( nparse_error > 0 ) YYABORT;
+ do {
+ if( ! goodnight_gracie() ) YYABORT; // no recovery
+ } while( current.program_level() > 0 );
+ YYACCEPT;
+ }
+ ;
+
+statements: statement { $$ = $1; }
+ | statements statement { $$ = $2; }
+ ;
+
+statement: error {
+ if( current.declarative_section_name() ) {
+ error_msg(@1, "missing END DECLARATIVES or SECTION name",
+ nparse_error);
+ YYABORT;
+ }
+ if( max_errors_exceeded(nparse_error) ) {
+ error_msg(@1, "max errors %d reached", nparse_error);
+ YYABORT;
+ }
+ }
+ | accept { $$ = ACCEPT; }
+ | add { $$ = ADD; }
+ | allocate { $$ = ALLOCATE; }
+ | alter { $$ = ALTER; }
+ | call { $$ = CALL; }
+ | cancel { $$ = CANCEL; }
+ | close { $$ = CLOSE; }
+ | compute { $$ = COMPUTE; }
+ | continue_stmt { $$ = CONTINUE; }
+ | delete { $$ = DELETE; }
+ | display { $$ = DISPLAY; }
+ | divide { $$ = DIVIDE; }
+ | entry { $$ = ENTRY; }
+ | evaluate { $$ = EVALUATE; }
+ | exit { $$ = EXIT; }
+ | free { $$ = FREE; }
+ | go_to { $$ = GOTO; }
+ | if_stmt { $$ = IF; }
+ | initialize { $$ = INITIALIZE; }
+ | inspect { $$ = INSPECT; }
+ | merge { $$ = MERGE; }
+ | move { $$ = MOVE; }
+ | multiply { $$ = MULTIPLY; }
+ | open { $$ = OPEN; }
+ | return_stmt { $$ = RETURN; }
+ | perform { $$ = PERFORM; }
+ | raise { $$ = RAISE; }
+ | read { $$ = READ; }
+ | release { $$ = RELEASE; }
+ | resume { $$ = RESUME; }
+ | rewrite { $$ = REWRITE; }
+ | search { $$ = SEARCH; }
+ | set { $$ = SET; }
+ | sort { $$ = SORT; }
+ | start { $$ = START; }
+ | stop { $$ = STOP; }
+ | string { $$ = STRING_kw; }
+ | subtract { $$ = SUBTRACT; }
+ | unstring { $$ = UNSTRING; }
+ | write { $$ = WRITE; }
+ ;
+
+ /*
+ * ISO defines ON EXCEPTION only for Format 3 (screen). We
+ * implement extensions defined by MF and Fujitsu (and us) to
+ * use ACCEPT to interact with the command line and the
+ * environment.
+ *
+ * ISO ACCEPT and some others are implemented in accept_body,
+ * before the parser sees any ON EXCEPTION. In those cases
+ * accept_body returns accept_done_e to denote that the
+ * statement has been handled. If ON EXCEPTION is then parsed,
+ * it's an error. Otherwise, accept_body returns something
+ * else, and the relevant parser_accept_foo function is called
+ * in the "accept" action.
+ */
+accept: accept_body end_accept {
+ cbl_field_t *argi = register_find("_ARGI");
+ switch( $accept_body.func ) {
+ case accept_done_e:
+ break;
+ case accept_command_line_e:
+ if( $1.from->field == NULL ) { // take next command-line arg
+ parser_accept_command_line(*$1.into, argi, NULL, NULL);
+ cbl_num_result_t tgt { truncation_e, argi };
+ parser_add2(tgt, literally_one); // increment argi
+ } else if( $1.from->field == argi ) {
+ parser_move(*$1.into, *$1.from);
+ } else {
+ parser_accept_command_line(*$1.into, *$1.from, NULL, NULL);
+ }
+ break;
+ case accept_envar_e:
+ parser_accept_envar(*$1.into, *$1.from, NULL, NULL);
+ break;
+ }
+ }
+ | accept_body accept_excepts[ec] end_accept {
+ cbl_field_t *argi = register_find("_ARGI");
+ switch( $accept_body.func ) {
+ case accept_done_e:
+ error_msg(@ec, "ON EXCEPTION valid only "
+ "with ENVIRONMENT or COMAMND-LINE(n)");
+ break;
+ case accept_command_line_e:
+ if( $1.from->field == NULL ) { // take next command-line arg
+ parser_accept_command_line(*$1.into, argi,
+ $ec.on_error, $ec.not_error);
+ cbl_num_result_t tgt { truncation_e, argi };
+ parser_add2(tgt, literally_one); // increment argi
+ } else if( $1.from->field == argi ) {
+ parser_move(*$1.into, *$1.from);
+ if( $ec.on_error || $ec.not_error ) {
+ error_msg(@ec, "ON EXCEPTION valid only "
+ "with ENVIRONMENT or COMAMND-LINE(n)");
+ }
+ } else {
+ parser_accept_command_line(*$1.into, *$1.from,
+ $ec.on_error, $ec.not_error);
+ }
+ break;
+ case accept_envar_e:
+ parser_accept_envar(*$1.into, *$1.from,
+ $ec.on_error, $ec.not_error);
+ break;
+ }
+ }
+ ;
+end_accept: %empty %prec ACCEPT
+ | END_ACCEPT
+ ;
+
+accept_body: accept_refer
+ {
+ $$.func = accept_done_e;
+ parser_accept(*$1, CONSOLE_e);
+ }
+ | accept_refer FROM DATE
+ {
+ $$.func = accept_done_e;
+ if( $1->is_reference() ) {
+ error_msg(@1, "subscripts are unsupported here");
+ YYERROR;
+ }
+ parser_accept_date_yymmdd($1->field);
+ }
+ | accept_refer FROM DATE YYYYMMDD
+ {
+ $$.func = accept_done_e;
+ if( $1->is_reference() ) {
+ error_msg(@1, "subscripts are unsupported here");
+ YYERROR;
+ }
+ parser_accept_date_yyyymmdd($1->field);
+ }
+ | accept_refer FROM DAY
+ {
+ $$.func = accept_done_e;
+ if( $1->is_reference() ) {
+ error_msg(@1, "subscripts are unsupported here");
+ YYERROR;
+ }
+ parser_accept_date_yyddd($1->field);
+ }
+ | accept_refer FROM DAY YYYYDDD
+ {
+ $$.func = accept_done_e;
+ if( $1->is_reference() ) {
+ error_msg(@1, "subscripts are unsupported here");
+ YYERROR;
+ }
+ parser_accept_date_yyyyddd($1->field);
+ }
+ | accept_refer FROM DAY_OF_WEEK
+ {
+ $$.func = accept_done_e;
+ if( $1->is_reference() ) {
+ error_msg(@1, "subscripts are unsupported here");
+ YYERROR;
+ }
+ parser_accept_date_dow($1->field);
+ }
+
+ | accept_refer FROM TIME
+ {
+ $$.func = accept_done_e;
+ if( $1->is_reference() ) {
+ error_msg(@1, "subscripts are unsupported here");
+ YYERROR;
+ }
+ parser_accept_date_hhmmssff($1->field);
+ }
+ | accept_refer FROM acceptable
+ {
+ cbl_field_t *argc = register_find("_ARGI");
+ switch( $acceptable->id ) {
+ case ARG_NUM_e:
+ $$.func = accept_command_line_e;
+ $$.into = $1;
+ $$.from = new_reference(argc);
+ break;
+ case ARG_VALUE_e:
+ $$.func = accept_command_line_e;
+ $$.into = $1;
+ $$.from = cbl_refer_t::empty();
+ break;
+ default:
+ $$.func = accept_done_e;
+ parser_accept( *$1, $acceptable->id );
+ }
+ }
+ | accept_refer FROM ENVIRONMENT envar
+ {
+ $$.func = accept_envar_e;
+ $$.into = $1;
+ $$.from = $envar;
+ //// parser_accept_envar( *$1, *$envar );
+ }
+ | accept_refer FROM COMMAND_LINE
+ {
+ $$.func = accept_done_e;
+ parser_accept_command_line(*$1, NULL, NULL, NULL );
+ }
+ | accept_refer FROM COMMAND_LINE '(' expr ')'
+ {
+ $$.func = accept_command_line_e;
+ $$.into = $1;
+ $$.from = $expr;
+ //// parser_accept_command_line(*$1, $expr->field );
+ }
+ | accept_refer FROM COMMAND_LINE_COUNT {
+ $$.func = accept_done_e;
+ parser_accept_command_line_count(*$1);
+ }
+ ;
+
+accept_refer: ACCEPT scalar { statement_begin(@1, ACCEPT); $$ = $2; }
+ ;
+
+accept_excepts: accept_excepts[a] accept_except[b] statements %prec ACCEPT
+ {
+ if( $a.on_error && $a.not_error ) {
+ error_msg(@b, "too many ON EXCEPTION clauses");
+ YYERROR;
+ }
+ // "ON" and "NOT ON" could be reversed, but not duplicated.
+ if( $a.on_error && $b.on_error ) {
+ error_msg(@b, "duplicate ON EXCEPTION clauses");
+ YYERROR;
+ }
+ if( $a.not_error && $b.not_error ) {
+ error_msg(@b, "duplicate NOT ON EXCEPTION clauses");
+ YYERROR;
+ }
+ $$ = $a;
+ if( $b.on_error ) {
+ $$.on_error = $b.on_error;
+ assert($a.not_error);
+ } else {
+ $$.not_error = $b.not_error;
+ assert($a.on_error);
+ }
+ assert( $b.on_error || $b.not_error );
+ assert( ! ($b.on_error && $b.not_error) );
+ cbl_label_t *tgt = $b.on_error? $b.on_error : $b.not_error;
+ parser_accept_exception_end(tgt);
+ }
+ | accept_except[a] statements %prec ACCEPT
+ {
+ $$ = $a;
+ assert( $a.on_error || $a.not_error );
+ assert( ! ($a.on_error && $a.not_error) );
+ cbl_label_t *tgt = $a.on_error? $a.on_error : $a.not_error;
+ parser_accept_exception_end(tgt);
+ }
+ ;
+
+accept_except: EXCEPTION
+ {
+ $$.not_error = NULL;
+ $$.on_error = label_add(LblArith,
+ uniq_label("accept"), yylineno);
+ if( !$$.on_error ) YYERROR;
+ parser_accept_exception( $$.on_error );
+
+ assert( $1 == EXCEPTION || $1 == NOT );
+ if( $1 == NOT ) {
+ std::swap($$.on_error, $$.not_error);
+ }
+ }
+ ;
+
+envar: scalar { $$ = $1; $$->field->attr |= envar_e; }
+ | LITERAL {
+ $$ = new_reference(new_literal($1, quoted_e));
+ $$->field->attr |= envar_e;
+ }
+ ;
+
+acceptable: device_name
+ {
+ $$ = symbol_special( $1.id );
+ if( !$$ ) {
+ error_msg(@1, "no such environment name");
+ YYERROR;
+ }
+ }
+ | NAME
+ {
+ $$ = special_of($1);
+ if( !$$ ) {
+ error_msg(@NAME, "no such environment mnemonic name: %s", $NAME);
+ YYERROR;
+ }
+ }
+ ;
+
+add: add_impl end_add { ast_add($1); }
+ | add_cond end_add { ast_add($1); }
+ ;
+add_impl: ADD add_body
+ {
+ statement_begin(@1, ADD);
+ $$ = $2;
+ }
+ ;
+add_cond: ADD add_body[body] arith_errs[err]
+ {
+ statement_begin(@1, ADD);
+ $body->on_error = $err.on_error;
+ $body->not_error = $err.not_error;
+ $$ = $body;
+ }
+ ;
+end_add: %empty %prec ADD
+ | END_ADD
+ ;
+
+add_body: sum TO rnames
+ {
+ $$ = new arith_t(no_giving_e, $sum);
+ std::copy( rhs.begin(),
+ rhs.end(), back_inserter($$->tgts) );
+ rhs.clear();
+ }
+ | sum TO num_operand[value] GIVING rnames
+ {
+ $$ = new arith_t(giving_e, $sum);
+ $$->A.push_back(*$value);
+ std::copy( rhs.begin(),
+ rhs.end(), back_inserter($$->tgts) );
+ rhs.clear();
+ }
+ | sum GIVING rnames
+ { // implicit TO
+ $$ = new arith_t(giving_e, $sum);
+ std::copy( rhs.begin(),
+ rhs.end(), back_inserter($$->tgts) );
+ rhs.clear();
+ }
+ | CORRESPONDING sum TO rnames
+ {
+ corresponding_fields_t pairs =
+ corresponding_arith_fields( $sum->refers.front().field,
+ rhs.front().refer.field );
+ if( pairs.empty() ) {
+ yywarn( "%s and %s have no corresponding fields",
+ $sum->refers.front().field->name,
+ rhs.front().refer.field->name );
+ }
+ // First src/tgt elements are templates.
+ // Their subscripts apply to the correspondents.
+ $$ = new arith_t(corresponding_e, $sum);
+ $$->tgts.push_front(rhs.front());
+ // use arith_t functor to populate A and tgts
+ *$$ = std::for_each( pairs.begin(), pairs.end(), *$$ );
+ $$->A.pop_front();
+ $$->tgts.pop_front();
+ rhs.clear();
+ }
+ ;
+
+rounded: %empty { $$ = truncation_e; }
+ | ROUNDED { $$ = current_rounded_mode(); }
+ | ROUNDED rounded_mode { $$ = rounded_of($rounded_mode); }
+ ;
+rounded_mode: MODE is rounded_type { $$ = $rounded_type; }
+ ;
+rounded_type: AWAY_FROM_ZERO { $$ = away_from_zero_e; }
+ | NEAREST_TOWARD_ZERO { $$ = nearest_toward_zero_e; }
+ | TOWARD_GREATER { $$ = toward_greater_e; }
+ | TOWARD_LESSER { $$ = toward_lesser_e; }
+ | round_between
+ ;
+round_between: NEAREST_AWAY_FROM_ZERO { $$ = nearest_away_from_zero_e; }
+ | NEAREST_EVEN { $$ = nearest_even_e; }
+ | PROHIBITED { $$ = prohibited_e; }
+ | TRUNCATION { $$ = truncation_e; }
+ ;
+
+might_be: %empty { $$ = IS; }
+ | MIGHT_BE
+ ;
+
+posneg: POSITIVE { $$ = $1 == NOT? le_op : gt_op; }
+ | NEGATIVE { $$ = $1 == NOT? ge_op : lt_op; }
+ | ZERO { $$ = $1 == NOT? ne_op : eq_op; }
+ ;
+
+scalar88s: scalar88 { $$ = new refer_list_t($1); }
+ | scalar88s scalar88 { $1->push_back($2); }
+ ;
+
+name88: NAME88 {
+ name_queue.qualify(@1, $1);
+ auto namelocs( name_queue.pop() );
+ auto names( name_queue.namelist_of(namelocs) );
+ if( ($$ = field_find(names)) == NULL ) {
+ if( procedure_div_e == current_division ) {
+ error_msg(namelocs.back().loc,
+ "DATA-ITEM '%s' not found", names.back() );
+ YYERROR;
+ }
+ }
+ assert($$->level == 88);
+ }
+ ;
+
+scalar88: name88 subscripts[subs] refmod[ref]
+ {
+ size_t n = $subs->size();
+ auto subscripts = new cbl_refer_t[n];
+ $subs->use_list(subscripts);
+ if( $ref.from->is_reference() || $ref.len->is_reference() ) {
+ error_msg(@subs, "subscripts on start:len refmod "
+ "parameters are unsupported");
+ YYERROR;
+ }
+ cbl_span_t span( $ref.from, $ref.len );
+ $$ = new cbl_refer_t($1, n, subscripts, span);
+ }
+ | name88 refmod[ref]
+ {
+ if( $ref.from->is_reference() || $ref.len->is_reference() ) {
+ error_msg(@ref, "subscripts on start:len refmod "
+ "parameters are unsupported");
+ YYERROR;
+ }
+ cbl_span_t span( $ref.from, $ref.len );
+ $$ = new cbl_refer_t($1, span);
+ }
+ | name88 subscripts[subs]
+ {
+ $$ = new cbl_refer_t($1);
+ if( $subs->refers.size() != $$->subscripts_set($subs->refers) ) {
+ subscript_dimension_error(@subs, $subs->refers.size(), $$);
+ }
+ }
+ | name88
+ {
+ $$ = new_reference($1);
+ }
+ ;
+
+allocate: ALLOCATE expr[size] CHARACTERS initialized RETURNING scalar[returning]
+ {
+ statement_begin(@1, ALLOCATE);
+ if( $size->field->type == FldLiteralN ) {
+ if( $size->field->data.value <= 0 ) {
+ error_msg(@size, "size must be greater than 0");
+ YYERROR;
+ }
+ }
+ reject_refmod( @returning, *$returning );
+ if( ! require_pointer(@returning, *$returning) ) YYERROR;
+ parser_allocate( *$size, *$returning, $initialized );
+ }
+ | ALLOCATE scalar[based] initialized alloc_ret[returning]
+ {
+ statement_begin(@1, ALLOCATE);
+ if( ! $based->field->has_attr(based_e) ) {
+ error_msg(@based, "%s must be BASED", $based->name());
+ YYERROR;
+ }
+ reject_refmod( @based, *$based );
+ reject_refmod( @returning, *$returning );
+ if( $returning->field &&
+ ! require_pointer(@returning, *$returning) ) YYERROR;
+ parser_allocate( *$based, *$returning, $initialized );
+ if( $initialized ) {
+ initialize_allocated(*$based);
+ }
+ }
+ ;
+initialized: %empty { $$ = false; }
+ | INITIALIZED { $$ = true; }
+ ;
+alloc_ret: %empty { static cbl_refer_t empty; $$ = &empty; }
+ | RETURNING scalar[name] { $$ = $name; }
+ ;
+
+compute: compute_impl end_compute { current.compute_end(); }
+ | compute_cond end_compute { current.compute_end(); }
+ ;
+compute_impl: COMPUTE compute_body[body]
+ {
+ parser_assign( $body.ntgt, $body.tgts, *$body.expr,
+ NULL, NULL, current.compute_label() );
+ current.declaratives_evaluate(ec_none_e);
+ }
+ ;
+compute_cond: COMPUTE compute_body[body] arith_errs[err]
+ {
+ parser_assign( $body.ntgt, $body.tgts, *$body.expr,
+ $err.on_error, $err.not_error,
+ current.compute_label() );
+ current.declaratives_evaluate(ec_size_e);
+ }
+ ;
+end_compute: %empty %prec COMPUTE
+ | END_COMPUTE
+ ;
+
+compute_body: rnames { statement_begin(@$, COMPUTE); } compute_expr[expr] {
+ $$.ntgt = rhs.size();
+ auto C = new cbl_num_result_t[$$.ntgt];
+ $$.tgts = use_any(rhs, C);
+ $$.expr = $expr;
+ }
+ ;
+compute_expr: '=' {
+ current.compute_begin();
+ } expr {
+ $$ = $expr;
+ }
+ ;
+ | EQUAL {
+ if( ! dialect_ibm() ) {
+ dialect_error(@1, "EQUAL invalid as assignment operator", "ibm");
+ }
+ current.compute_begin();
+ } expr {
+ $$ = $expr;
+ }
+ ;
+
+display: disp_body end_display
+ {
+ size_t len = $1.vargs->args.size();
+ struct cbl_refer_t args[len];
+
+ if( $1.special && $1.special->id == ARG_NUM_e ) {
+ if( $1.vargs->args.size() != 1 ) {
+ error_msg(@1, "ARGUMENT-NUMBER can be set to only one value");
+ }
+ cbl_refer_t& src( $1.vargs->args.front() );
+ cbl_field_t *dst = register_find("_ARGI");
+ parser_move( dst, src );
+ } else {
+ parser_display($1.special, use_vargs($1.vargs, args), len,
+ DISPLAY_ADVANCE);
+ }
+ current.declaratives_evaluate(ec_none_e);
+ }
+ | disp_body NO ADVANCING end_display
+ {
+ size_t len = $1.vargs->args.size();
+ struct cbl_refer_t args[len];
+
+ if( $1.special && $1.special->id == ARG_NUM_e ) {
+ if( $1.vargs->args.size() != 1 ) {
+ error_msg(@1, "ARGUMENT-NUMBER can be set to only one value");
+ }
+ cbl_refer_t& src( $1.vargs->args.front() );
+ cbl_field_t *dst = register_find("_ARGI");
+ parser_move( dst, src );
+ } else {
+ parser_display($1.special, use_vargs($1.vargs, args), len,
+ DISPLAY_NO_ADVANCE);
+ }
+ current.declaratives_evaluate(ec_none_e);
+ }
+ ;
+end_display: %empty
+ | END_DISPLAY
+ ;
+disp_body: disp_vargs[vargs]
+ {
+ $$.special = NULL;
+ $$.vargs = $vargs;
+ }
+ | disp_vargs[vargs] UPON disp_target[special]
+ {
+ $$.special = $special;
+ $$.vargs = $vargs;
+ }
+ ;
+disp_vargs: DISPLAY vargs {
+ statement_begin(@1, DISPLAY);
+ $$ = $vargs;
+ }
+ ;
+
+disp_target: device_name {
+ $$ = symbol_special($1.id);
+ }
+ | NAME
+ {
+ symbol_elem_t *e = symbol_special(PROGRAM, $1);
+ if( !e ) {
+ error_msg(@NAME, "no such special name '%s'", $NAME);
+ YYERROR;
+ }
+ $$ = cbl_special_name_of(e);
+ }
+ ;
+
+divide: divide_impl end_divide { ast_divide($1); }
+ | divide_cond end_divide { ast_divide($1); }
+ ;
+
+divide_impl: DIVIDE divide_body[body]
+ {
+ statement_begin(@1, DIVIDE);
+ $$ = $body;
+ }
+ ;
+divide_cond: DIVIDE divide_body[body] arith_errs[err]
+ {
+ statement_begin(@1, DIVIDE);
+ $$ = $body;
+ $$->on_error = $err.on_error;
+ $$->not_error = $err.not_error;
+ }
+ ;
+end_divide: %empty %prec DIVIDE
+ | END_DIVIDE
+ ;
+
+divide_body: num_operand INTO rnames
+ { /* format 1 */
+ $$ = new arith_t(no_giving_e);
+ $$->A.push_back(*$num_operand);
+ std::copy( rhs.begin(),
+ rhs.end(), back_inserter($$->tgts) );
+ rhs.clear();
+ }
+ | divide_into
+ | divide_into REMAINDER scalar[rem]
+ {
+ if( $1->tgts.size() != 1 ) {
+ error_msg(@1, "only 1 (not %zu) "
+ "GIVING with REMAINDER", $1->tgts.size());
+ YYERROR;
+ }
+ $$ = $1;
+ $$->remainder = *$rem;
+ }
+ | divide_by
+ | divide_by REMAINDER scalar[rem]
+ {
+ if( $1->tgts.size() != 1 ) {
+ error_msg(@1, "only 1 (not %zu) "
+ "GIVING with REMAINDER", $1->tgts.size());
+ YYERROR;
+ }
+ $$ = $1;
+ $$->remainder = *$rem;
+ }
+ ;
+
+divide_into: num_operand[b] INTO num_operand[a] GIVING rnames
+ { // format 2 & 4
+ $$ = new arith_t(giving_e);
+ $$->A.push_back(*$a);
+ $$->B.push_back(*$b);
+ std::copy( rhs.begin(),
+ rhs.end(), back_inserter($$->tgts) );
+ rhs.clear();
+ }
+ ;
+divide_by: num_operand[a] BY num_operand[b] GIVING rnames
+ { // format 3 & 5
+ $$ = new arith_t(giving_e);
+ $$->A.push_back(*$a);
+ $$->B.push_back(*$b);
+ std::copy( rhs.begin(),
+ rhs.end(), back_inserter($$->tgts) );
+ rhs.clear();
+ }
+ ;
+
+end_program: end_program1[end] '.'
+ {
+ const cbl_label_t *prog = current.program();
+ assert(prog);
+ const char *name = string_of($end.name);
+
+ bool matches = false;
+ const char *token_name = keyword_str($end.token) + 4;
+ switch($end.token) {
+ case END_PROGRAM:
+ matches = prog->type == LblProgram;
+ break;
+ case END_FUNCTION:
+ matches = prog->type == LblFunction;
+ break;
+ default:
+ error_msg(@end, "logic error: END token invalid '%s'", name);
+ gcc_unreachable();
+ }
+ if( !matches ) {
+ error_msg(@end, "END %s %s' does not match IDENTIFICATION DIVISION '%s'",
+ token_name, name, prog->name);
+ YYERROR;
+ }
+
+ if( 0 != strcasecmp(prog->name, name) ) {
+ error_msg(@end, "END PROGRAM '%s' does not match PROGRAM-ID '%s'",
+ name, prog->name);
+ YYERROR;
+ }
+ std::set<std::string> externals = current.end_program();
+ if( !externals.empty() ) {
+ for( auto name : externals ) {
+ yywarn("%s calls external symbol '%s'", prog->name, name.c_str());
+ }
+ YYERROR;
+ }
+ // pointer still valid because name is in symbol table
+ ast_end_program(prog->name);
+ }
+ | end_program1[end] error
+ {
+ const char *token_name = "???";
+ switch($end.token) {
+ case END_PROGRAM:
+ token_name = "PROGRAM";
+ break;
+ case END_FUNCTION:
+ token_name = "FUNCTION";
+ break;
+ default:
+ cbl_internal_error( "END token invalid");
+ }
+ error_msg(@end, "END %s requires NAME before '.'", token_name);
+ YYERROR;
+ }
+ ;
+end_program1: END_PROGRAM namestr[name]
+ {
+ $$.token = END_PROGRAM;
+ $$.name = $name;
+ }
+ | END_FUNCTION namestr[name]
+ {
+ $$.token = END_FUNCTION;
+ $$.name = $name;
+ }
+ | END_PROGRAM '.' // error
+ {
+ $$.token = END_PROGRAM;
+ }
+ | END_FUNCTION '.' // error
+ {
+ $$.token = END_FUNCTION;
+ }
+ ;
+
+continue_stmt: CONTINUE {
+ statement_begin(@1, CONTINUE);
+ parser_sleep(*cbl_refer_t::empty());
+ }
+ | CONTINUE AFTER expr SECONDS {
+ statement_begin(@1, CONTINUE);
+ parser_sleep(*$expr);
+ }
+ ;
+
+exit: GOBACK exit_with[status]
+ {
+ statement_begin(@1, GOBACK);
+ parser_exit(*$status);
+ }
+ | GOBACK exit_raising[ec]
+ {
+ statement_begin(@1, GOBACK);
+ parser_exit(*cbl_refer_t::empty(), $ec);
+ }
+ | EXIT { statement_begin(@1, EXIT); } exit_what
+ | SIMPLE_EXIT
+ {
+ error_msg(@1, "EXIT is invalid here");
+ }
+ ;
+ /* Valid "simple" EXIT (Format 1) swallowed by lexer */
+
+ /*
+ * If the EXIT PROGRAM statement is executed in a program that
+ * is not under the control of a calling runtime element, the
+ * EXIT PROGRAM statement is treated as if it were a CONTINUE
+ * statement.
+ * To indicate this, We pass a "magic" refer with prog_func set.
+ */
+exit_with: %empty
+ {
+ /* "If a RETURNING phrase is specified in the procedure
+ * division header of the program containing the GOBACK
+ * statement, the value in the data item referenced by that
+ * RETURNING phrase becomes the result of the program
+ * activation. Execution continues in the calling element
+ * as specified in the rules."
+ */
+ $$ = cbl_refer_t::empty();
+ if( dialect_ibm() ) {
+ static auto rt = cbl_field_of(symbol_at(return_code_register()));
+ static cbl_refer_t status(rt);
+ $$ = &status;
+ }
+ auto prog = cbl_label_of(symbol_at(current_program_index()));
+ if( prog->returning ) {
+ $$ = new cbl_refer_t( cbl_field_of(symbol_at(prog->returning)) );
+ }
+ }
+ | with NORMAL stop_status
+ {
+ $$ = $stop_status? $stop_status : new_reference(literally_zero);
+ }
+ | with ERROR stop_status
+ {
+ $$ = $stop_status? $stop_status : new_reference(literally_one);
+ }
+ | RETURNING stop_status
+ {
+ if( ! dialect_mf() ) {
+ dialect_error(@2, "RETURNING <number>", "mf");
+ }
+ $$ = $stop_status? $stop_status : new_reference(literally_one);
+ }
+ ;
+exit_what: PROGRAM_kw { parser_exit_program(); }
+ | PROGRAM_kw exit_raising[ec] { parser_exit_program(); }
+ | SECTION { parser_exit_section(); }
+ | PARAGRAPH { parser_exit_paragraph(); }
+ | PERFORM {
+ if( performs.empty() ) {
+ error_msg(@$, "EXIT PERFORM valid only "
+ "within inline PERFORM procedure" );
+ YYERROR;
+ }
+ parser_exit_perform(&perform_current()->tgt, $1);
+ }
+ ;
+
+exit_raising: RAISING EXCEPTION EXCEPTION_NAME[ec]
+ {
+ $$ = $ec;
+ }
+ | RAISING error {
+ cbl_unimplemented("RAISING exception-object");
+ $$ = ec_none_e;
+ }
+ | RAISING LAST /* lexer swallows EXCEPTION */
+ {
+ $$ = ec_all_e;
+ }
+ ;
+
+free: FREE free_tgts
+ {
+ size_t n = $free_tgts->size();
+ assert( n > 0 );
+ auto tgts = new cbl_refer_t[n];
+ parser_free( n, $free_tgts->use_list(tgts) );
+ }
+ ;
+free_tgts: free_tgt { $$ = new refer_list_t($1); }
+ | free_tgts free_tgt { $$->push_back($2); }
+ ;
+free_tgt: scalar {
+ $$ = $1;
+ reject_refmod(@scalar, *$1);
+ }
+ | ADDRESS OF scalar[name]
+ {
+ $$ = $name;
+ $$->addr_of = true;
+ reject_refmod(@name, *$name);
+ }
+ ;
+
+ /*
+ * Conditional Expressions
+ */
+simple_cond: kind_of_name
+ {
+ $$ = new_reference($1);
+ }
+ | SWITCH
+ {
+ $$ = new_reference(new_temporary(FldConditional));
+ cbl_field_t *field = cbl_field_of(symbol_find(@1, $1));
+ assert(field->type == FldSwitch);
+ cbl_field_t *parent = parent_of(field);
+ size_t value = field->data.upsi_mask->value;
+ bitop_t op = field->data.upsi_mask->on_off?
+ bit_on_op : bit_off_op;
+ parser_bitop($$->cond(), parent, op, value );
+ }
+ | expr is CLASS_NAME[domain]
+ {
+ $$ = new_reference(new_temporary(FldConditional));
+ // symbol_find does not find FldClass symbols
+ struct symbol_elem_t *e = symbol_field(PROGRAM, 0, $domain);
+ parser_setop($$->cond(), $1->field, is_op, cbl_field_of(e));
+ }
+ | expr NOT CLASS_NAME[domain] {
+ $$ = new_reference(new_temporary(FldConditional));
+ // symbol_find does not find FldClass symbols
+ struct symbol_elem_t *e = symbol_field(PROGRAM, 0, $domain);
+ parser_setop($$->cond(), $1->field, is_op, cbl_field_of(e));
+ parser_logop($$->cond(), NULL, not_op, $$->cond());
+ }
+ | expr is OMITTED
+ {
+ auto lhs = cbl_refer_t($expr->field);
+ lhs.addr_of = true;
+ auto rhs = cbl_field_of(symbol_field(0,0, "NULLS"));
+ $$ = new_reference(new_temporary(FldConditional));
+ parser_relop($$->field, lhs, eq_op, rhs);
+ }
+ | expr NOT OMITTED
+ {
+ auto lhs = cbl_refer_t($expr->field);
+ lhs.addr_of = true;
+ auto rhs = cbl_field_of(symbol_field(0,0, "NULLS"));
+ $$ = new_reference(new_temporary(FldConditional));
+ parser_relop($$->field, lhs, ne_op, rhs);
+ }
+ | expr posneg[op] {
+ $$ = new_reference(new_temporary(FldConditional));
+ relop_t op = static_cast<relop_t>($op);
+ cbl_field_t *zero = constant_of(constant_index(ZERO));
+ parser_relop($$->cond(), *$1, op, zero);
+ }
+ | scalar88 {
+ // copy the subscripts and set the parent field
+ cbl_refer_t parent = *$scalar88;
+ parent.field = parent_of($scalar88->field);
+ if( !parent.field ) {
+ cbl_internal_error("Type 88 has no referent");
+ YYERROR;
+ }
+ $$ = new_reference(new_temporary(FldConditional));
+ $$->field->parent = field_index($scalar88->field);
+ parser_relop($$->cond(), parent, eq_op, *$scalar88);
+ }
+ ;
+
+kind_of_name: expr might_be variable_type
+ {
+ $$ = new_temporary(FldConditional);
+ enum classify_t type = classify_of($3);
+ assert(type != ClassInvalidType );
+
+ parser_classify( $$, *$1, type );
+ if( $2 == NOT ) {
+ parser_logop($$, NULL, not_op, $$);
+ }
+ }
+ ;
+
+bool_expr: log_expr { $$ = new_reference($1->resolve()); }
+ ;
+
+log_expr: log_term { $$ = new log_expr_t($1); } %prec AND
+ | log_expr[lhs] OR rel_abbr[rhs]
+ {
+ $$ = $1;
+ $$->or_term($rhs);
+ }
+ | log_expr[lhs] OR log_expr[rhs]
+ {
+ $$ = $lhs;
+ assert( ! $rhs->unresolved() ); // what to do?
+ $$->or_term($rhs->and_term());
+ }
+ | log_expr[lhs] AND rel_abbr[rhs]
+ {
+ $$ = $1;
+ $$->and_term($rhs);
+ }
+ | log_expr[lhs] AND log_expr[rhs]
+ {
+ $$ = $lhs;
+ assert( ! $rhs->unresolved() ); // what to do?
+ $$->and_term($rhs->and_term());
+ }
+ ;
+
+log_term: '(' log_expr ')' {
+ current.antecedent_reset();
+ $$ = $log_expr->resolve();
+ }
+ | NOT '(' log_expr ')' {
+ current.antecedent_reset();
+ $$ = $log_expr->resolve();
+ parser_logop($$, NULL, not_op, $$);
+ }
+ | rel_expr
+ | simple_cond {
+ current.antecedent_reset();
+ $$ = $1->cond();
+ }
+ | NOT simple_cond {
+ current.antecedent_reset();
+ $$ = $2->cond();
+ parser_logop($$, NULL, not_op, $$);
+ }
+ ;
+
+rel_expr: rel_lhs rel_term[rhs]
+ {
+ rel_part_t& ante = current.antecedent();
+ if( $rhs.invert ) {
+ error_msg(@rhs, "NOT %s is invalid, cannot negate RHS",
+ ante.operand->field->name);
+ }
+ auto op = ante.relop;
+ if( ante.invert ) {
+ op = relop_invert(op);
+ ante.invert = false;
+ }
+ auto cond = new_temporary(FldConditional);
+ parser_relop( cond, *ante.operand, op, *$rhs.term );
+ $$ = cond;
+ }
+ | rel_lhs[lhs] '(' rel_abbrs ')' {
+ $$ = $rel_abbrs->resolve();
+ }
+ ;
+
+rel_abbrs: rel_abbr { $$ = new log_expr_t($1); }
+ | '(' rel_abbrs ')' {
+ $$ = $2;
+ $$->resolve();
+
+ }
+ | rel_abbrs OR rel_abbr[rhs] {
+ $$ = $1;
+ $$->or_term($rhs);
+ }
+ | rel_abbrs OR '(' rel_abbr[rhs] ')' {
+ $$ = $1;
+ $$->or_term($rhs);
+ }
+ | rel_abbrs AND rel_abbr[rhs] {
+ $$ = $1;
+ $$->and_term($rhs);
+ }
+ | rel_abbrs AND '(' rel_abbr[rhs] ')' {
+ $$ = $1;
+ $$->and_term($rhs);
+ }
+ ;
+
+rel_lhs: rel_term[lhs] relop {
+ // no value, just set current antecedent
+ auto op = relop_of($relop);
+ auto ante = new rel_part_t($lhs.term, op, $lhs.invert);
+ current.antecedent(*ante);
+ }
+ ;
+
+rel_abbr: rel_term {
+ static rel_part_t ante;
+ ante = current.antecedent();
+ if( ! ante.operand ) {
+ error_msg(@1, "'AND %s' invalid because "
+ "LHS is not a relation condition",
+ name_of($rel_term.term->field) );
+ YYERROR;
+ }
+ assert(ante.has_relop);
+ if( $rel_term.invert ) ante.relop = relop_invert(ante.relop);
+ auto cond = new_temporary(FldConditional);
+ parser_relop(cond, *ante.operand, ante.relop, *$rel_term.term);
+ $$ = cond;
+ }
+ | relop rel_term {
+ static rel_part_t ante;
+ if( $rel_term.invert ) {
+ error_msg(@2, "%s NOT %s is invalid",
+ keyword_str($relop),
+ name_of($rel_term.term->field));
+ }
+ auto op( relop_of($relop) );
+ ante = current.antecedent().relop_set(op);
+ if( ! ante.operand ) {
+ error_msg(@1, "AND %s invalid because "
+ "LHS is not a relation condition",
+ name_of($rel_term.term->field) );
+ YYERROR;
+ }
+ auto cond = new_temporary(FldConditional);
+ parser_relop(cond, *ante.operand, ante.relop, *$rel_term.term);
+ $$ = cond;
+ }
+ ;
+
+rel_term: rel_term1
+ ;
+
+rel_term1: all LITERAL
+ {
+ $$.invert = false;
+ $$.term = new_reference(new_literal($2, quoted_e));
+ $$.term->all = $all;
+ }
+ | all spaces_etc[value]
+ {
+ $$.invert = false;
+ $$.term = new_reference(constant_of(constant_index($value)));
+ $$.term->all = $all;
+ }
+ | all NULLS
+ {
+ $$.invert = false;
+ $$.term = new_reference(constant_of(constant_index(NULLS)));
+ $$.term->all = $all;
+ }
+ | ALL ZERO
+ { // ZERO without ALL comes from expr, from num_term.
+ $$.invert = false;
+ $$.term = new_reference(constant_of(constant_index(ZERO)));
+ $$.term->all = true;
+ }
+ | expr {
+ $$.invert = false;
+ $$.term = $1;
+ }
+ | NOT rel_term {
+ $$ = $2;
+ $$.invert = true;
+ }
+ ;
+
+expr: expr_term
+ ;
+expr_term: expr_term '+' num_term
+ {
+ if( ($$ = ast_op($1, '+', $3)) == NULL ) YYERROR;
+ }
+ | expr_term '-' num_term
+ {
+ if( ($$ = ast_op($1, '-', $3)) == NULL ) YYERROR;
+ }
+ | num_term
+ ;
+
+num_term: num_term '*' value
+ {
+ if( ($$ = ast_op($1, '*', $3)) == NULL ) YYERROR;
+ }
+ | num_term '/' value
+ {
+ if( ($$ = ast_op($1, '/', $3)) == NULL ) YYERROR;
+ }
+ | value
+ ;
+
+value: value POW factor
+ {
+ if( ($$ = ast_op($1, '^', $3)) == NULL ) YYERROR;
+ }
+ | '-' value %prec NEG { $$ = negate( $2 );}
+ | '+' factor %prec NEG { $$ = $2;}
+ | factor[rhs]
+ ;
+
+factor: '(' expr ')' { $$ = $2; }
+ | num_value { $$ = $num_value; }
+ ;
+
+if_stmt: if_impl end_if
+ ;
+
+if_impl: if_verb if_test if_body
+ {
+ parser_fi();
+ }
+ ;
+if_verb: IF { statement_begin(@1, IF); }
+ ;
+if_test: bool_expr then
+ {
+ if( ! is_conditional($bool_expr) ) {
+ error_msg(@1, "%s is not a Boolean expression",
+ name_of($bool_expr->field) );
+ YYERROR;
+ }
+ parser_if( $bool_expr->cond() );
+ }
+ ;
+
+if_body: next_statements
+ {
+ parser_else();
+ }
+ | next_statements ELSE {
+ location_set(@2);
+ parser_else();
+ } next_statements
+ ;
+
+next_statements: statements %prec ADD
+ | NEXT SENTENCE %prec ADD
+ {
+ next_sentence = label_add(LblNone, "next_sentence", 0);
+ parser_label_goto(next_sentence);
+ }
+ ;
+
+end_if: %empty %prec ADD
+ | END_IF
+ ;
+
+evaluate: eval_verb eval_subjects eval_switch end_evaluate {
+ auto& ev( eval_stack.current() );
+ parser_label_label(ev.when());
+ parser_label_label(ev.done());
+ eval_stack.free();
+ }
+ ;
+eval_verb: EVALUATE {
+ statement_begin(@1, EVALUATE);
+ eval_stack.alloc();
+ }
+ ;
+
+eval_subjects: eval_subject
+ | eval_subjects ALSO eval_subject
+ ;
+eval_subject: eval_subject1 {
+ auto& ev( eval_stack.current() );
+ ev.append(*$1);
+ }
+ ;
+eval_subject1: bool_expr
+ | expr
+ | true_false
+ {
+ static cbl_field_t *zero = constant_of(constant_index(ZERO));
+ enum relop_t op = $1 == TRUE_kw? eq_op : ne_op;
+ $$ = new cbl_refer_t( new_temporary(FldConditional) );
+ parser_relop($$->field, zero, op, zero);
+ }
+ ;
+
+eval_switch: eval_cases
+ | eval_cases WHEN OTHER {
+ auto& ev( eval_stack.current() );
+ ev.write_when_label();
+ }
+ statements %prec ADD
+ ;
+
+eval_cases: eval_case
+ | eval_cases eval_case
+ ;
+
+eval_case: eval_objects statements %prec ADD {
+ auto& ev( eval_stack.current() );
+ parser_label_goto( ev.done() );
+ ev.rewind();
+ }
+ | eval_objects NEXT SENTENCE %prec ADD
+ {
+ auto& ev( eval_stack.current() );
+ ev.write_when_label();
+ next_sentence = label_add(LblNone, "next_sentence", 0);
+ parser_label_goto(next_sentence);
+ }
+ ;
+
+eval_objects: eval_whens {
+ auto& ev( eval_stack.current() );
+ // Place the object's Yeah label before the statements.
+ ev.write_yeah_label();
+ }
+ ;
+eval_whens: eval_when
+ | eval_whens eval_when
+ ;
+
+eval_when: WHEN {
+ auto& ev( eval_stack.current() );
+ ev.write_when_label();
+ }
+ eval_obj_cols %prec ADD { // all TRUE, go to statements
+ auto& ev( eval_stack.current() );
+ parser_label_goto(ev.yeah());
+ auto subj( ev.subject() );
+ if( subj ) {
+ error_msg(@2, "WHEN clause incomplete, %zu of %zu evaluated",
+ ev.object_count(), ev.subject_count());
+ }
+ ev.rewind();
+ }
+ | WHEN error
+ ;
+
+eval_obj_cols: eval_obj_col
+ | eval_obj_cols ALSO eval_obj_col
+ ;
+
+eval_obj_col: ANY {
+ auto& ev( eval_stack.current() );
+ if( ! ev.decide(ANY) ) {
+ error_msg(@1, "WHEN 'ANY' phrase exceeds subject set count of %zu",
+ ev.subject_count());
+ YYERROR;
+ }
+ }
+ | true_false {
+ auto& ev( eval_stack.current() );
+ auto subj( ev.subject() );
+ if( !subj ) {
+ error_msg(@$, "WHEN '%s' phrase exceeds subject set count of %zu",
+ keyword_str($1), ev.subject_count());
+ YYERROR;
+ }
+ if( ! is_conditional( subj ) ) {
+ error_msg(@1, "subject %s, type %s, "
+ "cannot be compared to TRUE/FALSE",
+ subj->name, 3 + cbl_field_type_str(subj->type) );
+ }
+ ev.decide($1);
+ }
+ | eval_posneg[op] {
+ relop_t op = static_cast<relop_t>($op);
+ cbl_field_t *zero = constant_of(constant_index(ZERO));
+ auto& ev( eval_stack.current() );
+ auto subj( ev.subject() );
+ if( !subj ) {
+ error_msg(@1, "WHEN '%s' phrase exceeds subject set count of %zu",
+ relop_str(op), ev.subject_count());
+ YYERROR;
+ }
+ ev.decide(op, zero, false);
+ }
+ | bool_expr {
+ auto& ev( eval_stack.current() );
+ auto subj( ev.subject() );
+ if( !subj ) {
+ error_msg(@1, "WHEN CONDITIONAL phrase exceeds "
+ "subject set count of %zu",
+ ev.subject_count());
+ YYERROR;
+ }
+ if( ! is_conditional( subj ) ) {
+ error_msg(@1, "subject %s, type %s, "
+ "cannot be compared to conditional expression",
+ subj->name, 3 + cbl_field_type_str(subj->type) );
+ }
+ ev.decide(*$1, false);
+ }
+ | eval_abbrs {
+ auto& ev( eval_stack.current() );
+ ev.decided( $1->resolve() );
+ }
+ | rel_term[a] THRU rel_term[b] %prec THRU {
+ auto& ev( eval_stack.current() );
+ auto subj( ev.subject() );
+ if( !subj ) {
+ error_msg(@a, "WHEN %s THRU %s phrase exceeds "
+ "subject set count of %zu",
+ $a.term->name(), $b.term->name(), ev.subject_count());
+ YYERROR;
+ }
+ if( is_conditional($a.term) || is_conditional($b.term) ) {
+ error_msg(@a, "THRU with boolean operand");
+ }
+ if( $b.invert ) {
+ error_msg(@b, "NOT %s is invalid with THRU",
+ name_of($b.term->field));
+ }
+ ev.decide(*$a.term, *$b.term, $a.invert);
+ }
+ | rel_term[a] ELSE
+ {
+ error_msg(@ELSE, "ELSE not valid in WHEN");
+ YYERROR;
+ }
+ ;
+eval_posneg: POSITIVE { $$ = $1 == NOT? le_op : gt_op; }
+ | NEGATIVE { $$ = $1 == NOT? ge_op : lt_op; }
+ ;
+
+eval_abbrs: rel_term[a] {
+ auto& ev( eval_stack.current() );
+ auto subj( ev.subject() );
+ if( !subj ) {
+ error_msg(@1, "WHEN %s phrase exceeds "
+ "subject set count of %zu",
+ $a.term->name(), ev.subject_count());
+ YYERROR;
+ }
+ if( ! ev.compatible($a.term->field) ) {
+ auto obj($a.term->field);
+ error_msg(@1, "subject %s, type %s, "
+ "cannot be compared %s, type %s",
+ subj->name, 3 + cbl_field_type_str(subj->type),
+ obj->name, 3 + cbl_field_type_str(obj->type) );
+ }
+ auto result = ev.compare(*$a.term);
+ if( ! result ) YYERROR;
+ if( $a.invert ) {
+ parser_logop(result, nullptr, not_op, result);
+ }
+ $$ = new log_expr_t(result);
+ }
+ | relop rel_term[a] {
+ auto& ev( eval_stack.current() );
+ relop_t relop(relop_of($relop));
+ ev.object_relop(relop);
+ auto subj( ev.subject() );
+ if( !subj ) {
+ error_msg(@1, "WHEN %s %s phrase exceeds "
+ "subject set count of %zu",
+ relop_str(relop_of($relop)), $a.term->name(), ev.subject_count());
+ YYERROR;
+ }
+ if( ! ev.compatible($a.term->field) ) {
+ auto obj($a.term->field);
+ error_msg(@1, "subject %s, type %s, "
+ "cannot be compared %s, type %s",
+ subj->name, 3 + cbl_field_type_str(subj->type),
+ obj->name, 3 + cbl_field_type_str(obj->type) );
+ }
+ if( is_conditional(ev.subject()) ) {
+ auto obj($a.term->field);
+ error_msg(@1, "subject %s, type %s, "
+ "cannot be %s %s, type %s",
+ subj->name, 3 + cbl_field_type_str(subj->type),
+ relop_str(relop_of($relop)),
+ obj->name, 3 + cbl_field_type_str(obj->type) );
+ }
+ auto result = ev.compare(relop, *$a.term);
+ if( ! result ) YYERROR;
+ if( $a.invert ) {
+ parser_logop(result, nullptr, not_op, result);
+ }
+ $$ = new log_expr_t(result);
+ }
+ | '(' eval_abbrs ')' {
+ $$ = $2;
+ $$->resolve();
+ }
+ | eval_abbrs OR eval_abbr[rhs] {
+ $$ = $1;
+ $$->or_term($rhs);
+ }
+ | eval_abbrs OR '(' eval_abbr[rhs] ')' {
+ $$ = $1;
+ $$->or_term($rhs);
+ }
+ | eval_abbrs AND eval_abbr[rhs] {
+ $$ = $1;
+ $$->and_term($rhs);
+ }
+ | eval_abbrs AND '(' eval_abbr[rhs] ')' {
+ $$ = $1;
+ $$->and_term($rhs);
+ }
+ ;
+
+eval_abbr: rel_term[a] {
+ auto& ev( eval_stack.current() );
+ relop_t relop(ev.object_relop());
+ auto subj( ev.subject() );
+ assert( subj );
+ $$ = ev.compare(relop, *$a.term);
+ if( $a.invert ) {
+ parser_logop($$, nullptr, not_op, $$);
+ }
+ }
+ | relop rel_term[a] {
+ auto& ev( eval_stack.current() );
+ relop_t relop(relop_of($relop));
+ ev.object_relop(relop);
+ $$ = ev.compare(relop, *$a.term);
+ if( $a.invert ) {
+ parser_logop($$, nullptr, not_op, $$);
+ }
+ }
+ ;
+
+end_evaluate: %empty %prec EVALUATE
+ | END_EVALUATE
+ ;
+
+true_false: TRUE_kw { $$ = TRUE_kw; }
+ | FALSE_kw { $$ = FALSE_kw; }
+ ;
+
+scalar: tableref {
+ // Check for missing subscript; others already checked.
+ if( $1->nsubscript == 0 && 0 < dimensions($1->field) ) {
+ subscript_dimension_error(@1, 0, $$);
+ }
+ }
+ ;
+
+tableref: tableish {
+ // tableref is used by SORT. It may name a table without subscripts.
+ $$ = $1;
+ $$->loc = @1;
+ if( $$->is_table_reference() ) {
+ if( $$->nsubscript != dimensions($$->field) ) {
+ subscript_dimension_error(@1, $$->nsubscript, $$);
+ YYERROR;
+ }
+ }
+ }
+tableish: name subscripts[subs] refmod[ref] %prec NAME
+ {
+ assert(yychar != LPAREN);
+ $$ = new cbl_refer_t($name);
+ $$->subscripts_set($subs->refers);
+ literal_subscripts_valid( @subs, *$$ );
+ $$->refmod = cbl_span_t( $ref.from,
+ $ref.len );
+ literal_refmod_valid( @ref, *$$ );
+ }
+ | name refmod[ref] %prec NAME
+ {
+ $$ = new cbl_refer_t($name);
+ $$->refmod = cbl_span_t( $ref.from,
+ $ref.len );
+ literal_refmod_valid( @ref, *$$ );
+ }
+ | name subscripts[subs] %prec NAME
+ {
+ $$ = new cbl_refer_t($name);
+ $$->subscripts_set($subs->refers);
+ literal_subscripts_valid( @subs, *$$ );
+ }
+ | name %prec NAME
+ {
+ $$ = new cbl_refer_t($name);
+ }
+ ;
+
+refmod: LPAREN expr[from] ':' expr[len] ')' %prec NAME
+ {
+ if( ! require_numeric(@from, *$from) ) YYERROR;
+ if( ! require_numeric(@len, *$len) ) YYERROR;
+ $$.from = $from;
+ $$.len = $len;
+ }
+ | LPAREN expr[from] ':' ')' %prec NAME
+ {
+ if( ! require_numeric(@from, *$from) ) YYERROR;
+ $$.from = $from;
+ $$.len = nullptr;
+ }
+ ;
+
+typename: NAME
+ {
+ auto e = symbol_typedef(PROGRAM, $NAME);
+ if( ! e ) {
+ error_msg(@1, "DATA-ITEM '%s' not found", $NAME );
+ YYERROR;
+ }
+ $$ = cbl_field_of(e);
+ }
+ ;
+
+name: qname
+ {
+ build_symbol_map();
+ auto namelocs( name_queue.pop() );
+ auto names( name_queue.namelist_of(namelocs) );
+ auto inner = namelocs.back();
+ if( ($$ = field_find(names)) == NULL ) {
+ if( procedure_div_e == current_division ) {
+ error_msg(inner.loc,
+ "DATA-ITEM '%s' not found", inner.name );
+ YYERROR;
+ }
+ /*
+ * Insert forward references, starting outermost.
+ */
+ size_t parent = 0;
+ while( ! names.empty() ) {
+ auto name = names.front();
+ names.pop_front();
+ auto e = symbol_field_forward_add(PROGRAM, parent,
+ name, yylineno);
+ if( !e ) YYERROR;
+ symbol_field_location( symbol_index(e), @qname );
+ parent = symbol_index(e);
+ $$ = cbl_field_of(e);
+ }
+ }
+ gcc_assert($$);
+ }
+ ;
+
+qname: ctx_name
+ {
+ name_queue.qualify(@1, $1);
+ }
+ | qname inof ctx_name
+ {
+ name_queue.qualify(@3, $3);
+ }
+ ;
+inof: IN
+ | OF
+ ;
+
+ctx_name: NAME
+ | context_word
+ ;
+
+context_word: APPLY { static char s[] ="APPLY";
+ $$ = s; } // screen description entry
+ | ARITHMETIC { static char s[] ="ARITHMETIC";
+ $$ = s; } // OPTIONS paragraph
+ | ATTRIBUTE { static char s[] ="ATTRIBUTE";
+ $$ = s; } // SET statement
+ | AUTO { static char s[] ="AUTO";
+ $$ = s; } // screen description entry
+ | AUTOMATIC { static char s[] ="AUTOMATIC";
+ $$ = s; } // LOCK MODE clause
+ | AWAY_FROM_ZERO { static char s[] ="AWAY-FROM-ZERO";
+ $$ = s; } // ROUNDED phrase
+ | BACKGROUND_COLOR { static char s[] ="BACKGROUND-COLOR";
+ $$ = s; } // screen description entry
+ | BELL { static char s[] ="BELL";
+ $$ = s; } // screen description entry and SET attribute statement
+ | BINARY_ENCODING { static char s[] ="BINARY-ENCODING";
+ $$ = s; } // USAGE clause and FLOAT-DECIMAL clause
+ | BLINK { static char s[] ="BLINK";
+ $$ = s; } // screen description entry and SET attribute statement
+ | BYTE_LENGTH { static char s[] ="BYTE-LENGTH";
+ $$ = s; } // constant entry
+ | CAPACITY { static char s[] ="CAPACITY";
+ $$ = s; } // OCCURS clause
+ | CENTER { static char s[] ="CENTER";
+ $$ = s; } // COLUMN clause
+ | CLASSIFICATION { static char s[] ="CLASSIFICATION";
+ $$ = s; } // OBJECT-COMPUTER paragraph
+ | CYCLE { static char s[] ="CYCLE";
+ $$ = s; } // EXIT statement
+ | DECIMAL_ENCODING { static char s[] ="DECIMAL-ENCODING";
+ $$ = s; } // USAGE clause and FLOAT-DECIMAL clause
+ | EOL { static char s[] ="EOL";
+ $$ = s; } // ERASE clause in a screen description entry
+ | EOS { static char s[] ="EOS";
+ $$ = s; } // ERASE clause in a screen description entry
+ | ENTRY_CONVENTION { static char s[] ="ENTRY-CONVENTION";
+ $$ = s; } // OPTIONS paragraph
+ | ERASE { static char s[] ="ERASE";
+ $$ = s; } // screen description entry
+ | EXPANDS { static char s[] ="EXPANDS";
+ $$ = s; } // class-specifier and interface-specifier of the REPOSITORY paragraph
+ | FEATURE { static char s[] ="FEATURE";
+ $$ = s; } // gcobol CDF token
+ | FLOAT_BINARY { static char s[] ="FLOAT-BINARY";
+ $$ = s; } // OPTIONS paragraph
+ | FLOAT_DECIMAL { static char s[] ="FLOAT-DECIMAL";
+ $$ = s; } // OPTIONS paragraph
+ | FOREGROUND_COLOR { static char s[] ="FOREGROUND-COLOR";
+ $$ = s; } // screen description entry
+ | FOREVER { static char s[] ="FOREVER";
+ $$ = s; } // RETRY phrase
+ | FULL { static char s[] ="FULL";
+ $$ = s; } // screen description entry
+ | HIGH_ORDER_LEFT { static char s[] ="HIGH-ORDER-LEFT";
+ $$ = s; } // FLOAT-BINARY clause, FLOAT-DECIMAL clause, and USAGE clause
+ | HIGH_ORDER_RIGHT { static char s[] ="HIGH-ORDER-RIGHT";
+ $$ = s; } // FLOAT-BINARY clause, FLOAT-DECIMAL clause, and USAGE clause
+ | HIGHLIGHT { static char s[] ="HIGHLIGHT";
+ $$ = s; } // screen description entry and SET attribute statement
+ | IGNORING { static char s[] ="IGNORING";
+ $$ = s; } // READ statement
+ | IMPLEMENTS { static char s[] ="IMPLEMENTS";
+ $$ = s; } // FACTORY paragraph and OBJECT paragraph
+ | INITIALIZED { static char s[] ="INITIALIZED";
+ $$ = s; } // ALLOCATE statement and OCCURS clause
+ | INTERMEDIATE { static char s[] ="INTERMEDIATE";
+ $$ = s; } // OPTIONS paragraph
+ | INTRINSIC { static char s[] ="INTRINSIC";
+ $$ = s; } // function-specifier of the REPOSITORY paragraph
+ | LC_ALL_kw { static char s[] ="LC_ALL";
+ $$ = s; } // SET statement
+ | LC_COLLATE_kw { static char s[] ="LC_COLLATE";
+ $$ = s; } // SET statement
+ | LC_CTYPE_kw { static char s[] ="LC_CTYPE";
+ $$ = s; } // SET statement
+ | LC_MESSAGES_kw { static char s[] ="LC_MESSAGES";
+ $$ = s; } // SET statement
+ | LC_MONETARY_kw { static char s[] ="LC_MONETARY";
+ $$ = s; } // SET statement
+ | LC_NUMERIC_kw { static char s[] ="LC_NUMERIC";
+ $$ = s; } // SET statement
+ | LC_TIME_kw { static char s[] ="LC_TIME";
+ $$ = s; } // SET statement
+ | LOWLIGHT { static char s[] ="LOWLIGHT";
+ $$ = s; } // screen description entry and SET attribute statement
+ | MANUAL { static char s[] ="MANUAL";
+ $$ = s; } // LOCK MODE clause
+ | MULTIPLE { static char s[] ="MULTIPLE";
+ $$ = s; } // LOCK ON phrase
+ | NEAREST_AWAY_FROM_ZERO { static char s[] ="NEAREST-AWAY-FROM-ZERO";
+ $$ = s; } // INTERMEDIATE ROUNDING clause and ROUNDED phrase
+ | NEAREST_EVEN { static char s[] ="NEAREST-EVEN";
+ $$ = s; } // INTERMEDIATE ROUNDING clause and ROUNDED phrase
+ | NEAREST_TOWARD_ZERO { static char s[] ="NEAREST-TOWARD-ZERO";
+ $$ = s; } // INTERMEDIATE ROUNDING clause and ROUNDED phrase
+ | NONE { static char s[] ="NONE";
+ $$ = s; } // DEFAULT clause
+ | NORMAL { static char s[] ="NORMAL";
+ $$ = s; } // STOP statement
+ | NUMBERS { static char s[] ="NUMBERS";
+ $$ = s; } // COLUMN clause and LINE clause
+ | ONLY { static char s[] ="ONLY";
+ $$ = s; } // Object-view, SHARING clause, SHARING phrase, and USAGE clause
+ | PREFIXED { static char s[] ="PREFIXED";
+ $$ = s; } // DYNAMIC LENGTH STRUCTURE clause
+ | PREVIOUS { static char s[] ="PREVIOUS";
+ $$ = s; } // READ statement
+ | PROHIBITED { static char s[] ="PROHIBITED";
+ $$ = s; } // INTERMEDIATE ROUNDING clause and ROUNDED phrase
+ | RECURSIVE { static char s[] ="RECURSIVE";
+ $$ = s; } // PROGRAM-ID paragraph
+ | RELATION { static char s[] ="RELATION";
+ $$ = s; } // VALIDATE-STATUS clause
+ | REQUIRED { static char s[] ="REQUIRED";
+ $$ = s; } // screen description entry
+ | REVERSE_VIDEO { static char s[] ="REVERSE-VIDEO";
+ $$ = s; } // screen description entry and SET attribute statement
+ | ROUNDING { static char s[] ="ROUNDING";
+ $$ = s; } // OPTIONS paragraph
+ | SECONDS { static char s[] ="SECONDS";
+ $$ = s; } // RETRY phrase
+ | SECURE { static char s[] ="SECURE";
+ $$ = s; } // screen description entry
+ | SHORT { static char s[] ="SHORT";
+ $$ = s; } // DYNAMIC LENGTH STRUCTURE clause
+ | SIGNED { static char s[] ="SIGNED";
+ $$ = s; } // DYNAMIC LENGTH STRUCTURE clause and USAGE clause
+ | STANDARD_BINARY { static char s[] ="STANDARD-BINARY";
+ $$ = s; } // ARITHMETIC clause
+ | STANDARD_DECIMAL { static char s[] ="STANDARD-DECIMAL";
+ $$ = s; } // ARITHMETIC clause
+ | STATEMENT { static char s[] ="STATEMENT";
+ $$ = s; } // RESUME statement
+ | STEP { static char s[] ="STEP";
+ $$ = s; } // OCCURS clause
+ | STRONG { static char s[] ="STRONG";
+ $$ = s; } // TYPEDEF clause
+ | STRUCTURE { static char s[] ="STRUCTURE";
+ $$ = s; } // DYNAMIC LENGTH STRUCTURE clause
+ | SYMBOL { static char s[] ="SYMBOL";
+ $$ = s; } // CURRENCY clause
+ | TOWARD_GREATER { static char s[] ="TOWARD-GREATER";
+ $$ = s; } // ROUNDED phrase
+ | TOWARD_LESSER { static char s[] ="TOWARD-LESSER";
+ $$ = s; } // ROUNDED phrase
+ | TRUNCATION { static char s[] ="TRUNCATION";
+ $$ = s; } // INTERMEDIATE ROUNDING clause and ROUNDED phrase
+ | UCS_4 { static char s[] ="UCS-4";
+ $$ = s; } // ALPHABET clause
+ | UNDERLINE { static char s[] ="UNDERLINE";
+ $$ = s; } // screen description entry and SET attribute statement
+ | UNSIGNED { static char s[] ="UNSIGNED";
+ $$ = s; } // USAGE clause
+ | UTF_8 { static char s[] ="UTF-8";
+ $$ = s; } // ALPHABET clause
+ | UTF_16 { static char s[] ="UTF-16";
+ $$ = s; } // ALPHABET clause
+ | YYYYDDD { static char s[] ="YYYYDDD";
+ $$ = s; } // ACCEPT statement
+ | YYYYMMDD { static char s[] ="YYYYMMDD";
+ $$ = s; } // ACCEPT statement
+ ;
+
+move: MOVE scalar TO move_tgts[tgts]
+ {
+ statement_begin(@1, MOVE);
+ if( $scalar->field->type == FldIndex ) {
+ error_msg(@1, "'%s' cannot be MOVEd because it's an INDEX",
+ name_of($scalar->field) );
+ YYERROR;
+ }
+ if( !parser_move2($tgts, *$scalar) ) { YYERROR; }
+ }
+ | MOVE all literalism[input] TO move_tgts[tgts]
+ {
+ statement_begin(@1, MOVE);
+ struct cbl_refer_t *src = new_reference(new_literal($input,
+ quoted_e));
+ src->all = $all;
+ if( !parser_move2($tgts, *src) ) { YYERROR; }
+ }
+ | MOVE all spaces_etc[src] TO move_tgts[tgts]
+ {
+ statement_begin(@1, MOVE);
+ cbl_field_t *field;
+ auto p = std::find_if( $tgts->targets.begin(),
+ $tgts->targets.end(),
+ [&field]( const auto& num_result ) {
+ const cbl_refer_t& tgt = num_result.refer;
+ field = tgt.field;
+ return is_numeric(tgt.field);
+ } );
+
+ if( p != $tgts->targets.end() ) {
+ error_msg(@src, "cannot MOVE %s "
+ "to numeric receiving field %s",
+ constant_of(constant_index($src))->name,
+ field->name );
+ YYERROR;
+ }
+
+ struct cbl_field_t* src = constant_of(constant_index($src));
+ if( !parser_move2($tgts, src) ) { YYERROR; }
+ }
+ | MOVE all signed_literal[lit] TO move_tgts[tgts]
+ {
+ statement_begin(@1, MOVE);
+ cbl_refer_t src( $lit, $all);
+ if( !parser_move2($tgts, src) ) { YYERROR; }
+ }
+
+ | MOVE intrinsic_call TO move_tgts[tgts]
+ {
+ statement_begin(@1, MOVE);
+ if( !parser_move2($tgts, *$2) ) { YYERROR; }
+ }
+
+ | MOVE CORRESPONDING scalar[from] TO scalar[to]
+ {
+ statement_begin(@1, MOVE);
+ if( $from->field->type != FldGroup ) {
+ error_msg(@from, "%s does not name a group", $from->name());
+ YYERROR;
+ }
+ if( $to->field->type != FldGroup ) {
+ error_msg(@to, "%s does not name a group", $to->name());
+ YYERROR;
+ }
+
+ if( !move_corresponding(*$to, *$from) ) {
+ yywarn( "%s and %s have no corresponding fields",
+ $from->field->name, $to->field->name );
+ }
+ }
+ ;
+
+move_tgts: move_tgt[tgt] {
+ $$ = new tgt_list_t;
+ if( $tgt ) list_add($$->targets, *$tgt, current_rounded_mode());
+ }
+ | move_tgts move_tgt[tgt]
+ {
+ if( $tgt ) list_add($1->targets, *$tgt, current_rounded_mode());
+ }
+ ;
+move_tgt: scalar[tgt] {
+ if( is_literal($tgt->field) ) {
+ auto litcon = $tgt->field->name[0] == '_'? "literal" : "constant";
+ error_msg(@1, "%s is a %s", name_of($tgt->field), litcon);
+ }
+ }
+ | literal {
+ const auto& field(*$1);
+ static char buf[32];
+ const char *value_str( name_of($literal) );
+ if( is_numeric($1) && float(field.data.value) == int(field.data.value) ) {
+ sprintf(buf, "%d", int(field.data.value));
+ value_str = buf;
+ }
+ auto litcon = field.name[0] == '_'? "literal" : "constant";
+ error_msg(@literal, "%s is a %s", value_str, litcon);
+ $$ = NULL;
+ }
+ | error
+ {
+ static const char * error_at;
+ if( error_at != yytext ) { // avoid repeated message
+ error_at = yytext;
+ error_msg(first_line_of(@1), "invalid receiving operand");
+ }
+ $$ = NULL;
+ }
+ ;
+
+multiply: multiply_impl end_multiply { ast_multiply($1); }
+ | multiply_cond end_multiply { ast_multiply($1); }
+ ;
+multiply_impl: MULTIPLY multiply_body
+ {
+ statement_begin(@1, MULTIPLY);
+ $$ = $2;
+ }
+ ;
+multiply_cond: MULTIPLY multiply_body[body] arith_errs[err]
+ {
+ statement_begin(@1, MULTIPLY);
+ $$ = $body;
+ $$->on_error = $err.on_error;
+ $$->not_error = $err.not_error;
+ }
+ ;
+end_multiply: %empty %prec MULTIPLY
+ | END_MULTIPLY
+ ;
+
+multiply_body: num_operand BY rnames
+ {
+ $$ = new arith_t(no_giving_e);
+ $$->A.push_back(*$num_operand);
+ std::copy( rhs.begin(),
+ rhs.end(), back_inserter($$->tgts) );
+ rhs.clear();
+ }
+ | num_operand BY signed_literal[lit]
+ {
+ error_msg(@lit, "%s is not a receiving field", name_of($lit));
+ YYERROR;
+ }
+ | num_operand[a] BY num_operand[b] GIVING rnames
+ {
+ $$ = new arith_t(giving_e);
+ $$->A.push_back(*$a);
+ $$->B.push_back(*$b);
+ std::copy( rhs.begin(),
+ rhs.end(), back_inserter($$->tgts) );
+ rhs.clear();
+ }
+ | num_operand[a] BY num_operand[b] GIVING signed_literal[lit]
+ {
+ error_msg(@lit, "%s is not a receiving field", name_of($lit));
+ YYERROR;
+ }
+ | LITERAL
+ {
+ error_msg(@1, "invalid string operand '%s'", $1.data);
+ YYERROR;
+ }
+ ;
+
+arith_errs: arith_err[a] statements %prec ADD
+ {
+ assert( $a.on_error || $a.not_error );
+ assert( ! ($a.on_error && $a.not_error) );
+ cbl_label_t *tgt = $a.on_error? $a.on_error : $a.not_error;
+ parser_arith_error_end(tgt);
+ }
+ | arith_errs[a] arith_err[b] statements %prec ADD
+ {
+ if( $a.on_error && $a.not_error ) {
+ error_msg(@1, "too many ON ERROR clauses");
+ YYERROR;
+ }
+ // "ON" and "NOT ON" could be reversed, but not duplicated.
+ if( $a.on_error && $b.on_error ) {
+ error_msg(@1, "duplicate ON ERROR clauses");
+ YYERROR;
+ }
+ if( $a.not_error && $b.not_error ) {
+ error_msg(@1, "duplicate NOT ON ERROR clauses");
+ YYERROR;
+ }
+ $$ = $a;
+ if( $b.on_error ) {
+ $$.on_error = $b.on_error;
+ assert($a.not_error);
+ } else {
+ $$.not_error = $b.not_error;
+ assert($a.on_error);
+ }
+ assert( $b.on_error || $b.not_error );
+ assert( ! ($b.on_error && $b.not_error) );
+ cbl_label_t *tgt = $b.on_error? $b.on_error : $b.not_error;
+ parser_arith_error_end(tgt);
+ }
+ ;
+
+arith_err: SIZE_ERROR
+ {
+ assert( $1 == ERROR || $1 == NOT );
+ $$.on_error = NULL;
+ $$.not_error = NULL;
+ cbl_label_t **ptgt = $1 == NOT? &$$.not_error : &$$.on_error;
+ if( current.in_compute() ) {
+ *ptgt = $1 == NOT?
+ current.compute_not_error() : current.compute_on_error();
+ } else {
+ *ptgt = label_add(LblArith, uniq_label("arith"), yylineno);
+ }
+ (*ptgt)->lain = yylineno;
+ parser_arith_error( *ptgt );
+ }
+ ;
+
+ /*
+ * Relational operator Can be written
+ * IS GREATER THAN IS >
+ * IS NOT GREATER THAN IS NOT >
+ * IS LESS THAN IS <
+ * IS NOT LESS THAN IS NOT <
+ * IS EQUAL TO IS =
+ * IS NOT EQUAL TO IS NOT =
+ * IS GREATER THAN OR EQUAL TO IS >=
+ * IS LESS THAN OR EQUAL TO IS <=
+ *
+ * The lexer returns simple tokens.
+ */
+
+relop: '<' { $$ = '<'; }
+ | LE { $$ = LE; }
+ | '=' { $$ = '='; }
+ | NE { $$ = NE; }
+ | GE { $$ = GE; }
+ | '>' { $$ = '>'; }
+ ;
+
+rnames: scalar rounded
+ {
+ list_add( rhs, *$scalar, $rounded );
+ }
+ | rnames scalar rounded
+ {
+ cbl_num_result_t arg = { static_cast<cbl_round_t>($rounded),
+ *$scalar };
+ rhs.push_back(arg);
+ }
+ ;
+
+sum: num_operand { $$ = new refer_list_t($num_operand); }
+ | sum num_operand { $$->push_back($num_operand); }
+ ;
+
+num_operand: scalar
+ | signed_literal { $$ = new_reference($1); }
+ | intrinsic_call
+ ;
+
+num_value: scalar // might actually be a string
+ | intrinsic_call
+ | num_literal { $$ = new_reference($1); }
+ | ADDRESS OF scalar {$$ = $scalar; $$->addr_of = true; }
+ | DETAIL OF scalar {$$ = $scalar; }
+ | LENGTH_OF name[val] {
+ location_set(@1);
+ $$ = new cbl_refer_t( new_tempnumeric() );
+ $$->field->clear_attr(signable_e);
+ if( dialect_gcc() ) {
+ dialect_error(@1, "LENGTH OF", "ibm");
+ }
+ parser_set_numeric($$->field, $val->data.capacity);
+ }
+ | LENGTH_OF name[val] subscripts[subs] {
+ location_set(@1);
+ $$ = new cbl_refer_t( new_tempnumeric() );
+ $$->field->clear_attr(signable_e);
+ if( dialect_gcc() ) {
+ dialect_error(@1, "LENGTH OF", "ibm");
+ }
+ if( 0 == dimensions($val) ) {
+ cbl_refer_t r1($val);
+ subscript_dimension_error( @subs, $subs->refers.size(), &r1 );
+ }
+ parser_set_numeric($$->field, $val->data.capacity);
+ }
+ ;
+
+
+ /*
+ * Constant Compile-time Expressions
+ */
+
+/* cce_cond_expr: cce_bool_expr { $$ = $1 == 0? false : true; } */
+/* ; */
+/* cce_bool_expr: cce_and */
+/* | cce_bool_expr OR cce_and { $$ = $1 || $3; } */
+/* ; */
+/* cce_and: cce_reloper */
+/* | cce_and AND cce_reloper { $$ = $1 && $3; } */
+/* ; */
+/* cce_reloper: cce_relexpr */
+/* | NOT cce_relexpr { $$ = $2 != 0; } */
+/* ; */
+/* cce_relexpr: cce_expr */
+/* | cce_relexpr '<' cce_expr { $$ = $1 < $3; } */
+/* | cce_relexpr LE cce_expr { $$ = $1 <= $3; } */
+/* | cce_relexpr '=' cce_expr { $$ = $1 == $3; } */
+/* | cce_relexpr NE cce_expr { $$ = $1 != $3; } */
+/* | cce_relexpr GE cce_expr { $$ = $1 >= $3; } */
+/* | cce_relexpr '>' cce_expr { $$ = $1 > $3; } */
+/* ; */
+
+cce_expr: cce_factor
+ | cce_expr '+' cce_expr { $$ = $1 + $3; }
+ | cce_expr '-' cce_expr { $$ = $1 - $3; }
+ | cce_expr '*' cce_expr { $$ = $1 * $3; }
+ | cce_expr '/' cce_expr { $$ = $1 / $3; }
+ | '+' cce_expr %prec NEG { $$ = $2; }
+ | '-' cce_expr %prec NEG { $$ = -$2; }
+ | '(' cce_expr ')' { $$ = $2; }
+ ;
+
+cce_factor: NUMSTR {
+ /*
+ * As of March 2023, glibc printf does not deal with
+ * __int128_t. The below assertion is not required. It
+ * serves only remind us we're far short of the precision
+ * required by ISO.
+ */
+ static_assert( sizeof($$) == sizeof(_Float128),
+ "quadmath?" );
+ static_assert( sizeof($$) == 16,
+ "long doubles?" );
+ $$ = numstr2i($1.string, $1.radix);
+ }
+ ;
+
+ /*
+ * End Constant Compile-time Expressions
+ */
+
+section_name: NAME section_kw '.'
+ {
+ statement_begin(@1, SECTION);
+ $$ = label_add(@1, LblSection, $1);
+ ast_enter_section($$);
+ apply_declaratives();
+ }
+ | NAME section_kw // lexer swallows '.' before USE
+ <label>{
+ statement_begin(@1, SECTION);
+ $$ = label_add(@1, LblSection, $1);
+ ast_enter_section($$);
+ apply_declaratives();
+ } [label]
+ cdf_use dot
+ {
+ $$ = $label;
+ }
+ ;
+
+section_kw: SECTION
+ {
+ if( $1 ) {
+ if( *$1 == '-' ) {
+ error_msg(@1, "SECTION segment %s is negative", $1);
+ } else {
+ cbl_unimplementedw("SECTION segment %s was ignored", $1);
+ }
+ }
+ }
+ | SECTION error
+ {
+ error_msg(@1, "unknown section qualifier");
+ }
+ ;
+
+stop: STOP RUN exit_with
+ {
+ statement_begin(@1, STOP);
+ parser_see_stop_run( *$exit_with, NULL );
+ }
+ | STOP NUMSTR[status] // IBM syntax
+ {
+ statement_begin(@1, STOP);
+ if( ! dialect_ibm() ) {
+ dialect_error(@2, "STOP <number> is not ISO syntax,", "ibm");
+ YYERROR;
+ }
+ cbl_refer_t status( new_literal($status.string, $status.radix) );
+ parser_see_stop_run( status, NULL );
+ }
+ | STOP LITERAL[name] // CCVS-85 && IBM syntax
+ {
+ statement_begin(@1, STOP);
+ const char *name = string_of($name);
+ if( ! name ) {
+ error_msg(@name, "'%s' has embedded NUL", $name.data);
+ YYERROR;
+ }
+ parser_see_stop_run( literally_zero, $name.data );
+ }
+ ;
+stop_status: status { $$ = NULL; }
+ | status scalar { $$ = $2; }
+ | status NUMSTR {
+ $$ = new_reference(new_literal($2.string, $2.radix));
+ }
+ ;
+
+subscripts: LPAREN expr_list ')' {
+ $$ = $2;
+ const auto& exprs( $$->refers );
+ bool ok = std::all_of( exprs.begin(), exprs.end(),
+ []( const auto& refer ) {
+ return is_numeric(refer.field);
+ } );
+ if( ! ok ) {
+ int i=0;
+ for( auto refer : exprs ) {
+ if( ! is_numeric(refer.field) ) {
+ error_msg(@1, "subscript %d, %s, is not numeric (%s)",
+ ++i, name_of(refer.field),
+ cbl_field_type_str(refer.field->type) + 3);
+ }
+ }
+ YYERROR;
+ }
+ }
+ ;
+expr_list: expr
+ {
+ if( ! require_numeric(@expr, *$expr) ) YYERROR;
+ $$ = new refer_list_t($expr);
+ }
+ | expr_list expr {
+ if( $1->size() == MAXIMUM_TABLE_DIMENSIONS ) {
+ error_msg(@1, "table dimensions limited to %d",
+ MAXIMUM_TABLE_DIMENSIONS);
+ YYERROR;
+ }
+ if( ! require_numeric(@expr, *$expr) ) YYERROR;
+ $1->push_back($2); $$ = $1;
+ }
+ | ALL {
+ auto ref = new_reference(constant_of(constant_index(ZERO)));
+ $$ = new refer_list_t(ref);
+ }
+ ;
+
+arg_list: any_arg { $$ = new refer_list_t($1); }
+ | arg_list any_arg { $1->push_back($2); $$ = $1; }
+ ;
+any_arg: expr
+ | LITERAL {$$ = new_reference(new_literal($1, quoted_e)); }
+ ;
+
+ /*
+ * Because num_literal includes ZERO, this grammar
+ * allows -ZERO and +ZERO. FWIW.
+ */
+signed_literal: num_literal
+ | '+' num_literal { $$ = $2; }
+ | '-' num_literal
+ {
+ $$ = new_tempnumeric();
+ struct cbl_field_t *zero = constant_of(constant_index(ZERO));
+ parser_subtract( $$, zero, $2, current_rounded_mode() );
+ }
+ | LENGTH_OF name[val] {
+ location_set(@1);
+ $$ = new_tempnumeric();
+ $$->clear_attr(signable_e);
+ if( dialect_gcc() ) {
+ dialect_error(@1, "LENGTH OF", "ibm");
+ }
+ parser_set_numeric($$, $val->data.capacity);
+ }
+ | LENGTH_OF name[val] subscripts[subs] {
+ location_set(@1);
+ $$ = new_tempnumeric();
+ $$->clear_attr(signable_e);
+ if( dialect_gcc() ) {
+ dialect_error(@1, "LENGTH OF", "ibm");
+ }
+ if( 0 == dimensions($val) ) {
+ cbl_refer_t r1($val);
+ subscript_dimension_error( @subs, $subs->refers.size(), &r1 );
+ }
+ parser_set_numeric($$, $val->data.capacity);
+ }
+ ;
+
+num_literal: NUMSTR { $$ = new_literal($1.string, $1.radix); }
+ | ZERO { $$ = constant_of(constant_index(ZERO)); }
+ ;
+
+open: OPEN { statement_begin(@1, OPEN); } open_files
+ ;
+open_files: open_file
+ | open_files open_file
+ ;
+open_file: open_io[mode] filenames {
+ size_t n = $2->files.size();
+ parser_file_open( n, use_list($2->files, false), $mode );
+ current.declaratives_evaluate($2->files);
+ $2->files.clear();
+ }
+ ;
+open_io: INPUT { $$ = 'r'; }
+ | OUTPUT { $$ = 'w'; }
+ | EXTEND { $$ = 'a'; }
+ | IO { $$ = '+'; }
+ ;
+
+close: CLOSE { statement_begin(@1, CLOSE); } close_files
+ ;
+close_files: close_file
+ | close_files close_file
+ ;
+close_file: NAME close_how
+ {
+ struct symbol_elem_t *e = symbol_file(PROGRAM, $1);
+ if( !e ) {
+ error_msg(@1, "invalid file name '%s'", $1);
+ YYERROR;
+ }
+ auto how = static_cast<file_close_how_t>($close_how);
+ bool reel_unit = (file_close_reel_unit_e & $close_how) > 0;
+ auto file = cbl_file_of(e);
+ switch( file->org ) {
+ case file_disorganized_e:
+ gcc_unreachable();
+ break;
+ case file_sequential_e:
+ case file_line_sequential_e:
+ break;
+ case file_indexed_e:;
+ case file_relative_e:
+ if( $close_how & ~file_close_with_lock_e ) {
+ error_msg(@1, "INDEXED or RELATIVE file "
+ "closed with incompatible qualifier" );
+ YYERROR;
+ }
+ break;
+ }
+ if(reel_unit)
+ {
+ how = file_close_reel_unit_e;
+ }
+ parser_file_close( file, how );
+ current.declaratives_evaluate( file );
+ }
+ ;
+close_how: %empty { $$ = file_close_no_how_e; }
+ | reel_unit { $$ = file_close_reel_unit_e; }
+ | reel_unit for_kw REMOVAL {
+ $$ = file_close_reel_unit_e | file_close_removal_e;
+ }
+ | reel_unit WITH NO REWIND {
+ $$ = file_close_reel_unit_e | file_close_no_rewind_e;
+ }
+ | with NO REWIND { $$ = file_close_no_rewind_e; }
+ | with LOCK { $$ = file_close_with_lock_e; }
+ ;
+reel_unit: REEL
+ | UNIT
+ ;
+for_kw: %empty
+ | FOR
+ ;
+
+perform: perform_verb perform_proc { perform_free(); }
+ | perform_verb perform_stmts {
+ perform_ec_cleanup();
+ perform_free();
+ }
+ | perform_verb perform_except {
+ perform_ec_cleanup();
+ perform_free();
+ }
+ ;
+
+perform_stmts: perform_until perform_inline[in]
+ {
+ size_t n = $in->varys.size();
+ struct cbl_perform_vary_t varys[n];
+ std::copy( $in->varys.begin(), $in->varys.end(), varys );
+
+ parser_perform_until(&$in->tgt, $in->before, n, varys);
+ }
+ | perform_vary perform_inline[in]
+ {
+ struct perform_t *p = $in;
+ size_t n = p->varys.size();
+ struct cbl_perform_vary_t varys[n];
+ std::copy( p->varys.begin(), p->varys.end(), varys );
+
+ parser_perform_until(&$in->tgt, $in->before, n, varys);
+ }
+ | perform_times perform_inline[in]
+ {
+ parser_perform_inline_times(&$in->tgt, *$perform_times);
+ }
+ | perform_inline[in]
+ {
+ parser_perform_inline_times(&$in->tgt, literally_one);
+ }
+ ;
+
+perform_proc: perform_names %prec NAME
+ {
+ struct perform_t *p = perform_current();
+ if( yydebug ) p->tgt.dump();
+ parser_perform(&p->tgt, NULL);
+ }
+ | perform_names num_operand TIMES
+ {
+ struct perform_t *p = perform_current();
+ if( yydebug ) p->tgt.dump();
+ parser_perform(&p->tgt, *$2);
+ }
+ | perform_names perform_until
+ {
+ struct perform_t *p = perform_current();
+ if( yydebug ) p->tgt.dump();
+ assert(1 == p->varys.size());
+ parser_perform_until( &p->tgt, p->before, 1, &p->varys.front() );
+ }
+ | perform_names perform_vary
+ {
+ struct perform_t *p = perform_current();
+ if( yydebug ) p->tgt.dump();
+
+ size_t n = p->varys.size();
+ struct cbl_perform_vary_t varys[n];
+ std::copy( p->varys.begin(), p->varys.end(), varys );
+
+ parser_perform_until( &p->tgt, p->before, n, varys );
+ }
+ ;
+
+perform_names: label_1[para]
+ {
+ perform_tgt_set($para);
+ }
+ | label_1[para1] THRU label_1[para2]
+ {
+ perform_tgt_set($para1, $para2);
+ }
+ ;
+
+perform_times: num_operand TIMES
+ {
+ $$ = $1;
+ }
+ ;
+
+perform_vary: test_before varying vary_afters
+ {
+ perform_current()->before = $1 == BEFORE;
+ }
+ | varying vary_afters
+ | test_before varying
+ {
+ perform_current()->before = $1 == BEFORE;
+ }
+ | varying
+ ;
+
+perform_verb: PERFORM {
+ statement_begin(@1, PERFORM);
+ $$ = perform_alloc();
+ }
+ ;
+
+perform_until: test_before perform_cond
+ {
+ struct perform_t *p = perform_current();
+ struct cbl_perform_vary_t vary;
+
+ p->before = $1 == BEFORE;
+ vary.until = $2;
+ p->varys.push_back(vary);
+ }
+ | perform_cond
+ {
+ struct perform_t *p = perform_current();
+ struct cbl_perform_vary_t vary;
+
+ vary.until = $1;
+ p->varys.push_back(vary);
+ }
+ ;
+perform_cond: UNTIL { parser_perform_conditional( &perform_current()->tgt); }
+ bool_expr
+ {
+ parser_perform_conditional_end( &perform_current()->tgt);
+ if( !is_conditional($bool_expr) ) {
+ error_msg(@1, "%s is not a condition expression",
+ name_of($bool_expr->field));
+ YYERROR;
+ }
+ $$ = $bool_expr->cond();
+ }
+ ;
+
+perform_inline: perform_start statements END_PERFORM
+ {
+ location_set(@END_PERFORM);
+ $$ = perform_current();
+ if( $perform_start == LOCATION ) {
+ error_msg(@1, "LOCATION not valid with PERFORM Format 2");
+ }
+ }
+ | perform_start END_PERFORM
+ {
+ location_set(@END_PERFORM);
+ $$ = perform_current();
+ if( $perform_start == LOCATION ) {
+ error_msg(@1, "LOCATION not valid with PERFORM Format 2");
+ }
+ }
+ ;
+perform_start: %empty %prec LOCATION {
+ perform_ec_setup();
+ $$ = 0;
+ }
+ | with LOCATION {
+ perform_ec_setup();
+ $$ = LOCATION;
+ }
+ ;
+
+perform_except: perform_start
+ statements
+ {
+ auto perf = perform_current();
+ parser_perform_inline_times(&perf->tgt, literally_one);
+ }
+ perform_when // paragraphs
+ perform_ec_other // paragraph
+ perform_ec_common // paragraph
+ {
+ auto perf = perform_current();
+ parser_label_goto(perf->ec_labels.finally);
+ }
+ perform_ec_finally
+ END_PERFORM
+ {
+ auto perf = perform_current();
+ // produce blob, jumped over by FINALLY paragraph
+ size_t iblob = symbol_declaratives_add( PROGRAM, perf->dcls );
+ auto lave = perf->ec_labels.new_label(LblParagraph, "lave");
+ auto handlers = cbl_field_of(symbol_at(iblob));
+
+ // install blob
+ parser_label_label(perf->ec_labels.init);
+ declarative_runtime_match(handlers, lave);
+
+ // uninstall blob
+ parser_label_label(perf->ec_labels.fini);
+ }
+ ;
+
+perform_when: perform_when1
+ | perform_when perform_when1
+ ;
+perform_when1: WHEN perform_ec {
+ // accumulate handlers and their paragraphs
+ auto perf = perform_current();
+ auto when = perf->ec_labels.new_label(LblParagraph, "when");
+ for( auto& dcl : $perform_ec->elems ) {
+ // use section to hold paragraph
+ dcl->section = symbol_index(symbol_elem_of(when));
+ }
+ std::transform( $perform_ec->elems.begin(),
+ $perform_ec->elems.end(),
+ std::back_inserter(perf->dcls),
+ []( cbl_declarative_t *p ) {
+ return *p;
+ } );
+ ast_enter_paragraph(when);
+ }
+ statements {
+ parser_exit_paragraph();
+ }
+ ;
+
+perform_ec: EXCEPTION filenames {
+ auto dcls = new declarative_list_t;
+ auto p = $filenames->files.begin();
+ auto pend = p;
+ while( pend != $filenames->files.end() ) {
+ for( size_t i=0; i < COUNT_OF(cbl_declarative_t::files); i++ ) {
+ if( ++pend == $filenames->files.end() ) break;
+ }
+ std::list<size_t> files;
+ std::transform( p, pend, std::back_inserter(files),
+ []( const cbl_file_t* f ) {
+ return symbol_index(symbol_elem_of(f)); } );
+
+ auto dcl = new cbl_declarative_t(0, ec_io_e, files, file_mode_none_e);
+ dcls->elems.push_back(dcl);
+ }
+ $$ = dcls;
+ }
+ | EXCEPTION io_mode {
+ auto dcl = new cbl_declarative_t($io_mode);
+ $$ = new declarative_list_t(dcl);
+ }
+ | except_names {
+ auto dcls = new declarative_list_t;
+ const ec_list_t * ecs($except_names);
+ // one cbl_declarative_t per EC
+ std::transform( ecs->elems.begin(), ecs->elems.end(),
+ std::back_inserter(dcls->elems),
+ []( ec_type_t ec )
+ {
+ return new cbl_declarative_t(ec);
+ } );
+ $$ = dcls;
+ }
+ | except_files {
+ // one cbl_declarative_t per 16 files
+ auto dcls = new declarative_list_t;
+ for( auto p = $except_files->elems.begin();
+ p != $except_files->elems.end(); ) {
+ auto dcl = new cbl_declarative_t;
+ for( auto file = dcl->files;
+ file < dcl->files + COUNT_OF(dcl->files); file++ ) {
+ if( p != $except_files->elems.end() ) break;
+ *file = *p++;
+ }
+ dcls->elems.push_back(dcl);
+ }
+ $$ = dcls;
+ }
+ ;
+
+except_names: except_name { $$ = new ec_list_t($1); }
+ | except_names except_name {
+ $$ = $1->push_back($2);
+ }
+ ;
+except_name: EXCEPTION_NAME[ec] {
+ assert($ec != ec_none_e);
+ $$ = $1;
+ }
+ ;
+
+except_files: except_name[ec] FILE_KW filenames {
+ assert($ec != ec_none_e);
+ if( ec_io_e != (ec_io_e & $ec) ) {
+ error_msg(@1, "%s is not of type EC-I-O",
+ ec_type_str($ec));
+ }
+ $$ = new isym_list_t;
+ std::list<size_t>& files( $$->elems );
+ std::transform( $filenames->files.begin(),
+ $filenames->files.end(),
+ std::back_inserter(files),
+ []( const cbl_file_t* f ) {
+ return symbol_index(symbol_elem_of(f)); } );
+ }
+ ;
+
+perform_ec_other:
+ %empty %prec WHEN {
+ auto& ec_labels( perform_current()->ec_labels );
+ ast_enter_paragraph(ec_labels.other);
+ parser_exit_paragraph();
+ }
+ | WHEN OTHER {
+ auto& ec_labels( perform_current()->ec_labels );
+ ast_enter_paragraph(ec_labels.other);
+ }
+ exception statements %prec WHEN {
+ parser_exit_paragraph();
+ }
+ ;
+perform_ec_common:
+ %empty {
+ auto& ec_labels( perform_current()->ec_labels );
+ ast_enter_paragraph(ec_labels.common);
+ parser_exit_paragraph();
+ }
+ | WHEN COMMON {
+ auto& ec_labels( perform_current()->ec_labels );
+ ast_enter_paragraph(ec_labels.common);
+ }
+ exception statements {
+ parser_exit_paragraph();
+ }
+ ;
+perform_ec_finally:
+ %empty {
+ auto& ec_labels( perform_current()->ec_labels );
+ ast_enter_paragraph(ec_labels.finally);
+ parser_exit_paragraph();
+ parser_label_goto(ec_labels.fini);
+ }
+ | FINALLY {
+ auto& ec_labels( perform_current()->ec_labels );
+ ast_enter_paragraph(ec_labels.finally);
+ }
+ exception statements {
+ parser_exit_paragraph();
+ auto& ec_labels( perform_current()->ec_labels );
+ parser_label_goto(ec_labels.fini);
+ }
+ ;
+
+test_before: with TEST BEFORE { $$ = BEFORE; }
+ | with TEST AFTER { $$ = AFTER; }
+ ;
+
+varying: VARYING num_operand[tgt] FROM num_operand[from] vary_by[by]
+ perform_cond[until]
+ {
+ struct cbl_perform_vary_t vary(*$tgt, *$from, *$by, $until);
+ perform_current()->varys.push_back(vary);
+ }
+ ;
+
+vary_afters: vary_after
+ | vary_afters vary_after
+ ;
+vary_after: AFTER num_operand[tgt] FROM num_operand[from] vary_by[by]
+ perform_cond[until]
+ {
+ struct cbl_perform_vary_t vary(*$tgt, *$from, *$by, $until);
+ perform_current()->varys.push_back(vary);
+ }
+ ;
+vary_by: %empty { $$ = new cbl_refer_t(literally_one); }
+ | BY num_operand { $$ = $2; }
+ ;
+
+reserved_value: spaces_etc
+ | ZERO { $$ = ZERO; }
+ | NULLS { $$ = NULLS; }
+ ;
+spaces_etc: SPACES { $$ = SPACES; }
+ | HIGH_VALUES { $$ = HIGH_VALUES; }
+ | LOW_VALUES { $$ = LOW_VALUES; }
+ | QUOTES { $$ = QUOTES; }
+ ;
+
+variable_type: NUMERIC { $$ = NUMERIC; }
+ | ALPHABETIC { $$ = ALPHABETIC; }
+ | ALPHABETIC_LOWER { $$ = ALPHABETIC_LOWER; }
+ | ALPHABETIC_UPPER { $$ = ALPHABETIC_UPPER; }
+ | DBCS { $$ = DBCS; }
+ | KANJI { $$ = KANJI; }
+ ;
+
+subtract: subtract_impl end_subtract { ast_subtract($1); }
+ | subtract_cond end_subtract { ast_subtract($1); }
+ ;
+subtract_impl: SUBTRACT subtract_body[body]
+ {
+ statement_begin(@1, SUBTRACT);
+ $$ = $body;
+ }
+ ;
+subtract_cond: SUBTRACT subtract_body[body] arith_errs[err]
+ {
+ statement_begin(@1, SUBTRACT);
+ $body->on_error = $err.on_error;
+ $body->not_error = $err.not_error;
+ $$ = $body;
+ }
+ ;
+end_subtract: %empty %prec SUBTRACT
+ | END_SUBTRACT
+ ;
+
+subtract_body: sum FROM rnames
+ {
+ $$ = new arith_t(no_giving_e, $sum);
+ std::copy( rhs.begin(),
+ rhs.end(), back_inserter($$->tgts) );
+ rhs.clear();
+ }
+ | sum FROM num_operand[input] GIVING rnames
+ {
+ $$ = new arith_t(giving_e, $sum);
+ $$->B.push_back(*$input);
+ std::copy( rhs.begin(),
+ rhs.end(), back_inserter($$->tgts) );
+ rhs.clear();
+ }
+ | CORRESPONDING sum FROM rnames
+ {
+ corresponding_fields_t pairs =
+ corresponding_arith_fields( $sum->refers.front().field,
+ rhs.front().refer.field );
+ if( pairs.empty() ) {
+ yywarn( "%s and %s have no corresponding fields",
+ $sum->refers.front().field->name,
+ rhs.front().refer.field->name );
+ }
+ // First src/tgt elements are templates.
+ // Their subscripts apply to the correspondents.
+ $$ = new arith_t(corresponding_e, $sum);
+ $$->tgts.push_front(rhs.front());
+ // use arith_t functor to populate A and tgts
+ *$$ = std::for_each( pairs.begin(), pairs.end(), *$$ );
+ $$->A.pop_front();
+ $$->tgts.pop_front();
+ rhs.clear();
+ }
+ ;
+
+vargs: varg { $$ = new vargs_t($varg); }
+ | vargs[args] varg { $args->push_back($varg); $$ = $args; }
+ ;
+
+varg: varg1
+ | ALL varg1 { $$ = $2; $$->all = true; }
+ ;
+
+varg1: scalar
+ | varg1a
+ ;
+varg1a: ADDRESS OF scalar {
+ $$ = $scalar;
+ $$->addr_of = true;
+ }
+ | intrinsic_call
+ | literal
+ {
+ $$ = new_reference($1);
+ }
+ | reserved_value
+ {
+ $$ = new_reference(constant_of(constant_index($1)));
+ }
+ | LENGTH_OF name[val] {
+ location_set(@1);
+ $$ = new cbl_refer_t( new_tempnumeric() );
+ $$->field->clear_attr(signable_e);
+ if( dialect_gcc() ) {
+ dialect_error(@1, "LENGTH OF", "ibm");
+ }
+ parser_set_numeric($$->field, $val->size());
+ }
+ | LENGTH_OF name[val] subscripts[subs] {
+ location_set(@1);
+ $$ = new cbl_refer_t( new_tempnumeric() );
+ $$->field->clear_attr(signable_e);
+ if( dialect_gcc() ) {
+ dialect_error(@1, "LENGTH OF", "ibm");
+ }
+ if( 0 == dimensions($val) ) {
+ cbl_refer_t r1($val);
+ subscript_dimension_error( @subs, $subs->refers.size(), &r1 );
+ }
+ parser_set_numeric($$->field, $val->data.capacity);
+ }
+ ;
+
+literal: literalism
+ {
+ $$ = $1.isymbol()?
+ cbl_field_of(symbol_at($1.isymbol()))
+ :
+ new_literal($1, quoted_e);
+ }
+ | NUMSTR
+ {
+ $$ = new_literal($1.string, $1.radix);
+ }
+ | DATETIME_FMT
+ {
+ $$ = new_literal(strlen($1), $1, quoted_e);
+ }
+ | DATE_FMT
+ {
+ $$ = new_literal(strlen($1), $1, quoted_e);
+ }
+ | TIME_FMT
+ {
+ $$ = new_literal(strlen($1), $1, quoted_e);
+ }
+ ;
+
+raise: RAISE EXCEPTION NAME
+ {
+ auto ec = ec_type_of($NAME);
+ if( ec == ec_none_e ) {
+ error_msg(@NAME, "not an EXCEPTION CONDITION: %s", $NAME);
+ YYERROR;
+ }
+ statement_begin(@$, RAISE);
+ parser_exception_raise(ec);
+ }
+ | RAISE NAME
+ {
+ auto ec = ec_type_of($NAME);
+ if( ec != ec_none_e ) {
+ error_msg(@NAME, "RAISE EXCEPTION required for "
+ "EXCEPTION CONDITION: %s", $NAME);
+ YYERROR;
+ }
+ cbl_unimplemented("RAISE <EXCEPTION OBJECT>");
+ YYERROR;
+ }
+ ;
+
+read: read_file
+ {
+ current.declaratives_evaluate($1.file, $1.handled);
+ }
+ ;
+
+read_file: READ read_body {
+ file_read_args.call_parser_file_read();
+ $$.file = $2; $$.handled = FsSuccess;
+ }
+ | READ read_body END_READ {
+ file_read_args.call_parser_file_read();
+ $$.file = $2; $$.handled = FsSuccess;
+ }
+ | READ read_body read_eofs[err] {
+ bool handled = $err.nclause == 2 || !$err.tf;
+ $$.file = $2; $$.handled = handled? FsEofSeq : FsSuccess;
+ if( $$.file->access == file_access_rnd_e ) {
+ // None of ADVANCING, AT END, NEXT, NOT AT END, or PREVIOUS
+ // shall be specified if ACCESS MODE RANDOM
+ error_msg(@err, "%s: AT END invalid for ACCESS MODE RANDOM", $$.file->name);
+ YYERROR;
+ }
+ parser_fi();
+ }
+ | READ read_body read_eofs[err] END_READ {
+ bool handled = $err.nclause == 2 || !$err.tf;
+ $$.file = $2; $$.handled = handled? FsEofSeq : FsSuccess;
+ if( $$.file->access == file_access_rnd_e ) {
+ error_msg(@err, "%s: AT END invalid for ACCESS MODE RANDOM", $$.file->name);
+ YYERROR;
+ }
+ parser_fi();
+ }
+ | READ read_body io_invalids[err] {
+ bool handled = $err.nclause == 2 || !$err.tf;
+ $$.file = $2; $$.handled = handled? FsNotFound : FsSuccess;
+ parser_fi();
+ }
+ | READ read_body io_invalids[err] END_READ {
+ bool handled = $err.nclause == 2 || !$err.tf;
+ $$.file = $2; $$.handled = handled? FsNotFound : FsSuccess;
+ parser_fi();
+ }
+ ;
+
+read_body: NAME read_next read_into read_key
+ {
+ statement_begin(@$, READ);
+ struct symbol_elem_t *e = symbol_file(PROGRAM, $NAME);
+ if( !e ) {
+ error_msg(@1, "invalid file name '%s'", $NAME);
+ YYERROR;
+ }
+
+ $$ = cbl_file_of(e);
+
+ struct cbl_field_t *record = symbol_file_record($$);
+ if( !record ) {
+ error_msg(@1, "syntax error? invalid file record name");
+ YYERROR;
+ }
+ if( 0 && $$->access == file_access_dyn_e && $read_next >= 0 ) {
+ error_msg(@1, "sequential DYNAMIC access requires NEXT RECORD");
+ YYERROR;
+ }
+ if( $read_key->field && is_sequential($$) ) {
+ error_msg(@1, "SEQUENTIAL file %s has no KEY", $$->name);
+ YYERROR;
+ }
+ if( $$->org == file_line_sequential_e && $read_next == -2 ) {
+ error_msg(@1, "LINE SEQUENTIAL file %s cannot READ PREVIOUS",
+ $$->name);
+ YYERROR;
+ }
+ if( $read_key->field && $read_next < 0 ) {
+ error_msg(@1, "cannot read NEXT with KEY", $$->name);
+ YYERROR;
+ }
+
+ int ikey = $read_next;
+ if( $read_key->field ) {
+ ikey = $$->key_one($read_key->field);
+ }
+
+ file_read_args.init( $$, record, $read_into, ikey );
+ }
+ ;
+
+read_next: %empty { $$ = 0; }
+ | PREVIOUS RECORD { $$ = -2; }
+ | PREVIOUS { $$ = -2; }
+ | NEXT RECORD { $$ = -1; }
+ | NEXT { $$ = -1; }
+ | RECORD { $$ = 0; }
+ ;
+
+read_into: %empty { $$ = NULL; }
+ | INTO scalar { $$ = $scalar; }
+ ;
+
+ /*
+ * read_eofs may have 1 or 2 clauses, plus a boolean that
+ * represents whether the last one is a NOT clause. That is,
+ * there's an AT END clause if there are 2 clauses, or if
+ * there's one clause that is an AT END clause (tf is false).
+ */
+read_eofs: read_eof { $$.nclause = 1; $$.tf = $1; }
+ | read_eofs read_eof
+ {
+ $$ = $1;
+ if( ++$$.nclause > 2 ) {
+ error_msg(@2, "too many AT END conditions");
+ YYERROR;
+ }
+ if( $$.tf == $read_eof ) {
+ error_msg(@2, "duplicate AT END conditions");
+ YYERROR;
+ }
+ parser_fi();
+ }
+ ;
+
+read_eof: END
+ {
+ if( file_read_args.ready() ) {
+ file_read_args.default_march(true);
+ file_read_args.call_parser_file_read();
+ }
+
+ static const struct status_t { file_status_t L, U; }
+ at_end = { FsEofSeq, FsKeySeq },
+ not_at_end = { FsSuccess, FsEofSeq };
+ assert( $1 == END || $1 == NOT );
+ status_t st = $1 == END? at_end : not_at_end;
+ // L <= ec < U
+ cbl_field_t *cond = ast_file_status_between(st.L, st.U);
+
+ parser_if(cond);
+ parser_exception_clear();
+ } statements {
+ parser_else();
+ $$ = $1 == NOT;
+ }
+ ;
+
+write_eops: write_eop { $$.nclause = 1; $$.tf = $1; }
+ | write_eops write_eop
+ {
+ $$ = $1;
+ if( ++$$.nclause > 2 ) {
+ error_msg(@2, "too many AT EOP conditions");
+ YYERROR;
+ }
+ if( $$.tf == $write_eop ) {
+ error_msg(@2, "duplicate AT EOP conditions");
+ YYERROR;
+ }
+ }
+ ;
+
+write_eop: EOP
+ {
+ // cond represents the _FILE_STATUS of the last WRITE.
+ static cbl_field_t *cond = constant_of(constant_index(ZERO));
+
+ if( file_write_args.ready() ) {
+ file_write_args.call_parser_file_write(true);
+ cond = ast_file_status_between(FsEofSeq, FsKeySeq);
+ }
+ assert( $1 == EOP || $1 == NOT );
+ if( $1 == NOT ) {
+ parser_logop(cond, NULL, not_op, cond);
+ }
+ parser_if(cond);
+ parser_exception_clear();
+ } statements {
+ parser_else();
+ parser_fi();
+ $$ = $1 == NOT;
+ }
+ ;
+
+read_key: %empty { $$ = new cbl_refer_t(); }
+ | KEY is name { $$ = new cbl_refer_t($name); }
+ ;
+
+write: write_file
+ {
+ current.declaratives_evaluate( $1.file, $1.handled );
+ }
+ ;
+
+write_file: WRITE write_body
+ {
+ $$.file = $2; $$.handled = FsSuccess;
+ bool sequentially = $$.file->access == file_access_seq_e;
+ file_write_args.call_parser_file_write(sequentially);
+ }
+ | WRITE write_body END_WRITE
+ {
+ $$.file = $2; $$.handled = FsSuccess;
+ bool sequentially = $$.file->access == file_access_seq_e;
+ file_write_args.call_parser_file_write(sequentially);
+ }
+ | WRITE write_body write_eops[err] {
+ bool handled = $err.nclause == 2 || !$err.tf;
+ $$.file = $2; $$.handled = handled? FsEofSeq : FsSuccess;
+ }
+ | WRITE write_body write_eops[err] END_WRITE {
+ bool handled = $err.nclause == 2 || !$err.tf;
+ $$.file = $2; $$.handled = handled? FsEofSeq : FsSuccess;
+ }
+ | WRITE write_body io_invalids[err] {
+ bool handled = $err.nclause == 2 || !$err.tf;
+ $$.file = $2; $$.handled = handled? FsEofSeq : FsSuccess;
+ parser_fi();
+ }
+ | WRITE write_body io_invalids[err] END_WRITE {
+ bool handled = $err.nclause == 2 || !$err.tf;
+ $$.file = $2; $$.handled = handled? FsEofSeq : FsSuccess;
+ parser_fi();
+ }
+ ;
+
+write_body: write_what[field] advance_when[when] advancing
+ {
+ statement_begin(@$, WRITE);
+ cbl_file_t *file = symbol_record_file($field);
+ if( !file ) {
+ error_msg(@1, "no FD record found for %s", $field->name);
+ YYERROR;
+ }
+ $$ = file_write_args.init( file, $field, $when==AFTER, $advancing );
+ current.declaratives_evaluate( file );
+ }
+ | write_what[field]
+ {
+ statement_begin(@$, WRITE);
+ cbl_file_t *file = symbol_record_file($field);
+ if( !file ) {
+ error_msg(@1, "no FD record found for %s", $field->name);
+ YYERROR;
+ }
+ cbl_refer_t lines;
+ switch(file->org) {
+ case file_sequential_e:
+ break;
+ case file_line_sequential_e:
+ lines.field = literally_one;
+ break;
+ case file_disorganized_e:
+ case file_indexed_e:
+ case file_relative_e:
+ break;
+ }
+ $$ = file_write_args.init( file, $field, false, &lines );
+ }
+ ;
+write_what: file_record FROM alpha_val[input]
+ {
+ $$ = $1;
+ parser_move($$, *$input);
+ }
+ | file_record
+ ;
+file_record: NAME
+ {
+ name_queue.qualify(@1, $1);
+ auto namelocs( name_queue.pop() );
+ auto names( name_queue.namelist_of(namelocs) );
+ auto inner = namelocs.back();
+ if( ($$ = field_find(names)) == NULL ) {
+ error_msg(inner.loc, "no record name '%s'", inner.name);
+ YYERROR;
+ }
+ }
+ | NAME inof filename
+ {
+ std::list<const char *> names = {$filename->name, $NAME};
+ auto record = symbol_find(names);
+ if( !record ) {
+ error_msg(@$, "%s IN %s not found",
+ $NAME, $filename->name);
+ YYERROR;
+ }
+ $$ = cbl_field_of(record);
+ }
+ | FILE_KW filename
+ {
+ $$ = cbl_field_of(symbol_at($filename->default_record));
+ }
+ ;
+advance_when: BEFORE { $$ = BEFORE; }
+ | AFTER { $$ = AFTER; }
+ ;
+
+advancing: advance_by
+ | ADVANCING advance_by { $$ = $2; }
+ ;
+advance_by: scalar lines { $$ = $1; } /* BUG: should accept reference */
+ | signed_literal lines { $$ = new_reference($1); }
+ | PAGE
+ {
+ /*
+ * The standard says behavior is undefined when the
+ * number of lines is negative. So, we use the
+ * negative Number Of The Beast as a PAGE flag.
+ */
+ $$ = new_reference( new_literal("-666") );
+ }
+ | device_name { $$ = new_reference(literally_one); }
+ ;
+
+io_invalids: io_invalid { $$.nclause = 1; $$.tf = $io_invalid; }
+ | io_invalids io_invalid
+ {
+ $$ = $1;
+ if( ++$$.nclause > 2 ) {
+ error_msg(@2, "too many INVALID clauses");
+ YYERROR;
+ }
+ if( $$.tf == $io_invalid ) {
+ error_msg(@2, "duplicate INVALID conditions");
+ YYERROR;
+ }
+ parser_fi();
+ }
+ ;
+
+io_invalid: INVALID key {
+ if( file_delete_args.ready() ) {
+ file_delete_args.call_parser_file_delete(false);
+ }
+ if( file_read_args.ready() ) {
+ file_read_args.default_march(false);
+ file_read_args.call_parser_file_read();
+ }
+ if( file_rewrite_args.ready() ) {
+ file_rewrite_args.call_parser_file_rewrite(false);
+ }
+ if( file_start_args.ready() ) {
+ file_start_args.call_parser_file_start();
+ }
+ if( file_write_args.ready() ) {
+ file_write_args.call_parser_file_write(false);
+ }
+
+ static const struct status_t { file_status_t L, U; }
+ invalid = { FsKeySeq, FsOsError },
+ not_invalid = { FsSuccess, FsEofSeq };
+ assert( $1 == INVALID || $1 == NOT );
+ status_t st = $1 == INVALID? invalid : not_invalid;
+ // L <= ec < U
+ cbl_field_t *cond = ast_file_status_between(st.L, st.U);
+
+ parser_if(cond);
+ parser_exception_clear();
+ } statements {
+ parser_else();
+ $$ = $1 == NOT;
+ }
+ ;
+
+delete: delete_impl end_delete
+ | delete_cond end_delete
+ ;
+delete_impl: DELETE delete_body[file]
+ {
+ file_delete_args.call_parser_file_delete(true);
+ current.declaratives_evaluate( $file );
+ }
+ ;
+delete_cond: DELETE delete_body[file] io_invalids
+ {
+ if( is_sequential($file) ) {
+ error_msg(@2, "INVALID KEY phrase invalid for sequential file '%s'",
+ $file->name);
+ YYERROR;
+ }
+ if( $file->access == file_access_seq_e ) {
+ error_msg(@2, "INVALID KEY phrase invalid for "
+ "sequential access mode on '%s'",
+ $file->name);
+ YYERROR;
+ }
+ parser_fi();
+ // call happens in io_invalid
+ current.declaratives_evaluate( $file );
+ }
+ ;
+
+delete_body: filename[file] record
+ {
+ statement_begin(@1, DELETE);
+ file_delete_args.init( $file );
+ $$ = $file;
+ }
+ ;
+end_delete: %empty %prec DELETE
+ | END_DELETE
+ ;
+
+rewrite: rewrite1
+ {
+ current.declaratives_evaluate($1.file, $1.handled);
+ }
+ ;
+
+rewrite1: REWRITE rewrite_body end_rewrite {
+ $$.file = $2.file; $$.handled = FsSuccess;
+ file_rewrite_args.call_parser_file_rewrite( true );
+ }
+ | REWRITE rewrite_body io_invalids[err] end_rewrite {
+ bool handled = $err.nclause == 2 || !$err.tf;
+ $$.file = $2.file; $$.handled = handled? FsNotFound : FsSuccess;
+
+ if( is_sequential($$.file) ) {
+ error_msg(@2, "INVALID KEY for sequential file '%s'",
+ $$.file->name);
+ YYERROR;
+ }
+ if( $$.file->relative_sequential() ) {
+ error_msg(@2, "%s: INVALID KEY may not be specified for "
+ "RELATIVE file and SEQUENTIAL access",
+ $$.file->name);
+ YYERROR;
+ }
+ parser_fi();
+ }
+ ;
+
+rewrite_body: write_what record
+ {
+ statement_begin(@$, REWRITE);
+ symbol_elem_t *e = symbol_file(PROGRAM, $1->name);
+ file_rewrite_args.init(cbl_file_of(e), $1);
+ $$.file = cbl_file_of(e);
+ $$.buffer = $1;
+ }
+ ;
+end_rewrite: %empty %prec REWRITE
+ | END_REWRITE
+ ;
+
+start: start_impl end_start
+ | start_cond end_start
+ ;
+start_impl: START start_body
+ ;
+start_cond: START start_body io_invalids {
+ parser_fi();
+ }
+ ;
+end_start: %empty %prec START
+ | END_START
+ ;
+
+start_body: filename[file]
+ {
+ statement_begin(@$, START);
+ file_start_args.init(@file, $file);
+ parser_file_start( $file, lt_op, 0 );
+ }
+ | filename[file] KEY relop name[key]
+ { // lexer swallows IS, although relop allows it.
+ statement_begin(@$, START);
+ int key = $file->key_one($key);
+ int size = key == 0 ? 0 : $file->keys[key - 1].size();
+ auto ksize = new_tempnumeric();
+ parser_set_numeric(ksize, size);
+ if( yydebug ) {
+ yywarn("START: key #%d '%s' has size %d",
+ key, $key->name, size);
+ }
+ file_start_args.init(@file, $file);
+ parser_file_start( $file, relop_of($relop), key, ksize );
+ }
+ | filename[file] KEY relop name[key] with LENGTH expr
+ { // lexer swallows IS, although relop allows it.
+ statement_begin(@$, START);
+ int key = $file->key_one($key);
+ file_start_args.init(@file, $file);
+ parser_file_start( $file, relop_of($relop), key, *$expr );
+ }
+ | filename[file] FIRST
+ {
+ statement_begin(@$, START);
+ file_start_args.init(@file, $file);
+ parser_file_start( $file, lt_op, -1 );
+ }
+ | filename[file] LAST
+ {
+ statement_begin(@$, START);
+ file_start_args.init(@file, $file);
+ parser_file_start( $file, gt_op, -2 );
+ }
+ ;
+
+merge: MERGE { statement_begin(@1, MERGE); }
+ filename[file] sort_keys sort_seq
+ USING filenames[inputs] sort_output
+ {
+ size_t nkey = $sort_keys->key_list.size();
+ cbl_key_t keys[nkey], *pkey = keys;
+
+ for( auto p = $sort_keys->key_list.begin();
+ p != $sort_keys->key_list.end(); p++, pkey++ )
+ {
+ cbl_key_t k(*p);
+ *pkey = k;
+ }
+
+ size_t ninput = $inputs->files.size();
+ size_t noutput = $sort_output->nfile();
+ cbl_file_t **inputs = NULL, **outputs = NULL;
+ cbl_perform_tgt_t *out_proc = NULL;
+
+ inputs = new cbl_file_t * [ ninput ];
+ std::copy($inputs->files.begin(),
+ $inputs->files.end(), inputs);
+
+ if( noutput > 0 ) {
+ outputs = new cbl_file_t * [ noutput ];
+ std::copy($sort_output->file_list.files.begin(),
+ $sort_output->file_list.files.end(), outputs);
+ } else {
+ out_proc = &$sort_output->tgt;
+ }
+
+ parser_file_merge( $file, $sort_seq,
+ nkey, keys,
+ ninput, inputs,
+ noutput, outputs,
+ out_proc );
+ }
+ ;
+
+set_tgts: set_tgt {
+ $$ = new tgt_list_t;
+ list_add($$->targets, *$set_tgt, current_rounded_mode());
+ }
+ | set_tgts set_tgt
+ {
+ list_add($1->targets, *$set_tgt, current_rounded_mode());
+ }
+ ;
+set_operand: set_tgt
+ | signed_literal { $$ = new_reference($1); }
+ | ADDRESS of FUNCTION ctx_name[name]
+ {
+ $$ = NULL;
+ auto e = symbol_function(0, $name);
+ if( e ) {
+ $$ = new cbl_refer_t(cbl_label_of(e));
+ } else {
+ e = symbol_find(@name, $name);
+ if( !e ) {
+ error_msg(@name, "%s not found", $name);
+ YYERROR;
+ }
+ $$ = new cbl_refer_t(cbl_field_of(e));
+ }
+ assert($$);
+ }
+ | ADDRESS of PROGRAM_kw ctx_name[name]
+ {
+ $$ = NULL;
+ auto label = symbol_program(0, $name);
+ if( label ) {
+ $$ = new cbl_refer_t(label);
+ } else {
+ auto e = symbol_find(@name, $name);
+ if( !e ) {
+ error_msg(@name, "%s not found", $name);
+ YYERROR;
+ }
+ $$ = new cbl_refer_t(cbl_field_of(e));
+ }
+ assert($$);
+ }
+ | ADDRESS of PROGRAM_kw LITERAL[lit]
+ {
+ auto label = symbol_program(0, $lit.data);
+ $$ = new cbl_refer_t( label );
+ }
+ ;
+set_tgt: scalar
+ | ADDRESS of scalar { $$ = $scalar; $$->addr_of = true; }
+ ;
+
+set: SET set_tgts[tgts] TO set_operand[src]
+ {
+ statement_begin(@1, SET);
+
+ switch( set_operand_type(*$src) ) {
+ case FldInvalid:
+ if( ! ($src->prog_func && $src->addr_of) ) {
+ error_msg(@src, "SET source operand '%s' is invalid", $src->name());
+ YYERROR;
+ break;
+ }
+ __attribute__((fallthrough));
+ case FldPointer:
+ if( !valid_set_targets(*$tgts, true) ) {
+ YYERROR;
+ }
+ ast_set_pointers($tgts->targets, *$src);
+ break;
+
+ case FldIndex:
+ case FldPacked:
+ case FldNumericDisplay:
+ case FldNumericBinary:
+ case FldFloat:
+ case FldNumericBin5:
+ case FldLiteralN:
+ if( !valid_set_targets(*$tgts, $src->is_pointer()) ) {
+ YYERROR;
+ }
+ parser_index($tgts, *$src);
+ break;
+ default:
+ if( strcmp($src->field->name, "ZEROS") != 0 ) {
+ error_msg(@src, "%s must be numeric or POINTER type",
+ $src->field->name);
+ YYERROR;
+ }
+ }
+ }
+ | SET set_tgts[tgts] TO NULLS[src]
+ {
+ statement_begin(@1, SET);
+ if( !valid_set_targets(*$tgts, true) ) {
+ YYERROR;
+ }
+ ast_set_pointers($tgts->targets, constant_of(constant_index(NULLS)));
+ }
+ | SET set_tgts TO spaces_etc[error]
+ {
+ error_msg(@2, "invalid value for SET TO");
+ }
+ | SET set_tgts[tgts] TO ENTRY scalar[src]
+ {
+ ast_set_pointers($tgts->targets, *$src);
+ }
+ | SET set_tgts[tgts] TO ENTRY LITERAL[src]
+ {
+ auto literal = $src.isymbol()?
+ cbl_field_of(symbol_at($src.isymbol()))
+ :
+ new_literal($src, quoted_e);
+ ast_set_pointers($tgts->targets, literal);
+ }
+ | SET set_tgts[tgts] UP BY num_operand[src]
+ {
+ statement_begin(@1, SET);
+ list<cbl_num_result_t>& tgts = $tgts->targets;
+
+ for( auto p = tgts.begin(); p != tgts.end(); p++ ) {
+ parser_add2( *p, *$src );
+ }
+ delete $tgts;
+ }
+ | SET set_tgts[tgts] DOWN BY num_operand[src]
+ {
+ statement_begin(@1, SET);
+ list<cbl_num_result_t>& tgts = $tgts->targets;
+
+ for( auto p = tgts.begin(); p != tgts.end(); p++ ) {
+ parser_subtract2( *p, *$src );
+ }
+ delete $tgts;
+ }
+ | SET ENVIRONMENT envar TO alpha_val[scalar]
+ {
+ statement_begin(@1, SET);
+ parser_set_envar(*$envar, *$scalar);
+ }
+ | SET LAST EXCEPTION TO OFF
+ {
+ statement_begin(@1, SET);
+ // send the signal to clear the stashed exception values
+ parser_exception_raise(ec_none_e);
+ }
+ | SET LENGTH_OF scalar TO scalar
+ {
+ statement_begin(@1, SET);
+ cbl_unimplemented("SET LENGTH OF");
+ YYERROR;
+ }
+ | SET scalar88s[names] TO true_false[yn]
+ {
+ statement_begin(@1, SET);
+ class set_conditional {
+ bool tf;
+ public:
+ set_conditional( int token ) : tf(token == TRUE_kw) {}
+ void operator()(cbl_refer_t& refer) {
+ if( refer.field->data.false_value == NULL && !tf ) {
+ auto loc = symbol_field_location(field_index(refer.field));
+ error_msg(loc, "%s has no WHEN SET TO FALSE",
+ refer.field->name);
+ return;
+ }
+ parser_set_conditional88(refer, tf);
+ }
+ };
+ std::for_each($names->refers.begin(), $names->refers.end(),
+ set_conditional($yn));
+ }
+ | SET { statement_begin(@1, SET); } many_switches
+ ;
+
+many_switches: set_switches
+ | many_switches set_switches
+ ;
+
+set_switches: switches TO on_off
+ {
+ struct switcheroo {
+ bitop_t op;
+ switcheroo( bool tf ) : op( tf? bit_set_op : bit_clear_op ) {}
+ switcheroo& operator()(cbl_field_t* sw) {
+ assert(sw->type == FldSwitch);
+ assert(sw->data.initial); // not a switch condition
+ parser_bitop(NULL, parent_of(sw),
+ op, sw->data.upsi_mask_of());
+ return *this;
+ }
+ };
+ std::for_each( $switches->fields.begin(), $switches->fields.end(),
+ switcheroo($on_off) );
+ }
+ ;
+
+switches: one_switch { $$ = new field_list_t($1); }
+ | switches one_switch[sw] { $$->fields.push_back($sw); }
+ ;
+one_switch: SWITCH {
+ $$ = cbl_field_of(symbol_find(@1, $1));
+ }
+ ;
+
+on_off: ON { $$ = true; }
+ | OFF { $$ = false; }
+ ;
+
+search: search_linear end_search
+ | search_binary end_search
+ ;
+
+search_linear: SEARCH search_1_place search_1_cases
+ {
+ parser_lsearch_end(search_current());
+ search_free();
+ }
+ ;
+end_search: %empty %prec SEARCH
+ | END_SEARCH
+ ;
+
+search_1_place: search_1_body
+ | search_1_body at END statements
+ ;
+
+search_1_body: name[table] search_varying[varying]
+ {
+ statement_begin(@$, SEARCH);
+ cbl_field_t *index = table_primary_index($table);
+ if( !index ) {
+ error_msg(@1, "%s has no defined index", $table->name);
+ YYERROR;
+ }
+
+ cbl_name_t label_name;
+ auto len = snprintf(label_name, sizeof(label_name),
+ "linear_search_%d", yylineno);
+ if( ! (0 < len && len < int(sizeof(label_name))) ) {
+ gcc_unreachable();
+ }
+ cbl_label_t *name = label_add( LblSearch,
+ label_name, yylineno );
+ auto varying($varying);
+ if( index == varying ) varying = NULL;
+ parser_lsearch_start( name, $table, index, varying );
+ search_alloc(name);
+ }
+ ;
+
+search_varying: %empty { $$ = NULL; }
+ | VARYING name { $$ = $2; }
+ ;
+
+search_1_cases: search_1_case
+ {
+ if( yydebug ) {
+ const char *lookahead = "?";
+ switch( yychar ) {
+ case 0: lookahead = "YYEOF"; break;
+ case -2: lookahead = "YYEMPTY"; break;
+ default:
+ if( yychar > 0 ) {
+ lookahead = keyword_str(yychar);
+ }
+ }
+ yywarn("Just one case, lookahead is '%s'", lookahead);
+ }
+ }
+ | search_1_cases search_1_case
+ ;
+search_1_case: search_1_when search_1_test search_stmts
+ ;
+search_1_when: WHEN { parser_lsearch_conditional(search_current()); }
+ ;
+search_1_test: bool_expr {
+ parser_lsearch_when( search_current(), $bool_expr->cond() );
+ }
+ ;
+
+search_binary: SEARCH ALL search_2_body search_2_cases
+ {
+ parser_bsearch_end(search_current());
+ search_free();
+ }
+ | SEARCH ALL search_2_body at END statements search_2_cases
+ {
+ parser_bsearch_end(search_current());
+ search_free();
+ }
+ ;
+
+search_2_body: name[table]
+ {
+ statement_begin(@$, SEARCH);
+ char *label_name = xasprintf("binary_search_%d", yylineno);
+ cbl_label_t *name = label_add( LblSearch,
+ label_name, yylineno );
+ parser_bsearch_start( name, $table );
+ search_alloc(name);
+ }
+ ;
+
+search_2_cases: search_2_case
+ | search_2_cases search_2_case
+ ;
+search_2_case: WHEN { parser_bsearch_conditional(search_current()); }
+ search_terms search_stmts
+ ;
+
+search_stmts: statements %prec ADD
+ | NEXT SENTENCE %prec ADD {
+ next_sentence = label_add(LblNone, "next_sentence", 0);
+ parser_label_goto(next_sentence);
+ }
+ ;
+
+search_terms: search_term
+ | search_terms AND search_term
+ ;
+search_term: scalar[key] '=' search_expr[sarg]
+ {
+ if( $key->nsubscript == 0 ) {
+ error_msg(@1, "no index for key");
+ YYERROR;
+ }
+ if( dimensions($key->field) < $key->nsubscript ) {
+ error_msg(@1, "too many subscripts: "
+ "%zu for table of %zu dimensions",
+ $key->nsubscript, dimensions($key->field) );
+ YYERROR;
+ }
+
+ parser_bsearch_when( search_current(),
+ *$key,
+ *$sarg,
+ is_ascending_key(*$key) );
+ }
+ | scalar88[sarg] {
+ cbl_field_t *key = field_at($sarg->field->parent);
+ parser_bsearch_when( search_current(), key, *$sarg,
+ is_ascending_key(key) );
+ }
+ ;
+search_expr: expr
+ | LITERAL { $$ = new_reference(new_literal($1, quoted_e)); }
+ ;
+
+sort: sort_table
+ | sort_file
+ ;
+
+sort_table: SORT tableref[table] sort_keys sort_dup sort_seq {
+ statement_begin(@1, SORT);
+ size_t nkey = $sort_keys->key_list.size();
+ cbl_key_t keys[nkey], *pkey = keys;
+ if( ! is_table($table->field) ) {
+ error_msg(@1, "%s has no OCCURS clause", $table->field->name);
+ }
+ // 23) If data-name-1 is omitted, the data item referenced by
+ // data-name-2 is the key data item.
+ for( auto k : $sort_keys->key_list ) {
+ if( k.fields.empty() ) {
+ k.fields.push_back($table->field);
+ }
+ *pkey++ = cbl_key_t(k);
+ }
+
+ parser_sort( *$table, $sort_dup, $sort_seq, nkey, keys );
+ }
+ | SORT tableref[table] sort_dup sort_seq {
+ statement_begin(@1, SORT);
+ if( ! is_table($table->field) ) {
+ error_msg(@1, "%s has no OCCURS clause", $table->field->name);
+ }
+ cbl_key_t
+ key = cbl_key_t($table->field->occurs.keys[0]),
+ guess(1, &$table->field);
+ ;
+ if( key.nfield == 0 ) key = guess;
+ parser_sort( *$table, $sort_dup, $sort_seq, 1, &key );
+ }
+ ;
+
+sort_file: SORT FILENAME[file] sort_keys sort_dup sort_seq
+ sort_input sort_output
+ {
+ statement_begin(@1, SORT);
+ struct symbol_elem_t *e = symbol_file(PROGRAM, $file);
+ if( !(e && e->type == SymFile) ) {
+ error_msg(@file, "invalid file name");
+ YYERROR;
+ }
+ cbl_file_t *file = cbl_file_of(e);
+ size_t nkey = $sort_keys->key_list.size();
+ cbl_key_t keys[nkey], *pkey = keys;
+
+ for( auto p = $sort_keys->key_list.begin();
+ p != $sort_keys->key_list.end(); p++, pkey++ )
+ {
+ cbl_key_t k(*p);
+ *pkey = k;
+ }
+
+ size_t ninput = $sort_input->nfile();
+ size_t noutput = $sort_output->nfile();
+ cbl_file_t **inputs = NULL, **outputs = NULL;
+ cbl_perform_tgt_t *in_proc = NULL, *out_proc = NULL;
+
+ if( ninput > 0 ) {
+ inputs = new cbl_file_t * [ ninput ];
+ std::copy($sort_input->file_list.files.begin(),
+ $sort_input->file_list.files.end(), inputs);
+ } else {
+ in_proc = &$sort_input->tgt;
+ }
+ if( noutput > 0 ) {
+ outputs = new cbl_file_t * [ noutput ];
+ std::copy($sort_output->file_list.files.begin(),
+ $sort_output->file_list.files.end(), outputs);
+ } else {
+ out_proc = &$sort_output->tgt;
+ }
+
+ parser_file_sort( file,
+ $sort_dup,
+ $sort_seq,
+ nkey, keys,
+ ninput, inputs,
+ noutput, outputs,
+ in_proc, out_proc );
+ }
+ | SORT FILENAME[file] sort_keys sort_dup sort_seq error
+ {
+ error_msg(@file, "SORT missing INPUT or OUTPUT phrase");
+ }
+
+
+sort_keys: sort_key {
+ $$ = new sort_keys_t();
+ $$->key_list.push_back(*$sort_key);
+ }
+ | sort_keys sort_key { $$->key_list.push_back(*$sort_key); }
+ ;
+
+sort_key: on forward_order key field_list %prec NAME
+ {
+ $$ = new sort_key_t( $forward_order, *$field_list );
+ }
+ | on forward_order key %prec NAME
+ {
+ field_list_t flist;
+ $$ = new sort_key_t( $forward_order, flist );
+ }
+ ;
+
+forward_order: ASCENDING { $$ = true; }
+ | DESCENDING { $$ = false; }
+ ;
+field_list: name { $$ = new field_list_t($1); }
+ | field_list name { $1->fields.push_back($name); }
+ ;
+
+sort_dup: %empty { $$ = false; }
+ | with DUPLICATES in order { $$ = true; }
+ ;
+sort_seq: %empty { $$ = NULL; }
+ | collating SEQUENCE is ctx_name[name]
+ {
+ symbol_elem_t *e = symbol_alphabet(PROGRAM, $name);
+ if( !e ) {
+ error_msg(@name, "not an alphabet: '%s'", $name);
+ $$ = NULL;
+ }
+ $$ = cbl_alphabet_of(e);
+ }
+ ;
+
+sort_input: USING filenames
+ {
+ $$ = new file_sort_io_t(*$2);
+ delete $2;
+ }
+ | INPUT PROCEDURE is sort_target
+ {
+ $$ = new file_sort_io_t(*$sort_target);
+ delete $sort_target;
+ }
+ ;
+sort_output: GIVING filenames
+ {
+ $$ = new file_sort_io_t(*$2);
+ }
+ | OUTPUT PROCEDURE is sort_target
+ {
+ $$ = new file_sort_io_t(*$sort_target);
+ }
+ ;
+
+sort_target: label_name
+ {
+ $$ = new cbl_perform_tgt_t($1);
+ }
+ | label_name THRU label_name
+ {
+ $$ = new cbl_perform_tgt_t($1, $3);
+ }
+ ;
+
+release: RELEASE NAME[record] FROM scalar[name]
+ {
+ statement_begin(@1, RELEASE);
+ symbol_elem_t *record = symbol_find(@record, $record);
+ parser_move(cbl_field_of(record), *$name);
+ parser_release(cbl_field_of(record));
+ }
+ | RELEASE NAME[record]
+ {
+ statement_begin(@1, RELEASE);
+ symbol_elem_t *record = symbol_find(@record, $record);
+ parser_release(cbl_field_of(record));
+ }
+ ;
+
+return_stmt: return_impl return_end
+ | return_cond return_end
+ ;
+
+return_impl: RETURN return_body[body]
+ {
+ cbl_file_t *file = cbl_file_of(symbol_at(current_sort_file));
+ parser_return_finish(file);
+ current_sort_file = $body;
+ }
+ ;
+
+return_cond: RETURN return_body[body] return_outputs
+ {
+ cbl_file_t *file = cbl_file_of(symbol_at(current_sort_file));
+ parser_return_finish(file);
+ current_sort_file = $body;
+ }
+ ;
+return_end: %empty %prec RETURN
+ | END_RETURN
+ ;
+
+return_body: return_file
+ {
+ file_return_args.call_parser_return_start();
+ }
+ | return_file INTO scalar
+ {
+ file_return_args.call_parser_return_start(*$scalar);
+ }
+ ;
+
+return_file: filename
+ {
+ statement_begin(@$, RETURN);
+ $$ = current_sort_file; // preserve current sort file
+ current_sort_file = symbol_index(symbol_elem_of($filename));
+ file_return_args.init($filename);
+ }
+ | filename RECORD
+ {
+ statement_begin(@$, RETURN);
+ $$ = current_sort_file; // preserve current sort file
+ current_sort_file = symbol_index(symbol_elem_of($filename));
+ file_return_args.init($filename);
+ }
+ ;
+return_outputs: return_output
+ | return_outputs return_output // TODO: only 2, AT END and/or NOT AT END
+ ;
+return_output: output_atend statements %prec RETURN
+ ;
+
+output_atend: END {
+ assert($1 == END || $1 == NOT);
+ auto func = $1 == END?
+ parser_return_atend : parser_return_notatend ;
+ func(cbl_file_of(symbol_at(current_sort_file)));
+ }
+ ;
+filenames: filename { $$ = new file_list_t($1); }
+ | filenames filename { $1->files.push_back($2); }
+ ;
+filename: NAME
+ {
+ struct symbol_elem_t *e = symbol_file(PROGRAM, $1);
+ if( !(e && e->type == SymFile) ) {
+ error_msg(@NAME, "invalid file name");
+ YYERROR;
+ }
+ $$ = cbl_file_of(e);
+ }
+ ;
+
+label_name: NAME
+ {
+ struct cbl_label_t *label = symbol_label(PROGRAM,
+ LblNone, 0, $1);
+ if( !label ) { // no line number for forward declaraion
+ label = label_add(@NAME, LblNone, $1);
+ }
+ $$ = label;
+ }
+ ;
+
+inspected: scalar
+ | intrinsic_call
+ ;
+backward: %empty { $$ = false; }
+ | BACKWARD { $$ = true; }
+ ;
+inspect: INSPECT backward inspected TALLYING tallies
+ {
+ statement_begin(@1, INSPECT);
+ ast_inspect( *$inspected, $backward, *$tallies );
+ }
+ | INSPECT backward inspected TALLYING tallies REPLACING replacements
+ {
+ if( is_constant($inspected->field) ) {
+ auto name = nice_name_of($inspected->field);
+ if( !name[0] ) name = "its argument";
+ error_msg(@inspected, "INSPECT cannot write to %s", name);
+ YYERROR;
+ }
+ statement_begin(@1, INSPECT);
+ // All tallying is done before any replacing
+ ast_inspect( *$inspected, $backward, *$tallies );
+ ast_inspect( *$inspected, $backward, *$replacements );
+ }
+ | INSPECT backward inspected REPLACING replacements
+ {
+ if( is_constant($inspected->field) ) {
+ auto name = nice_name_of($inspected->field);
+ if( !name[0] ) name = "its argument";
+ error_msg(@inspected, "INSPECT cannot write to %s", name);
+ YYERROR;
+ }
+ statement_begin(@1, INSPECT);
+ ast_inspect( *$inspected, $backward, *$replacements );
+ }
+ | INSPECT backward inspected CONVERTING alpha_val[match]
+ TO all alpha_val[replace_oper]
+ insp_mtquals[qual]
+ {
+ if( $all ) {
+ $replace_oper->all = true;
+ if( is_literal($replace_oper->field) ) {
+ if( $replace_oper->field->data.capacity != 1 ) {
+ error_msg(@all, "ALL %s must be a single character",
+ $replace_oper->field->data.initial);
+ YYERROR;
+ }
+ } else {
+ error_msg(@all, "ALL must be part of a figurative constant");
+ YYERROR;
+ }
+ }
+ if( is_constant($inspected->field) ) {
+ auto name = nice_name_of($inspected->field);
+ if( !name[0] ) name = "its argument";
+ error_msg(@inspected, "INSPECT cannot write to %s", name);
+ YYERROR;
+ }
+ statement_begin(@1, INSPECT);
+ // IBM Format 4 does not show the qualifiers as optional, but
+ // they don't appear in Listing-15-1.
+ parser_inspect_conv( *$inspected, $backward,
+ *$match,
+ *$replace_oper,
+ $qual->before, $qual->after );
+ }
+ ;
+
+tallies: { need_nume_set(); } tally
+ {
+ $$ = new ast_inspect_list_t( *$tally );
+ }
+ | tallies { need_nume_set(); } tally
+ {
+ $$ = $1;
+ cbl_inspect_t& next(*$tally);
+
+ if( !next.tally.field ) {
+ // prior tally swallowed one too many
+ cbl_inspect_t& prior = $$->back();
+ assert(prior.nbound > 0);
+ assert(prior.opers);
+ cbl_inspect_oper_t& prior_op = prior.opers[prior.nbound - 1];
+
+ assert(prior_op.n_identifier_3 > 0 );
+ next.tally = prior_op.matches[--prior_op.n_identifier_3].matching;
+ }
+ if( !next.tally.field ) {
+ error_msg(@$, "missing summation field before FOR");
+ YYERROR;
+ }
+ $$->push_back(next);
+ }
+ ;
+
+ /*
+ * numref might be "empty" only because it was consumed by a
+ * prior insp_mtquals, which can end in a scalar. If that
+ * happens, the tallies target, above, takes back the borrowed
+ * scalar and assigns it to be the tally total, as the user
+ * intended.
+ */
+tally: numeref[total] FOR tally_fors[fors]
+ { // reduce ast_inspect_t to cbl_inspect_t
+ if( yydebug && !$total ) {
+ error_msg(@FOR, "caution: missing summation field before FOR");
+ }
+ cbl_refer_t total( $total? *$total : cbl_refer_t() );
+ $$ = new cbl_inspect_t( total, $fors->opers() );
+ }
+ ;
+
+tally_fors: tally_forth
+ { // reduce ast_inspect_oper_t to cbl_inspect_oper_t
+ cbl_inspect_oper_t oper( $1->bound, $1->matches );
+ $$ = new ast_inspect_t;
+ $$ ->push_back(oper);
+ }
+ | tally_fors tally_forth
+ {
+ cbl_inspect_oper_t oper( $2->bound, $2->matches );
+ $1 ->push_back(oper);
+ }
+ ;
+
+tally_forth: CHARACTERS insp_mtquals[q] scalar[next_tally]
+ {
+ // Add ensuing scalar as if it were an argument to CHARACTERS.
+ // It will be moved to the succeeding FOR as its tally.
+ $q->matching = *$next_tally;
+ $$ = new ast_inspect_oper_t(*$q);
+ }
+ | CHARACTERS insp_mtquals[q]
+ {
+ $$ = new ast_inspect_oper_t(*$q);
+ }
+ | ALL tally_matches[q]
+ { $q->bound = bound_all_e;
+ $$ = $q;
+ }
+ | LEADING tally_matches[q]
+ { $q->bound = bound_leading_e;
+ $$ = $q;
+ }
+ | TRAILING tally_matches[q]
+ { $q->bound = bound_trailing_e;
+ $$ = $q;
+ if( ! dialect_mf() ) {
+ dialect_error(@1, "TRAILING", "mf");
+ }
+ }
+ ;
+
+tally_matches: tally_match { $$ = new ast_inspect_oper_t(*$1); }
+ | tally_matches tally_match
+ { // add to the list of matches for an operand
+ $1->matches.push_back(*$2);
+ }
+ ;
+tally_match: alpha_val[matching] insp_mtquals[q]
+ { // include the matching field with the qualifiers
+ $$ = $q;
+ $$->matching = *$matching;
+ }
+ ;
+
+numeref: %empty { $$ = NULL; need_nume_set(false); }
+ | nume[name] subscripts[subs]
+ {
+ size_t n = $subs->size();
+ auto offsets = new cbl_refer_t[n];
+ std::copy( $subs->begin(), $subs->end(), offsets );
+ $$ = new cbl_refer_t($name, n, offsets);
+ }
+ | nume { $$ = new cbl_refer_t($nume); }
+ ;
+
+nume: qnume {
+ $$ = NULL;
+ struct symbol_elem_t *e = NULL;
+ size_t index = 0;
+ auto names( name_queue.pop() );
+
+ for( ; !names.empty(); names.pop_front() ) {
+ auto nameloc = names.front();
+ if( (e = symbol_field(PROGRAM,
+ index, nameloc.name)) == NULL ) {
+ error_msg(nameloc.loc, "DATA-ITEM '%s' not found", nameloc.name );
+ YYERROR;
+ }
+ $$ = cbl_field_of(e);
+ index = symbol_index(e);
+ }
+ }
+ ;
+
+qnume: NUME { name_queue.qualify(@1, $1); }
+ | qnume inof NUME { name_queue.qualify(@3, $3); }
+ ;
+
+replacements: replacement
+ {
+ cbl_inspect_t inspect( cbl_refer_t(), $1->opers() );
+ $$ = new ast_inspect_list_t(inspect);
+ }
+ ;
+replacement: replace_oper
+ {
+ $$ = new ast_inspect_t;
+ $$->push_back( cbl_inspect_oper_t($1->bound, $1->replaces) );
+ }
+ | replacement replace_oper
+ {
+ $$->push_back( cbl_inspect_oper_t($2->bound, $2->replaces) );
+ }
+ ;
+replace_oper: CHARACTERS BY alpha_val[replace] insp_mtquals[q]
+ {
+ $$ = new ast_inspect_oper_t( cbl_inspect_replace_t(NULL,
+ *$replace,
+ $q->before,
+ $q->after) );
+ }
+ | first_leading x_by_ys %prec NAME
+ {
+ $$ = $2;
+ $$->bound = static_cast<cbl_inspect_bound_t>($1);
+ }
+ ;
+
+x_by_ys: x_by_y
+ {
+ $$ = new ast_inspect_oper_t(*$1);
+ }
+ | x_by_ys x_by_y
+ {
+ $$->replaces.push_back(*$2);
+ }
+ ;
+x_by_y: alpha_val[matching] BY alpha_val[replace] insp_mtquals[q]
+ {
+ $$ = new cbl_inspect_replace_t(*$matching, *$replace,
+ $q->before, $q->after);
+ }
+ ;
+
+insp_mtquals: %empty { $$ = new cbl_inspect_match_t; }
+ | insp_quals
+ ;
+insp_quals: insp_qual {
+ $$ = new cbl_inspect_match_t;
+ if( $insp_qual.before ) {
+ $$->before = *$insp_qual.qual;
+ } else {
+ $$->after = *$insp_qual.qual;
+ }
+ }
+ | insp_quals insp_qual
+ {
+ if( ($$->before.active() && $insp_qual.before) ||
+ ($$->after.active() && !$insp_qual.before) ) {
+ error_msg(@2, "duplicate BEFORE/AFTER phrase");
+ YYERROR;
+ }
+ auto p = $insp_qual.before? &$$->before : &$$->after;
+ *p = *$insp_qual.qual;
+ }
+ ;
+insp_qual: befter initial alpha_val
+ {
+ // NIST NC115A: INITIAL has no effect (GnuCOBOL & ISO say same).
+ bool initial = $initial == INITIAL_kw;
+ $$.before = $befter == BEFORE;
+ $$.qual = new cbl_inspect_qual_t(initial, *$3);
+ }
+ ;
+
+first_leading: FIRST { $$ = bound_first_e; }
+ | ALL { $$ = bound_all_e; }
+ | LEADING { $$ = bound_leading_e; }
+ | TRAILING { $$ = bound_trailing_e;
+ if( ! dialect_mf() ) {
+ dialect_error(@1, "TRAILING", "mf");
+ }
+ }
+ ;
+
+alphaval: LITERAL { $$ = new_reference(new_literal($1, quoted_e)); }
+ | reserved_value
+ {
+ $$ = new_reference( constant_of(constant_index($1)) );
+ }
+ | intrinsic_call
+ ;
+
+befter: BEFORE { $$ = BEFORE; }
+ | AFTER { $$ = AFTER; }
+ ;
+
+initialize: INITIALIZE move_tgts[tgts]
+ {
+ statement_begin(@1, INITIALIZE);
+ initialize_statement( $tgts->targets, false, data_category_none );
+ }
+ | INITIALIZE move_tgts[tgts] with FILLER_kw
+ {
+ statement_begin(@1, INITIALIZE);
+ initialize_statement( $tgts->targets, true, data_category_none );
+ }
+ | INITIALIZE move_tgts[tgts] init_clause[ini]
+ {
+ statement_begin(@1, INITIALIZE);
+ initialize_statement( $tgts->targets, false, $ini->category,
+ $ini->replacement);
+ }
+ | INITIALIZE move_tgts[tgts] init_clause[ini] with FILLER_kw
+ {
+ statement_begin(@1, INITIALIZE);
+ initialize_statement( $tgts->targets, true, $ini->category,
+ $ini->replacement);
+ }
+ | INITIALIZE move_tgts[tgts] with FILLER_kw init_clause[ini]
+ {
+ statement_begin(@1, INITIALIZE);
+ initialize_statement( $tgts->targets, true, $ini->category,
+ $ini->replacement );
+ }
+ ;
+
+init_clause: init_value
+ | init_categora
+ {
+ $$ = new init_statement_t(false);
+ $$->category = $1;
+ }
+ | init_categora to VALUE
+ {
+ $$ = new init_statement_t(true);
+ $$->category = $1;
+ }
+ | init_categora to VALUE init_value
+ {
+ $$ = $init_value;
+ $$->category = $1;
+ }
+ ;
+
+init_value: init_replace then to DEFAULT
+ {
+ $$ = new init_statement_t( *$init_replace);
+ }
+ | init_replace
+ {
+ $$ = new init_statement_t( *$init_replace);
+ }
+ | then to DEFAULT
+ {
+ $$ = new init_statement_t( false );
+ }
+ ;
+
+init_categora: init_category
+ | ALL { $$ = data_category_all; }
+ ;
+init_category: ALPHABETIC { $$ = data_alphabetic_e; }
+ | ALPHANUMERIC { $$ = data_alphanumeric_e; }
+ | ALPHANUMERIC_EDITED { $$ = data_alphanumeric_edited_e; }
+ | DBCS { $$ = data_dbcs_e; }
+ | EGCS { $$ = data_egcs_e; }
+ | NATIONAL { $$ = data_national_e; }
+ | NATIONAL_EDITED { $$ = data_national_edited_e; }
+ | NUMERIC { $$ = data_numeric_e; }
+ | NUMERIC_EDITED { $$ = data_numeric_edited_e; }
+ ;
+
+init_replace: then REPLACING init_bys { $$ = $init_bys; }
+ ;
+init_bys: init_by
+ {
+ $$ = new category_map_t;
+ category_map_t& replacements = *$$;
+ replacements[$init_by.category] = $init_by.replacement;
+ }
+ | init_bys init_by
+ {
+ $$ = $1;
+ category_map_t& replacements = *$$;
+ replacements[$init_by.category] = $init_by.replacement;
+ }
+ ;
+init_by: init_category data BY init_data
+ {
+ $$.category = $init_category;
+ $$.replacement = $init_data;
+ }
+ ;
+init_data: alpha_val
+ | NUMSTR {
+ $$ = new_reference(new_literal($1.string, $1.radix));
+ }
+ ;
+
+call: call_impl end_call
+ | call_cond end_call
+ ;
+
+call_impl: CALL call_body[body]
+ {
+ ffi_args_t *params = $body.using_params;
+ if( yydebug && params ) params->dump();
+ size_t narg = params? params->elems.size() : 0;
+ cbl_ffi_arg_t args[1 + narg], *pargs = NULL;
+ if( narg > 0 ) {
+ pargs = use_list(params, args);
+ }
+ ast_call( $body.loc, *$body.ffi_name,
+ *$body.ffi_returning, narg, pargs, NULL, NULL, false );
+ current.declaratives_evaluate();
+ }
+ ;
+call_cond: CALL call_body[body] call_excepts[except]
+ {
+ ffi_args_t *params = $body.using_params;
+ if( yydebug && params ) params->dump();
+ size_t narg = params? params->elems.size() : 0;
+ cbl_ffi_arg_t args[1 + narg], *pargs = NULL;
+ if( narg > 0 ) {
+ pargs = use_list(params, args);
+ }
+ ast_call( $body.loc, *$body.ffi_name,
+ *$body.ffi_returning, narg, pargs,
+ $except.on_error, $except.not_error, false );
+ auto handled = ec_type_t( static_cast<size_t>(ec_program_e) |
+ static_cast<size_t>(ec_external_e));
+ current.declaratives_evaluate(handled);
+ }
+ ;
+end_call: %empty %prec CALL
+ | END_CALL
+ ;
+
+call_body: ffi_name
+ { statement_begin(@1, CALL);
+ $$.ffi_name = $ffi_name;
+ $$.using_params = NULL;
+ $$.ffi_returning = cbl_refer_t::empty();
+ }
+
+ | ffi_name USING parameters
+ { statement_begin(@1, CALL);
+ $$.ffi_name = $ffi_name;
+ $$.using_params = $parameters;
+ $$.ffi_returning = cbl_refer_t::empty();
+ }
+ | ffi_name call_returning scalar[ret]
+ { statement_begin(@1, CALL);
+ $$.ffi_name = $ffi_name;
+ $$.using_params = NULL;
+ $$.ffi_returning = $ret;
+ }
+ | ffi_name USING parameters call_returning scalar[ret]
+ { statement_begin(@1, CALL);
+ $$.ffi_name = $ffi_name;
+ $$.using_params = $parameters;
+ $$.ffi_returning = $ret;
+ }
+ ;
+call_returning: RETURNING
+ | GIVING {
+ if( !dialect_mf() ) {
+ dialect_error(@1, "CALL ... GIVING", "mf");
+ }
+ }
+ ;
+
+entry: ENTRY LITERAL
+ { statement_begin(@1, ENTRY);
+ auto name = new_literal($2, quoted_e);
+ parser_entry( name );
+ }
+ | ENTRY LITERAL USING parameters
+ { statement_begin(@1, ENTRY);
+ auto name = new_literal($2, quoted_e);
+ ffi_args_t *params = $parameters;
+ size_t narg = params? params->elems.size() : 0;
+ cbl_ffi_arg_t args[1 + narg], *pargs = NULL;
+ if( narg > 0 ) {
+ pargs = use_list(params, args);
+ }
+ parser_entry( name, narg, pargs );
+ }
+ ;
+
+ffi_name: scalar
+ {
+ $$ = $1;
+ if( ! is_callable($1->field) ) {
+ error_msg(@1, "CALL requires %s to be "
+ "PROGRAM-POINTER or alphanumeric", $1->name());
+ YYERROR;
+ }
+ if( $1->field->type == FldLiteralA ) {
+ // Replace repository literal with aliased program's name.
+ assert($1->field->parent > 0);
+ auto& L = *cbl_label_of(symbol_at($1->field->parent));
+ $$->field = new_literal(strlen(L.name), L.name, quoted_e);
+ }
+ }
+ | LITERAL { $$ = new_reference(new_literal($1, quoted_e)); }
+ ;
+
+parameters: parameter { $$ = new ffi_args_t($1); }
+ | parameters parameter
+ {
+ $1->push_back($2);
+ $$ = $1;
+ }
+ ;
+parameter: ffi_by_ref { $$ = $1; $$->crv = by_default_e; }
+ | by REFERENCE ffi_by_ref { $$ = $3; }
+ | by CONTENT ffi_by_con { $$ = $3; }
+ | by VALUE ffi_by_val { $$ = $3; }
+ ;
+ffi_by_ref: scalar_arg[refer]
+ {
+ $$ = new cbl_ffi_arg_t(by_reference_e, $refer);
+ }
+ | ADDRESS OF scalar_arg[refer]
+ {
+ $$ = new cbl_ffi_arg_t(by_reference_e, $refer, address_of_e);
+ }
+ | OMITTED
+ {
+ cbl_refer_t *r = new cbl_refer_t();
+ $$ = new cbl_ffi_arg_t(by_reference_e, r);
+ }
+ ;
+
+ffi_by_con: expr
+ {
+ cbl_refer_t *r = new cbl_refer_t(*$1);
+ $$ = new cbl_ffi_arg_t(by_content_e, r);
+ }
+ | LITERAL
+ {
+ cbl_refer_t *r = new_reference(new_literal($1, quoted_e));
+ $$ = new cbl_ffi_arg_t(by_content_e, r);
+ }
+ | OMITTED
+ {
+ cbl_refer_t *r = new cbl_refer_t();
+ $$ = new cbl_ffi_arg_t(by_content_e, r);
+ }
+ ;
+
+ffi_by_val: by_value_arg
+ {
+ $$ = new cbl_ffi_arg_t(by_value_e, $1);
+ }
+ | cce_expr %prec NAME
+ {
+ auto r = new_reference(new_literal(string_of($1)));
+ $$ = new cbl_ffi_arg_t(by_value_e, r);
+ }
+ | ADDRESS OF scalar
+ {
+ $$ = new cbl_ffi_arg_t(by_value_e, $scalar, address_of_e);
+ }
+ | LENGTH_OF scalar
+ {
+ $$ = new cbl_ffi_arg_t(by_value_e, $scalar, length_of_e);
+ }
+ ;
+
+scalar_arg: scalar
+ | scalar AS FIXED LENGTH %prec NAME
+ ;
+
+call_excepts: call_excepts[a] call_except[b] statements %prec CALL
+ {
+ if( $a.on_error && $a.not_error ) {
+ error_msg(@b, "too many ON EXCEPTION clauses");
+ YYERROR;
+ }
+ // "ON" and "NOT ON" could be reversed, but not duplicated.
+ if( $a.on_error && $b.on_error ) {
+ error_msg(@b, "duplicate ON EXCEPTION clauses");
+ YYERROR;
+ }
+ if( $a.not_error && $b.not_error ) {
+ error_msg(@b, "duplicate NOT ON EXCEPTION clauses");
+ YYERROR;
+ }
+ $$ = $a;
+ if( $b.on_error ) {
+ $$.on_error = $b.on_error;
+ assert($a.not_error);
+ } else {
+ $$.not_error = $b.not_error;
+ assert($a.on_error);
+ }
+ assert( $b.on_error || $b.not_error );
+ assert( ! ($b.on_error && $b.not_error) );
+ cbl_label_t *tgt = $a.on_error? $a.on_error : $a.not_error;
+ parser_call_exception_end(tgt);
+ }
+ | call_except[a] statements %prec CALL
+ {
+ $$ = $a;
+ assert( $a.on_error || $a.not_error );
+ assert( ! ($a.on_error && $a.not_error) );
+ cbl_label_t *tgt = $a.on_error? $a.on_error : $a.not_error;
+ parser_call_exception_end(tgt);
+ }
+ ;
+
+call_except: EXCEPTION
+ {
+ $$.not_error = NULL;
+ $$.on_error = label_add(LblArith,
+ uniq_label("call"), yylineno);
+ if( !$$.on_error ) YYERROR;
+ parser_call_exception( $$.on_error );
+
+ assert( $1 == EXCEPTION || $1 == NOT );
+ if( $1 == NOT ) {
+ std::swap($$.on_error, $$.not_error);
+ }
+ }
+ | OVERFLOW
+ {
+ $$.not_error = NULL;
+ $$.on_error = label_add(LblArith,
+ uniq_label("call"), yylineno);
+ if( !$$.on_error ) YYERROR;
+ parser_call_exception( $$.on_error );
+
+ assert( $1 == OVERFLOW || $1 == NOT );
+ if( $1 == NOT ) {
+ std::swap($$.on_error, $$.not_error);
+ }
+ }
+ ;
+
+cancel: CANCEL ffi_names
+ {
+ statement_begin(@1, CANCEL);
+ auto nprog = $ffi_names->refers.size();
+ cbl_refer_t progs[nprog];
+ parser_initialize_programs(nprog, $ffi_names->use_list(progs));
+ }
+ ;
+ffi_names: ffi_name { $$ = new refer_list_t($1); }
+ | ffi_names ffi_name { $$ = $1->push_back($2); }
+ ;
+
+alter: ALTER { statement_begin(@1, ALTER); } alter_tgts
+ ;
+
+alter_tgts: alter_tgt
+ | alter_tgts alter_tgt
+ ;
+alter_tgt: label_1[old] alter_to label_1[new]
+ {
+ cbl_perform_tgt_t tgt( $old, $new );
+ parser_alter(&tgt);
+
+ auto prog = cbl_label_of( symbol_at(symbol_elem_of($old)->program));
+ if( prog->initial ) {
+ cbl_unimplemented("ALTER %s", $old->name);
+ }
+ }
+ ;
+
+alter_to: TO
+ | TO PROCEED TO
+ ;
+
+go_to: GOTO labels[args]
+ {
+ statement_begin(@1, GOTO);
+ size_t narg = $args->elems.size();
+ if( 1 != narg ) {
+ error_msg(@args, "more than one GO TO label requires DEPENDING");
+ YYERROR;
+ }
+
+ for( auto& label : $args->elems ) {
+ label->used = yylineno;
+ }
+ cbl_label_t *args[narg];
+ parser_goto( cbl_refer_t(), 1, use_list($args, args) );
+ }
+ | GOTO labels[args] DEPENDING on scalar[value]
+ {
+ statement_begin(@1, GOTO);
+ size_t narg = $args->elems.size();
+ assert(narg > 0);
+ for( auto& label : $args->elems ) {
+ label->used = yylineno;
+ }
+ cbl_label_t *args[narg];
+ parser_goto( *$value, narg, use_list($args, args) );
+ }
+ | GOTO
+ {
+ cbl_unimplemented("altered GO TO syntax (format 3)");
+ YYERROR;
+ }
+ ;
+
+resume: RESUME NEXT STATEMENT
+ {
+ statement_begin(@1, RESUME);
+ parser_clear_exception();
+ }
+ | RESUME label_1[tgt]
+ {
+ statement_begin(@1, RESUME);
+ parser_clear_exception();
+ $tgt->used = yylineno;
+ parser_goto( cbl_refer_t(), 1, &$tgt );
+ }
+ ;
+
+labels: label_1 { $$ = new Label_list_t($1); }
+ | labels label_1 { $$ = $1->push_back($2); }
+ ;
+label_1: qname
+ { // Add a forward label with no line number, or get an existing.
+ assert(!name_queue.empty());
+ auto namelocs( name_queue.pop() );
+
+ auto nameloc = namelocs.back();
+ if( namelocs.size() > 2 ) {
+ error_msg(nameloc.loc,
+ "too many qualifications for %s", nameloc.name);
+ YYERROR;
+ }
+ const char *para = nameloc.name;
+ size_t isect = 0;
+
+ if( namelocs.size() == 2 ) {
+ auto nameloc = namelocs.front();
+ cbl_label_t *sect = label_add(nameloc.loc, LblSection, nameloc.name);
+ isect = symbol_index(symbol_elem_of(sect));
+ }
+
+ $$ = paragraph_reference(para, isect);
+ assert($$);
+ if( yydebug ) dbgmsg( "using procedure %s of line %d",
+ $$->name, $$->line );
+ }
+ | NUMSTR
+ {
+ // Add a forward label with no line number, or get an existing.
+ $$ = label_add(@1, LblNone, $1.string);
+ assert($$ != NULL);
+ }
+ ;
+
+ /* string & unstring */
+
+
+string: string_impl end_string
+ | string_cond end_string
+ ;
+string_impl: STRING_kw string_body[body]
+ {
+ stringify($body.inputs, *$body.into.first, *$body.into.second);
+ current.declaratives_evaluate(ec_none_e);
+ }
+ ;
+string_cond: STRING_kw string_body[body] on_overflows[over]
+ {
+ stringify($body.inputs, *$body.into.first, *$body.into.second,
+ $over.on_error, $over.not_error);
+ current.declaratives_evaluate(ec_overflow_e);
+ }
+ ;
+end_string: %empty %prec LITERAL
+ | END_STRING
+ ;
+
+string_body: str_delimiteds[inputs] str_into[into]
+ {
+ statement_begin(@$, STRING_kw);
+ $$.inputs = $inputs;
+ $$.into = $into;
+ }
+ ;
+
+str_delimiteds: str_delimited
+ {
+ refer_marked_list_t marked($1.delimiter, $1.input);
+ $$ = new refer_collection_t(marked);
+ }
+ | str_delimiteds str_delimited[input]
+ {
+ // matching delimiters (or none) adds to the list
+ refer_marked_list_t& marked = $1->lists.back();
+ if( !marked.marker ) {
+ marked.push_on($input.delimiter, $input.input);
+ } else { // start a new list
+ $1->push_back( refer_marked_list_t($input.delimiter,
+ $input.input) );
+ }
+ }
+ ;
+
+str_delimited: str_input DELIMITED by str_size
+ {
+ $$.input = $str_input;
+ $$.delimiter = $str_size;
+ }
+ | str_input
+ {
+ $$.input = $str_input;
+ $$.delimiter = NULL;
+ }
+ ;
+
+str_input: scalar
+ | LITERAL { $$ = new_reference(new_literal($1, quoted_e)); }
+ | reserved_value
+ {
+ $$ = new_reference(constant_of(constant_index($1)));
+ }
+ | intrinsic_call
+ ;
+
+str_size: SIZE { $$ = new_reference(NULL); }
+ | LITERAL { $$ = new_reference(new_literal($1, quoted_e)); }
+ | scalar
+ | reserved_value
+ {
+ $$ = new_reference(constant_of(constant_index($1)));
+ }
+ ;
+
+str_into: INTO scalar
+ {
+ $$.first = $2;
+ $$.second = new_reference(NULL);
+ }
+ | INTO scalar with POINTER scalar[from]
+ {
+ $$.first = $2;
+ $$.second = $from;
+ }
+ ;
+
+on_overflows: on_overflow[over] statements %prec ADD
+ {
+ assert( $over.on_error || $over.not_error );
+ assert( ! ($over.on_error && $over.not_error) );
+ cbl_label_t *tgt = $over.on_error?
+ $over.on_error : $over.not_error;
+ parser_string_overflow_end(tgt);
+ }
+ | on_overflows[a] on_overflow[b] statements %prec ADD
+ {
+ if( $a.on_error && $a.not_error ) {
+ error_msg(@b, "too many ON OVERFLOW clauses");
+ YYERROR;
+ }
+ // "ON" and "NOT ON" could be reversed, but not duplicated.
+ if( $a.on_error && $b.on_error ) {
+ error_msg(@b, "duplicate ON OVERFLOW clauses");
+ YYERROR;
+ }
+ if( $a.not_error && $b.not_error ) {
+ error_msg(@b, "duplicate NOT ON OVERFLOW clauses");
+ YYERROR;
+ }
+ $$ = $a;
+ if( $b.on_error ) {
+ $$.on_error = $b.on_error;
+ assert($a.not_error);
+ } else {
+ $$.not_error = $b.not_error;
+ assert($a.on_error);
+ }
+ assert( $b.on_error || $b.not_error );
+ assert( ! ($b.on_error && $b.not_error) );
+ cbl_label_t *tgt = $b.on_error?
+ $b.on_error : $b.not_error;
+ parser_string_overflow_end(tgt);
+ }
+ ;
+
+on_overflow: OVERFLOW
+ {
+ $$.not_error = NULL;
+ $$.on_error = label_add(LblString,
+ uniq_label("string"), yylineno);
+ if( !$$.on_error ) YYERROR;
+ parser_string_overflow( $$.on_error );
+
+ assert( $1 == OVERFLOW || $1 == NOT );
+ if( $1 == NOT ) {
+ std::swap($$.on_error, $$.not_error);
+ }
+ }
+ ;
+
+unstring: unstring_impl end_unstring
+ | unstring_cond end_unstring
+ ;
+end_unstring: %empty %prec UNSTRING
+ | END_UNSTRING
+ ;
+
+unstring_impl: UNSTRING unstring_body[body]
+ {
+ unstringify( *$body.input, $body.delimited, $body.into );
+ current.declaratives_evaluate(ec_none_e);
+ }
+ ;
+unstring_cond: UNSTRING unstring_body[body] on_overflows[over]
+ {
+ unstringify( *$body.input, $body.delimited, $body.into,
+ $over.on_error, $over.not_error );
+ current.declaratives_evaluate(ec_overflow_e);
+ }
+ ;
+
+unstring_body: unstring_src[src] uns_delimited INTO uns_into[into]
+ {
+ statement_begin(@$, UNSTRING);
+ $$.input = $src;
+ $$.delimited = $uns_delimited;
+ $$.into = $into;
+ }
+unstring_src: scalar
+ | intrinsic_call
+ | LITERAL
+ {
+ $$ = new_reference(new_literal($1, quoted_e));
+ }
+ ;
+
+uns_delimited: %empty { $$ = NULL; }
+ | DELIMITED by uns_delimiters { $$ = $3; }
+ ;
+
+uns_delimiters: uns_delimiter { $$ = new refer_list_t($1); }
+ | uns_delimiters OR uns_delimiter
+ {
+ $$ = $1;
+ $$->push_back($3);
+ }
+ ;
+uns_delimiter: all str_input
+ {
+ $$ = $2;
+ $$->all = $all;
+ }
+ ;
+
+uns_into: uns_tgts %prec NAME
+ {
+ $$ = new unstring_into_t($1);
+ }
+ | uns_tgts with POINTER scalar[ptr]
+ {
+ $$ = new unstring_into_t($1, $ptr);
+ }
+ | uns_tgts TALLYING in scalar[tally]
+ {
+ $$ = new unstring_into_t($1, NULL, $tally);
+ }
+ | uns_tgts with POINTER scalar[ptr] TALLYING in scalar[tally]
+ {
+ $$ = new unstring_into_t($1, $ptr, $tally);
+ }
+ ;
+
+uns_tgts: uns_tgt { $$ = new unstring_tgt_list_t($1); }
+ | uns_tgts uns_tgt { $$ = $1; $$->push_back($2); }
+ ;
+uns_tgt: scalar[tgt]
+ {
+ $$ = new unstring_tgt_t($tgt);
+ }
+ | scalar[tgt] DELIMITER in scalar[delim]
+ {
+ $$ = new unstring_tgt_t($tgt, $delim);
+ }
+ | scalar[tgt] COUNT in scalar[count]
+ {
+ if( ! $count->field->is_integer() ) {
+ error_msg(@count, "COUNT %s must be integer type",
+ $count->field->name);
+ }
+ if( $count->field->has_attr(scaled_e) ) {
+ error_msg(@count, "COUNT %s may not be P scaled",
+ $count->field->name);
+ }
+ $$ = new unstring_tgt_t($tgt, NULL, $count);
+ }
+ | scalar[tgt] DELIMITER in scalar[delim] COUNT in scalar[count]
+ {
+ if( ! $count->field->is_integer() ) {
+ error_msg(@count, "COUNT %s must be integer type",
+ $count->field->name);
+ }
+ if( $count->field->has_attr(scaled_e) ) {
+ error_msg(@count, "COUNT %s may not be P scaled",
+ $count->field->name);
+ }
+ $$ = new unstring_tgt_t($tgt, $delim, $count);
+ }
+ ;
+
+ /* intrinsics */
+intrinsic_call: function intrinsic { // "intrinsic" includes UDFs.
+ $$ = new_reference($intrinsic);
+ $$->field->attr |= constant_e;
+ }
+ | function intrinsic refmod[ref]
+ {
+ if( $ref.from->is_reference() || $ref.len->is_reference() ) {
+ error_msg(@ref, "subscripts on start:len refmod "
+ "parameters are unsupported");
+ YYERROR;
+ }
+ if( $intrinsic->type != FldAlphanumeric ) {
+ error_msg(@ref, "'%s' only AlphaNumeric fields accept refmods",
+ $intrinsic->name);
+ YYERROR;
+ }
+ cbl_span_t span( $ref.from, $ref.len );
+ $$ = new cbl_refer_t($intrinsic, span);
+ $$->field->attr |= constant_e;
+ }
+ | function NAME {
+ error_msg(@NAME, "no such function: %s", $NAME);
+ YYERROR;
+ }
+
+ ;
+function: %empty %prec FUNCTION
+ {
+ statement_begin(@$, FUNCTION);
+ }
+ | FUNCTION
+ {
+ statement_begin(@1, FUNCTION);
+ }
+ ;
+
+function_udf: FUNCTION_UDF '(' arg_list[args] ')' {
+ std::vector<function_descr_arg_t> params;
+ auto L = cbl_label_of(symbol_at($1));
+ if( ! current.udf_args_valid(L, $args->refers, params) ) {
+ YYERROR;
+ }
+ $$ = new_temporary_clone(cbl_field_of(symbol_at(L->returning)));
+ auto narg = $args->refers.size();
+ cbl_ffi_arg_t args[narg];
+ size_t i = 0;
+ // Pass parameters as defined by the function.
+ std::transform( $args->refers.begin(), $args->refers.end(), args,
+ [params, &i]( cbl_refer_t& arg ) {
+ function_descr_arg_t param = params.at(i++);
+ auto ar = new cbl_refer_t(arg);
+ cbl_ffi_arg_t actual(param.crv, ar);
+ return actual;
+ } );
+ auto name = new_literal(strlen(L->name), L->name, quoted_e);
+ ast_call( @1, name, $$, narg, args, NULL, NULL, true );
+ }
+ | FUNCTION_UDF_0 {
+ static const size_t narg = 0;
+ static cbl_ffi_arg_t *args = NULL;
+
+ auto L = cbl_label_of(symbol_at($1));
+ $$ = new_temporary_clone(cbl_field_of(symbol_at(L->returning)));
+
+ auto name = new_literal(strlen(L->name), L->name, quoted_e);
+ ast_call( @1, name, $$, narg, args, NULL, NULL, true );
+ }
+ ;
+
+ /*
+ * The scanner returns a function-token (e.g. NUMVAL) if it was
+ * preceded by FUNCTION, or if the name is in the program's
+ * function repository. Else it returns NAME, because it looks
+ * like a user-defined name (possibly a data item). If the user
+ * attempts to use an intrinsic function without using
+ * REPOSITORY or FUNCTION, the NAME results in a syntax error.
+ *
+ * Function arguments may be variables or literals or
+ * functions, and string-valued functions accept a refmod. In
+ * addition to "scalar", we have this inconsistent set:
+ * var: [ALL] LITERAL, NUMSTR, instrinsic, or scalar
+ * num_operand: signed NUMSTR/ZERO, instrinsic, or scalar
+ * alpahaval: LITERAL, reserved_value, instrinsic, or scalar
+ * Probably any numeric argument could be an expression.
+ */
+intrinsic: function_udf
+ | intrinsic0
+ | intrinsic_v '(' arg_list[args] ')' {
+ location_set(@1);
+ size_t n = $args->size();
+ assert(n > 0);
+ cbl_refer_t args[n];
+ std::copy( $args->begin(), $args->end(), args );
+ cbl_refer_t *p = intrinsic_inconsistent_parameter(n, args);
+ if( p != NULL ) {
+ auto loc = symbol_field_location(field_index(p->field));
+ error_msg(loc, "FUNCTION %s has "
+ "inconsistent parameter type %zu ('%s')",
+ keyword_str($1), p - args, name_of(p->field) );
+ YYERROR;
+ }
+ $$ = is_numeric(args[0].field)?
+ new_tempnumeric_float() :
+ new_alphanumeric(args[0].field->data.capacity);
+
+ parser_intrinsic_callv( $$, intrinsic_cname($1), n, args );
+ }
+
+ | PRESENT_VALUE '(' expr_list[args] ')'
+ {
+ static char s[] = "__gg__present_value";
+ location_set(@1);
+ $$ = new_tempnumeric_float();
+ size_t n = $args->size();
+ assert(n > 0);
+ if( n < 2 ) {
+ error_msg(@args, "PRESENT VALUE requires 2 parameters");
+ YYERROR;
+ }
+ cbl_refer_t args[n];
+ parser_intrinsic_callv( $$, s, n, $args->use_list(args) );
+ }
+
+ | BASECONVERT '(' varg[r1] varg[r2] varg[r3] ')' {
+ location_set(@1);
+ $$ = new_tempnumeric();
+ cbl_unimplemented("BASECONVERT");
+ if( ! intrinsic_call_3($$, BASECONVERT, $r1, $r2, $r3 )) YYERROR;
+ }
+ | BIT_OF '(' expr[r1] ')' {
+ location_set(@1);
+ $$ = new_alphanumeric(8 * $r1->field->data.capacity);
+ if( ! intrinsic_call_1($$, BIT_OF, $r1, @r1)) YYERROR;
+ }
+ | CHAR '(' expr[r1] ')' {
+ location_set(@1);
+ $$ = new_alphanumeric(1);
+ if( ! intrinsic_call_1($$, CHAR, $r1, @r1)) YYERROR;
+ }
+
+ | CONVERT '(' varg[r1] convert_src[src] convert_dst[dst] ')' {
+ location_set(@1);
+ $$ = new_alphanumeric(1);
+ cbl_unimplemented("CONVERT");
+ /* if( ! intrinsic_call_3($$, CONVERT, $r1, $src, $dst) ) YYERROR; */
+ }
+
+ | DISPLAY_OF '(' varg[r1] ')' {
+ location_set(@1);
+ uint32_t len = $r1->field->data.capacity;
+ $$ = new_alphanumeric(4 * len);
+ if( ! intrinsic_call_2($$, DISPLAY_OF, $r1, NULL) ) YYERROR;
+ }
+ | DISPLAY_OF '(' varg[r1] varg[r2] ')' {
+ location_set(@1);
+ uint32_t len = $r1->field->data.capacity
+ + $r2->field->data.capacity;
+ $$ = new_alphanumeric(4 * len);
+ if( ! intrinsic_call_2($$, DISPLAY_OF, $r1, $r2) ) YYERROR;
+ }
+
+ | EXCEPTION_FILE filename {
+ location_set(@1);
+ $$ = new_alphanumeric(256);
+ parser_exception_file( $$, $filename );
+ }
+
+ | FIND_STRING '(' varg[r1] last start_after anycase ')' {
+ location_set(@1);
+ $$ = new_alphanumeric($r1->field->data.capacity);
+ /* auto r1 = new_reference(new_literal(strlen($r1), $r1, quoted_e)); */
+ cbl_unimplemented("FIND_STRING");
+ /* if( ! intrinsic_call_4($$, FIND_STRING, r1, $r2) ) YYERROR; */
+ }
+
+ | FORMATTED_DATE '(' DATE_FMT[r1] expr[r2] ')' {
+ location_set(@1);
+ $$ = new_alphanumeric(MAXLENGTH_FORMATTED_DATE);
+ auto r1 = new_reference(new_literal(strlen($r1), $r1, quoted_e));
+ if( ! intrinsic_call_2($$, FORMATTED_DATE, r1, $r2) ) YYERROR;
+ }
+
+
+ | FORMATTED_DATETIME '(' DATETIME_FMT[r1] expr[r2]
+ expr[r3] ')' {
+ location_set(@1);
+ $$ = new_alphanumeric(MAXLENGTH_FORMATTED_DATETIME);
+ auto r1 = new_reference(new_literal(strlen($r1), $r1, quoted_e));
+ static cbl_refer_t r3(literally_zero);
+ if( ! intrinsic_call_4($$, FORMATTED_DATETIME,
+ r1, $r2, $r3, &r3) ) YYERROR;
+ }
+ | FORMATTED_DATETIME '(' DATETIME_FMT[r1] expr[r2]
+ expr[r3] expr[r4] ')' {
+ location_set(@1);
+ $$ = new_alphanumeric(MAXLENGTH_FORMATTED_DATETIME);
+ auto r1 = new_reference(new_literal(strlen($r1), $r1, quoted_e));
+ if( ! intrinsic_call_4($$, FORMATTED_DATETIME,
+ r1, $r2, $r3, $r4) ) YYERROR;
+ }
+ | FORMATTED_DATETIME '(' error ')' {
+ YYERROR;
+ }
+ | FORMATTED_TIME '(' TIME_FMT[r1] expr[r2]
+ expr[r3] ')' {
+ location_set(@1);
+ $$ = new_alphanumeric(MAXLENGTH_FORMATTED_TIME);
+ auto r1 = new_reference(new_literal(strlen($r1), $r1, quoted_e));
+ if( ! intrinsic_call_3($$, FORMATTED_TIME,
+ r1, $r2, $r3) ) YYERROR;
+ }
+ | FORMATTED_TIME '(' TIME_FMT[r1] expr[r2] ')' {
+ location_set(@1);
+ $$ = new_alphanumeric(MAXLENGTH_FORMATTED_TIME);
+ auto r3 = new_reference(new_literal("0"));
+ auto r1 = new_reference(new_literal(strlen($r1), $r1, quoted_e));
+ if( ! intrinsic_call_3($$, FORMATTED_TIME,
+ r1, $r2, r3) ) YYERROR;
+ }
+ | FORMATTED_CURRENT_DATE '(' DATETIME_FMT[r1] ')' {
+ location_set(@1);
+ $$ = new_alphanumeric(MAXLENGTH_FORMATTED_DATETIME);
+ auto r1 = new_reference(new_literal(strlen($r1), $r1, quoted_e));
+ if( ! intrinsic_call_1($$, FORMATTED_CURRENT_DATE, r1, @r1) )
+ YYERROR;
+ }
+ | TEST_FORMATTED_DATETIME '(' DATE_FMT[r1] varg[r2] ')' {
+ location_set(@1);
+ $$ = new_tempnumeric();
+ auto r1 = new_reference(new_literal(strlen($r1), $r1, quoted_e));
+ if( ! intrinsic_call_2($$, TEST_FORMATTED_DATETIME,
+ r1, $r2) ) YYERROR;
+ }
+ | TEST_FORMATTED_DATETIME '(' TIME_FMT[r1] varg[r2] ')' {
+ location_set(@1);
+ $$ = new_tempnumeric();
+ auto r1 = new_reference(new_literal(strlen($r1), $r1, quoted_e));
+ if( ! intrinsic_call_2($$, TEST_FORMATTED_DATETIME,
+ r1, $r2) ) YYERROR;
+ }
+ | TEST_FORMATTED_DATETIME '(' DATETIME_FMT[r1] varg[r2] ')'
+ {
+ location_set(@1);
+ $$ = new_tempnumeric();
+ auto r1 = new_reference(new_literal(strlen($r1), $r1, quoted_e));
+ if( ! intrinsic_call_2($$, TEST_FORMATTED_DATETIME,
+ r1, $r2) ) YYERROR;
+ }
+ | INTEGER_OF_FORMATTED_DATE '(' DATE_FMT[r1] varg[r2] ')' {
+ location_set(@1);
+ $$ = new_tempnumeric();
+ auto r1 = new_reference(new_literal(strlen($r1), $r1, quoted_e));
+ if( ! intrinsic_call_2($$, INTEGER_OF_FORMATTED_DATE,
+ r1, $r2) ) YYERROR;
+ }
+ | INTEGER_OF_FORMATTED_DATE '(' DATETIME_FMT[r1] varg[r2] ')'
+ {
+ location_set(@1);
+ $$ = new_tempnumeric();
+ auto r1 = new_reference(new_literal(strlen($r1), $r1, quoted_e));
+ if( ! intrinsic_call_2($$, INTEGER_OF_FORMATTED_DATE,
+ r1, $r2) ) YYERROR;
+ }
+ | SECONDS_FROM_FORMATTED_TIME '(' TIME_FMT[r1] varg[r2] ')' {
+ location_set(@1);
+ $$ = new_tempnumeric();
+ auto r1 = new_reference(new_literal(strlen($r1), $r1, quoted_e));
+ if( ! intrinsic_call_2($$, SECONDS_FROM_FORMATTED_TIME,
+ r1, $r2) ) YYERROR;
+ }
+ | SECONDS_FROM_FORMATTED_TIME '(' DATETIME_FMT[r1] varg[r2] ')'
+ {
+ location_set(@1);
+ $$ = new_tempnumeric();
+ auto r1 = new_reference(new_literal(strlen($r1), $r1, quoted_e));
+ if( ! intrinsic_call_2($$, SECONDS_FROM_FORMATTED_TIME,
+ r1, $r2) ) YYERROR;
+ }
+
+ | HEX_OF '(' varg[r1] ')' {
+ location_set(@1);
+ $$ = new_alphanumeric(2 * $r1->field->data.capacity);
+ if( ! intrinsic_call_1($$, HEX_OF, $r1, @r1)) YYERROR;
+ }
+ | LENGTH '(' tableish[val] ')' {
+ location_set(@1);
+ $$ = new_tempnumeric();
+ $$->clear_attr(signable_e);
+ parser_set_numeric($$, $val->field->size());
+ if( ! intrinsic_call_1($$, LENGTH, $val, @val)) YYERROR;
+ }
+ | LENGTH '(' varg1a[val] ')' {
+ location_set(@1);
+ $$ = new_tempnumeric();
+ $$->clear_attr(signable_e);
+ parser_set_numeric($$, $val->field->data.capacity);
+ if( ! intrinsic_call_1($$, LENGTH, $val, @val)) YYERROR;
+ }
+ | lopper_case[func] '(' alpha_val[r1] ')' {
+ location_set(@1);
+ $$ = new_alphanumeric($r1->field->data.capacity);
+ if( ! intrinsic_call_1($$, $func, $r1, @r1)) YYERROR;
+ }
+
+ | MODULE_NAME '(' module_type[type] ')'
+ {
+ $$ = new_alphanumeric(sizeof(cbl_name_t));
+ parser_module_name( $$, $type );
+ }
+
+ | NUMVAL_C '(' varg[r1] numval_locale[r2] anycase ')' {
+ location_set(@1);
+ $$ = new_tempnumeric();
+ parser_intrinsic_numval_c( $$, *$r1, $r2.is_locale,
+ *$r2.arg2, $anycase );
+ }
+ | ORD '(' alpha_val[r1] ')'
+ {
+ location_set(@1);
+ $$ = new_tempnumeric();
+ if( ! intrinsic_call_1($$, ORD, $r1, @r1)) YYERROR;
+ }
+ | RANDOM
+ {
+ location_set(@1);
+ $$ = new_tempnumeric_float();
+ parser_intrinsic_call_0( $$, intrinsic_cname(RANDOM) );
+ }
+ | RANDOM_SEED expr[r1] ')'
+ { // left parenthesis consumed by lexer
+ location_set(@1);
+ $$ = new_tempnumeric_float();
+ if( ! intrinsic_call_1($$, RANDOM, $r1, @r1)) YYERROR;
+ }
+
+ | STANDARD_COMPARE '(' varg[r1] varg[r2] varg[r3] varg[r4] ')'
+ {
+ location_set(@1);
+ $$ = new_tempnumeric();
+ cbl_unimplemented("STANDARD-COMPARE");
+ /* if( ! intrinsic_call_4($$, STANDARD_COMPARE, $r1) ) YYERROR; */
+ }
+ | STANDARD_COMPARE '(' varg[r1] varg[r2] varg[r3] ')'
+ {
+ location_set(@1);
+ $$ = new_tempnumeric();
+ cbl_unimplemented("STANDARD-COMPARE");
+ /* if( ! intrinsic_call_4($$, STANDARD_COMPARE, $r1) ) YYERROR; */
+ }
+ | STANDARD_COMPARE '(' varg[r1] varg[r2] ')'
+ {
+ location_set(@1);
+ $$ = new_tempnumeric();
+ cbl_unimplemented("STANDARD-COMPARE");
+ /* if( ! intrinsic_call_4($$, STANDARD_COMPARE, $r1) ) YYERROR; */
+ }
+
+ | SUBSTITUTE '(' varg[r1] subst_inputs[inputs] ')' {
+ location_set(@1);
+ $$ = new_alphanumeric(64);
+ auto narg = $inputs->size();
+ cbl_substitute_t args[narg];
+ std::transform( $inputs->begin(), $inputs->end(), args,
+ []( const substitution_t& arg ) {
+ cbl_substitute_t output( arg.anycase,
+ char(arg.first_last),
+ arg.orig,
+ arg.replacement );
+ return output; } );
+
+ parser_intrinsic_subst($$, *$r1, narg, args);
+ }
+
+
+ | TEST_NUMVAL_C '(' varg[r1] numval_locale[r2] anycase ')' {
+ location_set(@1);
+ $$ = new_tempnumeric();
+ parser_intrinsic_numval_c( $$, *$r1, $r2.is_locale,
+ *$r2.arg2, $anycase, true );
+ }
+ | TRIM '(' error ')' {
+ error_msg(@error, "invalid TRIM argument");
+ YYERROR;
+ }
+ | TRIM '(' expr[r1] trim_trailing ')'
+ {
+ location_set(@1);
+ switch( $r1->field->type ) {
+ case FldGroup:
+ case FldAlphanumeric:
+ case FldLiteralA:
+ case FldAlphaEdited:
+ case FldNumericEdited:
+ break; // alphanumeric OK
+ default:
+ // BLANK WHEN ZERO implies numeric-edited, so OK
+ if( $r1->field->has_attr(blank_zero_e) ) {
+ break;
+ }
+ error_msg(@r1, "TRIM argument must be alphanumeric");
+ YYERROR;
+ break;
+ }
+ $$ = new_alphanumeric($r1->field->data.capacity);
+ cbl_refer_t * how = new_reference($trim_trailing);
+ if( ! intrinsic_call_2($$, TRIM, $r1, how) ) YYERROR;
+ }
+
+ | USUBSTR '(' alpha_val[r1] expr[r2] expr[r3] ')' {
+ location_set(@1);
+ $$ = new_alphanumeric(32); // how long?
+ if( ! intrinsic_call_3($$, FORMATTED_DATETIME,
+ $r1, $r2, $r3) ) YYERROR;
+ }
+
+ | intrinsic_I '(' expr[r1] ')'
+ {
+ location_set(@1);
+ $$ = new_tempnumeric();
+ if( ! intrinsic_call_1($$, $1, $r1, @r1)) YYERROR;
+ }
+
+ | intrinsic_N '(' expr[r1] ')'
+ {
+ location_set(@1);
+ $$ = new_tempnumeric_float();
+ if( ! intrinsic_call_1($$, $1, $r1, @r1)) YYERROR;
+ }
+
+ | intrinsic_X '(' varg[r1] ')'
+ {
+ location_set(@1);
+ auto type = intrinsic_return_type($1);
+ switch(type) {
+ case FldAlphanumeric:
+ $$ = new_alphanumeric($r1->field->data.capacity);
+ break;
+ default:
+ if( $1 == NUMVAL || $1 == NUMVAL_F )
+ {
+ $$ = new_temporary(FldFloat);
+ }
+ else
+ {
+ $$ = new_temporary(type);
+ }
+ }
+ if( $1 == NUMVAL_F ) {
+ if( is_literal($r1->field) ) {
+ _Float128 output __attribute__ ((__unused__));
+ auto input = $r1->field->data.initial;
+ auto local = xstrdup(input), pend = local;
+ std::replace(local, local + strlen(local), ',', '.');
+ std::remove_if(local, local + strlen(local), isspace);
+ output = strtof128(local, &pend);
+ // bad if strtof128 could not convert input
+ if( *pend != '\0' ) {
+ error_msg(@r1, "'%s' is not a numeric string", input);
+ }
+ }
+ }
+ if( ! intrinsic_call_1($$, $1, $r1, @r1)) YYERROR;
+ }
+
+ | intrinsic_I2 '(' expr[r1] expr[r2] ')'
+ {
+ location_set(@1);
+ $$ = new_tempnumeric();
+ if( ! intrinsic_call_2($$, $1, $r1, $r2) ) YYERROR;
+ }
+
+ | DATE_TO_YYYYMMDD '(' expr[r1] ')'
+ {
+ location_set(@1);
+ static auto r2 = new_reference(FldNumericDisplay, "50");
+ static auto one = new cbl_refer_t( new_literal("1") );
+ static auto four = new cbl_refer_t( new_literal("4") );
+ cbl_span_t year(one, four);
+ auto r3 = new_reference(new_alphanumeric(21));
+ r3->refmod = year;
+
+ parser_intrinsic_call_0( r3->field, "__gg__current_date" );
+
+ $$ = new_tempnumeric();
+ if( ! intrinsic_call_3($$, DATE_TO_YYYYMMDD,
+ $r1, r2, r3) ) YYERROR;
+ }
+
+ | DATE_TO_YYYYMMDD '(' expr[r1] expr[r2] ')'
+ {
+ location_set(@1);
+ static auto one = new cbl_refer_t( new_literal("1") );
+ static auto four = new cbl_refer_t( new_literal("4") );
+ cbl_span_t year(one, four);
+ auto r3 = new_reference(new_alphanumeric(21));
+ r3->refmod = year;
+
+ parser_intrinsic_call_0( r3->field, "__gg__current_date" );
+
+ $$ = new_tempnumeric();
+ if( ! intrinsic_call_3($$, DATE_TO_YYYYMMDD,
+ $r1, $r2, r3) ) YYERROR;
+ }
+
+ | DATE_TO_YYYYMMDD '(' expr[r1]
+ expr[r2] expr[r3] ')'
+ {
+ location_set(@1);
+ $$ = new_tempnumeric();
+ if( ! intrinsic_call_3($$, DATE_TO_YYYYMMDD,
+ $r1, $r2, $r3) ) YYERROR;
+ }
+
+ | DAY_TO_YYYYDDD '(' expr[r1] ')'
+ {
+ location_set(@1);
+ static auto r2 = new_reference(FldNumericDisplay, "50");
+ static auto one = new cbl_refer_t( new_literal("1") );
+ static auto four = new cbl_refer_t( new_literal("4") );
+ cbl_span_t year(one, four);
+ auto r3 = new_reference(new_alphanumeric(21));
+ r3->refmod = year;
+
+ parser_intrinsic_call_0( r3->field, "__gg__current_date" );
+
+ $$ = new_tempnumeric();
+ if( ! intrinsic_call_3($$, DAY_TO_YYYYDDD,
+ $r1, r2, r3) ) YYERROR;
+ }
+
+ | DAY_TO_YYYYDDD '(' expr[r1] expr[r2] ')'
+ {
+ location_set(@1);
+ static auto one = new cbl_refer_t( new_literal("1") );
+ static auto four = new cbl_refer_t( new_literal("4") );
+ cbl_span_t year(one, four);
+ auto r3 = new_reference(new_alphanumeric(21));
+ r3->refmod = year;
+
+ parser_intrinsic_call_0( r3->field, "__gg__current_date" );
+
+ $$ = new_tempnumeric();
+ if( ! intrinsic_call_3($$, DAY_TO_YYYYDDD,
+ $r1, $r2, r3) ) YYERROR;
+ }
+
+ | DAY_TO_YYYYDDD '(' expr[r1]
+ expr[r2] expr[r3] ')'
+ {
+ location_set(@1);
+ $$ = new_tempnumeric();
+ if( ! intrinsic_call_3($$, DAY_TO_YYYYDDD,
+ $r1, $r2, $r3) ) YYERROR;
+ }
+
+ | YEAR_TO_YYYY '(' expr[r1] ')'
+ {
+ location_set(@1);
+ static auto r2 = new_reference(new_literal("50", decimal_e));
+ static auto one = new cbl_refer_t( new_literal("1") );
+ static auto four = new cbl_refer_t( new_literal("4") );
+ cbl_span_t year(one, four);
+ auto r3 = new_reference(new_alphanumeric(21));
+ r3->refmod = year;
+
+ parser_intrinsic_call_0( r3->field, "__gg__current_date" );
+
+ $$ = new_tempnumeric();
+ if( ! intrinsic_call_3($$, YEAR_TO_YYYY,
+ $r1, r2, r3) ) YYERROR;
+ }
+
+ | YEAR_TO_YYYY '(' expr[r1] expr[r2] ')'
+ {
+ location_set(@1);
+ static auto one = new cbl_refer_t( new_literal("1") );
+ static auto four = new cbl_refer_t( new_literal("4") );
+ cbl_span_t year(one, four);
+ auto r3 = new_reference(new_alphanumeric(21));
+ r3->refmod = year;
+
+ parser_intrinsic_call_0( r3->field, "__gg__current_date" );
+
+ $$ = new_tempnumeric();
+ if( ! intrinsic_call_3($$, YEAR_TO_YYYY,
+ $r1, $r2, r3) ) YYERROR;
+ }
+
+ | YEAR_TO_YYYY '(' expr[r1]
+ expr[r2] expr[r3] ')'
+ {
+ location_set(@1);
+ $$ = new_tempnumeric();
+ if( ! intrinsic_call_3($$, YEAR_TO_YYYY,
+ $r1, $r2, $r3) ) YYERROR;
+ }
+
+ | intrinsic_N2 '(' expr[r1] expr[r2] ')'
+ {
+ location_set(@1);
+ switch($1)
+ {
+ case ANNUITY:
+ $$ = new_tempnumeric_float();
+ break;
+ case COMBINED_DATETIME:
+ $$ = new_tempnumeric();
+ break;
+ case REM:
+ $$ = new_tempnumeric_float();
+ break;
+ }
+ if( ! intrinsic_call_2($$, $1, $r1, $r2) ) YYERROR;
+ }
+
+ | intrinsic_X2 '(' varg[r1] varg[r2] ')'
+ {
+ location_set(@1);
+ $$ = new_alphanumeric($r1->field->data.capacity);
+ if( ! intrinsic_call_2($$, $1, $r1, $r2) ) YYERROR;
+ }
+ | intrinsic_locale
+ ;
+
+module_type: ACTIVATING { $$ = module_activating_e; }
+ | CURRENT { $$ = module_current_e; }
+ | NESTED { $$ = module_nested_e; }
+ | STACK { $$ = module_stack_e; }
+ | TOP_LEVEL { $$ = module_toplevel_e; }
+ ;
+
+convert_src: ANY
+ | HEX
+ | convert_fmt
+ ;
+convert_dst: convert_fmt HEX
+ | BYTE
+ ;
+convert_fmt: ALPHANUMERIC
+ | ANUM
+ | NAT
+ | NATIONAL
+ ;
+
+numval_locale: %empty {
+ $$.is_locale = false;
+ $$.arg2 = cbl_refer_t::empty();
+ }
+ | LOCALE NAME { $$.is_locale = true; $$.arg2 = NULL;
+ cbl_unimplemented("NUMVAL_C LOCALE"); YYERROR;
+ }
+ | varg { $$.is_locale = false; $$.arg2 = $1; }
+ ;
+
+subst_inputs: subst_input { $$ = new substitutions_t; $$->push_back($1); }
+ | subst_inputs subst_input { $$ = $1; $$->push_back($2); }
+ ;
+subst_input: anycase first_last varg[v1] varg[v2] {
+ $$.init( $anycase, $first_last, $v1, $v2 );
+ }
+ ;
+
+intrinsic_locale:
+ LOCALE_COMPARE '(' varg[r1] varg[r2] ')'
+ {
+ location_set(@1);
+ $$ = new_alphanumeric($r1->field->data.capacity);
+ cbl_refer_t dummy = {};
+ if( ! intrinsic_call_3($$, LOCALE_COMPARE, $r1, $r2, &dummy) ) YYERROR;
+ }
+ | LOCALE_COMPARE '(' varg[r1] varg[r2] varg[r3] ')'
+ {
+ location_set(@1);
+ $$ = new_alphanumeric($r1->field->data.capacity);
+ if( ! intrinsic_call_3($$, LOCALE_COMPARE, $r1, $r2, $r3) ) YYERROR;
+ }
+
+ | LOCALE_DATE '(' varg[r1] ')'
+ {
+ location_set(@1);
+ $$ = new_alphanumeric($r1->field->data.capacity);
+ cbl_refer_t dummy = {};
+ if( ! intrinsic_call_2($$, LOCALE_DATE, $r1, &dummy) ) YYERROR;
+ }
+ | LOCALE_DATE '(' varg[r1] varg[r2] ')'
+ {
+ location_set(@1);
+ $$ = new_alphanumeric($r1->field->data.capacity);
+ if( ! intrinsic_call_2($$, LOCALE_DATE, $r1, $r2) ) YYERROR;
+ }
+ | LOCALE_TIME '(' varg[r1] ')'
+ {
+ location_set(@1);
+ $$ = new_alphanumeric($r1->field->data.capacity);
+ cbl_refer_t dummy = {};
+ if( ! intrinsic_call_2($$, LOCALE_TIME, $r1, &dummy) ) YYERROR;
+ }
+ | LOCALE_TIME '(' varg[r1] varg[r2] ')'
+ {
+ location_set(@1);
+ $$ = new_alphanumeric($r1->field->data.capacity);
+ if( ! intrinsic_call_2($$, LOCALE_TIME, $r1, $r2) ) YYERROR;
+ }
+ | LOCALE_TIME_FROM_SECONDS '(' varg[r1] ')'
+ {
+ location_set(@1);
+ $$ = new_alphanumeric($r1->field->data.capacity);
+ cbl_refer_t dummy = {};
+ if( ! intrinsic_call_2($$, LOCALE_TIME_FROM_SECONDS, $r1, &dummy) ) YYERROR;
+ }
+ | LOCALE_TIME_FROM_SECONDS '(' varg[r1] varg[r2] ')'
+ {
+ location_set(@1);
+ $$ = new_alphanumeric($r1->field->data.capacity);
+ if( ! intrinsic_call_2($$, LOCALE_TIME_FROM_SECONDS, $r1, $r2) ) YYERROR;
+ }
+ ;
+
+lopper_case: LOWER_CASE { $$ = LOWER_CASE; }
+ | UPPER_CASE { $$ = UPPER_CASE; }
+ ;
+
+trim_trailing: %empty { $$ = new_literal("0"); } // Remove both
+ | LEADING { $$ = new_literal("1"); } // Remove leading spaces
+ | TRAILING { $$ = new_literal("2"); } // Remove trailing spaces
+ ;
+
+intrinsic0: CURRENT_DATE {
+ location_set(@1);
+ $$ = new_alphanumeric(21);
+ parser_intrinsic_call_0( $$, "__gg__current_date" );
+ }
+ | E {
+ location_set(@1);
+ $$ = new_tempnumeric();
+ parser_intrinsic_call_0( $$, "__gg__e" );
+ }
+
+ | EXCEPTION_FILE_N {
+ location_set(@1);
+ $$ = new_alphanumeric(256);
+ intrinsic_call_0( $$, EXCEPTION_FILE_N );
+ }
+
+ | EXCEPTION_FILE {
+ location_set(@1);
+ $$ = new_alphanumeric(256);
+ parser_exception_file( $$ );
+ }
+ | EXCEPTION_LOCATION_N {
+ location_set(@1);
+ $$ = new_alphanumeric(256);
+ intrinsic_call_0( $$, EXCEPTION_LOCATION_N );
+ }
+ | EXCEPTION_LOCATION {
+ location_set(@1);
+ $$ = new_alphanumeric(256);
+ intrinsic_call_0( $$, EXCEPTION_LOCATION );
+ }
+ | EXCEPTION_STATEMENT {
+ location_set(@1);
+ $$ = new_alphanumeric(63);
+ intrinsic_call_0( $$, EXCEPTION_STATEMENT );
+ }
+ | EXCEPTION_STATUS {
+ location_set(@1);
+ $$ = new_alphanumeric(31);
+ intrinsic_call_0( $$, EXCEPTION_STATUS );
+ }
+
+ | PI {
+ location_set(@1);
+ $$ = new_tempnumeric_float();
+ parser_intrinsic_call_0( $$, "__gg__pi" );
+ }
+ | SECONDS_PAST_MIDNIGHT {
+ location_set(@1);
+ $$ = new_tempnumeric();
+ intrinsic_call_0( $$, SECONDS_PAST_MIDNIGHT );
+ }
+ | UUID4 {
+ location_set(@1);
+ $$ = new_alphanumeric(32); // don't know correct size
+ parser_intrinsic_call_0( $$, "__gg__uuid4" );
+ }
+ | WHEN_COMPILED {
+ location_set(@1);
+ $$ = new_alphanumeric(21); // Returns YYYYMMDDhhmmssss-0500
+ parser_intrinsic_call_0( $$, "__gg__when_compiled" );
+ }
+ ;
+
+intrinsic_I: BOOLEAN_OF_INTEGER { $$ = BOOLEAN_OF_INTEGER;
+ cbl_unimplemented("BOOLEAN-OF-INTEGER");
+ }
+ | CHAR_NATIONAL { $$ = CHAR_NATIONAL;
+ cbl_unimplemented("CHAR-NATIONAL");
+ }
+ | DATE_OF_INTEGER { $$ = DATE_OF_INTEGER; }
+ | DAY_OF_INTEGER { $$ = DAY_OF_INTEGER; }
+ | FACTORIAL { $$ = FACTORIAL; }
+ | FRACTION_PART { $$ = FRACTION_PART; }
+ | HIGHEST_ALGEBRAIC { $$ = HIGHEST_ALGEBRAIC; }
+ | INTEGER { $$ = INTEGER; }
+ | INTEGER_OF_BOOLEAN { $$ = INTEGER_OF_BOOLEAN;
+ cbl_unimplemented("INTEGER-OF-BOOLEAN");
+ }
+ | INTEGER_OF_DATE { $$ = INTEGER_OF_DATE; }
+ | INTEGER_OF_DAY { $$ = INTEGER_OF_DAY; }
+ | INTEGER_PART { $$ = INTEGER_PART; }
+ | LOWEST_ALGEBRAIC { $$ = LOWEST_ALGEBRAIC; }
+ | SIGN { $$ = SIGN; }
+ | TEST_DATE_YYYYMMDD { $$ = TEST_DATE_YYYYMMDD; }
+ | TEST_DAY_YYYYDDD { $$ = TEST_DAY_YYYYDDD; }
+ | ULENGTH { $$ = ULENGTH; }
+ | UPOS { $$ = UPOS; }
+ | USUPPLEMENTARY { $$ = USUPPLEMENTARY; }
+ | UVALID { $$ = UVALID; }
+ | UWIDTH { $$ = UWIDTH; }
+ ;
+
+intrinsic_I2: MOD { $$ = MOD; }
+ ;
+
+intrinsic_N: ABS { $$ = ABS; }
+ | ACOS { $$ = ACOS; }
+ | ASIN { $$ = ASIN; }
+ | ATAN { $$ = ATAN; }
+ | COS { $$ = COS; }
+ | EXP { $$ = EXP; }
+ | EXP10 { $$ = EXP10; }
+ | LOG { $$ = LOG; }
+ | LOG10 { $$ = LOG10; }
+ | SIN { $$ = SIN; }
+ | SMALLEST_ALGEBRAIC { $$ = SMALLEST_ALGEBRAIC;
+ cbl_unimplemented("SMALLEST-ALGEBRAIC");
+ }
+ | SQRT { $$ = SQRT; }
+ | TAN { $$ = TAN; }
+ ;
+
+intrinsic_N2: ANNUITY { $$ = ANNUITY; }
+ | COMBINED_DATETIME { $$ = COMBINED_DATETIME; }
+ | REM { $$ = REM; }
+ ;
+
+intrinsic_X: BIT_TO_CHAR { $$ = BIT_TO_CHAR; }
+ | BYTE_LENGTH { $$ = BYTE_LENGTH; }
+ | HEX_TO_CHAR { $$ = HEX_TO_CHAR; }
+ | NUMVAL { $$ = NUMVAL; }
+ | NUMVAL_F { $$ = NUMVAL_F; }
+ | REVERSE { $$ = REVERSE; }
+ | TEST_NUMVAL { $$ = TEST_NUMVAL; }
+ | TEST_NUMVAL_F { $$ = TEST_NUMVAL_F; }
+ ;
+
+intrinsic_X2: NATIONAL_OF { $$ = NATIONAL_OF; }
+ ;
+
+intrinsic_v: CONCAT { $$ = CONCAT; }
+ | MAXX { $$ = MAXX; }
+ | MEAN { $$ = MEAN; }
+ | MEDIAN { $$ = MEDIAN; }
+ | MIDRANGE { $$ = MIDRANGE; }
+ | MINN { $$ = MINN; }
+ | ORD_MAX { $$ = ORD_MAX; }
+ | ORD_MIN { $$ = ORD_MIN; }
+ | RANGE { $$ = RANGE; }
+ | STANDARD_DEVIATION { $$ = STANDARD_DEVIATION; }
+ | SUM { $$ = SUM; }
+ | VARIANCE { $$ = VARIANCE; }
+ ;
+
+all: %empty { $$ = false; }
+ | ALL { $$ = true; }
+ ;
+
+anycase: %empty { $$ = false; }
+ | ANYCASE { $$ = true; }
+ ;
+
+as: %empty
+ | AS
+ ;
+
+at: %empty
+ | AT
+ ;
+
+by: %empty
+ | BY
+ ;
+
+characters: %empty
+ | CHARACTERS
+ ;
+
+collating: %empty
+ | COLLATING
+ ;
+
+contains: %empty
+ | CONTAINS
+ ;
+
+in: %empty
+ | IN
+ ;
+
+data: %empty
+ | DATA
+ ;
+
+exception: %empty
+ | EXCEPTION
+ ;
+
+file: %empty
+ | FILE_KW
+ ;
+
+first_last: %empty { $$ = 0; }
+ | FIRST { $$ = 'F'; }
+ | LAST { $$ = 'L'; }
+ ;
+
+is_global: %empty %prec GLOBAL { $$ = false; }
+ | is GLOBAL { $$ = true; }
+ ;
+
+global: %empty %prec GLOBAL { $$ = false; }
+ | GLOBAL { $$ = true; }
+ ;
+
+initial: %empty { $$ = 0; }
+ | INITIAL_kw { $$ = INITIAL_kw; }
+ ;
+
+is: %empty
+ | IS
+ ;
+
+key: %empty
+ | KEY
+ ;
+
+last: %empty %prec LAST
+ | LAST
+ ;
+
+lines: %empty
+ | LINE
+ | LINES
+ ;
+
+mode: %empty
+ | MODE
+ ;
+
+native: %empty
+ | NATIVE
+ ;
+
+of: %empty
+ | OF
+ ;
+
+on: %empty
+ | ON
+ ;
+
+optional: %empty { $$ = false; }
+ | OPTIONAL { $$ = true; }
+ ;
+
+program_kw: %empty
+ | PROGRAM_kw
+ ;
+
+order: %empty
+ | ORDER
+ ;
+
+record: %empty
+ | RECORD
+ ;
+
+sign: %empty
+ | SIGN
+ ;
+
+start_after: %empty %prec AFTER
+ | START AFTER varg
+ ;
+
+status: %empty
+ | STATUS
+ ;
+strong: %empty { $$ = true; }
+ | STRONG { $$ = false; }
+ ;
+
+times: %empty
+ | TIMES
+ ;
+then: %empty
+ | THEN
+ ;
+
+to: %empty
+ | TO
+ ;
+
+usage: %empty
+ | USAGE
+ | USAGE IS
+ ;
+
+with: %empty
+ | WITH
+ ;
+
+ /*
+ * CDF: Compiler-directing Facility
+ */
+cdf: cdf_none
+ | cdf_library
+ | cdf_listing
+ | cdf_option
+ ;
+
+cdf_library: cdf_basis
+ /* | DELETE */
+ | INSERTT
+ ;
+cdf_basis: BASIS NAME /* BASIS is never passed to the parser. */
+ | BASIS LITERAL
+ ;
+
+cdf_use: USE DEBUGGING on labels
+ {
+ if( ! current.declarative_section_name() ) {
+ error_msg(@1, "USE valid only in DECLARATIVES");
+ YYERROR;
+ }
+ std::for_each($labels->elems.begin(), $labels->elems.end(),
+ add_debugging_declarative);
+
+ }
+ | USE DEBUGGING on ALL PROCEDURES
+ {
+ if( ! current.declarative_section_name() ) {
+ error_msg(@1, "USE valid only in DECLARATIVES");
+ YYERROR;
+ }
+ static const cbl_label_t all = {
+ .type = LblNone,
+ .name = { ':', 'a', 'l', 'l', ':', } // workaround for gcc < 11.3
+ };
+ add_debugging_declarative(&all);
+ }
+
+ | USE globally mistake procedure on filenames
+ {
+ if( ! current.declarative_section_name() ) {
+ error_msg(@1, "USE valid only in DECLARATIVES");
+ YYERROR;
+ }
+ bool global = $globally == GLOBAL;
+ std::list<size_t> files;
+ auto& culprits = $filenames->files;
+ std::transform( culprits.begin(), culprits.end(),
+ std::back_inserter(files),
+ file_list_t::symbol_index );
+ cbl_declarative_t declarative(current.declarative_section(),
+ ec_all_e, files,
+ file_mode_none_e, global);
+ current.declaratives.add(declarative);
+ }
+
+ | USE globally mistake procedure on io_mode
+ { // Format 1
+ if( ! current.declarative_section_name() ) {
+ error_msg(@1, "USE valid only in DECLARATIVES");
+ YYERROR;
+ }
+ bool global = $globally == GLOBAL;
+ std::list<size_t> files;
+ cbl_declarative_t declarative(current.declarative_section(),
+ ec_all_e, files,
+ $io_mode, global);
+ current.declaratives.add(declarative);
+ }
+ | USE cdf_use_excepts // Format 3: AFTER swallowed by lexer
+ {
+ if( ! current.declarative_section_name() ) {
+ error_msg(@1, "USE valid only in DECLARATIVES");
+ YYERROR;
+ }
+ }
+ ;
+
+cdf_use_excepts:
+ cdf_use_except
+ | cdf_use_excepts cdf_use_except
+ ;
+cdf_use_except: EC NAME cdf_use_files[files]
+ {
+ auto ec = ec_type_of($NAME);
+ if( ec == ec_none_e ) {
+ error_msg(@NAME, "not an EXCEPTION CONDITION: %s", $NAME);
+ YYERROR;
+ }
+ std::list<size_t> files;
+ if( $files ) {
+ if( ec_io_e != (ec_io_e & ec) ) {
+ error_msg(@NAME, "not an I-O EXCEPTION CONDITION: %s", $NAME);
+ YYERROR;
+ }
+ auto& culprits = $files->files;
+ std::transform( culprits.begin(), culprits.end(),
+ std::back_inserter(files),
+ file_list_t::symbol_index );
+ }
+
+ cbl_declarative_t declarative(current.declarative_section(),
+ ec, files, file_mode_none_e);
+ // Check for duplicates, but keep going.
+ current.declaratives.add(declarative);
+ }
+ ;
+cdf_use_files: %empty { $$ = NULL; }
+ | FILE_KW filenames { $$ = $2; }
+ ;
+
+io_mode: INPUT { $$ = file_mode_input_e; }
+ | OUTPUT { $$ = file_mode_output_e; }
+ | IO { $$ = file_mode_io_e; }
+ | EXTEND { $$ = file_mode_extend_e; }
+ ;
+
+globally: global { $$ = $1? GLOBAL : 0; }
+ | global STANDARD { $$ = $1? GLOBAL : STANDARD; }
+ | global AFTER { $$ = $1? GLOBAL : 0; }
+ | global AFTER STANDARD { $$ = $1? GLOBAL : STANDARD; }
+ ;
+mistake: EXCEPTION { $$ = EXCEPTION; }
+ | ERROR { $$ = ERROR; }
+ ;
+procedure: %empty
+ | PROCEDURE
+ ;
+
+cdf_listing: STAR_CBL star_cbl_opts
+ ;
+star_cbl_opts: star_cbl_opt
+ | star_cbl_opts star_cbl_opt
+ ;
+star_cbl_opt: LIST { $$ = $LIST[0] == 'N'? NOLIST : LIST; }
+ | MAP { $$ = $MAP[0] == 'N'? NOMAP : MAP; }
+ /* | SOURCE { $$ = $SOURCE[0] == 'N'? NOSOURCE : SOURCE; } */
+ ;
+
+cdf_option: CBL cbl_options
+ ;
+cbl_options: cbl_option
+ | cbl_options cbl_option
+ ;
+cbl_option: LITERAL
+ ; /* Ignore all options. */
+
+ /* The following compiler directing statements have no effect */
+cdf_none: ENTER
+ | READY
+ | RESET
+ | TRACE
+ | SERVICE_RELOAD
+ ;
+
+
+%%
+
+static YYLTYPE
+first_line_of( YYLTYPE loc ) {
+ if( loc.first_line < loc.last_line ) loc.last_line = loc.first_line;
+ if( loc.last_column < loc.first_column ) loc.last_column = loc.first_column;
+ return loc;
+}
+
+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)
+{
+ if( is_literal(name.field) ) {
+ cbl_field_t called = { 0, FldLiteralA, FldInvalid, quoted_e | constant_e,
+ 0, 0, 77, nonarray, 0, "",
+ 0, cbl_field_t::linkage_t(),
+ {0,0,0,0, NULL, NULL, {NULL}, {NULL}}, NULL };
+ snprintf(called.name, sizeof(called.name), "_%s", name.field->data.initial);
+ called.data = name.field->data;
+ name.field = cbl_field_of(symbol_field_add(PROGRAM, &called));
+ symbol_field_location(field_index(name.field), loc);
+ parser_symbol_add(name.field);
+ }
+
+ if( getenv("ast_call") ) {
+ dbgmsg("%s: calling %s returning %s with %zu args:", __func__,
+ name_of(name.field),
+ (returning.field)? returning.field->name : "[none]",
+ narg);
+ for( size_t i=0; i < narg; i++ ) {
+ const char *crv = "?";
+ switch(args[i].crv) {
+ case by_default_e: crv = "def"; break;
+ case by_reference_e: crv = "ref"; break;
+ case by_content_e: crv = "con"; break;
+ case by_value_e: crv = "val"; break;
+ }
+ dbgmsg("%s: %4zu: %s @%p %s", __func__,
+ i, crv, args[i].refer.field, args[i].refer.field->name);
+ }
+ }
+ parser_call( name, returning, narg, args, except, not_except, is_function );
+}
+
+static size_t
+statement_begin( const YYLTYPE& loc, int token ) {
+ // The following statement generates a message at run-time
+ // parser_print_string("statement_begin()\n");
+ location_set(loc);
+ prior_statement = token;
+
+ parser_statement_begin();
+
+ if( token != CONTINUE ) {
+ if( enabled_exceptions.size() ) {
+ current.declaratives_evaluate(ec_none_e);
+ cbl_enabled_exceptions_array_t enabled(enabled_exceptions);
+ parser_exception_prepare( keyword_str(token), &enabled );
+ }
+ }
+ return 0;
+}
+
+#include "parse_util.h"
+#include <sys/types.h>
+
+struct string_match {
+ const char *name;
+ string_match( const char name[] ) : name(name) {}
+ bool operator()( const char input[] ) const {
+ return strlen(name) == strlen(input) && 0 == strcasecmp(name, input);
+ }
+};
+
+const char *
+keyword_str( int token ) {
+ if( token == YYEOF ) return "YYEOF";
+ if( token == YYEMPTY ) return "YYEMPTY";
+
+ if( token < 256 ) {
+ static char ascii[2];
+ ascii[0] = token;
+ return ascii;
+ }
+
+ return tokens.name_of(token);
+}
+
+/*
+ * Return the token for the Cobol name, unless it is a function name. The
+ * lexer uses keyword_tok to determine if what appears to be a NAME is in fact
+ * a token defined by the parser. For functions, the situation is unambiguous:
+ * a function name appears only after FUNCTION or in the REPOSITORY paragraph.
+ * All function names are rejected here; the lexer uses typed_name to check
+ * REPOSITORY names.
+ */
+
+// tokens.h is generated as needed from parse.h with tokens.h.gen
+tokenset_t::tokenset_t() {
+#include "token_names.h"
+}
+
+// Look up the lowercase form of a keyword, excluding some CDF names.
+int
+tokenset_t::find( const cbl_name_t name, bool include_intrinsics ) {
+ static const cbl_name_t non_names[] = { // including CDF NAMES, and "SWITCH"
+ "CHECKING", "LIST", "LOCATION", "MAP", "SWITCH",
+ }, * const eonames = non_names + COUNT_OF(non_names);
+
+ if( std::any_of(non_names, eonames,
+ [candidate=name](const cbl_name_t non_name) {
+ return 0 == strcasecmp(non_name, candidate)
+ && strlen(non_name) == strlen(candidate);
+ } ) ) {
+ return 0; // CDF names are never ordinary tokens
+ }
+
+ if( dialect_ibm() ) {
+ static const cbl_name_t ibm_non_names[] = {
+ "RESUME",
+ }, * const eonames = ibm_non_names + COUNT_OF(ibm_non_names);
+
+ if( std::any_of(ibm_non_names, eonames,
+ [candidate=name](const cbl_name_t non_name) {
+ return 0 == strcasecmp(non_name, candidate)
+ && strlen(non_name) == strlen(candidate);
+ } ) ) {
+ return 0; // Names not reserved by IBM are never ordinary IBM tokens
+ }
+ }
+
+ cbl_name_t lname;
+ std::transform(name, name + strlen(name) + 1, lname, tolower);
+ auto p = tokens.find(lname);
+ if( p == tokens.end() ) return 0;
+ int token = p->second;
+
+ if( token == SECTION ) yylval.number = 0;
+
+ if( include_intrinsics ) return token;
+
+ return intrinsic_cname(token)? 0 : token;
+}
+
+int
+keyword_tok( const char * text, bool include_intrinsics ) {
+ return tokens.find(text, include_intrinsics);
+}
+
+static inline size_t
+verify_figconst( enum cbl_figconst_t figconst , size_t pos ) {
+ cbl_field_t *f = cbl_field_of(symbol_at(pos));
+ assert((f->attr & FIGCONST_MASK) == figconst);
+ return pos;
+}
+
+static size_t
+constant_index( int token ) {
+ switch(token) {
+ case SPACES : return 0;
+ case LOW_VALUES : return verify_figconst(low_value_e, 2);
+ case ZERO : return verify_figconst(zero_value_e, 3);
+ case HIGH_VALUES : return verify_figconst(high_value_e, 4);
+ case QUOTES : return 5;
+ case NULLS : return 6;
+ }
+ cbl_errx( "%s:%d: no such constant %d", __func__, __LINE__, token);
+ return (size_t)-1;
+}
+
+
+static enum relop_t
+relop_of(int token) {
+ switch(token) {
+ case '<': return lt_op;
+ case LE: return le_op;
+ case '=': return eq_op;
+ case NE: return ne_op;
+ case GE: return ge_op;
+ case '>': return gt_op;
+ }
+ cbl_internal_error( "%s:%d: invalid relop token %d",
+ __func__, __LINE__, token);
+
+ return lt_op; // not reached
+}
+
+static relop_t
+relop_invert(relop_t op) {
+ switch(op) {
+ case lt_op: return ge_op;
+ case le_op: return gt_op;
+ case eq_op: return ne_op;
+ case ne_op: return eq_op;
+ case ge_op: return lt_op;
+ case gt_op: return le_op;
+ }
+ cbl_errx( "%s:%d: invalid relop_t %d", __func__, __LINE__, op);
+
+ return relop_t(0); // not reached
+}
+
+#if needed
+static const char *
+relop_debug_str(int token) {
+ switch(token) {
+ case 0: return "zilch";
+ case '<': return "<";
+ case LE: return "LE";
+ case '=': return "=";
+ case NE: return "NE";
+ case GE: return "GE";
+ case '>': return ">";
+ }
+ dbgmsg("%s:%d: invalid relop token %d", __func__, __LINE__, token);
+ return "???";
+}
+
+static int
+token_of(enum relop_t op) {
+ switch(op) {
+ case lt_op: return '<';
+ case le_op: return LE;
+ case eq_op: return '=';
+ case ne_op: return NE;
+ case ge_op: return GE;
+ case gt_op: return '>';
+ }
+ cbl_errx( "%s:%d: invalid relop_t %d", __func__, __LINE__, op);
+
+ return 0; // not reached
+}
+#endif
+
+static enum classify_t
+classify_of( int token ) {
+ switch(token) {
+ case NUMERIC: return ClassNumericType;
+ case ALPHABETIC: return ClassAlphabeticType;
+ case ALPHABETIC_LOWER: return ClassLowerType;
+ case ALPHABETIC_UPPER: return ClassUpperType;
+ case DBCS: return ClassDbcsType;
+ case KANJI: return ClassKanjiType;
+ }
+ return (enum classify_t)-1;
+}
+
+static cbl_round_t
+rounded_of( int token ) {
+ cbl_round_t mode = current_rounded_mode();
+
+ switch(token) {
+ case 0 ... int(truncation_e):
+ mode = cbl_round_t(token);
+ break;
+ case ROUNDED:
+ mode = current.rounded_mode();
+ break;
+ case AWAY_FROM_ZERO:
+ mode = away_from_zero_e;
+ break;
+ case NEAREST_TOWARD_ZERO:
+ mode = nearest_toward_zero_e;
+ break;
+ case TOWARD_GREATER:
+ mode = toward_greater_e;
+ break;
+ case TOWARD_LESSER:
+ mode = toward_lesser_e;
+ break;
+ case NEAREST_AWAY_FROM_ZERO:
+ mode = nearest_away_from_zero_e;
+ break;
+ case NEAREST_EVEN:
+ mode = nearest_even_e;
+ break;
+ case PROHIBITED:
+ mode = prohibited_e;
+ break;
+ case TRUNCATION:
+ mode = truncation_e;
+ break;
+ default:
+ dbgmsg("%s: logic error: unrecognized rounding value %d", __func__, token);
+ }
+ return mode;
+}
+
+static cbl_round_t
+current_rounded_mode( int token ) {
+ cbl_round_t mode = rounded_of(token);
+ return current.rounded_mode(mode);
+}
+
+template <cbl_label_type_t T>
+class label_named {
+ size_t program;
+ const char *name;
+ public:
+ label_named( size_t program, const char name[] )
+ : program(program), name(name) {}
+ bool operator()( const symbol_elem_t& sym ) const {
+ if( sym.program == program && sym.type == SymLabel ) {
+ auto p = cbl_label_of(&sym);
+ return p->type == T && 0 == strcasecmp(p->name, name);
+ }
+ return false;
+ }
+};
+
+typedef label_named<LblSection> section_named;
+typedef label_named<LblParagraph> paragraph_named;
+
+static struct cbl_label_t *
+label_add( const YYLTYPE& loc,
+ enum cbl_label_type_t type, const char name[] ) {
+ size_t parent = 0;
+
+ // Verify the new paragraph doesn't conflict with a section
+ if( type == LblParagraph ) {
+ parent = current.program_section();
+ auto p = std::find_if(symbols_begin(PROGRAM), symbols_end(),
+ section_named(PROGRAM, name));
+ if( p != symbols_end() ) {
+ error_msg(loc, "paragraph %s conflicts with section %s on line %d",
+ name, cbl_label_of(p)->name, cbl_label_of(p)->line);
+ }
+ }
+
+ // Verify the new section doesn't conflict with a paragraph
+ if( type == LblSection ) {
+ // line is zero if the forward reference is to PARA OF SECT
+ auto p = std::find_if(symbols_begin(PROGRAM), symbols_end(),
+ paragraph_named(PROGRAM, name));
+ if( p != symbols_end() ) {
+ error_msg(loc, "section %s conflicts with paragraph %s on line %d",
+ name, cbl_label_of(p)->name, cbl_label_of(p)->line);
+ }
+ }
+ struct cbl_label_t label = { type, parent, loc.last_line };
+
+ if( !namcpy(loc, label.name, name) ) return NULL;
+ auto p = symbol_label_add(PROGRAM, &label);
+
+ if( type == LblParagraph || type == LblSection ) {
+ procedure_definition_add(PROGRAM, p);
+ }
+
+ assert( !(p->type == LblSection && p->parent > 0) );
+
+ if( getenv(__func__) ) {
+ yywarn("%s: added label %3zu %10s for '%s' of %zu", __func__,
+ symbol_elem_of(p) - symbols_begin(), p->type_str()+3, p->name, p->parent);
+ }
+
+ return p;
+}
+
+/*
+ * Many label names are defined statically and so are guaranteed to be in
+ * bounds. Often they are created far away from the yacc metavariables, so
+ * there's no location to access.
+ */
+static struct cbl_label_t *
+label_add( enum cbl_label_type_t type, const char name[], int line ) {
+ YYLTYPE loc { line, 1, line, 1 };
+ return label_add(loc, type, name);
+}
+
+cbl_label_t *
+perform_t::ec_labels_t::new_label( cbl_label_type_t type,
+ const cbl_name_t role )
+{
+ size_t n = 1 + symbols_end() - symbols_begin();
+ cbl_name_t name;
+ sprintf(name, "_perf_%s_%zu", role, n);
+ return label_add( type, name, yylineno );
+}
+
+/*
+ * An unqualified procedure reference occurs within a section may refer to a:
+ * 1. section
+ * 2. paragraph, perhaps in a section, perhaps the current section.
+ *
+ * The named procedure need only be unique, either within the current section
+ * or globally. A paragraph within one section may be referenced without
+ * qualification in another section if its name is unique.
+ *
+ * An otherwise globally unique name is shadowed by the same name in the
+ * current section, and the section-local name may be referenced before being
+ * defined. That is, given:
+ *
+ * S1 SECTION.
+ * PROC.
+ * ...
+ * S2 SECTION.
+ * PERFORM PROC.
+ * PROC. ...
+ *
+ * the procedure performed is PROC OF S2.
+ *
+ * That creates a challenge for the compiler, because PROC appears to have been
+ * defined when PERFORM is encountered. When PROC OF S2 is defined, the parser
+ * detects and corrects its misstep.
+ */
+static struct cbl_label_t *
+paragraph_reference( const char name[], size_t section )
+{
+ // A reference has line == 0. It is LblParagraph if the section is
+ // explicitly named, else LblNone (because we don't know).
+ struct cbl_label_t *p, label = { section? LblParagraph : LblNone, section };
+ assert(strlen(name) < sizeof(label.name)); // caller ensures
+ strcpy(label.name, name);
+ if( label.type == LblNone ) assert(label.parent == 0);
+
+ const symbol_elem_t *last = symbols_end();
+
+ p = symbol_label_add(PROGRAM, &label);
+ assert(p);
+
+ const char *sect_name = section? cbl_label_of(symbol_at(section))->name : NULL;
+ procedure_reference_add(sect_name, p->name, yylineno, current.program_section());
+
+ if( getenv(__func__) ) {
+ yywarn("%s: %s label %3zu %10s for '%s' of %zu", __func__,
+ symbols_end() == last? "added" : "found",
+ symbol_index(symbol_elem_of(p)), p->type_str()+3, p->name, p->parent);
+ }
+
+ return p;
+}
+
+static struct cbl_refer_t *
+use_vargs( struct vargs_t *v, struct cbl_refer_t *tgt) {
+ assert(v);
+ assert(tgt);
+ std::copy(v->args.begin(), v->args.end(), tgt);
+ v->args.clear();
+ delete v;
+
+ return tgt;
+}
+
+void
+current_t::repository_add_all() {
+ assert( !programs.empty() );
+ auto& repository = programs.top().function_repository;
+ std::copy( function_descrs, function_descrs_end,
+ std::inserter(repository, repository.begin()) );
+}
+
+/*
+ * A function is added to the symbol table when first named, in Identification
+ * Division. It's also added to the current list of UDFs in current_t::udfs.
+ * Its return type and parameters, if any, are defined later, in Procedure
+ * Division. When they are parsed, we call udf_update to finalize the
+ * functions's descriptor, giving us enough information to validate the
+ * arguments at point of invocation.
+ */
+void
+current_t::udf_update( const ffi_args_t *ffi_args ) {
+ auto L = cbl_label_of(symbol_at(program_index()));
+ assert(L);
+ assert(L->type == LblFunction);
+ assert(L->returning);
+ if( ! ffi_args ) return;
+ assert(ffi_args->elems.size() < sizeof(function_descr_t::types));
+
+ auto returning = cbl_field_of(symbol_at(L->returning));
+ auto key = function_descr_t::init(L->name);
+ auto func = udfs.find(key);
+ assert(func != udfs.end());
+
+ function_descr_t udf = *func;
+
+ udf.ret_type = returning->type;
+ udf.token = ffi_args->elems.empty()? FUNCTION_UDF_0 : FUNCTION_UDF;
+ auto types = ffi_args->parameter_types();
+ strcpy(udf.types, types);
+
+ std::transform( ffi_args->elems.begin(), ffi_args->elems.end(),
+ std::back_inserter(udf.linkage_fields),
+ []( const cbl_ffi_arg_t& arg ) {
+ return function_descr_arg_t( field_index( arg.refer.field ),
+ arg.crv, arg.optional );
+ } );
+
+ udfs.erase(func);
+ auto result = udfs.insert(udf);
+ assert(result.second);
+}
+
+bool
+current_t::udf_args_valid( const cbl_label_t *L,
+ const std::list<cbl_refer_t>& args,
+ std::vector<function_descr_arg_t>& params /*out*/ )
+{
+ auto key = function_descr_t::init(L->name);
+ auto func = udfs.find(key);
+ assert(func != udfs.end());
+ function_descr_t udf = *func;
+ params = udf.linkage_fields;
+
+ if( udf.linkage_fields.size() < args.size() ) {
+ auto loc = symbol_field_location(field_index(args.back().field));
+ error_msg(loc, "too many parameters for UDF %s", L->name);
+ return false;
+ }
+
+ size_t i = 0;
+ for( cbl_refer_t arg : args ) {
+ if( arg.field ) { // else omitted
+ auto tgt = cbl_field_of(symbol_at(udf.linkage_fields.at(i).isym));
+ if( ! valid_move(tgt, arg.field) ) {
+ auto loc = symbol_field_location(field_index(arg.field));
+ error_msg(loc, "FUNCTION %s arg %zu, '%s' cannot be passed to %s, type %s",
+ L->name, i, arg.field->pretty_name(),
+ tgt->pretty_name(), 3 + cbl_field_type_str(tgt->type) );
+ return false;
+ }
+ }
+ i++;
+ }
+ return true;
+}
+
+bool
+current_t::repository_add( const char name[]) {
+ assert( !programs.empty() );
+ function_descr_t arg = function_descr_t::init(name);
+ auto parg = std::find( function_descrs, function_descrs_end, arg );
+ if( parg == function_descrs_end ) return false;
+ auto p = programs.top().function_repository.insert(*parg);
+ 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 p.second;
+}
+
+int
+current_t::repository_in( const char name[]) {
+ assert( !programs.empty() );
+ auto isym = programs.top().program_index;
+ // possible to call self
+ auto self = cbl_label_of(symbol_at(isym));
+ if( self->type == LblFunction ) {
+ if( 0 == strcasecmp(self->name, name) ) {
+ return FUNCTION_UDF;
+ }
+ }
+ function_descr_t arg = function_descr_t::init(name);
+ auto repository = programs.top().function_repository;
+ auto p = repository.find(arg);
+ return p != repository.end()? p->token : 0;
+}
+
+int repository_function_tok( const char name[] ) {
+ return current.repository_in(name);
+}
+
+function_descr_t
+function_descr_t::init( int isym ) {
+ function_descr_t descr = { .token = FUNCTION_UDF_0, .ret_type = FldInvalid };
+ auto L = cbl_label_of(symbol_at(isym));
+ bool ok = namcpy(YYLTYPE(), descr.name, L->name);
+ gcc_assert(ok);
+ return descr;
+}
+
+arith_t::arith_t( cbl_arith_format_t format, refer_list_t * refers )
+ : format(format), on_error(NULL), not_error(NULL)
+{
+ std::copy( refers->refers.begin(), refers->refers.end(), back_inserter(A) );
+ refers->refers.clear();
+ delete refers;
+}
+
+
+cbl_key_t::cbl_key_t( const sort_key_t& that )
+ : ascending(that.ascending)
+ , nfield(that.fields.size())
+ , fields(NULL)
+{
+ if( nfield > 0 ) {
+ fields = new cbl_field_t* [nfield];
+ std::copy(that.fields.begin(), that.fields.end(), fields);
+ }
+}
+
+static cbl_refer_t *
+ast_op( cbl_refer_t *lhs, char op, cbl_refer_t *rhs ) {
+ assert(lhs);
+ assert(rhs);
+ if( ! (is_numeric(lhs->field) && is_numeric(rhs->field)) ) {
+ // If one of the fields isn't numeric, allow for index addition.
+ switch(op) {
+ case '+':
+ case '-':
+ // Simple addition OK for table indexes.
+ if( lhs->field->type == FldIndex || rhs->field->type == FldIndex ) {
+ goto ok;
+ }
+ }
+
+ auto f = !is_numeric(lhs->field)? lhs->field : rhs->field;
+ auto loc = symbol_field_location(field_index(f));
+ error_msg(loc, "'%s' is not numeric", f->name);
+ return NULL;
+ }
+ ok:
+ cbl_field_t skel = determine_intermediate_type( *lhs, op, *rhs );
+ cbl_refer_t *tgt = new_reference_like(skel);
+ if( !mode_syntax_only() ) {
+ parser_op( *tgt, *lhs, op, *rhs, current.compute_label() );
+ }
+ return tgt;
+}
+
+static void
+ast_add( arith_t *arith ) {
+ size_t nC = arith->tgts.size(), nA = arith->A.size();
+ cbl_num_result_t *pC, C[nC];
+ cbl_refer_t *pA, A[nA];
+
+ pC = use_any(arith->tgts, C);
+ pA = use_any(arith->A, A);
+
+ if( getenv(__func__) ) {
+ dbgmsg("%s:%d: %-12s C{%zu %p} A{%zu %p}", __func__, __LINE__,
+ arith->format_str(), nC, pC, nA, pA );
+ }
+ parser_add( nC, pC, nA, pA, arith->format, arith->on_error, arith->not_error );
+
+ ec_type_t handled = arith->on_error || arith->not_error ? ec_size_e : ec_none_e;
+ current.declaratives_evaluate(handled);
+}
+
+static bool
+ast_subtract( arith_t *arith ) {
+ size_t nC = arith->tgts.size(), nA = arith->A.size(), nB = arith->B.size();
+ cbl_num_result_t *pC, C[nC];
+ cbl_refer_t *pA, A[nA], *pB, B[nB];
+
+ pC = use_any(arith->tgts, C);
+ pA = use_any(arith->A, A);
+ pB = use_any(arith->B, B);
+
+ parser_subtract( nC, pC, nA, pA, nB, pB, arith->format, arith->on_error, arith->not_error );
+
+ ec_type_t handled = arith->on_error || arith->not_error ? ec_size_e : ec_none_e;
+ current.declaratives_evaluate(handled);
+ return true;
+}
+
+static bool
+ast_multiply( arith_t *arith ) {
+ size_t nC = arith->tgts.size(), nA = arith->A.size(), nB = arith->B.size();
+ cbl_num_result_t *pC, C[nC];
+ cbl_refer_t *pA, A[nA], *pB, B[nB];
+
+ pC = use_any(arith->tgts, C);
+ pA = use_any(arith->A, A);
+ pB = use_any(arith->B, B);
+
+ parser_multiply( nC, pC, nA, pA, nB, pB, arith->on_error, arith->not_error );
+
+ ec_type_t handled = arith->on_error || arith->not_error ? ec_size_e : ec_none_e;
+ current.declaratives_evaluate(handled);
+ return true;
+}
+
+static bool
+ast_divide( arith_t *arith ) {
+ size_t nC = arith->tgts.size(), nA = arith->A.size(), nB = arith->B.size();
+ cbl_num_result_t *pC, C[nC];
+ cbl_refer_t *pA, A[nA], *pB, B[nB];
+
+ pC = use_any(arith->tgts, C);
+ pA = use_any(arith->A, A);
+ pB = use_any(arith->B, B);
+
+ parser_divide( nC, pC, nA, pA, nB, pB,
+ arith->remainder, arith->on_error, arith->not_error );
+
+ ec_type_t handled = arith->on_error || arith->not_error ? ec_size_e : ec_none_e;
+ current.declaratives_evaluate(handled);
+ return true;
+}
+
+/*
+ * Populate a parser API struct from lists built up by the parser.
+ * The API doesn't use STL containers or classes that exist only for
+ * the convenience of the parser.
+*/
+struct stringify_src_t : public cbl_string_src_t {
+ stringify_src_t( const refer_marked_list_t& marked = refer_marked_list_t() )
+ : cbl_string_src_t( marked.marker? *marked.marker : null_reference,
+ marked.refers.size(),
+ new cbl_refer_t[marked.refers.size()] )
+ {
+ std::copy( marked.refers.begin(), marked.refers.end(), inputs );
+ }
+
+ static void dump( const cbl_string_src_t& src ) {
+ dbgmsg( "%s:%d:, %zu inputs delimited by %s:", __func__, __LINE__,
+ src.ninput,
+ src.delimited_by.field? field_str(src.delimited_by.field) : "SIZE" );
+ std::for_each(src.inputs, src.inputs + src.ninput, dump_input);
+ }
+
+ protected:
+ static void dump_input( const cbl_refer_t& refer ) {
+ yywarn( "%s:\t%s", __func__, field_str(refer.field) );
+ }
+};
+
+void
+stringify( refer_collection_t *inputs,
+ cbl_refer_t into, cbl_refer_t pointer,
+ cbl_label_t *on_error,
+ cbl_label_t *not_error )
+{
+ size_t n = inputs->lists.size();
+ stringify_src_t sources[n];
+
+ if( inputs->lists.back().marker == NULL ) {
+ inputs->lists.back().marker = cbl_refer_t::empty();
+ }
+ assert( inputs->lists.back().marker );
+ std::copy( inputs->lists.begin(), inputs->lists.end(), sources );
+ if( getenv(__func__) ) {
+ std::for_each(sources, sources+n, stringify_src_t::dump);
+ }
+ parser_string( into, pointer, n, sources, on_error, not_error );
+}
+
+void
+unstringify( cbl_refer_t& src,
+ refer_list_t *delimited,
+ unstring_into_t * into,
+ cbl_label_t *on_error,
+ cbl_label_t *not_error )
+{
+ size_t ndelimited = delimited? delimited->size() : 0;
+ cbl_refer_t delimiteds[1 + ndelimited], *pdelimited = NULL;
+ if( ndelimited > 0 ) {
+ pdelimited = delimited->use_list( delimiteds );
+ }
+
+ size_t noutput = into->size();
+ cbl_refer_t outputs[noutput];
+ into->use_list( outputs, unstring_tgt_t::tgt_of );
+
+ cbl_refer_t delimiters[noutput];
+ into->use_list( delimiters, unstring_tgt_t::delimiter_of );
+
+ cbl_refer_t counts[noutput];
+ into->use_list( counts, unstring_tgt_t::count_of );
+
+ parser_unstring( src,
+ ndelimited, pdelimited,
+ // into
+ noutput,
+ outputs, delimiters, counts,
+ into->pointer, into->tally,
+ on_error, not_error );
+ delete into;
+}
+
+static const char *
+data_section_str( data_section_t section ) {
+ switch(section) {
+ case not_data_datasect_e:
+ return "NONE";
+ case local_storage_datasect_e:
+ return "LOCAL";
+ case file_datasect_e:
+ return "FILE";
+ case working_storage_datasect_e:
+ return "WORKING";
+ case linkage_datasect_e:
+ return "LINKAGE";
+ }
+ gcc_unreachable();
+ return NULL;
+}
+
+static bool
+current_data_section_set(const YYLTYPE& loc, data_section_t data_section ) {
+ // order is mandatory
+ if( data_section < current_data_section ) {
+ error_msg(loc, "%s SECTION must precede %s SECTION",
+ data_section_str(data_section),
+ data_section_str(current_data_section));
+ return false;
+ }
+
+ cbl_section_type_t type = file_sect_e;
+
+ switch(data_section) {
+ case not_data_datasect_e:
+ gcc_unreachable();
+ break;
+ case file_datasect_e:
+ type = file_sect_e;
+ break;
+ case working_storage_datasect_e:
+ type = working_sect_e;
+ break;
+ case local_storage_datasect_e:
+ type = local_sect_e;
+ break;
+ case linkage_datasect_e:
+ type = linkage_sect_e;
+ break;
+ }
+
+ cbl_section_t section = { type, yylineno, NULL };
+
+ if( ! symbol_section_add(PROGRAM, &section) ) {
+ error_msg(loc, "could not add section %s to program %s, exists line %d",
+ section.name(), current.program()->name,
+ symbol_section(PROGRAM, &section)->line );
+ return false;
+ }
+
+ current_data_section = data_section ;
+ return true;
+}
+
+void apply_declaratives() {
+ // look for declaratives for this procedure, and all procedures
+ bool tf[2] = { false, true };
+ for( bool *yn = tf; yn < tf + COUNT_OF(tf); yn++ ) {
+ auto declaratives = current.debugging_declaratives(*yn);
+ for( auto p = declaratives.begin() ;
+ p != declaratives.end(); p++ ) {
+ // TODO: delarative for PARA OF SECTION
+ cbl_label_t *label = symbol_label(PROGRAM, LblNone, 0, p->c_str());
+ assert(label);
+ parser_perform(label);
+ }
+ }
+}
+#define FIG_CONST(X) constant_of(constant_index((X)))
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+
+int warn_abi_version = -1;
+int cp_unevaluated_operand;
+void
+lang_check_failed (const char* file, int line, const char* function) {}
+
+#pragma GCC diagnostic pop
+
+void ast_inspect( cbl_refer_t& input, bool backward, ast_inspect_list_t& inspects ) {
+ if( yydebug ) {
+ dbgmsg("%s:%d: INSPECT %zu operations on %s, line %d", __func__, __LINE__,
+ inspects.size(), input.field->name, yylineno);
+ }
+ std::for_each(inspects.begin(), inspects.end(), dump_inspect);
+ auto array = inspects.as_array();
+ parser_inspect( input, backward, inspects.size(), array );
+ delete[] array;
+}
+
+static const char *
+cbl_refer_str( char output[], const cbl_refer_t& R ) {
+ sprintf( output, "refer = %s %s %s",
+ R.field? field_str(R.field) : "(none)",
+ R.is_table_reference()? "(table)" : "",
+ R.is_refmod_reference()? "(refmod)" : "" );
+ return output;
+}
+
+static void
+dump_inspect_match( const cbl_inspect_match_t& M ) {
+ static char fields[3][4 * 64];
+ cbl_refer_str(fields[0], M.matching);
+ cbl_refer_str(fields[1], M.before.identifier_4);
+ cbl_refer_str(fields[2], M.after.identifier_4);
+
+ yywarn( "matching %s \n\t\tbefore %s%s \n\t\tafter %s%s",
+ fields[0],
+ M.before.initial? "initial " : "", fields[1],
+ M.after.initial? "initial " : "", fields[2] );
+}
+
+static void
+dump_inspect_replace( const cbl_inspect_replace_t& R ) {
+ static char fields[4][4 * 64];
+ cbl_refer_str(fields[0], R.matching);
+ cbl_refer_str(fields[1], R.before.identifier_4);
+ cbl_refer_str(fields[2], R.after.identifier_4);
+ cbl_refer_str(fields[3], R.replacement);
+
+ yywarn( "matching %s \n\treplacement %s\n\t\tbefore %s%s \n\t\tafter %s%s",
+ fields[0], fields[3],
+ R.before.initial? "initial " : "", fields[1],
+ R.after.initial? "initial " : "", fields[2] );
+}
+
+static const char *
+bound_str( cbl_inspect_bound_t bound ) {
+ switch(bound) {
+ case bound_characters_e: return "characters";
+ case bound_all_e: return "all";
+ case bound_first_e: return "first";
+ case bound_leading_e: return "leading";
+ case bound_trailing_e: return "trailing";
+ }
+ return "bound?";
+}
+
+/*
+ * INITIALIZE
+ */
+static data_category_t
+data_category_of( const cbl_refer_t& refer ) {
+ assert(refer.field);
+ switch( refer.field->type ) {
+ case FldInvalid:
+ assert(refer.field->type != FldInvalid);
+ return data_category_none;
+
+ case FldGroup:
+ return data_category_none;
+
+ case FldLiteralA:
+ case FldAlphanumeric:
+ return refer.field->has_attr(all_alpha_e)?
+ data_alphabetic_e : data_alphanumeric_e;
+
+ case FldNumericBinary:
+ case FldFloat:
+ case FldNumericBin5:
+ case FldPacked:
+ case FldNumericDisplay:
+ case FldLiteralN:
+ return data_numeric_e;
+
+ case FldNumericEdited:
+ return data_numeric_edited_e;
+ case FldAlphaEdited:
+ return data_alphanumeric_edited_e;
+
+ case FldPointer:
+ return data_data_pointer_e;
+
+ case FldClass:
+ case FldConditional:
+ case FldForward:
+ case FldIndex:
+ case FldSwitch:
+ case FldDisplay:
+ case FldBlob:
+ return data_category_none;
+ }
+ gcc_unreachable();
+ return data_category_none;
+}
+
+static bool
+valid_target( const cbl_refer_t& refer ) {
+ assert(refer.field);
+ switch( refer.field->type ) {
+ case FldInvalid:
+ assert(refer.field->type != FldInvalid);
+ return false;
+ case FldGroup:
+ case FldAlphanumeric:
+ case FldNumericBinary:
+ case FldFloat:
+ case FldNumericBin5:
+ case FldPacked:
+ case FldNumericDisplay:
+ case FldNumericEdited:
+ case FldAlphaEdited:
+ case FldPointer:
+ return true;
+ case FldLiteralA:
+ case FldLiteralN:
+ case FldClass:
+ case FldConditional:
+ case FldForward:
+ case FldIndex:
+ case FldSwitch:
+ case FldDisplay:
+ case FldBlob:
+ return false;
+ }
+ gcc_unreachable();
+ return false;
+}
+
+static _Float128
+numstr2i( const char input[], radix_t radix ) {
+ _Float128 output = 0.0;
+ size_t bit, integer = 0;
+ int erc=0, n=0;
+
+ switch( radix ) {
+ case decimal_e: { // Use decimal point for comma, just in case.
+ auto local = xstrdup(input), pend = local;
+ if( !local ) { erc = -1; break; }
+ std::replace(local, local + strlen(local), ',', '.');
+ output = strtof128(local, &pend);
+ n = pend - local;
+ }
+ break;
+ case hexadecimal_e:
+ erc = sscanf(input, "%zx%n", &integer, &n);
+ output = integer;
+ break;
+ case boolean_e:
+ for( const char *p = input; *p != '\0'; p++ ) {
+ if( ssize_t(8 * sizeof(integer) - 1) < p - input ) {
+ yywarn("'%s' was accepted as %d", input, integer);
+ return integer;
+ }
+ switch(*p) {
+ case '0': bit = 0; break;
+ case '1': bit = 1; break;
+ break;
+ default:
+ yywarn("'%s' was accepted as %d", input, integer);
+ return integer;
+ }
+ integer = (integer << (p - input));
+ integer |= bit;
+ }
+ return integer;
+ break;
+ }
+ if( erc == -1 || n < int(strlen(input)) ) {
+ yywarn("'%s' was accepted as %lld", input, output);
+ }
+ return output;
+}
+
+static inline cbl_field_t *
+new_literal( const char initial[], enum radix_t radix ) {
+ auto attr = constant_e;
+
+ switch( radix ) {
+ case decimal_e:
+ break;
+ case hexadecimal_e:
+ attr = hex_encoded_e;
+ break;
+ case boolean_e:
+ attr = bool_encoded_e;
+ break;
+ }
+ return new_literal(strlen(initial), initial,
+ cbl_field_attr_t(constant_e | attr));
+}
+
+class is_elementary_type { // for INITIALIZE purposes
+ bool with_filler;
+public:
+ is_elementary_type( bool with_filler ) : with_filler(with_filler) {}
+
+ bool operator()( const symbol_elem_t& elem ) const {
+ if( elem.type != SymField ) return false;
+ const cbl_field_t *f = cbl_field_of(&elem);
+ if( symbol_redefines(f) ) return false;
+ return ( f->has_attr(filler_e) && with_filler )
+ || ::is_elementary(f->type);
+ }
+};
+
+size_t end_of_group( size_t igroup );
+
+static std::list<cbl_refer_t>
+symbol_group_data_members( cbl_refer_t refer, bool with_filler ) {
+ std::list<cbl_refer_t> refers;
+ refers.push_front( refer );
+
+ if( refer.field->type != FldGroup ) return refers;
+
+ class refer_of : public cbl_refer_t {
+ public:
+ refer_of( const cbl_refer_t& refer ) : cbl_refer_t(refer) {}
+ cbl_refer_t operator()( symbol_elem_t& elem ) {
+ this->field = cbl_field_of(&elem); // preserve subscript/refmod
+ return *this;
+ }
+ };
+
+ size_t igroup = field_index(refer.field), eogroup = end_of_group(igroup);
+ std::list<symbol_elem_t> elems;
+ is_elementary_type is_elem(with_filler);
+
+ std::copy_if( symbols_begin(igroup), symbols_begin(eogroup),
+ std::back_inserter(elems), [is_elem]( const symbol_elem_t& elem ) {
+ return is_elem(elem) || cbl_field_of(&elem)->occurs.ntimes() > 0; } );
+ std::transform( elems.begin(), elems.end(),
+ std::back_inserter(refers), refer_of(refer) );
+ return refers;
+}
+
+struct expand_group : public std::list<cbl_refer_t> {
+ static cbl_refer_t referize( cbl_field_t *field ) {
+ return cbl_refer_t(field);
+ }
+ bool with_filler;
+ expand_group( bool with_filler ) : with_filler(with_filler) {}
+
+ void operator()( const cbl_refer_t& refer ) {
+ assert(refer.field);
+ if( refer.field->type != FldGroup ) {
+ push_back(refer);
+ return;
+ }
+ std::list<cbl_refer_t> members = symbol_group_data_members( refer,
+ with_filler );
+ std::copy( members.begin(), members.end(), back_inserter(*this) );
+ }
+};
+
+
+static const char * initial_default_value;
+ const char * wsclear() { return initial_default_value; }
+
+void
+wsclear( char ch ) {
+ static char byte = ch;
+ initial_default_value = &byte;
+ current.program_needs_initial();
+}
+
+static void
+initialize_allocated( cbl_refer_t input ) {
+ cbl_num_result_t result = { truncation_e, input };
+ std::list<cbl_num_result_t> results;
+ results.push_back(result);
+ initialize_statement(results, true,
+ data_category_all, category_map_t());
+}
+
+static int
+initialize_with( cbl_refer_t tgt ) {
+ if( tgt.field->type == FldPointer ) return ZERO;
+ if( tgt.is_refmod_reference() ) return SPACES;
+ return is_numeric(tgt.field)? ZERO : SPACES;
+}
+
+static bool
+initialize_one( cbl_num_result_t target, bool with_filler,
+ data_category_t value_category,
+ const category_map_t& replacements,
+ bool explicitly )
+{
+ cbl_refer_t& tgt( target.refer );
+ if( ! valid_target(tgt) ) return false;
+
+ // Rule 1 c: is valid for VALUE, REPLACING, or DEFAULT
+ // If no VALUE (category none), set to blank/zero.
+ if( value_category == data_category_none && replacements.empty() ) {
+ auto token = initialize_with(tgt);
+ auto src = constant_of(constant_index(token));
+ cbl_refer_t source(src);
+ auto s = wsclear();
+ if( s ) {
+ char ach[5];
+ int v = *s;
+ sprintf(ach, "%d", v);
+ source.field = new_literal(ach);
+ source.addr_of = true;
+ }
+
+ if( tgt.field->type == FldPointer ) {
+ parser_set_pointers(1, &tgt, source);
+ } else {
+ parser_move(tgt, src, current_rounded_mode());
+ }
+ if( getenv(__func__) ) {
+ yywarn("%s:%-5s: %s", __func__, keyword_str(token), field_str(tgt.field));
+ }
+ return true;
+ }
+
+ /*
+ * Either VALUE or REPLACING specified.
+ */
+
+ if( value_category == data_category_all ||
+ value_category == data_category_of(tgt) ) {
+ // apply any applicable VALUE
+ if( explicitly || tgt.field->data.initial ) {
+ assert( with_filler || !tgt.field->has_attr(filler_e) );
+ if( tgt.field->data.initial ) {
+ parser_initialize(tgt);
+ }
+ }
+
+ if( getenv(__func__) ) {
+ yywarn("%s: value: %s", __func__, field_str(tgt.field));
+ }
+ }
+
+ // apply REPLACING, possibly overwriting VALUE
+ // N.B., may be wrong:
+ /*
+ * "If the data item does not qualify as a receiving-operand because of the
+ * VALUE phrase, but does qualify because of the REPLACING phrase ..."
+ */
+ auto r = replacements.find(data_category_of(tgt));
+ if( r != replacements.end() ) {
+ parser_move( tgt, *r->second );
+
+ if( getenv(__func__) ) {
+ cbl_field_t *from = r->second->field;
+ char from_str[128]; // copy static buffer from field_str
+ strcpy( from_str, field_str(from) );
+ yywarn("%s: move: %-18s %s \n\t from %-18s %s", __func__,
+ cbl_field_type_str(tgt.field->type) + 3, field_str(tgt.field),
+ cbl_field_type_str(from->type) + 3, from_str);
+ }
+ return true;
+ }
+
+ return true;
+
+}
+
+typedef std::pair<cbl_field_t*,cbl_field_t*> field_span_t;
+typedef std::pair<size_t, size_t> cbl_bytespan_t;
+
+static void
+dump_spans( size_t isym,
+ const cbl_field_t *table,
+ const std::list<field_span_t>& spans,
+ size_t nrange,
+ const cbl_bytespan_t ranges[],
+ size_t depth,
+ const std::list<cbl_subtable_t>& subtables )
+{
+ int i=0;
+ assert( nrange == 0 || nrange == spans.size() );
+
+ if( isym != field_index(table) ) {
+ dbgmsg("%s:%d: isym %zu is not #%zu %02u %s", __func__, __LINE__,
+ isym, field_index(table), table->level, table->name);
+ }
+ dbgmsg( "%s: [%zu] #%zu %s has %zu spans and %zu subtables",
+ __func__, depth, isym, table->name, nrange, subtables.size() );
+ for( auto span : spans ) {
+ unsigned int last_level = 0;
+ const char *last_name = "<none>";
+ if( span.second ) {
+ last_level = span.second->level;
+ last_name = span.second->name;
+ }
+
+ char at_subtable[64] = {};
+ size_t offset = nrange? ranges[i].first : 0;
+ auto p = std::find_if(subtables.begin(), subtables.end(),
+ [offset]( const cbl_subtable_t& tbl ) {
+ return tbl.offset == offset;
+ });
+ if( p != subtables.end() ) {
+ sprintf(at_subtable, "(subtable #%zu)", p->isym);
+ }
+ dbgmsg("\t %02u %-20s to %02u %-20s: %3zu-%zu %s",
+ span.first->level, span.first->name,
+ last_level, last_name,
+ nrange? ranges[i].first : 1,
+ nrange? ranges[i].second : 0,
+ at_subtable);
+ i++;
+ }
+ if( ! subtables.empty() ) {
+ dbgmsg("\ttable #%zu has %zu subtables", isym, subtables.size());
+ for( auto tbl : subtables ) {
+ dbgmsg("\t #%zu @ %4zu", tbl.isym, tbl.offset);
+ }
+ }
+}
+
+/*
+ * After the 1st record is initialized, copy it to the others.
+ */
+static bool
+initialize_table( cbl_num_result_t target,
+ size_t nspan, const cbl_bytespan_t spans[],
+ const std::list<cbl_subtable_t>& subtables )
+{
+ if( getenv("initialize_statement") ) {
+ dbgmsg("%s:%d: %s ", __func__, __LINE__, target.refer.str());
+ }
+ assert( target.refer.nsubscript == dimensions(target.refer.field) );
+ const cbl_refer_t& src( target.refer );
+ size_t n( src.field->occurs.ntimes());
+ assert( 0 < n );
+
+ size_t isym( field_index(src.field) );
+ size_t ntbl = subtables.size();
+ cbl_subtable_t tbls[ntbl], *ptbls = 0 < ntbl? tbls : NULL;
+ std::copy( subtables.begin(), subtables.end(), tbls );
+ parser_initialize_table( n, src, nspan, spans, isym, ntbl, ptbls );
+ return true;
+}
+
+static cbl_refer_t
+synthesize_table_refer( cbl_refer_t tgt ) {
+ // For a table, use supplied subscripts or start with 1.
+ auto ndim( dimensions(tgt.field) );
+ if( tgt.nsubscript < ndim ) { // it's an incomplete table
+ cbl_refer_t subscripts[ndim];
+ for( size_t i=0; i < ndim; i++ ) {
+ if( i < tgt.nsubscript ) {
+ subscripts[i] = tgt.subscripts[i];
+ continue;
+ }
+ subscripts[i].field = new_tempnumeric();
+ parser_set_numeric(subscripts[i].field, 1);
+ }
+ return cbl_refer_t( tgt.field, ndim, subscripts );
+ }
+ return tgt;
+}
+
+static size_t
+group_offset( const cbl_field_t *field ) {
+ if( field->parent ) {
+ auto e = symbol_at(field->parent);
+ if( e->type == SymField ) {
+ auto parent = cbl_field_of(e);
+ return field->offset - parent->offset;
+ }
+ }
+ return field->offset;
+}
+
+static bool
+initialize_statement( const cbl_num_result_t& target, bool with_filler,
+ data_category_t value_category,
+ const category_map_t& replacements,
+ size_t depth = 0 )
+{
+ if( getenv(__func__) ) {
+ dbgmsg("%s:%d: %2zu: %s (%s%zuR)",
+ __func__, __LINE__, depth, target.refer.str(),
+ with_filler? "F" : "",
+ replacements.size());
+ }
+ const cbl_refer_t& tgt( target.refer );
+ assert(dimensions(tgt.field) == tgt.nsubscript || 0 < depth);
+ assert(!is_literal(tgt.field));
+
+ if( tgt.field->type == FldGroup ) {
+ if( tgt.field->data.initial ) goto initialize_this;
+ if( tgt.is_refmod_reference() ) goto initialize_this;
+ // iterate over group memebers
+ auto imember = field_index(tgt.field);
+ auto eogroup = end_of_group(imember);
+ bool fOK = true;
+ std::list<cbl_field_t*> members;
+ std::list<cbl_subtable_t> subtables;
+
+ while( ++imember < eogroup ) {
+ auto e = symbol_at(imember);
+ if( e->type != SymField ) continue;
+ auto f = cbl_field_of(e);
+ if( ! (f->type == FldGroup || is_elementary(f->type)) ) continue;
+ if( ! symbol_redefines(f) ) {
+ members.push_back(f);
+ if( is_table(f) ) {
+ size_t offset = group_offset(f);
+ subtables.push_back( cbl_subtable_t { offset, imember } );
+ }
+ cbl_num_result_t next_target(target);
+ next_target.refer.field = f;
+ // recurse on each member, which might be a table or group
+ fOK = fOK && initialize_statement( next_target, with_filler, value_category,
+ replacements, 1 + depth );
+ }
+ if( f->type == FldGroup ) {
+ imember = end_of_group(imember) - 1;
+ }
+ }
+
+ if( fOK && is_table(tgt.field) ) {
+ cbl_num_result_t output = { target.rounded, synthesize_table_refer(tgt) };
+ if( tgt.nsubscript < output.refer.nsubscript ) { // tgt is whole table
+ std::list<field_span_t> field_spans;
+ static const field_span_t empty_span = { NULL, NULL };
+ field_span_t span = empty_span;
+ bool honor_filler = false;
+ // construct non-filler field spans
+ for( auto member : members ) {
+ if( !with_filler && member->has_attr(filler_e) ) {
+ if( span.first ) { // conclude the span and begin to skip filler
+ field_spans.push_back(span);
+ span = empty_span;
+ honor_filler = true;
+ }
+ continue;
+ }
+ if( span.first ) {
+ span.second = member; // extend the span
+ } else {
+ span.first = member; // start a new span
+ }
+ }
+ if( span.first ) {
+ field_spans.push_back(span);
+ }
+ // convert field spans to byte ranges
+ cbl_bytespan_t ranges[ field_spans.size() ];
+ size_t nrange = 0;
+ if( honor_filler ) {
+ nrange = COUNT_OF(ranges);
+ std::transform( field_spans.begin(), field_spans.end(), ranges,
+ []( const auto& span ) {
+ size_t first, second;
+ first = second = group_offset(span.first);
+ if( ! span.second ) {
+ second += std::max(span.first->data.capacity,
+ span.first->data.memsize);
+ } else {
+ second = group_offset(span.second)
+ - group_offset(span.first);
+ second += std::max(span.second->data.capacity,
+ span.second->data.memsize);
+ }
+ return std::make_pair(first, second);
+ } );
+ }
+ if( getenv("initialize_statement") ) {
+ dump_spans( field_index(output.refer.field), output.refer.field,
+ field_spans, nrange, ranges, depth, subtables );
+ }
+ return initialize_table( output, nrange, ranges, subtables );
+ }
+ }
+ return fOK;
+ }
+
+ if( !is_elementary(tgt.field->type) ) return false;
+
+ assert(is_elementary(tgt.field->type));
+ assert(data_category_of(tgt) != data_category_none);
+
+ /*
+ * Initialize elementary field.
+ */
+
+ initialize_this:
+ // Cannot initialize constants
+ if( is_constant(tgt.field) ) {
+ auto loc = symbol_field_location(field_index(tgt.field));
+ error_msg(loc, "%s is constant", name_of(tgt.field));
+ return false;
+ }
+ // Ignore filler unless instructed otherwise.
+ if( !with_filler && tgt.field->has_attr(filler_e) ) return true;
+
+ cbl_num_result_t output = { target.rounded, synthesize_table_refer(tgt) };
+
+ bool fOK = initialize_one( output, with_filler, value_category,
+ replacements, depth == 0 );
+
+ if( fOK && is_table(tgt.field) ) {
+ return initialize_table( output,
+ 0, NULL, std::list<cbl_subtable_t>() );
+ }
+
+ return fOK;
+}
+
+const char *
+data_category_str( data_category_t category ) {
+ switch(category) {
+ case data_category_none: return "category_none";
+ case data_category_all: return "category_all";
+ case data_alphabetic_e: return "alphabetic";
+ case data_alphanumeric_e: return "alphanumeric";
+ case data_alphanumeric_edited_e: return "alphanumeric_edited";
+ case data_boolean_e: return "data_boolean";
+ case data_data_pointer_e: return "data_data_pointer";
+ case data_function_pointer_e: return "data_function_pointer";
+ case data_msg_tag_e: return "data_msg_tag";
+ case data_dbcs_e: return "dbcs";
+ case data_egcs_e: return "egcs";
+ case data_national_e: return "national";
+ case data_national_edited_e: return "national_edited";
+ case data_numeric_e: return "numeric";
+ case data_numeric_edited_e: return "numeric_edited";
+ case data_object_referenc_e: return "data_object_referenc";
+ case data_program_pointer_e: return "data_program_pointer";
+ }
+ return "???";
+}
+
+static void
+initialize_statement( std::list<cbl_num_result_t>& tgts, bool with_filler,
+ data_category_t value_category,
+ const category_map_t& replacements) {
+ if( yydebug && getenv(__func__) ) {
+ yywarn( "%s: %zu targets, %s filler",
+ __func__, tgts.size(), with_filler? "with" : "no");
+ for( auto tgt : tgts ) {
+ fprintf( stderr, "%28s: %s\n", __func__, name_of(tgt.refer.field) );
+ }
+ for( const auto& elem : replacements ) {
+ fprintf( stderr, "%28s: %s <-%s\n", __func__,
+ data_category_str(elem.first),
+ name_of(elem.second->field) );
+ }
+ }
+
+ bool is_refmod = std::any_of( tgts.begin(), tgts.end(),
+ []( const auto& tgt ) {
+ return tgt.refer.is_refmod_reference();
+ } );
+ if( false && is_refmod ) { // refmod seems valid per ISO
+ dbgmsg("INITIALIZE cannot initialize a refmod");
+ return;
+ }
+
+ for( auto tgt : tgts ) {
+ initialize_statement( tgt, with_filler, value_category,
+ replacements );
+ }
+ tgts.clear();
+}
+
+static void
+dump_inspect_oper( const cbl_inspect_oper_t& op ) {
+ dbgmsg("\t%s: %zu \"matches\", %zu \"replaces\"",
+ bound_str(op.bound),
+ op.matches? op.n_identifier_3 : 0, op.replaces? op.n_identifier_3 : 0);
+ if( op.matches )
+ std::for_each(op.matches, op.matches + op.n_identifier_3, dump_inspect_match);
+ if( op.replaces )
+ std::for_each(op.replaces, op.replaces + op.n_identifier_3, dump_inspect_replace);
+}
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-function"
+
+static void
+dump_inspect( const cbl_inspect_t& I ) {
+ if( !yydebug ) return;
+ if( I.tally.field ) {
+ fprintf( stderr, "\tTALLYING to %s %s %s:\n",
+ field_str(I.tally.field),
+ I.tally.is_table_reference()? "(table)" : "",
+ I.tally.is_refmod_reference()? "(refmod)" : "" );
+ } else {
+ fprintf( stderr, "\tREPLACING:\n" );
+ }
+ std::for_each( I.opers, I.opers + I.nbound, dump_inspect_oper );
+}
+#pragma GCC diagnostic pop
+
+#include <iterator>
+
+struct declarative_file_list_t : protected cbl_declarative_t {
+ declarative_file_list_t( const cbl_declarative_t& d )
+ : cbl_declarative_t(d)
+ {
+ if( nfile > 0 )
+ assert(d.files[0] == this->files[0]);
+ }
+ static std::ostream&
+ splat( std::ostream& os, const declarative_file_list_t& dcl ) {
+ static int i=0;
+
+ os << "static size_t dcl_file_list_" << i++
+ << "[" << dcl.nfile << "] = { ";
+ std::ostream_iterator<size_t> out(os, ", ");
+ std::copy( dcl.files, dcl.files + dcl.nfile, out );
+ return os << "};";
+ }
+};
+
+std::ostream&
+operator<<( std::ostream& os, const declarative_file_list_t& dcl ) {
+ return dcl.splat( os, dcl );
+}
+
+static declarative_file_list_t
+file_list_of( const cbl_declarative_t& dcl ) {
+ return dcl;
+}
+
+std::ostream&
+operator<<( std::ostream& os, const cbl_declarative_t& dcl ) {
+ static int i=0;
+
+ return os <<
+ "\t{ " << dcl.section << ", "
+ << std::boolalpha << dcl.global << ", "
+ << ec_type_str(dcl.type) << ", "
+ << dcl.nfile << ", "
+ << "dcl_file_list_" << i++ << ", "
+ << cbl_file_mode_str(dcl.mode) << " }"
+ << std::flush;
+}
+
+void parser_add_declaratives( size_t n, cbl_declarative_t *declaratives) {
+ const char *prog = cbl_label_of(symbol_at(PROGRAM))->name;
+ char *filename = xasprintf("declaratives.%s.h", prog);
+ std::ofstream os(filename);
+ {
+ std::ostream_iterator<declarative_file_list_t> out(os, "\n");
+ std::transform( declaratives, declaratives + n, out, file_list_of );
+ }
+ os << "\nstatic cbl_declarative_base_t declaratives[] = {\n";
+ std::ostream_iterator<cbl_declarative_t> out(os, ", \n");
+ std::copy( declaratives, declaratives + n, out );
+ os << "};\n" << std::endl;
+}
+
+cbl_field_t *
+new_literal( const literal_t& lit, enum cbl_field_attr_t attr ) {
+ bool zstring = lit.prefix[0] == 'Z';
+ if( !zstring && lit.data[lit.len] != '\0' ) {
+ dbgmsg("%s:%d: line %d, no NUL terminator '%-*.*s'{%zu/%zu}",
+ __func__, __LINE__, yylineno,
+ int(lit.len), int(lit.len),
+ lit.data, strlen(lit.data), lit.len);
+ }
+ assert(zstring || lit.data[lit.len] == '\0');
+
+ size_t attrs(attr);
+ attrs |= constant_e;
+ attrs |= literal_attr(lit.prefix);
+
+ return new_literal(lit.len, lit.data, cbl_field_attr_t(attrs));
+}
+
+bool
+cbl_file_t::validate_forward( size_t isym ) const {
+ if( isym > 0 && FldForward == symbol_field_forward(isym)->type ) {
+ auto loc = symbol_field_location(isym);
+ error_msg(loc, "line %d: %s of %s is not defined",
+ this->line, cbl_field_of(symbol_at(isym))->name,
+ this->name );
+ return false;
+ }
+ return true;
+}
+
+bool
+cbl_file_t::validate_key( const cbl_file_key_t& key ) const {
+ for( auto f = key.fields; f < key.fields + key.nfield; f++ ) {
+ if( ! validate_forward(*f) ) return false;
+ }
+ return true;
+}
+
+bool
+cbl_file_t::validate() const {
+ size_t members[] = { user_status, vsam_status, record_length };
+ bool tf = true;
+
+ for( auto isym : members ) {
+ if( ! validate_forward(isym) ) tf = false;
+ }
+
+ for( auto p = keys; p < keys + nkey; p++ ) {
+ if( ! validate_key(*p) ) tf = false;
+ }
+
+ return tf;
+}
+
+bool
+cbl_file_lock_t::mode_set( int token ) {
+ switch( token ) {
+ case MANUAL: mode = manual_e; break;
+ case RECORD: mode = record_e; break;
+ case AUTOMATIC: mode = automatic_e; break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+enum cbl_figconst_t
+cbl_figconst_of( const char *value ) {
+ struct values_t {
+ const char *value; cbl_figconst_t type;
+ } static const values[] = {
+ { constant_of(constant_index(ZERO))->data.initial, zero_value_e },
+ { constant_of(constant_index(SPACES))->data.initial, space_value_e },
+ { constant_of(constant_index(HIGH_VALUES))->data.initial, high_value_e },
+ { constant_of(constant_index(LOW_VALUES))->data.initial, low_value_e },
+ { constant_of(constant_index(QUOTES))->data.initial, quote_value_e },
+ { constant_of(constant_index(NULLS))->data.initial, null_value_e },
+ }, *eovalues = values + COUNT_OF(values);
+
+ auto p = std::find_if( values, eovalues,
+ [value]( const values_t& elem ) {
+ return elem.value == value;
+ } );
+
+ return p == eovalues? normal_value_e : p->type;
+}
+
+cbl_field_attr_t
+literal_attr( const char prefix[] ) {
+ switch(strlen(prefix)) {
+ case 0: return none_e;
+
+ case 1:
+ switch(prefix[0]) {
+ case 'B': return bool_encoded_e;
+ case 'N': cbl_unimplemented("National"); return none_e;
+ case 'X': return hex_encoded_e;
+ case 'Z': return quoted_e;
+ }
+ break;
+
+ case 2:
+ switch(prefix[1]) {
+ case 'X':
+ switch(prefix[0]) {
+ case 'B': return cbl_field_attr_t(hex_encoded_e | bool_encoded_e);
+ case 'N': cbl_unimplemented("National"); return none_e;
+ }
+ break;
+ }
+ }
+
+ // must be [BN]X
+ cbl_internal_error("'%s': invalid literal prefix", prefix);
+ gcc_unreachable();
+ return none_e;
+}
+
+bool
+cbl_field_t::has_subordinate( const cbl_field_t *that ) const {
+ while( (that = parent_of(that)) != NULL ) {
+ if( field_index(this) == field_index(that) ) return true;
+ }
+ return false;
+}
+
+bool
+cbl_field_t::value_set( _Float128 value ) {
+ data.value = value;
+ char *initial = string_of(data.value);
+ if( !initial ) return false;
+
+ // Trim trailing zeros.
+ char *p = initial + strlen(initial);
+ for( --p; initial <= p; --p ) {
+ if( *p != '0' ) break;
+ *p = '\0';
+ }
+
+ data.digits = (p - initial) + 1;
+ p = strchr(initial, '.');
+ data.rdigits = p? initial + data.digits - p : 0;
+
+ data.initial = initial;
+ data.capacity = type_capacity(type, data.digits);
+ return true;
+}
+
+const char *
+cbl_field_t::value_str() const {
+ return string_of(data.value);
+}
+
+static const cbl_division_t not_syntax_only = cbl_division_t(-1);
+ cbl_division_t cbl_syntax_only = not_syntax_only;
+
+void
+mode_syntax_only( cbl_division_t division ) {
+ cbl_syntax_only = division;
+}
+
+// Parser moves to syntax-only mode if data-division errors preclude compilation.
+bool
+mode_syntax_only() {
+ return cbl_syntax_only != not_syntax_only
+ && cbl_syntax_only <= current_division;
+}
+
+void
+cobol_dialect_set( cbl_dialect_t dialect ) {
+ cbl_dialect = dialect;
+ if( dialect & dialect_ibm_e ) cobol_gcobol_feature_set(feature_embiggen_e);
+}
+cbl_dialect_t cobol_dialect() { return cbl_dialect; }
+
+static bool internal_ebcdic_locked = false;
+
+void internal_ebcdic_lock() {
+ internal_ebcdic_locked = true;
+}
+void internal_ebcdic_unlock() {
+ internal_ebcdic_locked = false;
+}
+
+bool
+cobol_gcobol_feature_set( cbl_gcobol_feature_t gcobol_feature, bool on ) {
+ if( gcobol_feature == feature_internal_ebcdic_e ) {
+ if( internal_ebcdic_locked ) return false;
+ }
+ if( on ) {
+ cbl_gcobol_features |= gcobol_feature;
+ } else {
+ cbl_gcobol_features &= ~gcobol_feature;
+ }
+ return true;
+}
+
+static bool
+literal_refmod_valid( YYLTYPE loc, const cbl_refer_t& r ) {
+ if( r.field->has_attr(any_length_e) ) return true;
+
+ const cbl_span_t& refmod(r.refmod);
+
+ if( ! is_literal(refmod.from->field) ) {
+ if( ! refmod.len ) return true;
+ if( ! is_literal(refmod.len->field) ) return true;
+ auto edge = refmod.len->field->data.value;
+ if( 0 < edge ) {
+ if( --edge < r.field->data.capacity ) return true;
+ }
+ // len < 0 or not: 0 < from + len <= capacity
+ error_msg(loc, "%s(%s:%zu) out of bounds, "
+ "size is %u",
+ r.field->name,
+ refmod.from->name(),
+ size_t(refmod.len->field->data.value),
+ static_cast<unsigned int>(r.field->data.capacity) );
+ return false;
+ }
+
+ if( refmod.from->field->data.value > 0 ) {
+ auto edge = refmod.from->field->data.value;
+ if( --edge < r.field->data.capacity ) {
+ if( ! refmod.len ) return true;
+ if( ! is_literal(refmod.len->field) ) return true;
+ if( refmod.len->field->data.value > 0 ) {
+ edge += refmod.len->field->data.value;
+ if( --edge < r.field->data.capacity ) return true;
+ }
+ // len < 0 or not: 0 < from + len <= capacity
+ auto loc = symbol_field_location(field_index(r.field));
+ error_msg(loc, "%s(%zu:%zu) out of bounds, "
+ "size is %u",
+ r.field->name,
+ size_t(refmod.from->field->data.value),
+ size_t(refmod.len->field->data.value),
+ static_cast<unsigned int>(r.field->data.capacity) );
+ return false;
+ }
+ }
+ // not: 0 < from <= capacity
+ error_msg(loc,"%s(%zu) out of bounds, size is %u",
+ r.field->name,
+ size_t(refmod.from->field->data.value),
+ static_cast<unsigned int>(r.field->data.capacity) );
+ return false;
+}
+
+const cbl_field_t *
+literal_subscript_oob( const cbl_refer_t& r, size_t& isub );
+
+static bool
+literal_subscripts_valid( YYLTYPE loc, const cbl_refer_t& name ) {
+ static char subs[ 7 * 32 ], *esub = subs + sizeof(subs);
+ char *p = subs;
+ size_t isub;
+
+ // Find subscript in the supplied refer
+ const cbl_field_t *oob = literal_subscript_oob(name, isub);
+ if( oob ) {
+ const char *sep = "";
+ for( auto r = name.subscripts; r < name.subscripts + name.nsubscript; r++ ) {
+ snprintf( p, esub - p, "%s%s", sep, nice_name_of(r->field) );
+ sep = " ";
+ }
+
+ const char *upper_phrase = "";
+ if( ! oob->occurs.bounds.fixed_size() ) {
+ static char ub[32] = "boo";
+ sprintf(ub, " to %lu", oob->occurs.bounds.upper);
+ upper_phrase = ub;
+ }
+
+ // X(0): subscript 1 of for out of range for 02 X OCCURS 4 to 6
+ error_msg(loc, "%s(%s): subscript %zu out of range "
+ "for %s %s OCCURS %lu%s",
+ oob->name, subs, 1 + isub,
+ oob->level_str(), oob->name,
+ oob->occurs.bounds.lower, upper_phrase );
+ return false;
+ }
+ return true;
+}
+
+static void
+subscript_dimension_error( YYLTYPE loc, size_t nsub, const cbl_refer_t *scalar ) {
+ if( 0 == dimensions(scalar->field) ) {
+ error_msg(loc, "%zu subscripts provided for %s, "
+ "which has no dimensions",
+ nsub, scalar->name() );
+ } else {
+ error_msg(loc, "%zu subscripts provided for %s, "
+ "which requires %zu dimensions",
+ nsub, scalar->name(), dimensions(scalar->field) );
+ }
+}
+
+static void
+reject_refmod( YYLTYPE loc, cbl_refer_t scalar ) {
+ if( scalar.is_refmod_reference() ) {
+ error_msg(loc, "%s cannot be reference-modified here", scalar.name());
+ }
+}
+
+static bool
+require_pointer( YYLTYPE loc, cbl_refer_t scalar ) {
+ if( scalar.field->type != FldPointer ) {
+ error_msg(loc, "%s must have USAGE POINTER", scalar.name());
+ return false;
+ }
+ return true;
+}
+
+static bool
+require_numeric( YYLTYPE loc, cbl_refer_t scalar ) {
+ if( ! is_numeric(scalar.field) ) {
+ error_msg(loc, "%s must have numeric USAGE", scalar.name());
+ return false;
+ }
+ return true;
+}
+
+/* eval methods */
+
+eval_subject_t::eval_subject_t()
+ : result( new_temporary(FldConditional) )
+{
+ labels.when = label("when");
+ labels.yeah = label("yeah");
+ labels.done = label("done");
+ pcol = columns.begin();
+}
+
+cbl_label_t *
+eval_subject_t::label( const char skel[] ) {
+ static const cbl_label_t protolabel = { .type = LblEvaluate };
+ cbl_label_t label = protolabel;
+ label.line = yylineno;
+ size_t n = 1 + symbols_end() - symbols_begin();
+ snprintf(label.name, sizeof(label.name), "_eval_%s_%zu", skel, n);
+ auto output = symbol_label_add( PROGRAM, &label );
+ return output;
+}
+
+bool
+eval_subject_t::compatible( const cbl_field_t *object ) const {
+ assert(pcol != columns.end());
+ assert(pcol->field);
+ auto subject(pcol->field);
+ if( subject->type != object->type ) {
+ if( is_conditional(subject) ) {
+ return is_conditional(object);
+ }
+ return ! is_conditional(object);
+ }
+ return true;
+}
+
+
+cbl_field_t *
+eval_subject_t::compare( int token ) {
+ size_t tf( very_false_register() );
+
+ switch( token ) {
+ case ANY:
+ parser_logop(result,
+ field_at(very_true_register()), and_op,
+ field_at(very_true_register()));
+ break;
+ case TRUE_kw:
+ tf = very_true_register();
+ __attribute__((fallthrough));
+ case FALSE_kw:
+ assert( is_conditional(pcol->field) );
+ parser_logop(this->result, pcol->field, xnor_op, field_at(tf));
+ break;
+ default:
+ assert(token == -1 && false );
+ break;
+ }
+ return result;
+}
+
+cbl_field_t *
+eval_subject_t::compare( relop_t op, const cbl_refer_t& object, bool deciding ) {
+ auto subject(*pcol);
+ if( compatible(object.field) ) {
+ if( ! is_conditional(subject.field) ) {
+ auto result = deciding? this->result : new_temporary(FldConditional);
+ parser_relop(result, subject, op, object);
+ return result;
+ }
+ }
+ if( yydebug ) {
+ dbgmsg("%s:%d: failed for %s %s %s",
+ __func__, __LINE__,
+ name_of(subject.field), relop_str(op), name_of(object.field));
+ }
+ return nullptr;
+}
+
+cbl_field_t *
+eval_subject_t::compare( const cbl_refer_t& object,
+ const cbl_refer_t& object2 ) {
+ auto subject(*pcol);
+
+ if( ! compatible( object.field ) ) {
+ if( yydebug ) {
+ dbgmsg("%s:%d: failed for %s %s",
+ __func__, __LINE__,
+ name_of(subject.field), name_of(object.field));
+ }
+ return nullptr;
+ }
+ if( object2.field ) {
+ if( ! compatible( object2.field ) ) {
+ if( yydebug ) {
+ dbgmsg("%s:%d: failed for %s %s",
+ __func__, __LINE__,
+ name_of(subject.field), name_of(object2.field));
+ }
+ return nullptr;
+ }
+ }
+
+ if( is_conditional(subject.field) ) {
+ assert( object2.field == nullptr );
+ parser_logop(result, subject.field, xnor_op, object.field);
+ return result;
+ }
+
+ if( object2.field ) {
+ assert( ! is_conditional(object.field) );
+ assert( ! is_conditional(object2.field) );
+
+ cbl_field_t * gte = new_temporary(FldConditional);
+ cbl_field_t * lte = new_temporary(FldConditional);
+
+ parser_relop( gte, object, le_op, subject );
+ parser_relop( lte, subject, le_op, object2 );
+
+ parser_logop(result, gte, and_op, lte);
+ return result;
+ }
+
+ parser_relop(result, subject, eq_op, object);
+ return result;
+}