From bbfdea24b2b940031e86e990bc457785dd378b58 Mon Sep 17 00:00:00 2001 From: Nick Bowler Date: Thu, 11 Mar 2021 23:58:52 -0500 Subject: [PATCH] Hand-code the normalized specifier ordering. For the most part, the enumerated values for specifier types in the library are already in an acceptable order for normalization. This list is part of the API and is expected to be stable. We can achieve the desired ordering by simply tweaking these values as needed. There is also no need to totally order the specifiers because some combinations are never valid. This change not only eliminates a pile of comparatively complex code generation, but also reduces the overall size of the library somewhat. Add a new testcase to validate the resulting ordering. --- Makefile.am | 18 ++--- src/.gitignore | 1 - src/cdecl.h | 4 +- src/normalize.c | 39 ++++++++--- src/ordspecs.sed | 24 ------- test/.gitignore | 7 +- test/normalize.c | 175 ++++++++++++++++++++++++++++++++++++++++++++++ test/testlib.c | 4 +- tests/internal.at | 71 +++++++++++++++++++ testsuite.at | 1 + 10 files changed, 293 insertions(+), 51 deletions(-) delete mode 100644 src/ordspecs.sed create mode 100644 test/normalize.c create mode 100644 tests/internal.at diff --git a/Makefile.am b/Makefile.am index 82a6c0d..9698512 100644 --- a/Makefile.am +++ b/Makefile.am @@ -23,13 +23,12 @@ MAINTAINERCLEANFILES = src/scan.c src/scan.h src/scan.stamp \ DISTCLEANFILES = -CLEANFILES = src/validtypes.h src/namespecs.h src/ordspecs.h \ - $(EXTRA_LTLIBRARIES) +CLEANFILES = src/validtypes.h src/namespecs.h $(EXTRA_LTLIBRARIES) EXTRA_DIST = bootstrap $(DX_BASEDIR)/scripts/fix-gnulib.pl m4/gnulib-cache.m4 \ src/types.lst src/validtypes.sed src/specs.lst src/namespecs.sed \ - src/ordspecs.sed src/parse.y src/parse.stamp src/scan.l \ - src/scan.stamp COPYING.WTFPL2 README.md INSTALL + src/parse.y src/parse.stamp src/scan.l src/scan.stamp \ + COPYING.WTFPL2 README.md INSTALL dist_man_MANS = doc/cdecl99.1 doc/libcdecl.3 @@ -66,7 +65,7 @@ libmain_a_SOURCES = src/cdecl99.c src/options.h $(libmain_a_OBJECTS): $(gnulib_headers) $(libmain_a_OBJECTS): src/options.h -check_PROGRAMS = test/crossparse +check_PROGRAMS = test/crossparse test/normalize check_LTLIBRARIES = libtest.la libtest_la_LIBADD = $(GSL_LIBS) libtest_la_SOURCES = test/testlib.c @@ -81,13 +80,15 @@ test_crossparse_LDADD = libtest.la libcdecl.la libgnu.la $(test_crossparse_OBJECTS): $(gnulib_headers) test_randomdecl_LDADD = libtest.la libcdecl.la libgnu.la $(test_randomdecl_OBJECTS): $(gnulib_headers) +test_normalize_LDADD = libtest.la src/output.lo src/normalize.lo \ + libcdecl.la libgnu.la +$(test_normalize_OBJECTS): $(gnulib_headers) src/parse.lo: src/scan.h src/scan.lo: src/parse.h src/parse-decl.lo: src/scan.h src/parse.h src/typemap.lo: src/validtypes.h src/error.lo: src/errtab.h -src/normalize.lo: src/ordspecs.h src/output.lo: src/namespecs.h test/declgen.lo: test/typegen.h @@ -101,11 +102,6 @@ src/namespecs.h: $(srcdir)/src/specs.lst $(srcdir)/src/namespecs.sed < $(srcdir)/src/specs.lst > $@.tmp $(AM_V_at) mv -f $@.tmp $@ -src/ordspecs.h: $(srcdir)/src/specs.lst $(srcdir)/src/ordspecs.sed - $(AM_V_GEN) sed -f $(srcdir)/src/ordspecs.sed \ - < $(srcdir)/src/specs.lst > $@.tmp - $(AM_V_at) mv -f $@.tmp $@ - # Supporting rules for gettext. include $(top_srcdir)/common/snippet/gettext.mk diff --git a/src/.gitignore b/src/.gitignore index 9428cc9..8456972 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -5,7 +5,6 @@ /namespecs.h /options.h /commands.h -/ordspecs.h /validtypes.h /execute.c /cmdlist.h diff --git a/src/cdecl.h b/src/cdecl.h index 695024f..11e0099 100644 --- a/src/cdecl.h +++ b/src/cdecl.h @@ -102,12 +102,12 @@ void cdecl_free(struct cdecl *decl); size_t cdecl_explain(char *buf, size_t n, struct cdecl *decl); size_t cdecl_declare(char *buf, size_t n, struct cdecl *decl); -static inline int cdecl_spec_kind(struct cdecl_declspec *spec) +static inline int cdecl_spec_kind(const struct cdecl_declspec *spec) { return spec->type & ~(CDECL_SPEC_TYPE-1u); } -static inline _Bool cdecl_is_abstract(struct cdecl_declarator *d) +static inline _Bool cdecl_is_abstract(const struct cdecl_declarator *d) { while (d->child) d = d->child; diff --git a/src/normalize.c b/src/normalize.c index e6502d2..eac71f3 100644 --- a/src/normalize.c +++ b/src/normalize.c @@ -24,19 +24,42 @@ #include "cdecl-internal.h" /* - * Totally order the declaration specifier types by defining an injection into - * the natural numbers. + * Partially order the declaration specifiers by assigning each specifier + * type an appropriate integer value. These values need not be unique for + * specifiers that never appear together. + * + * This implementation relies implicitly on the specific enumeration values. + * These values are expected to be stable as they are exposed library API. */ -static unsigned order_spec(struct cdecl_declspec *spec) +static int order_spec(const struct cdecl_declspec *spec) { - switch (spec->type) { -# include "ordspecs.h" - default: - assert(0); + unsigned kind = cdecl_spec_kind(spec); + unsigned ret = spec->type; + + /* General sequence: storage, function, qualifier, type */ + switch (kind) { + case CDECL_SPEC_QUAL: + ret |= CDECL_SPEC_FUNC; + break; + case CDECL_SPEC_TYPE: + /* Manually reorder some elements that are in the wrong place */ + switch (ret) { + case CDECL_TYPE_INT: + ret += 2; /* OK because "float int" is invalid. */ + break; + case CDECL_TYPE_SIGNED: + case CDECL_TYPE_UNSIGNED: + ret = CDECL_TYPE_VOID; + break; + } + ret |= CDECL_SPEC_FUNC|CDECL_SPEC_QUAL; } + + return ret; } -static int compare_specs(struct cdecl_declspec *a, struct cdecl_declspec *b) +static int compare_specs(const struct cdecl_declspec *a, + const struct cdecl_declspec *b) { return order_spec(a) - order_spec(b); } diff --git a/src/ordspecs.sed b/src/ordspecs.sed deleted file mode 100644 index 7eb559c..0000000 --- a/src/ordspecs.sed +++ /dev/null @@ -1,24 +0,0 @@ -1i\ -/*\ - * Copyright © 2011 Nick Bowler\ - *\ - * License WTFPL2: Do What The Fuck You Want To Public License, version 2.\ - * This is free software: you are free to do what the fuck you want to.\ - * There is NO WARRANTY, to the extent permitted by law.\ - */ -1i\ -/*\ - * This file is automatically generated by ordspecs.sed.\ - */ -/[^_[:alpha:][:space:]]/b -s/[[:space:]][[:space:]]*/ /g -s/_//g -y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/ -s/\([[:upper:]]*\) \([[:upper:]]*\)/case CDECL_\1_\2:/ -p -i\ - return -= -a\ - ; -d diff --git a/test/.gitignore b/test/.gitignore index e8d2dda..3825ec6 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -1,3 +1,4 @@ -typegen.h -randomdecl -crossparse +/typegen.h +/randomdecl +/crossparse +/normalize diff --git a/test/normalize.c b/test/normalize.c new file mode 100644 index 0000000..b535973 --- /dev/null +++ b/test/normalize.c @@ -0,0 +1,175 @@ +/* + * Helper application to test normalization of declaration specifiers. + * Copyright © 2021 Nick Bowler + * + * 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 . + */ + +#include +#include +#include +#include +#include + +#include "cdecl.h" +#include "cdecl-internal.h" +#include "test.h" + +#define PROGNAME "normalize" +static const char *progname = PROGNAME; +static const char sopts[] = "f:VH"; +static const struct option lopts[] = { + { "file", 1, NULL, 'f' }, + { "version", 0, NULL, 'V' }, + { "help", 0, NULL, 'H' }, + { 0 } +}; + +static void print_usage(FILE *f) +{ + fprintf(f, "Usage: %s [options]\n", progname); +} + +static void print_help(void) +{ + const struct option *opt; + + print_usage(stdout); + puts("Test normalization of declaration specifiers.\n"); + + puts("Options:"); + for (opt = lopts; opt->val; opt++) { + int w = print_option_start(opt, NULL); + + if (w) + putchar('\n'); + } +} + +static unsigned to_spectype(const char *tok) +{ + if (!strcmp(tok, "typedef")) return CDECL_STOR_TYPEDEF; + if (!strcmp(tok, "extern")) return CDECL_STOR_EXTERN; + if (!strcmp(tok, "static")) return CDECL_STOR_STATIC; + if (!strcmp(tok, "auto")) return CDECL_STOR_AUTO; + if (!strcmp(tok, "register")) return CDECL_STOR_REGISTER; + if (!strcmp(tok, "inline")) return CDECL_FUNC_INLINE; + if (!strcmp(tok, "restrict")) return CDECL_QUAL_RESTRICT; + if (!strcmp(tok, "volatile")) return CDECL_QUAL_VOLATILE; + if (!strcmp(tok, "const")) return CDECL_QUAL_CONST; + if (!strcmp(tok, "void")) return CDECL_TYPE_VOID; + if (!strcmp(tok, "signed")) return CDECL_TYPE_SIGNED; + if (!strcmp(tok, "unsigned")) return CDECL_TYPE_UNSIGNED; + if (!strcmp(tok, "char")) return CDECL_TYPE_CHAR; + if (!strcmp(tok, "short")) return CDECL_TYPE_SHORT; + if (!strcmp(tok, "long")) return CDECL_TYPE_LONG; + if (!strcmp(tok, "int")) return CDECL_TYPE_INT; + if (!strcmp(tok, "float")) return CDECL_TYPE_FLOAT; + if (!strcmp(tok, "double")) return CDECL_TYPE_DOUBLE; + if (!strcmp(tok, "_Complex")) return CDECL_TYPE_COMPLEX; + if (!strcmp(tok, "_Imaginary")) return CDECL_TYPE_IMAGINARY; + if (!strcmp(tok, "_Bool")) return CDECL_TYPE_BOOL; + if (!strcmp(tok, "struct")) return CDECL_TYPE_STRUCT; + if (!strcmp(tok, "union")) return CDECL_TYPE_UNION; + if (!strcmp(tok, "enum")) return CDECL_TYPE_ENUM; + + return CDECL_TYPE_IDENT; +} + +int do_normalize(char *line, size_t n) +{ + struct cdecl_declspec *specs = NULL, **newspec = &specs; + char *tok = NULL; + + while ((tok = strtok(tok ? NULL : line, " "))) { + struct cdecl_declspec *spec = malloc_nofail(sizeof *spec); + + spec->next = NULL; + spec->ident = NULL; + spec->type = to_spectype(tok); + switch (spec->type) { + case CDECL_TYPE_STRUCT: + case CDECL_TYPE_UNION: + case CDECL_TYPE_ENUM: + spec->ident = strtok(NULL, " "); + break; + case CDECL_TYPE_IDENT: + spec->ident = tok; + } + + *newspec = spec; + newspec = &spec->next; + } + + line = malloc_nofail(n); + line[0] = 0; + if (specs) { + specs = cdecl__normalize_specs(specs); + cdecl__explain_specs(line, n, specs, -1); + } + printf("%s\n", line); + free(line); + + while (specs) { + struct cdecl_declspec *c = specs; + specs = specs->next; + free(c); + } + + return 0; +} + +int main(int argc, char **argv) +{ + int opt, ret = EXIT_SUCCESS; + char *filename = NULL, *line = NULL; + FILE *infile = stdin; + size_t n = 0; + ssize_t rc; + + while ((opt = getopt_long(argc, argv, sopts, lopts, NULL)) != -1) { + switch (opt) { + case 'f': + filename = optarg; + break; + case 'V': + test_print_version(PROGNAME); + return EXIT_SUCCESS; + case 'H': + print_help(); + return EXIT_SUCCESS; + default: + print_usage(stderr); + return EXIT_FAILURE; + } + } + + if (filename) { + infile = fopen(filename, "r"); + if (!infile) { + perror(filename); + return EXIT_FAILURE; + } + } + + while ((rc = getline(&line, &n, infile)) >= 0) { + if (rc > 0 && line[rc-1] == '\n') + line[rc-1] = 0; + if (do_normalize(line, n) < 0) + ret = EXIT_FAILURE; + } + + free(line); + return ret; +} diff --git a/test/testlib.c b/test/testlib.c index 2c859a9..7471ece 100644 --- a/test/testlib.c +++ b/test/testlib.c @@ -1,6 +1,6 @@ /* * Miscellaneous functions used by the cdecl99 test suite. - * Copyright © 2011 Nick Bowler + * Copyright © 2011-2012, 2021 Nick Bowler * * 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 @@ -89,7 +89,7 @@ bool strict_strtoul(unsigned long *val, const char *str, int base) void test_print_version(const char *program) { printf("%s (%s) %s\n", program, PACKAGE_NAME, PACKAGE_VERSION); - puts("Copyright (C) 2011 Nick Bowler."); + puts("Copyright (C) 2021 Nick Bowler."); puts("License GPLv3+: GNU GPL version 3 or later ."); puts("This is free software: you are free to change and redistribute it."); puts("There is NO WARRANTY, to the extent permitted by law."); diff --git a/tests/internal.at b/tests/internal.at new file mode 100644 index 0000000..e9fdcf4 --- /dev/null +++ b/tests/internal.at @@ -0,0 +1,71 @@ +# Copyright © 2021 Nick Bowler +# +# 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 . + +AT_BANNER([Internal library behaviour]) + +AT_SETUP([cdecl__normalize_specs]) + +TEST_NEED_PROGRAM([normalize]) + +# Note that not all combinations of specifiers make a specifier list that can +# appear in a valid C declaration. We don't want to test normalization of +# invalid specifier lists: each line in the below input file is a specifier +# list that is possible to use. +AT_DATA([input], +[[int long signed long +long int long unsigned +short signed int +int short unsigned +char signed +char unsigned +_Complex float +_Imaginary float +_Imaginary double long +long _Complex double + +const struct foo volatile restrict restrict const volatile const restrict +volatile const const restrict union bar restrict volatile volatile const +const const volatile volatile restrict restrict volatile enum baz volatile + +int signed long const volatile inline static +short inline int extern volatile unsigned const +_Complex volatile double const long typedef +long const _Imaginary double register volatile +volatile _Bool const auto +]]) + +AT_CHECK([normalize -f input], [0], [[signed long long int +unsigned long long int +signed short int +unsigned short int +signed char +unsigned char +float _Complex +float _Imaginary +long double _Imaginary +long double _Complex + +restrict volatile const struct foo +restrict volatile const union bar +restrict volatile const enum baz + +static inline volatile const signed long int +extern inline volatile const unsigned short int +typedef volatile const long double _Complex +register volatile const long double _Imaginary +auto volatile const _Bool +]]) + +AT_CLEANUP diff --git a/testsuite.at b/testsuite.at index 2fb25be..1e67965 100644 --- a/testsuite.at +++ b/testsuite.at @@ -45,4 +45,5 @@ exit 77])]) m4_include([tests/general.at]) m4_include([tests/decl-good.at]) m4_include([tests/decl-bad.at]) +m4_include([tests/internal.at]) m4_include([tests/stress.at]) -- 2.43.0