aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--am/grecs.m42
-rw-r--r--doc/grecs-syntax.texi42
-rw-r--r--include/grecs/lex.h6
-rw-r--r--include/grecs/preproc.h7
-rw-r--r--src/Make.am1
-rw-r--r--src/bind-lex.l6
-rw-r--r--src/dhcpd-lex.l6
-rw-r--r--src/grecs-gram.y5
-rw-r--r--src/grecs-lex.l1140
-rw-r--r--src/preproc.c812
-rw-r--r--tests/.gitignore1
-rw-r--r--tests/Makefile.am9
-rw-r--r--tests/abend.at45
-rw-r--r--tests/epp.at78
-rw-r--r--tests/error.at32
-rw-r--r--tests/failpp.c57
-rw-r--r--tests/gcffmt.c2
-rw-r--r--tests/gcfpp.c37
-rw-r--r--tests/incl01.at4
-rw-r--r--tests/ipp.at168
-rw-r--r--tests/ppexit.at64
-rw-r--r--tests/testsuite.at11
-rw-r--r--tests/warning.at34
23 files changed, 1560 insertions, 1009 deletions
diff --git a/am/grecs.m4 b/am/grecs.m4
index 24071e3..fda6dcb 100644
--- a/am/grecs.m4
+++ b/am/grecs.m4
@@ -113,7 +113,7 @@ AC_DEFUN([GRECS_SETUP],[
if test -z "$DEFAULT_PREPROCESSOR"; then
PPBIN=
else
- PPBIN=${DEFAULT_PREPROCESSOR%%[ \t]*}
+ PPBIN=${DEFAULT_PREPROCESSOR%%[[ \t]]*}
case "$PPBIN" in
/*) if ! test -x $PPBIN; then
AC_MSG_WARN([preprocessor program $PPBIN not found on disk])
diff --git a/doc/grecs-syntax.texi b/doc/grecs-syntax.texi
index 08564d2..ffe729d 100644
--- a/doc/grecs-syntax.texi
+++ b/doc/grecs-syntax.texi
@@ -78,10 +78,8 @@ except that they cause some changes in the way the configuration is
parsed. Pragmatic comments begin with a @samp{#} sign and end with the
next physical newline character.
-@table @code
-@kwindex #include
-@item #include <@var{file}>
-@itemx #include @var{file}
+@deffn {Pragma} #include <@var{file}>
+@deffnx {Pragma} #include @var{file}
Include the contents of the file @var{file}. There are three possible
use cases.
@@ -103,8 +101,8 @@ be issued.
The default include search path is:
@enumerate 1
-@item @file{@var{prefix}/share/@value{PROGNAME}/@value{VERSION}/include}
@item @file{@var{prefix}/share/@value{PROGNAME}/include}
+@item @file{@var{prefix}/share/@value{PROGNAME}/@value{VERSION}/include}
@end enumerate
@noindent
@@ -117,25 +115,43 @@ New directories can be appended in front of it using @option{-I}
(@pxref{Preprocessor, include-directory}).
}
@end ignore
+@end deffn
-@kwindex #include_once
-@item #include_once <@var{file}>
-@itemx #include_once @var{file}
+@deffn {Pragma} #include_once <@var{file}>
+@deffnx {Pragma} #include_once @var{file}
Same as @code{#include}, except that, if the @var{file} has already
been included, it will not be included again.
+@end deffn
-@kwindex #line
-@item #line @var{num}
-@itemx #line @var{num} "@var{file}"
+@deffn {Pragma} #line @var{num}
+@deffnx {Pragma} #line @var{num} "@var{file}"
This line causes the parser to believe, for purposes of error
diagnostics, that the line number of the next source line is given by
@var{num} and the current input file is named by @var{file}.
If the latter is absent, the remembered file name does not change.
+@end deffn
-@item # @var{num} "@var{file}"
+@deffn {Pragma} # @var{num} "@var{file}"
This is a special form of @code{#line} statement, understood for
compatibility with the @sc{c} preprocessor.
-@end table
+@end deffn
+
+@deffn {Pragma} #warning "@var{text}"
+Emit a warning message @var{text} in the current location.
+@end deffn
+
+@deffn {Pragma} #error "@var{text}"
+Emit error message @var{text} in the current location and continue
+parsing. The error counter is increased. It is
+implementation-dependent whether this error will cause the
+configuration to be rejected as invalid.
+@end deffn
+
+@deffn {Pragma} #abend "@var{text}"
+Emit error message @var{text} in the current location and terminate
+further parsing. This statement is guaranteed to abort the processing
+of the configuration file and to reject it due to syntax errors.
+@end deffn
In fact, these statements provide a rudimentary preprocessing
features. For more sophisticated ways to modify configuration before
diff --git a/include/grecs/lex.h b/include/grecs/lex.h
index 6ef7114..545427a 100644
--- a/include/grecs/lex.h
+++ b/include/grecs/lex.h
@@ -25,11 +25,9 @@ void grecs_destroy_text(void);
struct grecs_symtab *grecs_text_table(void);
void grecs_parse_line_directive(char *text, grecs_locus_t *ploc,
- struct grecs_locus_point *ppoint,
- size_t *pxlines);
+ struct grecs_locus_point *ppoint);
void grecs_parse_line_directive_cpp(char *text, grecs_locus_t *ploc,
- struct grecs_locus_point *ppoint,
- size_t *pxlines);
+ struct grecs_locus_point *ppoint);
void grecs_line_acc_create(void);
void grecs_line_acc_free(void);
diff --git a/include/grecs/preproc.h b/include/grecs/preproc.h
index 592de4f..5ce26f1 100644
--- a/include/grecs/preproc.h
+++ b/include/grecs/preproc.h
@@ -21,11 +21,8 @@
extern const char *grecs_preprocessor;
-size_t grecs_preproc_fill_buffer(char *buf, size_t size);
void grecs_preproc_add_include_dir(char *dir);
-int grecs_preproc_init(const char *name);
-void grecs_preproc_done(void);
-int grecs_preproc_run(const char *config_file, const char *extpp);
+int grecs_preprocess(char const *name, int trace);
#define GRECS_STD_INCLUDE 0x01
#define GRECS_USR_INCLUDE 0x02
@@ -37,7 +34,7 @@ int grecs_foreach_include_dir(int flag, int (*fun)(int, const char *, void *),
char *grecs_find_include_file(const char *name, int allow_cwd);
FILE *grecs_preproc_extrn_start(const char *file, pid_t *ppid);
-void grecs_preproc_extrn_shutdown(pid_t pid);
+int grecs_preproc_extrn_shutdown(pid_t pid);
void grecs_include_path_clear(void);
void grecs_include_path_setup(const char *dir, ...);
diff --git a/src/Make.am b/src/Make.am
index 38edba6..8cd8ebd 100644
--- a/src/Make.am
+++ b/src/Make.am
@@ -39,7 +39,6 @@ GRECS_SRC = \
parser.c\
parsertab.c\
path-parser.c\
- preproc.c\
sort.c\
symtab.c\
text.c\
diff --git a/src/bind-lex.l b/src/bind-lex.l
index c1f37d7..7201e53 100644
--- a/src/bind-lex.l
+++ b/src/bind-lex.l
@@ -60,12 +60,10 @@ P [1-9][0-9]*
/* Line directive */
^[ \t]*#[ \t]*{P}[ \t]+\".*\".*\n { grecs_parse_line_directive_cpp(yytext,
&yylloc,
- &grecs_current_locus_point,
- NULL); }
+ &grecs_current_locus_point); }
^[ \t]*#[ \t]*line[ \t].*\n { grecs_parse_line_directive(yytext,
&yylloc,
- &grecs_current_locus_point,
- NULL); }
+ &grecs_current_locus_point); }
/* End-of-line comments */
#.*\n { grecs_locus_point_advance_line(grecs_current_locus_point); }
#.* /* end-of-file comment */;
diff --git a/src/dhcpd-lex.l b/src/dhcpd-lex.l
index 6d05fc9..2e0b667 100644
--- a/src/dhcpd-lex.l
+++ b/src/dhcpd-lex.l
@@ -56,12 +56,10 @@ P [1-9][0-9]*
/* Line directive */
^[ \t]*#[ \t]*{P}[ \t]+\".*\".*\n { grecs_parse_line_directive_cpp(yytext,
&yylloc,
- &grecs_current_locus_point,
- NULL); }
+ &grecs_current_locus_point); }
^[ \t]*#[ \t]*line[ \t].*\n { grecs_parse_line_directive(yytext,
&yylloc,
- &grecs_current_locus_point,
- NULL); }
+ &grecs_current_locus_point); }
/* End-of-line comments */
#.*\n { grecs_locus_point_advance_line(grecs_current_locus_point); }
#.* /* end-of-file comment */;
diff --git a/src/grecs-gram.y b/src/grecs-gram.y
index b72b52c..826a4d4 100644
--- a/src/grecs-gram.y
+++ b/src/grecs-gram.y
@@ -241,10 +241,13 @@ opt_sc : /* empty */
%%
+int grecs_yyerror_suppress;
+
int
yyerror(char const *s)
{
- grecs_error(&yylloc, 0, "%s", s);
+ if (!grecs_yyerror_suppress)
+ grecs_error(&yylloc, 0, "%s", s);
return 0;
}
diff --git a/src/grecs-lex.l b/src/grecs-lex.l
index 27d3fd2..c09020e 100644
--- a/src/grecs-lex.l
+++ b/src/grecs-lex.l
@@ -1,6 +1,5 @@
/* grecs - Gray's Extensible Configuration System -*- c -*- */
%option nounput
-%option noinput
%top {
#ifdef HAVE_CONFIG_H
# include <config.h>
@@ -30,47 +29,19 @@
#include <ctype.h>
#include <stdlib.h>
#include <errno.h>
-
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <glob.h>
+#include <signal.h>
#include <wordsplit.h>
-static char *multiline_delimiter;
-static size_t multiline_delimiter_len;
-static int multiline_unescape; /* Unescape here-document contents */
-static int (*char_to_strip)(char); /* Strip matching characters of each
- here-document line */
-
+/*
+ * Locus tracking
+ */
struct grecs_locus_point grecs_current_locus_point; /* Input file location */
-/* Line correction. Equals to the number of #line directives inserted into
- the input by the preprocessor instance. The external preprocessor, if
- any, counts these as input lines and therefore the line numbers in *its*
- #line directives are offset by the value of XLINES.
-
- Uff, running two preprocessors is confusing...
-*/
-static size_t xlines;
-
-static void multiline_begin(char *);
-static void multiline_add(char *);
-static char *multiline_strip_tabs(char *text);
-static int ident(void);
-static int isemptystr(int off);
-static void qstring_locus_fixup(void);
-
-#define qstring() \
- ((grecs_parser_options & GRECS_OPTION_QUOTED_STRING_CONCAT) \
- ? QSTRING : STRING)
-
-#undef YY_INPUT
-#define YY_INPUT(buf,result,max_size) \
- do { \
- if (grecs_preprocessor) \
- result = fread(buf, 1, max_size, yyin); \
- else \
- result = grecs_preproc_fill_buffer(buf, max_size); \
- } while(0)
#define YY_USER_ACTION do { \
- if (YYSTATE == 0) { \
+ if (YYSTATE == INITIAL || YYSTATE == PP) { \
yylloc.beg = grecs_current_locus_point; \
yylloc.beg.col++; \
} \
@@ -78,9 +49,118 @@ static void qstring_locus_fixup(void);
yylloc.end = grecs_current_locus_point; \
} while (0);
+/*
+ * Redefine default error reporting function.
+ */
+#define YY_FATAL_ERROR(t) grecs_error(NULL, errno, "%s", t)
+
+/*
+ * Preprocessing and synclines.
+ */
+static int initial_state; /* Lexer initial state.
+ * Set to INITIAL when parsing the file, and to PP
+ * when generating preprocessed output.
+ */
+static int emit_syncline; /* If not null, emit syncline before next output
+ * line.
+ */
+static int beginning_of_line; /* True if output is at the beginning of line */
+
+/*
+ * Echo LEN bytes from TEXT to yyout.
+ */
+static void
+echo(char const *text, size_t len)
+{
+ if (len == 0) return;
+ /* Check if we should print a syncline */
+ if (beginning_of_line && emit_syncline) {
+ fprintf(yyout, "#line %lu \"%s\"\n",
+ (unsigned long)yylloc.end.line,
+ yylloc.end.file);
+ emit_syncline = 0;
+ }
+ /* Actually echo the text. */
+ fwrite(text, len, 1, yyout);
+ /* Determine the beginning of line status. */
+ beginning_of_line = text[len-1] == '\n';
+}
+/* Inform flex that it should use echo. */
+#define ECHO echo(yytext, yyleng)
+
+/*
+ * String concatenation support.
+ *
+ * String concatenation is a legacy feature of Grecs parser that instructs
+ * it to concatenate adjacent quoted strings. It is enabled if the
+ * GRECS_OPTION_QUOTED_STRING_CONCAT bit is set in parser options. This
+ * bit causes the lexer to qualify each double-quoted string as QSTRING.
+ */
+#define qstring() \
+ ((grecs_parser_options & GRECS_OPTION_QUOTED_STRING_CONCAT) \
+ ? QSTRING : STRING)
+
+/*
+ * Lexical tie-in variables.
+ */
+extern int grecs_yyerror_suppress;
+
+/*
+ * Here-document support.
+ */
+static char *multiline_delimiter; /* Here-document delimiter text. */
+static size_t multiline_delimiter_len; /* Its length. */
+static int multiline_unescape; /* Unescape here-document contents */
+static int (*char_to_strip)(char); /* Strips matching characters of each
+ here-document line */
+
+static void multiline_begin(char *);
+static void multiline_add(char *);
+static char *multiline_strip_tabs(char *text);
+static int isemptystr(int off);
+
+/*
+ * Forward declarations.
+ */
+static int ident(void);
+static void qstring_locus_fixup(void);
+static int parse_include(char *text);
+static int push_source(const char *name, int once);
+static int pop_source(void);
+static void ctx_abort(void);
+
+/*
+ * The report_diag should be called only when yytext begins with the following
+ * prefix:
+ * ^[ \t]*#[ \t]*.{LEN}
+ * It returns a pointer to yytext past the prefix.
+ */
+static char *
+report_diag(size_t len)
+{
+ char *p = strchr(yytext, '#');
+ p += strspn(p, " \t") + len;
+ p += strspn(p, " \t");
+ yytext[yyleng-1] = 0;
+ if (p[0] == '"' && yytext[yyleng-2] == '"') {
+ char *q, *r;
+
+ yytext[yyleng-2] = 0;
+ p++;
+ q = r = p;
+ do {
+ if (*r == '\\') {
+ int c = wordsplit_c_unquote_char(*++r);
+ *q = c ? c : *r;
+ } else
+ *q = *r++;
+ } while (*q++ == 0);
+ }
+ return p;
+}
%}
-
-%x COMMENT ML STR
+
+%x COMMENT ML STR PP
WS [ \t\f][ \t\f]*
ID [a-zA-Z_][a-zA-Z_0-9-]*
@@ -88,27 +168,69 @@ P [1-9][0-9]*
%%
/* C-style comments */
-"/*" BEGIN(COMMENT);
-<COMMENT>[^*\n]* /* eat anything that's not a '*' */
-<COMMENT>"*"+[^*/\n]* /* eat up '*'s not followed by '/'s */
-<COMMENT>\n grecs_locus_point_advance_line(grecs_current_locus_point);
-<COMMENT>"*"+"/" BEGIN(INITIAL);
+<INITIAL,PP>"/*" { BEGIN(COMMENT); beginning_of_line = 0; }
+<COMMENT>{
+[^*\n]* /* eat anything that's not a '*' */
+"*"+[^*/\n]* /* eat up '*'s not followed by '/'s */
+\n { grecs_locus_point_advance_line(grecs_current_locus_point);
+ emit_syncline = 1; }
+"*"+"/" { BEGIN(initial_state); }
+}
+
/* Line directive */
-^[ \t]*#[ \t]*{P}[ \t]+\".*\".*\n { grecs_parse_line_directive_cpp(yytext,
- &yylloc,
- &grecs_current_locus_point,
- &xlines); }
-^[ \t]*#[ \t]*line[ \t].*\n { grecs_parse_line_directive(yytext,
- &yylloc,
- &grecs_current_locus_point,
- &xlines); }
+^[ \t]*#[ \t]*{P}[ \t]+\".*\".*\n {
+ grecs_parse_line_directive_cpp(yytext,
+ &yylloc,
+ &grecs_current_locus_point);
+ }
+^[ \t]*#[ \t]*line[ \t].*\n {
+ grecs_parse_line_directive(yytext,
+ &yylloc,
+ &grecs_current_locus_point);
+ }
+
+<PP>{
+ /* Line directive */
+^[ \t]*#[ \t]*{P}[ \t]+\".*\".*\n {
+ grecs_parse_line_directive_cpp(yytext,
+ &yylloc,
+ &grecs_current_locus_point);
+ ECHO;
+ }
+^[ \t]*#[ \t]*line[ \t].*\n {
+ grecs_parse_line_directive(yytext,
+ &yylloc,
+ &grecs_current_locus_point);
+ ECHO;
+ }
+}
+
+<INITIAL,PP>{
+ /* Includes */
+^[ \t]*#[ \t]*include(_once)?.*/\n { parse_include(yytext); }
+ /* Error directives */
+^[ \t]*#[ \t]*error[ \t].*\n {
+ grecs_error(&yylloc, 0, "%s", report_diag(sizeof("error")));
+ }
+^[ \t]*#[ \t]*warning[ \t].*\n {
+ grecs_warning(&yylloc, 0, "%s", report_diag(sizeof("warning")));
+ }
+^[ \t]*#[ \t]*abend[ \t].*\n {
+ grecs_error(&yylloc, 0, "%s", report_diag(sizeof("abend")));
+ ctx_abort();
+ grecs_yyerror_suppress = 1;
+ return 0;
+ }
/* End-of-line comments */
-#.*\n { grecs_locus_point_advance_line(grecs_current_locus_point); }
-#.* /* end-of-file comment */;
-"//".*\n { grecs_locus_point_advance_line(grecs_current_locus_point); }
-"//".* /* end-of-file comment */;
+#.*\n { grecs_locus_point_advance_line(grecs_current_locus_point);
+ emit_syncline = 1; }
+#.* /* end-of-file comment */;
+"//".*\n { grecs_locus_point_advance_line(grecs_current_locus_point);
+ emit_syncline = 1; }
+"//".* /* end-of-file comment */;
+}
/* Identifiers */
-<INITIAL>{ID} return ident();
+{ID} return ident();
/* Strings */
[a-zA-Z0-9_\.\*/:@\[\]-]([a-zA-Z0-9_\./:@\[\]-][a-zA-Z0-9_\.\*/:@\[\]-]*)? {
grecs_line_begin();
@@ -132,17 +254,17 @@ P [1-9][0-9]*
grecs_line_acc_grow_unescape_last(yytext + 1,
yyleng - 1,
&yylloc); }
-<STR>\"[^\\"\n]*\\\n { grecs_line_acc_grow_unescape_last(yytext, yyleng,
- &yylloc);
- grecs_locus_point_advance_line(grecs_current_locus_point); }
-<STR>[^\\"\n]*\\. { grecs_line_acc_grow_unescape_last(yytext, yyleng,
- &yylloc); }
-<STR>[^\\"\n]*\" { BEGIN(INITIAL);
- if (yyleng > 1)
- grecs_line_add(yytext, yyleng - 1);
- yylval.string = grecs_line_finish();
- qstring_locus_fixup();
- return qstring(); }
+<STR>{
+\"[^\\"\n]*\\\n { grecs_line_acc_grow_unescape_last(yytext, yyleng, &yylloc);
+ grecs_locus_point_advance_line(grecs_current_locus_point); }
+[^\\"\n]*\\. { grecs_line_acc_grow_unescape_last(yytext, yyleng, &yylloc); }
+[^\\"\n]*\" { BEGIN(initial_state);
+ if (yyleng > 1)
+ grecs_line_add(yytext, yyleng - 1);
+ yylval.string = grecs_line_finish();
+ qstring_locus_fixup();
+ return qstring(); }
+}
/* Multiline strings */
"<<"(-" "?)?\\?{ID}[ \t]*#.*\n |
"<<"(-" "?)?\\?{ID}[ \t]*"//".*\n |
@@ -152,31 +274,35 @@ P [1-9][0-9]*
"<<"(-" "?)?\"{ID}\"[ \t]*\n {
BEGIN(ML);
multiline_begin(yytext+2); }
- /* Ignore m4 line statements */
-<ML>^"#line ".*\n {
+<ML>{
+ /* Ignore m4 line statements */
+^"#line ".*\n {
grecs_locus_point_advance_line(grecs_current_locus_point);
}
-<ML>.*\n { char *p = multiline_strip_tabs(yytext);
+.*\n { char *p = multiline_strip_tabs(yytext);
- if (!strncmp(p, multiline_delimiter, multiline_delimiter_len)
- && isemptystr(p + multiline_delimiter_len - yytext)) {
- grecs_free(multiline_delimiter);
- multiline_delimiter = NULL;
- BEGIN(INITIAL);
- yylval.string = grecs_line_finish();
-
- /* Update end pos */
- yylloc.end.line--;
- for (yylloc.end.col = 0,
- p = yylval.string + strlen(yylval.string) - 1;
- p > yylval.string && p[-1] != '\n';
- yylloc.end.col++, p--);
- if (yylloc.end.col == 0)
- yylloc.end.col = 1;
- return MSTRING;
- }
- grecs_locus_point_advance_line(grecs_current_locus_point);
- multiline_add(p); }
+ if (!strncmp(p, multiline_delimiter, multiline_delimiter_len)
+ && isemptystr(p + multiline_delimiter_len - yytext)) {
+ grecs_free(multiline_delimiter);
+ multiline_delimiter = NULL;
+ BEGIN(initial_state);
+ yylval.string = grecs_line_finish();
+
+ /* Update end pos */
+ yylloc.end.line--;
+ for (yylloc.end.col = 0,
+ p = yylval.string + strlen(yylval.string) - 1;
+ p > yylval.string && p[-1] != '\n';
+ yylloc.end.col++, p--);
+ if (yylloc.end.col == 0)
+ yylloc.end.col = 1;
+ return MSTRING;
+ }
+ grecs_locus_point_advance_line(grecs_current_locus_point);
+ multiline_add(p);
+ }
+}
+
{WS} ;
/* Other tokens */
\n { grecs_locus_point_advance_line(grecs_current_locus_point); }
@@ -188,58 +314,559 @@ P [1-9][0-9]*
grecs_error(&yylloc, 0,
_("stray character \\%03o"),
(unsigned char) yytext[0]); }
+
+<PP>{
+\n { grecs_locus_point_advance_line(grecs_current_locus_point);
+ ECHO; }
+}
+
%%
+/*
+ * Source inclusion support.
+ */
+static glob_t include_glob;
+static size_t include_pos;
+static int include_once;
-pid_t grecs_preproc_pid;
+static int
+isglob(const char *s)
+{
+ return s[strcspn(s, "*?[")] != 0;
+}
-int
-yywrap()
+static int
+parse_include(char *text)
{
- if (grecs_preprocessor) {
- grecs_preproc_extrn_shutdown(grecs_preproc_pid);
- fclose(yyin);
+ char *p;
+ int rc = 1;
+ int once;
+
+ p = strchr(text, '#');
+ p += strspn(p, " \t") + sizeof("include");
+ if (strncmp(p, "_once", 5) == 0) {
+ once = 1;
+ p += 5;
} else
- grecs_preproc_done();
- grecs_current_locus_point.file = NULL;
- return 1;
+ once = 0;
+ p += strspn(p, " \t");
+
+ if (!*p) {
+ grecs_error(&yylloc, 0, _("invalid include statement"));
+ return 1;
+ } else {
+ size_t len;
+ int allow_cwd;
+
+ len = strlen (p);
+
+ if (p[0] == '<' && p[len - 1] == '>') {
+ allow_cwd = 0;
+ p[len - 1] = 0;
+ p++;
+ } else if (p[0] == '"' && p[len - 1] == '"') {
+ allow_cwd = 1;
+ p[len - 1] = 0;
+ p++;
+ } else {
+ grecs_error(&yylloc, 0, _("invalid include statement"));
+ return 1;
+ }
+
+ if (isglob(p)) {
+ switch (glob(p, 0, NULL, &include_glob)) {
+ case 0:
+ include_pos = 0;
+ include_once = once;
+ break;
+ case GLOB_NOSPACE:
+ grecs_alloc_die();
+ case GLOB_NOMATCH:
+ break;
+ default:
+ grecs_error(&yylloc, 0, _("read error"));
+ }
+ p = NULL;
+ } else if (p[0] != '/') {
+ char *q = p;
+ p = grecs_find_include_file(q, allow_cwd);
+ if (!p)
+ grecs_error(&yylloc, 0,
+ _("%s: No such file or directory"),
+ q);
+ }
+ }
+
+ if (p)
+ rc = push_source(p, once);
+ else if (include_pos < include_glob.gl_pathc)
+ rc = push_source(include_glob.gl_pathv[include_pos++], once);
+ return rc;
+}
+
+/*
+ * Include search path.
+ */
+static struct grecs_list *grecs_usr_include_path;
+static struct grecs_list *grecs_std_include_path;
+
+size_t
+grecs_include_path_count(int flag)
+{
+ size_t count = 0;
+ if (flag & GRECS_STD_INCLUDE)
+ count += grecs_list_size(grecs_std_include_path);
+ if (flag & GRECS_USR_INCLUDE)
+ count += grecs_list_size(grecs_usr_include_path);
+ return count;
+}
+
+static int
+foreach_dir(struct grecs_list *list, int flag,
+ int (*fun)(int, const char *, void *), void *data)
+{
+ int rc = 0;
+ struct grecs_list_entry *ep;
+
+ for (ep = list->head; rc == 0 && ep; ep = ep->next)
+ rc = fun(flag, ep->data, data);
+ return rc;
}
int
-grecs_lex_begin(const char *name, int trace)
+grecs_foreach_include_dir(int flag, int (*fun)(int, const char *, void *),
+ void *data)
{
- yy_flex_debug = trace;
+ int rc = 0;
- grecs_line_acc_create();
+ if (flag & GRECS_STD_INCLUDE)
+ rc = foreach_dir(grecs_std_include_path, GRECS_STD_INCLUDE, fun, data);
+ if (rc == 0 && (flag & GRECS_USR_INCLUDE))
+ rc = foreach_dir(grecs_usr_include_path, GRECS_USR_INCLUDE, fun, data);
+ return rc;
+}
+
+void
+grecs_include_path_clear()
+{
+ if (grecs_usr_include_path)
+ grecs_list_clear(grecs_usr_include_path);
+ if (grecs_std_include_path)
+ grecs_list_clear(grecs_std_include_path);
+}
+
+static void
+incl_free(void *data)
+{
+ grecs_free(data);
+}
+
+void
+grecs_include_path_setup_v(char **dirs)
+{
+ if (!grecs_usr_include_path) {
+ grecs_usr_include_path = grecs_list_create();
+ grecs_usr_include_path->free_entry = incl_free;
+ }
+ grecs_std_include_path = grecs_list_create();
+ grecs_std_include_path->free_entry = incl_free;
+ if (dirs) {
+ int i;
+ for (i = 0; dirs[i]; i++)
+ /* FIXME: Element never freed */
+ grecs_list_append(grecs_std_include_path,
+ grecs_strdup(dirs[i]));
+ }
+}
+
+void
+grecs_include_path_setup(const char *dir, ...)
+{
+ const char *p;
+ char **argv = NULL;
+ size_t argc = 0;
+ size_t argi = 0;
+ va_list ap;
- if (grecs_preprocessor) {
- int fd;
+ va_start(ap, dir);
+ p = dir;
+ while (1) {
+ if (argi == argc) {
+ if (argc == 0)
+ argc = 16;
+ else
+ argc += 16;
+ argv = grecs_realloc(argv, argc * sizeof(argv[0]));
+ }
+ argv[argi++] = (char*) p;
+ if (!p)
+ break;
+ p = va_arg(ap, const char*);
+ }
+ grecs_include_path_setup_v(argv);
+ grecs_free(argv);
+ va_end(ap);
+}
+
+void
+grecs_preproc_add_include_dir(char *dir)
+{
+ if (!grecs_usr_include_path) {
+ grecs_usr_include_path = grecs_list_create();
+ grecs_usr_include_path->free_entry = incl_free;
+ }
+ grecs_list_append(grecs_usr_include_path, grecs_strdup(dir));
+}
+
+struct file_data {
+ const char *name;
+ size_t namelen;
+ char *buf;
+ size_t buflen;
+ int found;
+};
+
+static int
+pp_list_find(struct grecs_list *list, struct file_data *dptr)
+{
+ struct grecs_list_entry *ep;
+
+ if (!list)
+ return 0;
+ for (ep = list->head; !dptr->found && ep; ep = ep->next) {
+ const char *dir = ep->data;
+ size_t size = strlen (dir) + 1 + dptr->namelen + 1;
+ if (size > dptr->buflen) {
+ dptr->buflen = size;
+ dptr->buf = grecs_realloc(dptr->buf, dptr->buflen);
+ }
+ strcpy(dptr->buf, dir);
+ strcat(dptr->buf, "/");
+ strcat(dptr->buf, dptr->name);
+ dptr->found = access(dptr->buf, F_OK) == 0;
+ }
+ return dptr->found;
+}
+
+char *
+grecs_find_include_file(const char *name, int allow_cwd)
+{
+ static char *cwd = ".";
+ struct file_data fd;
+
+ fd.name = name;
+ fd.namelen = strlen(name);
+ fd.buf = NULL;
+ fd.buflen = 0;
+ fd.found = 0;
+
+ if (!grecs_usr_include_path)
+ grecs_include_path_setup(NULL);
+ if (allow_cwd) {
+ grecs_list_append(grecs_usr_include_path, cwd);
+ pp_list_find(grecs_usr_include_path, &fd);
+ grecs_list_remove_tail(grecs_usr_include_path);
+ } else
+ pp_list_find(grecs_usr_include_path, &fd);
+
+ if (!fd.found) {
+ pp_list_find(grecs_std_include_path, &fd);
+ if (!fd.found)
+ return NULL;
+ }
+ return fd.buf;
+}
+
+/*
+ * Input context support.
+ */
+
+struct input_file_ident {
+ ino_t i_node;
+ dev_t device;
+ pid_t pid;
+};
+
+struct buffer_ctx {
+ struct buffer_ctx *prev; /* Pointer to previous context */
+ struct grecs_locus locus;
+ struct grecs_locus_point point; /* Current input location */
+ size_t namelen; /* Length of the file name */
+ struct input_file_ident id;
+ FILE *infile;
+ YY_BUFFER_STATE state;
+};
+
+static struct buffer_ctx *context_stack;
+static struct input_file_ident input_file_ident;
+
+#define STAT_ID_EQ(st,id) ((id).i_node == (st).st_ino \
+ && (id).device == (st).st_dev)
+
+static struct buffer_ctx *
+ctx_lookup(struct stat *st)
+{
+ struct buffer_ctx *ctx;
+
+ if (!context_stack)
+ return NULL;
+
+ for (ctx = context_stack; ctx; ctx = ctx->prev)
+ if (STAT_ID_EQ(*st, ctx->id))
+ break;
+ return ctx;
+}
+
+static void
+ctx_abort(void)
+{
+ /* Delete current input buffer */
+ yy_delete_buffer(YY_CURRENT_BUFFER);
+
+ /* Clean-up context stack */
+ while (context_stack) {
+ struct buffer_ctx *prev = context_stack->prev;
+ if (context_stack->id.pid > 0)
+ kill(context_stack->id.pid, SIGKILL);
+ fclose(context_stack->infile);
+ grecs_free(context_stack);
+ context_stack = prev;
+ }
+}
+
+static struct grecs_symtab *incl_sources;
+
+/* Calculate the hash of a struct input_file_ident. */
+static unsigned
+incl_hasher(void *data, unsigned long n_buckets)
+{
+ const struct input_file_ident *id = data;
+ return (id->i_node + id->device) % n_buckets;
+}
+
+/* Compare two input_file_idents for equality. */
+static int
+incl_compare(void const *data1, void const *data2)
+{
+ const struct input_file_ident *id1 = data1;
+ const struct input_file_ident *id2 = data2;
+ return !(id1->device == id2->device && id1->i_node == id2->i_node);
+}
+
+static int
+incl_copy(void *dst, void *src)
+{
+ memcpy(dst, src, sizeof(struct input_file_ident));
+ return 0;
+}
+
+static int
+source_lookup(struct stat *st)
+{
+ struct input_file_ident key;
+ int install = 1;
+
+ if (!incl_sources) {
+ incl_sources = grecs_symtab_create(
+ sizeof(struct input_file_ident),
+ incl_hasher,
+ incl_compare,
+ incl_copy,
+ NULL,/*FIXME: alloc*/
+ NULL);
+ if (!incl_sources)
+ grecs_alloc_die();
+ }
- fd = open(name, O_RDONLY);
- if (fd == -1) {
- grecs_error(NULL, errno, _("Cannot open `%s'"), name);
+ key.i_node = st->st_ino;
+ key.device = st->st_dev;
+ if (!grecs_symtab_lookup_or_install(incl_sources, &key, &install))
+ grecs_alloc_die();
+ return !install;
+}
+
+static int
+push_source(const char *name, int once)
+{
+ FILE *fp;
+ pid_t pid;
+ struct buffer_ctx *ctx;
+ struct stat st;
+ int rc = stat(name, &st);
+
+ if (context_stack) {
+ if (rc) {
+ grecs_error(&yylloc, errno,
+ _("Cannot stat `%s'"), name);
return 1;
}
- close(fd);
- yyin = grecs_preproc_extrn_start(name, &grecs_preproc_pid);
- if (!yyin) {
- grecs_error(NULL, errno,
- _("Unable to start external preprocessor `%s'"),
- grecs_preprocessor);
+ if (grecs_current_locus_point.col &&
+ STAT_ID_EQ(st, input_file_ident)) {
+ grecs_error(&yylloc, 0, _("Recursive inclusion"));
return 1;
}
- } else
- return grecs_preproc_init(name);
+
+ if ((ctx = ctx_lookup(&st))) {
+ grecs_error(&yylloc, 0, _("Recursive inclusion"));
+ if (ctx->prev)
+ grecs_error(&ctx->prev->locus, 0,
+ _("`%s' already included here"),
+ name);
+ else
+ grecs_error(&yylloc, 0,
+ _("`%s' already included at top level"),
+ name);
+ return 1;
+ }
+ } else if (rc) {
+ grecs_error(NULL, errno, _("Cannot stat `%s'"), name);
+ return 1;
+ }
+
+ if (once && source_lookup(&st))
+ return -1;
+ if (grecs_preprocessor) {
+ if (access(name,R_OK)) {
+ grecs_error(context_stack ? &yylloc : NULL, errno,
+ _("Cannot open `%s'"), name);
+ return -1;
+ }
+ fp = grecs_preproc_extrn_start(name, &pid);
+ } else {
+ fp = fopen(name, "r");
+ pid = -1;
+ }
+ if (!fp) {
+ grecs_error(context_stack ? &yylloc : NULL, errno,
+ _("Cannot open `%s'"), name);
+ return 1;
+ }
+
+ /* Push current context */
+ if (yyin) {
+ ctx = grecs_malloc(sizeof(*ctx));
+ ctx->locus = yylloc;
+ ctx->point = grecs_current_locus_point;
+ ctx->id = input_file_ident;
+ ctx->infile = yyin;
+ ctx->state = YY_CURRENT_BUFFER;
+ ctx->prev = context_stack;
+ context_stack = ctx;
+ }
+
+ yyin = fp;
+ yy_switch_to_buffer(yy_create_buffer(yyin, YY_BUF_SIZE));
+
+ grecs_current_locus_point.file = grecs_install_text(name);
+ grecs_current_locus_point.line = 1;
+ grecs_current_locus_point.col = 0;
+
+ input_file_ident.i_node = st.st_ino;
+ input_file_ident.device = st.st_dev;
+ input_file_ident.pid = pid;
+
+ beginning_of_line = 1;
+
+ if (yy_flex_debug)
+ fprintf (stderr, "Processing file `%s'\n", name);
+
+ if (initial_state == PP)
+ emit_syncline = 1;
+
+ return 0;
+}
+
+static int
+pop_source(void)
+{
+ struct buffer_ctx *ctx;
+
+ fclose(yyin);
+ if (input_file_ident.pid > 0) {
+ if (grecs_preproc_extrn_shutdown(input_file_ident.pid)) {
+ /* Abort further input */
+ ctx_abort();
+
+ /* Suppress further errors */
+ grecs_yyerror_suppress = 1;
+
+ /* Indicate there is no more input */
+ return 1;
+ }
+ }
+
+ if (!context_stack) {
+ if (yy_flex_debug)
+ fprintf(stderr, "End of input\n");
+ yyin = NULL;
+ return 1;
+ }
+
+ /* Restore previous context */
+ yyin = context_stack->infile;
+ grecs_current_locus_point = context_stack->point;
+ input_file_ident = context_stack->id;
+
+ yy_delete_buffer(YY_CURRENT_BUFFER);
+ yy_switch_to_buffer(context_stack->state);
+
+ ctx = context_stack->prev;
+ grecs_free(context_stack);
+ context_stack = ctx;
+
+ while (include_pos < include_glob.gl_pathc) {
+ if (push_source(include_glob.gl_pathv[include_pos++],
+ include_once) == 0)
+ return 0;
+ }
+
+ if (include_glob.gl_pathc) {
+ globfree(&include_glob);
+ include_pos = include_glob.gl_pathc = 0;
+ }
+
+ if (yy_flex_debug)
+ fprintf(stderr, "Resuming file `%s' at line %lu\n",
+ grecs_current_locus_point.file,
+ (unsigned long) grecs_current_locus_point.line);
+
+ if (initial_state == PP) {
+ input();
+ grecs_locus_point_advance_line(grecs_current_locus_point);
+ emit_syncline = 1;
+ }
+
return 0;
}
+
+/*
+ * Internal lexer functions.
+ */
+int
+yywrap()
+{
+ return pop_source();
+}
+
+int
+grecs_lex_begin(const char *name, int trace)
+{
+ yy_flex_debug = trace;
+ initial_state = INITIAL;
+ grecs_line_acc_create();
+ return push_source(name, 1);
+}
void
grecs_lex_end(int err)
{
grecs_line_acc_free();
}
-
+
+/*
+ * Here-document functions.
+ */
static int
isemptystr(int off)
{
@@ -331,9 +958,12 @@ multiline_begin(char *p)
yylloc.beg = grecs_current_locus_point;
yylloc.beg.col++;
}
-
+
+/*
+ * Auxiliary lexer functions.
+ */
static int
-ident()
+ident(void)
{
char *p;
char *str;
@@ -350,7 +980,7 @@ ident()
}
static void
-qstring_locus_fixup()
+qstring_locus_fixup(void)
{
if (grecs_parser_options & GRECS_OPTION_ADJUST_STRING_LOCATIONS) {
yylloc.beg.col++;
@@ -365,27 +995,27 @@ grecs_value_ptr_from_static(grecs_value_t *input)
*ptr = *input;
return ptr;
}
-
+/*
+ * Syncline directive parsers.
+ */
+
static int
-assign_locus(struct grecs_locus_point *ploc,
- char *name, char *line, size_t *pxlines)
+assign_locus(struct grecs_locus_point *ploc, char *name, char *line)
{
char *p;
if (name) {
- if (pxlines && (!ploc->file || strcmp(name, ploc->file)))
- *pxlines = 0;
ploc->file = grecs_install_text(name);
}
- ploc->line = strtoul(line, &p, 10) - (pxlines ? *pxlines : 0);
+ ploc->line = strtoul(line, &p, 10);
ploc->col = 0;
return *p != 0;
}
void
grecs_parse_line_directive(char *text, grecs_locus_t *ploc,
- struct grecs_locus_point *ppoint, size_t *pxlines)
+ struct grecs_locus_point *ppoint)
{
int rc = 1;
struct wordsplit ws;
@@ -393,51 +1023,241 @@ grecs_parse_line_directive(char *text, grecs_locus_t *ploc,
if (wordsplit(text, &ws, WRDSF_DEFFLAGS))
grecs_error(ploc, 0, _("cannot parse #line line: %s"),
wordsplit_strerror(&ws));
- else {
- if (ws.ws_wordc == 2)
- rc = assign_locus(ppoint, NULL,
- ws.ws_wordv[1], pxlines);
- else if (ws.ws_wordc == 3)
- rc = assign_locus(ppoint, ws.ws_wordv[2],
- ws.ws_wordv[1], pxlines);
- else if (ws.ws_wordc == 4) {
- rc = assign_locus(ppoint, ws.ws_wordv[2],
- ws.ws_wordv[1], 0);
- if (pxlines && rc == 0) {
- char *p;
- unsigned long x = strtoul(ws.ws_wordv[3],
- &p, 10);
- rc = *p != 0;
- if (rc == 0)
- *pxlines = x;
- }
- } else
- grecs_error(ploc, 0, _("invalid #line statement"));
+ else if (ws.ws_wordc == 2)
+ rc = assign_locus(ppoint, NULL, ws.ws_wordv[1]);
+ else if (ws.ws_wordc >= 3)
+ rc = assign_locus(ppoint, ws.ws_wordv[2], ws.ws_wordv[1]);
+ else
+ grecs_error(ploc, 0, _("invalid #line statement"));
- if (rc)
- grecs_error(ploc, 0, _("malformed #line statement"));
- wordsplit_free(&ws);
- }
+ if (rc)
+ grecs_error(ploc, 0, _("malformed #line statement"));
+ wordsplit_free(&ws);
}
void
grecs_parse_line_directive_cpp(char *text, grecs_locus_t *ploc,
- struct grecs_locus_point *ppoint,
- size_t *pxlines)
+ struct grecs_locus_point *ppoint)
{
struct wordsplit ws;
- if (wordsplit(text, &ws, WRDSF_DEFFLAGS)) {
+ if (wordsplit(text, &ws, WRDSF_DEFFLAGS))
grecs_error(ploc, 0, _("cannot parse #line line: %s"),
wordsplit_strerror(&ws));
- return;
- } else if (ws.ws_wordc < 3)
+ else if (ws.ws_wordc < 3)
grecs_error(ploc, 0, _("invalid #line statement"));
- else {
- if (assign_locus(ppoint, ws.ws_wordv[2],
- ws.ws_wordv[1], pxlines))
- grecs_error(ploc, 0, _("malformed #line statement"));
- }
+ else if (assign_locus(ppoint, ws.ws_wordv[2], ws.ws_wordv[1]))
+ grecs_error(ploc, 0, _("malformed #line statement"));
wordsplit_free(&ws);
}
+
+/*
+ * External preprocessor support.
+ */
+const char *grecs_preprocessor = NULL; /* Preprocessor command */
+int grecs_log_to_stderr = 1; /* Log to stderr or to syslog? */
+void (*grecs_log_setup_hook) () = NULL;/* Log subsystem setup routine. */
+
+static int
+preproc_file(const char *file)
+{
+ size_t size = 0;
+ char *cmd = NULL;
+ char *setup_file;
+ char *argv[3];
+ int rc;
+
+ setup_file = grecs_find_include_file("pp-setup", 0);
+ if (setup_file)
+ rc = grecs_asprintf(&cmd, &size,
+ "%s \"%s\" \"%s\"",
+ grecs_preprocessor, setup_file, file);
+ else
+ rc = grecs_asprintf(&cmd, &size,
+ "%s \"%s\"", grecs_preprocessor, file);
+ if (rc)
+ grecs_alloc_die();
+ if ((argv[0] = getenv("SHELL")) == NULL)
+ argv[0] = "/bin/sh";
+ argv[1] = "-c";
+ argv[2] = cmd;
+ argv[3] = NULL;
+ execvp(argv[0], argv);
+ return -1;
+}
+FILE *
+grecs_preproc_extrn_start(const char *file_name, pid_t *ppid)
+{
+ int pout[2];
+ pid_t pid;
+ int i;
+ FILE *fp = NULL;
+ int rc;
+
+ if (pipe(pout)) {
+ grecs_error(NULL, errno, "pipe");
+ return NULL;
+ }
+ switch (pid = fork()) {
+ /* The child branch. */
+ case 0:
+ if (pout[1] != 1) {
+ if (dup2(pout[1], 1) == -1) {
+ grecs_error(NULL, errno, "dup2");
+ exit(127);
+ }
+ }
+
+ if (!grecs_log_to_stderr) {
+ int p[2];
+ char *buf = NULL;
+ size_t size = 0;
+ FILE *fp;
+
+ signal(SIGCHLD, SIG_DFL);
+ if (pipe(p)) {
+ grecs_error(NULL, errno, "pipe");
+ exit(127);
+ }
+ switch (pid = fork()) {
+ /* Grandchild */
+ case 0:
+ if (p[1] != 2 && dup2(p[1], 2) == -1) {
+ grecs_error(NULL, errno, "dup2");
+ exit(127);
+ }
+ close(p[0]);
+
+ rc = preproc_file(file_name);
+ break;
+
+ case -1:
+ /* Fork failed */
+ if (grecs_log_setup_hook)
+ grecs_log_setup_hook();
+ grecs_error(NULL, errno, _("Cannot run `%s'"),
+ grecs_preprocessor);
+ exit(127);
+
+ default:
+ /* Sub-master */
+ close (p[1]);
+ fp = fdopen(p[0], "r");
+ if (grecs_log_setup_hook)
+ grecs_log_setup_hook();
+ while (grecs_getline(&buf, &size, fp) > 0) {
+ size_t n = strlen(buf);
+ if (buf[n-1] == '\n')
+ buf[n-1] = 0;
+ grecs_error(NULL, 0, "%s", buf);
+ }
+ wait(&i);
+ if (WIFEXITED(i))
+ rc = WEXITSTATUS(i);
+ else
+ rc = 127;
+ }
+ } else {
+ rc = preproc_file(file_name);
+ }
+ if (rc == -1)
+ exit(127);
+ exit(rc);
+
+ case -1:
+ /* Fork failed */
+ grecs_error(NULL, errno, _("Cannot run `%s'"),
+ grecs_preprocessor);
+ break;
+
+ default:
+ close(pout[1]);
+ fp = fdopen(pout[0], "r");
+ break;
+ }
+ *ppid = pid;
+ return fp;
+}
+
+int
+grecs_preproc_extrn_shutdown(pid_t pid)
+{
+ int status;
+ waitpid(pid, &status, 0);
+ if (WIFEXITED(status)) {
+ if (WEXITSTATUS(status)) {
+ grecs_error(NULL, 0, _("%s exited with status %d"),
+ grecs_preprocessor, WEXITSTATUS(status));
+ return -1;
+ }
+ } else if (WIFSIGNALED(status)) {
+ grecs_error(NULL, 0, _("%s terminated on signal %d"),
+ grecs_preprocessor, WTERMSIG(status));
+ return -1;
+ } else {
+ grecs_error(NULL, 0, _("%s terminated abnormally (%d)"),
+ grecs_preprocessor, status);
+ return -1;
+ }
+ return 0;
+}
+
+int
+grecs_preprocess(char const *name, int trace)
+{
+ int rc;
+
+ yy_flex_debug = trace;
+ yyout = stdout;
+ grecs_line_acc_create();
+ initial_state = PP;
+ rc = push_source(name, 1);
+ if (rc == 0) {
+ BEGIN(PP);
+ rc = yylex();
+ grecs_lex_end(rc);
+ }
+ return rc;
+}
+
+/*
+ * Additional function.
+ *
+ * Not used by the lexer, but retained for backward compatibility.
+ */
+ssize_t
+grecs_getline(char **pbuf, size_t *psize, FILE *fp)
+{
+ char *buf = *pbuf;
+ size_t size = *psize;
+ ssize_t off = 0;
+
+ if (!buf) {
+ size = 1;
+ buf = grecs_malloc(size);
+ }
+
+ do {
+ if (off == size - 1) {
+ size_t nsize = 2 * size;
+ if (nsize < size)
+ grecs_alloc_die();
+ buf = grecs_realloc(buf, nsize);
+ size = nsize;
+ }
+ if (!fgets(buf + off, size - off, fp)) {
+ if (off == 0)
+ off = -1;
+ break;
+ }
+ off += strlen(buf + off);
+ } while (buf[off - 1] != '\n');
+
+ *pbuf = buf;
+ *psize = size;
+ return off;
+}
+
+
+
diff --git a/src/preproc.c b/src/preproc.c
deleted file mode 100644
index 8cb1413..0000000
--- a/src/preproc.c
+++ /dev/null
@@ -1,812 +0,0 @@
-/* grecs - Gray's Extensible Configuration System
- Copyright (C) 2007-2021 Sergey Poznyakoff
-
- Grecs is free software; you can redistribute it and/or modify it
- under the terms of the GNU General Public License as published by the
- Free Software Foundation; either version 3 of the License, or (at your
- option) any later version.
-
- Grecs is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along
- with Grecs. If not, see <http://www.gnu.org/licenses/>. */
-
-#ifdef HAVE_CONFIG_H
-# include <config.h>
-#endif
-#include <grecs.h>
-#include <wordsplit.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/wait.h>
-#include <ctype.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <string.h>
-#include <errno.h>
-#include <signal.h>
-#include <glob.h>
-#include <unistd.h>
-
-
-int grecs_log_to_stderr = 1;
-void (*grecs_log_setup_hook) () = NULL;
-
-struct input_file_ident {
- ino_t i_node;
- dev_t device;
-};
-
-struct buffer_ctx {
- struct buffer_ctx *prev; /* Pointer to previous context */
- grecs_locus_t locus; /* Current input location */
- size_t namelen; /* Length of the file name */
- size_t xlines; /* Number of #line directives output so far */
- struct input_file_ident id;
- FILE *infile;
-};
-
-extern int grecs_grecs__flex_debug;
-static struct buffer_ctx *context_stack;
-static char *linebufbase = NULL;
-static size_t linebufsize = 0;
-
-#define INFILE context_stack->infile
-#define LOCUS context_stack->locus
-#define POINT context_stack->locus.beg
-
-static char *linebuf;
-static size_t bufsize;
-static char *putback_buffer;
-static size_t putback_size;
-static size_t putback_max;
-static glob_t include_glob;
-static size_t include_pos;
-static int include_once;
-
-static int push_source (const char *name, int once);
-static int pop_source (void);
-static int parse_include (const char *text, int once);
-
-ssize_t
-grecs_getline(char **pbuf, size_t *psize, FILE *fp)
-{
- char *buf = *pbuf;
- size_t size = *psize;
- ssize_t off = 0;
-
- if (!buf) {
- size = 1;
- buf = grecs_malloc(size);
- }
-
- do {
- if (off == size - 1) {
- size_t nsize = 2 * size;
- if (nsize < size)
- grecs_alloc_die();
- buf = grecs_realloc(buf, nsize);
- size = nsize;
- }
- if (!fgets(buf + off, size - off, fp)) {
- if (off == 0)
- off = -1;
- break;
- }
- off += strlen(buf + off);
- } while (buf[off - 1] != '\n');
-
- *pbuf = buf;
- *psize = size;
- return off;
-}
-
-static void
-putback(const char *str)
-{
- size_t len;
-
- if (!*str)
- return;
- len = strlen(str) + 1;
- if (len > putback_max) {
- putback_max = len;
- putback_buffer = grecs_realloc(putback_buffer, putback_max);
- }
- strcpy(putback_buffer, str);
- putback_size = len - 1;
-}
-
-static void
-pp_line_stmt()
-{
- size_t ls_size;
- size_t pb_size;
-
- if (grecs_asprintf(&linebufbase, &linebufsize,
- "#line %lu \"%s\" %lu\n",
- (unsigned long) POINT.line,
- POINT.file, (unsigned long) context_stack->xlines))
- grecs_alloc_die();
-
- ls_size = strlen(linebufbase);
- pb_size = putback_size + ls_size + 1;
-
- if (pb_size > putback_max) {
- putback_max = pb_size;
- putback_buffer = grecs_realloc(putback_buffer, putback_max);
- }
-
- context_stack->xlines++;
- strcpy(putback_buffer + putback_size, linebufbase);
- putback_size += ls_size;
-}
-
-#define STRMATCH(p, len, s) (len >= sizeof(s) \
- && memcmp(p, s, sizeof(s) - 1) == 0 \
- && isspace(p[sizeof(s) - 1]))
-
-static int
-next_line()
-{
- ssize_t rc;
-
- do {
- if (putback_size) {
- if (putback_size + 1 > bufsize) {
- bufsize = putback_size + 1;
- linebuf = grecs_realloc(linebuf, bufsize);
- }
- strcpy(linebuf, putback_buffer);
- rc = putback_size;
- putback_size = 0;
- }
- else if (!context_stack)
- return 0;
- else
- rc = grecs_getline(&linebuf, &bufsize, INFILE);
- } while (rc == -1 && pop_source() == 0);
- return rc;
-}
-
-size_t
-grecs_preproc_fill_buffer(char *buf, size_t size)
-{
- size_t bufsize = size;
-
- while (next_line() > 0) {
- char *p;
- size_t len;
- int is_line = 0;
-
- for (p = linebuf; *p && isspace(*p); p++)
- ;
- if (*p == '#') {
- size_t l;
- for (p++; *p && isspace(*p); p++)
- ;
- l = strlen(p);
- if (STRMATCH(p, l, "include_once")) {
- if (parse_include(linebuf, 1))
- putback("/*include_once*/\n");
- continue;
- } else if (STRMATCH(p, l, "include")) {
- if (parse_include(linebuf, 0))
- putback("/*include*/\n");
- continue;
- } else if (STRMATCH(p, l, "line"))
- is_line = 1;
- }
-
- len = strlen(linebuf);
-
- if (len > size)
- len = size;
-
- memcpy(buf, linebuf, len);
- buf += len;
- size -= len;
-
- if (size == 0) {
- putback(linebuf + len);
- break;
- }
-
- if (!is_line && len > 0 && linebuf[len - 1] == '\n')
- POINT.line++;
- }
- return bufsize - size;
-}
-
-#define STAT_ID_EQ(st,id) ((id).i_node == (st).st_ino \
- && (id).device == (st).st_dev)
-
-static struct buffer_ctx *
-ctx_lookup(struct stat *st)
-{
- struct buffer_ctx *ctx;
-
- if (!context_stack)
- return NULL;
-
- for (ctx = context_stack->prev; ctx; ctx = ctx->prev)
- if (STAT_ID_EQ(*st, ctx->id))
- break;
- return ctx;
-}
-
-const char *grecs_preprocessor = NULL;
-static struct grecs_list *grecs_usr_include_path;
-static struct grecs_list *grecs_std_include_path;
-
-size_t
-grecs_include_path_count(int flag)
-{
- size_t count = 0;
- if (flag & GRECS_STD_INCLUDE)
- count += grecs_list_size(grecs_std_include_path);
- if (flag & GRECS_USR_INCLUDE)
- count += grecs_list_size(grecs_usr_include_path);
- return count;
-}
-
-static int
-foreach_dir(struct grecs_list *list, int flag,
- int (*fun)(int, const char *, void *), void *data)
-{
- int rc = 0;
- struct grecs_list_entry *ep;
-
- for (ep = list->head; rc == 0 && ep; ep = ep->next)
- rc = fun(flag, ep->data, data);
- return rc;
-}
-
-int
-grecs_foreach_include_dir(int flag, int (*fun)(int, const char *, void *),
- void *data)
-{
- int rc = 0;
-
- if (flag & GRECS_STD_INCLUDE)
- rc = foreach_dir(grecs_std_include_path, GRECS_STD_INCLUDE, fun, data);
- if (rc == 0 && (flag & GRECS_USR_INCLUDE))
- rc = foreach_dir(grecs_usr_include_path, GRECS_USR_INCLUDE, fun, data);
- return rc;
-}
-
-
-struct file_data {
- const char *name;
- size_t namelen;
- char *buf;
- size_t buflen;
- int found;
-};
-
-static int
-pp_list_find(struct grecs_list *list, struct file_data *dptr)
-{
- struct grecs_list_entry *ep;
-
- if (!list)
- return 0;
- for (ep = list->head; !dptr->found && ep; ep = ep->next) {
- const char *dir = ep->data;
- size_t size = strlen (dir) + 1 + dptr->namelen + 1;
- if (size > dptr->buflen) {
- dptr->buflen = size;
- dptr->buf = grecs_realloc(dptr->buf, dptr->buflen);
- }
- strcpy(dptr->buf, dir);
- strcat(dptr->buf, "/");
- strcat(dptr->buf, dptr->name);
- dptr->found = access(dptr->buf, F_OK) == 0;
- }
- return dptr->found;
-}
-
-static void
-incl_free(void *data)
-{
- grecs_free(data);
-}
-
-void
-grecs_include_path_clear()
-{
- if (grecs_usr_include_path)
- grecs_list_clear(grecs_usr_include_path);
- if (grecs_std_include_path)
- grecs_list_clear(grecs_std_include_path);
-}
-
-void
-grecs_include_path_setup_v(char **dirs)
-{
- if (!grecs_usr_include_path) {
- grecs_usr_include_path = grecs_list_create();
- grecs_usr_include_path->free_entry = incl_free;
- }
- grecs_std_include_path = grecs_list_create();
- grecs_std_include_path->free_entry = incl_free;
- if (dirs) {
- int i;
- for (i = 0; dirs[i]; i++)
- /* FIXME: Element never freed */
- grecs_list_append(grecs_std_include_path,
- grecs_strdup(dirs[i]));
- }
-}
-
-void
-grecs_include_path_setup(const char *dir, ...)
-{
- const char *p;
- char **argv = NULL;
- size_t argc = 0;
- size_t argi = 0;
- va_list ap;
-
- va_start(ap, dir);
- p = dir;
- while (1) {
- if (argi == argc) {
- if (argc == 0)
- argc = 16;
- else
- argc += 16;
- argv = grecs_realloc(argv, argc * sizeof(argv[0]));
- }
- argv[argi++] = (char*) p;
- if (!p)
- break;
- p = va_arg(ap, const char*);
- }
- grecs_include_path_setup_v(argv);
- grecs_free(argv);
- va_end(ap);
-}
-
-void
-grecs_preproc_add_include_dir(char *dir)
-{
- if (!grecs_usr_include_path) {
- grecs_usr_include_path = grecs_list_create();
- grecs_usr_include_path->free_entry = incl_free;
- }
- grecs_list_append(grecs_usr_include_path, grecs_strdup(dir));
-}
-
-static struct grecs_symtab *incl_sources;
-
-/* Calculate the hash of a struct input_file_ident. */
-static unsigned
-incl_hasher(void *data, unsigned long n_buckets)
-{
- const struct input_file_ident *id = data;
- return (id->i_node + id->device) % n_buckets;
-}
-
-/* Compare two input_file_idents for equality. */
-static int
-incl_compare(void const *data1, void const *data2)
-{
- const struct input_file_ident *id1 = data1;
- const struct input_file_ident *id2 = data2;
- return !(id1->device == id2->device && id1->i_node == id2->i_node);
-}
-
-static int
-incl_copy(void *dst, void *src)
-{
- memcpy(dst, src, sizeof(struct input_file_ident));
- return 0;
-}
-
-static int
-source_lookup(struct stat *st)
-{
- struct input_file_ident key;
- int install = 1;
-
- if (!incl_sources) {
- incl_sources = grecs_symtab_create(
- sizeof(struct input_file_ident),
- incl_hasher,
- incl_compare,
- incl_copy,
- NULL,/*FIXME: alloc*/
- NULL);
- if (!incl_sources)
- grecs_alloc_die();
- }
-
- key.i_node = st->st_ino;
- key.device = st->st_dev;
- if (!grecs_symtab_lookup_or_install(incl_sources, &key, &install))
- grecs_alloc_die();
- return !install;
-}
-
-
-static int
-push_source(const char *name, int once)
-{
- FILE *fp;
- struct buffer_ctx *ctx;
- struct stat st;
- int rc = stat(name, &st);
-
- if (context_stack) {
- if (rc) {
- grecs_error(&LOCUS, errno,
- _("Cannot stat `%s'"), name);
- return 1;
- }
-
- if (POINT.file && STAT_ID_EQ(st, context_stack->id)) {
- grecs_error(&LOCUS, 0, _("Recursive inclusion"));
- return 1;
- }
-
- if ((ctx = ctx_lookup(&st))) {
- grecs_error(&LOCUS, 0, _("Recursive inclusion"));
- if (ctx->prev)
- grecs_error(&ctx->prev->locus, 0,
- _("`%s' already included here"),
- name);
- else
- grecs_error(&LOCUS, 0,
- _("`%s' already included at top level"),
- name);
- return 1;
- }
- } else if (rc) {
- grecs_error(NULL, errno, _("Cannot stat `%s'"), name);
- return 1;
- }
-
- if (once && source_lookup(&st))
- return -1;
-
- fp = fopen(name, "r");
- if (!fp) {
- grecs_error(context_stack ? &LOCUS : NULL, errno,
- _("Cannot open `%s'"), name);
- return 1;
- }
-
- /* Push current context */
- ctx = grecs_malloc(sizeof(*ctx));
- ctx->locus.beg.file = grecs_install_text(name);
- ctx->locus.beg.line = 1;
- ctx->locus.beg.col = 0;
- ctx->locus.end.file = NULL;
- ctx->locus.end.line = ctx->locus.end.col = 0;
- ctx->xlines = 0;
- ctx->namelen = strlen(ctx->locus.beg.file);
- ctx->id.i_node = st.st_ino;
- ctx->id.device = st.st_dev;
- ctx->infile = fp;
- ctx->prev = context_stack;
- context_stack = ctx;
-
- if (grecs_grecs__flex_debug)
- fprintf (stderr, "Processing file `%s'\n", name);
-
- pp_line_stmt();
-
- return 0;
-}
-
-static int
-pop_source()
-{
- struct buffer_ctx *ctx;
-
- if (!context_stack)
- return 1;
-
- fclose(INFILE);
-
- /* Restore previous context */
- ctx = context_stack->prev;
- grecs_free(context_stack);
- context_stack = ctx;
-
- if (include_pos < include_glob.gl_pathc) {
- push_source(include_glob.gl_pathv[include_pos++], include_once);
- return 0;
- } else if (include_glob.gl_pathc) {
- globfree(&include_glob);
- include_pos = include_glob.gl_pathc = 0;
- }
-
- if (!context_stack) {
- if (grecs_grecs__flex_debug)
- fprintf(stderr, "End of input\n");
- return 1;
- }
-
- POINT.line++;
-
- if (grecs_grecs__flex_debug)
- fprintf(stderr, "Resuming file `%s' at line %lu\n",
- POINT.file, (unsigned long) POINT.line);
-
- pp_line_stmt();
-
- return 0;
-}
-
-char *
-grecs_find_include_file(const char *name, int allow_cwd)
-{
- static char *cwd = ".";
- struct file_data fd;
-
- fd.name = name;
- fd.namelen = strlen(name);
- fd.buf = NULL;
- fd.buflen = 0;
- fd.found = 0;
-
- if (!grecs_usr_include_path)
- grecs_include_path_setup(NULL);
- if (allow_cwd) {
- grecs_list_append(grecs_usr_include_path, cwd);
- pp_list_find(grecs_usr_include_path, &fd);
- grecs_list_remove_tail(grecs_usr_include_path);
- } else
- pp_list_find(grecs_usr_include_path, &fd);
-
- if (!fd.found) {
- pp_list_find(grecs_std_include_path, &fd);
- if (!fd.found)
- return NULL;
- }
- return fd.buf;
-}
-
-static int
-isglob(const char *s)
-{
- for (; *s; s++) {
- if (strchr("*?[", *s))
- return 1;
- }
- return 0;
-}
-
-static int
-parse_include(const char *text, int once)
-{
- struct wordsplit ws;
- char *tmp = NULL;
- char *p = NULL;
- int rc = 1;
-
- if (wordsplit(text, &ws, WRDSF_DEFFLAGS))
- grecs_error(&LOCUS, 0, _("Cannot parse include line"));
- else if (ws.ws_wordc != 2) {
- wordsplit_free(&ws);
- grecs_error(&LOCUS, 0, _("invalid include statement"));
- } else {
- size_t len;
- int allow_cwd;
-
- p = ws.ws_wordv[1];
- len = strlen (p);
-
- if (p[0] == '<' && p[len - 1] == '>') {
- allow_cwd = 0;
- p[len - 1] = 0;
- p++;
- } else
- allow_cwd = 1;
-
- if (isglob(p)) {
- switch (glob(p, 0, NULL, &include_glob)) {
- case 0:
- include_pos = 0;
- include_once = once;
- break;
- case GLOB_NOSPACE:
- grecs_alloc_die();
- case GLOB_NOMATCH:
- break;
- default:
- grecs_error(&LOCUS, 0, _("read error"));
- }
- p = NULL;
- } else if (p[0] != '/') {
- char *q = p;
- p = grecs_find_include_file(q, allow_cwd);
- if (!p)
- grecs_error(&LOCUS, 0,
- _("%s: No such file or directory"),
- q);
- }
- }
-
- if (p)
- rc = push_source(p, once);
- else if (include_pos < include_glob.gl_pathc)
- rc = push_source(include_glob.gl_pathv[include_pos++], once);
-
- grecs_free(tmp);
- wordsplit_free(&ws);
- return rc;
-}
-
-int
-grecs_preproc_init(const char *name)
-{
- return push_source(name, 0);
-}
-
-void
-grecs_preproc_done()
-{
- grecs_symtab_free(incl_sources);
- incl_sources = NULL;
-
- grecs_free(linebuf);
- linebuf = NULL;
- bufsize = 0;
-
- grecs_free(putback_buffer);
- putback_buffer = NULL;
- putback_size = putback_max = 0;
-
- free(linebufbase); /* Allocated via standard malloc/realloc */
- linebufbase = NULL;
- linebufsize = 0;
-}
-
-int
-grecs_preproc_run(const char *config_file, const char *extpp)
-{
- size_t i;
- char buffer[512];
-
- if (grecs_preproc_init(config_file))
- return 1;
- if (extpp) {
- FILE *outfile;
- char *setup_file;
- char *cmd = NULL;
-
- setup_file = grecs_find_include_file("pp-setup", 0);
- if (setup_file) {
- size_t size = 0;
- if (grecs_asprintf(&cmd, &size,
- "%s %s -", extpp, setup_file))
- grecs_alloc_die();
- grecs_free(setup_file);
- } else
- cmd = grecs_strdup(extpp);
- /*FIXME_DEBUG_F1 (2, "Running preprocessor: `%s'", cmd);*/
- outfile = popen(cmd, "w");
- if (!outfile) {
- grecs_error(NULL, errno,
- _("Unable to start external preprocessor `%s'"),
- cmd);
- grecs_free(cmd);
- return 1;
- }
-
- while ((i = grecs_preproc_fill_buffer(buffer, sizeof buffer)))
- fwrite(buffer, 1, i, outfile);
- pclose(outfile);
- grecs_free(cmd);
- } else {
- while ((i = grecs_preproc_fill_buffer(buffer, sizeof buffer)))
- fwrite(buffer, 1, i, stdout);
- }
- grecs_preproc_done();
- return 0;
-}
-
-FILE *
-grecs_preproc_extrn_start(const char *file_name, pid_t *ppid)
-{
- int pout[2];
- pid_t pid;
- int i;
- FILE *fp = NULL;
-
- /*FIXME_DEBUG_F1 (2, "Running preprocessor: `%s'", ppcmd);*/
-
- if (pipe(pout)) {
- grecs_error(NULL, errno, "pipe");
- return NULL;
- }
- switch (pid = fork()) {
- /* The child branch. */
- case 0:
- if (pout[1] != 1) {
- if (dup2(pout[1], 1) == -1) {
- grecs_error(NULL, errno, "dup2");
- exit(127);
- }
- }
-
- /* Close unneeded descripitors */
- for (i = getdtablesize(); i > 2; i--)
- close(i);
-
- if (!grecs_log_to_stderr) {
- int p[2];
- char *buf = NULL;
- size_t size = 0;
- FILE *fp;
-
- signal(SIGCHLD, SIG_DFL);
- if (pipe(p)) {
- grecs_error(NULL, errno, "pipe");
- exit(127);
- }
- switch (pid = fork()) {
- /* Grandchild */
- case 0:
- if (p[1] != 2 && dup2(p[1], 2) == -1) {
- grecs_error(NULL, errno, "dup2");
- exit(127);
- }
- close(p[0]);
-
- if (grecs_preproc_run(file_name,
- grecs_preprocessor))
- exit(127);
- exit(0);
-
- case -1:
- /* Fork failed */
- if (grecs_log_setup_hook)
- grecs_log_setup_hook();
- grecs_error(NULL, errno, _("Cannot run `%s'"),
- grecs_preprocessor);
- exit(127);
-
- default:
- /* Sub-master */
- close (p[1]);
- fp = fdopen(p[0], "r");
- if (grecs_log_setup_hook)
- grecs_log_setup_hook();
- while (grecs_getline(&buf, &size, fp) > 0)
- grecs_error(NULL, 0, "%s", buf);
- }
- } else {
- grecs_preproc_run(file_name, grecs_preprocessor);
- }
- exit (0);
-
- case -1:
- /* Fork failed */
- grecs_error(NULL, errno, _("Cannot run `%s'"),
- grecs_preprocessor);
- break;
-
- default:
- close(pout[1]);
- fp = fdopen(pout[0], "r");
- break;
- }
- *ppid = pid;
- return fp;
-}
-
-void
-grecs_preproc_extrn_shutdown(pid_t pid)
-{
- int status;
- waitpid(pid, &status, 0);
-}
-
diff --git a/tests/.gitignore b/tests/.gitignore
index 8ddd703..b30f26f 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -1,6 +1,7 @@
atconfig
atlocal
distck
+failpp
gcfenum
gcffmt
gcfpeek
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 6d393e1..3ef4436 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -81,6 +81,7 @@ TESTSUITE_AT = \
bind00.at\
cfhelp.at\
empty.at\
+ error.at\
format00.at\
format01.at\
format02.at\
@@ -92,10 +93,12 @@ TESTSUITE_AT = \
glob05.at\
grecs00.at\
enum.at\
+ epp.at\
incl00.at\
incl01.at\
incl02.at\
incl03.at\
+ ipp.at\
join.at\
json00.at\
json01.at\
@@ -114,6 +117,7 @@ TESTSUITE_AT = \
peek01.at\
peek02.at\
peek03.at\
+ ppexit.at\
reduce00.at\
reduce01.at\
reduce02.at\
@@ -125,6 +129,7 @@ TESTSUITE_AT = \
strcat.at\
testsuite.at\
vercmp.at\
+ warning.at\
@GRECS_DISTCK_AT@
TESTSUITE = $(srcdir)/testsuite
@@ -153,8 +158,10 @@ noinst_PROGRAMS = \
gcffmt\
gcfenum\
gcfpeek\
+ gcfpp\
gcfset\
- gcfver
+ gcfver\
+ failpp
if GRECS_COND_JSON
noinst_PROGRAMS += json
diff --git a/tests/abend.at b/tests/abend.at
new file mode 100644
index 0000000..17c6157
--- /dev/null
+++ b/tests/abend.at
@@ -0,0 +1,45 @@
+# This file is part of grecs -*- Autotest -*-
+# Copyright (C) 2014-2021 Sergey Poznyakoff
+#
+# Grecs is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# Grecs is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Grecs. If not, see <http://www.gnu.org/licenses/>.
+
+AT_SETUP([#abend])
+AT_KEYWORDS([diag abend])
+
+AT_CHECK([
+AT_DATA([test.cf],[before 1;
+#abend "everything is FOOBAR"
+after 1;
+])
+gcffmt test.cf
+],
+[1],
+[],
+[test.cf:2.1-30: everything is FOOBAR
+])
+
+AT_CHECK([
+AT_DATA([test.cf],[before 1;
+bad
+#abend "everything is FOOBAR"
+after 1;
+])
+gcffmt test.cf
+],
+[1],
+[],
+[test.cf:3.1-30: everything is FOOBAR
+])
+
+AT_CLEANUP
diff --git a/tests/epp.at b/tests/epp.at
new file mode 100644
index 0000000..2bf7e75
--- /dev/null
+++ b/tests/epp.at
@@ -0,0 +1,78 @@
+# This file is part of grecs -*- Autotest -*-
+# Copyright (C) 2021 Sergey Poznyakoff
+#
+# Grecs is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# Grecs is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Grecs. If not, see <http://www.gnu.org/licenses/>.
+AT_SETUP([External preprocessor])
+
+AT_CHECK([m4 -s </dev/null 2>/dev/null || AT_SKIP_TEST])
+
+AT_CHECK([
+AT_DATA([input],
+[define(`X',`cupidatat non proident, sunt in culpa
+qui officia deserunt mollit')dnl
+Duis aute irure dolor X anim id est laborum.
+])
+gcfpp -pm4 input
+],
+[0],
+[#line 1 "input"
+Duis aute irure dolor cupidatat non proident, sunt in culpa
+qui officia deserunt mollit anim id est laborum.
+])
+
+AT_CHECK([
+AT_DATA([input],
+[define(`FWD',`forward {
+ address "$1";
+ dist $2;
+}')dnl
+id 1;
+FWD(foo, 10)
+test "end";
+])
+gcffmt -preproc=m4 input
+],
+[0],
+[.id: "1"
+.forward.address: "foo"
+.forward.dist: "10"
+.test: "end"
+])
+
+#
+# Check the pp-setup file
+#
+AT_CHECK([
+mkdir inc
+AT_DATA([inc/pp-setup],
+[define(`FWD',`forward {
+ address "$1";
+ dist $2;
+}')
+])
+
+AT_DATA([input],
+[foo X;
+FWD(bar,15)
+])
+
+gcffmt -preproc=m4 -I$(pwd)/inc input
+],
+[0],
+[.foo: "X"
+.forward.address: "bar"
+.forward.dist: "15"
+])
+
+AT_CLEANUP
diff --git a/tests/error.at b/tests/error.at
new file mode 100644
index 0000000..846ec6e
--- /dev/null
+++ b/tests/error.at
@@ -0,0 +1,32 @@
+# This file is part of grecs -*- Autotest -*-
+# Copyright (C) 2014-2021 Sergey Poznyakoff
+#
+# Grecs is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# Grecs is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Grecs. If not, see <http://www.gnu.org/licenses/>.
+
+AT_SETUP([#error])
+AT_KEYWORDS([diag error])
+
+AT_CHECK([
+AT_DATA([test.cf],[before 1;
+#error "everything is FOOBAR"
+after 1;
+])
+gcffmt test.cf
+],
+[1],
+[],
+[test.cf:2.1-30: everything is FOOBAR
+])
+
+AT_CLEANUP
diff --git a/tests/failpp.c b/tests/failpp.c
new file mode 100644
index 0000000..819e8ee
--- /dev/null
+++ b/tests/failpp.c
@@ -0,0 +1,57 @@
+#include <stdio.h>
+#include <ctype.h>
+
+static char errmark[] = "stop+";
+#define ERRLEN (sizeof(errmark)-1)
+
+int
+main(int argc, char **argv)
+{
+ int c;
+ int i = 0;
+ int bol = 1;
+ int ex = 0;
+ FILE *input;
+
+ if (argc > 1) {
+ input = fopen(argv[1], "r");
+ if (!input) {
+ perror("input");
+ return 1;
+ }
+ }
+
+ while ((c = fgetc(input)) != EOF) {
+ if (bol) {
+ if (i < ERRLEN) {
+ if (c != errmark[i]) {
+ if (i)
+ fwrite(errmark, i, 1, stdout);
+ fputc(c, stdout);
+ bol = i = 0;
+ }
+ } else if (i == ERRLEN) {
+ if (!isdigit(c)) {
+ fwrite(errmark, i, 1, stdout);
+ fputc(c, stdout);
+ bol = i = 0;
+ }
+ ex = c - '0';
+ } else if (isdigit(c)) {
+ ex = ex * 10 + c - '0';
+ } else {
+ return ex;
+ }
+ i++;
+ } else {
+ fputc(c, stdout);
+ if (c == '\n') {
+ bol = 1;
+ i = 0;
+ }
+ }
+ }
+ return 0;
+}
+
+
diff --git a/tests/gcffmt.c b/tests/gcffmt.c
index 1bc4cd9..4695712 100644
--- a/tests/gcffmt.c
+++ b/tests/gcffmt.c
@@ -133,6 +133,8 @@ main(int argc, char **argv)
grecs_parser_options |= GRECS_OPTION_QUOTED_STRING_CONCAT;
else if (strcmp(arg, "-stradj") == 0)
grecs_parser_options |= GRECS_OPTION_ADJUST_STRING_LOCATIONS;
+ else if (strncmp(arg, "-preproc=", 9) == 0)
+ grecs_preprocessor = arg + 9;
else if (arg[0] == '-')
usage(progname, stderr, 1);
else {
diff --git a/tests/gcfpp.c b/tests/gcfpp.c
new file mode 100644
index 0000000..4ece893
--- /dev/null
+++ b/tests/gcfpp.c
@@ -0,0 +1,37 @@
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include <stdlib.h>
+#include <unistd.h>
+#include "grecs.h"
+
+int
+main(int argc, char **argv)
+{
+ int trace = 0;
+ int i;
+
+ while ((i = getopt(argc, argv, "I:p:t")) != EOF) {
+ switch (i) {
+ case 'I':
+ grecs_preproc_add_include_dir(optarg);
+ break;
+
+ case 'p':
+ grecs_preprocessor = optarg;
+ break;
+
+ case 't':
+ trace = 1;
+ break;
+
+ default:
+ return 1;
+ }
+ }
+
+ for (i = optind; i < argc; i++) {
+ grecs_preprocess(argv[i], trace);
+ }
+ return 0;
+}
diff --git a/tests/incl01.at b/tests/incl01.at
index 2d2006d..e57e36f 100644
--- a/tests/incl01.at
+++ b/tests/incl01.at
@@ -34,8 +34,8 @@ gcffmt ./test.cf
],
[1],
[],
-[./b.inc:1: Recursive inclusion
-./test.cf:2: `./a.inc' already included here
+[./b.inc:1.1-16: Recursive inclusion
+./test.cf:2.1-16: `./a.inc' already included here
])
AT_CLEANUP
diff --git a/tests/ipp.at b/tests/ipp.at
new file mode 100644
index 0000000..825674f
--- /dev/null
+++ b/tests/ipp.at
@@ -0,0 +1,168 @@
+# This file is part of grecs -*- Autotest -*-
+# Copyright (C) 2021 Sergey Poznyakoff
+#
+# Grecs is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# Grecs is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Grecs. If not, see <http://www.gnu.org/licenses/>.
+AT_SETUP([Internal preprocessor])
+
+AT_CHECK([
+AT_DATA([input],
+[begin
+// Inline comment
+# Another online comment
+statement
+/* Multiline
+ comments
+ span several
+ lines */
+end
+inline /* 1 */ C/* 2 */style /* text */comments
+])
+gcfpp input
+],
+[0],
+[#line 1 "input"
+begin
+#line 4 "input"
+statement
+
+#line 9 "input"
+end
+inline Cstyle comments
+])
+
+AT_CHECK([
+AT_DATA([input],
+[first
+#line 20 "i.cf"
+/*
+-
+*/
+line
+])
+gcfpp input
+],
+[0],
+[#line 1 "input"
+first
+#line 20 "i.cf"
+
+#line 23 "i.cf"
+line
+])
+
+AT_CHECK([
+AT_DATA([input],
+[#abend "Failed"
+tez
+])
+gcfpp input
+],
+[0],
+[],
+[input:1.1-16: Failed
+])
+
+#
+#
+#
+AT_CHECK([
+AT_DATA([input],
+[Ut enim ad minim
+#include "a"
+veniam, quis
+adipisci
+#include "b"
+commodo consequat
+eo finem
+])
+AT_DATA([a],[exercitation
+ullamco
+])
+AT_DATA([b],[labori nisi
+ut aliquip ex
+ea
+])
+gcfpp input
+],
+[0],
+[#line 1 "input"
+Ut enim ad minim
+#line 1 "./a"
+exercitation
+ullamco
+#line 3 "input"
+veniam, quis
+adipisci
+#line 1 "./b"
+labori nisi
+ut aliquip ex
+ea
+#line 6 "input"
+commodo consequat
+eo finem
+])
+
+#
+#
+#
+AT_CHECK([
+AT_DATA([a.in],
+[Duis aute irure
+#include_once "a.d/*.in"
+est laborum.
+])
+
+mkdir a.d
+AT_DATA([a.d/b.in],
+[dolor in
+# reprehenderit
+# in
+voluptate
+])
+
+AT_DATA([a.d/c.in],
+[velit esse
+cupidatat non proident,
+])
+
+AT_DATA([a.d/d.in],
+[sunt in culpa
+#warning "values of B give rise to dom"
+qui officia
+deserunt
+])
+
+gcfpp a.in
+],
+[0],
+[#line 1 "a.in"
+Duis aute irure
+#line 1 "a.d/b.in"
+dolor in
+#line 4 "a.d/b.in"
+voluptate
+#line 1 "a.d/c.in"
+velit esse
+cupidatat non proident,
+#line 1 "a.d/d.in"
+sunt in culpa
+qui officia
+deserunt
+#line 3 "a.in"
+est laborum.
+],
+[a.d/d.in:2.1-40: warning: values of B give rise to dom
+])
+
+AT_CLEANUP
diff --git a/tests/ppexit.at b/tests/ppexit.at
new file mode 100644
index 0000000..73f66c7
--- /dev/null
+++ b/tests/ppexit.at
@@ -0,0 +1,64 @@
+# This file is part of grecs -*- Autotest -*-
+# Copyright (C) 2014-2021 Sergey Poznyakoff
+#
+# Grecs is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# Grecs is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Grecs. If not, see <http://www.gnu.org/licenses/>.
+
+AT_SETUP([Exit status])
+
+AT_CHECK([
+AT_DATA([input],[string "text";
+comp {
+ int 10;
+ string "foo";
+}
+])
+gcffmt -preproc=failpp input
+],
+[0],
+[.string: "text"
+.comp.int: "10"
+.comp.string: "foo"
+])
+
+AT_CHECK([
+AT_DATA([input],[string "text";
+stop+22
+comp {
+ int 10;
+ string "foo";
+}
+])
+gcffmt -preproc=failpp input
+],
+[1],
+[],
+[failpp exited with status 22
+])
+
+AT_CHECK([
+AT_DATA([input],[string "text";
+comp {
+stop+22
+ int 10;
+ string "foo";
+}
+])
+gcffmt -preproc=failpp input
+],
+[1],
+[],
+[failpp exited with status 22
+])
+
+AT_CLEANUP
diff --git a/tests/testsuite.at b/tests/testsuite.at
index c302f2b..bb65d8d 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -89,6 +89,16 @@ m4_include([incl01.at])
m4_include([incl02.at])
m4_include([incl03.at])
+AT_BANNER([Preprocessor])
+m4_include([ipp.at])
+m4_include([epp.at])
+m4_include([ppexit.at])
+
+AT_BANNER([Forced diagnostics])
+m4_include([error.at])
+m4_include([warning.at])
+m4_include([abend.at])
+
m4_ifdef([ENABLE_BIND_PARSER],[
AT_BANNER([BIND Parser])
m4_include([parser-bind.at])
@@ -132,5 +142,4 @@ AT_BANNER(JSON)
m4_include([json00.at])
m4_include([json01.at])
])
-
# End of testsuite.at
diff --git a/tests/warning.at b/tests/warning.at
new file mode 100644
index 0000000..25a31a5
--- /dev/null
+++ b/tests/warning.at
@@ -0,0 +1,34 @@
+# This file is part of grecs -*- Autotest -*-
+# Copyright (C) 2014-2021 Sergey Poznyakoff
+#
+# Grecs is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# Grecs is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Grecs. If not, see <http://www.gnu.org/licenses/>.
+
+AT_SETUP([#warning])
+AT_KEYWORDS([diag warning])
+
+AT_CHECK([
+AT_DATA([test.cf],[before 1;
+#warning "everything is FOOBAR"
+after 1;
+])
+gcffmt test.cf
+],
+[0],
+[.before: "1"
+.after: "1"
+],
+[test.cf:2.1-32: warning: everything is FOOBAR
+])
+
+AT_CLEANUP

Return to:

Send suggestions and report system problems to the System administrator.