]> git.draconx.ca Git - cdecl99.git/commitdiff
Add a tool to generate random declarations.
authorNick Bowler <nbowler@draconx.ca>
Fri, 24 Feb 2012 03:14:46 +0000 (22:14 -0500)
committerNick Bowler <nbowler@draconx.ca>
Thu, 1 Mar 2012 01:16:58 +0000 (20:16 -0500)
The first of (hopefully) many components of a test suite.

13 files changed:
Makefile.am
configure.ac
m4/cstring.m4 [new file with mode: 0644]
m4/dxutils.m4 [new file with mode: 0644]
m4/gsl.m4 [new file with mode: 0644]
m4/strings.m4 [new file with mode: 0644]
test/.gitignore [new file with mode: 0644]
test/declgen.c [new file with mode: 0644]
test/declgen.h [new file with mode: 0644]
test/randomdecl.c [new file with mode: 0644]
test/test.h [new file with mode: 0644]
test/testlib.c [new file with mode: 0644]
test/typegen.sh [new file with mode: 0755]

index 469f962de2953bbdc9e49d5be50902c7ceac70ab..5781b234e1f8fac84e0e5efc623b218309510129 100644 (file)
@@ -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)
index dcb55043595271729efff47c697121f8d311ef3b..f0b606f1aebc244e5d21ec5d79570012fe30977c 100644 (file)
@@ -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 <cfgtail.h>])
 
+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 (file)
index 0000000..9973f68
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+$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 (file)
index 0000000..2f976d3
--- /dev/null
@@ -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 (file)
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/gsl_version.h>
+], [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 <gsl/gsl_matrix.h>
+#include <gsl/gsl_blas.h>
+], [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 (file)
index 0000000..6b0f269
--- /dev/null
@@ -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 (file)
index 0000000..7a3928c
--- /dev/null
@@ -0,0 +1,2 @@
+typegen.h
+randomdecl
diff --git a/test/declgen.c b/test/declgen.c
new file mode 100644 (file)
index 0000000..b9fb733
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <stdbool.h>
+#include <gsl/gsl_rng.h>
+
+#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 (file)
index 0000000..159c794
--- /dev/null
@@ -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 (file)
index 0000000..12728cc
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <getopt.h>
+#include <cdecl.h>
+
+#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 <http://gnu.org/licenses/gpl.html>.");
+       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 (file)
index 0000000..dc661e3
--- /dev/null
@@ -0,0 +1,17 @@
+#ifndef CDECL_TEST_H_
+#define CDECL_TEST_H_
+
+#include <stddef.h>
+#include <stdbool.h>
+
+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 (file)
index 0000000..e5143c2
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <cdecl.h>
+#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 (executable)
index 0000000..1658d5d
--- /dev/null
@@ -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 <<EOF
+/* This file was automatically generated by $0 $*. */
+struct cdecl_declspec *s, *p;
+EOF
+
+i=0
+while read line
+do
+       case "$line" in
+       [[:alpha:]_]*)
+               :
+               ;;
+       *)
+               printf '%s\n' "$line";
+               continue
+               ;;
+       esac
+
+       cat <<EOF
+case $i:
+       s = NULL;
+EOF
+
+       set $line
+       while test $# -gt 0
+       do
+               token=`printf '%s' "$1" | tr [:lower:] [:upper:] | tr -d _`
+               cat <<EOF
+       p = s;
+       s = malloc_nofail(sizeof *s);
+       *s = (struct cdecl_declspec) {
+               .type = CDECL_TYPE_$token,
+               .next = p,
+       };
+EOF
+               shift
+       done
+
+       i=`expr $i + 1`
+       cat <<EOF
+       return s;
+EOF
+done
+
+cat <<EOF
+default:
+       assert(0);
+#define TOTAL_TYPES $i
+EOF