diff --git a/config.d/ac_config_files.m4 b/config.d/ac_config_files.m4 index 4c9142099..587a515d9 100644 --- a/config.d/ac_config_files.m4 +++ b/config.d/ac_config_files.m4 @@ -62,6 +62,7 @@ tools/qfcc/doc/man/Makefile tools/qfcc/include/Makefile tools/qfcc/source/Makefile + tools/qfcc/test/Makefile tools/qflight/Makefile tools/qflight/include/Makefile tools/qflight/source/Makefile diff --git a/config.d/build_control.m4 b/config.d/build_control.m4 index ff4b17d8e..c15bfad66 100644 --- a/config.d/build_control.m4 +++ b/config.d/build_control.m4 @@ -365,6 +365,12 @@ QF_DEPS(QFCC, $(top_builddir)/libs/util/libQFutil.la], [$(WIN32_LIBS)], ) +QF_DEPS(QFCC_TEST, + [], + [$(top_builddir)/libs/ruamoko/libQFruamoko.la + $(top_builddir)/libs/util/libQFutil.la], + [$(WIN32_LIBS)], +) QF_DEPS(QFLIGHT, [-I$(top_srcdir)/tools/qflight/include], [$(top_builddir)/libs/util/libQFutil.la], diff --git a/tools/qfcc/Makefile.am b/tools/qfcc/Makefile.am index e931fc675..b9050ce13 100644 --- a/tools/qfcc/Makefile.am +++ b/tools/qfcc/Makefile.am @@ -26,7 +26,7 @@ # AUTOMAKE_OPTIONS= foreign -SUBDIRS= include source doc +SUBDIRS= include source doc test dist-zip: distdir -chmod -R a+r $(distdir) diff --git a/tools/qfcc/test/Makefile.am b/tools/qfcc/test/Makefile.am new file mode 100644 index 000000000..7b62758e4 --- /dev/null +++ b/tools/qfcc/test/Makefile.am @@ -0,0 +1,43 @@ +AUTOMAKE_OPTIONS= foreign +INCLUDES= -I$(top_srcdir)/include $(QFCC_INCS) + +QFCC_DEP=$(builddir)/../source/qfcc$(EXEEXT) +QFCC=$(QFCC_DEP) + +QCFLAGS=-qq -g --no-default-paths +QCPPFLAGS= + +SUFFIXES=.qfo .r +.r.qfo: + $(QFCC) $(QCFLAGS) $(QCPPFLAGS) -c -o $@ $< + +QFCC_TEST_LIBS=@QFCC_TEST_LIBS@ +QFCC_TEST_DEPS=@QFCC_TEST_DEPS@ +QFCC_TEST_INCS=@QFCC_TEST_INCS@ + +test_progs_dat=structptr.dat while.dat + +TESTS=$(test_progs_dat:.dat=.run) + +check_PROGRAMS=test-harness $(test_progs_dat) + +test_harness_SOURCES= test-bi.c test-harness.c +test_harness_LDADD= $(QFCC_TEST_LIBS) +test_harness_DEPENDENCIES= $(QFCC_TEST_DEPS) + +structptr_src=structptr.r +structptr_obj=$(structptr_src:.r=.qfo) +structptr.dat: $(structptr_obj) $(QFCC_DEP) + $(QFCC) $(QCFLAGS) -o $@ $(structptr_obj) +structptr.run: Makefile build-run + $(srcdir)/build-run $@ + +while_src=while.r +while_obj=$(while_src:.r=.qfo) +while.dat: $(while_obj) $(QFCC_DEP) + $(QFCC) $(QCFLAGS) -o $@ $(while_obj) +while.run: Makefile build-run + $(srcdir)/build-run $@ + +EXTRA_DIST= test-bi.h $(structptr_src) $(while_src) +CLEANFILES= *.dat *.sym *.qfo *.run diff --git a/tools/qfcc/test/build-run b/tools/qfcc/test/build-run new file mode 100755 index 000000000..c141d6684 --- /dev/null +++ b/tools/qfcc/test/build-run @@ -0,0 +1,11 @@ +#! /bin/sh + +script=$1 +progs=`basename $script .run`.dat +shift + +cat > $script < + + Author: Bill Currie + Date: 2012/11/22 + + 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 2 + 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, write to: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + +*/ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include + +#include "QF/dstring.h" +#include "QF/progs.h" + +#include "test-bi.h" + +static void +bi_printf (progs_t *pr) +{ + const char *fmt = P_GSTRING (pr, 0); + int count = pr->pr_argc - 1; + pr_type_t **args = pr->pr_params + 1; + static dstring_t *dstr; + + if (!dstr) + dstr = dstring_new (); + + PR_Sprintf (pr, dstr, "bi_printf", fmt, count, args); + if (dstr->str) + fputs (dstr->str, stdout); +} + +static void +bi_errno (progs_t *pr) +{ + R_INT (pr) = errno; +} + +static void +bi_strerror (progs_t *pr) +{ + int err = P_INT (pr, 0); + RETURN_STRING (pr, strerror (err)); +} + +static builtin_t builtins[] = { + {"printf", bi_printf, -1}, + {"errno", bi_errno, -1}, + {"strerror", bi_strerror, -1}, + {0} +}; + +void +BI_Init (progs_t *pr) +{ + PR_RegisterBuiltins (pr, builtins); +} diff --git a/tools/qfcc/test/test-bi.h b/tools/qfcc/test/test-bi.h new file mode 100644 index 000000000..67e198434 --- /dev/null +++ b/tools/qfcc/test/test-bi.h @@ -0,0 +1 @@ +void BI_Init (struct progs_s *pr); diff --git a/tools/qfcc/test/test-harness.c b/tools/qfcc/test/test-harness.c new file mode 100644 index 000000000..522c709e1 --- /dev/null +++ b/tools/qfcc/test/test-harness.c @@ -0,0 +1,255 @@ +/* + test-harness.c + + Program for testing qfcc generated code. + + Copyright (C) 2012 Bill Currie + + Author: Bill Currie + Date: 2012/11/22 + + 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 2 + 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, write to: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + +*/ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +#include +#include +#include +#include +#include "QF/ruamoko.h" +#include +#include "QF/va.h" +#include + +#include "test-bi.h" + +#define MAX_EDICTS 64 // 64 edicts should be enough for testing +#define MAX_HEAP 1024*1024 // 1MB should be enough for testing + +// keep me sane when adding long options :P +enum { + start_opts = 255, // not used, starts the enum. + OPT_DEVELOPER, +}; + +static struct option const long_options[] = { + {"developer", required_argument, 0, OPT_DEVELOPER}, + {"trace", no_argument, 0, 't'}, + {"help", no_argument, 0, 'h'}, + {"version", no_argument, 0, 'V'}, +}; + +static const char *short_options = + "+" // magic option parsing mode doohicky (must come first) + "h" // help + "t" // tracing + "V" // version + ; + +static edict_t *edicts; +static int num_edicts; +static int reserved_edicts; +static progs_t pr; +static const char *this_program; + +static struct { + int developer; + int trace; +} options; + +static QFile * +open_file (const char *path, int *len) +{ + QFile *file = Qopen (path, "rbz"); + + if (!file) { + perror (path); + return 0; + } + *len = Qfilesize (file); + return file; +} + +static void * +load_file (progs_t *pr, const char *name) +{ + QFile *file; + int size; + char *sym; + + file = open_file (name, &size); + if (!file) { + file = open_file (va ("%s.gz", name), &size); + if (!file) { + return 0; + } + } + sym = malloc (size + 1); + sym[size] = 0; + Qread (file, sym, size); + return sym; +} + +static void * +allocate_progs_mem (progs_t *pr, int size) +{ + return malloc (size); +} + +static void +free_progs_mem (progs_t *pr, void *mem) +{ + free (mem); +} + +static void +init_qf (void) +{ + Sys_Init (); + //Cvar_Get ("developer", "128", 0, 0, 0); + + Memory_Init (malloc (1024 * 1024), 1024 * 1024); + + Cvar_Get ("pr_debug", "2", 0, 0, 0); + Cvar_Get ("pr_boundscheck", "0", 0, 0, 0); + + pr.edicts = &edicts; + pr.num_edicts = &num_edicts; + pr.reserved_edicts = &reserved_edicts; + pr.load_file = load_file; + pr.allocate_progs_mem = allocate_progs_mem; + pr.free_progs_mem = free_progs_mem; + pr.no_exec_limit = 1; + + PR_Init_Cvars (); + PR_Init (); + RUA_Init (&pr, 0); + PR_Cmds_Init(&pr); + BI_Init (&pr); +} + +static int +load_progs (const char *name) +{ + QFile *file; + int size; + + file = open_file (name, &size); + if (!file) { + return 0; + } + pr.progs_name = name; + PR_LoadProgsFile (&pr, file, size, 1, 1024 * 1024); + Qclose (file); + if (!PR_RunLoadFuncs (&pr)) + PR_Error (&pr, "unable to load %s", pr.progs_name); + return 1; +} + +static void +usage (int exitval) +{ + printf ("%s - QuakeForge VM test harness\n", this_program); + printf ("Usage: %s [options] [progs.dat [progs options]]", this_program); + printf ( +"Options:\n" +" --developer FLAGS Set the developer cvar to FLAGS.\n" +" -h, --help Display this help and exit\n" +" -t, --trace Set the trace flag in the VM.\n" +" -V, --version Output version information and exit\n" + ); + exit (exitval); +} + +static int +parse_options (int argc, char **argv) +{ + int c; + + this_program = argv[0]; + while ((c = getopt_long (argc, argv, short_options, long_options, 0)) + != -1) { + switch (c) { + case OPT_DEVELOPER: + options.developer = atoi (optarg); + break; + case 't': + options.trace = 1; + break; + case 'h': + usage (0); + break; + case 'V': + printf ("%s version %s\n", PACKAGE_NAME, PACKAGE_VERSION); + exit (0); + default: + usage (1); + } + } + return optind; +} + +int +main (int argc, char **argv) +{ + dfunction_t *dfunc; + func_t main_func = 0; + const char *name = "progs.dat"; + string_t *pr_argv; + int pr_argc = 1, i; + + i = parse_options (argc, argv); + argc -= i; + argv += i; + + init_qf (); + + if (argc > 0) + name = argv[0]; + + if (!load_progs (name)) + Sys_Error ("couldn't load %s", name); + + PR_PushFrame (&pr); + if (argc > 2) + pr_argc = argc - 1; + pr_argv = PR_Zone_Malloc (&pr, (pr_argc + 1) * 4); + pr_argv[0] = PR_SetTempString (&pr, name); + for (i = 1; i < pr_argc; i++) + pr_argv[i] = PR_SetTempString (&pr, argv[1 + i]); + pr_argv[i] = 0; + + if ((dfunc = PR_FindFunction (&pr, ".main")) + || (dfunc = PR_FindFunction (&pr, "main"))) + main_func = dfunc - pr.pr_functions; + else + PR_Undefined (&pr, "function", "main"); + PR_RESET_PARAMS (&pr); + P_INT (&pr, 0) = pr_argc; + P_POINTER (&pr, 1) = PR_SetPointer (&pr, pr_argv); + PR_ExecuteProgram (&pr, main_func); + PR_PopFrame (&pr); + return R_INT (&pr); +}