From 568c39ad1836f8cf87ccc1b2ba577b6f6ba37e25 Mon Sep 17 00:00:00 2001 From: Nick Bowler Date: Thu, 23 Feb 2012 22:14:46 -0500 Subject: [PATCH] Add a tool to generate random declarations. The first of (hopefully) many components of a test suite. --- Makefile.am | 26 ++- configure.ac | 20 +++ m4/cstring.m4 | 66 ++++++++ m4/dxutils.m4 | 78 +++++++++ m4/gsl.m4 | 74 +++++++++ m4/strings.m4 | 3 + test/.gitignore | 2 + test/declgen.c | 400 ++++++++++++++++++++++++++++++++++++++++++++++ test/declgen.h | 21 +++ test/randomdecl.c | 133 +++++++++++++++ test/test.h | 17 ++ test/testlib.c | 71 ++++++++ test/typegen.sh | 57 +++++++ 13 files changed, 967 insertions(+), 1 deletion(-) create mode 100644 m4/cstring.m4 create mode 100644 m4/dxutils.m4 create mode 100644 m4/gsl.m4 create mode 100644 m4/strings.m4 create mode 100644 test/.gitignore create mode 100644 test/declgen.c create mode 100644 test/declgen.h create mode 100644 test/randomdecl.c create mode 100644 test/test.h create mode 100644 test/testlib.c create mode 100755 test/typegen.sh diff --git a/Makefile.am b/Makefile.am index 469f962..5781b23 100644 --- a/Makefile.am +++ b/Makefile.am @@ -20,7 +20,7 @@ AM_CPPFLAGS = -I$(top_builddir)/src -I$(top_srcdir)/src \ MAINTAINERCLEANFILES = src/scan.c src/scan.h src/scan.stamp \ src/parse.c src/parse.h src/parse.stamp -CLEANFILES = src/validtypes.h src/errtab.h +CLEANFILES = src/validtypes.h src/errtab.h test/typegen.h EXTRA_DIST = m4/gnulib-cache.m4 src/types.lst src/validtypes.sed \ src/errors.lst src/strtab.sed \ @@ -45,10 +45,25 @@ libcdecl_la_LIBADD = libgnu.la $(LTLIBINTL) $(LTLIBTHREAD) $(libcdecl_la_OBJECTS): $(gnulib_headers) bin_PROGRAMS = cdecl99 + +check_PROGRAMS = +EXTRA_LTLIBRARIES = libtest.la +libtest_la_CFLAGS = $(AM_CFLAGS) $(GSL_CFLAGS) +libtest_la_LIBADD = $(GSL_LIBS) +libtest_la_SOURCES = test/testlib.c + +if HAVE_GSL +libtest_la_SOURCES += test/declgen.c +check_PROGRAMS += test/randomdecl +endif + cdecl99_SOURCES = src/cdecl99.c cdecl99_LDADD = libcdecl.la libgnu.la $(LTLIBINTL) $(LTLIBREADLINE) $(cdecl99_OBJECTS): $(gnulib_headers) +test_randomdecl_LDADD = libcdecl.la libtest.la libgnu.la +$(test_randomdecl_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 @@ -57,6 +72,10 @@ src/error.lo: src/errtab.h src/normalize.lo: src/ordspecs.h src/output.lo: src/namespecs.h +# This is gross. +declgen_o = test/libtest_la-declgen.lo +$(declgen_o): test/typegen.h + src/validtypes.h: $(srcdir)/src/types.lst $(srcdir)/src/validtypes.sed $(AM_V_GEN)sed -f $(srcdir)/src/validtypes.sed \ < $(srcdir)/src/types.lst > $@.tmp @@ -77,6 +96,11 @@ src/errtab.h: $(srcdir)/src/errors.lst $(srcdir)/src/strtab.sed < $(srcdir)/src/errors.lst > $@.tmp $(AM_V_at)mv -f $@.tmp $@ +test/typegen.h: $(srcdir)/src/types.lst $(srcdir)/test/typegen.sh + $(AM_V_GEN) $(SHELL) $(srcdir)/test/typegen.sh \ + < $(srcdir)/src/types.lst > $@.tmp + $(AM_V_at)mv -f $@.tmp $@ + # Supporting rules for gettext. ALL_MOFILES = $(POFILES:.po=.mo) EXTRA_DIST += po/$(PACKAGE).pot po/LINGUAS $(POFILES) $(ALL_MOFILES) diff --git a/configure.ac b/configure.ac index dcb5504..f0b606f 100644 --- a/configure.ac +++ b/configure.ac @@ -13,6 +13,7 @@ AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects]) AM_SILENT_RULES([yes]) AC_PROG_CC_C99 +AM_PROG_CC_C_O gl_EARLY LT_INIT @@ -41,6 +42,25 @@ AC_SUBST([GLOBAL_SYMBOL_PIPE]) AH_BOTTOM([#include ]) +AC_ARG_WITH([gsl], + [AS_HELP_STRING([--with-gsl], + [specify whether to build test programs requiring the GNU Scientific Library. [default=auto]] + )], [with_gsl=$withval], [with_gsl=auto]) + +case $with_gsl in +no) + have_gsl=no +;; +yes) + DX_CHECK_GSL([1.0], [have_gsl=yes]) +;; +*) + DX_CHECK_GSL([1.0], [have_gsl=yes], [have_gsl=no]) +;; +esac + +AM_CONDITIONAL([HAVE_GSL], [test x"$have_gsl" = x"yes"]) + AC_CONFIG_FILES([ exported.sh Makefile diff --git a/m4/cstring.m4 b/m4/cstring.m4 new file mode 100644 index 0000000..9973f68 --- /dev/null +++ b/m4/cstring.m4 @@ -0,0 +1,66 @@ +dnl Copyright © 2011 Nick Bowler +dnl +dnl License WTFPL2: Do What The Fuck You Want To Public License, version 2. +dnl This is free software: you are free to do what the fuck you want to. +dnl There is NO WARRANTY, to the extent permitted by law. + +m4_define([_DX_COMPUTE_C_STRING_PROG], [AC_LANG_PROGRAM([dnl +#include +#include +$2 +char thestr@<:@@:>@ = $1; +char padstr@<:@@:>@ = "\n!&?~" $1 "~?&!\n"; +], [dnl +size_t i; +FILE *f = fopen("conftest.out", "w"); +if (!f) + return EXIT_FAILURE; +for (i = 0; i < sizeof thestr; i++) { + if (*(thestr+i) == '\n') + *(thestr+i) = ' '; +} +fprintf(f, "%s\n", thestr); +return 0; +])]) + +AC_DEFUN([_DX_COMPUTE_C_STRING_NORUN], [dnl +AC_REQUIRE([DX_PROG_STRINGS]) +if test x"$STRINGS" = x""; then + AC_MSG_WARN([strings is recommended for cross compilation]) + dx_c_strings=cat +else + dx_c_strings="$STRINGS -n 1" +fi +dnl else +AC_COMPILE_IFELSE([_DX_COMPUTE_C_STRING_PROG([$2], [$3])], [dnl + $dx_c_strings conftest.$OBJEXT | sed -n '/^!&?~/ { + t top + :top + s/^!&?~\(.*\)~?&!$/\1/p + t + N + s/\n/ /g + t top + }' > conftest.out + if read $1 < conftest.out; then :; else + AC_MSG_ERROR([failed to extract string due to cross compilation.]) + fi +], [dnl + ifelse([$4], [], [:], [$4])]) +dnl fi +]) + +dnl DX_COMPUTE_C_STRING(variable, string, [prologue], [action-if-failed]) +dnl +dnl Assigns the value of a C string literal specified by string to the shell +dnl variable specified by variable. Any newlines in the string are converted +dnl to spaces. In a cross compilation environment, it works by scanning a +dnl compiled object file. +AC_DEFUN([DX_COMPUTE_C_STRING], [dnl +AC_LANG_PUSH([C]) +AC_RUN_IFELSE([_DX_COMPUTE_C_STRING_PROG([$2], [$3])], + [read $1 < conftest.out], + [ifelse([$4], [], [:], [$4])], + [_DX_COMPUTE_C_STRING_NORUN([$1], [$2], [$3], [$4])]) +AC_LANG_POP([C]) +]) diff --git a/m4/dxutils.m4 b/m4/dxutils.m4 new file mode 100644 index 0000000..2f976d3 --- /dev/null +++ b/m4/dxutils.m4 @@ -0,0 +1,78 @@ +dnl Copyright © 2010-2011 Nick Bowler +dnl +dnl License WTFPL2: Do What The Fuck You Want To Public License, version 2. +dnl This is free software: you are free to do what the fuck you want to. +dnl There is NO WARRANTY, to the extent permitted by law. + +m4_pattern_forbid([^_?DX_]) + +dnl DX_PKG_CONFIG(env-base, [pkg-id], [pkg-help]) +AC_DEFUN([DX_PKG_CONFIG], [dnl +AC_ARG_VAR(m4_toupper([$1])[_CFLAGS], [C compiler flags for ]ifelse( + [$3], [], [$1], [$3]))dnl +AC_ARG_VAR(m4_toupper([$1])[_LIBS], [linker flags for ]ifelse( + [$3], [], [$1], [$3]))dnl + +ifelse([$2], [], [], [dnl +AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl +if test x"$PKG_CONFIG" != x; then + AC_MSG_CHECKING([pkg-config database for $2]) + pkg_failed=no + _PKG_CONFIG(m4_tolower([$1])[_cflags], [cflags], [$2]) + _PKG_CONFIG(m4_tolower([$1])[_libs], [libs], [$2]) + if test x$pkg_failed = xyes; then + errors=`$PKG_CONFIG --errors-to-stdout --print-errors $2` + echo "$errors" >&AS_MESSAGE_LOG_FD + AC_MSG_RESULT([no]) + else + AC_MSG_RESULT([yes]) + fi +fi])]) + +dnl _DX_CHECK_LIB(env-base, check-func, [cflags], [libs], [test]) +AC_DEFUN([_DX_CHECK_LIB], [dnl +if test x"$dx_cv_[]m4_tolower([$1])_found" = x"no"; then +if ifelse([$5], [], [:], [$5]); then + CFLAGS="$3 $dx_old_cflags" + LIBS="$4 $dx_old_libs" + + $2([dnl + dx_cv_[]m4_tolower([$1])_found="yes" + dx_cv_[]m4_tolower([$1])_cflags="$3" + dx_cv_[]m4_tolower([$1])_libs="$4" + ]) +fi +fi +]) + +dnl DX_CHECK_LIB_EXT(env-base, message, check-func, +dnl [[[cflags1],[libs1],[test1]],[[cflags2],[libs2],[test2]],...]) +AC_DEFUN([DX_CHECK_LIB_EXT], [dnl +AC_LANG_PUSH([C]) +dx_old_cflags=$CFLAGS +dx_old_libs=$LIBS + +AC_CACHE_CHECK([$2], [dx_cv_]m4_tolower([$1])[_found], [dnl +AC_CACHE_VAL([dx_cv_]m4_tolower([$1])[_libs], [dnl +AC_CACHE_VAL([dx_cv_]m4_tolower([$1])[_cflags], [dnl + dx_cv_[]m4_tolower([$1])_found=no + m4_foreach([dx_tuple], [$4], [_DX_CHECK_LIB([$1], [$3], dx_tuple)]) +])])]) + +if test x"$dx_cv_[]m4_tolower([$1])_found" = x"yes"; then + m4_toupper([$1])_CFLAGS=$dx_cv_[]m4_tolower([$1])_cflags + m4_toupper([$1])_LIBS=$dx_cv_[]m4_tolower([$1])_libs + AC_SUBST(m4_toupper([$1])[_CFLAGS]) + AC_SUBST(m4_toupper([$1])[_LIBS]) +fi + +CFLAGS=$dx_old_cflags +LIBS=$dx_old_libs +AC_LANG_POP([C]) +]) + +dnl DX_CHECK_LIB(env-base, message, test-program, +dnl [[[cflags1],[libs1],[test1]],[[cflags2],[libs2],[test2]],...]) +AC_DEFUN([DX_CHECK_LIB], [dnl + DX_CHECK_LIB_EXT([$1], [$2], [m4_curry([AC_LINK_IFELSE], [$3])], [$4]) +]) diff --git a/m4/gsl.m4 b/m4/gsl.m4 new file mode 100644 index 0000000..781fed5 --- /dev/null +++ b/m4/gsl.m4 @@ -0,0 +1,74 @@ +dnl Copyright © 2009-2011 Nick Bowler +dnl +dnl License WTFPL2: Do What The Fuck You Want To Public License, version 2. +dnl This is free software: you are free to do what the fuck you want to. +dnl There is NO WARRANTY, to the extent permitted by law. + +AC_DEFUN([_DX_CHECK_GSL_TEST], [dnl + DX_COMPUTE_C_STRING([gsl_version], [GSL_VERSION], [dnl +#include +], [gsl_version=0]) + + AS_VERSION_COMPARE([$gsl_version], [$1], + [gsl_version_ok=no], + [gsl_version_ok=yes], + [gsl_version_ok=yes]) + + if test x"$gsl_version_ok" = x"yes"; then + AC_LINK_IFELSE([AC_LANG_PROGRAM([dnl +#include +#include +], [dnl +double m1buf@<:@16@:>@, m2buf@<:@16@:>@, m3buf@<:@16@:>@; + +gsl_matrix_view m1 = gsl_matrix_view_array(m1buf, 4, 4); +gsl_matrix_view m2 = gsl_matrix_view_array(m2buf, 4, 4); +gsl_matrix_view m3 = gsl_matrix_view_array(m3buf, 4, 4); + +gsl_matrix_set_identity(&m1.matrix); +gsl_matrix_set_identity(&m2.matrix); +gsl_blas_dgemm(CblasNoTrans, CblasNoTrans, 1, &m1.matrix, &m2.matrix, + 0, &m3.matrix); + +return 0; +])], [$2]) + fi +]) + +dnl DX_CHECK_GSL([min-version], [action-if-found], [action-if-not-found]) +AC_DEFUN([DX_CHECK_GSL], [dnl +DX_PKG_CONFIG([gsl], [gsl], [GSL]) +AC_ARG_VAR([GSL_CONFIG], [path to gsl-config binary]) +AC_PATH_PROG([GSL_CONFIG], [gsl-config]) + +if test x"$GSL_CONFIG" != x; then + gsl_config_cflags=`$GSL_CONFIG --cflags` + gsl_config_libs=`$GSL_CONFIG --libs` +fi + +DX_CHECK_LIB_EXT([gsl], + [for GSL version at least ifelse([$1], [], [1.0], [$1])], + [m4_curry([_DX_CHECK_GSL_TEST], [ifelse([$1], [], [1.0], [$1])])], + [dnl + [[$GSL_CFLAGS], [$GSL_LIBS]], + [[$pkg_cv_gsl_cflags], [$pkg_cv_gsl_libs], + [test x"$pkg_failed" != x"yes"]], + [[$gsl_config_cflags], [$gsl_config_libs], + [test x"$SDL_CONFIG" != x""]], + ]) + +if test x"$dx_cv_gsl_found" = x"yes"; then + ifelse([$2], [], [:], [$2]) +else + ifelse([$3], [], [AC_MSG_FAILURE([dnl +GSL version at least ifelse([$1], [], [1.0], [$1]) is required. The +latest version can be optained from http://gnu.org/software/gsl/. + +If GSL is installed but was not found by this configure script, consider +adjusting GSL_CFLAGS, GSL_LIBS and/or GSL_CONFIG as necessary. + +If pkg-config is installed, it may help to adjust PKG_CONFIG_PATH +if GSL is installed in a non-standard prefix. +])], [$3]) +fi +]) diff --git a/m4/strings.m4 b/m4/strings.m4 new file mode 100644 index 0000000..6b0f269 --- /dev/null +++ b/m4/strings.m4 @@ -0,0 +1,3 @@ +AC_DEFUN([DX_PROG_STRINGS], [dnl +AC_CHECK_TOOL([STRINGS], [strings]) +]) diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 0000000..7a3928c --- /dev/null +++ b/test/.gitignore @@ -0,0 +1,2 @@ +typegen.h +randomdecl diff --git a/test/declgen.c b/test/declgen.c new file mode 100644 index 0000000..b9fb733 --- /dev/null +++ b/test/declgen.c @@ -0,0 +1,400 @@ +/* + * Generate random C declarations for testing. + * Copyright © 2011 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 +#include +#include + +#include "declgen.h" +#include "cdecl.h" +#include "test.h" + +struct gen_rng { + gsl_rng *rng; +}; + +/* + * Generate a random identifier. We arbitrarily pick 10 as the largest + * possible. Avoid generating keywords, including the English ones, by + * excluding the letters t, o, a and n. Reserved words are OK. + */ +char *gen_identifier(struct gen_rng *rng) +{ + static const char valid[59] = "_bcdefghijklmpqrsuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + char *str; + size_t n; + + n = gsl_rng_uniform_int(rng->rng, 10)+1; + str = malloc_nofail(n+1); + + /* Exclude 10 digits from the first character. */ + str[0] = valid[gsl_rng_uniform_int(rng->rng, sizeof valid - 10)]; + for (size_t i = 1; i < n; i++) + str[i] = valid[gsl_rng_uniform_int(rng->rng, sizeof valid)]; + str[n] = 0; + + return str; +} + +/* + * Generate random qualifiers. Qualifiers can appear multiple times, so the + * list is (potentially) unbounded. We handle the potential unboundedness by + * generating a list of n qualifiers with probability 1/2^(n+1). Each + * qualifier is chosen uniformly at random from the set of possibilities: + * const, volatile and, if restrictqual is true, restrict. + */ +struct cdecl_declspec *gen_qualifiers(struct gen_rng *rng, bool restrictqual) +{ + struct cdecl_declspec *s = NULL, *p; + + while (gsl_rng_uniform(rng->rng) < 0.5) { + p = s; + s = malloc_nofail(sizeof *s); + *s = (struct cdecl_declspec) { + .next = p, + }; + + switch (gsl_rng_uniform_int(rng->rng, 2+restrictqual)) { + case 0: + s->type = CDECL_QUAL_CONST; + break; + case 1: + s->type = CDECL_QUAL_VOLATILE; + break; + case 2: /* Only if restrictqual is true. */ + s->type = CDECL_QUAL_RESTRICT; + break; + default: + assert(0); + } + } + + return s; +} + +/* + * Generate random function specifiers. Like qualifiers, function specifiers + * can appear multiple times. + */ +struct cdecl_declspec *gen_funcspecs(struct gen_rng *rng) +{ + struct cdecl_declspec *s = NULL, *p; + + while (gsl_rng_uniform(rng->rng) < 0.5) { + p = s; + s = malloc_nofail(sizeof *s); + + *s = (struct cdecl_declspec) { + .type = CDECL_FUNC_INLINE, + .next = p, + }; + } + + return s; +} + +/* + * Generate zero or one random storage-class specifier. If registeronly is + * true, then the only possible storage-class specifier is "register". + * Otherwise, a specifier type will be selected uniformly at random. + */ +struct cdecl_declspec *gen_storspecs(struct gen_rng *rng, bool registeronly) +{ + struct cdecl_declspec *s; + + if (gsl_rng_uniform(rng->rng) < 0.5) + return NULL; + + s = malloc_nofail(sizeof *s); + *s = (struct cdecl_declspec) { + .type = CDECL_STOR_REGISTER, + }; + + if (registeronly) + return s; + + switch (gsl_rng_uniform_int(rng->rng, 5)) { + case 0: s->type = CDECL_STOR_TYPEDEF; break; + case 1: s->type = CDECL_STOR_REGISTER; break; + case 2: s->type = CDECL_STOR_STATIC; break; + case 3: s->type = CDECL_STOR_AUTO; break; + case 4: s->type = CDECL_STOR_EXTERN; break; + default: assert(0); + } + + return s; +} + +/* + * Generate random type specifiers. There is a short list of valid + * combinations, from which we can select one uniformly at random. + */ +static const unsigned long total_types; +static struct cdecl_declspec *gen_raw_typespecs(struct gen_rng *rng) +{ + switch (gsl_rng_uniform_int(rng->rng, total_types)) { +# include "typegen.h" + } +} +static const unsigned long total_types = TOTAL_TYPES; + +struct cdecl_declspec *gen_typespecs(struct gen_rng *rng, bool voidtype) +{ + struct cdecl_declspec *specs; + +retry: + specs = gen_raw_typespecs(rng); + + switch (specs->type) { + /* void is not always valid, so we might need to pick again. */ + case CDECL_TYPE_VOID: + if (!voidtype) + goto retry; + break; + /* A few kinds of type specifiers need identifiers to go with them. */ + case CDECL_TYPE_STRUCT: + case CDECL_TYPE_UNION: + case CDECL_TYPE_ENUM: + case CDECL_TYPE_IDENT: + assert(!specs->next); + specs->ident = gen_identifier(rng); + } + + return specs; +} + +struct cdecl_declspec * +gen_randomize_specs(struct gen_rng *rng, struct cdecl_declspec *specs) +{ + struct cdecl_declspec **p; + size_t n = 0; + + if (!specs) + return specs; + + for (struct cdecl_declspec *s = specs; s; s = s->next) + n++; + + p = malloc_nofail((n+1) * sizeof *p); + + /* Build a temporary array for easy element swapping. */ + p[0] = specs; + p[n] = NULL; + for (size_t i = 1; i < n; i++) + p[i] = p[i-1]->next; + + /* Knuth shuffle. */ + for (size_t i = 0; i < n; i++) { + struct cdecl_declspec *tmp; + size_t r; + + r = gsl_rng_uniform_int(rng->rng, n-i); + tmp = p[i]; + p[i] = p[r+i]; + p[r+i] = tmp; + } + + /* Fix up the pointers. */ + for (size_t i = 1; i <= n; i++) + p[i-1]->next = p[i]; + specs = p[0]; + + free(p); + return specs; +} + +/* + * Generate random declaration specifiers. This consists of random qualifiers + * and type specifiers, as described in the functions above, plus up to one + * storage-class specifier and zero or more function specifiers. + * + * The flags parameter controls what sort of specifiers can be generated in + * order to handle various special cases in the C language. It is the + * bitwise-or of zero or more values, which have the following meanings: + * + * GEN_NO_FUNCTION: Do not generate any function specifiers at all. + * GEN_NO_STORAGE: Do not generate any storage-class specifiers at all. + * GEN_NO_VOID: Do not generate the "void" type specifier. + * GEN_ONLY_REGISTER: Never generate any storage-class specifiers other than + * "register". + * + * Notwithstanding any of the above, the "restrict" type qualifier will never + * be generated by this function: use gen_qualifiers directly. + */ +struct cdecl_declspec * +gen_declspecs(struct gen_rng *rng, unsigned flags) +{ + struct cdecl_declspec *s, *p; + + s = gen_typespecs(rng, ~flags & GEN_NO_VOID); + for (p = s; p->next;) + p = p->next; + + if (~flags & GEN_NO_FUNCTION) + p->next = gen_funcspecs(rng); + for (p = s; p->next;) + p = p->next; + + if (~flags & GEN_NO_STORAGE) + p->next = gen_storspecs(rng, flags & GEN_ONLY_REGISTER); + for (p = s; p->next;) + p = p->next; + + p->next = gen_qualifiers(rng, false); + return gen_randomize_specs(rng, s); +} + +/* + * Generate a random array declarator, selecting one of four possibilities + * uniformly at random. + */ +static void gen_array(struct gen_rng *rng, struct cdecl_declarator *d) +{ + d->type = CDECL_DECL_ARRAY; + d->u.array = (struct cdecl_array){0}; + + switch (gsl_rng_uniform_int(rng->rng, 4)) { + case '0': + d->u.array.vla = malloc_nofail(1); + d->u.array.vla[0] = 0; + break; + case '1': + d->u.array.vla = gen_identifier(rng); + break; + case '2': + d->u.array.length = 0; + break; + case '3': + d->u.array.length = gsl_rng_uniform_int(rng->rng, -1); + break; + } +} + +/* Generate a function declarator. */ +static void gen_function(struct gen_rng *rng, struct cdecl_declarator *d) +{ + d->type = CDECL_DECL_FUNCTION; + d->u.function.parameters = NULL; + + while (gsl_rng_uniform(rng->rng) < 0.5) { + unsigned flags = GEN_ONLY_REGISTER | GEN_NO_FUNCTION; + struct cdecl *param; + + param = malloc_nofail(sizeof *param); + *param = (struct cdecl) { + .next = d->u.function.parameters, + .declarators = gen_declarators(rng), + }; + + if (param->declarators->type == CDECL_DECL_ARRAY + || param->declarators->type == CDECL_DECL_IDENT) + flags |= GEN_NO_VOID; + + param->specifiers = gen_declspecs(rng, flags); + d->u.function.parameters = param; + } + + if (d->u.function.parameters) { + d->u.function.variadic = gsl_rng_uniform(rng->rng) < 0.5; + } +} + +/* + * Generate a random chain of declarators. Like qualifiers above, a chain of + * length N has probability 1/2^(N+1) of occurring. All declarator chains + * are ultimately terminated by either a null declarator or an identifier, + * selected uniformly at random. + */ +struct cdecl_declarator *gen_declarators(struct gen_rng *rng) +{ + struct cdecl_declarator *d, *p; + unsigned limit = 3; + + d = malloc_nofail(sizeof *d); + if (gsl_rng_uniform(rng->rng) < 0.5) { + *d = (struct cdecl_declarator) { + .type = CDECL_DECL_NULL, + }; + } else { + *d = (struct cdecl_declarator) { + .type = CDECL_DECL_IDENT, + .u.ident = gen_identifier(rng), + }; + } + + while (gsl_rng_uniform(rng->rng) < 0.5) { + p = d; + d = malloc_nofail(sizeof *d); + *d = (struct cdecl_declarator) { + .child = p, + }; + + switch (gsl_rng_uniform_int(rng->rng, limit)) { + case 0: + d->type = CDECL_DECL_POINTER; + d->u.pointer = (struct cdecl_pointer) { + .qualifiers = gen_qualifiers(rng, true), + }; + limit = 3; + break; + case 1: + gen_array(rng, d); + limit = 2; + break; + case 2: + gen_function(rng, d); + limit = 1; + break; + default: + assert(0); + } + } + + return d; +} + +struct gen_rng *gen_alloc_rng(const char *seed_str) +{ + unsigned long seed; + struct gen_rng *rng; + char *end; + + errno = 0; + seed = strtoul(seed_str, &end, 0); + if (*end != 0) { + fprintf(stderr, "%s: invalid seed\n", seed_str); + return NULL; + } else if (errno != 0) { + fprintf(stderr, "%s: invalid seed: %s\n", + seed_str, strerror(errno)); + return NULL; + } + + rng = malloc(sizeof *rng); + if (rng) { + rng->rng = gsl_rng_alloc(gsl_rng_mt19937); + gsl_rng_set(rng->rng, seed); + } + + return rng; +} diff --git a/test/declgen.h b/test/declgen.h new file mode 100644 index 0000000..159c794 --- /dev/null +++ b/test/declgen.h @@ -0,0 +1,21 @@ +#ifndef CDECL_DECLGEN_H_ +#define CDECL_DECLGEN_H_ + +struct gen_rng; + +enum { + GEN_NO_FUNCTION = 0x01, + GEN_NO_STORAGE = 0x02, + GEN_NO_VOID = 0x04, + GEN_ONLY_REGISTER = 0x08, +}; + +struct cdecl_declspec *gen_qualifiers(struct gen_rng *rng, _Bool pointer); +struct cdecl_declspec *gen_typespecs(struct gen_rng *rng, _Bool novoid); +struct cdecl_declspec *gen_randomize_specs(struct gen_rng *rng, + struct cdecl_declspec *specs); +struct cdecl_declspec *gen_declspecs(struct gen_rng *rng, unsigned flags); +struct cdecl_declarator *gen_declarators(struct gen_rng *rng); +struct gen_rng *gen_alloc_rng(const char *seed); + +#endif diff --git a/test/randomdecl.c b/test/randomdecl.c new file mode 100644 index 0000000..12728cc --- /dev/null +++ b/test/randomdecl.c @@ -0,0 +1,133 @@ +/* + * Generate random C declarations for testing. + * Copyright © 2011 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 +#include +#include +#include + +#include "declgen.h" +#include "test.h" + +#define PROGNAME "randomdecl" +static const char *progname = PROGNAME; +static const char sopts[] = "s:n:VH"; +static const struct option lopts[] = { + { "seed", 1, NULL, 's' }, + { "count", 1, NULL, 'n' }, + { "version", 0, NULL, 'V' }, + { "help", 0, NULL, 'H' }, + { 0 } +}; + +static void print_version() +{ + puts(PROGNAME " (" PACKAGE_NAME ") " PACKAGE_VERSION); + puts("Copyright (C) 2011 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."); +} + +static void print_usage(FILE *f) +{ + fprintf(f, "Usage: %s [options]\n", progname); +} + +static void print_help(void) +{ + print_usage(stdout); + puts("Detailed help coming soon."); +} + +static struct cdecl *random_decl(struct gen_rng *rng) +{ + struct cdecl *decl; + unsigned flags = 0; + + decl = malloc_nofail(sizeof *decl); + *decl = (struct cdecl) { 0 }; + + decl->declarators = gen_declarators(rng); + if (decl->declarators->type != CDECL_DECL_FUNCTION) + flags |= GEN_NO_FUNCTION; + if (cdecl_is_abstract(decl->declarators)) + flags |= GEN_NO_STORAGE | GEN_NO_FUNCTION; + if (decl->declarators->type == CDECL_DECL_ARRAY + || decl->declarators->type == CDECL_DECL_IDENT) + flags |= GEN_NO_VOID; + + decl->specifiers = gen_declspecs(rng, flags); + return decl; +} + +int main(int argc, char **argv) +{ + const char *seed = "", *count_str = NULL; + struct gen_rng *rng; + struct cdecl *decl; + unsigned long count = 0; + int opt; + + if (argc > 0) + progname = argv[0]; + + while ((opt = getopt_long(argc, argv, sopts, lopts, NULL)) != -1) { + switch (opt) { + case 's': + seed = optarg; + break; + case 'n': + count_str = optarg; + break; + case 'V': + print_version(); + return EXIT_SUCCESS; + case 'H': + print_help(); + return EXIT_SUCCESS; + default: + print_usage(stderr); + return EXIT_FAILURE; + } + } + + if (count_str && !strict_strtoul(&count, count_str, 0)) { + fprintf(stderr, "%s: invalid count: %s\n", progname, count_str); + return EXIT_FAILURE; + } + + rng = gen_alloc_rng(seed); + if (!rng) + return EXIT_FAILURE; + + for (unsigned long i = 0; !count || i < count; i++) { + decl = random_decl(rng); + if (!decl) + return EXIT_FAILURE; + + test_print_decl(decl); + } + + return EXIT_SUCCESS; +} diff --git a/test/test.h b/test/test.h new file mode 100644 index 0000000..dc661e3 --- /dev/null +++ b/test/test.h @@ -0,0 +1,17 @@ +#ifndef CDECL_TEST_H_ +#define CDECL_TEST_H_ + +#include +#include + +struct cdecl_declspec; +struct cdecl; + +void *malloc_nofail(size_t size); +void *realloc_nofail(void *ptr, size_t size); +void test_print_specifiers(struct cdecl_declspec *spec); +void test_print_decl(struct cdecl *decl); + +bool strict_strtoul(unsigned long *val, const char *str, int base); + +#endif diff --git a/test/testlib.c b/test/testlib.c new file mode 100644 index 0000000..e5143c2 --- /dev/null +++ b/test/testlib.c @@ -0,0 +1,71 @@ +/* + * Miscellaneous functions used by the cdecl99 test suite. + * Copyright © 2011 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 "test.h" + +void *realloc_nofail(void *ptr, size_t size) +{ + void *p; + + p = realloc(ptr, size); + if (!p) { + perror("failed to allocate memory"); + abort(); + } + + return p; +} + +void *malloc_nofail(size_t size) +{ + return realloc_nofail(NULL, size); +} + +void test_print_decl(struct cdecl *decl) +{ + static size_t bufsz; + static char *buf; + size_t rc; + +retry: + rc = cdecl_declare(buf, bufsz, decl); + if (rc >= bufsz) { + bufsz = rc + 1; + buf = realloc_nofail(buf, bufsz); + goto retry; + } + + printf("%s\n", buf); +} + +bool strict_strtoul(unsigned long *val, const char *str, int base) +{ + char *end; + + errno = 0; + *val = strtoul(str, &end, base); + if (errno != 0 || *end != 0) + return false; + + return true; +} diff --git a/test/typegen.sh b/test/typegen.sh new file mode 100755 index 0000000..1658d5d --- /dev/null +++ b/test/typegen.sh @@ -0,0 +1,57 @@ +#!/bin/sh +# +# 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. + +cat <