Integration of EXEC preprocessor beyond baseline. - #294
Conversation
GitMensch
left a comment
There was a problem hiding this comment.
- new changes need a new Changelog entry
- we'll need a NEWS entry for the new --preparser, as well as help.c and gnucobol.texi addition; the later should also document a bit about the file format
- please add your tests (you've seem to have done some) to used_binaries.at; we'd need at least:
- --preparser for not existing file
- --preparser for existing, but invalid file
- --preparser for existing file
- --preparser for two existing files
- --preparser, where pseudo preparser runs fine
- --preparser, where psuedo preparser runs in error
| /* Resolve preparser config filename: explicit path (contains separator | ||
| * or exists as-is) is used directly; otherwise look up | ||
| * COB_CONFIG_DIR/<name>.conf, mirroring cb_load_conf_file(). */ | ||
| static FILE * | ||
| open_preparser_conf (const char *name, char *resolved, size_t resolved_size) | ||
| { | ||
| FILE *fp; | ||
| size_t i; | ||
|
|
||
| for (i = 0; name[i] != 0 && name[i] != SLASH_CHAR; i++); | ||
|
|
||
| if (name[i] != 0 || access (name, F_OK) == 0) { | ||
| /* contains a path separator, or exists as given */ | ||
| snprintf (resolved, resolved_size, "%s", name); | ||
| } else { | ||
| /* plain name: look in COB_CONFIG_DIR/<name>.conf */ | ||
| snprintf (resolved, resolved_size, "%s%c%s.conf", | ||
| cob_config_dir, SLASH_CHAR, name); | ||
| } | ||
|
|
||
| fp = fopen (resolved, "r"); | ||
| return fp; | ||
| } | ||
|
|
||
| /* Parse a single "key: value" or "key value" line. | ||
| * Leading/trailing whitespace and comments (#) are stripped. */ | ||
| static int | ||
| parse_preparser_line (char *buff, char **key, char **val) | ||
| { | ||
| char *p, *colon; | ||
|
|
||
| /* strip comment */ | ||
| if ((p = strchr (buff, '#')) != NULL) { | ||
| *p = '\0'; | ||
| } | ||
| /* strip trailing whitespace/newline */ | ||
| for (p = buff + strlen (buff); p > buff && isspace ((unsigned char)p[-1]); p--); | ||
| *p = '\0'; | ||
| /* strip leading whitespace */ | ||
| for (p = buff; isspace ((unsigned char)*p); p++); | ||
| if (*p == '\0') { | ||
| return 1; /* blank line */ | ||
| } | ||
|
|
||
| *key = p; | ||
| if ((colon = strchr (p, ':')) != NULL) { | ||
| *colon = '\0'; | ||
| p = colon + 1; | ||
| } else { | ||
| /* space-separated: key value */ | ||
| for (; *p && !isspace ((unsigned char)*p); p++); | ||
| if (*p) { | ||
| *p++ = '\0'; | ||
| } | ||
| } | ||
| for (; isspace ((unsigned char)*p); p++); | ||
| *val = p; | ||
|
|
||
| /* trim trailing whitespace of key */ | ||
| for (p = *key + strlen (*key); p > *key && isspace ((unsigned char)p[-1]); p--); | ||
| *p = '\0'; | ||
|
|
||
| return 0; | ||
| } | ||
|
|
||
| /* Load an external preparser configuration file and register it | ||
| * in cb_preparser_list. Returns 0 on success, non-zero on error. */ | ||
| int | ||
| cb_load_preparser_conf (const char *name) | ||
| { | ||
| FILE *fp; | ||
| char resolved[COB_NORMAL_BUFF]; | ||
| char buff[COB_SMALL_BUFF]; | ||
| struct cb_preparser_entry *pe; | ||
|
|
||
| fp = open_preparser_conf (name, resolved, sizeof (resolved)); | ||
| if (!fp) { | ||
| cb_error (_("preparser configuration '%s' not found"), name); | ||
| return 1; | ||
| } | ||
|
|
||
| pe = cobc_main_malloc (sizeof (struct cb_preparser_entry)); | ||
| pe->tag = NULL; | ||
| pe->command = NULL; | ||
| pe->cflags = NULL; | ||
| pe->ldflags = NULL; | ||
| pe->on_error = 1; /* default: error */ | ||
| pe->used = 0; | ||
| pe->next = NULL; | ||
|
|
||
| while (fgets (buff, sizeof (buff), fp)) { | ||
| char *key, *val; | ||
|
|
||
| if (parse_preparser_line (buff, &key, &val) != 0) { | ||
| continue; | ||
| } | ||
|
|
||
| if (strcasecmp (key, "tag") == 0) { | ||
| size_t i; | ||
| pe->tag = cobc_main_strdup (val); | ||
| for (i = 0; pe->tag[i]; i++) { | ||
| pe->tag[i] = (char)toupper ((unsigned char)pe->tag[i]); | ||
| } | ||
| } else if (strcasecmp (key, "command") == 0) { | ||
| pe->command = cobc_main_strdup (val); | ||
| } else if (strcasecmp (key, "cflags") == 0) { | ||
| pe->cflags = cobc_main_strdup (val); | ||
| } else if (strcasecmp (key, "ldflags") == 0) { | ||
| pe->ldflags = cobc_main_strdup (val); | ||
| } else if (strcasecmp (key, "on-error") == 0) { | ||
| pe->on_error = (strcasecmp (val, "warn") == 0) ? 0 : 1; | ||
| } else { | ||
| cb_warning (cb_warn_unsupported, | ||
| _("unknown preparser configuration key '%s' in '%s'"), | ||
| key, resolved); | ||
| } | ||
| } | ||
| fclose (fp); | ||
|
|
||
| if (!pe->tag || !pe->command) { | ||
| cb_error (_("preparser configuration '%s' is missing 'tag' or 'command'"), | ||
| resolved); | ||
| return 1; | ||
| } | ||
|
|
||
| pe->next = cb_preparser_list; | ||
| cb_preparser_list = pe; | ||
| return 0; | ||
| } |
There was a problem hiding this comment.
those functions should be moved to config.c - and, where possible, the existing functions be refactored to not have duplicated code for how the lines are read/split
There was a problem hiding this comment.
I kept the implementation for the two differently as they follow different kinds of implementation ( config table and Linked list ) the coupling between these two may make it difficult to maintain in my opinion.
There was a problem hiding this comment.
parse_preparser_line() should be replaced by re-using the code for the config lines; to do so move the code of cb_config_entry for the parsing out of the function (the new static function gets a pointer to name+value, just as your version) - in this new variant name and value will be parsed first before cb_config_entry() checks the name (which is now intermixed).
This way we have a single new function called for both compiler and preparser config, ensuring the format is exactly identical, by keeping the way it is looked up / used out of that code.
I agree that cb_load_preparser_conf() itself should be kept separate from the config file loading (though they will look very similar) because of the struct / array approach (which we don't need fo the preparser).
| User-defined dialect configuration. | ||
|
|
||
| @item --preparser=<file> | ||
| Register external preparser configuration. |
There was a problem hiding this comment.
That's a start, but similar to the note below you need to explain what this is used for and either document the structure here or add an example definition (for esqlOC and/or GixSQL) showing the format with explanations.
| /* Resolve preparser config filename: explicit path (contains separator | ||
| * or exists as-is) is used directly; otherwise look up | ||
| * COB_CONFIG_DIR/<name>.conf, mirroring cb_load_conf_file(). */ | ||
| static FILE * | ||
| open_preparser_conf (const char *name, char *resolved, size_t resolved_size) | ||
| { | ||
| FILE *fp; | ||
| size_t i; | ||
|
|
||
| for (i = 0; name[i] != 0 && name[i] != SLASH_CHAR; i++); | ||
|
|
||
| if (name[i] != 0 || access (name, F_OK) == 0) { | ||
| /* contains a path separator, or exists as given */ | ||
| snprintf (resolved, resolved_size, "%s", name); | ||
| } else { | ||
| /* plain name: look in COB_CONFIG_DIR/<name>.conf */ | ||
| snprintf (resolved, resolved_size, "%s%c%s.conf", | ||
| cob_config_dir, SLASH_CHAR, name); | ||
| } | ||
|
|
||
| fp = fopen (resolved, "r"); | ||
| return fp; | ||
| } | ||
|
|
||
| /* Parse a single "key: value" or "key value" line. | ||
| * Leading/trailing whitespace and comments (#) are stripped. */ | ||
| static int | ||
| parse_preparser_line (char *buff, char **key, char **val) | ||
| { | ||
| char *p, *colon; | ||
|
|
||
| /* strip comment */ | ||
| if ((p = strchr (buff, '#')) != NULL) { | ||
| *p = '\0'; | ||
| } | ||
| /* strip trailing whitespace/newline */ | ||
| for (p = buff + strlen (buff); p > buff && isspace ((unsigned char)p[-1]); p--); | ||
| *p = '\0'; | ||
| /* strip leading whitespace */ | ||
| for (p = buff; isspace ((unsigned char)*p); p++); | ||
| if (*p == '\0') { | ||
| return 1; /* blank line */ | ||
| } | ||
|
|
||
| *key = p; | ||
| if ((colon = strchr (p, ':')) != NULL) { | ||
| *colon = '\0'; | ||
| p = colon + 1; | ||
| } else { | ||
| /* space-separated: key value */ | ||
| for (; *p && !isspace ((unsigned char)*p); p++); | ||
| if (*p) { | ||
| *p++ = '\0'; | ||
| } | ||
| } | ||
| for (; isspace ((unsigned char)*p); p++); | ||
| *val = p; | ||
|
|
||
| /* trim trailing whitespace of key */ | ||
| for (p = *key + strlen (*key); p > *key && isspace ((unsigned char)p[-1]); p--); | ||
| *p = '\0'; | ||
|
|
||
| return 0; | ||
| } | ||
|
|
||
| /* Load an external preparser configuration file and register it | ||
| * in cb_preparser_list. Returns 0 on success, non-zero on error. */ | ||
| int | ||
| cb_load_preparser_conf (const char *name) | ||
| { | ||
| FILE *fp; | ||
| char resolved[COB_NORMAL_BUFF]; | ||
| char buff[COB_SMALL_BUFF]; | ||
| struct cb_preparser_entry *pe; | ||
|
|
||
| fp = open_preparser_conf (name, resolved, sizeof (resolved)); | ||
| if (!fp) { | ||
| cb_error (_("preparser configuration '%s' not found"), name); | ||
| return 1; | ||
| } | ||
|
|
||
| pe = cobc_main_malloc (sizeof (struct cb_preparser_entry)); | ||
| pe->tag = NULL; | ||
| pe->command = NULL; | ||
| pe->cflags = NULL; | ||
| pe->ldflags = NULL; | ||
| pe->on_error = 1; /* default: error */ | ||
| pe->used = 0; | ||
| pe->next = NULL; | ||
|
|
||
| while (fgets (buff, sizeof (buff), fp)) { | ||
| char *key, *val; | ||
|
|
||
| if (parse_preparser_line (buff, &key, &val) != 0) { | ||
| continue; | ||
| } | ||
|
|
||
| if (strcasecmp (key, "tag") == 0) { | ||
| size_t i; | ||
| pe->tag = cobc_main_strdup (val); | ||
| for (i = 0; pe->tag[i]; i++) { | ||
| pe->tag[i] = (char)toupper ((unsigned char)pe->tag[i]); | ||
| } | ||
| } else if (strcasecmp (key, "command") == 0) { | ||
| pe->command = cobc_main_strdup (val); | ||
| } else if (strcasecmp (key, "cflags") == 0) { | ||
| pe->cflags = cobc_main_strdup (val); | ||
| } else if (strcasecmp (key, "ldflags") == 0) { | ||
| pe->ldflags = cobc_main_strdup (val); | ||
| } else if (strcasecmp (key, "on-error") == 0) { | ||
| pe->on_error = (strcasecmp (val, "warn") == 0) ? 0 : 1; | ||
| } else { | ||
| cb_warning (cb_warn_unsupported, | ||
| _("unknown preparser configuration key '%s' in '%s'"), | ||
| key, resolved); | ||
| } | ||
| } | ||
| fclose (fp); | ||
|
|
||
| if (!pe->tag || !pe->command) { | ||
| cb_error (_("preparser configuration '%s' is missing 'tag' or 'command'"), | ||
| resolved); | ||
| return 1; | ||
| } | ||
|
|
||
| pe->next = cb_preparser_list; | ||
| cb_preparser_list = pe; | ||
| return 0; | ||
| } |
There was a problem hiding this comment.
parse_preparser_line() should be replaced by re-using the code for the config lines; to do so move the code of cb_config_entry for the parsing out of the function (the new static function gets a pointer to name+value, just as your version) - in this new variant name and value will be parsed first before cb_config_entry() checks the name (which is now intermixed).
This way we have a single new function called for both compiler and preparser config, ensuring the format is exactly identical, by keeping the way it is looked up / used out of that code.
I agree that cb_load_preparser_conf() itself should be kept separate from the config file loading (though they will look very similar) because of the struct / array approach (which we don't need fo the preparser).
| (full copybook expansion with -I and -ffold-copy support); | ||
| all other EXEC TAG blocks warn at "unsupported" level | ||
| (defaulting to error) and are ignored; add _exec_token_list | ||
| to consume body tokens for parsing purposes only |
There was a problem hiding this comment.
| to consume body tokens for parsing purposes only | |
| to consume body tokens for parsing purposes only | |
There was a problem hiding this comment.
you want one entry for the baseline (with the old date) and another one for the follow-up work, which then outlines the changes in config.c, tree.h, ...
| @@ -1,2 +1,2 @@ | |||
|
|
|||
| 2026-03-16 Uttam Singh Bhadauriya <uttamsinghbhadoriya23@gmail.com> | |||
There was a problem hiding this comment.
that whole block should - for the baseline changes - be after the may changes, nomt up-front
|
|
||
| char *orig_source = cobc_strdup (fn->source); | ||
| char *orig_preprocess = cobc_strdup (fn->preprocess); | ||
| struct cb_preparser_entry *p_reset; |
There was a problem hiding this comment.
You likely can just assign them (otherwise you'd need to ensure to free those pointers once they are not used any more)
| #include <string.h> | ||
| #include <limits.h> | ||
|
|
||
| #include <ctype.h> |
There was a problem hiding this comment.
the existing code has no isspace, so you can get rid of that header when refactoring the existing code to be used for the tag/value split
| 2026-07-31 Uttam Bhadauriya <uttamsinghbhadoriya23@gmail.com> | ||
|
|
||
| * cobc/cobc.h, cobc/tree.h, cobc/cobc.c, cobc/config.c, cobc/pplex.l: | ||
| Refactor external preparser subsystem, move struct and functions, | ||
| rename tag to subsystem, improve error messages and indentation. | ||
| * cobc/help.c, doc/gnucobol.texi: Add --preparser documentation. | ||
| * tests/testsuite.src/used_binaries.at: Add preparser tests. | ||
|
|
There was a problem hiding this comment.
cobc and doc go to their own changelog files, the testsuite sources don't get an entry for new elements
|
|
||
| * New GnuCOBOL features | ||
|
|
||
| ** cobc provides a --preparser option to register external preparser configurations |
There was a problem hiding this comment.
explain here that EXEC is now supported using external preparsers, registered with --preparser (you can add a hint to check the details in the manual, similar to the existing entries doing so) with a fallback of ignoring any EXEC parts but INCLUDE
| * New GnuCOBOL features | ||
|
|
||
| ** cobc provides a --preparser option to register external preparser configurations | ||
|
|
There was a problem hiding this comment.
new entries to the NEWS file are added at the end of the relevant chapter
| AT_CHECK([$COBC --preparser missing.conf prog.cob], [1], [], [stderr]) | ||
| AT_CHECK([$GREP "cannot load preparser configuration 'missing.conf'" stderr], [0], [ignore], [ignore]) |
There was a problem hiding this comment.
haven't checked the ones below, but instead of placing that to an stderr where you grep from (which either needs a redirection or I learn something new here) you'd just check the output in that [] directly.
| AT_CLEANUP | ||
|
|
||
| AT_SETUP([external preparser --preparser]) | ||
| AT_KEYWORDS([preparser]) |
There was a problem hiding this comment.
| AT_KEYWORDS([preparser]) | |
| AT_KEYWORDS([cobc configuration EXEC]) |
you can replace the keyword by cobc + configuration - other words are already included by AT_SETUP content
| AT_CLEANUP | ||
|
|
||
| AT_SETUP([external preparser --preparser]) |
There was a problem hiding this comment.
| AT_CLEANUP | |
| AT_SETUP([external preparser --preparser]) | |
| AT_CLEANUP | |
| AT_SETUP([external preparser --preparser]) |
nice test with multiple checks - just add two empty lines before each new test group :-)
Description of PR
This is a follow - up to #279.
Approach and basic idea
cb_preparser_entryis added tocobc.c, which handles the associated pre-parser metadata.cb_preparser_listandcb_active_preparserare created, one being a list to registered preparsers, and other is a pointer forpplex.lto identify the tag.cb_load_preparser_conf()andcb_find_preparser()are created based on similar mechanism ofcb_config_entry().In
ppparse.ythe following macro is defined#define pperror(x) do { if (!cb_active_preparser) cb_error_always ("%s", x); } while(0)the idea is to identify the abort of pre-parser inpplex.lwhich is artificially generated.So far only general tests have been performed by using a
test.conffile and setting up a pseudo - preprocessor via bash, which shows the replacement works and flag-injection is performed via gcc.