| # Checking GLR Parsing. -*- Autotest -*- |
| |
| # Copyright (C) 2002-2015, 2018-2022, 2025 Free Software Foundation, |
| # Inc. |
| |
| # This program 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. |
| # |
| # This program 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 this program. If not, see <https://www.gnu.org/licenses/>. |
| |
| AT_BANNER([[C++ Type Syntax (GLR).]]) |
| |
| # _AT_TEST_GLR_CXXTYPES(DECL, RESOLVE1, RESOLVE2) |
| # ----------------------------------------------- |
| # Store into types.y the calc program, with DECL inserted as a declaration, |
| # and with RESOLVE1 and RESOLVE2 as annotations on the conflicted rule for |
| # stmt. Then compile the result. |
| m4_define([_AT_TEST_GLR_CXXTYPES], |
| [AT_BISON_OPTION_PUSHDEFS([%glr-parser $1]) |
| |
| AT_DATA_GRAMMAR([types.y], |
| [[/* Simplified C++ Type and Expression Grammar. */ |
| |
| %define parse.trace |
| $1 |
| |
| %code requires |
| { |
| #include <stdio.h> |
| union node_t { |
| struct { |
| int is_nterm; |
| int parents; |
| } node_info; |
| struct { |
| int is_nterm; /* 1 */ |
| int parents; |
| char const *form; |
| union node_t *children[3]; |
| } nterm; |
| struct { |
| int is_nterm; /* 0 */ |
| int parents; |
| char *text; |
| } term; |
| }; |
| typedef union node_t node_t; |
| #define YYSTYPE node_t * |
| } |
| |
| %code |
| { |
| static node_t *new_nterm (char const *, node_t *, node_t *, node_t *); |
| static node_t *new_term (char *); |
| static void free_node (node_t *); |
| static char *node_to_string (node_t *); |
| ]m4_bmatch([$2], [stmt_merge], |
| [ static YYSTYPE stmt_merge (YYSTYPE x0, YYSTYPE x1);])[ |
| #define YYINITDEPTH 10 |
| #define YYSTACKEXPANDABLE 1 |
| ]AT_YYERROR_DECLARE[ |
| ]AT_YYLEX_DECLARE[ |
| } |
| |
| %token TYPENAME ID |
| |
| %right '=' |
| %left '+' |
| |
| %glr-parser |
| |
| %destructor { free_node ($$); } stmt expr decl declarator TYPENAME ID |
| |
| %% |
| |
| prog : |
| | prog stmt { |
| char *output;]AT_LOCATION_IF([ |
| printf ("%d.%d-%d.%d: ", |
| @2.first_line, @2.first_column, |
| @2.last_line, @2.last_column);])[ |
| output = node_to_string (]$[2); |
| printf ("%s\n", output); |
| free (output); |
| free_node (]$[2); |
| } |
| ; |
| |
| stmt : expr ';' $2 { $$ = ]$[1; } |
| | decl $3 |
| | error ';' { $$ = new_nterm ("<error>", YY_NULLPTR, YY_NULLPTR, YY_NULLPTR); } |
| | '@' { YYACCEPT; } |
| ; |
| |
| expr : ID |
| | TYPENAME '(' expr ')' |
| { $$ = new_nterm ("<cast>(%s,%s)", ]$[3, ]$[1, YY_NULLPTR); } |
| | expr '+' expr { $$ = new_nterm ("+(%s,%s)", ]$[1, ]$[3, YY_NULLPTR); } |
| | expr '=' expr { $$ = new_nterm ("=(%s,%s)", ]$[1, ]$[3, YY_NULLPTR); } |
| ; |
| |
| decl : TYPENAME declarator ';' |
| { $$ = new_nterm ("<declare>(%s,%s)", ]$[1, ]$[2, YY_NULLPTR); } |
| | TYPENAME declarator '=' expr ';' |
| { $$ = new_nterm ("<init-declare>(%s,%s,%s)", ]$[1, |
| ]$[2, ]$[4); } |
| ; |
| |
| declarator : ID |
| | '(' declarator ')' { $$ = ]$[2; } |
| ; |
| |
| %% |
| |
| #include <ctype.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <stdarg.h> |
| #include <assert.h> |
| |
| int |
| main (int argc, char **argv) |
| { |
| if (getenv ("YYDEBUG")) |
| yydebug = 1; |
| for (int i = 1; i < argc; ++i) |
| // Enable parse traces on option -p. |
| if (strcmp (argv[i], "-p") == 0) |
| yydebug = 1; |
| else |
| { |
| if (!freopen (argv[i], "r", stdin)) |
| return 3; |
| int status = yyparse (); |
| if (!status) |
| return status; |
| } |
| return 0; |
| } |
| |
| ]AT_YYERROR_DEFINE[ |
| |
| ]AT_YYLEX_PROTOTYPE[ |
| {]AT_LOCATION_IF([[ |
| static int line_num = 1; |
| static int col_num = 0; |
| ]])[ |
| #if YYPURE]AT_LOCATION_IF([[ |
| # undef yylloc |
| # define yylloc (*llocp)]])[ |
| # undef yylval |
| # define yylval (*lvalp) |
| #endif |
| |
| while (1) |
| { |
| int c; |
| assert (!feof (stdin)); |
| c = getchar (); |
| switch (c) |
| { |
| case EOF: |
| return 0; |
| case '\t':]AT_LOCATION_IF([[ |
| col_num = (col_num + 7) & ~7;]])[ |
| break; |
| case ' ': case '\f':]AT_LOCATION_IF([[ |
| col_num += 1;]])[ |
| break; |
| case '\n':]AT_LOCATION_IF([[ |
| line_num += 1; |
| col_num = 0;]])[ |
| break; |
| default: |
| { |
| int tok;]AT_LOCATION_IF([[ |
| yylloc.first_line = yylloc.last_line = line_num; |
| yylloc.first_column = col_num;]])[ |
| if (isalpha (c)) |
| { |
| char buffer[256]; |
| unsigned i = 0; |
| |
| do |
| { |
| buffer[i++] = YY_CAST (char, c);]AT_LOCATION_IF([[ |
| col_num += 1;]])[ |
| assert (i != sizeof buffer - 1); |
| c = getchar (); |
| } |
| while (isalnum (c) || c == '_'); |
| |
| ungetc (c, stdin); |
| buffer[i++] = 0; |
| tok = isupper (YY_CAST (unsigned char, buffer[0])) ? TYPENAME : ID; |
| yylval = new_term (strdup (buffer)); |
| } |
| else |
| {]AT_LOCATION_IF([[ |
| col_num += 1;]])[ |
| tok = c; |
| yylval = YY_NULLPTR; |
| }]AT_LOCATION_IF([[ |
| yylloc.last_column = col_num;]])[ |
| return tok; |
| } |
| } |
| } |
| } |
| |
| static node_t * |
| new_nterm (char const *form, node_t *child0, node_t *child1, node_t *child2) |
| { |
| node_t *res = YY_CAST (node_t *, malloc (sizeof *res)); |
| res->nterm.is_nterm = 1; |
| res->nterm.parents = 0; |
| res->nterm.form = form; |
| res->nterm.children[0] = child0; |
| if (child0) |
| child0->node_info.parents += 1; |
| res->nterm.children[1] = child1; |
| if (child1) |
| child1->node_info.parents += 1; |
| res->nterm.children[2] = child2; |
| if (child2) |
| child2->node_info.parents += 1; |
| return res; |
| } |
| |
| static node_t * |
| new_term (char *text) |
| { |
| node_t *res = YY_CAST (node_t *, malloc (sizeof *res)); |
| res->term.is_nterm = 0; |
| res->term.parents = 0; |
| res->term.text = text; |
| return res; |
| } |
| |
| static void |
| free_node (node_t *node) |
| { |
| if (!node) |
| return; |
| node->node_info.parents -= 1; |
| /* Free only if 0 (last parent) or -1 (no parents). */ |
| if (node->node_info.parents > 0) |
| return; |
| if (node->node_info.is_nterm == 1) |
| { |
| free_node (node->nterm.children[0]); |
| free_node (node->nterm.children[1]); |
| free_node (node->nterm.children[2]); |
| } |
| else |
| free (node->term.text); |
| free (node); |
| } |
| |
| static char * |
| node_to_string (node_t *node) |
| { |
| char *res; |
| if (!node) |
| res = strdup (""); |
| else if (node->node_info.is_nterm) |
| { |
| char *child0 = node_to_string (node->nterm.children[0]); |
| char *child1 = node_to_string (node->nterm.children[1]); |
| char *child2 = node_to_string (node->nterm.children[2]); |
| res = YY_CAST (char *, malloc (strlen (node->nterm.form) + strlen (child0) |
| + strlen (child1) + strlen (child2) + 1)); |
| sprintf (res, node->nterm.form, child0, child1, child2); |
| free (child2); |
| free (child1); |
| free (child0); |
| } |
| else |
| res = strdup (node->term.text); |
| return res; |
| } |
| |
| ]] |
| m4_bmatch([$2], [stmt_merge], |
| [[static YYSTYPE |
| stmt_merge (YYSTYPE x0, YYSTYPE x1) |
| { |
| return new_nterm ("<OR>(%s,%s)", x0, x1, YY_NULLPTR); |
| } |
| ]]) |
| ) |
| |
| AT_DATA([test-input], |
| [[ |
| |
| z + q; |
| |
| T x; |
| |
| T x = y; |
| |
| x = y; |
| |
| T (x) + y; |
| |
| T (x); |
| |
| T (y) = z + q; |
| |
| T (y y) = z + q; |
| |
| z + q; |
| |
| @ |
| |
| This is total garbage, but it should be ignored. |
| ]]) |
| |
| AT_BISON_CHECK([-o types.c types.y], 0, [], ignore) |
| AT_COMPILE([types]) |
| AT_BISON_OPTION_POPDEFS |
| ]) |
| |
| m4_define([_AT_RESOLVED_GLR_OUTPUT], |
| [[+(z,q) |
| <declare>(T,x) |
| <init-declare>(T,x,y) |
| =(x,y) |
| +(<cast>(x,T),y) |
| <declare>(T,x) |
| <init-declare>(T,y,+(z,q)) |
| <error> |
| +(z,q) |
| ]]) |
| |
| m4_define([_AT_RESOLVED_GLR_OUTPUT_WITH_LOC], |
| [[3.0-3.6: +(z,q) |
| 5.0-5.4: <declare>(T,x) |
| 7.0-7.8: <init-declare>(T,x,y) |
| 9.0-9.6: =(x,y) |
| 11.0-11.10: +(<cast>(x,T),y) |
| 13.0-13.6: <declare>(T,x) |
| 15.0-15.14: <init-declare>(T,y,+(z,q)) |
| 17.0-17.16: <error> |
| 19.0-19.6: +(z,q) |
| ]]) |
| |
| m4_define([_AT_AMBIG_GLR_OUTPUT], |
| [[+(z,q) |
| <declare>(T,x) |
| <init-declare>(T,x,y) |
| =(x,y) |
| +(<cast>(x,T),y) |
| <OR>(<declare>(T,x),<cast>(x,T)) |
| <OR>(<init-declare>(T,y,+(z,q)),=(<cast>(y,T),+(z,q))) |
| <error> |
| +(z,q) |
| ]]) |
| |
| m4_define([_AT_AMBIG_GLR_OUTPUT_WITH_LOC], |
| [[3.0-3.6: +(z,q) |
| 5.0-5.4: <declare>(T,x) |
| 7.0-7.8: <init-declare>(T,x,y) |
| 9.0-9.6: =(x,y) |
| 11.0-11.10: +(<cast>(x,T),y) |
| 13.0-13.6: <OR>(<declare>(T,x),<cast>(x,T)) |
| 15.0-15.14: <OR>(<init-declare>(T,y,+(z,q)),=(<cast>(y,T),+(z,q))) |
| 17.0-17.16: <error> |
| 19.0-19.6: +(z,q) |
| ]]) |
| |
| m4_define([_AT_GLR_STDERR], |
| [[syntax error |
| ]]) |
| |
| m4_define([_AT_GLR_STDERR_WITH_LOC], |
| [[17.5: syntax error |
| ]]) |
| |
| m4_define([_AT_VERBOSE_GLR_STDERR], |
| [[syntax error, unexpected ID, expecting '=' or '+' or ')' |
| ]]) |
| |
| m4_define([_AT_VERBOSE_GLR_STDERR_WITH_LOC], |
| [[17.5: syntax error, unexpected ID, expecting '=' or '+' or ')' |
| ]]) |
| |
| |
| ## ---------------------------------------------------- ## |
| ## Compile the grammar described in the documentation. ## |
| ## ---------------------------------------------------- ## |
| |
| # AT_TEST([STDOUT], [STDERR]) |
| m4_pushdef([AT_TEST], |
| [AT_PARSER_CHECK([[types test-input]], 0, [$1], [$2]) |
| AT_PARSER_CHECK([[types -p test-input]], 0, [$1], [ignore]) |
| ]) |
| |
| AT_SETUP([GLR: Resolve ambiguity, impure, no locations]) |
| _AT_TEST_GLR_CXXTYPES([], |
| [%dprec 1], [%dprec 2]) |
| AT_TEST([_AT_RESOLVED_GLR_OUTPUT], [_AT_GLR_STDERR]) |
| AT_CLEANUP |
| |
| AT_SETUP([GLR: Resolve ambiguity, impure, locations]) |
| _AT_TEST_GLR_CXXTYPES([%locations],[%dprec 1],[%dprec 2]) |
| AT_TEST([_AT_RESOLVED_GLR_OUTPUT_WITH_LOC], [_AT_GLR_STDERR_WITH_LOC]) |
| AT_CLEANUP |
| |
| AT_SETUP([GLR: Resolve ambiguity, pure, no locations]) |
| _AT_TEST_GLR_CXXTYPES([%define api.pure], |
| [%dprec 1], [%dprec 2]) |
| AT_TEST([_AT_RESOLVED_GLR_OUTPUT], [_AT_GLR_STDERR]) |
| AT_CLEANUP |
| |
| AT_SETUP([GLR: Resolve ambiguity, pure, locations]) |
| _AT_TEST_GLR_CXXTYPES([%define api.pure %locations], |
| [%dprec 1], [%dprec 2]) |
| AT_TEST([_AT_RESOLVED_GLR_OUTPUT_WITH_LOC], [_AT_GLR_STDERR_WITH_LOC]) |
| AT_CLEANUP |
| |
| AT_SETUP([GLR: Merge conflicting parses, impure, no locations]) |
| _AT_TEST_GLR_CXXTYPES([], |
| [%merge <stmt_merge>], [%merge <stmt_merge>]) |
| AT_TEST([_AT_AMBIG_GLR_OUTPUT], [_AT_GLR_STDERR]) |
| AT_CLEANUP |
| |
| AT_SETUP([GLR: Merge conflicting parses, impure, locations]) |
| _AT_TEST_GLR_CXXTYPES([%locations], |
| [%merge <stmt_merge>], [%merge <stmt_merge>]) |
| AT_TEST([_AT_AMBIG_GLR_OUTPUT_WITH_LOC], [_AT_GLR_STDERR_WITH_LOC]) |
| AT_CLEANUP |
| |
| AT_SETUP([GLR: Merge conflicting parses, pure, no locations]) |
| _AT_TEST_GLR_CXXTYPES([%define api.pure], |
| [%merge <stmt_merge>], [%merge <stmt_merge>]) |
| AT_TEST([_AT_AMBIG_GLR_OUTPUT], [_AT_GLR_STDERR]) |
| AT_CLEANUP |
| AT_SETUP([GLR: Merge conflicting parses, pure, locations]) |
| _AT_TEST_GLR_CXXTYPES([%define api.pure %locations], |
| [%merge <stmt_merge>],[%merge <stmt_merge>]) |
| AT_TEST([_AT_AMBIG_GLR_OUTPUT_WITH_LOC], [_AT_GLR_STDERR_WITH_LOC]) |
| AT_CLEANUP |
| |
| AT_SETUP([GLR: Verbose messages, resolve ambiguity, impure, no locations]) |
| _AT_TEST_GLR_CXXTYPES([%define parse.error verbose], |
| [%merge <stmt_merge>], [%merge <stmt_merge>]) |
| AT_TEST([_AT_AMBIG_GLR_OUTPUT], [_AT_VERBOSE_GLR_STDERR]) |
| AT_CLEANUP |
| |
| m4_popdef([AT_TEST]) |