Skip to content

Integration of EXEC preprocessor beyond baseline. - #294

Open
utam-1 wants to merge 12 commits into
OCamlPro:gitside-gnucobol-3.xfrom
utam-1:exec-preprocessor-beyond-baseline
Open

Integration of EXEC preprocessor beyond baseline.#294
utam-1 wants to merge 12 commits into
OCamlPro:gitside-gnucobol-3.xfrom
utam-1:exec-preprocessor-beyond-baseline

Conversation

@utam-1

@utam-1 utam-1 commented Jun 14, 2026

Copy link
Copy Markdown

Description of PR

This is a follow - up to #279.

Approach and basic idea

cb_preparser_entry is added to cobc.c, which handles the associated pre-parser metadata. cb_preparser_list and cb_active_preparser are created, one being a list to registered preparsers, and other is a pointer for pplex.l to identify the tag.

cb_load_preparser_conf() and cb_find_preparser() are created based on similar mechanism of cb_config_entry().
In ppparse.y the 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 in pplex.l which is artificially generated.

So far only general tests have been performed by using a test.conf file and setting up a pseudo - preprocessor via bash, which shows the replacement works and flag-injection is performed via gcc.

@utam-1
utam-1 marked this pull request as draft June 14, 2026 02:38
@utam-1
utam-1 marked this pull request as ready for review June 29, 2026 14:40

@GitMensch GitMensch left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • 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

Comment thread cobc/cobc.h
Comment thread cobc/pplex.l Outdated
Comment thread cobc/cobc.c Outdated
Comment thread cobc/cobc.c Outdated
Comment thread cobc/cobc.c Outdated
Comment on lines +3125 to +3253
/* 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;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Comment thread cobc/cobc.h Outdated
Comment thread cobc/cobc.c Outdated
Comment thread cobc/cobc.c Outdated
Comment thread cobc/cobc.c Outdated

@GitMensch GitMensch left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

open from last review:

  • new changes need a new Changelog entry (you've added them, just in the wrong place)
  • gnucobol.texi should also document a bit about the file format

Comment thread doc/gnucobol.texi
User-defined dialect configuration.

@item --preparser=<file>
Register external preparser configuration.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread cobc/cobc.c Outdated
Comment on lines +3125 to +3253
/* 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;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Comment thread cobc/ChangeLog
(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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
to consume body tokens for parsing purposes only
to consume body tokens for parsing purposes only

Comment thread cobc/ChangeLog

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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, ...

Comment thread cobc/ChangeLog
@@ -1,2 +1,2 @@

2026-03-16 Uttam Singh Bhadauriya <uttamsinghbhadoriya23@gmail.com>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that whole block should - for the baseline changes - be after the may changes, nomt up-front

Comment thread cobc/cobc.c

char *orig_source = cobc_strdup (fn->source);
char *orig_preprocess = cobc_strdup (fn->preprocess);
struct cb_preparser_entry *p_reset;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You likely can just assign them (otherwise you'd need to ensure to free those pointers once they are not used any more)

Comment thread cobc/config.c
#include <string.h>
#include <limits.h>

#include <ctype.h>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread ChangeLog
Comment on lines +2 to +9
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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cobc and doc go to their own changelog files, the testsuite sources don't get an entry for new elements

Comment thread NEWS

* New GnuCOBOL features

** cobc provides a --preparser option to register external preparser configurations

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread NEWS
* New GnuCOBOL features

** cobc provides a --preparser option to register external preparser configurations

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new entries to the NEWS file are added at the end of the relevant chapter

Comment on lines +1626 to +1627
AT_CHECK([$COBC --preparser missing.conf prog.cob], [1], [], [stderr])
AT_CHECK([$GREP "cannot load preparser configuration 'missing.conf'" stderr], [0], [ignore], [ignore])

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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])

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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

Comment on lines 1611 to +1613
AT_CLEANUP

AT_SETUP([external preparser --preparser])

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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 :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants