]> git.xonotic.org Git - xonotic/gmqcc.git/commitdiff
Merge branch 'master' into cleanup
authorDale Weiler <weilercdale@gmail.com>
Sun, 3 Jan 2016 03:30:35 +0000 (22:30 -0500)
committerDale Weiler <weilercdale@gmail.com>
Sun, 3 Jan 2016 03:30:35 +0000 (22:30 -0500)
106 files changed:
.travis.yml
BSDmakefile [deleted file]
CHANGES [deleted file]
INSTALL [deleted file]
Makefile
PORTING [deleted file]
README
TODO [deleted file]
algo.h [new file with mode: 0644]
ansi.c [deleted file]
ast.c [deleted file]
ast.cpp [new file with mode: 0644]
ast.h
code.c [deleted file]
code.cpp [new file with mode: 0644]
conout.c [deleted file]
conout.cpp [new file with mode: 0644]
correct.c [deleted file]
distro/Makefile [deleted file]
distro/archbsd/git/PKGBUILD [deleted file]
distro/archbsd/release/PKGBUILD [deleted file]
distro/archbsd/this/Makefile [deleted file]
distro/archlinux/git/PKGBUILD [deleted file]
distro/archlinux/release/PKGBUILD [deleted file]
distro/archlinux/this/Makefile [deleted file]
distro/deb/Makefile [deleted file]
distro/fedora/spec/INSTALL [deleted file]
distro/fedora/spec/gmqcc.spec [deleted file]
distro/fedora/this/Makefile [deleted file]
distro/gentoo/INSTALL [deleted file]
distro/gentoo/gmqcc-0.3.0.ebuild [deleted file]
distro/slackware/this/Makefile [deleted file]
distro/slackware/this/slack-desc [deleted file]
distro/win32/Makefile [deleted file]
distro/win64/Makefile [deleted file]
doc/gmqpak.1 [deleted file]
doc/html/download.c [deleted file]
doc/html/gmqcc.png [deleted file]
doc/html/style.css [deleted file]
doc/specification.tex [deleted file]
exec.c [deleted file]
exec.cpp [new file with mode: 0644]
fold.c [deleted file]
fold.cpp [new file with mode: 0644]
fold.h [new file with mode: 0644]
fs.c [deleted file]
ftepp.c [deleted file]
ftepp.cpp [new file with mode: 0644]
gmqcc.h
hash.c [deleted file]
include.mk [deleted file]
intrin.c [deleted file]
intrin.cpp [new file with mode: 0644]
intrin.h [new file with mode: 0644]
ir.c [deleted file]
ir.cpp [new file with mode: 0644]
ir.h
lexer.c [deleted file]
lexer.cpp [new file with mode: 0644]
lexer.h
main.c [deleted file]
main.cpp [new file with mode: 0644]
misc/check-proj.sh [deleted file]
misc/nexuiz_export.sh [deleted file]
misc/xonotic_export.sh [deleted file]
msvc.c [deleted file]
msvc/gmqcc.sln [deleted file]
msvc/gmqcc/gmqcc.vcxproj [deleted file]
msvc/gmqcc/gmqcc.vcxproj.filters [deleted file]
msvc/pak/pak.vcxproj [deleted file]
msvc/pak/pak.vcxproj.filters [deleted file]
msvc/qcvm/qcvm.vcxproj [deleted file]
msvc/qcvm/qcvm.vcxproj.filters [deleted file]
msvc/testsuite/testsuite.vcxproj [deleted file]
msvc/testsuite/testsuite.vcxproj.filters [deleted file]
opts.c [deleted file]
opts.cpp [new file with mode: 0644]
opts.def
pak.c [deleted file]
parser.c [deleted file]
parser.cpp [new file with mode: 0644]
parser.h
platform.h [deleted file]
stat.c [deleted file]
stat.cpp [new file with mode: 0644]
syntax/README [deleted file]
syntax/geany/README [deleted file]
syntax/geany/filetypes.qc [deleted file]
syntax/gtksourceview/README [deleted file]
syntax/gtksourceview/qc.lang [deleted file]
syntax/jedit/README [deleted file]
syntax/jedit/qc.xml [deleted file]
syntax/kate/README [deleted file]
syntax/kate/qc.xml [deleted file]
syntax/nano/README [deleted file]
syntax/nano/qc.nanorc [deleted file]
test.c [deleted file]
test.cpp [new file with mode: 0644]
tests/fieldfuncs.qc [new file with mode: 0644]
tests/fieldfuncs.tmpl [new file with mode: 0644]
tests/forloop.qc [new file with mode: 0644]
tests/forloop.tmpl [new file with mode: 0644]
utf8.c [deleted file]
utf8.cpp [new file with mode: 0644]
util.c [deleted file]
util.cpp [new file with mode: 0644]

index 4b6f9595939150d67b64c7380bbc0bce022376ad..830f373cebfae37fa14ed957173b775db810b39d 100644 (file)
@@ -1,9 +1,27 @@
-language: c
+language: cpp
+
 compiler:
   - gcc
   - clang
-# Change this to your needs
-script: make && make check
+
+before_install:
+  # g++4.8.1
+  - if [ "$CXX" == "g++" ]; then sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test; fi
+  # clang 3.4
+  - if [ "$CXX" == "clang++" ]; then sudo add-apt-repository -y ppa:h-rayflood/llvm; fi
+  - sudo apt-get update -qq
+
+install:
+  # g++4.8.1
+  - if [ "$CXX" = "g++" ]; then sudo apt-get install -qq g++-4.8; fi
+  - if [ "$CXX" = "g++" ]; then export CXX="g++-4.8"; fi
+  # clang 3.4
+  - if [ "$CXX" == "clang++" ]; then sudo apt-get install --allow-unauthenticated -qq clang-3.4; fi
+  - if [ "$CXX" == "clang++" ]; then export CXX="clang++-3.4"; fi
+
+script:
+    - make test
+
 notifications:
     irc:
         channels:
diff --git a/BSDmakefile b/BSDmakefile
deleted file mode 100644 (file)
index 44a742a..0000000
+++ /dev/null
@@ -1,130 +0,0 @@
-#
-# This is the Makefile for the BSD flavor
-#
-.include "include.mk"
-
-GITTEST  != git describe --always 2>/dev/null
-VALTEST  != valgrind --version 2>/dev/null
-GITINFO  :=
-
-.if $(GITTEST)
-    GITINFO != git describe --always
-.endif
-
-.if $(CC) == clang
-    CFLAGS +=   -Weverything\
-                -Wno-padded\
-                -Wno-format-nonliteral\
-                -Wno-disabled-macro-expansion\
-                -Wno-conversion\
-                -Wno-float-equal\
-                -Wno-unknown-warning-option\
-                -Wno-cast-align\
-                -Wno-assign-enum\
-                -Wno-empty-body\
-                -Wno-date-time\
-                -pedantic-errors
-.else
-.    if $(CC) != g++
-       CFLAGS += -Wmissing-prototypes -Wstrict-prototypes
-.    endif
-
-.    if $(CC) != tcc
-       CFLAGS += -pedantic-errors
-.    else
-       CFLAGS += -Wno-pointer-sign -fno-common
-.    endif
-.endif
-
-.if !$(VALTEST)
-    CFLAGS += -DNVALGRIND
-.endif
-
-
-CFLAGS += -DGMQCC_GITINFO=\"$(GITINFO)\" $(OPTIONAL)
-DEPS != for i in $(OBJ_C) $(OBJ_P) $(OBJ_T) $(OBJ_X); do echo $$i; done | sort | uniq
-
-QCVM      = qcvm
-GMQCC     = gmqcc
-TESTSUITE = testsuite
-PAK       = gmqpak
-
-#standard rules
-c.o: ${.IMPSRC}
-       $(CC) -c ${.IMPSRC} -o ${.TARGET} $(CFLAGS) $(CPPFLAGS)
-
-$(QCVM): $(OBJ_X)
-       $(CC) -o ${.TARGET} ${.IMPSRC} $(LDFLAGS) $(LIBS) $(OBJ_X)
-
-$(GMQCC): $(OBJ_C)
-       $(CC) -o ${.TARGET} ${.IMPSRC} $(LDFLAGS) $(LIBS) $(OBJ_C)
-
-$(TESTSUITE): $(OBJ_T)
-       $(CC) -o ${.TARGET} ${.IMPSRC} $(LDFLAGS) $(LIBS) $(OBJ_T)
-
-$(PAK): $(OBJ_P)
-       $(CC) -o ${.TARGET} ${.IMPSRC} $(LDFLAGS) $(OBJ_P)
-
-all: $(GMQCC) $(QCVM) $(TESTSUITE) $(PAK)
-
-check: all
-       @ ./$(TESTSUITE)
-test: all
-       @ ./$(TESTSUITE)
-
-strip: $(GMQCC) $(QCVM) $(TESTSUITE)
-       strip $(GMQCC)
-       strip $(QCVM)
-       strip $(TESTSUITE)
-
-clean:
-       rm -rf *.o $(GMQCC) $(QCVM) $(TESTSUITE) $(PAK) *.dat gource.mp4 *.exe gm-qcc.tgz ./cov-int
-
-depend:
-       @makedepend -Y -f BSDmakefile -w 65536 2> /dev/null ${DEPS:C/\.o/.c/g}
-
-coverity:
-       @cov-build --dir cov-int $(MAKE) -f BSDmakefile
-       @tar czf gm-qcc.tgz cov-int
-       @rm -rf cov-int
-       @echo gm-qcc.tgz generated, submit for analysis
-
-install: install-gmqcc install-qcvm install-gmqpak install-doc
-install-gmqcc: $(GMQCC)
-       install -d -m755              $(DESTDIR)$(BINDIR)
-       install    -m755 $(GMQCC)     $(DESTDIR)$(BINDIR)/$(GMQCC)
-install-qcvm: $(QCVM)
-       install -d -m755              $(DESTDIR)$(BINDIR)
-       install    -m755 $(QCVM)      $(DESTDIR)$(BINDIR)/$(QCVM)
-install-gmqpak: $(PAK)
-       install -d -m755              $(DESTDIR)$(BINDIR)
-       install    -m755 $(PAK)       $(DESTDIR)$(BINDIR)/$(PAK)
-install-doc:
-       install -d -m755              $(DESTDIR)$(MANDIR)/man1
-       install    -m644 doc/gmqcc.1  $(DESTDIR)$(MANDIR)/man1/
-       install    -m644 doc/qcvm.1   $(DESTDIR)$(MANDIR)/man1/
-       install    -m644 doc/gmqpak.1 $(DESTDIR)$(MANDIR)/man1/
-
-# DO NOT DELETE
-
-ansi.o: platform.h gmqcc.h opts.def
-ast.o: gmqcc.h opts.def ast.h ir.h parser.h lexer.h
-code.o: gmqcc.h opts.def
-conout.o: gmqcc.h opts.def
-correct.o: gmqcc.h opts.def
-exec.o: gmqcc.h opts.def
-fold.o: ast.h ir.h gmqcc.h opts.def parser.h lexer.h
-fs.o: gmqcc.h opts.def platform.h
-ftepp.o: gmqcc.h opts.def lexer.h
-hash.o: gmqcc.h opts.def
-intrin.o: parser.h gmqcc.h opts.def lexer.h ast.h ir.h
-ir.o: gmqcc.h opts.def ir.h
-lexer.o: gmqcc.h opts.def lexer.h
-main.o: gmqcc.h opts.def lexer.h
-opts.o: gmqcc.h opts.def
-pak.o: gmqcc.h opts.def
-parser.o: parser.h gmqcc.h opts.def lexer.h ast.h ir.h
-stat.o: gmqcc.h opts.def
-test.o: gmqcc.h opts.def platform.h
-utf8.o: gmqcc.h opts.def
-util.o: gmqcc.h opts.def platform.h
diff --git a/CHANGES b/CHANGES
deleted file mode 100644 (file)
index 1dfa59d..0000000
--- a/CHANGES
+++ /dev/null
@@ -1,195 +0,0 @@
-Release 0.3.5:
-    * Preprocessor:
-        - Added warning for when preprocessor directives are used in
-          a macro.
-    * Language:
-        - Added vector bit operations.
-        - Added vector cross product operator `><` for -std=gmqcc.
-        - Added `[[eraseable]]` and `[[accumulation]]` attributes.
-        - Removed &~= operator for -std=gmqcc.
-        - #pragmas which are ignored are actually ignored to EOL.
-    * Compilation:
-        - Fixed constant folding.
-        - Fixed column printing in diagnostics.
-        - Added support for columns in LNOF.
-        - Shadowed locals fix.
-        - Improved warning diagnostics for precedence usage and other
-          various diagnostics.
-        - Added constant folding logic for builtin functions.
-        - Prevent divide by zero in constant folding.
-        - Added unary elision optimization, cases where a unary expression
-          cancels itself out, e.g `-(-a)` now simplifies to `a`.
-        - Added strength reduce optimization, for divisions e.g `(A / 10)`
-          now strength reduces to `(A * (1 / 10)` where `(1 / 10)` is
-          evaluated at compile time.
-        - Added superfluous expression elision optimization, e.g `(A + 0),
-          (A - 0), (A * 1), (A / 1)` can be simplified to just `A`.
-        - Quake world now compiles with -std=qcc
-        - Constant folding for `if than` now works for ternary expressions
-          too.
-        - Fixed `[[alias]]` attribute type checking, now you can't alias
-          invalid types.
-    * QCVM:
-        - Properly exits on error now.
-    * Lexer:
-        - Now prints the character the lexer fails on instead of the
-          token value.
-    * Testsuite:
-        - Important fixes to the testsuite (removal of false positives
-          and false negitives).
-        - Added a new utility `check-proj.sh` which downloads various Quake
-          mods and attempts to compile them.
-    * Commandline:
-        - Made -fshort-logic, -funtyped-nil and -fvariadic-args default
-          for -std=gmqcc.
-    * Build:
-        - Generate PDFs of man documents for Windows releases.
-        - Archlinux/Archbsd package now generates MTREE.
-    * Documentation:
-        - Fixed mdoc(s).
-    * Misc:
-        - Added some export scripts for xonotic and nexuiz which generate
-          specially crafted packages that are used by the check-proj script.
-
-2013-08-20 v0.3.0
-    * Language:
-        - Return assignments, the ability to assign to the return keyword
-          as if it were a local variable.
-        - Added bitwise XOR operator (^)
-        - Array initializers: e.g float a[] = {1, 2, 3};
-        - Fix bug that dissalowed language operators to be used in strings.
-    * Compilation:
-        - Optimized memory usage (now uses on average %3 less memory for
-          compilation).
-        - Fixed dotranslate (translatable strings)
-        - Rewrote constant folding optimization pass for the parser.
-        - New additional dead-code-elimination-consatant-fold pass for
-          if statements whos expression can be evaluated at compile-time
-          (allowing the if/else branch to be entierly elided at compile-time).
-        - Added support for columns in error diagnostics.
-        - Limit corrector to <= 16 byte strings.
-        - Improved hash function for hashtable (old hash function had 15% error,
-          this speeds up compilation)
-        - Improved performance of in-house allocator with branch-hinting, speeds
-          up compilation of Xonotic by 3 seconds!
-    * QCVM:
-        - Escape strings for -printdefs
-    * Commandline:
-        - Added statistic dumps, gives information about the number of used
-          hashtables, vectors, and number of unique sizes of vectors and
-          hashtables. The amount of memory used for vectors. As well as the
-          number of strdups used in total for compilation.
-        - Added compile statistic dumps, gives information about the compiled
-          binary, and LNO, such as the size, CRC, the number of times a
-          specific optimization was applied, etc.
-        - Make -std=qcc default.
-    * Testsuite:
-        - Fixed a floating point exception rasied by modulo operation in
-          -memchk.
-        - Added support for the test-suite to source tests and task-template
-          files from subdirectories in the test/ directory.
-        - Now prints the number of failed tests (if any) after all tests
-          are attempted.
-        - Fixed some bugs with error handling resulting in false-positives.
-    * Build:
-        - Can now be compiled with TCC (Tiny C compiler) and function as
-          intended (previously couldn't due to bug in TCC codegen).
-        - Added Gentoo ebuilds.
-        - Added Win32 Makefile for building Win32 packages.
-        - Added Slackware pkg build files.
-        - Added Fedora spec files.
-        - Added Makefile for the BSD make variant.
-    * Misc:
-        - Added valgrind memcheck hook to in-house allocator to help aid
-          in finding invalid reads/writes, and more accurate leaks.
-
-2012-04-27 v0.2.9
-    * Preprocessor:
-        - __VA_ARGS__ support
-        _ __VA_ARGS__ indexing
-        - Predefined macros like __DATE__, __TIME__, ...
-          (check the manpage for a full list).
-        - Signed numbers as single token in the.
-        - Fixes some issues with #if operations on macros.
-        - Speed improvements.
-    * Language:
-        - Untyped `nil` keyword.
-        - Removed the `noreturn` keyword.
-        - Added generic attribute syntax and reintroduced `noreturn`
-          as [[noreturn]].
-        - Added [[deprecated]] and [[deprecated("message")]].
-        - Support for `static` variables in functions.
-        - Support for labeled loops.
-        - UTF-8 Support.
-        - enum support: without enum-types
-          (ie no `typedef enum { } foo;`).
-        - Accessing vector components via the dot operator on all
-          expressions. Eg: `(3 * v).y`.
-        - Type restricted variadict parameters:
-          ie: `void print(string...);`.
-        - Accessing varargs from QC via: `...(index, type)`
-        - New operators: ** (exponentiation), % (modulo), etc.
-        - Enumeration attributes: `flag`, `reverse`.
-    * Compilation:
-        - Various optimizations and progs-size reductions.
-        - A new spell-checking algorithm tries to hint you at existing
-          variables on error.
-        - Some problems with VM related vector-instructions issues
-          have been solved in both DP and our own executor. A new
-          compatbility option (enabled by default) has been added for
-          now: -flegacy-vector-maths.
-        - Compiler intrinsics: __builtin_floor, __builtin_mod,
-          __builtin_exp, __builtin_isnan.
-        - Improved memory tracing.
-        - Speed improvements.
-    * QCVM:
-        - Improved commandline argument handling.
-        - More builtins: sqrt(), normalize(), floor().
-    * Commandline:
-        - Nicer memory dumps.
-        - Support for making individual warnings an error
-        - via -Werror-<warning>.
-        - added --add-info.
-    * Testsuite:
-        - Support for QCFLAGS to run tests with several additional
-          flags.
-        - Added support for preprocessor tests.
-        - Added preprocessor tests.
-        - Added defs.qh (auto included) for qcvm definitions.
-    * Syntax Highlighting:
-        - Added various syntax highlighting description files for
-          various text editors / integrated development envirorments,
-          including support for: geany, kate, kwrite, kdevelop, QtCreator,
-          gtksourceview, gedit, sany, nano, jedit.
-    * Build:
-        - Build scripts for building debian, archlinux and archbsd
-          packages for x86, and x86_64.
-        - Makefile targets for gource visualization, and render of
-          gource visualization.
-
-
-2012-12-27 Hotfix v0.2.2
-    * Liferanges
-    * Crashes
-
-2012-12-23 Hotfix v0.2.1
-    * General bugfixes
-
-2012-12-23 Release 0.2
-    * Preprocessor:
-        - Added xonotic compatible preprocessor.
-    * Language
-        - Basic xonotic compatibility.
-        - Array support.
-        - Added fteqcc's string escape sequences.
-        - Support for `noref`.
-        - Support for `goto` with labels like in fteqcc.
-        - `break` and `continue`.
-        - Short circuit logic.
-        - Support for translatable strings via _("str") like in
-          fteqcc.
-    * Compilation
-        - Warnings about uninitialized values.
-
-2012-11-17 Release 0.1
-    * Compiles id1 code.
diff --git a/INSTALL b/INSTALL
deleted file mode 100644 (file)
index ffd45bd..0000000
--- a/INSTALL
+++ /dev/null
@@ -1,62 +0,0 @@
-                           Installing gmqcc
-
-1. Prerequisites
-    - A C-Compiler such as gcc or clang
-    - GNU Make or BSD Make
-
-2. Compilation
-    If using GNU make program
-        make
-
-    If using BSD make program, the BSDmakefile should be
-    used instead when you invoke make, if however it ignores BSDmakefile
-    you can supply it with -f.
-
-        make -f BSDmakefile
-
-    If no error appears, the following binary files will have been
-    created:
-        - gmqcc
-        - qcvm
-        - gmqpak
-
-3. Installation
-    The `install' target will install the 2 binaries to /usr/local/bin
-    by default.
-    The Makefile honors the following variables:
-
-        - DESTDIR: The installation directory root.
-        - PREFIX:  The installation prefix, default: /usr/local
-        - BINDIR:  Directory for binary executables,
-                   deafult: $PREFIX/bin
-
-    To install to /usr/local run:
-
-        make install
-
-    To install to /usr run:
-
-        make PREFIX=/usr install
-
-    To install to a package-staging directory such as $pkgdir when
-    writing a build script file:
-
-        make DESTDIR=$pkgdir install
-
-
-    ArchLinux PKGBUILDs (release and git build) can be found in the
-    respective folders in ./distro/archlinux
-    
-    ArchBSD PKGBUILDs (release and git build) can be found in the
-    respective folders in ./distro/archbsd
-
-    Slackware SlackBuilds (git build) can be found in ./distro/slackware
-    
-    Gentoo ebuilds (release) can be found in ./distro/gentoo, as well
-    as a README explaining how to build them
-    
-    Debian archives (git build) can be created invoking make in
-    ./distro/deb
-
-    Fedora spec files (release) can be found in ./distro/fedora, as well
-    as a README explaining how to build them.
index 530bd4cd4ea42249b3315b14951ec65724b3e56a..c6a6327ed02269a35d0146662a6edac6007ccbbc 100644 (file)
--- a/Makefile
+++ b/Makefile
-include include.mk
-
-UNAME  ?= $(shell uname)
-CYGWIN  = $(findstring CYGWIN, $(UNAME))
-MINGW   = $(findstring MINGW,  $(UNAME))
-
-# turn on tons of warnings if clang is present
-# but also turn off the STUPID ONES
-ifeq ($(CC), clang)
-       CFLAGS +=                              \
-           -Weverything                       \
-           -Wno-padded                        \
-           -Wno-format-nonliteral             \
-           -Wno-disabled-macro-expansion      \
-           -Wno-conversion                    \
-           -Wno-float-equal                   \
-           -Wno-unknown-warning-option        \
-           -Wno-cast-align                    \
-           -Wno-assign-enum                   \
-           -Wno-empty-body                    \
-           -Wno-date-time                     \
-           -pedantic-errors
-else
-       ifneq ($(CC), g++)
-               CFLAGS += -Wmissing-prototypes -Wstrict-prototypes
-       endif
-
-       ifneq ($(CC), tcc)
-               CFLAGS += -pedantic-errors
-       else
-               CFLAGS += -Wno-pointer-sign -fno-common
-       endif
-endif
-
-ifneq ($(shell git describe --always 2>/dev/null),)
-       CFLAGS += -DGMQCC_GITINFO="\"$(shell git describe --always)\""
-endif
-
-ifeq ($(shell valgrind --version 2>/dev/null),)
-       CFLAGS += -DNVALGRIND
-endif
-
-# do this last otherwise there is whitespace in the command output and
-# it makes my OCD act up
-CFLAGS += $(OPTIONAL_CFLAGS)
-LDFLAGS += $(OPTIONAL_LDFLAGS)
-
-#we have duplicate object files when dealing with creating a simple list
-#for dependinces. To combat this we use some clever recrusive-make to
-#filter the list and remove duplicates which we use for make depend
-RMDUP = $(if $1,$(firstword $1) $(call RMDUP,$(filter-out $(firstword $1),$1)))
-DEPS := $(call RMDUP, $(OBJ_P) $(OBJ_T) $(OBJ_C) $(OBJ_X))
-
-ifneq ("$(CYGWIN)", "")
-       #nullify the common variables that
-       #most *nix systems have (for windows)
-       PREFIX   :=
-       BINDIR   :=
-       DATADIR  :=
-       MANDIR   :=
-       QCVM      = qcvm.exe
-       GMQCC     = gmqcc.exe
-       TESTSUITE = testsuite.exe
-       PAK       = gmqpak.exe
-       CFLAGS   += -DNVALGRIND
-else
-ifneq ("$(MINGW)", "")
-       #nullify the common variables that
-       #most *nix systems have (for windows)
-       PREFIX   :=
-       BINDIR   :=
-       DATADIR  :=
-       MANDIR   :=
-       QCVM      = qcvm.exe
-       GMQCC     = gmqcc.exe
-       TESTSUITE = testsuite.exe
-       PAK       = gmqpak.exe
-       CFLAGS   += -DNVALGRIND
-else
-       QCVM      = qcvm
-       GMQCC     = gmqcc
-       TESTSUITE = testsuite
-       PAK       = gmqpak
-endif
-endif
-
-#standard rules
-c.o:
-       $(CC) -c $< -o $@ $(CFLAGS) $(CPPFLAGS)
-
-$(QCVM): $(OBJ_X)
-       $(CC) -o $@ $^ $(LDFLAGS) $(LIBS)
-
-$(GMQCC): $(OBJ_C) $(OBJ_D)
-       $(CC) -o $@ $^ $(LDFLAGS) $(LIBS)
-
-$(TESTSUITE): $(OBJ_T)
-       $(CC) -o $@ $^ $(LDFLAGS) $(LIBS)
-
-$(PAK): $(OBJ_P)
-       $(CC) -o $@ $^ $(LDFLAGS)
-
-all: $(GMQCC) $(QCVM) $(TESTSUITE) $(PAK)
-
-check: all
-       @ ./$(TESTSUITE)
-test: all
-       @ ./$(TESTSUITE)
-
-strip: $(GMQCC) $(QCVM) $(TESTSUITE)
-       strip $(GMQCC)
-       strip $(QCVM)
-       strip $(TESTSUITE)
+CXX ?= clang++
+CXXFLAGS = \
+       -std=c++11 \
+       -Wall \
+       -Wextra \
+       -fno-exceptions \
+       -fno-rtti \
+       -MD \
+       -g3
+
+CSRCS = \
+       ast.cpp \
+       code.cpp \
+       conout.cpp \
+       fold.cpp \
+       ftepp.cpp \
+       intrin.cpp \
+       ir.cpp \
+       lexer.cpp \
+       main.cpp \
+       opts.cpp \
+       parser.cpp \
+       stat.cpp \
+       utf8.cpp \
+       util.cpp
+
+TSRCS = \
+       conout.cpp \
+       opts.cpp \
+       stat.cpp \
+       test.cpp \
+       util.cpp
+
+VSRCS = \
+       exec.cpp \
+       stat.cpp \
+       util.cpp
+
+COBJS = $(CSRCS:.cpp=.o)
+TOBJS = $(TSRCS:.cpp=.o)
+VOBJS = $(VSRCS:.cpp=.o)
+
+CDEPS = $(CSRCS:.cpp=.d)
+TDEPS = $(TSRCS:.cpp=.d)
+VDEPS = $(VSRCS:.cpp=.d)
+
+CBIN = gmqcc
+TBIN = testsuite
+VBIN = qcvm
+
+all: $(CBIN) $(TBIN) $(VBIN)
+
+$(CBIN): $(COBJS)
+       $(CXX) $(COBJS) -o $@
+
+$(TBIN): $(TOBJS)
+       $(CXX) $(TOBJS) -o $@
+
+$(VBIN): $(VOBJS)
+       $(CXX) $(VOBJS) -o $@
+
+.cpp.o:
+       $(CXX) -c $(CXXFLAGS) $< -o $@
+
+test: $(CBIN) $(TBIN) $(VBIN)
+       @./$(TBIN)
 
 clean:
-       rm -rf *.o $(GMQCC) $(QCVM) $(TESTSUITE) $(PAK) *.dat gource.mp4 *.exe gm-qcc.tgz ./cov-int
-
-depend:
-       @ makedepend -Y -w 65536 2> /dev/null $(subst .o,.c,$(DEPS))
-
-
-coverity:
-       @cov-build --dir cov-int $(MAKE)
-       @tar czf gm-qcc.tgz cov-int
-       @rm -rf cov-int
-       @echo gm-qcc.tgz generated, submit for analysis
-
-#install rules
-install: install-gmqcc install-qcvm install-gmqpak install-doc
-install-gmqcc: $(GMQCC)
-       install -d -m755               $(DESTDIR)$(BINDIR)
-       install    -m755  $(GMQCC)     $(DESTDIR)$(BINDIR)/$(GMQCC)
-install-qcvm: $(QCVM)
-       install -d -m755               $(DESTDIR)$(BINDIR)
-       install    -m755  $(QCVM)      $(DESTDIR)$(BINDIR)/$(QCVM)
-install-gmqpak: $(PAK)
-       install -d -m755               $(DESTDIR)$(BINDIR)
-       install    -m755  $(PAK)       $(DESTDIR)$(BINDIR)/$(PAK)
-install-doc:
-       install -d -m755               $(DESTDIR)$(MANDIR)/man1
-       install    -m644  doc/gmqcc.1  $(DESTDIR)$(MANDIR)/man1/
-       install    -m644  doc/qcvm.1   $(DESTDIR)$(MANDIR)/man1/
-       install    -m644  doc/gmqpak.1 $(DESTDIR)$(MANDIR)/man1/
-
-# DO NOT DELETE
+       rm -f *.d
+       rm -f $(COBJS) $(CDEPS) $(CBIN)
+       rm -f $(TOBJS) $(TDEPS) $(TBIN)
+       rm -f $(VOBJS) $(VDEPS) $(VBIN)
 
-ansi.o: platform.h gmqcc.h opts.def
-util.o: gmqcc.h opts.def platform.h
-hash.o: gmqcc.h opts.def
-stat.o: gmqcc.h opts.def
-fs.o: gmqcc.h opts.def platform.h
-opts.o: gmqcc.h opts.def
-conout.o: gmqcc.h opts.def
-pak.o: gmqcc.h opts.def
-test.o: gmqcc.h opts.def platform.h
-main.o: gmqcc.h opts.def lexer.h
-lexer.o: gmqcc.h opts.def lexer.h
-parser.o: parser.h gmqcc.h opts.def lexer.h ast.h ir.h
-code.o: gmqcc.h opts.def
-ast.o: gmqcc.h opts.def ast.h ir.h parser.h lexer.h
-ir.o: gmqcc.h opts.def ir.h
-ftepp.o: gmqcc.h opts.def lexer.h
-utf8.o: gmqcc.h opts.def
-correct.o: gmqcc.h opts.def
-fold.o: ast.h ir.h gmqcc.h opts.def parser.h lexer.h
-intrin.o: parser.h gmqcc.h opts.def lexer.h ast.h ir.h
-exec.o: gmqcc.h opts.def
+-include *.d
diff --git a/PORTING b/PORTING
deleted file mode 100644 (file)
index bbdb274..0000000
--- a/PORTING
+++ /dev/null
@@ -1,4 +0,0 @@
-Porting gmqcc to a new platform is farily trivial, in most cases ansi.c
-will be sufficent enough to get it to run on your favorite platform. If
-however it isn't you can duplicate ansi.c and change it accordingly.
-Changes to platform.h may also be required.
diff --git a/README b/README
index 36e1cf75d1170704e62e61e3c87430ae19b28fe1..993b8fb5d3b5f760426fa7efc8c017427d992b0d 100644 (file)
--- a/README
+++ b/README
@@ -1,18 +1 @@
-GMQCC: An improved Quake C compiler
-
-For licensing:           see the LICENSE file.
-For installation notes:  see the INSTALL file.
-For a list of authors:   see the AUTHORS file.
-For a list of changes:   see the CHANGES file.
-
-For documentation:
-    See the manpages, or visit the documentation online at
-    http://graphitemaster.github.com/gmqcc/doc.html
-
-For syntax highlighting description files, or information
-regarding how to install them:
-    See the README in syntax directory
-
-For description on porting GMQCC to other platforms, or information
-on how to approach porting GMQCC to more 'exotic' platforms:
-    See the PORTING file.
+An improved QuakeC compiler
diff --git a/TODO b/TODO
deleted file mode 100644 (file)
index 356c4b8..0000000
--- a/TODO
+++ /dev/null
@@ -1,64 +0,0 @@
-GMQCC is quite feature complete.  But that doesn't address the fact that
-it can be improved.  This is a list of things that we'd like to support
-in the distant future.  When the time comes, we can just select a topic
-from here and open a ticket for it on the issue tracker.  But for the
-meantime, this is sort of a cultivating flat file database.
-
-Optimizations:
-    The following are optimizations that can be implemented after the
-    transformation into static-single assignment (SSA).
-
-        Global Value Numbering:
-            Eliminate redundancy by constructing a value graph of the source
-            then determining which values are computed by equivalent expressions.
-            Similar to Common Subexpression Elimination (CSE), however expressions
-            are determined via underlying equivalence, opposed to lexically identical
-            expressions (CSE).
-
-    The following are optimizations that can be implemented before the
-    transformation into a binary (code generator).
-
-        Code factoring:
-            The process of finding sequences of code that are identical,
-            or can be parameterized or reordered to be identical.
-            Which can be replaced with calls to a shared subroutine. To
-            reduce duplicated code. (Size optimization)
-
-Language Features:
-    The following are language features that we'd like to see implemented in the
-    future.
-
-    AST Macros:
-        Macros with sanity.  Not textual substiution.
-
-    Classes:
-        Like C++, but minus the stupidity:
-            - No type operator overloads
-            - Keep operator overloading for basic operators though.
-            - No inheritance
-            - No virtuals / pure virtuals
-            - Essentially "C structs but with operators" :)
-
-    Overloaded Functions:
-        Ability to make individual functions with the same name, but take
-        different amount of arguments or type of arguments.
-
-    Default Argument Substiution:
-        Ability to specify default values for arguments in functions.
-        void foo(string bar, string baz="default");
-        Supplying just one argument will expand the second argument to
-        become "default", otherwise if two arguments are specified then
-        the "default" string is overrode with what ever the user passes.
-
-    Namespaces:
-        There is already a ticket open on this. They'd work just like C++
-        identically even.
-
-Testsuite:
-    The following are things we'd like to see added to the testsuite
-    in the distant future:
-
-    Interface:
-        Ability to select individual tests, or set parameters manually
-        opposed to using the static task-template files. (A method to
-        override them rather).
diff --git a/algo.h b/algo.h
new file mode 100644 (file)
index 0000000..3218b1b
--- /dev/null
+++ b/algo.h
@@ -0,0 +1,18 @@
+#ifndef GMQCC_ALGO_HDR
+#define GMQCC_ALGO_HDR
+
+namespace algo {
+
+template<typename ITER>
+void shiftback(ITER element, ITER end) {
+    //typename ITER::value_type backup(move(*element)); // hold the element
+    typename std::remove_reference<decltype(*element)>::type backup(move(*element)); // hold the element
+    ITER p = element++;
+    for (; element != end; p = element++)
+        *p = move(*element);
+    *p = move(backup);
+}
+
+} // ::algo
+
+#endif
diff --git a/ansi.c b/ansi.c
deleted file mode 100644 (file)
index 1317e15..0000000
--- a/ansi.c
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright (C) 2012, 2013, 2014, 2015
- *     Dale Weiler
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
- * this software and associated documentation files (the "Software"), to deal in
- * the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is furnished to do
- * so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-#define GMQCC_PLATFORM_HEADER
-#include <string.h>
-#include <stdlib.h>
-
-#include "platform.h"
-#include "gmqcc.h"
-
-int platform_vsnprintf(char *buffer, size_t bytes, const char *format, va_list arg) {
-    return vsnprintf(buffer, bytes, format, arg);
-}
-
-int platform_vsscanf(const char *str, const char *format, va_list arg) {
-    return vsscanf(str, format, arg);
-}
-
-const struct tm *platform_localtime(const time_t *timer) {
-    return localtime(timer);
-}
-
-const char *platform_ctime(const time_t *timer) {
-    return ctime(timer);
-}
-
-char *platform_strncat(char *dest, const char *src, size_t num) {
-    return strncat(dest, src, num);
-}
-
-const char *platform_getenv(const char *var) {
-    return getenv(var);
-}
-
-int platform_vasprintf(char **dat, const char *fmt, va_list args) {
-    int     ret;
-    int     len;
-    char   *tmp = NULL;
-    char    buf[128];
-    va_list cpy;
-
-    va_copy(cpy, args);
-    len = vsnprintf(buf, sizeof(buf), fmt, cpy);
-    va_end (cpy);
-
-    if (len < 0)
-        return len;
-
-    if (len < (int)sizeof(buf)) {
-        *dat = util_strdup(buf);
-        return len;
-    }
-
-    tmp = (char*)mem_a(len + 1);
-    if ((ret = vsnprintf(tmp, len + 1, fmt, args)) != len) {
-        mem_d(tmp);
-        *dat = NULL;
-        return -1;
-    }
-
-    *dat = tmp;
-    return len;
-}
-
-char *platform_strcat(char *dest, const char *src) {
-    return strcat(dest, src);
-}
-
-char *platform_strncpy(char *dest, const char *src, size_t num) {
-    return strncpy(dest, src, num);
-}
-
-const char *platform_strerror(int err) {
-    return strerror(err);
-}
-
-FILE *platform_fopen(const char *filename, const char *mode) {
-    return fopen(filename, mode);
-}
-
-size_t platform_fread(void *ptr, size_t size, size_t count, FILE *stream) {
-    return fread(ptr, size, count, stream);
-}
-
-size_t platform_fwrite(const void *ptr, size_t size, size_t count, FILE *stream) {
-    return fwrite(ptr, size, count, stream);
-}
-
-int platform_fflush(FILE *stream) {
-    return fflush(stream);
-}
-
-int platform_vfprintf(FILE *stream, const char *format, va_list arg) {
-    return vfprintf(stream, format, arg);
-}
-
-int platform_fclose(FILE *stream) {
-    return fclose(stream);
-}
-
-int platform_ferror(FILE *stream) {
-    return ferror(stream);
-}
-
-int platform_fgetc(FILE *stream) {
-    return fgetc(stream);
-}
-
-int platform_fputs(const char *str, FILE *stream) {
-    return fputs(str, stream);
-}
-
-int platform_fseek(FILE *stream, long offset, int origin) {
-    return fseek(stream, offset, origin);
-}
-
-long platform_ftell(FILE *stream) {
-    return ftell(stream);
-}
-
-int platform_mkdir(const char *path, int mode) {
-    /*
-     * For some reason mingw32 just doesn't have a correct mkdir impl
-     * so we handle that here.
-     */
-#   ifdef _WIN32
-    (void)mode;
-    return mkdir(path);
-#   else
-    return mkdir(path, mode);
-#   endif /*!_WIN32*/
-}
-
-DIR *platform_opendir(const char *path) {
-    return opendir(path);
-}
-
-int platform_closedir(DIR *dir) {
-    return closedir(dir);
-}
-
-struct dirent *platform_readdir(DIR *dir) {
-    return readdir(dir);
-}
-
-int platform_isatty(int fd) {
-    return isatty(fd);
-}
diff --git a/ast.c b/ast.c
deleted file mode 100644 (file)
index 4a07cc2..0000000
--- a/ast.c
+++ /dev/null
@@ -1,3486 +0,0 @@
-/*
- * Copyright (C) 2012, 2013, 2014, 2015
- *     Wolfgang Bumiller
- *     Dale Weiler
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
- * this software and associated documentation files (the "Software"), to deal in
- * the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is furnished to do
- * so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-#include <stdlib.h>
-#include <string.h>
-
-#include "gmqcc.h"
-#include "ast.h"
-#include "parser.h"
-
-#define ast_instantiate(T, ctx, destroyfn)                          \
-    T* self = (T*)mem_a(sizeof(T));                                 \
-    if (!self) {                                                    \
-        return NULL;                                                \
-    }                                                               \
-    ast_node_init((ast_node*)self, ctx, TYPE_##T);                  \
-    ( (ast_node*)self )->destroy = (ast_node_delete*)destroyfn
-
-/*
- * forward declarations, these need not be in ast.h for obvious
- * static reasons.
- */
-static bool ast_member_codegen(ast_member*, ast_function*, bool lvalue, ir_value**);
-static void ast_array_index_delete(ast_array_index*);
-static bool ast_array_index_codegen(ast_array_index*, ast_function*, bool lvalue, ir_value**);
-static void ast_argpipe_delete(ast_argpipe*);
-static bool ast_argpipe_codegen(ast_argpipe*, ast_function*, bool lvalue, ir_value**);
-static void ast_store_delete(ast_store*);
-static bool ast_store_codegen(ast_store*, ast_function*, bool lvalue, ir_value**);
-static void ast_ifthen_delete(ast_ifthen*);
-static bool ast_ifthen_codegen(ast_ifthen*, ast_function*, bool lvalue, ir_value**);
-static void ast_ternary_delete(ast_ternary*);
-static bool ast_ternary_codegen(ast_ternary*, ast_function*, bool lvalue, ir_value**);
-static void ast_loop_delete(ast_loop*);
-static bool ast_loop_codegen(ast_loop*, ast_function*, bool lvalue, ir_value**);
-static void ast_breakcont_delete(ast_breakcont*);
-static bool ast_breakcont_codegen(ast_breakcont*, ast_function*, bool lvalue, ir_value**);
-static void ast_switch_delete(ast_switch*);
-static bool ast_switch_codegen(ast_switch*, ast_function*, bool lvalue, ir_value**);
-static void ast_label_delete(ast_label*);
-static void ast_label_register_goto(ast_label*, ast_goto*);
-static bool ast_label_codegen(ast_label*, ast_function*, bool lvalue, ir_value**);
-static bool ast_goto_codegen(ast_goto*, ast_function*, bool lvalue, ir_value**);
-static void ast_goto_delete(ast_goto*);
-static void ast_call_delete(ast_call*);
-static bool ast_call_codegen(ast_call*, ast_function*, bool lvalue, ir_value**);
-static bool ast_block_codegen(ast_block*, ast_function*, bool lvalue, ir_value**);
-static void ast_unary_delete(ast_unary*);
-static bool ast_unary_codegen(ast_unary*, ast_function*, bool lvalue, ir_value**);
-static void ast_entfield_delete(ast_entfield*);
-static bool ast_entfield_codegen(ast_entfield*, ast_function*, bool lvalue, ir_value**);
-static void ast_return_delete(ast_return*);
-static bool ast_return_codegen(ast_return*, ast_function*, bool lvalue, ir_value**);
-static void ast_binstore_delete(ast_binstore*);
-static bool ast_binstore_codegen(ast_binstore*, ast_function*, bool lvalue, ir_value**);
-static void ast_binary_delete(ast_binary*);
-static bool ast_binary_codegen(ast_binary*, ast_function*, bool lvalue, ir_value**);
-static bool ast_state_codegen(ast_state*, ast_function*, bool lvalue, ir_value**);
-
-/* It must not be possible to get here. */
-static GMQCC_NORETURN void _ast_node_destroy(ast_node *self)
-{
-    (void)self;
-    con_err("ast node missing destroy()\n");
-    exit(EXIT_FAILURE);
-}
-
-/* Initialize main ast node aprts */
-static void ast_node_init(ast_node *self, lex_ctx_t ctx, int nodetype)
-{
-    self->context = ctx;
-    self->destroy = &_ast_node_destroy;
-    self->keep    = false;
-    self->nodetype = nodetype;
-    self->side_effects = false;
-}
-
-/* weight and side effects */
-static void _ast_propagate_effects(ast_node *self, ast_node *other)
-{
-    if (ast_side_effects(other))
-        ast_side_effects(self) = true;
-}
-#define ast_propagate_effects(s,o) _ast_propagate_effects(((ast_node*)(s)), ((ast_node*)(o)))
-
-/* General expression initialization */
-static void ast_expression_init(ast_expression *self,
-                                ast_expression_codegen *codegen)
-{
-    self->codegen  = codegen;
-    self->vtype    = TYPE_VOID;
-    self->next     = NULL;
-    self->outl     = NULL;
-    self->outr     = NULL;
-    self->params   = NULL;
-    self->count    = 0;
-    self->varparam = NULL;
-    self->flags    = 0;
-    if (OPTS_OPTION_BOOL(OPTION_COVERAGE))
-        self->flags |= AST_FLAG_BLOCK_COVERAGE;
-}
-
-static void ast_expression_delete(ast_expression *self)
-{
-    size_t i;
-    if (self->next)
-        ast_delete(self->next);
-    for (i = 0; i < vec_size(self->params); ++i) {
-        ast_delete(self->params[i]);
-    }
-    vec_free(self->params);
-    if (self->varparam)
-        ast_delete(self->varparam);
-}
-
-static void ast_expression_delete_full(ast_expression *self)
-{
-    ast_expression_delete(self);
-    mem_d(self);
-}
-
-ast_value* ast_value_copy(const ast_value *self)
-{
-    size_t i;
-    const ast_expression *fromex;
-    ast_expression       *selfex;
-    ast_value *cp = ast_value_new(self->expression.node.context, self->name, self->expression.vtype);
-    if (self->expression.next) {
-        cp->expression.next = ast_type_copy(self->expression.node.context, self->expression.next);
-    }
-    fromex   = &self->expression;
-    selfex = &cp->expression;
-    selfex->count    = fromex->count;
-    selfex->flags    = fromex->flags;
-    for (i = 0; i < vec_size(fromex->params); ++i) {
-        ast_value *v = ast_value_copy(fromex->params[i]);
-        vec_push(selfex->params, v);
-    }
-    return cp;
-}
-
-void ast_type_adopt_impl(ast_expression *self, const ast_expression *other)
-{
-    size_t i;
-    const ast_expression *fromex;
-    ast_expression       *selfex;
-    self->vtype = other->vtype;
-    if (other->next) {
-        self->next = (ast_expression*)ast_type_copy(ast_ctx(self), other->next);
-    }
-    fromex = other;
-    selfex = self;
-    selfex->count    = fromex->count;
-    selfex->flags    = fromex->flags;
-    for (i = 0; i < vec_size(fromex->params); ++i) {
-        ast_value *v = ast_value_copy(fromex->params[i]);
-        vec_push(selfex->params, v);
-    }
-}
-
-static ast_expression* ast_shallow_type(lex_ctx_t ctx, int vtype)
-{
-    ast_instantiate(ast_expression, ctx, ast_expression_delete_full);
-    ast_expression_init(self, NULL);
-    self->codegen = NULL;
-    self->next    = NULL;
-    self->vtype   = vtype;
-    return self;
-}
-
-ast_expression* ast_type_copy(lex_ctx_t ctx, const ast_expression *ex)
-{
-    size_t i;
-    const ast_expression *fromex;
-    ast_expression       *selfex;
-
-    if (!ex)
-        return NULL;
-    else
-    {
-        ast_instantiate(ast_expression, ctx, ast_expression_delete_full);
-        ast_expression_init(self, NULL);
-
-        fromex = ex;
-        selfex = self;
-
-        /* This may never be codegen()d */
-        selfex->codegen = NULL;
-
-        selfex->vtype = fromex->vtype;
-        if (fromex->next)
-            selfex->next = ast_type_copy(ctx, fromex->next);
-        else
-            selfex->next = NULL;
-
-        selfex->count    = fromex->count;
-        selfex->flags    = fromex->flags;
-        for (i = 0; i < vec_size(fromex->params); ++i) {
-            ast_value *v = ast_value_copy(fromex->params[i]);
-            vec_push(selfex->params, v);
-        }
-
-        return self;
-    }
-}
-
-bool ast_compare_type(ast_expression *a, ast_expression *b)
-{
-    if (a->vtype == TYPE_NIL ||
-        b->vtype == TYPE_NIL)
-        return true;
-    if (a->vtype != b->vtype)
-        return false;
-    if (!a->next != !b->next)
-        return false;
-    if (vec_size(a->params) != vec_size(b->params))
-        return false;
-    if ((a->flags & AST_FLAG_TYPE_MASK) !=
-        (b->flags & AST_FLAG_TYPE_MASK) )
-    {
-        return false;
-    }
-    if (vec_size(a->params)) {
-        size_t i;
-        for (i = 0; i < vec_size(a->params); ++i) {
-            if (!ast_compare_type((ast_expression*)a->params[i],
-                                  (ast_expression*)b->params[i]))
-                return false;
-        }
-    }
-    if (a->next)
-        return ast_compare_type(a->next, b->next);
-    return true;
-}
-
-static size_t ast_type_to_string_impl(ast_expression *e, char *buf, size_t bufsize, size_t pos)
-{
-    const char *typestr;
-    size_t typelen;
-    size_t i;
-
-    if (!e) {
-        if (pos + 6 >= bufsize)
-            goto full;
-        util_strncpy(buf + pos, "(null)", 6);
-        return pos + 6;
-    }
-
-    if (pos + 1 >= bufsize)
-        goto full;
-
-    switch (e->vtype) {
-        case TYPE_VARIANT:
-            util_strncpy(buf + pos, "(variant)", 9);
-            return pos + 9;
-
-        case TYPE_FIELD:
-            buf[pos++] = '.';
-            return ast_type_to_string_impl(e->next, buf, bufsize, pos);
-
-        case TYPE_POINTER:
-            if (pos + 3 >= bufsize)
-                goto full;
-            buf[pos++] = '*';
-            buf[pos++] = '(';
-            pos = ast_type_to_string_impl(e->next, buf, bufsize, pos);
-            if (pos + 1 >= bufsize)
-                goto full;
-            buf[pos++] = ')';
-            return pos;
-
-        case TYPE_FUNCTION:
-            pos = ast_type_to_string_impl(e->next, buf, bufsize, pos);
-            if (pos + 2 >= bufsize)
-                goto full;
-            if (!vec_size(e->params)) {
-                buf[pos++] = '(';
-                buf[pos++] = ')';
-                return pos;
-            }
-            buf[pos++] = '(';
-            pos = ast_type_to_string_impl((ast_expression*)(e->params[0]), buf, bufsize, pos);
-            for (i = 1; i < vec_size(e->params); ++i) {
-                if (pos + 2 >= bufsize)
-                    goto full;
-                buf[pos++] = ',';
-                buf[pos++] = ' ';
-                pos = ast_type_to_string_impl((ast_expression*)(e->params[i]), buf, bufsize, pos);
-            }
-            if (pos + 1 >= bufsize)
-                goto full;
-            buf[pos++] = ')';
-            return pos;
-
-        case TYPE_ARRAY:
-            pos = ast_type_to_string_impl(e->next, buf, bufsize, pos);
-            if (pos + 1 >= bufsize)
-                goto full;
-            buf[pos++] = '[';
-            pos += util_snprintf(buf + pos, bufsize - pos - 1, "%i", (int)e->count);
-            if (pos + 1 >= bufsize)
-                goto full;
-            buf[pos++] = ']';
-            return pos;
-
-        default:
-            typestr = type_name[e->vtype];
-            typelen = strlen(typestr);
-            if (pos + typelen >= bufsize)
-                goto full;
-            util_strncpy(buf + pos, typestr, typelen);
-            return pos + typelen;
-    }
-
-full:
-    buf[bufsize-3] = '.';
-    buf[bufsize-2] = '.';
-    buf[bufsize-1] = '.';
-    return bufsize;
-}
-
-void ast_type_to_string(ast_expression *e, char *buf, size_t bufsize)
-{
-    size_t pos = ast_type_to_string_impl(e, buf, bufsize-1, 0);
-    buf[pos] = 0;
-}
-
-static bool ast_value_codegen(ast_value *self, ast_function *func, bool lvalue, ir_value **out);
-ast_value* ast_value_new(lex_ctx_t ctx, const char *name, int t)
-{
-    ast_instantiate(ast_value, ctx, ast_value_delete);
-    ast_expression_init((ast_expression*)self,
-                        (ast_expression_codegen*)&ast_value_codegen);
-    self->expression.node.keep = true; /* keep */
-
-    self->name = name ? util_strdup(name) : NULL;
-    self->expression.vtype = t;
-    self->expression.next  = NULL;
-    self->isfield  = false;
-    self->cvq      = CV_NONE;
-    self->hasvalue = false;
-    self->isimm    = false;
-    self->inexact  = false;
-    self->uses     = 0;
-    memset(&self->constval, 0, sizeof(self->constval));
-    self->initlist = NULL;
-
-    self->ir_v           = NULL;
-    self->ir_values      = NULL;
-    self->ir_value_count = 0;
-
-    self->setter = NULL;
-    self->getter = NULL;
-    self->desc   = NULL;
-
-    self->argcounter = NULL;
-    self->intrinsic = false;
-
-    return self;
-}
-
-void ast_value_delete(ast_value* self)
-{
-    if (self->name)
-        mem_d((void*)self->name);
-    if (self->argcounter)
-        mem_d((void*)self->argcounter);
-    if (self->hasvalue) {
-        switch (self->expression.vtype)
-        {
-        case TYPE_STRING:
-            mem_d((void*)self->constval.vstring);
-            break;
-        case TYPE_FUNCTION:
-            /* unlink us from the function node */
-            self->constval.vfunc->vtype = NULL;
-            break;
-        /* NOTE: delete function? currently collected in
-         * the parser structure
-         */
-        default:
-            break;
-        }
-    }
-    if (self->ir_values)
-        mem_d(self->ir_values);
-
-    if (self->desc)
-        mem_d(self->desc);
-
-    if (self->initlist) {
-        if (self->expression.next->vtype == TYPE_STRING) {
-            /* strings are allocated, free them */
-            size_t i, len = vec_size(self->initlist);
-            /* in theory, len should be expression.count
-             * but let's not take any chances */
-            for (i = 0; i < len; ++i) {
-                if (self->initlist[i].vstring)
-                    mem_d(self->initlist[i].vstring);
-            }
-        }
-        vec_free(self->initlist);
-    }
-
-    ast_expression_delete((ast_expression*)self);
-    mem_d(self);
-}
-
-void ast_value_params_add(ast_value *self, ast_value *p)
-{
-    vec_push(self->expression.params, p);
-}
-
-bool ast_value_set_name(ast_value *self, const char *name)
-{
-    if (self->name)
-        mem_d((void*)self->name);
-    self->name = util_strdup(name);
-    return !!self->name;
-}
-
-ast_binary* ast_binary_new(lex_ctx_t ctx, int op,
-                           ast_expression* left, ast_expression* right)
-{
-    ast_instantiate(ast_binary, ctx, ast_binary_delete);
-    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_binary_codegen);
-
-    if (ast_istype(right, ast_unary) && OPTS_OPTIMIZATION(OPTIM_PEEPHOLE)) {
-        ast_unary      *unary  = ((ast_unary*)right);
-        ast_expression *normal = unary->operand;
-
-        /* make a-(-b) => a + b */
-        if (unary->op == VINSTR_NEG_F || unary->op == VINSTR_NEG_V) {
-            if (op == INSTR_SUB_F) {
-                op = INSTR_ADD_F;
-                right = normal;
-                ++opts_optimizationcount[OPTIM_PEEPHOLE];
-            } else if (op == INSTR_SUB_V) {
-                op = INSTR_ADD_V;
-                right = normal;
-                ++opts_optimizationcount[OPTIM_PEEPHOLE];
-            }
-        }
-    }
-
-    self->op = op;
-    self->left = left;
-    self->right = right;
-    self->right_first = false;
-
-    ast_propagate_effects(self, left);
-    ast_propagate_effects(self, right);
-
-    if (op >= INSTR_EQ_F && op <= INSTR_GT)
-        self->expression.vtype = TYPE_FLOAT;
-    else if (op == INSTR_AND || op == INSTR_OR) {
-        if (OPTS_FLAG(PERL_LOGIC))
-            ast_type_adopt(self, right);
-        else
-            self->expression.vtype = TYPE_FLOAT;
-    }
-    else if (op == INSTR_BITAND || op == INSTR_BITOR)
-        self->expression.vtype = TYPE_FLOAT;
-    else if (op == INSTR_MUL_VF || op == INSTR_MUL_FV)
-        self->expression.vtype = TYPE_VECTOR;
-    else if (op == INSTR_MUL_V)
-        self->expression.vtype = TYPE_FLOAT;
-    else
-        self->expression.vtype = left->vtype;
-
-    /* references all */
-    self->refs = AST_REF_ALL;
-
-    return self;
-}
-
-void ast_binary_delete(ast_binary *self)
-{
-    if (self->refs & AST_REF_LEFT)  ast_unref(self->left);
-    if (self->refs & AST_REF_RIGHT) ast_unref(self->right);
-
-    ast_expression_delete((ast_expression*)self);
-    mem_d(self);
-}
-
-ast_binstore* ast_binstore_new(lex_ctx_t ctx, int storop, int op,
-                               ast_expression* left, ast_expression* right)
-{
-    ast_instantiate(ast_binstore, ctx, ast_binstore_delete);
-    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_binstore_codegen);
-
-    ast_side_effects(self) = true;
-
-    self->opstore = storop;
-    self->opbin   = op;
-    self->dest    = left;
-    self->source  = right;
-
-    self->keep_dest = false;
-
-    ast_type_adopt(self, left);
-    return self;
-}
-
-void ast_binstore_delete(ast_binstore *self)
-{
-    if (!self->keep_dest)
-        ast_unref(self->dest);
-    ast_unref(self->source);
-    ast_expression_delete((ast_expression*)self);
-    mem_d(self);
-}
-
-ast_unary* ast_unary_new(lex_ctx_t ctx, int op,
-                         ast_expression *expr)
-{
-    ast_instantiate(ast_unary, ctx, ast_unary_delete);
-    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_unary_codegen);
-
-    self->op      = op;
-    self->operand = expr;
-
-
-    if (ast_istype(expr, ast_unary) && OPTS_OPTIMIZATION(OPTIM_PEEPHOLE)) {
-        ast_unary *prev = (ast_unary*)((ast_unary*)expr)->operand;
-
-        /* Handle for double negation */
-        if (((ast_unary*)expr)->op == op)
-            prev = (ast_unary*)((ast_unary*)expr)->operand;
-
-        if (ast_istype(prev, ast_unary)) {
-            ast_expression_delete((ast_expression*)self);
-            mem_d(self);
-            ++opts_optimizationcount[OPTIM_PEEPHOLE];
-            return prev;
-        }
-    }
-
-    ast_propagate_effects(self, expr);
-
-    if ((op >= INSTR_NOT_F && op <= INSTR_NOT_FNC) || op == VINSTR_NEG_F) {
-        self->expression.vtype = TYPE_FLOAT;
-    } else if (op == VINSTR_NEG_V) {
-        self->expression.vtype = TYPE_VECTOR;
-    } else {
-        compile_error(ctx, "cannot determine type of unary operation %s", util_instr_str[op]);
-    }
-
-    return self;
-}
-
-void ast_unary_delete(ast_unary *self)
-{
-    if (self->operand) ast_unref(self->operand);
-    ast_expression_delete((ast_expression*)self);
-    mem_d(self);
-}
-
-ast_return* ast_return_new(lex_ctx_t ctx, ast_expression *expr)
-{
-    ast_instantiate(ast_return, ctx, ast_return_delete);
-    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_return_codegen);
-
-    self->operand = expr;
-
-    if (expr)
-        ast_propagate_effects(self, expr);
-
-    return self;
-}
-
-void ast_return_delete(ast_return *self)
-{
-    if (self->operand)
-        ast_unref(self->operand);
-    ast_expression_delete((ast_expression*)self);
-    mem_d(self);
-}
-
-ast_entfield* ast_entfield_new(lex_ctx_t ctx, ast_expression *entity, ast_expression *field)
-{
-    if (field->vtype != TYPE_FIELD) {
-        compile_error(ctx, "ast_entfield_new with expression not of type field");
-        return NULL;
-    }
-    return ast_entfield_new_force(ctx, entity, field, field->next);
-}
-
-ast_entfield* ast_entfield_new_force(lex_ctx_t ctx, ast_expression *entity, ast_expression *field, const ast_expression *outtype)
-{
-    ast_instantiate(ast_entfield, ctx, ast_entfield_delete);
-
-    if (!outtype) {
-        mem_d(self);
-        /* Error: field has no type... */
-        return NULL;
-    }
-
-    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_entfield_codegen);
-
-    self->entity = entity;
-    self->field  = field;
-    ast_propagate_effects(self, entity);
-    ast_propagate_effects(self, field);
-
-    ast_type_adopt(self, outtype);
-    return self;
-}
-
-void ast_entfield_delete(ast_entfield *self)
-{
-    ast_unref(self->entity);
-    ast_unref(self->field);
-    ast_expression_delete((ast_expression*)self);
-    mem_d(self);
-}
-
-ast_member* ast_member_new(lex_ctx_t ctx, ast_expression *owner, unsigned int field, const char *name)
-{
-    ast_instantiate(ast_member, ctx, ast_member_delete);
-    if (field >= 3) {
-        mem_d(self);
-        return NULL;
-    }
-
-    if (owner->vtype != TYPE_VECTOR &&
-        owner->vtype != TYPE_FIELD) {
-        compile_error(ctx, "member-access on an invalid owner of type %s", type_name[owner->vtype]);
-        mem_d(self);
-        return NULL;
-    }
-
-    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_member_codegen);
-    self->expression.node.keep = true; /* keep */
-
-    if (owner->vtype == TYPE_VECTOR) {
-        self->expression.vtype = TYPE_FLOAT;
-        self->expression.next  = NULL;
-    } else {
-        self->expression.vtype = TYPE_FIELD;
-        self->expression.next = ast_shallow_type(ctx, TYPE_FLOAT);
-    }
-
-    self->rvalue = false;
-    self->owner  = owner;
-    ast_propagate_effects(self, owner);
-
-    self->field = field;
-    if (name)
-        self->name = util_strdup(name);
-    else
-        self->name = NULL;
-
-    return self;
-}
-
-void ast_member_delete(ast_member *self)
-{
-    /* The owner is always an ast_value, which has .keep=true,
-     * also: ast_members are usually deleted after the owner, thus
-     * this will cause invalid access
-    ast_unref(self->owner);
-     * once we allow (expression).x to access a vector-member, we need
-     * to change this: preferably by creating an alternate ast node for this
-     * purpose that is not garbage-collected.
-    */
-    ast_expression_delete((ast_expression*)self);
-    mem_d(self->name);
-    mem_d(self);
-}
-
-bool ast_member_set_name(ast_member *self, const char *name)
-{
-    if (self->name)
-        mem_d((void*)self->name);
-    self->name = util_strdup(name);
-    return !!self->name;
-}
-
-ast_array_index* ast_array_index_new(lex_ctx_t ctx, ast_expression *array, ast_expression *index)
-{
-    ast_expression *outtype;
-    ast_instantiate(ast_array_index, ctx, ast_array_index_delete);
-
-    outtype = array->next;
-    if (!outtype) {
-        mem_d(self);
-        /* Error: field has no type... */
-        return NULL;
-    }
-
-    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_array_index_codegen);
-
-    self->array = array;
-    self->index = index;
-    ast_propagate_effects(self, array);
-    ast_propagate_effects(self, index);
-
-    ast_type_adopt(self, outtype);
-    if (array->vtype == TYPE_FIELD && outtype->vtype == TYPE_ARRAY) {
-        if (self->expression.vtype != TYPE_ARRAY) {
-            compile_error(ast_ctx(self), "array_index node on type");
-            ast_array_index_delete(self);
-            return NULL;
-        }
-        self->array = outtype;
-        self->expression.vtype = TYPE_FIELD;
-    }
-
-    return self;
-}
-
-void ast_array_index_delete(ast_array_index *self)
-{
-    if (self->array)
-        ast_unref(self->array);
-    if (self->index)
-        ast_unref(self->index);
-    ast_expression_delete((ast_expression*)self);
-    mem_d(self);
-}
-
-ast_argpipe* ast_argpipe_new(lex_ctx_t ctx, ast_expression *index)
-{
-    ast_instantiate(ast_argpipe, ctx, ast_argpipe_delete);
-    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_argpipe_codegen);
-    self->index = index;
-    self->expression.vtype = TYPE_NOEXPR;
-    return self;
-}
-
-void ast_argpipe_delete(ast_argpipe *self)
-{
-    if (self->index)
-        ast_unref(self->index);
-    ast_expression_delete((ast_expression*)self);
-    mem_d(self);
-}
-
-ast_ifthen* ast_ifthen_new(lex_ctx_t ctx, ast_expression *cond, ast_expression *ontrue, ast_expression *onfalse)
-{
-    ast_instantiate(ast_ifthen, ctx, ast_ifthen_delete);
-    if (!ontrue && !onfalse) {
-        /* because it is invalid */
-        mem_d(self);
-        return NULL;
-    }
-    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_ifthen_codegen);
-
-    self->cond     = cond;
-    self->on_true  = ontrue;
-    self->on_false = onfalse;
-    ast_propagate_effects(self, cond);
-    if (ontrue)
-        ast_propagate_effects(self, ontrue);
-    if (onfalse)
-        ast_propagate_effects(self, onfalse);
-
-    return self;
-}
-
-void ast_ifthen_delete(ast_ifthen *self)
-{
-    ast_unref(self->cond);
-    if (self->on_true)
-        ast_unref(self->on_true);
-    if (self->on_false)
-        ast_unref(self->on_false);
-    ast_expression_delete((ast_expression*)self);
-    mem_d(self);
-}
-
-ast_ternary* ast_ternary_new(lex_ctx_t ctx, ast_expression *cond, ast_expression *ontrue, ast_expression *onfalse)
-{
-    ast_expression *exprtype = ontrue;
-    ast_instantiate(ast_ternary, ctx, ast_ternary_delete);
-    /* This time NEITHER must be NULL */
-    if (!ontrue || !onfalse) {
-        mem_d(self);
-        return NULL;
-    }
-    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_ternary_codegen);
-
-    self->cond     = cond;
-    self->on_true  = ontrue;
-    self->on_false = onfalse;
-    ast_propagate_effects(self, cond);
-    ast_propagate_effects(self, ontrue);
-    ast_propagate_effects(self, onfalse);
-
-    if (ontrue->vtype == TYPE_NIL)
-        exprtype = onfalse;
-    ast_type_adopt(self, exprtype);
-
-    return self;
-}
-
-void ast_ternary_delete(ast_ternary *self)
-{
-    /* the if()s are only there because computed-gotos can set them
-     * to NULL
-     */
-    if (self->cond)     ast_unref(self->cond);
-    if (self->on_true)  ast_unref(self->on_true);
-    if (self->on_false) ast_unref(self->on_false);
-    ast_expression_delete((ast_expression*)self);
-    mem_d(self);
-}
-
-ast_loop* ast_loop_new(lex_ctx_t ctx,
-                       ast_expression *initexpr,
-                       ast_expression *precond, bool pre_not,
-                       ast_expression *postcond, bool post_not,
-                       ast_expression *increment,
-                       ast_expression *body)
-{
-    ast_instantiate(ast_loop, ctx, ast_loop_delete);
-    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_loop_codegen);
-
-    self->initexpr  = initexpr;
-    self->precond   = precond;
-    self->postcond  = postcond;
-    self->increment = increment;
-    self->body      = body;
-
-    self->pre_not   = pre_not;
-    self->post_not  = post_not;
-
-    if (initexpr)
-        ast_propagate_effects(self, initexpr);
-    if (precond)
-        ast_propagate_effects(self, precond);
-    if (postcond)
-        ast_propagate_effects(self, postcond);
-    if (increment)
-        ast_propagate_effects(self, increment);
-    if (body)
-        ast_propagate_effects(self, body);
-
-    return self;
-}
-
-void ast_loop_delete(ast_loop *self)
-{
-    if (self->initexpr)
-        ast_unref(self->initexpr);
-    if (self->precond)
-        ast_unref(self->precond);
-    if (self->postcond)
-        ast_unref(self->postcond);
-    if (self->increment)
-        ast_unref(self->increment);
-    if (self->body)
-        ast_unref(self->body);
-    ast_expression_delete((ast_expression*)self);
-    mem_d(self);
-}
-
-ast_breakcont* ast_breakcont_new(lex_ctx_t ctx, bool iscont, unsigned int levels)
-{
-    ast_instantiate(ast_breakcont, ctx, ast_breakcont_delete);
-    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_breakcont_codegen);
-
-    self->is_continue = iscont;
-    self->levels      = levels;
-
-    return self;
-}
-
-void ast_breakcont_delete(ast_breakcont *self)
-{
-    ast_expression_delete((ast_expression*)self);
-    mem_d(self);
-}
-
-ast_switch* ast_switch_new(lex_ctx_t ctx, ast_expression *op)
-{
-    ast_instantiate(ast_switch, ctx, ast_switch_delete);
-    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_switch_codegen);
-
-    self->operand = op;
-    self->cases   = NULL;
-
-    ast_propagate_effects(self, op);
-
-    return self;
-}
-
-void ast_switch_delete(ast_switch *self)
-{
-    size_t i;
-    ast_unref(self->operand);
-
-    for (i = 0; i < vec_size(self->cases); ++i) {
-        if (self->cases[i].value)
-            ast_unref(self->cases[i].value);
-        ast_unref(self->cases[i].code);
-    }
-    vec_free(self->cases);
-
-    ast_expression_delete((ast_expression*)self);
-    mem_d(self);
-}
-
-ast_label* ast_label_new(lex_ctx_t ctx, const char *name, bool undefined)
-{
-    ast_instantiate(ast_label, ctx, ast_label_delete);
-    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_label_codegen);
-
-    self->expression.vtype = TYPE_NOEXPR;
-
-    self->name      = util_strdup(name);
-    self->irblock   = NULL;
-    self->gotos     = NULL;
-    self->undefined = undefined;
-
-    return self;
-}
-
-void ast_label_delete(ast_label *self)
-{
-    mem_d((void*)self->name);
-    vec_free(self->gotos);
-    ast_expression_delete((ast_expression*)self);
-    mem_d(self);
-}
-
-static void ast_label_register_goto(ast_label *self, ast_goto *g)
-{
-    vec_push(self->gotos, g);
-}
-
-ast_goto* ast_goto_new(lex_ctx_t ctx, const char *name)
-{
-    ast_instantiate(ast_goto, ctx, ast_goto_delete);
-    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_goto_codegen);
-
-    self->name    = util_strdup(name);
-    self->target  = NULL;
-    self->irblock_from = NULL;
-
-    return self;
-}
-
-void ast_goto_delete(ast_goto *self)
-{
-    mem_d((void*)self->name);
-    ast_expression_delete((ast_expression*)self);
-    mem_d(self);
-}
-
-void ast_goto_set_label(ast_goto *self, ast_label *label)
-{
-    self->target = label;
-}
-
-ast_state* ast_state_new(lex_ctx_t ctx, ast_expression *frame, ast_expression *think)
-{
-    ast_instantiate(ast_state, ctx, ast_state_delete);
-    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_state_codegen);
-    self->framenum  = frame;
-    self->nextthink = think;
-    return self;
-}
-
-void ast_state_delete(ast_state *self)
-{
-    if (self->framenum)
-        ast_unref(self->framenum);
-    if (self->nextthink)
-        ast_unref(self->nextthink);
-
-    ast_expression_delete((ast_expression*)self);
-    mem_d(self);
-}
-
-ast_call* ast_call_new(lex_ctx_t ctx,
-                       ast_expression *funcexpr)
-{
-    ast_instantiate(ast_call, ctx, ast_call_delete);
-    if (!funcexpr->next) {
-        compile_error(ctx, "not a function");
-        mem_d(self);
-        return NULL;
-    }
-    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_call_codegen);
-
-    ast_side_effects(self) = true;
-
-    self->params   = NULL;
-    self->func     = funcexpr;
-    self->va_count = NULL;
-
-    ast_type_adopt(self, funcexpr->next);
-
-    return self;
-}
-
-void ast_call_delete(ast_call *self)
-{
-    size_t i;
-    for (i = 0; i < vec_size(self->params); ++i)
-        ast_unref(self->params[i]);
-    vec_free(self->params);
-
-    if (self->func)
-        ast_unref(self->func);
-
-    if (self->va_count)
-        ast_unref(self->va_count);
-
-    ast_expression_delete((ast_expression*)self);
-    mem_d(self);
-}
-
-static bool ast_call_check_vararg(ast_call *self, ast_expression *va_type, ast_expression *exp_type)
-{
-    char texp[1024];
-    char tgot[1024];
-    if (!exp_type)
-        return true;
-    if (!va_type || !ast_compare_type(va_type, exp_type))
-    {
-        if (va_type && exp_type)
-        {
-            ast_type_to_string(va_type,  tgot, sizeof(tgot));
-            ast_type_to_string(exp_type, texp, sizeof(texp));
-            if (OPTS_FLAG(UNSAFE_VARARGS)) {
-                if (compile_warning(ast_ctx(self), WARN_UNSAFE_TYPES,
-                                    "piped variadic argument differs in type: constrained to type %s, expected type %s",
-                                    tgot, texp))
-                    return false;
-            } else {
-                compile_error(ast_ctx(self),
-                              "piped variadic argument differs in type: constrained to type %s, expected type %s",
-                              tgot, texp);
-                return false;
-            }
-        }
-        else
-        {
-            ast_type_to_string(exp_type, texp, sizeof(texp));
-            if (OPTS_FLAG(UNSAFE_VARARGS)) {
-                if (compile_warning(ast_ctx(self), WARN_UNSAFE_TYPES,
-                                    "piped variadic argument may differ in type: expected type %s",
-                                    texp))
-                    return false;
-            } else {
-                compile_error(ast_ctx(self),
-                              "piped variadic argument may differ in type: expected type %s",
-                              texp);
-                return false;
-            }
-        }
-    }
-    return true;
-}
-
-bool ast_call_check_types(ast_call *self, ast_expression *va_type)
-{
-    char texp[1024];
-    char tgot[1024];
-    size_t i;
-    bool   retval = true;
-    const  ast_expression *func = self->func;
-    size_t count = vec_size(self->params);
-    if (count > vec_size(func->params))
-        count = vec_size(func->params);
-
-    for (i = 0; i < count; ++i) {
-        if (ast_istype(self->params[i], ast_argpipe)) {
-            /* warn about type safety instead */
-            if (i+1 != count) {
-                compile_error(ast_ctx(self), "argpipe must be the last parameter to a function call");
-                return false;
-            }
-            if (!ast_call_check_vararg(self, va_type, (ast_expression*)func->params[i]))
-                retval = false;
-        }
-        else if (!ast_compare_type(self->params[i], (ast_expression*)(func->params[i])))
-        {
-            ast_type_to_string(self->params[i], tgot, sizeof(tgot));
-            ast_type_to_string((ast_expression*)func->params[i], texp, sizeof(texp));
-            compile_error(ast_ctx(self), "invalid type for parameter %u in function call: expected %s, got %s",
-                     (unsigned int)(i+1), texp, tgot);
-            /* we don't immediately return */
-            retval = false;
-        }
-    }
-    count = vec_size(self->params);
-    if (count > vec_size(func->params) && func->varparam) {
-        for (; i < count; ++i) {
-            if (ast_istype(self->params[i], ast_argpipe)) {
-                /* warn about type safety instead */
-                if (i+1 != count) {
-                    compile_error(ast_ctx(self), "argpipe must be the last parameter to a function call");
-                    return false;
-                }
-                if (!ast_call_check_vararg(self, va_type, func->varparam))
-                    retval = false;
-            }
-            else if (!ast_compare_type(self->params[i], func->varparam))
-            {
-                ast_type_to_string(self->params[i], tgot, sizeof(tgot));
-                ast_type_to_string(func->varparam, texp, sizeof(texp));
-                compile_error(ast_ctx(self), "invalid type for variadic parameter %u in function call: expected %s, got %s",
-                         (unsigned int)(i+1), texp, tgot);
-                /* we don't immediately return */
-                retval = false;
-            }
-        }
-    }
-    return retval;
-}
-
-ast_store* ast_store_new(lex_ctx_t ctx, int op,
-                         ast_expression *dest, ast_expression *source)
-{
-    ast_instantiate(ast_store, ctx, ast_store_delete);
-    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_store_codegen);
-
-    ast_side_effects(self) = true;
-
-    self->op = op;
-    self->dest = dest;
-    self->source = source;
-
-    ast_type_adopt(self, dest);
-
-    return self;
-}
-
-void ast_store_delete(ast_store *self)
-{
-    ast_unref(self->dest);
-    ast_unref(self->source);
-    ast_expression_delete((ast_expression*)self);
-    mem_d(self);
-}
-
-ast_block* ast_block_new(lex_ctx_t ctx)
-{
-    ast_instantiate(ast_block, ctx, ast_block_delete);
-    ast_expression_init((ast_expression*)self,
-                        (ast_expression_codegen*)&ast_block_codegen);
-
-    self->locals  = NULL;
-    self->exprs   = NULL;
-    self->collect = NULL;
-
-    return self;
-}
-
-bool ast_block_add_expr(ast_block *self, ast_expression *e)
-{
-    ast_propagate_effects(self, e);
-    vec_push(self->exprs, e);
-    if (self->expression.next) {
-        ast_delete(self->expression.next);
-        self->expression.next = NULL;
-    }
-    ast_type_adopt(self, e);
-    return true;
-}
-
-void ast_block_collect(ast_block *self, ast_expression *expr)
-{
-    vec_push(self->collect, expr);
-    expr->node.keep = true;
-}
-
-void ast_block_delete(ast_block *self)
-{
-    size_t i;
-    for (i = 0; i < vec_size(self->exprs); ++i)
-        ast_unref(self->exprs[i]);
-    vec_free(self->exprs);
-    for (i = 0; i < vec_size(self->locals); ++i)
-        ast_delete(self->locals[i]);
-    vec_free(self->locals);
-    for (i = 0; i < vec_size(self->collect); ++i)
-        ast_delete(self->collect[i]);
-    vec_free(self->collect);
-    ast_expression_delete((ast_expression*)self);
-    mem_d(self);
-}
-
-void ast_block_set_type(ast_block *self, ast_expression *from)
-{
-    if (self->expression.next)
-        ast_delete(self->expression.next);
-    ast_type_adopt(self, from);
-}
-
-ast_function* ast_function_new(lex_ctx_t ctx, const char *name, ast_value *vtype)
-{
-    ast_instantiate(ast_function, ctx, ast_function_delete);
-
-    if (!vtype) {
-        compile_error(ast_ctx(self), "internal error: ast_function_new condition 0");
-        goto cleanup;
-    } else if (vtype->hasvalue || vtype->expression.vtype != TYPE_FUNCTION) {
-        compile_error(ast_ctx(self), "internal error: ast_function_new condition %i %i type=%i (probably 2 bodies?)",
-                 (int)!vtype,
-                 (int)vtype->hasvalue,
-                 vtype->expression.vtype);
-        goto cleanup;
-    }
-
-    self->vtype  = vtype;
-    self->name   = name ? util_strdup(name) : NULL;
-    self->blocks = NULL;
-
-    self->labelcount = 0;
-    self->builtin = 0;
-
-    self->ir_func = NULL;
-    self->curblock = NULL;
-
-    self->breakblocks    = NULL;
-    self->continueblocks = NULL;
-
-    vtype->hasvalue = true;
-    vtype->constval.vfunc = self;
-
-    self->varargs          = NULL;
-    self->argc             = NULL;
-    self->fixedparams      = NULL;
-    self->return_value     = NULL;
-
-    self->static_names     = NULL;
-    self->static_count     = 0;
-
-    return self;
-
-cleanup:
-    mem_d(self);
-    return NULL;
-}
-
-void ast_function_delete(ast_function *self)
-{
-    size_t i;
-    if (self->name)
-        mem_d((void*)self->name);
-    if (self->vtype) {
-        /* ast_value_delete(self->vtype); */
-        self->vtype->hasvalue = false;
-        self->vtype->constval.vfunc = NULL;
-        /* We use unref - if it was stored in a global table it is supposed
-         * to be deleted from *there*
-         */
-        ast_unref(self->vtype);
-    }
-    for (i = 0; i < vec_size(self->static_names); ++i)
-        mem_d(self->static_names[i]);
-    vec_free(self->static_names);
-    for (i = 0; i < vec_size(self->blocks); ++i)
-        ast_delete(self->blocks[i]);
-    vec_free(self->blocks);
-    vec_free(self->breakblocks);
-    vec_free(self->continueblocks);
-    if (self->varargs)
-        ast_delete(self->varargs);
-    if (self->argc)
-        ast_delete(self->argc);
-    if (self->fixedparams)
-        ast_unref(self->fixedparams);
-    if (self->return_value)
-        ast_unref(self->return_value);
-    mem_d(self);
-}
-
-const char* ast_function_label(ast_function *self, const char *prefix)
-{
-    size_t id;
-    size_t len;
-    char  *from;
-
-    if (!OPTS_OPTION_BOOL(OPTION_DUMP)    &&
-        !OPTS_OPTION_BOOL(OPTION_DUMPFIN) &&
-        !OPTS_OPTION_BOOL(OPTION_DEBUG))
-    {
-        return NULL;
-    }
-
-    id  = (self->labelcount++);
-    len = strlen(prefix);
-
-    from = self->labelbuf + sizeof(self->labelbuf)-1;
-    *from-- = 0;
-    do {
-        *from-- = (id%10) + '0';
-        id /= 10;
-    } while (id);
-    ++from;
-    memcpy(from - len, prefix, len);
-    return from - len;
-}
-
-/*********************************************************************/
-/* AST codegen part
- * by convention you must never pass NULL to the 'ir_value **out'
- * parameter. If you really don't care about the output, pass a dummy.
- * But I can't imagine a pituation where the output is truly unnecessary.
- */
-
-static void _ast_codegen_output_type(ast_expression *self, ir_value *out)
-{
-    if (out->vtype == TYPE_FIELD)
-        out->fieldtype = self->next->vtype;
-    if (out->vtype == TYPE_FUNCTION)
-        out->outtype = self->next->vtype;
-}
-
-#define codegen_output_type(a,o) (_ast_codegen_output_type(&((a)->expression),(o)))
-
-bool ast_value_codegen(ast_value *self, ast_function *func, bool lvalue, ir_value **out)
-{
-    (void)func;
-    (void)lvalue;
-    if (self->expression.vtype == TYPE_NIL) {
-        *out = func->ir_func->owner->nil;
-        return true;
-    }
-    /* NOTE: This is the codegen for a variable used in an expression.
-     * It is not the codegen to generate the value. For this purpose,
-     * ast_local_codegen and ast_global_codegen are to be used before this
-     * is executed. ast_function_codegen should take care of its locals,
-     * and the ast-user should take care of ast_global_codegen to be used
-     * on all the globals.
-     */
-    if (!self->ir_v) {
-        char tname[1024]; /* typename is reserved in C++ */
-        ast_type_to_string((ast_expression*)self, tname, sizeof(tname));
-        compile_error(ast_ctx(self), "ast_value used before generated %s %s", tname, self->name);
-        return false;
-    }
-    *out = self->ir_v;
-    return true;
-}
-
-static bool ast_global_array_set(ast_value *self)
-{
-    size_t count = vec_size(self->initlist);
-    size_t i;
-
-    if (count > self->expression.count) {
-        compile_error(ast_ctx(self), "too many elements in initializer");
-        count = self->expression.count;
-    }
-    else if (count < self->expression.count) {
-        /* add this?
-        compile_warning(ast_ctx(self), "not all elements are initialized");
-        */
-    }
-
-    for (i = 0; i != count; ++i) {
-        switch (self->expression.next->vtype) {
-            case TYPE_FLOAT:
-                if (!ir_value_set_float(self->ir_values[i], self->initlist[i].vfloat))
-                    return false;
-                break;
-            case TYPE_VECTOR:
-                if (!ir_value_set_vector(self->ir_values[i], self->initlist[i].vvec))
-                    return false;
-                break;
-            case TYPE_STRING:
-                if (!ir_value_set_string(self->ir_values[i], self->initlist[i].vstring))
-                    return false;
-                break;
-            case TYPE_ARRAY:
-                /* we don't support them in any other place yet either */
-                compile_error(ast_ctx(self), "TODO: nested arrays");
-                return false;
-            case TYPE_FUNCTION:
-                /* this requiers a bit more work - similar to the fields I suppose */
-                compile_error(ast_ctx(self), "global of type function not properly generated");
-                return false;
-            case TYPE_FIELD:
-                if (!self->initlist[i].vfield) {
-                    compile_error(ast_ctx(self), "field constant without vfield set");
-                    return false;
-                }
-                if (!self->initlist[i].vfield->ir_v) {
-                    compile_error(ast_ctx(self), "field constant generated before its field");
-                    return false;
-                }
-                if (!ir_value_set_field(self->ir_values[i], self->initlist[i].vfield->ir_v))
-                    return false;
-                break;
-            default:
-                compile_error(ast_ctx(self), "TODO: global constant type %i", self->expression.vtype);
-                break;
-        }
-    }
-    return true;
-}
-
-static bool check_array(ast_value *self, ast_value *array)
-{
-    if (array->expression.flags & AST_FLAG_ARRAY_INIT && !array->initlist) {
-        compile_error(ast_ctx(self), "array without size: %s", self->name);
-        return false;
-    }
-    /* we are lame now - considering the way QC works we won't tolerate arrays > 1024 elements */
-    if (!array->expression.count || array->expression.count > OPTS_OPTION_U32(OPTION_MAX_ARRAY_SIZE)) {
-        compile_error(ast_ctx(self), "Invalid array of size %lu", (unsigned long)array->expression.count);
-        return false;
-    }
-    return true;
-}
-
-bool ast_global_codegen(ast_value *self, ir_builder *ir, bool isfield)
-{
-    ir_value *v = NULL;
-
-    if (self->expression.vtype == TYPE_NIL) {
-        compile_error(ast_ctx(self), "internal error: trying to generate a variable of TYPE_NIL");
-        return false;
-    }
-
-    if (self->hasvalue && self->expression.vtype == TYPE_FUNCTION)
-    {
-        ir_function *func = ir_builder_create_function(ir, self->name, self->expression.next->vtype);
-        if (!func)
-            return false;
-        func->context = ast_ctx(self);
-        func->value->context = ast_ctx(self);
-
-        self->constval.vfunc->ir_func = func;
-        self->ir_v = func->value;
-        if (self->expression.flags & AST_FLAG_INCLUDE_DEF)
-            self->ir_v->flags |= IR_FLAG_INCLUDE_DEF;
-        if (self->expression.flags & AST_FLAG_ERASEABLE)
-            self->ir_v->flags |= IR_FLAG_ERASABLE;
-        if (self->expression.flags & AST_FLAG_BLOCK_COVERAGE)
-            func->flags |= IR_FLAG_BLOCK_COVERAGE;
-        /* The function is filled later on ast_function_codegen... */
-        return true;
-    }
-
-    if (isfield && self->expression.vtype == TYPE_FIELD) {
-        ast_expression *fieldtype = self->expression.next;
-
-        if (self->hasvalue) {
-            compile_error(ast_ctx(self), "TODO: constant field pointers with value");
-            goto error;
-        }
-
-        if (fieldtype->vtype == TYPE_ARRAY) {
-            size_t ai;
-            char   *name;
-            size_t  namelen;
-
-            ast_expression *elemtype;
-            int             vtype;
-            ast_value      *array = (ast_value*)fieldtype;
-
-            if (!ast_istype(fieldtype, ast_value)) {
-                compile_error(ast_ctx(self), "internal error: ast_value required");
-                return false;
-            }
-
-            if (!check_array(self, array))
-                return false;
-
-            elemtype = array->expression.next;
-            vtype = elemtype->vtype;
-
-            v = ir_builder_create_field(ir, self->name, vtype);
-            if (!v) {
-                compile_error(ast_ctx(self), "ir_builder_create_global failed on `%s`", self->name);
-                return false;
-            }
-            v->context = ast_ctx(self);
-            v->unique_life = true;
-            v->locked      = true;
-            array->ir_v = self->ir_v = v;
-
-            if (self->expression.flags & AST_FLAG_INCLUDE_DEF)
-                self->ir_v->flags |= IR_FLAG_INCLUDE_DEF;
-            if (self->expression.flags & AST_FLAG_ERASEABLE)
-                self->ir_v->flags |= IR_FLAG_ERASABLE;
-
-            namelen = strlen(self->name);
-            name    = (char*)mem_a(namelen + 16);
-            util_strncpy(name, self->name, namelen);
-
-            array->ir_values = (ir_value**)mem_a(sizeof(array->ir_values[0]) * array->expression.count);
-            array->ir_values[0] = v;
-            for (ai = 1; ai < array->expression.count; ++ai) {
-                util_snprintf(name + namelen, 16, "[%u]", (unsigned int)ai);
-                array->ir_values[ai] = ir_builder_create_field(ir, name, vtype);
-                if (!array->ir_values[ai]) {
-                    mem_d(name);
-                    compile_error(ast_ctx(self), "ir_builder_create_global failed on `%s`", name);
-                    return false;
-                }
-                array->ir_values[ai]->context = ast_ctx(self);
-                array->ir_values[ai]->unique_life = true;
-                array->ir_values[ai]->locked      = true;
-                if (self->expression.flags & AST_FLAG_INCLUDE_DEF)
-                    self->ir_values[ai]->flags |= IR_FLAG_INCLUDE_DEF;
-            }
-            mem_d(name);
-        }
-        else
-        {
-            v = ir_builder_create_field(ir, self->name, self->expression.next->vtype);
-            if (!v)
-                return false;
-            v->context = ast_ctx(self);
-            self->ir_v = v;
-            if (self->expression.flags & AST_FLAG_INCLUDE_DEF)
-                self->ir_v->flags |= IR_FLAG_INCLUDE_DEF;
-
-            if (self->expression.flags & AST_FLAG_ERASEABLE)
-                self->ir_v->flags |= IR_FLAG_ERASABLE;
-        }
-        return true;
-    }
-
-    if (self->expression.vtype == TYPE_ARRAY) {
-        size_t ai;
-        char   *name;
-        size_t  namelen;
-
-        ast_expression *elemtype = self->expression.next;
-        int vtype = elemtype->vtype;
-
-        if (self->expression.flags & AST_FLAG_ARRAY_INIT && !self->expression.count) {
-            compile_error(ast_ctx(self), "array `%s' has no size", self->name);
-            return false;
-        }
-
-        /* same as with field arrays */
-        if (!check_array(self, self))
-            return false;
-
-        v = ir_builder_create_global(ir, self->name, vtype);
-        if (!v) {
-            compile_error(ast_ctx(self), "ir_builder_create_global failed `%s`", self->name);
-            return false;
-        }
-        v->context = ast_ctx(self);
-        v->unique_life = true;
-        v->locked      = true;
-
-        if (self->expression.flags & AST_FLAG_INCLUDE_DEF)
-            v->flags |= IR_FLAG_INCLUDE_DEF;
-        if (self->expression.flags & AST_FLAG_ERASEABLE)
-            self->ir_v->flags |= IR_FLAG_ERASABLE;
-
-        namelen = strlen(self->name);
-        name    = (char*)mem_a(namelen + 16);
-        util_strncpy(name, self->name, namelen);
-
-        self->ir_values = (ir_value**)mem_a(sizeof(self->ir_values[0]) * self->expression.count);
-        self->ir_values[0] = v;
-        for (ai = 1; ai < self->expression.count; ++ai) {
-            util_snprintf(name + namelen, 16, "[%u]", (unsigned int)ai);
-            self->ir_values[ai] = ir_builder_create_global(ir, name, vtype);
-            if (!self->ir_values[ai]) {
-                mem_d(name);
-                compile_error(ast_ctx(self), "ir_builder_create_global failed `%s`", name);
-                return false;
-            }
-            self->ir_values[ai]->context = ast_ctx(self);
-            self->ir_values[ai]->unique_life = true;
-            self->ir_values[ai]->locked      = true;
-            if (self->expression.flags & AST_FLAG_INCLUDE_DEF)
-                self->ir_values[ai]->flags |= IR_FLAG_INCLUDE_DEF;
-        }
-        mem_d(name);
-    }
-    else
-    {
-        /* Arrays don't do this since there's no "array" value which spans across the
-         * whole thing.
-         */
-        v = ir_builder_create_global(ir, self->name, self->expression.vtype);
-        if (!v) {
-            compile_error(ast_ctx(self), "ir_builder_create_global failed on `%s`", self->name);
-            return false;
-        }
-        codegen_output_type(self, v);
-        v->context = ast_ctx(self);
-    }
-
-    /* link us to the ir_value */
-    v->cvq = self->cvq;
-    self->ir_v = v;
-
-    if (self->expression.flags & AST_FLAG_INCLUDE_DEF)
-        self->ir_v->flags |= IR_FLAG_INCLUDE_DEF;
-    if (self->expression.flags & AST_FLAG_ERASEABLE)
-        self->ir_v->flags |= IR_FLAG_ERASABLE;
-
-    /* initialize */
-    if (self->hasvalue) {
-        switch (self->expression.vtype)
-        {
-            case TYPE_FLOAT:
-                if (!ir_value_set_float(v, self->constval.vfloat))
-                    goto error;
-                break;
-            case TYPE_VECTOR:
-                if (!ir_value_set_vector(v, self->constval.vvec))
-                    goto error;
-                break;
-            case TYPE_STRING:
-                if (!ir_value_set_string(v, self->constval.vstring))
-                    goto error;
-                break;
-            case TYPE_ARRAY:
-                ast_global_array_set(self);
-                break;
-            case TYPE_FUNCTION:
-                compile_error(ast_ctx(self), "global of type function not properly generated");
-                goto error;
-                /* Cannot generate an IR value for a function,
-                 * need a pointer pointing to a function rather.
-                 */
-            case TYPE_FIELD:
-                if (!self->constval.vfield) {
-                    compile_error(ast_ctx(self), "field constant without vfield set");
-                    goto error;
-                }
-                if (!self->constval.vfield->ir_v) {
-                    compile_error(ast_ctx(self), "field constant generated before its field");
-                    goto error;
-                }
-                if (!ir_value_set_field(v, self->constval.vfield->ir_v))
-                    goto error;
-                break;
-            default:
-                compile_error(ast_ctx(self), "TODO: global constant type %i", self->expression.vtype);
-                break;
-        }
-    }
-    return true;
-
-error: /* clean up */
-    if(v) ir_value_delete(v);
-    return false;
-}
-
-static bool ast_local_codegen(ast_value *self, ir_function *func, bool param)
-{
-    ir_value *v = NULL;
-
-    if (self->expression.vtype == TYPE_NIL) {
-        compile_error(ast_ctx(self), "internal error: trying to generate a variable of TYPE_NIL");
-        return false;
-    }
-
-    if (self->hasvalue && self->expression.vtype == TYPE_FUNCTION)
-    {
-        /* Do we allow local functions? I think not...
-         * this is NOT a function pointer atm.
-         */
-        return false;
-    }
-
-    if (self->expression.vtype == TYPE_ARRAY) {
-        size_t ai;
-        char   *name;
-        size_t  namelen;
-
-        ast_expression *elemtype = self->expression.next;
-        int vtype = elemtype->vtype;
-
-        func->flags |= IR_FLAG_HAS_ARRAYS;
-
-        if (param && !(self->expression.flags & AST_FLAG_IS_VARARG)) {
-            compile_error(ast_ctx(self), "array-parameters are not supported");
-            return false;
-        }
-
-        /* we are lame now - considering the way QC works we won't tolerate arrays > 1024 elements */
-        if (!check_array(self, self))
-            return false;
-
-        self->ir_values = (ir_value**)mem_a(sizeof(self->ir_values[0]) * self->expression.count);
-        if (!self->ir_values) {
-            compile_error(ast_ctx(self), "failed to allocate array values");
-            return false;
-        }
-
-        v = ir_function_create_local(func, self->name, vtype, param);
-        if (!v) {
-            compile_error(ast_ctx(self), "internal error: ir_function_create_local failed");
-            return false;
-        }
-        v->context = ast_ctx(self);
-        v->unique_life = true;
-        v->locked      = true;
-
-        namelen = strlen(self->name);
-        name    = (char*)mem_a(namelen + 16);
-        util_strncpy(name, self->name, namelen);
-
-        self->ir_values[0] = v;
-        for (ai = 1; ai < self->expression.count; ++ai) {
-            util_snprintf(name + namelen, 16, "[%u]", (unsigned int)ai);
-            self->ir_values[ai] = ir_function_create_local(func, name, vtype, param);
-            if (!self->ir_values[ai]) {
-                compile_error(ast_ctx(self), "internal_error: ir_builder_create_global failed on `%s`", name);
-                return false;
-            }
-            self->ir_values[ai]->context = ast_ctx(self);
-            self->ir_values[ai]->unique_life = true;
-            self->ir_values[ai]->locked      = true;
-        }
-        mem_d(name);
-    }
-    else
-    {
-        v = ir_function_create_local(func, self->name, self->expression.vtype, param);
-        if (!v)
-            return false;
-        codegen_output_type(self, v);
-        v->context = ast_ctx(self);
-    }
-
-    /* A constant local... hmmm...
-     * I suppose the IR will have to deal with this
-     */
-    if (self->hasvalue) {
-        switch (self->expression.vtype)
-        {
-            case TYPE_FLOAT:
-                if (!ir_value_set_float(v, self->constval.vfloat))
-                    goto error;
-                break;
-            case TYPE_VECTOR:
-                if (!ir_value_set_vector(v, self->constval.vvec))
-                    goto error;
-                break;
-            case TYPE_STRING:
-                if (!ir_value_set_string(v, self->constval.vstring))
-                    goto error;
-                break;
-            default:
-                compile_error(ast_ctx(self), "TODO: global constant type %i", self->expression.vtype);
-                break;
-        }
-    }
-
-    /* link us to the ir_value */
-    v->cvq = self->cvq;
-    self->ir_v = v;
-
-    if (!ast_generate_accessors(self, func->owner))
-        return false;
-    return true;
-
-error: /* clean up */
-    ir_value_delete(v);
-    return false;
-}
-
-bool ast_generate_accessors(ast_value *self, ir_builder *ir)
-{
-    size_t i;
-    bool warn = OPTS_WARN(WARN_USED_UNINITIALIZED);
-    if (!self->setter || !self->getter)
-        return true;
-    for (i = 0; i < self->expression.count; ++i) {
-        if (!self->ir_values) {
-            compile_error(ast_ctx(self), "internal error: no array values generated for `%s`", self->name);
-            return false;
-        }
-        if (!self->ir_values[i]) {
-            compile_error(ast_ctx(self), "internal error: not all array values have been generated for `%s`", self->name);
-            return false;
-        }
-        if (self->ir_values[i]->life) {
-            compile_error(ast_ctx(self), "internal error: function containing `%s` already generated", self->name);
-            return false;
-        }
-    }
-
-    opts_set(opts.warn, WARN_USED_UNINITIALIZED, false);
-    if (self->setter) {
-        if (!ast_global_codegen  (self->setter, ir, false) ||
-            !ast_function_codegen(self->setter->constval.vfunc, ir) ||
-            !ir_function_finalize(self->setter->constval.vfunc->ir_func))
-        {
-            compile_error(ast_ctx(self), "internal error: failed to generate setter for `%s`", self->name);
-            opts_set(opts.warn, WARN_USED_UNINITIALIZED, warn);
-            return false;
-        }
-    }
-    if (self->getter) {
-        if (!ast_global_codegen  (self->getter, ir, false) ||
-            !ast_function_codegen(self->getter->constval.vfunc, ir) ||
-            !ir_function_finalize(self->getter->constval.vfunc->ir_func))
-        {
-            compile_error(ast_ctx(self), "internal error: failed to generate getter for `%s`", self->name);
-            opts_set(opts.warn, WARN_USED_UNINITIALIZED, warn);
-            return false;
-        }
-    }
-    for (i = 0; i < self->expression.count; ++i) {
-        vec_free(self->ir_values[i]->life);
-    }
-    opts_set(opts.warn, WARN_USED_UNINITIALIZED, warn);
-    return true;
-}
-
-bool ast_function_codegen(ast_function *self, ir_builder *ir)
-{
-    ir_function *irf;
-    ir_value    *dummy;
-    ast_expression         *ec;
-    ast_expression_codegen *cgen;
-
-    size_t    i;
-
-    (void)ir;
-
-    irf = self->ir_func;
-    if (!irf) {
-        compile_error(ast_ctx(self), "internal error: ast_function's related ast_value was not generated yet");
-        return false;
-    }
-
-    /* fill the parameter list */
-    ec = &self->vtype->expression;
-    for (i = 0; i < vec_size(ec->params); ++i)
-    {
-        if (ec->params[i]->expression.vtype == TYPE_FIELD)
-            vec_push(irf->params, ec->params[i]->expression.next->vtype);
-        else
-            vec_push(irf->params, ec->params[i]->expression.vtype);
-        if (!self->builtin) {
-            if (!ast_local_codegen(ec->params[i], self->ir_func, true))
-                return false;
-        }
-    }
-
-    if (self->varargs) {
-        if (!ast_local_codegen(self->varargs, self->ir_func, true))
-            return false;
-        irf->max_varargs = self->varargs->expression.count;
-    }
-
-    if (self->builtin) {
-        irf->builtin = self->builtin;
-        return true;
-    }
-
-    /* have a local return value variable? */
-    if (self->return_value) {
-        if (!ast_local_codegen(self->return_value, self->ir_func, false))
-            return false;
-    }
-
-    if (!vec_size(self->blocks)) {
-        compile_error(ast_ctx(self), "function `%s` has no body", self->name);
-        return false;
-    }
-
-    irf->first = self->curblock = ir_function_create_block(ast_ctx(self), irf, "entry");
-    if (!self->curblock) {
-        compile_error(ast_ctx(self), "failed to allocate entry block for `%s`", self->name);
-        return false;
-    }
-
-    if (self->argc) {
-        ir_value *va_count;
-        ir_value *fixed;
-        ir_value *sub;
-        if (!ast_local_codegen(self->argc, self->ir_func, true))
-            return false;
-        cgen = self->argc->expression.codegen;
-        if (!(*cgen)((ast_expression*)(self->argc), self, false, &va_count))
-            return false;
-        cgen = self->fixedparams->expression.codegen;
-        if (!(*cgen)((ast_expression*)(self->fixedparams), self, false, &fixed))
-            return false;
-        sub = ir_block_create_binop(self->curblock, ast_ctx(self),
-                                    ast_function_label(self, "va_count"), INSTR_SUB_F,
-                                    ir_builder_get_va_count(ir), fixed);
-        if (!sub)
-            return false;
-        if (!ir_block_create_store_op(self->curblock, ast_ctx(self), INSTR_STORE_F,
-                                      va_count, sub))
-        {
-            return false;
-        }
-    }
-
-    for (i = 0; i < vec_size(self->blocks); ++i) {
-        cgen = self->blocks[i]->expression.codegen;
-        if (!(*cgen)((ast_expression*)self->blocks[i], self, false, &dummy))
-            return false;
-    }
-
-    /* TODO: check return types */
-    if (!self->curblock->final)
-    {
-        if (!self->vtype->expression.next ||
-            self->vtype->expression.next->vtype == TYPE_VOID)
-        {
-            return ir_block_create_return(self->curblock, ast_ctx(self), NULL);
-        }
-        else if (vec_size(self->curblock->entries) || self->curblock == irf->first)
-        {
-            if (self->return_value) {
-                cgen = self->return_value->expression.codegen;
-                if (!(*cgen)((ast_expression*)(self->return_value), self, false, &dummy))
-                    return false;
-                return ir_block_create_return(self->curblock, ast_ctx(self), dummy);
-            }
-            else if (compile_warning(ast_ctx(self), WARN_MISSING_RETURN_VALUES,
-                                "control reaches end of non-void function (`%s`) via %s",
-                                self->name, self->curblock->label))
-            {
-                return false;
-            }
-            return ir_block_create_return(self->curblock, ast_ctx(self), NULL);
-        }
-    }
-    return true;
-}
-
-static bool starts_a_label(ast_expression *ex)
-{
-    while (ex && ast_istype(ex, ast_block)) {
-        ast_block *b = (ast_block*)ex;
-        ex = b->exprs[0];
-    }
-    if (!ex)
-        return false;
-    return ast_istype(ex, ast_label);
-}
-
-/* Note, you will not see ast_block_codegen generate ir_blocks.
- * To the AST and the IR, blocks are 2 different things.
- * In the AST it represents a block of code, usually enclosed in
- * curly braces {...}.
- * While in the IR it represents a block in terms of control-flow.
- */
-bool ast_block_codegen(ast_block *self, ast_function *func, bool lvalue, ir_value **out)
-{
-    size_t i;
-
-    /* We don't use this
-     * Note: an ast-representation using the comma-operator
-     * of the form: (a, b, c) = x should not assign to c...
-     */
-    if (lvalue) {
-        compile_error(ast_ctx(self), "not an l-value (code-block)");
-        return false;
-    }
-
-    if (self->expression.outr) {
-        *out = self->expression.outr;
-        return true;
-    }
-
-    /* output is NULL at first, we'll have each expression
-     * assign to out output, thus, a comma-operator represention
-     * using an ast_block will return the last generated value,
-     * so: (b, c) + a  executed both b and c, and returns c,
-     * which is then added to a.
-     */
-    *out = NULL;
-
-    /* generate locals */
-    for (i = 0; i < vec_size(self->locals); ++i)
-    {
-        if (!ast_local_codegen(self->locals[i], func->ir_func, false)) {
-            if (OPTS_OPTION_BOOL(OPTION_DEBUG))
-                compile_error(ast_ctx(self), "failed to generate local `%s`", self->locals[i]->name);
-            return false;
-        }
-    }
-
-    for (i = 0; i < vec_size(self->exprs); ++i)
-    {
-        ast_expression_codegen *gen;
-        if (func->curblock->final && !starts_a_label(self->exprs[i])) {
-            if (compile_warning(ast_ctx(self->exprs[i]), WARN_UNREACHABLE_CODE, "unreachable statement"))
-                return false;
-            continue;
-        }
-        gen = self->exprs[i]->codegen;
-        if (!(*gen)(self->exprs[i], func, false, out))
-            return false;
-    }
-
-    self->expression.outr = *out;
-
-    return true;
-}
-
-bool ast_store_codegen(ast_store *self, ast_function *func, bool lvalue, ir_value **out)
-{
-    ast_expression_codegen *cgen;
-    ir_value *left  = NULL;
-    ir_value *right = NULL;
-
-    ast_value       *arr;
-    ast_value       *idx = 0;
-    ast_array_index *ai = NULL;
-
-    if (lvalue && self->expression.outl) {
-        *out = self->expression.outl;
-        return true;
-    }
-
-    if (!lvalue && self->expression.outr) {
-        *out = self->expression.outr;
-        return true;
-    }
-
-    if (ast_istype(self->dest, ast_array_index))
-    {
-
-        ai = (ast_array_index*)self->dest;
-        idx = (ast_value*)ai->index;
-
-        if (ast_istype(ai->index, ast_value) && idx->hasvalue && idx->cvq == CV_CONST)
-            ai = NULL;
-    }
-
-    if (ai) {
-        /* we need to call the setter */
-        ir_value  *iridx, *funval;
-        ir_instr  *call;
-
-        if (lvalue) {
-            compile_error(ast_ctx(self), "array-subscript assignment cannot produce lvalues");
-            return false;
-        }
-
-        arr = (ast_value*)ai->array;
-        if (!ast_istype(ai->array, ast_value) || !arr->setter) {
-            compile_error(ast_ctx(self), "value has no setter (%s)", arr->name);
-            return false;
-        }
-
-        cgen = idx->expression.codegen;
-        if (!(*cgen)((ast_expression*)(idx), func, false, &iridx))
-            return false;
-
-        cgen = arr->setter->expression.codegen;
-        if (!(*cgen)((ast_expression*)(arr->setter), func, true, &funval))
-            return false;
-
-        cgen = self->source->codegen;
-        if (!(*cgen)((ast_expression*)(self->source), func, false, &right))
-            return false;
-
-        call = ir_block_create_call(func->curblock, ast_ctx(self), ast_function_label(func, "store"), funval, false);
-        if (!call)
-            return false;
-        ir_call_param(call, iridx);
-        ir_call_param(call, right);
-        self->expression.outr = right;
-    }
-    else
-    {
-        /* regular code */
-
-        cgen = self->dest->codegen;
-        /* lvalue! */
-        if (!(*cgen)((ast_expression*)(self->dest), func, true, &left))
-            return false;
-        self->expression.outl = left;
-
-        cgen = self->source->codegen;
-        /* rvalue! */
-        if (!(*cgen)((ast_expression*)(self->source), func, false, &right))
-            return false;
-
-        if (!ir_block_create_store_op(func->curblock, ast_ctx(self), self->op, left, right))
-            return false;
-        self->expression.outr = right;
-    }
-
-    /* Theoretically, an assinment returns its left side as an
-     * lvalue, if we don't need an lvalue though, we return
-     * the right side as an rvalue, otherwise we have to
-     * somehow know whether or not we need to dereference the pointer
-     * on the left side - that is: OP_LOAD if it was an address.
-     * Also: in original QC we cannot OP_LOADP *anyway*.
-     */
-    *out = (lvalue ? left : right);
-
-    return true;
-}
-
-bool ast_binary_codegen(ast_binary *self, ast_function *func, bool lvalue, ir_value **out)
-{
-    ast_expression_codegen *cgen;
-    ir_value *left, *right;
-
-    /* A binary operation cannot yield an l-value */
-    if (lvalue) {
-        compile_error(ast_ctx(self), "not an l-value (binop)");
-        return false;
-    }
-
-    if (self->expression.outr) {
-        *out = self->expression.outr;
-        return true;
-    }
-
-    if ((OPTS_FLAG(SHORT_LOGIC) || OPTS_FLAG(PERL_LOGIC)) &&
-        (self->op == INSTR_AND || self->op == INSTR_OR))
-    {
-        /* NOTE: The short-logic path will ignore right_first */
-
-        /* short circuit evaluation */
-        ir_block *other, *merge;
-        ir_block *from_left, *from_right;
-        ir_instr *phi;
-        size_t    merge_id;
-
-        /* prepare end-block */
-        merge_id = vec_size(func->ir_func->blocks);
-        merge    = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "sce_merge"));
-
-        /* generate the left expression */
-        cgen = self->left->codegen;
-        if (!(*cgen)((ast_expression*)(self->left), func, false, &left))
-            return false;
-        /* remember the block */
-        from_left = func->curblock;
-
-        /* create a new block for the right expression */
-        other = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "sce_other"));
-        if (self->op == INSTR_AND) {
-            /* on AND: left==true -> other */
-            if (!ir_block_create_if(func->curblock, ast_ctx(self), left, other, merge))
-                return false;
-        } else {
-            /* on OR: left==false -> other */
-            if (!ir_block_create_if(func->curblock, ast_ctx(self), left, merge, other))
-                return false;
-        }
-        /* use the likely flag */
-        vec_last(func->curblock->instr)->likely = true;
-
-        /* enter the right-expression's block */
-        func->curblock = other;
-        /* generate */
-        cgen = self->right->codegen;
-        if (!(*cgen)((ast_expression*)(self->right), func, false, &right))
-            return false;
-        /* remember block */
-        from_right = func->curblock;
-
-        /* jump to the merge block */
-        if (!ir_block_create_jump(func->curblock, ast_ctx(self), merge))
-            return false;
-
-        vec_remove(func->ir_func->blocks, merge_id, 1);
-        vec_push(func->ir_func->blocks, merge);
-
-        func->curblock = merge;
-        phi = ir_block_create_phi(func->curblock, ast_ctx(self),
-                                  ast_function_label(func, "sce_value"),
-                                  self->expression.vtype);
-        ir_phi_add(phi, from_left, left);
-        ir_phi_add(phi, from_right, right);
-        *out = ir_phi_value(phi);
-        if (!*out)
-            return false;
-
-        if (!OPTS_FLAG(PERL_LOGIC)) {
-            /* cast-to-bool */
-            if (OPTS_FLAG(CORRECT_LOGIC) && (*out)->vtype == TYPE_VECTOR) {
-                *out = ir_block_create_unary(func->curblock, ast_ctx(self),
-                                             ast_function_label(func, "sce_bool_v"),
-                                             INSTR_NOT_V, *out);
-                if (!*out)
-                    return false;
-                *out = ir_block_create_unary(func->curblock, ast_ctx(self),
-                                             ast_function_label(func, "sce_bool"),
-                                             INSTR_NOT_F, *out);
-                if (!*out)
-                    return false;
-            }
-            else if (OPTS_FLAG(FALSE_EMPTY_STRINGS) && (*out)->vtype == TYPE_STRING) {
-                *out = ir_block_create_unary(func->curblock, ast_ctx(self),
-                                             ast_function_label(func, "sce_bool_s"),
-                                             INSTR_NOT_S, *out);
-                if (!*out)
-                    return false;
-                *out = ir_block_create_unary(func->curblock, ast_ctx(self),
-                                             ast_function_label(func, "sce_bool"),
-                                             INSTR_NOT_F, *out);
-                if (!*out)
-                    return false;
-            }
-            else {
-                *out = ir_block_create_binop(func->curblock, ast_ctx(self),
-                                             ast_function_label(func, "sce_bool"),
-                                             INSTR_AND, *out, *out);
-                if (!*out)
-                    return false;
-            }
-        }
-
-        self->expression.outr = *out;
-        codegen_output_type(self, *out);
-        return true;
-    }
-
-    if (self->right_first) {
-        cgen = self->right->codegen;
-        if (!(*cgen)((ast_expression*)(self->right), func, false, &right))
-            return false;
-        cgen = self->left->codegen;
-        if (!(*cgen)((ast_expression*)(self->left), func, false, &left))
-            return false;
-    } else {
-        cgen = self->left->codegen;
-        if (!(*cgen)((ast_expression*)(self->left), func, false, &left))
-            return false;
-        cgen = self->right->codegen;
-        if (!(*cgen)((ast_expression*)(self->right), func, false, &right))
-            return false;
-    }
-
-    *out = ir_block_create_binop(func->curblock, ast_ctx(self), ast_function_label(func, "bin"),
-                                 self->op, left, right);
-    if (!*out)
-        return false;
-    self->expression.outr = *out;
-    codegen_output_type(self, *out);
-
-    return true;
-}
-
-bool ast_binstore_codegen(ast_binstore *self, ast_function *func, bool lvalue, ir_value **out)
-{
-    ast_expression_codegen *cgen;
-    ir_value *leftl = NULL, *leftr, *right, *bin;
-
-    ast_value       *arr;
-    ast_value       *idx = 0;
-    ast_array_index *ai = NULL;
-    ir_value        *iridx = NULL;
-
-    if (lvalue && self->expression.outl) {
-        *out = self->expression.outl;
-        return true;
-    }
-
-    if (!lvalue && self->expression.outr) {
-        *out = self->expression.outr;
-        return true;
-    }
-
-    if (ast_istype(self->dest, ast_array_index))
-    {
-
-        ai = (ast_array_index*)self->dest;
-        idx = (ast_value*)ai->index;
-
-        if (ast_istype(ai->index, ast_value) && idx->hasvalue && idx->cvq == CV_CONST)
-            ai = NULL;
-    }
-
-    /* for a binstore we need both an lvalue and an rvalue for the left side */
-    /* rvalue of destination! */
-    if (ai) {
-        cgen = idx->expression.codegen;
-        if (!(*cgen)((ast_expression*)(idx), func, false, &iridx))
-            return false;
-    }
-    cgen = self->dest->codegen;
-    if (!(*cgen)((ast_expression*)(self->dest), func, false, &leftr))
-        return false;
-
-    /* source as rvalue only */
-    cgen = self->source->codegen;
-    if (!(*cgen)((ast_expression*)(self->source), func, false, &right))
-        return false;
-
-    /* now the binary */
-    bin = ir_block_create_binop(func->curblock, ast_ctx(self), ast_function_label(func, "binst"),
-                                self->opbin, leftr, right);
-    self->expression.outr = bin;
-
-
-    if (ai) {
-        /* we need to call the setter */
-        ir_value  *funval;
-        ir_instr  *call;
-
-        if (lvalue) {
-            compile_error(ast_ctx(self), "array-subscript assignment cannot produce lvalues");
-            return false;
-        }
-
-        arr = (ast_value*)ai->array;
-        if (!ast_istype(ai->array, ast_value) || !arr->setter) {
-            compile_error(ast_ctx(self), "value has no setter (%s)", arr->name);
-            return false;
-        }
-
-        cgen = arr->setter->expression.codegen;
-        if (!(*cgen)((ast_expression*)(arr->setter), func, true, &funval))
-            return false;
-
-        call = ir_block_create_call(func->curblock, ast_ctx(self), ast_function_label(func, "store"), funval, false);
-        if (!call)
-            return false;
-        ir_call_param(call, iridx);
-        ir_call_param(call, bin);
-        self->expression.outr = bin;
-    } else {
-        /* now store them */
-        cgen = self->dest->codegen;
-        /* lvalue of destination */
-        if (!(*cgen)((ast_expression*)(self->dest), func, true, &leftl))
-            return false;
-        self->expression.outl = leftl;
-
-        if (!ir_block_create_store_op(func->curblock, ast_ctx(self), self->opstore, leftl, bin))
-            return false;
-        self->expression.outr = bin;
-    }
-
-    /* Theoretically, an assinment returns its left side as an
-     * lvalue, if we don't need an lvalue though, we return
-     * the right side as an rvalue, otherwise we have to
-     * somehow know whether or not we need to dereference the pointer
-     * on the left side - that is: OP_LOAD if it was an address.
-     * Also: in original QC we cannot OP_LOADP *anyway*.
-     */
-    *out = (lvalue ? leftl : bin);
-
-    return true;
-}
-
-bool ast_unary_codegen(ast_unary *self, ast_function *func, bool lvalue, ir_value **out)
-{
-    ast_expression_codegen *cgen;
-    ir_value *operand;
-
-    /* An unary operation cannot yield an l-value */
-    if (lvalue) {
-        compile_error(ast_ctx(self), "not an l-value (binop)");
-        return false;
-    }
-
-    if (self->expression.outr) {
-        *out = self->expression.outr;
-        return true;
-    }
-
-    cgen = self->operand->codegen;
-    /* lvalue! */
-    if (!(*cgen)((ast_expression*)(self->operand), func, false, &operand))
-        return false;
-
-    *out = ir_block_create_unary(func->curblock, ast_ctx(self), ast_function_label(func, "unary"),
-                                 self->op, operand);
-    if (!*out)
-        return false;
-    self->expression.outr = *out;
-
-    return true;
-}
-
-bool ast_return_codegen(ast_return *self, ast_function *func, bool lvalue, ir_value **out)
-{
-    ast_expression_codegen *cgen;
-    ir_value *operand;
-
-    *out = NULL;
-
-    /* In the context of a return operation, we don't actually return
-     * anything...
-     */
-    if (lvalue) {
-        compile_error(ast_ctx(self), "return-expression is not an l-value");
-        return false;
-    }
-
-    if (self->expression.outr) {
-        compile_error(ast_ctx(self), "internal error: ast_return cannot be reused, it bears no result!");
-        return false;
-    }
-    self->expression.outr = (ir_value*)1;
-
-    if (self->operand) {
-        cgen = self->operand->codegen;
-        /* lvalue! */
-        if (!(*cgen)((ast_expression*)(self->operand), func, false, &operand))
-            return false;
-
-        if (!ir_block_create_return(func->curblock, ast_ctx(self), operand))
-            return false;
-    } else {
-        if (!ir_block_create_return(func->curblock, ast_ctx(self), NULL))
-            return false;
-    }
-
-    return true;
-}
-
-bool ast_entfield_codegen(ast_entfield *self, ast_function *func, bool lvalue, ir_value **out)
-{
-    ast_expression_codegen *cgen;
-    ir_value *ent, *field;
-
-    /* This function needs to take the 'lvalue' flag into account!
-     * As lvalue we provide a field-pointer, as rvalue we provide the
-     * value in a temp.
-     */
-
-    if (lvalue && self->expression.outl) {
-        *out = self->expression.outl;
-        return true;
-    }
-
-    if (!lvalue && self->expression.outr) {
-        *out = self->expression.outr;
-        return true;
-    }
-
-    cgen = self->entity->codegen;
-    if (!(*cgen)((ast_expression*)(self->entity), func, false, &ent))
-        return false;
-
-    cgen = self->field->codegen;
-    if (!(*cgen)((ast_expression*)(self->field), func, false, &field))
-        return false;
-
-    if (lvalue) {
-        /* address! */
-        *out = ir_block_create_fieldaddress(func->curblock, ast_ctx(self), ast_function_label(func, "efa"),
-                                            ent, field);
-    } else {
-        *out = ir_block_create_load_from_ent(func->curblock, ast_ctx(self), ast_function_label(func, "efv"),
-                                             ent, field, self->expression.vtype);
-        /* Done AFTER error checking:
-        codegen_output_type(self, *out);
-        */
-    }
-    if (!*out) {
-        compile_error(ast_ctx(self), "failed to create %s instruction (output type %s)",
-                 (lvalue ? "ADDRESS" : "FIELD"),
-                 type_name[self->expression.vtype]);
-        return false;
-    }
-    if (!lvalue)
-        codegen_output_type(self, *out);
-
-    if (lvalue)
-        self->expression.outl = *out;
-    else
-        self->expression.outr = *out;
-
-    /* Hm that should be it... */
-    return true;
-}
-
-bool ast_member_codegen(ast_member *self, ast_function *func, bool lvalue, ir_value **out)
-{
-    ast_expression_codegen *cgen;
-    ir_value *vec;
-
-    /* in QC this is always an lvalue */
-    if (lvalue && self->rvalue) {
-        compile_error(ast_ctx(self), "not an l-value (member access)");
-        return false;
-    }
-    if (self->expression.outl) {
-        *out = self->expression.outl;
-        return true;
-    }
-
-    cgen = self->owner->codegen;
-    if (!(*cgen)((ast_expression*)(self->owner), func, false, &vec))
-        return false;
-
-    if (vec->vtype != TYPE_VECTOR &&
-        !(vec->vtype == TYPE_FIELD && self->owner->next->vtype == TYPE_VECTOR))
-    {
-        return false;
-    }
-
-    *out = ir_value_vector_member(vec, self->field);
-    self->expression.outl = *out;
-
-    return (*out != NULL);
-}
-
-bool ast_array_index_codegen(ast_array_index *self, ast_function *func, bool lvalue, ir_value **out)
-{
-    ast_value *arr;
-    ast_value *idx;
-
-    if (!lvalue && self->expression.outr) {
-        *out = self->expression.outr;
-        return true;
-    }
-    if (lvalue && self->expression.outl) {
-        *out = self->expression.outl;
-        return true;
-    }
-
-    if (!ast_istype(self->array, ast_value)) {
-        compile_error(ast_ctx(self), "array indexing this way is not supported");
-        /* note this would actually be pointer indexing because the left side is
-         * not an actual array but (hopefully) an indexable expression.
-         * Once we get integer arithmetic, and GADDRESS/GSTORE/GLOAD instruction
-         * support this path will be filled.
-         */
-        return false;
-    }
-
-    arr = (ast_value*)self->array;
-    idx = (ast_value*)self->index;
-
-    if (!ast_istype(self->index, ast_value) || !idx->hasvalue || idx->cvq != CV_CONST) {
-        /* Time to use accessor functions */
-        ast_expression_codegen *cgen;
-        ir_value               *iridx, *funval;
-        ir_instr               *call;
-
-        if (lvalue) {
-            compile_error(ast_ctx(self), "(.2) array indexing here needs a compile-time constant");
-            return false;
-        }
-
-        if (!arr->getter) {
-            compile_error(ast_ctx(self), "value has no getter, don't know how to index it");
-            return false;
-        }
-
-        cgen = self->index->codegen;
-        if (!(*cgen)((ast_expression*)(self->index), func, false, &iridx))
-            return false;
-
-        cgen = arr->getter->expression.codegen;
-        if (!(*cgen)((ast_expression*)(arr->getter), func, true, &funval))
-            return false;
-
-        call = ir_block_create_call(func->curblock, ast_ctx(self), ast_function_label(func, "fetch"), funval, false);
-        if (!call)
-            return false;
-        ir_call_param(call, iridx);
-
-        *out = ir_call_value(call);
-        self->expression.outr = *out;
-        (*out)->vtype = self->expression.vtype;
-        codegen_output_type(self, *out);
-        return true;
-    }
-
-    if (idx->expression.vtype == TYPE_FLOAT) {
-        unsigned int arridx = idx->constval.vfloat;
-        if (arridx >= self->array->count)
-        {
-            compile_error(ast_ctx(self), "array index out of bounds: %i", arridx);
-            return false;
-        }
-        *out = arr->ir_values[arridx];
-    }
-    else if (idx->expression.vtype == TYPE_INTEGER) {
-        unsigned int arridx = idx->constval.vint;
-        if (arridx >= self->array->count)
-        {
-            compile_error(ast_ctx(self), "array index out of bounds: %i", arridx);
-            return false;
-        }
-        *out = arr->ir_values[arridx];
-    }
-    else {
-        compile_error(ast_ctx(self), "array indexing here needs an integer constant");
-        return false;
-    }
-    (*out)->vtype = self->expression.vtype;
-    codegen_output_type(self, *out);
-    return true;
-}
-
-bool ast_argpipe_codegen(ast_argpipe *self, ast_function *func, bool lvalue, ir_value **out)
-{
-    *out = NULL;
-    if (lvalue) {
-        compile_error(ast_ctx(self), "argpipe node: not an lvalue");
-        return false;
-    }
-    (void)func;
-    (void)out;
-    compile_error(ast_ctx(self), "TODO: argpipe codegen not implemented");
-    return false;
-}
-
-bool ast_ifthen_codegen(ast_ifthen *self, ast_function *func, bool lvalue, ir_value **out)
-{
-    ast_expression_codegen *cgen;
-
-    ir_value *condval;
-    ir_value *dummy;
-
-    ir_block *cond;
-    ir_block *ontrue;
-    ir_block *onfalse;
-    ir_block *ontrue_endblock = NULL;
-    ir_block *onfalse_endblock = NULL;
-    ir_block *merge = NULL;
-    int       fold  = 0;
-
-    /* We don't output any value, thus also don't care about r/lvalue */
-    (void)out;
-    (void)lvalue;
-
-    if (self->expression.outr) {
-        compile_error(ast_ctx(self), "internal error: ast_ifthen cannot be reused, it bears no result!");
-        return false;
-    }
-    self->expression.outr = (ir_value*)1;
-
-    /* generate the condition */
-    cgen = self->cond->codegen;
-    if (!(*cgen)((ast_expression*)(self->cond), func, false, &condval))
-        return false;
-    /* update the block which will get the jump - because short-logic or ternaries may have changed this */
-    cond = func->curblock;
-
-    /* try constant folding away the condition */
-    if ((fold = fold_cond_ifthen(condval, func, self)) != -1)
-        return fold;
-
-    if (self->on_true) {
-        /* create on-true block */
-        ontrue = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "ontrue"));
-        if (!ontrue)
-            return false;
-
-        /* enter the block */
-        func->curblock = ontrue;
-
-        /* generate */
-        cgen = self->on_true->codegen;
-        if (!(*cgen)((ast_expression*)(self->on_true), func, false, &dummy))
-            return false;
-
-        /* we now need to work from the current endpoint */
-        ontrue_endblock = func->curblock;
-    } else
-        ontrue = NULL;
-
-    /* on-false path */
-    if (self->on_false) {
-        /* create on-false block */
-        onfalse = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "onfalse"));
-        if (!onfalse)
-            return false;
-
-        /* enter the block */
-        func->curblock = onfalse;
-
-        /* generate */
-        cgen = self->on_false->codegen;
-        if (!(*cgen)((ast_expression*)(self->on_false), func, false, &dummy))
-            return false;
-
-        /* we now need to work from the current endpoint */
-        onfalse_endblock = func->curblock;
-    } else
-        onfalse = NULL;
-
-    /* Merge block were they all merge in to */
-    if (!ontrue || !onfalse || !ontrue_endblock->final || !onfalse_endblock->final)
-    {
-        merge = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "endif"));
-        if (!merge)
-            return false;
-        /* add jumps ot the merge block */
-        if (ontrue && !ontrue_endblock->final && !ir_block_create_jump(ontrue_endblock, ast_ctx(self), merge))
-            return false;
-        if (onfalse && !onfalse_endblock->final && !ir_block_create_jump(onfalse_endblock, ast_ctx(self), merge))
-            return false;
-
-        /* Now enter the merge block */
-        func->curblock = merge;
-    }
-
-    /* we create the if here, that way all blocks are ordered :)
-     */
-    if (!ir_block_create_if(cond, ast_ctx(self), condval,
-                            (ontrue  ? ontrue  : merge),
-                            (onfalse ? onfalse : merge)))
-    {
-        return false;
-    }
-
-    return true;
-}
-
-bool ast_ternary_codegen(ast_ternary *self, ast_function *func, bool lvalue, ir_value **out)
-{
-    ast_expression_codegen *cgen;
-
-    ir_value *condval;
-    ir_value *trueval, *falseval;
-    ir_instr *phi;
-
-    ir_block *cond = func->curblock;
-    ir_block *cond_out = NULL;
-    ir_block *ontrue, *ontrue_out = NULL;
-    ir_block *onfalse, *onfalse_out = NULL;
-    ir_block *merge;
-    int       fold  = 0;
-
-    /* Ternary can never create an lvalue... */
-    if (lvalue)
-        return false;
-
-    /* In theory it shouldn't be possible to pass through a node twice, but
-     * in case we add any kind of optimization pass for the AST itself, it
-     * may still happen, thus we remember a created ir_value and simply return one
-     * if it already exists.
-     */
-    if (self->expression.outr) {
-        *out = self->expression.outr;
-        return true;
-    }
-
-    /* In the following, contraty to ast_ifthen, we assume both paths exist. */
-
-    /* generate the condition */
-    func->curblock = cond;
-    cgen = self->cond->codegen;
-    if (!(*cgen)((ast_expression*)(self->cond), func, false, &condval))
-        return false;
-    cond_out = func->curblock;
-
-    /* try constant folding away the condition */
-    if ((fold = fold_cond_ternary(condval, func, self)) != -1)
-        return fold;
-
-    /* create on-true block */
-    ontrue = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "tern_T"));
-    if (!ontrue)
-        return false;
-    else
-    {
-        /* enter the block */
-        func->curblock = ontrue;
-
-        /* generate */
-        cgen = self->on_true->codegen;
-        if (!(*cgen)((ast_expression*)(self->on_true), func, false, &trueval))
-            return false;
-
-        ontrue_out = func->curblock;
-    }
-
-    /* create on-false block */
-    onfalse = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "tern_F"));
-    if (!onfalse)
-        return false;
-    else
-    {
-        /* enter the block */
-        func->curblock = onfalse;
-
-        /* generate */
-        cgen = self->on_false->codegen;
-        if (!(*cgen)((ast_expression*)(self->on_false), func, false, &falseval))
-            return false;
-
-        onfalse_out = func->curblock;
-    }
-
-    /* create merge block */
-    merge = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "tern_out"));
-    if (!merge)
-        return false;
-    /* jump to merge block */
-    if (!ir_block_create_jump(ontrue_out, ast_ctx(self), merge))
-        return false;
-    if (!ir_block_create_jump(onfalse_out, ast_ctx(self), merge))
-        return false;
-
-    /* create if instruction */
-    if (!ir_block_create_if(cond_out, ast_ctx(self), condval, ontrue, onfalse))
-        return false;
-
-    /* Now enter the merge block */
-    func->curblock = merge;
-
-    /* Here, now, we need a PHI node
-     * but first some sanity checking...
-     */
-    if (trueval->vtype != falseval->vtype && trueval->vtype != TYPE_NIL && falseval->vtype != TYPE_NIL) {
-        /* error("ternary with different types on the two sides"); */
-        compile_error(ast_ctx(self), "internal error: ternary operand types invalid");
-        return false;
-    }
-
-    /* create PHI */
-    phi = ir_block_create_phi(merge, ast_ctx(self), ast_function_label(func, "phi"), self->expression.vtype);
-    if (!phi) {
-        compile_error(ast_ctx(self), "internal error: failed to generate phi node");
-        return false;
-    }
-    ir_phi_add(phi, ontrue_out,  trueval);
-    ir_phi_add(phi, onfalse_out, falseval);
-
-    self->expression.outr = ir_phi_value(phi);
-    *out = self->expression.outr;
-
-    codegen_output_type(self, *out);
-
-    return true;
-}
-
-bool ast_loop_codegen(ast_loop *self, ast_function *func, bool lvalue, ir_value **out)
-{
-    ast_expression_codegen *cgen;
-
-    ir_value *dummy      = NULL;
-    ir_value *precond    = NULL;
-    ir_value *postcond   = NULL;
-
-    /* Since we insert some jumps "late" so we have blocks
-     * ordered "nicely", we need to keep track of the actual end-blocks
-     * of expressions to add the jumps to.
-     */
-    ir_block *bbody      = NULL, *end_bbody      = NULL;
-    ir_block *bprecond   = NULL, *end_bprecond   = NULL;
-    ir_block *bpostcond  = NULL, *end_bpostcond  = NULL;
-    ir_block *bincrement = NULL, *end_bincrement = NULL;
-    ir_block *bout       = NULL, *bin            = NULL;
-
-    /* let's at least move the outgoing block to the end */
-    size_t    bout_id;
-
-    /* 'break' and 'continue' need to be able to find the right blocks */
-    ir_block *bcontinue     = NULL;
-    ir_block *bbreak        = NULL;
-
-    ir_block *tmpblock      = NULL;
-
-    (void)lvalue;
-    (void)out;
-
-    if (self->expression.outr) {
-        compile_error(ast_ctx(self), "internal error: ast_loop cannot be reused, it bears no result!");
-        return false;
-    }
-    self->expression.outr = (ir_value*)1;
-
-    /* NOTE:
-     * Should we ever need some kind of block ordering, better make this function
-     * move blocks around than write a block ordering algorithm later... after all
-     * the ast and ir should work together, not against each other.
-     */
-
-    /* initexpr doesn't get its own block, it's pointless, it could create more blocks
-     * anyway if for example it contains a ternary.
-     */
-    if (self->initexpr)
-    {
-        cgen = self->initexpr->codegen;
-        if (!(*cgen)((ast_expression*)(self->initexpr), func, false, &dummy))
-            return false;
-    }
-
-    /* Store the block from which we enter this chaos */
-    bin = func->curblock;
-
-    /* The pre-loop condition needs its own block since we
-     * need to be able to jump to the start of that expression.
-     */
-    if (self->precond)
-    {
-        bprecond = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "pre_loop_cond"));
-        if (!bprecond)
-            return false;
-
-        /* the pre-loop-condition the least important place to 'continue' at */
-        bcontinue = bprecond;
-
-        /* enter */
-        func->curblock = bprecond;
-
-        /* generate */
-        cgen = self->precond->codegen;
-        if (!(*cgen)((ast_expression*)(self->precond), func, false, &precond))
-            return false;
-
-        end_bprecond = func->curblock;
-    } else {
-        bprecond = end_bprecond = NULL;
-    }
-
-    /* Now the next blocks won't be ordered nicely, but we need to
-     * generate them this early for 'break' and 'continue'.
-     */
-    if (self->increment) {
-        bincrement = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "loop_increment"));
-        if (!bincrement)
-            return false;
-        bcontinue = bincrement; /* increment comes before the pre-loop-condition */
-    } else {
-        bincrement = end_bincrement = NULL;
-    }
-
-    if (self->postcond) {
-        bpostcond = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "post_loop_cond"));
-        if (!bpostcond)
-            return false;
-        bcontinue = bpostcond; /* postcond comes before the increment */
-    } else {
-        bpostcond = end_bpostcond = NULL;
-    }
-
-    bout_id = vec_size(func->ir_func->blocks);
-    bout = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "after_loop"));
-    if (!bout)
-        return false;
-    bbreak = bout;
-
-    /* The loop body... */
-    /* if (self->body) */
-    {
-        bbody = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "loop_body"));
-        if (!bbody)
-            return false;
-
-        /* enter */
-        func->curblock = bbody;
-
-        vec_push(func->breakblocks,    bbreak);
-        if (bcontinue)
-            vec_push(func->continueblocks, bcontinue);
-        else
-            vec_push(func->continueblocks, bbody);
-
-        /* generate */
-        if (self->body) {
-            cgen = self->body->codegen;
-            if (!(*cgen)((ast_expression*)(self->body), func, false, &dummy))
-                return false;
-        }
-
-        end_bbody = func->curblock;
-        vec_pop(func->breakblocks);
-        vec_pop(func->continueblocks);
-    }
-
-    /* post-loop-condition */
-    if (self->postcond)
-    {
-        /* enter */
-        func->curblock = bpostcond;
-
-        /* generate */
-        cgen = self->postcond->codegen;
-        if (!(*cgen)((ast_expression*)(self->postcond), func, false, &postcond))
-            return false;
-
-        end_bpostcond = func->curblock;
-    }
-
-    /* The incrementor */
-    if (self->increment)
-    {
-        /* enter */
-        func->curblock = bincrement;
-
-        /* generate */
-        cgen = self->increment->codegen;
-        if (!(*cgen)((ast_expression*)(self->increment), func, false, &dummy))
-            return false;
-
-        end_bincrement = func->curblock;
-    }
-
-    /* In any case now, we continue from the outgoing block */
-    func->curblock = bout;
-
-    /* Now all blocks are in place */
-    /* From 'bin' we jump to whatever comes first */
-    if      (bprecond)   tmpblock = bprecond;
-    else                 tmpblock = bbody;    /* can never be null */
-
-    /* DEAD CODE
-    else if (bpostcond)  tmpblock = bpostcond;
-    else                 tmpblock = bout;
-    */
-
-    if (!ir_block_create_jump(bin, ast_ctx(self), tmpblock))
-        return false;
-
-    /* From precond */
-    if (bprecond)
-    {
-        ir_block *ontrue, *onfalse;
-        ontrue = bbody; /* can never be null */
-
-        /* all of this is dead code
-        else if (bincrement) ontrue = bincrement;
-        else                 ontrue = bpostcond;
-        */
-
-        onfalse = bout;
-        if (self->pre_not) {
-            tmpblock = ontrue;
-            ontrue   = onfalse;
-            onfalse  = tmpblock;
-        }
-        if (!ir_block_create_if(end_bprecond, ast_ctx(self), precond, ontrue, onfalse))
-            return false;
-    }
-
-    /* from body */
-    if (bbody)
-    {
-        if      (bincrement) tmpblock = bincrement;
-        else if (bpostcond)  tmpblock = bpostcond;
-        else if (bprecond)   tmpblock = bprecond;
-        else                 tmpblock = bbody;
-        if (!end_bbody->final && !ir_block_create_jump(end_bbody, ast_ctx(self), tmpblock))
-            return false;
-    }
-
-    /* from increment */
-    if (bincrement)
-    {
-        if      (bpostcond)  tmpblock = bpostcond;
-        else if (bprecond)   tmpblock = bprecond;
-        else if (bbody)      tmpblock = bbody;
-        else                 tmpblock = bout;
-        if (!ir_block_create_jump(end_bincrement, ast_ctx(self), tmpblock))
-            return false;
-    }
-
-    /* from postcond */
-    if (bpostcond)
-    {
-        ir_block *ontrue, *onfalse;
-        if      (bprecond)   ontrue = bprecond;
-        else                 ontrue = bbody; /* can never be null */
-
-        /* all of this is dead code
-        else if (bincrement) ontrue = bincrement;
-        else                 ontrue = bpostcond;
-        */
-
-        onfalse = bout;
-        if (self->post_not) {
-            tmpblock = ontrue;
-            ontrue   = onfalse;
-            onfalse  = tmpblock;
-        }
-        if (!ir_block_create_if(end_bpostcond, ast_ctx(self), postcond, ontrue, onfalse))
-            return false;
-    }
-
-    /* Move 'bout' to the end */
-    vec_remove(func->ir_func->blocks, bout_id, 1);
-    vec_push(func->ir_func->blocks, bout);
-
-    return true;
-}
-
-bool ast_breakcont_codegen(ast_breakcont *self, ast_function *func, bool lvalue, ir_value **out)
-{
-    ir_block *target;
-
-    *out = NULL;
-
-    if (lvalue) {
-        compile_error(ast_ctx(self), "break/continue expression is not an l-value");
-        return false;
-    }
-
-    if (self->expression.outr) {
-        compile_error(ast_ctx(self), "internal error: ast_breakcont cannot be reused!");
-        return false;
-    }
-    self->expression.outr = (ir_value*)1;
-
-    if (self->is_continue)
-        target = func->continueblocks[vec_size(func->continueblocks)-1-self->levels];
-    else
-        target = func->breakblocks[vec_size(func->breakblocks)-1-self->levels];
-
-    if (!target) {
-        compile_error(ast_ctx(self), "%s is lacking a target block", (self->is_continue ? "continue" : "break"));
-        return false;
-    }
-
-    if (!ir_block_create_jump(func->curblock, ast_ctx(self), target))
-        return false;
-    return true;
-}
-
-bool ast_switch_codegen(ast_switch *self, ast_function *func, bool lvalue, ir_value **out)
-{
-    ast_expression_codegen *cgen;
-
-    ast_switch_case *def_case     = NULL;
-    ir_block        *def_bfall    = NULL;
-    ir_block        *def_bfall_to = NULL;
-    bool set_def_bfall_to = false;
-
-    ir_value *dummy     = NULL;
-    ir_value *irop      = NULL;
-    ir_block *bout      = NULL;
-    ir_block *bfall     = NULL;
-    size_t    bout_id;
-    size_t    c;
-
-    char      typestr[1024];
-    uint16_t  cmpinstr;
-
-    if (lvalue) {
-        compile_error(ast_ctx(self), "switch expression is not an l-value");
-        return false;
-    }
-
-    if (self->expression.outr) {
-        compile_error(ast_ctx(self), "internal error: ast_switch cannot be reused!");
-        return false;
-    }
-    self->expression.outr = (ir_value*)1;
-
-    (void)lvalue;
-    (void)out;
-
-    cgen = self->operand->codegen;
-    if (!(*cgen)((ast_expression*)(self->operand), func, false, &irop))
-        return false;
-
-    if (!vec_size(self->cases))
-        return true;
-
-    cmpinstr = type_eq_instr[irop->vtype];
-    if (cmpinstr >= VINSTR_END) {
-        ast_type_to_string(self->operand, typestr, sizeof(typestr));
-        compile_error(ast_ctx(self), "invalid type to perform a switch on: %s", typestr);
-        return false;
-    }
-
-    bout_id = vec_size(func->ir_func->blocks);
-    bout = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "after_switch"));
-    if (!bout)
-        return false;
-
-    /* setup the break block */
-    vec_push(func->breakblocks, bout);
-
-    /* Now create all cases */
-    for (c = 0; c < vec_size(self->cases); ++c) {
-        ir_value *cond, *val;
-        ir_block *bcase, *bnot;
-        size_t bnot_id;
-
-        ast_switch_case *swcase = &self->cases[c];
-
-        if (swcase->value) {
-            /* A regular case */
-            /* generate the condition operand */
-            cgen = swcase->value->codegen;
-            if (!(*cgen)((ast_expression*)(swcase->value), func, false, &val))
-                return false;
-            /* generate the condition */
-            cond = ir_block_create_binop(func->curblock, ast_ctx(self), ast_function_label(func, "switch_eq"), cmpinstr, irop, val);
-            if (!cond)
-                return false;
-
-            bcase = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "case"));
-            bnot_id = vec_size(func->ir_func->blocks);
-            bnot = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "not_case"));
-            if (!bcase || !bnot)
-                return false;
-            if (set_def_bfall_to) {
-                set_def_bfall_to = false;
-                def_bfall_to = bcase;
-            }
-            if (!ir_block_create_if(func->curblock, ast_ctx(self), cond, bcase, bnot))
-                return false;
-
-            /* Make the previous case-end fall through */
-            if (bfall && !bfall->final) {
-                if (!ir_block_create_jump(bfall, ast_ctx(self), bcase))
-                    return false;
-            }
-
-            /* enter the case */
-            func->curblock = bcase;
-            cgen = swcase->code->codegen;
-            if (!(*cgen)((ast_expression*)swcase->code, func, false, &dummy))
-                return false;
-
-            /* remember this block to fall through from */
-            bfall = func->curblock;
-
-            /* enter the else and move it down */
-            func->curblock = bnot;
-            vec_remove(func->ir_func->blocks, bnot_id, 1);
-            vec_push(func->ir_func->blocks, bnot);
-        } else {
-            /* The default case */
-            /* Remember where to fall through from: */
-            def_bfall = bfall;
-            bfall     = NULL;
-            /* remember which case it was */
-            def_case  = swcase;
-            /* And the next case will be remembered */
-            set_def_bfall_to = true;
-        }
-    }
-
-    /* Jump from the last bnot to bout */
-    if (bfall && !bfall->final && !ir_block_create_jump(bfall, ast_ctx(self), bout)) {
-        /*
-        astwarning(ast_ctx(bfall), WARN_???, "missing break after last case");
-        */
-        return false;
-    }
-
-    /* If there was a default case, put it down here */
-    if (def_case) {
-        ir_block *bcase;
-
-        /* No need to create an extra block */
-        bcase = func->curblock;
-
-        /* Insert the fallthrough jump */
-        if (def_bfall && !def_bfall->final) {
-            if (!ir_block_create_jump(def_bfall, ast_ctx(self), bcase))
-                return false;
-        }
-
-        /* Now generate the default code */
-        cgen = def_case->code->codegen;
-        if (!(*cgen)((ast_expression*)def_case->code, func, false, &dummy))
-            return false;
-
-        /* see if we need to fall through */
-        if (def_bfall_to && !func->curblock->final)
-        {
-            if (!ir_block_create_jump(func->curblock, ast_ctx(self), def_bfall_to))
-                return false;
-        }
-    }
-
-    /* Jump from the last bnot to bout */
-    if (!func->curblock->final && !ir_block_create_jump(func->curblock, ast_ctx(self), bout))
-        return false;
-    /* enter the outgoing block */
-    func->curblock = bout;
-
-    /* restore the break block */
-    vec_pop(func->breakblocks);
-
-    /* Move 'bout' to the end, it's nicer */
-    vec_remove(func->ir_func->blocks, bout_id, 1);
-    vec_push(func->ir_func->blocks, bout);
-
-    return true;
-}
-
-bool ast_label_codegen(ast_label *self, ast_function *func, bool lvalue, ir_value **out)
-{
-    size_t i;
-    ir_value *dummy;
-
-    if (self->undefined) {
-        compile_error(ast_ctx(self), "internal error: ast_label never defined");
-        return false;
-    }
-
-    *out = NULL;
-    if (lvalue) {
-        compile_error(ast_ctx(self), "internal error: ast_label cannot be an lvalue");
-        return false;
-    }
-
-    /* simply create a new block and jump to it */
-    self->irblock = ir_function_create_block(ast_ctx(self), func->ir_func, self->name);
-    if (!self->irblock) {
-        compile_error(ast_ctx(self), "failed to allocate label block `%s`", self->name);
-        return false;
-    }
-    if (!func->curblock->final) {
-        if (!ir_block_create_jump(func->curblock, ast_ctx(self), self->irblock))
-            return false;
-    }
-
-    /* enter the new block */
-    func->curblock = self->irblock;
-
-    /* Generate all the leftover gotos */
-    for (i = 0; i < vec_size(self->gotos); ++i) {
-        if (!ast_goto_codegen(self->gotos[i], func, false, &dummy))
-            return false;
-    }
-
-    return true;
-}
-
-bool ast_goto_codegen(ast_goto *self, ast_function *func, bool lvalue, ir_value **out)
-{
-    *out = NULL;
-    if (lvalue) {
-        compile_error(ast_ctx(self), "internal error: ast_goto cannot be an lvalue");
-        return false;
-    }
-
-    if (self->target->irblock) {
-        if (self->irblock_from) {
-            /* we already tried once, this is the callback */
-            self->irblock_from->final = false;
-            if (!ir_block_create_goto(self->irblock_from, ast_ctx(self), self->target->irblock)) {
-                compile_error(ast_ctx(self), "failed to generate goto to `%s`", self->name);
-                return false;
-            }
-        }
-        else
-        {
-            if (!ir_block_create_goto(func->curblock, ast_ctx(self), self->target->irblock)) {
-                compile_error(ast_ctx(self), "failed to generate goto to `%s`", self->name);
-                return false;
-            }
-        }
-    }
-    else
-    {
-        /* the target has not yet been created...
-         * close this block in a sneaky way:
-         */
-        func->curblock->final = true;
-        self->irblock_from = func->curblock;
-        ast_label_register_goto(self->target, self);
-    }
-
-    return true;
-}
-
-#include <stdio.h>
-bool ast_state_codegen(ast_state *self, ast_function *func, bool lvalue, ir_value **out)
-{
-    ast_expression_codegen *cgen;
-
-    ir_value *frameval, *thinkval;
-
-    if (lvalue) {
-        compile_error(ast_ctx(self), "not an l-value (state operation)");
-        return false;
-    }
-    if (self->expression.outr) {
-        compile_error(ast_ctx(self), "internal error: ast_state cannot be reused!");
-        return false;
-    }
-    *out = NULL;
-
-    cgen = self->framenum->codegen;
-    if (!(*cgen)((ast_expression*)(self->framenum), func, false, &frameval))
-        return false;
-    if (!frameval)
-        return false;
-
-    cgen = self->nextthink->codegen;
-    if (!(*cgen)((ast_expression*)(self->nextthink), func, false, &thinkval))
-        return false;
-    if (!frameval)
-        return false;
-
-    if (!ir_block_create_state_op(func->curblock, ast_ctx(self), frameval, thinkval)) {
-        compile_error(ast_ctx(self), "failed to create STATE instruction");
-        return false;
-    }
-
-    self->expression.outr = (ir_value*)1;
-    return true;
-}
-
-bool ast_call_codegen(ast_call *self, ast_function *func, bool lvalue, ir_value **out)
-{
-    ast_expression_codegen *cgen;
-    ir_value              **params;
-    ir_instr               *callinstr;
-    size_t i;
-
-    ir_value *funval = NULL;
-
-    /* return values are never lvalues */
-    if (lvalue) {
-        compile_error(ast_ctx(self), "not an l-value (function call)");
-        return false;
-    }
-
-    if (self->expression.outr) {
-        *out = self->expression.outr;
-        return true;
-    }
-
-    cgen = self->func->codegen;
-    if (!(*cgen)((ast_expression*)(self->func), func, false, &funval))
-        return false;
-    if (!funval)
-        return false;
-
-    params = NULL;
-
-    /* parameters */
-    for (i = 0; i < vec_size(self->params); ++i)
-    {
-        ir_value *param;
-        ast_expression *expr = self->params[i];
-
-        cgen = expr->codegen;
-        if (!(*cgen)(expr, func, false, &param))
-            goto error;
-        if (!param)
-            goto error;
-        vec_push(params, param);
-    }
-
-    /* varargs counter */
-    if (self->va_count) {
-        ir_value   *va_count;
-        ir_builder *builder = func->curblock->owner->owner;
-        cgen = self->va_count->codegen;
-        if (!(*cgen)((ast_expression*)(self->va_count), func, false, &va_count))
-            return false;
-        if (!ir_block_create_store_op(func->curblock, ast_ctx(self), INSTR_STORE_F,
-                                      ir_builder_get_va_count(builder), va_count))
-        {
-            return false;
-        }
-    }
-
-    callinstr = ir_block_create_call(func->curblock, ast_ctx(self),
-                                     ast_function_label(func, "call"),
-                                     funval, !!(self->func->flags & AST_FLAG_NORETURN));
-    if (!callinstr)
-        goto error;
-
-    for (i = 0; i < vec_size(params); ++i) {
-        ir_call_param(callinstr, params[i]);
-    }
-
-    *out = ir_call_value(callinstr);
-    self->expression.outr = *out;
-
-    codegen_output_type(self, *out);
-
-    vec_free(params);
-    return true;
-error:
-    vec_free(params);
-    return false;
-}
diff --git a/ast.cpp b/ast.cpp
new file mode 100644 (file)
index 0000000..cf8ffc7
--- /dev/null
+++ b/ast.cpp
@@ -0,0 +1,3051 @@
+#include <new>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "gmqcc.h"
+#include "ast.h"
+#include "fold.h"
+//#include "parser.h"
+
+#include "algo.h"
+
+/* Initialize main ast node aprts */
+ast_node::ast_node(lex_ctx_t ctx, int node_type)
+    : m_context(ctx)
+    , m_node_type(node_type)
+    , m_keep_node(false)
+    , m_side_effects(false)
+{
+}
+
+ast_node::~ast_node()
+{
+}
+
+/* weight and side effects */
+void ast_node::propagateSideEffects(const ast_node *other)
+{
+    if (other->m_side_effects)
+        m_side_effects = true;
+}
+
+/* General expression initialization */
+ast_expression::ast_expression(lex_ctx_t ctx, int nodetype, qc_type type)
+    : ast_node(ctx, nodetype)
+    , m_vtype(type)
+{
+    if (OPTS_OPTION_BOOL(OPTION_COVERAGE))
+        m_flags |= AST_FLAG_BLOCK_COVERAGE;
+}
+ast_expression::ast_expression(lex_ctx_t ctx, int nodetype)
+    : ast_expression(ctx, nodetype, TYPE_VOID)
+{}
+
+ast_expression::~ast_expression()
+{
+    if (m_next)
+        delete m_next;
+    if (m_varparam)
+        delete m_varparam;
+}
+
+ast_expression::ast_expression(ast_copy_type_t, const ast_expression &other)
+    : ast_expression(ast_copy_type, other.m_context, other)
+{}
+
+ast_expression::ast_expression(ast_copy_type_t, lex_ctx_t ctx, const ast_expression &other)
+    : ast_expression(ast_copy_type, TYPE_ast_expression, ctx, other)
+{}
+
+ast_expression::ast_expression(ast_copy_type_t, int nodetype, const ast_expression &other)
+    : ast_expression(ast_copy_type, nodetype, other.m_context, other)
+{}
+
+ast_expression::ast_expression(ast_copy_type_t, int nodetype, lex_ctx_t ctx, const ast_expression &other)
+    : ast_expression(ctx, nodetype)
+{
+    m_vtype = other.m_vtype;
+    m_count = other.m_count;
+    m_flags = other.m_flags;
+    if (other.m_next)
+        m_next = new ast_expression(ast_copy_type, *other.m_next);
+    m_type_params.reserve(other.m_type_params.size());
+    for (auto &it : other.m_type_params)
+        m_type_params.emplace_back(new ast_value(ast_copy_type, *it));
+}
+
+
+ast_expression *ast_expression::shallowType(lex_ctx_t ctx, qc_type vtype) {
+    auto expr = new ast_expression(ctx, TYPE_ast_expression);
+    expr->m_vtype = vtype;
+    return expr;
+}
+
+void ast_expression::adoptType(const ast_expression &other)
+{
+    m_vtype = other.m_vtype;
+    if (other.m_next)
+        m_next = new ast_expression(ast_copy_type, *other.m_next);
+    m_count = other.m_count;
+    m_flags = other.m_flags;
+    m_type_params.clear();
+    m_type_params.reserve(other.m_type_params.size());
+    for (auto &it : other.m_type_params)
+        m_type_params.emplace_back(new ast_value(ast_copy_type, *it));
+}
+
+bool ast_expression::compareType(const ast_expression &other) const
+{
+    if (m_vtype == TYPE_NIL ||
+        other.m_vtype == TYPE_NIL)
+        return true;
+    if (m_vtype != other.m_vtype)
+        return false;
+    if (!m_next != !other.m_next)
+        return false;
+    if (m_type_params.size() != other.m_type_params.size())
+        return false;
+    if ((m_flags & AST_FLAG_TYPE_MASK) !=
+        (other.m_flags & AST_FLAG_TYPE_MASK) )
+    {
+        return false;
+    }
+    if (m_type_params.size()) {
+        size_t i;
+        for (i = 0; i < m_type_params.size(); ++i) {
+            if (!m_type_params[i]->compareType(*other.m_type_params[i]))
+                return false;
+        }
+    }
+    if (m_next)
+        return m_next->compareType(*other.m_next);
+    return true;
+}
+
+bool ast_expression::codegen(ast_function*, bool, ir_value**) {
+    compile_error(m_context, "ast_expression::codegen called!");
+    abort();
+    return false;
+}
+
+ast_value::ast_value(ast_copy_type_t, const ast_value &other, const std::string &name)
+    : ast_value(ast_copy_type, static_cast<const ast_expression&>(other), name)
+{
+    m_keep_node = true; // keep values, always
+    memset(&m_constval, 0, sizeof(m_constval));
+}
+
+ast_value::ast_value(ast_copy_type_t, const ast_value &other)
+    : ast_value(ast_copy_type, static_cast<const ast_expression&>(other), other.m_name)
+{
+    m_keep_node = true; // keep values, always
+    memset(&m_constval, 0, sizeof(m_constval));
+}
+
+ast_value::ast_value(ast_copy_type_t, const ast_expression &other, const std::string &name)
+    : ast_expression(ast_copy_type, TYPE_ast_value, other)
+    , m_name(name)
+{
+    m_keep_node = true; // keep values, always
+    memset(&m_constval, 0, sizeof(m_constval));
+}
+
+ast_value::ast_value(lex_ctx_t ctx, const std::string &name, qc_type t)
+    : ast_expression(ctx, TYPE_ast_value, t)
+    , m_name(name)
+{
+    m_keep_node = true; // keep values, always
+    memset(&m_constval, 0, sizeof(m_constval));
+}
+
+ast_value::~ast_value()
+{
+    if (m_argcounter)
+        mem_d((void*)m_argcounter);
+    if (m_hasvalue) {
+        switch (m_vtype)
+        {
+        case TYPE_STRING:
+            mem_d((void*)m_constval.vstring);
+            break;
+        case TYPE_FUNCTION:
+            // unlink us from the function node
+            m_constval.vfunc->m_function_type = nullptr;
+            break;
+        // NOTE: delete function? currently collected in
+        // the parser structure
+        default:
+            break;
+        }
+    }
+
+    // initlist imples an array which implies .next in the expression exists.
+    if (m_initlist.size() && m_next->m_vtype == TYPE_STRING) {
+        for (auto &it : m_initlist)
+            if (it.vstring)
+                mem_d(it.vstring);
+    }
+}
+
+static size_t ast_type_to_string_impl(const ast_expression *e, char *buf, size_t bufsize, size_t pos)
+{
+    const char *typestr;
+    size_t typelen;
+    size_t i;
+
+    if (!e) {
+        if (pos + 6 >= bufsize)
+            goto full;
+        util_strncpy(buf + pos, "(null)", 6);
+        return pos + 6;
+    }
+
+    if (pos + 1 >= bufsize)
+        goto full;
+
+    switch (e->m_vtype) {
+        case TYPE_VARIANT:
+            util_strncpy(buf + pos, "(variant)", 9);
+            return pos + 9;
+
+        case TYPE_FIELD:
+            buf[pos++] = '.';
+            return ast_type_to_string_impl(e->m_next, buf, bufsize, pos);
+
+        case TYPE_POINTER:
+            if (pos + 3 >= bufsize)
+                goto full;
+            buf[pos++] = '*';
+            buf[pos++] = '(';
+            pos = ast_type_to_string_impl(e->m_next, buf, bufsize, pos);
+            if (pos + 1 >= bufsize)
+                goto full;
+            buf[pos++] = ')';
+            return pos;
+
+        case TYPE_FUNCTION:
+            pos = ast_type_to_string_impl(e->m_next, buf, bufsize, pos);
+            if (pos + 2 >= bufsize)
+                goto full;
+            if (e->m_type_params.empty()) {
+                buf[pos++] = '(';
+                buf[pos++] = ')';
+                return pos;
+            }
+            buf[pos++] = '(';
+            pos = ast_type_to_string_impl(e->m_type_params[0].get(), buf, bufsize, pos);
+            for (i = 1; i < e->m_type_params.size(); ++i) {
+                if (pos + 2 >= bufsize)
+                    goto full;
+                buf[pos++] = ',';
+                buf[pos++] = ' ';
+                pos = ast_type_to_string_impl(e->m_type_params[i].get(), buf, bufsize, pos);
+            }
+            if (pos + 1 >= bufsize)
+                goto full;
+            buf[pos++] = ')';
+            return pos;
+
+        case TYPE_ARRAY:
+            pos = ast_type_to_string_impl(e->m_next, buf, bufsize, pos);
+            if (pos + 1 >= bufsize)
+                goto full;
+            buf[pos++] = '[';
+            pos += util_snprintf(buf + pos, bufsize - pos - 1, "%i", (int)e->m_count);
+            if (pos + 1 >= bufsize)
+                goto full;
+            buf[pos++] = ']';
+            return pos;
+
+        default:
+            typestr = type_name[e->m_vtype];
+            typelen = strlen(typestr);
+            if (pos + typelen >= bufsize)
+                goto full;
+            util_strncpy(buf + pos, typestr, typelen);
+            return pos + typelen;
+    }
+
+full:
+    buf[bufsize-3] = '.';
+    buf[bufsize-2] = '.';
+    buf[bufsize-1] = '.';
+    return bufsize;
+}
+
+void ast_type_to_string(const ast_expression *e, char *buf, size_t bufsize)
+{
+    size_t pos = ast_type_to_string_impl(e, buf, bufsize-1, 0);
+    buf[pos] = 0;
+}
+
+void ast_value::addParam(ast_value *p)
+{
+    m_type_params.emplace_back(p);
+}
+
+ast_binary::ast_binary(lex_ctx_t ctx, int op,
+                       ast_expression* left, ast_expression* right)
+    : ast_expression(ctx, TYPE_ast_binary)
+    , m_op(op)
+    // m_left/m_right happen after the peephole step right below
+    , m_right_first(false)
+{
+    if (ast_istype(right, ast_unary) && OPTS_OPTIMIZATION(OPTIM_PEEPHOLE)) {
+        ast_unary      *unary  = ((ast_unary*)right);
+        ast_expression *normal = unary->m_operand;
+
+        /* make a-(-b) => a + b */
+        if (unary->m_op == VINSTR_NEG_F || unary->m_op == VINSTR_NEG_V) {
+            if (op == INSTR_SUB_F) {
+                op = INSTR_ADD_F;
+                right = normal;
+                ++opts_optimizationcount[OPTIM_PEEPHOLE];
+            } else if (op == INSTR_SUB_V) {
+                op = INSTR_ADD_V;
+                right = normal;
+                ++opts_optimizationcount[OPTIM_PEEPHOLE];
+            }
+        }
+    }
+
+    m_left = left;
+    m_right = right;
+
+    propagateSideEffects(left);
+    propagateSideEffects(right);
+
+    if (op >= INSTR_EQ_F && op <= INSTR_GT)
+        m_vtype = TYPE_FLOAT;
+    else if (op == INSTR_AND || op == INSTR_OR) {
+        if (OPTS_FLAG(PERL_LOGIC))
+            adoptType(*right);
+        else
+            m_vtype = TYPE_FLOAT;
+    }
+    else if (op == INSTR_BITAND || op == INSTR_BITOR)
+        m_vtype = TYPE_FLOAT;
+    else if (op == INSTR_MUL_VF || op == INSTR_MUL_FV)
+        m_vtype = TYPE_VECTOR;
+    else if (op == INSTR_MUL_V)
+        m_vtype = TYPE_FLOAT;
+    else
+        m_vtype = left->m_vtype;
+
+    // references all
+    m_refs = AST_REF_ALL;
+}
+
+ast_binary::~ast_binary()
+{
+    if (m_refs & AST_REF_LEFT)  ast_unref(m_left);
+    if (m_refs & AST_REF_RIGHT) ast_unref(m_right);
+}
+
+ast_binstore::ast_binstore(lex_ctx_t ctx, int storop, int mathop,
+                           ast_expression* left, ast_expression* right)
+    : ast_expression(ctx, TYPE_ast_binstore)
+    , m_opstore(storop)
+    , m_opbin(mathop)
+    , m_dest(left)
+    , m_source(right)
+    , m_keep_dest(false)
+{
+    m_side_effects = true;
+    adoptType(*left);
+}
+
+ast_binstore::~ast_binstore()
+{
+    if (!m_keep_dest)
+        ast_unref(m_dest);
+    ast_unref(m_source);
+}
+
+ast_unary* ast_unary::make(lex_ctx_t ctx, int op, ast_expression *expr)
+{
+    // handle double negation, double bitwise or logical not
+    if (op == opid2('!','P') ||
+        op == opid2('~','P') ||
+        op == opid2('-','P'))
+    {
+        if (ast_istype(expr, ast_unary) && OPTS_OPTIMIZATION(OPTIM_PEEPHOLE)) {
+            ast_unary *unary = reinterpret_cast<ast_unary*>(expr);
+            if (unary->m_op == op) {
+                auto out = reinterpret_cast<ast_unary*>(unary->m_operand);
+                unary->m_operand = nullptr;
+                delete unary;
+                ++opts_optimizationcount[OPTIM_PEEPHOLE];
+                return out;
+            }
+        }
+    }
+
+    return new ast_unary(ctx, op, expr);
+}
+
+ast_unary::ast_unary(lex_ctx_t ctx, int op, ast_expression *expr)
+    : ast_expression(ctx, TYPE_ast_unary)
+    , m_op(op)
+    , m_operand(expr)
+{
+    propagateSideEffects(expr);
+    if ((op >= INSTR_NOT_F && op <= INSTR_NOT_FNC) || op == VINSTR_NEG_F) {
+        m_vtype = TYPE_FLOAT;
+    } else if (op == VINSTR_NEG_V) {
+        m_vtype = TYPE_VECTOR;
+    } else {
+        compile_error(ctx, "cannot determine type of unary operation %s", util_instr_str[op]);
+    }
+}
+
+ast_unary::~ast_unary()
+{
+    if (m_operand)
+        ast_unref(m_operand);
+}
+
+ast_return::ast_return(lex_ctx_t ctx, ast_expression *expr)
+    : ast_expression(ctx, TYPE_ast_return)
+    , m_operand(expr)
+{
+    if (expr)
+        propagateSideEffects(expr);
+}
+
+ast_return::~ast_return()
+{
+    if (m_operand)
+        ast_unref(m_operand);
+}
+
+ast_entfield::ast_entfield(lex_ctx_t ctx, ast_expression *entity, ast_expression *field)
+    : ast_entfield(ctx, entity, field, field->m_next)
+{
+    if (field->m_vtype != TYPE_FIELD)
+        compile_error(ctx, "ast_entfield with expression not of type field");
+}
+
+ast_entfield::ast_entfield(lex_ctx_t ctx, ast_expression *entity, ast_expression *field, const ast_expression *outtype)
+    : ast_expression(ctx, TYPE_ast_entfield)
+    , m_entity(entity)
+    , m_field(field)
+{
+    propagateSideEffects(m_entity);
+    propagateSideEffects(m_field);
+
+    if (!outtype) {
+        compile_error(ctx, "ast_entfield: field has no type");
+        m_vtype = TYPE_VOID;
+    }
+    else
+        adoptType(*outtype);
+}
+
+ast_entfield::~ast_entfield()
+{
+    ast_unref(m_entity);
+    ast_unref(m_field);
+}
+
+ast_member *ast_member::make(lex_ctx_t ctx, ast_expression *owner, unsigned int field, const std::string &name)
+{
+    if (field >= 3) {
+        compile_error(ctx, "ast_member: invalid field (>=3): %u", field);
+        return nullptr;
+    }
+    if (owner->m_vtype != TYPE_VECTOR &&
+        owner->m_vtype != TYPE_FIELD)
+    {
+        compile_error(ctx, "member-access on an invalid owner of type %s", type_name[owner->m_vtype]);
+        return nullptr;
+    }
+    return new ast_member(ctx, owner, field, name);
+}
+
+ast_member::ast_member(lex_ctx_t ctx, ast_expression *owner, unsigned int field, const std::string &name)
+    : ast_expression(ctx, TYPE_ast_member)
+    , m_owner(owner)
+    , m_field(field)
+    , m_name(name)
+    , m_rvalue(false)
+{
+    m_keep_node = true;
+
+    if (m_owner->m_vtype == TYPE_VECTOR) {
+        m_vtype = TYPE_FLOAT;
+        m_next  = nullptr;
+    } else {
+        m_vtype = TYPE_FIELD;
+        m_next = ast_expression::shallowType(ctx, TYPE_FLOAT);
+    }
+
+    propagateSideEffects(owner);
+}
+
+ast_member::~ast_member()
+{
+    // The owner is always an ast_value, which has .keep_node=true,
+    // also: ast_members are usually deleted after the owner, thus
+    // this will cause invalid access
+        //ast_unref(self->m_owner);
+    // once we allow (expression).x to access a vector-member, we need
+    // to change this: preferably by creating an alternate ast node for this
+    // purpose that is not garbage-collected.
+}
+
+ast_array_index* ast_array_index::make(lex_ctx_t ctx, ast_expression *array, ast_expression *index)
+{
+    ast_expression *outtype = array->m_next;
+    if (!outtype) {
+        // field has no type
+        return nullptr;
+    }
+
+    return new ast_array_index(ctx, array, index);
+}
+
+ast_array_index::ast_array_index(lex_ctx_t ctx, ast_expression *array, ast_expression *index)
+    : ast_expression(ctx, TYPE_ast_array_index)
+    , m_array(array)
+    , m_index(index)
+{
+    propagateSideEffects(array);
+    propagateSideEffects(index);
+
+    ast_expression *outtype = m_array->m_next;
+    adoptType(*outtype);
+
+    if (array->m_vtype == TYPE_FIELD && outtype->m_vtype == TYPE_ARRAY) {
+        // FIXME: investigate - this is not possible after adoptType
+        //if (m_vtype != TYPE_ARRAY) {
+        //    compile_error(self->m_context, "array_index node on type");
+        //    ast_array_index_delete(self);
+        //    return nullptr;
+        //}
+
+        m_array = outtype;
+        m_vtype = TYPE_FIELD;
+    }
+}
+
+ast_array_index::~ast_array_index()
+{
+    if (m_array)
+        ast_unref(m_array);
+    if (m_index)
+        ast_unref(m_index);
+}
+
+ast_argpipe::ast_argpipe(lex_ctx_t ctx, ast_expression *index)
+    : ast_expression(ctx, TYPE_ast_argpipe)
+    , m_index(index)
+{
+    m_vtype = TYPE_NOEXPR;
+}
+
+ast_argpipe::~ast_argpipe()
+{
+    if (m_index)
+        ast_unref(m_index);
+}
+
+ast_store::ast_store(lex_ctx_t ctx, int op, ast_expression *dest, ast_expression *source)
+    : ast_expression(ctx, TYPE_ast_store)
+    , m_op(op)
+    , m_dest(dest)
+    , m_source(source)
+{
+    m_side_effects = true;
+    adoptType(*dest);
+}
+
+ast_store::~ast_store()
+{
+    ast_unref(m_dest);
+    ast_unref(m_source);
+}
+
+ast_ifthen::ast_ifthen(lex_ctx_t ctx, ast_expression *cond, ast_expression *ontrue, ast_expression *onfalse)
+    : ast_expression(ctx, TYPE_ast_ifthen)
+    , m_cond(cond)
+    , m_on_true(ontrue)
+    , m_on_false(onfalse)
+{
+    propagateSideEffects(cond);
+    if (ontrue)
+        propagateSideEffects(ontrue);
+    if (onfalse)
+        propagateSideEffects(onfalse);
+}
+
+ast_ifthen::~ast_ifthen()
+{
+    ast_unref(m_cond);
+    if (m_on_true)
+        ast_unref(m_on_true);
+    if (m_on_false)
+        ast_unref(m_on_false);
+}
+
+ast_ternary::ast_ternary(lex_ctx_t ctx, ast_expression *cond, ast_expression *ontrue, ast_expression *onfalse)
+    : ast_expression(ctx, TYPE_ast_ternary)
+    , m_cond(cond)
+    , m_on_true(ontrue)
+    , m_on_false(onfalse)
+{
+    propagateSideEffects(cond);
+    propagateSideEffects(ontrue);
+    propagateSideEffects(onfalse);
+
+    if (ontrue->m_vtype == TYPE_NIL)
+        adoptType(*onfalse);
+    else
+        adoptType(*ontrue);
+}
+
+ast_ternary::~ast_ternary()
+{
+    /* the if()s are only there because computed-gotos can set them
+     * to nullptr
+     */
+    if (m_cond)     ast_unref(m_cond);
+    if (m_on_true)  ast_unref(m_on_true);
+    if (m_on_false) ast_unref(m_on_false);
+}
+
+ast_loop::ast_loop(lex_ctx_t ctx,
+                   ast_expression *initexpr,
+                   ast_expression *precond, bool pre_not,
+                   ast_expression *postcond, bool post_not,
+                   ast_expression *increment,
+                   ast_expression *body)
+    : ast_expression(ctx, TYPE_ast_loop)
+    , m_initexpr(initexpr)
+    , m_precond(precond)
+    , m_postcond(postcond)
+    , m_increment(increment)
+    , m_body(body)
+    , m_pre_not(pre_not)
+    , m_post_not(post_not)
+{
+    if (initexpr)
+        propagateSideEffects(initexpr);
+    if (precond)
+        propagateSideEffects(precond);
+    if (postcond)
+        propagateSideEffects(postcond);
+    if (increment)
+        propagateSideEffects(increment);
+    if (body)
+        propagateSideEffects(body);
+}
+
+ast_loop::~ast_loop()
+{
+    if (m_initexpr)
+        ast_unref(m_initexpr);
+    if (m_precond)
+        ast_unref(m_precond);
+    if (m_postcond)
+        ast_unref(m_postcond);
+    if (m_increment)
+        ast_unref(m_increment);
+    if (m_body)
+        ast_unref(m_body);
+}
+
+ast_breakcont::ast_breakcont(lex_ctx_t ctx, bool iscont, unsigned int levels)
+    : ast_expression(ctx, TYPE_ast_breakcont)
+    , m_is_continue(iscont)
+    , m_levels(levels)
+{
+}
+
+ast_breakcont::~ast_breakcont()
+{
+}
+
+ast_switch::ast_switch(lex_ctx_t ctx, ast_expression *op)
+    : ast_expression(ctx, TYPE_ast_switch)
+    , m_operand(op)
+{
+    propagateSideEffects(op);
+}
+
+ast_switch::~ast_switch()
+{
+    ast_unref(m_operand);
+
+    for (auto &it : m_cases) {
+        if (it.m_value)
+            ast_unref(it.m_value);
+        ast_unref(it.m_code);
+    }
+}
+
+ast_label::ast_label(lex_ctx_t ctx, const std::string &name, bool undefined)
+    : ast_expression(ctx, TYPE_ast_label)
+    , m_name(name)
+    , m_irblock(nullptr)
+    , m_undefined(undefined)
+{
+    m_vtype = TYPE_NOEXPR;
+}
+
+ast_label::~ast_label()
+{
+}
+
+void ast_label::registerGoto(ast_goto *g)
+{
+   m_gotos.push_back(g);
+}
+
+ast_goto::ast_goto(lex_ctx_t ctx, const std::string &name)
+    : ast_expression(ctx, TYPE_ast_goto)
+    , m_name(name)
+    , m_target(nullptr)
+    , m_irblock_from(nullptr)
+{
+}
+
+ast_goto::~ast_goto()
+{
+}
+
+void ast_goto::setLabel(ast_label *label)
+{
+    m_target = label;
+}
+
+ast_state::ast_state(lex_ctx_t ctx, ast_expression *frame, ast_expression *think)
+    : ast_expression(ctx, TYPE_ast_expression)
+    , m_framenum(frame)
+    , m_nextthink(think)
+{
+}
+
+ast_state::~ast_state()
+{
+    if (m_framenum)
+        ast_unref(m_framenum);
+    if (m_nextthink)
+        ast_unref(m_nextthink);
+}
+
+ast_call *ast_call::make(lex_ctx_t ctx, ast_expression *funcexpr)
+{
+    if (!funcexpr->m_next) {
+        compile_error(ctx, "not a function");
+        return nullptr;
+    }
+    return new ast_call(ctx, funcexpr);
+}
+
+ast_call::ast_call(lex_ctx_t ctx, ast_expression *funcexpr)
+    : ast_expression(ctx, TYPE_ast_call)
+    , m_func(funcexpr)
+    , m_va_count(nullptr)
+{
+    m_side_effects = true;
+    adoptType(*funcexpr->m_next);
+}
+
+ast_call::~ast_call()
+{
+    for (auto &it : m_params)
+        ast_unref(it);
+
+    if (m_func)
+        ast_unref(m_func);
+
+    if (m_va_count)
+        ast_unref(m_va_count);
+}
+
+bool ast_call::checkVararg(ast_expression *va_type, ast_expression *exp_type) const
+{
+    char texp[1024];
+    char tgot[1024];
+    if (!exp_type)
+        return true;
+    if (!va_type || !va_type->compareType(*exp_type))
+    {
+        if (va_type && exp_type)
+        {
+            ast_type_to_string(va_type,  tgot, sizeof(tgot));
+            ast_type_to_string(exp_type, texp, sizeof(texp));
+            if (OPTS_FLAG(UNSAFE_VARARGS)) {
+                if (compile_warning(m_context, WARN_UNSAFE_TYPES,
+                                    "piped variadic argument differs in type: constrained to type %s, expected type %s",
+                                    tgot, texp))
+                    return false;
+            } else {
+                compile_error(m_context,
+                              "piped variadic argument differs in type: constrained to type %s, expected type %s",
+                              tgot, texp);
+                return false;
+            }
+        }
+        else
+        {
+            ast_type_to_string(exp_type, texp, sizeof(texp));
+            if (OPTS_FLAG(UNSAFE_VARARGS)) {
+                if (compile_warning(m_context, WARN_UNSAFE_TYPES,
+                                    "piped variadic argument may differ in type: expected type %s",
+                                    texp))
+                    return false;
+            } else {
+                compile_error(m_context,
+                              "piped variadic argument may differ in type: expected type %s",
+                              texp);
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
+bool ast_call::checkTypes(ast_expression *va_type) const
+{
+    char texp[1024];
+    char tgot[1024];
+    size_t i;
+    bool retval = true;
+
+    size_t count = m_params.size();
+    if (count > m_func->m_type_params.size())
+        count = m_func->m_type_params.size();
+
+    for (i = 0; i < count; ++i) {
+        if (ast_istype(m_params[i], ast_argpipe)) {
+            /* warn about type safety instead */
+            if (i+1 != count) {
+                compile_error(m_context, "argpipe must be the last parameter to a function call");
+                return false;
+            }
+            if (!checkVararg(va_type, m_func->m_type_params[i].get()))
+                retval = false;
+        }
+        else if (!m_params[i]->compareType(*m_func->m_type_params[i]))
+        {
+            ast_type_to_string(m_params[i], tgot, sizeof(tgot));
+            ast_type_to_string(m_func->m_type_params[i].get(), texp, sizeof(texp));
+            compile_error(m_context, "invalid type for parameter %u in function call: expected %s, got %s",
+                     (unsigned int)(i+1), texp, tgot);
+            /* we don't immediately return */
+            retval = false;
+        }
+    }
+    count = m_params.size();
+    if (count > m_func->m_type_params.size() && m_func->m_varparam) {
+        for (; i < count; ++i) {
+            if (ast_istype(m_params[i], ast_argpipe)) {
+                /* warn about type safety instead */
+                if (i+1 != count) {
+                    compile_error(m_context, "argpipe must be the last parameter to a function call");
+                    return false;
+                }
+                if (!checkVararg(va_type, m_func->m_varparam))
+                    retval = false;
+            }
+            else if (!m_params[i]->compareType(*m_func->m_varparam))
+            {
+                ast_type_to_string(m_params[i], tgot, sizeof(tgot));
+                ast_type_to_string(m_func->m_varparam, texp, sizeof(texp));
+                compile_error(m_context, "invalid type for variadic parameter %u in function call: expected %s, got %s",
+                         (unsigned int)(i+1), texp, tgot);
+                /* we don't immediately return */
+                retval = false;
+            }
+        }
+    }
+    return retval;
+}
+
+ast_block::ast_block(lex_ctx_t ctx)
+    : ast_expression(ctx, TYPE_ast_block)
+{
+}
+
+ast_block::~ast_block()
+{
+    for (auto &it : m_exprs) ast_unref(it);
+    for (auto &it : m_locals) delete it;
+    for (auto &it : m_collect) delete it;
+}
+
+void ast_block::setType(const ast_expression &from)
+{
+    if (m_next)
+        delete m_next;
+    adoptType(from);
+}
+
+
+bool ast_block::addExpr(ast_expression *e)
+{
+    propagateSideEffects(e);
+    m_exprs.push_back(e);
+    if (m_next) {
+        delete m_next;
+        m_next = nullptr;
+    }
+    adoptType(*e);
+    return true;
+}
+
+void ast_block::collect(ast_expression *expr)
+{
+    m_collect.push_back(expr);
+    expr->m_keep_node = true;
+}
+
+ast_function *ast_function::make(lex_ctx_t ctx, const std::string &name, ast_value *vtype)
+{
+    if (!vtype) {
+        compile_error(ctx, "internal error: ast_function_new condition 0");
+        return nullptr;
+    } else if (vtype->m_hasvalue || vtype->m_vtype != TYPE_FUNCTION) {
+        compile_error(ctx, "internal error: ast_function_new condition %i %i type=%i (probably 2 bodies?)",
+                 (int)!vtype,
+                 (int)vtype->m_hasvalue,
+                 vtype->m_vtype);
+        return nullptr;
+    }
+    return new ast_function(ctx, name, vtype);
+}
+
+ast_function::ast_function(lex_ctx_t ctx, const std::string &name, ast_value *vtype)
+    : ast_node(ctx, TYPE_ast_function)
+    , m_function_type(vtype)
+    , m_name(name)
+    , m_builtin(0)
+    , m_static_count(0)
+    , m_ir_func(nullptr)
+    , m_curblock(nullptr)
+    , m_labelcount(0)
+    , m_varargs(nullptr)
+    , m_argc(nullptr)
+    , m_fixedparams(nullptr)
+    , m_return_value(nullptr)
+{
+    vtype->m_hasvalue = true;
+    vtype->m_constval.vfunc = this;
+}
+
+ast_function::~ast_function()
+{
+    if (m_function_type) {
+        // ast_value_delete(m_function_type);
+        m_function_type->m_hasvalue = false;
+        m_function_type->m_constval.vfunc = nullptr;
+        // We use unref - if it was stored in a global table it is supposed
+        // to be deleted from *there*
+        ast_unref(m_function_type);
+    }
+
+    if (m_fixedparams)
+        ast_unref(m_fixedparams);
+    if (m_return_value)
+        ast_unref(m_return_value);
+
+    // force this to be cleared before m_varargs/m_argc as blocks might
+    // try to access them via ast_unref()
+    m_blocks.clear();
+}
+
+const char* ast_function::makeLabel(const char *prefix)
+{
+    size_t id;
+    size_t len;
+    char  *from;
+
+    if (!OPTS_OPTION_BOOL(OPTION_DUMP)    &&
+        !OPTS_OPTION_BOOL(OPTION_DUMPFIN) &&
+        !OPTS_OPTION_BOOL(OPTION_DEBUG))
+    {
+        return nullptr;
+    }
+
+    id  = (m_labelcount++);
+    len = strlen(prefix);
+
+    from = m_labelbuf + sizeof(m_labelbuf)-1;
+    *from-- = 0;
+    do {
+        *from-- = (id%10) + '0';
+        id /= 10;
+    } while (id);
+    ++from;
+    memcpy(from - len, prefix, len);
+    return from - len;
+}
+
+/*********************************************************************/
+/* AST codegen part
+ * by convention you must never pass nullptr to the 'ir_value **out'
+ * parameter. If you really don't care about the output, pass a dummy.
+ * But I can't imagine a pituation where the output is truly unnecessary.
+ */
+
+static void codegen_output_type(ast_expression *self, ir_value *out)
+{
+    if (out->m_vtype == TYPE_FIELD)
+        out->m_fieldtype = self->m_next->m_vtype;
+    if (out->m_vtype == TYPE_FUNCTION)
+        out->m_outtype = self->m_next->m_vtype;
+}
+
+bool ast_value::codegen(ast_function *func, bool lvalue, ir_value **out)
+{
+    (void)func;
+    (void)lvalue;
+    if (m_vtype == TYPE_NIL) {
+        *out = func->m_ir_func->m_owner->m_nil;
+        return true;
+    }
+    // NOTE: This is the codegen for a variable used in an expression.
+    // It is not the codegen to generate the value storage. For this purpose,
+    // generateLocal and generateGlobal are to be used before this
+    // is executed. ast_function::generateFunction should take care of its
+    // locals, and the ast-user should take care of generateGlobal to be used
+    // on all the globals.
+    if (!m_ir_v) {
+        char tname[1024]; /* typename is reserved in C++ */
+        ast_type_to_string(this, tname, sizeof(tname));
+        compile_error(m_context, "ast_value used before generated %s %s", tname, m_name);
+        return false;
+    }
+    *out = m_ir_v;
+    return true;
+}
+
+bool ast_value::setGlobalArray()
+{
+    size_t count = m_initlist.size();
+    size_t i;
+
+    if (count > m_count) {
+        compile_error(m_context, "too many elements in initializer");
+        count = m_count;
+    }
+    else if (count < m_count) {
+        /* add this?
+        compile_warning(m_context, "not all elements are initialized");
+        */
+    }
+
+    for (i = 0; i != count; ++i) {
+        switch (m_next->m_vtype) {
+            case TYPE_FLOAT:
+                if (!m_ir_values[i]->setFloat(m_initlist[i].vfloat))
+                    return false;
+                break;
+            case TYPE_VECTOR:
+                if (!m_ir_values[i]->setVector(m_initlist[i].vvec))
+                    return false;
+                break;
+            case TYPE_STRING:
+                if (!m_ir_values[i]->setString(m_initlist[i].vstring))
+                    return false;
+                break;
+            case TYPE_ARRAY:
+                /* we don't support them in any other place yet either */
+                compile_error(m_context, "TODO: nested arrays");
+                return false;
+            case TYPE_FUNCTION:
+                /* this requiers a bit more work - similar to the fields I suppose */
+                compile_error(m_context, "global of type function not properly generated");
+                return false;
+            case TYPE_FIELD:
+                if (!m_initlist[i].vfield) {
+                    compile_error(m_context, "field constant without vfield set");
+                    return false;
+                }
+                if (!m_initlist[i].vfield->m_ir_v) {
+                    compile_error(m_context, "field constant generated before its field");
+                    return false;
+                }
+                if (!m_ir_values[i]->setField(m_initlist[i].vfield->m_ir_v))
+                    return false;
+                break;
+            default:
+                compile_error(m_context, "TODO: global constant type %i", m_vtype);
+                break;
+        }
+    }
+    return true;
+}
+
+bool ast_value::checkArray(const ast_value &array) const
+{
+    if (array.m_flags & AST_FLAG_ARRAY_INIT && array.m_initlist.empty()) {
+        compile_error(m_context, "array without size: %s", m_name);
+        return false;
+    }
+    // we are lame now - considering the way QC works we won't tolerate arrays > 1024 elements
+    if (!array.m_count || array.m_count > OPTS_OPTION_U32(OPTION_MAX_ARRAY_SIZE)) {
+        compile_error(m_context, "Invalid array of size %lu", (unsigned long)array.m_count);
+        return false;
+    }
+    return true;
+}
+
+bool ast_value::generateGlobal(ir_builder *ir, bool isfield)
+{
+    if (m_vtype == TYPE_NIL) {
+        compile_error(m_context, "internal error: trying to generate a variable of TYPE_NIL");
+        return false;
+    }
+
+    if (m_hasvalue && m_vtype == TYPE_FUNCTION)
+        return generateGlobalFunction(ir);
+
+    if (isfield && m_vtype == TYPE_FIELD)
+        return generateGlobalField(ir);
+
+    ir_value *v = nullptr;
+    if (m_vtype == TYPE_ARRAY) {
+        v = prepareGlobalArray(ir);
+        if (!v)
+            return false;
+    } else {
+        // Arrays don't do this since there's no "array" value which spans across the
+        // whole thing.
+        v = ir->createGlobal(m_name, m_vtype);
+        if (!v) {
+            compile_error(m_context, "ir_builder::createGlobal failed on `%s`", m_name);
+            return false;
+        }
+        codegen_output_type(this, v);
+        v->m_context = m_context;
+    }
+
+    /* link us to the ir_value */
+    v->m_cvq = m_cvq;
+    m_ir_v = v;
+
+    if (m_flags & AST_FLAG_INCLUDE_DEF)
+        m_ir_v->m_flags |= IR_FLAG_INCLUDE_DEF;
+    if (m_flags & AST_FLAG_ERASEABLE)
+        m_ir_v->m_flags |= IR_FLAG_ERASABLE;
+
+    /* initialize */
+    if (m_hasvalue) {
+        switch (m_vtype)
+        {
+            case TYPE_FLOAT:
+                if (!v->setFloat(m_constval.vfloat))
+                    return false;
+                break;
+            case TYPE_VECTOR:
+                if (!v->setVector(m_constval.vvec))
+                    return false;
+                break;
+            case TYPE_STRING:
+                if (!v->setString(m_constval.vstring))
+                    return false;
+                break;
+            case TYPE_ARRAY:
+                if (!setGlobalArray())
+                    return false;
+                break;
+            case TYPE_FUNCTION:
+                compile_error(m_context, "global of type function not properly generated");
+                return false;
+                /* Cannot generate an IR value for a function,
+                 * need a pointer pointing to a function rather.
+                 */
+            case TYPE_FIELD:
+                if (!m_constval.vfield) {
+                    compile_error(m_context, "field constant without vfield set");
+                    return false;
+                }
+                if (!m_constval.vfield->m_ir_v) {
+                    compile_error(m_context, "field constant generated before its field");
+                    return false;
+                }
+                if (!v->setField(m_constval.vfield->m_ir_v))
+                    return false;
+                break;
+            default:
+                compile_error(m_context, "TODO: global constant type %i", m_vtype);
+                break;
+        }
+    }
+
+    return true;
+}
+
+bool ast_value::generateGlobalFunction(ir_builder *ir)
+{
+    ir_function *func = ir->createFunction(m_name, m_next->m_vtype);
+    if (!func)
+        return false;
+    func->m_context = m_context;
+    func->m_value->m_context = m_context;
+
+    m_constval.vfunc->m_ir_func = func;
+    m_ir_v = func->m_value;
+    if (m_flags & AST_FLAG_INCLUDE_DEF)
+        m_ir_v->m_flags |= IR_FLAG_INCLUDE_DEF;
+    if (m_flags & AST_FLAG_ERASEABLE)
+        m_ir_v->m_flags |= IR_FLAG_ERASABLE;
+    if (m_flags & AST_FLAG_BLOCK_COVERAGE)
+        func->m_flags |= IR_FLAG_BLOCK_COVERAGE;
+    // The function is filled later on ast_function::generateFunction...
+    return true;
+}
+
+bool ast_value::generateGlobalField(ir_builder *ir)
+{
+    ast_expression *fieldtype = m_next;
+
+    if (m_hasvalue) {
+        compile_error(m_context, "TODO: constant field pointers with value");
+        return false;
+    }
+
+    if (fieldtype->m_vtype == TYPE_ARRAY) {
+        if (!ast_istype(fieldtype, ast_value)) {
+            compile_error(m_context, "internal error: ast_value required");
+            return false;
+        }
+        ast_value      *array = reinterpret_cast<ast_value*>(fieldtype);
+
+        if (!checkArray(*array))
+            return false;
+
+        ast_expression *elemtype = array->m_next;
+        qc_type vtype = elemtype->m_vtype;
+
+        ir_value *v = ir->createField(m_name, vtype);
+        if (!v) {
+            compile_error(m_context, "ir_builder::createGlobal failed on `%s`", m_name);
+            return false;
+        }
+        v->m_context = m_context;
+        v->m_unique_life = true;
+        v->m_locked      = true;
+        array->m_ir_v = m_ir_v = v;
+
+        if (m_flags & AST_FLAG_INCLUDE_DEF)
+            m_ir_v->m_flags |= IR_FLAG_INCLUDE_DEF;
+        if (m_flags & AST_FLAG_ERASEABLE)
+            m_ir_v->m_flags |= IR_FLAG_ERASABLE;
+
+        const size_t namelen = m_name.length();
+        std::unique_ptr<char[]> name(new char[namelen+16]);
+        util_strncpy(name.get(), m_name.c_str(), namelen);
+
+        array->m_ir_values.resize(array->m_count);
+        array->m_ir_values[0] = v;
+        for (size_t ai = 1; ai < array->m_count; ++ai) {
+            util_snprintf(name.get() + namelen, 16, "[%u]", (unsigned int)ai);
+            array->m_ir_values[ai] = ir->createField(name.get(), vtype);
+            if (!array->m_ir_values[ai]) {
+                compile_error(m_context, "ir_builder::createGlobal failed on `%s`", name.get());
+                return false;
+            }
+            array->m_ir_values[ai]->m_context = m_context;
+            array->m_ir_values[ai]->m_unique_life = true;
+            array->m_ir_values[ai]->m_locked      = true;
+            if (m_flags & AST_FLAG_INCLUDE_DEF)
+                m_ir_values[ai]->m_flags |= IR_FLAG_INCLUDE_DEF;
+        }
+    }
+    else
+    {
+        ir_value *v = ir->createField(m_name, m_next->m_vtype);
+        if (!v)
+            return false;
+        v->m_context = m_context;
+        m_ir_v = v;
+        if (m_flags & AST_FLAG_INCLUDE_DEF)
+            m_ir_v->m_flags |= IR_FLAG_INCLUDE_DEF;
+
+        if (m_flags & AST_FLAG_ERASEABLE)
+            m_ir_v->m_flags |= IR_FLAG_ERASABLE;
+    }
+    return true;
+}
+
+ir_value *ast_value::prepareGlobalArray(ir_builder *ir)
+{
+    ast_expression *elemtype = m_next;
+    qc_type vtype = elemtype->m_vtype;
+
+    if (m_flags & AST_FLAG_ARRAY_INIT && !m_count) {
+        compile_error(m_context, "array `%s' has no size", m_name);
+        return nullptr;
+    }
+
+    /* same as with field arrays */
+    if (!checkArray(*this))
+        return nullptr;
+
+    ir_value *v = ir->createGlobal(m_name, vtype);
+    if (!v) {
+        compile_error(m_context, "ir_builder::createGlobal failed `%s`", m_name);
+        return nullptr;
+    }
+    v->m_context = m_context;
+    v->m_unique_life = true;
+    v->m_locked      = true;
+
+    if (m_flags & AST_FLAG_INCLUDE_DEF)
+        v->m_flags |= IR_FLAG_INCLUDE_DEF;
+    if (m_flags & AST_FLAG_ERASEABLE)
+        m_ir_v->m_flags |= IR_FLAG_ERASABLE;
+
+    const size_t namelen = m_name.length();
+    std::unique_ptr<char[]> name(new char[namelen+16]);
+    util_strncpy(name.get(), m_name.c_str(), namelen);
+
+    m_ir_values.resize(m_count);
+    m_ir_values[0] = v;
+    for (size_t ai = 1; ai < m_count; ++ai) {
+        util_snprintf(name.get() + namelen, 16, "[%u]", (unsigned int)ai);
+        m_ir_values[ai] = ir->createGlobal(name.get(), vtype);
+        if (!m_ir_values[ai]) {
+            compile_error(m_context, "ir_builder::createGlobal failed `%s`", name.get());
+            return nullptr;
+        }
+        m_ir_values[ai]->m_context = m_context;
+        m_ir_values[ai]->m_unique_life = true;
+        m_ir_values[ai]->m_locked      = true;
+        if (m_flags & AST_FLAG_INCLUDE_DEF)
+            m_ir_values[ai]->m_flags |= IR_FLAG_INCLUDE_DEF;
+    }
+
+    return v;
+}
+
+bool ast_value::generateLocal(ir_function *func, bool param)
+{
+    if (m_vtype == TYPE_NIL) {
+        compile_error(m_context, "internal error: trying to generate a variable of TYPE_NIL");
+        return false;
+    }
+
+    if (m_hasvalue && m_vtype == TYPE_FUNCTION)
+    {
+        /* Do we allow local functions? I think not...
+         * this is NOT a function pointer atm.
+         */
+        return false;
+    }
+
+    ir_value *v = nullptr;
+    if (m_vtype == TYPE_ARRAY) {
+        ast_expression *elemtype = m_next;
+        qc_type vtype = elemtype->m_vtype;
+
+        func->m_flags |= IR_FLAG_HAS_ARRAYS;
+
+        if (param && !(m_flags & AST_FLAG_IS_VARARG)) {
+            compile_error(m_context, "array-parameters are not supported");
+            return false;
+        }
+
+        /* we are lame now - considering the way QC works we won't tolerate arrays > 1024 elements */
+        if (!checkArray(*this))
+            return false;
+
+        m_ir_values.resize(m_count);
+        v = ir_function_create_local(func, m_name, vtype, param);
+        if (!v) {
+            compile_error(m_context, "internal error: ir_function_create_local failed");
+            return false;
+        }
+        v->m_context = m_context;
+        v->m_unique_life = true;
+        v->m_locked      = true;
+
+        const size_t namelen = m_name.length();
+        std::unique_ptr<char[]> name(new char[namelen+16]);
+        util_strncpy(name.get(), m_name.c_str(), namelen);
+
+        m_ir_values[0] = v;
+        for (size_t ai = 1; ai < m_count; ++ai) {
+            util_snprintf(name.get() + namelen, 16, "[%u]", (unsigned int)ai);
+            m_ir_values[ai] = ir_function_create_local(func, name.get(), vtype, param);
+            if (!m_ir_values[ai]) {
+                compile_error(m_context, "internal_error: ir_builder::createGlobal failed on `%s`", name.get());
+                return false;
+            }
+            m_ir_values[ai]->m_context = m_context;
+            m_ir_values[ai]->m_unique_life = true;
+            m_ir_values[ai]->m_locked      = true;
+        }
+    }
+    else
+    {
+        v = ir_function_create_local(func, m_name, m_vtype, param);
+        if (!v)
+            return false;
+        codegen_output_type(this, v);
+        v->m_context = m_context;
+    }
+
+    // A constant local... hmmm...
+    // I suppose the IR will have to deal with this
+    if (m_hasvalue) {
+        switch (m_vtype)
+        {
+            case TYPE_FLOAT:
+                if (!v->setFloat(m_constval.vfloat))
+                    goto error;
+                break;
+            case TYPE_VECTOR:
+                if (!v->setVector(m_constval.vvec))
+                    goto error;
+                break;
+            case TYPE_STRING:
+                if (!v->setString(m_constval.vstring))
+                    goto error;
+                break;
+            default:
+                compile_error(m_context, "TODO: global constant type %i", m_vtype);
+                break;
+        }
+    }
+
+    // link us to the ir_value
+    v->m_cvq = m_cvq;
+    m_ir_v = v;
+
+    if (!generateAccessors(func->m_owner))
+        return false;
+    return true;
+
+error: /* clean up */
+    delete v;
+    return false;
+}
+
+bool ast_value::generateAccessors(ir_builder *ir)
+{
+    size_t i;
+    bool warn = OPTS_WARN(WARN_USED_UNINITIALIZED);
+    if (!m_setter || !m_getter)
+        return true;
+    if (m_count && m_ir_values.empty()) {
+        compile_error(m_context, "internal error: no array values generated for `%s`", m_name);
+        return false;
+    }
+    for (i = 0; i < m_count; ++i) {
+        if (!m_ir_values[i]) {
+            compile_error(m_context, "internal error: not all array values have been generated for `%s`", m_name);
+            return false;
+        }
+        if (!m_ir_values[i]->m_life.empty()) {
+            compile_error(m_context, "internal error: function containing `%s` already generated", m_name);
+            return false;
+        }
+    }
+
+    opts_set(opts.warn, WARN_USED_UNINITIALIZED, false);
+    if (m_setter) {
+        if (!m_setter->generateGlobal(ir, false) ||
+            !m_setter->m_constval.vfunc->generateFunction(ir) ||
+            !ir_function_finalize(m_setter->m_constval.vfunc->m_ir_func))
+        {
+            compile_error(m_context, "internal error: failed to generate setter for `%s`", m_name);
+            opts_set(opts.warn, WARN_USED_UNINITIALIZED, warn);
+            return false;
+        }
+    }
+    if (m_getter) {
+        if (!m_getter->generateGlobal(ir, false) ||
+            !m_getter->m_constval.vfunc->generateFunction(ir) ||
+            !ir_function_finalize(m_getter->m_constval.vfunc->m_ir_func))
+        {
+            compile_error(m_context, "internal error: failed to generate getter for `%s`", m_name);
+            opts_set(opts.warn, WARN_USED_UNINITIALIZED, warn);
+            return false;
+        }
+    }
+    for (i = 0; i < m_count; ++i)
+        m_ir_values[i]->m_life.clear();
+    opts_set(opts.warn, WARN_USED_UNINITIALIZED, warn);
+    return true;
+}
+
+bool ast_function::generateFunction(ir_builder *ir)
+{
+    (void)ir;
+
+    ir_value *dummy;
+
+    ir_function *irf = m_ir_func;
+    if (!irf) {
+        compile_error(m_context, "internal error: ast_function's related ast_value was not generated yet");
+        return false;
+    }
+
+    /* fill the parameter list */
+    for (auto &it : m_function_type->m_type_params) {
+        if (it->m_vtype == TYPE_FIELD)
+            vec_push(irf->m_params, it->m_next->m_vtype);
+        else
+            vec_push(irf->m_params, it->m_vtype);
+        if (!m_builtin) {
+            if (!it->generateLocal(m_ir_func, true))
+                return false;
+        }
+    }
+
+    if (m_varargs) {
+        if (!m_varargs->generateLocal(m_ir_func, true))
+            return false;
+        irf->m_max_varargs = m_varargs->m_count;
+    }
+
+    if (m_builtin) {
+        irf->m_builtin = m_builtin;
+        return true;
+    }
+
+    /* have a local return value variable? */
+    if (m_return_value) {
+        if (!m_return_value->generateLocal(m_ir_func, false))
+            return false;
+    }
+
+    if (m_blocks.empty()) {
+        compile_error(m_context, "function `%s` has no body", m_name);
+        return false;
+    }
+
+    irf->m_first = m_curblock = ir_function_create_block(m_context, irf, "entry");
+    if (!m_curblock) {
+        compile_error(m_context, "failed to allocate entry block for `%s`", m_name);
+        return false;
+    }
+
+    if (m_argc) {
+        ir_value *va_count;
+        ir_value *fixed;
+        ir_value *sub;
+        if (!m_argc->generateLocal(m_ir_func, true))
+            return false;
+        if (!m_argc->codegen(this, false, &va_count))
+            return false;
+        if (!m_fixedparams->codegen(this, false, &fixed))
+            return false;
+        sub = ir_block_create_binop(m_curblock, m_context,
+                                    makeLabel("va_count"), INSTR_SUB_F,
+                                    ir->get_va_count(), fixed);
+        if (!sub)
+            return false;
+        if (!ir_block_create_store_op(m_curblock, m_context, INSTR_STORE_F,
+                                      va_count, sub))
+        {
+            return false;
+        }
+    }
+
+    for (auto &it : m_blocks) {
+        if (!it->codegen(this, false, &dummy))
+          return false;
+    }
+
+    /* TODO: check return types */
+    if (!m_curblock->m_final)
+    {
+        if (!m_function_type->m_next ||
+            m_function_type->m_next->m_vtype == TYPE_VOID)
+        {
+            return ir_block_create_return(m_curblock, m_context, nullptr);
+        }
+        else if (vec_size(m_curblock->m_entries) || m_curblock == irf->m_first)
+        {
+            if (m_return_value) {
+                if (!m_return_value->codegen(this, false, &dummy))
+                    return false;
+                return ir_block_create_return(m_curblock, m_context, dummy);
+            }
+            else if (compile_warning(m_context, WARN_MISSING_RETURN_VALUES,
+                                "control reaches end of non-void function (`%s`) via %s",
+                                m_name.c_str(), m_curblock->m_label.c_str()))
+            {
+                return false;
+            }
+            return ir_block_create_return(m_curblock, m_context, nullptr);
+        }
+    }
+    return true;
+}
+
+static bool starts_a_label(const ast_expression *ex)
+{
+    while (ex && ast_istype(ex, ast_block)) {
+        auto b = reinterpret_cast<const ast_block*>(ex);
+        ex = b->m_exprs[0];
+    }
+    if (!ex)
+        return false;
+    return ast_istype(ex, ast_label);
+}
+
+/* Note, you will not see ast_block_codegen generate ir_blocks.
+ * To the AST and the IR, blocks are 2 different things.
+ * In the AST it represents a block of code, usually enclosed in
+ * curly braces {...}.
+ * While in the IR it represents a block in terms of control-flow.
+ */
+bool ast_block::codegen(ast_function *func, bool lvalue, ir_value **out)
+{
+    /* We don't use this
+     * Note: an ast-representation using the comma-operator
+     * of the form: (a, b, c) = x should not assign to c...
+     */
+    if (lvalue) {
+        compile_error(m_context, "not an l-value (code-block)");
+        return false;
+    }
+
+    if (m_outr) {
+        *out = m_outr;
+        return true;
+    }
+
+    /* output is nullptr at first, we'll have each expression
+     * assign to out output, thus, a comma-operator represention
+     * using an ast_block will return the last generated value,
+     * so: (b, c) + a  executed both b and c, and returns c,
+     * which is then added to a.
+     */
+    *out = nullptr;
+
+    /* generate locals */
+    for (auto &it : m_locals) {
+        if (!it->generateLocal(func->m_ir_func, false)) {
+            if (OPTS_OPTION_BOOL(OPTION_DEBUG))
+                compile_error(m_context, "failed to generate local `%s`", it->m_name);
+            return false;
+        }
+    }
+
+    for (auto &it : m_exprs) {
+        if (func->m_curblock->m_final && !starts_a_label(it)) {
+            if (compile_warning(it->m_context, WARN_UNREACHABLE_CODE, "unreachable statement"))
+                return false;
+            continue;
+        }
+        if (!it->codegen(func, false, out))
+            return false;
+    }
+
+    m_outr = *out;
+
+    return true;
+}
+
+bool ast_store::codegen(ast_function *func, bool lvalue, ir_value **out)
+{
+    ir_value *left  = nullptr;
+    ir_value *right = nullptr;
+
+    ast_value       *idx = 0;
+    ast_array_index *ai = nullptr;
+
+    if (lvalue && m_outl) {
+        *out = m_outl;
+        return true;
+    }
+
+    if (!lvalue && m_outr) {
+        *out = m_outr;
+        return true;
+    }
+
+    if (ast_istype(m_dest, ast_array_index))
+    {
+
+        ai = (ast_array_index*)m_dest;
+        idx = (ast_value*)ai->m_index;
+
+        if (ast_istype(ai->m_index, ast_value) && idx->m_hasvalue && idx->m_cvq == CV_CONST)
+            ai = nullptr;
+    }
+
+    if (ai) {
+        /* we need to call the setter */
+        ir_value  *iridx, *funval;
+        ir_instr  *call;
+
+        if (lvalue) {
+            compile_error(m_context, "array-subscript assignment cannot produce lvalues");
+            return false;
+        }
+
+        auto arr = reinterpret_cast<ast_value*>(ai->m_array);
+        if (!ast_istype(ai->m_array, ast_value) || !arr->m_setter) {
+            compile_error(m_context, "value has no setter (%s)", arr->m_name);
+            return false;
+        }
+
+        if (!idx->codegen(func, false, &iridx))
+            return false;
+
+        if (!arr->m_setter->codegen(func, true, &funval))
+            return false;
+
+        if (!m_source->codegen(func, false, &right))
+            return false;
+
+        call = ir_block_create_call(func->m_curblock, m_context, func->makeLabel("store"), funval, false);
+        if (!call)
+            return false;
+        ir_call_param(call, iridx);
+        ir_call_param(call, right);
+        m_outr = right;
+    }
+    else
+    {
+        // regular code
+
+        // lvalue!
+        if (!m_dest->codegen(func, true, &left))
+            return false;
+        m_outl = left;
+
+        /* rvalue! */
+        if (!m_source->codegen(func, false, &right))
+            return false;
+
+        if (!ir_block_create_store_op(func->m_curblock, m_context, m_op, left, right))
+            return false;
+        m_outr = right;
+    }
+
+    /* Theoretically, an assinment returns its left side as an
+     * lvalue, if we don't need an lvalue though, we return
+     * the right side as an rvalue, otherwise we have to
+     * somehow know whether or not we need to dereference the pointer
+     * on the left side - that is: OP_LOAD if it was an address.
+     * Also: in original QC we cannot OP_LOADP *anyway*.
+     */
+    *out = (lvalue ? left : right);
+
+    return true;
+}
+
+bool ast_binary::codegen(ast_function *func, bool lvalue, ir_value **out)
+{
+    ir_value *left, *right;
+
+    /* A binary operation cannot yield an l-value */
+    if (lvalue) {
+        compile_error(m_context, "not an l-value (binop)");
+        return false;
+    }
+
+    if (m_outr) {
+        *out = m_outr;
+        return true;
+    }
+
+    if ((OPTS_FLAG(SHORT_LOGIC) || OPTS_FLAG(PERL_LOGIC)) &&
+        (m_op == INSTR_AND || m_op == INSTR_OR))
+    {
+        /* NOTE: The short-logic path will ignore right_first */
+
+        /* short circuit evaluation */
+        ir_block *other, *merge;
+        ir_block *from_left, *from_right;
+        ir_instr *phi;
+        size_t    merge_id;
+
+        /* prepare end-block */
+        merge_id = func->m_ir_func->m_blocks.size();
+        merge    = ir_function_create_block(m_context, func->m_ir_func, func->makeLabel("sce_merge"));
+
+        /* generate the left expression */
+        if (!m_left->codegen(func, false, &left))
+            return false;
+        /* remember the block */
+        from_left = func->m_curblock;
+
+        /* create a new block for the right expression */
+        other = ir_function_create_block(m_context, func->m_ir_func, func->makeLabel("sce_other"));
+        if (m_op == INSTR_AND) {
+            /* on AND: left==true -> other */
+            if (!ir_block_create_if(func->m_curblock, m_context, left, other, merge))
+                return false;
+        } else {
+            /* on OR: left==false -> other */
+            if (!ir_block_create_if(func->m_curblock, m_context, left, merge, other))
+                return false;
+        }
+        /* use the likely flag */
+        vec_last(func->m_curblock->m_instr)->m_likely = true;
+
+        /* enter the right-expression's block */
+        func->m_curblock = other;
+        /* generate */
+        if (!m_right->codegen(func, false, &right))
+            return false;
+        /* remember block */
+        from_right = func->m_curblock;
+
+        /* jump to the merge block */
+        if (!ir_block_create_jump(func->m_curblock, m_context, merge))
+            return false;
+
+        algo::shiftback(func->m_ir_func->m_blocks.begin() + merge_id,
+                        func->m_ir_func->m_blocks.end());
+        // FIXME::DELME::
+        //func->m_ir_func->m_blocks[merge_id].release();
+        //func->m_ir_func->m_blocks.erase(func->m_ir_func->m_blocks.begin() + merge_id);
+        //func->m_ir_func->m_blocks.emplace_back(merge);
+
+        func->m_curblock = merge;
+        phi = ir_block_create_phi(func->m_curblock, m_context,
+                                  func->makeLabel("sce_value"),
+                                  m_vtype);
+        ir_phi_add(phi, from_left, left);
+        ir_phi_add(phi, from_right, right);
+        *out = ir_phi_value(phi);
+        if (!*out)
+            return false;
+
+        if (!OPTS_FLAG(PERL_LOGIC)) {
+            /* cast-to-bool */
+            if (OPTS_FLAG(CORRECT_LOGIC) && (*out)->m_vtype == TYPE_VECTOR) {
+                *out = ir_block_create_unary(func->m_curblock, m_context,
+                                             func->makeLabel("sce_bool_v"),
+                                             INSTR_NOT_V, *out);
+                if (!*out)
+                    return false;
+                *out = ir_block_create_unary(func->m_curblock, m_context,
+                                             func->makeLabel("sce_bool"),
+                                             INSTR_NOT_F, *out);
+                if (!*out)
+                    return false;
+            }
+            else if (OPTS_FLAG(FALSE_EMPTY_STRINGS) && (*out)->m_vtype == TYPE_STRING) {
+                *out = ir_block_create_unary(func->m_curblock, m_context,
+                                             func->makeLabel("sce_bool_s"),
+                                             INSTR_NOT_S, *out);
+                if (!*out)
+                    return false;
+                *out = ir_block_create_unary(func->m_curblock, m_context,
+                                             func->makeLabel("sce_bool"),
+                                             INSTR_NOT_F, *out);
+                if (!*out)
+                    return false;
+            }
+            else {
+                *out = ir_block_create_binop(func->m_curblock, m_context,
+                                             func->makeLabel("sce_bool"),
+                                             INSTR_AND, *out, *out);
+                if (!*out)
+                    return false;
+            }
+        }
+
+        m_outr = *out;
+        codegen_output_type(this, *out);
+        return true;
+    }
+
+    if (m_right_first) {
+        if (!m_right->codegen(func, false, &right))
+            return false;
+        if (!m_left->codegen(func, false, &left))
+            return false;
+    } else {
+        if (!m_left->codegen(func, false, &left))
+            return false;
+        if (!m_right->codegen(func, false, &right))
+            return false;
+    }
+
+    *out = ir_block_create_binop(func->m_curblock, m_context, func->makeLabel("bin"),
+                                 m_op, left, right);
+    if (!*out)
+        return false;
+    m_outr = *out;
+    codegen_output_type(this, *out);
+
+    return true;
+}
+
+bool ast_binstore::codegen(ast_function *func, bool lvalue, ir_value **out)
+{
+    ir_value *leftl = nullptr, *leftr, *right, *bin;
+
+    ast_value       *arr;
+    ast_value       *idx = 0;
+    ast_array_index *ai = nullptr;
+    ir_value        *iridx = nullptr;
+
+    if (lvalue && m_outl) {
+        *out = m_outl;
+        return true;
+    }
+
+    if (!lvalue && m_outr) {
+        *out = m_outr;
+        return true;
+    }
+
+    if (ast_istype(m_dest, ast_array_index))
+    {
+
+        ai = (ast_array_index*)m_dest;
+        idx = (ast_value*)ai->m_index;
+
+        if (ast_istype(ai->m_index, ast_value) && idx->m_hasvalue && idx->m_cvq == CV_CONST)
+            ai = nullptr;
+    }
+
+    /* for a binstore we need both an lvalue and an rvalue for the left side */
+    /* rvalue of destination! */
+    if (ai) {
+        if (!idx->codegen(func, false, &iridx))
+            return false;
+    }
+    if (!m_dest->codegen(func, false, &leftr))
+        return false;
+
+    /* source as rvalue only */
+    if (!m_source->codegen(func, false, &right))
+        return false;
+
+    /* now the binary */
+    bin = ir_block_create_binop(func->m_curblock, m_context, func->makeLabel("binst"),
+                                m_opbin, leftr, right);
+    m_outr = bin;
+
+    if (ai) {
+        /* we need to call the setter */
+        ir_value  *funval;
+        ir_instr  *call;
+
+        if (lvalue) {
+            compile_error(m_context, "array-subscript assignment cannot produce lvalues");
+            return false;
+        }
+
+        arr = (ast_value*)ai->m_array;
+        if (!ast_istype(ai->m_array, ast_value) || !arr->m_setter) {
+            compile_error(m_context, "value has no setter (%s)", arr->m_name);
+            return false;
+        }
+
+        if (!arr->m_setter->codegen(func, true, &funval))
+            return false;
+
+        call = ir_block_create_call(func->m_curblock, m_context, func->makeLabel("store"), funval, false);
+        if (!call)
+            return false;
+        ir_call_param(call, iridx);
+        ir_call_param(call, bin);
+        m_outr = bin;
+    } else {
+        // now store them
+        // lvalue of destination
+        if (!m_dest->codegen(func, true, &leftl))
+            return false;
+        m_outl = leftl;
+
+        if (!ir_block_create_store_op(func->m_curblock, m_context, m_opstore, leftl, bin))
+            return false;
+        m_outr = bin;
+    }
+
+    /* Theoretically, an assinment returns its left side as an
+     * lvalue, if we don't need an lvalue though, we return
+     * the right side as an rvalue, otherwise we have to
+     * somehow know whether or not we need to dereference the pointer
+     * on the left side - that is: OP_LOAD if it was an address.
+     * Also: in original QC we cannot OP_LOADP *anyway*.
+     */
+    *out = (lvalue ? leftl : bin);
+
+    return true;
+}
+
+bool ast_unary::codegen(ast_function *func, bool lvalue, ir_value **out)
+{
+    ir_value *operand;
+
+    /* An unary operation cannot yield an l-value */
+    if (lvalue) {
+        compile_error(m_context, "not an l-value (binop)");
+        return false;
+    }
+
+    if (m_outr) {
+        *out = m_outr;
+        return true;
+    }
+
+    /* lvalue! */
+    if (!m_operand->codegen(func, false, &operand))
+        return false;
+
+    *out = ir_block_create_unary(func->m_curblock, m_context, func->makeLabel("unary"),
+                                 m_op, operand);
+    if (!*out)
+        return false;
+    m_outr = *out;
+
+    return true;
+}
+
+bool ast_return::codegen(ast_function *func, bool lvalue, ir_value **out)
+{
+    ir_value *operand;
+
+    *out = nullptr;
+
+    /* In the context of a return operation, we don't actually return
+     * anything...
+     */
+    if (lvalue) {
+        compile_error(m_context, "return-expression is not an l-value");
+        return false;
+    }
+
+    if (m_outr) {
+        compile_error(m_context, "internal error: ast_return cannot be reused, it bears no result!");
+        return false;
+    }
+    m_outr = (ir_value*)1;
+
+    if (m_operand) {
+        /* lvalue! */
+        if (!m_operand->codegen(func, false, &operand))
+            return false;
+
+        if (!ir_block_create_return(func->m_curblock, m_context, operand))
+            return false;
+    } else {
+        if (!ir_block_create_return(func->m_curblock, m_context, nullptr))
+            return false;
+    }
+
+    return true;
+}
+
+bool ast_entfield::codegen(ast_function *func, bool lvalue, ir_value **out)
+{
+    ir_value *ent, *field;
+
+    // This function needs to take the 'lvalue' flag into account!
+    // As lvalue we provide a field-pointer, as rvalue we provide the
+    // value in a temp.
+
+    if (lvalue && m_outl) {
+        *out = m_outl;
+        return true;
+    }
+
+    if (!lvalue && m_outr) {
+        *out = m_outr;
+        return true;
+    }
+
+    if (!m_entity->codegen(func, false, &ent))
+        return false;
+
+    if (!m_field->codegen(func, false, &field))
+        return false;
+
+    if (lvalue) {
+        /* address! */
+        *out = ir_block_create_fieldaddress(func->m_curblock, m_context, func->makeLabel("efa"),
+                                            ent, field);
+    } else {
+        *out = ir_block_create_load_from_ent(func->m_curblock, m_context, func->makeLabel("efv"),
+                                             ent, field, m_vtype);
+        /* Done AFTER error checking:
+        codegen_output_type(this, *out);
+        */
+    }
+    if (!*out) {
+        compile_error(m_context, "failed to create %s instruction (output type %s)",
+                 (lvalue ? "ADDRESS" : "FIELD"),
+                 type_name[m_vtype]);
+        return false;
+    }
+    if (!lvalue)
+        codegen_output_type(this, *out);
+
+    if (lvalue)
+        m_outl = *out;
+    else
+        m_outr = *out;
+
+    // Hm that should be it...
+    return true;
+}
+
+bool ast_member::codegen(ast_function *func, bool lvalue, ir_value **out)
+{
+    ir_value *vec;
+
+    /* in QC this is always an lvalue */
+    if (lvalue && m_rvalue) {
+        compile_error(m_context, "not an l-value (member access)");
+        return false;
+    }
+    if (m_outl) {
+        *out = m_outl;
+        return true;
+    }
+
+    if (!m_owner->codegen(func, false, &vec))
+        return false;
+
+    if (vec->m_vtype != TYPE_VECTOR &&
+        !(vec->m_vtype == TYPE_FIELD && m_owner->m_next->m_vtype == TYPE_VECTOR))
+    {
+        return false;
+    }
+
+    *out = vec->vectorMember(m_field);
+    m_outl = *out;
+
+    return (*out != nullptr);
+}
+
+bool ast_array_index::codegen(ast_function *func, bool lvalue, ir_value **out)
+{
+    ast_value *arr;
+    ast_value *idx;
+
+    if (!lvalue && m_outr) {
+        *out = m_outr;
+        return true;
+    }
+    if (lvalue && m_outl) {
+        *out = m_outl;
+        return true;
+    }
+
+    if (!ast_istype(m_array, ast_value)) {
+        compile_error(m_context, "array indexing this way is not supported");
+        /* note this would actually be pointer indexing because the left side is
+         * not an actual array but (hopefully) an indexable expression.
+         * Once we get integer arithmetic, and GADDRESS/GSTORE/GLOAD instruction
+         * support this path will be filled.
+         */
+        return false;
+    }
+
+    arr = reinterpret_cast<ast_value*>(m_array);
+    idx = reinterpret_cast<ast_value*>(m_index);
+
+    if (!ast_istype(m_index, ast_value) || !idx->m_hasvalue || idx->m_cvq != CV_CONST) {
+        /* Time to use accessor functions */
+        ir_value               *iridx, *funval;
+        ir_instr               *call;
+
+        if (lvalue) {
+            compile_error(m_context, "(.2) array indexing here needs a compile-time constant");
+            return false;
+        }
+
+        if (!arr->m_getter) {
+            compile_error(m_context, "value has no getter, don't know how to index it");
+            return false;
+        }
+
+        if (!m_index->codegen(func, false, &iridx))
+            return false;
+
+        if (!arr->m_getter->codegen(func, true, &funval))
+            return false;
+
+        call = ir_block_create_call(func->m_curblock, m_context, func->makeLabel("fetch"), funval, false);
+        if (!call)
+            return false;
+        ir_call_param(call, iridx);
+
+        *out = ir_call_value(call);
+        m_outr = *out;
+        (*out)->m_vtype = m_vtype;
+        codegen_output_type(this, *out);
+        return true;
+    }
+
+    if (idx->m_vtype == TYPE_FLOAT) {
+        unsigned int arridx = idx->m_constval.vfloat;
+        if (arridx >= m_array->m_count)
+        {
+            compile_error(m_context, "array index out of bounds: %i", arridx);
+            return false;
+        }
+        *out = arr->m_ir_values[arridx];
+    }
+    else if (idx->m_vtype == TYPE_INTEGER) {
+        unsigned int arridx = idx->m_constval.vint;
+        if (arridx >= m_array->m_count)
+        {
+            compile_error(m_context, "array index out of bounds: %i", arridx);
+            return false;
+        }
+        *out = arr->m_ir_values[arridx];
+    }
+    else {
+        compile_error(m_context, "array indexing here needs an integer constant");
+        return false;
+    }
+    (*out)->m_vtype = m_vtype;
+    codegen_output_type(this, *out);
+    return true;
+}
+
+bool ast_argpipe::codegen(ast_function *func, bool lvalue, ir_value **out)
+{
+    *out = nullptr;
+    if (lvalue) {
+        compile_error(m_context, "argpipe node: not an lvalue");
+        return false;
+    }
+    (void)func;
+    (void)out;
+    compile_error(m_context, "TODO: argpipe codegen not implemented");
+    return false;
+}
+
+bool ast_ifthen::codegen(ast_function *func, bool lvalue, ir_value **out)
+{
+    ir_value *condval;
+    ir_value *dummy;
+
+    ir_block *cond;
+    ir_block *ontrue;
+    ir_block *onfalse;
+    ir_block *ontrue_endblock = nullptr;
+    ir_block *onfalse_endblock = nullptr;
+    ir_block *merge = nullptr;
+    int folded = 0;
+
+    /* We don't output any value, thus also don't care about r/lvalue */
+    (void)out;
+    (void)lvalue;
+
+    if (m_outr) {
+        compile_error(m_context, "internal error: ast_ifthen cannot be reused, it bears no result!");
+        return false;
+    }
+    m_outr = (ir_value*)1;
+
+    /* generate the condition */
+    if (!m_cond->codegen(func, false, &condval))
+        return false;
+    /* update the block which will get the jump - because short-logic or ternaries may have changed this */
+    cond = func->m_curblock;
+
+    /* try constant folding away the condition */
+    if ((folded = fold::cond_ifthen(condval, func, this)) != -1)
+        return folded;
+
+    if (m_on_true) {
+        /* create on-true block */
+        ontrue = ir_function_create_block(m_context, func->m_ir_func, func->makeLabel("ontrue"));
+        if (!ontrue)
+            return false;
+
+        /* enter the block */
+        func->m_curblock = ontrue;
+
+        /* generate */
+        if (!m_on_true->codegen(func, false, &dummy))
+            return false;
+
+        /* we now need to work from the current endpoint */
+        ontrue_endblock = func->m_curblock;
+    } else
+        ontrue = nullptr;
+
+    /* on-false path */
+    if (m_on_false) {
+        /* create on-false block */
+        onfalse = ir_function_create_block(m_context, func->m_ir_func, func->makeLabel("onfalse"));
+        if (!onfalse)
+            return false;
+
+        /* enter the block */
+        func->m_curblock = onfalse;
+
+        /* generate */
+        if (!m_on_false->codegen(func, false, &dummy))
+            return false;
+
+        /* we now need to work from the current endpoint */
+        onfalse_endblock = func->m_curblock;
+    } else
+        onfalse = nullptr;
+
+    /* Merge block were they all merge in to */
+    if (!ontrue || !onfalse || !ontrue_endblock->m_final || !onfalse_endblock->m_final)
+    {
+        merge = ir_function_create_block(m_context, func->m_ir_func, func->makeLabel("endif"));
+        if (!merge)
+            return false;
+        /* add jumps ot the merge block */
+        if (ontrue && !ontrue_endblock->m_final && !ir_block_create_jump(ontrue_endblock, m_context, merge))
+            return false;
+        if (onfalse && !onfalse_endblock->m_final && !ir_block_create_jump(onfalse_endblock, m_context, merge))
+            return false;
+
+        /* Now enter the merge block */
+        func->m_curblock = merge;
+    }
+
+    /* we create the if here, that way all blocks are ordered :)
+     */
+    if (!ir_block_create_if(cond, m_context, condval,
+                            (ontrue  ? ontrue  : merge),
+                            (onfalse ? onfalse : merge)))
+    {
+        return false;
+    }
+
+    return true;
+}
+
+bool ast_ternary::codegen(ast_function *func, bool lvalue, ir_value **out)
+{
+    ir_value *condval;
+    ir_value *trueval, *falseval;
+    ir_instr *phi;
+
+    ir_block *cond = func->m_curblock;
+    ir_block *cond_out = nullptr;
+    ir_block *ontrue, *ontrue_out = nullptr;
+    ir_block *onfalse, *onfalse_out = nullptr;
+    ir_block *merge;
+    int folded = 0;
+
+    /* Ternary can never create an lvalue... */
+    if (lvalue)
+        return false;
+
+    /* In theory it shouldn't be possible to pass through a node twice, but
+     * in case we add any kind of optimization pass for the AST itself, it
+     * may still happen, thus we remember a created ir_value and simply return one
+     * if it already exists.
+     */
+    if (m_outr) {
+        *out = m_outr;
+        return true;
+    }
+
+    /* In the following, contraty to ast_ifthen, we assume both paths exist. */
+
+    /* generate the condition */
+    func->m_curblock = cond;
+    if (!m_cond->codegen(func, false, &condval))
+        return false;
+    cond_out = func->m_curblock;
+
+    /* try constant folding away the condition */
+    if ((folded = fold::cond_ternary(condval, func, this)) != -1)
+        return folded;
+
+    /* create on-true block */
+    ontrue = ir_function_create_block(m_context, func->m_ir_func, func->makeLabel("tern_T"));
+    if (!ontrue)
+        return false;
+    else
+    {
+        /* enter the block */
+        func->m_curblock = ontrue;
+
+        /* generate */
+        if (!m_on_true->codegen(func, false, &trueval))
+            return false;
+
+        ontrue_out = func->m_curblock;
+    }
+
+    /* create on-false block */
+    onfalse = ir_function_create_block(m_context, func->m_ir_func, func->makeLabel("tern_F"));
+    if (!onfalse)
+        return false;
+    else
+    {
+        /* enter the block */
+        func->m_curblock = onfalse;
+
+        /* generate */
+        if (!m_on_false->codegen(func, false, &falseval))
+            return false;
+
+        onfalse_out = func->m_curblock;
+    }
+
+    /* create merge block */
+    merge = ir_function_create_block(m_context, func->m_ir_func, func->makeLabel("tern_out"));
+    if (!merge)
+        return false;
+    /* jump to merge block */
+    if (!ir_block_create_jump(ontrue_out, m_context, merge))
+        return false;
+    if (!ir_block_create_jump(onfalse_out, m_context, merge))
+        return false;
+
+    /* create if instruction */
+    if (!ir_block_create_if(cond_out, m_context, condval, ontrue, onfalse))
+        return false;
+
+    /* Now enter the merge block */
+    func->m_curblock = merge;
+
+    /* Here, now, we need a PHI node
+     * but first some sanity checking...
+     */
+    if (trueval->m_vtype != falseval->m_vtype && trueval->m_vtype != TYPE_NIL && falseval->m_vtype != TYPE_NIL) {
+        /* error("ternary with different types on the two sides"); */
+        compile_error(m_context, "internal error: ternary operand types invalid");
+        return false;
+    }
+
+    /* create PHI */
+    phi = ir_block_create_phi(merge, m_context, func->makeLabel("phi"), m_vtype);
+    if (!phi) {
+        compile_error(m_context, "internal error: failed to generate phi node");
+        return false;
+    }
+    ir_phi_add(phi, ontrue_out,  trueval);
+    ir_phi_add(phi, onfalse_out, falseval);
+
+    m_outr = ir_phi_value(phi);
+    *out = m_outr;
+
+    codegen_output_type(this, *out);
+
+    return true;
+}
+
+bool ast_loop::codegen(ast_function *func, bool lvalue, ir_value **out)
+{
+    ir_value *dummy      = nullptr;
+    ir_value *precond    = nullptr;
+    ir_value *postcond   = nullptr;
+
+    /* Since we insert some jumps "late" so we have blocks
+     * ordered "nicely", we need to keep track of the actual end-blocks
+     * of expressions to add the jumps to.
+     */
+    ir_block *bbody      = nullptr, *end_bbody      = nullptr;
+    ir_block *bprecond   = nullptr, *end_bprecond   = nullptr;
+    ir_block *bpostcond  = nullptr, *end_bpostcond  = nullptr;
+    ir_block *bincrement = nullptr, *end_bincrement = nullptr;
+    ir_block *bout       = nullptr, *bin            = nullptr;
+
+    /* let's at least move the outgoing block to the end */
+    size_t    bout_id;
+
+    /* 'break' and 'continue' need to be able to find the right blocks */
+    ir_block *bcontinue     = nullptr;
+    ir_block *bbreak        = nullptr;
+
+    ir_block *tmpblock      = nullptr;
+
+    (void)lvalue;
+    (void)out;
+
+    if (m_outr) {
+        compile_error(m_context, "internal error: ast_loop cannot be reused, it bears no result!");
+        return false;
+    }
+    m_outr = (ir_value*)1;
+
+    /* NOTE:
+     * Should we ever need some kind of block ordering, better make this function
+     * move blocks around than write a block ordering algorithm later... after all
+     * the ast and ir should work together, not against each other.
+     */
+
+    /* initexpr doesn't get its own block, it's pointless, it could create more blocks
+     * anyway if for example it contains a ternary.
+     */
+    if (m_initexpr)
+    {
+        if (!m_initexpr->codegen(func, false, &dummy))
+            return false;
+    }
+
+    /* Store the block from which we enter this chaos */
+    bin = func->m_curblock;
+
+    /* The pre-loop condition needs its own block since we
+     * need to be able to jump to the start of that expression.
+     */
+    if (m_precond)
+    {
+        bprecond = ir_function_create_block(m_context, func->m_ir_func, func->makeLabel("pre_loop_cond"));
+        if (!bprecond)
+            return false;
+
+        /* the pre-loop-condition the least important place to 'continue' at */
+        bcontinue = bprecond;
+
+        /* enter */
+        func->m_curblock = bprecond;
+
+        /* generate */
+        if (!m_precond->codegen(func, false, &precond))
+            return false;
+
+        end_bprecond = func->m_curblock;
+    } else {
+        bprecond = end_bprecond = nullptr;
+    }
+
+    /* Now the next blocks won't be ordered nicely, but we need to
+     * generate them this early for 'break' and 'continue'.
+     */
+    if (m_increment) {
+        bincrement = ir_function_create_block(m_context, func->m_ir_func, func->makeLabel("loop_increment"));
+        if (!bincrement)
+            return false;
+        bcontinue = bincrement; /* increment comes before the pre-loop-condition */
+    } else {
+        bincrement = end_bincrement = nullptr;
+    }
+
+    if (m_postcond) {
+        bpostcond = ir_function_create_block(m_context, func->m_ir_func, func->makeLabel("post_loop_cond"));
+        if (!bpostcond)
+            return false;
+        bcontinue = bpostcond; /* postcond comes before the increment */
+    } else {
+        bpostcond = end_bpostcond = nullptr;
+    }
+
+    bout_id = func->m_ir_func->m_blocks.size();
+    bout = ir_function_create_block(m_context, func->m_ir_func, func->makeLabel("after_loop"));
+    if (!bout)
+        return false;
+    bbreak = bout;
+
+    /* The loop body... */
+    /* if (m_body) */
+    {
+        bbody = ir_function_create_block(m_context, func->m_ir_func, func->makeLabel("loop_body"));
+        if (!bbody)
+            return false;
+
+        /* enter */
+        func->m_curblock = bbody;
+
+        func->m_breakblocks.push_back(bbreak);
+        if (bcontinue)
+            func->m_continueblocks.push_back(bcontinue);
+        else
+            func->m_continueblocks.push_back(bbody);
+
+        /* generate */
+        if (m_body) {
+            if (!m_body->codegen(func, false, &dummy))
+                return false;
+        }
+
+        end_bbody = func->m_curblock;
+        func->m_breakblocks.pop_back();
+        func->m_continueblocks.pop_back();
+    }
+
+    /* post-loop-condition */
+    if (m_postcond)
+    {
+        /* enter */
+        func->m_curblock = bpostcond;
+
+        /* generate */
+        if (!m_postcond->codegen(func, false, &postcond))
+            return false;
+
+        end_bpostcond = func->m_curblock;
+    }
+
+    /* The incrementor */
+    if (m_increment)
+    {
+        /* enter */
+        func->m_curblock = bincrement;
+
+        /* generate */
+        if (!m_increment->codegen(func, false, &dummy))
+            return false;
+
+        end_bincrement = func->m_curblock;
+    }
+
+    /* In any case now, we continue from the outgoing block */
+    func->m_curblock = bout;
+
+    /* Now all blocks are in place */
+    /* From 'bin' we jump to whatever comes first */
+    if      (bprecond)   tmpblock = bprecond;
+    else                 tmpblock = bbody;    /* can never be null */
+
+    /* DEAD CODE
+    else if (bpostcond)  tmpblock = bpostcond;
+    else                 tmpblock = bout;
+    */
+
+    if (!ir_block_create_jump(bin, m_context, tmpblock))
+        return false;
+
+    /* From precond */
+    if (bprecond)
+    {
+        ir_block *ontrue, *onfalse;
+        ontrue = bbody; /* can never be null */
+
+        /* all of this is dead code
+        else if (bincrement) ontrue = bincrement;
+        else                 ontrue = bpostcond;
+        */
+
+        onfalse = bout;
+        if (m_pre_not) {
+            tmpblock = ontrue;
+            ontrue   = onfalse;
+            onfalse  = tmpblock;
+        }
+        if (!ir_block_create_if(end_bprecond, m_context, precond, ontrue, onfalse))
+            return false;
+    }
+
+    /* from body */
+    if (bbody)
+    {
+        if      (bincrement) tmpblock = bincrement;
+        else if (bpostcond)  tmpblock = bpostcond;
+        else if (bprecond)   tmpblock = bprecond;
+        else                 tmpblock = bbody;
+        if (!end_bbody->m_final && !ir_block_create_jump(end_bbody, m_context, tmpblock))
+            return false;
+    }
+
+    /* from increment */
+    if (bincrement)
+    {
+        if      (bpostcond)  tmpblock = bpostcond;
+        else if (bprecond)   tmpblock = bprecond;
+        else if (bbody)      tmpblock = bbody;
+        else                 tmpblock = bout;
+        if (!ir_block_create_jump(end_bincrement, m_context, tmpblock))
+            return false;
+    }
+
+    /* from postcond */
+    if (bpostcond)
+    {
+        ir_block *ontrue, *onfalse;
+        if      (bprecond)   ontrue = bprecond;
+        else                 ontrue = bbody; /* can never be null */
+
+        /* all of this is dead code
+        else if (bincrement) ontrue = bincrement;
+        else                 ontrue = bpostcond;
+        */
+
+        onfalse = bout;
+        if (m_post_not) {
+            tmpblock = ontrue;
+            ontrue   = onfalse;
+            onfalse  = tmpblock;
+        }
+        if (!ir_block_create_if(end_bpostcond, m_context, postcond, ontrue, onfalse))
+            return false;
+    }
+
+    /* Move 'bout' to the end */
+    algo::shiftback(func->m_ir_func->m_blocks.begin() + bout_id,
+                    func->m_ir_func->m_blocks.end());
+    // FIXME::DELME::
+    //func->m_ir_func->m_blocks[bout_id].release(); // it's a vector<std::unique_ptr<>>
+    //func->m_ir_func->m_blocks.erase(func->m_ir_func->m_blocks.begin() + bout_id);
+    //func->m_ir_func->m_blocks.emplace_back(bout);
+
+    return true;
+}
+
+bool ast_breakcont::codegen(ast_function *func, bool lvalue, ir_value **out)
+{
+    ir_block *target;
+
+    *out = nullptr;
+
+    if (lvalue) {
+        compile_error(m_context, "break/continue expression is not an l-value");
+        return false;
+    }
+
+    if (m_outr) {
+        compile_error(m_context, "internal error: ast_breakcont cannot be reused!");
+        return false;
+    }
+    m_outr = (ir_value*)1;
+
+    if (m_is_continue)
+        target = func->m_continueblocks[func->m_continueblocks.size()-1-m_levels];
+    else
+        target = func->m_breakblocks[func->m_breakblocks.size()-1-m_levels];
+
+    if (!target) {
+        compile_error(m_context, "%s is lacking a target block", (m_is_continue ? "continue" : "break"));
+        return false;
+    }
+
+    if (!ir_block_create_jump(func->m_curblock, m_context, target))
+        return false;
+    return true;
+}
+
+bool ast_switch::codegen(ast_function *func, bool lvalue, ir_value **out)
+{
+    ast_switch_case *def_case     = nullptr;
+    ir_block        *def_bfall    = nullptr;
+    ir_block        *def_bfall_to = nullptr;
+    bool set_def_bfall_to = false;
+
+    ir_value *dummy     = nullptr;
+    ir_value *irop      = nullptr;
+    ir_block *bout      = nullptr;
+    ir_block *bfall     = nullptr;
+    size_t    bout_id;
+
+    char      typestr[1024];
+    uint16_t  cmpinstr;
+
+    if (lvalue) {
+        compile_error(m_context, "switch expression is not an l-value");
+        return false;
+    }
+
+    if (m_outr) {
+        compile_error(m_context, "internal error: ast_switch cannot be reused!");
+        return false;
+    }
+    m_outr = (ir_value*)1;
+
+    (void)lvalue;
+    (void)out;
+
+    if (!m_operand->codegen(func, false, &irop))
+        return false;
+
+    if (m_cases.empty())
+        return true;
+
+    cmpinstr = type_eq_instr[irop->m_vtype];
+    if (cmpinstr >= VINSTR_END) {
+        ast_type_to_string(m_operand, typestr, sizeof(typestr));
+        compile_error(m_context, "invalid type to perform a switch on: %s", typestr);
+        return false;
+    }
+
+    bout_id = func->m_ir_func->m_blocks.size();
+    bout = ir_function_create_block(m_context, func->m_ir_func, func->makeLabel("after_switch"));
+    if (!bout)
+        return false;
+
+    /* setup the break block */
+    func->m_breakblocks.push_back(bout);
+
+    /* Now create all cases */
+    for (auto &it : m_cases) {
+        ir_value *cond, *val;
+        ir_block *bcase, *bnot;
+        size_t bnot_id;
+
+        ast_switch_case *swcase = &it;
+
+        if (swcase->m_value) {
+            /* A regular case */
+            /* generate the condition operand */
+            if (!swcase->m_value->codegen(func, false, &val))
+                return false;
+            /* generate the condition */
+            cond = ir_block_create_binop(func->m_curblock, m_context, func->makeLabel("switch_eq"), cmpinstr, irop, val);
+            if (!cond)
+                return false;
+
+            bcase = ir_function_create_block(m_context, func->m_ir_func, func->makeLabel("case"));
+            bnot_id = func->m_ir_func->m_blocks.size();
+            bnot = ir_function_create_block(m_context, func->m_ir_func, func->makeLabel("not_case"));
+            if (!bcase || !bnot)
+                return false;
+            if (set_def_bfall_to) {
+                set_def_bfall_to = false;
+                def_bfall_to = bcase;
+            }
+            if (!ir_block_create_if(func->m_curblock, m_context, cond, bcase, bnot))
+                return false;
+
+            /* Make the previous case-end fall through */
+            if (bfall && !bfall->m_final) {
+                if (!ir_block_create_jump(bfall, m_context, bcase))
+                    return false;
+            }
+
+            /* enter the case */
+            func->m_curblock = bcase;
+            if (!swcase->m_code->codegen(func, false, &dummy))
+                return false;
+
+            /* remember this block to fall through from */
+            bfall = func->m_curblock;
+
+            /* enter the else and move it down */
+            func->m_curblock = bnot;
+            algo::shiftback(func->m_ir_func->m_blocks.begin() + bnot_id,
+                            func->m_ir_func->m_blocks.end());
+            // FIXME::DELME::
+            //func->m_ir_func->m_blocks[bnot_id].release();
+            //func->m_ir_func->m_blocks.erase(func->m_ir_func->m_blocks.begin() + bnot_id);
+            //func->m_ir_func->m_blocks.emplace_back(bnot);
+        } else {
+            /* The default case */
+            /* Remember where to fall through from: */
+            def_bfall = bfall;
+            bfall     = nullptr;
+            /* remember which case it was */
+            def_case  = swcase;
+            /* And the next case will be remembered */
+            set_def_bfall_to = true;
+        }
+    }
+
+    /* Jump from the last bnot to bout */
+    if (bfall && !bfall->m_final && !ir_block_create_jump(bfall, m_context, bout)) {
+        /*
+        astwarning(bfall->m_context, WARN_???, "missing break after last case");
+        */
+        return false;
+    }
+
+    /* If there was a default case, put it down here */
+    if (def_case) {
+        ir_block *bcase;
+
+        /* No need to create an extra block */
+        bcase = func->m_curblock;
+
+        /* Insert the fallthrough jump */
+        if (def_bfall && !def_bfall->m_final) {
+            if (!ir_block_create_jump(def_bfall, m_context, bcase))
+                return false;
+        }
+
+        /* Now generate the default code */
+        if (!def_case->m_code->codegen(func, false, &dummy))
+            return false;
+
+        /* see if we need to fall through */
+        if (def_bfall_to && !func->m_curblock->m_final)
+        {
+            if (!ir_block_create_jump(func->m_curblock, m_context, def_bfall_to))
+                return false;
+        }
+    }
+
+    /* Jump from the last bnot to bout */
+    if (!func->m_curblock->m_final && !ir_block_create_jump(func->m_curblock, m_context, bout))
+        return false;
+    /* enter the outgoing block */
+    func->m_curblock = bout;
+
+    /* restore the break block */
+    func->m_breakblocks.pop_back();
+
+    /* Move 'bout' to the end, it's nicer */
+    algo::shiftback(func->m_ir_func->m_blocks.begin() + bout_id,
+                    func->m_ir_func->m_blocks.end());
+    // FIXME::DELME::
+    //func->m_ir_func->m_blocks[bout_id].release();
+    //func->m_ir_func->m_blocks.erase(func->m_ir_func->m_blocks.begin() + bout_id);
+    //func->m_ir_func->m_blocks.emplace_back(bout);
+
+    return true;
+}
+
+bool ast_label::codegen(ast_function *func, bool lvalue, ir_value **out)
+{
+    ir_value *dummy;
+
+    if (m_undefined) {
+        compile_error(m_context, "internal error: ast_label never defined");
+        return false;
+    }
+
+    *out = nullptr;
+    if (lvalue) {
+        compile_error(m_context, "internal error: ast_label cannot be an lvalue");
+        return false;
+    }
+
+    /* simply create a new block and jump to it */
+    m_irblock = ir_function_create_block(m_context, func->m_ir_func, m_name.c_str());
+    if (!m_irblock) {
+        compile_error(m_context, "failed to allocate label block `%s`", m_name);
+        return false;
+    }
+    if (!func->m_curblock->m_final) {
+        if (!ir_block_create_jump(func->m_curblock, m_context, m_irblock))
+            return false;
+    }
+
+    /* enter the new block */
+    func->m_curblock = m_irblock;
+
+    /* Generate all the leftover gotos */
+    for (auto &it : m_gotos) {
+        if (!it->codegen(func, false, &dummy))
+            return false;
+    }
+
+    return true;
+}
+
+bool ast_goto::codegen(ast_function *func, bool lvalue, ir_value **out)
+{
+    *out = nullptr;
+    if (lvalue) {
+        compile_error(m_context, "internal error: ast_goto cannot be an lvalue");
+        return false;
+    }
+
+    if (m_target->m_irblock) {
+        if (m_irblock_from) {
+            /* we already tried once, this is the callback */
+            m_irblock_from->m_final = false;
+            if (!ir_block_create_goto(m_irblock_from, m_context, m_target->m_irblock)) {
+                compile_error(m_context, "failed to generate goto to `%s`", m_name);
+                return false;
+            }
+        }
+        else
+        {
+            if (!ir_block_create_goto(func->m_curblock, m_context, m_target->m_irblock)) {
+                compile_error(m_context, "failed to generate goto to `%s`", m_name);
+                return false;
+            }
+        }
+    }
+    else
+    {
+        /* the target has not yet been created...
+         * close this block in a sneaky way:
+         */
+        func->m_curblock->m_final = true;
+        m_irblock_from = func->m_curblock;
+        m_target->registerGoto(this);
+    }
+
+    return true;
+}
+
+bool ast_state::codegen(ast_function *func, bool lvalue, ir_value **out)
+{
+    ir_value *frameval, *thinkval;
+
+    if (lvalue) {
+        compile_error(m_context, "not an l-value (state operation)");
+        return false;
+    }
+    if (m_outr) {
+        compile_error(m_context, "internal error: ast_state cannot be reused!");
+        return false;
+    }
+    *out = nullptr;
+
+    if (!m_framenum->codegen(func, false, &frameval))
+        return false;
+    if (!frameval)
+        return false;
+
+    if (!m_nextthink->codegen(func, false, &thinkval))
+        return false;
+    if (!frameval)
+        return false;
+
+    if (!ir_block_create_state_op(func->m_curblock, m_context, frameval, thinkval)) {
+        compile_error(m_context, "failed to create STATE instruction");
+        return false;
+    }
+
+    m_outr = (ir_value*)1;
+    return true;
+}
+
+bool ast_call::codegen(ast_function *func, bool lvalue, ir_value **out)
+{
+    std::vector<ir_value*> params;
+    ir_instr *callinstr;
+
+    ir_value *funval = nullptr;
+
+    /* return values are never lvalues */
+    if (lvalue) {
+        compile_error(m_context, "not an l-value (function call)");
+        return false;
+    }
+
+    if (m_outr) {
+        *out = m_outr;
+        return true;
+    }
+
+    if (!m_func->codegen(func, false, &funval))
+        return false;
+    if (!funval)
+        return false;
+
+    /* parameters */
+    for (auto &it : m_params) {
+        ir_value *param;
+        if (!it->codegen(func, false, &param))
+            return false;
+        if (!param)
+            return false;
+        params.push_back(param);
+    }
+
+    /* varargs counter */
+    if (m_va_count) {
+        ir_value   *va_count;
+        ir_builder *builder = func->m_curblock->m_owner->m_owner;
+        if (!m_va_count->codegen(func, false, &va_count))
+            return false;
+        if (!ir_block_create_store_op(func->m_curblock, m_context, INSTR_STORE_F,
+                                      builder->get_va_count(), va_count))
+        {
+            return false;
+        }
+    }
+
+    callinstr = ir_block_create_call(func->m_curblock, m_context,
+                                     func->makeLabel("call"),
+                                     funval, !!(m_func->m_flags & AST_FLAG_NORETURN));
+    if (!callinstr)
+        return false;
+
+    for (auto &it : params)
+        ir_call_param(callinstr, it);
+
+    *out = ir_call_value(callinstr);
+    m_outr = *out;
+
+    codegen_output_type(this, *out);
+
+    return true;
+}
diff --git a/ast.h b/ast.h
index 5b556e1be52912e44a554c1b4e99a4d059b3097b..b305dac393f82001e018cc2a0d53d7c64394ffc4 100644 (file)
--- a/ast.h
+++ b/ast.h
@@ -1,28 +1,6 @@
-/*
- * Copyright (C) 2012, 2013, 2014, 2015
- *     Wolfgang Bumiller
- *     Dale Weiler
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
- * this software and associated documentation files (the "Software"), to deal in
- * the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is furnished to do
- * so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
 #ifndef GMQCC_AST_HDR
 #define GMQCC_AST_HDR
+#include <vector>
 #include "ir.h"
 
 typedef uint16_t ast_flag_t;
@@ -31,30 +9,29 @@ typedef uint16_t ast_flag_t;
  * "main" ast node types for now.
  */
 
-typedef struct ast_node_common       ast_node;
-typedef struct ast_expression_common ast_expression;
-
-typedef struct ast_value_s       ast_value;
-typedef struct ast_function_s    ast_function;
-typedef struct ast_block_s       ast_block;
-typedef struct ast_binary_s      ast_binary;
-typedef struct ast_store_s       ast_store;
-typedef struct ast_binstore_s    ast_binstore;
-typedef struct ast_entfield_s    ast_entfield;
-typedef struct ast_ifthen_s      ast_ifthen;
-typedef struct ast_ternary_s     ast_ternary;
-typedef struct ast_loop_s        ast_loop;
-typedef struct ast_call_s        ast_call;
-typedef struct ast_unary_s       ast_unary;
-typedef struct ast_return_s      ast_return;
-typedef struct ast_member_s      ast_member;
-typedef struct ast_array_index_s ast_array_index;
-typedef struct ast_breakcont_s   ast_breakcont;
-typedef struct ast_switch_s      ast_switch;
-typedef struct ast_label_s       ast_label;
-typedef struct ast_goto_s        ast_goto;
-typedef struct ast_argpipe_s     ast_argpipe;
-typedef struct ast_state_s       ast_state;
+struct ast_node;
+struct ast_expression;
+struct ast_value;
+struct ast_function;
+struct ast_block;
+struct ast_binary;
+struct ast_store;
+struct ast_binstore;
+struct ast_entfield;
+struct ast_ifthen;
+struct ast_ternary;
+struct ast_loop;
+struct ast_call;
+struct ast_unary;
+struct ast_return;
+struct ast_member;
+struct ast_array_index;
+struct ast_breakcont;
+struct ast_switch;
+struct ast_label;
+struct ast_goto;
+struct ast_argpipe;
+struct ast_state;
 
 enum {
     AST_FLAG_VARIADIC       = 1 << 0,
@@ -116,43 +93,36 @@ enum {
     TYPE_ast_state        /* 22 */
 };
 
-#define ast_istype(x, t) ( ((ast_node*)x)->nodetype == (TYPE_##t) )
-#define ast_ctx(node) (((ast_node*)(node))->context)
-#define ast_side_effects(node) (((ast_node*)(node))->side_effects)
+#define ast_istype(x, t) ( (x)->m_node_type == (TYPE_##t) )
 
-/* Node interface with common components
- */
-typedef void ast_node_delete(ast_node*);
-struct ast_node_common
+struct ast_node
 {
-    lex_ctx_t          context;
+    ast_node() = delete;
+    ast_node(lex_ctx_t, int nodetype);
+    virtual ~ast_node();
+
+    lex_ctx_t m_context;
     /* I don't feel comfortable using keywords like 'delete' as names... */
-    ast_node_delete *destroy;
-    int              nodetype;
-    /* keep: if a node contains this node, 'keep'
+    int              m_node_type;
+    /* keep_node: if a node contains this node, 'keep_node'
      * prevents its dtor from destroying this node as well.
      */
-    bool             keep;
-    bool             side_effects;
+    bool             m_keep_node;
+    bool             m_side_effects;
+
+    void propagateSideEffects(const ast_node *other);
 };
 
-#define ast_delete(x) (*( ((ast_node*)(x))->destroy ))((ast_node*)(x))
-#define ast_unref(x) do                \
-{                                      \
-    if (! (((ast_node*)(x))->keep) ) { \
-        ast_delete(x);                 \
-    }                                  \
+#define ast_unref(x) do        \
+{                              \
+    if (! (x)->m_keep_node ) { \
+        delete (x);            \
+    }                          \
 } while(0)
 
-/* Expression interface
- *
- * Any expression or block returns an ir_value, and needs
- * to know the current function.
- */
-typedef bool ast_expression_codegen(ast_expression*,
-                                    ast_function*,
-                                    bool lvalue,
-                                    ir_value**);
+enum class ast_copy_type_t { value };
+static const ast_copy_type_t ast_copy_type = ast_copy_type_t::value;
+
 /* TODO: the codegen function should take an output-type parameter
  * indicating whether a variable, type, label etc. is expected, and
  * an environment!
@@ -163,27 +133,42 @@ typedef bool ast_expression_codegen(ast_expression*,
  * type `expression`, so the ast_ident's codegen would search for
  * variables through the environment (or functions, constants...).
  */
-struct ast_expression_common
-{
-    ast_node                node;
-    ast_expression_codegen *codegen;
-    int                     vtype;
-    ast_expression         *next;
+struct ast_expression : ast_node {
+    ast_expression() = delete;
+    ast_expression(lex_ctx_t ctx, int nodetype, qc_type vtype);
+    ast_expression(lex_ctx_t ctx, int nodetype);
+    ~ast_expression();
+
+    ast_expression(ast_copy_type_t, const ast_expression&);
+    ast_expression(ast_copy_type_t, lex_ctx_t ctx, const ast_expression&);
+    ast_expression(ast_copy_type_t, int nodetype, const ast_expression&);
+    ast_expression(ast_copy_type_t, int nodetype, lex_ctx_t ctx, const ast_expression&);
+
+    static ast_expression *shallowType(lex_ctx_t ctx, qc_type vtype);
+
+    bool compareType(const ast_expression &other) const;
+    void adoptType(const ast_expression &other);
+
+    qc_type                 m_vtype = TYPE_VOID;
+    ast_expression         *m_next = nullptr;
     /* arrays get a member-count */
-    size_t                  count;
-    ast_value*             *params;
-    ast_flag_t              flags;
+    size_t                  m_count = 0;
+    std::vector<std::unique_ptr<ast_value>> m_type_params;
+
+    ast_flag_t              m_flags = 0;
     /* void foo(string...) gets varparam set as a restriction
      * for variadic parameters
      */
-    ast_expression         *varparam;
+    ast_expression         *m_varparam = nullptr;
     /* The codegen functions should store their output values
      * so we can call it multiple times without re-evaluating.
      * Store lvalue and rvalue seperately though. So that
      * ast_entfield for example can generate both if required.
      */
-    ir_value               *outl;
-    ir_value               *outr;
+    ir_value               *m_outl = nullptr;
+    ir_value               *m_outr = nullptr;
+
+    virtual bool codegen(ast_function *current, bool lvalue, ir_value **out);
 };
 
 /* Value
@@ -193,7 +178,7 @@ struct ast_expression_common
  * typedef float foo;
  * is like creating a 'float foo', foo serving as the type's name.
  */
-typedef union {
+union basic_value_t {
     qcfloat_t     vfloat;
     int           vint;
     vec3_t        vvec;
@@ -201,129 +186,133 @@ typedef union {
     int           ventity;
     ast_function *vfunc;
     ast_value    *vfield;
-} basic_value_t;
+};
 
-struct ast_value_s
+struct ast_value : ast_expression
 {
-    ast_expression        expression;
+    ast_value() = delete;
+    ast_value(lex_ctx_t ctx, const std::string &name, qc_type qctype);
+    ~ast_value();
+
+    ast_value(ast_copy_type_t, const ast_expression&, const std::string&);
+    ast_value(ast_copy_type_t, const ast_value&);
+    ast_value(ast_copy_type_t, const ast_value&, const std::string&);
+
+    bool codegen(ast_function *current, bool lvalue, ir_value **out) override;
 
-    const char *name;
-    const char *desc;
+    void addParam(ast_value*);
 
-    const char *argcounter;
+    bool generateGlobal(ir_builder*, bool isfield);
+    bool generateLocal(ir_function*, bool param);
+    bool generateAccessors(ir_builder*);
 
-    int  cvq;     /* const/var qualifier */
-    bool isfield; /* this declares a field */
-    bool isimm;   /* an immediate, not just const */
-    bool hasvalue;
-    bool inexact; /* inexact coming from folded expression */
-    basic_value_t constval;
+    std::string m_name;
+    std::string m_desc;
+
+    const char *m_argcounter = nullptr;
+
+    int m_cvq = CV_NONE;     /* const/var qualifier */
+    bool m_isfield = false; /* this declares a field */
+    bool m_isimm = false;   /* an immediate, not just const */
+    bool m_hasvalue = false;
+    bool m_inexact = false; /* inexact coming from folded expression */
+    basic_value_t m_constval;
     /* for TYPE_ARRAY we have an optional vector
      * of constants when an initializer list
      * was provided.
      */
-    basic_value_t *initlist;
+    std::vector<basic_value_t> m_initlist;
 
     /* usecount for the parser */
-    size_t uses;
+    size_t m_uses = 0;
 
-    ir_value *ir_v;
-    ir_value **ir_values;
-    size_t   ir_value_count;
+    ir_value *m_ir_v = nullptr;
+    std::vector<ir_value*> m_ir_values;
+    size_t m_ir_value_count = 0;
 
     /* ONLY for arrays in progs version up to 6 */
-    ast_value *setter;
-    ast_value *getter;
+    ast_value *m_setter = nullptr;
+    ast_value *m_getter = nullptr;
 
+    bool m_intrinsic = false; /* true if associated with intrinsic */
 
-    bool      intrinsic; /* true if associated with intrinsic */
+private:
+    bool generateGlobalFunction(ir_builder*);
+    bool generateGlobalField(ir_builder*);
+    ir_value *prepareGlobalArray(ir_builder*);
+    bool setGlobalArray();
+    bool checkArray(const ast_value &array) const;
 };
 
-ast_value* ast_value_new(lex_ctx_t ctx, const char *name, int qctype);
-ast_value* ast_value_copy(const ast_value *self);
-/* This will NOT delete an underlying ast_function */
-void ast_value_delete(ast_value*);
-
-bool ast_value_set_name(ast_value*, const char *name);
-
-/*
-bool ast_value_codegen(ast_value*, ast_function*, bool lvalue, ir_value**);
-bool ast_local_codegen(ast_value *self, ir_function *func, bool isparam);
-*/
-
-bool ast_global_codegen(ast_value *self, ir_builder *ir, bool isfield);
+void ast_type_to_string(const ast_expression *e, char *buf, size_t bufsize);
 
-void ast_value_params_add(ast_value*, ast_value*);
-
-bool ast_compare_type(ast_expression *a, ast_expression *b);
-ast_expression* ast_type_copy(lex_ctx_t ctx, const ast_expression *ex);
-#define ast_type_adopt(a, b) ast_type_adopt_impl((ast_expression*)(a), (ast_expression*)(b))
-void ast_type_adopt_impl(ast_expression *self, const ast_expression *other);
-void ast_type_to_string(ast_expression *e, char *buf, size_t bufsize);
-
-typedef enum ast_binary_ref_s {
+enum ast_binary_ref {
     AST_REF_NONE  = 0,
     AST_REF_LEFT  = 1 << 1,
     AST_REF_RIGHT = 1 << 2,
     AST_REF_ALL   = (AST_REF_LEFT | AST_REF_RIGHT)
-} ast_binary_ref;
+};
 
 
 /* Binary
  *
  * A value-returning binary expression.
  */
-struct ast_binary_s
+struct ast_binary : ast_expression
 {
-    ast_expression        expression;
+    ast_binary() = delete;
+    ast_binary(lex_ctx_t ctx, int op, ast_expression *l, ast_expression *r);
+    ~ast_binary();
 
-    int             op;
-    ast_expression *left;
-    ast_expression *right;
-    ast_binary_ref  refs;
-    bool            right_first;
+    bool codegen(ast_function *current, bool lvalue, ir_value **out) override;
+
+    int m_op;
+    ast_expression *m_left;
+    ast_expression *m_right;
+    ast_binary_ref m_refs;
+    bool m_right_first;
 };
-ast_binary* ast_binary_new(lex_ctx_t    ctx,
-                           int        op,
-                           ast_expression *left,
-                           ast_expression *right);
 
 /* Binstore
  *
  * An assignment including a binary expression with the source as left operand.
  * Eg. a += b; is a binstore { INSTR_STORE, INSTR_ADD, a, b }
  */
-struct ast_binstore_s
+struct ast_binstore : ast_expression
 {
-    ast_expression        expression;
+    ast_binstore() = delete;
+    ast_binstore(lex_ctx_t ctx, int storeop, int mathop, ast_expression *l, ast_expression *r);
+    ~ast_binstore();
+
+    bool codegen(ast_function *current, bool lvalue, ir_value **out) override;
 
-    int             opstore;
-    int             opbin;
-    ast_expression *dest;
-    ast_expression *source;
+    int m_opstore;
+    int m_opbin;
+    ast_expression *m_dest;
+    ast_expression *m_source;
     /* for &~= which uses the destination in a binary in source we can use this */
-    bool            keep_dest;
+    bool m_keep_dest;
 };
-ast_binstore* ast_binstore_new(lex_ctx_t    ctx,
-                               int        storeop,
-                               int        op,
-                               ast_expression *left,
-                               ast_expression *right);
 
 /* Unary
  *
  * Regular unary expressions: not,neg
  */
-struct ast_unary_s
+struct ast_unary : ast_expression
 {
-    ast_expression        expression;
+    ast_unary() = delete;
+    ~ast_unary();
 
-    int             op;
-    ast_expression *operand;
+    static ast_unary* make(lex_ctx_t ctx, int op, ast_expression *expr);
+
+    bool codegen(ast_function *current, bool lvalue, ir_value **out) override;
+
+    int m_op;
+    ast_expression *m_operand;
+
+private:
+    ast_unary(lex_ctx_t ctx, int op, ast_expression *expr);
 };
-ast_unary* ast_unary_new(lex_ctx_t    ctx,
-                         int        op,
-                         ast_expression *expr);
 
 /* Return
  *
@@ -331,13 +320,16 @@ ast_unary* ast_unary_new(lex_ctx_t    ctx,
  * will refuse to create further instructions.
  * This should be honored by the parser.
  */
-struct ast_return_s
+struct ast_return : ast_expression
 {
-    ast_expression        expression;
-    ast_expression *operand;
+    ast_return() = delete;
+    ast_return(lex_ctx_t ctx, ast_expression *expr);
+    ~ast_return();
+
+    bool codegen(ast_function *current, bool lvalue, ir_value **out) override;
+
+    ast_expression *m_operand;
 };
-ast_return* ast_return_new(lex_ctx_t    ctx,
-                           ast_expression *expr);
 
 /* Entity-field
  *
@@ -352,34 +344,42 @@ ast_return* ast_return_new(lex_ctx_t    ctx,
  * For this we will have to extend the codegen() functions with
  * a flag saying whether or not we need an L or an R-value.
  */
-struct ast_entfield_s
+struct ast_entfield : ast_expression
 {
-    ast_expression        expression;
-    /* The entity can come from an expression of course. */
-    ast_expression *entity;
-    /* As can the field, it just must result in a value of TYPE_FIELD */
-    ast_expression *field;
+    ast_entfield() = delete;
+    ast_entfield(lex_ctx_t ctx, ast_expression *entity, ast_expression *field);
+    ast_entfield(lex_ctx_t ctx, ast_expression *entity, ast_expression *field, const ast_expression *outtype);
+    ~ast_entfield();
+
+    bool codegen(ast_function *current, bool lvalue, ir_value **out) override;
+
+    // The entity can come from an expression of course.
+    ast_expression *m_entity;
+    // As can the field, it just must result in a value of TYPE_FIELD
+    ast_expression *m_field;
 };
-ast_entfield* ast_entfield_new(lex_ctx_t ctx, ast_expression *entity, ast_expression *field);
-ast_entfield* ast_entfield_new_force(lex_ctx_t ctx, ast_expression *entity, ast_expression *field, const ast_expression *outtype);
 
 /* Member access:
  *
  * For now used for vectors. If we get structs or unions
  * we can have them handled here as well.
  */
-struct ast_member_s
+struct ast_member : ast_expression
 {
-    ast_expression  expression;
-    ast_expression *owner;
-    unsigned int    field;
-    const char     *name;
-    bool            rvalue;
-};
-ast_member* ast_member_new(lex_ctx_t ctx, ast_expression *owner, unsigned int field, const char *name);
-void ast_member_delete(ast_member*);
-bool ast_member_set_name(ast_member*, const char *name);
+    static ast_member *make(lex_ctx_t ctx, ast_expression *owner, unsigned int field, const std::string &name);
+    ~ast_member();
+
+    bool codegen(ast_function *current, bool lvalue, ir_value **out) override;
+
+    ast_expression *m_owner;
+    unsigned int m_field;
+    std::string m_name;
+    bool m_rvalue;
 
+private:
+    ast_member() = delete;
+    ast_member(lex_ctx_t ctx, ast_expression *owner, unsigned int field, const std::string &name);
+};
 
 /* Array index access:
  *
@@ -391,60 +391,77 @@ bool ast_member_set_name(ast_member*, const char *name);
  * In any case, accessing an element via a compiletime-constant index will
  * result in quick access to that variable.
  */
-struct ast_array_index_s
+struct ast_array_index : ast_expression
 {
-    ast_expression  expression;
-    ast_expression *array;
-    ast_expression *index;
+    static ast_array_index* make(lex_ctx_t ctx, ast_expression *array, ast_expression *index);
+    ~ast_array_index();
+
+    bool codegen(ast_function *current, bool lvalue, ir_value **out) override;
+
+    ast_expression *m_array;
+    ast_expression *m_index;
+private:
+    ast_array_index() = delete;
+    ast_array_index(lex_ctx_t ctx, ast_expression *array, ast_expression *index);
 };
-ast_array_index* ast_array_index_new(lex_ctx_t ctx, ast_expression *array, ast_expression *index);
 
 /* Vararg pipe node:
  *
  * copy all varargs starting from a specific index
  */
-struct ast_argpipe_s
+struct ast_argpipe : ast_expression
 {
-    ast_expression  expression;
-    ast_expression *index;
+    ast_argpipe() = delete;
+    ast_argpipe(lex_ctx_t ctx, ast_expression *index);
+
+    bool codegen(ast_function *current, bool lvalue, ir_value **out) override;
+
+    ~ast_argpipe();
+    ast_expression *m_index;
 };
-ast_argpipe* ast_argpipe_new(lex_ctx_t ctx, ast_expression *index);
 
 /* Store
  *
  * Stores left<-right and returns left.
  * Specialized binary expression node
  */
-struct ast_store_s
+struct ast_store : ast_expression
 {
-    ast_expression  expression;
-    int             op;
-    ast_expression *dest;
-    ast_expression *source;
+    ast_store() = delete;
+    ast_store(lex_ctx_t ctx, int op, ast_expression *d, ast_expression *s);
+    ~ast_store();
+
+    bool codegen(ast_function *current, bool lvalue, ir_value **out) override;
+
+    int m_op;
+    ast_expression *m_dest;
+    ast_expression *m_source;
 };
-ast_store* ast_store_new(lex_ctx_t ctx, int op,
-                         ast_expression *d, ast_expression *s);
 
 /* If
  *
- * A general 'if then else' statement, either side can be NULL and will
- * thus be omitted. It is an error for *both* cases to be NULL at once.
+ * A general 'if then else' statement, either side can be nullptr and will
+ * thus be omitted. It is an error for *both* cases to be nullptr at once.
  *
  * During its 'codegen' it'll be changing the ast_function's block.
  *
- * An if is also an "expression". Its codegen will put NULL into the
+ * An if is also an "expression". Its codegen will put nullptr into the
  * output field though. For ternary expressions an ast_ternary will be
  * added.
  */
-struct ast_ifthen_s
+struct ast_ifthen : ast_expression
 {
-    ast_expression  expression;
-    ast_expression *cond;
+    ast_ifthen() = delete;
+    ast_ifthen(lex_ctx_t ctx, ast_expression *cond, ast_expression *ontrue, ast_expression *onfalse);
+    ~ast_ifthen();
+
+    bool codegen(ast_function *current, bool lvalue, ir_value **out) override;
+
+    ast_expression *m_cond;
     /* It's all just 'expressions', since an ast_block is one too. */
-    ast_expression *on_true;
-    ast_expression *on_false;
+    ast_expression *m_on_true;
+    ast_expression *m_on_false;
 };
-ast_ifthen* ast_ifthen_new(lex_ctx_t ctx, ast_expression *cond, ast_expression *ontrue, ast_expression *onfalse);
 
 /* Ternary expressions...
  *
@@ -454,20 +471,24 @@ ast_ifthen* ast_ifthen_new(lex_ctx_t ctx, ast_expression *cond, ast_expression *
  * a PHI node.
  *
  * The other difference is that in an ast_ternary, NEITHER side
- * must be NULL, there's ALWAYS an else branch.
+ * must be nullptr, there's ALWAYS an else branch.
  *
  * This is the only ast_node beside ast_value which contains
  * an ir_value. Theoretically we don't need to remember it though.
  */
-struct ast_ternary_s
+struct ast_ternary : ast_expression
 {
-    ast_expression  expression;
-    ast_expression *cond;
+    ast_ternary() = delete;
+    ast_ternary(lex_ctx_t ctx, ast_expression *cond, ast_expression *ontrue, ast_expression *onfalse);
+    ~ast_ternary();
+
+    bool codegen(ast_function *current, bool lvalue, ir_value **out) override;
+
+    ast_expression *m_cond;
     /* It's all just 'expressions', since an ast_block is one too. */
-    ast_expression *on_true;
-    ast_expression *on_false;
+    ast_expression *m_on_true;
+    ast_expression *m_on_false;
 };
-ast_ternary* ast_ternary_new(lex_ctx_t ctx, ast_expression *cond, ast_expression *ontrue, ast_expression *onfalse);
 
 /* A general loop node
  *
@@ -492,39 +513,48 @@ continue:      // a 'continue' will jump here
     {inc};
 }
  */
-struct ast_loop_s
+struct ast_loop : ast_expression
 {
-    ast_expression  expression;
-    ast_expression *initexpr;
-    ast_expression *precond;
-    ast_expression *postcond;
-    ast_expression *increment;
-    ast_expression *body;
+    ast_loop() = delete;
+    ast_loop(lex_ctx_t ctx,
+             ast_expression *initexpr,
+             ast_expression *precond, bool pre_not,
+             ast_expression *postcond, bool post_not,
+             ast_expression *increment,
+             ast_expression *body);
+    ~ast_loop();
+
+    bool codegen(ast_function *current, bool lvalue, ir_value **out) override;
+
+    ast_expression *m_initexpr;
+    ast_expression *m_precond;
+    ast_expression *m_postcond;
+    ast_expression *m_increment;
+    ast_expression *m_body;
     /* For now we allow a seperate flag on whether or not the condition
      * is supposed to be true or false.
      * That way, the parser can generate a 'while not(!x)' for `while(x)`
      * if desired, which is useful for the new -f{true,false}-empty-strings
      * flag.
      */
-    bool pre_not;
-    bool post_not;
+    bool m_pre_not;
+    bool m_post_not;
 };
-ast_loop* ast_loop_new(lex_ctx_t ctx,
-                       ast_expression *initexpr,
-                       ast_expression *precond, bool pre_not,
-                       ast_expression *postcond, bool post_not,
-                       ast_expression *increment,
-                       ast_expression *body);
 
 /* Break/Continue
  */
-struct ast_breakcont_s
+struct ast_breakcont : ast_expression
 {
-    ast_expression expression;
-    bool           is_continue;
-    unsigned int   levels;
+    ast_breakcont() = delete;
+    ast_breakcont(lex_ctx_t ctx, bool iscont, unsigned int levels);
+    ~ast_breakcont();
+
+    bool codegen(ast_function *current, bool lvalue, ir_value **out) override;
+
+
+    bool         m_is_continue;
+    unsigned int m_levels;
 };
-ast_breakcont* ast_breakcont_new(lex_ctx_t ctx, bool iscont, unsigned int levels);
 
 /* Switch Statements
  *
@@ -536,64 +566,82 @@ ast_breakcont* ast_breakcont_new(lex_ctx_t ctx, bool iscont, unsigned int levels
  * be expected from it.
  * TODO: Ticket #20
  */
-typedef struct {
-    ast_expression *value; /* #20 will replace this */
-    ast_expression *code;
-} ast_switch_case;
-struct ast_switch_s
+struct ast_switch_case {
+    ast_expression *m_value; /* #20 will replace this */
+    ast_expression *m_code;
+};
+
+struct ast_switch : ast_expression
 {
-    ast_expression   expression;
+    ast_switch() = delete;
+    ast_switch(lex_ctx_t ctx, ast_expression *op);
+    ~ast_switch();
 
-    ast_expression  *operand;
-    ast_switch_case *cases;
+    bool codegen(ast_function *current, bool lvalue, ir_value **out) override;
+
+    ast_expression *m_operand;
+    std::vector<ast_switch_case> m_cases;
 };
 
-ast_switch* ast_switch_new(lex_ctx_t ctx, ast_expression *op);
 
 /* Label nodes
  *
  * Introduce a label which can be used together with 'goto'
  */
-struct ast_label_s
+struct ast_label : ast_expression
 {
-    ast_expression  expression;
-    const char     *name;
-    ir_block       *irblock;
-    ast_goto      **gotos;
+    ast_label() = delete;
+    ast_label(lex_ctx_t ctx, const std::string &name, bool undefined);
+    ~ast_label();
+
+    bool codegen(ast_function *current, bool lvalue, ir_value **out) override;
+
+    std::string m_name;
+    ir_block *m_irblock;
+    std::vector<ast_goto*> m_gotos;
 
     /* means it has not yet been defined */
-    bool           undefined;
-};
+    bool m_undefined;
 
-ast_label* ast_label_new(lex_ctx_t ctx, const char *name, bool undefined);
+private:
+    void registerGoto(ast_goto*);
+    friend struct ast_goto;
+};
 
 /* GOTO nodes
  *
  * Go to a label, the label node is filled in at a later point!
  */
-struct ast_goto_s
+struct ast_goto : ast_expression
 {
-    ast_expression expression;
-    const char    *name;
-    ast_label     *target;
-    ir_block      *irblock_from;
-};
+    ast_goto() = delete;
+    ast_goto(lex_ctx_t ctx, const std::string &name);
+    ~ast_goto();
+
+    bool codegen(ast_function *current, bool lvalue, ir_value **out) override;
+
+    void setLabel(ast_label*);
 
-ast_goto* ast_goto_new(lex_ctx_t ctx, const char *name);
-void ast_goto_set_label(ast_goto*, ast_label*);
+    std::string m_name;
+    ast_label *m_target;
+    ir_block *m_irblock_from;
+};
 
 /* STATE node
  *
  * For frame/think state updates: void foo() [framenum, nextthink] {}
  */
-struct ast_state_s
+struct ast_state : ast_expression
 {
-    ast_expression  expression;
-    ast_expression *framenum;
-    ast_expression *nextthink;
+    ast_state() = delete;
+    ast_state(lex_ctx_t ctx, ast_expression *frame, ast_expression *think);
+    ~ast_state();
+
+    bool codegen(ast_function *current, bool lvalue, ir_value **out) override;
+
+    ast_expression *m_framenum;
+    ast_expression *m_nextthink;
 };
-ast_state* ast_state_new(lex_ctx_t ctx, ast_expression *frame, ast_expression *think);
-void ast_state_delete(ast_state*);
 
 /* CALL node
  *
@@ -605,34 +653,44 @@ void ast_state_delete(ast_state*);
  * Additionally it contains a list of ast_expressions as parameters.
  * Since calls can return values, an ast_call is also an ast_expression.
  */
-struct ast_call_s
+struct ast_call : ast_expression
 {
-    ast_expression  expression;
-    ast_expression *func;
-    ast_expression **params;
-    ast_expression *va_count;
+    ast_call() = delete;
+    static ast_call *make(lex_ctx_t, ast_expression*);
+    ~ast_call();
+
+    bool codegen(ast_function *current, bool lvalue, ir_value **out) override;
+
+    bool checkTypes(ast_expression *this_func_va_type) const;
+
+    ast_expression *m_func;
+    std::vector<ast_expression *> m_params;
+    ast_expression *m_va_count;
+
+private:
+    ast_call(lex_ctx_t ctx, ast_expression *funcexpr);
+    bool checkVararg(ast_expression *va_type, ast_expression *exp_type) const;
 };
-ast_call* ast_call_new(lex_ctx_t ctx,
-                       ast_expression *funcexpr);
-bool ast_call_check_types(ast_call*, ast_expression *this_func_va_type);
 
 /* Blocks
  *
  */
-struct ast_block_s
+struct ast_block : ast_expression
 {
-    ast_expression   expression;
+    ast_block() = delete;
+    ast_block(lex_ctx_t ctx);
+    ~ast_block();
 
-    ast_value*      *locals;
-    ast_expression* *exprs;
-    ast_expression* *collect;
-};
-ast_block* ast_block_new(lex_ctx_t ctx);
-void ast_block_delete(ast_block*);
-void ast_block_set_type(ast_block*, ast_expression *from);
-void ast_block_collect(ast_block*, ast_expression*);
+    bool codegen(ast_function *current, bool lvalue, ir_value **out) override;
 
-bool GMQCC_WARN ast_block_add_expr(ast_block*, ast_expression*);
+    std::vector<ast_value*>      m_locals;
+    std::vector<ast_expression*> m_exprs;
+    std::vector<ast_expression*> m_collect;
+
+    void setType(const ast_expression &from);
+    bool GMQCC_WARN addExpr(ast_expression*);
+    void collect(ast_expression*);
+};
 
 /* Function
  *
@@ -643,62 +701,53 @@ bool GMQCC_WARN ast_block_add_expr(ast_block*, ast_expression*);
  * neither functions inside functions, nor lambdas, and function
  * pointers could just work with a name. However, this way could be
  * more flexible, and adds no real complexity.
+ *
+ * The destructor will NOT delete the underlying ast_value
+ *
  */
-struct ast_function_s
+struct ast_function : ast_node
 {
-    ast_node    node;
+    ast_function() = delete;
+    static ast_function *make(lex_ctx_t ctx, const std::string &name, ast_value *vtype);
+    ~ast_function();
+
+    const char* makeLabel(const char *prefix);
+    virtual bool generateFunction(ir_builder*);
 
-    ast_value  *vtype;
-    const char *name;
+    ast_value  *m_function_type = nullptr;
+    std::string m_name;
 
-    int builtin;
+    int m_builtin = 0;
 
     /* list of used-up names for statics without the count suffix */
-    char       **static_names;
+    std::vector<std::string> m_static_names;
     /* number of static variables, by convention this includes the
      * ones without the count-suffix - remember this when dealing
      * with savegames. uint instead of size_t as %zu in printf is
      * C99, so no windows support. */
-    unsigned int static_count;
+    unsigned int m_static_count = 0;
 
-    ir_function *ir_func;
-    ir_block    *curblock;
-    ir_block    **breakblocks;
-    ir_block    **continueblocks;
+    ir_function *m_ir_func = nullptr;
+    ir_block *m_curblock = nullptr;
+    std::vector<ir_block*> m_breakblocks;
+    std::vector<ir_block*> m_continueblocks;
 
-#if 0
-    /* In order for early-out logic not to go over
-     * excessive jumps, we remember their target
-     * blocks...
-     */
-    ir_block    *iftrue;
-    ir_block    *iffalse;
-#endif
-
-    size_t       labelcount;
+    size_t m_labelcount = 0;
     /* in order for thread safety - for the optional
      * channel abesed multithreading... keeping a buffer
      * here to use in ast_function_label.
      */
-    char         labelbuf[64];
+    std::vector<std::unique_ptr<ast_block>> m_blocks;
+    std::unique_ptr<ast_value> m_varargs;
+    std::unique_ptr<ast_value> m_argc;
+    ast_value *m_fixedparams = nullptr; // these use unref()
+    ast_value *m_return_value = nullptr;
 
-    ast_block* *blocks;
+private:
+    ast_function(lex_ctx_t ctx, const std::string &name, ast_value *vtype);
 
-    ast_value   *varargs;
-    ast_value   *argc;
-    ast_value   *fixedparams;
-    ast_value   *return_value;
+    char m_labelbuf[64];
 };
-ast_function* ast_function_new(lex_ctx_t ctx, const char *name, ast_value *vtype);
-/* This will NOT delete the underlying ast_value */
-void ast_function_delete(ast_function*);
-/* For "optimized" builds this can just keep returning "foo"...
- * or whatever...
- */
-const char* ast_function_label(ast_function*, const char *prefix);
-
-bool ast_function_codegen(ast_function *self, ir_builder *builder);
-bool ast_generate_accessors(ast_value *asvalue, ir_builder *ir);
 
 /*
  * If the condition creates a situation where this becomes -1 size it means there are
diff --git a/code.c b/code.c
deleted file mode 100644 (file)
index 6f040ce..0000000
--- a/code.c
+++ /dev/null
@@ -1,461 +0,0 @@
-/*
- * Copyright (C) 2012, 2013, 2014, 2015
- *     Dale Weiler
- *     Wolfgang Bumiller
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
- * this software and associated documentation files (the "Software"), to deal in
- * the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is furnished to do
- * so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-#include <string.h>
-#include "gmqcc.h"
-
-/*
- * We could use the old method of casting to uintptr_t then to void*
- * or qcint_t; however, it's incredibly unsafe for two reasons.
- * 1) The compilers aliasing optimization can legally make it unstable
- *    (it's undefined behaviour).
- *
- * 2) The cast itself depends on fresh storage (newly allocated in which
- *    ever function is using the cast macros), the contents of which are
- *    transferred in a way that the obligation to release storage is not
- *    propagated.
- */
-typedef union {
-    void   *enter;
-    qcint_t leave;
-} code_hash_entry_t;
-
-/* Some sanity macros */
-#define CODE_HASH_ENTER(ENTRY) ((ENTRY).enter)
-#define CODE_HASH_LEAVE(ENTRY) ((ENTRY).leave)
-
-void code_push_statement(code_t *code, prog_section_statement_t *stmt_in, lex_ctx_t ctx)
-{
-    prog_section_statement_t stmt = *stmt_in;
-
-    if (OPTS_FLAG(TYPELESS_STORES)) {
-        switch (stmt.opcode) {
-            case INSTR_LOAD_S:
-            case INSTR_LOAD_ENT:
-            case INSTR_LOAD_FLD:
-            case INSTR_LOAD_FNC:
-                stmt.opcode = INSTR_LOAD_F;
-                break;
-            case INSTR_STORE_S:
-            case INSTR_STORE_ENT:
-            case INSTR_STORE_FLD:
-            case INSTR_STORE_FNC:
-                stmt.opcode = INSTR_STORE_F;
-                break;
-            case INSTR_STOREP_S:
-            case INSTR_STOREP_ENT:
-            case INSTR_STOREP_FLD:
-            case INSTR_STOREP_FNC:
-                stmt.opcode = INSTR_STOREP_F;
-                break;
-        }
-    }
-
-
-    if (OPTS_FLAG(SORT_OPERANDS)) {
-        uint16_t pair;
-
-        switch (stmt.opcode) {
-            case INSTR_MUL_F:
-            case INSTR_MUL_V:
-            case INSTR_ADD_F:
-            case INSTR_EQ_F:
-            case INSTR_EQ_S:
-            case INSTR_EQ_E:
-            case INSTR_EQ_FNC:
-            case INSTR_NE_F:
-            case INSTR_NE_V:
-            case INSTR_NE_S:
-            case INSTR_NE_E:
-            case INSTR_NE_FNC:
-            case INSTR_AND:
-            case INSTR_OR:
-            case INSTR_BITAND:
-            case INSTR_BITOR:
-                if (stmt.o1.u1 < stmt.o2.u1) {
-                    uint16_t a = stmt.o2.u1;
-                    stmt.o1.u1 = stmt.o2.u1;
-                    stmt.o2.u1 = a;
-                }
-                break;
-
-            case INSTR_MUL_VF: pair = INSTR_MUL_FV; goto case_pair_gen;
-            case INSTR_MUL_FV: pair = INSTR_MUL_VF; goto case_pair_gen;
-            case INSTR_LT:     pair = INSTR_GT;     goto case_pair_gen;
-            case INSTR_GT:     pair = INSTR_LT;     goto case_pair_gen;
-            case INSTR_LE:     pair = INSTR_GT;     goto case_pair_gen;
-            case INSTR_GE:     pair = INSTR_LE;
-
-            case_pair_gen:
-                if (stmt.o1.u1 < stmt.o2.u1) {
-                    uint16_t x  = stmt.o1.u1;
-                    stmt.o1.u1  = stmt.o2.u1;
-                    stmt.o2.u1  = x;
-                    stmt.opcode = pair;
-                }
-                break;
-        }
-    }
-
-    vec_push(code->statements, stmt);
-    vec_push(code->linenums,   (int)ctx.line);
-    vec_push(code->columnnums, (int)ctx.column);
-}
-
-void code_pop_statement(code_t *code)
-{
-    vec_pop(code->statements);
-    vec_pop(code->linenums);
-    vec_pop(code->columnnums);
-}
-
-code_t *code_init() {
-    static lex_ctx_t                empty_ctx       = {0, 0, 0};
-    static prog_section_function_t  empty_function  = {0,0,0,0,0,0,0,{0,0,0,0,0,0,0,0}};
-    static prog_section_statement_t empty_statement = {0,{0},{0},{0}};
-    static prog_section_def_t       empty_def       = {0, 0, 0};
-
-    code_t *code       = (code_t*)mem_a(sizeof(code_t));
-    int     i          = 0;
-
-    memset(code, 0, sizeof(code_t));
-    code->entfields    = 0;
-    code->string_cache = util_htnew(OPTS_OPTIMIZATION(OPTIM_OVERLAP_STRINGS) ? 0x100 : 1024);
-
-    /*
-     * The way progs.dat is suppose to work is odd, there needs to be
-     * some null (empty) statements, functions, and 28 globals
-     */
-    for(; i < 28; i++)
-        vec_push(code->globals, 0);
-
-    vec_push(code->chars, '\0');
-    vec_push(code->functions,  empty_function);
-
-    code_push_statement(code, &empty_statement, empty_ctx);
-
-    vec_push(code->defs,    empty_def);
-    vec_push(code->fields,  empty_def);
-
-    return code;
-}
-
-void *code_util_str_htgeth(hash_table_t *ht, const char *key, size_t bin);
-
-uint32_t code_genstring(code_t *code, const char *str) {
-    size_t            hash;
-    code_hash_entry_t existing;
-
-    if (!str)
-        return 0;
-
-    if (!*str) {
-        if (!code->string_cached_empty) {
-            code->string_cached_empty = vec_size(code->chars);
-            vec_push(code->chars, 0);
-        }
-        return code->string_cached_empty;
-    }
-
-    if (OPTS_OPTIMIZATION(OPTIM_OVERLAP_STRINGS)) {
-        hash                      = ((unsigned char*)str)[strlen(str)-1];
-        CODE_HASH_ENTER(existing) = code_util_str_htgeth(code->string_cache, str, hash);
-    } else {
-        hash                      = util_hthash(code->string_cache, str);
-        CODE_HASH_ENTER(existing) = util_htgeth(code->string_cache, str, hash);
-    }
-
-    if (CODE_HASH_ENTER(existing))
-        return CODE_HASH_LEAVE(existing);
-
-    CODE_HASH_LEAVE(existing) = vec_size(code->chars);
-    vec_append(code->chars, strlen(str)+1, str);
-
-    util_htseth(code->string_cache, str, hash, CODE_HASH_ENTER(existing));
-    return CODE_HASH_LEAVE(existing);
-}
-
-qcint_t code_alloc_field (code_t *code, size_t qcsize)
-{
-    qcint_t pos = (qcint_t)code->entfields;
-    code->entfields += qcsize;
-    return pos;
-}
-
-static size_t code_size_generic(code_t *code, prog_header_t *code_header, bool lno) {
-    size_t size = 0;
-    if (lno) {
-        size += 4;  /* LNOF */
-        size += sizeof(uint32_t); /* version */
-        size += sizeof(code_header->defs.length);
-        size += sizeof(code_header->globals.length);
-        size += sizeof(code_header->fields.length);
-        size += sizeof(code_header->statements.length);
-        size += sizeof(code->linenums[0])   * vec_size(code->linenums);
-        size += sizeof(code->columnnums[0]) * vec_size(code->columnnums);
-    } else {
-        size += sizeof(prog_header_t);
-        size += sizeof(prog_section_statement_t) * vec_size(code->statements);
-        size += sizeof(prog_section_def_t)       * vec_size(code->defs);
-        size += sizeof(prog_section_field_t)     * vec_size(code->fields);
-        size += sizeof(prog_section_function_t)  * vec_size(code->functions);
-        size += sizeof(int32_t)                  * vec_size(code->globals);
-        size += 1                                * vec_size(code->chars);
-    }
-    return size;
-}
-
-#define code_size_binary(C, H) code_size_generic((C), (H), false)
-#define code_size_debug(C, H)  code_size_generic((C), (H), true)
-
-static void code_create_header(code_t *code, prog_header_t *code_header, const char *filename, const char *lnofile) {
-    size_t i;
-
-    code_header->statements.offset = sizeof(prog_header_t);
-    code_header->statements.length = vec_size(code->statements);
-    code_header->defs.offset       = code_header->statements.offset + (sizeof(prog_section_statement_t) * vec_size(code->statements));
-    code_header->defs.length       = vec_size(code->defs);
-    code_header->fields.offset     = code_header->defs.offset       + (sizeof(prog_section_def_t)       * vec_size(code->defs));
-    code_header->fields.length     = vec_size(code->fields);
-    code_header->functions.offset  = code_header->fields.offset     + (sizeof(prog_section_field_t)     * vec_size(code->fields));
-    code_header->functions.length  = vec_size(code->functions);
-    code_header->globals.offset    = code_header->functions.offset  + (sizeof(prog_section_function_t)  * vec_size(code->functions));
-    code_header->globals.length    = vec_size(code->globals);
-    code_header->strings.offset    = code_header->globals.offset    + (sizeof(int32_t)                  * vec_size(code->globals));
-    code_header->strings.length    = vec_size(code->chars);
-    code_header->version           = 6;
-    code_header->skip              = 0;
-
-    if (OPTS_OPTION_BOOL(OPTION_FORCECRC))
-        code_header->crc16         = OPTS_OPTION_U16(OPTION_FORCED_CRC);
-    else
-        code_header->crc16         = code->crc;
-    code_header->entfield          = code->entfields;
-
-    if (OPTS_FLAG(DARKPLACES_STRING_TABLE_BUG)) {
-        /* >= + P */
-        vec_push(code->chars, '\0'); /* > */
-        vec_push(code->chars, '\0'); /* = */
-        vec_push(code->chars, '\0'); /* P */
-    }
-
-    /* ensure all data is in LE format */
-    util_swap_header(code_header);
-
-    /*
-     * These are not part of the header but we ensure LE format here to save on duplicated
-     * code.
-     */
-
-    util_swap_statements (code->statements);
-    util_swap_defs_fields(code->defs);
-    util_swap_defs_fields(code->fields);
-    util_swap_functions  (code->functions);
-    util_swap_globals    (code->globals);
-
-    if (!OPTS_OPTION_BOOL(OPTION_QUIET)) {
-        if (lnofile)
-            con_out("writing '%s' and '%s'...\n", filename, lnofile);
-        else
-            con_out("writing '%s'\n", filename);
-    }
-
-    if (!OPTS_OPTION_BOOL(OPTION_QUIET) &&
-        !OPTS_OPTION_BOOL(OPTION_PP_ONLY))
-    {
-        char buffer[1024];
-        con_out("\nOptimizations:\n");
-        for (i = 0; i < COUNT_OPTIMIZATIONS; ++i) {
-            if (opts_optimizationcount[i]) {
-                util_optimizationtostr(opts_opt_list[i].name, buffer, sizeof(buffer));
-                con_out(
-                    "    %s: %u\n",
-                    buffer,
-                    (unsigned int)opts_optimizationcount[i]
-                );
-            }
-        }
-    }
-}
-
-static void code_stats(const char *filename, const char *lnofile, code_t *code, prog_header_t *code_header) {
-    if (OPTS_OPTION_BOOL(OPTION_QUIET) ||
-        OPTS_OPTION_BOOL(OPTION_PP_ONLY))
-            return;
-
-    con_out("\nFile statistics:\n");
-    con_out("    dat:\n");
-    con_out("        name: %s\n",         filename);
-    con_out("        size: %u (bytes)\n", code_size_binary(code, code_header));
-    con_out("        crc:  0x%04X\n",     code->crc);
-
-    if (lnofile) {
-        con_out("    lno:\n");
-        con_out("        name: %s\n",  lnofile);
-        con_out("        size: %u (bytes)\n",  code_size_debug(code, code_header));
-    }
-
-    con_out("\n");
-}
-
-/*
- * Same principle except this one allocates memory and writes the lno(optional) and the dat file
- * directly out to allocated memory. Which is actually very useful for the future library support
- * we're going to add.
- */
-#if 0
-static bool code_write_memory(code_t *code, uint8_t **datmem, size_t *sizedat, uint8_t **lnomem, size_t *sizelno) GMQCC_UNUSED {
-    prog_header_t code_header;
-    uint32_t      offset  = 0;
-
-    if (!datmem)
-        return false;
-
-    code_create_header(code, &code_header, "<<memory>>", "<<memory>>");
-
-    #define WRITE_CHUNK(C,X,S)                                     \
-        do {                                                       \
-            memcpy((void*)(&(*C)[offset]), (const void*)(X), (S)); \
-            offset += (S);                                         \
-        } while (0)
-
-    /* Calculate size required to store entire file out to memory */
-    if (lnomem) {
-        uint32_t version = 1;
-
-        *sizelno = code_size_debug(code, &code_header);
-        *lnomem  = (uint8_t*)mem_a(*sizelno);
-
-        WRITE_CHUNK(lnomem, "LNOF",                         4);
-        WRITE_CHUNK(lnomem, &version,                       sizeof(version));
-        WRITE_CHUNK(lnomem, &code_header.defs.length,       sizeof(code_header.defs.length));
-        WRITE_CHUNK(lnomem, &code_header.globals.length,    sizeof(code_header.globals.length));
-        WRITE_CHUNK(lnomem, &code_header.fields.length,     sizeof(code_header.fields.length));
-        WRITE_CHUNK(lnomem, &code_header.statements.length, sizeof(code_header.statements.length));
-
-        /* something went terribly wrong */
-        if (offset != *sizelno) {
-            mem_d(*lnomem);
-            *sizelno = 0;
-            return false;
-        }
-        offset = 0;
-    }
-
-    /* Write out the dat */
-    *sizedat = code_size_binary(code, &code_header);
-    *datmem  = (uint8_t*)mem_a(*sizedat);
-
-    WRITE_CHUNK(datmem, &code_header,     sizeof(prog_header_t));
-    WRITE_CHUNK(datmem, code->statements, sizeof(prog_section_statement_t) * vec_size(code->statements));
-    WRITE_CHUNK(datmem, code->defs,       sizeof(prog_section_def_t)       * vec_size(code->defs));
-    WRITE_CHUNK(datmem, code->fields,     sizeof(prog_section_field_t)     * vec_size(code->fields));
-    WRITE_CHUNK(datmem, code->functions,  sizeof(prog_section_function_t)  * vec_size(code->functions));
-    WRITE_CHUNK(datmem, code->globals,    sizeof(int32_t)                  * vec_size(code->globals));
-    WRITE_CHUNK(datmem, code->chars,      1                                * vec_size(code->chars));
-
-    vec_free(code->statements);
-    vec_free(code->linenums);
-    vec_free(code->columnnums);
-    vec_free(code->defs);
-    vec_free(code->fields);
-    vec_free(code->functions);
-    vec_free(code->globals);
-    vec_free(code->chars);
-
-    util_htdel(code->string_cache);
-    mem_d(code);
-    code_stats("<<memory>>", (lnomem) ? "<<memory>>" : NULL, code, &code_header);
-    return true;
-}
-#endif /*!#if 0 reenable when ready to be used */
-#undef WRITE_CHUNK
-
-bool code_write(code_t *code, const char *filename, const char *lnofile) {
-    prog_header_t  code_header;
-    fs_file_t     *fp = NULL;
-
-    code_create_header(code, &code_header, filename, lnofile);
-
-    if (lnofile) {
-        uint32_t version = 1;
-
-        fp = fs_file_open(lnofile, "wb");
-        if (!fp)
-            return false;
-
-        util_endianswap(&version,         1,                          sizeof(version));
-        util_endianswap(code->linenums,   vec_size(code->linenums),   sizeof(code->linenums[0]));
-        util_endianswap(code->columnnums, vec_size(code->columnnums), sizeof(code->columnnums[0]));
-
-        if (fs_file_write("LNOF",                          4,                                      1,                          fp) != 1 ||
-            fs_file_write(&version,                        sizeof(version),                        1,                          fp) != 1 ||
-            fs_file_write(&code_header.defs.length,        sizeof(code_header.defs.length),        1,                          fp) != 1 ||
-            fs_file_write(&code_header.globals.length,     sizeof(code_header.globals.length),     1,                          fp) != 1 ||
-            fs_file_write(&code_header.fields.length,      sizeof(code_header.fields.length),      1,                          fp) != 1 ||
-            fs_file_write(&code_header.statements.length,  sizeof(code_header.statements.length),  1,                          fp) != 1 ||
-            fs_file_write(code->linenums,                  sizeof(code->linenums[0]),              vec_size(code->linenums),   fp) != vec_size(code->linenums) ||
-            fs_file_write(code->columnnums,                sizeof(code->columnnums[0]),            vec_size(code->columnnums), fp) != vec_size(code->columnnums))
-        {
-            con_err("failed to write lno file\n");
-        }
-
-        fs_file_close(fp);
-        fp = NULL;
-    }
-
-    fp = fs_file_open(filename, "wb");
-    if (!fp)
-        return false;
-
-    if (1                          != fs_file_write(&code_header,     sizeof(prog_header_t)           , 1                         , fp) ||
-        vec_size(code->statements) != fs_file_write(code->statements, sizeof(prog_section_statement_t), vec_size(code->statements), fp) ||
-        vec_size(code->defs)       != fs_file_write(code->defs,       sizeof(prog_section_def_t)      , vec_size(code->defs)      , fp) ||
-        vec_size(code->fields)     != fs_file_write(code->fields,     sizeof(prog_section_field_t)    , vec_size(code->fields)    , fp) ||
-        vec_size(code->functions)  != fs_file_write(code->functions,  sizeof(prog_section_function_t) , vec_size(code->functions) , fp) ||
-        vec_size(code->globals)    != fs_file_write(code->globals,    sizeof(int32_t)                 , vec_size(code->globals)   , fp) ||
-        vec_size(code->chars)      != fs_file_write(code->chars,      1                               , vec_size(code->chars)     , fp))
-    {
-        fs_file_close(fp);
-        return false;
-    }
-
-    fs_file_close(fp);
-    code_stats(filename, lnofile, code, &code_header);
-    return true;
-}
-
-void code_cleanup(code_t *code) {
-    vec_free(code->statements);
-    vec_free(code->linenums);
-    vec_free(code->columnnums);
-    vec_free(code->defs);
-    vec_free(code->fields);
-    vec_free(code->functions);
-    vec_free(code->globals);
-    vec_free(code->chars);
-
-    util_htdel(code->string_cache);
-
-    mem_d(code);
-}
diff --git a/code.cpp b/code.cpp
new file mode 100644 (file)
index 0000000..645ad25
--- /dev/null
+++ b/code.cpp
@@ -0,0 +1,348 @@
+#include <string.h>
+#include "gmqcc.h"
+
+/*
+ * We could use the old method of casting to uintptr_t then to void*
+ * or qcint_t; however, it's incredibly unsafe for two reasons.
+ * 1) The compilers aliasing optimization can legally make it unstable
+ *    (it's undefined behaviour).
+ *
+ * 2) The cast itself depends on fresh storage (newly allocated in which
+ *    ever function is using the cast macros), the contents of which are
+ *    transferred in a way that the obligation to release storage is not
+ *    propagated.
+ */
+typedef union {
+    void   *enter;
+    qcint_t leave;
+} code_hash_entry_t;
+
+/* Some sanity macros */
+#define CODE_HASH_ENTER(ENTRY) ((ENTRY).enter)
+#define CODE_HASH_LEAVE(ENTRY) ((ENTRY).leave)
+
+void code_push_statement(code_t *code, prog_section_statement_t *stmt_in, lex_ctx_t ctx)
+{
+    prog_section_statement_t stmt = *stmt_in;
+
+    if (OPTS_FLAG(TYPELESS_STORES)) {
+        switch (stmt.opcode) {
+            case INSTR_LOAD_S:
+            case INSTR_LOAD_ENT:
+            case INSTR_LOAD_FLD:
+            case INSTR_LOAD_FNC:
+                stmt.opcode = INSTR_LOAD_F;
+                break;
+            case INSTR_STORE_S:
+            case INSTR_STORE_ENT:
+            case INSTR_STORE_FLD:
+            case INSTR_STORE_FNC:
+                stmt.opcode = INSTR_STORE_F;
+                break;
+            case INSTR_STOREP_S:
+            case INSTR_STOREP_ENT:
+            case INSTR_STOREP_FLD:
+            case INSTR_STOREP_FNC:
+                stmt.opcode = INSTR_STOREP_F;
+                break;
+        }
+    }
+
+
+    if (OPTS_FLAG(SORT_OPERANDS)) {
+        uint16_t pair;
+
+        switch (stmt.opcode) {
+            case INSTR_MUL_F:
+            case INSTR_MUL_V:
+            case INSTR_ADD_F:
+            case INSTR_EQ_F:
+            case INSTR_EQ_S:
+            case INSTR_EQ_E:
+            case INSTR_EQ_FNC:
+            case INSTR_NE_F:
+            case INSTR_NE_V:
+            case INSTR_NE_S:
+            case INSTR_NE_E:
+            case INSTR_NE_FNC:
+            case INSTR_AND:
+            case INSTR_OR:
+            case INSTR_BITAND:
+            case INSTR_BITOR:
+                if (stmt.o1.u1 < stmt.o2.u1) {
+                    uint16_t a = stmt.o2.u1;
+                    stmt.o1.u1 = stmt.o2.u1;
+                    stmt.o2.u1 = a;
+                }
+                break;
+
+            case INSTR_MUL_VF: pair = INSTR_MUL_FV; goto case_pair_gen;
+            case INSTR_MUL_FV: pair = INSTR_MUL_VF; goto case_pair_gen;
+            case INSTR_LT:     pair = INSTR_GT;     goto case_pair_gen;
+            case INSTR_GT:     pair = INSTR_LT;     goto case_pair_gen;
+            case INSTR_LE:     pair = INSTR_GT;     goto case_pair_gen;
+            case INSTR_GE:     pair = INSTR_LE;
+
+            case_pair_gen:
+                if (stmt.o1.u1 < stmt.o2.u1) {
+                    uint16_t x  = stmt.o1.u1;
+                    stmt.o1.u1  = stmt.o2.u1;
+                    stmt.o2.u1  = x;
+                    stmt.opcode = pair;
+                }
+                break;
+        }
+    }
+
+    code->statements.push_back(stmt);
+    code->linenums.push_back(ctx.line);
+    code->columnnums.push_back(ctx.column);
+}
+
+void code_pop_statement(code_t *code)
+{
+    code->statements.pop_back();
+    code->linenums.pop_back();
+    code->columnnums.pop_back();
+}
+
+void *code_t::operator new(std::size_t bytes) {
+  return mem_a(bytes);
+}
+
+void code_t::operator delete(void *ptr) {
+  mem_d(ptr);
+}
+
+code_t::code_t()
+{
+    static lex_ctx_t                empty_ctx       = {0, 0, 0};
+    static prog_section_function_t  empty_function  = {0,0,0,0,0,0,0,{0,0,0,0,0,0,0,0}};
+    static prog_section_statement_t empty_statement = {0,{0},{0},{0}};
+    static prog_section_def_t       empty_def       = {0, 0, 0};
+
+    string_cache = util_htnew(OPTS_OPTIMIZATION(OPTIM_OVERLAP_STRINGS) ? 0x100 : 1024);
+
+    // The way progs.dat is suppose to work is odd, there needs to be
+    // some null (empty) statements, functions, and 28 globals
+    globals.insert(globals.begin(), 28, 0);
+
+    chars.push_back('\0');
+    functions.push_back(empty_function);
+
+    code_push_statement(this, &empty_statement, empty_ctx);
+
+    defs.push_back(empty_def);
+    fields.push_back(empty_def);
+}
+
+code_t::~code_t()
+{
+    util_htdel(string_cache);
+}
+
+void *code_util_str_htgeth(hash_table_t *ht, const char *key, size_t bin);
+
+uint32_t code_genstring(code_t *code, const char *str) {
+    size_t            hash;
+    code_hash_entry_t existing;
+
+    if (!str)
+        return 0;
+
+    if (!*str) {
+        if (!code->string_cached_empty) {
+            code->string_cached_empty = code->chars.size();
+            code->chars.push_back(0);
+        }
+        return code->string_cached_empty;
+    }
+
+    if (OPTS_OPTIMIZATION(OPTIM_OVERLAP_STRINGS)) {
+        hash                      = ((unsigned char*)str)[strlen(str)-1];
+        CODE_HASH_ENTER(existing) = code_util_str_htgeth(code->string_cache, str, hash);
+    } else {
+        hash                      = util_hthash(code->string_cache, str);
+        CODE_HASH_ENTER(existing) = util_htgeth(code->string_cache, str, hash);
+    }
+
+    if (CODE_HASH_ENTER(existing))
+        return CODE_HASH_LEAVE(existing);
+
+    CODE_HASH_LEAVE(existing) = code->chars.size();
+    code->chars.insert(code->chars.end(), str, str + strlen(str) + 1);
+
+    util_htseth(code->string_cache, str, hash, CODE_HASH_ENTER(existing));
+    return CODE_HASH_LEAVE(existing);
+}
+
+qcint_t code_alloc_field (code_t *code, size_t qcsize)
+{
+    qcint_t pos = (qcint_t)code->entfields;
+    code->entfields += qcsize;
+    return pos;
+}
+
+static size_t code_size_generic(code_t *code, prog_header_t *code_header, bool lno) {
+    size_t size = 0;
+    if (lno) {
+        size += 4;  /* LNOF */
+        size += sizeof(uint32_t); /* version */
+        size += sizeof(code_header->defs.length);
+        size += sizeof(code_header->globals.length);
+        size += sizeof(code_header->fields.length);
+        size += sizeof(code_header->statements.length);
+        size += sizeof(code->linenums[0])   * code->linenums.size();
+        size += sizeof(code->columnnums[0]) * code->columnnums.size();
+    } else {
+        size += sizeof(prog_header_t);
+        size += sizeof(prog_section_statement_t) * code->statements.size();
+        size += sizeof(prog_section_def_t)       * code->defs.size();
+        size += sizeof(prog_section_field_t)     * code->fields.size();
+        size += sizeof(prog_section_function_t)  * code->functions.size();
+        size += sizeof(int32_t)                  * code->globals.size();
+        size += 1                                * code->chars.size();
+    }
+    return size;
+}
+
+#define code_size_binary(C, H) code_size_generic((C), (H), false)
+#define code_size_debug(C, H)  code_size_generic((C), (H), true)
+
+static void code_create_header(code_t *code, prog_header_t *code_header, const char *filename, const char *lnofile) {
+    size_t i;
+
+    code_header->statements.offset = sizeof(prog_header_t);
+    code_header->statements.length = code->statements.size();
+    code_header->defs.offset       = code_header->statements.offset + (sizeof(prog_section_statement_t) * code->statements.size());
+    code_header->defs.length       = code->defs.size();
+    code_header->fields.offset     = code_header->defs.offset       + (sizeof(prog_section_def_t)       * code->defs.size());
+    code_header->fields.length     = code->fields.size();
+    code_header->functions.offset  = code_header->fields.offset     + (sizeof(prog_section_field_t)     * code->fields.size());
+    code_header->functions.length  = code->functions.size();
+    code_header->globals.offset    = code_header->functions.offset  + (sizeof(prog_section_function_t)  * code->functions.size());
+    code_header->globals.length    = code->globals.size();
+    code_header->strings.offset    = code_header->globals.offset    + (sizeof(int32_t)                  * code->globals.size());
+    code_header->strings.length    = code->chars.size();
+    code_header->version           = 6;
+    code_header->skip              = 0;
+
+    if (OPTS_OPTION_BOOL(OPTION_FORCECRC))
+        code_header->crc16         = OPTS_OPTION_U16(OPTION_FORCED_CRC);
+    else
+        code_header->crc16         = code->crc;
+    code_header->entfield          = code->entfields;
+
+    if (OPTS_FLAG(DARKPLACES_STRING_TABLE_BUG)) {
+        /* >= + P */
+        code->chars.push_back('\0'); /* > */
+        code->chars.push_back('\0'); /* = */
+        code->chars.push_back('\0'); /* P */
+    }
+
+    /* ensure all data is in LE format */
+    util_swap_header(*code_header);
+    util_swap_statements(code->statements);
+    util_swap_defs_fields(code->defs);
+    util_swap_defs_fields(code->fields);
+    util_swap_functions(code->functions);
+    util_swap_globals(code->globals);
+
+    if (!OPTS_OPTION_BOOL(OPTION_QUIET)) {
+        if (lnofile)
+            con_out("writing '%s' and '%s'...\n", filename, lnofile);
+        else
+            con_out("writing '%s'\n", filename);
+    }
+
+    if (!OPTS_OPTION_BOOL(OPTION_QUIET) &&
+        !OPTS_OPTION_BOOL(OPTION_PP_ONLY))
+    {
+        char buffer[1024];
+        con_out("\nOptimizations:\n");
+        for (i = 0; i < COUNT_OPTIMIZATIONS; ++i) {
+            if (opts_optimizationcount[i]) {
+                util_optimizationtostr(opts_opt_list[i].name, buffer, sizeof(buffer));
+                con_out(
+                    "    %s: %u\n",
+                    buffer,
+                    (unsigned int)opts_optimizationcount[i]
+                );
+            }
+        }
+    }
+}
+
+static void code_stats(const char *filename, const char *lnofile, code_t *code, prog_header_t *code_header) {
+    if (OPTS_OPTION_BOOL(OPTION_QUIET) ||
+        OPTS_OPTION_BOOL(OPTION_PP_ONLY))
+            return;
+
+    con_out("\nFile statistics:\n");
+    con_out("    dat:\n");
+    con_out("        name: %s\n",         filename);
+    con_out("        size: %u (bytes)\n", code_size_binary(code, code_header));
+    con_out("        crc:  0x%04X\n",     code->crc);
+
+    if (lnofile) {
+        con_out("    lno:\n");
+        con_out("        name: %s\n",  lnofile);
+        con_out("        size: %u (bytes)\n",  code_size_debug(code, code_header));
+    }
+
+    con_out("\n");
+}
+
+bool code_write(code_t *code, const char *filename, const char *lnofile) {
+    prog_header_t code_header;
+    FILE *fp = nullptr;
+
+    code_create_header(code, &code_header, filename, lnofile);
+
+    if (lnofile) {
+        uint32_t version = 1;
+
+        fp = fopen(lnofile, "wb");
+        if (!fp)
+            return false;
+
+        util_endianswap(&version,             1,                       sizeof(version));
+        util_endianswap(&code->linenums[0],   code->linenums.size(),   sizeof(code->linenums[0]));
+        util_endianswap(&code->columnnums[0], code->columnnums.size(), sizeof(code->columnnums[0]));
+
+        if (fwrite("LNOF",                          4,                                      1,                          fp) != 1 ||
+            fwrite(&version,                        sizeof(version),                        1,                          fp) != 1 ||
+            fwrite(&code_header.defs.length,        sizeof(code_header.defs.length),        1,                          fp) != 1 ||
+            fwrite(&code_header.globals.length,     sizeof(code_header.globals.length),     1,                          fp) != 1 ||
+            fwrite(&code_header.fields.length,      sizeof(code_header.fields.length),      1,                          fp) != 1 ||
+            fwrite(&code_header.statements.length,  sizeof(code_header.statements.length),  1,                          fp) != 1 ||
+            fwrite(&code->linenums[0],              sizeof(code->linenums[0]),              code->linenums.size(),      fp) != code->linenums.size() ||
+            fwrite(&code->columnnums[0],            sizeof(code->columnnums[0]),            code->columnnums.size(),    fp) != code->columnnums.size())
+        {
+            con_err("failed to write lno file\n");
+        }
+
+        fclose(fp);
+        fp = nullptr;
+    }
+
+    fp = fopen(filename, "wb");
+    if (!fp)
+        return false;
+
+    if (1                       != fwrite(&code_header,         sizeof(prog_header_t)           , 1                      , fp) ||
+        code->statements.size() != fwrite(&code->statements[0], sizeof(prog_section_statement_t), code->statements.size(), fp) ||
+        code->defs.size()       != fwrite(&code->defs[0],       sizeof(prog_section_def_t)      , code->defs.size()      , fp) ||
+        code->fields.size()     != fwrite(&code->fields[0],     sizeof(prog_section_field_t)    , code->fields.size()    , fp) ||
+        code->functions.size()  != fwrite(&code->functions[0],  sizeof(prog_section_function_t) , code->functions.size() , fp) ||
+        code->globals.size()    != fwrite(&code->globals[0],    sizeof(int32_t)                 , code->globals.size()   , fp) ||
+        code->chars.size()      != fwrite(&code->chars[0],      1                               , code->chars.size()     , fp))
+    {
+        fclose(fp);
+        return false;
+    }
+
+    fclose(fp);
+    code_stats(filename, lnofile, code, &code_header);
+    return true;
+}
diff --git a/conout.c b/conout.c
deleted file mode 100644 (file)
index c4c285b..0000000
--- a/conout.c
+++ /dev/null
@@ -1,280 +0,0 @@
-/*
- * Copyright (C) 2012, 2013, 2014, 2015
- *     Dale Weiler
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
- * this software and associated documentation files (the "Software"), to deal in
- * the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is furnished to do
- * so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-#include <stdio.h>
-#include "gmqcc.h"
-
-#define GMQCC_IS_STDOUT(X) ((fs_file_t*)((void*)X) == (fs_file_t*)stdout)
-#define GMQCC_IS_STDERR(X) ((fs_file_t*)((void*)X) == (fs_file_t*)stderr)
-#define GMQCC_IS_DEFINE(X) (GMQCC_IS_STDERR(X) || GMQCC_IS_STDOUT(X))
-
-typedef struct {
-    fs_file_t *handle_err;
-    fs_file_t *handle_out;
-    int        color_err;
-    int        color_out;
-} con_t;
-
-/*
- * We use standard files as default. These can be changed at any time
- * with con_change(F, F)
- */
-static con_t console;
-
-/*
- * Enables color on output if supported.
- * NOTE: The support for checking colors is NULL.  On windows this will
- * always work, on *nix it depends if the term has colors.
- *
- * NOTE: This prevents colored output to piped stdout/err via isatty
- * checks.
- */
-static void con_enablecolor(void) {
-    console.color_err = util_isatty(console.handle_err);
-    console.color_out = util_isatty(console.handle_out);
-}
-
-/*
- * Does a write to the handle with the format string and list of
- * arguments.  This colorizes for windows as well via translate
- * step.
- */
-static int con_write(fs_file_t *handle, const char *fmt, va_list va) {
-    return vfprintf((FILE*)handle, fmt, va);
-}
-
-/**********************************************************************
- * EXPOSED INTERFACE BEGINS
- *********************************************************************/
-
-void con_close() {
-    if (!GMQCC_IS_DEFINE(console.handle_err))
-        fs_file_close(console.handle_err);
-    if (!GMQCC_IS_DEFINE(console.handle_out))
-        fs_file_close(console.handle_out);
-}
-
-void con_color(int state) {
-    if (state)
-        con_enablecolor();
-    else {
-        console.color_err = 0;
-        console.color_out = 0;
-    }
-}
-
-void con_init() {
-    console.handle_err = (fs_file_t*)stderr;
-    console.handle_out = (fs_file_t*)stdout;
-    con_enablecolor();
-}
-
-void con_reset() {
-    con_close();
-    con_init ();
-}
-
-/*
- * This is clever, say you want to change the console to use two
- * files for out/err.  You pass in two strings, it will properly
- * close the existing handles (if they're not std* handles) and
- * open them.  Now say you want TO use stdout and stderr, this
- * allows you to do that so long as you cast them to (char*).
- * Say you need stdout for out, but want a file for error, you can
- * do this too, just cast the stdout for (char*) and stick to a
- * string for the error file.
- */
-int con_change(const char *out, const char *err) {
-    con_close();
-
-    if (!out) out = (const char *)((!console.handle_out) ? (fs_file_t*)stdout : console.handle_out);
-    if (!err) err = (const char *)((!console.handle_err) ? (fs_file_t*)stderr : console.handle_err);
-
-    if (GMQCC_IS_DEFINE(out)) {
-        console.handle_out = (fs_file_t*)(GMQCC_IS_STDOUT(out) ? stdout : stderr);
-        con_enablecolor();
-    } else if (!(console.handle_out = fs_file_open(out, "w"))) return 0;
-
-    if (GMQCC_IS_DEFINE(err)) {
-        console.handle_err = (fs_file_t*)(GMQCC_IS_STDOUT(err) ? stdout : stderr);
-        con_enablecolor();
-    } else if (!(console.handle_err = fs_file_open(err, "w"))) return 0;
-
-    return 1;
-}
-
-/*
- * Defaultizer because stdio.h shouldn't be used anywhere except here
- * and inside file.c To prevent mis-match of wrapper-interfaces.
- */
-fs_file_t *con_default_out() {
-    return (fs_file_t*)(console.handle_out = (fs_file_t*)stdout);
-}
-fs_file_t *con_default_err() {
-    return (fs_file_t*)(console.handle_err = (fs_file_t*)stderr);
-}
-
-int con_verr(const char *fmt, va_list va) {
-    return con_write(console.handle_err, fmt, va);
-}
-int con_vout(const char *fmt, va_list va) {
-    return con_write(console.handle_out, fmt, va);
-}
-
-/*
- * Standard stdout/stderr printf functions used generally where they need
- * to be used.
- */
-int con_err(const char *fmt, ...) {
-    va_list  va;
-    int      ln = 0;
-    va_start(va, fmt);
-    con_verr(fmt, va);
-    va_end  (va);
-    return   ln;
-}
-int con_out(const char *fmt, ...) {
-    va_list  va;
-    int      ln = 0;
-    va_start(va, fmt);
-    con_vout(fmt, va);
-    va_end  (va);
-    return   ln;
-}
-
-/*
- * Utility console message writes for lexer contexts.  These will allow
- * for reporting of file:line based on lexer context, These are used
- * heavily in the parser/ir/ast.
- */
-static void con_vprintmsg_c(int level, const char *name, size_t line, size_t column, const char *msgtype, const char *msg, va_list ap, const char *condname) {
-    /* color selection table */
-    static int sel[] = {
-        CON_WHITE,
-        CON_CYAN,
-        CON_RED
-    };
-
-    int  err                             = !!(level == LVL_ERROR);
-    int  color                           = (err) ? console.color_err : console.color_out;
-    int (*print) (const char *, ...)     = (err) ? &con_err          : &con_out;
-    int (*vprint)(const char *, va_list) = (err) ? &con_verr         : &con_vout;
-
-    if (color)
-        print("\033[0;%dm%s:%d:%d: \033[0;%dm%s: \033[0m", CON_CYAN, name, (int)line, (int)column, sel[level], msgtype);
-    else
-        print("%s:%d:%d: %s: ", name, (int)line, (int)column, msgtype);
-
-    vprint(msg, ap);
-    if (condname)
-        print(" [%s]\n", condname);
-    else
-        print("\n");
-}
-
-void con_vprintmsg(int level, const char *name, size_t line, size_t column, const char *msgtype, const char *msg, va_list ap) {
-    con_vprintmsg_c(level, name, line, column, msgtype, msg, ap, NULL);
-}
-
-void con_printmsg(int level, const char *name, size_t line, size_t column, const char *msgtype, const char *msg, ...) {
-    va_list   va;
-    va_start(va, msg);
-    con_vprintmsg(level, name, line, column, msgtype, msg, va);
-    va_end  (va);
-}
-
-void con_cvprintmsg(lex_ctx_t ctx, int lvl, const char *msgtype, const char *msg, va_list ap) {
-    con_vprintmsg(lvl, ctx.file, ctx.line, ctx.column, msgtype, msg, ap);
-}
-
-void con_cprintmsg(lex_ctx_t ctx, int lvl, const char *msgtype, const char *msg, ...) {
-    va_list   va;
-    va_start(va, msg);
-    con_cvprintmsg(ctx, lvl, msgtype, msg, va);
-    va_end  (va);
-}
-
-/* General error interface: TODO seperate as part of the compiler front-end */
-size_t compile_errors   = 0;
-size_t compile_warnings = 0;
-size_t compile_Werrors  = 0;
-static lex_ctx_t first_werror;
-
-void compile_show_werrors()
-{
-    con_cprintmsg(first_werror, LVL_ERROR, "first warning", "was here");
-}
-
-void vcompile_error(lex_ctx_t ctx, const char *msg, va_list ap)
-{
-    ++compile_errors;
-    con_cvprintmsg(ctx, LVL_ERROR, "error", msg, ap);
-}
-
-void compile_error(lex_ctx_t ctx, const char *msg, ...)
-{
-    va_list ap;
-    va_start(ap, msg);
-    vcompile_error(ctx, msg, ap);
-    va_end(ap);
-}
-
-bool GMQCC_WARN vcompile_warning(lex_ctx_t ctx, int warntype, const char *fmt, va_list ap)
-{
-    const char *msgtype = "warning";
-    int         lvl     = LVL_WARNING;
-    char        warn_name[1024];
-
-    if (!OPTS_WARN(warntype))
-        return false;
-
-    warn_name[0] = '-';
-    warn_name[1] = 'W';
-    (void)util_strtononcmd(opts_warn_list[warntype].name, warn_name+2, sizeof(warn_name)-2);
-
-    ++compile_warnings;
-    if (OPTS_WERROR(warntype)) {
-        if (!compile_Werrors)
-            first_werror = ctx;
-        ++compile_Werrors;
-        msgtype = "Werror";
-        if (OPTS_FLAG(BAIL_ON_WERROR)) {
-            msgtype = "error";
-            ++compile_errors;
-        }
-        lvl = LVL_ERROR;
-    }
-
-    con_vprintmsg_c(lvl, ctx.file, ctx.line, ctx.column, msgtype, fmt, ap, warn_name);
-
-    return OPTS_WERROR(warntype) && OPTS_FLAG(BAIL_ON_WERROR);
-}
-
-bool GMQCC_WARN compile_warning(lex_ctx_t ctx, int warntype, const char *fmt, ...)
-{
-    bool r;
-    va_list ap;
-    va_start(ap, fmt);
-    r = vcompile_warning(ctx, warntype, fmt, ap);
-    va_end(ap);
-    return r;
-}
diff --git a/conout.cpp b/conout.cpp
new file mode 100644 (file)
index 0000000..5eb81db
--- /dev/null
@@ -0,0 +1,226 @@
+#include <stdio.h>
+#include "gmqcc.h"
+
+#define GMQCC_IS_STDOUT(X) ((X) == stdout)
+#define GMQCC_IS_STDERR(X) ((X) == stderr)
+#define GMQCC_IS_DEFINE(X) (GMQCC_IS_STDERR(X) || GMQCC_IS_STDOUT(X))
+
+struct con_t {
+    FILE *handle_err;
+    FILE *handle_out;
+    int color_err;
+    int color_out;
+};
+
+static con_t console;
+
+/*
+ * Enables color on output if supported.
+ * NOTE: The support for checking colors is nullptr.  On windows this will
+ * always work, on *nix it depends if the term has colors.
+ *
+ * NOTE: This prevents colored output to piped stdout/err via isatty
+ * checks.
+ */
+static void con_enablecolor(void) {
+    console.color_err = util_isatty(console.handle_err);
+    console.color_out = util_isatty(console.handle_out);
+}
+
+/*
+ * Does a write to the handle with the format string and list of
+ * arguments.  This colorizes for windows as well via translate
+ * step.
+ */
+static int con_write(FILE *handle, const char *fmt, va_list va) {
+    return vfprintf(handle, fmt, va);
+}
+
+/**********************************************************************
+ * EXPOSED INTERFACE BEGINS
+ *********************************************************************/
+
+void con_close() {
+    if (!GMQCC_IS_DEFINE(console.handle_err))
+        fclose(console.handle_err);
+    if (!GMQCC_IS_DEFINE(console.handle_out))
+        fclose(console.handle_out);
+}
+
+void con_color(int state) {
+    if (state)
+        con_enablecolor();
+    else {
+        console.color_err = 0;
+        console.color_out = 0;
+    }
+}
+
+void con_init() {
+    console.handle_err = stderr;
+    console.handle_out = stdout;
+    con_enablecolor();
+}
+
+void con_reset() {
+    con_close();
+    con_init();
+}
+
+/*
+ * Defaultizer because stdio.h shouldn't be used anywhere except here
+ * and inside file.c To prevent mis-match of wrapper-interfaces.
+ */
+FILE *con_default_out() {
+    return console.handle_out = stdout;
+}
+
+FILE *con_default_err() {
+    return console.handle_err = stderr;
+}
+
+int con_verr(const char *fmt, va_list va) {
+    return con_write(console.handle_err, fmt, va);
+}
+int con_vout(const char *fmt, va_list va) {
+    return con_write(console.handle_out, fmt, va);
+}
+
+/*
+ * Standard stdout/stderr printf functions used generally where they need
+ * to be used.
+ */
+int con_err(const char *fmt, ...) {
+    va_list va;
+    int ln = 0;
+    va_start(va, fmt);
+    con_verr(fmt, va);
+    va_end(va);
+    return ln;
+}
+int con_out(const char *fmt, ...) {
+    va_list va;
+    int ln = 0;
+    va_start(va, fmt);
+    con_vout(fmt, va);
+    va_end (va);
+    return ln;
+}
+
+/*
+ * Utility console message writes for lexer contexts.  These will allow
+ * for reporting of file:line based on lexer context, These are used
+ * heavily in the parser/ir/ast.
+ */
+static void con_vprintmsg_c(int level, const char *name, size_t line, size_t column, const char *msgtype, const char *msg, va_list ap, const char *condname) {
+    /* color selection table */
+    static int sel[] = {
+        CON_WHITE,
+        CON_CYAN,
+        CON_RED
+    };
+
+    int  err                             = !!(level == LVL_ERROR);
+    int  color                           = (err) ? console.color_err : console.color_out;
+    int (*print) (const char *, ...)     = (err) ? &con_err          : &con_out;
+    int (*vprint)(const char *, va_list) = (err) ? &con_verr         : &con_vout;
+
+    if (color)
+        print("\033[0;%dm%s:%d:%d: \033[0;%dm%s: \033[0m", CON_CYAN, name, (int)line, (int)column, sel[level], msgtype);
+    else
+        print("%s:%d:%d: %s: ", name, (int)line, (int)column, msgtype);
+
+    vprint(msg, ap);
+    if (condname)
+        print(" [%s]\n", condname);
+    else
+        print("\n");
+}
+
+void con_vprintmsg(int level, const char *name, size_t line, size_t column, const char *msgtype, const char *msg, va_list ap) {
+    con_vprintmsg_c(level, name, line, column, msgtype, msg, ap, nullptr);
+}
+
+void con_printmsg(int level, const char *name, size_t line, size_t column, const char *msgtype, const char *msg, ...) {
+    va_list   va;
+    va_start(va, msg);
+    con_vprintmsg(level, name, line, column, msgtype, msg, va);
+    va_end  (va);
+}
+
+void con_cvprintmsg(lex_ctx_t ctx, int lvl, const char *msgtype, const char *msg, va_list ap) {
+    con_vprintmsg(lvl, ctx.file, ctx.line, ctx.column, msgtype, msg, ap);
+}
+
+void con_cprintmsg(lex_ctx_t ctx, int lvl, const char *msgtype, const char *msg, ...) {
+    va_list   va;
+    va_start(va, msg);
+    con_cvprintmsg(ctx, lvl, msgtype, msg, va);
+    va_end  (va);
+}
+
+/* General error interface: TODO seperate as part of the compiler front-end */
+size_t compile_errors   = 0;
+size_t compile_warnings = 0;
+size_t compile_Werrors  = 0;
+static lex_ctx_t first_werror;
+
+void compile_show_werrors()
+{
+    con_cprintmsg(first_werror, LVL_ERROR, "first warning", "was here");
+}
+
+void vcompile_error(lex_ctx_t ctx, const char *msg, va_list ap)
+{
+    ++compile_errors;
+    con_cvprintmsg(ctx, LVL_ERROR, "error", msg, ap);
+}
+
+void compile_error_(lex_ctx_t ctx, const char *msg, ...)
+{
+    va_list ap;
+    va_start(ap, msg);
+    vcompile_error(ctx, msg, ap);
+    va_end(ap);
+}
+
+bool GMQCC_WARN vcompile_warning(lex_ctx_t ctx, int warntype, const char *fmt, va_list ap)
+{
+    const char *msgtype = "warning";
+    int         lvl     = LVL_WARNING;
+    char        warn_name[1024];
+
+    if (!OPTS_WARN(warntype))
+        return false;
+
+    warn_name[0] = '-';
+    warn_name[1] = 'W';
+    (void)util_strtononcmd(opts_warn_list[warntype].name, warn_name+2, sizeof(warn_name)-2);
+
+    ++compile_warnings;
+    if (OPTS_WERROR(warntype)) {
+        if (!compile_Werrors)
+            first_werror = ctx;
+        ++compile_Werrors;
+        msgtype = "Werror";
+        if (OPTS_FLAG(BAIL_ON_WERROR)) {
+            msgtype = "error";
+            ++compile_errors;
+        }
+        lvl = LVL_ERROR;
+    }
+
+    con_vprintmsg_c(lvl, ctx.file, ctx.line, ctx.column, msgtype, fmt, ap, warn_name);
+
+    return OPTS_WERROR(warntype) && OPTS_FLAG(BAIL_ON_WERROR);
+}
+
+bool GMQCC_WARN compile_warning_(lex_ctx_t ctx, int warntype, const char *fmt, ...)
+{
+    bool r;
+    va_list ap;
+    va_start(ap, fmt);
+    r = vcompile_warning(ctx, warntype, fmt, ap);
+    va_end(ap);
+    return r;
+}
diff --git a/correct.c b/correct.c
deleted file mode 100644 (file)
index 1f7a381..0000000
--- a/correct.c
+++ /dev/null
@@ -1,548 +0,0 @@
-/*
- * Copyright (C) 2012, 2013, 2014, 2015
- *     Dale Weiler
- *     Wolfgang Bumiller
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
- * this software and associated documentation files (the "Software"), to deal in
- * the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is furnished to do
- * so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-#include <string.h>
-#include "gmqcc.h"
-
-/*
- * This is a very clever method for correcting mistakes in QuakeC code
- * most notably when invalid identifiers are used or inproper assignments;
- * we can proprly lookup in multiple dictonaries (depening on the rules
- * of what the task is trying to acomplish) to find the best possible
- * match.
- *
- *
- * A little about how it works, and probability theory:
- *
- *  When given an identifier (which we will denote I), we're essentially
- *  just trying to choose the most likely correction for that identifier.
- *  (the actual "correction" can very well be the identifier itself).
- *  There is actually no way to know for sure that certian identifers
- *  such as "lates", need to be corrected to "late" or "latest" or any
- *  other permutations that look lexically the same.  This is why we
- *  must advocate the usage of probabilities.  This means that instead of
- *  just guessing, instead we're trying to find the correction for C,
- *  out of all possible corrections that maximizes the probability of C
- *  for the original identifer I.
- *
- *  Thankfully there exists some theroies for probalistic interpretations
- *  of data.  Since we're operating on two distictive intepretations, the
- *  transposition from I to C. We need something that can express how much
- *  degree of I should rationally change to become C.  this is called the
- *  Bayesian interpretation. You can read more about it from here:
- *  http://www.celiagreen.com/charlesmccreery/statistics/bayestutorial.pdf
- *  (which is probably the only good online documentation for bayes theroy
- *  no lie.  Everything else just sucks ..)
- *
- *  Bayes' Thereom suggests something like the following:
- *      AC P(I|C) P(C) / P(I)
- *
- *  However since P(I) is the same for every possibility of I, we can
- *  completley ignore it giving just:
- *      AC P(I|C) P(C)
- *
- *  This greatly helps visualize how the parts of the expression are performed
- *  there is essentially three, from right to left we perform the following:
- *
- *  1: P(C), the probability that a proposed correction C will stand on its
- *     own.  This is called the language model.
- *
- *  2: P(I|C), the probability that I would be used, when the programmer
- *     really meant C.  This is the error model.
- *
- *  3: AC, the control mechanisim, an enumerator if you will, one that
- *     enumerates all feasible values of C, to determine the one that
- *     gives the greatest probability score.
- *
- *  In reality the requirement for a more complex expression involving
- *  two seperate models is considerably a waste.  But one must recognize
- *  that P(C|I) is already conflating two factors.  It's just much simpler
- *  to seperate the two models and deal with them explicitaly.  To properly
- *  estimate P(C|I) you have to consider both the probability of C and
- *  probability of the transposition from C to I.  It's simply much more
- *  cleaner, and direct to seperate the two factors.
- *
- *  Research tells us that 80% to 95% of all spelling errors have an edit
- *  distance no greater than one.  Knowing this we can optimize for most
- *  cases of mistakes without taking a performance hit.  Which is what we
- *  base longer edit distances off of.  Opposed to the original method of
- *  I had concieved of checking everything.
- *
- * A little information on additional algorithms used:
- *
- *   Initially when I implemented this corrector, it was very slow.
- *   Need I remind you this is essentially a brute force attack on strings,
- *   and since every transformation requires dynamic memory allocations,
- *   you can easily imagine where most of the runtime conflated.  Yes
- *   It went right to malloc.  More than THREE MILLION malloc calls are
- *   performed for an identifier about 16 bytes long.  This was such a
- *   shock to me.  A forward allocator (or as some call it a bump-point
- *   allocator, or just a memory pool) was implemented. To combat this.
- *
- *   But of course even other factors were making it slow.  Initially
- *   this used a hashtable.  And hashtables have a good constant lookup
- *   time complexity.  But the problem wasn't in the hashtable, it was
- *   in the hashing (despite having one of the fastest hash functions
- *   known).  Remember those 3 million mallocs? Well for every malloc
- *   there is also a hash.  After 3 million hashes .. you start to get
- *   very slow.  To combat this I had suggested burst tries to Blub.
- *   The next day he had implemented them. Sure enough this brought
- *   down the runtime by a factor > 100%
- *
- *   The trie initially was designed to work on all strings, but later it
- *   became aparent that not only was this not a requirement. It was also
- *   slowing down get/sets' for the trie.  To fully understand, only
- *   correct_alpha needs to be understood by the trie system, knowing this
- *   We can combat the slowness using a very clever but evil optimization.
- *   By Setting a fixed sized amount of branches for the trie using a
- *   char-to-index map into the branches. We've complelty made the trie
- *   accesses entierly constant in lookup time.  No really, a lookup is
- *   literally trie[str[0]] [str[1]] [2] .... .value.
- *
- *
- * Future Work (If we really need it)
- *
- *   Currently we can only distinguish one source of error in the
- *   language model we use.  This could become an issue for identifiers
- *   that have close colliding rates, e.g colate->coat yields collate.
- *
- *   Currently the error model has been fairly trivial, the smaller the
- *   edit distance the smaller the error.  This usually causes some un-
- *   expected problems. e.g reciet->recite yields recipt.  For QuakeC
- *   this could become a problem when lots of identifiers are involved.
- */
-
-
-#define CORRECT_POOL_SIZE (128*1024*1024)
-/*
- * A forward allcator for the corrector.  This corrector requires a lot
- * of allocations.  This forward allocator combats all those allocations
- * and speeds us up a little.  It also saves us space in a way since each
- * allocation isn't wasting a little header space for when NOTRACK isn't
- * defined.
- */
-static unsigned char **correct_pool_data = NULL;
-static unsigned char  *correct_pool_this = NULL;
-static size_t          correct_pool_addr = 0;
-
-static GMQCC_INLINE void correct_pool_new(void) {
-    correct_pool_addr = 0;
-    correct_pool_this = (unsigned char *)mem_a(CORRECT_POOL_SIZE);
-
-    vec_push(correct_pool_data, correct_pool_this);
-}
-
-static GMQCC_INLINE void *correct_pool_alloc(size_t bytes) {
-    void *data;
-    if (correct_pool_addr + bytes>= CORRECT_POOL_SIZE)
-        correct_pool_new();
-
-    data               = (void*)correct_pool_this;
-    correct_pool_this += bytes;
-    correct_pool_addr += bytes;
-    return data;
-}
-
-static GMQCC_INLINE void correct_pool_delete(void) {
-    size_t i;
-    for (i = 0; i < vec_size(correct_pool_data); ++i)
-        mem_d(correct_pool_data[i]);
-
-    correct_pool_data = NULL;
-    correct_pool_this = NULL;
-    correct_pool_addr = 0;
-}
-
-
-static GMQCC_INLINE char *correct_pool_claim(const char *data) {
-    char *claim = util_strdup(data);
-    return claim;
-}
-
-/*
- * _ is valid in identifiers. I've yet to implement numerics however
- * because they're only valid after the first character is of a _, or
- * alpha character.
- */
-static const char correct_alpha[] = "abcdefghijklmnopqrstuvwxyz"
-                                    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
-                                    "_"; /* TODO: Numbers ... */
-
-static const size_t correct_alpha_index[0x80] = {
-     0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
-     0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
-     0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
-     0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
-     0,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
-    15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,  0,  0,  0,  0, 52,
-     0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
-    41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,  0,  0,  0,  0,  0
-};
-
-/*
- * A fast space efficent trie for a dictionary of identifiers.  This is
- * faster than a hashtable for one reason.  A hashtable itself may have
- * fast constant lookup time, but the hash itself must be very fast. We
- * have one of the fastest hash functions for strings, but if you do a
- * lost of hashing (which we do, almost 3 million hashes per identifier)
- * a hashtable becomes slow.
- */
-correct_trie_t* correct_trie_new() {
-    correct_trie_t *t = (correct_trie_t*)mem_a(sizeof(correct_trie_t));
-    t->value   = NULL;
-    t->entries = NULL;
-    return t;
-}
-
-static GMQCC_INLINE void correct_trie_del_sub(correct_trie_t *t) {
-    size_t i;
-    if (!t->entries)
-        return;
-    for (i = 0; i < sizeof(correct_alpha)-1; ++i) {
-        correct_trie_del_sub(&t->entries[i]);
-    }
-    mem_d(t->entries);
-}
-
-static GMQCC_INLINE void correct_trie_del(correct_trie_t *t) {
-    size_t i;
-    if (t->entries) {
-        for (i = 0; i < sizeof(correct_alpha)-1; ++i)
-            correct_trie_del_sub(&t->entries[i]);
-        mem_d(t->entries);
-    }
-    mem_d(t);
-}
-
-static GMQCC_INLINE void* correct_trie_get(const correct_trie_t *t, const char *key) {
-    const unsigned char *data = (const unsigned char*)key;
-
-    while (*data) {
-        if (!t->entries)
-            return NULL;
-        t = t->entries + correct_alpha_index[*data];
-        ++data;
-    }
-    return t->value;
-}
-
-static GMQCC_INLINE void correct_trie_set(correct_trie_t *t, const char *key, void * const value) {
-    const unsigned char *data = (const unsigned char*)key;
-    while (*data) {
-        if (!t->entries) {
-            t->entries = (correct_trie_t*)mem_a(sizeof(correct_trie_t)*(sizeof(correct_alpha)-1));
-            memset(t->entries, 0, sizeof(correct_trie_t)*(sizeof(correct_alpha)-1));
-        }
-        t = t->entries + correct_alpha_index[*data];
-        ++data;
-    }
-    t->value = value;
-}
-
-
-/*
- * Implementation of the corrector algorithm commences. A very efficent
- * brute-force attack (thanks to tries and mempool :-)).
- */
-static GMQCC_INLINE size_t *correct_find(correct_trie_t *table, const char *word) {
-    return (size_t*)correct_trie_get(table, word);
-}
-
-static GMQCC_INLINE bool correct_update(correct_trie_t* *table, const char *word) {
-    size_t *data = correct_find(*table, word);
-    if (!data)
-        return false;
-
-    (*data)++;
-    return true;
-}
-
-void correct_add(correct_trie_t* table, size_t ***size, const char *ident) {
-    size_t     *data = NULL;
-    const char *add  = ident;
-
-    if (!correct_update(&table, add)) {
-        data  = (size_t*)mem_a(sizeof(size_t));
-        *data = 1;
-
-        vec_push((*size), data);
-        correct_trie_set(table, add, data);
-    }
-}
-
-void correct_del(correct_trie_t* dictonary, size_t **data) {
-    size_t       i;
-    const size_t vs = vec_size(data);
-
-    for (i = 0; i < vs; i++)
-        mem_d(data[i]);
-
-    vec_free(data);
-    correct_trie_del(dictonary);
-}
-
-/*
- * correcting logic for the following forms of transformations:
- *  1) deletion
- *  2) transposition
- *  3) alteration
- *  4) insertion
- *
- * These functions could take an additional size_t **size paramater
- * and store back the results of their new length in an array that
- * is the same as **array for the memcmp in correct_exists. I'm just
- * not able to figure out how to do that just yet.  As my brain is
- * not in the mood to figure out that logic.  This is a reminder to
- * do it, or for someone else to :-) correct_edit however would also
- * need to take a size_t ** to carry it along (would all the argument
- * overhead be worth it?)
- */
-static GMQCC_INLINE size_t correct_deletion(const char *ident, char **array) {
-    size_t       itr = 0;
-    const size_t len = strlen(ident);
-
-    for (; itr < len; itr++) {
-        char *a = (char*)correct_pool_alloc(len+1);
-        memcpy(a, ident, itr);
-        memcpy(a + itr, ident + itr + 1, len - itr);
-        array[itr] = a;
-    }
-
-    return itr;
-}
-
-static GMQCC_INLINE size_t correct_transposition(const char *ident, char **array) {
-    size_t       itr = 0;
-    const size_t len = strlen(ident);
-
-    for (; itr < len - 1; itr++) {
-        char  tmp;
-        char *a = (char*)correct_pool_alloc(len+1);
-        memcpy(a, ident, len+1);
-        tmp      = a[itr];
-        a[itr  ] = a[itr+1];
-        a[itr+1] = tmp;
-        array[itr] = a;
-    }
-
-    return itr;
-}
-
-static GMQCC_INLINE size_t correct_alteration(const char *ident, char **array) {
-    size_t       itr = 0;
-    size_t       jtr = 0;
-    size_t       ktr = 0;
-    const size_t len = strlen(ident);
-
-    for (; itr < len; itr++) {
-        for (jtr = 0; jtr < sizeof(correct_alpha)-1; jtr++, ktr++) {
-            char *a = (char*)correct_pool_alloc(len+1);
-            memcpy(a, ident, len+1);
-            a[itr] = correct_alpha[jtr];
-            array[ktr] = a;
-        }
-    }
-
-    return ktr;
-}
-
-static GMQCC_INLINE size_t correct_insertion(const char *ident, char **array) {
-    size_t       itr = 0;
-    size_t       jtr = 0;
-    const size_t len = strlen(ident);
-
-    for (; itr <= len; itr++) {
-        for (jtr = 0; jtr < sizeof(correct_alpha)-1; jtr++) {
-            char *a = (char*)correct_pool_alloc(len+2);
-            memcpy(a, ident, itr);
-            memcpy(a + itr + 1, ident + itr, len - itr + 1);
-            a[itr] = correct_alpha[jtr];
-            array[itr * (sizeof(correct_alpha)-1) + jtr] = a;
-        }
-    }
-
-    return (len+1)*(sizeof(correct_alpha)-1);
-}
-
-static GMQCC_INLINE size_t correct_size(const char *ident) {
-    /*
-     * deletion      = len
-     * transposition = len - 1
-     * alteration    = len * sizeof(correct_alpha)
-     * insertion     = (len + 1) * sizeof(correct_alpha)
-     */
-
-    register size_t len = strlen(ident);
-    return (len) + (len - 1) + (len * (sizeof(correct_alpha)-1)) + ((len + 1) * (sizeof(correct_alpha)-1));
-}
-
-static GMQCC_INLINE char **correct_edit(const char *ident, size_t **lens) {
-    size_t next;
-    size_t size = correct_size(ident);
-    char **find = (char**)correct_pool_alloc(size * sizeof(char*));
-
-    if (!find || !(*lens = (size_t*)correct_pool_alloc(size * sizeof(size_t))))
-        return NULL;
-
-    next  = correct_deletion     (ident, find);
-    next += correct_transposition(ident, find+next);
-    next += correct_alteration   (ident, find+next);
-    /*****/ correct_insertion    (ident, find+next);
-
-    /* precompute lengths */
-    for (next = 0; next < size; next++)
-        (*lens)[next] = strlen(find[next]);
-
-    return find;
-}
-
-static GMQCC_INLINE int correct_exist(char **array, register size_t *sizes, size_t rows, char *ident, register size_t len) {
-    size_t itr;
-    for (itr = 0; itr < rows; itr++) {
-        /*
-         * We can save tons of calls to memcmp if we simply ignore comparisions
-         * that we know cannot contain the same length.
-         */
-        if (sizes[itr] == len && !memcmp(array[itr], ident, len))
-            return 1;
-    }
-
-    return 0;
-}
-
-static GMQCC_INLINE char **correct_known_resize(char **res, size_t *allocated, size_t size) {
-    size_t oldallocated = *allocated;
-    char **out;
-    if (size < oldallocated)
-        return res;
-
-    out = (char**)correct_pool_alloc(sizeof(*res) * oldallocated + 32);
-    memcpy(out, res, sizeof(*res) * oldallocated);
-
-    *allocated += 32;
-    return out;
-}
-
-static char **correct_known(correction_t *corr, correct_trie_t* table, char **array, size_t rows, size_t *next) {
-    size_t itr = 0;
-    size_t jtr = 0;
-    size_t len = 0;
-    size_t row = 0;
-    size_t nxt = 8;
-    char   **res = (char**)correct_pool_alloc(sizeof(char *) * nxt);
-    char   **end = NULL;
-    size_t  *bit = NULL;
-
-    for (; itr < rows; itr++) {
-        if (!array[itr][0])
-            continue;
-        if (vec_size(corr->edits) > itr+1) {
-            end = corr->edits[itr+1];
-            bit = corr->lens [itr+1];
-        } else {
-            end = correct_edit(array[itr], &bit);
-            vec_push(corr->edits, end);
-            vec_push(corr->lens,  bit);
-        }
-        row = correct_size(array[itr]);
-
-        for (jtr = 0; jtr < row; jtr++) {
-            if (correct_find(table, end[jtr]) && !correct_exist(res, bit, len, end[jtr], bit[jtr])) {
-                res        = correct_known_resize(res, &nxt, len+1);
-                res[len++] = end[jtr];
-            }
-        }
-    }
-
-    *next = len;
-    return res;
-}
-
-static GMQCC_INLINE char *correct_maximum(correct_trie_t* table, char **array, size_t rows) {
-    char   *str = NULL;
-    size_t *itm = NULL;
-    size_t  itr = 0;
-    size_t  top = 0;
-
-    for (; itr < rows; itr++) {
-        if ((itm = correct_find(table, array[itr])) && (*itm > top)) {
-            top = *itm;
-            str = array[itr];
-        }
-    }
-
-    return str;
-}
-
-/*
- * This is the exposed interface:
- * takes a table for the dictonary a vector of sizes (used for internal
- * probability calculation), and an identifier to "correct".
- */
-void correct_init(correction_t *c)
-{
-    correct_pool_new();
-    c->edits = NULL;
-    c->lens  = NULL;
-}
-
-void correct_free(correction_t *c)
-{
-    vec_free(c->edits);
-    vec_free(c->lens);
-    correct_pool_delete();
-}
-
-char *correct_str(correction_t *corr, correct_trie_t* table, const char *ident) {
-    char **e1      = NULL;
-    char **e2      = NULL;
-    char  *e1ident = NULL;
-    char  *e2ident = NULL;
-    size_t e1rows  = 0;
-    size_t e2rows  = 0;
-    size_t *bits   = NULL;
-
-    /* needs to be allocated for free later */
-    if (correct_find(table, ident))
-        return correct_pool_claim(ident);
-
-    if ((e1rows = correct_size(ident))) {
-        if (vec_size(corr->edits) > 0)
-            e1 = corr->edits[0];
-        else {
-            e1 = correct_edit(ident, &bits);
-            vec_push(corr->edits, e1);
-            vec_push(corr->lens,  bits);
-        }
-
-        if ((e1ident = correct_maximum(table, e1, e1rows)))
-            return correct_pool_claim(e1ident);
-    }
-
-    e2 = correct_known(corr, table, e1, e1rows, &e2rows);
-    if (e2rows && ((e2ident = correct_maximum(table, e2, e2rows))))
-        return correct_pool_claim(e2ident);
-
-
-    return util_strdup(ident);
-}
diff --git a/distro/Makefile b/distro/Makefile
deleted file mode 100644 (file)
index 3436cca..0000000
+++ /dev/null
@@ -1,92 +0,0 @@
-DROPBOX := dropbox_uploader.sh
-UNAME   := $(shell uname -m)
-DOWNLOAD:= ../doc/html/download.c
-BRANCH  := $(shell git rev-parse --abbrev-ref HEAD)
-ifneq ($(shell uname -m), x86_64)
-    $(error Cannot build packages without an x86_64 capable CPU)
-endif
-
-.NOTPARALLEL: base
-.NOTPARALLEL: upload
-
-HEADER=\e[5;32;40m
-RESET=\e[0;37;40m
-INFO=\e[5;33;40m
-
-base:
-       @echo -e "\n$(HEADER)Building Debian packages ...$(RESET)"
-       @echo -e "   $(INFO)=> building 64-bit package$(RESET)"
-       @$(MAKE) -C deb/ >/dev/null
-       @echo -e "   $(INFO)=> building 32-bit package$(RESET)"
-       @$(MAKE) -C deb/ CARCH=i686 OPTIONAL_CFLAGS=-m32 OPTIONAL_LDFLAGS=-m32 >/dev/null
-       
-       @echo -e "\n$(HEADER)Building ArchLinux packages ...$(RESET)"
-       @echo -e "   $(INFO)=> building 64-bit package$(RESET)"
-       @$(MAKE) -C archlinux/this/ >/dev/null
-       @echo -e "   $(INFO)=> building 32-bit package$(RESET)"
-       @$(MAKE) -C archlinux/this/ CARCH=i686 OPTIONAL_CFLAGS=-m32 OPTIONAL_LDFLAGS=-m32 >/dev/null
-
-       @echo -e "\n$(HEADER)Building ArchBSD packages ...$(RESET)"
-       @echo -e "   $(INFO)=> building 64-bit package$(RESET)"
-       @$(MAKE) -C archbsd/this/ >/dev/null
-       @echo -e "   $(INFO)=> building 32-bit package$(RESET)"
-       @$(MAKE) -C archbsd/this/ CARCH=i686 OPTIONAL_CFLAGS=-m32 OPTIONAL_LDFLAGS=-m32 >/dev/null
-       
-       @echo -e "\n$(HEADER)Building Slackware packages ...$(RESET)"
-       @echo -e "   $(INFO)=> building 64-bit package$(RESET)"
-       @$(MAKE) -C slackware/this/ >/dev/null
-       @echo -e "   $(INFO)=> building 32-bit package$(RESET)"
-       @$(MAKE) -C slackware/this/ CARCH=i686 OPTIONAL_CFLAGS=-m32 OPTIONAL_LDFLAGS=-m32 >/dev/null
-
-       @echo -e "\n$(HEADER)Building Fedora packages ...$(RESET)"
-       @echo -e "   $(INFO)=> building 64-bit package$(RESET)"
-       @$(MAKE) -C fedora/this/ >/dev/null
-
-       @echo -e "\n\$(HEADER)Building Windows packages ...$(RESET)"
-       @echo -e "   $(INFO)=> building 64-bit package$(RESET)"
-       @$(MAKE) -C win64/ >/dev/null
-       @echo -e "   $(INFO)=> building 32-bit package$(RESET)"
-       @$(MAKE) -C win32/ >/dev/null
-       
-       @rm -rf pkgs/
-       @mkdir pkgs/
-       @mv deb/*.deb ./pkgs/
-       @mv archlinux/this/*pkg.tar.xz ./pkgs/
-       @mv archbsd/this/*pkg.tar.xz ./pkgs/
-       @mv win32/*.zip ./pkgs/
-       @mv win64/*.zip ./pkgs/
-       @mv slackware/this/*.txz ./pkgs/
-       @mv fedora/this/*.rpm ./pkgs/
-       
-       @echo -e "\n\n$(HEADER)Completed:$(RESET)"
-       @find ./pkgs/ -type f -regex ".*/.*\.\(xz\|deb\|zip\|txz\|rpm\)" -exec echo -e "  $(INFO)=>$(RESET) {}" \;
-       
-upload:
-       @echo "APPKEY:76vh3q42hnvmzm3" > dropbox_config
-       @echo "APPSECRET:tmeecht2cmh72xa" >> dropbox_config
-       @echo "ACCESS_LEVEL:sandbox" >> dropbox_config
-       @echo "OAUTH_ACCESS_TOKEN:w0bxzf0dft8edfq" >> dropbox_config
-       @echo "OAUTH_ACCESS_TOKEN_SECRET:9vosx7x8gy4kgjk" >> dropbox_config
-       @wget -q "http://raw.github.com/andreafabrizi/Dropbox-Uploader/master/dropbox_uploader.sh"
-       @chmod +x dropbox_uploader.sh
-       @sed -i -e "s/~\/.dropbox_uploader/.\/dropbox_config/g" $$(basename $(DROPBOX))
-       @find ./pkgs -type f -regex ".*/.*\.\(xz\|deb\|zip\|txz\|rpm\)" -exec ./$$(basename $(DROPBOX)) upload {} \;
-       @rm dropbox_config dropbox_uploader.sh
-
-website:
-       $(CC) $(DOWNLOAD) -o html.gen
-       @./html.gen ../
-       @rm html.gen
-       @git stash
-       @git checkout gh-pages
-       @rm -f ../download.html
-       @mv -f download.html ../download.html
-       @cd ..; git add download.html; git commit -m 'update download page'; git push origin gh-pages;
-       @git checkout $(BRANCH)
-       @git stash apply
-
-clean:
-       @rm -rf pkgs/
-       @rm -f *.html
-
-all: base upload
diff --git a/distro/archbsd/git/PKGBUILD b/distro/archbsd/git/PKGBUILD
deleted file mode 100644 (file)
index f1ec819..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-# Contributor: matthiaskrgr <matthiaskrgr _strange_curverd_character_ freedroid D0T org>
-# Contributor: Wolfgang Bumiller <blub@speed.at>
-
-pkgname=gmqcc-git
-pkgver=20131031
-pkgrel=1
-pkgdesc="An Improved Quake C Compiler"
-arch=('i686' 'x86_64')
-depends=()
-conflicts=('gmqcc')
-provides=('gmqcc=0.3.5')
-makedepends=('git')
-url="https://github.com/graphitemaster/gmqcc.git"
-license=('MIT')
-
-_gitroot="git://github.com/graphitemaster/gmqcc.git"
-_gitname="gmqcc"
-
-build() {
-       cd $srcdir
-       msg "Connecting to the GIT server..."
-       if [[ -d $srcdir/$_gitname ]] ; then
-               cd $_gitname
-               msg "Removing build files..."
-               git clean -dfx
-               msg "Updating..."
-               git pull --no-tags
-               msg "The local files are updated."
-       else
-               msg "Cloning..."
-               git clone $_gitroot $_gitname --depth 1
-               msg "Clone done."
-       fi
-
-       msg "Starting compilation..."
-       cd "$srcdir"/"$_gitname"
-
-       msg "Compiling..."
-       gmake
-}
-
-check() {
-       cd "$srcdir"/"$_gitname"
-       gmake check
-}
-
-package() {
-       cd "$srcdir"/"$_gitname"
-       msg "Compiling and installing to pkgdir this time..."
-       gmake install DESTDIR=$pkgdir PREFIX=/usr
-       msg "Compiling done."
-
-       install -dm755 ${pkgdir}/usr/share/licenses/gmqcc
-       install -m644 LICENSE ${pkgdir}/usr/share/licenses/gmqcc/LICENSE
-}
diff --git a/distro/archbsd/release/PKGBUILD b/distro/archbsd/release/PKGBUILD
deleted file mode 100644 (file)
index a57846c..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-# Contributor: matthiaskrgr <matthiaskrgr _strange_curverd_character_ freedroid D0T org>
-# Contributor: Wolfgang Bumiller <blub@speed.at>
-
-pkgname=gmqcc
-pkgver=0.3.5
-pkgrel=1
-pkgdesc="An Improved Quake C Compiler"
-arch=('i686' 'x86_64')
-depends=()
-url="https://github.com/graphitemaster/gmqcc.git"
-license=('MIT')
-source=(gmqcc-$pkgver.zip::https://github.com/graphitemaster/gmqcc/zipball/$pkgver)
-sha1sums=('69085478f48f04eefbd2a088c1bd8c266b59f952')
-
-_gitname=graphitemaster-gmqcc-7f2b206/
-
-build() {
-       msg "Starting compilation..."
-       cd "$srcdir"/"$_gitname"
-
-       msg "Compiling..."
-       gmake
-}
-
-check() {
-       cd "$srcdir"/"$_gitname"
-       gmake check
-}
-
-package() {
-       cd "$srcdir"/"$_gitname"
-       msg "Compiling and installing to pkgdir this time..."
-       gmake install DESTDIR=$pkgdir PREFIX=/usr
-       msg "Compiling done."
-
-       install -dm755 ${pkgdir}/usr/share/licenses/gmqcc
-       install -m644 LICENSE ${pkgdir}/usr/share/licenses/gmqcc/LICENSE
-}
diff --git a/distro/archbsd/this/Makefile b/distro/archbsd/this/Makefile
deleted file mode 100644 (file)
index 6bfe650..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-all:
-       $(MAKE) -f ../../archlinux/this/Makefile \
-         LIBC_DEPEND=libc \
-         DESTDIR=distro/archbsd/this \
-         SUFFIX=archbsd
diff --git a/distro/archlinux/git/PKGBUILD b/distro/archlinux/git/PKGBUILD
deleted file mode 100644 (file)
index 7fbe857..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-# Contributor: matthiaskrgr <matthiaskrgr _strange_curverd_character_ freedroid D0T org>
-
-pkgname=gmqcc-git
-pkgver=0.3.5
-pkgver(){
-    cd gmqcc
-    git describe --tags | sed -e 's/^gmqcc\-//' -e 's/-/./g'
-}
-pkgrel=1
-pkgdesc="An Improved Quake C Compiler"
-arch=('i686' 'x86_64')
-depends=('glibc')
-conflicts=('gmqcc')
-provides=('gmqcc=0.3.5')
-makedepends=('git')
-url="https://github.com/graphitemaster/gmqcc.git"
-license=('MIT')
-source=('gmqcc::git://github.com/graphitemaster/gmqcc.git')
-sha1sums=('SKIP')
-
-
-build() {
-       msg "Starting compilation..."
-       cd "$srcdir"/"gmqcc"
-
-       msg "Compiling..."
-       make
-}
-
-check() {
-       cd "$srcdir"/"gmqcc"
-       make check
-}
-
-package() {
-       cd "$srcdir"/"gmqcc"
-       msg "Compiling and installing to pkgdir this time..."
-       make install DESTDIR=$pkgdir PREFIX=/usr
-       msg "Compiling done."
-
-       install -dm755 ${pkgdir}/usr/share/geany
-       install -m644 syntax/geany/filetypes.qc \
-                      ${pkgdir}/usr/share/geany/filetypes.qc
-
-       install -dm755 ${pkgdir}/usr/share/gtksourceview-3.0/language-specs
-       install -m644 syntax/gtksourceview/qc.lang \
-                      ${pkgdir}/usr/share/gtksourceview-3.0/language-specs/qc.lang
-
-       install -dm755 ${pkgdir}/usr/share/apps/katepart/syntax
-       install -m644 syntax/kate/qc.xml \
-                      ${pkgdir}/usr/share/apps/katepart/syntax/qc.xml
-
-       install -dm755 ${pkgdir}/usr/share/nano
-       install -m644 syntax/nano/qc.nanorc \
-                      ${pkgdir}/usr/share/nano/qc.nanorc
-
-       install -dm755 ${pkgdir}/usr/share/licenses/gmqcc
-       install -m644 LICENSE \
-                      ${pkgdir}/usr/share/licenses/gmqcc/LICENSE
-}
diff --git a/distro/archlinux/release/PKGBUILD b/distro/archlinux/release/PKGBUILD
deleted file mode 100644 (file)
index 980ca55..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-# Contributor: matthiaskrgr <matthiaskrgr _strange_curverd_character_ freedroid D0T org>
-# Contributor: Wolfgang Bumiller <blub@speed.at>
-
-pkgname=gmqcc
-pkgver=0.3.5
-pkgrel=1
-pkgdesc="An Improved Quake C Compiler"
-arch=('i686' 'x86_64')
-depends=('glibc')
-url="https://github.com/graphitemaster/gmqcc.git"
-license=('MIT')
-source=(gmqcc-$pkgver.zip::https://github.com/graphitemaster/gmqcc/zipball/$pkgver)
-sha1sums=('69085478f48f04eefbd2a088c1bd8c266b59f952')
-
-_gitname=graphitemaster-gmqcc-7f2b206/
-
-build() {
-       msg "Starting compilation..."
-       cd "$srcdir"/"$_gitname"
-
-       msg "Compiling..."
-       make
-}
-
-check() {
-       cd "$srcdir"/"$_gitname"
-       make check
-}
-
-package() {
-       cd "$srcdir"/"$_gitname"
-       msg "Compiling and installing to pkgdir this time..."
-       make install DESTDIR=$pkgdir PREFIX=/usr
-       msg "Compiling done."
-
-       install -dm755 ${pkgdir}/usr/share/licenses/gmqcc
-       install -m644 LICENSE ${pkgdir}/usr/share/licenses/gmqcc/LICENSE
-}
diff --git a/distro/archlinux/this/Makefile b/distro/archlinux/this/Makefile
deleted file mode 100644 (file)
index 91693a4..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-BASEDIR := ../../../
-PREFIX  := /usr
-HEADER  := $(BASEDIR)/gmqcc.h
-MAJOR   := $(shell sed -n -e '/GMQCC_VERSION_MAJOR/{s/.* .* //;p;q;}' $(HEADER))
-MINOR   := $(shell sed -n -e '/GMQCC_VERSION_MINOR/{s/.* .* //;p;q;}' $(HEADER))
-PATCH   := $(shell sed -n -e '/GMQCC_VERSION_PATCH/{s/.* .* //;p;q;}' $(HEADER))
-PKGREL  := 1
-SUFFIX  ?= archlinux
-CARCH   := $(shell uname -m)
-PKGDIR  := gmqcc-$(MAJOR).$(MINOR).$(PATCH)-$(PKGREL)-$(CARCH)-$(SUFFIX)
-TARCOMP := -J
-PKG     := $(PKGDIR).pkg.tar.xz
-PKGINFO := $(PKGDIR)/.PKGINFO
-DESTDIR := distro/archlinux/this
-CFLAGS  :=
-
-LIBC_DEPEND := glibc
-
-base:
-       $(MAKE) -C $(BASEDIR) clean
-       CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" \
-         $(MAKE) -C $(BASEDIR) "DESTDIR=$(DESTDIR)/$(PKGDIR)" "PREFIX=$(PREFIX)" strip install
-       @echo "pkgname = gmqcc" > $(PKGINFO)
-       @echo "pkgver = $(MAJOR).$(MINOR).$(PATCH)-$(PKGREL)" >> $(PKGINFO)
-       @echo "pkgdesc = An Improved Quake C Compiler" >> $(PKGINFO)
-       @echo "url = https://github.com/graphitemaster/gmqcc.git" >> $(PKGINFO)
-       @echo "builddate = `date -u \"+%s\"`" >> $(PKGINFO)
-       @echo "packager = Unknown Packager" >> $(PKGINFO)
-       @echo "size = `du -sk $(PKGDIR) | awk '{print $$1 * 1024}'`" >> $(PKGINFO)
-       @echo "arch = $(CARCH)" >> $(PKGINFO)
-       @echo "license = MIT" >> $(PKGINFO)
-       @echo "conflict = gmqcc" >> $(PKGINFO)
-       @echo "depend = $(LIBC_DEPEND)" >> $(PKGINFO)
-       @echo "makepkgopt = strip" >> $(PKGINFO)
-       @echo "makepkgopt = docs" >> $(PKGINFO)
-       @echo "makepkgopt = libtool" >> $(PKGINFO)
-       @echo "makepkgopt = emptydirs" >> $(PKGINFO)
-       @echo "makepkgopt = zipman" >> $(PKGINFO)
-       @echo "makepkgopt = purge" >> $(PKGINFO)
-       @echo "makepkgopt = !upx" >> $(PKGINFO)
-       @bsdtar -C $(PKGDIR) -czf $(PKGDIR)/.MTREE \
-           --format=mtree \
-           --options='!all,use-set,type,uid,gid,mode,time,size,md5,sha256,link' \
-           .PKGINFO usr/ 2>&1 >/dev/null
-       @bsdtar $(TARCOMP) -cvf $(PKG) -C $(PKGDIR)/ .PKGINFO .MTREE usr/ 2>&1 >/dev/null
-       @rm -rf $(PKGDIR)
-
-clean:
-       $(MAKE) -C $(BASEDIR) clean
-       @rm -f *.pkg.tar.xz
-
-all: base
diff --git a/distro/deb/Makefile b/distro/deb/Makefile
deleted file mode 100644 (file)
index fefb3e5..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-BASEDIR := ../..
-PREFIX  := /usr
-HEADER  := $(BASEDIR)/gmqcc.h
-MAJOR   := `sed -n -e '/GMQCC_VERSION_MAJOR/{s/.* .* //;p;q;}' $(HEADER)`
-MINOR   := `sed -n -e '/GMQCC_VERSION_MINOR/{s/.* .* //;p;q;}' $(HEADER)`
-PATCH   := `sed -n -e '/GMQCC_VERSION_PATCH/{s/.* .* //;p;q;}' $(HEADER)`
-DEBDIR  := gmqcc-$(MAJOR).$(MINOR).$(PATCH)
-CARCH   := $(shell uname -m)
-DEB     := $(DEBDIR)-$(CARCH).deb
-CONTROL := $(DEBDIR)/DEBIAN/control
-
-ifneq (, $(findstring i686, $(CARCH)))
-       CFLAGS += -m32
-       LDFLAGS += -m32
-endif
-
-base:
-       $(MAKE) -C $(BASEDIR) clean
-       CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" \
-         $(MAKE) -C $(BASEDIR) DESTDIR=distro/deb/$(DEBDIR) PREFIX=$(PREFIX) strip install
-       @install -d -m755 $(DEBDIR)/DEBIAN
-       @echo "Package: gmqcc" > $(CONTROL)
-       @echo "Version: $(MAJOR).$(MINOR).$(PATCH)" >> $(CONTROL)
-       @echo "Section: user/hidden" >> $(CONTROL)
-       @echo "Priority: optional" >> $(CONTROL)
-       @echo "Architecture: $(CARCH)" >> $(CONTROL)
-       @echo "Installed-Size: `du -ks $($(DEBDIR)/usr) | cut -f 1`" >> $(CONTROL)
-       @echo "Maintainer: Dale Weiler <killfieldengine@gmail.com>" >> $(CONTROL)
-       @echo "Description: An improved Quake C Compiler" >> $(CONTROL)
-       @echo "   For an enduring period of time the options for a decent compiler for the Quake C programming language" >> $(CONTROL)
-       @echo "   were confined to a specific compiler known as QCC. Attempts were made to extend and improve upon the" >> $(CONTROL)
-       @echo "   design of QCC, but many foreseen the consequences of building on a broken foundation. The solution" >> $(CONTROL)
-       @echo "   was obvious, a new compiler; one born from the NIH realm of sarcastic wit. We welcome you. You won't" >> $(CONTROL)
-       @echo "   find a better Quake C compiler." >> $(CONTROL)
-       @tar czf data.tar.gz -C $(DEBDIR)/ . --exclude=DEBIAN
-       @tar czf control.tar.gz -C $(DEBDIR)/DEBIAN/ .
-       @echo 2.0 > debian-binary
-       @ar r $(DEB) debian-binary control.tar.gz data.tar.gz 2>&1 >/dev/null
-       @rm -rf debian-binary control.tar.gz data.tar.gz $(DEBDIR)
-
-clean:
-       $(MAKE) -C $(BASEDIR) clean
-       @rm -f *.deb
-
-all: base
diff --git a/distro/fedora/spec/INSTALL b/distro/fedora/spec/INSTALL
deleted file mode 100644 (file)
index 8540670..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-File is constantly updated!
-
-Since Fedora 19 we have this package in main repos. Please use it!
-
-For install gmqcc do this:
-# yum install gmqcc
-
-For install qcvm do this:
-# yum install qcvm
-
-For install gmqpak do this:
-# yum install gmqpak
-
-To use the spec files in this fedora directory you need the core
-development tools , building enviroment for the user, and
-the directory structure for it. If you don't already have these, issue
-the following commands.
-
-# yum groupinstall "Development Tools"
-# yum install rpmdevtools
-$ rm -rf ~/rpmbuild
-$ rpmdev-setuptree
-
-Prepare and build the RPMs.
-
-$ cp gmqcc.spec ~/rpmbuild/SPECS/
-$ wget https://github.com/graphitemaster/gmqcc/archive/0.3.5.tar.gz -o ~/rpmbuild/SOURCES/gmqcc-0.3.5.tar.gz
-$ rpmbuild -ba ~/rpmbuild/SPECS/gmqcc.spec
-
-Now we have RPMs in ~/rpmbuild/RPMS/ and you can install it via yum.
-
diff --git a/distro/fedora/spec/gmqcc.spec b/distro/fedora/spec/gmqcc.spec
deleted file mode 100644 (file)
index 95fb611..0000000
+++ /dev/null
@@ -1,96 +0,0 @@
-Name:           gmqcc
-Version:        0.3.6
-Release:        2%{?dist}
-Summary:        Improved Quake C Compiler
-License:        MIT
-URL:            http://graphitemaster.github.io/gmqcc/
-Source0:        https://github.com/graphitemaster/%{name}/archive/%{version}.tar.gz#/%{name}-%{version}.tar.gz
-
-# tests fail on big endians
-ExclusiveArch:  %{ix86} x86_64 %{arm}
-
-%description
-Modern written-from-scratch compiler for the QuakeC language with
-support for many common features found in other QC compilers.
-
-%package -n qcvm
-Summary:        Standalone QuakeC VM binary executor
-
-%description -n qcvm
-Executor for QuakeC VM binary files created using a QC compiler such
-as gmqcc or fteqcc. It provides a small set of built-in functions, and
-by default executes the main function if there is one. Some options
-useful for debugging are available as well.
-
-%package -n gmqpak
-Summary:        Standalone Quake PAK file utility
-
-%description -n gmqpak
-Standalone Quake PAK file utility supporting the extraction of files,
-directories, or whole PAKs, as well as the opposite (creation of PAK files).
-
-%prep
-%setup -q
-echo '#!/bin/sh' > ./configure
-chmod +x ./configure 
-
-# and for all for all of those switches they increase the runtime of the compile
-# making compiles of code slower
-
-# we don't need compile time buffer protection, we test with clang's address
-# sanatizer and valgrind before releases
-%global optflags %(echo %{optflags} | sed 's/-D_FORTIFY_SOURCE=2 //')
-# there is no exceptions in C
-%global optflags %(echo %{optflags} | sed 's/-fexceptions //')
-# same with clangs address sanatizer and valgrind testing
-%global optflags %(echo %{optflags} | sed 's/-fstack-protector-strong //')
-# buffer overflow protection is unrequired since most (if not all) allocations
-# happen dynamically and we have our own memory allocator which checks this
-# (with valgrind integration), also clang's address santatizer cathes it as
-# for grecord-gcc-switches, that just adds pointless information to the binary
-# increasing its size
-%global optflags %(echo %{optflags} | sed 's/--param=ssp-buffer-size=4 //')
-
-%build
-%configure
-make %{?_smp_mflags}
-
-%install
-%make_install PREFIX=%{_prefix}
-
-%check
-make check
-
-%files
-%doc LICENSE README AUTHORS CHANGES TODO
-%doc gmqcc.ini.example
-%{_mandir}/man1/gmqcc.1*
-%{_bindir}/gmqcc
-
-%files -n qcvm
-%doc LICENSE README AUTHORS CHANGES TODO
-%{_mandir}/man1/qcvm.1*
-%{_bindir}/qcvm
-
-%files -n gmqpak
-%doc LICENSE README AUTHORS CHANGES TODO
-%{_mandir}/man1/gmqpak.1*
-%{_bindir}/gmqpak
-
-%changelog
-* Sat Nov 16 2013 Dan Horák <dan[at]danny.cz> - 0.3.5-2
-- fix build on big endian arches
-- use the standard wildcarded filename for man pages
-- and make it Exclusive for little endians because tests fail on big endians
-
-* Thu Nov 14 2013 Igor Gnatenko <i.gnatenko.brain@gmail.com> - 0.3.5-1
-- 0.3.5 upstream release
-
-* Thu Sep 26 2013 Igor Gnatenko <i.gnatenko.brain@gmail.com> - 0.3.0-2
-- Optimizing compile flags
-
-* Fri Sep 20 2013 Igor Gnatenko <i.gnatenko.brain@gmail.com> - 0.3.0-1
-- Update to 0.3.0 (improved new package: gmqpak)
-
-* Sat Jul 27 2013 Igor Gnatenko <i.gnatenko.brain@gmail.com> - 0.2.9-1
-- Initial release
diff --git a/distro/fedora/this/Makefile b/distro/fedora/this/Makefile
deleted file mode 100644 (file)
index 49b752f..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-BASEDIR := $(CURDIR)/../../..
-HEADER  := $(BASEDIR)/gmqcc.h
-MAJOR   := `sed -n -e '/GMQCC_VERSION_MAJOR/{s/.* .* //;p;q;}' $(HEADER)`
-MINOR   := `sed -n -e '/GMQCC_VERSION_MINOR/{s/.* .* //;p;q;}' $(HEADER)`
-PATCH   := `sed -n -e '/GMQCC_VERSION_PATCH/{s/.* .* //;p;q;}' $(HEADER)`
-NAME    := gmqcc-$(MAJOR).$(MINOR).$(PATCH)
-TARFILE := $(NAME).tar.gz
-
-all:
-       @mkdir -p ~/rpmbuild/SPECS
-       @mkdir -p ~/rpmbuild/SOURCES
-       @cp ../spec/gmqcc.spec ~/rpmbuild/SPECS
-       @mkdir -p /tmp/$(NAME)
-       @cp -R $(BASEDIR) /tmp/$(NAME)/
-       @cd /tmp && tar -zcf ~/rpmbuild/SOURCES/$(TARFILE) $(NAME)/
-       @rm -rf /tmp/$(NAME)
-       @rpmbuild -ba ../spec/gmqcc.spec 2>&1 >/dev/null
-       
-       @mv ~/rpmbuild/RPMS/x86_64/gmqcc*.rpm . 2>/dev/null; true
-       @mv ~/rpmbuild/RPMS/x86_64/qcvm*.rpm . 2>/dev/null; true
-       @mv ~/rpmbuild/RPMS/x86_64/gmqpak*.rpm . 2>/dev/null; true
diff --git a/distro/gentoo/INSTALL b/distro/gentoo/INSTALL
deleted file mode 100644 (file)
index 7a4f778..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-To use the ebuilds provided in this gentoo directory you first must
-create a directory in your overlay tree.
-
-If you don't already have your own directory for custom ebuilds, you can
-create one. If you already have one, and that directory is set in your
-/etc/make.conf for PORTDIR_OVERLAY, this step can be skiped. Otherwise
-if you don't already, you can create one as such.
-
-# mkdir -p /usr/local/portage
-# vim /etc/make.conf
-       Set PORTDIR_OVERLAY=/usr/local/portage
-       Then save and exit
-
-Once that is completed, or you skiped that step, you need to create a
-directory in your overlay tree for gmqcc, this can be done as such:
-(subsitute [[PORTDIR_OVERLAY]] with the one set in /etc/make.conf)
-
-# mkdir -p [[PORTDIR_OVERLAY]]/gmqcc
-
-After the directory is created you need to move the correct version ebuild
-into that directory depending on which version of GMQCC you want. For
-instance, if you want gmqcc 0.3.0, you move gmqcc-0.3.0.ebuild into that
-directory.
-
-# mv gmqcc-{version}.ebuild [[PORTDIR_OVERLAY]]/gmqcc/
-
-After the file is moved into your newly created portage overlay tree, you'll
-need to build a digest for it with ebuild. A digest is simply a Manifest and
-digital signature for the source files used.
-
-# ebuild gmqcc-0.3.0.ebuild digest
-
-After the digest is built, you can emerge gmqcc as usual.
-
-# emerge gmqcc
diff --git a/distro/gentoo/gmqcc-0.3.0.ebuild b/distro/gentoo/gmqcc-0.3.0.ebuild
deleted file mode 100644 (file)
index 28630d3..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-EAPI=5
-
-DESCRIPTION="An Improved Quake C Compiler"
-HOMEPAGE="http://graphitemaster.github.com/gmqcc/"
-SRC_URI="https://github.com/graphitemaster/${PN}/archive/${PV}.tar.gz -> ${P}.tar.gz"
-
-LICENSE="MIT"
-
-SLOT="0"
-IUSE=""
-KEYWORDS="~amd64 ~x86"
-
-src_prepare() {
-       sed -i -e "s:-Werror ::" Makefile || die
-}
-
-src_install() {
-       emake install PREFIX="${D}/usr"
-       dodoc README
-}
diff --git a/distro/slackware/this/Makefile b/distro/slackware/this/Makefile
deleted file mode 100644 (file)
index 3c28cb0..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-BASEDIR := ../../../
-PREFIX  := /usr
-HEADER  := $(BASEDIR)/gmqcc.h
-MAJOR   := $(shell sed -n -e '/GMQCC_VERSION_MAJOR/{s/.* .* //;p;q;}' $(HEADER))
-MINOR   := $(shell sed -n -e '/GMQCC_VERSION_MINOR/{s/.* .* //;p;q;}' $(HEADER))
-PATCH   := $(shell sed -n -e '/GMQCC_VERSION_PATCH/{s/.* .* //;p;q;}' $(HEADER))
-CARCH   := $(shell uname -m)
-PKGDIR  := gmqcc-$(MAJOR).$(MINOR).$(PATCH)-$(CARCH)
-PKG     := $(PKGDIR).txz
-PKGINFO := $(PKGDIR)/.PKGINFO
-DESTDIR := distro/slackware/this/$(PKGDIR)
-CFLAGS  :=
-
-
-ifneq (, $(findstring i686, $(CARCH)))
-       CFLAGS += -m32
-       LDFLAGS += -m32
-endif
-
-base:
-       $(MAKE) -C $(BASEDIR) clean
-       CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" \
-         $(MAKE) -C $(BASEDIR) "DESTDIR=$(DESTDIR)" "PREFIX=$(PREFIX)" strip install
-       gzip -9 $(PKGDIR)/usr/share/man/man?/*.?
-       strip -s $(PKGDIR)/usr/bin/*
-       mkdir $(PKGDIR)/install
-       cp slack-desc $(PKGDIR)/install
-       @tar -cJvf $(PKG) -C $(PKGDIR)/ install/ usr/
-       @rm -rf $(PKGDIR)
-
-clean:
-       $(MAKE) -C $(BASEDIR) clean
-       @rm -f *.txz
-
-all: base
diff --git a/distro/slackware/this/slack-desc b/distro/slackware/this/slack-desc
deleted file mode 100644 (file)
index 3384ef9..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-     |-----handy-ruler------------------------------------------------------|
-gmqcc: gmqcc (Quake C compiler)
-gmqcc:
-gmqcc: A modern written-from-scratch compiler for the QuakeC language with
-gmqcc: support for many common features found in other QC compilers.
-gmqcc: Additionally contains a standalone QCVM executor, and a tool to deal
-gmqcc: with .pak archive files.
-gmqcc:
-gmqcc:
-gmqcc: github page:
-gmqcc:   http://github.com/graphitemaster/gmqcc
-gmqcc:
diff --git a/distro/win32/Makefile b/distro/win32/Makefile
deleted file mode 100644 (file)
index 20f580c..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-BASEDIR := ../..
-HEADER  := $(BASEDIR)/gmqcc.h
-MAJOR   := `sed -n -e '/GMQCC_VERSION_MAJOR/{s/.* .* //;p;q;}' $(HEADER)`
-MINOR   := `sed -n -e '/GMQCC_VERSION_MINOR/{s/.* .* //;p;q;}' $(HEADER)`
-PATCH   := `sed -n -e '/GMQCC_VERSION_PATCH/{s/.* .* //;p;q;}' $(HEADER)`
-BINDIR  := gmqcc-$(MAJOR).$(MINOR).$(PATCH)
-
-base:
-       $(MAKE) CC=i686-w64-mingw32-gcc UNAME=MINGW -C $(BASEDIR) clean
-       $(MAKE) CC=i686-w64-mingw32-gcc UNAME=MINGW -C $(BASEDIR) DESTDIR=distro/win32/$(BINDIR) PREFIX=/ strip install
-       @mkdir -p $(BINDIR)/doc
-       @groff -mandoc $(BINDIR)/man1/gmqpak.1 | ps2pdf - $(BINDIR)/doc/gmqpak.pdf
-       @groff -mandoc $(BINDIR)/man1/qcvm.1 | ps2pdf - $(BINDIR)/doc/qcvm.pdf
-       @groff -mandoc $(BINDIR)/man1/gmqcc.1 | ps2pdf - $(BINDIR)/doc/gmqcc.pdf
-       @rm -rf $(BINDIR)/man1/
-       @zip -r $(BINDIR)-win32.zip $(BINDIR)
-       @rm -rf $(BINDIR)
-clean:
-       $(MAKE) -C $(BASEDIR) clean
-       @rm -f *.zip
-
-all: base
diff --git a/distro/win64/Makefile b/distro/win64/Makefile
deleted file mode 100644 (file)
index f45f351..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-BASEDIR := ../..
-HEADER  := $(BASEDIR)/gmqcc.h
-MAJOR   := `sed -n -e '/GMQCC_VERSION_MAJOR/{s/.* .* //;p;q;}' $(HEADER)`
-MINOR   := `sed -n -e '/GMQCC_VERSION_MINOR/{s/.* .* //;p;q;}' $(HEADER)`
-PATCH   := `sed -n -e '/GMQCC_VERSION_PATCH/{s/.* .* //;p;q;}' $(HEADER)`
-BINDIR  := gmqcc-$(MAJOR).$(MINOR).$(PATCH)
-
-base:
-       $(MAKE) CC=x86_64-w64-mingw32-gcc UNAME=MINGW -C $(BASEDIR) clean
-       $(MAKE) CC=x86_64-w64-mingw32-gcc UNAME=MINGW -C $(BASEDIR) DESTDIR=distro/win64/$(BINDIR) PREFIX=/ strip install
-       @mkdir -p $(BINDIR)/doc
-       @groff -mandoc $(BINDIR)/man1/gmqpak.1 | ps2pdf - $(BINDIR)/doc/gmqpak.pdf
-       @groff -mandoc $(BINDIR)/man1/qcvm.1 | ps2pdf - $(BINDIR)/doc/qcvm.pdf
-       @groff -mandoc $(BINDIR)/man1/gmqcc.1 | ps2pdf - $(BINDIR)/doc/gmqcc.pdf
-       @rm -rf $(BINDIR)/man1/
-       @zip -r $(BINDIR)-win64.zip $(BINDIR)
-       @rm -rf $(BINDIR)
-clean:
-       $(MAKE) -C $(BASEDIR) clean
-       @rm -f *.zip
-
-all: base
diff --git a/doc/gmqpak.1 b/doc/gmqpak.1
deleted file mode 100644 (file)
index f4ef65d..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-.\" gmqpak mdoc manpage
-.Dd April 27, 2013
-.Dt GMQPAK 2 PRM
-.Os
-.Sh NAME
-.Nm gmqpak
-.Nd A standalone Quake PAK utility
-.Sh SYNOPSIS
-.Nm gmqpak
-.Op Cm options
-.Op Cm files
-.Sh DESCRIPTION
-.Nm gmqpak
-Is a standalone Quake PAK file utility supporting the extraction of files,
-directories, or whole PAKs, as well as the opposite (creation of PAK files).
-.Sh OPTIONS
-.Bl -tag -width indent
-.It Fl -file Ar file
-Specify the PAK file to create or extract
-.It Fl -e
-Used to denote the extraction operation on a PAK file.
-.It Fl -c
-Used to denote the creation operation on a PAK file.
-.El
-.Sh EXAMPLES
-Here's some examples of how to use the utility to manipulate PAK files.
-.Bl -ohang
-.It Li gmqpak -file id1.pak -e
-.D1 extracts a PAK to ./
-.It Li gmqpak -file new.pak -c file1 dir/file2
-.D1 creates a PAK with the files specified
-.It Li gmqpak -file new1.pak -c directory.
-.D1 creates a PAK from files within the directory, including subdirectories and files.
-.El
-.Sh AUTHOR
-See <http://graphitemaster.github.com/gmqcc>.
-.Sh BUGS
-Please report bugs on <http://github.com/graphitemaster/gmqcc/issues>,
-or see <http://graphitemaster.github.com/gmqcc> on how to contact us.
diff --git a/doc/html/download.c b/doc/html/download.c
deleted file mode 100644 (file)
index f31545c..0000000
+++ /dev/null
@@ -1,293 +0,0 @@
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
-#include <errno.h>
-
-/*
- * protect some information, not that I care, but this is just to stay
- * safer.
- */
-#define SECURITY_BASE  "\
-ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
-#define SECURITY_TOKEN "\
-P29hdXRoX2NvbnN1bWVyX2tleT03NnZoM3E0Mmhudm16bTMmb2F1dGhfdG9rZW49\
-dzBieHpmMGRmdDhlZGZxJm9hdXRoX3NpZ25hdHVyZV9tZXRob2Q9UExBSU5URVhU\
-Jm9hdXRoX3NpZ25hdHVyZT10bWVlY2h0MmNtaDcyeGElMjY5dm9zeDd4OGd5NGtn\
-amsmb2F1dGhfdGltZXN0YW1wPSZvYXV0aF9ub25jZT0xMjE2NQo="
-
-int isbase64(char c) {
-   return !!(c && strchr(SECURITY_BASE, c) != NULL);
-}
-char value(char c) {
-    const char *load = SECURITY_BASE;
-    const char *find = strchr(load, c);
-
-    return (find) ? find - load : 0;
-}
-
-int security_decode(unsigned char *dest, const unsigned char *src, int srclen) {
-    unsigned char *p;
-
-    if(!*src)
-        return 0;
-
-    *dest = 0;
-    p = dest;
-
-    do {
-        *p++ = (value(src[0]) << 2) | (value(src[1]) >> 4);
-        *p++ = (value(src[1]) << 4) | (value(src[2]) >> 2);
-        *p++ = (value(src[2]) << 6) | (value(src[3]) >> 0);
-
-        if(!isbase64(src[1])) {
-            p -= 2;
-            break;
-        }
-        else if(!isbase64(src[2])) {
-            p -= 2;
-            break;
-        }
-        else if(!isbase64(src[3])) {
-            p--;
-            break;
-        }
-        src += 4;
-
-        while(*src && (*src == 13 || *src == 10))
-        src++;
-    } while(srclen-= 4);
-
-    *p = 0;
-    return p-dest;
-}
-
-#define BASEURL          " https://api-content.dropbox.com/1/files/sandbox/"
-
-/*
- * If more platforms are supported add the entries between the start
- * tag here, and the end tag below. Nothing else needs to be done
- * <tag> (the table needs to match the HTML too)
- */
-#define ARCHLINUX_32_REF "%sgmqcc-%c.%c.%c-1-i686.pkg.tar.xz%s"
-#define ARCHLINUX_64_REF "%sgmqcc-%c.%c.%c-1-x86_64.pkg.tar.xz%s"
-#define DEBIAN_32_REF    "%sgmqcc-%c.%c.%c-i686.deb%s"
-#define DEBIAN_64_REF    "%sgmqcc-%c.%c.%c-x86_64.deb%s"
-#define WINDOWS_32_REF   "%sgmqcc-%c.%c.%c-win32.zip%s"
-#define WINDOWS_64_REF   "%sgmqcc-%c.%c.%c-win64.zip%s"
-#define SLACKWARE_32_REF "%sgmqcc-%c.%c.%c-i686.txz%s"
-#define SLACKWARE_64_REF "%sgmqcc-%c.%c.%c-x86_64.txz%s"
-
-
-#define HTML "\
-<!doctype html>\
-<html>\
-<head>\
- <meta charset=\"utf-8\">\
- <meta http-equiv=\"X-UA-Compatible\" content=\"chrome=1\">\
- <title>GMQCC</title>\
- <link rel=\"stylesheet\" href=\"stylesheets/styles.css\">\
- <link rel=\"stylesheet\" href=\"stylesheets/pygment_trac.css\">\
- <script src=\"javascripts/scale.fix.js\"></script>\
- <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, user-scalable=no\">\
- <!--[if lt IE 9]>\
- <script src=\"//html5shiv.googlecode.com/svn/trunk/html5.js\"></script>\
- <![endif]-->\
-</head>\
-<body>\
- <a href=\"https://github.com/graphitemaster/gmqcc\"><div class=\"fork\"></div></a>\
- <div class=\"wrapper\">\
-  <header>\
-   <h1 class=\"header\">GMQCC</h1>\
-   <p class=\"header\">An Improved Quake C Compiler</p>\
-   <ul>\
-    <li class=\"buttons\"><a href=index.html>Index</a></li>\
-    <li class=\"download\"><a href=\"download.html\">Download</a></li>\
-    <li class=\"buttons\"><a href=\"https://github.com/graphitemaster/gmqcc/issues\">Issues</a></li>\
-    <li class=\"buttons\"><a href=\"doc.html\">Documentation</a></li>\
-    <li class=\"buttons\"><a href=\"https://github.com/graphitemaster/gmqcc\">View On GitHub</a></li>\
-   </ul>\
-  </header>\
-  <section>\
-    <table>\
-     <tr>\
-      <th>Operating System / Distribution</th>\
-      <th>x86 Architecture</th>\
-      <th>x86_64 Architecture</th>\
-     </tr>\
-     <tr>\
-      <td>Archlinux</td>\
-      <td><a href=\"%s\">Download</a></td>\
-      <td><a href=\"%s\">Download</a></td>\
-     </tr>\
-     <tr>\
-      <td>Debian</td>\
-      <td><a href=\"%s\">Download</a></td>\
-      <td><a href=\"%s\">Download</a></td>\
-     </tr>\
-     <tr>\
-      <td>Slackware</td>\
-      <td><a href=\"%s\">Download</a></td>\
-      <td><a href=\"%s\">Download</a></td>\
-     <tr>\
-      <td>Windows</td>\
-      <td><a href=\"%s\">Download</a></td>\
-      <td><a href=\"%s\">Download</a></td>\
-    </tr>\
-    </table>\
-  </section>\
-  <footer>\
-   <script type=\"text/javascript\" src=\"http://www.ohloh.net/p/602517/widgets/project_partner_badge.js\"></script>\
-  </footer>\
- </div>\
- <!--[if !IE]><script>fixScale(document);</script><![endif]-->\
-</body>\
-</html>\
-"
-
-static char build_table[][4096] = {
-    ARCHLINUX_32_REF, ARCHLINUX_64_REF,
-    DEBIAN_32_REF,    DEBIAN_64_REF,
-    SLACKWARE_32_REF, SLACKWARE_64_REF,
-    WINDOWS_32_REF,   WINDOWS_64_REF
-};
-/* </tag> */
-
-#define ISXDIGIT(c) ((c >= 48 && c <= 57) || ((c & ~0x20) >= 65 && (c & ~0x20) <= 70))
-typedef struct {
-    char        *data;
-    unsigned int len;
-    unsigned int size;
-} url_t;
-void escape(url_t *str) {
-    char *p, *ptr;
-    char hexstr[3];
-    unsigned int  i=0;
-    unsigned long l=0;
-
-    p = str->data;
-    for(i=0; i < str->len; i++) {
-        if((p - str->data) >= str->len)
-            break;
-        if(*p == '%' &&
-            ((p - str->data)+2) < str->len &&
-            ISXDIGIT(*(p+1)) &&
-            ISXDIGIT(*(p+2))
-        ) {
-            p++;
-            hexstr[0] = *p++;
-            hexstr[1] = *p++;
-            hexstr[2] = 0;
-            l = strtoul(hexstr, &ptr, 16);
-            str->data[i] = (char)(l & 0x7f);
-            continue;
-        }
-        if(*p == '+') {
-            *p = ' ';
-        }
-        str->data[i] = *p++;
-    }
-    str->data[i] = 0;
-    str->len     = i;
-}
-
-void version(const char *directory, char *major, char *minor, char *patch) {
-    FILE    *handle;
-    char     file[4096];
-    size_t   size = 0;
-    char    *data = NULL;
-    snprintf(file, sizeof(file), "%s/gmqcc.h", directory);
-
-    handle = fopen(file, "r");
-    if (!handle) {
-        fprintf(stderr, "failed to open %s for reading version (%s)\n",
-            file, strerror(errno)
-        );
-        abort();
-    }
-
-    while (getline(&data, &size, handle) != EOF) {
-
-        #define TEST(TYPE, STORE)                               \
-            if (strstr(data, "#define GMQCC_VERSION_" TYPE )) { \
-                char *get = data;                               \
-                while (!isdigit(*get))                          \
-                    get++;                                      \
-                *STORE = *get;                                  \
-            }
-
-        TEST("MAJOR", major)
-        TEST("MINOR", minor)
-        TEST("PATCH", patch)
-
-        #undef TEST
-    }
-
-    free(data);
-}
-
-void genhtml() {
-    FILE *fp = fopen("download.html", "w");
-    if (!fp) {
-        fprintf(stderr, "failed to generate HTML: %s\n", strerror(errno));
-        abort();
-    }
-
-    fprintf(fp, HTML,
-        build_table[0], build_table[1],
-        build_table[2], build_table[3],
-        build_table[4], build_table[5],
-        build_table[6], build_table[7]
-    );
-    fclose (fp);
-}
-
-/*
- * Builds a list of download links with the right version and handles the
- * rest of the magic.
- */
-void build(const char *directory) {
-    /* Figure out version number */
-    char   find[3];
-    char   decode[4096];
-    size_t size;
-    version(directory, &find[0], &find[1], &find[2]);
-
-    /*
-     * decode the secuity stuff for preparing the URLs which will be used
-     * as links.
-     */
-    memset(decode, 0, sizeof(decode));
-    security_decode(decode, SECURITY_TOKEN, strlen(SECURITY_TOKEN));
-
-    for (size = 0; size < sizeof(build_table) / sizeof(*build_table); size++) {
-        char *load = strdup(build_table[size]);
-        url_t esc  = { NULL, 0 };
-
-        snprintf(build_table[size], 4096, load, BASEURL, find[0], find[1], find[2], decode);
-        esc.data = strdup(build_table[size]);
-        esc.size = strlen(build_table[size]);
-        esc.len  = esc.size;
-
-        /* Yes we also need to escape URLs just incase */
-        escape(&esc);
-        free(load);
-    }
-
-    /*
-     * Now generate the HTML file for those download links by asking tinyurl to
-     */
-    genhtml();
-}
-
-int main(int argc, char **argv) {
-    size_t itr;
-
-    argc--;
-    argv++;
-    if (!argc) {
-        printf("usage: %s [gmqcc.h location]\n", argv[-1]);
-        return 0;
-    }
-
-    build(*argv);
-}
diff --git a/doc/html/gmqcc.png b/doc/html/gmqcc.png
deleted file mode 100644 (file)
index 0d9b79f..0000000
Binary files a/doc/html/gmqcc.png and /dev/null differ
diff --git a/doc/html/style.css b/doc/html/style.css
deleted file mode 100644 (file)
index fa67901..0000000
+++ /dev/null
@@ -1,92 +0,0 @@
-html, body, div, span, applet, object, iframe,
-h1, h2, h3, h4, h5, h6, p, blockquote, pre,
-a, abbr, acronym, address, big, cite, code,
-del, dfn, em, img, ins, kbd, q, s, samp,
-small, strike, strong, sub, sup, tt, var,
-b, u, i, center,
-dl, dt, dd, ol, ul, li,
-fieldset, form, label, legend,
-table, caption, tbody, tfoot, thead, tr, th, td,
-article, aside, canvas, details, embed,
-figure, figcaption, footer, header, hgroup,
-menu, nav, output, ruby, section, summary,
-time, mark, audio, video {
-  margin: 0;
-  padding: 0;
-  border: 0;
-  font: inherit;
-  vertical-align: baseline;
-}
-
-body {
-    font-family:"Helvetica Neue", Helvetica, Arial, sans-serif;
-    margin: 0;
-    line-height: 1.8em;
-    -webkit-font-smoothing: antialiased;
-    background: #CDC9C9;
-}
-
-h1, h2, h3, h4, h5, h6 {
-  color:#232323;
-  margin:36px 0 10px;
-}
-
-.head-ltitle, .head-rtitle, .head-vol {
-    font: bold 0.8em Arial, Helvectia, sans-serif;
-}
-
-.head-vol {
-    visibility: hidden;
-}
-
-.name {
-    font: bold 0.8em Monospace, serif;
-}
-
-.ftype {
-    font: normal 1em Monospace, serif;
-}
-
-h1 {
-    color: #c30000;
-    margin-top: 0.3em;
-    margin-bottom: 0.3em;
-    line-height: 1.3;
-    font: normal 1.4em Arvo, Monaco, sans-serif;
-       /*font: bold 1.4em Arial, Helvetica, sans-serif*/
-}
-
-.section {
-       margin-bottom: 1em;
-       margin-top: 1em;
-       padding-left: 1em;
-       border-top: 1px #cccccc solid;
-       font: normal 0.9em Arial, Helvetica, sans-serif;
-       text-align: justify;
-       border: 0;
-       padding-bottom: 1em;
-       border-bottom: 1px solid #f0e0e0;
-}
-
-.list-tag {
-    padding-left: 1em;
-}
-
-pre {
-    border: 1px dashed #ffffff;
-    background: #ddd8d8;
-}
-
-.flag {
-    font: normal 1em Monospace, serif;
-}
-
-a {
-  color:#C30000;
-  font-weight:200;
-  text-decoration:none;
-}
-
-a:hover {
-       text-decoration: underline;
-}
diff --git a/doc/specification.tex b/doc/specification.tex
deleted file mode 100644 (file)
index 74190fc..0000000
+++ /dev/null
@@ -1,759 +0,0 @@
-\documentclass{article}
-
-%%% PACKAGES
-\usepackage{geometry}
-\usepackage[utf8]{inputenc}
-\usepackage[parfill]{parskip}
-\usepackage{subfig}
-\usepackage{listings}
-\usepackage{color}
-\usepackage{sectsty}
-
-%%% GEOMETRY FOR DOCUMENT
-\geometry{a4paper}
-
-%%% HEADERS/FOOTERS APPEARANCE
-\usepackage{fancyhdr} % This should be set AFTER setting up the page geometry
-\pagestyle{fancy} % options: empty , plain , fancy
-\renewcommand{\headrulewidth}{0pt} % customise the layout...
-\lhead{}\chead{}\rhead{}
-\lfoot{}\cfoot{\thepage}\rfoot{}
-
-%%% SECTION TITLE APPEARANCE
-\allsectionsfont{\sffamily\mdseries\upshape} % (See the fntguide.pdf for font help)
-
-%%% ToC APPEARANCE
-\usepackage[nottoc,notlof,notlot]{tocbibind} % Put the bibliography in the ToC
-\usepackage[titles,subfigure]{tocloft} % Alter the style of the Table of Contents
-\renewcommand{\cftsecfont}{\rmfamily\mdseries\upshape}
-\renewcommand{\cftsecpagefont}{\rmfamily\mdseries\upshape} % No bold!
-
-%%% listing language definitions
-%%% BNF for now, QuakeC will be later
-\definecolor{keyword1}{RGB}{0,102,153}
-\definecolor{keyword2}{RGB}{0,153,102}
-\definecolor{keyword3}{RGB}{0,153,255}
-\definecolor{comment}{RGB}{204,0,0}
-\definecolor{function}{RGB}{153,102,255}
-\definecolor{digit}{RGB}{255,0,0}
-\definecolor{string}{RGB}{255,0,204}
-\definecolor{rule}{RGB}{192,192,192}
-\definecolor{back}{RGB}{250,250,250}
-
-\lstdefinelanguage{bnf}{
-  keywordstyle={\color{keyword2}\bfseries},
-  keywords={},
-  otherkeywords={::=,|},
-  morecomment=[s][\color{comment}]{(*}{*)},
-  stringstyle=\color{string},
-  showstringspaces=false,
-  frame=none,
-  rulecolor=\color{rule},
-  backgroundcolor=\color{back}
-}
-
-%% Title Information %%
-\title{The GMQCC QuakeC Programming Language}
-\author{Dale Weiler}
-\date{\today}
-
-\begin{document}
-
-%% Title Page %%
-\maketitle
-\thispagestyle{empty}
-\raggedright
-\abstract
-This document specifies the form and establishes the interpretation of programs written in
-the GMQCC QuakeC programming language variant (refereed simply as QuakeC throughout this
-document). It specifies:
-\begin{itemize}
-       \item the representation of QuakeC programs;
-       \item the syntax and constraints of the QuakeC language;
-       \item the semantic rules for interpreting QuakeC programs;
-       \item the representation of input data to be processed by QuakeC programs;
-       \item the representation of output data produced by QuakeC programs;
-       \item the restrictions and limits imposed by a conforming implementation of QuakeC.
-\end{itemize}
-This document does not specify
-\begin{itemize}
-       \item the mechanism by which QuakeC programs are transformed for use by a data-
-             processing system;
-       \item the mechanism by which QuakeC programs are invoked for use by a data-processing
-             system;
-       \item the mechanism by which input data are transformed for use by a QuakeC program;
-       \item the size or complexity of a program and its data that will exceed the capacity
-             of any specific data-processing system or the capacity of a particular
-             execution environment;
-       \item all minimal requirements of a data-processing system that is capable of
-             supporting a conforming implementation.
-\end{itemize}
-
-%% Table Of Contents %%
-\newpage
-\thispagestyle{empty}
-\tableofcontents
-\newpage
-
-%% Begin Contents %%
-\raggedright % No weird TEX spacing on lines to fill page
-
-%% -> Terms, definitions, and symbols %%
-\section{Terms, definitions, and symbols}
-\subsection*{argument}
-Expression in the comma-separated list bounded by the parentheses in a function call
-expression, or a sequence of preprocessing tokens in the comma-separated list bounded
-by the parentheses in a function-like macro invocation.
-
-\subsection*{behavior}
-External appearance or action
-
-\subsection*{implementation-defined behavior}
-Unspecified behavior where each implementation documents how the choice is made.
-
-\subsection*{undefined behavior}
-Behavior, upon use of a non-portable or erroneous program construct or of erroneous data,
-for which this document imposes no actual requirements.
-
-\subsection*{unspecified behavior}
-Use of an unspecified value, or other behavior where this document provides two or more
-possibilities and imposes no further requirements on which is chosen in any instance.
-
-\subsection*{constraint}
-Restriction, either syntactic or semantic, by which the exposition of language elements
-is to be interpreted.
-
-\subsection*{diagnostic message}
-Message belonging to an implementation-defined subset of the implementation's message
-output.
-
-\subsection*{object}
-Region of data storage in the execution environment, the contents of which can represent
-values.
-
-\subsection*{parameter}
-Object declared as part of a function declaration or definition that acquires a value on
-entry to the function, or an identifier from the comma-separated list bounded by the
-parentheses immediately following the macro name in a function-like macro definition.
-
-\subsection*{recommended practice}
-Specification that is strongly recommended as being in keeping with the intent of this
-document, but that may be impractical for some implementations.
-
-\subsection*{value}
-Precise meaning of the contents of an object when interpreted as having a specific type.
-
-\subsection*{implementation}
-Particular set of software, running in a particular translation environment under
-particular control options, that performs translation of programs for, and supports
-execution of functions in, a particular execution environment.
-
-\subsection*{implementation-defined value}
-Unspecified value where each implementation documents how the choice is made.
-
-\subsection*{unspecified value}
-Valid value of the relevant type where this document imposes no requirements on which
-value is chosen in any instance.
-
-%% -> Conformance %%
-\section{Conformance}
-In this document, "shall" is to be interpreted as a requirement on an implementation
-or on a program; conversely, "shall not" is to be interpreted as a prohibition. \\
-If a "shall" or "shall not" requirement that appears outside of a constraint is violated,
-the behavior is undefined.  Undefined behavior is otherwise indicated in this document by
-the words "undefined behavior" or by the omission of any explicit definition of behavior.
-There is no difference in emphasis among these three; they all describe "behavior that is
-undefined".
-
-%% -> Enviroment %%
-\section{Environment}
-An implementation that translates QuakeC source files and executes QuakeC programs in two
-data processing-system environments, which will be called the translation environment and
-the execution environment in this document. Their characteristics define and constrain the
-results of executing QuakeC programs constructed according to the syntactic and semantic
-rules for conforming implementations.
-\subsection{Conceptual models}
-\subsubsection{Translation environment}
-\paragraph*{Translation steps}
-The precedence among the syntax rules of translation is specified by the following steps
-\begin{enumerate}
-       \item Physical source file characters are mapped, in an implementation-defined manner,
-             to the source character set (introducing new-line characters for end-of-line
-             indicators) if necessary.  Trigraph and digraph sequences are replaced by their
-             corresponding single-character internal representations.
-       \item The source file is decomposed into preprocessing tokens and sequences of white-
-             space characters (including comments). A source file shall not end in a partial
-             preprocessing token or in a partial comment. Each comment is replaced by one
-             space character. New-line characters are retained. Whether each nonempty
-             sequences of white-space characters other than new-line is retained or replaced
-             by one space character is implementation-defined.
-       \item Preprocessing directives are executed, macro invocations are expanded 
-             recursively. A \#include preprocessing directive causes the named header or
-             source file to be processed from step one through step three, recursively. All
-             preprocessing directives are then deleted.
-       \item Each source character set member and escape sequence in character constants and
-             string literals is converted to the corresponding member of the execution
-             character set; if there is no corresponding member, it is converted to an
-             implementation-defined member other than the null character.
-       \item Adjacent string literal tokens are concatenated.
-       \item White-space characters seperating tokens are no longer significant. Each
-             preprocessing token is converted into a token. The resulting tokens are then
-             syntactically and semantically analyzed and translated.                         
-\end{enumerate}
-\subparagraph*{Footnotes}
-Implementations shall behave as if these steps occur separately, even though many are likely
-to be folded together in practice. Source files need not be stored as file, nor need there
-be any one-to-one correspondence between these items and any external representation. The
-description is conceptual only, and does not specify any particular implementation.
-
-\paragraph*{Diagnostics}
-A conforming implementation shall produce at least on diagnostic message(identified in an
-implementation-defined manner) if a source file contains a violation of any syntax rule or
-constraint, even if the behavior is also explicitly specified as undefined or
-implementation-defined. Diagnostic messages need not be produced in other circumstances.
-
-%% ->-> Execution environments %%
-\subsubsection{Execution environment}
-A conforming execution environment shall provide at minimal the following 15 definitions
-for built in functions, with an accompanying header or source file that defines them.
-\begin{enumerate}
-       \item entity ()                                spawn
-       \item void   (entity)                          remove
-       
-       \item string (float)                           ftos
-       \item string (vector)                          vtos
-       \item string (entity)                          etos
-       \item float  (string)                          stof
-       
-       \item void   (string, ...)                     dprint
-       \item void   (entity)                          eprint
-       
-       \item float  (float)                           rint
-       \item float  (float)                           floor
-       \item float  (float)                           ceil
-       \item float  (float)                           fabs
-       \item float  (float)                           sin
-       \item float  (float)                           cos
-       \item float  (float)                           sqrt
-\end{enumerate}
-The numbers of which these built-ins are assigned is implementation-defined;
-an implementation is allowed to use these built-ins however it sees fit.
-
-\pagebreak
-%% -> Language %%
-\section{Language}
-\subsection{Notation}
-The syntax notation used in this document is that of a BNF specification.  A set of
-derivation rules, often written as:
-\begin{lstlisting}[language=bnf]
-       symbol ::= expression
-\end{lstlisting}
-Where symbol is a nonterminal, and the expression consists of one or more sequences of
-symbols; more sequences are separated by a vertical bar \textbar, indicating a choice,
-the whole being a possible substitution for the symbol on the left.  Symbols that never
-appear on the left side are terminals.
-\linebreak
-
-This document defines language syntax throughout it's way at defining language
-constructs If you're interested in a summary of the language syntax, one is given in 
-annex A.
-
-%% -> Concepts %%
-\subsection{Concepts}
-%% ->-> Scopes of identifiers %%
-\subsubsection{Scopes of identifiers}
-An identifier can denote an object; a function, or enumeration; a label name; a macro
-name; or a macro parameter. The same identifier can denote different items at different
-points in the program. A member of an enumeration is called an enumeration constant.
-Macro names and macro parameters are not considered further here, because prior to the
-semantic phase of program translation any occurrences of macro names in the source file
-are replaced by the preprocessing token sequences that constitute their macro definitions.
-\linebreak
-
-For each different item that an identifier designates, the identifier is visible (i.e, 
-can be used) only within a region of program text called its scope.  Different items 
-designated by the same identifier either have different scopes, or are in different name
-spaces. There are four kinds of scopes: function, file, block and function prototype.
-(A function prototype is a declaration of a function that declares the types of its
-parameters.)
-\linebreak
-
-A label name is the only kind of identifier that has function scope.  It can be used (in 
-a goto statement) anywhere in the function in which it appears, and is declared
-implicitly by its syntactic appearance (prefixed by a colon :, and suffixed with a 
-statement).
-\linebreak
-
-Every other identifier has scope determined by the placement of its declaration (in a 
-declarator or type specifier).  If the declarator or type specifier that declares the 
-identifier appears outside any block or list of parameters, the identifier has file 
-scope,  which terminates at the end of the file.  If the declartor or type specifier that
-declares the identifier appears inside a block or within the list of parameter
-declarations in a  function definition, the identifier has block scope, which terminates
-at the end of the  associated block.  If the declarator or type specifier that declares 
-the identifier appears  within the list of parameter declarations in a function prototype
-(not part of a function definition), the identifier has function prototype scope, which 
-terminates at the end of the function declarator.  If an identifier designates two 
-different items in the same  name space, the scopes might overlap.  If so, the scope of 
-one item (the inner scope) will be a strict subset of the scope of the other item (the 
-outer scope). Within the inner scope, the identifier designates the item declared in the
-inner scope; the item declared in the  outer scope is hidden (and not visible) within 
-the inner scope.
-\linebreak
-
-Unless explicitly stated otherwise, where this document uses the term "identifier" to 
-refer to some item (as opposed to the syntactic construct), it refers to the item in the 
-relevant name space whose declaration is visible at the point the identifier occurs.
-\linebreak
-
-Two identifiers have the same scope if and only if their scopes terminate at the same 
-point.
-\linebreak
-
-Each enumeration constant has scope that begins just after the appearance of its defining
-enumerator in an enumerator list. Any other identifier has scope that begins just after 
-the completion of its declarator.
-
-%% ->-> Name spaces of identifiers %%
-\subsubsection{Name spaces of identifiers}
-If more than one declaration of a particular identifier is visible at any point in a 
-source file, the syntactic context disambiguates uses that refer to different items. 
-Thus, there  are separate name spaces for various categories of identifiers, as follows:
-\linebreak
-\begin{itemize}
-       \item Label names (disambiguated by the syntax of the label declaration and use);
-       \item Enumerations (disambiguated by following the keyword enum);
-       \item All other identifiers, called ordinary identifiers (declared in ordinary
-             declarators or as enumeration constants).
-\end{itemize}
-
-%% ->-> Types %%
-\subsubsection{Types}
-The meaning of a value stored in an object returned by a function is determined by the 
-type  of the expression used to access it. (An identifier declared to be an object is the simplest
-such expression; the type is specified in the declaration of the identifier.)  Types are 
-partitioned into object types (types that fully describe objects), function types(types 
-that  describe functions), and incomplete types(types that describe objects but lack 
-information).
-\linebreak
-
-An object declared type bool is large enough to store the values 0 and 1.
-\linebreak
-
-An object declared type float is a real type; An object declared type vector is a 
-comprised set of three floats that respectively represent the \underline{x,y,z} 
-components of a three-dimensional vector.
-\linebreak
-
-An enumeration comprises a set of named integer constant values.  Each distinct 
-enumeration constitutes a different enumerated type.
-\linebreak
-
-Enumeration types and float are collectively called arithmetic types. Each arithmetic 
-type belongs to one type domain.
-\linebreak
-
-The void type comprises an empty set of values; it is an incomplete type that cannot be
-completed.
-\linebreak
-
-A number of derived types can be constructed from the object, function and incomplete 
-types, as follows:
-\linebreak
-
-\begin{itemize}
-       \item An array type describes a contiguously allocated nonempty set of objects with a 
-          particular object type, called the element type. Array types are characterized 
-          by their element type and by the number of elements in the array.  An array type
-          is said to be derived        from its element type, and if its element is type T, the 
-          array type is sometimes called "array of T".  The construction of an array type
-          from an element type is called "array type derivation".
-       \item A function type describes a function with a specified return type. A function
-          type is characterized by its return type and the number and types of its
-          parameters. A function type is said to be derived from its return type, and if 
-          its return type is T, the function type is sometimes called "function returning
-          T". The construction of a function type from a return type is called "function
-          type derivation".
-\end{itemize}
-
-Arithmetic types are collectively called scalar types. Arrays and vectors are 
-collectively called aggregate types.
-\linebreak
-
-An array of unknown size is an incomplete type.  It is completed, for an identifier of 
-that type, by specifying the size in a later declaration.  Arrays are required to have 
-known constant size.
-\linebreak
-
-A type is characterized by its type category, which is either the outermost derivation 
-of a derived type (as noted above in the construction of derived types), or the type 
-itself if the type consists of no derived types.
-\linebreak
-
-Any type so far mentioned is an unqualified type.  Each unqualified type has several 
-qualified versions of its type, corresponding to the combinations of one, two, or all 
-two of const and volatile qualifiers.  The qualified or unqualified versions of a type 
-are distinct types that belong to the same type category and have the same representation.
-A derived type is not qualified by the qualifiers (if any) of the type from which it 
-is derived.
-\linebreak
-
-%% ->-> Compatible types and composite type %%
-\subsubsection{Compatible types and composite type}
-Two types have compatible type if their types are the same.
-\linebreak
-
-All declarations that refer to the same object or function shall have compatible type; 
-otherwise the behavior is undefined.
-\linebreak
-
-A composite type can be constructed from two types that are compatible; it is a type that 
-is compatible with both of the two types and satisfies the following conditions:
-\begin{itemize}
-       \item If one type is an array, the composite type is an array of that size.
-       \item If only one type is a function type with a parameter type list(a function 
-             prototype), the composite type is a function prototype with the parameter type 
-             list.
-       \item If both types are function types with parameter type lists, the type of each 
-             parameter in the composite parameter type list is the composite type of the 
-             corresponding parameters.
-\end{itemize}
-These rules apply recursively to types from which the two types are derived.
-\linebreak
-
-%% ->Conversions %%
-\subsection{Conversions}
-Several operators convert operand values from one type to another automatically. This 
-sub-clause specifies the result required from such an implicit conversion.
-\linebreak
-
-Conversion from an operand value to a compatible type causes no change to the value or
-the representation.
-\linebreak
-
-TODO: Specify all implicit conversions.
-
-%% ->->Aritmetic operands %%
-\subsubsection{Arithmetic operands}
-\paragraph*{Boolean type}
-When any scalar value is converted to bool, the result is 0 if the value compares equal 
-to 0; otherwise the result is 1.
-
-%% ->->Other operands %%
-\subsubsection{Other operands}
-\paragraph{Lvalues, arrays and function designators}
-An lvalue is an expression with an object type or an incomplete type other than void; 
-if an lvalue does not designate an object when it is evaluated, the behavior is undefined.
-When an object is said to have a particular type, the type is specified by the lvalue 
-used to designate the object.  A modifiable lvalue is an lvalue that does not have an 
-array type, does not have an incomplete type, and does not have a const-qualified type.
-\linebreak
-
-Except when it is the operand of the unary \& operator, the ++ operator, the -- operator,
-or the left operand of the . operator or an assignment operator, an lvalue that does not 
-have array type is converted to the value stored in the designated object (and is no 
-longer an lvalue).  If the lvalue has qualified type, the value has the unqualified 
-version of the type of the lvalue; otherwise, the value has the type of the lvalue. If 
-the lvalue has an incomplete type and does not have array type, the behavior is undefined.
-\linebreak
-
-A function designator is an expression that has function type.
-
-\paragraph*{void}
-The (nonexistent) value of a void expression (an expression that has type void) shall not 
-be used in any way, and implicit conversions (except to void) shall not be applied to 
-such an expression.  If an expression of any other type is evaluated as a void expression,
-its value or designator is discarded. (A void expression is only evaluated for its 
-side effects.)
-\pagebreak
-
-\subsection{Lexical elements}
-\paragraph*{Syntax}
-\begin{lstlisting}[language=bnf]
-token ::= keyword    
-        | identifier
-        | constant   
-        | string-literal
-        | punctuator
-preprocessing-token ::= header-name 
-                      | identifier
-                      | pp-number   
-                      | string-literal
-                      | punctuator
-\end{lstlisting}
-\paragraph*{Constraints}
-Each preprocessing token that is converted to a token shall have the lexical form of a 
-keyword, an identifier, a constant, a string literal, or a punctuator.
-
-\paragraph*{Semantics}
-A token is the minimal lexical element of the language in translation steps six and seven.
-The categories of tokens are: keywords, identifiers, constants, string literals, and
-punctuators. A preprocessing token is the minimal lexical element of the language in
-translation steps three through five. The categories of preprocessing tokens are: header
-names, identifiers, preprocessing numbers, string literals, punctuators and other single
-non-white-space characters that do not lexically match the other preprocessing token
-categories. If a ' or a " character matches the last category, the behavior is undefined.
-Preprocessing tokens can be separated by white space; this consists of comments (described
-later), or white-space characters (space, horizontal tab, new-line, vertical tab, and form
--feed), or both. In certain circumstances during translation step four, white space (or
-the absence thereof) serves as more than preprocessing token separation. White space may 
-appear within a preprocessing token only as part of a header name or between the quotation
-characters in a string literal.
-\linebreak
-
-If the input stream has been parsed into preprocessing tokens up to a given character, the
-next preprocessing token is the longest sequence of characters that could constitute a
-preprocessing token. There is one exception to this rule: header name preprocessing tokens
-are recognized only within \#include preprocessing directives and in implementation-defined
-locations within \#pragma directives. In such contexts, a sequence of characters that
-could be either a header name or string literal is recognized as the former.
-
-%% ->-> Keywords %%
-\subsubsection{Keywords}
-\paragraph*{Syntax}
-\begin{lstlisting}[language=bnf]
-keyword ::= enum     | break  | return   | void
-          | case     | float  | volatile | for
-          | while    | const  | goto     | bool
-          | continue | if     | static   | default
-          | inline   | do     | switch   | else
-          | vector   | entity
-\end{lstlisting}
-\paragraph*{Semantics}
-The above tokens (case sensitive) are reserved (in translation step seven and eight) for 
-use as keywords, and shall not be used otherwise.
-
-%% ->->Identifiers %%
-\subsubsection{Identifiers}
-\begin{lstlisting}[language=bnf]
-identifier ::= nondigit
-             | identifier nondigit
-             | identifier digit
-             
-nondigit ::= _ | a | b | c | d | e | f | g | h | i 
-           | j | k | l | m | n | o | p | q | r | s
-           | t | u | v | w | x | y | z | A | B | C
-           | D | E | F | G | H | I | J | K | L | M
-           | N | P | Q | R | S | T | U | V | W | X
-           | Y | Z
-
-digit ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
-\end{lstlisting}
-\paragraph*{Semantics}
-An identifier is a sequence of nondigit characters (including the underscore \_, the lower
-case and upper case Latin letters, and other characters) and digits, which designates one
-or more items. Lowercase and uppercase letters are distinct. There is a specific limit of
-65535 characters for an identifier.
-\linebreak
-
-When preprocessing tokens are converted to tokens during translation step six, if a 
-preprocessing token could not be converted to either a keyword or an identifier, it is
-converted to a keyword.
-
-\paragraph*{Predefined identifiers}
-Any identifiers that begin with the prefix \_\_builtin, or are within the reserved name
-space are reserved by the implementation.
-
-%% ->->Constants %%
-\subsubsection{Constants}
-\begin{lstlisting}[language=bnf]
-constant             ::= integer-constant
-                       | floating-constant
-                       | enumeration-constant
-                       | character-constant
-                       | vector-constant
-           
-integer-constant     ::= decimal-constant
-                       | octal-constant
-                       | hexadecimal-constant
-                   
-decimal-constant     ::= nonzero-digit
-                       | decimal-constant digit
-                   
-octal-constant       ::= 0
-                       | octal-constant octal-digit
-                   
-hexadecimal-constant ::= hexdecimal-prefix
-                         hexadecimal-digit
-                       | hexadecimal-digit
-                         hexadecimal-constant
-
-hexadecimal-prefix:  ::= 0x | 0X
-
-nonzero-digit        ::= 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8
-                       | 9
-                       
-octal-digit          ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7
-
-hexadecimal-digit    ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7
-                       | 8 | 9 | a | b | c | d | e | f
-                       | A | B | C | D | E | F
-\end{lstlisting}
-
-%% ->-> String literals %%
-\subsubsection{String literals}
-\begin{lstlisting}[language=bnf]
-string-literal  := " s-char-sequence "
-
-s-char-sequence := s-char
-                 | s-char-sequence s-char
-                 
-s-char          := ` | ! | @ | # | $ | % | ^ | & | *
-                 | ( | ) | _ | - | + | = | { | } | [
-                 | ] | | | : | ; | ' | < | , | > | .
-                 | ? | / | 1 | 2 | 3 | 4 | 5 | 6 | 7
-                 | 8 | 9 | 0 | q | w | e | r | t | y
-                 | u | i | o | p | a | s | d | f | g
-                 | h | j | k | l | z | x | c | v | b 
-                 | n | m | Q | W | E | R | T | Y | U
-                 | I | O | P | A | S | D | F | G | |
-                 | H | J | K | L | Z | X | C | V | B
-                 | N | M
-\end{lstlisting}
-\paragraph*{Description}
-A character string literal is a sequence of zero or more characters enclosed in 
-double-quotes, as in "xyz".
-\linebreak
-
-The same considerations apply to each element of the sequence in a character string
-literal as if it where an integer character constant, except that the single-quote 
-' is representable either by itself or by the escape sequence \textbackslash', but 
-the double-quote " shall be represented by the escape sequence \textbackslash".
-
-\paragraph*{Semantics}
-In translation stage six, the character sequences specified by any sequence of adjacent 
-character string literal tokens are concatenated into a single character sequence.
-
-%% ->-> Punctuators %%
-\subsubsection{Punctuators}
-TODO: BNF
-
-A punctuator is a symbol that has independent syntactic and semantic significance. 
-Depending on context, it may specify an operation to be performed (which in turn 
-may yield a value or a function designator, produce a side effect, or some combination 
-thereof) in which case it is known as an operator (other forms of operator also exist 
-in some contexts). An operand is an item on which an operator acts.
-\linebreak
-
-TODO: Trigraphs \& Digraphs
-
-\subsubsection{Header names}
-TODO
-\subsubsection{Preprocessing numbers}
-TODO
-\subsubsection{Comments}
-Except within a character constant, a string literal, or a comment, the characters /* 
-introduce a comment.  The contents of such a comment are examined only to identify 
-characters and to find the characters */ that terminate it.
-\linebreak
-
-Except within a character constant, a string literal, or a comment, the characters // 
-introduce a comment that includes all characters up to, but not including, the next 
-new-line character.  The contents of such a comment are examined only to identify 
-characters and to find the terminating new-line character.
-\linebreak
-
-%% -> Expressions %%
-\subsection{Expressions}
-An expression is a sequence of operators and operands that specifies computation of a 
-value, or that designates an object or function, or that generates side effects, or that 
-performs a combination thereof.
-\linebreak
-
-Between the previous and next sequence point an object shall have its stored value 
-modified at most once by the evaluation of an expression. Furthermore, the prior value 
-shall be read only to determine the value to be stored.
-\linebreak
-
-The grouping of operators and operands is indicated by the syntax. Except as specified 
-later (for the function call (), \&\&, \textbar\textbar ?:, and comma operators), the 
-order of evaluation of sub-expressions and the order in which side effects take place 
-are both unspecified.
-\linebreak
-
-Some operators (the unary \textasciitilde operator, and the binary operators \textless
-\textless, \textgreater\textgreater, \&, \^, and \textbar, collectively describe bitwise 
-operators) are required to have operands that are either integer, or floating point with
-zero points of decimal precision.
-\linebreak
-
-If an exceptional condition occurs during the evaluation of an expression (that is, if 
-the result is not mathematically defined or not in the range or representable values for 
-its type), the behavior is undefined.
-
-%% ->-> Primary expressions %%
-\subsubsection{Primary expressions}
-\paragraph*{Syntax}
-\begin{lstlisting}[language=bnf]
-primary-expression ::= identifier
-                     | constant
-                     | string-literal
-                     ( expression )
-\end{lstlisting}
-\paragraph*{Semantics}
-An identifier is a primary expression, provided it has been declared as designating an 
-object(in which case it is an lvalue) or a function(in which case it is a function 
-designator).
-\linebreak
-
-A constant is a primary expression. Its type depends on its form and value.
-\linebreak
-
-A string literal is a primary expression.  It is an lvalue.
-\linebreak
-
-A parenthesized expression is a primary expression.  Its type and value identical to 
-those of the unparenthesized expression.  It is an lvalue, a function designator, or a 
-void expression if the unparenthesized expression is, respectively, an lvalue, a 
-function designator, or a void expression.
-
-%% ->-> Constant expressions %%
-\subsubsection{Constant expressions}
-\paragraph*{Syntax}
-\begin{lstlisting}[language=bnf]
-constant-expression ::= conditional-expression
-\end{lstlisting}
-\paragraph*{Description}
-A constant expression can be evaluated during translation rather than runtime, and
-accordingly may be used in any place that a constant may be.
-\paragraph*{Constraints}
-\begin{itemize}
-       \item Constant expressions shall not contain assignment, increment, decrement,
-                 function-call, or comma operators, except when contained within a subexpression
-                 that is not evaluated.
-       \item Each constant expression shall evaluate to a constant that is in range of
-                 representable values for its type.
-\end{itemize}
-\paragraph*{Semantics}
-An expression that evaluates to a constant is required in several contexts. If a floating
-point expression is evaluated in the translation environment, the arithmetic precision range
-shall be as great is if the expression were being evaluated in the execution environment.
-\linebreak
-
-An integer constant expression shall have integer type and shall only have operands that 
-are integer constants, enumeration constants, character constants, and floating constants
-that are the immediate operand of casts. Cast operators in an integer constant expression 
-shall only convert arithmetic types to integer types.
-\linebreak
-
-More latitude is permitted for constant expressions in initializers. Such a constant expression
-shall be, or evaluate to an arithmetic constant expression.
-\linebreak
-
-An arithmetic constant expression shall have arithmetic type and shall only have operands that
-are integer constants, floating constants, enumeration constants, and character constants. Cast
-operators in an arithmetic constant expression shall only convert arithmetic types to arithmetic
-types.
-\linebreak
-
-An implementation may accept other forms of constant expressions.
-\linebreak
-
-The semantic rules for the evaluation of a constant expression are the same as for nonconstant
-expressions.
-
-
-\bibliographystyle{abbrv}
-\bibliography{main}
-
-\end{document}
diff --git a/exec.c b/exec.c
deleted file mode 100644 (file)
index b24cda1..0000000
--- a/exec.c
+++ /dev/null
@@ -1,1660 +0,0 @@
-/*
- * Copyright (C) 2012, 2013, 2014, 2015
- *     Wolfgang Bumiller
- *     Dale Weiler
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
- * this software and associated documentation files (the "Software"), to deal in
- * the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is furnished to do
- * so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-#ifndef QCVM_LOOP
-#include <errno.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-
-#include "gmqcc.h"
-
-static void loaderror(const char *fmt, ...)
-{
-    int     err = errno;
-    va_list ap;
-    va_start(ap, fmt);
-    vprintf(fmt, ap);
-    va_end(ap);
-    printf(": %s\n", util_strerror(err));
-}
-
-static void qcvmerror(qc_program_t *prog, const char *fmt, ...)
-{
-    va_list ap;
-
-    prog->vmerror++;
-
-    va_start(ap, fmt);
-    vprintf(fmt, ap);
-    va_end(ap);
-    putchar('\n');
-}
-
-qc_program_t* prog_load(const char *filename, bool skipversion)
-{
-    prog_header_t   header;
-    qc_program_t   *prog;
-    size_t          i;
-    fs_file_t      *file  = fs_file_open(filename, "rb");
-
-    /* we need all those in order to support INSTR_STATE: */
-    bool            has_self      = false,
-                    has_time      = false,
-                    has_think     = false,
-                    has_nextthink = false,
-                    has_frame     = false;
-
-    if (!file)
-        return NULL;
-
-    if (fs_file_read(&header, sizeof(header), 1, file) != 1) {
-        loaderror("failed to read header from '%s'", filename);
-        fs_file_close(file);
-        return NULL;
-    }
-
-    util_swap_header(&header);
-
-    if (!skipversion && header.version != 6) {
-        loaderror("header says this is a version %i progs, we need version 6\n", header.version);
-        fs_file_close(file);
-        return NULL;
-    }
-
-    prog = (qc_program_t*)mem_a(sizeof(qc_program_t));
-    if (!prog) {
-        fs_file_close(file);
-        fprintf(stderr, "failed to allocate program data\n");
-        return NULL;
-    }
-    memset(prog, 0, sizeof(*prog));
-
-    prog->entityfields = header.entfield;
-    prog->crc16 = header.crc16;
-
-    prog->filename = util_strdup(filename);
-    if (!prog->filename) {
-        loaderror("failed to store program name");
-        goto error;
-    }
-
-#define read_data(hdrvar, progvar, reserved)                           \
-    if (fs_file_seek(file, header.hdrvar.offset, SEEK_SET) != 0) {     \
-        loaderror("seek failed");                                      \
-        goto error;                                                    \
-    }                                                                  \
-    if (fs_file_read (                                                 \
-            vec_add(prog->progvar, header.hdrvar.length + reserved),   \
-            sizeof(*prog->progvar),                                    \
-            header.hdrvar.length,                                      \
-            file                                                       \
-        )!= header.hdrvar.length                                       \
-    ) {                                                                \
-        loaderror("read failed");                                      \
-        goto error;                                                    \
-    }
-#define read_data1(x)    read_data(x, x, 0)
-#define read_data2(x, y) read_data(x, x, y)
-
-    read_data (statements, code, 0);
-    read_data1(defs);
-    read_data1(fields);
-    read_data1(functions);
-    read_data1(strings);
-    read_data2(globals, 2); /* reserve more in case a RETURN using with the global at "the end" exists */
-
-    util_swap_statements (prog->code);
-    util_swap_defs_fields(prog->defs);
-    util_swap_defs_fields(prog->fields);
-    util_swap_functions  (prog->functions);
-    util_swap_globals    (prog->globals);
-
-    fs_file_close(file);
-
-    /* profile counters */
-    memset(vec_add(prog->profile, vec_size(prog->code)), 0, sizeof(prog->profile[0]) * vec_size(prog->code));
-
-    /* Add tempstring area */
-    prog->tempstring_start = vec_size(prog->strings);
-    prog->tempstring_at    = vec_size(prog->strings);
-    memset(vec_add(prog->strings, 16*1024), 0, 16*1024);
-
-    /* spawn the world entity */
-    vec_push(prog->entitypool, true);
-    memset(vec_add(prog->entitydata, prog->entityfields), 0, prog->entityfields * sizeof(prog->entitydata[0]));
-    prog->entities = 1;
-
-    /* cache some globals and fields from names */
-    for (i = 0; i < vec_size(prog->defs); ++i) {
-        const char *name = prog_getstring(prog, prog->defs[i].name);
-        if      (!strcmp(name, "self")) {
-            prog->cached_globals.self = prog->defs[i].offset;
-            has_self = true;
-        }
-        else if (!strcmp(name, "time")) {
-            prog->cached_globals.time = prog->defs[i].offset;
-            has_time = true;
-        }
-    }
-    for (i = 0; i < vec_size(prog->fields); ++i) {
-        const char *name = prog_getstring(prog, prog->fields[i].name);
-        if      (!strcmp(name, "think")) {
-            prog->cached_fields.think     = prog->fields[i].offset;
-            has_think = true;
-        }
-        else if (!strcmp(name, "nextthink")) {
-            prog->cached_fields.nextthink = prog->fields[i].offset;
-            has_nextthink = true;
-        }
-        else if (!strcmp(name, "frame")) {
-            prog->cached_fields.frame     = prog->fields[i].offset;
-            has_frame = true;
-        }
-    }
-    if (has_self && has_time && has_think && has_nextthink && has_frame)
-        prog->supports_state = true;
-
-    return prog;
-
-error:
-    if (prog->filename)
-        mem_d(prog->filename);
-    vec_free(prog->code);
-    vec_free(prog->defs);
-    vec_free(prog->fields);
-    vec_free(prog->functions);
-    vec_free(prog->strings);
-    vec_free(prog->globals);
-    vec_free(prog->entitydata);
-    vec_free(prog->entitypool);
-    mem_d(prog);
-
-    fs_file_close(file);
-    return NULL;
-}
-
-void prog_delete(qc_program_t *prog)
-{
-    if (prog->filename) mem_d(prog->filename);
-    vec_free(prog->code);
-    vec_free(prog->defs);
-    vec_free(prog->fields);
-    vec_free(prog->functions);
-    vec_free(prog->strings);
-    vec_free(prog->globals);
-    vec_free(prog->entitydata);
-    vec_free(prog->entitypool);
-    vec_free(prog->localstack);
-    vec_free(prog->stack);
-    vec_free(prog->profile);
-    mem_d(prog);
-}
-
-/***********************************************************************
- * VM code
- */
-
-const char* prog_getstring(qc_program_t *prog, qcint_t str) {
-    /* cast for return required for C++ */
-    if (str < 0 || str >= (qcint_t)vec_size(prog->strings))
-        return  "<<<invalid string>>>";
-
-    return prog->strings + str;
-}
-
-prog_section_def_t* prog_entfield(qc_program_t *prog, qcint_t off) {
-    size_t i;
-    for (i = 0; i < vec_size(prog->fields); ++i) {
-        if (prog->fields[i].offset == off)
-            return (prog->fields + i);
-    }
-    return NULL;
-}
-
-prog_section_def_t* prog_getdef(qc_program_t *prog, qcint_t off)
-{
-    size_t i;
-    for (i = 0; i < vec_size(prog->defs); ++i) {
-        if (prog->defs[i].offset == off)
-            return (prog->defs + i);
-    }
-    return NULL;
-}
-
-qcany_t* prog_getedict(qc_program_t *prog, qcint_t e) {
-    if (e >= (qcint_t)vec_size(prog->entitypool)) {
-        prog->vmerror++;
-        fprintf(stderr, "Accessing out of bounds edict %i\n", (int)e);
-        e = 0;
-    }
-    return (qcany_t*)(prog->entitydata + (prog->entityfields * e));
-}
-
-static qcint_t prog_spawn_entity(qc_program_t *prog) {
-    char  *data;
-    qcint_t  e;
-    for (e = 0; e < (qcint_t)vec_size(prog->entitypool); ++e) {
-        if (!prog->entitypool[e]) {
-            data = (char*)(prog->entitydata + (prog->entityfields * e));
-            memset(data, 0, prog->entityfields * sizeof(qcint_t));
-            return e;
-        }
-    }
-    vec_push(prog->entitypool, true);
-    prog->entities++;
-    data = (char*)vec_add(prog->entitydata, prog->entityfields);
-    memset(data, 0, prog->entityfields * sizeof(qcint_t));
-    return e;
-}
-
-static void prog_free_entity(qc_program_t *prog, qcint_t e) {
-    if (!e) {
-        prog->vmerror++;
-        fprintf(stderr, "Trying to free world entity\n");
-        return;
-    }
-    if (e >= (qcint_t)vec_size(prog->entitypool)) {
-        prog->vmerror++;
-        fprintf(stderr, "Trying to free out of bounds entity\n");
-        return;
-    }
-    if (!prog->entitypool[e]) {
-        prog->vmerror++;
-        fprintf(stderr, "Double free on entity\n");
-        return;
-    }
-    prog->entitypool[e] = false;
-}
-
-qcint_t prog_tempstring(qc_program_t *prog, const char *str) {
-    size_t len = strlen(str);
-    size_t at = prog->tempstring_at;
-
-    /* when we reach the end we start over */
-    if (at + len >= vec_size(prog->strings))
-        at = prog->tempstring_start;
-
-    /* when it doesn't fit, reallocate */
-    if (at + len >= vec_size(prog->strings))
-    {
-        (void)vec_add(prog->strings, len+1);
-        memcpy(prog->strings + at, str, len+1);
-        return at;
-    }
-
-    /* when it fits, just copy */
-    memcpy(prog->strings + at, str, len+1);
-    prog->tempstring_at += len+1;
-    return at;
-}
-
-static size_t print_escaped_string(const char *str, size_t maxlen) {
-    size_t len = 2;
-    putchar('"');
-    --maxlen; /* because we're lazy and have escape sequences */
-    while (*str) {
-        if (len >= maxlen) {
-            putchar('.');
-            putchar('.');
-            putchar('.');
-            len += 3;
-            break;
-        }
-        switch (*str) {
-            case '\a': len += 2; putchar('\\'); putchar('a'); break;
-            case '\b': len += 2; putchar('\\'); putchar('b'); break;
-            case '\r': len += 2; putchar('\\'); putchar('r'); break;
-            case '\n': len += 2; putchar('\\'); putchar('n'); break;
-            case '\t': len += 2; putchar('\\'); putchar('t'); break;
-            case '\f': len += 2; putchar('\\'); putchar('f'); break;
-            case '\v': len += 2; putchar('\\'); putchar('v'); break;
-            case '\\': len += 2; putchar('\\'); putchar('\\'); break;
-            case '"':  len += 2; putchar('\\'); putchar('"'); break;
-            default:
-                ++len;
-                putchar(*str);
-                break;
-        }
-        ++str;
-    }
-    putchar('"');
-    return len;
-}
-
-static void trace_print_global(qc_program_t *prog, unsigned int glob, int vtype) {
-    static char spaces[28+1] = "                            ";
-    prog_section_def_t *def;
-    qcany_t    *value;
-    int       len;
-
-    if (!glob) {
-        if ((len = printf("<null>,")) == -1)
-            len = 0;
-
-        goto done;
-    }
-
-    def = prog_getdef(prog, glob);
-    value = (qcany_t*)(&prog->globals[glob]);
-
-    len = printf("[@%u] ", glob);
-    if (def) {
-        const char *name = prog_getstring(prog, def->name);
-        if (name[0] == '#')
-            len += printf("$");
-        else
-            len += printf("%s ", name);
-        vtype = def->type & DEF_TYPEMASK;
-    }
-
-    switch (vtype) {
-        case TYPE_VOID:
-        case TYPE_ENTITY:
-        case TYPE_FIELD:
-        case TYPE_FUNCTION:
-        case TYPE_POINTER:
-            len += printf("(%i),", value->_int);
-            break;
-        case TYPE_VECTOR:
-            len += printf("'%g %g %g',", value->vector[0],
-                                         value->vector[1],
-                                         value->vector[2]);
-            break;
-        case TYPE_STRING:
-            if (value->string)
-                len += print_escaped_string(prog_getstring(prog, value->string), sizeof(spaces)-len-5);
-            else
-                len += printf("(null)");
-            len += printf(",");
-            /* len += printf("\"%s\",", prog_getstring(prog, value->string)); */
-            break;
-        case TYPE_FLOAT:
-        default:
-            len += printf("%g,", value->_float);
-            break;
-    }
-done:
-    if (len < (int)sizeof(spaces)-1) {
-        spaces[sizeof(spaces)-1-len] = 0;
-        fs_file_puts((fs_file_t*)stdout, spaces);
-        spaces[sizeof(spaces)-1-len] = ' ';
-    }
-}
-
-static void prog_print_statement(qc_program_t *prog, prog_section_statement_t *st) {
-    if (st->opcode >= VINSTR_END) {
-        printf("<illegal instruction %d>\n", st->opcode);
-        return;
-    }
-    if ((prog->xflags & VMXF_TRACE) && vec_size(prog->function_stack)) {
-        size_t i;
-        for (i = 0; i < vec_size(prog->function_stack); ++i)
-            printf("->");
-        printf("%s:", vec_last(prog->function_stack));
-    }
-    printf(" <> %-12s", util_instr_str[st->opcode]);
-    if (st->opcode >= INSTR_IF &&
-        st->opcode <= INSTR_IFNOT)
-    {
-        trace_print_global(prog, st->o1.u1, TYPE_FLOAT);
-        printf("%d\n", st->o2.s1);
-    }
-    else if (st->opcode >= INSTR_CALL0 &&
-             st->opcode <= INSTR_CALL8)
-    {
-        trace_print_global(prog, st->o1.u1, TYPE_FUNCTION);
-        printf("\n");
-    }
-    else if (st->opcode == INSTR_GOTO)
-    {
-        printf("%i\n", st->o1.s1);
-    }
-    else
-    {
-        int t[3] = { TYPE_FLOAT, TYPE_FLOAT, TYPE_FLOAT };
-        switch (st->opcode)
-        {
-            case INSTR_MUL_FV:
-                t[1] = t[2] = TYPE_VECTOR;
-                break;
-            case INSTR_MUL_VF:
-                t[0] = t[2] = TYPE_VECTOR;
-                break;
-            case INSTR_MUL_V:
-                t[0] = t[1] = TYPE_VECTOR;
-                break;
-            case INSTR_ADD_V:
-            case INSTR_SUB_V:
-            case INSTR_EQ_V:
-            case INSTR_NE_V:
-                t[0] = t[1] = t[2] = TYPE_VECTOR;
-                break;
-            case INSTR_EQ_S:
-            case INSTR_NE_S:
-                t[0] = t[1] = TYPE_STRING;
-                break;
-            case INSTR_STORE_F:
-            case INSTR_STOREP_F:
-                t[2] = -1;
-                break;
-            case INSTR_STORE_V:
-                t[0] = t[1] = TYPE_VECTOR; t[2] = -1;
-                break;
-            case INSTR_STORE_S:
-                t[0] = t[1] = TYPE_STRING; t[2] = -1;
-                break;
-            case INSTR_STORE_ENT:
-                t[0] = t[1] = TYPE_ENTITY; t[2] = -1;
-                break;
-            case INSTR_STORE_FLD:
-                t[0] = t[1] = TYPE_FIELD; t[2] = -1;
-                break;
-            case INSTR_STORE_FNC:
-                t[0] = t[1] = TYPE_FUNCTION; t[2] = -1;
-                break;
-            case INSTR_STOREP_V:
-                t[0] = TYPE_VECTOR; t[1] = TYPE_ENTITY; t[2] = -1;
-                break;
-            case INSTR_STOREP_S:
-                t[0] = TYPE_STRING; t[1] = TYPE_ENTITY; t[2] = -1;
-                break;
-            case INSTR_STOREP_ENT:
-                t[0] = TYPE_ENTITY; t[1] = TYPE_ENTITY; t[2] = -1;
-                break;
-            case INSTR_STOREP_FLD:
-                t[0] = TYPE_FIELD; t[1] = TYPE_ENTITY; t[2] = -1;
-                break;
-            case INSTR_STOREP_FNC:
-                t[0] = TYPE_FUNCTION; t[1] = TYPE_ENTITY; t[2] = -1;
-                break;
-        }
-        if (t[0] >= 0) trace_print_global(prog, st->o1.u1, t[0]);
-        else           printf("(none),          ");
-        if (t[1] >= 0) trace_print_global(prog, st->o2.u1, t[1]);
-        else           printf("(none),          ");
-        if (t[2] >= 0) trace_print_global(prog, st->o3.u1, t[2]);
-        else           printf("(none)");
-        printf("\n");
-    }
-}
-
-static qcint_t prog_enterfunction(qc_program_t *prog, prog_section_function_t *func) {
-    qc_exec_stack_t st;
-    size_t  parampos;
-    int32_t p;
-
-    /* back up locals */
-    st.localsp  = vec_size(prog->localstack);
-    st.stmt     = prog->statement;
-    st.function = func;
-
-    if (prog->xflags & VMXF_TRACE) {
-        const char *str = prog_getstring(prog, func->name);
-        vec_push(prog->function_stack, str);
-    }
-
-#ifdef QCVM_BACKUP_STRATEGY_CALLER_VARS
-    if (vec_size(prog->stack))
-    {
-        prog_section_function_t *cur;
-        cur = prog->stack[vec_size(prog->stack)-1].function;
-        if (cur)
-        {
-            qcint_t *globals = prog->globals + cur->firstlocal;
-            vec_append(prog->localstack, cur->locals, globals);
-        }
-    }
-#else
-    {
-        qcint_t *globals = prog->globals + func->firstlocal;
-        vec_append(prog->localstack, func->locals, globals);
-    }
-#endif
-
-    /* copy parameters */
-    parampos = func->firstlocal;
-    for (p = 0; p < func->nargs; ++p)
-    {
-        size_t s;
-        for (s = 0; s < func->argsize[p]; ++s) {
-            prog->globals[parampos] = prog->globals[OFS_PARM0 + 3*p + s];
-            ++parampos;
-        }
-    }
-
-    vec_push(prog->stack, st);
-
-    return func->entry;
-}
-
-static qcint_t prog_leavefunction(qc_program_t *prog) {
-    prog_section_function_t *prev = NULL;
-    size_t oldsp;
-
-    qc_exec_stack_t st = vec_last(prog->stack);
-
-    if (prog->xflags & VMXF_TRACE) {
-        if (vec_size(prog->function_stack))
-            vec_pop(prog->function_stack);
-    }
-
-#ifdef QCVM_BACKUP_STRATEGY_CALLER_VARS
-    if (vec_size(prog->stack) > 1) {
-        prev  = prog->stack[vec_size(prog->stack)-2].function;
-        oldsp = prog->stack[vec_size(prog->stack)-2].localsp;
-    }
-#else
-    prev  = prog->stack[vec_size(prog->stack)-1].function;
-    oldsp = prog->stack[vec_size(prog->stack)-1].localsp;
-#endif
-    if (prev) {
-        qcint_t *globals = prog->globals + prev->firstlocal;
-        memcpy(globals, prog->localstack + oldsp, prev->locals * sizeof(prog->localstack[0]));
-        /* vec_remove(prog->localstack, oldsp, vec_size(prog->localstack)-oldsp); */
-        vec_shrinkto(prog->localstack, oldsp);
-    }
-
-    vec_pop(prog->stack);
-
-    return st.stmt - 1; /* offset the ++st */
-}
-
-bool prog_exec(qc_program_t *prog, prog_section_function_t *func, size_t flags, long maxjumps) {
-    long jumpcount = 0;
-    size_t oldxflags = prog->xflags;
-    prog_section_statement_t *st;
-
-    prog->vmerror = 0;
-    prog->xflags = flags;
-
-    st = prog->code + prog_enterfunction(prog, func);
-    --st;
-    switch (flags)
-    {
-        default:
-        case 0:
-        {
-#define QCVM_LOOP    1
-#define QCVM_PROFILE 0
-#define QCVM_TRACE   0
-#           include __FILE__
-        }
-        case (VMXF_TRACE):
-        {
-#define QCVM_PROFILE 0
-#define QCVM_TRACE   1
-#           include __FILE__
-        }
-        case (VMXF_PROFILE):
-        {
-#define QCVM_PROFILE 1
-#define QCVM_TRACE   0
-#           include __FILE__
-        }
-        case (VMXF_TRACE|VMXF_PROFILE):
-        {
-#define QCVM_PROFILE 1
-#define QCVM_TRACE   1
-#           include __FILE__
-        }
-    };
-
-cleanup:
-    prog->xflags = oldxflags;
-    vec_free(prog->localstack);
-    vec_free(prog->stack);
-    if (prog->vmerror)
-        return false;
-    return true;
-}
-
-/***********************************************************************
- * main for when building the standalone executor
- */
-
-#include <math.h>
-
-const char *type_name[TYPE_COUNT] = {
-    "void",
-    "string",
-    "float",
-    "vector",
-    "entity",
-    "field",
-    "function",
-    "pointer",
-    "integer",
-
-    "variant",
-
-    "struct",
-    "union",
-    "array",
-
-    "nil",
-    "noexpr"
-};
-
-typedef struct {
-    int         vtype;
-    const char *value;
-} qcvm_parameter;
-
-static qcvm_parameter *main_params = NULL;
-
-#define CheckArgs(num) do {                                                    \
-    if (prog->argc != (num)) {                                                 \
-        prog->vmerror++;                                                       \
-        fprintf(stderr, "ERROR: invalid number of arguments for %s: %i, expected %i\n", \
-        __func__, prog->argc, (num));                                      \
-        return -1;                                                             \
-    }                                                                          \
-} while (0)
-
-#define GetGlobal(idx) ((qcany_t*)(prog->globals + (idx)))
-#define GetArg(num) GetGlobal(OFS_PARM0 + 3*(num))
-#define Return(any) *(GetGlobal(OFS_RETURN)) = (any)
-
-static int qc_print(qc_program_t *prog) {
-    size_t i;
-    const char *laststr = NULL;
-    for (i = 0; i < (size_t)prog->argc; ++i) {
-        qcany_t *str = (qcany_t*)(prog->globals + OFS_PARM0 + 3*i);
-        laststr = prog_getstring(prog, str->string);
-        printf("%s", laststr);
-    }
-    if (laststr && (prog->xflags & VMXF_TRACE)) {
-        size_t len = strlen(laststr);
-        if (!len || laststr[len-1] != '\n')
-            printf("\n");
-    }
-    return 0;
-}
-
-static int qc_error(qc_program_t *prog) {
-    fprintf(stderr, "*** VM raised an error:\n");
-    qc_print(prog);
-    prog->vmerror++;
-    return -1;
-}
-
-static int qc_ftos(qc_program_t *prog) {
-    char buffer[512];
-    qcany_t *num;
-    qcany_t str;
-    CheckArgs(1);
-    num = GetArg(0);
-    util_snprintf(buffer, sizeof(buffer), "%g", num->_float);
-    str.string = prog_tempstring(prog, buffer);
-    Return(str);
-    return 0;
-}
-
-static int qc_stof(qc_program_t *prog) {
-    qcany_t *str;
-    qcany_t num;
-    CheckArgs(1);
-    str = GetArg(0);
-    num._float = (float)strtod(prog_getstring(prog, str->string), NULL);
-    Return(num);
-    return 0;
-}
-
-static int qc_vtos(qc_program_t *prog) {
-    char buffer[512];
-    qcany_t *num;
-    qcany_t str;
-    CheckArgs(1);
-    num = GetArg(0);
-    util_snprintf(buffer, sizeof(buffer), "'%g %g %g'", num->vector[0], num->vector[1], num->vector[2]);
-    str.string = prog_tempstring(prog, buffer);
-    Return(str);
-    return 0;
-}
-
-static int qc_etos(qc_program_t *prog) {
-    char buffer[512];
-    qcany_t *num;
-    qcany_t str;
-    CheckArgs(1);
-    num = GetArg(0);
-    util_snprintf(buffer, sizeof(buffer), "%i", num->_int);
-    str.string = prog_tempstring(prog, buffer);
-    Return(str);
-    return 0;
-}
-
-static int qc_spawn(qc_program_t *prog) {
-    qcany_t ent;
-    CheckArgs(0);
-    ent.edict = prog_spawn_entity(prog);
-    Return(ent);
-    return (ent.edict ? 0 : -1);
-}
-
-static int qc_kill(qc_program_t *prog) {
-    qcany_t *ent;
-    CheckArgs(1);
-    ent = GetArg(0);
-    prog_free_entity(prog, ent->edict);
-    return 0;
-}
-
-static int qc_sqrt(qc_program_t *prog) {
-    qcany_t *num, out;
-    CheckArgs(1);
-    num = GetArg(0);
-    out._float = sqrt(num->_float);
-    Return(out);
-    return 0;
-}
-
-static int qc_vlen(qc_program_t *prog) {
-    qcany_t *vec, len;
-    CheckArgs(1);
-    vec = GetArg(0);
-    len._float = sqrt(vec->vector[0] * vec->vector[0] +
-                      vec->vector[1] * vec->vector[1] +
-                      vec->vector[2] * vec->vector[2]);
-    Return(len);
-    return 0;
-}
-
-static int qc_normalize(qc_program_t *prog) {
-    double len;
-    qcany_t *vec;
-    qcany_t out;
-    CheckArgs(1);
-    vec = GetArg(0);
-    len = sqrt(vec->vector[0] * vec->vector[0] +
-               vec->vector[1] * vec->vector[1] +
-               vec->vector[2] * vec->vector[2]);
-    if (len)
-        len = 1.0 / len;
-    else
-        len = 0;
-    out.vector[0] = len * vec->vector[0];
-    out.vector[1] = len * vec->vector[1];
-    out.vector[2] = len * vec->vector[2];
-    Return(out);
-    return 0;
-}
-
-static int qc_strcat(qc_program_t *prog) {
-    char  *buffer;
-    size_t len1,   len2;
-    qcany_t *str1,  *str2;
-    qcany_t  out;
-
-    const char *cstr1;
-    const char *cstr2;
-
-    CheckArgs(2);
-    str1 = GetArg(0);
-    str2 = GetArg(1);
-    cstr1 = prog_getstring(prog, str1->string);
-    cstr2 = prog_getstring(prog, str2->string);
-    len1 = strlen(cstr1);
-    len2 = strlen(cstr2);
-    buffer = (char*)mem_a(len1 + len2 + 1);
-    memcpy(buffer, cstr1, len1);
-    memcpy(buffer+len1, cstr2, len2+1);
-    out.string = prog_tempstring(prog, buffer);
-    mem_d(buffer);
-    Return(out);
-    return 0;
-}
-
-static int qc_strcmp(qc_program_t *prog) {
-    qcany_t *str1,  *str2;
-    qcany_t out;
-
-    const char *cstr1;
-    const char *cstr2;
-
-    if (prog->argc != 2 && prog->argc != 3) {
-        fprintf(stderr, "ERROR: invalid number of arguments for strcmp/strncmp: %i, expected 2 or 3\n",
-               prog->argc);
-        return -1;
-    }
-
-    str1 = GetArg(0);
-    str2 = GetArg(1);
-    cstr1 = prog_getstring(prog, str1->string);
-    cstr2 = prog_getstring(prog, str2->string);
-    if (prog->argc == 3)
-        out._float = strncmp(cstr1, cstr2, GetArg(2)->_float);
-    else
-        out._float = strcmp(cstr1, cstr2);
-    Return(out);
-    return 0;
-}
-
-static int qc_floor(qc_program_t *prog) {
-    qcany_t *num, out;
-    CheckArgs(1);
-    num = GetArg(0);
-    out._float = floor(num->_float);
-    Return(out);
-    return 0;
-}
-
-static int qc_pow(qc_program_t *prog) {
-    qcany_t *base, *exp, out;
-    CheckArgs(2);
-    base = GetArg(0);
-    exp = GetArg(1);
-    out._float = powf(base->_float, exp->_float);
-    Return(out);
-    return 0;
-}
-
-static prog_builtin_t qc_builtins[] = {
-    NULL,
-    &qc_print,       /*   1   */
-    &qc_ftos,        /*   2   */
-    &qc_spawn,       /*   3   */
-    &qc_kill,        /*   4   */
-    &qc_vtos,        /*   5   */
-    &qc_error,       /*   6   */
-    &qc_vlen,        /*   7   */
-    &qc_etos,        /*   8   */
-    &qc_stof,        /*   9   */
-    &qc_strcat,      /*   10  */
-    &qc_strcmp,      /*   11  */
-    &qc_normalize,   /*   12  */
-    &qc_sqrt,        /*   13  */
-    &qc_floor,       /*   14  */
-    &qc_pow          /*   15  */
-};
-
-static const char *arg0 = NULL;
-
-static void version(void) {
-    printf("GMQCC-QCVM %d.%d.%d Built %s %s\n",
-           GMQCC_VERSION_MAJOR,
-           GMQCC_VERSION_MINOR,
-           GMQCC_VERSION_PATCH,
-           __DATE__,
-           __TIME__
-    );
-}
-
-static void usage(void) {
-    printf("usage: %s [options] [parameters] file\n", arg0);
-    printf("options:\n");
-    printf("  -h, --help         print this message\n"
-           "  -trace             trace the execution\n"
-           "  -profile           perform profiling during execution\n"
-           "  -info              print information from the prog's header\n"
-           "  -disasm            disassemble and exit\n"
-           "  -disasm-func func  disassemble and exit\n"
-           "  -printdefs         list the defs section\n"
-           "  -printfields       list the field section\n"
-           "  -printfuns         list functions information\n"
-           "  -v                 be verbose\n"
-           "  -vv                be even more verbose\n");
-    printf("parameters:\n");
-    printf("  -vector <V>   pass a vector parameter to main()\n"
-           "  -float  <f>   pass a float parameter to main()\n"
-           "  -string <s>   pass a string parameter to main() \n");
-}
-
-static void prog_main_setparams(qc_program_t *prog) {
-    size_t i;
-    qcany_t *arg;
-
-    for (i = 0; i < vec_size(main_params); ++i) {
-        arg = GetGlobal(OFS_PARM0 + 3*i);
-        arg->vector[0] = 0;
-        arg->vector[1] = 0;
-        arg->vector[2] = 0;
-        switch (main_params[i].vtype) {
-            case TYPE_VECTOR:
-                (void)util_sscanf(main_params[i].value, " %f %f %f ",
-                                       &arg->vector[0],
-                                       &arg->vector[1],
-                                       &arg->vector[2]);
-                break;
-            case TYPE_FLOAT:
-                arg->_float = atof(main_params[i].value);
-                break;
-            case TYPE_STRING:
-                arg->string = prog_tempstring(prog, main_params[i].value);
-                break;
-            default:
-                fprintf(stderr, "error: unhandled parameter type: %i\n", main_params[i].vtype);
-                break;
-        }
-    }
-}
-
-static void prog_disasm_function(qc_program_t *prog, size_t id);
-
-int main(int argc, char **argv) {
-    size_t      i;
-    qcint_t       fnmain = -1;
-    qc_program_t *prog;
-    size_t      xflags = VMXF_DEFAULT;
-    bool        opts_printfields = false;
-    bool        opts_printdefs   = false;
-    bool        opts_printfuns   = false;
-    bool        opts_disasm      = false;
-    bool        opts_info        = false;
-    bool        noexec           = false;
-    const char *progsfile        = NULL;
-    const char **dis_list        = NULL;
-    int         opts_v           = 0;
-
-    arg0 = argv[0];
-
-    if (argc < 2) {
-        usage();
-        exit(EXIT_FAILURE);
-    }
-
-    while (argc > 1) {
-        if (!strcmp(argv[1], "-h") ||
-            !strcmp(argv[1], "-help") ||
-            !strcmp(argv[1], "--help"))
-        {
-            usage();
-            exit(EXIT_SUCCESS);
-        }
-        else if (!strcmp(argv[1], "-v")) {
-            ++opts_v;
-            --argc;
-            ++argv;
-        }
-        else if (!strncmp(argv[1], "-vv", 3)) {
-            const char *av = argv[1]+1;
-            for (; *av; ++av) {
-                if (*av == 'v')
-                    ++opts_v;
-                else {
-                    usage();
-                    exit(EXIT_FAILURE);
-                }
-            }
-            --argc;
-            ++argv;
-        }
-        else if (!strcmp(argv[1], "-version") ||
-                 !strcmp(argv[1], "--version"))
-        {
-            version();
-            exit(EXIT_SUCCESS);
-        }
-        else if (!strcmp(argv[1], "-trace")) {
-            --argc;
-            ++argv;
-            xflags |= VMXF_TRACE;
-        }
-        else if (!strcmp(argv[1], "-profile")) {
-            --argc;
-            ++argv;
-            xflags |= VMXF_PROFILE;
-        }
-        else if (!strcmp(argv[1], "-info")) {
-            --argc;
-            ++argv;
-            opts_info = true;
-            noexec = true;
-        }
-        else if (!strcmp(argv[1], "-disasm")) {
-            --argc;
-            ++argv;
-            opts_disasm = true;
-            noexec = true;
-        }
-        else if (!strcmp(argv[1], "-disasm-func")) {
-            --argc;
-            ++argv;
-            if (argc <= 1) {
-                usage();
-                exit(EXIT_FAILURE);
-            }
-            vec_push(dis_list, argv[1]);
-            --argc;
-            ++argv;
-            noexec = true;
-        }
-        else if (!strcmp(argv[1], "-printdefs")) {
-            --argc;
-            ++argv;
-            opts_printdefs = true;
-            noexec = true;
-        }
-        else if (!strcmp(argv[1], "-printfuns")) {
-            --argc;
-            ++argv;
-            opts_printfuns = true;
-            noexec = true;
-        }
-        else if (!strcmp(argv[1], "-printfields")) {
-            --argc;
-            ++argv;
-            opts_printfields = true;
-            noexec = true;
-        }
-        else if (!strcmp(argv[1], "-vector") ||
-                 !strcmp(argv[1], "-string") ||
-                 !strcmp(argv[1], "-float") )
-        {
-            qcvm_parameter p;
-            if (argv[1][1] == 'f')
-                p.vtype = TYPE_FLOAT;
-            else if (argv[1][1] == 's')
-                p.vtype = TYPE_STRING;
-            else if (argv[1][1] == 'v')
-                p.vtype = TYPE_VECTOR;
-            else
-                p.vtype = TYPE_VOID;
-
-            --argc;
-            ++argv;
-            if (argc < 2) {
-                usage();
-                exit(EXIT_FAILURE);
-            }
-            p.value = argv[1];
-
-            vec_push(main_params, p);
-            --argc;
-            ++argv;
-        }
-        else if (!strcmp(argv[1], "--")) {
-            --argc;
-            ++argv;
-            break;
-        }
-        else if (argv[1][0] != '-') {
-            if (progsfile) {
-                fprintf(stderr, "only 1 program file may be specified\n");
-                usage();
-                exit(EXIT_FAILURE);
-            }
-            progsfile = argv[1];
-            --argc;
-            ++argv;
-        }
-        else
-        {
-            fprintf(stderr, "unknown parameter: %s\n", argv[1]);
-            usage();
-            exit(EXIT_FAILURE);
-        }
-    }
-
-    if (argc == 2 && !progsfile) {
-        progsfile = argv[1];
-        --argc;
-        ++argv;
-    }
-
-    if (!progsfile) {
-        fprintf(stderr, "must specify a program to execute\n");
-        usage();
-        exit(EXIT_FAILURE);
-    }
-
-    prog = prog_load(progsfile, noexec);
-    if (!prog) {
-        fprintf(stderr, "failed to load program '%s'\n", progsfile);
-        exit(EXIT_FAILURE);
-    }
-
-    prog->builtins       = qc_builtins;
-    prog->builtins_count = GMQCC_ARRAY_COUNT(qc_builtins);
-
-    if (opts_info) {
-        printf("Program's system-checksum = 0x%04x\n", (unsigned int)prog->crc16);
-        printf("Entity field space: %u\n", (unsigned int)prog->entityfields);
-        printf("Globals: %u\n", (unsigned int)vec_size(prog->globals));
-        printf("Counts:\n"
-               "      code: %lu\n"
-               "      defs: %lu\n"
-               "    fields: %lu\n"
-               " functions: %lu\n"
-               "   strings: %lu\n",
-               (unsigned long)vec_size(prog->code),
-               (unsigned long)vec_size(prog->defs),
-               (unsigned long)vec_size(prog->fields),
-               (unsigned long)vec_size(prog->functions),
-               (unsigned long)vec_size(prog->strings));
-    }
-
-    if (opts_info) {
-        prog_delete(prog);
-        return 0;
-    }
-    for (i = 0; i < vec_size(dis_list); ++i) {
-        size_t k;
-        printf("Looking for `%s`\n", dis_list[i]);
-        for (k = 1; k < vec_size(prog->functions); ++k) {
-            const char *name = prog_getstring(prog, prog->functions[k].name);
-            if (!strcmp(name, dis_list[i])) {
-                prog_disasm_function(prog, k);
-                break;
-            }
-        }
-    }
-    if (opts_disasm) {
-        for (i = 1; i < vec_size(prog->functions); ++i)
-            prog_disasm_function(prog, i);
-        return 0;
-    }
-    if (opts_printdefs) {
-        const char *getstring = NULL;
-        for (i = 0; i < vec_size(prog->defs); ++i) {
-            printf("Global: %8s %-16s at %u%s",
-                   type_name[prog->defs[i].type & DEF_TYPEMASK],
-                   prog_getstring(prog, prog->defs[i].name),
-                   (unsigned int)prog->defs[i].offset,
-                   ((prog->defs[i].type & DEF_SAVEGLOBAL) ? " [SAVE]" : ""));
-            if (opts_v) {
-                switch (prog->defs[i].type & DEF_TYPEMASK) {
-                    case TYPE_FLOAT:
-                        printf(" [init: %g]", ((qcany_t*)(prog->globals + prog->defs[i].offset))->_float);
-                        break;
-                    case TYPE_INTEGER:
-                        printf(" [init: %i]", (int)( ((qcany_t*)(prog->globals + prog->defs[i].offset))->_int ));
-                        break;
-                    case TYPE_ENTITY:
-                    case TYPE_FUNCTION:
-                    case TYPE_FIELD:
-                    case TYPE_POINTER:
-                        printf(" [init: %u]", (unsigned)( ((qcany_t*)(prog->globals + prog->defs[i].offset))->_int ));
-                        break;
-                    case TYPE_STRING:
-                        getstring = prog_getstring(prog, ((qcany_t*)(prog->globals + prog->defs[i].offset))->string);
-                        printf(" [init: `");
-                        print_escaped_string(getstring, strlen(getstring));
-                        printf("`]\n");
-                        break;
-                    default:
-                        break;
-                }
-            }
-            printf("\n");
-        }
-    }
-    if (opts_printfields) {
-        for (i = 0; i < vec_size(prog->fields); ++i) {
-            printf("Field: %8s %-16s at %u%s\n",
-                   type_name[prog->fields[i].type],
-                   prog_getstring(prog, prog->fields[i].name),
-                   (unsigned int)prog->fields[i].offset,
-                   ((prog->fields[i].type & DEF_SAVEGLOBAL) ? " [SAVE]" : ""));
-        }
-    }
-    if (opts_printfuns) {
-        for (i = 0; i < vec_size(prog->functions); ++i) {
-            int32_t a;
-            printf("Function: %-16s taking %u parameters:(",
-                   prog_getstring(prog, prog->functions[i].name),
-                   (unsigned int)prog->functions[i].nargs);
-            for (a = 0; a < prog->functions[i].nargs; ++a) {
-                printf(" %i", prog->functions[i].argsize[a]);
-            }
-            if (opts_v > 1) {
-                int32_t start = prog->functions[i].entry;
-                if (start < 0)
-                    printf(") builtin %i\n", (int)-start);
-                else {
-                    size_t funsize = 0;
-                    prog_section_statement_t *st = prog->code + start;
-                    for (;st->opcode != INSTR_DONE; ++st)
-                        ++funsize;
-                    printf(") - %lu instructions", (unsigned long)funsize);
-                    if (opts_v > 2) {
-                        printf(" - locals: %i + %i\n",
-                               prog->functions[i].firstlocal,
-                               prog->functions[i].locals);
-                    }
-                    else
-                        printf("\n");
-                }
-            }
-            else if (opts_v) {
-                printf(") locals: %i + %i\n",
-                       prog->functions[i].firstlocal,
-                       prog->functions[i].locals);
-            }
-            else
-                printf(")\n");
-        }
-    }
-    if (!noexec) {
-        for (i = 1; i < vec_size(prog->functions); ++i) {
-            const char *name = prog_getstring(prog, prog->functions[i].name);
-            if (!strcmp(name, "main"))
-                fnmain = (qcint_t)i;
-        }
-        if (fnmain > 0)
-        {
-            prog_main_setparams(prog);
-            prog_exec(prog, &prog->functions[fnmain], xflags, VM_JUMPS_DEFAULT);
-        }
-        else
-            fprintf(stderr, "No main function found\n");
-    }
-
-    prog_delete(prog);
-    return 0;
-}
-
-static void prog_disasm_function(qc_program_t *prog, size_t id) {
-    prog_section_function_t *fdef = prog->functions + id;
-    prog_section_statement_t *st;
-
-    if (fdef->entry < 0) {
-        printf("FUNCTION \"%s\" = builtin #%i\n", prog_getstring(prog, fdef->name), (int)-fdef->entry);
-        return;
-    }
-    else
-        printf("FUNCTION \"%s\"\n", prog_getstring(prog, fdef->name));
-
-    st = prog->code + fdef->entry;
-    while (st->opcode != INSTR_DONE) {
-        prog_print_statement(prog, st);
-        ++st;
-    }
-}
-#else /* !QCVM_LOOP */
-/*
- * Everything from here on is not including into the compilation of the
- * executor.  This is simply code that is #included via #include __FILE__
- * see when QCVM_LOOP is defined, the rest of the code above do not get
- * re-included.  So this really just acts like one large macro, but it
- * sort of isn't, which makes it nicer looking.
- */
-
-#define OPA ( (qcany_t*) (prog->globals + st->o1.u1) )
-#define OPB ( (qcany_t*) (prog->globals + st->o2.u1) )
-#define OPC ( (qcany_t*) (prog->globals + st->o3.u1) )
-
-#define GLOBAL(x) ( (qcany_t*) (prog->globals + (x)) )
-
-/* to be consistent with current darkplaces behaviour */
-#if !defined(FLOAT_IS_TRUE_FOR_INT)
-#   define FLOAT_IS_TRUE_FOR_INT(x) ( (x) & 0x7FFFFFFF )
-#endif
-
-while (prog->vmerror == 0) {
-    prog_section_function_t  *newf;
-    qcany_t          *ed;
-    qcany_t          *ptr;
-
-    ++st;
-
-#if QCVM_PROFILE
-    prog->profile[st - prog->code]++;
-#endif
-
-#if QCVM_TRACE
-    prog_print_statement(prog, st);
-#endif
-
-    switch (st->opcode)
-    {
-        default:
-            qcvmerror(prog, "Illegal instruction in %s\n", prog->filename);
-            goto cleanup;
-
-        case INSTR_DONE:
-        case INSTR_RETURN:
-            /* TODO: add instruction count to function profile count */
-            GLOBAL(OFS_RETURN)->ivector[0] = OPA->ivector[0];
-            GLOBAL(OFS_RETURN)->ivector[1] = OPA->ivector[1];
-            GLOBAL(OFS_RETURN)->ivector[2] = OPA->ivector[2];
-
-            st = prog->code + prog_leavefunction(prog);
-            if (!vec_size(prog->stack))
-                goto cleanup;
-
-            break;
-
-        case INSTR_MUL_F:
-            OPC->_float = OPA->_float * OPB->_float;
-            break;
-        case INSTR_MUL_V:
-            OPC->_float = OPA->vector[0]*OPB->vector[0] +
-                          OPA->vector[1]*OPB->vector[1] +
-                          OPA->vector[2]*OPB->vector[2];
-            break;
-        case INSTR_MUL_FV:
-        {
-            qcfloat_t f = OPA->_float;
-            OPC->vector[0] = f * OPB->vector[0];
-            OPC->vector[1] = f * OPB->vector[1];
-            OPC->vector[2] = f * OPB->vector[2];
-            break;
-        }
-        case INSTR_MUL_VF:
-        {
-            qcfloat_t f = OPB->_float;
-            OPC->vector[0] = f * OPA->vector[0];
-            OPC->vector[1] = f * OPA->vector[1];
-            OPC->vector[2] = f * OPA->vector[2];
-            break;
-        }
-        case INSTR_DIV_F:
-            if (OPB->_float != 0.0f)
-                OPC->_float = OPA->_float / OPB->_float;
-            else
-                OPC->_float = 0;
-            break;
-
-        case INSTR_ADD_F:
-            OPC->_float = OPA->_float + OPB->_float;
-            break;
-        case INSTR_ADD_V:
-            OPC->vector[0] = OPA->vector[0] + OPB->vector[0];
-            OPC->vector[1] = OPA->vector[1] + OPB->vector[1];
-            OPC->vector[2] = OPA->vector[2] + OPB->vector[2];
-            break;
-        case INSTR_SUB_F:
-            OPC->_float = OPA->_float - OPB->_float;
-            break;
-        case INSTR_SUB_V:
-            OPC->vector[0] = OPA->vector[0] - OPB->vector[0];
-            OPC->vector[1] = OPA->vector[1] - OPB->vector[1];
-            OPC->vector[2] = OPA->vector[2] - OPB->vector[2];
-            break;
-
-        case INSTR_EQ_F:
-            OPC->_float = (OPA->_float == OPB->_float);
-            break;
-        case INSTR_EQ_V:
-            OPC->_float = ((OPA->vector[0] == OPB->vector[0]) &&
-                           (OPA->vector[1] == OPB->vector[1]) &&
-                           (OPA->vector[2] == OPB->vector[2]) );
-            break;
-        case INSTR_EQ_S:
-            OPC->_float = !strcmp(prog_getstring(prog, OPA->string),
-                                  prog_getstring(prog, OPB->string));
-            break;
-        case INSTR_EQ_E:
-            OPC->_float = (OPA->_int == OPB->_int);
-            break;
-        case INSTR_EQ_FNC:
-            OPC->_float = (OPA->function == OPB->function);
-            break;
-        case INSTR_NE_F:
-            OPC->_float = (OPA->_float != OPB->_float);
-            break;
-        case INSTR_NE_V:
-            OPC->_float = ((OPA->vector[0] != OPB->vector[0]) ||
-                           (OPA->vector[1] != OPB->vector[1]) ||
-                           (OPA->vector[2] != OPB->vector[2]) );
-            break;
-        case INSTR_NE_S:
-            OPC->_float = !!strcmp(prog_getstring(prog, OPA->string),
-                                   prog_getstring(prog, OPB->string));
-            break;
-        case INSTR_NE_E:
-            OPC->_float = (OPA->_int != OPB->_int);
-            break;
-        case INSTR_NE_FNC:
-            OPC->_float = (OPA->function != OPB->function);
-            break;
-
-        case INSTR_LE:
-            OPC->_float = (OPA->_float <= OPB->_float);
-            break;
-        case INSTR_GE:
-            OPC->_float = (OPA->_float >= OPB->_float);
-            break;
-        case INSTR_LT:
-            OPC->_float = (OPA->_float < OPB->_float);
-            break;
-        case INSTR_GT:
-            OPC->_float = (OPA->_float > OPB->_float);
-            break;
-
-        case INSTR_LOAD_F:
-        case INSTR_LOAD_S:
-        case INSTR_LOAD_FLD:
-        case INSTR_LOAD_ENT:
-        case INSTR_LOAD_FNC:
-            if (OPA->edict < 0 || OPA->edict >= prog->entities) {
-                qcvmerror(prog, "progs `%s` attempted to read an out of bounds entity", prog->filename);
-                goto cleanup;
-            }
-            if ((unsigned int)(OPB->_int) >= (unsigned int)(prog->entityfields)) {
-                qcvmerror(prog, "prog `%s` attempted to read an invalid field from entity (%i)",
-                          prog->filename,
-                          OPB->_int);
-                goto cleanup;
-            }
-            ed = prog_getedict(prog, OPA->edict);
-            OPC->_int = ((qcany_t*)( ((qcint_t*)ed) + OPB->_int ))->_int;
-            break;
-        case INSTR_LOAD_V:
-            if (OPA->edict < 0 || OPA->edict >= prog->entities) {
-                qcvmerror(prog, "progs `%s` attempted to read an out of bounds entity", prog->filename);
-                goto cleanup;
-            }
-            if (OPB->_int < 0 || OPB->_int + 3 > (qcint_t)prog->entityfields)
-            {
-                qcvmerror(prog, "prog `%s` attempted to read an invalid field from entity (%i)",
-                          prog->filename,
-                          OPB->_int + 2);
-                goto cleanup;
-            }
-            ed = prog_getedict(prog, OPA->edict);
-            ptr = (qcany_t*)( ((qcint_t*)ed) + OPB->_int );
-            OPC->ivector[0] = ptr->ivector[0];
-            OPC->ivector[1] = ptr->ivector[1];
-            OPC->ivector[2] = ptr->ivector[2];
-            break;
-
-        case INSTR_ADDRESS:
-            if (OPA->edict < 0 || OPA->edict >= prog->entities) {
-                qcvmerror(prog, "prog `%s` attempted to address an out of bounds entity %i", prog->filename, OPA->edict);
-                goto cleanup;
-            }
-            if ((unsigned int)(OPB->_int) >= (unsigned int)(prog->entityfields))
-            {
-                qcvmerror(prog, "prog `%s` attempted to read an invalid field from entity (%i)",
-                          prog->filename,
-                          OPB->_int);
-                goto cleanup;
-            }
-
-            ed = prog_getedict(prog, OPA->edict);
-            OPC->_int = ((qcint_t*)ed) - prog->entitydata + OPB->_int;
-            break;
-
-        case INSTR_STORE_F:
-        case INSTR_STORE_S:
-        case INSTR_STORE_ENT:
-        case INSTR_STORE_FLD:
-        case INSTR_STORE_FNC:
-            OPB->_int = OPA->_int;
-            break;
-        case INSTR_STORE_V:
-            OPB->ivector[0] = OPA->ivector[0];
-            OPB->ivector[1] = OPA->ivector[1];
-            OPB->ivector[2] = OPA->ivector[2];
-            break;
-
-        case INSTR_STOREP_F:
-        case INSTR_STOREP_S:
-        case INSTR_STOREP_ENT:
-        case INSTR_STOREP_FLD:
-        case INSTR_STOREP_FNC:
-            if (OPB->_int < 0 || OPB->_int >= (qcint_t)vec_size(prog->entitydata)) {
-                qcvmerror(prog, "`%s` attempted to write to an out of bounds edict (%i)", prog->filename, OPB->_int);
-                goto cleanup;
-            }
-            if (OPB->_int < (qcint_t)prog->entityfields && !prog->allowworldwrites)
-                qcvmerror(prog, "`%s` tried to assign to world.%s (field %i)\n",
-                          prog->filename,
-                          prog_getstring(prog, prog_entfield(prog, OPB->_int)->name),
-                          OPB->_int);
-            ptr = (qcany_t*)(prog->entitydata + OPB->_int);
-            ptr->_int = OPA->_int;
-            break;
-        case INSTR_STOREP_V:
-            if (OPB->_int < 0 || OPB->_int + 2 >= (qcint_t)vec_size(prog->entitydata)) {
-                qcvmerror(prog, "`%s` attempted to write to an out of bounds edict (%i)", prog->filename, OPB->_int);
-                goto cleanup;
-            }
-            if (OPB->_int < (qcint_t)prog->entityfields && !prog->allowworldwrites)
-                qcvmerror(prog, "`%s` tried to assign to world.%s (field %i)\n",
-                          prog->filename,
-                          prog_getstring(prog, prog_entfield(prog, OPB->_int)->name),
-                          OPB->_int);
-            ptr = (qcany_t*)(prog->entitydata + OPB->_int);
-            ptr->ivector[0] = OPA->ivector[0];
-            ptr->ivector[1] = OPA->ivector[1];
-            ptr->ivector[2] = OPA->ivector[2];
-            break;
-
-        case INSTR_NOT_F:
-            OPC->_float = !FLOAT_IS_TRUE_FOR_INT(OPA->_int);
-            break;
-        case INSTR_NOT_V:
-            OPC->_float = !OPA->vector[0] &&
-                          !OPA->vector[1] &&
-                          !OPA->vector[2];
-            break;
-        case INSTR_NOT_S:
-            OPC->_float = !OPA->string ||
-                          !*prog_getstring(prog, OPA->string);
-            break;
-        case INSTR_NOT_ENT:
-            OPC->_float = (OPA->edict == 0);
-            break;
-        case INSTR_NOT_FNC:
-            OPC->_float = !OPA->function;
-            break;
-
-        case INSTR_IF:
-            /* this is consistent with darkplaces' behaviour */
-            if(FLOAT_IS_TRUE_FOR_INT(OPA->_int))
-            {
-                st += st->o2.s1 - 1;    /* offset the s++ */
-                if (++jumpcount >= maxjumps)
-                    qcvmerror(prog, "`%s` hit the runaway loop counter limit of %li jumps", prog->filename, jumpcount);
-            }
-            break;
-        case INSTR_IFNOT:
-            if(!FLOAT_IS_TRUE_FOR_INT(OPA->_int))
-            {
-                st += st->o2.s1 - 1;    /* offset the s++ */
-                if (++jumpcount >= maxjumps)
-                    qcvmerror(prog, "`%s` hit the runaway loop counter limit of %li jumps", prog->filename, jumpcount);
-            }
-            break;
-
-        case INSTR_CALL0:
-        case INSTR_CALL1:
-        case INSTR_CALL2:
-        case INSTR_CALL3:
-        case INSTR_CALL4:
-        case INSTR_CALL5:
-        case INSTR_CALL6:
-        case INSTR_CALL7:
-        case INSTR_CALL8:
-            prog->argc = st->opcode - INSTR_CALL0;
-            if (!OPA->function)
-                qcvmerror(prog, "NULL function in `%s`", prog->filename);
-
-            if(!OPA->function || OPA->function >= (qcint_t)vec_size(prog->functions))
-            {
-                qcvmerror(prog, "CALL outside the program in `%s`", prog->filename);
-                goto cleanup;
-            }
-
-            newf = &prog->functions[OPA->function];
-            newf->profile++;
-
-            prog->statement = (st - prog->code) + 1;
-
-            if (newf->entry < 0)
-            {
-                /* negative statements are built in functions */
-                qcint_t builtinnumber = -newf->entry;
-                if (builtinnumber < (qcint_t)prog->builtins_count && prog->builtins[builtinnumber])
-                    prog->builtins[builtinnumber](prog);
-                else
-                    qcvmerror(prog, "No such builtin #%i in %s! Try updating your gmqcc sources",
-                              builtinnumber, prog->filename);
-            }
-            else
-                st = prog->code + prog_enterfunction(prog, newf) - 1; /* offset st++ */
-            if (prog->vmerror)
-                goto cleanup;
-            break;
-
-        case INSTR_STATE:
-        {
-            qcfloat_t *nextthink;
-            qcfloat_t *time;
-            qcfloat_t *frame;
-            if (!prog->supports_state) {
-                qcvmerror(prog, "`%s` tried to execute a STATE operation but misses its defs!", prog->filename);
-                goto cleanup;
-            }
-            ed = prog_getedict(prog, prog->globals[prog->cached_globals.self]);
-            ((qcint_t*)ed)[prog->cached_fields.think] = OPB->function;
-
-            frame     = (qcfloat_t*)&((qcint_t*)ed)[prog->cached_fields.frame];
-            *frame    = OPA->_float;
-            nextthink = (qcfloat_t*)&((qcint_t*)ed)[prog->cached_fields.nextthink];
-            time      = (qcfloat_t*)(prog->globals + prog->cached_globals.time);
-            *nextthink = *time + 0.1;
-            break;
-        }
-
-        case INSTR_GOTO:
-            st += st->o1.s1 - 1;    /* offset the s++ */
-            if (++jumpcount == 10000000)
-                qcvmerror(prog, "`%s` hit the runaway loop counter limit of %li jumps", prog->filename, jumpcount);
-            break;
-
-        case INSTR_AND:
-            OPC->_float = FLOAT_IS_TRUE_FOR_INT(OPA->_int) &&
-                          FLOAT_IS_TRUE_FOR_INT(OPB->_int);
-            break;
-        case INSTR_OR:
-            OPC->_float = FLOAT_IS_TRUE_FOR_INT(OPA->_int) ||
-                          FLOAT_IS_TRUE_FOR_INT(OPB->_int);
-            break;
-
-        case INSTR_BITAND:
-            OPC->_float = ((int)OPA->_float) & ((int)OPB->_float);
-            break;
-        case INSTR_BITOR:
-            OPC->_float = ((int)OPA->_float) | ((int)OPB->_float);
-            break;
-    }
-}
-
-#undef QCVM_PROFILE
-#undef QCVM_TRACE
-#endif /* !QCVM_LOOP */
diff --git a/exec.cpp b/exec.cpp
new file mode 100644 (file)
index 0000000..e76004e
--- /dev/null
+++ b/exec.cpp
@@ -0,0 +1,1622 @@
+#ifndef QCVM_LOOP
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "gmqcc.h"
+
+static void loaderror(const char *fmt, ...)
+{
+    int     err = errno;
+    va_list ap;
+    va_start(ap, fmt);
+    vprintf(fmt, ap);
+    va_end(ap);
+    printf(": %s\n", util_strerror(err));
+}
+
+static void qcvmerror(qc_program_t *prog, const char *fmt, ...)
+{
+    va_list ap;
+
+    prog->vmerror++;
+
+    va_start(ap, fmt);
+    vprintf(fmt, ap);
+    va_end(ap);
+    putchar('\n');
+}
+
+qc_program_t* prog_load(const char *filename, bool skipversion)
+{
+    prog_header_t header;
+    qc_program_t *prog;
+    FILE *file = fopen(filename, "rb");
+
+    /* we need all those in order to support INSTR_STATE: */
+    bool            has_self      = false,
+                    has_time      = false,
+                    has_think     = false,
+                    has_nextthink = false,
+                    has_frame     = false;
+
+    if (!file)
+        return nullptr;
+
+    if (fread(&header, sizeof(header), 1, file) != 1) {
+        loaderror("failed to read header from '%s'", filename);
+        fclose(file);
+        return nullptr;
+    }
+
+    util_swap_header(header);
+
+    if (!skipversion && header.version != 6) {
+        loaderror("header says this is a version %i progs, we need version 6\n", header.version);
+        fclose(file);
+        return nullptr;
+    }
+
+    prog = (qc_program_t*)mem_a(sizeof(qc_program_t));
+    if (!prog) {
+        fclose(file);
+        fprintf(stderr, "failed to allocate program data\n");
+        return nullptr;
+    }
+    memset(prog, 0, sizeof(*prog));
+
+    prog->entityfields = header.entfield;
+    prog->crc16 = header.crc16;
+
+    prog->filename = util_strdup(filename);
+    if (!prog->filename) {
+        loaderror("failed to store program name");
+        goto error;
+    }
+
+#define read_data(hdrvar, progvar, reserved)                           \
+    if (fseek(file, header.hdrvar.offset, SEEK_SET) != 0) {            \
+        loaderror("seek failed");                                      \
+        goto error;                                                    \
+    }                                                                  \
+    prog->progvar.resize(header.hdrvar.length + reserved);             \
+    if (fread(                                                         \
+            &prog->progvar[0],                                         \
+            sizeof(prog->progvar[0]),                                  \
+            header.hdrvar.length,                                      \
+            file                                                       \
+        )!= header.hdrvar.length                                       \
+    ) {                                                                \
+        loaderror("read failed");                                      \
+        goto error;                                                    \
+    }
+#define read_data1(x)    read_data(x, x, 0)
+#define read_data2(x, y) read_data(x, x, y)
+
+    read_data (statements, code, 0);
+    read_data1(defs);
+    read_data1(fields);
+    read_data1(functions);
+    read_data1(strings);
+    read_data2(globals, 2); /* reserve more in case a RETURN using with the global at "the end" exists */
+
+    util_swap_statements(prog->code);
+    util_swap_defs_fields(prog->defs);
+    util_swap_defs_fields(prog->fields);
+    util_swap_functions(prog->functions);
+    util_swap_globals(prog->globals);
+
+    fclose(file);
+
+    /* profile counters */
+    memset(vec_add(prog->profile, prog->code.size()), 0, sizeof(prog->profile[0]) * prog->code.size());
+
+    /* Add tempstring area */
+    prog->tempstring_start = prog->strings.size();
+    prog->tempstring_at = prog->strings.size();
+
+    prog->strings.resize(prog->strings.size() + 16*1024, '\0');
+
+    /* spawn the world entity */
+    vec_push(prog->entitypool, true);
+    memset(vec_add(prog->entitydata, prog->entityfields), 0, prog->entityfields * sizeof(prog->entitydata[0]));
+    prog->entities = 1;
+
+    /* cache some globals and fields from names */
+    for (auto &it : prog->defs) {
+        const char *name = prog_getstring(prog, it.name);
+        if (!strcmp(name, "self")) {
+            prog->cached_globals.self = it.offset;
+            has_self = true;
+        }
+        else if (!strcmp(name, "time")) {
+            prog->cached_globals.time = it.offset;
+            has_time = true;
+        }
+    }
+    for (auto &it : prog->fields) {
+        const char *name = prog_getstring(prog, it.name);
+        if (!strcmp(name, "think")) {
+            prog->cached_fields.think = it.offset;
+            has_think = true;
+        }
+        else if (!strcmp(name, "nextthink")) {
+            prog->cached_fields.nextthink = it.offset;
+            has_nextthink = true;
+        }
+        else if (!strcmp(name, "frame")) {
+            prog->cached_fields.frame  = it.offset;
+            has_frame = true;
+        }
+    }
+    if (has_self && has_time && has_think && has_nextthink && has_frame)
+        prog->supports_state = true;
+
+    return prog;
+
+error:
+    if (prog->filename)
+        mem_d(prog->filename);
+    vec_free(prog->entitydata);
+    vec_free(prog->entitypool);
+    mem_d(prog);
+
+    fclose(file);
+    return nullptr;
+}
+
+void prog_delete(qc_program_t *prog)
+{
+    if (prog->filename) mem_d(prog->filename);
+    vec_free(prog->entitydata);
+    vec_free(prog->entitypool);
+    vec_free(prog->localstack);
+    vec_free(prog->stack);
+    vec_free(prog->profile);
+    mem_d(prog);
+}
+
+/***********************************************************************
+ * VM code
+ */
+
+const char* prog_getstring(qc_program_t *prog, qcint_t str) {
+    /* cast for return required for C++ */
+    if (str < 0 || str >= (qcint_t)prog->strings.size())
+        return  "<<<invalid string>>>";
+
+    return &prog->strings[0] + str;
+}
+
+prog_section_def_t* prog_entfield(qc_program_t *prog, qcint_t off) {
+    for (auto &it : prog->fields)
+        if (it.offset == off)
+            return &it;
+    return nullptr;
+}
+
+prog_section_def_t* prog_getdef(qc_program_t *prog, qcint_t off)
+{
+    for (auto &it : prog->defs)
+        if (it.offset == off)
+            return &it;
+    return nullptr;
+}
+
+qcany_t* prog_getedict(qc_program_t *prog, qcint_t e) {
+    if (e >= (qcint_t)vec_size(prog->entitypool)) {
+        prog->vmerror++;
+        fprintf(stderr, "Accessing out of bounds edict %i\n", (int)e);
+        e = 0;
+    }
+    return (qcany_t*)(prog->entitydata + (prog->entityfields * e));
+}
+
+static qcint_t prog_spawn_entity(qc_program_t *prog) {
+    char  *data;
+    qcint_t  e;
+    for (e = 0; e < (qcint_t)vec_size(prog->entitypool); ++e) {
+        if (!prog->entitypool[e]) {
+            data = (char*)(prog->entitydata + (prog->entityfields * e));
+            memset(data, 0, prog->entityfields * sizeof(qcint_t));
+            return e;
+        }
+    }
+    vec_push(prog->entitypool, true);
+    prog->entities++;
+    data = (char*)vec_add(prog->entitydata, prog->entityfields);
+    memset(data, 0, prog->entityfields * sizeof(qcint_t));
+    return e;
+}
+
+static void prog_free_entity(qc_program_t *prog, qcint_t e) {
+    if (!e) {
+        prog->vmerror++;
+        fprintf(stderr, "Trying to free world entity\n");
+        return;
+    }
+    if (e >= (qcint_t)vec_size(prog->entitypool)) {
+        prog->vmerror++;
+        fprintf(stderr, "Trying to free out of bounds entity\n");
+        return;
+    }
+    if (!prog->entitypool[e]) {
+        prog->vmerror++;
+        fprintf(stderr, "Double free on entity\n");
+        return;
+    }
+    prog->entitypool[e] = false;
+}
+
+qcint_t prog_tempstring(qc_program_t *prog, const char *str) {
+    size_t len = strlen(str);
+    size_t at = prog->tempstring_at;
+
+    /* when we reach the end we start over */
+    if (at + len >= prog->strings.size())
+        at = prog->tempstring_start;
+
+    /* when it doesn't fit, reallocate */
+    if (at + len >= prog->strings.size())
+    {
+        prog->strings.resize(prog->strings.size() + len+1);
+        memcpy(&prog->strings[0] + at, str, len+1);
+        return at;
+    }
+
+    /* when it fits, just copy */
+    memcpy(&prog->strings[0] + at, str, len+1);
+    prog->tempstring_at += len+1;
+    return at;
+}
+
+static size_t print_escaped_string(const char *str, size_t maxlen) {
+    size_t len = 2;
+    putchar('"');
+    --maxlen; /* because we're lazy and have escape sequences */
+    while (*str) {
+        if (len >= maxlen) {
+            putchar('.');
+            putchar('.');
+            putchar('.');
+            len += 3;
+            break;
+        }
+        switch (*str) {
+            case '\a': len += 2; putchar('\\'); putchar('a'); break;
+            case '\b': len += 2; putchar('\\'); putchar('b'); break;
+            case '\r': len += 2; putchar('\\'); putchar('r'); break;
+            case '\n': len += 2; putchar('\\'); putchar('n'); break;
+            case '\t': len += 2; putchar('\\'); putchar('t'); break;
+            case '\f': len += 2; putchar('\\'); putchar('f'); break;
+            case '\v': len += 2; putchar('\\'); putchar('v'); break;
+            case '\\': len += 2; putchar('\\'); putchar('\\'); break;
+            case '"':  len += 2; putchar('\\'); putchar('"'); break;
+            default:
+                ++len;
+                putchar(*str);
+                break;
+        }
+        ++str;
+    }
+    putchar('"');
+    return len;
+}
+
+static void trace_print_global(qc_program_t *prog, unsigned int glob, int vtype) {
+    static char spaces[28+1] = "                            ";
+    prog_section_def_t *def;
+    qcany_t    *value;
+    int       len;
+
+    if (!glob) {
+        if ((len = printf("<null>,")) == -1)
+            len = 0;
+
+        goto done;
+    }
+
+    def = prog_getdef(prog, glob);
+    value = (qcany_t*)(&prog->globals[glob]);
+
+    len = printf("[@%u] ", glob);
+    if (def) {
+        const char *name = prog_getstring(prog, def->name);
+        if (name[0] == '#')
+            len += printf("$");
+        else
+            len += printf("%s ", name);
+        vtype = def->type & DEF_TYPEMASK;
+    }
+
+    switch (vtype) {
+        case TYPE_VOID:
+        case TYPE_ENTITY:
+        case TYPE_FIELD:
+        case TYPE_FUNCTION:
+        case TYPE_POINTER:
+            len += printf("(%i),", value->_int);
+            break;
+        case TYPE_VECTOR:
+            len += printf("'%g %g %g',", value->vector[0],
+                                         value->vector[1],
+                                         value->vector[2]);
+            break;
+        case TYPE_STRING:
+            if (value->string)
+                len += print_escaped_string(prog_getstring(prog, value->string), sizeof(spaces)-len-5);
+            else
+                len += printf("(null)");
+            len += printf(",");
+            /* len += printf("\"%s\",", prog_getstring(prog, value->string)); */
+            break;
+        case TYPE_FLOAT:
+        default:
+            len += printf("%g,", value->_float);
+            break;
+    }
+done:
+    if (len < (int)sizeof(spaces)-1) {
+        spaces[sizeof(spaces)-1-len] = 0;
+        fputs(spaces, stdout);
+        spaces[sizeof(spaces)-1-len] = ' ';
+    }
+}
+
+static void prog_print_statement(qc_program_t *prog, prog_section_statement_t *st) {
+    if (st->opcode >= VINSTR_END) {
+        printf("<illegal instruction %d>\n", st->opcode);
+        return;
+    }
+    if ((prog->xflags & VMXF_TRACE) && vec_size(prog->function_stack)) {
+        size_t i;
+        for (i = 0; i < vec_size(prog->function_stack); ++i)
+            printf("->");
+        printf("%s:", vec_last(prog->function_stack));
+    }
+    printf(" <> %-12s", util_instr_str[st->opcode]);
+    if (st->opcode >= INSTR_IF &&
+        st->opcode <= INSTR_IFNOT)
+    {
+        trace_print_global(prog, st->o1.u1, TYPE_FLOAT);
+        printf("%d\n", st->o2.s1);
+    }
+    else if (st->opcode >= INSTR_CALL0 &&
+             st->opcode <= INSTR_CALL8)
+    {
+        trace_print_global(prog, st->o1.u1, TYPE_FUNCTION);
+        printf("\n");
+    }
+    else if (st->opcode == INSTR_GOTO)
+    {
+        printf("%i\n", st->o1.s1);
+    }
+    else
+    {
+        int t[3] = { TYPE_FLOAT, TYPE_FLOAT, TYPE_FLOAT };
+        switch (st->opcode)
+        {
+            case INSTR_MUL_FV:
+                t[1] = t[2] = TYPE_VECTOR;
+                break;
+            case INSTR_MUL_VF:
+                t[0] = t[2] = TYPE_VECTOR;
+                break;
+            case INSTR_MUL_V:
+                t[0] = t[1] = TYPE_VECTOR;
+                break;
+            case INSTR_ADD_V:
+            case INSTR_SUB_V:
+            case INSTR_EQ_V:
+            case INSTR_NE_V:
+                t[0] = t[1] = t[2] = TYPE_VECTOR;
+                break;
+            case INSTR_EQ_S:
+            case INSTR_NE_S:
+                t[0] = t[1] = TYPE_STRING;
+                break;
+            case INSTR_STORE_F:
+            case INSTR_STOREP_F:
+                t[2] = -1;
+                break;
+            case INSTR_STORE_V:
+                t[0] = t[1] = TYPE_VECTOR; t[2] = -1;
+                break;
+            case INSTR_STORE_S:
+                t[0] = t[1] = TYPE_STRING; t[2] = -1;
+                break;
+            case INSTR_STORE_ENT:
+                t[0] = t[1] = TYPE_ENTITY; t[2] = -1;
+                break;
+            case INSTR_STORE_FLD:
+                t[0] = t[1] = TYPE_FIELD; t[2] = -1;
+                break;
+            case INSTR_STORE_FNC:
+                t[0] = t[1] = TYPE_FUNCTION; t[2] = -1;
+                break;
+            case INSTR_STOREP_V:
+                t[0] = TYPE_VECTOR; t[1] = TYPE_ENTITY; t[2] = -1;
+                break;
+            case INSTR_STOREP_S:
+                t[0] = TYPE_STRING; t[1] = TYPE_ENTITY; t[2] = -1;
+                break;
+            case INSTR_STOREP_ENT:
+                t[0] = TYPE_ENTITY; t[1] = TYPE_ENTITY; t[2] = -1;
+                break;
+            case INSTR_STOREP_FLD:
+                t[0] = TYPE_FIELD; t[1] = TYPE_ENTITY; t[2] = -1;
+                break;
+            case INSTR_STOREP_FNC:
+                t[0] = TYPE_FUNCTION; t[1] = TYPE_ENTITY; t[2] = -1;
+                break;
+        }
+        if (t[0] >= 0) trace_print_global(prog, st->o1.u1, t[0]);
+        else           printf("(none),          ");
+        if (t[1] >= 0) trace_print_global(prog, st->o2.u1, t[1]);
+        else           printf("(none),          ");
+        if (t[2] >= 0) trace_print_global(prog, st->o3.u1, t[2]);
+        else           printf("(none)");
+        printf("\n");
+    }
+}
+
+static qcint_t prog_enterfunction(qc_program_t *prog, prog_section_function_t *func) {
+    qc_exec_stack_t st;
+    size_t  parampos;
+    int32_t p;
+
+    /* back up locals */
+    st.localsp  = vec_size(prog->localstack);
+    st.stmt     = prog->statement;
+    st.function = func;
+
+    if (prog->xflags & VMXF_TRACE) {
+        const char *str = prog_getstring(prog, func->name);
+        vec_push(prog->function_stack, str);
+    }
+
+#ifdef QCVM_BACKUP_STRATEGY_CALLER_VARS
+    if (vec_size(prog->stack))
+    {
+        prog_section_function_t *cur;
+        cur = prog->stack[vec_size(prog->stack)-1].function;
+        if (cur)
+        {
+            qcint_t *globals = &prog->globals[0] + cur->firstlocal;
+            vec_append(prog->localstack, cur->locals, globals);
+        }
+    }
+#else
+    {
+        qcint_t *globals = &prog->globals[0] + func->firstlocal;
+        vec_append(prog->localstack, func->locals, globals);
+    }
+#endif
+
+    /* copy parameters */
+    parampos = func->firstlocal;
+    for (p = 0; p < func->nargs; ++p)
+    {
+        size_t s;
+        for (s = 0; s < func->argsize[p]; ++s) {
+            prog->globals[parampos] = prog->globals[OFS_PARM0 + 3*p + s];
+            ++parampos;
+        }
+    }
+
+    vec_push(prog->stack, st);
+
+    return func->entry;
+}
+
+static qcint_t prog_leavefunction(qc_program_t *prog) {
+    prog_section_function_t *prev = nullptr;
+    size_t oldsp;
+
+    qc_exec_stack_t st = vec_last(prog->stack);
+
+    if (prog->xflags & VMXF_TRACE) {
+        if (vec_size(prog->function_stack))
+            vec_pop(prog->function_stack);
+    }
+
+#ifdef QCVM_BACKUP_STRATEGY_CALLER_VARS
+    if (vec_size(prog->stack) > 1) {
+        prev  = prog->stack[vec_size(prog->stack)-2].function;
+        oldsp = prog->stack[vec_size(prog->stack)-2].localsp;
+    }
+#else
+    prev  = prog->stack[vec_size(prog->stack)-1].function;
+    oldsp = prog->stack[vec_size(prog->stack)-1].localsp;
+#endif
+    if (prev) {
+        qcint_t *globals = &prog->globals[0] + prev->firstlocal;
+        memcpy(globals, prog->localstack + oldsp, prev->locals * sizeof(prog->localstack[0]));
+        /* vec_remove(prog->localstack, oldsp, vec_size(prog->localstack)-oldsp); */
+        vec_shrinkto(prog->localstack, oldsp);
+    }
+
+    vec_pop(prog->stack);
+
+    return st.stmt - 1; /* offset the ++st */
+}
+
+bool prog_exec(qc_program_t *prog, prog_section_function_t *func, size_t flags, long maxjumps) {
+    long jumpcount = 0;
+    size_t oldxflags = prog->xflags;
+    prog_section_statement_t *st;
+
+    prog->vmerror = 0;
+    prog->xflags = flags;
+
+    st = &prog->code[0] + prog_enterfunction(prog, func);
+    --st;
+    switch (flags)
+    {
+        default:
+        case 0:
+        {
+#define QCVM_LOOP    1
+#define QCVM_PROFILE 0
+#define QCVM_TRACE   0
+#           include __FILE__
+        }
+        case (VMXF_TRACE):
+        {
+#define QCVM_PROFILE 0
+#define QCVM_TRACE   1
+#           include __FILE__
+        }
+        case (VMXF_PROFILE):
+        {
+#define QCVM_PROFILE 1
+#define QCVM_TRACE   0
+#           include __FILE__
+        }
+        case (VMXF_TRACE|VMXF_PROFILE):
+        {
+#define QCVM_PROFILE 1
+#define QCVM_TRACE   1
+#           include __FILE__
+        }
+    };
+
+cleanup:
+    prog->xflags = oldxflags;
+    vec_free(prog->localstack);
+    vec_free(prog->stack);
+    if (prog->vmerror)
+        return false;
+    return true;
+}
+
+/***********************************************************************
+ * main for when building the standalone executor
+ */
+
+#include <math.h>
+
+const char *type_name[TYPE_COUNT] = {
+    "void",
+    "string",
+    "float",
+    "vector",
+    "entity",
+    "field",
+    "function",
+    "pointer",
+    "integer",
+
+    "variant",
+
+    "struct",
+    "union",
+    "array",
+
+    "nil",
+    "noexpr"
+};
+
+struct qcvm_parameter {
+    int         vtype;
+    const char *value;
+};
+
+static qcvm_parameter *main_params = nullptr;
+
+#define CheckArgs(num) do {                                                    \
+    if (prog->argc != (num)) {                                                 \
+        prog->vmerror++;                                                       \
+        fprintf(stderr, "ERROR: invalid number of arguments for %s: %i, expected %i\n", \
+        __func__, prog->argc, (num));                                      \
+        return -1;                                                             \
+    }                                                                          \
+} while (0)
+
+#define GetGlobal(idx) ((qcany_t*)(&prog->globals[0] + (idx)))
+#define GetArg(num) GetGlobal(OFS_PARM0 + 3*(num))
+#define Return(any) *(GetGlobal(OFS_RETURN)) = (any)
+
+static int qc_print(qc_program_t *prog) {
+    size_t i;
+    const char *laststr = nullptr;
+    for (i = 0; i < (size_t)prog->argc; ++i) {
+        qcany_t *str = (qcany_t*)(&prog->globals[0] + OFS_PARM0 + 3*i);
+        laststr = prog_getstring(prog, str->string);
+        printf("%s", laststr);
+    }
+    if (laststr && (prog->xflags & VMXF_TRACE)) {
+        size_t len = strlen(laststr);
+        if (!len || laststr[len-1] != '\n')
+            printf("\n");
+    }
+    return 0;
+}
+
+static int qc_error(qc_program_t *prog) {
+    fprintf(stderr, "*** VM raised an error:\n");
+    qc_print(prog);
+    prog->vmerror++;
+    return -1;
+}
+
+static int qc_ftos(qc_program_t *prog) {
+    char buffer[512];
+    qcany_t *num;
+    qcany_t str;
+    CheckArgs(1);
+    num = GetArg(0);
+    util_snprintf(buffer, sizeof(buffer), "%g", num->_float);
+    str.string = prog_tempstring(prog, buffer);
+    Return(str);
+    return 0;
+}
+
+static int qc_stof(qc_program_t *prog) {
+    qcany_t *str;
+    qcany_t num;
+    CheckArgs(1);
+    str = GetArg(0);
+    num._float = (float)strtod(prog_getstring(prog, str->string), nullptr);
+    Return(num);
+    return 0;
+}
+
+static int qc_vtos(qc_program_t *prog) {
+    char buffer[512];
+    qcany_t *num;
+    qcany_t str;
+    CheckArgs(1);
+    num = GetArg(0);
+    util_snprintf(buffer, sizeof(buffer), "'%g %g %g'", num->vector[0], num->vector[1], num->vector[2]);
+    str.string = prog_tempstring(prog, buffer);
+    Return(str);
+    return 0;
+}
+
+static int qc_etos(qc_program_t *prog) {
+    char buffer[512];
+    qcany_t *num;
+    qcany_t str;
+    CheckArgs(1);
+    num = GetArg(0);
+    util_snprintf(buffer, sizeof(buffer), "%i", num->_int);
+    str.string = prog_tempstring(prog, buffer);
+    Return(str);
+    return 0;
+}
+
+static int qc_spawn(qc_program_t *prog) {
+    qcany_t ent;
+    CheckArgs(0);
+    ent.edict = prog_spawn_entity(prog);
+    Return(ent);
+    return (ent.edict ? 0 : -1);
+}
+
+static int qc_kill(qc_program_t *prog) {
+    qcany_t *ent;
+    CheckArgs(1);
+    ent = GetArg(0);
+    prog_free_entity(prog, ent->edict);
+    return 0;
+}
+
+static int qc_sqrt(qc_program_t *prog) {
+    qcany_t *num, out;
+    CheckArgs(1);
+    num = GetArg(0);
+    out._float = sqrt(num->_float);
+    Return(out);
+    return 0;
+}
+
+static int qc_vlen(qc_program_t *prog) {
+    qcany_t *vec, len;
+    CheckArgs(1);
+    vec = GetArg(0);
+    len._float = sqrt(vec->vector[0] * vec->vector[0] +
+                      vec->vector[1] * vec->vector[1] +
+                      vec->vector[2] * vec->vector[2]);
+    Return(len);
+    return 0;
+}
+
+static int qc_normalize(qc_program_t *prog) {
+    double len;
+    qcany_t *vec;
+    qcany_t out;
+    CheckArgs(1);
+    vec = GetArg(0);
+    len = sqrt(vec->vector[0] * vec->vector[0] +
+               vec->vector[1] * vec->vector[1] +
+               vec->vector[2] * vec->vector[2]);
+    if (len)
+        len = 1.0 / len;
+    else
+        len = 0;
+    out.vector[0] = len * vec->vector[0];
+    out.vector[1] = len * vec->vector[1];
+    out.vector[2] = len * vec->vector[2];
+    Return(out);
+    return 0;
+}
+
+static int qc_strcat(qc_program_t *prog) {
+    char  *buffer;
+    size_t len1,   len2;
+    qcany_t *str1,  *str2;
+    qcany_t  out;
+
+    const char *cstr1;
+    const char *cstr2;
+
+    CheckArgs(2);
+    str1 = GetArg(0);
+    str2 = GetArg(1);
+    cstr1 = prog_getstring(prog, str1->string);
+    cstr2 = prog_getstring(prog, str2->string);
+    len1 = strlen(cstr1);
+    len2 = strlen(cstr2);
+    buffer = (char*)mem_a(len1 + len2 + 1);
+    memcpy(buffer, cstr1, len1);
+    memcpy(buffer+len1, cstr2, len2+1);
+    out.string = prog_tempstring(prog, buffer);
+    mem_d(buffer);
+    Return(out);
+    return 0;
+}
+
+static int qc_strcmp(qc_program_t *prog) {
+    qcany_t *str1,  *str2;
+    qcany_t out;
+
+    const char *cstr1;
+    const char *cstr2;
+
+    if (prog->argc != 2 && prog->argc != 3) {
+        fprintf(stderr, "ERROR: invalid number of arguments for strcmp/strncmp: %i, expected 2 or 3\n",
+               prog->argc);
+        return -1;
+    }
+
+    str1 = GetArg(0);
+    str2 = GetArg(1);
+    cstr1 = prog_getstring(prog, str1->string);
+    cstr2 = prog_getstring(prog, str2->string);
+    if (prog->argc == 3)
+        out._float = strncmp(cstr1, cstr2, GetArg(2)->_float);
+    else
+        out._float = strcmp(cstr1, cstr2);
+    Return(out);
+    return 0;
+}
+
+static int qc_floor(qc_program_t *prog) {
+    qcany_t *num, out;
+    CheckArgs(1);
+    num = GetArg(0);
+    out._float = floor(num->_float);
+    Return(out);
+    return 0;
+}
+
+static int qc_pow(qc_program_t *prog) {
+    qcany_t *base, *exp, out;
+    CheckArgs(2);
+    base = GetArg(0);
+    exp = GetArg(1);
+    out._float = powf(base->_float, exp->_float);
+    Return(out);
+    return 0;
+}
+
+static prog_builtin_t qc_builtins[] = {
+    nullptr,
+    &qc_print,       /*   1   */
+    &qc_ftos,        /*   2   */
+    &qc_spawn,       /*   3   */
+    &qc_kill,        /*   4   */
+    &qc_vtos,        /*   5   */
+    &qc_error,       /*   6   */
+    &qc_vlen,        /*   7   */
+    &qc_etos,        /*   8   */
+    &qc_stof,        /*   9   */
+    &qc_strcat,      /*   10  */
+    &qc_strcmp,      /*   11  */
+    &qc_normalize,   /*   12  */
+    &qc_sqrt,        /*   13  */
+    &qc_floor,       /*   14  */
+    &qc_pow          /*   15  */
+};
+
+static const char *arg0 = nullptr;
+
+static void version(void) {
+    printf("GMQCC-QCVM %d.%d.%d Built %s %s\n",
+           GMQCC_VERSION_MAJOR,
+           GMQCC_VERSION_MINOR,
+           GMQCC_VERSION_PATCH,
+           __DATE__,
+           __TIME__
+    );
+}
+
+static void usage(void) {
+    printf("usage: %s [options] [parameters] file\n", arg0);
+    printf("options:\n");
+    printf("  -h, --help         print this message\n"
+           "  -trace             trace the execution\n"
+           "  -profile           perform profiling during execution\n"
+           "  -info              print information from the prog's header\n"
+           "  -disasm            disassemble and exit\n"
+           "  -disasm-func func  disassemble and exit\n"
+           "  -printdefs         list the defs section\n"
+           "  -printfields       list the field section\n"
+           "  -printfuns         list functions information\n"
+           "  -v                 be verbose\n"
+           "  -vv                be even more verbose\n");
+    printf("parameters:\n");
+    printf("  -vector <V>   pass a vector parameter to main()\n"
+           "  -float  <f>   pass a float parameter to main()\n"
+           "  -string <s>   pass a string parameter to main() \n");
+}
+
+static void prog_main_setparams(qc_program_t *prog) {
+    size_t i;
+    qcany_t *arg;
+
+    for (i = 0; i < vec_size(main_params); ++i) {
+        arg = GetGlobal(OFS_PARM0 + 3*i);
+        arg->vector[0] = 0;
+        arg->vector[1] = 0;
+        arg->vector[2] = 0;
+        switch (main_params[i].vtype) {
+            case TYPE_VECTOR:
+                (void)util_sscanf(main_params[i].value, " %f %f %f ",
+                                       &arg->vector[0],
+                                       &arg->vector[1],
+                                       &arg->vector[2]);
+                break;
+            case TYPE_FLOAT:
+                arg->_float = atof(main_params[i].value);
+                break;
+            case TYPE_STRING:
+                arg->string = prog_tempstring(prog, main_params[i].value);
+                break;
+            default:
+                fprintf(stderr, "error: unhandled parameter type: %i\n", main_params[i].vtype);
+                break;
+        }
+    }
+}
+
+static void prog_disasm_function(qc_program_t *prog, size_t id);
+
+int main(int argc, char **argv) {
+    size_t      i;
+    qcint_t       fnmain = -1;
+    qc_program_t *prog;
+    size_t      xflags = VMXF_DEFAULT;
+    bool        opts_printfields = false;
+    bool        opts_printdefs   = false;
+    bool        opts_printfuns   = false;
+    bool        opts_disasm      = false;
+    bool        opts_info        = false;
+    bool        noexec           = false;
+    const char *progsfile        = nullptr;
+    const char **dis_list        = nullptr;
+    int         opts_v           = 0;
+
+    arg0 = argv[0];
+
+    if (argc < 2) {
+        usage();
+        exit(EXIT_FAILURE);
+    }
+
+    while (argc > 1) {
+        if (!strcmp(argv[1], "-h") ||
+            !strcmp(argv[1], "-help") ||
+            !strcmp(argv[1], "--help"))
+        {
+            usage();
+            exit(EXIT_SUCCESS);
+        }
+        else if (!strcmp(argv[1], "-v")) {
+            ++opts_v;
+            --argc;
+            ++argv;
+        }
+        else if (!strncmp(argv[1], "-vv", 3)) {
+            const char *av = argv[1]+1;
+            for (; *av; ++av) {
+                if (*av == 'v')
+                    ++opts_v;
+                else {
+                    usage();
+                    exit(EXIT_FAILURE);
+                }
+            }
+            --argc;
+            ++argv;
+        }
+        else if (!strcmp(argv[1], "-version") ||
+                 !strcmp(argv[1], "--version"))
+        {
+            version();
+            exit(EXIT_SUCCESS);
+        }
+        else if (!strcmp(argv[1], "-trace")) {
+            --argc;
+            ++argv;
+            xflags |= VMXF_TRACE;
+        }
+        else if (!strcmp(argv[1], "-profile")) {
+            --argc;
+            ++argv;
+            xflags |= VMXF_PROFILE;
+        }
+        else if (!strcmp(argv[1], "-info")) {
+            --argc;
+            ++argv;
+            opts_info = true;
+            noexec = true;
+        }
+        else if (!strcmp(argv[1], "-disasm")) {
+            --argc;
+            ++argv;
+            opts_disasm = true;
+            noexec = true;
+        }
+        else if (!strcmp(argv[1], "-disasm-func")) {
+            --argc;
+            ++argv;
+            if (argc <= 1) {
+                usage();
+                exit(EXIT_FAILURE);
+            }
+            vec_push(dis_list, argv[1]);
+            --argc;
+            ++argv;
+            noexec = true;
+        }
+        else if (!strcmp(argv[1], "-printdefs")) {
+            --argc;
+            ++argv;
+            opts_printdefs = true;
+            noexec = true;
+        }
+        else if (!strcmp(argv[1], "-printfuns")) {
+            --argc;
+            ++argv;
+            opts_printfuns = true;
+            noexec = true;
+        }
+        else if (!strcmp(argv[1], "-printfields")) {
+            --argc;
+            ++argv;
+            opts_printfields = true;
+            noexec = true;
+        }
+        else if (!strcmp(argv[1], "-vector") ||
+                 !strcmp(argv[1], "-string") ||
+                 !strcmp(argv[1], "-float") )
+        {
+            qcvm_parameter p;
+            if (argv[1][1] == 'f')
+                p.vtype = TYPE_FLOAT;
+            else if (argv[1][1] == 's')
+                p.vtype = TYPE_STRING;
+            else if (argv[1][1] == 'v')
+                p.vtype = TYPE_VECTOR;
+            else
+                p.vtype = TYPE_VOID;
+
+            --argc;
+            ++argv;
+            if (argc < 2) {
+                usage();
+                exit(EXIT_FAILURE);
+            }
+            p.value = argv[1];
+
+            vec_push(main_params, p);
+            --argc;
+            ++argv;
+        }
+        else if (!strcmp(argv[1], "--")) {
+            --argc;
+            ++argv;
+            break;
+        }
+        else if (argv[1][0] != '-') {
+            if (progsfile) {
+                fprintf(stderr, "only 1 program file may be specified\n");
+                usage();
+                exit(EXIT_FAILURE);
+            }
+            progsfile = argv[1];
+            --argc;
+            ++argv;
+        }
+        else
+        {
+            fprintf(stderr, "unknown parameter: %s\n", argv[1]);
+            usage();
+            exit(EXIT_FAILURE);
+        }
+    }
+
+    if (argc == 2 && !progsfile) {
+        progsfile = argv[1];
+        --argc;
+        ++argv;
+    }
+
+    if (!progsfile) {
+        fprintf(stderr, "must specify a program to execute\n");
+        usage();
+        exit(EXIT_FAILURE);
+    }
+
+    prog = prog_load(progsfile, noexec);
+    if (!prog) {
+        fprintf(stderr, "failed to load program '%s'\n", progsfile);
+        exit(EXIT_FAILURE);
+    }
+
+    prog->builtins       = qc_builtins;
+    prog->builtins_count = GMQCC_ARRAY_COUNT(qc_builtins);
+
+    if (opts_info) {
+        printf("Program's system-checksum = 0x%04x\n", (unsigned int)prog->crc16);
+        printf("Entity field space: %u\n", (unsigned int)prog->entityfields);
+        printf("Globals: %zu\n", prog->globals.size());
+        printf("Counts:\n"
+               "      code: %zu\n"
+               "      defs: %zu\n"
+               "    fields: %zu\n"
+               " functions: %zu\n"
+               "   strings: %zu\n",
+               prog->code.size(),
+               prog->defs.size(),
+               prog->fields.size(),
+               prog->functions.size(),
+               prog->strings.size());
+    }
+
+    if (opts_info) {
+        prog_delete(prog);
+        return 0;
+    }
+    for (i = 0; i < vec_size(dis_list); ++i) {
+        size_t k;
+        printf("Looking for `%s`\n", dis_list[i]);
+        for (k = 1; k < prog->functions.size(); ++k) {
+            const char *name = prog_getstring(prog, prog->functions[k].name);
+            if (!strcmp(name, dis_list[i])) {
+                prog_disasm_function(prog, k);
+                break;
+            }
+        }
+    }
+    if (opts_disasm) {
+        for (i = 1; i < prog->functions.size(); ++i)
+            prog_disasm_function(prog, i);
+        return 0;
+    }
+    if (opts_printdefs) {
+        const char *getstring = nullptr;
+        for (auto &it : prog->defs) {
+            printf("Global: %8s %-16s at %u%s",
+                   type_name[it.type & DEF_TYPEMASK],
+                   prog_getstring(prog, it.name),
+                   (unsigned int)it.offset,
+                   ((it.type & DEF_SAVEGLOBAL) ? " [SAVE]" : ""));
+            if (opts_v) {
+                switch (it.type & DEF_TYPEMASK) {
+                    case TYPE_FLOAT:
+                        printf(" [init: %g]", ((qcany_t*)(&prog->globals[0] + it.offset))->_float);
+                        break;
+                    case TYPE_INTEGER:
+                        printf(" [init: %i]", (int)( ((qcany_t*)(&prog->globals[0] + it.offset))->_int ));
+                        break;
+                    case TYPE_ENTITY:
+                    case TYPE_FUNCTION:
+                    case TYPE_FIELD:
+                    case TYPE_POINTER:
+                        printf(" [init: %u]", (unsigned)( ((qcany_t*)(&prog->globals[0] + it.offset))->_int ));
+                        break;
+                    case TYPE_STRING:
+                        getstring = prog_getstring(prog, ((qcany_t*)(&prog->globals[0] + it.offset))->string);
+                        printf(" [init: `");
+                        print_escaped_string(getstring, strlen(getstring));
+                        printf("`]\n");
+                        break;
+                    default:
+                        break;
+                }
+            }
+            printf("\n");
+        }
+    }
+    if (opts_printfields) {
+        for (auto &it : prog->fields) {
+            printf("Field: %8s %-16s at %d%s\n",
+                   type_name[it.type],
+                   prog_getstring(prog, it.name),
+                   it.offset,
+                   ((it.type & DEF_SAVEGLOBAL) ? " [SAVE]" : ""));
+        }
+    }
+    if (opts_printfuns) {
+        for (auto &it : prog->functions) {
+            int32_t a;
+            printf("Function: %-16s taking %u parameters:(",
+                   prog_getstring(prog, it.name),
+                   (unsigned int)it.nargs);
+            for (a = 0; a < it.nargs; ++a) {
+                printf(" %i", it.argsize[a]);
+            }
+            if (opts_v > 1) {
+                int32_t start = it.entry;
+                if (start < 0)
+                    printf(") builtin %i\n", (int)-start);
+                else {
+                    size_t funsize = 0;
+                    prog_section_statement_t *st = &prog->code[0] + start;
+                    for (;st->opcode != INSTR_DONE; ++st)
+                        ++funsize;
+                    printf(") - %zu instructions", funsize);
+                    if (opts_v > 2) {
+                        printf(" - locals: %i + %i\n",
+                               it.firstlocal,
+                               it.locals);
+                    }
+                    else
+                        printf("\n");
+                }
+            }
+            else if (opts_v) {
+                printf(") locals: %i + %i\n",
+                       it.firstlocal,
+                       it.locals);
+            }
+            else
+                printf(")\n");
+        }
+    }
+    if (!noexec) {
+        for (i = 1; i < prog->functions.size(); ++i) {
+            const char *name = prog_getstring(prog, prog->functions[i].name);
+            if (!strcmp(name, "main"))
+                fnmain = (qcint_t)i;
+        }
+        if (fnmain > 0)
+        {
+            prog_main_setparams(prog);
+            prog_exec(prog, &prog->functions[fnmain], xflags, VM_JUMPS_DEFAULT);
+        }
+        else
+            fprintf(stderr, "No main function found\n");
+    }
+
+    prog_delete(prog);
+    return 0;
+}
+
+static void prog_disasm_function(qc_program_t *prog, size_t id) {
+    prog_section_function_t *fdef = &prog->functions[0] + id;
+    prog_section_statement_t *st;
+
+    if (fdef->entry < 0) {
+        printf("FUNCTION \"%s\" = builtin #%i\n", prog_getstring(prog, fdef->name), (int)-fdef->entry);
+        return;
+    }
+    else
+        printf("FUNCTION \"%s\"\n", prog_getstring(prog, fdef->name));
+
+    st = &prog->code[0] + fdef->entry;
+    while (st->opcode != INSTR_DONE) {
+        prog_print_statement(prog, st);
+        ++st;
+    }
+}
+#else /* !QCVM_LOOP */
+/*
+ * Everything from here on is not including into the compilation of the
+ * executor.  This is simply code that is #included via #include __FILE__
+ * see when QCVM_LOOP is defined, the rest of the code above do not get
+ * re-included.  So this really just acts like one large macro, but it
+ * sort of isn't, which makes it nicer looking.
+ */
+
+#define OPA ( (qcany_t*) (&prog->globals[0] + st->o1.u1) )
+#define OPB ( (qcany_t*) (&prog->globals[0] + st->o2.u1) )
+#define OPC ( (qcany_t*) (&prog->globals[0] + st->o3.u1) )
+
+#define GLOBAL(x) ( (qcany_t*) (&prog->globals[0] + (x)) )
+
+/* to be consistent with current darkplaces behaviour */
+#if !defined(FLOAT_IS_TRUE_FOR_INT)
+#   define FLOAT_IS_TRUE_FOR_INT(x) ( (x) & 0x7FFFFFFF )
+#endif
+
+while (prog->vmerror == 0) {
+    prog_section_function_t  *newf;
+    qcany_t          *ed;
+    qcany_t          *ptr;
+
+    ++st;
+
+#if QCVM_PROFILE
+    prog->profile[st - &prog->code[0]]++;
+#endif
+
+#if QCVM_TRACE
+    prog_print_statement(prog, st);
+#endif
+
+    switch (st->opcode)
+    {
+        default:
+            qcvmerror(prog, "Illegal instruction in %s\n", prog->filename);
+            goto cleanup;
+
+        case INSTR_DONE:
+        case INSTR_RETURN:
+            /* TODO: add instruction count to function profile count */
+            GLOBAL(OFS_RETURN)->ivector[0] = OPA->ivector[0];
+            GLOBAL(OFS_RETURN)->ivector[1] = OPA->ivector[1];
+            GLOBAL(OFS_RETURN)->ivector[2] = OPA->ivector[2];
+
+            st = &prog->code[0] + prog_leavefunction(prog);
+            if (!vec_size(prog->stack))
+                goto cleanup;
+
+            break;
+
+        case INSTR_MUL_F:
+            OPC->_float = OPA->_float * OPB->_float;
+            break;
+        case INSTR_MUL_V:
+            OPC->_float = OPA->vector[0]*OPB->vector[0] +
+                          OPA->vector[1]*OPB->vector[1] +
+                          OPA->vector[2]*OPB->vector[2];
+            break;
+        case INSTR_MUL_FV:
+        {
+            qcfloat_t f = OPA->_float;
+            OPC->vector[0] = f * OPB->vector[0];
+            OPC->vector[1] = f * OPB->vector[1];
+            OPC->vector[2] = f * OPB->vector[2];
+            break;
+        }
+        case INSTR_MUL_VF:
+        {
+            qcfloat_t f = OPB->_float;
+            OPC->vector[0] = f * OPA->vector[0];
+            OPC->vector[1] = f * OPA->vector[1];
+            OPC->vector[2] = f * OPA->vector[2];
+            break;
+        }
+        case INSTR_DIV_F:
+            if (OPB->_float != 0.0f)
+                OPC->_float = OPA->_float / OPB->_float;
+            else
+                OPC->_float = 0;
+            break;
+
+        case INSTR_ADD_F:
+            OPC->_float = OPA->_float + OPB->_float;
+            break;
+        case INSTR_ADD_V:
+            OPC->vector[0] = OPA->vector[0] + OPB->vector[0];
+            OPC->vector[1] = OPA->vector[1] + OPB->vector[1];
+            OPC->vector[2] = OPA->vector[2] + OPB->vector[2];
+            break;
+        case INSTR_SUB_F:
+            OPC->_float = OPA->_float - OPB->_float;
+            break;
+        case INSTR_SUB_V:
+            OPC->vector[0] = OPA->vector[0] - OPB->vector[0];
+            OPC->vector[1] = OPA->vector[1] - OPB->vector[1];
+            OPC->vector[2] = OPA->vector[2] - OPB->vector[2];
+            break;
+
+        case INSTR_EQ_F:
+            OPC->_float = (OPA->_float == OPB->_float);
+            break;
+        case INSTR_EQ_V:
+            OPC->_float = ((OPA->vector[0] == OPB->vector[0]) &&
+                           (OPA->vector[1] == OPB->vector[1]) &&
+                           (OPA->vector[2] == OPB->vector[2]) );
+            break;
+        case INSTR_EQ_S:
+            OPC->_float = !strcmp(prog_getstring(prog, OPA->string),
+                                  prog_getstring(prog, OPB->string));
+            break;
+        case INSTR_EQ_E:
+            OPC->_float = (OPA->_int == OPB->_int);
+            break;
+        case INSTR_EQ_FNC:
+            OPC->_float = (OPA->function == OPB->function);
+            break;
+        case INSTR_NE_F:
+            OPC->_float = (OPA->_float != OPB->_float);
+            break;
+        case INSTR_NE_V:
+            OPC->_float = ((OPA->vector[0] != OPB->vector[0]) ||
+                           (OPA->vector[1] != OPB->vector[1]) ||
+                           (OPA->vector[2] != OPB->vector[2]) );
+            break;
+        case INSTR_NE_S:
+            OPC->_float = !!strcmp(prog_getstring(prog, OPA->string),
+                                   prog_getstring(prog, OPB->string));
+            break;
+        case INSTR_NE_E:
+            OPC->_float = (OPA->_int != OPB->_int);
+            break;
+        case INSTR_NE_FNC:
+            OPC->_float = (OPA->function != OPB->function);
+            break;
+
+        case INSTR_LE:
+            OPC->_float = (OPA->_float <= OPB->_float);
+            break;
+        case INSTR_GE:
+            OPC->_float = (OPA->_float >= OPB->_float);
+            break;
+        case INSTR_LT:
+            OPC->_float = (OPA->_float < OPB->_float);
+            break;
+        case INSTR_GT:
+            OPC->_float = (OPA->_float > OPB->_float);
+            break;
+
+        case INSTR_LOAD_F:
+        case INSTR_LOAD_S:
+        case INSTR_LOAD_FLD:
+        case INSTR_LOAD_ENT:
+        case INSTR_LOAD_FNC:
+            if (OPA->edict < 0 || OPA->edict >= prog->entities) {
+                qcvmerror(prog, "progs `%s` attempted to read an out of bounds entity", prog->filename);
+                goto cleanup;
+            }
+            if ((unsigned int)(OPB->_int) >= (unsigned int)(prog->entityfields)) {
+                qcvmerror(prog, "prog `%s` attempted to read an invalid field from entity (%i)",
+                          prog->filename,
+                          OPB->_int);
+                goto cleanup;
+            }
+            ed = prog_getedict(prog, OPA->edict);
+            OPC->_int = ((qcany_t*)( ((qcint_t*)ed) + OPB->_int ))->_int;
+            break;
+        case INSTR_LOAD_V:
+            if (OPA->edict < 0 || OPA->edict >= prog->entities) {
+                qcvmerror(prog, "progs `%s` attempted to read an out of bounds entity", prog->filename);
+                goto cleanup;
+            }
+            if (OPB->_int < 0 || OPB->_int + 3 > (qcint_t)prog->entityfields)
+            {
+                qcvmerror(prog, "prog `%s` attempted to read an invalid field from entity (%i)",
+                          prog->filename,
+                          OPB->_int + 2);
+                goto cleanup;
+            }
+            ed = prog_getedict(prog, OPA->edict);
+            ptr = (qcany_t*)( ((qcint_t*)ed) + OPB->_int );
+            OPC->ivector[0] = ptr->ivector[0];
+            OPC->ivector[1] = ptr->ivector[1];
+            OPC->ivector[2] = ptr->ivector[2];
+            break;
+
+        case INSTR_ADDRESS:
+            if (OPA->edict < 0 || OPA->edict >= prog->entities) {
+                qcvmerror(prog, "prog `%s` attempted to address an out of bounds entity %i", prog->filename, OPA->edict);
+                goto cleanup;
+            }
+            if ((unsigned int)(OPB->_int) >= (unsigned int)(prog->entityfields))
+            {
+                qcvmerror(prog, "prog `%s` attempted to read an invalid field from entity (%i)",
+                          prog->filename,
+                          OPB->_int);
+                goto cleanup;
+            }
+
+            ed = prog_getedict(prog, OPA->edict);
+            OPC->_int = ((qcint_t*)ed) - prog->entitydata + OPB->_int;
+            break;
+
+        case INSTR_STORE_F:
+        case INSTR_STORE_S:
+        case INSTR_STORE_ENT:
+        case INSTR_STORE_FLD:
+        case INSTR_STORE_FNC:
+            OPB->_int = OPA->_int;
+            break;
+        case INSTR_STORE_V:
+            OPB->ivector[0] = OPA->ivector[0];
+            OPB->ivector[1] = OPA->ivector[1];
+            OPB->ivector[2] = OPA->ivector[2];
+            break;
+
+        case INSTR_STOREP_F:
+        case INSTR_STOREP_S:
+        case INSTR_STOREP_ENT:
+        case INSTR_STOREP_FLD:
+        case INSTR_STOREP_FNC:
+            if (OPB->_int < 0 || OPB->_int >= (qcint_t)vec_size(prog->entitydata)) {
+                qcvmerror(prog, "`%s` attempted to write to an out of bounds edict (%i)", prog->filename, OPB->_int);
+                goto cleanup;
+            }
+            if (OPB->_int < (qcint_t)prog->entityfields && !prog->allowworldwrites)
+                qcvmerror(prog, "`%s` tried to assign to world.%s (field %i)\n",
+                          prog->filename,
+                          prog_getstring(prog, prog_entfield(prog, OPB->_int)->name),
+                          OPB->_int);
+            ptr = (qcany_t*)(prog->entitydata + OPB->_int);
+            ptr->_int = OPA->_int;
+            break;
+        case INSTR_STOREP_V:
+            if (OPB->_int < 0 || OPB->_int + 2 >= (qcint_t)vec_size(prog->entitydata)) {
+                qcvmerror(prog, "`%s` attempted to write to an out of bounds edict (%i)", prog->filename, OPB->_int);
+                goto cleanup;
+            }
+            if (OPB->_int < (qcint_t)prog->entityfields && !prog->allowworldwrites)
+                qcvmerror(prog, "`%s` tried to assign to world.%s (field %i)\n",
+                          prog->filename,
+                          prog_getstring(prog, prog_entfield(prog, OPB->_int)->name),
+                          OPB->_int);
+            ptr = (qcany_t*)(prog->entitydata + OPB->_int);
+            ptr->ivector[0] = OPA->ivector[0];
+            ptr->ivector[1] = OPA->ivector[1];
+            ptr->ivector[2] = OPA->ivector[2];
+            break;
+
+        case INSTR_NOT_F:
+            OPC->_float = !FLOAT_IS_TRUE_FOR_INT(OPA->_int);
+            break;
+        case INSTR_NOT_V:
+            OPC->_float = !OPA->vector[0] &&
+                          !OPA->vector[1] &&
+                          !OPA->vector[2];
+            break;
+        case INSTR_NOT_S:
+            OPC->_float = !OPA->string ||
+                          !*prog_getstring(prog, OPA->string);
+            break;
+        case INSTR_NOT_ENT:
+            OPC->_float = (OPA->edict == 0);
+            break;
+        case INSTR_NOT_FNC:
+            OPC->_float = !OPA->function;
+            break;
+
+        case INSTR_IF:
+            /* this is consistent with darkplaces' behaviour */
+            if(FLOAT_IS_TRUE_FOR_INT(OPA->_int))
+            {
+                st += st->o2.s1 - 1;    /* offset the s++ */
+                if (++jumpcount >= maxjumps)
+                    qcvmerror(prog, "`%s` hit the runaway loop counter limit of %li jumps", prog->filename, jumpcount);
+            }
+            break;
+        case INSTR_IFNOT:
+            if(!FLOAT_IS_TRUE_FOR_INT(OPA->_int))
+            {
+                st += st->o2.s1 - 1;    /* offset the s++ */
+                if (++jumpcount >= maxjumps)
+                    qcvmerror(prog, "`%s` hit the runaway loop counter limit of %li jumps", prog->filename, jumpcount);
+            }
+            break;
+
+        case INSTR_CALL0:
+        case INSTR_CALL1:
+        case INSTR_CALL2:
+        case INSTR_CALL3:
+        case INSTR_CALL4:
+        case INSTR_CALL5:
+        case INSTR_CALL6:
+        case INSTR_CALL7:
+        case INSTR_CALL8:
+            prog->argc = st->opcode - INSTR_CALL0;
+            if (!OPA->function)
+                qcvmerror(prog, "nullptr function in `%s`", prog->filename);
+
+            if(!OPA->function || OPA->function >= (qcint_t)prog->functions.size())
+            {
+                qcvmerror(prog, "CALL outside the program in `%s`", prog->filename);
+                goto cleanup;
+            }
+
+            newf = &prog->functions[OPA->function];
+            newf->profile++;
+
+            prog->statement = (st - &prog->code[0]) + 1;
+
+            if (newf->entry < 0)
+            {
+                /* negative statements are built in functions */
+                qcint_t builtinnumber = -newf->entry;
+                if (builtinnumber < (qcint_t)prog->builtins_count && prog->builtins[builtinnumber])
+                    prog->builtins[builtinnumber](prog);
+                else
+                    qcvmerror(prog, "No such builtin #%i in %s! Try updating your gmqcc sources",
+                              builtinnumber, prog->filename);
+            }
+            else
+                st = &prog->code[0] + prog_enterfunction(prog, newf) - 1; /* offset st++ */
+            if (prog->vmerror)
+                goto cleanup;
+            break;
+
+        case INSTR_STATE:
+        {
+            qcfloat_t *nextthink;
+            qcfloat_t *time;
+            qcfloat_t *frame;
+            if (!prog->supports_state) {
+                qcvmerror(prog, "`%s` tried to execute a STATE operation but misses its defs!", prog->filename);
+                goto cleanup;
+            }
+            ed = prog_getedict(prog, prog->globals[prog->cached_globals.self]);
+            ((qcint_t*)ed)[prog->cached_fields.think] = OPB->function;
+
+            frame     = (qcfloat_t*)&((qcint_t*)ed)[prog->cached_fields.frame];
+            *frame    = OPA->_float;
+            nextthink = (qcfloat_t*)&((qcint_t*)ed)[prog->cached_fields.nextthink];
+            time      = (qcfloat_t*)(&prog->globals[0] + prog->cached_globals.time);
+            *nextthink = *time + 0.1;
+            break;
+        }
+
+        case INSTR_GOTO:
+            st += st->o1.s1 - 1;    /* offset the s++ */
+            if (++jumpcount == 10000000)
+                qcvmerror(prog, "`%s` hit the runaway loop counter limit of %li jumps", prog->filename, jumpcount);
+            break;
+
+        case INSTR_AND:
+            OPC->_float = FLOAT_IS_TRUE_FOR_INT(OPA->_int) &&
+                          FLOAT_IS_TRUE_FOR_INT(OPB->_int);
+            break;
+        case INSTR_OR:
+            OPC->_float = FLOAT_IS_TRUE_FOR_INT(OPA->_int) ||
+                          FLOAT_IS_TRUE_FOR_INT(OPB->_int);
+            break;
+
+        case INSTR_BITAND:
+            OPC->_float = ((int)OPA->_float) & ((int)OPB->_float);
+            break;
+        case INSTR_BITOR:
+            OPC->_float = ((int)OPA->_float) | ((int)OPB->_float);
+            break;
+    }
+}
+
+#undef QCVM_PROFILE
+#undef QCVM_TRACE
+#endif /* !QCVM_LOOP */
diff --git a/fold.c b/fold.c
deleted file mode 100644 (file)
index 10145bc..0000000
--- a/fold.c
+++ /dev/null
@@ -1,1694 +0,0 @@
-/*
- * Copyright (C) 2012, 2013, 2014, 2015
- *     Dale Weiler
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
- * this software and associated documentation files (the "Software"), to deal in
- * the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is furnished to do
- * so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-#include <string.h>
-#include <math.h>
-
-#include "ast.h"
-#include "parser.h"
-
-#define FOLD_STRING_UNTRANSLATE_HTSIZE 1024
-#define FOLD_STRING_DOTRANSLATE_HTSIZE 1024
-
-/* The options to use for inexact and arithmetic exceptions */
-#define FOLD_ROUNDING SFLOAT_ROUND_NEAREST_EVEN
-#define FOLD_TINYNESS SFLOAT_TBEFORE
-
-/*
- * Comparing float values is an unsafe operation when the operands to the
- * comparison are floating point values that are inexact. For instance 1/3 is an
- * inexact value. The FPU is meant to raise exceptions when these sorts of things
- * happen, including division by zero, underflows and overflows. The C standard
- * library provides us with the <fenv.h> header to gain access to the floating-
- * point environment and lets us set the rounding mode and check for these exceptions.
- * The problem is the standard C library allows an implementation to leave these
- * stubbed out and does not require they be implemented. Furthermore, depending
- * on implementations there is no control over the FPU. This is an IEE 754
- * conforming implementation in software to compensate.
- */
-typedef uint32_t sfloat_t;
-
-typedef union {
-    qcfloat_t f;
-    sfloat_t  s;
-} sfloat_cast_t;
-
-/* Exception flags */
-typedef enum {
-    SFLOAT_NOEXCEPT  = 0,
-    SFLOAT_INVALID   = 1,
-    SFLOAT_DIVBYZERO = 4,
-    SFLOAT_OVERFLOW  = 8,
-    SFLOAT_UNDERFLOW = 16,
-    SFLOAT_INEXACT   = 32
-} sfloat_exceptionflags_t;
-
-/* Rounding modes */
-typedef enum {
-    SFLOAT_ROUND_NEAREST_EVEN,
-    SFLOAT_ROUND_DOWN,
-    SFLOAT_ROUND_UP,
-    SFLOAT_ROUND_TO_ZERO
-} sfloat_roundingmode_t;
-
-/* Underflow tininess-detection mode */
-typedef enum {
-    SFLOAT_TAFTER,
-    SFLOAT_TBEFORE
-} sfloat_tdetect_t;
-
-typedef struct {
-    sfloat_roundingmode_t   roundingmode;
-    sfloat_exceptionflags_t exceptionflags;
-    sfloat_tdetect_t        tiny;
-} sfloat_state_t;
-
-/* Counts the number of leading zero bits before the most-significand one bit. */
-#ifdef _MSC_VER
-/* MSVC has an intrinsic for this */
-    static GMQCC_INLINE uint32_t sfloat_clz(uint32_t x) {
-        int r = 0;
-        _BitScanForward(&r, x);
-        return r;
-    }
-#   define SFLOAT_CLZ(X, SUB) \
-        (sfloat_clz((X)) - (SUB))
-#elif defined(__GNUC__) || defined(__CLANG__)
-/* Clang and GCC have a builtin for this */
-#   define SFLOAT_CLZ(X, SUB) \
-        (__builtin_clz((X)) - (SUB))
-#else
-/* Native fallback */
-    static GMQCC_INLINE uint32_t sfloat_popcnt(uint32_t x) {
-        x -= ((x >> 1) & 0x55555555);
-        x  = (((x >> 2) & 0x33333333) + (x & 0x33333333));
-        x  = (((x >> 4) + x) & 0x0F0F0F0F);
-        x += x >> 8;
-        x += x >> 16;
-        return x & 0x0000003F;
-    }
-    static GMQCC_INLINE uint32_t sfloat_clz(uint32_t x) {
-        x |= (x >> 1);
-        x |= (x >> 2);
-        x |= (x >> 4);
-        x |= (x >> 8);
-        x |= (x >> 16);
-        return 32 - sfloat_popcnt(x);
-    }
-#   define SFLOAT_CLZ(X, SUB) \
-        (sfloat_clz((X) - (SUB)))
-#endif
-
-/* The value of a NaN */
-#define SFLOAT_NAN 0xFFFFFFFF
-/* Test if NaN */
-#define SFLOAT_ISNAN(A) \
-    (0xFF000000 < (uint32_t)((A) << 1))
-/* Test if signaling NaN */
-#define SFLOAT_ISSNAN(A) \
-    (((((A) >> 22) & 0x1FF) == 0x1FE) && ((A) & 0x003FFFFF))
-/* Raise exception */
-#define SFLOAT_RAISE(STATE, FLAGS) \
-    ((STATE)->exceptionflags = (sfloat_exceptionflags_t)((STATE)->exceptionflags | (FLAGS)))
-/*
- * Shifts `A' right by the number of bits given in `COUNT'. If any non-zero bits
- * are shifted off they are forced into the least significand bit of the result
- * by setting it to one. As a result of this, the value of `COUNT' can be
- * arbitrarily large; if `COUNT' is greater than 32, the result will be either
- * zero or one, depending on whether `A' is a zero or non-zero. The result is
- * stored into the value pointed by `Z'.
- */
-#define SFLOAT_SHIFT(SIZE, A, COUNT, Z)                                      \
-    *(Z) = ((COUNT) == 0)                                                    \
-        ? 1                                                                  \
-        : (((COUNT) < (SIZE))                                                \
-            ? ((A) >> (COUNT)) | (((A) << ((-(COUNT)) & ((SIZE) - 1))) != 0) \
-            : ((A) != 0))
-
-/* Extract fractional component */
-#define SFLOAT_EXTRACT_FRAC(X) \
-    ((uint32_t)((X) & 0x007FFFFF))
-/* Extract exponent component */
-#define SFLOAT_EXTRACT_EXP(X) \
-    ((int16_t)((X) >> 23) & 0xFF)
-/* Extract sign bit */
-#define SFLOAT_EXTRACT_SIGN(X) \
-    ((X) >> 31)
-/*
- * Normalizes the subnormal value represented by the denormalized significand
- * `SA'. The normalized exponent and significand are stored at the locations
- * pointed by `Z' and `SZ' respectively.
- */
-#define SFLOAT_SUBNORMALIZE(SA, Z, SZ) \
-    (void)(*(SZ) = (SA) << SFLOAT_CLZ((SA), 8), *(Z) = 1 - SFLOAT_CLZ((SA), 8))
-/*
- * Packs the sign `SIGN', exponent `EXP' and significand `SIG' into the value
- * giving the result.
- *
- * After the shifting into their proper positions, the fields are added together
- * to form the result. This means any integer portion of `SIG' will be added
- * to the exponent. Similarly, because a properly normalized significand will
- * always have an integer portion equal to one, the exponent input `EXP' should
- * be one less than the desired result exponent whenever the significant input
- * `SIG' is a complete, normalized significand.
- */
-#define SFLOAT_PACK(SIGN, EXP, SIG) \
-    (sfloat_t)((((uint32_t)(SIGN)) << 31) + (((uint32_t)(EXP)) << 23) + (SIG))
-
-/*
- * Takes two values `a' and `b', one of which is a NaN, and returns the appropriate
- * NaN result. If either `a' or `b' is a signaling NaN than an invalid exception is
- * raised.
- */
-static sfloat_t sfloat_propagate_nan(sfloat_state_t *state, sfloat_t a, sfloat_t b) {
-    bool isnan_a  = SFLOAT_ISNAN(a);
-    bool issnan_a = SFLOAT_ISSNAN(a);
-    bool isnan_b  = SFLOAT_ISNAN(b);
-    bool issnan_b = SFLOAT_ISSNAN(b);
-
-    a |= 0x00400000;
-    b |= 0x00400000;
-
-    if (issnan_a | issnan_b)
-        SFLOAT_RAISE(state, SFLOAT_INVALID);
-    if (isnan_a)
-        return (issnan_a & isnan_b) ? b : a;
-    return b;
-}
-
-/*
- * Takes an abstract value having sign `sign_z', exponent `exp_z', and significand
- * `sig_z' and returns the appropriate value corresponding to the abstract input.
- *
- * The abstract value is simply rounded and packed into the format. If the abstract
- * input cannot be represented exactly an inexact exception is raised. If the
- * abstract input is too large, the overflow and inexact exceptions are both raised
- * and an infinity or maximal finite value is returned. If the abstract value is
- * too small, the value is rounded to a subnormal and the underflow and inexact
- * exceptions are only raised if the value cannot be represented exactly with
- * a subnormal.
- *
- * The input significand `sig_z' has it's binary point between bits 30 and 29,
- * this is seven bits to the left of its usual location. The shifted significand
- * must be normalized or smaller than this. If it's not normalized then the exponent
- * `exp_z' must be zero; in that case, the result returned is a subnormal number
- * which must not require rounding. In the more usual case where the significand
- * is normalized, the exponent must be one less than the *true* exponent.
- *
- * The handling of underflow and overflow is otherwise in alignment with IEC/IEEE.
- */
-static sfloat_t SFLOAT_PACK_round(sfloat_state_t *state, bool sign_z, int16_t exp_z, uint32_t sig_z) {
-    sfloat_roundingmode_t mode      = state->roundingmode;
-    bool                  even      = !!(mode == SFLOAT_ROUND_NEAREST_EVEN);
-    unsigned char         increment = 0x40;
-    unsigned char         bits      = sig_z & 0x7F;
-
-    if (!even) {
-        if (mode == SFLOAT_ROUND_TO_ZERO)
-            increment = 0;
-        else {
-            increment = 0x7F;
-            if (sign_z) {
-                if (mode == SFLOAT_ROUND_UP)
-                    increment = 0;
-            } else {
-                if (mode == SFLOAT_ROUND_DOWN)
-                    increment = 0;
-            }
-        }
-    }
-
-    if (0xFD <= (uint16_t)exp_z) {
-        if ((0xFD < exp_z) || ((exp_z == 0xFD) && ((int32_t)(sig_z + increment) < 0))) {
-            SFLOAT_RAISE(state, SFLOAT_OVERFLOW | SFLOAT_INEXACT);
-            return SFLOAT_PACK(sign_z, 0xFF, 0) - (increment == 0);
-        }
-        if (exp_z < 0) {
-            /* Check for underflow */
-            bool tiny = (state->tiny == SFLOAT_TBEFORE) || (exp_z < -1) || (sig_z + increment < 0x80000000);
-            SFLOAT_SHIFT(32, sig_z, -exp_z, &sig_z);
-            exp_z = 0;
-            bits = sig_z & 0x7F;
-            if (tiny && bits)
-                SFLOAT_RAISE(state, SFLOAT_UNDERFLOW);
-        }
-    }
-    if (bits)
-        SFLOAT_RAISE(state, SFLOAT_INEXACT);
-    sig_z = (sig_z + increment) >> 7;
-    sig_z &= ~(((bits ^ 0x40) == 0) & even);
-    if (sig_z == 0)
-        exp_z = 0;
-    return SFLOAT_PACK(sign_z, exp_z, sig_z);
-}
-
-/*
- * Takes an abstract value having sign `sign_z', exponent `exp_z' and significand
- * `sig_z' and returns the appropriate value corresponding to the abstract input.
- * This function is exactly like `PACK_round' except the significand does not have
- * to be normalized.
- *
- * Bit 31 of the significand must be zero and the exponent must be one less than
- * the *true* exponent.
- */
-static sfloat_t SFLOAT_PACK_normal(sfloat_state_t *state, bool sign_z, int16_t exp_z, uint32_t sig_z) {
-    unsigned char c = SFLOAT_CLZ(sig_z, 1);
-    return SFLOAT_PACK_round(state, sign_z, exp_z - c, sig_z << c);
-}
-
-/*
- * Returns the result of adding the absolute values of `a' and `b'. The sign
- * `sign_z' is ignored if the result is a NaN.
- */
-static sfloat_t sfloat_add_impl(sfloat_state_t *state, sfloat_t a, sfloat_t b, bool sign_z) {
-    int16_t  exp_a = SFLOAT_EXTRACT_EXP(a);
-    int16_t  exp_b = SFLOAT_EXTRACT_EXP(b);
-    int16_t  exp_z = 0;
-    int16_t  exp_d = exp_a - exp_b;
-    uint32_t sig_a = SFLOAT_EXTRACT_FRAC(a) << 6;
-    uint32_t sig_b = SFLOAT_EXTRACT_FRAC(b) << 6;
-    uint32_t sig_z = 0;
-
-    if (0 < exp_d) {
-        if (exp_a == 0xFF)
-            return sig_a ? sfloat_propagate_nan(state, a, b) : a;
-        if (exp_b == 0)
-            --exp_d;
-        else
-            sig_b |= 0x20000000;
-        SFLOAT_SHIFT(32, sig_b, exp_d, &sig_b);
-        exp_z = exp_a;
-    } else if (exp_d < 0) {
-        if (exp_b == 0xFF)
-            return sig_b ? sfloat_propagate_nan(state, a, b) : SFLOAT_PACK(sign_z, 0xFF, 0);
-        if (exp_a == 0)
-            ++exp_d;
-        else
-            sig_a |= 0x20000000;
-        SFLOAT_SHIFT(32, sig_a, -exp_d, &sig_a);
-        exp_z = exp_b;
-    } else {
-        if (exp_a == 0xFF)
-            return (sig_a | sig_b) ? sfloat_propagate_nan(state, a, b) : a;
-        if (exp_a == 0)
-            return SFLOAT_PACK(sign_z, 0, (sig_a + sig_b) >> 6);
-        sig_z = 0x40000000 + sig_a + sig_b;
-        exp_z = exp_a;
-        goto end;
-    }
-    sig_a |= 0x20000000;
-    sig_z = (sig_a + sig_b) << 1;
-    --exp_z;
-    if ((int32_t)sig_z < 0) {
-        sig_z = sig_a + sig_b;
-        ++exp_z;
-    }
-end:
-    return SFLOAT_PACK_round(state, sign_z, exp_z, sig_z);
-}
-
-/*
- * Returns the result of subtracting the absolute values of `a' and `b'. If the
- * sign `sign_z' is one, the difference is negated before being returned. The
- * sign is ignored if the result is a NaN.
- */
-static sfloat_t sfloat_sub_impl(sfloat_state_t *state, sfloat_t a, sfloat_t b, bool sign_z) {
-    int16_t  exp_a = SFLOAT_EXTRACT_EXP(a);
-    int16_t  exp_b = SFLOAT_EXTRACT_EXP(b);
-    int16_t  exp_z = 0;
-    int16_t  exp_d = exp_a - exp_b;
-    uint32_t sig_a = SFLOAT_EXTRACT_FRAC(a) << 7;
-    uint32_t sig_b = SFLOAT_EXTRACT_FRAC(b) << 7;
-    uint32_t sig_z = 0;
-
-    if (0 < exp_d) goto exp_greater_a;
-    if (exp_d < 0) goto exp_greater_b;
-
-    if (exp_a == 0xFF) {
-        if (sig_a | sig_b)
-            return sfloat_propagate_nan(state, a, b);
-        SFLOAT_RAISE(state, SFLOAT_INVALID);
-        return SFLOAT_NAN;
-    }
-
-    if (exp_a == 0)
-        exp_a = exp_b = 1;
-
-    if (sig_b < sig_a) goto greater_a;
-    if (sig_a < sig_b) goto greater_b;
-
-    return SFLOAT_PACK(state->roundingmode == SFLOAT_ROUND_DOWN, 0, 0);
-
-exp_greater_b:
-    if (exp_b == 0xFF)
-        return (sig_b) ? sfloat_propagate_nan(state, a, b) : SFLOAT_PACK(sign_z ^ 1, 0xFF, 0);
-    if (exp_a == 0)
-        ++exp_d;
-    else
-        sig_a |= 0x40000000;
-    SFLOAT_SHIFT(32, sig_a, -exp_d, &sig_a);
-    sig_b |= 0x40000000;
-greater_b:
-    sig_z = sig_b - sig_a;
-    exp_z = exp_b;
-    sign_z ^= 1;
-    goto end;
-
-exp_greater_a:
-    if (exp_a == 0xFF)
-        return (sig_a) ? sfloat_propagate_nan(state, a, b) : a;
-    if (exp_b == 0)
-        --exp_d;
-    else
-        sig_b |= 0x40000000;
-    SFLOAT_SHIFT(32, sig_b, exp_d, &sig_b);
-    sig_a |= 0x40000000;
-greater_a:
-    sig_z = sig_a - sig_b;
-    exp_z = exp_a;
-
-end:
-    --exp_z;
-    return SFLOAT_PACK_normal(state, sign_z, exp_z, sig_z);
-}
-
-static GMQCC_INLINE sfloat_t sfloat_add(sfloat_state_t *state, sfloat_t a, sfloat_t b) {
-    bool sign_a = SFLOAT_EXTRACT_SIGN(a);
-    bool sign_b = SFLOAT_EXTRACT_SIGN(b);
-    return (sign_a == sign_b) ? sfloat_add_impl(state, a, b, sign_a)
-                              : sfloat_sub_impl(state, a, b, sign_a);
-}
-
-static GMQCC_INLINE sfloat_t sfloat_sub(sfloat_state_t *state, sfloat_t a, sfloat_t b) {
-    bool sign_a = SFLOAT_EXTRACT_SIGN(a);
-    bool sign_b = SFLOAT_EXTRACT_SIGN(b);
-    return (sign_a == sign_b) ? sfloat_sub_impl(state, a, b, sign_a)
-                              : sfloat_add_impl(state, a, b, sign_a);
-}
-
-static sfloat_t sfloat_mul(sfloat_state_t *state, sfloat_t a, sfloat_t b) {
-    int16_t  exp_a   = SFLOAT_EXTRACT_EXP(a);
-    int16_t  exp_b   = SFLOAT_EXTRACT_EXP(b);
-    int16_t  exp_z   = 0;
-    uint32_t sig_a   = SFLOAT_EXTRACT_FRAC(a);
-    uint32_t sig_b   = SFLOAT_EXTRACT_FRAC(b);
-    uint32_t sig_z   = 0;
-    uint64_t sig_z64 = 0;
-    bool     sign_a  = SFLOAT_EXTRACT_SIGN(a);
-    bool     sign_b  = SFLOAT_EXTRACT_SIGN(b);
-    bool     sign_z  = sign_a ^ sign_b;
-
-    if (exp_a == 0xFF) {
-        if (sig_a || ((exp_b == 0xFF) && sig_b))
-            return sfloat_propagate_nan(state, a, b);
-        if ((exp_b | sig_b) == 0) {
-            SFLOAT_RAISE(state, SFLOAT_INVALID);
-            return SFLOAT_NAN;
-        }
-        return SFLOAT_PACK(sign_z, 0xFF, 0);
-    }
-    if (exp_b == 0xFF) {
-        if (sig_b)
-            return sfloat_propagate_nan(state, a, b);
-        if ((exp_a | sig_a) == 0) {
-            SFLOAT_RAISE(state, SFLOAT_INVALID);
-            return SFLOAT_NAN;
-        }
-        return SFLOAT_PACK(sign_z, 0xFF, 0);
-    }
-    if (exp_a == 0) {
-        if (sig_a == 0)
-            return SFLOAT_PACK(sign_z, 0, 0);
-        SFLOAT_SUBNORMALIZE(sig_a, &exp_a, &sig_a);
-    }
-    if (exp_b == 0) {
-        if (sig_b == 0)
-            return SFLOAT_PACK(sign_z, 0, 0);
-        SFLOAT_SUBNORMALIZE(sig_b, &exp_b, &sig_b);
-    }
-    exp_z = exp_a + exp_b - 0x7F;
-    sig_a = (sig_a | 0x00800000) << 7;
-    sig_b = (sig_b | 0x00800000) << 8;
-    SFLOAT_SHIFT(64, ((uint64_t)sig_a) * sig_b, 32, &sig_z64);
-    sig_z = sig_z64;
-    if (0 <= (int32_t)(sig_z << 1)) {
-        sig_z <<= 1;
-        --exp_z;
-    }
-    return SFLOAT_PACK_round(state, sign_z, exp_z, sig_z);
-}
-
-static sfloat_t sfloat_div(sfloat_state_t *state, sfloat_t a, sfloat_t b) {
-    int16_t  exp_a   = SFLOAT_EXTRACT_EXP(a);
-    int16_t  exp_b   = SFLOAT_EXTRACT_EXP(b);
-    int16_t  exp_z   = 0;
-    uint32_t sig_a   = SFLOAT_EXTRACT_FRAC(a);
-    uint32_t sig_b   = SFLOAT_EXTRACT_FRAC(b);
-    uint32_t sig_z   = 0;
-    bool     sign_a  = SFLOAT_EXTRACT_SIGN(a);
-    bool     sign_b  = SFLOAT_EXTRACT_SIGN(b);
-    bool     sign_z  = sign_a ^ sign_b;
-
-    if (exp_a == 0xFF) {
-        if (sig_a)
-            return sfloat_propagate_nan(state, a, b);
-        if (exp_b == 0xFF) {
-            if (sig_b)
-                return sfloat_propagate_nan(state, a, b);
-            SFLOAT_RAISE(state, SFLOAT_INVALID);
-            return SFLOAT_NAN;
-        }
-        return SFLOAT_PACK(sign_z, 0xFF, 0);
-    }
-    if (exp_b == 0xFF)
-        return (sig_b) ? sfloat_propagate_nan(state, a, b) : SFLOAT_PACK(sign_z, 0, 0);
-    if (exp_b == 0) {
-        if (sig_b == 0) {
-            if ((exp_a | sig_a) == 0) {
-                SFLOAT_RAISE(state, SFLOAT_INVALID);
-                return SFLOAT_NAN;
-            }
-            SFLOAT_RAISE(state, SFLOAT_DIVBYZERO);
-            return SFLOAT_PACK(sign_z, 0xFF, 0);
-        }
-        SFLOAT_SUBNORMALIZE(sig_b, &exp_b, &sig_b);
-    }
-    if (exp_a == 0) {
-        if (sig_a == 0)
-            return SFLOAT_PACK(sign_z, 0, 0);
-        SFLOAT_SUBNORMALIZE(sig_a, &exp_a, &sig_a);
-    }
-    exp_z = exp_a - exp_b + 0x7D;
-    sig_a = (sig_a | 0x00800000) << 7;
-    sig_b = (sig_b | 0x00800000) << 8;
-    if (sig_b <= (sig_a + sig_a)) {
-        sig_a >>= 1;
-        ++exp_z;
-    }
-    sig_z = (((uint64_t)sig_a) << 32) / sig_b;
-    if ((sig_z & 0x3F) == 0)
-        sig_z |= ((uint64_t)sig_b * sig_z != ((uint64_t)sig_a) << 32);
-    return SFLOAT_PACK_round(state, sign_z, exp_z, sig_z);
-}
-
-static sfloat_t sfloat_neg(sfloat_state_t *state, sfloat_t a) {
-    sfloat_cast_t neg;
-    neg.f = -1;
-    return sfloat_mul(state, a, neg.s);
-}
-
-static GMQCC_INLINE void sfloat_check(lex_ctx_t ctx, sfloat_state_t *state, const char *vec) {
-    /* Exception comes from vector component */
-    if (vec) {
-        if (state->exceptionflags & SFLOAT_DIVBYZERO)
-            compile_error(ctx, "division by zero in `%s' component", vec);
-        if (state->exceptionflags & SFLOAT_INVALID)
-            compile_error(ctx, "undefined (inf) in `%s' component", vec);
-        if (state->exceptionflags & SFLOAT_OVERFLOW)
-            compile_error(ctx, "arithmetic overflow in `%s' component", vec);
-        if (state->exceptionflags & SFLOAT_UNDERFLOW)
-            compile_error(ctx, "arithmetic underflow in `%s' component", vec);
-            return;
-    }
-    if (state->exceptionflags & SFLOAT_DIVBYZERO)
-        compile_error(ctx, "division by zero");
-    if (state->exceptionflags & SFLOAT_INVALID)
-        compile_error(ctx, "undefined (inf)");
-    if (state->exceptionflags & SFLOAT_OVERFLOW)
-        compile_error(ctx, "arithmetic overflow");
-    if (state->exceptionflags & SFLOAT_UNDERFLOW)
-        compile_error(ctx, "arithmetic underflow");
-}
-
-static GMQCC_INLINE void sfloat_init(sfloat_state_t *state) {
-    state->exceptionflags = SFLOAT_NOEXCEPT;
-    state->roundingmode   = FOLD_ROUNDING;
-    state->tiny           = FOLD_TINYNESS;
-}
-
-/*
- * There is two stages to constant folding in GMQCC: there is the parse
- * stage constant folding, where, with the help of the AST, operator
- * usages can be constant folded. Then there is the constant folding
- * in the IR for things like eliding if statements, can occur.
- *
- * This file is thus, split into two parts.
- */
-
-#define isfloat(X)      (((ast_expression*)(X))->vtype == TYPE_FLOAT)
-#define isvector(X)     (((ast_expression*)(X))->vtype == TYPE_VECTOR)
-#define isstring(X)     (((ast_expression*)(X))->vtype == TYPE_STRING)
-#define isarray(X)      (((ast_expression*)(X))->vtype == TYPE_ARRAY)
-#define isfloats(X,Y)   (isfloat  (X) && isfloat (Y))
-
-/*
- * Implementation of basic vector math for vec3_t, for trivial constant
- * folding.
- *
- * TODO: gcc/clang hinting for autovectorization
- */
-typedef enum {
-    VEC_COMP_X = 1 << 0,
-    VEC_COMP_Y = 1 << 1,
-    VEC_COMP_Z = 1 << 2
-} vec3_comp_t;
-
-typedef struct {
-    sfloat_cast_t x;
-    sfloat_cast_t y;
-    sfloat_cast_t z;
-} vec3_soft_t;
-
-typedef struct {
-    vec3_comp_t    faults;
-    sfloat_state_t state[3];
-} vec3_soft_state_t;
-
-static GMQCC_INLINE vec3_soft_t vec3_soft_convert(vec3_t vec) {
-    vec3_soft_t soft;
-    soft.x.f = vec.x;
-    soft.y.f = vec.y;
-    soft.z.f = vec.z;
-    return soft;
-}
-
-static GMQCC_INLINE bool vec3_soft_exception(vec3_soft_state_t *vstate, size_t index) {
-    sfloat_exceptionflags_t flags = vstate->state[index].exceptionflags;
-    if (flags & SFLOAT_DIVBYZERO) return true;
-    if (flags & SFLOAT_INVALID)   return true;
-    if (flags & SFLOAT_OVERFLOW)  return true;
-    if (flags & SFLOAT_UNDERFLOW) return true;
-    return false;
-}
-
-static GMQCC_INLINE void vec3_soft_eval(vec3_soft_state_t *state,
-                                        sfloat_t         (*callback)(sfloat_state_t *, sfloat_t, sfloat_t),
-                                        vec3_t             a,
-                                        vec3_t             b)
-{
-    vec3_soft_t sa = vec3_soft_convert(a);
-    vec3_soft_t sb = vec3_soft_convert(b);
-    callback(&state->state[0], sa.x.s, sb.x.s);
-    if (vec3_soft_exception(state, 0)) state->faults = (vec3_comp_t)(state->faults | VEC_COMP_X);
-    callback(&state->state[1], sa.y.s, sb.y.s);
-    if (vec3_soft_exception(state, 1)) state->faults = (vec3_comp_t)(state->faults | VEC_COMP_Y);
-    callback(&state->state[2], sa.z.s, sb.z.s);
-    if (vec3_soft_exception(state, 2)) state->faults = (vec3_comp_t)(state->faults | VEC_COMP_Z);
-}
-
-static GMQCC_INLINE void vec3_check_except(vec3_t     a,
-                                           vec3_t     b,
-                                           lex_ctx_t  ctx,
-                                           sfloat_t (*callback)(sfloat_state_t *, sfloat_t, sfloat_t))
-{
-    vec3_soft_state_t state;
-
-    if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS))
-        return;
-
-    sfloat_init(&state.state[0]);
-    sfloat_init(&state.state[1]);
-    sfloat_init(&state.state[2]);
-
-    vec3_soft_eval(&state, callback, a, b);
-    if (state.faults & VEC_COMP_X) sfloat_check(ctx, &state.state[0], "x");
-    if (state.faults & VEC_COMP_Y) sfloat_check(ctx, &state.state[1], "y");
-    if (state.faults & VEC_COMP_Z) sfloat_check(ctx, &state.state[2], "z");
-}
-
-static GMQCC_INLINE vec3_t vec3_add(lex_ctx_t ctx, vec3_t a, vec3_t b) {
-    vec3_t out;
-    vec3_check_except(a, b, ctx, &sfloat_add);
-    out.x = a.x + b.x;
-    out.y = a.y + b.y;
-    out.z = a.z + b.z;
-    return out;
-}
-
-static GMQCC_INLINE vec3_t vec3_sub(lex_ctx_t ctx, vec3_t a, vec3_t b) {
-    vec3_t out;
-    vec3_check_except(a, b, ctx, &sfloat_sub);
-    out.x = a.x - b.x;
-    out.y = a.y - b.y;
-    out.z = a.z - b.z;
-    return out;
-}
-
-static GMQCC_INLINE vec3_t vec3_neg(lex_ctx_t ctx, vec3_t a) {
-    vec3_t         out;
-    sfloat_cast_t  v[3];
-    sfloat_state_t s[3];
-
-    if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS))
-        goto end;
-
-    v[0].f = a.x;
-    v[1].f = a.y;
-    v[2].f = a.z;
-
-    sfloat_init(&s[0]);
-    sfloat_init(&s[1]);
-    sfloat_init(&s[2]);
-
-    sfloat_neg(&s[0], v[0].s);
-    sfloat_neg(&s[1], v[1].s);
-    sfloat_neg(&s[2], v[2].s);
-
-    sfloat_check(ctx, &s[0], NULL);
-    sfloat_check(ctx, &s[1], NULL);
-    sfloat_check(ctx, &s[2], NULL);
-
-end:
-    out.x = -a.x;
-    out.y = -a.y;
-    out.z = -a.z;
-    return out;
-}
-
-static GMQCC_INLINE vec3_t vec3_or(vec3_t a, vec3_t b) {
-    vec3_t out;
-    out.x = (qcfloat_t)(((qcint_t)a.x) | ((qcint_t)b.x));
-    out.y = (qcfloat_t)(((qcint_t)a.y) | ((qcint_t)b.y));
-    out.z = (qcfloat_t)(((qcint_t)a.z) | ((qcint_t)b.z));
-    return out;
-}
-
-static GMQCC_INLINE vec3_t vec3_orvf(vec3_t a, qcfloat_t b) {
-    vec3_t out;
-    out.x = (qcfloat_t)(((qcint_t)a.x) | ((qcint_t)b));
-    out.y = (qcfloat_t)(((qcint_t)a.y) | ((qcint_t)b));
-    out.z = (qcfloat_t)(((qcint_t)a.z) | ((qcint_t)b));
-    return out;
-}
-
-static GMQCC_INLINE vec3_t vec3_and(vec3_t a, vec3_t b) {
-    vec3_t out;
-    out.x = (qcfloat_t)(((qcint_t)a.x) & ((qcint_t)b.x));
-    out.y = (qcfloat_t)(((qcint_t)a.y) & ((qcint_t)b.y));
-    out.z = (qcfloat_t)(((qcint_t)a.z) & ((qcint_t)b.z));
-    return out;
-}
-
-static GMQCC_INLINE vec3_t vec3_andvf(vec3_t a, qcfloat_t b) {
-    vec3_t out;
-    out.x = (qcfloat_t)(((qcint_t)a.x) & ((qcint_t)b));
-    out.y = (qcfloat_t)(((qcint_t)a.y) & ((qcint_t)b));
-    out.z = (qcfloat_t)(((qcint_t)a.z) & ((qcint_t)b));
-    return out;
-}
-
-static GMQCC_INLINE vec3_t vec3_xor(vec3_t a, vec3_t b) {
-    vec3_t out;
-    out.x = (qcfloat_t)(((qcint_t)a.x) ^ ((qcint_t)b.x));
-    out.y = (qcfloat_t)(((qcint_t)a.y) ^ ((qcint_t)b.y));
-    out.z = (qcfloat_t)(((qcint_t)a.z) ^ ((qcint_t)b.z));
-    return out;
-}
-
-static GMQCC_INLINE vec3_t vec3_xorvf(vec3_t a, qcfloat_t b) {
-    vec3_t out;
-    out.x = (qcfloat_t)(((qcint_t)a.x) ^ ((qcint_t)b));
-    out.y = (qcfloat_t)(((qcint_t)a.y) ^ ((qcint_t)b));
-    out.z = (qcfloat_t)(((qcint_t)a.z) ^ ((qcint_t)b));
-    return out;
-}
-
-static GMQCC_INLINE vec3_t vec3_not(vec3_t a) {
-    vec3_t out;
-    out.x = -1-a.x;
-    out.y = -1-a.y;
-    out.z = -1-a.z;
-    return out;
-}
-
-static GMQCC_INLINE qcfloat_t vec3_mulvv(lex_ctx_t ctx, vec3_t a, vec3_t b) {
-    vec3_soft_t    sa;
-    vec3_soft_t    sb;
-    sfloat_state_t s[5];
-    sfloat_t       r[5];
-
-    if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS))
-        goto end;
-
-    sa = vec3_soft_convert(a);
-    sb = vec3_soft_convert(b);
-
-    sfloat_init(&s[0]);
-    sfloat_init(&s[1]);
-    sfloat_init(&s[2]);
-    sfloat_init(&s[3]);
-    sfloat_init(&s[4]);
-
-    r[0] = sfloat_mul(&s[0], sa.x.s, sb.x.s);
-    r[1] = sfloat_mul(&s[1], sa.y.s, sb.y.s);
-    r[2] = sfloat_mul(&s[2], sa.z.s, sb.z.s);
-    r[3] = sfloat_add(&s[3], r[0],   r[1]);
-    r[4] = sfloat_add(&s[4], r[3],   r[2]);
-
-    sfloat_check(ctx, &s[0], NULL);
-    sfloat_check(ctx, &s[1], NULL);
-    sfloat_check(ctx, &s[2], NULL);
-    sfloat_check(ctx, &s[3], NULL);
-    sfloat_check(ctx, &s[4], NULL);
-
-end:
-    return (a.x * b.x + a.y * b.y + a.z * b.z);
-}
-
-static GMQCC_INLINE vec3_t vec3_mulvf(lex_ctx_t ctx, vec3_t a, qcfloat_t b) {
-    vec3_t         out;
-    vec3_soft_t    sa;
-    sfloat_cast_t  sb;
-    sfloat_state_t s[3];
-
-    if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS))
-        goto end;
-
-    sa   = vec3_soft_convert(a);
-    sb.f = b;
-    sfloat_init(&s[0]);
-    sfloat_init(&s[1]);
-    sfloat_init(&s[2]);
-
-    sfloat_mul(&s[0], sa.x.s, sb.s);
-    sfloat_mul(&s[1], sa.y.s, sb.s);
-    sfloat_mul(&s[2], sa.z.s, sb.s);
-
-    sfloat_check(ctx, &s[0], "x");
-    sfloat_check(ctx, &s[1], "y");
-    sfloat_check(ctx, &s[2], "z");
-
-end:
-    out.x = a.x * b;
-    out.y = a.y * b;
-    out.z = a.z * b;
-    return out;
-}
-
-static GMQCC_INLINE bool vec3_cmp(vec3_t a, vec3_t b) {
-    return a.x == b.x &&
-           a.y == b.y &&
-           a.z == b.z;
-}
-
-static GMQCC_INLINE vec3_t vec3_create(float x, float y, float z) {
-    vec3_t out;
-    out.x = x;
-    out.y = y;
-    out.z = z;
-    return out;
-}
-
-static GMQCC_INLINE qcfloat_t vec3_notf(vec3_t a) {
-    return (!a.x && !a.y && !a.z);
-}
-
-static GMQCC_INLINE bool vec3_pbool(vec3_t a) {
-    return (a.x || a.y || a.z);
-}
-
-static GMQCC_INLINE vec3_t vec3_cross(lex_ctx_t ctx, vec3_t a, vec3_t b) {
-    vec3_t         out;
-    vec3_soft_t    sa;
-    vec3_soft_t    sb;
-    sfloat_t       r[9];
-    sfloat_state_t s[9];
-
-    if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS))
-        goto end;
-
-    sa = vec3_soft_convert(a);
-    sb = vec3_soft_convert(b);
-
-    sfloat_init(&s[0]);
-    sfloat_init(&s[1]);
-    sfloat_init(&s[2]);
-    sfloat_init(&s[3]);
-    sfloat_init(&s[4]);
-    sfloat_init(&s[5]);
-    sfloat_init(&s[6]);
-    sfloat_init(&s[7]);
-    sfloat_init(&s[8]);
-
-    r[0] = sfloat_mul(&s[0], sa.y.s, sb.z.s);
-    r[1] = sfloat_mul(&s[1], sa.z.s, sb.y.s);
-    r[2] = sfloat_mul(&s[2], sa.z.s, sb.x.s);
-    r[3] = sfloat_mul(&s[3], sa.x.s, sb.z.s);
-    r[4] = sfloat_mul(&s[4], sa.x.s, sb.y.s);
-    r[5] = sfloat_mul(&s[5], sa.y.s, sb.x.s);
-    r[6] = sfloat_sub(&s[6], r[0],   r[1]);
-    r[7] = sfloat_sub(&s[7], r[2],   r[3]);
-    r[8] = sfloat_sub(&s[8], r[4],   r[5]);
-
-    sfloat_check(ctx, &s[0], NULL);
-    sfloat_check(ctx, &s[1], NULL);
-    sfloat_check(ctx, &s[2], NULL);
-    sfloat_check(ctx, &s[3], NULL);
-    sfloat_check(ctx, &s[4], NULL);
-    sfloat_check(ctx, &s[5], NULL);
-    sfloat_check(ctx, &s[6], "x");
-    sfloat_check(ctx, &s[7], "y");
-    sfloat_check(ctx, &s[8], "z");
-
-end:
-    out.x = a.y * b.z - a.z * b.y;
-    out.y = a.z * b.x - a.x * b.z;
-    out.z = a.x * b.y - a.y * b.x;
-    return out;
-}
-
-static lex_ctx_t fold_ctx(fold_t *fold) {
-    lex_ctx_t ctx;
-    if (fold->parser->lex)
-        return parser_ctx(fold->parser);
-
-    memset(&ctx, 0, sizeof(ctx));
-    return ctx;
-}
-
-static GMQCC_INLINE bool fold_immediate_true(fold_t *fold, ast_value *v) {
-    switch (v->expression.vtype) {
-        case TYPE_FLOAT:
-            return !!v->constval.vfloat;
-        case TYPE_INTEGER:
-            return !!v->constval.vint;
-        case TYPE_VECTOR:
-            if (OPTS_FLAG(CORRECT_LOGIC))
-                return vec3_pbool(v->constval.vvec);
-            return !!(v->constval.vvec.x);
-        case TYPE_STRING:
-            if (!v->constval.vstring)
-                return false;
-            if (OPTS_FLAG(TRUE_EMPTY_STRINGS))
-                return true;
-            return !!v->constval.vstring[0];
-        default:
-            compile_error(fold_ctx(fold), "internal error: fold_immediate_true on invalid type");
-            break;
-    }
-    return !!v->constval.vfunc;
-}
-
-/* Handy macros to determine if an ast_value can be constant folded. */
-#define fold_can_1(X)  \
-    (ast_istype(((ast_expression*)(X)), ast_value) && (X)->hasvalue && ((X)->cvq == CV_CONST) && \
-                ((ast_expression*)(X))->vtype != TYPE_FUNCTION)
-
-#define fold_can_2(X, Y) (fold_can_1(X) && fold_can_1(Y))
-
-#define fold_immvalue_float(E)  ((E)->constval.vfloat)
-#define fold_immvalue_vector(E) ((E)->constval.vvec)
-#define fold_immvalue_string(E) ((E)->constval.vstring)
-
-fold_t *fold_init(parser_t *parser) {
-    fold_t *fold                 = (fold_t*)mem_a(sizeof(fold_t));
-    fold->parser                 = parser;
-    fold->imm_float              = NULL;
-    fold->imm_vector             = NULL;
-    fold->imm_string             = NULL;
-    fold->imm_string_untranslate = util_htnew(FOLD_STRING_UNTRANSLATE_HTSIZE);
-    fold->imm_string_dotranslate = util_htnew(FOLD_STRING_DOTRANSLATE_HTSIZE);
-
-    /*
-     * prime the tables with common constant values at constant
-     * locations.
-     */
-    (void)fold_constgen_float (fold,  0.0f, false);
-    (void)fold_constgen_float (fold,  1.0f, false);
-    (void)fold_constgen_float (fold, -1.0f, false);
-    (void)fold_constgen_float (fold,  2.0f, false);
-
-    (void)fold_constgen_vector(fold, vec3_create(0.0f, 0.0f, 0.0f));
-    (void)fold_constgen_vector(fold, vec3_create(-1.0f, -1.0f, -1.0f));
-
-    return fold;
-}
-
-bool fold_generate(fold_t *fold, ir_builder *ir) {
-    /* generate globals for immediate folded values */
-    size_t     i;
-    ast_value *cur;
-
-    for (i = 0; i < vec_size(fold->imm_float);   ++i)
-        if (!ast_global_codegen ((cur = fold->imm_float[i]), ir, false)) goto err;
-    for (i = 0; i < vec_size(fold->imm_vector);  ++i)
-        if (!ast_global_codegen((cur = fold->imm_vector[i]), ir, false)) goto err;
-    for (i = 0; i < vec_size(fold->imm_string);  ++i)
-        if (!ast_global_codegen((cur = fold->imm_string[i]), ir, false)) goto err;
-
-    return true;
-
-err:
-    con_out("failed to generate global %s\n", cur->name);
-    ir_builder_delete(ir);
-    return false;
-}
-
-void fold_cleanup(fold_t *fold) {
-    size_t i;
-
-    for (i = 0; i < vec_size(fold->imm_float);  ++i) ast_delete(fold->imm_float[i]);
-    for (i = 0; i < vec_size(fold->imm_vector); ++i) ast_delete(fold->imm_vector[i]);
-    for (i = 0; i < vec_size(fold->imm_string); ++i) ast_delete(fold->imm_string[i]);
-
-    vec_free(fold->imm_float);
-    vec_free(fold->imm_vector);
-    vec_free(fold->imm_string);
-
-    util_htdel(fold->imm_string_untranslate);
-    util_htdel(fold->imm_string_dotranslate);
-
-    mem_d(fold);
-}
-
-ast_expression *fold_constgen_float(fold_t *fold, qcfloat_t value, bool inexact) {
-    ast_value  *out = NULL;
-    size_t      i;
-
-    for (i = 0; i < vec_size(fold->imm_float); i++) {
-        if (!memcmp(&fold->imm_float[i]->constval.vfloat, &value, sizeof(qcfloat_t)))
-            return (ast_expression*)fold->imm_float[i];
-    }
-
-    out                  = ast_value_new(fold_ctx(fold), "#IMMEDIATE", TYPE_FLOAT);
-    out->cvq             = CV_CONST;
-    out->hasvalue        = true;
-    out->inexact         = inexact;
-    out->constval.vfloat = value;
-
-    vec_push(fold->imm_float, out);
-
-    return (ast_expression*)out;
-}
-
-ast_expression *fold_constgen_vector(fold_t *fold, vec3_t value) {
-    ast_value *out;
-    size_t     i;
-
-    for (i = 0; i < vec_size(fold->imm_vector); i++) {
-        if (vec3_cmp(fold->imm_vector[i]->constval.vvec, value))
-            return (ast_expression*)fold->imm_vector[i];
-    }
-
-    out                = ast_value_new(fold_ctx(fold), "#IMMEDIATE", TYPE_VECTOR);
-    out->cvq           = CV_CONST;
-    out->hasvalue      = true;
-    out->constval.vvec = value;
-
-    vec_push(fold->imm_vector, out);
-
-    return (ast_expression*)out;
-}
-
-ast_expression *fold_constgen_string(fold_t *fold, const char *str, bool translate) {
-    hash_table_t *table = (translate) ? fold->imm_string_untranslate : fold->imm_string_dotranslate;
-    ast_value    *out   = NULL;
-    size_t        hash  = util_hthash(table, str);
-
-    if ((out = (ast_value*)util_htgeth(table, str, hash)))
-        return (ast_expression*)out;
-
-    if (translate) {
-        char name[32];
-        util_snprintf(name, sizeof(name), "dotranslate_%lu", (unsigned long)(fold->parser->translated++));
-        out                    = ast_value_new(parser_ctx(fold->parser), name, TYPE_STRING);
-        out->expression.flags |= AST_FLAG_INCLUDE_DEF; /* def needs to be included for translatables */
-    } else
-        out                    = ast_value_new(fold_ctx(fold), "#IMMEDIATE", TYPE_STRING);
-
-    out->cvq              = CV_CONST;
-    out->hasvalue         = true;
-    out->isimm            = true;
-    out->constval.vstring = parser_strdup(str);
-
-    vec_push(fold->imm_string, out);
-    util_htseth(table, str, hash, out);
-
-    return (ast_expression*)out;
-}
-
-typedef union {
-    void     (*callback)(void);
-    sfloat_t (*binary)(sfloat_state_t *, sfloat_t, sfloat_t);
-    sfloat_t (*unary)(sfloat_state_t *, sfloat_t);
-} float_check_callback_t;
-
-static bool fold_check_except_float_impl(void     (*callback)(void),
-                                         fold_t    *fold,
-                                         ast_value *a,
-                                         ast_value *b)
-{
-    float_check_callback_t call;
-    sfloat_state_t s;
-    sfloat_cast_t ca;
-
-    if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS) && !OPTS_WARN(WARN_INEXACT_COMPARES))
-        return false;
-
-    call.callback = callback;
-    sfloat_init(&s);
-    ca.f = fold_immvalue_float(a);
-    if (b) {
-        sfloat_cast_t cb;
-        cb.f = fold_immvalue_float(b);
-        call.binary(&s, ca.s, cb.s);
-    } else {
-        call.unary(&s, ca.s);
-    }
-
-    if (s.exceptionflags == 0)
-        return false;
-
-    if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS))
-        goto inexact_possible;
-
-    sfloat_check(fold_ctx(fold), &s, NULL);
-
-inexact_possible:
-    return s.exceptionflags & SFLOAT_INEXACT;
-}
-
-#define fold_check_except_float(CALLBACK, FOLD, A, B) \
-    fold_check_except_float_impl(((void (*)(void))(CALLBACK)), (FOLD), (A), (B))
-
-static bool fold_check_inexact_float(fold_t *fold, ast_value *a, ast_value *b) {
-    lex_ctx_t ctx = fold_ctx(fold);
-    if (!OPTS_WARN(WARN_INEXACT_COMPARES))
-        return false;
-    if (!a->inexact && !b->inexact)
-        return false;
-    return compile_warning(ctx, WARN_INEXACT_COMPARES, "inexact value in comparison");
-}
-
-static GMQCC_INLINE ast_expression *fold_op_mul_vec(fold_t *fold, vec3_t vec, ast_value *sel, const char *set) {
-    qcfloat_t x = (&vec.x)[set[0]-'x'];
-    qcfloat_t y = (&vec.x)[set[1]-'x'];
-    qcfloat_t z = (&vec.x)[set[2]-'x'];
-    if (!y && !z) {
-        ast_expression *out;
-        ++opts_optimizationcount[OPTIM_VECTOR_COMPONENTS];
-        out                        = (ast_expression*)ast_member_new(fold_ctx(fold), (ast_expression*)sel, set[0]-'x', NULL);
-        out->node.keep             = false;
-        ((ast_member*)out)->rvalue = true;
-        if (x != -1.0f)
-            return (ast_expression*)ast_binary_new(fold_ctx(fold), INSTR_MUL_F, fold_constgen_float(fold, x, false), out);
-    }
-    return NULL;
-}
-
-
-static GMQCC_INLINE ast_expression *fold_op_neg(fold_t *fold, ast_value *a) {
-    if (isfloat(a)) {
-        if (fold_can_1(a)) {
-            /* Negation can produce inexact as well */
-            bool inexact = fold_check_except_float(&sfloat_neg, fold, a, NULL);
-            return fold_constgen_float(fold, -fold_immvalue_float(a), inexact);
-        }
-    } else if (isvector(a)) {
-        if (fold_can_1(a))
-            return fold_constgen_vector(fold, vec3_neg(fold_ctx(fold), fold_immvalue_vector(a)));
-    }
-    return NULL;
-}
-
-static GMQCC_INLINE ast_expression *fold_op_not(fold_t *fold, ast_value *a) {
-    if (isfloat(a)) {
-        if (fold_can_1(a))
-            return fold_constgen_float(fold, !fold_immvalue_float(a), false);
-    } else if (isvector(a)) {
-        if (fold_can_1(a))
-            return fold_constgen_float(fold, vec3_notf(fold_immvalue_vector(a)), false);
-    } else if (isstring(a)) {
-        if (fold_can_1(a)) {
-            if (OPTS_FLAG(TRUE_EMPTY_STRINGS))
-                return fold_constgen_float(fold, !fold_immvalue_string(a), false);
-            else
-                return fold_constgen_float(fold, !fold_immvalue_string(a) || !*fold_immvalue_string(a), false);
-        }
-    }
-    return NULL;
-}
-
-static GMQCC_INLINE ast_expression *fold_op_add(fold_t *fold, ast_value *a, ast_value *b) {
-    if (isfloat(a)) {
-        if (fold_can_2(a, b)) {
-            bool inexact = fold_check_except_float(&sfloat_add, fold, a, b);
-            return fold_constgen_float(fold, fold_immvalue_float(a) + fold_immvalue_float(b), inexact);
-        }
-    } else if (isvector(a)) {
-        if (fold_can_2(a, b))
-            return fold_constgen_vector(fold, vec3_add(fold_ctx(fold),
-                                                       fold_immvalue_vector(a),
-                                                       fold_immvalue_vector(b)));
-    }
-    return NULL;
-}
-
-static GMQCC_INLINE ast_expression *fold_op_sub(fold_t *fold, ast_value *a, ast_value *b) {
-    if (isfloat(a)) {
-        if (fold_can_2(a, b)) {
-            bool inexact = fold_check_except_float(&sfloat_sub, fold, a, b);
-            return fold_constgen_float(fold, fold_immvalue_float(a) - fold_immvalue_float(b), inexact);
-        }
-    } else if (isvector(a)) {
-        if (fold_can_2(a, b))
-            return fold_constgen_vector(fold, vec3_sub(fold_ctx(fold),
-                                                       fold_immvalue_vector(a),
-                                                       fold_immvalue_vector(b)));
-    }
-    return NULL;
-}
-
-static GMQCC_INLINE ast_expression *fold_op_mul(fold_t *fold, ast_value *a, ast_value *b) {
-    if (isfloat(a)) {
-        if (isvector(b)) {
-            if (fold_can_2(a, b))
-                return fold_constgen_vector(fold, vec3_mulvf(fold_ctx(fold), fold_immvalue_vector(b), fold_immvalue_float(a)));
-        } else {
-            if (fold_can_2(a, b)) {
-                bool inexact = fold_check_except_float(&sfloat_mul, fold, a, b);
-                return fold_constgen_float(fold, fold_immvalue_float(a) * fold_immvalue_float(b), inexact);
-            }
-        }
-    } else if (isvector(a)) {
-        if (isfloat(b)) {
-            if (fold_can_2(a, b))
-                return fold_constgen_vector(fold, vec3_mulvf(fold_ctx(fold), fold_immvalue_vector(a), fold_immvalue_float(b)));
-        } else {
-            if (fold_can_2(a, b)) {
-                return fold_constgen_float(fold, vec3_mulvv(fold_ctx(fold), fold_immvalue_vector(a), fold_immvalue_vector(b)), false);
-            } else if (OPTS_OPTIMIZATION(OPTIM_VECTOR_COMPONENTS) && fold_can_1(a)) {
-                ast_expression *out;
-                if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(a), b, "xyz"))) return out;
-                if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(a), b, "yxz"))) return out;
-                if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(a), b, "zxy"))) return out;
-            } else if (OPTS_OPTIMIZATION(OPTIM_VECTOR_COMPONENTS) && fold_can_1(b)) {
-                ast_expression *out;
-                if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(b), a, "xyz"))) return out;
-                if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(b), a, "yxz"))) return out;
-                if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(b), a, "zxy"))) return out;
-            }
-        }
-    }
-    return NULL;
-}
-
-static GMQCC_INLINE ast_expression *fold_op_div(fold_t *fold, ast_value *a, ast_value *b) {
-    if (isfloat(a)) {
-        if (fold_can_2(a, b)) {
-            bool inexact = fold_check_except_float(&sfloat_div, fold, a, b);
-            return fold_constgen_float(fold, fold_immvalue_float(a) / fold_immvalue_float(b), inexact);
-        } else if (fold_can_1(b)) {
-            return (ast_expression*)ast_binary_new(
-                fold_ctx(fold),
-                INSTR_MUL_F,
-                (ast_expression*)a,
-                fold_constgen_float(fold, 1.0f / fold_immvalue_float(b), false)
-            );
-        }
-    } else if (isvector(a)) {
-        if (fold_can_2(a, b)) {
-            return fold_constgen_vector(fold, vec3_mulvf(fold_ctx(fold), fold_immvalue_vector(a), 1.0f / fold_immvalue_float(b)));
-        } else {
-            return (ast_expression*)ast_binary_new(
-                fold_ctx(fold),
-                INSTR_MUL_VF,
-                (ast_expression*)a,
-                (fold_can_1(b))
-                    ? (ast_expression*)fold_constgen_float(fold, 1.0f / fold_immvalue_float(b), false)
-                    : (ast_expression*)ast_binary_new(
-                                            fold_ctx(fold),
-                                            INSTR_DIV_F,
-                                            (ast_expression*)fold->imm_float[1],
-                                            (ast_expression*)b
-                    )
-            );
-        }
-    }
-    return NULL;
-}
-
-static GMQCC_INLINE ast_expression *fold_op_mod(fold_t *fold, ast_value *a, ast_value *b) {
-    return (fold_can_2(a, b))
-                ? fold_constgen_float(fold, fmod(fold_immvalue_float(a), fold_immvalue_float(b)), false)
-                : NULL;
-}
-
-static GMQCC_INLINE ast_expression *fold_op_bor(fold_t *fold, ast_value *a, ast_value *b) {
-    if (isfloat(a)) {
-        if (fold_can_2(a, b))
-            return fold_constgen_float(fold, (qcfloat_t)(((qcint_t)fold_immvalue_float(a)) | ((qcint_t)fold_immvalue_float(b))), false);
-    } else {
-        if (isvector(b)) {
-            if (fold_can_2(a, b))
-                return fold_constgen_vector(fold, vec3_or(fold_immvalue_vector(a), fold_immvalue_vector(b)));
-        } else {
-            if (fold_can_2(a, b))
-                return fold_constgen_vector(fold, vec3_orvf(fold_immvalue_vector(a), fold_immvalue_float(b)));
-        }
-    }
-    return NULL;
-}
-
-static GMQCC_INLINE ast_expression *fold_op_band(fold_t *fold, ast_value *a, ast_value *b) {
-    if (isfloat(a)) {
-        if (fold_can_2(a, b))
-            return fold_constgen_float(fold, (qcfloat_t)(((qcint_t)fold_immvalue_float(a)) & ((qcint_t)fold_immvalue_float(b))), false);
-    } else {
-        if (isvector(b)) {
-            if (fold_can_2(a, b))
-                return fold_constgen_vector(fold, vec3_and(fold_immvalue_vector(a), fold_immvalue_vector(b)));
-        } else {
-            if (fold_can_2(a, b))
-                return fold_constgen_vector(fold, vec3_andvf(fold_immvalue_vector(a), fold_immvalue_float(b)));
-        }
-    }
-    return NULL;
-}
-
-static GMQCC_INLINE ast_expression *fold_op_xor(fold_t *fold, ast_value *a, ast_value *b) {
-    if (isfloat(a)) {
-        if (fold_can_2(a, b))
-            return fold_constgen_float(fold, (qcfloat_t)(((qcint_t)fold_immvalue_float(a)) ^ ((qcint_t)fold_immvalue_float(b))), false);
-    } else {
-        if (fold_can_2(a, b)) {
-            if (isvector(b))
-                return fold_constgen_vector(fold, vec3_xor(fold_immvalue_vector(a), fold_immvalue_vector(b)));
-            else
-                return fold_constgen_vector(fold, vec3_xorvf(fold_immvalue_vector(a), fold_immvalue_float(b)));
-        }
-    }
-    return NULL;
-}
-
-static GMQCC_INLINE ast_expression *fold_op_lshift(fold_t *fold, ast_value *a, ast_value *b) {
-    if (fold_can_2(a, b) && isfloats(a, b))
-        return fold_constgen_float(fold, (qcfloat_t)floorf(fold_immvalue_float(a) * powf(2.0f, fold_immvalue_float(b))), false);
-    return NULL;
-}
-
-static GMQCC_INLINE ast_expression *fold_op_rshift(fold_t *fold, ast_value *a, ast_value *b) {
-    if (fold_can_2(a, b) && isfloats(a, b))
-        return fold_constgen_float(fold, (qcfloat_t)floorf(fold_immvalue_float(a) / powf(2.0f, fold_immvalue_float(b))), false);
-    return NULL;
-}
-
-static GMQCC_INLINE ast_expression *fold_op_andor(fold_t *fold, ast_value *a, ast_value *b, float expr) {
-    if (fold_can_2(a, b)) {
-        if (OPTS_FLAG(PERL_LOGIC)) {
-            if (expr)
-                return (fold_immediate_true(fold, a)) ? (ast_expression*)a : (ast_expression*)b;
-            else
-                return (fold_immediate_true(fold, a)) ? (ast_expression*)b : (ast_expression*)a;
-        } else {
-            return fold_constgen_float (
-                fold,
-                ((expr) ? (fold_immediate_true(fold, a) || fold_immediate_true(fold, b))
-                        : (fold_immediate_true(fold, a) && fold_immediate_true(fold, b)))
-                            ? 1
-                            : 0,
-                false
-            );
-        }
-    }
-    return NULL;
-}
-
-static GMQCC_INLINE ast_expression *fold_op_tern(fold_t *fold, ast_value *a, ast_value *b, ast_value *c) {
-    if (fold_can_1(a)) {
-        return fold_immediate_true(fold, a)
-                    ? (ast_expression*)b
-                    : (ast_expression*)c;
-    }
-    return NULL;
-}
-
-static GMQCC_INLINE ast_expression *fold_op_exp(fold_t *fold, ast_value *a, ast_value *b) {
-    if (fold_can_2(a, b))
-        return fold_constgen_float(fold, (qcfloat_t)powf(fold_immvalue_float(a), fold_immvalue_float(b)), false);
-    return NULL;
-}
-
-static GMQCC_INLINE ast_expression *fold_op_lteqgt(fold_t *fold, ast_value *a, ast_value *b) {
-    if (fold_can_2(a,b)) {
-        fold_check_inexact_float(fold, a, b);
-        if (fold_immvalue_float(a) <  fold_immvalue_float(b)) return (ast_expression*)fold->imm_float[2];
-        if (fold_immvalue_float(a) == fold_immvalue_float(b)) return (ast_expression*)fold->imm_float[0];
-        if (fold_immvalue_float(a) >  fold_immvalue_float(b)) return (ast_expression*)fold->imm_float[1];
-    }
-    return NULL;
-}
-
-static GMQCC_INLINE ast_expression *fold_op_ltgt(fold_t *fold, ast_value *a, ast_value *b, bool lt) {
-    if (fold_can_2(a, b)) {
-        fold_check_inexact_float(fold, a, b);
-        return (lt) ? (ast_expression*)fold->imm_float[!!(fold_immvalue_float(a) < fold_immvalue_float(b))]
-                    : (ast_expression*)fold->imm_float[!!(fold_immvalue_float(a) > fold_immvalue_float(b))];
-    }
-    return NULL;
-}
-
-static GMQCC_INLINE ast_expression *fold_op_cmp(fold_t *fold, ast_value *a, ast_value *b, bool ne) {
-    if (fold_can_2(a, b)) {
-        if (isfloat(a) && isfloat(b)) {
-            float la = fold_immvalue_float(a);
-            float lb = fold_immvalue_float(b);
-            fold_check_inexact_float(fold, a, b);
-            return (ast_expression*)fold->imm_float[ne ? la != lb : la == lb];
-        } else if (isvector(a) && isvector(b)) {
-            vec3_t la = fold_immvalue_vector(a);
-            vec3_t lb = fold_immvalue_vector(b);
-            bool compare = vec3_cmp(la, lb);
-            return (ast_expression*)fold->imm_float[ne ? !compare : compare];
-        } else if (isstring(a) && isstring(b)) {
-            bool compare = !strcmp(fold_immvalue_string(a), fold_immvalue_string(b));
-            return (ast_expression*)fold->imm_float[ne ? !compare : compare];
-        }
-    }
-    return NULL;
-}
-
-static GMQCC_INLINE ast_expression *fold_op_bnot(fold_t *fold, ast_value *a) {
-    if (isfloat(a)) {
-        if (fold_can_1(a))
-            return fold_constgen_float(fold, -1-fold_immvalue_float(a), false);
-    } else {
-        if (isvector(a)) {
-            if (fold_can_1(a))
-                return fold_constgen_vector(fold, vec3_not(fold_immvalue_vector(a)));
-        }
-    }
-    return NULL;
-}
-
-static GMQCC_INLINE ast_expression *fold_op_cross(fold_t *fold, ast_value *a, ast_value *b) {
-    if (fold_can_2(a, b))
-        return fold_constgen_vector(fold, vec3_cross(fold_ctx(fold),
-                                                     fold_immvalue_vector(a),
-                                                     fold_immvalue_vector(b)));
-    return NULL;
-}
-
-static GMQCC_INLINE ast_expression *fold_op_length(fold_t *fold, ast_value *a) {
-    if (fold_can_1(a) && isstring(a))
-        return fold_constgen_float(fold, strlen(fold_immvalue_string(a)), false);
-    if (isarray(a))
-        return fold_constgen_float(fold, vec_size(a->initlist), false);
-    return NULL;
-}
-
-ast_expression *fold_op(fold_t *fold, const oper_info *info, ast_expression **opexprs) {
-    ast_value      *a = (ast_value*)opexprs[0];
-    ast_value      *b = (ast_value*)opexprs[1];
-    ast_value      *c = (ast_value*)opexprs[2];
-    ast_expression *e = NULL;
-
-    /* can a fold operation be applied to this operator usage? */
-    if (!info->folds)
-        return NULL;
-
-    switch(info->operands) {
-        case 3: if(!c) return NULL;
-        case 2: if(!b) return NULL;
-        case 1:
-        if(!a) {
-            compile_error(fold_ctx(fold), "internal error: fold_op no operands to fold\n");
-            return NULL;
-        }
-    }
-
-    /*
-     * we could use a boolean and default case but ironically gcc produces
-     * invalid broken assembly from that operation. clang/tcc get it right,
-     * but interestingly ignore compiling this to a jump-table when I do that,
-     * this happens to be the most efficent method, since you have per-level
-     * granularity on the pointer check happening only for the case you check
-     * it in. Opposed to the default method which would involve a boolean and
-     * pointer check after wards.
-     */
-    #define fold_op_case(ARGS, ARGS_OPID, OP, ARGS_FOLD)    \
-        case opid##ARGS ARGS_OPID:                          \
-            if ((e = fold_op_##OP ARGS_FOLD)) {             \
-                ++opts_optimizationcount[OPTIM_CONST_FOLD]; \
-            }                                               \
-            return e
-
-    switch(info->id) {
-        fold_op_case(2, ('-', 'P'),      neg,    (fold, a));
-        fold_op_case(2, ('!', 'P'),      not,    (fold, a));
-        fold_op_case(1, ('+'),           add,    (fold, a, b));
-        fold_op_case(1, ('-'),           sub,    (fold, a, b));
-        fold_op_case(1, ('*'),           mul,    (fold, a, b));
-        fold_op_case(1, ('/'),           div,    (fold, a, b));
-        fold_op_case(1, ('%'),           mod,    (fold, a, b));
-        fold_op_case(1, ('|'),           bor,    (fold, a, b));
-        fold_op_case(1, ('&'),           band,   (fold, a, b));
-        fold_op_case(1, ('^'),           xor,    (fold, a, b));
-        fold_op_case(1, ('<'),           ltgt,   (fold, a, b, true));
-        fold_op_case(1, ('>'),           ltgt,   (fold, a, b, false));
-        fold_op_case(2, ('<', '<'),      lshift, (fold, a, b));
-        fold_op_case(2, ('>', '>'),      rshift, (fold, a, b));
-        fold_op_case(2, ('|', '|'),      andor,  (fold, a, b, true));
-        fold_op_case(2, ('&', '&'),      andor,  (fold, a, b, false));
-        fold_op_case(2, ('?', ':'),      tern,   (fold, a, b, c));
-        fold_op_case(2, ('*', '*'),      exp,    (fold, a, b));
-        fold_op_case(3, ('<','=','>'),   lteqgt, (fold, a, b));
-        fold_op_case(2, ('!', '='),      cmp,    (fold, a, b, true));
-        fold_op_case(2, ('=', '='),      cmp,    (fold, a, b, false));
-        fold_op_case(2, ('~', 'P'),      bnot,   (fold, a));
-        fold_op_case(2, ('>', '<'),      cross,  (fold, a, b));
-        fold_op_case(3, ('l', 'e', 'n'), length, (fold, a));
-    }
-    #undef fold_op_case
-    compile_error(fold_ctx(fold), "internal error: attempted to constant-fold for unsupported operator");
-    return NULL;
-}
-
-/*
- * Constant folding for compiler intrinsics, similar approach to operator
- * folding, primarily: individual functions for each intrinsics to fold,
- * and a generic selection function.
- */
-static GMQCC_INLINE ast_expression *fold_intrin_isfinite(fold_t *fold, ast_value *a) {
-    return fold_constgen_float(fold, isfinite(fold_immvalue_float(a)), false);
-}
-static GMQCC_INLINE ast_expression *fold_intrin_isinf(fold_t *fold, ast_value *a) {
-    return fold_constgen_float(fold, isinf(fold_immvalue_float(a)), false);
-}
-static GMQCC_INLINE ast_expression *fold_intrin_isnan(fold_t *fold, ast_value *a) {
-    return fold_constgen_float(fold, isnan(fold_immvalue_float(a)), false);
-}
-static GMQCC_INLINE ast_expression *fold_intrin_isnormal(fold_t *fold, ast_value *a) {
-    return fold_constgen_float(fold, isnormal(fold_immvalue_float(a)), false);
-}
-static GMQCC_INLINE ast_expression *fold_intrin_signbit(fold_t *fold, ast_value *a) {
-    return fold_constgen_float(fold, signbit(fold_immvalue_float(a)), false);
-}
-static GMQCC_INLINE ast_expression *fold_intirn_acosh(fold_t *fold, ast_value *a) {
-    return fold_constgen_float(fold, acoshf(fold_immvalue_float(a)), false);
-}
-static GMQCC_INLINE ast_expression *fold_intrin_asinh(fold_t *fold, ast_value *a) {
-    return fold_constgen_float(fold, asinhf(fold_immvalue_float(a)), false);
-}
-static GMQCC_INLINE ast_expression *fold_intrin_atanh(fold_t *fold, ast_value *a) {
-    return fold_constgen_float(fold, (float)atanh(fold_immvalue_float(a)), false);
-}
-static GMQCC_INLINE ast_expression *fold_intrin_exp(fold_t *fold, ast_value *a) {
-    return fold_constgen_float(fold, expf(fold_immvalue_float(a)), false);
-}
-static GMQCC_INLINE ast_expression *fold_intrin_exp2(fold_t *fold, ast_value *a) {
-    return fold_constgen_float(fold, exp2f(fold_immvalue_float(a)), false);
-}
-static GMQCC_INLINE ast_expression *fold_intrin_expm1(fold_t *fold, ast_value *a) {
-    return fold_constgen_float(fold, expm1f(fold_immvalue_float(a)), false);
-}
-static GMQCC_INLINE ast_expression *fold_intrin_mod(fold_t *fold, ast_value *lhs, ast_value *rhs) {
-    return fold_constgen_float(fold, fmodf(fold_immvalue_float(lhs), fold_immvalue_float(rhs)), false);
-}
-static GMQCC_INLINE ast_expression *fold_intrin_pow(fold_t *fold, ast_value *lhs, ast_value *rhs) {
-    return fold_constgen_float(fold, powf(fold_immvalue_float(lhs), fold_immvalue_float(rhs)), false);
-}
-static GMQCC_INLINE ast_expression *fold_intrin_fabs(fold_t *fold, ast_value *a) {
-    return fold_constgen_float(fold, fabsf(fold_immvalue_float(a)), false);
-}
-
-
-ast_expression *fold_intrin(fold_t *fold, const char *intrin, ast_expression **arg) {
-    ast_expression *ret = NULL;
-    ast_value      *a   = (ast_value*)arg[0];
-    ast_value      *b   = (ast_value*)arg[1];
-
-    if (!strcmp(intrin, "isfinite")) ret = fold_intrin_isfinite(fold, a);
-    if (!strcmp(intrin, "isinf"))    ret = fold_intrin_isinf(fold, a);
-    if (!strcmp(intrin, "isnan"))    ret = fold_intrin_isnan(fold, a);
-    if (!strcmp(intrin, "isnormal")) ret = fold_intrin_isnormal(fold, a);
-    if (!strcmp(intrin, "signbit"))  ret = fold_intrin_signbit(fold, a);
-    if (!strcmp(intrin, "acosh"))    ret = fold_intirn_acosh(fold, a);
-    if (!strcmp(intrin, "asinh"))    ret = fold_intrin_asinh(fold, a);
-    if (!strcmp(intrin, "atanh"))    ret = fold_intrin_atanh(fold, a);
-    if (!strcmp(intrin, "exp"))      ret = fold_intrin_exp(fold, a);
-    if (!strcmp(intrin, "exp2"))     ret = fold_intrin_exp2(fold, a);
-    if (!strcmp(intrin, "expm1"))    ret = fold_intrin_expm1(fold, a);
-    if (!strcmp(intrin, "mod"))      ret = fold_intrin_mod(fold, a, b);
-    if (!strcmp(intrin, "pow"))      ret = fold_intrin_pow(fold, a, b);
-    if (!strcmp(intrin, "fabs"))     ret = fold_intrin_fabs(fold, a);
-
-    if (ret)
-        ++opts_optimizationcount[OPTIM_CONST_FOLD];
-
-    return ret;
-}
-
-/*
- * These are all the actual constant folding methods that happen in between
- * the AST/IR stage of the compiler , i.e eliminating branches for const
- * expressions, which is the only supported thing so far. We undefine the
- * testing macros here because an ir_value is differant than an ast_value.
- */
-#undef expect
-#undef isfloat
-#undef isstring
-#undef isvector
-#undef fold_immvalue_float
-#undef fold_immvalue_string
-#undef fold_immvalue_vector
-#undef fold_can_1
-#undef fold_can_2
-
-#define isfloat(X)              ((X)->vtype == TYPE_FLOAT)
-/*#define isstring(X)             ((X)->vtype == TYPE_STRING)*/
-/*#define isvector(X)             ((X)->vtype == TYPE_VECTOR)*/
-#define fold_immvalue_float(X)  ((X)->constval.vfloat)
-#define fold_immvalue_vector(X) ((X)->constval.vvec)
-/*#define fold_immvalue_string(X) ((X)->constval.vstring)*/
-#define fold_can_1(X)           ((X)->hasvalue && (X)->cvq == CV_CONST)
-/*#define fold_can_2(X,Y)         (fold_can_1(X) && fold_can_1(Y))*/
-
-static ast_expression *fold_superfluous(ast_expression *left, ast_expression *right, int op) {
-    ast_expression *swapped = NULL; /* using this as bool */
-    ast_value *load;
-
-    if (!ast_istype(right, ast_value) || !fold_can_1((load = (ast_value*)right))) {
-        swapped = left;
-        left    = right;
-        right   = swapped;
-    }
-
-    if (!ast_istype(right, ast_value) || !fold_can_1((load = (ast_value*)right)))
-        return NULL;
-
-    switch (op) {
-        case INSTR_DIV_F:
-            if (swapped)
-                return NULL;
-        case INSTR_MUL_F:
-            if (fold_immvalue_float(load) == 1.0f) {
-                ++opts_optimizationcount[OPTIM_PEEPHOLE];
-                ast_unref(right);
-                return left;
-            }
-            break;
-
-
-        case INSTR_SUB_F:
-            if (swapped)
-                return NULL;
-        case INSTR_ADD_F:
-            if (fold_immvalue_float(load) == 0.0f) {
-                ++opts_optimizationcount[OPTIM_PEEPHOLE];
-                ast_unref(right);
-                return left;
-            }
-            break;
-
-        case INSTR_MUL_V:
-            if (vec3_cmp(fold_immvalue_vector(load), vec3_create(1, 1, 1))) {
-                ++opts_optimizationcount[OPTIM_PEEPHOLE];
-                ast_unref(right);
-                return left;
-            }
-            break;
-
-        case INSTR_SUB_V:
-            if (swapped)
-                return NULL;
-        case INSTR_ADD_V:
-            if (vec3_cmp(fold_immvalue_vector(load), vec3_create(0, 0, 0))) {
-                ++opts_optimizationcount[OPTIM_PEEPHOLE];
-                ast_unref(right);
-                return left;
-            }
-            break;
-    }
-
-    return NULL;
-}
-
-ast_expression *fold_binary(lex_ctx_t ctx, int op, ast_expression *left, ast_expression *right) {
-    ast_expression *ret = fold_superfluous(left, right, op);
-    if (ret)
-        return ret;
-    return (ast_expression*)ast_binary_new(ctx, op, left, right);
-}
-
-static GMQCC_INLINE int fold_cond(ir_value *condval, ast_function *func, ast_ifthen *branch) {
-    if (isfloat(condval) && fold_can_1(condval) && OPTS_OPTIMIZATION(OPTIM_CONST_FOLD_DCE)) {
-        ast_expression_codegen *cgen;
-        ir_block               *elide;
-        ir_value               *dummy;
-        bool                    istrue  = (fold_immvalue_float(condval) != 0.0f && branch->on_true);
-        bool                    isfalse = (fold_immvalue_float(condval) == 0.0f && branch->on_false);
-        ast_expression         *path    = (istrue)  ? branch->on_true  :
-                                          (isfalse) ? branch->on_false : NULL;
-        if (!path) {
-            /*
-             * no path to take implies that the evaluation is if(0) and there
-             * is no else block. so eliminate all the code.
-             */
-            ++opts_optimizationcount[OPTIM_CONST_FOLD_DCE];
-            return true;
-        }
-
-        if (!(elide = ir_function_create_block(ast_ctx(branch), func->ir_func, ast_function_label(func, ((istrue) ? "ontrue" : "onfalse")))))
-            return false;
-        if (!(*(cgen = path->codegen))((ast_expression*)path, func, false, &dummy))
-            return false;
-        if (!ir_block_create_jump(func->curblock, ast_ctx(branch), elide))
-            return false;
-        /*
-         * now the branch has been eliminated and the correct block for the constant evaluation
-         * is expanded into the current block for the function.
-         */
-        func->curblock = elide;
-        ++opts_optimizationcount[OPTIM_CONST_FOLD_DCE];
-        return true;
-    }
-    return -1; /* nothing done */
-}
-
-int fold_cond_ternary(ir_value *condval, ast_function *func, ast_ternary *branch) {
-    return fold_cond(condval, func, (ast_ifthen*)branch);
-}
-
-int fold_cond_ifthen(ir_value *condval, ast_function *func, ast_ifthen *branch) {
-    return fold_cond(condval, func, branch);
-}
diff --git a/fold.cpp b/fold.cpp
new file mode 100644 (file)
index 0000000..6d56d8a
--- /dev/null
+++ b/fold.cpp
@@ -0,0 +1,1653 @@
+#include <string.h>
+#include <math.h>
+
+#include "fold.h"
+#include "ast.h"
+#include "ir.h"
+
+#include "parser.h"
+
+#define FOLD_STRING_UNTRANSLATE_HTSIZE 1024
+#define FOLD_STRING_DOTRANSLATE_HTSIZE 1024
+
+/* The options to use for inexact and arithmetic exceptions */
+#define FOLD_ROUNDING SFLOAT_ROUND_NEAREST_EVEN
+#define FOLD_TINYNESS SFLOAT_TBEFORE
+
+/*
+ * Comparing float values is an unsafe operation when the operands to the
+ * comparison are floating point values that are inexact. For instance 1/3 is an
+ * inexact value. The FPU is meant to raise exceptions when these sorts of things
+ * happen, including division by zero, underflows and overflows. The C standard
+ * library provides us with the <fenv.h> header to gain access to the floating-
+ * point environment and lets us set the rounding mode and check for these exceptions.
+ * The problem is the standard C library allows an implementation to leave these
+ * stubbed out and does not require they be implemented. Furthermore, depending
+ * on implementations there is no control over the FPU. This is an IEE 754
+ * conforming implementation in software to compensate.
+ */
+typedef uint32_t sfloat_t;
+
+union sfloat_cast_t {
+    qcfloat_t f;
+    sfloat_t s;
+};
+
+/* Exception flags */
+enum sfloat_exceptionflags_t {
+    SFLOAT_NOEXCEPT  = 0,
+    SFLOAT_INVALID   = 1,
+    SFLOAT_DIVBYZERO = 4,
+    SFLOAT_OVERFLOW  = 8,
+    SFLOAT_UNDERFLOW = 16,
+    SFLOAT_INEXACT   = 32
+};
+
+/* Rounding modes */
+enum sfloat_roundingmode_t {
+    SFLOAT_ROUND_NEAREST_EVEN,
+    SFLOAT_ROUND_DOWN,
+    SFLOAT_ROUND_UP,
+    SFLOAT_ROUND_TO_ZERO
+};
+
+/* Underflow tininess-detection mode */
+enum sfloat_tdetect_t {
+    SFLOAT_TAFTER,
+    SFLOAT_TBEFORE
+};
+
+struct sfloat_state_t {
+    sfloat_roundingmode_t roundingmode;
+    sfloat_exceptionflags_t exceptionflags;
+    sfloat_tdetect_t tiny;
+};
+
+/* Counts the number of leading zero bits before the most-significand one bit. */
+#ifdef _MSC_VER
+/* MSVC has an intrinsic for this */
+    static GMQCC_INLINE uint32_t sfloat_clz(uint32_t x) {
+        int r = 0;
+        _BitScanForward(&r, x);
+        return r;
+    }
+#   define SFLOAT_CLZ(X, SUB) \
+        (sfloat_clz((X)) - (SUB))
+#elif defined(__GNUC__) || defined(__CLANG__)
+/* Clang and GCC have a builtin for this */
+#   define SFLOAT_CLZ(X, SUB) \
+        (__builtin_clz((X)) - (SUB))
+#else
+/* Native fallback */
+    static GMQCC_INLINE uint32_t sfloat_popcnt(uint32_t x) {
+        x -= ((x >> 1) & 0x55555555);
+        x  = (((x >> 2) & 0x33333333) + (x & 0x33333333));
+        x  = (((x >> 4) + x) & 0x0F0F0F0F);
+        x += x >> 8;
+        x += x >> 16;
+        return x & 0x0000003F;
+    }
+    static GMQCC_INLINE uint32_t sfloat_clz(uint32_t x) {
+        x |= (x >> 1);
+        x |= (x >> 2);
+        x |= (x >> 4);
+        x |= (x >> 8);
+        x |= (x >> 16);
+        return 32 - sfloat_popcnt(x);
+    }
+#   define SFLOAT_CLZ(X, SUB) \
+        (sfloat_clz((X) - (SUB)))
+#endif
+
+/* The value of a NaN */
+#define SFLOAT_NAN 0xFFFFFFFF
+/* Test if NaN */
+#define SFLOAT_ISNAN(A) \
+    (0xFF000000 < (uint32_t)((A) << 1))
+/* Test if signaling NaN */
+#define SFLOAT_ISSNAN(A) \
+    (((((A) >> 22) & 0x1FF) == 0x1FE) && ((A) & 0x003FFFFF))
+/* Raise exception */
+#define SFLOAT_RAISE(STATE, FLAGS) \
+    ((STATE)->exceptionflags = (sfloat_exceptionflags_t)((STATE)->exceptionflags | (FLAGS)))
+/*
+ * Shifts `A' right by the number of bits given in `COUNT'. If any non-zero bits
+ * are shifted off they are forced into the least significand bit of the result
+ * by setting it to one. As a result of this, the value of `COUNT' can be
+ * arbitrarily large; if `COUNT' is greater than 32, the result will be either
+ * zero or one, depending on whether `A' is a zero or non-zero. The result is
+ * stored into the value pointed by `Z'.
+ */
+#define SFLOAT_SHIFT(SIZE, A, COUNT, Z)                                      \
+    *(Z) = ((COUNT) == 0)                                                    \
+        ? 1                                                                  \
+        : (((COUNT) < (SIZE))                                                \
+            ? ((A) >> (COUNT)) | (((A) << ((-(COUNT)) & ((SIZE) - 1))) != 0) \
+            : ((A) != 0))
+
+/* Extract fractional component */
+#define SFLOAT_EXTRACT_FRAC(X) \
+    ((uint32_t)((X) & 0x007FFFFF))
+/* Extract exponent component */
+#define SFLOAT_EXTRACT_EXP(X) \
+    ((int16_t)((X) >> 23) & 0xFF)
+/* Extract sign bit */
+#define SFLOAT_EXTRACT_SIGN(X) \
+    ((X) >> 31)
+/*
+ * Normalizes the subnormal value represented by the denormalized significand
+ * `SA'. The normalized exponent and significand are stored at the locations
+ * pointed by `Z' and `SZ' respectively.
+ */
+#define SFLOAT_SUBNORMALIZE(SA, Z, SZ) \
+    (void)(*(SZ) = (SA) << SFLOAT_CLZ((SA), 8), *(Z) = 1 - SFLOAT_CLZ((SA), 8))
+/*
+ * Packs the sign `SIGN', exponent `EXP' and significand `SIG' into the value
+ * giving the result.
+ *
+ * After the shifting into their proper positions, the fields are added together
+ * to form the result. This means any integer portion of `SIG' will be added
+ * to the exponent. Similarly, because a properly normalized significand will
+ * always have an integer portion equal to one, the exponent input `EXP' should
+ * be one less than the desired result exponent whenever the significant input
+ * `SIG' is a complete, normalized significand.
+ */
+#define SFLOAT_PACK(SIGN, EXP, SIG) \
+    (sfloat_t)((((uint32_t)(SIGN)) << 31) + (((uint32_t)(EXP)) << 23) + (SIG))
+
+/*
+ * Takes two values `a' and `b', one of which is a NaN, and returns the appropriate
+ * NaN result. If either `a' or `b' is a signaling NaN than an invalid exception is
+ * raised.
+ */
+static sfloat_t sfloat_propagate_nan(sfloat_state_t *state, sfloat_t a, sfloat_t b) {
+    bool isnan_a  = SFLOAT_ISNAN(a);
+    bool issnan_a = SFLOAT_ISSNAN(a);
+    bool isnan_b  = SFLOAT_ISNAN(b);
+    bool issnan_b = SFLOAT_ISSNAN(b);
+
+    a |= 0x00400000;
+    b |= 0x00400000;
+
+    if (issnan_a | issnan_b)
+        SFLOAT_RAISE(state, SFLOAT_INVALID);
+    if (isnan_a)
+        return (issnan_a & isnan_b) ? b : a;
+    return b;
+}
+
+/*
+ * Takes an abstract value having sign `sign_z', exponent `exp_z', and significand
+ * `sig_z' and returns the appropriate value corresponding to the abstract input.
+ *
+ * The abstract value is simply rounded and packed into the format. If the abstract
+ * input cannot be represented exactly an inexact exception is raised. If the
+ * abstract input is too large, the overflow and inexact exceptions are both raised
+ * and an infinity or maximal finite value is returned. If the abstract value is
+ * too small, the value is rounded to a subnormal and the underflow and inexact
+ * exceptions are only raised if the value cannot be represented exactly with
+ * a subnormal.
+ *
+ * The input significand `sig_z' has it's binary point between bits 30 and 29,
+ * this is seven bits to the left of its usual location. The shifted significand
+ * must be normalized or smaller than this. If it's not normalized then the exponent
+ * `exp_z' must be zero; in that case, the result returned is a subnormal number
+ * which must not require rounding. In the more usual case where the significand
+ * is normalized, the exponent must be one less than the *true* exponent.
+ *
+ * The handling of underflow and overflow is otherwise in alignment with IEC/IEEE.
+ */
+static sfloat_t SFLOAT_PACK_round(sfloat_state_t *state, bool sign_z, int16_t exp_z, uint32_t sig_z) {
+    sfloat_roundingmode_t mode      = state->roundingmode;
+    bool                  even      = !!(mode == SFLOAT_ROUND_NEAREST_EVEN);
+    unsigned char         increment = 0x40;
+    unsigned char         bits      = sig_z & 0x7F;
+
+    if (!even) {
+        if (mode == SFLOAT_ROUND_TO_ZERO)
+            increment = 0;
+        else {
+            increment = 0x7F;
+            if (sign_z) {
+                if (mode == SFLOAT_ROUND_UP)
+                    increment = 0;
+            } else {
+                if (mode == SFLOAT_ROUND_DOWN)
+                    increment = 0;
+            }
+        }
+    }
+
+    if (0xFD <= (uint16_t)exp_z) {
+        if ((0xFD < exp_z) || ((exp_z == 0xFD) && ((int32_t)(sig_z + increment) < 0))) {
+            SFLOAT_RAISE(state, SFLOAT_OVERFLOW | SFLOAT_INEXACT);
+            return SFLOAT_PACK(sign_z, 0xFF, 0) - (increment == 0);
+        }
+        if (exp_z < 0) {
+            /* Check for underflow */
+            bool tiny = (state->tiny == SFLOAT_TBEFORE) || (exp_z < -1) || (sig_z + increment < 0x80000000);
+            SFLOAT_SHIFT(32, sig_z, -exp_z, &sig_z);
+            exp_z = 0;
+            bits = sig_z & 0x7F;
+            if (tiny && bits)
+                SFLOAT_RAISE(state, SFLOAT_UNDERFLOW);
+        }
+    }
+    if (bits)
+        SFLOAT_RAISE(state, SFLOAT_INEXACT);
+    sig_z = (sig_z + increment) >> 7;
+    sig_z &= ~(((bits ^ 0x40) == 0) & even);
+    if (sig_z == 0)
+        exp_z = 0;
+    return SFLOAT_PACK(sign_z, exp_z, sig_z);
+}
+
+/*
+ * Takes an abstract value having sign `sign_z', exponent `exp_z' and significand
+ * `sig_z' and returns the appropriate value corresponding to the abstract input.
+ * This function is exactly like `PACK_round' except the significand does not have
+ * to be normalized.
+ *
+ * Bit 31 of the significand must be zero and the exponent must be one less than
+ * the *true* exponent.
+ */
+static sfloat_t SFLOAT_PACK_normal(sfloat_state_t *state, bool sign_z, int16_t exp_z, uint32_t sig_z) {
+    unsigned char c = SFLOAT_CLZ(sig_z, 1);
+    return SFLOAT_PACK_round(state, sign_z, exp_z - c, sig_z << c);
+}
+
+/*
+ * Returns the result of adding the absolute values of `a' and `b'. The sign
+ * `sign_z' is ignored if the result is a NaN.
+ */
+static sfloat_t sfloat_add_impl(sfloat_state_t *state, sfloat_t a, sfloat_t b, bool sign_z) {
+    int16_t  exp_a = SFLOAT_EXTRACT_EXP(a);
+    int16_t  exp_b = SFLOAT_EXTRACT_EXP(b);
+    int16_t  exp_z = 0;
+    int16_t  exp_d = exp_a - exp_b;
+    uint32_t sig_a = SFLOAT_EXTRACT_FRAC(a) << 6;
+    uint32_t sig_b = SFLOAT_EXTRACT_FRAC(b) << 6;
+    uint32_t sig_z = 0;
+
+    if (0 < exp_d) {
+        if (exp_a == 0xFF)
+            return sig_a ? sfloat_propagate_nan(state, a, b) : a;
+        if (exp_b == 0)
+            --exp_d;
+        else
+            sig_b |= 0x20000000;
+        SFLOAT_SHIFT(32, sig_b, exp_d, &sig_b);
+        exp_z = exp_a;
+    } else if (exp_d < 0) {
+        if (exp_b == 0xFF)
+            return sig_b ? sfloat_propagate_nan(state, a, b) : SFLOAT_PACK(sign_z, 0xFF, 0);
+        if (exp_a == 0)
+            ++exp_d;
+        else
+            sig_a |= 0x20000000;
+        SFLOAT_SHIFT(32, sig_a, -exp_d, &sig_a);
+        exp_z = exp_b;
+    } else {
+        if (exp_a == 0xFF)
+            return (sig_a | sig_b) ? sfloat_propagate_nan(state, a, b) : a;
+        if (exp_a == 0)
+            return SFLOAT_PACK(sign_z, 0, (sig_a + sig_b) >> 6);
+        sig_z = 0x40000000 + sig_a + sig_b;
+        exp_z = exp_a;
+        goto end;
+    }
+    sig_a |= 0x20000000;
+    sig_z = (sig_a + sig_b) << 1;
+    --exp_z;
+    if ((int32_t)sig_z < 0) {
+        sig_z = sig_a + sig_b;
+        ++exp_z;
+    }
+end:
+    return SFLOAT_PACK_round(state, sign_z, exp_z, sig_z);
+}
+
+/*
+ * Returns the result of subtracting the absolute values of `a' and `b'. If the
+ * sign `sign_z' is one, the difference is negated before being returned. The
+ * sign is ignored if the result is a NaN.
+ */
+static sfloat_t sfloat_sub_impl(sfloat_state_t *state, sfloat_t a, sfloat_t b, bool sign_z) {
+    int16_t  exp_a = SFLOAT_EXTRACT_EXP(a);
+    int16_t  exp_b = SFLOAT_EXTRACT_EXP(b);
+    int16_t  exp_z = 0;
+    int16_t  exp_d = exp_a - exp_b;
+    uint32_t sig_a = SFLOAT_EXTRACT_FRAC(a) << 7;
+    uint32_t sig_b = SFLOAT_EXTRACT_FRAC(b) << 7;
+    uint32_t sig_z = 0;
+
+    if (0 < exp_d) goto exp_greater_a;
+    if (exp_d < 0) goto exp_greater_b;
+
+    if (exp_a == 0xFF) {
+        if (sig_a | sig_b)
+            return sfloat_propagate_nan(state, a, b);
+        SFLOAT_RAISE(state, SFLOAT_INVALID);
+        return SFLOAT_NAN;
+    }
+
+    if (exp_a == 0)
+        exp_a = exp_b = 1;
+
+    if (sig_b < sig_a) goto greater_a;
+    if (sig_a < sig_b) goto greater_b;
+
+    return SFLOAT_PACK(state->roundingmode == SFLOAT_ROUND_DOWN, 0, 0);
+
+exp_greater_b:
+    if (exp_b == 0xFF)
+        return (sig_b) ? sfloat_propagate_nan(state, a, b) : SFLOAT_PACK(sign_z ^ 1, 0xFF, 0);
+    if (exp_a == 0)
+        ++exp_d;
+    else
+        sig_a |= 0x40000000;
+    SFLOAT_SHIFT(32, sig_a, -exp_d, &sig_a);
+    sig_b |= 0x40000000;
+greater_b:
+    sig_z = sig_b - sig_a;
+    exp_z = exp_b;
+    sign_z ^= 1;
+    goto end;
+
+exp_greater_a:
+    if (exp_a == 0xFF)
+        return (sig_a) ? sfloat_propagate_nan(state, a, b) : a;
+    if (exp_b == 0)
+        --exp_d;
+    else
+        sig_b |= 0x40000000;
+    SFLOAT_SHIFT(32, sig_b, exp_d, &sig_b);
+    sig_a |= 0x40000000;
+greater_a:
+    sig_z = sig_a - sig_b;
+    exp_z = exp_a;
+
+end:
+    --exp_z;
+    return SFLOAT_PACK_normal(state, sign_z, exp_z, sig_z);
+}
+
+static GMQCC_INLINE sfloat_t sfloat_add(sfloat_state_t *state, sfloat_t a, sfloat_t b) {
+    bool sign_a = SFLOAT_EXTRACT_SIGN(a);
+    bool sign_b = SFLOAT_EXTRACT_SIGN(b);
+    return (sign_a == sign_b) ? sfloat_add_impl(state, a, b, sign_a)
+                              : sfloat_sub_impl(state, a, b, sign_a);
+}
+
+static GMQCC_INLINE sfloat_t sfloat_sub(sfloat_state_t *state, sfloat_t a, sfloat_t b) {
+    bool sign_a = SFLOAT_EXTRACT_SIGN(a);
+    bool sign_b = SFLOAT_EXTRACT_SIGN(b);
+    return (sign_a == sign_b) ? sfloat_sub_impl(state, a, b, sign_a)
+                              : sfloat_add_impl(state, a, b, sign_a);
+}
+
+static sfloat_t sfloat_mul(sfloat_state_t *state, sfloat_t a, sfloat_t b) {
+    int16_t  exp_a   = SFLOAT_EXTRACT_EXP(a);
+    int16_t  exp_b   = SFLOAT_EXTRACT_EXP(b);
+    int16_t  exp_z   = 0;
+    uint32_t sig_a   = SFLOAT_EXTRACT_FRAC(a);
+    uint32_t sig_b   = SFLOAT_EXTRACT_FRAC(b);
+    uint32_t sig_z   = 0;
+    uint64_t sig_z64 = 0;
+    bool     sign_a  = SFLOAT_EXTRACT_SIGN(a);
+    bool     sign_b  = SFLOAT_EXTRACT_SIGN(b);
+    bool     sign_z  = sign_a ^ sign_b;
+
+    if (exp_a == 0xFF) {
+        if (sig_a || ((exp_b == 0xFF) && sig_b))
+            return sfloat_propagate_nan(state, a, b);
+        if ((exp_b | sig_b) == 0) {
+            SFLOAT_RAISE(state, SFLOAT_INVALID);
+            return SFLOAT_NAN;
+        }
+        return SFLOAT_PACK(sign_z, 0xFF, 0);
+    }
+    if (exp_b == 0xFF) {
+        if (sig_b)
+            return sfloat_propagate_nan(state, a, b);
+        if ((exp_a | sig_a) == 0) {
+            SFLOAT_RAISE(state, SFLOAT_INVALID);
+            return SFLOAT_NAN;
+        }
+        return SFLOAT_PACK(sign_z, 0xFF, 0);
+    }
+    if (exp_a == 0) {
+        if (sig_a == 0)
+            return SFLOAT_PACK(sign_z, 0, 0);
+        SFLOAT_SUBNORMALIZE(sig_a, &exp_a, &sig_a);
+    }
+    if (exp_b == 0) {
+        if (sig_b == 0)
+            return SFLOAT_PACK(sign_z, 0, 0);
+        SFLOAT_SUBNORMALIZE(sig_b, &exp_b, &sig_b);
+    }
+    exp_z = exp_a + exp_b - 0x7F;
+    sig_a = (sig_a | 0x00800000) << 7;
+    sig_b = (sig_b | 0x00800000) << 8;
+    SFLOAT_SHIFT(64, ((uint64_t)sig_a) * sig_b, 32, &sig_z64);
+    sig_z = sig_z64;
+    if (0 <= (int32_t)(sig_z << 1)) {
+        sig_z <<= 1;
+        --exp_z;
+    }
+    return SFLOAT_PACK_round(state, sign_z, exp_z, sig_z);
+}
+
+static sfloat_t sfloat_div(sfloat_state_t *state, sfloat_t a, sfloat_t b) {
+    int16_t  exp_a   = SFLOAT_EXTRACT_EXP(a);
+    int16_t  exp_b   = SFLOAT_EXTRACT_EXP(b);
+    int16_t  exp_z   = 0;
+    uint32_t sig_a   = SFLOAT_EXTRACT_FRAC(a);
+    uint32_t sig_b   = SFLOAT_EXTRACT_FRAC(b);
+    uint32_t sig_z   = 0;
+    bool     sign_a  = SFLOAT_EXTRACT_SIGN(a);
+    bool     sign_b  = SFLOAT_EXTRACT_SIGN(b);
+    bool     sign_z  = sign_a ^ sign_b;
+
+    if (exp_a == 0xFF) {
+        if (sig_a)
+            return sfloat_propagate_nan(state, a, b);
+        if (exp_b == 0xFF) {
+            if (sig_b)
+                return sfloat_propagate_nan(state, a, b);
+            SFLOAT_RAISE(state, SFLOAT_INVALID);
+            return SFLOAT_NAN;
+        }
+        return SFLOAT_PACK(sign_z, 0xFF, 0);
+    }
+    if (exp_b == 0xFF)
+        return (sig_b) ? sfloat_propagate_nan(state, a, b) : SFLOAT_PACK(sign_z, 0, 0);
+    if (exp_b == 0) {
+        if (sig_b == 0) {
+            if ((exp_a | sig_a) == 0) {
+                SFLOAT_RAISE(state, SFLOAT_INVALID);
+                return SFLOAT_NAN;
+            }
+            SFLOAT_RAISE(state, SFLOAT_DIVBYZERO);
+            return SFLOAT_PACK(sign_z, 0xFF, 0);
+        }
+        SFLOAT_SUBNORMALIZE(sig_b, &exp_b, &sig_b);
+    }
+    if (exp_a == 0) {
+        if (sig_a == 0)
+            return SFLOAT_PACK(sign_z, 0, 0);
+        SFLOAT_SUBNORMALIZE(sig_a, &exp_a, &sig_a);
+    }
+    exp_z = exp_a - exp_b + 0x7D;
+    sig_a = (sig_a | 0x00800000) << 7;
+    sig_b = (sig_b | 0x00800000) << 8;
+    if (sig_b <= (sig_a + sig_a)) {
+        sig_a >>= 1;
+        ++exp_z;
+    }
+    sig_z = (((uint64_t)sig_a) << 32) / sig_b;
+    if ((sig_z & 0x3F) == 0)
+        sig_z |= ((uint64_t)sig_b * sig_z != ((uint64_t)sig_a) << 32);
+    return SFLOAT_PACK_round(state, sign_z, exp_z, sig_z);
+}
+
+static sfloat_t sfloat_neg(sfloat_state_t *state, sfloat_t a) {
+    sfloat_cast_t neg;
+    neg.f = -1;
+    return sfloat_mul(state, a, neg.s);
+}
+
+static GMQCC_INLINE void sfloat_check(lex_ctx_t ctx, sfloat_state_t *state, const char *vec) {
+    /* Exception comes from vector component */
+    if (vec) {
+        if (state->exceptionflags & SFLOAT_DIVBYZERO)
+            compile_error(ctx, "division by zero in `%s' component", vec);
+        if (state->exceptionflags & SFLOAT_INVALID)
+            compile_error(ctx, "undefined (inf) in `%s' component", vec);
+        if (state->exceptionflags & SFLOAT_OVERFLOW)
+            compile_error(ctx, "arithmetic overflow in `%s' component", vec);
+        if (state->exceptionflags & SFLOAT_UNDERFLOW)
+            compile_error(ctx, "arithmetic underflow in `%s' component", vec);
+            return;
+    }
+    if (state->exceptionflags & SFLOAT_DIVBYZERO)
+        compile_error(ctx, "division by zero");
+    if (state->exceptionflags & SFLOAT_INVALID)
+        compile_error(ctx, "undefined (inf)");
+    if (state->exceptionflags & SFLOAT_OVERFLOW)
+        compile_error(ctx, "arithmetic overflow");
+    if (state->exceptionflags & SFLOAT_UNDERFLOW)
+        compile_error(ctx, "arithmetic underflow");
+}
+
+static GMQCC_INLINE void sfloat_init(sfloat_state_t *state) {
+    state->exceptionflags = SFLOAT_NOEXCEPT;
+    state->roundingmode   = FOLD_ROUNDING;
+    state->tiny           = FOLD_TINYNESS;
+}
+
+/*
+ * There is two stages to constant folding in GMQCC: there is the parse
+ * stage constant folding, where, with the help of the AST, operator
+ * usages can be constant folded. Then there is the constant folding
+ * in the IR for things like eliding if statements, can occur.
+ *
+ * This file is thus, split into two parts.
+ */
+
+#define isfloat(X)      (((X))->m_vtype == TYPE_FLOAT)
+#define isvector(X)     (((X))->m_vtype == TYPE_VECTOR)
+#define isstring(X)     (((X))->m_vtype == TYPE_STRING)
+#define isarray(X)      (((X))->m_vtype == TYPE_ARRAY)
+#define isfloats(X,Y)   (isfloat  (X) && isfloat (Y))
+
+/*
+ * Implementation of basic vector math for vec3_t, for trivial constant
+ * folding.
+ *
+ * TODO: gcc/clang hinting for autovectorization
+ */
+enum vec3_comp_t {
+    VEC_COMP_X = 1 << 0,
+    VEC_COMP_Y = 1 << 1,
+    VEC_COMP_Z = 1 << 2
+};
+
+struct vec3_soft_t {
+    sfloat_cast_t x;
+    sfloat_cast_t y;
+    sfloat_cast_t z;
+};
+
+struct vec3_soft_state_t {
+    vec3_comp_t faults;
+    sfloat_state_t state[3];
+};
+
+static GMQCC_INLINE vec3_soft_t vec3_soft_convert(vec3_t vec) {
+    vec3_soft_t soft;
+    soft.x.f = vec.x;
+    soft.y.f = vec.y;
+    soft.z.f = vec.z;
+    return soft;
+}
+
+static GMQCC_INLINE bool vec3_soft_exception(vec3_soft_state_t *vstate, size_t index) {
+    sfloat_exceptionflags_t flags = vstate->state[index].exceptionflags;
+    if (flags & SFLOAT_DIVBYZERO) return true;
+    if (flags & SFLOAT_INVALID)   return true;
+    if (flags & SFLOAT_OVERFLOW)  return true;
+    if (flags & SFLOAT_UNDERFLOW) return true;
+    return false;
+}
+
+static GMQCC_INLINE void vec3_soft_eval(vec3_soft_state_t *state,
+                                        sfloat_t         (*callback)(sfloat_state_t *, sfloat_t, sfloat_t),
+                                        vec3_t             a,
+                                        vec3_t             b)
+{
+    vec3_soft_t sa = vec3_soft_convert(a);
+    vec3_soft_t sb = vec3_soft_convert(b);
+    callback(&state->state[0], sa.x.s, sb.x.s);
+    if (vec3_soft_exception(state, 0)) state->faults = (vec3_comp_t)(state->faults | VEC_COMP_X);
+    callback(&state->state[1], sa.y.s, sb.y.s);
+    if (vec3_soft_exception(state, 1)) state->faults = (vec3_comp_t)(state->faults | VEC_COMP_Y);
+    callback(&state->state[2], sa.z.s, sb.z.s);
+    if (vec3_soft_exception(state, 2)) state->faults = (vec3_comp_t)(state->faults | VEC_COMP_Z);
+}
+
+static GMQCC_INLINE void vec3_check_except(vec3_t     a,
+                                           vec3_t     b,
+                                           lex_ctx_t  ctx,
+                                           sfloat_t (*callback)(sfloat_state_t *, sfloat_t, sfloat_t))
+{
+    vec3_soft_state_t state;
+
+    if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS))
+        return;
+
+    sfloat_init(&state.state[0]);
+    sfloat_init(&state.state[1]);
+    sfloat_init(&state.state[2]);
+
+    vec3_soft_eval(&state, callback, a, b);
+    if (state.faults & VEC_COMP_X) sfloat_check(ctx, &state.state[0], "x");
+    if (state.faults & VEC_COMP_Y) sfloat_check(ctx, &state.state[1], "y");
+    if (state.faults & VEC_COMP_Z) sfloat_check(ctx, &state.state[2], "z");
+}
+
+static GMQCC_INLINE vec3_t vec3_add(lex_ctx_t ctx, vec3_t a, vec3_t b) {
+    vec3_t out;
+    vec3_check_except(a, b, ctx, &sfloat_add);
+    out.x = a.x + b.x;
+    out.y = a.y + b.y;
+    out.z = a.z + b.z;
+    return out;
+}
+
+static GMQCC_INLINE vec3_t vec3_sub(lex_ctx_t ctx, vec3_t a, vec3_t b) {
+    vec3_t out;
+    vec3_check_except(a, b, ctx, &sfloat_sub);
+    out.x = a.x - b.x;
+    out.y = a.y - b.y;
+    out.z = a.z - b.z;
+    return out;
+}
+
+static GMQCC_INLINE vec3_t vec3_neg(lex_ctx_t ctx, vec3_t a) {
+    vec3_t         out;
+    sfloat_cast_t  v[3];
+    sfloat_state_t s[3];
+
+    if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS))
+        goto end;
+
+    v[0].f = a.x;
+    v[1].f = a.y;
+    v[2].f = a.z;
+
+    sfloat_init(&s[0]);
+    sfloat_init(&s[1]);
+    sfloat_init(&s[2]);
+
+    sfloat_neg(&s[0], v[0].s);
+    sfloat_neg(&s[1], v[1].s);
+    sfloat_neg(&s[2], v[2].s);
+
+    sfloat_check(ctx, &s[0], nullptr);
+    sfloat_check(ctx, &s[1], nullptr);
+    sfloat_check(ctx, &s[2], nullptr);
+
+end:
+    out.x = -a.x;
+    out.y = -a.y;
+    out.z = -a.z;
+    return out;
+}
+
+static GMQCC_INLINE vec3_t vec3_or(vec3_t a, vec3_t b) {
+    vec3_t out;
+    out.x = (qcfloat_t)(((qcint_t)a.x) | ((qcint_t)b.x));
+    out.y = (qcfloat_t)(((qcint_t)a.y) | ((qcint_t)b.y));
+    out.z = (qcfloat_t)(((qcint_t)a.z) | ((qcint_t)b.z));
+    return out;
+}
+
+static GMQCC_INLINE vec3_t vec3_orvf(vec3_t a, qcfloat_t b) {
+    vec3_t out;
+    out.x = (qcfloat_t)(((qcint_t)a.x) | ((qcint_t)b));
+    out.y = (qcfloat_t)(((qcint_t)a.y) | ((qcint_t)b));
+    out.z = (qcfloat_t)(((qcint_t)a.z) | ((qcint_t)b));
+    return out;
+}
+
+static GMQCC_INLINE vec3_t vec3_and(vec3_t a, vec3_t b) {
+    vec3_t out;
+    out.x = (qcfloat_t)(((qcint_t)a.x) & ((qcint_t)b.x));
+    out.y = (qcfloat_t)(((qcint_t)a.y) & ((qcint_t)b.y));
+    out.z = (qcfloat_t)(((qcint_t)a.z) & ((qcint_t)b.z));
+    return out;
+}
+
+static GMQCC_INLINE vec3_t vec3_andvf(vec3_t a, qcfloat_t b) {
+    vec3_t out;
+    out.x = (qcfloat_t)(((qcint_t)a.x) & ((qcint_t)b));
+    out.y = (qcfloat_t)(((qcint_t)a.y) & ((qcint_t)b));
+    out.z = (qcfloat_t)(((qcint_t)a.z) & ((qcint_t)b));
+    return out;
+}
+
+static GMQCC_INLINE vec3_t vec3_xor(vec3_t a, vec3_t b) {
+    vec3_t out;
+    out.x = (qcfloat_t)(((qcint_t)a.x) ^ ((qcint_t)b.x));
+    out.y = (qcfloat_t)(((qcint_t)a.y) ^ ((qcint_t)b.y));
+    out.z = (qcfloat_t)(((qcint_t)a.z) ^ ((qcint_t)b.z));
+    return out;
+}
+
+static GMQCC_INLINE vec3_t vec3_xorvf(vec3_t a, qcfloat_t b) {
+    vec3_t out;
+    out.x = (qcfloat_t)(((qcint_t)a.x) ^ ((qcint_t)b));
+    out.y = (qcfloat_t)(((qcint_t)a.y) ^ ((qcint_t)b));
+    out.z = (qcfloat_t)(((qcint_t)a.z) ^ ((qcint_t)b));
+    return out;
+}
+
+static GMQCC_INLINE vec3_t vec3_not(vec3_t a) {
+    vec3_t out;
+    out.x = -1-a.x;
+    out.y = -1-a.y;
+    out.z = -1-a.z;
+    return out;
+}
+
+static GMQCC_INLINE qcfloat_t vec3_mulvv(lex_ctx_t ctx, vec3_t a, vec3_t b) {
+    vec3_soft_t    sa;
+    vec3_soft_t    sb;
+    sfloat_state_t s[5];
+    sfloat_t       r[5];
+
+    if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS))
+        goto end;
+
+    sa = vec3_soft_convert(a);
+    sb = vec3_soft_convert(b);
+
+    sfloat_init(&s[0]);
+    sfloat_init(&s[1]);
+    sfloat_init(&s[2]);
+    sfloat_init(&s[3]);
+    sfloat_init(&s[4]);
+
+    r[0] = sfloat_mul(&s[0], sa.x.s, sb.x.s);
+    r[1] = sfloat_mul(&s[1], sa.y.s, sb.y.s);
+    r[2] = sfloat_mul(&s[2], sa.z.s, sb.z.s);
+    r[3] = sfloat_add(&s[3], r[0],   r[1]);
+    r[4] = sfloat_add(&s[4], r[3],   r[2]);
+
+    sfloat_check(ctx, &s[0], nullptr);
+    sfloat_check(ctx, &s[1], nullptr);
+    sfloat_check(ctx, &s[2], nullptr);
+    sfloat_check(ctx, &s[3], nullptr);
+    sfloat_check(ctx, &s[4], nullptr);
+
+end:
+    return (a.x * b.x + a.y * b.y + a.z * b.z);
+}
+
+static GMQCC_INLINE vec3_t vec3_mulvf(lex_ctx_t ctx, vec3_t a, qcfloat_t b) {
+    vec3_t         out;
+    vec3_soft_t    sa;
+    sfloat_cast_t  sb;
+    sfloat_state_t s[3];
+
+    if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS))
+        goto end;
+
+    sa   = vec3_soft_convert(a);
+    sb.f = b;
+    sfloat_init(&s[0]);
+    sfloat_init(&s[1]);
+    sfloat_init(&s[2]);
+
+    sfloat_mul(&s[0], sa.x.s, sb.s);
+    sfloat_mul(&s[1], sa.y.s, sb.s);
+    sfloat_mul(&s[2], sa.z.s, sb.s);
+
+    sfloat_check(ctx, &s[0], "x");
+    sfloat_check(ctx, &s[1], "y");
+    sfloat_check(ctx, &s[2], "z");
+
+end:
+    out.x = a.x * b;
+    out.y = a.y * b;
+    out.z = a.z * b;
+    return out;
+}
+
+static GMQCC_INLINE bool vec3_cmp(vec3_t a, vec3_t b) {
+    return a.x == b.x &&
+           a.y == b.y &&
+           a.z == b.z;
+}
+
+static GMQCC_INLINE vec3_t vec3_create(float x, float y, float z) {
+    vec3_t out;
+    out.x = x;
+    out.y = y;
+    out.z = z;
+    return out;
+}
+
+static GMQCC_INLINE qcfloat_t vec3_notf(vec3_t a) {
+    return (!a.x && !a.y && !a.z);
+}
+
+static GMQCC_INLINE bool vec3_pbool(vec3_t a) {
+    return (a.x || a.y || a.z);
+}
+
+static GMQCC_INLINE vec3_t vec3_cross(lex_ctx_t ctx, vec3_t a, vec3_t b) {
+    vec3_t         out;
+    vec3_soft_t    sa;
+    vec3_soft_t    sb;
+    sfloat_t       r[9];
+    sfloat_state_t s[9];
+
+    if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS))
+        goto end;
+
+    sa = vec3_soft_convert(a);
+    sb = vec3_soft_convert(b);
+
+    sfloat_init(&s[0]);
+    sfloat_init(&s[1]);
+    sfloat_init(&s[2]);
+    sfloat_init(&s[3]);
+    sfloat_init(&s[4]);
+    sfloat_init(&s[5]);
+    sfloat_init(&s[6]);
+    sfloat_init(&s[7]);
+    sfloat_init(&s[8]);
+
+    r[0] = sfloat_mul(&s[0], sa.y.s, sb.z.s);
+    r[1] = sfloat_mul(&s[1], sa.z.s, sb.y.s);
+    r[2] = sfloat_mul(&s[2], sa.z.s, sb.x.s);
+    r[3] = sfloat_mul(&s[3], sa.x.s, sb.z.s);
+    r[4] = sfloat_mul(&s[4], sa.x.s, sb.y.s);
+    r[5] = sfloat_mul(&s[5], sa.y.s, sb.x.s);
+    r[6] = sfloat_sub(&s[6], r[0],   r[1]);
+    r[7] = sfloat_sub(&s[7], r[2],   r[3]);
+    r[8] = sfloat_sub(&s[8], r[4],   r[5]);
+
+    sfloat_check(ctx, &s[0], nullptr);
+    sfloat_check(ctx, &s[1], nullptr);
+    sfloat_check(ctx, &s[2], nullptr);
+    sfloat_check(ctx, &s[3], nullptr);
+    sfloat_check(ctx, &s[4], nullptr);
+    sfloat_check(ctx, &s[5], nullptr);
+    sfloat_check(ctx, &s[6], "x");
+    sfloat_check(ctx, &s[7], "y");
+    sfloat_check(ctx, &s[8], "z");
+
+end:
+    out.x = a.y * b.z - a.z * b.y;
+    out.y = a.z * b.x - a.x * b.z;
+    out.z = a.x * b.y - a.y * b.x;
+    return out;
+}
+
+qcfloat_t fold::immvalue_float(ast_value *value) {
+    return value->m_constval.vfloat;
+}
+
+vec3_t fold::immvalue_vector(ast_value *value) {
+    return value->m_constval.vvec;
+}
+
+const char *fold::immvalue_string(ast_value *value) {
+    return value->m_constval.vstring;
+}
+
+lex_ctx_t fold::ctx() {
+    lex_ctx_t ctx;
+    if (m_parser->lex)
+        return parser_ctx(m_parser);
+    memset(&ctx, 0, sizeof(ctx));
+    return ctx;
+}
+
+bool fold::immediate_true(ast_value *v) {
+    switch (v->m_vtype) {
+        case TYPE_FLOAT:
+            return !!v->m_constval.vfloat;
+        case TYPE_INTEGER:
+            return !!v->m_constval.vint;
+        case TYPE_VECTOR:
+            if (OPTS_FLAG(CORRECT_LOGIC))
+                return vec3_pbool(v->m_constval.vvec);
+            return !!(v->m_constval.vvec.x);
+        case TYPE_STRING:
+            if (!v->m_constval.vstring)
+                return false;
+            if (OPTS_FLAG(TRUE_EMPTY_STRINGS))
+                return true;
+            return !!v->m_constval.vstring[0];
+        default:
+            compile_error(ctx(), "internal error: fold_immediate_true on invalid type");
+            break;
+    }
+    return !!v->m_constval.vfunc;
+}
+
+/* Handy macros to determine if an ast_value can be constant folded. */
+#define fold_can_1(X)  \
+    (ast_istype(((X)), ast_value) && (X)->m_hasvalue && ((X)->m_cvq == CV_CONST) && \
+                ((X))->m_vtype != TYPE_FUNCTION)
+
+#define fold_can_2(X, Y) (fold_can_1(X) && fold_can_1(Y))
+
+fold::fold()
+    : m_parser(nullptr)
+{
+}
+
+fold::fold(parser_t *parser)
+    : m_parser(parser)
+{
+    m_imm_string_untranslate = util_htnew(FOLD_STRING_UNTRANSLATE_HTSIZE);
+    m_imm_string_dotranslate = util_htnew(FOLD_STRING_DOTRANSLATE_HTSIZE);
+
+    constgen_float(0.0f, false);
+    constgen_float(1.0f, false);
+    constgen_float(-1.0f, false);
+    constgen_float(2.0f, false);
+
+    constgen_vector(vec3_create(0.0f, 0.0f, 0.0f));
+    constgen_vector(vec3_create(-1.0f, -1.0f, -1.0f));
+}
+
+bool fold::generate(ir_builder *ir) {
+    // generate globals for immediate folded values
+    ast_value *cur;
+    for (auto &it : m_imm_float)
+        if (!(cur = it)->generateGlobal(ir, false)) goto err;
+    for (auto &it : m_imm_vector)
+        if (!(cur = it)->generateGlobal(ir, false)) goto err;
+    for (auto &it : m_imm_string)
+        if (!(cur = it)->generateGlobal(ir, false)) goto err;
+    return true;
+err:
+    con_out("failed to generate global %s\n", cur->m_name.c_str());
+    delete ir;
+    return false;
+}
+
+fold::~fold() {
+// TODO: parser lifetime so this is called when it should be
+#if 0
+    for (auto &it : m_imm_float) ast_delete(it);
+    for (auto &it : m_imm_vector) ast_delete(it);
+    for (auto &it : m_imm_string) ast_delete(it);
+
+    util_htdel(m_imm_string_untranslate);
+    util_htdel(m_imm_string_dotranslate);
+#endif
+}
+
+ast_expression *fold::constgen_float(qcfloat_t value, bool inexact) {
+    for (auto &it : m_imm_float)
+        if (!memcmp(&it->m_constval.vfloat, &value, sizeof(qcfloat_t)))
+            return it;
+
+    ast_value *out  = new ast_value(ctx(), "#IMMEDIATE", TYPE_FLOAT);
+    out->m_cvq = CV_CONST;
+    out->m_hasvalue = true;
+    out->m_inexact = inexact;
+    out->m_constval.vfloat = value;
+
+    m_imm_float.push_back(out);
+
+    return out;
+}
+
+ast_expression *fold::constgen_vector(vec3_t value) {
+    for (auto &it : m_imm_vector)
+        if (vec3_cmp(it->m_constval.vvec, value))
+            return it;
+
+    ast_value *out = new ast_value(ctx(), "#IMMEDIATE", TYPE_VECTOR);
+    out->m_cvq = CV_CONST;
+    out->m_hasvalue = true;
+    out->m_constval.vvec = value;
+
+    m_imm_vector.push_back(out);
+
+    return out;
+}
+
+ast_expression *fold::constgen_string(const char *str, bool translate) {
+    hash_table_t *table = translate ? m_imm_string_untranslate : m_imm_string_dotranslate;
+    ast_value *out = nullptr;
+    size_t hash = util_hthash(table, str);
+
+    if ((out = (ast_value*)util_htgeth(table, str, hash)))
+        return out;
+
+    if (translate) {
+        char name[32];
+        util_snprintf(name, sizeof(name), "dotranslate_%zu", m_parser->translated++);
+        out = new ast_value(ctx(), name, TYPE_STRING);
+        out->m_flags |= AST_FLAG_INCLUDE_DEF; /* def needs to be included for translatables */
+    } else {
+        out = new ast_value(ctx(), "#IMMEDIATE", TYPE_STRING);
+    }
+
+    out->m_cvq = CV_CONST;
+    out->m_hasvalue = true;
+    out->m_isimm = true;
+    out->m_constval.vstring = parser_strdup(str);
+
+    m_imm_string.push_back(out);
+    util_htseth(table, str, hash, out);
+
+    return out;
+}
+
+ast_expression *fold::constgen_string(const std::string &str, bool translate) {
+  return constgen_string(str.c_str(), translate);
+}
+
+typedef union {
+    void (*callback)(void);
+    sfloat_t (*binary)(sfloat_state_t *, sfloat_t, sfloat_t);
+    sfloat_t (*unary)(sfloat_state_t *, sfloat_t);
+} float_check_callback_t;
+
+bool fold::check_except_float_impl(void (*callback)(void), ast_value *a, ast_value *b) {
+    float_check_callback_t call;
+    sfloat_state_t s;
+    sfloat_cast_t ca;
+
+    if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS) && !OPTS_WARN(WARN_INEXACT_COMPARES))
+        return false;
+
+    call.callback = callback;
+    sfloat_init(&s);
+    ca.f = immvalue_float(a);
+    if (b) {
+        sfloat_cast_t cb;
+        cb.f = immvalue_float(b);
+        call.binary(&s, ca.s, cb.s);
+    } else {
+        call.unary(&s, ca.s);
+    }
+
+    if (s.exceptionflags == 0)
+        return false;
+
+    if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS))
+        goto inexact_possible;
+
+    sfloat_check(ctx(), &s, nullptr);
+
+inexact_possible:
+    return s.exceptionflags & SFLOAT_INEXACT;
+}
+
+#define check_except_float(CALLBACK, A, B) \
+    check_except_float_impl(((void (*)(void))(CALLBACK)), (A), (B))
+
+bool fold::check_inexact_float(ast_value *a, ast_value *b) {
+    if (!OPTS_WARN(WARN_INEXACT_COMPARES))
+        return false;
+    if (!a->m_inexact && !b->m_inexact)
+        return false;
+    return compile_warning(ctx(), WARN_INEXACT_COMPARES, "inexact value in comparison");
+}
+
+ast_expression *fold::op_mul_vec(vec3_t vec, ast_value *sel, const char *set) {
+    qcfloat_t x = (&vec.x)[set[0]-'x'];
+    qcfloat_t y = (&vec.x)[set[1]-'x'];
+    qcfloat_t z = (&vec.x)[set[2]-'x'];
+    if (!y && !z) {
+        ast_expression *out;
+        ++opts_optimizationcount[OPTIM_VECTOR_COMPONENTS];
+        out = ast_member::make(ctx(), sel, set[0]-'x', "");
+        out->m_keep_node = false;
+        ((ast_member*)out)->m_rvalue = true;
+        if (x != -1.0f)
+            return new ast_binary(ctx(), INSTR_MUL_F, constgen_float(x, false), out);
+    }
+    return nullptr;
+}
+
+
+ast_expression *fold::op_neg(ast_value *a) {
+    if (isfloat(a)) {
+        if (fold_can_1(a)) {
+            /* Negation can produce inexact as well */
+            bool inexact = check_except_float(&sfloat_neg, a, nullptr);
+            return constgen_float(-immvalue_float(a), inexact);
+        }
+    } else if (isvector(a)) {
+        if (fold_can_1(a))
+            return constgen_vector(vec3_neg(ctx(), immvalue_vector(a)));
+    }
+    return nullptr;
+}
+
+ast_expression *fold::op_not(ast_value *a) {
+    if (isfloat(a)) {
+        if (fold_can_1(a))
+            return constgen_float(!immvalue_float(a), false);
+    } else if (isvector(a)) {
+        if (fold_can_1(a))
+            return constgen_float(vec3_notf(immvalue_vector(a)), false);
+    } else if (isstring(a)) {
+        if (fold_can_1(a)) {
+            if (OPTS_FLAG(TRUE_EMPTY_STRINGS))
+                return constgen_float(!immvalue_string(a), false);
+            else
+                return constgen_float(!immvalue_string(a) || !*immvalue_string(a), false);
+        }
+    }
+    return nullptr;
+}
+
+ast_expression *fold::op_add(ast_value *a, ast_value *b) {
+    if (isfloat(a)) {
+        if (fold_can_2(a, b)) {
+            bool inexact = check_except_float(&sfloat_add, a, b);
+            return constgen_float(immvalue_float(a) + immvalue_float(b), inexact);
+        }
+    } else if (isvector(a)) {
+        if (fold_can_2(a, b))
+            return constgen_vector(vec3_add(ctx(),
+                                                       immvalue_vector(a),
+                                                       immvalue_vector(b)));
+    }
+    return nullptr;
+}
+
+ast_expression *fold::op_sub(ast_value *a, ast_value *b) {
+    if (isfloat(a)) {
+        if (fold_can_2(a, b)) {
+            bool inexact = check_except_float(&sfloat_sub, a, b);
+            return constgen_float(immvalue_float(a) - immvalue_float(b), inexact);
+        }
+    } else if (isvector(a)) {
+        if (fold_can_2(a, b))
+            return constgen_vector(vec3_sub(ctx(),
+                                                       immvalue_vector(a),
+                                                       immvalue_vector(b)));
+    }
+    return nullptr;
+}
+
+ast_expression *fold::op_mul(ast_value *a, ast_value *b) {
+    if (isfloat(a)) {
+        if (isvector(b)) {
+            if (fold_can_2(a, b))
+                return constgen_vector(vec3_mulvf(ctx(), immvalue_vector(b), immvalue_float(a)));
+        } else {
+            if (fold_can_2(a, b)) {
+                bool inexact = check_except_float(&sfloat_mul, a, b);
+                return constgen_float(immvalue_float(a) * immvalue_float(b), inexact);
+            }
+        }
+    } else if (isvector(a)) {
+        if (isfloat(b)) {
+            if (fold_can_2(a, b))
+                return constgen_vector(vec3_mulvf(ctx(), immvalue_vector(a), immvalue_float(b)));
+        } else {
+            if (fold_can_2(a, b)) {
+                return constgen_float(vec3_mulvv(ctx(), immvalue_vector(a), immvalue_vector(b)), false);
+            } else if (OPTS_OPTIMIZATION(OPTIM_VECTOR_COMPONENTS) && fold_can_1(a)) {
+                ast_expression *out;
+                if ((out = op_mul_vec(immvalue_vector(a), b, "xyz"))) return out;
+                if ((out = op_mul_vec(immvalue_vector(a), b, "yxz"))) return out;
+                if ((out = op_mul_vec(immvalue_vector(a), b, "zxy"))) return out;
+            } else if (OPTS_OPTIMIZATION(OPTIM_VECTOR_COMPONENTS) && fold_can_1(b)) {
+                ast_expression *out;
+                if ((out = op_mul_vec(immvalue_vector(b), a, "xyz"))) return out;
+                if ((out = op_mul_vec(immvalue_vector(b), a, "yxz"))) return out;
+                if ((out = op_mul_vec(immvalue_vector(b), a, "zxy"))) return out;
+            }
+        }
+    }
+    return nullptr;
+}
+
+ast_expression *fold::op_div(ast_value *a, ast_value *b) {
+    if (isfloat(a)) {
+        if (fold_can_2(a, b)) {
+            bool inexact = check_except_float(&sfloat_div, a, b);
+            return constgen_float(immvalue_float(a) / immvalue_float(b), inexact);
+        } else if (fold_can_1(b)) {
+            return new ast_binary(
+                ctx(),
+                INSTR_MUL_F,
+                a,
+                constgen_float(1.0f / immvalue_float(b), false)
+            );
+        }
+    } else if (isvector(a)) {
+        if (fold_can_2(a, b)) {
+            return constgen_vector(vec3_mulvf(ctx(), immvalue_vector(a), 1.0f / immvalue_float(b)));
+        } else {
+            return new ast_binary(
+                ctx(),
+                INSTR_MUL_VF,
+                a,
+                (fold_can_1(b))
+                    ? constgen_float(1.0f / immvalue_float(b), false)
+                    : new ast_binary(ctx(),
+                                     INSTR_DIV_F,
+                                     m_imm_float[1],
+                                     b
+                    )
+            );
+        }
+    }
+    return nullptr;
+}
+
+ast_expression *fold::op_mod(ast_value *a, ast_value *b) {
+    return (fold_can_2(a, b))
+                ? constgen_float(fmod(immvalue_float(a), immvalue_float(b)), false)
+                : nullptr;
+}
+
+ast_expression *fold::op_bor(ast_value *a, ast_value *b) {
+    if (isfloat(a)) {
+        if (fold_can_2(a, b))
+            return constgen_float((qcfloat_t)(((qcint_t)immvalue_float(a)) | ((qcint_t)immvalue_float(b))), false);
+    } else {
+        if (isvector(b)) {
+            if (fold_can_2(a, b))
+                return constgen_vector(vec3_or(immvalue_vector(a), immvalue_vector(b)));
+        } else {
+            if (fold_can_2(a, b))
+                return constgen_vector(vec3_orvf(immvalue_vector(a), immvalue_float(b)));
+        }
+    }
+    return nullptr;
+}
+
+ast_expression *fold::op_band(ast_value *a, ast_value *b) {
+    if (isfloat(a)) {
+        if (fold_can_2(a, b))
+            return constgen_float((qcfloat_t)(((qcint_t)immvalue_float(a)) & ((qcint_t)immvalue_float(b))), false);
+    } else {
+        if (isvector(b)) {
+            if (fold_can_2(a, b))
+                return constgen_vector(vec3_and(immvalue_vector(a), immvalue_vector(b)));
+        } else {
+            if (fold_can_2(a, b))
+                return constgen_vector(vec3_andvf(immvalue_vector(a), immvalue_float(b)));
+        }
+    }
+    return nullptr;
+}
+
+ast_expression *fold::op_xor(ast_value *a, ast_value *b) {
+    if (isfloat(a)) {
+        if (fold_can_2(a, b))
+            return constgen_float((qcfloat_t)(((qcint_t)immvalue_float(a)) ^ ((qcint_t)immvalue_float(b))), false);
+    } else {
+        if (fold_can_2(a, b)) {
+            if (isvector(b))
+                return constgen_vector(vec3_xor(immvalue_vector(a), immvalue_vector(b)));
+            else
+                return constgen_vector(vec3_xorvf(immvalue_vector(a), immvalue_float(b)));
+        }
+    }
+    return nullptr;
+}
+
+ast_expression *fold::op_lshift(ast_value *a, ast_value *b) {
+    if (fold_can_2(a, b) && isfloats(a, b))
+        return constgen_float((qcfloat_t)floorf(immvalue_float(a) * powf(2.0f, immvalue_float(b))), false);
+    return nullptr;
+}
+
+ast_expression *fold::op_rshift(ast_value *a, ast_value *b) {
+    if (fold_can_2(a, b) && isfloats(a, b))
+        return constgen_float((qcfloat_t)floorf(immvalue_float(a) / powf(2.0f, immvalue_float(b))), false);
+    return nullptr;
+}
+
+ast_expression *fold::op_andor(ast_value *a, ast_value *b, float expr) {
+    if (fold_can_2(a, b)) {
+        if (OPTS_FLAG(PERL_LOGIC)) {
+            if (expr)
+                return immediate_true(a) ? a : b;
+            else
+                return immediate_true(a) ? b : a;
+        } else {
+            return constgen_float(
+                ((expr) ? (immediate_true(a) || immediate_true(b))
+                        : (immediate_true(a) && immediate_true(b)))
+                            ? 1
+                            : 0,
+                false
+            );
+        }
+    }
+    return nullptr;
+}
+
+ast_expression *fold::op_tern(ast_value *a, ast_value *b, ast_value *c) {
+    if (fold_can_1(a)) {
+        return immediate_true(a)
+                    ? b
+                    : c;
+    }
+    return nullptr;
+}
+
+ast_expression *fold::op_exp(ast_value *a, ast_value *b) {
+    if (fold_can_2(a, b))
+        return constgen_float((qcfloat_t)powf(immvalue_float(a), immvalue_float(b)), false);
+    return nullptr;
+}
+
+ast_expression *fold::op_lteqgt(ast_value *a, ast_value *b) {
+    if (fold_can_2(a,b)) {
+        check_inexact_float(a, b);
+        if (immvalue_float(a) <  immvalue_float(b)) return m_imm_float[2];
+        if (immvalue_float(a) == immvalue_float(b)) return m_imm_float[0];
+        if (immvalue_float(a) >  immvalue_float(b)) return m_imm_float[1];
+    }
+    return nullptr;
+}
+
+ast_expression *fold::op_ltgt(ast_value *a, ast_value *b, bool lt) {
+    if (fold_can_2(a, b)) {
+        check_inexact_float(a, b);
+        return (lt) ? m_imm_float[!!(immvalue_float(a) < immvalue_float(b))]
+                    : m_imm_float[!!(immvalue_float(a) > immvalue_float(b))];
+    }
+    return nullptr;
+}
+
+ast_expression *fold::op_cmp(ast_value *a, ast_value *b, bool ne) {
+    if (fold_can_2(a, b)) {
+        if (isfloat(a) && isfloat(b)) {
+            float la = immvalue_float(a);
+            float lb = immvalue_float(b);
+            check_inexact_float(a, b);
+            return m_imm_float[ne ? la != lb : la == lb];
+        } else if (isvector(a) && isvector(b)) {
+            vec3_t la = immvalue_vector(a);
+            vec3_t lb = immvalue_vector(b);
+            bool compare = vec3_cmp(la, lb);
+            return m_imm_float[ne ? !compare : compare];
+        } else if (isstring(a) && isstring(b)) {
+            bool compare = !strcmp(immvalue_string(a), immvalue_string(b));
+            return m_imm_float[ne ? !compare : compare];
+        }
+    }
+    return nullptr;
+}
+
+ast_expression *fold::op_bnot(ast_value *a) {
+    if (isfloat(a)) {
+        if (fold_can_1(a))
+            return constgen_float(-1-immvalue_float(a), false);
+    } else {
+        if (isvector(a)) {
+            if (fold_can_1(a))
+                return constgen_vector(vec3_not(immvalue_vector(a)));
+        }
+    }
+    return nullptr;
+}
+
+ast_expression *fold::op_cross(ast_value *a, ast_value *b) {
+    if (fold_can_2(a, b))
+        return constgen_vector(vec3_cross(ctx(),
+                                          immvalue_vector(a),
+                                          immvalue_vector(b)));
+    return nullptr;
+}
+
+ast_expression *fold::op_length(ast_value *a) {
+    if (fold_can_1(a) && isstring(a))
+        return constgen_float(strlen(immvalue_string(a)), false);
+    if (isarray(a))
+        return constgen_float(a->m_initlist.size(), false);
+    return nullptr;
+}
+
+ast_expression *fold::op(const oper_info *info, ast_expression **opexprs) {
+    ast_value *a = (ast_value*)opexprs[0];
+    ast_value *b = (ast_value*)opexprs[1];
+    ast_value *c = (ast_value*)opexprs[2];
+    ast_expression *e = nullptr;
+
+    /* can a fold operation be applied to this operator usage? */
+    if (!info->folds)
+        return nullptr;
+
+    switch(info->operands) {
+        case 3: if(!c) return nullptr;
+        case 2: if(!b) return nullptr;
+        case 1:
+        if(!a) {
+            compile_error(ctx(), "internal error: fold_op no operands to fold\n");
+            return nullptr;
+        }
+    }
+
+    #define fold_op_case(ARGS, ARGS_OPID, OP, ARGS_FOLD)    \
+        case opid##ARGS ARGS_OPID:                          \
+            if ((e = op_##OP ARGS_FOLD)) {                  \
+                ++opts_optimizationcount[OPTIM_CONST_FOLD]; \
+            }                                               \
+            return e
+
+    switch(info->id) {
+        fold_op_case(2, ('-', 'P'),      neg,    (a));
+        fold_op_case(2, ('!', 'P'),      not,    (a));
+        fold_op_case(1, ('+'),           add,    (a, b));
+        fold_op_case(1, ('-'),           sub,    (a, b));
+        fold_op_case(1, ('*'),           mul,    (a, b));
+        fold_op_case(1, ('/'),           div,    (a, b));
+        fold_op_case(1, ('%'),           mod,    (a, b));
+        fold_op_case(1, ('|'),           bor,    (a, b));
+        fold_op_case(1, ('&'),           band,   (a, b));
+        fold_op_case(1, ('^'),           xor,    (a, b));
+        fold_op_case(1, ('<'),           ltgt,   (a, b, true));
+        fold_op_case(1, ('>'),           ltgt,   (a, b, false));
+        fold_op_case(2, ('<', '<'),      lshift, (a, b));
+        fold_op_case(2, ('>', '>'),      rshift, (a, b));
+        fold_op_case(2, ('|', '|'),      andor,  (a, b, true));
+        fold_op_case(2, ('&', '&'),      andor,  (a, b, false));
+        fold_op_case(2, ('?', ':'),      tern,   (a, b, c));
+        fold_op_case(2, ('*', '*'),      exp,    (a, b));
+        fold_op_case(3, ('<','=','>'),   lteqgt, (a, b));
+        fold_op_case(2, ('!', '='),      cmp,    (a, b, true));
+        fold_op_case(2, ('=', '='),      cmp,    (a, b, false));
+        fold_op_case(2, ('~', 'P'),      bnot,   (a));
+        fold_op_case(2, ('>', '<'),      cross,  (a, b));
+        fold_op_case(3, ('l', 'e', 'n'), length, (a));
+    }
+    #undef fold_op_case
+    compile_error(ctx(), "internal error: attempted to constant-fold for unsupported operator");
+    return nullptr;
+}
+
+/*
+ * Constant folding for compiler intrinsics, similar approach to operator
+ * folding, primarily: individual functions for each intrinsics to fold,
+ * and a generic selection function.
+ */
+ast_expression *fold::intrinsic_isfinite(ast_value *a) {
+    return constgen_float(isfinite(immvalue_float(a)), false);
+}
+ast_expression *fold::intrinsic_isinf(ast_value *a) {
+    return constgen_float(isinf(immvalue_float(a)), false);
+}
+ast_expression *fold::intrinsic_isnan(ast_value *a) {
+    return constgen_float(isnan(immvalue_float(a)), false);
+}
+ast_expression *fold::intrinsic_isnormal(ast_value *a) {
+    return constgen_float(isnormal(immvalue_float(a)), false);
+}
+ast_expression *fold::intrinsic_signbit(ast_value *a) {
+    return constgen_float(signbit(immvalue_float(a)), false);
+}
+ast_expression *fold::intrinsic_acosh(ast_value *a) {
+    return constgen_float(acoshf(immvalue_float(a)), false);
+}
+ast_expression *fold::intrinsic_asinh(ast_value *a) {
+    return constgen_float(asinhf(immvalue_float(a)), false);
+}
+ast_expression *fold::intrinsic_atanh(ast_value *a) {
+    return constgen_float((float)atanh(immvalue_float(a)), false);
+}
+ast_expression *fold::intrinsic_exp(ast_value *a) {
+    return constgen_float(expf(immvalue_float(a)), false);
+}
+ast_expression *fold::intrinsic_exp2(ast_value *a) {
+    return constgen_float(exp2f(immvalue_float(a)), false);
+}
+ast_expression *fold::intrinsic_expm1(ast_value *a) {
+    return constgen_float(expm1f(immvalue_float(a)), false);
+}
+ast_expression *fold::intrinsic_mod(ast_value *lhs, ast_value *rhs) {
+    return constgen_float(fmodf(immvalue_float(lhs), immvalue_float(rhs)), false);
+}
+ast_expression *fold::intrinsic_pow(ast_value *lhs, ast_value *rhs) {
+    return constgen_float(powf(immvalue_float(lhs), immvalue_float(rhs)), false);
+}
+ast_expression *fold::intrinsic_fabs(ast_value *a) {
+    return constgen_float(fabsf(immvalue_float(a)), false);
+}
+
+ast_expression *fold::intrinsic(const char *intrinsic, ast_expression **arg) {
+    ast_expression *ret = nullptr;
+    ast_value *a = (ast_value*)arg[0];
+    ast_value *b = (ast_value*)arg[1];
+
+    if (!strcmp(intrinsic, "isfinite")) ret = intrinsic_isfinite(a);
+    if (!strcmp(intrinsic, "isinf"))    ret = intrinsic_isinf(a);
+    if (!strcmp(intrinsic, "isnan"))    ret = intrinsic_isnan(a);
+    if (!strcmp(intrinsic, "isnormal")) ret = intrinsic_isnormal(a);
+    if (!strcmp(intrinsic, "signbit"))  ret = intrinsic_signbit(a);
+    if (!strcmp(intrinsic, "acosh"))    ret = intrinsic_acosh(a);
+    if (!strcmp(intrinsic, "asinh"))    ret = intrinsic_asinh(a);
+    if (!strcmp(intrinsic, "atanh"))    ret = intrinsic_atanh(a);
+    if (!strcmp(intrinsic, "exp"))      ret = intrinsic_exp(a);
+    if (!strcmp(intrinsic, "exp2"))     ret = intrinsic_exp2(a);
+    if (!strcmp(intrinsic, "expm1"))    ret = intrinsic_expm1(a);
+    if (!strcmp(intrinsic, "mod"))      ret = intrinsic_mod(a, b);
+    if (!strcmp(intrinsic, "pow"))      ret = intrinsic_pow(a, b);
+    if (!strcmp(intrinsic, "fabs"))     ret = intrinsic_fabs(a);
+
+    if (ret)
+        ++opts_optimizationcount[OPTIM_CONST_FOLD];
+
+    return ret;
+}
+
+/*
+ * These are all the actual constant folding methods that happen in between
+ * the AST/IR stage of the compiler , i.e eliminating branches for const
+ * expressions, which is the only supported thing so far. We undefine the
+ * testing macros here because an ir_value is differant than an ast_value.
+ */
+#undef expect
+#undef isfloat
+#undef isstring
+#undef isvector
+#undef fold__immvalue_float
+#undef fold__immvalue_string
+#undef fold__immvalue_vector
+#undef fold_can_1
+#undef fold_can_2
+
+#define isfloat(X)              ((X)->m_vtype == TYPE_FLOAT)
+/*#define isstring(X)             ((X)->m_vtype == TYPE_STRING)*/
+/*#define isvector(X)             ((X)->m_vtype == TYPE_VECTOR)*/
+#define fold_can_1(X)           ((X)->m_hasvalue && (X)->m_cvq == CV_CONST)
+/*#define fold_can_2(X,Y)         (fold_can_1(X) && fold_can_1(Y))*/
+
+qcfloat_t fold::immvalue_float(ir_value *value) {
+    return value->m_constval.vfloat;
+}
+
+vec3_t fold::immvalue_vector(ir_value *value) {
+    return value->m_constval.vvec;
+}
+
+ast_expression *fold::superfluous(ast_expression *left, ast_expression *right, int op) {
+    ast_expression *swapped = nullptr; /* using this as bool */
+    ast_value *load;
+
+    if (!ast_istype(right, ast_value) || !fold_can_1((load = (ast_value*)right))) {
+        swapped = left;
+        left    = right;
+        right   = swapped;
+    }
+
+    if (!ast_istype(right, ast_value) || !fold_can_1((load = (ast_value*)right)))
+        return nullptr;
+
+    switch (op) {
+        case INSTR_DIV_F:
+            if (swapped)
+                return nullptr;
+        case INSTR_MUL_F:
+            if (immvalue_float(load) == 1.0f) {
+                ++opts_optimizationcount[OPTIM_PEEPHOLE];
+                ast_unref(right);
+                return left;
+            }
+            break;
+
+
+        case INSTR_SUB_F:
+            if (swapped)
+                return nullptr;
+        case INSTR_ADD_F:
+            if (immvalue_float(load) == 0.0f) {
+                ++opts_optimizationcount[OPTIM_PEEPHOLE];
+                ast_unref(right);
+                return left;
+            }
+            break;
+
+        case INSTR_MUL_V:
+            if (vec3_cmp(immvalue_vector(load), vec3_create(1, 1, 1))) {
+                ++opts_optimizationcount[OPTIM_PEEPHOLE];
+                ast_unref(right);
+                return left;
+            }
+            break;
+
+        case INSTR_SUB_V:
+            if (swapped)
+                return nullptr;
+        case INSTR_ADD_V:
+            if (vec3_cmp(immvalue_vector(load), vec3_create(0, 0, 0))) {
+                ++opts_optimizationcount[OPTIM_PEEPHOLE];
+                ast_unref(right);
+                return left;
+            }
+            break;
+    }
+
+    return nullptr;
+}
+
+ast_expression *fold::binary(lex_ctx_t ctx, int op, ast_expression *left, ast_expression *right) {
+    ast_expression *ret = superfluous(left, right, op);
+    if (ret)
+        return ret;
+    return new ast_binary(ctx, op, left, right);
+}
+
+int fold::cond(ir_value *condval, ast_function *func, ast_ifthen *branch) {
+    if (isfloat(condval) && fold_can_1(condval) && OPTS_OPTIMIZATION(OPTIM_CONST_FOLD_DCE)) {
+        ir_block               *elide;
+        ir_value               *dummy;
+        bool                    istrue  = (immvalue_float(condval) != 0.0f && branch->m_on_true);
+        bool                    isfalse = (immvalue_float(condval) == 0.0f && branch->m_on_false);
+        ast_expression         *path    = (istrue)  ? branch->m_on_true  :
+                                          (isfalse) ? branch->m_on_false : nullptr;
+        if (!path) {
+            /*
+             * no path to take implies that the evaluation is if(0) and there
+             * is no else block. so eliminate all the code.
+             */
+            ++opts_optimizationcount[OPTIM_CONST_FOLD_DCE];
+            return true;
+        }
+
+        if (!(elide = ir_function_create_block(branch->m_context, func->m_ir_func, func->makeLabel((istrue) ? "ontrue" : "onfalse"))))
+            return false;
+        if (!path->codegen(func, false, &dummy))
+            return false;
+        if (!ir_block_create_jump(func->m_curblock, branch->m_context, elide))
+            return false;
+        /*
+         * now the branch has been eliminated and the correct block for the constant evaluation
+         * is expanded into the current block for the function.
+         */
+        func->m_curblock = elide;
+        ++opts_optimizationcount[OPTIM_CONST_FOLD_DCE];
+        return true;
+    }
+    return -1; /* nothing done */
+}
+
+int fold::cond_ternary(ir_value *condval, ast_function *func, ast_ternary *branch) {
+    return cond(condval, func, (ast_ifthen*)branch);
+}
+
+int fold::cond_ifthen(ir_value *condval, ast_function *func, ast_ifthen *branch) {
+    return cond(condval, func, branch);
+}
diff --git a/fold.h b/fold.h
new file mode 100644 (file)
index 0000000..1d096e8
--- /dev/null
+++ b/fold.h
@@ -0,0 +1,107 @@
+#ifndef GMQCC_FOLD_HDR
+#define GMQCC_FOLD_HDR
+#include "lexer.h"
+#include "gmqcc.h"
+
+struct ir_builder;
+struct ir_value;
+
+struct ast_function;
+struct ast_ifthen;
+struct ast_ternary;
+struct ast_expression;
+struct ast_value;
+
+struct parser_t;
+
+struct fold {
+    fold();
+    fold(parser_t *parser);
+    ~fold();
+
+    bool generate(ir_builder *ir);
+    ast_expression *op(const oper_info *info, ast_expression **opexprs);
+    ast_expression *intrinsic(const char *intrinsic, ast_expression **arg);
+
+    static int cond_ternary(ir_value *condval, ast_function *func, ast_ternary *branch);
+    static int cond_ifthen(ir_value *condval, ast_function *func, ast_ifthen *branch);
+
+    static ast_expression *superfluous(ast_expression *left, ast_expression *right, int op);
+    static ast_expression *binary(lex_ctx_t ctx, int op, ast_expression *left, ast_expression *right);
+
+    ast_expression *constgen_float(qcfloat_t value, bool inexact);
+    ast_expression *constgen_vector(vec3_t value);
+    ast_expression *constgen_string(const char *str, bool translate);
+    ast_expression *constgen_string(const std::string &str, bool translate);
+
+    ast_value *imm_float(size_t index) const { return m_imm_float[index]; }
+    ast_value *imm_vector(size_t index) const { return m_imm_vector[index]; }
+
+protected:
+    static qcfloat_t immvalue_float(ast_value *value);
+    static vec3_t immvalue_vector(ast_value *value);
+    static const char *immvalue_string(ast_value *value);
+
+    lex_ctx_t ctx();
+
+    bool immediate_true(ast_value *v);
+
+    bool check_except_float_impl(void (*callback)(void), ast_value *a, ast_value *b);
+    bool check_inexact_float(ast_value *a, ast_value *b);
+
+    ast_expression *op_mul_vec(vec3_t vec, ast_value *sel, const char *set);
+    ast_expression *op_neg(ast_value *a);
+    ast_expression *op_not(ast_value *a);
+    ast_expression *op_add(ast_value *a, ast_value *b);
+    ast_expression *op_sub(ast_value *a, ast_value *b);
+    ast_expression *op_mul(ast_value *a, ast_value *b);
+    ast_expression *op_div(ast_value *a, ast_value *b);
+    ast_expression *op_mod(ast_value *a, ast_value *b);
+    ast_expression *op_bor(ast_value *a, ast_value *b);
+    ast_expression *op_band(ast_value *a, ast_value *b);
+    ast_expression *op_xor(ast_value *a, ast_value *b);
+    ast_expression *op_lshift(ast_value *a, ast_value *b);
+    ast_expression *op_rshift(ast_value *a, ast_value *b);
+    ast_expression *op_andor(ast_value *a, ast_value *b, float expr);
+    ast_expression *op_tern(ast_value *a, ast_value *b, ast_value *c);
+    ast_expression *op_exp(ast_value *a, ast_value *b);
+    ast_expression *op_lteqgt(ast_value *a, ast_value *b);
+    ast_expression *op_ltgt(ast_value *a, ast_value *b, bool lt);
+    ast_expression *op_cmp(ast_value *a, ast_value *b, bool ne);
+    ast_expression *op_bnot(ast_value *a);
+    ast_expression *op_cross(ast_value *a, ast_value *b);
+    ast_expression *op_length(ast_value *a);
+
+    ast_expression *intrinsic_isfinite(ast_value *a);
+    ast_expression *intrinsic_isinf(ast_value *a);
+    ast_expression *intrinsic_isnan(ast_value *a);
+    ast_expression *intrinsic_isnormal(ast_value *a);
+    ast_expression *intrinsic_signbit(ast_value *a);
+    ast_expression *intrinsic_acosh(ast_value *a);
+    ast_expression *intrinsic_asinh(ast_value *a);
+    ast_expression *intrinsic_atanh(ast_value *a);
+    ast_expression *intrinsic_exp(ast_value *a);
+    ast_expression *intrinsic_exp2(ast_value *a);
+    ast_expression *intrinsic_expm1(ast_value *a);
+    ast_expression *intrinsic_mod(ast_value *lhs, ast_value *rhs);
+    ast_expression *intrinsic_pow(ast_value *lhs, ast_value *rhs);
+    ast_expression *intrinsic_fabs(ast_value *a);
+
+    static qcfloat_t immvalue_float(ir_value *value);
+    static vec3_t immvalue_vector(ir_value *value);
+
+    static int cond(ir_value *condval, ast_function *func, ast_ifthen *branch);
+
+private:
+    friend struct intrin;
+
+    std::vector<ast_value*> m_imm_float;
+    std::vector<ast_value*> m_imm_vector;
+    std::vector<ast_value*> m_imm_string;
+    hash_table_t *m_imm_string_untranslate; /* map<string, ast_value*> */
+    hash_table_t *m_imm_string_dotranslate; /* map<string, ast_value*> */
+    parser_t *m_parser;
+    bool m_initialized;
+};
+
+#endif
diff --git a/fs.c b/fs.c
deleted file mode 100644 (file)
index 1e06ecc..0000000
--- a/fs.c
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright (C) 2012, 2013, 2014, 2015
- *     Dale Weiler
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
- * this software and associated documentation files (the "Software"), to deal in
- * the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is furnished to do
- * so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-#define GMQCC_PLATFORM_HEADER
-#include "gmqcc.h"
-#include "platform.h"
-
-fs_file_t *fs_file_open(const char *filename, const char *mode) {
-    return (fs_file_t*)platform_fopen(filename, mode);
-}
-
-size_t fs_file_read(void *buffer, size_t size, size_t count, fs_file_t *fp) {
-    return platform_fread(buffer, size, count, (FILE*)fp);
-}
-
-int fs_file_printf(fs_file_t *fp, const char *format, ...) {
-    int      rt;
-    va_list  va;
-    va_start(va, format);
-    rt = platform_vfprintf((FILE*)fp, format, va);
-    va_end  (va);
-
-    return rt;
-}
-
-void fs_file_close(fs_file_t *fp) {
-    platform_fclose((FILE*)fp);
-}
-
-size_t  fs_file_write (
-    const void    *buffer,
-    size_t         size,
-    size_t         count,
-    fs_file_t     *fp
-) {
-    return platform_fwrite(buffer, size, count, (FILE*)fp);
-}
-
-int fs_file_error(fs_file_t *fp) {
-    return platform_ferror((FILE*)fp);
-}
-
-int fs_file_getc(fs_file_t *fp) {
-    int get = platform_fgetc((FILE*)fp);
-    return (get == EOF) ? FS_FILE_EOF : get;
-}
-
-int fs_file_puts(fs_file_t *fp, const char *str) {
-    return platform_fputs(str, (FILE*)fp);
-}
-
-int fs_file_seek(fs_file_t *fp, long int off, int whence) {
-    switch(whence) {
-        case FS_FILE_SEEK_CUR: whence = SEEK_CUR; break;
-        case FS_FILE_SEEK_SET: whence = SEEK_SET; break;
-        case FS_FILE_SEEK_END: whence = SEEK_END; break;
-    }
-    return platform_fseek((FILE*)fp, off, whence);
-}
-
-long int fs_file_tell(fs_file_t *fp) {
-    return platform_ftell((FILE*)fp);
-}
-
-int fs_file_flush(fs_file_t *fp) {
-    return platform_fflush((FILE*)fp);
-}
-
-/*
- * Implements libc getline for systems that don't have it, which is
- * assmed all.  This works the same as getline().
- */
-int fs_file_getline(char **lineptr, size_t *n, fs_file_t *stream) {
-    int   chr;
-    int   ret;
-    char *pos;
-
-    if (!lineptr || !n || !stream)
-        return -1;
-    if (!*lineptr) {
-        if (!(*lineptr = (char*)mem_a((*n=64))))
-            return -1;
-    }
-
-    chr = *n;
-    pos = *lineptr;
-
-    for (;;) {
-        int c = fs_file_getc(stream);
-
-        if (chr < 2) {
-            *n += (*n > 16) ? *n : 64;
-            chr = *n + *lineptr - pos;
-            if (!(*lineptr = (char*)mem_r(*lineptr,*n)))
-                return -1;
-            pos = *n - chr + *lineptr;
-        }
-
-        if (fs_file_error(stream))
-            return -1;
-        if (c == EOF) {
-            if (pos == *lineptr)
-                return -1;
-            else
-                break;
-        }
-
-        *pos++ = c;
-        chr--;
-        if (c == '\n')
-            break;
-    }
-    *pos = '\0';
-    return (ret = pos - *lineptr);
-}
-
-int fs_dir_make(const char *path) {
-    return platform_mkdir(path, 0700);
-}
-
-fs_dir_t *fs_dir_open(const char *name) {
-    return (fs_dir_t*)platform_opendir(name);
-}
-
-int fs_dir_close(fs_dir_t *dir) {
-    return platform_closedir((DIR*)dir);
-}
-
-fs_dirent_t *fs_dir_read(fs_dir_t *dir) {
-    return (fs_dirent_t*)platform_readdir((DIR*)dir);
-}
diff --git a/ftepp.c b/ftepp.c
deleted file mode 100644 (file)
index d2c01df..0000000
--- a/ftepp.c
+++ /dev/null
@@ -1,1987 +0,0 @@
-/*
- * Copyright (C) 2012, 2013, 2014, 2015
- *     Wolfgang Bumiller
- *     Dale Weiler
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
- * this software and associated documentation files (the "Software"), to deal in
- * the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is furnished to do
- * so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-#include <string.h>
-#include <stdlib.h>
-#include <sys/stat.h>
-
-#include "gmqcc.h"
-#include "lexer.h"
-
-#define HT_MACROS 1024
-
-typedef struct {
-    bool on;
-    bool was_on;
-    bool had_else;
-} ppcondition;
-
-typedef struct {
-    int   token;
-    char *value;
-    /* a copy from the lexer */
-    union {
-        vec3_t v;
-        int    i;
-        double f;
-        int    t; /* type */
-    } constval;
-} pptoken;
-
-typedef struct {
-    lex_ctx_t ctx;
-
-    char   *name;
-    char  **params;
-    /* yes we need an extra flag since `#define FOO x` is not the same as `#define FOO() x` */
-    bool    has_params;
-    bool    variadic;
-
-    pptoken **output;
-} ppmacro;
-
-typedef struct ftepp_s {
-    lex_file    *lex;
-    int          token;
-    unsigned int errors;
-
-    bool         output_on;
-    ppcondition *conditions;
-    /*ppmacro    **macros;*/
-    ht           macros;  /* hashtable<string, ppmacro*> */
-    char        *output_string;
-
-    char        *itemname;
-    char        *includename;
-    bool         in_macro;
-
-    uint32_t predef_countval;
-    uint32_t predef_randval;
-} ftepp_t;
-
-/* __DATE__ */
-static char *ftepp_predef_date(ftepp_t *context) {
-    const struct tm *itime = NULL;
-    char            *value = (char*)mem_a(82);
-    time_t           rtime;
-
-    (void)context;
-
-    time (&rtime);
-    itime = util_localtime(&rtime);
-    strftime(value, 82, "\"%b %d %Y\"", itime);
-
-    return value;
-}
-
-/* __TIME__ */
-static char *ftepp_predef_time(ftepp_t *context) {
-    const struct tm *itime = NULL;
-    char            *value = (char*)mem_a(82);
-    time_t           rtime;
-
-    (void)context;
-
-    time (&rtime);
-    itime = util_localtime(&rtime);
-    strftime(value, 82, "\"%X\"", itime);
-
-    return value;
-}
-
-/* __LINE__ */
-static char *ftepp_predef_line(ftepp_t *context) {
-    char *value;
-
-    util_asprintf(&value, "%d", (int)context->lex->line);
-    return value;
-}
-/* __FILE__ */
-static char *ftepp_predef_file(ftepp_t *context) {
-    size_t length = strlen(context->lex->name) + 3; /* two quotes and a terminator */
-    char  *value  = (char*)mem_a(length);
-
-    util_snprintf(value, length, "\"%s\"", context->lex->name);
-    return value;
-}
-/* __COUNTER_LAST__ */
-static char *ftepp_predef_counterlast(ftepp_t *context) {
-    char *value;
-    util_asprintf(&value, "%u", context->predef_countval);
-    return value;
-}
-/* __COUNTER__ */
-static char *ftepp_predef_counter(ftepp_t *context) {
-    char *value;
-
-    context->predef_countval ++;
-    util_asprintf(&value, "%u", context->predef_countval);
-
-    return value;
-}
-/* __RANDOM__ */
-static char *ftepp_predef_random(ftepp_t *context) {
-    char *value;
-
-    context->predef_randval = (util_rand() % 0xFF) + 1;
-    util_asprintf(&value, "%u", context->predef_randval);
-    return value;
-}
-/* __RANDOM_LAST__ */
-static char *ftepp_predef_randomlast(ftepp_t *context) {
-    char *value;
-
-    util_asprintf(&value, "%u", context->predef_randval);
-    return value;
-}
-/* __TIMESTAMP__ */
-static char *ftepp_predef_timestamp(ftepp_t *context) {
-    struct stat finfo;
-    const char *find;
-    char       *value;
-    size_t      size;
-
-    if (stat(context->lex->name, &finfo))
-        return util_strdup("\"<failed to determine timestamp>\"");
-
-    find = util_ctime(&finfo.st_mtime);
-    value = (char*)mem_a(strlen(find) + 1);
-    memcpy(&value[1], find, (size = strlen(find)) - 1);
-
-    value[0]    = '"';
-    value[size] = '"';
-
-    return value;
-}
-
-typedef struct {
-    const char   *name;
-    char       *(*func)(ftepp_t *);
-} ftepp_predef_t;
-
-static const ftepp_predef_t ftepp_predefs[] = {
-    { "__LINE__",         &ftepp_predef_line        },
-    { "__FILE__",         &ftepp_predef_file        },
-    { "__COUNTER__",      &ftepp_predef_counter     },
-    { "__COUNTER_LAST__", &ftepp_predef_counterlast },
-    { "__RANDOM__",       &ftepp_predef_random      },
-    { "__RANDOM_LAST__",  &ftepp_predef_randomlast  },
-    { "__DATE__",         &ftepp_predef_date        },
-    { "__TIME__",         &ftepp_predef_time        },
-    { "__TIME_STAMP__",   &ftepp_predef_timestamp   }
-};
-
-static GMQCC_INLINE size_t ftepp_predef_index(const char *name) {
-    /* no hashtable here, we simply check for one to exist the naive way */
-    size_t i;
-    for(i = 1; i < GMQCC_ARRAY_COUNT(ftepp_predefs) + 1; i++)
-        if (!strcmp(ftepp_predefs[i-1].name, name))
-            return i;
-    return 0;
-}
-
-bool ftepp_predef_exists(const char *name);
-bool ftepp_predef_exists(const char *name) {
-    return ftepp_predef_index(name) != 0;
-}
-
-/* singleton because we're allowed */
-static GMQCC_INLINE char *(*ftepp_predef(const char *name))(ftepp_t *context) {
-    size_t i = ftepp_predef_index(name);
-    return (i != 0) ? ftepp_predefs[i-1].func : NULL;
-}
-
-#define ftepp_tokval(f) ((f)->lex->tok.value)
-#define ftepp_ctx(f)    ((f)->lex->tok.ctx)
-
-static void ftepp_errorat(ftepp_t *ftepp, lex_ctx_t ctx, const char *fmt, ...)
-{
-    va_list ap;
-
-    ftepp->errors++;
-
-    va_start(ap, fmt);
-    con_cvprintmsg(ctx, LVL_ERROR, "error", fmt, ap);
-    va_end(ap);
-}
-
-static void ftepp_error(ftepp_t *ftepp, const char *fmt, ...)
-{
-    va_list ap;
-
-    ftepp->errors++;
-
-    va_start(ap, fmt);
-    con_cvprintmsg(ftepp->lex->tok.ctx, LVL_ERROR, "error", fmt, ap);
-    va_end(ap);
-}
-
-static bool GMQCC_WARN ftepp_warn(ftepp_t *ftepp, int warntype, const char *fmt, ...)
-{
-    bool    r;
-    va_list ap;
-
-    va_start(ap, fmt);
-    r = vcompile_warning(ftepp->lex->tok.ctx, warntype, fmt, ap);
-    va_end(ap);
-    return r;
-}
-
-static pptoken *pptoken_make(ftepp_t *ftepp)
-{
-    pptoken *token = (pptoken*)mem_a(sizeof(pptoken));
-    token->token = ftepp->token;
-#if 0
-    if (token->token == TOKEN_WHITE)
-        token->value = util_strdup(" ");
-    else
-#else
-        token->value = util_strdup(ftepp_tokval(ftepp));
-#endif
-    memcpy(&token->constval, &ftepp->lex->tok.constval, sizeof(token->constval));
-    return token;
-}
-
-static GMQCC_INLINE void pptoken_delete(pptoken *self)
-{
-    mem_d(self->value);
-    mem_d(self);
-}
-
-static ppmacro *ppmacro_new(lex_ctx_t ctx, const char *name)
-{
-    ppmacro *macro = (ppmacro*)mem_a(sizeof(ppmacro));
-
-    (void)ctx;
-    memset(macro, 0, sizeof(*macro));
-    macro->name = util_strdup(name);
-    return macro;
-}
-
-static void ppmacro_delete(ppmacro *self)
-{
-    size_t i;
-    for (i = 0; i < vec_size(self->params); ++i)
-        mem_d(self->params[i]);
-    vec_free(self->params);
-    for (i = 0; i < vec_size(self->output); ++i)
-        pptoken_delete(self->output[i]);
-    vec_free(self->output);
-    mem_d(self->name);
-    mem_d(self);
-}
-
-static ftepp_t* ftepp_new(void)
-{
-    ftepp_t *ftepp;
-
-    ftepp = (ftepp_t*)mem_a(sizeof(*ftepp));
-    memset(ftepp, 0, sizeof(*ftepp));
-
-    ftepp->macros          = util_htnew(HT_MACROS);
-    ftepp->output_on       = true;
-    ftepp->predef_countval = 0;
-    ftepp->predef_randval  = 0;
-
-    return ftepp;
-}
-
-static GMQCC_INLINE void ftepp_flush_do(ftepp_t *self)
-{
-    vec_free(self->output_string);
-}
-
-static void ftepp_delete(ftepp_t *self)
-{
-    ftepp_flush_do(self);
-    if (self->itemname)
-        mem_d(self->itemname);
-    if (self->includename)
-        vec_free(self->includename);
-
-    util_htrem(self->macros, (void (*)(void*))&ppmacro_delete);
-
-    vec_free(self->conditions);
-    if (self->lex)
-        lex_close(self->lex);
-    mem_d(self);
-}
-
-static void ftepp_out(ftepp_t *ftepp, const char *str, bool ignore_cond)
-{
-    if (ignore_cond || ftepp->output_on)
-    {
-        size_t len;
-        char  *data;
-        len = strlen(str);
-        data = vec_add(ftepp->output_string, len);
-        memcpy(data, str, len);
-    }
-}
-
-static GMQCC_INLINE void ftepp_update_output_condition(ftepp_t *ftepp)
-{
-    size_t i;
-    ftepp->output_on = true;
-    for (i = 0; i < vec_size(ftepp->conditions); ++i)
-        ftepp->output_on = ftepp->output_on && ftepp->conditions[i].on;
-}
-
-static GMQCC_INLINE ppmacro* ftepp_macro_find(ftepp_t *ftepp, const char *name)
-{
-    return (ppmacro*)util_htget(ftepp->macros, name);
-}
-
-static GMQCC_INLINE void ftepp_macro_delete(ftepp_t *ftepp, const char *name)
-{
-    util_htrm(ftepp->macros, name, (void (*)(void*))&ppmacro_delete);
-}
-
-static GMQCC_INLINE int ftepp_next(ftepp_t *ftepp)
-{
-    return (ftepp->token = lex_do(ftepp->lex));
-}
-
-/* Important: this does not skip newlines! */
-static bool ftepp_skipspace(ftepp_t *ftepp)
-{
-    if (ftepp->token != TOKEN_WHITE)
-        return true;
-    while (ftepp_next(ftepp) == TOKEN_WHITE) {}
-    if (ftepp->token >= TOKEN_EOF) {
-        ftepp_error(ftepp, "unexpected end of preprocessor directive");
-        return false;
-    }
-    return true;
-}
-
-/* this one skips EOLs as well */
-static bool ftepp_skipallwhite(ftepp_t *ftepp)
-{
-    if (ftepp->token != TOKEN_WHITE && ftepp->token != TOKEN_EOL)
-        return true;
-    do {
-        ftepp_next(ftepp);
-    } while (ftepp->token == TOKEN_WHITE || ftepp->token == TOKEN_EOL);
-    if (ftepp->token >= TOKEN_EOF) {
-        ftepp_error(ftepp, "unexpected end of preprocessor directive");
-        return false;
-    }
-    return true;
-}
-
-/**
- * The huge macro parsing code...
- */
-static bool ftepp_define_params(ftepp_t *ftepp, ppmacro *macro)
-{
-    do {
-        ftepp_next(ftepp);
-        if (!ftepp_skipspace(ftepp))
-            return false;
-        if (ftepp->token == ')')
-            break;
-        switch (ftepp->token) {
-            case TOKEN_IDENT:
-            case TOKEN_TYPENAME:
-            case TOKEN_KEYWORD:
-                vec_push(macro->params, util_strdup(ftepp_tokval(ftepp)));
-                break;
-            case TOKEN_DOTS:
-                macro->variadic = true;
-                break;
-            default:
-                ftepp_error(ftepp, "unexpected token in parameter list");
-                return false;
-        }
-        ftepp_next(ftepp);
-        if (!ftepp_skipspace(ftepp))
-            return false;
-        if (macro->variadic && ftepp->token != ')') {
-            ftepp_error(ftepp, "cannot have parameters after the variadic parameters");
-            return false;
-        }
-    } while (ftepp->token == ',');
-
-    if (ftepp->token != ')') {
-        ftepp_error(ftepp, "expected closing paren after macro parameter list");
-        return false;
-    }
-    ftepp_next(ftepp);
-    /* skipspace happens in ftepp_define */
-    return true;
-}
-
-static bool ftepp_define_body(ftepp_t *ftepp, ppmacro *macro)
-{
-    pptoken *ptok;
-    while (ftepp->token != TOKEN_EOL && ftepp->token < TOKEN_EOF) {
-        bool   subscript = false;
-        size_t index     = 0;
-        if (macro->variadic && !strcmp(ftepp_tokval(ftepp), "__VA_ARGS__")) {
-            subscript = !!(ftepp_next(ftepp) == '#');
-
-            if (subscript && ftepp_next(ftepp) != '#') {
-                ftepp_error(ftepp, "expected `##` in __VA_ARGS__ for subscripting");
-                return false;
-            } else if (subscript) {
-                if (ftepp_next(ftepp) == '[') {
-                    if (ftepp_next(ftepp) != TOKEN_INTCONST) {
-                        ftepp_error(ftepp, "expected index for __VA_ARGS__ subscript");
-                        return false;
-                    }
-
-                    index = (int)strtol(ftepp_tokval(ftepp), NULL, 10);
-
-                    if (ftepp_next(ftepp) != ']') {
-                        ftepp_error(ftepp, "expected `]` in __VA_ARGS__ subscript");
-                        return false;
-                    }
-
-                    /*
-                     * mark it as an array to be handled later as such and not
-                     * as traditional __VA_ARGS__
-                     */
-                    ftepp->token = TOKEN_VA_ARGS_ARRAY;
-                    ptok = pptoken_make(ftepp);
-                    ptok->constval.i = index;
-                    vec_push(macro->output, ptok);
-                    ftepp_next(ftepp);
-                } else {
-                    ftepp_error(ftepp, "expected `[` for subscripting of __VA_ARGS__");
-                    return false;
-                }
-            } else {
-                int old = ftepp->token;
-                ftepp->token = TOKEN_VA_ARGS;
-                ptok = pptoken_make(ftepp);
-                vec_push(macro->output, ptok);
-                ftepp->token = old;
-            }
-        }
-        else if (macro->variadic && !strcmp(ftepp_tokval(ftepp), "__VA_COUNT__")) {
-            ftepp->token = TOKEN_VA_COUNT;
-            ptok         = pptoken_make(ftepp);
-            vec_push(macro->output, ptok);
-            ftepp_next(ftepp);
-        } else {
-            ptok = pptoken_make(ftepp);
-            vec_push(macro->output, ptok);
-            ftepp_next(ftepp);
-        }
-    }
-    /* recursive expansion can cause EOFs here */
-    if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) {
-        ftepp_error(ftepp, "unexpected junk after macro or unexpected end of file");
-        return false;
-    }
-    return true;
-}
-
-static const char *ftepp_math_constants[][2] = {
-    { "M_E",        "2.7182818284590452354"  }, /* e          */
-    { "M_LOG2E",    "1.4426950408889634074"  }, /* log_2 e    */
-    { "M_LOG10E",   "0.43429448190325182765" }, /* log_10 e   */
-    { "M_LN2",      "0.69314718055994530942" }, /* log_e 2    */
-    { "M_LN10",     "2.30258509299404568402" }, /* log_e 10   */
-    { "M_PI",       "3.14159265358979323846" }, /* pi         */
-    { "M_PI_2",     "1.57079632679489661923" }, /* pi/2       */
-    { "M_PI_4",     "0.78539816339744830962" }, /* pi/4       */
-    { "M_1_PI",     "0.31830988618379067154" }, /* 1/pi       */
-    { "M_2_PI",     "0.63661977236758134308" }, /* 2/pi       */
-    { "M_2_SQRTPI", "1.12837916709551257390" }, /* 2/sqrt(pi) */
-    { "M_SQRT2",    "1.41421356237309504880" }, /* sqrt(2)    */
-    { "M_SQRT1_2",  "0.70710678118654752440" }, /* 1/sqrt(2)  */
-    { "M_TAU",      "6.28318530717958647692" }  /* pi*2       */
-};
-
-static bool ftepp_define(ftepp_t *ftepp)
-{
-    ppmacro *macro = NULL;
-    size_t l = ftepp_ctx(ftepp).line;
-    size_t i;
-    bool   mathconstant = false;
-
-    (void)ftepp_next(ftepp);
-    if (!ftepp_skipspace(ftepp))
-        return false;
-
-    switch (ftepp->token) {
-        case TOKEN_IDENT:
-        case TOKEN_TYPENAME:
-        case TOKEN_KEYWORD:
-            if (OPTS_FLAG(FTEPP_MATHDEFS)) {
-                for (i = 0; i < GMQCC_ARRAY_COUNT(ftepp_math_constants); i++) {
-                    if (!strcmp(ftepp_math_constants[i][0], ftepp_tokval(ftepp))) {
-                        mathconstant = true;
-                        break;
-                    }
-                }
-            }
-
-            macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
-
-            if (OPTS_FLAG(FTEPP_MATHDEFS)) {
-                /* user defined ones take precedence */
-                if (macro && mathconstant) {
-                    ftepp_macro_delete(ftepp, ftepp_tokval(ftepp));
-                    macro = NULL;
-                }
-            }
-
-            if (macro && ftepp->output_on) {
-                if (ftepp_warn(ftepp, WARN_CPP, "redefining `%s`", ftepp_tokval(ftepp)))
-                    return false;
-                ftepp_macro_delete(ftepp, ftepp_tokval(ftepp));
-            }
-            macro = ppmacro_new(ftepp_ctx(ftepp), ftepp_tokval(ftepp));
-            break;
-        default:
-            ftepp_error(ftepp, "expected macro name");
-            return false;
-    }
-
-    (void)ftepp_next(ftepp);
-
-    if (ftepp->token == '(') {
-        macro->has_params = true;
-        if (!ftepp_define_params(ftepp, macro)) {
-            ppmacro_delete(macro);
-            return false;
-        }
-    }
-
-    if (!ftepp_skipspace(ftepp)) {
-        ppmacro_delete(macro);
-        return false;
-    }
-
-    if (!ftepp_define_body(ftepp, macro)) {
-        ppmacro_delete(macro);
-        return false;
-    }
-
-    if (ftepp->output_on)
-        util_htset(ftepp->macros, macro->name, (void*)macro);
-    else {
-        ppmacro_delete(macro);
-    }
-
-    for (; l < ftepp_ctx(ftepp).line; ++l)
-        ftepp_out(ftepp, "\n", true);
-    return true;
-}
-
-/**
- * When a macro is used we have to handle parameters as well
- * as special-concatenation via ## or stringification via #
- *
- * Note: parenthesis can nest, so FOO((a),b) is valid, but only
- * this kind of parens. Curly braces or [] don't count towards the
- * paren-level.
- */
-typedef struct {
-    pptoken **tokens;
-} macroparam;
-
-static void macroparam_clean(macroparam *self)
-{
-    size_t i;
-    for (i = 0; i < vec_size(self->tokens); ++i)
-        pptoken_delete(self->tokens[i]);
-    vec_free(self->tokens);
-}
-
-/* need to leave the last token up */
-static bool ftepp_macro_call_params(ftepp_t *ftepp, macroparam **out_params)
-{
-    macroparam *params = NULL;
-    pptoken    *ptok;
-    macroparam  mp;
-    size_t      parens = 0;
-    size_t      i;
-
-    if (!ftepp_skipallwhite(ftepp))
-        return false;
-    while (ftepp->token != ')') {
-        mp.tokens = NULL;
-        if (!ftepp_skipallwhite(ftepp))
-            return false;
-        while (parens || ftepp->token != ',') {
-            if (ftepp->token == '(')
-                ++parens;
-            else if (ftepp->token == ')') {
-                if (!parens)
-                    break;
-                --parens;
-            }
-            ptok = pptoken_make(ftepp);
-            vec_push(mp.tokens, ptok);
-            if (ftepp_next(ftepp) >= TOKEN_EOF) {
-                ftepp_error(ftepp, "unexpected end of file in macro call");
-                goto on_error;
-            }
-        }
-        vec_push(params, mp);
-        mp.tokens = NULL;
-        if (ftepp->token == ')')
-            break;
-        if (ftepp->token != ',') {
-            ftepp_error(ftepp, "expected closing paren or comma in macro call");
-            goto on_error;
-        }
-        if (ftepp_next(ftepp) >= TOKEN_EOF) {
-            ftepp_error(ftepp, "unexpected end of file in macro call");
-            goto on_error;
-        }
-    }
-    *out_params = params;
-    return true;
-
-on_error:
-    if (mp.tokens)
-        macroparam_clean(&mp);
-    for (i = 0; i < vec_size(params); ++i)
-        macroparam_clean(&params[i]);
-    vec_free(params);
-    return false;
-}
-
-static bool macro_params_find(ppmacro *macro, const char *name, size_t *idx)
-{
-    size_t i;
-    for (i = 0; i < vec_size(macro->params); ++i) {
-        if (!strcmp(macro->params[i], name)) {
-            *idx = i;
-            return true;
-        }
-    }
-    return false;
-}
-
-static void ftepp_stringify_token(ftepp_t *ftepp, pptoken *token)
-{
-    char        chs[2];
-    const char *ch;
-    chs[1] = 0;
-    switch (token->token) {
-        case TOKEN_STRINGCONST:
-            ch = token->value;
-            while (*ch) {
-                /* in preprocessor mode strings already are string,
-                 * so we don't get actual newline bytes here.
-                 * Still need to escape backslashes and quotes.
-                 */
-                switch (*ch) {
-                    case '\\': ftepp_out(ftepp, "\\\\", false); break;
-                    case '"':  ftepp_out(ftepp, "\\\"", false); break;
-                    default:
-                        chs[0] = *ch;
-                        ftepp_out(ftepp, chs, false);
-                        break;
-                }
-                ++ch;
-            }
-            break;
-        /*case TOKEN_WHITE:
-            ftepp_out(ftepp, " ", false);
-            break;*/
-        case TOKEN_EOL:
-            ftepp_out(ftepp, "\\n", false);
-            break;
-        default:
-            ftepp_out(ftepp, token->value, false);
-            break;
-    }
-}
-
-static void ftepp_stringify(ftepp_t *ftepp, macroparam *param)
-{
-    size_t i;
-    ftepp_out(ftepp, "\"", false);
-    for (i = 0; i < vec_size(param->tokens); ++i)
-        ftepp_stringify_token(ftepp, param->tokens[i]);
-    ftepp_out(ftepp, "\"", false);
-}
-
-static void ftepp_recursion_header(ftepp_t *ftepp)
-{
-    ftepp_out(ftepp, "\n#pragma push(line)\n", false);
-}
-
-static void ftepp_recursion_footer(ftepp_t *ftepp)
-{
-    ftepp_out(ftepp, "\n#pragma pop(line)\n", false);
-}
-
-static bool ftepp_macro_expand(ftepp_t *ftepp, ppmacro *macro, macroparam *params, bool resetline);
-static void ftepp_param_out(ftepp_t *ftepp, macroparam *param)
-{
-    size_t   i;
-    pptoken *out;
-    for (i = 0; i < vec_size(param->tokens); ++i) {
-        out = param->tokens[i];
-        if (out->token == TOKEN_EOL)
-            ftepp_out(ftepp, "\n", false);
-        else {
-            ppmacro *find = ftepp_macro_find(ftepp, out->value);
-            if (OPTS_FLAG(FTEPP_INDIRECT_EXPANSION) && find && !find->has_params)
-                ftepp_macro_expand(ftepp, find, NULL, false);
-            else
-                ftepp_out(ftepp, out->value, false);
-        }
-    }
-}
-
-static bool ftepp_preprocess(ftepp_t *ftepp);
-static bool ftepp_macro_expand(ftepp_t *ftepp, ppmacro *macro, macroparam *params, bool resetline)
-{
-    char     *buffer       = NULL;
-    char     *old_string   = ftepp->output_string;
-    char     *inner_string;
-    lex_file *old_lexer    = ftepp->lex;
-    size_t    vararg_start = vec_size(macro->params);
-    bool      retval       = true;
-    bool      has_newlines;
-    size_t    varargs;
-
-    size_t    o, pi;
-    lex_file *inlex;
-
-    bool      old_inmacro;
-    bool      strip = false;
-
-    int nextok;
-
-    if (vararg_start < vec_size(params))
-        varargs = vec_size(params) - vararg_start;
-    else
-        varargs = 0;
-
-    /* really ... */
-    if (!vec_size(macro->output))
-        return true;
-
-    ftepp->output_string = NULL;
-    for (o = 0; o < vec_size(macro->output); ++o) {
-        pptoken *out = macro->output[o];
-        switch (out->token) {
-            case TOKEN_VA_ARGS:
-                if (!macro->variadic) {
-                    ftepp_error(ftepp, "internal preprocessor error: TOKEN_VA_ARGS in non-variadic macro");
-                    vec_free(old_string);
-                    return false;
-                }
-                if (!varargs)
-                    break;
-
-                pi = 0;
-                ftepp_param_out(ftepp, &params[pi + vararg_start]);
-                for (++pi; pi < varargs; ++pi) {
-                    ftepp_out(ftepp, ", ", false);
-                    ftepp_param_out(ftepp, &params[pi + vararg_start]);
-                }
-                break;
-
-            case TOKEN_VA_ARGS_ARRAY:
-                if ((size_t)out->constval.i >= varargs) {
-                    ftepp_error(ftepp, "subscript of `[%u]` is out of bounds for `__VA_ARGS__`", out->constval.i);
-                    vec_free(old_string);
-                    return false;
-                }
-
-                ftepp_param_out(ftepp, &params[out->constval.i + vararg_start]);
-                break;
-
-            case TOKEN_VA_COUNT:
-                util_asprintf(&buffer, "%d", varargs);
-                ftepp_out(ftepp, buffer, false);
-                mem_d(buffer);
-                break;
-
-            case TOKEN_IDENT:
-            case TOKEN_TYPENAME:
-            case TOKEN_KEYWORD:
-                if (!macro_params_find(macro, out->value, &pi)) {
-                    ftepp_out(ftepp, out->value, false);
-                    break;
-                } else
-                    ftepp_param_out(ftepp, &params[pi]);
-                break;
-            case '#':
-                if (o + 1 < vec_size(macro->output)) {
-                    nextok = macro->output[o+1]->token;
-                    if (nextok == '#') {
-                        /* raw concatenation */
-                        ++o;
-                        strip = true;
-                        break;
-                    }
-                    if ( (nextok == TOKEN_IDENT    ||
-                          nextok == TOKEN_KEYWORD  ||
-                          nextok == TOKEN_TYPENAME) &&
-                        macro_params_find(macro, macro->output[o+1]->value, &pi))
-                    {
-                        ++o;
-
-                        ftepp_stringify(ftepp, &params[pi]);
-                        break;
-                    }
-                }
-                ftepp_out(ftepp, "#", false);
-                break;
-            case TOKEN_EOL:
-                ftepp_out(ftepp, "\n", false);
-                break;
-            default:
-                buffer = out->value;
-                #define buffer_stripable(X) ((X) == ' ' || (X) == '\t')
-                if (vec_size(macro->output) > o + 1 && macro->output[o+1]->token == '#' && buffer_stripable(*buffer))
-                    buffer++;
-                if (strip) {
-                    while (buffer_stripable(*buffer)) buffer++;
-                    strip = false;
-                }
-                ftepp_out(ftepp, buffer, false);
-                break;
-        }
-    }
-    vec_push(ftepp->output_string, 0);
-    /* Now run the preprocessor recursively on this string buffer */
-    /*
-    printf("__________\n%s\n=========\n", ftepp->output_string);
-    */
-    inlex = lex_open_string(ftepp->output_string, vec_size(ftepp->output_string)-1, ftepp->lex->name);
-    if (!inlex) {
-        ftepp_error(ftepp, "internal error: failed to instantiate lexer");
-        retval = false;
-        goto cleanup;
-    }
-
-    inlex->line  = ftepp->lex->line;
-    inlex->sline = ftepp->lex->sline;
-    ftepp->lex   = inlex;
-
-    old_inmacro     = ftepp->in_macro;
-    ftepp->in_macro = true;
-    ftepp->output_string = NULL;
-    if (!ftepp_preprocess(ftepp)) {
-        ftepp->in_macro = old_inmacro;
-        vec_free(ftepp->lex->open_string);
-        vec_free(ftepp->output_string);
-        lex_close(ftepp->lex);
-        retval = false;
-        goto cleanup;
-    }
-    ftepp->in_macro = old_inmacro;
-    vec_free(ftepp->lex->open_string);
-    lex_close(ftepp->lex);
-
-    inner_string = ftepp->output_string;
-    ftepp->output_string = old_string;
-
-    has_newlines = (strchr(inner_string, '\n') != NULL);
-
-    if (has_newlines && !old_inmacro)
-        ftepp_recursion_header(ftepp);
-
-    vec_append(ftepp->output_string, vec_size(inner_string), inner_string);
-    vec_free(inner_string);
-
-    if (has_newlines && !old_inmacro)
-        ftepp_recursion_footer(ftepp);
-
-    if (resetline && !ftepp->in_macro) {
-        char lineno[128];
-        util_snprintf(lineno, 128, "\n#pragma line(%lu)\n", (unsigned long)(old_lexer->sline));
-        ftepp_out(ftepp, lineno, false);
-    }
-
-    old_string = ftepp->output_string;
-cleanup:
-    ftepp->lex           = old_lexer;
-    ftepp->output_string = old_string;
-    return retval;
-}
-
-static bool ftepp_macro_call(ftepp_t *ftepp, ppmacro *macro)
-{
-    size_t     o;
-    macroparam *params = NULL;
-    bool        retval = true;
-    size_t      paramline;
-
-    if (!macro->has_params) {
-        if (!ftepp_macro_expand(ftepp, macro, NULL, false))
-            return false;
-        ftepp_next(ftepp);
-        return true;
-    }
-    ftepp_next(ftepp);
-
-    if (!ftepp_skipallwhite(ftepp))
-        return false;
-
-    if (ftepp->token != '(') {
-        ftepp_error(ftepp, "expected macro parameters in parenthesis");
-        return false;
-    }
-
-    ftepp_next(ftepp);
-    paramline = ftepp->lex->sline;
-    if (!ftepp_macro_call_params(ftepp, &params))
-        return false;
-
-    if ( vec_size(params) < vec_size(macro->params) ||
-        (vec_size(params) > vec_size(macro->params) && !macro->variadic) )
-    {
-        ftepp_error(ftepp, "macro %s expects%s %u paramteters, %u provided", macro->name,
-                    (macro->variadic ? " at least" : ""),
-                    (unsigned int)vec_size(macro->params),
-                    (unsigned int)vec_size(params));
-        retval = false;
-        goto cleanup;
-    }
-
-    if (!ftepp_macro_expand(ftepp, macro, params, (paramline != ftepp->lex->sline)))
-        retval = false;
-    ftepp_next(ftepp);
-
-cleanup:
-    for (o = 0; o < vec_size(params); ++o)
-        macroparam_clean(&params[o]);
-    vec_free(params);
-    return retval;
-}
-
-/**
- * #if - the FTEQCC way:
- *    defined(FOO) => true if FOO was #defined regardless of parameters or contents
- *    <numbers>    => True if the number is not 0
- *    !<factor>    => True if the factor yields false
- *    !!<factor>   => ERROR on 2 or more unary nots
- *    <macro>      => becomes the macro's FIRST token regardless of parameters
- *    <e> && <e>   => True if both expressions are true
- *    <e> || <e>   => True if either expression is true
- *    <string>     => False
- *    <ident>      => False (remember for macros the <macro> rule applies instead)
- * Unary + and - are weird and wrong in fteqcc so we don't allow them
- * parenthesis in expressions are allowed
- * parameter lists on macros are errors
- * No mathematical calculations are executed
- */
-static bool ftepp_if_expr(ftepp_t *ftepp, bool *out, double *value_out);
-static bool ftepp_if_op(ftepp_t *ftepp)
-{
-    ftepp->lex->flags.noops = false;
-    ftepp_next(ftepp);
-    if (!ftepp_skipspace(ftepp))
-        return false;
-    ftepp->lex->flags.noops = true;
-    return true;
-}
-static bool ftepp_if_value(ftepp_t *ftepp, bool *out, double *value_out)
-{
-    ppmacro *macro;
-    bool     wasnot = false;
-    bool     wasneg = false;
-
-    if (!ftepp_skipspace(ftepp))
-        return false;
-
-    while (ftepp->token == '!') {
-        wasnot = true;
-        ftepp_next(ftepp);
-        if (!ftepp_skipspace(ftepp))
-            return false;
-    }
-
-    if (ftepp->token == TOKEN_OPERATOR && !strcmp(ftepp_tokval(ftepp), "-"))
-    {
-        wasneg = true;
-        ftepp_next(ftepp);
-        if (!ftepp_skipspace(ftepp))
-            return false;
-    }
-
-    switch (ftepp->token) {
-        case TOKEN_IDENT:
-        case TOKEN_TYPENAME:
-        case TOKEN_KEYWORD:
-            if (!strcmp(ftepp_tokval(ftepp), "defined")) {
-                ftepp_next(ftepp);
-                if (!ftepp_skipspace(ftepp))
-                    return false;
-                if (ftepp->token != '(') {
-                    ftepp_error(ftepp, "`defined` keyword in #if requires a macro name in parenthesis");
-                    return false;
-                }
-                ftepp_next(ftepp);
-                if (!ftepp_skipspace(ftepp))
-                    return false;
-                if (ftepp->token != TOKEN_IDENT &&
-                    ftepp->token != TOKEN_TYPENAME &&
-                    ftepp->token != TOKEN_KEYWORD)
-                {
-                    ftepp_error(ftepp, "defined() used on an unexpected token type");
-                    return false;
-                }
-                macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
-                *out = !!macro;
-                ftepp_next(ftepp);
-                if (!ftepp_skipspace(ftepp))
-                    return false;
-                if (ftepp->token != ')') {
-                    ftepp_error(ftepp, "expected closing paren");
-                    return false;
-                }
-                break;
-            }
-
-            macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
-            if (!macro || !vec_size(macro->output)) {
-                *out = false;
-                *value_out = 0;
-            } else {
-                /* This does not expand recursively! */
-                switch (macro->output[0]->token) {
-                    case TOKEN_INTCONST:
-                        *value_out = macro->output[0]->constval.i;
-                        *out = !!(macro->output[0]->constval.i);
-                        break;
-                    case TOKEN_FLOATCONST:
-                        *value_out = macro->output[0]->constval.f;
-                        *out = !!(macro->output[0]->constval.f);
-                        break;
-                    default:
-                        *out = false;
-                        break;
-                }
-            }
-            break;
-        case TOKEN_STRINGCONST:
-            *value_out = 0;
-            *out = false;
-            break;
-        case TOKEN_INTCONST:
-            *value_out = ftepp->lex->tok.constval.i;
-            *out = !!(ftepp->lex->tok.constval.i);
-            break;
-        case TOKEN_FLOATCONST:
-            *value_out = ftepp->lex->tok.constval.f;
-            *out = !!(ftepp->lex->tok.constval.f);
-            break;
-
-        case '(':
-            ftepp_next(ftepp);
-            if (!ftepp_if_expr(ftepp, out, value_out))
-                return false;
-            if (ftepp->token != ')') {
-                ftepp_error(ftepp, "expected closing paren in #if expression");
-                return false;
-            }
-            break;
-
-        default:
-            ftepp_error(ftepp, "junk in #if: `%s` ...", ftepp_tokval(ftepp));
-            if (OPTS_OPTION_BOOL(OPTION_DEBUG))
-                ftepp_error(ftepp, "internal: token %i\n", ftepp->token);
-            return false;
-    }
-    if (wasneg)
-        *value_out = -*value_out;
-    if (wasnot) {
-        *out = !*out;
-        *value_out = (*out ? 1 : 0);
-    }
-    return true;
-}
-
-/*
-static bool ftepp_if_nextvalue(ftepp_t *ftepp, bool *out, double *value_out)
-{
-    if (!ftepp_next(ftepp))
-        return false;
-    return ftepp_if_value(ftepp, out, value_out);
-}
-*/
-
-static bool ftepp_if_expr(ftepp_t *ftepp, bool *out, double *value_out)
-{
-    if (!ftepp_if_value(ftepp, out, value_out))
-        return false;
-
-    if (!ftepp_if_op(ftepp))
-        return false;
-
-    if (ftepp->token == ')' || ftepp->token != TOKEN_OPERATOR)
-        return true;
-
-    /* FTEQCC is all right-associative and no precedence here */
-    if (!strcmp(ftepp_tokval(ftepp), "&&") ||
-        !strcmp(ftepp_tokval(ftepp), "||"))
-    {
-        bool next = false;
-        char opc  = ftepp_tokval(ftepp)[0];
-        double nextvalue;
-
-        (void)nextvalue;
-        if (!ftepp_next(ftepp))
-            return false;
-        if (!ftepp_if_expr(ftepp, &next, &nextvalue))
-            return false;
-
-        if (opc == '&')
-            *out = *out && next;
-        else
-            *out = *out || next;
-
-        *value_out = (*out ? 1 : 0);
-        return true;
-    }
-    else if (!strcmp(ftepp_tokval(ftepp), "==") ||
-             !strcmp(ftepp_tokval(ftepp), "!=") ||
-             !strcmp(ftepp_tokval(ftepp), ">=") ||
-             !strcmp(ftepp_tokval(ftepp), "<=") ||
-             !strcmp(ftepp_tokval(ftepp), ">") ||
-             !strcmp(ftepp_tokval(ftepp), "<"))
-    {
-        bool next = false;
-        const char opc0 = ftepp_tokval(ftepp)[0];
-        const char opc1 = ftepp_tokval(ftepp)[1];
-        double other;
-
-        if (!ftepp_next(ftepp))
-            return false;
-        if (!ftepp_if_expr(ftepp, &next, &other))
-            return false;
-
-        if (opc0 == '=')
-            *out = (*value_out == other);
-        else if (opc0 == '!')
-            *out = (*value_out != other);
-        else if (opc0 == '>') {
-            if (opc1 == '=') *out = (*value_out >= other);
-            else             *out = (*value_out > other);
-        }
-        else if (opc0 == '<') {
-            if (opc1 == '=') *out = (*value_out <= other);
-            else             *out = (*value_out < other);
-        }
-        *value_out = (*out ? 1 : 0);
-
-        return true;
-    }
-    else {
-        ftepp_error(ftepp, "junk after #if");
-        return false;
-    }
-}
-
-static bool ftepp_if(ftepp_t *ftepp, ppcondition *cond)
-{
-    bool result = false;
-    double dummy = 0;
-
-    memset(cond, 0, sizeof(*cond));
-    (void)ftepp_next(ftepp);
-
-    if (!ftepp_skipspace(ftepp))
-        return false;
-    if (ftepp->token == TOKEN_EOL) {
-        ftepp_error(ftepp, "expected expression for #if-directive");
-        return false;
-    }
-
-    if (!ftepp_if_expr(ftepp, &result, &dummy))
-        return false;
-
-    cond->on = result;
-    return true;
-}
-
-/**
- * ifdef is rather simple
- */
-static bool ftepp_ifdef(ftepp_t *ftepp, ppcondition *cond)
-{
-    ppmacro *macro;
-    memset(cond, 0, sizeof(*cond));
-    (void)ftepp_next(ftepp);
-    if (!ftepp_skipspace(ftepp))
-        return false;
-
-    switch (ftepp->token) {
-        case TOKEN_IDENT:
-        case TOKEN_TYPENAME:
-        case TOKEN_KEYWORD:
-            macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
-            break;
-        default:
-            ftepp_error(ftepp, "expected macro name");
-            return false;
-    }
-
-    (void)ftepp_next(ftepp);
-    if (!ftepp_skipspace(ftepp))
-        return false;
-    /* relaxing this condition
-    if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) {
-        ftepp_error(ftepp, "stray tokens after #ifdef");
-        return false;
-    }
-    */
-    cond->on = !!macro;
-    return true;
-}
-
-/**
- * undef is also simple
- */
-static bool ftepp_undef(ftepp_t *ftepp)
-{
-    (void)ftepp_next(ftepp);
-    if (!ftepp_skipspace(ftepp))
-        return false;
-
-    if (ftepp->output_on) {
-        switch (ftepp->token) {
-            case TOKEN_IDENT:
-            case TOKEN_TYPENAME:
-            case TOKEN_KEYWORD:
-                ftepp_macro_delete(ftepp, ftepp_tokval(ftepp));
-                break;
-            default:
-                ftepp_error(ftepp, "expected macro name");
-                return false;
-        }
-    }
-
-    (void)ftepp_next(ftepp);
-    if (!ftepp_skipspace(ftepp))
-        return false;
-    /* relaxing this condition
-    if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) {
-        ftepp_error(ftepp, "stray tokens after #ifdef");
-        return false;
-    }
-    */
-    return true;
-}
-
-/* Special unescape-string function which skips a leading quote
- * and stops at a quote, not just at \0
- */
-static void unescape(const char *str, char *out) {
-    ++str;
-    while (*str && *str != '"') {
-        if (*str == '\\') {
-            ++str;
-            switch (*str) {
-                case '\\': *out++ = *str; break;
-                case '"':  *out++ = *str; break;
-                case 'a':  *out++ = '\a'; break;
-                case 'b':  *out++ = '\b'; break;
-                case 'r':  *out++ = '\r'; break;
-                case 'n':  *out++ = '\n'; break;
-                case 't':  *out++ = '\t'; break;
-                case 'f':  *out++ = '\f'; break;
-                case 'v':  *out++ = '\v'; break;
-                default:
-                    *out++ = '\\';
-                    *out++ = *str;
-                    break;
-            }
-            ++str;
-            continue;
-        }
-
-        *out++ = *str++;
-    }
-    *out = 0;
-}
-
-static char *ftepp_include_find_path(const char *file, const char *pathfile)
-{
-    fs_file_t  *fp;
-    char       *filename = NULL;
-    const char *last_slash;
-    size_t      len;
-
-    if (!pathfile)
-        return NULL;
-
-    last_slash = strrchr(pathfile, '/');
-
-    if (last_slash) {
-        len = last_slash - pathfile;
-        memcpy(vec_add(filename, len), pathfile, len);
-        vec_push(filename, '/');
-    }
-
-    len = strlen(file);
-    memcpy(vec_add(filename, len+1), file, len);
-    vec_last(filename) = 0;
-
-    fp = fs_file_open(filename, "rb");
-    if (fp) {
-        fs_file_close(fp);
-        return filename;
-    }
-    vec_free(filename);
-    return NULL;
-}
-
-static char *ftepp_include_find(ftepp_t *ftepp, const char *file)
-{
-    char *filename = NULL;
-
-    filename = ftepp_include_find_path(file, ftepp->includename);
-    if (!filename)
-        filename = ftepp_include_find_path(file, ftepp->itemname);
-    return filename;
-}
-
-static bool ftepp_directive_warning(ftepp_t *ftepp) {
-    char *message = NULL;
-
-    if (!ftepp_skipspace(ftepp))
-        return false;
-
-    /* handle the odd non string constant case so it works like C */
-    if (ftepp->token != TOKEN_STRINGCONST) {
-        bool  store   = false;
-        vec_append(message, 8, "#warning");
-        ftepp_next(ftepp);
-        while (ftepp->token != TOKEN_EOL) {
-            vec_append(message, strlen(ftepp_tokval(ftepp)), ftepp_tokval(ftepp));
-            ftepp_next(ftepp);
-        }
-        vec_push(message, '\0');
-        if (ftepp->output_on)
-            store = ftepp_warn(ftepp, WARN_CPP, message);
-        else
-            store = false;
-        vec_free(message);
-        return store;
-    }
-
-    if (!ftepp->output_on)
-        return false;
-
-    unescape  (ftepp_tokval(ftepp), ftepp_tokval(ftepp));
-    return ftepp_warn(ftepp, WARN_CPP, "#warning %s", ftepp_tokval(ftepp));
-}
-
-static void ftepp_directive_error(ftepp_t *ftepp) {
-    char *message = NULL;
-
-    if (!ftepp_skipspace(ftepp))
-        return;
-
-    /* handle the odd non string constant case so it works like C */
-    if (ftepp->token != TOKEN_STRINGCONST) {
-        vec_append(message, 6, "#error");
-        ftepp_next(ftepp);
-        while (ftepp->token != TOKEN_EOL) {
-            vec_append(message, strlen(ftepp_tokval(ftepp)), ftepp_tokval(ftepp));
-            ftepp_next(ftepp);
-        }
-        vec_push(message, '\0');
-        if (ftepp->output_on)
-            ftepp_error(ftepp, message);
-        vec_free(message);
-        return;
-    }
-
-    if (!ftepp->output_on)
-        return;
-
-    unescape  (ftepp_tokval(ftepp), ftepp_tokval(ftepp));
-    ftepp_error(ftepp, "#error %s", ftepp_tokval(ftepp));
-}
-
-static void ftepp_directive_message(ftepp_t *ftepp) {
-    char *message = NULL;
-
-    if (!ftepp_skipspace(ftepp))
-        return;
-
-    /* handle the odd non string constant case so it works like C */
-    if (ftepp->token != TOKEN_STRINGCONST) {
-        vec_append(message, 8, "#message");
-        ftepp_next(ftepp);
-        while (ftepp->token != TOKEN_EOL) {
-            vec_append(message, strlen(ftepp_tokval(ftepp)), ftepp_tokval(ftepp));
-            ftepp_next(ftepp);
-        }
-        vec_push(message, '\0');
-        if (ftepp->output_on)
-            con_cprintmsg(ftepp->lex->tok.ctx, LVL_MSG, "message", message);
-        vec_free(message);
-        return;
-    }
-
-    if (!ftepp->output_on)
-        return;
-
-    unescape     (ftepp_tokval(ftepp), ftepp_tokval(ftepp));
-    con_cprintmsg(ftepp->lex->tok.ctx, LVL_MSG, "message",  ftepp_tokval(ftepp));
-}
-
-/**
- * Include a file.
- * FIXME: do we need/want a -I option?
- * FIXME: what about when dealing with files in subdirectories coming from a progs.src?
- */
-static bool ftepp_include(ftepp_t *ftepp)
-{
-    lex_file *old_lexer = ftepp->lex;
-    lex_file *inlex;
-    lex_ctx_t ctx;
-    char     lineno[128];
-    char     *filename;
-    char     *parsename = NULL;
-    char     *old_includename;
-
-    (void)ftepp_next(ftepp);
-    if (!ftepp_skipspace(ftepp))
-        return false;
-
-    if (ftepp->token != TOKEN_STRINGCONST) {
-        ppmacro *macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
-        if (macro) {
-            char *backup = ftepp->output_string;
-            ftepp->output_string = NULL;
-            if (ftepp_macro_expand(ftepp, macro, NULL, true)) {
-                parsename = util_strdup(ftepp->output_string);
-                vec_free(ftepp->output_string);
-                ftepp->output_string = backup;
-            } else {
-                ftepp->output_string = backup;
-                ftepp_error(ftepp, "expected filename to include");
-                return false;
-            }
-        } else if (OPTS_FLAG(FTEPP_PREDEFS)) {
-            /* Well it could be a predefine like __LINE__ */
-            char *(*predef)(ftepp_t*) = ftepp_predef(ftepp_tokval(ftepp));
-            if (predef) {
-                parsename = predef(ftepp);
-            } else {
-                ftepp_error(ftepp, "expected filename to include");
-                return false;
-            }
-        }
-    }
-
-    if (!ftepp->output_on) {
-        (void)ftepp_next(ftepp);
-        return true;
-    }
-
-    if (parsename)
-        unescape(parsename, parsename);
-    else {
-        char *tokval = ftepp_tokval(ftepp);
-        unescape(tokval, tokval);
-        parsename = util_strdup(tokval);
-    }
-
-    ctx = ftepp_ctx(ftepp);
-    ftepp_out(ftepp, "\n#pragma file(", false);
-    ftepp_out(ftepp, parsename, false);
-    ftepp_out(ftepp, ")\n#pragma line(1)\n", false);
-
-    filename = ftepp_include_find(ftepp, parsename);
-    if (!filename) {
-        ftepp_error(ftepp, "failed to open include file `%s`", parsename);
-        mem_d(parsename);
-        return false;
-    }
-    mem_d(parsename);
-    inlex = lex_open(filename);
-    if (!inlex) {
-        ftepp_error(ftepp, "open failed on include file `%s`", filename);
-        vec_free(filename);
-        return false;
-    }
-    ftepp->lex = inlex;
-    old_includename = ftepp->includename;
-    ftepp->includename = filename;
-    if (!ftepp_preprocess(ftepp)) {
-        vec_free(ftepp->includename);
-        ftepp->includename = old_includename;
-        lex_close(ftepp->lex);
-        ftepp->lex = old_lexer;
-        return false;
-    }
-    vec_free(ftepp->includename);
-    ftepp->includename = old_includename;
-    lex_close(ftepp->lex);
-    ftepp->lex = old_lexer;
-
-    ftepp_out(ftepp, "\n#pragma file(", false);
-    ftepp_out(ftepp, ctx.file, false);
-    util_snprintf(lineno, sizeof(lineno), ")\n#pragma line(%lu)\n", (unsigned long)(ctx.line+1));
-    ftepp_out(ftepp, lineno, false);
-
-    /* skip the line */
-    (void)ftepp_next(ftepp);
-    if (!ftepp_skipspace(ftepp))
-        return false;
-    if (ftepp->token != TOKEN_EOL) {
-        ftepp_error(ftepp, "stray tokens after #include");
-        return false;
-    }
-    (void)ftepp_next(ftepp);
-
-    return true;
-}
-
-/* Basic structure handlers */
-static bool ftepp_else_allowed(ftepp_t *ftepp)
-{
-    if (!vec_size(ftepp->conditions)) {
-        ftepp_error(ftepp, "#else without #if");
-        return false;
-    }
-    if (vec_last(ftepp->conditions).had_else) {
-        ftepp_error(ftepp, "multiple #else for a single #if");
-        return false;
-    }
-    return true;
-}
-
-static GMQCC_INLINE void ftepp_inmacro(ftepp_t *ftepp, const char *hash) {
-    if (ftepp->in_macro)
-        (void)!ftepp_warn(ftepp, WARN_DIRECTIVE_INMACRO, "`#%s` directive in macro", hash);
-}
-
-static bool ftepp_hash(ftepp_t *ftepp)
-{
-    ppcondition cond;
-    ppcondition *pc;
-
-    lex_ctx_t ctx = ftepp_ctx(ftepp);
-
-    if (!ftepp_skipspace(ftepp))
-        return false;
-
-    switch (ftepp->token) {
-        case TOKEN_KEYWORD:
-        case TOKEN_IDENT:
-        case TOKEN_TYPENAME:
-            if (!strcmp(ftepp_tokval(ftepp), "define")) {
-                ftepp_inmacro(ftepp, "define");
-                return ftepp_define(ftepp);
-            }
-            else if (!strcmp(ftepp_tokval(ftepp), "undef")) {
-                ftepp_inmacro(ftepp, "undef");
-                return ftepp_undef(ftepp);
-            }
-            else if (!strcmp(ftepp_tokval(ftepp), "ifdef")) {
-                ftepp_inmacro(ftepp, "ifdef");
-                if (!ftepp_ifdef(ftepp, &cond))
-                    return false;
-                cond.was_on = cond.on;
-                vec_push(ftepp->conditions, cond);
-                ftepp->output_on = ftepp->output_on && cond.on;
-                break;
-            }
-            else if (!strcmp(ftepp_tokval(ftepp), "ifndef")) {
-                ftepp_inmacro(ftepp, "ifndef");
-                if (!ftepp_ifdef(ftepp, &cond))
-                    return false;
-                cond.on = !cond.on;
-                cond.was_on = cond.on;
-                vec_push(ftepp->conditions, cond);
-                ftepp->output_on = ftepp->output_on && cond.on;
-                break;
-            }
-            else if (!strcmp(ftepp_tokval(ftepp), "elifdef")) {
-                ftepp_inmacro(ftepp, "elifdef");
-                if (!ftepp_else_allowed(ftepp))
-                    return false;
-                if (!ftepp_ifdef(ftepp, &cond))
-                    return false;
-                pc = &vec_last(ftepp->conditions);
-                pc->on     = !pc->was_on && cond.on;
-                pc->was_on = pc->was_on || pc->on;
-                ftepp_update_output_condition(ftepp);
-                break;
-            }
-            else if (!strcmp(ftepp_tokval(ftepp), "elifndef")) {
-                ftepp_inmacro(ftepp, "elifndef");
-                if (!ftepp_else_allowed(ftepp))
-                    return false;
-                if (!ftepp_ifdef(ftepp, &cond))
-                    return false;
-                cond.on = !cond.on;
-                pc = &vec_last(ftepp->conditions);
-                pc->on     = !pc->was_on && cond.on;
-                pc->was_on = pc->was_on || pc->on;
-                ftepp_update_output_condition(ftepp);
-                break;
-            }
-            else if (!strcmp(ftepp_tokval(ftepp), "elif")) {
-                ftepp_inmacro(ftepp, "elif");
-                if (!ftepp_else_allowed(ftepp))
-                    return false;
-                if (!ftepp_if(ftepp, &cond))
-                    return false;
-                pc = &vec_last(ftepp->conditions);
-                pc->on     = !pc->was_on && cond.on;
-                pc->was_on = pc->was_on  || pc->on;
-                ftepp_update_output_condition(ftepp);
-                break;
-            }
-            else if (!strcmp(ftepp_tokval(ftepp), "if")) {
-                ftepp_inmacro(ftepp, "if");
-                if (!ftepp_if(ftepp, &cond))
-                    return false;
-                cond.was_on = cond.on;
-                vec_push(ftepp->conditions, cond);
-                ftepp->output_on = ftepp->output_on && cond.on;
-                break;
-            }
-            else if (!strcmp(ftepp_tokval(ftepp), "else")) {
-                ftepp_inmacro(ftepp, "else");
-                if (!ftepp_else_allowed(ftepp))
-                    return false;
-                pc = &vec_last(ftepp->conditions);
-                pc->on = !pc->was_on;
-                pc->had_else = true;
-                ftepp_next(ftepp);
-                ftepp_update_output_condition(ftepp);
-                break;
-            }
-            else if (!strcmp(ftepp_tokval(ftepp), "endif")) {
-                ftepp_inmacro(ftepp, "endif");
-                if (!vec_size(ftepp->conditions)) {
-                    ftepp_error(ftepp, "#endif without #if");
-                    return false;
-                }
-                vec_pop(ftepp->conditions);
-                ftepp_next(ftepp);
-                ftepp_update_output_condition(ftepp);
-                break;
-            }
-            else if (!strcmp(ftepp_tokval(ftepp), "include")) {
-                ftepp_inmacro(ftepp, "include");
-                return ftepp_include(ftepp);
-            }
-            else if (!strcmp(ftepp_tokval(ftepp), "pragma")) {
-                ftepp_out(ftepp, "#", false);
-                break;
-            }
-            else if (!strcmp(ftepp_tokval(ftepp), "warning")) {
-                ftepp_directive_warning(ftepp);
-                break;
-            }
-            else if (!strcmp(ftepp_tokval(ftepp), "error")) {
-                ftepp_directive_error(ftepp);
-                break;
-            }
-            else if (!strcmp(ftepp_tokval(ftepp), "message")) {
-                ftepp_directive_message(ftepp);
-                break;
-            }
-            else {
-                if (ftepp->output_on) {
-                    ftepp_error(ftepp, "unrecognized preprocessor directive: `%s`", ftepp_tokval(ftepp));
-                    return false;
-                } else {
-                    ftepp_next(ftepp);
-                    break;
-                }
-            }
-            /* break; never reached */
-        default:
-            ftepp_error(ftepp, "unexpected preprocessor token: `%s`", ftepp_tokval(ftepp));
-            return false;
-        case TOKEN_EOL:
-            ftepp_errorat(ftepp, ctx, "empty preprocessor directive");
-            return false;
-        case TOKEN_EOF:
-            ftepp_error(ftepp, "missing newline at end of file", ftepp_tokval(ftepp));
-            return false;
-
-        /* Builtins! Don't forget the builtins! */
-        case TOKEN_INTCONST:
-        case TOKEN_FLOATCONST:
-            ftepp_out(ftepp, "#", false);
-            return true;
-    }
-    if (!ftepp_skipspace(ftepp))
-        return false;
-    return true;
-}
-
-static bool ftepp_preprocess(ftepp_t *ftepp)
-{
-    ppmacro *macro;
-    bool     newline = true;
-
-    /* predef stuff */
-    char    *expand  = NULL;
-
-    ftepp->lex->flags.preprocessing = true;
-    ftepp->lex->flags.mergelines    = false;
-    ftepp->lex->flags.noops         = true;
-
-    ftepp_next(ftepp);
-    do
-    {
-        if (ftepp->token >= TOKEN_EOF)
-            break;
-#if 0
-        newline = true;
-#endif
-
-        switch (ftepp->token) {
-            case TOKEN_KEYWORD:
-            case TOKEN_IDENT:
-            case TOKEN_TYPENAME:
-                /* is it a predef? */
-                if (OPTS_FLAG(FTEPP_PREDEFS)) {
-                    char *(*predef)(ftepp_t*) = ftepp_predef(ftepp_tokval(ftepp));
-                    if (predef) {
-                        expand = predef(ftepp);
-                        ftepp_out (ftepp, expand, false);
-                        ftepp_next(ftepp);
-
-                        mem_d(expand);
-                        break;
-                    }
-                }
-
-                if (ftepp->output_on)
-                    macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
-                else
-                    macro = NULL;
-
-                if (!macro) {
-                    ftepp_out(ftepp, ftepp_tokval(ftepp), false);
-                    ftepp_next(ftepp);
-                    break;
-                }
-                if (!ftepp_macro_call(ftepp, macro))
-                    ftepp->token = TOKEN_ERROR;
-                break;
-            case '#':
-                if (!newline) {
-                    ftepp_out(ftepp, ftepp_tokval(ftepp), false);
-                    ftepp_next(ftepp);
-                    break;
-                }
-                ftepp->lex->flags.mergelines = true;
-                if (ftepp_next(ftepp) >= TOKEN_EOF) {
-                    ftepp_error(ftepp, "error in preprocessor directive");
-                    ftepp->token = TOKEN_ERROR;
-                    break;
-                }
-                if (!ftepp_hash(ftepp))
-                    ftepp->token = TOKEN_ERROR;
-                ftepp->lex->flags.mergelines = false;
-                break;
-            case TOKEN_EOL:
-                newline = true;
-                ftepp_out(ftepp, "\n", true);
-                ftepp_next(ftepp);
-                break;
-            case TOKEN_WHITE:
-                /* same as default but don't set newline=false */
-                ftepp_out(ftepp, ftepp_tokval(ftepp), true);
-                ftepp_next(ftepp);
-                break;
-            default:
-                newline = false;
-                ftepp_out(ftepp, ftepp_tokval(ftepp), false);
-                ftepp_next(ftepp);
-                break;
-        }
-    } while (!ftepp->errors && ftepp->token < TOKEN_EOF);
-
-    /* force a 0 at the end but don't count it as added to the output */
-    vec_push(ftepp->output_string, 0);
-    vec_shrinkby(ftepp->output_string, 1);
-
-    return (ftepp->token == TOKEN_EOF);
-}
-
-/* Like in parser.c - files keep the previous state so we have one global
- * preprocessor. Except here we will want to warn about dangling #ifs.
- */
-static bool ftepp_preprocess_done(ftepp_t *ftepp)
-{
-    bool retval = true;
-    if (vec_size(ftepp->conditions)) {
-        if (ftepp_warn(ftepp, WARN_MULTIFILE_IF, "#if spanning multiple files, is this intended?"))
-            retval = false;
-    }
-    lex_close(ftepp->lex);
-    ftepp->lex = NULL;
-    if (ftepp->itemname) {
-        mem_d(ftepp->itemname);
-        ftepp->itemname = NULL;
-    }
-    return retval;
-}
-
-bool ftepp_preprocess_file(ftepp_t *ftepp, const char *filename)
-{
-    ftepp->lex = lex_open(filename);
-    ftepp->itemname = util_strdup(filename);
-    if (!ftepp->lex) {
-        con_out("failed to open file \"%s\"\n", filename);
-        return false;
-    }
-    if (!ftepp_preprocess(ftepp))
-        return false;
-    return ftepp_preprocess_done(ftepp);
-}
-
-bool ftepp_preprocess_string(ftepp_t *ftepp, const char *name, const char *str)
-{
-    ftepp->lex = lex_open_string(str, strlen(str), name);
-    ftepp->itemname = util_strdup(name);
-    if (!ftepp->lex) {
-        con_out("failed to create lexer for string \"%s\"\n", name);
-        return false;
-    }
-    if (!ftepp_preprocess(ftepp))
-        return false;
-    return ftepp_preprocess_done(ftepp);
-}
-
-
-void ftepp_add_macro(ftepp_t *ftepp, const char *name, const char *value) {
-    char *create = NULL;
-
-    /* use saner path for empty macros */
-    if (!value) {
-        ftepp_add_define(ftepp, "__builtin__", name);
-        return;
-    }
-
-    vec_append(create, 8,           "#define ");
-    vec_append(create, strlen(name), name);
-    vec_push  (create, ' ');
-    vec_append(create, strlen(value), value);
-    vec_push  (create, 0);
-
-    ftepp_preprocess_string(ftepp, "__builtin__", create);
-    vec_free  (create);
-}
-
-ftepp_t *ftepp_create()
-{
-    ftepp_t *ftepp;
-    char minor[32];
-    char major[32];
-    size_t i;
-
-    ftepp = ftepp_new();
-    if (!ftepp)
-        return NULL;
-
-    memset(minor, 0, sizeof(minor));
-    memset(major, 0, sizeof(major));
-
-    /* set the right macro based on the selected standard */
-    ftepp_add_define(ftepp, NULL, "GMQCC");
-    if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_FTEQCC) {
-        ftepp_add_define(ftepp, NULL, "__STD_FTEQCC__");
-        /* 1.00 */
-        major[0] = '"';
-        major[1] = '1';
-        major[2] = '"';
-
-        minor[0] = '"';
-        minor[1] = '0';
-        minor[2] = '"';
-    } else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_GMQCC) {
-        ftepp_add_define(ftepp, NULL, "__STD_GMQCC__");
-        util_snprintf(major, 32, "\"%d\"", GMQCC_VERSION_MAJOR);
-        util_snprintf(minor, 32, "\"%d\"", GMQCC_VERSION_MINOR);
-    } else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCCX) {
-        ftepp_add_define(ftepp, NULL, "__STD_QCCX__");
-        util_snprintf(major, 32, "\"%d\"", GMQCC_VERSION_MAJOR);
-        util_snprintf(minor, 32, "\"%d\"", GMQCC_VERSION_MINOR);
-    } else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) {
-        ftepp_add_define(ftepp, NULL, "__STD_QCC__");
-        /* 1.0 */
-        major[0] = '"';
-        major[1] = '1';
-        major[2] = '"';
-
-        minor[0] = '"';
-        minor[1] = '0';
-        minor[2] = '"';
-    }
-
-    ftepp_add_macro(ftepp, "__STD_VERSION_MINOR__", minor);
-    ftepp_add_macro(ftepp, "__STD_VERSION_MAJOR__", major);
-
-    /*
-     * We're going to just make __NULL__ nil, which works for 60% of the
-     * cases of __NULL_ for fteqcc.
-     */
-    ftepp_add_macro(ftepp, "__NULL__", "nil");
-
-    /* add all the math constants if they can be */
-    if (OPTS_FLAG(FTEPP_MATHDEFS)) {
-        for (i = 0; i < GMQCC_ARRAY_COUNT(ftepp_math_constants); i++)
-            if (!ftepp_macro_find(ftepp, ftepp_math_constants[i][0]))
-                ftepp_add_macro(ftepp, ftepp_math_constants[i][0], ftepp_math_constants[i][1]);
-    }
-
-    return ftepp;
-}
-
-void ftepp_add_define(ftepp_t *ftepp, const char *source, const char *name)
-{
-    ppmacro *macro;
-    lex_ctx_t ctx = { "__builtin__", 0, 0 };
-    ctx.file = source;
-    macro = ppmacro_new(ctx, name);
-    /*vec_push(ftepp->macros, macro);*/
-    util_htset(ftepp->macros, name, macro);
-}
-
-const char *ftepp_get(ftepp_t *ftepp)
-{
-    return ftepp->output_string;
-}
-
-void ftepp_flush(ftepp_t *ftepp)
-{
-    ftepp_flush_do(ftepp);
-}
-
-void ftepp_finish(ftepp_t *ftepp)
-{
-    if (!ftepp)
-        return;
-    ftepp_delete(ftepp);
-}
diff --git a/ftepp.cpp b/ftepp.cpp
new file mode 100644 (file)
index 0000000..f1d5f36
--- /dev/null
+++ b/ftepp.cpp
@@ -0,0 +1,1947 @@
+#include <string.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+
+#include "gmqcc.h"
+#include "lexer.h"
+
+#define HT_MACROS 1024
+
+struct ppcondition {
+    bool on;
+    bool was_on;
+    bool had_else;
+};
+
+struct pptoken {
+    int token;
+    char *value;
+    /* a copy from the lexer */
+    union {
+        vec3_t v;
+        int i;
+        double f;
+        int t; /* type */
+    } constval;
+};
+
+struct ppmacro {
+    lex_ctx_t ctx;
+    char *name;
+    char **params;
+    /* yes we need an extra flag since `#define FOO x` is not the same as `#define FOO() x` */
+    bool has_params;
+    bool variadic;
+    pptoken **output;
+};
+
+struct ftepp_t {
+    lex_file *lex;
+    int token;
+    unsigned int errors;
+    bool output_on;
+    ppcondition *conditions;
+    ht macros;  /* hashtable<string, ppmacro*> */
+    char *output_string;
+    char *itemname;
+    char *includename;
+    bool in_macro;
+    uint32_t predef_countval;
+    uint32_t predef_randval;
+};
+
+/* __DATE__ */
+static char *ftepp_predef_date(ftepp_t *context) {
+    const struct tm *itime = nullptr;
+    char            *value = (char*)mem_a(82);
+    time_t           rtime;
+
+    (void)context;
+
+    time (&rtime);
+    itime = util_localtime(&rtime);
+    strftime(value, 82, "\"%b %d %Y\"", itime);
+
+    return value;
+}
+
+/* __TIME__ */
+static char *ftepp_predef_time(ftepp_t *context) {
+    const struct tm *itime = nullptr;
+    char            *value = (char*)mem_a(82);
+    time_t           rtime;
+
+    (void)context;
+
+    time (&rtime);
+    itime = util_localtime(&rtime);
+    strftime(value, 82, "\"%X\"", itime);
+
+    return value;
+}
+
+/* __LINE__ */
+static char *ftepp_predef_line(ftepp_t *context) {
+    char *value;
+
+    util_asprintf(&value, "%d", (int)context->lex->line);
+    return value;
+}
+/* __FILE__ */
+static char *ftepp_predef_file(ftepp_t *context) {
+    size_t length = strlen(context->lex->name) + 3; /* two quotes and a terminator */
+    char  *value  = (char*)mem_a(length);
+
+    util_snprintf(value, length, "\"%s\"", context->lex->name);
+    return value;
+}
+/* __COUNTER_LAST__ */
+static char *ftepp_predef_counterlast(ftepp_t *context) {
+    char *value;
+    util_asprintf(&value, "%u", context->predef_countval);
+    return value;
+}
+/* __COUNTER__ */
+static char *ftepp_predef_counter(ftepp_t *context) {
+    char *value;
+
+    context->predef_countval ++;
+    util_asprintf(&value, "%u", context->predef_countval);
+
+    return value;
+}
+/* __RANDOM__ */
+static char *ftepp_predef_random(ftepp_t *context) {
+    char *value;
+
+    context->predef_randval = (util_rand() % 0xFF) + 1;
+    util_asprintf(&value, "%u", context->predef_randval);
+    return value;
+}
+/* __RANDOM_LAST__ */
+static char *ftepp_predef_randomlast(ftepp_t *context) {
+    char *value;
+
+    util_asprintf(&value, "%u", context->predef_randval);
+    return value;
+}
+/* __TIMESTAMP__ */
+static char *ftepp_predef_timestamp(ftepp_t *context) {
+    struct stat finfo;
+    const char *find;
+    char       *value;
+    size_t      size;
+
+    if (stat(context->lex->name, &finfo))
+        return util_strdup("\"<failed to determine timestamp>\"");
+
+    find = util_ctime(&finfo.st_mtime);
+    value = (char*)mem_a(strlen(find) + 1);
+    memcpy(&value[1], find, (size = strlen(find)) - 1);
+
+    value[0]    = '"';
+    value[size] = '"';
+
+    return value;
+}
+
+struct ftepp_predef_t {
+    const char *name;
+    char *(*func)(ftepp_t *);
+};
+
+static const ftepp_predef_t ftepp_predefs[] = {
+    { "__LINE__",         &ftepp_predef_line        },
+    { "__FILE__",         &ftepp_predef_file        },
+    { "__COUNTER__",      &ftepp_predef_counter     },
+    { "__COUNTER_LAST__", &ftepp_predef_counterlast },
+    { "__RANDOM__",       &ftepp_predef_random      },
+    { "__RANDOM_LAST__",  &ftepp_predef_randomlast  },
+    { "__DATE__",         &ftepp_predef_date        },
+    { "__TIME__",         &ftepp_predef_time        },
+    { "__TIME_STAMP__",   &ftepp_predef_timestamp   }
+};
+
+static GMQCC_INLINE size_t ftepp_predef_index(const char *name) {
+    /* no hashtable here, we simply check for one to exist the naive way */
+    size_t i;
+    for(i = 1; i < GMQCC_ARRAY_COUNT(ftepp_predefs) + 1; i++)
+        if (!strcmp(ftepp_predefs[i-1].name, name))
+            return i;
+    return 0;
+}
+
+bool ftepp_predef_exists(const char *name);
+bool ftepp_predef_exists(const char *name) {
+    return ftepp_predef_index(name) != 0;
+}
+
+/* singleton because we're allowed */
+static GMQCC_INLINE char *(*ftepp_predef(const char *name))(ftepp_t *context) {
+    size_t i = ftepp_predef_index(name);
+    return (i != 0) ? ftepp_predefs[i-1].func : nullptr;
+}
+
+#define ftepp_tokval(f) ((f)->lex->tok.value)
+#define ftepp_ctx(f)    ((f)->lex->tok.ctx)
+
+static void ftepp_errorat(ftepp_t *ftepp, lex_ctx_t ctx, const char *fmt, ...)
+{
+    va_list ap;
+
+    ftepp->errors++;
+
+    va_start(ap, fmt);
+    con_cvprintmsg(ctx, LVL_ERROR, "error", fmt, ap);
+    va_end(ap);
+}
+
+static void ftepp_error(ftepp_t *ftepp, const char *fmt, ...)
+{
+    va_list ap;
+
+    ftepp->errors++;
+
+    va_start(ap, fmt);
+    con_cvprintmsg(ftepp->lex->tok.ctx, LVL_ERROR, "error", fmt, ap);
+    va_end(ap);
+}
+
+static bool GMQCC_WARN ftepp_warn(ftepp_t *ftepp, int warntype, const char *fmt, ...)
+{
+    bool    r;
+    va_list ap;
+
+    va_start(ap, fmt);
+    r = vcompile_warning(ftepp->lex->tok.ctx, warntype, fmt, ap);
+    va_end(ap);
+    return r;
+}
+
+static pptoken *pptoken_make(ftepp_t *ftepp)
+{
+    pptoken *token = (pptoken*)mem_a(sizeof(pptoken));
+    token->token = ftepp->token;
+    token->value = util_strdup(ftepp_tokval(ftepp));
+    memcpy(&token->constval, &ftepp->lex->tok.constval, sizeof(token->constval));
+    return token;
+}
+
+static GMQCC_INLINE void pptoken_delete(pptoken *self)
+{
+    mem_d(self->value);
+    mem_d(self);
+}
+
+static ppmacro *ppmacro_new(lex_ctx_t ctx, const char *name)
+{
+    ppmacro *macro = (ppmacro*)mem_a(sizeof(ppmacro));
+
+    (void)ctx;
+    memset(macro, 0, sizeof(*macro));
+    macro->name = util_strdup(name);
+    return macro;
+}
+
+static void ppmacro_delete(ppmacro *self)
+{
+    size_t i;
+    for (i = 0; i < vec_size(self->params); ++i)
+        mem_d(self->params[i]);
+    vec_free(self->params);
+    for (i = 0; i < vec_size(self->output); ++i)
+        pptoken_delete(self->output[i]);
+    vec_free(self->output);
+    mem_d(self->name);
+    mem_d(self);
+}
+
+static ftepp_t* ftepp_new(void)
+{
+    ftepp_t *ftepp;
+
+    ftepp = (ftepp_t*)mem_a(sizeof(*ftepp));
+    memset(ftepp, 0, sizeof(*ftepp));
+
+    ftepp->macros          = util_htnew(HT_MACROS);
+    ftepp->output_on       = true;
+    ftepp->predef_countval = 0;
+    ftepp->predef_randval  = 0;
+
+    return ftepp;
+}
+
+static GMQCC_INLINE void ftepp_flush_do(ftepp_t *self)
+{
+    vec_free(self->output_string);
+}
+
+static void ftepp_delete(ftepp_t *self)
+{
+    ftepp_flush_do(self);
+    if (self->itemname)
+        mem_d(self->itemname);
+    if (self->includename)
+        vec_free(self->includename);
+
+    util_htrem(self->macros, (void (*)(void*))&ppmacro_delete);
+
+    vec_free(self->conditions);
+    if (self->lex)
+        lex_close(self->lex);
+    mem_d(self);
+}
+
+static void ftepp_out(ftepp_t *ftepp, const char *str, bool ignore_cond)
+{
+    if (ignore_cond || ftepp->output_on)
+    {
+        size_t len;
+        char  *data;
+        len = strlen(str);
+        data = vec_add(ftepp->output_string, len);
+        memcpy(data, str, len);
+    }
+}
+
+static GMQCC_INLINE void ftepp_update_output_condition(ftepp_t *ftepp)
+{
+    size_t i;
+    ftepp->output_on = true;
+    for (i = 0; i < vec_size(ftepp->conditions); ++i)
+        ftepp->output_on = ftepp->output_on && ftepp->conditions[i].on;
+}
+
+static GMQCC_INLINE ppmacro* ftepp_macro_find(ftepp_t *ftepp, const char *name)
+{
+    return (ppmacro*)util_htget(ftepp->macros, name);
+}
+
+static GMQCC_INLINE void ftepp_macro_delete(ftepp_t *ftepp, const char *name)
+{
+    util_htrm(ftepp->macros, name, (void (*)(void*))&ppmacro_delete);
+}
+
+static GMQCC_INLINE int ftepp_next(ftepp_t *ftepp)
+{
+    return (ftepp->token = lex_do(ftepp->lex));
+}
+
+/* Important: this does not skip newlines! */
+static bool ftepp_skipspace(ftepp_t *ftepp)
+{
+    if (ftepp->token != TOKEN_WHITE)
+        return true;
+    while (ftepp_next(ftepp) == TOKEN_WHITE) {}
+    if (ftepp->token >= TOKEN_EOF) {
+        ftepp_error(ftepp, "unexpected end of preprocessor directive");
+        return false;
+    }
+    return true;
+}
+
+/* this one skips EOLs as well */
+static bool ftepp_skipallwhite(ftepp_t *ftepp)
+{
+    if (ftepp->token != TOKEN_WHITE && ftepp->token != TOKEN_EOL)
+        return true;
+    do {
+        ftepp_next(ftepp);
+    } while (ftepp->token == TOKEN_WHITE || ftepp->token == TOKEN_EOL);
+    if (ftepp->token >= TOKEN_EOF) {
+        ftepp_error(ftepp, "unexpected end of preprocessor directive");
+        return false;
+    }
+    return true;
+}
+
+/**
+ * The huge macro parsing code...
+ */
+static bool ftepp_define_params(ftepp_t *ftepp, ppmacro *macro)
+{
+    do {
+        ftepp_next(ftepp);
+        if (!ftepp_skipspace(ftepp))
+            return false;
+        if (ftepp->token == ')')
+            break;
+        switch (ftepp->token) {
+            case TOKEN_IDENT:
+            case TOKEN_TYPENAME:
+            case TOKEN_KEYWORD:
+                vec_push(macro->params, util_strdup(ftepp_tokval(ftepp)));
+                break;
+            case TOKEN_DOTS:
+                macro->variadic = true;
+                break;
+            default:
+                ftepp_error(ftepp, "unexpected token in parameter list");
+                return false;
+        }
+        ftepp_next(ftepp);
+        if (!ftepp_skipspace(ftepp))
+            return false;
+        if (macro->variadic && ftepp->token != ')') {
+            ftepp_error(ftepp, "cannot have parameters after the variadic parameters");
+            return false;
+        }
+    } while (ftepp->token == ',');
+
+    if (ftepp->token != ')') {
+        ftepp_error(ftepp, "expected closing paren after macro parameter list");
+        return false;
+    }
+    ftepp_next(ftepp);
+    /* skipspace happens in ftepp_define */
+    return true;
+}
+
+static bool ftepp_define_body(ftepp_t *ftepp, ppmacro *macro)
+{
+    pptoken *ptok;
+    while (ftepp->token != TOKEN_EOL && ftepp->token < TOKEN_EOF) {
+        bool   subscript = false;
+        size_t index     = 0;
+        if (macro->variadic && !strcmp(ftepp_tokval(ftepp), "__VA_ARGS__")) {
+            subscript = !!(ftepp_next(ftepp) == '#');
+
+            if (subscript && ftepp_next(ftepp) != '#') {
+                ftepp_error(ftepp, "expected `##` in __VA_ARGS__ for subscripting");
+                return false;
+            } else if (subscript) {
+                if (ftepp_next(ftepp) == '[') {
+                    if (ftepp_next(ftepp) != TOKEN_INTCONST) {
+                        ftepp_error(ftepp, "expected index for __VA_ARGS__ subscript");
+                        return false;
+                    }
+
+                    index = (int)strtol(ftepp_tokval(ftepp), nullptr, 10);
+
+                    if (ftepp_next(ftepp) != ']') {
+                        ftepp_error(ftepp, "expected `]` in __VA_ARGS__ subscript");
+                        return false;
+                    }
+
+                    /*
+                     * mark it as an array to be handled later as such and not
+                     * as traditional __VA_ARGS__
+                     */
+                    ftepp->token = TOKEN_VA_ARGS_ARRAY;
+                    ptok = pptoken_make(ftepp);
+                    ptok->constval.i = index;
+                    vec_push(macro->output, ptok);
+                    ftepp_next(ftepp);
+                } else {
+                    ftepp_error(ftepp, "expected `[` for subscripting of __VA_ARGS__");
+                    return false;
+                }
+            } else {
+                int old = ftepp->token;
+                ftepp->token = TOKEN_VA_ARGS;
+                ptok = pptoken_make(ftepp);
+                vec_push(macro->output, ptok);
+                ftepp->token = old;
+            }
+        }
+        else if (macro->variadic && !strcmp(ftepp_tokval(ftepp), "__VA_COUNT__")) {
+            ftepp->token = TOKEN_VA_COUNT;
+            ptok         = pptoken_make(ftepp);
+            vec_push(macro->output, ptok);
+            ftepp_next(ftepp);
+        } else {
+            ptok = pptoken_make(ftepp);
+            vec_push(macro->output, ptok);
+            ftepp_next(ftepp);
+        }
+    }
+    /* recursive expansion can cause EOFs here */
+    if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) {
+        ftepp_error(ftepp, "unexpected junk after macro or unexpected end of file");
+        return false;
+    }
+    return true;
+}
+
+static const char *ftepp_math_constants[][2] = {
+    { "M_E",        "2.7182818284590452354"  }, /* e          */
+    { "M_LOG2E",    "1.4426950408889634074"  }, /* log_2 e    */
+    { "M_LOG10E",   "0.43429448190325182765" }, /* log_10 e   */
+    { "M_LN2",      "0.69314718055994530942" }, /* log_e 2    */
+    { "M_LN10",     "2.30258509299404568402" }, /* log_e 10   */
+    { "M_PI",       "3.14159265358979323846" }, /* pi         */
+    { "M_PI_2",     "1.57079632679489661923" }, /* pi/2       */
+    { "M_PI_4",     "0.78539816339744830962" }, /* pi/4       */
+    { "M_1_PI",     "0.31830988618379067154" }, /* 1/pi       */
+    { "M_2_PI",     "0.63661977236758134308" }, /* 2/pi       */
+    { "M_2_SQRTPI", "1.12837916709551257390" }, /* 2/sqrt(pi) */
+    { "M_SQRT2",    "1.41421356237309504880" }, /* sqrt(2)    */
+    { "M_SQRT1_2",  "0.70710678118654752440" }, /* 1/sqrt(2)  */
+    { "M_TAU",      "6.28318530717958647692" }  /* pi*2       */
+};
+
+static bool ftepp_define(ftepp_t *ftepp)
+{
+    ppmacro *macro = nullptr;
+    size_t l = ftepp_ctx(ftepp).line;
+    size_t i;
+    bool   mathconstant = false;
+
+    (void)ftepp_next(ftepp);
+    if (!ftepp_skipspace(ftepp))
+        return false;
+
+    switch (ftepp->token) {
+        case TOKEN_IDENT:
+        case TOKEN_TYPENAME:
+        case TOKEN_KEYWORD:
+            if (OPTS_FLAG(FTEPP_MATHDEFS)) {
+                for (i = 0; i < GMQCC_ARRAY_COUNT(ftepp_math_constants); i++) {
+                    if (!strcmp(ftepp_math_constants[i][0], ftepp_tokval(ftepp))) {
+                        mathconstant = true;
+                        break;
+                    }
+                }
+            }
+
+            macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
+
+            if (OPTS_FLAG(FTEPP_MATHDEFS)) {
+                /* user defined ones take precedence */
+                if (macro && mathconstant) {
+                    ftepp_macro_delete(ftepp, ftepp_tokval(ftepp));
+                    macro = nullptr;
+                }
+            }
+
+            if (macro && ftepp->output_on) {
+                if (ftepp_warn(ftepp, WARN_CPP, "redefining `%s`", ftepp_tokval(ftepp)))
+                    return false;
+                ftepp_macro_delete(ftepp, ftepp_tokval(ftepp));
+            }
+            macro = ppmacro_new(ftepp_ctx(ftepp), ftepp_tokval(ftepp));
+            break;
+        default:
+            ftepp_error(ftepp, "expected macro name");
+            return false;
+    }
+
+    (void)ftepp_next(ftepp);
+
+    if (ftepp->token == '(') {
+        macro->has_params = true;
+        if (!ftepp_define_params(ftepp, macro)) {
+            ppmacro_delete(macro);
+            return false;
+        }
+    }
+
+    if (!ftepp_skipspace(ftepp)) {
+        ppmacro_delete(macro);
+        return false;
+    }
+
+    if (!ftepp_define_body(ftepp, macro)) {
+        ppmacro_delete(macro);
+        return false;
+    }
+
+    if (ftepp->output_on)
+        util_htset(ftepp->macros, macro->name, (void*)macro);
+    else {
+        ppmacro_delete(macro);
+    }
+
+    for (; l < ftepp_ctx(ftepp).line; ++l)
+        ftepp_out(ftepp, "\n", true);
+    return true;
+}
+
+/**
+ * When a macro is used we have to handle parameters as well
+ * as special-concatenation via ## or stringification via #
+ *
+ * Note: parenthesis can nest, so FOO((a),b) is valid, but only
+ * this kind of parens. Curly braces or [] don't count towards the
+ * paren-level.
+ */
+struct macroparam {
+    pptoken **tokens;
+};
+
+static void macroparam_clean(macroparam *self)
+{
+    size_t i;
+    for (i = 0; i < vec_size(self->tokens); ++i)
+        pptoken_delete(self->tokens[i]);
+    vec_free(self->tokens);
+}
+
+/* need to leave the last token up */
+static bool ftepp_macro_call_params(ftepp_t *ftepp, macroparam **out_params)
+{
+    macroparam *params = nullptr;
+    pptoken    *ptok;
+    macroparam  mp;
+    size_t      parens = 0;
+    size_t      i;
+
+    if (!ftepp_skipallwhite(ftepp))
+        return false;
+    while (ftepp->token != ')') {
+        mp.tokens = nullptr;
+        if (!ftepp_skipallwhite(ftepp))
+            return false;
+        while (parens || ftepp->token != ',') {
+            if (ftepp->token == '(')
+                ++parens;
+            else if (ftepp->token == ')') {
+                if (!parens)
+                    break;
+                --parens;
+            }
+            ptok = pptoken_make(ftepp);
+            vec_push(mp.tokens, ptok);
+            if (ftepp_next(ftepp) >= TOKEN_EOF) {
+                ftepp_error(ftepp, "unexpected end of file in macro call");
+                goto on_error;
+            }
+        }
+        vec_push(params, mp);
+        mp.tokens = nullptr;
+        if (ftepp->token == ')')
+            break;
+        if (ftepp->token != ',') {
+            ftepp_error(ftepp, "expected closing paren or comma in macro call");
+            goto on_error;
+        }
+        if (ftepp_next(ftepp) >= TOKEN_EOF) {
+            ftepp_error(ftepp, "unexpected end of file in macro call");
+            goto on_error;
+        }
+    }
+    *out_params = params;
+    return true;
+
+on_error:
+    if (mp.tokens)
+        macroparam_clean(&mp);
+    for (i = 0; i < vec_size(params); ++i)
+        macroparam_clean(&params[i]);
+    vec_free(params);
+    return false;
+}
+
+static bool macro_params_find(ppmacro *macro, const char *name, size_t *idx)
+{
+    size_t i;
+    for (i = 0; i < vec_size(macro->params); ++i) {
+        if (!strcmp(macro->params[i], name)) {
+            *idx = i;
+            return true;
+        }
+    }
+    return false;
+}
+
+static void ftepp_stringify_token(ftepp_t *ftepp, pptoken *token)
+{
+    char        chs[2];
+    const char *ch;
+    chs[1] = 0;
+    switch (token->token) {
+        case TOKEN_STRINGCONST:
+            ch = token->value;
+            while (*ch) {
+                /* in preprocessor mode strings already are string,
+                 * so we don't get actual newline bytes here.
+                 * Still need to escape backslashes and quotes.
+                 */
+                switch (*ch) {
+                    case '\\': ftepp_out(ftepp, "\\\\", false); break;
+                    case '"':  ftepp_out(ftepp, "\\\"", false); break;
+                    default:
+                        chs[0] = *ch;
+                        ftepp_out(ftepp, chs, false);
+                        break;
+                }
+                ++ch;
+            }
+            break;
+        /*case TOKEN_WHITE:
+            ftepp_out(ftepp, " ", false);
+            break;*/
+        case TOKEN_EOL:
+            ftepp_out(ftepp, "\\n", false);
+            break;
+        default:
+            ftepp_out(ftepp, token->value, false);
+            break;
+    }
+}
+
+static void ftepp_stringify(ftepp_t *ftepp, macroparam *param)
+{
+    size_t i;
+    ftepp_out(ftepp, "\"", false);
+    for (i = 0; i < vec_size(param->tokens); ++i)
+        ftepp_stringify_token(ftepp, param->tokens[i]);
+    ftepp_out(ftepp, "\"", false);
+}
+
+static void ftepp_recursion_header(ftepp_t *ftepp)
+{
+    ftepp_out(ftepp, "\n#pragma push(line)\n", false);
+}
+
+static void ftepp_recursion_footer(ftepp_t *ftepp)
+{
+    ftepp_out(ftepp, "\n#pragma pop(line)\n", false);
+}
+
+static bool ftepp_macro_expand(ftepp_t *ftepp, ppmacro *macro, macroparam *params, bool resetline);
+static void ftepp_param_out(ftepp_t *ftepp, macroparam *param)
+{
+    size_t   i;
+    pptoken *out;
+    for (i = 0; i < vec_size(param->tokens); ++i) {
+        out = param->tokens[i];
+        if (out->token == TOKEN_EOL)
+            ftepp_out(ftepp, "\n", false);
+        else {
+            ppmacro *find = ftepp_macro_find(ftepp, out->value);
+            if (OPTS_FLAG(FTEPP_INDIRECT_EXPANSION) && find && !find->has_params)
+                ftepp_macro_expand(ftepp, find, nullptr, false);
+            else
+                ftepp_out(ftepp, out->value, false);
+        }
+    }
+}
+
+static bool ftepp_preprocess(ftepp_t *ftepp);
+static bool ftepp_macro_expand(ftepp_t *ftepp, ppmacro *macro, macroparam *params, bool resetline)
+{
+    char     *buffer       = nullptr;
+    char     *old_string   = ftepp->output_string;
+    char     *inner_string;
+    lex_file *old_lexer    = ftepp->lex;
+    size_t    vararg_start = vec_size(macro->params);
+    bool      retval       = true;
+    bool      has_newlines;
+    size_t    varargs;
+
+    size_t    o, pi;
+    lex_file *inlex;
+
+    bool      old_inmacro;
+    bool      strip = false;
+
+    int nextok;
+
+    if (vararg_start < vec_size(params))
+        varargs = vec_size(params) - vararg_start;
+    else
+        varargs = 0;
+
+    /* really ... */
+    if (!vec_size(macro->output))
+        return true;
+
+    ftepp->output_string = nullptr;
+    for (o = 0; o < vec_size(macro->output); ++o) {
+        pptoken *out = macro->output[o];
+        switch (out->token) {
+            case TOKEN_VA_ARGS:
+                if (!macro->variadic) {
+                    ftepp_error(ftepp, "internal preprocessor error: TOKEN_VA_ARGS in non-variadic macro");
+                    vec_free(old_string);
+                    return false;
+                }
+                if (!varargs)
+                    break;
+
+                pi = 0;
+                ftepp_param_out(ftepp, &params[pi + vararg_start]);
+                for (++pi; pi < varargs; ++pi) {
+                    ftepp_out(ftepp, ", ", false);
+                    ftepp_param_out(ftepp, &params[pi + vararg_start]);
+                }
+                break;
+
+            case TOKEN_VA_ARGS_ARRAY:
+                if ((size_t)out->constval.i >= varargs) {
+                    ftepp_error(ftepp, "subscript of `[%u]` is out of bounds for `__VA_ARGS__`", out->constval.i);
+                    vec_free(old_string);
+                    return false;
+                }
+
+                ftepp_param_out(ftepp, &params[out->constval.i + vararg_start]);
+                break;
+
+            case TOKEN_VA_COUNT:
+                util_asprintf(&buffer, "%d", varargs);
+                ftepp_out(ftepp, buffer, false);
+                mem_d(buffer);
+                break;
+
+            case TOKEN_IDENT:
+            case TOKEN_TYPENAME:
+            case TOKEN_KEYWORD:
+                if (!macro_params_find(macro, out->value, &pi)) {
+                    ftepp_out(ftepp, out->value, false);
+                    break;
+                } else
+                    ftepp_param_out(ftepp, &params[pi]);
+                break;
+            case '#':
+                if (o + 1 < vec_size(macro->output)) {
+                    nextok = macro->output[o+1]->token;
+                    if (nextok == '#') {
+                        /* raw concatenation */
+                        ++o;
+                        strip = true;
+                        break;
+                    }
+                    if ( (nextok == TOKEN_IDENT    ||
+                          nextok == TOKEN_KEYWORD  ||
+                          nextok == TOKEN_TYPENAME) &&
+                        macro_params_find(macro, macro->output[o+1]->value, &pi))
+                    {
+                        ++o;
+
+                        ftepp_stringify(ftepp, &params[pi]);
+                        break;
+                    }
+                }
+                ftepp_out(ftepp, "#", false);
+                break;
+            case TOKEN_EOL:
+                ftepp_out(ftepp, "\n", false);
+                break;
+            default:
+                buffer = out->value;
+                #define buffer_stripable(X) ((X) == ' ' || (X) == '\t')
+                if (vec_size(macro->output) > o + 1 && macro->output[o+1]->token == '#' && buffer_stripable(*buffer))
+                    buffer++;
+                if (strip) {
+                    while (buffer_stripable(*buffer)) buffer++;
+                    strip = false;
+                }
+                ftepp_out(ftepp, buffer, false);
+                break;
+        }
+    }
+    vec_push(ftepp->output_string, 0);
+    /* Now run the preprocessor recursively on this string buffer */
+    /*
+    printf("__________\n%s\n=========\n", ftepp->output_string);
+    */
+    inlex = lex_open_string(ftepp->output_string, vec_size(ftepp->output_string)-1, ftepp->lex->name);
+    if (!inlex) {
+        ftepp_error(ftepp, "internal error: failed to instantiate lexer");
+        retval = false;
+        goto cleanup;
+    }
+
+    inlex->line  = ftepp->lex->line;
+    inlex->sline = ftepp->lex->sline;
+    ftepp->lex   = inlex;
+
+    old_inmacro     = ftepp->in_macro;
+    ftepp->in_macro = true;
+    ftepp->output_string = nullptr;
+    if (!ftepp_preprocess(ftepp)) {
+        ftepp->in_macro = old_inmacro;
+        vec_free(ftepp->lex->open_string);
+        vec_free(ftepp->output_string);
+        lex_close(ftepp->lex);
+        retval = false;
+        goto cleanup;
+    }
+    ftepp->in_macro = old_inmacro;
+    vec_free(ftepp->lex->open_string);
+    lex_close(ftepp->lex);
+
+    inner_string = ftepp->output_string;
+    ftepp->output_string = old_string;
+
+    has_newlines = (strchr(inner_string, '\n') != nullptr);
+
+    if (has_newlines && !old_inmacro)
+        ftepp_recursion_header(ftepp);
+
+    vec_append(ftepp->output_string, vec_size(inner_string), inner_string);
+    vec_free(inner_string);
+
+    if (has_newlines && !old_inmacro)
+        ftepp_recursion_footer(ftepp);
+
+    if (resetline && !ftepp->in_macro) {
+        char lineno[128];
+        util_snprintf(lineno, 128, "\n#pragma line(%lu)\n", (unsigned long)(old_lexer->sline));
+        ftepp_out(ftepp, lineno, false);
+    }
+
+    old_string = ftepp->output_string;
+cleanup:
+    ftepp->lex           = old_lexer;
+    ftepp->output_string = old_string;
+    return retval;
+}
+
+static bool ftepp_macro_call(ftepp_t *ftepp, ppmacro *macro)
+{
+    size_t     o;
+    macroparam *params = nullptr;
+    bool        retval = true;
+    size_t      paramline;
+
+    if (!macro->has_params) {
+        if (!ftepp_macro_expand(ftepp, macro, nullptr, false))
+            return false;
+        ftepp_next(ftepp);
+        return true;
+    }
+    ftepp_next(ftepp);
+
+    if (!ftepp_skipallwhite(ftepp))
+        return false;
+
+    if (ftepp->token != '(') {
+        ftepp_error(ftepp, "expected macro parameters in parenthesis");
+        return false;
+    }
+
+    ftepp_next(ftepp);
+    paramline = ftepp->lex->sline;
+    if (!ftepp_macro_call_params(ftepp, &params))
+        return false;
+
+    if ( vec_size(params) < vec_size(macro->params) ||
+        (vec_size(params) > vec_size(macro->params) && !macro->variadic) )
+    {
+        ftepp_error(ftepp, "macro %s expects%s %u paramteters, %u provided", macro->name,
+                    (macro->variadic ? " at least" : ""),
+                    (unsigned int)vec_size(macro->params),
+                    (unsigned int)vec_size(params));
+        retval = false;
+        goto cleanup;
+    }
+
+    if (!ftepp_macro_expand(ftepp, macro, params, (paramline != ftepp->lex->sline)))
+        retval = false;
+    ftepp_next(ftepp);
+
+cleanup:
+    for (o = 0; o < vec_size(params); ++o)
+        macroparam_clean(&params[o]);
+    vec_free(params);
+    return retval;
+}
+
+/**
+ * #if - the FTEQCC way:
+ *    defined(FOO) => true if FOO was #defined regardless of parameters or contents
+ *    <numbers>    => True if the number is not 0
+ *    !<factor>    => True if the factor yields false
+ *    !!<factor>   => ERROR on 2 or more unary nots
+ *    <macro>      => becomes the macro's FIRST token regardless of parameters
+ *    <e> && <e>   => True if both expressions are true
+ *    <e> || <e>   => True if either expression is true
+ *    <string>     => False
+ *    <ident>      => False (remember for macros the <macro> rule applies instead)
+ * Unary + and - are weird and wrong in fteqcc so we don't allow them
+ * parenthesis in expressions are allowed
+ * parameter lists on macros are errors
+ * No mathematical calculations are executed
+ */
+static bool ftepp_if_expr(ftepp_t *ftepp, bool *out, double *value_out);
+static bool ftepp_if_op(ftepp_t *ftepp)
+{
+    ftepp->lex->flags.noops = false;
+    ftepp_next(ftepp);
+    if (!ftepp_skipspace(ftepp))
+        return false;
+    ftepp->lex->flags.noops = true;
+    return true;
+}
+static bool ftepp_if_value(ftepp_t *ftepp, bool *out, double *value_out)
+{
+    ppmacro *macro;
+    bool     wasnot = false;
+    bool     wasneg = false;
+
+    if (!ftepp_skipspace(ftepp))
+        return false;
+
+    while (ftepp->token == '!') {
+        wasnot = true;
+        ftepp_next(ftepp);
+        if (!ftepp_skipspace(ftepp))
+            return false;
+    }
+
+    if (ftepp->token == TOKEN_OPERATOR && !strcmp(ftepp_tokval(ftepp), "-"))
+    {
+        wasneg = true;
+        ftepp_next(ftepp);
+        if (!ftepp_skipspace(ftepp))
+            return false;
+    }
+
+    switch (ftepp->token) {
+        case TOKEN_IDENT:
+        case TOKEN_TYPENAME:
+        case TOKEN_KEYWORD:
+            if (!strcmp(ftepp_tokval(ftepp), "defined")) {
+                ftepp_next(ftepp);
+                if (!ftepp_skipspace(ftepp))
+                    return false;
+                if (ftepp->token != '(') {
+                    ftepp_error(ftepp, "`defined` keyword in #if requires a macro name in parenthesis");
+                    return false;
+                }
+                ftepp_next(ftepp);
+                if (!ftepp_skipspace(ftepp))
+                    return false;
+                if (ftepp->token != TOKEN_IDENT &&
+                    ftepp->token != TOKEN_TYPENAME &&
+                    ftepp->token != TOKEN_KEYWORD)
+                {
+                    ftepp_error(ftepp, "defined() used on an unexpected token type");
+                    return false;
+                }
+                macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
+                *out = !!macro;
+                ftepp_next(ftepp);
+                if (!ftepp_skipspace(ftepp))
+                    return false;
+                if (ftepp->token != ')') {
+                    ftepp_error(ftepp, "expected closing paren");
+                    return false;
+                }
+                break;
+            }
+
+            macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
+            if (!macro || !vec_size(macro->output)) {
+                *out = false;
+                *value_out = 0;
+            } else {
+                /* This does not expand recursively! */
+                switch (macro->output[0]->token) {
+                    case TOKEN_INTCONST:
+                        *value_out = macro->output[0]->constval.i;
+                        *out = !!(macro->output[0]->constval.i);
+                        break;
+                    case TOKEN_FLOATCONST:
+                        *value_out = macro->output[0]->constval.f;
+                        *out = !!(macro->output[0]->constval.f);
+                        break;
+                    default:
+                        *out = false;
+                        break;
+                }
+            }
+            break;
+        case TOKEN_STRINGCONST:
+            *value_out = 0;
+            *out = false;
+            break;
+        case TOKEN_INTCONST:
+            *value_out = ftepp->lex->tok.constval.i;
+            *out = !!(ftepp->lex->tok.constval.i);
+            break;
+        case TOKEN_FLOATCONST:
+            *value_out = ftepp->lex->tok.constval.f;
+            *out = !!(ftepp->lex->tok.constval.f);
+            break;
+
+        case '(':
+            ftepp_next(ftepp);
+            if (!ftepp_if_expr(ftepp, out, value_out))
+                return false;
+            if (ftepp->token != ')') {
+                ftepp_error(ftepp, "expected closing paren in #if expression");
+                return false;
+            }
+            break;
+
+        default:
+            ftepp_error(ftepp, "junk in #if: `%s` ...", ftepp_tokval(ftepp));
+            if (OPTS_OPTION_BOOL(OPTION_DEBUG))
+                ftepp_error(ftepp, "internal: token %i\n", ftepp->token);
+            return false;
+    }
+    if (wasneg)
+        *value_out = -*value_out;
+    if (wasnot) {
+        *out = !*out;
+        *value_out = (*out ? 1 : 0);
+    }
+    return true;
+}
+
+/*
+static bool ftepp_if_nextvalue(ftepp_t *ftepp, bool *out, double *value_out)
+{
+    if (!ftepp_next(ftepp))
+        return false;
+    return ftepp_if_value(ftepp, out, value_out);
+}
+*/
+
+static bool ftepp_if_expr(ftepp_t *ftepp, bool *out, double *value_out)
+{
+    if (!ftepp_if_value(ftepp, out, value_out))
+        return false;
+
+    if (!ftepp_if_op(ftepp))
+        return false;
+
+    if (ftepp->token == ')' || ftepp->token != TOKEN_OPERATOR)
+        return true;
+
+    /* FTEQCC is all right-associative and no precedence here */
+    if (!strcmp(ftepp_tokval(ftepp), "&&") ||
+        !strcmp(ftepp_tokval(ftepp), "||"))
+    {
+        bool next = false;
+        char opc  = ftepp_tokval(ftepp)[0];
+        double nextvalue;
+
+        (void)nextvalue;
+        if (!ftepp_next(ftepp))
+            return false;
+        if (!ftepp_if_expr(ftepp, &next, &nextvalue))
+            return false;
+
+        if (opc == '&')
+            *out = *out && next;
+        else
+            *out = *out || next;
+
+        *value_out = (*out ? 1 : 0);
+        return true;
+    }
+    else if (!strcmp(ftepp_tokval(ftepp), "==") ||
+             !strcmp(ftepp_tokval(ftepp), "!=") ||
+             !strcmp(ftepp_tokval(ftepp), ">=") ||
+             !strcmp(ftepp_tokval(ftepp), "<=") ||
+             !strcmp(ftepp_tokval(ftepp), ">") ||
+             !strcmp(ftepp_tokval(ftepp), "<"))
+    {
+        bool next = false;
+        const char opc0 = ftepp_tokval(ftepp)[0];
+        const char opc1 = ftepp_tokval(ftepp)[1];
+        double other;
+
+        if (!ftepp_next(ftepp))
+            return false;
+        if (!ftepp_if_expr(ftepp, &next, &other))
+            return false;
+
+        if (opc0 == '=')
+            *out = (*value_out == other);
+        else if (opc0 == '!')
+            *out = (*value_out != other);
+        else if (opc0 == '>') {
+            if (opc1 == '=') *out = (*value_out >= other);
+            else             *out = (*value_out > other);
+        }
+        else if (opc0 == '<') {
+            if (opc1 == '=') *out = (*value_out <= other);
+            else             *out = (*value_out < other);
+        }
+        *value_out = (*out ? 1 : 0);
+
+        return true;
+    }
+    else {
+        ftepp_error(ftepp, "junk after #if");
+        return false;
+    }
+}
+
+static bool ftepp_if(ftepp_t *ftepp, ppcondition *cond)
+{
+    bool result = false;
+    double dummy = 0;
+
+    memset(cond, 0, sizeof(*cond));
+    (void)ftepp_next(ftepp);
+
+    if (!ftepp_skipspace(ftepp))
+        return false;
+    if (ftepp->token == TOKEN_EOL) {
+        ftepp_error(ftepp, "expected expression for #if-directive");
+        return false;
+    }
+
+    if (!ftepp_if_expr(ftepp, &result, &dummy))
+        return false;
+
+    cond->on = result;
+    return true;
+}
+
+/**
+ * ifdef is rather simple
+ */
+static bool ftepp_ifdef(ftepp_t *ftepp, ppcondition *cond)
+{
+    ppmacro *macro;
+    memset(cond, 0, sizeof(*cond));
+    (void)ftepp_next(ftepp);
+    if (!ftepp_skipspace(ftepp))
+        return false;
+
+    switch (ftepp->token) {
+        case TOKEN_IDENT:
+        case TOKEN_TYPENAME:
+        case TOKEN_KEYWORD:
+            macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
+            break;
+        default:
+            ftepp_error(ftepp, "expected macro name");
+            return false;
+    }
+
+    (void)ftepp_next(ftepp);
+    if (!ftepp_skipspace(ftepp))
+        return false;
+    /* relaxing this condition
+    if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) {
+        ftepp_error(ftepp, "stray tokens after #ifdef");
+        return false;
+    }
+    */
+    cond->on = !!macro;
+    return true;
+}
+
+/**
+ * undef is also simple
+ */
+static bool ftepp_undef(ftepp_t *ftepp)
+{
+    (void)ftepp_next(ftepp);
+    if (!ftepp_skipspace(ftepp))
+        return false;
+
+    if (ftepp->output_on) {
+        switch (ftepp->token) {
+            case TOKEN_IDENT:
+            case TOKEN_TYPENAME:
+            case TOKEN_KEYWORD:
+                ftepp_macro_delete(ftepp, ftepp_tokval(ftepp));
+                break;
+            default:
+                ftepp_error(ftepp, "expected macro name");
+                return false;
+        }
+    }
+
+    (void)ftepp_next(ftepp);
+    if (!ftepp_skipspace(ftepp))
+        return false;
+    /* relaxing this condition
+    if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) {
+        ftepp_error(ftepp, "stray tokens after #ifdef");
+        return false;
+    }
+    */
+    return true;
+}
+
+/* Special unescape-string function which skips a leading quote
+ * and stops at a quote, not just at \0
+ */
+static void unescape(const char *str, char *out) {
+    ++str;
+    while (*str && *str != '"') {
+        if (*str == '\\') {
+            ++str;
+            switch (*str) {
+                case '\\': *out++ = *str; break;
+                case '"':  *out++ = *str; break;
+                case 'a':  *out++ = '\a'; break;
+                case 'b':  *out++ = '\b'; break;
+                case 'r':  *out++ = '\r'; break;
+                case 'n':  *out++ = '\n'; break;
+                case 't':  *out++ = '\t'; break;
+                case 'f':  *out++ = '\f'; break;
+                case 'v':  *out++ = '\v'; break;
+                default:
+                    *out++ = '\\';
+                    *out++ = *str;
+                    break;
+            }
+            ++str;
+            continue;
+        }
+
+        *out++ = *str++;
+    }
+    *out = 0;
+}
+
+static char *ftepp_include_find_path(const char *file, const char *pathfile)
+{
+    FILE *fp;
+    char       *filename = nullptr;
+    const char *last_slash;
+    size_t      len;
+
+    if (!pathfile)
+        return nullptr;
+
+    last_slash = strrchr(pathfile, '/');
+
+    if (last_slash) {
+        len = last_slash - pathfile;
+        memcpy(vec_add(filename, len), pathfile, len);
+        vec_push(filename, '/');
+    }
+
+    len = strlen(file);
+    memcpy(vec_add(filename, len+1), file, len);
+    vec_last(filename) = 0;
+
+    fp = fopen(filename, "rb");
+    if (fp) {
+        fclose(fp);
+        return filename;
+    }
+    vec_free(filename);
+    return nullptr;
+}
+
+static char *ftepp_include_find(ftepp_t *ftepp, const char *file)
+{
+    char *filename = nullptr;
+
+    filename = ftepp_include_find_path(file, ftepp->includename);
+    if (!filename)
+        filename = ftepp_include_find_path(file, ftepp->itemname);
+    return filename;
+}
+
+static bool ftepp_directive_warning(ftepp_t *ftepp) {
+    char *message = nullptr;
+
+    if (!ftepp_skipspace(ftepp))
+        return false;
+
+    /* handle the odd non string constant case so it works like C */
+    if (ftepp->token != TOKEN_STRINGCONST) {
+        bool  store   = false;
+        vec_append(message, 8, "#warning");
+        ftepp_next(ftepp);
+        while (ftepp->token != TOKEN_EOL) {
+            vec_append(message, strlen(ftepp_tokval(ftepp)), ftepp_tokval(ftepp));
+            ftepp_next(ftepp);
+        }
+        vec_push(message, '\0');
+        if (ftepp->output_on)
+            store = ftepp_warn(ftepp, WARN_CPP, message);
+        else
+            store = false;
+        vec_free(message);
+        return store;
+    }
+
+    if (!ftepp->output_on)
+        return false;
+
+    unescape  (ftepp_tokval(ftepp), ftepp_tokval(ftepp));
+    return ftepp_warn(ftepp, WARN_CPP, "#warning %s", ftepp_tokval(ftepp));
+}
+
+static void ftepp_directive_error(ftepp_t *ftepp) {
+    char *message = nullptr;
+
+    if (!ftepp_skipspace(ftepp))
+        return;
+
+    /* handle the odd non string constant case so it works like C */
+    if (ftepp->token != TOKEN_STRINGCONST) {
+        vec_append(message, 6, "#error");
+        ftepp_next(ftepp);
+        while (ftepp->token != TOKEN_EOL) {
+            vec_append(message, strlen(ftepp_tokval(ftepp)), ftepp_tokval(ftepp));
+            ftepp_next(ftepp);
+        }
+        vec_push(message, '\0');
+        if (ftepp->output_on)
+            ftepp_error(ftepp, message);
+        vec_free(message);
+        return;
+    }
+
+    if (!ftepp->output_on)
+        return;
+
+    unescape  (ftepp_tokval(ftepp), ftepp_tokval(ftepp));
+    ftepp_error(ftepp, "#error %s", ftepp_tokval(ftepp));
+}
+
+static void ftepp_directive_message(ftepp_t *ftepp) {
+    char *message = nullptr;
+
+    if (!ftepp_skipspace(ftepp))
+        return;
+
+    /* handle the odd non string constant case so it works like C */
+    if (ftepp->token != TOKEN_STRINGCONST) {
+        vec_append(message, 8, "#message");
+        ftepp_next(ftepp);
+        while (ftepp->token != TOKEN_EOL) {
+            vec_append(message, strlen(ftepp_tokval(ftepp)), ftepp_tokval(ftepp));
+            ftepp_next(ftepp);
+        }
+        vec_push(message, '\0');
+        if (ftepp->output_on)
+            con_cprintmsg(ftepp->lex->tok.ctx, LVL_MSG, "message", message);
+        vec_free(message);
+        return;
+    }
+
+    if (!ftepp->output_on)
+        return;
+
+    unescape     (ftepp_tokval(ftepp), ftepp_tokval(ftepp));
+    con_cprintmsg(ftepp->lex->tok.ctx, LVL_MSG, "message",  ftepp_tokval(ftepp));
+}
+
+/**
+ * Include a file.
+ * FIXME: do we need/want a -I option?
+ * FIXME: what about when dealing with files in subdirectories coming from a progs.src?
+ */
+static bool ftepp_include(ftepp_t *ftepp)
+{
+    lex_file *old_lexer = ftepp->lex;
+    lex_file *inlex;
+    lex_ctx_t ctx;
+    char     lineno[128];
+    char     *filename;
+    char     *parsename = nullptr;
+    char     *old_includename;
+
+    (void)ftepp_next(ftepp);
+    if (!ftepp_skipspace(ftepp))
+        return false;
+
+    if (ftepp->token != TOKEN_STRINGCONST) {
+        ppmacro *macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
+        if (macro) {
+            char *backup = ftepp->output_string;
+            ftepp->output_string = nullptr;
+            if (ftepp_macro_expand(ftepp, macro, nullptr, true)) {
+                parsename = util_strdup(ftepp->output_string);
+                vec_free(ftepp->output_string);
+                ftepp->output_string = backup;
+            } else {
+                ftepp->output_string = backup;
+                ftepp_error(ftepp, "expected filename to include");
+                return false;
+            }
+        } else if (OPTS_FLAG(FTEPP_PREDEFS)) {
+            /* Well it could be a predefine like __LINE__ */
+            char *(*predef)(ftepp_t*) = ftepp_predef(ftepp_tokval(ftepp));
+            if (predef) {
+                parsename = predef(ftepp);
+            } else {
+                ftepp_error(ftepp, "expected filename to include");
+                return false;
+            }
+        }
+    }
+
+    if (!ftepp->output_on) {
+        (void)ftepp_next(ftepp);
+        return true;
+    }
+
+    if (parsename)
+        unescape(parsename, parsename);
+    else {
+        char *tokval = ftepp_tokval(ftepp);
+        unescape(tokval, tokval);
+        parsename = util_strdup(tokval);
+    }
+
+    ctx = ftepp_ctx(ftepp);
+    ftepp_out(ftepp, "\n#pragma file(", false);
+    ftepp_out(ftepp, parsename, false);
+    ftepp_out(ftepp, ")\n#pragma line(1)\n", false);
+
+    filename = ftepp_include_find(ftepp, parsename);
+    if (!filename) {
+        ftepp_error(ftepp, "failed to open include file `%s`", parsename);
+        mem_d(parsename);
+        return false;
+    }
+    mem_d(parsename);
+    inlex = lex_open(filename);
+    if (!inlex) {
+        ftepp_error(ftepp, "open failed on include file `%s`", filename);
+        vec_free(filename);
+        return false;
+    }
+    ftepp->lex = inlex;
+    old_includename = ftepp->includename;
+    ftepp->includename = filename;
+    if (!ftepp_preprocess(ftepp)) {
+        vec_free(ftepp->includename);
+        ftepp->includename = old_includename;
+        lex_close(ftepp->lex);
+        ftepp->lex = old_lexer;
+        return false;
+    }
+    vec_free(ftepp->includename);
+    ftepp->includename = old_includename;
+    lex_close(ftepp->lex);
+    ftepp->lex = old_lexer;
+
+    ftepp_out(ftepp, "\n#pragma file(", false);
+    ftepp_out(ftepp, ctx.file, false);
+    util_snprintf(lineno, sizeof(lineno), ")\n#pragma line(%lu)\n", (unsigned long)(ctx.line+1));
+    ftepp_out(ftepp, lineno, false);
+
+    /* skip the line */
+    (void)ftepp_next(ftepp);
+    if (!ftepp_skipspace(ftepp))
+        return false;
+    if (ftepp->token != TOKEN_EOL) {
+        ftepp_error(ftepp, "stray tokens after #include");
+        return false;
+    }
+    (void)ftepp_next(ftepp);
+
+    return true;
+}
+
+/* Basic structure handlers */
+static bool ftepp_else_allowed(ftepp_t *ftepp)
+{
+    if (!vec_size(ftepp->conditions)) {
+        ftepp_error(ftepp, "#else without #if");
+        return false;
+    }
+    if (vec_last(ftepp->conditions).had_else) {
+        ftepp_error(ftepp, "multiple #else for a single #if");
+        return false;
+    }
+    return true;
+}
+
+static GMQCC_INLINE void ftepp_inmacro(ftepp_t *ftepp, const char *hash) {
+    if (ftepp->in_macro)
+        (void)!ftepp_warn(ftepp, WARN_DIRECTIVE_INMACRO, "`#%s` directive in macro", hash);
+}
+
+static bool ftepp_hash(ftepp_t *ftepp)
+{
+    ppcondition cond;
+    ppcondition *pc;
+
+    lex_ctx_t ctx = ftepp_ctx(ftepp);
+
+    if (!ftepp_skipspace(ftepp))
+        return false;
+
+    switch (ftepp->token) {
+        case TOKEN_KEYWORD:
+        case TOKEN_IDENT:
+        case TOKEN_TYPENAME:
+            if (!strcmp(ftepp_tokval(ftepp), "define")) {
+                ftepp_inmacro(ftepp, "define");
+                return ftepp_define(ftepp);
+            }
+            else if (!strcmp(ftepp_tokval(ftepp), "undef")) {
+                ftepp_inmacro(ftepp, "undef");
+                return ftepp_undef(ftepp);
+            }
+            else if (!strcmp(ftepp_tokval(ftepp), "ifdef")) {
+                ftepp_inmacro(ftepp, "ifdef");
+                if (!ftepp_ifdef(ftepp, &cond))
+                    return false;
+                cond.was_on = cond.on;
+                vec_push(ftepp->conditions, cond);
+                ftepp->output_on = ftepp->output_on && cond.on;
+                break;
+            }
+            else if (!strcmp(ftepp_tokval(ftepp), "ifndef")) {
+                ftepp_inmacro(ftepp, "ifndef");
+                if (!ftepp_ifdef(ftepp, &cond))
+                    return false;
+                cond.on = !cond.on;
+                cond.was_on = cond.on;
+                vec_push(ftepp->conditions, cond);
+                ftepp->output_on = ftepp->output_on && cond.on;
+                break;
+            }
+            else if (!strcmp(ftepp_tokval(ftepp), "elifdef")) {
+                ftepp_inmacro(ftepp, "elifdef");
+                if (!ftepp_else_allowed(ftepp))
+                    return false;
+                if (!ftepp_ifdef(ftepp, &cond))
+                    return false;
+                pc = &vec_last(ftepp->conditions);
+                pc->on     = !pc->was_on && cond.on;
+                pc->was_on = pc->was_on || pc->on;
+                ftepp_update_output_condition(ftepp);
+                break;
+            }
+            else if (!strcmp(ftepp_tokval(ftepp), "elifndef")) {
+                ftepp_inmacro(ftepp, "elifndef");
+                if (!ftepp_else_allowed(ftepp))
+                    return false;
+                if (!ftepp_ifdef(ftepp, &cond))
+                    return false;
+                cond.on = !cond.on;
+                pc = &vec_last(ftepp->conditions);
+                pc->on     = !pc->was_on && cond.on;
+                pc->was_on = pc->was_on || pc->on;
+                ftepp_update_output_condition(ftepp);
+                break;
+            }
+            else if (!strcmp(ftepp_tokval(ftepp), "elif")) {
+                ftepp_inmacro(ftepp, "elif");
+                if (!ftepp_else_allowed(ftepp))
+                    return false;
+                if (!ftepp_if(ftepp, &cond))
+                    return false;
+                pc = &vec_last(ftepp->conditions);
+                pc->on     = !pc->was_on && cond.on;
+                pc->was_on = pc->was_on  || pc->on;
+                ftepp_update_output_condition(ftepp);
+                break;
+            }
+            else if (!strcmp(ftepp_tokval(ftepp), "if")) {
+                ftepp_inmacro(ftepp, "if");
+                if (!ftepp_if(ftepp, &cond))
+                    return false;
+                cond.was_on = cond.on;
+                vec_push(ftepp->conditions, cond);
+                ftepp->output_on = ftepp->output_on && cond.on;
+                break;
+            }
+            else if (!strcmp(ftepp_tokval(ftepp), "else")) {
+                ftepp_inmacro(ftepp, "else");
+                if (!ftepp_else_allowed(ftepp))
+                    return false;
+                pc = &vec_last(ftepp->conditions);
+                pc->on = !pc->was_on;
+                pc->had_else = true;
+                ftepp_next(ftepp);
+                ftepp_update_output_condition(ftepp);
+                break;
+            }
+            else if (!strcmp(ftepp_tokval(ftepp), "endif")) {
+                ftepp_inmacro(ftepp, "endif");
+                if (!vec_size(ftepp->conditions)) {
+                    ftepp_error(ftepp, "#endif without #if");
+                    return false;
+                }
+                vec_pop(ftepp->conditions);
+                ftepp_next(ftepp);
+                ftepp_update_output_condition(ftepp);
+                break;
+            }
+            else if (!strcmp(ftepp_tokval(ftepp), "include")) {
+                ftepp_inmacro(ftepp, "include");
+                return ftepp_include(ftepp);
+            }
+            else if (!strcmp(ftepp_tokval(ftepp), "pragma")) {
+                ftepp_out(ftepp, "#", false);
+                break;
+            }
+            else if (!strcmp(ftepp_tokval(ftepp), "warning")) {
+                ftepp_directive_warning(ftepp);
+                break;
+            }
+            else if (!strcmp(ftepp_tokval(ftepp), "error")) {
+                ftepp_directive_error(ftepp);
+                break;
+            }
+            else if (!strcmp(ftepp_tokval(ftepp), "message")) {
+                ftepp_directive_message(ftepp);
+                break;
+            }
+            else {
+                if (ftepp->output_on) {
+                    ftepp_error(ftepp, "unrecognized preprocessor directive: `%s`", ftepp_tokval(ftepp));
+                    return false;
+                } else {
+                    ftepp_next(ftepp);
+                    break;
+                }
+            }
+            /* break; never reached */
+        default:
+            ftepp_error(ftepp, "unexpected preprocessor token: `%s`", ftepp_tokval(ftepp));
+            return false;
+        case TOKEN_EOL:
+            ftepp_errorat(ftepp, ctx, "empty preprocessor directive");
+            return false;
+        case TOKEN_EOF:
+            ftepp_error(ftepp, "missing newline at end of file", ftepp_tokval(ftepp));
+            return false;
+
+        /* Builtins! Don't forget the builtins! */
+        case TOKEN_INTCONST:
+        case TOKEN_FLOATCONST:
+            ftepp_out(ftepp, "#", false);
+            return true;
+    }
+    if (!ftepp_skipspace(ftepp))
+        return false;
+    return true;
+}
+
+static bool ftepp_preprocess(ftepp_t *ftepp)
+{
+    ppmacro *macro;
+    bool     newline = true;
+
+    /* predef stuff */
+    char    *expand  = nullptr;
+
+    ftepp->lex->flags.preprocessing = true;
+    ftepp->lex->flags.mergelines    = false;
+    ftepp->lex->flags.noops         = true;
+
+    ftepp_next(ftepp);
+    do
+    {
+        if (ftepp->token >= TOKEN_EOF)
+            break;
+        switch (ftepp->token) {
+            case TOKEN_KEYWORD:
+            case TOKEN_IDENT:
+            case TOKEN_TYPENAME:
+                /* is it a predef? */
+                if (OPTS_FLAG(FTEPP_PREDEFS)) {
+                    char *(*predef)(ftepp_t*) = ftepp_predef(ftepp_tokval(ftepp));
+                    if (predef) {
+                        expand = predef(ftepp);
+                        ftepp_out (ftepp, expand, false);
+                        ftepp_next(ftepp);
+
+                        mem_d(expand);
+                        break;
+                    }
+                }
+
+                if (ftepp->output_on)
+                    macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
+                else
+                    macro = nullptr;
+
+                if (!macro) {
+                    ftepp_out(ftepp, ftepp_tokval(ftepp), false);
+                    ftepp_next(ftepp);
+                    break;
+                }
+                if (!ftepp_macro_call(ftepp, macro))
+                    ftepp->token = TOKEN_ERROR;
+                break;
+            case '#':
+                if (!newline) {
+                    ftepp_out(ftepp, ftepp_tokval(ftepp), false);
+                    ftepp_next(ftepp);
+                    break;
+                }
+                ftepp->lex->flags.mergelines = true;
+                if (ftepp_next(ftepp) >= TOKEN_EOF) {
+                    ftepp_error(ftepp, "error in preprocessor directive");
+                    ftepp->token = TOKEN_ERROR;
+                    break;
+                }
+                if (!ftepp_hash(ftepp))
+                    ftepp->token = TOKEN_ERROR;
+                ftepp->lex->flags.mergelines = false;
+                break;
+            case TOKEN_EOL:
+                newline = true;
+                ftepp_out(ftepp, "\n", true);
+                ftepp_next(ftepp);
+                break;
+            case TOKEN_WHITE:
+                /* same as default but don't set newline=false */
+                ftepp_out(ftepp, ftepp_tokval(ftepp), true);
+                ftepp_next(ftepp);
+                break;
+            default:
+                newline = false;
+                ftepp_out(ftepp, ftepp_tokval(ftepp), false);
+                ftepp_next(ftepp);
+                break;
+        }
+    } while (!ftepp->errors && ftepp->token < TOKEN_EOF);
+
+    /* force a 0 at the end but don't count it as added to the output */
+    vec_push(ftepp->output_string, 0);
+    vec_shrinkby(ftepp->output_string, 1);
+
+    return (ftepp->token == TOKEN_EOF);
+}
+
+/* Like in parser.c - files keep the previous state so we have one global
+ * preprocessor. Except here we will want to warn about dangling #ifs.
+ */
+static bool ftepp_preprocess_done(ftepp_t *ftepp)
+{
+    bool retval = true;
+    if (vec_size(ftepp->conditions)) {
+        if (ftepp_warn(ftepp, WARN_MULTIFILE_IF, "#if spanning multiple files, is this intended?"))
+            retval = false;
+    }
+    lex_close(ftepp->lex);
+    ftepp->lex = nullptr;
+    if (ftepp->itemname) {
+        mem_d(ftepp->itemname);
+        ftepp->itemname = nullptr;
+    }
+    return retval;
+}
+
+bool ftepp_preprocess_file(ftepp_t *ftepp, const char *filename)
+{
+    ftepp->lex = lex_open(filename);
+    ftepp->itemname = util_strdup(filename);
+    if (!ftepp->lex) {
+        con_out("failed to open file \"%s\"\n", filename);
+        return false;
+    }
+    if (!ftepp_preprocess(ftepp))
+        return false;
+    return ftepp_preprocess_done(ftepp);
+}
+
+bool ftepp_preprocess_string(ftepp_t *ftepp, const char *name, const char *str)
+{
+    ftepp->lex = lex_open_string(str, strlen(str), name);
+    ftepp->itemname = util_strdup(name);
+    if (!ftepp->lex) {
+        con_out("failed to create lexer for string \"%s\"\n", name);
+        return false;
+    }
+    if (!ftepp_preprocess(ftepp))
+        return false;
+    return ftepp_preprocess_done(ftepp);
+}
+
+
+void ftepp_add_macro(ftepp_t *ftepp, const char *name, const char *value) {
+    char *create = nullptr;
+
+    /* use saner path for empty macros */
+    if (!value) {
+        ftepp_add_define(ftepp, "__builtin__", name);
+        return;
+    }
+
+    vec_append(create, 8,           "#define ");
+    vec_append(create, strlen(name), name);
+    vec_push  (create, ' ');
+    vec_append(create, strlen(value), value);
+    vec_push  (create, 0);
+
+    ftepp_preprocess_string(ftepp, "__builtin__", create);
+    vec_free  (create);
+}
+
+ftepp_t *ftepp_create()
+{
+    ftepp_t *ftepp;
+    char minor[32];
+    char major[32];
+    size_t i;
+
+    ftepp = ftepp_new();
+    if (!ftepp)
+        return nullptr;
+
+    memset(minor, 0, sizeof(minor));
+    memset(major, 0, sizeof(major));
+
+    /* set the right macro based on the selected standard */
+    ftepp_add_define(ftepp, nullptr, "GMQCC");
+    if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_FTEQCC) {
+        ftepp_add_define(ftepp, nullptr, "__STD_FTEQCC__");
+        /* 1.00 */
+        major[0] = '"';
+        major[1] = '1';
+        major[2] = '"';
+
+        minor[0] = '"';
+        minor[1] = '0';
+        minor[2] = '"';
+    } else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_GMQCC) {
+        ftepp_add_define(ftepp, nullptr, "__STD_GMQCC__");
+        util_snprintf(major, 32, "\"%d\"", GMQCC_VERSION_MAJOR);
+        util_snprintf(minor, 32, "\"%d\"", GMQCC_VERSION_MINOR);
+    } else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCCX) {
+        ftepp_add_define(ftepp, nullptr, "__STD_QCCX__");
+        util_snprintf(major, 32, "\"%d\"", GMQCC_VERSION_MAJOR);
+        util_snprintf(minor, 32, "\"%d\"", GMQCC_VERSION_MINOR);
+    } else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) {
+        ftepp_add_define(ftepp, nullptr, "__STD_QCC__");
+        /* 1.0 */
+        major[0] = '"';
+        major[1] = '1';
+        major[2] = '"';
+
+        minor[0] = '"';
+        minor[1] = '0';
+        minor[2] = '"';
+    }
+
+    ftepp_add_macro(ftepp, "__STD_VERSION_MINOR__", minor);
+    ftepp_add_macro(ftepp, "__STD_VERSION_MAJOR__", major);
+
+    /*
+     * We're going to just make __NULL__ nil, which works for 60% of the
+     * cases of __NULL_ for fteqcc.
+     */
+    ftepp_add_macro(ftepp, "__NULL__", "nil");
+
+    /* add all the math constants if they can be */
+    if (OPTS_FLAG(FTEPP_MATHDEFS)) {
+        for (i = 0; i < GMQCC_ARRAY_COUNT(ftepp_math_constants); i++)
+            if (!ftepp_macro_find(ftepp, ftepp_math_constants[i][0]))
+                ftepp_add_macro(ftepp, ftepp_math_constants[i][0], ftepp_math_constants[i][1]);
+    }
+
+    return ftepp;
+}
+
+void ftepp_add_define(ftepp_t *ftepp, const char *source, const char *name)
+{
+    ppmacro *macro;
+    lex_ctx_t ctx = { "__builtin__", 0, 0 };
+    ctx.file = source;
+    macro = ppmacro_new(ctx, name);
+    util_htset(ftepp->macros, name, macro);
+}
+
+const char *ftepp_get(ftepp_t *ftepp)
+{
+    return ftepp->output_string;
+}
+
+void ftepp_flush(ftepp_t *ftepp)
+{
+    ftepp_flush_do(ftepp);
+}
+
+void ftepp_finish(ftepp_t *ftepp)
+{
+    if (!ftepp)
+        return;
+    ftepp_delete(ftepp);
+}
diff --git a/gmqcc.h b/gmqcc.h
index 43153e9271167187bf3727005938fae15101b4fb..4e3fb399eba42b7604ac7b76b413d6cd74ec8e92 100644 (file)
--- a/gmqcc.h
+++ b/gmqcc.h
@@ -1,30 +1,14 @@
-/*
- * Copyright (C) 2012, 2013, 2014, 2015
- *     Dale Weiler
- *     Wolfgang Bumiller
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
- * this software and associated documentation files (the "Software"), to deal in
- * the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is furnished to do
- * so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
 #ifndef GMQCC_HDR
 #define GMQCC_HDR
+#include <vector>
+#include <string>
+#include <utility>
+#include <memory>
+using std::move;
 #include <stdarg.h>
 #include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
 #include <time.h>
 
 #define GMQCC_VERSION_MAJOR 0
@@ -178,34 +162,15 @@ GMQCC_IND_STRING(GMQCC_VERSION_PATCH) \
 #define GMQCC_ARRAY_COUNT(X) (sizeof(X) / sizeof((X)[0]))
 
 /* stat.c */
-void  stat_info          (void);
-char *stat_mem_strdup    (const char *, size_t,         const char *, bool);
-void  stat_mem_deallocate(void *,       size_t,         const char *);
-void *stat_mem_reallocate(void *,       size_t, size_t, const char *, const char *);
-void *stat_mem_allocate  (size_t, size_t, const char *, const char *);
+char *stat_mem_strdup(const char *, bool);
 
-#define mem_a(SIZE)              stat_mem_allocate  ((SIZE), __LINE__, __FILE__, #SIZE)
-#define mem_d(PTRN)              stat_mem_deallocate((void*)(PTRN), __LINE__, __FILE__)
-#define mem_r(PTRN, SIZE)        stat_mem_reallocate((void*)(PTRN), (SIZE), __LINE__, __FILE__, #SIZE)
-#define mem_af(SIZE, FILE, LINE) stat_mem_allocate  ((SIZE), (LINE), (FILE), #SIZE)
+#define mem_a(SIZE)              malloc(SIZE)
+#define mem_d(PTRN)              free((void*)PTRN)
+#define mem_r(PTRN, SIZE)        realloc((void*)PTRN, SIZE)
 
-/* TODO: rename to mem variations */
-#define util_strdup(SRC)         stat_mem_strdup((char*)(SRC), __LINE__, __FILE__, false)
-#define util_strdupe(SRC)        stat_mem_strdup((char*)(SRC), __LINE__, __FILE__, true)
+#define util_strdup(SRC)         stat_mem_strdup((char*)(SRC), false)
+#define util_strdupe(SRC)        stat_mem_strdup((char*)(SRC), true)
 
-/* util.c */
-
-/*
- * Microsoft implements against the spec versions of ctype.h. Which
- * means what ever the current set locale is will render the actual
- * results of say isalpha('A') wrong for what ever retarded locale
- * is used. Simalerly these are also implemented inefficently on
- * some toolchains and end up becoming actual library calls. Perhaps
- * this is why tools like yacc provide their own? Regardless implementing
- * these as functions is equally as silly, the call overhead is not
- * justified when this could happen on every character from an input
- * stream. We provide our own as macros for absolute inlinability.
- */
 #define util_isalpha(a) ((((unsigned)(a)|32)-'a') < 26)
 #define util_isdigit(a) (((unsigned)(a)-'0') < 10)
 #define util_islower(a) (((unsigned)(a)-'a') < 26)
@@ -237,9 +202,7 @@ const char *util_strerror(int err);
 const struct tm *util_localtime(const time_t *timer);
 const char      *util_ctime    (const time_t *timer);
 
-typedef struct fs_file_s fs_file_t;
-
-bool             util_isatty(fs_file_t *);
+bool             util_isatty(FILE *);
 size_t           hash(const char *key);
 
 /*
@@ -249,26 +212,26 @@ size_t           hash(const char *key);
  * us to use the array [] to access individual elements from the vector
  * opposed to using set/get methods.
  */
-typedef struct {
+struct vector_t {
     size_t  allocated;
     size_t  used;
 
     /* can be extended now! whoot */
-} vector_t;
+};
 
 /* hidden interface */
 void _util_vec_grow(void **a, size_t i, size_t s);
-void _util_vec_delete(void *vec, size_t line, const char *file);
+void _util_vec_delete(void *vec);
 
-#define GMQCC_VEC_WILLGROW(X,Y) ( \
+#define GMQCC_VEC_WILLGROW(X, Y) ( \
     ((!(X) || vec_meta(X)->used + Y >= vec_meta(X)->allocated)) ? \
         (void)_util_vec_grow(((void**)&(X)), (Y), sizeof(*(X))) : \
         (void)0                                                   \
 )
 
 /* exposed interface */
-#define vec_meta(A)       ((vector_t*)(((char *)(A)) - (sizeof(vector_t) + 4)))
-#define vec_free(A)       ((void)((A) ? (_util_vec_delete((void *)(A), __LINE__, __FILE__), (A) = NULL) : 0))
+#define vec_meta(A)       ((vector_t*)(((char *)(A)) - sizeof(vector_t)))
+#define vec_free(A)       ((void)((A) ? (_util_vec_delete((void *)(A)), (A) = nullptr) : 0))
 #define vec_push(A,V)     (GMQCC_VEC_WILLGROW((A),1), (A)[vec_meta(A)->used++] = (V))
 #define vec_size(A)       ((A) ? vec_meta(A)->used : 0)
 #define vec_add(A,N)      (GMQCC_VEC_WILLGROW((A),(N)), vec_meta(A)->used += (N), &(A)[vec_meta(A)->used-(N)])
@@ -284,106 +247,23 @@ typedef struct hash_table_s {
     struct hash_node_t **table;
 } hash_table_t, *ht;
 
-/*
- * hashtable implementation:
- *
- * Note:
- *      This was designed for pointers:  you manage the life of the object yourself
- *      if you do use this for non-pointers please be warned that the object may not
- *      be valid if the duration of it exceeds (i.e on stack).  So you need to allocate
- *      yourself, or put those in global scope to ensure duration is for the whole
- *      runtime.
- *
- * util_htnew(size)                             -- to make a new hashtable
- * util_htset(table, key, value, sizeof(value)) -- to set something in the table
- * util_htget(table, key)                       -- to get something from the table
- * util_htdel(table)                            -- to delete the table
- *
- * example of use:
- *
- * ht    foo  = util_htnew(1024);
- * int   data = 100;
- * char *test = "hello world\n";
- * util_htset(foo, "foo", (void*)&data);
- * util_gtset(foo, "bar", (void*)test);
- *
- * printf("foo: %d, bar %s",
- *     *((int *)util_htget(foo, "foo")),
- *      ((char*)util_htget(foo, "bar"))
- * );
- *
- * util_htdel(foo);
- */
 hash_table_t *util_htnew (size_t size);
-void          util_htrem (hash_table_t *ht, void (*callback)(void *data));
-void          util_htset (hash_table_t *ht, const char *key, void *value);
-void          util_htdel (hash_table_t *ht);
-size_t        util_hthash(hash_table_t *ht, const char *key);
-void          util_htseth(hash_table_t *ht, const char *key, size_t hash, void *value);
-void          util_htrmh (hash_table_t *ht, const char *key, size_t bin, void (*cb)(void*));
-void          util_htrm  (hash_table_t *ht, const char *key, void (*cb)(void*));
-
-void         *util_htget (hash_table_t *ht, const char *key);
-void         *util_htgeth(hash_table_t *ht, const char *key, size_t hash);
-
-int           util_snprintf(char *str, size_t, const char *fmt, ...);
-
-
-/* fs.c */
-#define FS_FILE_SEEK_SET  0
-#define FS_FILE_SEEK_CUR  1
-#define FS_FILE_SEEK_END  2
-#define FS_FILE_EOF      -1
-
-typedef struct fs_dir_s  fs_dir_t;
-/*typedef struct fs_file_s fs_file_t;*/
-typedef struct dirent    fs_dirent_t;
-
-void           fs_file_close  (fs_file_t *);
-int            fs_file_error  (fs_file_t *);
-int            fs_file_getc   (fs_file_t *);
-int            fs_file_printf (fs_file_t *, const char *, ...);
-int            fs_file_puts   (fs_file_t *, const char *);
-int            fs_file_seek   (fs_file_t *, long int, int);
-long           fs_file_tell   (fs_file_t *);
-int            fs_file_flush  (fs_file_t *);
-
-size_t         fs_file_read   (void *,        size_t, size_t, fs_file_t *);
-size_t         fs_file_write  (const void *,  size_t, size_t, fs_file_t *);
-
-fs_file_t     *fs_file_open   (const char *, const char *);
-int            fs_file_getline(char  **, size_t *, fs_file_t *);
-
-int            fs_dir_make    (const char *);
-fs_dir_t      *fs_dir_open    (const char *);
-int            fs_dir_close   (fs_dir_t *);
-fs_dirent_t   *fs_dir_read    (fs_dir_t *);
-
-
-/* correct.c */
-typedef struct correct_trie_s {
-    void                  *value;
-    struct correct_trie_s *entries;
-} correct_trie_t;
-
-correct_trie_t* correct_trie_new(void);
-
-typedef struct {
-    char   ***edits;
-    size_t  **lens;
-} correction_t;
-
-void  correct_del (correct_trie_t*, size_t **);
-void  correct_add (correct_trie_t*, size_t ***, const char *);
-char *correct_str (correction_t *, correct_trie_t*, const char *);
-void  correct_init(correction_t *);
-void  correct_free(correction_t *);
-
+void util_htrem(hash_table_t *ht, void (*callback)(void *data));
+void util_htset(hash_table_t *ht, const char *key, void *value);
+void util_htdel(hash_table_t *ht);
+size_t util_hthash(hash_table_t *ht, const char *key);
+void util_htseth(hash_table_t *ht, const char *key, size_t hash, void *value);
+void util_htrmh(hash_table_t *ht, const char *key, size_t bin, void (*cb)(void*));
+void util_htrm(hash_table_t *ht, const char *key, void (*cb)(void*));
+void *util_htget(hash_table_t *ht, const char *key);
+void *util_htgeth(hash_table_t *ht, const char *key, size_t hash);
+int util_snprintf(char *str, size_t, const char *fmt, ...);
+int util_getline(char  **, size_t *, FILE *);
 
 /* code.c */
 
 /* Note: if you change the order, fix type_sizeof in ir.c */
-enum {
+enum qc_type {
     TYPE_VOID     ,
     TYPE_STRING   ,
     TYPE_FLOAT    ,
@@ -425,12 +305,12 @@ extern const uint16_t type_eq_instr    [TYPE_COUNT];
 extern const uint16_t type_ne_instr    [TYPE_COUNT];
 extern const uint16_t type_not_instr   [TYPE_COUNT];
 
-typedef struct {
+struct prog_section_t {
     uint32_t offset;      /* Offset in file of where data begins  */
     uint32_t length;      /* Length of section (how many of)      */
-} prog_section_t;
+};
 
-typedef struct {
+struct prog_header_t {
     uint32_t       version;      /* Program version (6)     */
     uint16_t       crc16;
     uint16_t       skip;
@@ -442,7 +322,7 @@ typedef struct {
     prog_section_t strings;
     prog_section_t globals;
     uint32_t       entfield;     /* Number of entity fields */
-} prog_header_t;
+};
 
 /*
  * Each paramater incerements by 3 since vector types hold
@@ -459,37 +339,19 @@ typedef struct {
 #define OFS_PARM6     (OFS_PARM5 +3)
 #define OFS_PARM7     (OFS_PARM6 +3)
 
-typedef struct {
-    uint16_t opcode;
-
-    /* operand 1 */
-    union {
-        int16_t  s1; /* signed   */
-        uint16_t u1; /* unsigned */
-    } o1;
-    /* operand 2 */
-    union {
-        int16_t  s1; /* signed   */
-        uint16_t u1; /* unsigned */
-    } o2;
-    /* operand 3 */
-    union {
-        int16_t  s1; /* signed   */
-        uint16_t u1; /* unsigned */
-    } o3;
+union operand_t {
+    int16_t s1;
+    uint16_t u1;
+};
 
-    /*
-     * This is the same as the structure in darkplaces
-     * {
-     *     unsigned short op;
-     *     short          a,b,c;
-     * }
-     * But this one is more sane to work with, and the
-     * type sizes are guranteed.
-     */
-} prog_section_statement_t;
+struct prog_section_statement_t {
+    uint16_t opcode;
+    operand_t o1;
+    operand_t o2;
+    operand_t o3;
+};
 
-typedef struct {
+struct prog_section_both_t {
     /*
      * The types:
      * 0 = ev_void
@@ -505,7 +367,7 @@ typedef struct {
     uint16_t type;
     uint16_t offset;
     uint32_t name;
-} prog_section_both_t;
+};
 
 typedef prog_section_both_t prog_section_def_t;
 typedef prog_section_both_t prog_section_field_t;
@@ -514,7 +376,7 @@ typedef prog_section_both_t prog_section_field_t;
 #define DEF_SAVEGLOBAL (1<<15)
 #define DEF_TYPEMASK   ((1<<15)-1)
 
-typedef struct {
+struct prog_section_function_t {
     int32_t   entry;      /* in statement table for instructions  */
     uint32_t  firstlocal; /* First local in local table           */
     uint32_t  locals;     /* Total ints of params + locals        */
@@ -523,7 +385,7 @@ typedef struct {
     uint32_t  file;       /* file of the source file              */
     int32_t   nargs;      /* number of arguments                  */
     uint8_t   argsize[8]; /* size of arguments (keep 8 always?)   */
-} prog_section_function_t;
+};
 
 /*
  * Instructions
@@ -629,40 +491,44 @@ enum {
 /* TODO: elide */
 extern const char *util_instr_str[VINSTR_END];
 
-void util_swap_header     (prog_header_t              *code_header);
-void util_swap_statements (prog_section_statement_t   *statements);
-void util_swap_defs_fields(prog_section_both_t        *section);
-void util_swap_functions  (prog_section_function_t    *functions);
-void util_swap_globals    (int32_t                    *globals);
+void util_swap_header(prog_header_t &code_header);
+void util_swap_statements(std::vector<prog_section_statement_t> &statements);
+void util_swap_defs_fields(std::vector<prog_section_both_t> &section);
+void util_swap_functions(std::vector<prog_section_function_t> &functions);
+void util_swap_globals(std::vector<int32_t> &globals);
 
-typedef float    qcfloat_t;
-typedef int32_t  qcint_t;
+typedef float qcfloat_t;
+typedef int32_t qcint_t;
 typedef uint32_t qcuint_t;
 
-typedef struct {
-    prog_section_statement_t *statements;
-    int                      *linenums;
-    int                      *columnnums;
-    prog_section_def_t       *defs;
-    prog_section_field_t     *fields;
-    prog_section_function_t  *functions;
-    int                      *globals;
-    char                     *chars;
-    uint16_t                  crc;
-    uint32_t                  entfields;
-    ht                        string_cache;
-    qcint_t                   string_cached_empty;
-} code_t;
+struct code_t {
+    void* operator new(std::size_t);
+    void operator delete(void*);
+    code_t();
+    ~code_t();
+    std::vector<prog_section_statement_t> statements;
+    std::vector<int> linenums;
+    std::vector<int> columnnums;
+    std::vector<prog_section_def_t> defs;
+    std::vector<prog_section_field_t> fields;
+    std::vector<prog_section_function_t> functions;
+    std::vector<int> globals;
+    std::vector<char> chars;
+    uint16_t crc = 0;
+    uint32_t entfields = 0;
+    ht string_cache;
+    qcint_t string_cached_empty = 0;
+};
 
 /*
  * A shallow copy of a lex_file to remember where which ast node
  * came from.
  */
-typedef struct {
+struct lex_ctx_t {
     const char *file;
-    size_t      line;
-    size_t      column;
-} lex_ctx_t;
+    size_t line;
+    size_t column;
+};
 
 /*
  * code_write          -- writes out the compiled file
@@ -701,8 +567,8 @@ enum {
     LVL_ERROR
 };
 
-fs_file_t *con_default_out(void);
-fs_file_t *con_default_err(void);
+FILE *con_default_out(void);
+FILE *con_default_err(void);
 
 void con_vprintmsg (int level, const char *name, size_t line, size_t column, const char *msgtype, const char *msg, va_list ap);
 void con_printmsg  (int level, const char *name, size_t line, size_t column, const char *msgtype, const char *msg, ...);
@@ -713,7 +579,6 @@ void con_close (void);
 void con_init  (void);
 void con_reset (void);
 void con_color (int);
-int  con_change(const char *, const char *);
 int  con_verr  (const char *, va_list);
 int  con_vout  (const char *, va_list);
 int  con_err   (const char *, ...);
@@ -724,15 +589,31 @@ extern size_t compile_errors;
 extern size_t compile_Werrors;
 extern size_t compile_warnings;
 
-void /********/ compile_error   (lex_ctx_t ctx, /*LVL_ERROR*/ const char *msg, ...);
+void /********/ compile_error_  (lex_ctx_t ctx, /*LVL_ERROR*/ const char *msg, ...);
 void /********/ vcompile_error  (lex_ctx_t ctx, /*LVL_ERROR*/ const char *msg, va_list ap);
-bool GMQCC_WARN compile_warning (lex_ctx_t ctx, int warntype, const char *fmt, ...);
+bool GMQCC_WARN compile_warning_(lex_ctx_t ctx, int warntype, const char *fmt, ...);
 bool GMQCC_WARN vcompile_warning(lex_ctx_t ctx, int warntype, const char *fmt, va_list ap);
 void            compile_show_werrors(void);
 
+template <typename T>
+inline constexpr const T formatNormalize(const T argument) { return argument; }
+
+inline const char *formatNormalize(const std::string &argument) {
+    return argument.c_str();
+}
+
+template<typename... Ts>
+inline bool GMQCC_WARN compile_warning(lex_ctx_t ctx, int warntype, const char *fmt, const Ts&... ts) {
+    return compile_warning_(ctx, warntype, fmt, formatNormalize(ts)...);
+}
+template<typename... Ts>
+inline void /********/ compile_error   (lex_ctx_t ctx, /*LVL_ERROR*/ const char *msg, const Ts&... ts) {
+    return compile_error_(ctx, msg, formatNormalize(ts)...);
+}
+
 /* ir.c */
 /* TODO: cleanup */
-enum store_types {
+enum store_type {
     store_global,
     store_local,  /* local, assignable for now, should get promoted later */
     store_param,  /* parameters, they are locals with a fixed position */
@@ -740,9 +621,9 @@ enum store_types {
     store_return  /* unassignable, at OFS_RETURN */
 };
 
-typedef struct {
+struct vec3_t {
     qcfloat_t x, y, z;
-} vec3_t;
+};
 
 /* exec.c */
 
@@ -779,27 +660,27 @@ enum {
 #define VMXF_TRACE   0x0001     /* trace: print statements before executing */
 #define VMXF_PROFILE 0x0002     /* profile: increment the profile counters */
 
-struct qc_program_s;
-typedef int (*prog_builtin_t)(struct qc_program_s *prog);
+struct qc_program_t;
+typedef int (*prog_builtin_t)(qc_program_t *prog);
 
-typedef struct {
-    qcint_t                    stmt;
-    size_t                   localsp;
+struct qc_exec_stack_t {
+    qcint_t stmt;
+    size_t localsp;
     prog_section_function_t *function;
-} qc_exec_stack_t;
+};
 
-typedef struct qc_program_s {
-    char                     *filename;
-    prog_section_statement_t *code;
-    prog_section_def_t       *defs;
-    prog_section_def_t       *fields;
-    prog_section_function_t  *functions;
-    char                     *strings;
-    qcint_t                  *globals;
-    qcint_t                  *entitydata;
-    bool                     *entitypool;
+struct qc_program_t {
+    char *filename;
+    std::vector<prog_section_statement_t> code;
+    std::vector<prog_section_def_t> defs;
+    std::vector<prog_section_def_t> fields;
+    std::vector<prog_section_function_t> functions;
+    std::vector<char> strings;
+    std::vector<qcint_t> globals;
+    qcint_t *entitydata;
+    bool *entitypool;
 
-    const char*              *function_stack;
+    const char* *function_stack;
 
     uint16_t crc16;
 
@@ -839,7 +720,7 @@ typedef struct qc_program_s {
     } cached_globals;
 
     bool supports_state; /* is INSTR_STATE supported? */
-} qc_program_t;
+};
 
 qc_program_t*       prog_load      (const char *filename, bool ignoreversion);
 void                prog_delete    (qc_program_t *prog);
@@ -848,27 +729,27 @@ const char*         prog_getstring (qc_program_t *prog, qcint_t str);
 prog_section_def_t* prog_entfield  (qc_program_t *prog, qcint_t off);
 prog_section_def_t* prog_getdef    (qc_program_t *prog, qcint_t off);
 qcany_t*            prog_getedict  (qc_program_t *prog, qcint_t e);
-qcint_t               prog_tempstring(qc_program_t *prog, const char *_str);
+qcint_t             prog_tempstring(qc_program_t *prog, const char *_str);
 
 
 /* parser.c */
-struct parser_s;
-struct parser_s *parser_create        (void);
-bool             parser_compile_file  (struct parser_s *parser, const char *);
-bool             parser_compile_string(struct parser_s *parser, const char *, const char *, size_t);
-bool             parser_finish        (struct parser_s *parser, const char *);
-void             parser_cleanup       (struct parser_s *parser);
+struct parser_t;
+parser_t *parser_create(void);
+bool parser_compile_file(parser_t *parser, const char *);
+bool parser_compile_string(parser_t *parser, const char *, const char *, size_t);
+bool parser_finish(parser_t *parser, const char *);
+void parser_cleanup(parser_t *parser);
 
 /* ftepp.c */
-struct ftepp_s;
-struct ftepp_s *ftepp_create           (void);
-bool            ftepp_preprocess_file  (struct ftepp_s *ftepp, const char *filename);
-bool            ftepp_preprocess_string(struct ftepp_s *ftepp, const char *name, const char *str);
-void            ftepp_finish           (struct ftepp_s *ftepp);
-const char     *ftepp_get              (struct ftepp_s *ftepp);
-void            ftepp_flush            (struct ftepp_s *ftepp);
-void            ftepp_add_define       (struct ftepp_s *ftepp, const char *source, const char *name);
-void            ftepp_add_macro        (struct ftepp_s *ftepp, const char *name,   const char *value);
+struct ftepp_t;
+ftepp_t *ftepp_create           (void);
+bool ftepp_preprocess_file  (ftepp_t *ftepp, const char *filename);
+bool ftepp_preprocess_string(ftepp_t *ftepp, const char *name, const char *str);
+void ftepp_finish(ftepp_t *ftepp);
+const char *ftepp_get(ftepp_t *ftepp);
+void ftepp_flush(ftepp_t *ftepp);
+void ftepp_add_define(ftepp_t *ftepp, const char *source, const char *name);
+void ftepp_add_macro(ftepp_t *ftepp, const char *name,   const char *value);
 
 /* main.c */
 
@@ -876,10 +757,10 @@ void            ftepp_add_macro        (struct ftepp_s *ftepp, const char *name,
 /* Helpers to allow for a whole lot of flags. Otherwise we'd limit
  * to 32 or 64 -f options...
  */
-typedef struct {
+struct longbit {
     size_t  idx; /* index into an array of 32 bit words */
     uint8_t bit; /* bit index for the 8 bit group idx points to */
-} longbit;
+};
 #define LONGBIT(bit) { ((bit)/32), ((bit)%32) }
 #define LONGBIT_SET(B, I) ((B).idx = (I)/32, (B).bit = ((I)%32))
 #else
@@ -894,10 +775,10 @@ int utf8_from(char *, utf8ch_t);
 int utf8_to(utf8ch_t *, const unsigned char *, size_t);
 
 /* opts.c */
-typedef struct {
+struct opts_flag_def_t {
     const char *name;
     longbit     bit;
-} opts_flag_def_t;
+};
 
 bool opts_setflag  (const char *, bool);
 bool opts_setwarn  (const char *, bool);
@@ -951,30 +832,27 @@ extern const unsigned int    opts_opt_oflag[COUNT_OPTIMIZATIONS+1];
 extern unsigned int          opts_optimizationcount[COUNT_OPTIMIZATIONS];
 
 /* other options: */
-typedef enum {
+enum {
     COMPILER_QCC,     /* circa  QuakeC */
     COMPILER_FTEQCC,  /* fteqcc QuakeC */
     COMPILER_QCCX,    /* qccx   QuakeC */
     COMPILER_GMQCC    /* this   QuakeC */
-} opts_std_t;
+};
 
-typedef struct {
+struct opt_value_t {
     union {
-        bool     b;
+        bool b;
         uint16_t u16;
         uint32_t u32;
-
         union {
-            char       *p;
+            char *p;
             const char *c;
         } str;
     } data;
-
     bool allocated;
-} opt_value_t;
-
+};
 
-typedef struct {
+struct opts_cmd_t {
     opt_value_t  options      [OPTION_COUNT];
     uint32_t     flags        [1 + (COUNT_FLAGS         / 32)];
     uint32_t     warn         [1 + (COUNT_WARNINGS      / 32)];
@@ -983,7 +861,7 @@ typedef struct {
     uint32_t     werror_backup[1 + (COUNT_WARNINGS      / 32)];
     uint32_t     optimization [1 + (COUNT_OPTIMIZATIONS / 32)];
     bool         optimizeoff; /* True when -O0 */
-} opts_cmd_t;
+};
 
 extern opts_cmd_t opts;
 
diff --git a/hash.c b/hash.c
deleted file mode 100644 (file)
index 9aba831..0000000
--- a/hash.c
+++ /dev/null
@@ -1,336 +0,0 @@
-/*
- * Copyright (C) 2012, 2013, 2014, 2015
- *     Dale Weiler
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
- * this software and associated documentation files (the "Software"), to deal in
- * the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is furnished to do
- * so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-#include "gmqcc.h"
-#include <limits.h>
-
-#if defined(_MSC_VER)
-#   define HASH_ROTL32(X, Y) _rotl((X), (Y))
-#elif defined (__GNUC__) && (defined(__i386__) || defined(__amd64__))
-    static GMQCC_FORCEINLINE uint32_t hash_rotl32(volatile uint32_t x, int8_t r) {
-        __asm__ __volatile__ ("roll %1,%0" : "+r"(x) : "c"(r));
-        return x;
-    }
-#   define HASH_ROTL32(X, Y) hash_rotl32((volatile uint32_t)(X), (Y))
-#else
-#   define HASH_ROTL32(X, Y) (((X) << (Y)) | ((X) >> (32 - (Y))))
-#endif
-
-/*
- * This is a version of the Murmur3 hashing function optimized for various
- * compilers/architectures. It uses the traditional Murmur2 mix staging
- * but fixes the mix staging inner loops.
- *
- * Murmur 2 contains an inner loop such as:
- * while (l >= 4) {
- *      u32 k = *(u32*)d;
- *      k *= m;
- *      k ^= k >> r;
- *      k *= m;
- *
- *      h *= m;
- *      h ^= k;
- *      d += 4;
- *      l -= 4;
- * }
- *
- * The two u32s that form the key are the same value for x
- * this premix stage will perform the same results for both values. Unrolled
- * this produces just:
- *  x *= m;
- *  x ^= x >> r;
- *  x *= m;
- *
- *  h *= m;
- *  h ^= x;
- *  h *= m;
- *  h ^= x;
- *
- * This appears to be fine, except what happens when m == 1? well x
- * cancels out entierly, leaving just:
- *  x ^= x >> r;
- *  h ^= x;
- *  h ^= x;
- *
- * So all keys hash to the same value, but how often does m == 1?
- * well, it turns out testing x for all possible values yeilds only
- * 172,013,942 unique results instead of 2^32. So nearly ~4.6 bits
- * are cancelled out on average!
- *
- * This means we have a 14.5% higher chance of collision. This is where
- * Murmur3 comes in to save the day.
- */
-static GMQCC_FORCEINLINE uint32_t hash_murmur_mix32(uint32_t hash) {
-    hash ^= hash >> 16;
-    hash *= 0x85EBCA6B;
-    hash ^= hash >> 13;
-    hash *= 0xC2B2AE35;
-    hash ^= hash >> 16;
-    return hash;
-}
-
-/*
- * These constants were calculated with SMHasher to determine the best
- * case senario for Murmur3:
- *  http://code.google.com/p/smhasher/
- */
-#define HASH_MURMUR_MASK1 0xCC9E2D51
-#define HASH_MURMUR_MASK2 0x1B873593
-#define HASH_MURMUR_SEED  0x9747B28C
-
-#if PLATFORM_BYTE_ORDER == GMQCC_BYTE_ORDER_LITTLE
-#   define HASH_MURMUR_SAFEREAD(PTR) (*((uint32_t*)(PTR)))
-#elif PLATFORM_BYTE_ORDER == GMQCC_BYTE_ORDER_BIG
-#   if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR >= 3))
-#       define HASH_MURMUR_SAFEREAD(PTR) (__builtin_bswap32(*((uint32_t*)(PTR))))
-#   endif
-#endif
-/* Process individual bytes at this point since the endianess isn't known. */
-#ifndef HASH_MURMUR_SAFEREAD
-#   define HASH_MURMUR_SAFEREAD(PTR) ((PTR)[0] | (PTR)[1] << 8 | (PTR)[2] << 16 | (PTR)[3] << 24)
-#endif
-
-#define HASH_MURMUR_BLOCK(H, K)                        \
-    do {                                               \
-        K *= HASH_MURMUR_MASK1;                        \
-        K  = HASH_ROTL32(K, 15);                       \
-        K *= HASH_MURMUR_MASK2;                        \
-        H ^= K;                                        \
-        H  = HASH_ROTL32(H, 13);                       \
-        H  = H * 5 + 0xE6546B64;                       \
-    } while (0)
-#define HASH_MURMUR_BYTES(COUNT, H, C, N, PTR, LENGTH) \
-    do {                                               \
-        int i = COUNT;                                 \
-        while (i--) {                                  \
-            C = C >> 8 | *PTR++ << 24;                 \
-            N++;                                       \
-            LENGTH--;                                  \
-            if (N == 4) {                              \
-                HASH_MURMUR_BLOCK(H, C);               \
-                N = 0;                                 \
-            }                                          \
-        }                                              \
-    } while (0)
-#define HASH_MURMUR_TAIL(P, Z, H, C, N, PTR, LEN)      \
-    do {                                               \
-        LEN -= LEN/4*4;                                \
-        HASH_MURMUR_BYTES(LEN, H, C, N, PTR, LEN);     \
-        *P = H;                                        \
-        *Z = ((C) & ~0xFF) | (N);                      \
-    } while (0)
-
-#if PLATFORM_BYTE_ORDER == GMQCC_BYTE_ORDER_LITTLE
-static GMQCC_FORCEINLINE void hash_murmur_process(uint32_t *ph1, uint32_t *carry, const void *key, int length) {
-    uint32_t h1 = *ph1;
-    uint32_t c  = *carry;
-
-    const uint8_t *ptr = (uint8_t*)key;
-    const uint8_t *end;
-
-    int n  = c & 3;
-    int it = (4 - n) & 3;
-    if (it && it <= length)
-        HASH_MURMUR_BYTES(it, h1, c, n, ptr, length);
-
-    end = ptr + length/4*4;
-    for (; ptr < end; ptr += 4) {
-        uint32_t k1 = HASH_MURMUR_SAFEREAD(ptr);
-        HASH_MURMUR_BLOCK(h1, k1);
-    }
-    HASH_MURMUR_TAIL(ph1, carry, h1, c, n, ptr, length);
-}
-#else
-static GMQCC_FORCEINLINE void hash_murmur_process(uint32_t *ph1, uint32_t *carry, const void *key, int length) {
-    uint32_t k1;
-    uint32_t h1 = *ph1;
-    uint32_t c  = *carry;
-
-    const uint8_t *ptr = (uint8_t*)key;
-    const uint8_t *end;
-
-    int n  = c & 3;
-    int it = -(long)ptr & 3;
-    if (it && it <= length)
-        HASH_MURMUR_BYTES(it, h1, c, n, ptr, length);
-
-    end = ptr + length / 4 * 4;
-    switch (n) {
-        case 0:
-            for (; ptr < end; ptr += 4) {
-                k1 = HASH_MURMUR_SAFEREAD(ptr);
-                HASH_MURMUR_BLOCK(h1, k1);
-            }
-            break;
-
-#       define NEXT(N, RIGHT, LEFT)                  \
-            case N:                                  \
-                for (; ptr < end; ptr += 4) {        \
-                    k1  = c >> RIGHT;                \
-                    c   = HASH_MURMUR_SAFEREAD(ptr); \
-                    k1 |= c << LEFT;                 \
-                    HASH_MURMUR_BLOCK(h1, k1);       \
-                }                                    \
-                break
-        NEXT(1, 24, 8);
-        NEXT(2, 16, 16);
-        NEXT(3, 8,  24);
-        #undef NEXT
-    }
-    HASH_MURMUR_TAIL(ph1, carry, h1, c, n, ptr, length);
-}
-#endif
-
-static GMQCC_FORCEINLINE uint32_t hash_murmur_result(uint32_t hash, uint32_t carry, size_t length) {
-    uint32_t k1;
-    int n = carry & 3;
-    if (GMQCC_LIKELY(n)) {
-        k1    = carry >> (4 - n) * 8;
-        k1   *= HASH_MURMUR_MASK1;
-        k1    = HASH_ROTL32(k1, 15);
-        k1   *= HASH_MURMUR_MASK2;
-        hash ^= k1;
-    }
-    hash ^= length;
-    hash  = hash_murmur_mix32(hash);
-
-    return hash;
-}
-
-static GMQCC_FORCEINLINE uint32_t hash_murmur(const void *GMQCC_RESTRICT key, size_t length) {
-    uint32_t hash  = HASH_MURMUR_SEED;
-    uint32_t carry = 0;
-    hash_murmur_process(&hash, &carry, key, length);
-    return hash_murmur_result(hash, carry, length);
-}
-
-/*
- * The following hash function implements it's own strlen to avoid using libc's
- * which isn't always slow but isn't always fastest either.
- *
- * Some things to note about this strlen that are otherwise confusing to grasp
- * at first is that it does intentionally depend on undefined behavior.
- *
- * The first step to the strlen is to ensure alignment before checking words,
- * without this step we risk crossing a page boundry with the word check and
- * that would cause a crash.
- *
- * The second step to the strlen contains intentional undefined behavior. When
- * accessing a word of any size, the first byte of that word is accessible if
- * and only if the whole word is accessible because words are aligned. This is
- * indicated by the fact that size / alignment always divides the page size.
- * One could argue that an architecture exists where size_t and alignment are
- * different, if that were the case, the alignment will always assume to be the
- * size of the type (size_t). So it's always safe in that regard.
- *
- * In other words, an aligned 2^n load cannot cross a page boundry unless
- * n > log2(PAGE_SIZE). There are no known architectures which support such
- * a wide load larger than PAGE_SIZE.
- *
- * Valgrind and address sanatizer may choke on this because they're strictly
- * trying to find bugs, it's a false positive to assume this is a bug when it's
- * intentional. To prevent these false positives, both things need to be taught
- * about the intentional behavior; for address sanatizer this can be done with
- * a compiler attribute, effectively preventing the function from being
- * instrumented. Valgrind requires a little more work as there is no way to
- * downright prevent a function from being instrumented, instead we can mark
- * + sizeof(size_t) bytes ahead of each byte we're reading as we calculate
- * the length of the string, then we can make that additional + sizeof(size_t)
- * on the end undefined after the length has been calculated.
- *
- * If the compiler doesn't have the attribute to disable address sanatizer
- * instrumentation we fall back to using libc's strlen instead. This isn't the
- * best solution. On windows we can assume this method always because neither
- * address sanatizer or valgrind exist.
- */
-
-/* Some compilers expose this */
-#if defined(__has_feature)
-#   if __has_feature(address_sanitizer)
-#       define ASAN_DISABLE __attribute__((no_sanitize_address))
-#       define HAS_ASAN_DISABLE
-#   endif
-#endif
-
-/* If they don't try to find by version the attriubte was introduces */
-#if defined(__GNUC__) && __GNUC__ >= 4
-#   define ASAN_DISABLE __attribute__((no_sanitize_address))
-#   define HAS_ASAN_DISABLE
-#elif defined(__clang__) && __clang_major__ >= 3
-#   define ASAN_DISABLE __attribute__((no_sanatize_address))
-#   define HAS_ASAN_DISABLE
-/* On windows asan doesn't exist */
-#elif defined(_WIN32)
-#   define ASAN_DISABLE /* nothing */
-#   define HAS_ASAN_DISABLE
-#endif
-
-#ifndef HAS_ASAN_DISABLE
-#   include <string.h>
-#endif
-
-#ifndef NVALGRIND
-#   include <valgrind/valgrind.h>
-#   include <valgrind/memcheck.h>
-#else
-#   define VALGRIND_MAKE_MEM_DEFINED(PTR, REDZONE_SIZE)
-#   define VALGRIND_MAKE_MEM_NOACCESS(PTR, REDZONE_SIZE)
-#endif
-
-#ifdef HAS_ASAN_DISABLE
-#define STRLEN_ALIGN (sizeof(size_t))
-#define STRLEN_ONES ((size_t)-1/UCHAR_MAX)
-#define STRLEN_HIGHS (STRLEN_ONES * (UCHAR_MAX/2+1))
-#define STRLEN_HASZERO(X) (((X)-STRLEN_ONES) & ~(X) & STRLEN_HIGHS)
-
-static ASAN_DISABLE size_t hash_strlen(const char *key) {
-    const char *s = key;
-    const char *a = s;
-    const size_t *w;
-
-    for (; (uintptr_t)s % STRLEN_ALIGN; s++) {
-        if (*s)
-            continue;
-        return s-a;
-    }
-
-    VALGRIND_MAKE_MEM_DEFINED(s, STRLEN_ALIGN);
-    for (w = (const size_t *)s; !STRLEN_HASZERO(*w); w++) {
-        /* Make the next word legal to access */
-        VALGRIND_MAKE_MEM_DEFINED(w + STRLEN_ALIGN, STRLEN_ALIGN);
-    }
-
-    for (s = (const char *)w; *s; s++);
-
-    /* It's not legal to access this area anymore */
-    VALGRIND_MAKE_MEM_NOACCESS(s + 1, STRLEN_ALIGN);
-    return s-a;
-}
-#else
-static GMQCC_INLINE size_t hash_strlen(const char *key) {
-    return strlen(key);
-}
-#endif
-
-size_t hash(const char *key) {
-    return hash_murmur((const void *)key, hash_strlen(key));
-}
diff --git a/include.mk b/include.mk
deleted file mode 100644 (file)
index 1f90111..0000000
+++ /dev/null
@@ -1,144 +0,0 @@
-# default directories and paths
-DESTDIR :=
-PREFIX  := /usr/local
-BINDIR  := $(PREFIX)/bin
-DATADIR := $(PREFIX)/share
-MANDIR  := $(DATADIR)/man
-
-# default flags
-CFLAGS  += -Wall -Wextra -Werror -Wstrict-aliasing -Wno-attributes -O2
-
-# compiler
-CC      ?= clang
-
-# linker flags and optional additional libraries if required
-LDFLAGS +=
-LIBS    += -lm
-
-#common objects
-COMMON   = ansi.o util.o hash.o stat.o fs.o opts.o conout.o
-
-#optional flags
-OPTIONAL_CFLAGS  :=
-OPTIONAL_LDFLAGS :=
-
-#objects
-OBJ_C = $(COMMON) main.o lexer.o parser.o code.o ast.o ir.o ftepp.o utf8.o correct.o fold.o intrin.o
-OBJ_P = $(COMMON) pak.o
-OBJ_T = $(COMMON) test.o
-OBJ_X = $(COMMON) exec.o
-
-#gource flags
-GOURCEFLAGS =                 \
-    --date-format "%d %B, %Y" \
-    --seconds-per-day 0.01    \
-    --auto-skip-seconds 1     \
-    --title "GMQCC"           \
-    --key                     \
-    --camera-mode overview    \
-    --highlight-all-users     \
-    --file-idle-time 0        \
-    --hide progress,mouse     \
-    --stop-at-end             \
-    --max-files 99999999999   \
-    --max-file-lag 0.000001   \
-    --bloom-multiplier 1.3    \
-    --logo doc/html/gmqcc.png \
-    -1280x720
-
-#ffmpeg flags for gource
-FFMPEGFLAGS=                  \
-    -y                        \
-    -r 60                     \
-    -f image2pipe             \
-    -vcodec ppm               \
-    -i -                      \
-    -vcodec libx264           \
-    -preset ultrafast         \
-    -crf 1                    \
-    -threads 0                \
-    -bf 0
-
-#splint flags
-SPLINTFLAGS =                 \
-    -preproc                  \
-    -redef                    \
-    -noeffect                 \
-    -nullderef                \
-    -usedef                   \
-    -type                     \
-    -mustfreeonly             \
-    -nullstate                \
-    -varuse                   \
-    -mustfreefresh            \
-    -compdestroy              \
-    -compmempass              \
-    -nullpass                 \
-    -onlytrans                \
-    -predboolint              \
-    -boolops                  \
-    -incondefs                \
-    -macroredef               \
-    -retvalint                \
-    -nullret                  \
-    -predboolothers           \
-    -globstate                \
-    -dependenttrans           \
-    -branchstate              \
-    -compdef                  \
-    -temptrans                \
-    -usereleased              \
-    -warnposix                \
-    +charindex                \
-    -kepttrans                \
-    -unqualifiedtrans         \
-    +matchanyintegral         \
-    +voidabstract             \
-    -nullassign               \
-    -unrecog                  \
-    -casebreak                \
-    -retvalbool               \
-    -retvalother              \
-    -mayaliasunique           \
-    -realcompare              \
-    -observertrans            \
-    -abstract                 \
-    -statictrans              \
-    -castfcnptr               \
-    -shiftimplementation      \
-    -shiftnegative            \
-    -boolcompare              \
-    -infloops                 \
-    -sysunrecog
-
-#always the right rule
-default: all
-
-#uninstall rule
-uninstall:
-       rm -f $(DESTDIR)$(BINDIR)/gmqcc
-       rm -f $(DESTDIR)$(BINDIR)/qcvm
-       rm -f $(DESTDIR)$(BINDIR)/gmqpak
-       rm -f $(DESTDIR)$(MANDIR)/man1/gmqcc.1
-       rm -f $(DESTDIR)$(MANDIR)/man1/qcvm.1
-       rm -f $(DESTDIR)$(MANDIR)/man1/gmqpak.1
-
-#style rule
-STYLE_MATCH = \( -name '*.[ch]' -or -name '*.def' -or -name '*.qc' \)
-
-# splint cannot parse the MSVC source
-SPLINT_MATCH = \( -name '*.[ch]' -and ! -name 'msvc.c' -and ! -path './doc/*' \)
-
-style:
-       find . -type f $(STYLE_MATCH) -exec sed -i 's/ *$$//' '{}' ';'
-       find . -type f $(STYLE_MATCH) -exec sed -i -e '$$a\' '{}' ';'
-       find . -type f $(STYLE_MATCH) -exec sed -i 's/\t/    /g' '{}' ';'
-
-splint:
-       @splint $(SPLINTFLAGS) `find . -type f $(SPLINT_MATCH)`
-
-gource:
-       @gource $(GOURCEFLAGS)
-
-gource-record:
-       @gource $(GOURCEFLAGS) -o - | ffmpeg $(FFMPEGFLAGS) gource.mp4
diff --git a/intrin.c b/intrin.c
deleted file mode 100644 (file)
index 9ddb1a2..0000000
--- a/intrin.c
+++ /dev/null
@@ -1,2113 +0,0 @@
-/*
- * Copyright (C) 2012, 2013, 2014, 2015
- *     Dale Weiler
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
- * this software and associated documentation files (the "Software"), to deal in
- * the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is furnished to do
- * so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-#include <string.h>
-#include "parser.h"
-
-/*
- * Provides all the "intrinsics" / "builtins" for GMQCC. These can do
- * a few things, they can provide fall back implementations for math
- * functions if the definitions don't exist for some given engine. Or
- * then can determine definitions for existing builtins, and simply
- * wrap back to them instead.  This is like a "portable" intrface that
- * is entered when -fintrin is used (causing all existing builtins to
- * be ignored by the compiler and instead interface through here.
- */
-#define intrin_ctx(I) parser_ctx((I)->parser)
-
-static GMQCC_INLINE ast_function *intrin_value(intrin_t *intrin, ast_value **out, const char *name, qcint_t vtype) {
-    ast_value    *value = NULL;
-    ast_function *func  = NULL;
-    char          buffer[1024];
-    char          stype [1024];
-
-    util_snprintf(buffer, sizeof(buffer), "__builtin_%s", name);
-    util_snprintf(stype,  sizeof(stype),   "<%s>",        type_name[vtype]);
-
-    value                    = ast_value_new(intrin_ctx(intrin), buffer, TYPE_FUNCTION);
-    value->intrinsic         = true;
-    value->expression.next   = (ast_expression*)ast_value_new(intrin_ctx(intrin), stype, vtype);
-    func                     = ast_function_new(intrin_ctx(intrin), buffer, value);
-    value->expression.flags |= AST_FLAG_ERASEABLE;
-
-    *out = value;
-    return func;
-}
-
-static GMQCC_INLINE void intrin_reg(intrin_t *intrin, ast_value *const value, ast_function *const func) {
-    vec_push(intrin->parser->functions, func);
-    vec_push(intrin->parser->globals,   (ast_expression*)value);
-}
-
-#define QC_POW_EPSILON 0.00001f
-
-/*
- * since some intrinsics depend on each other there is the possibility
- * that an intrinsic will fail to get a 'depended' function that a
- * builtin needs, causing some dependency in the chain to have a NULL
- * function. This will cause a segmentation fault at code generation,
- * even though an error was raised. To contiue to allow it (instead
- * of stopping compilation right away). We need to return from the
- * parser, before compilation stops after all the collected errors.
- */
-static ast_expression *intrin_func_self(intrin_t *intrin, const char *name, const char *from);
-static ast_expression *intrin_nullfunc(intrin_t *intrin) {
-    ast_value    *value = NULL;
-    ast_function *func  = intrin_value(intrin, &value, NULL, TYPE_VOID);
-    intrin_reg(intrin, value, func);
-    return (ast_expression*)value;
-}
-
-static ast_expression *intrin_isfinite(intrin_t *intrin) {
-    /*
-     * float isfinite(float x) {
-     *     return !(isnan(x) || isinf(x));
-     * }
-     */
-    ast_value    *value     = NULL;
-    ast_value    *x         = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT);
-    ast_function *func      = intrin_value(intrin, &value, "isfinite", TYPE_FLOAT);
-    ast_call     *callisnan = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "isnan", "isfinite"));
-    ast_call     *callisinf = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "isinf", "isfinite"));
-    ast_block    *block     = ast_block_new(intrin_ctx(intrin));
-
-    /* float x; */
-    vec_push(value->expression.params, x);
-
-    /* <callisnan> = isnan(x); */
-    vec_push(callisnan->params, (ast_expression*)x);
-
-    /* <callisinf> = isinf(x); */
-    vec_push(callisinf->params, (ast_expression*)x);
-
-    /* return (!<callisnan> || <callisinf>); */
-    vec_push(block->exprs,
-        (ast_expression*)ast_return_new(
-            intrin_ctx(intrin),
-            (ast_expression*)ast_unary_new(
-                intrin_ctx(intrin),
-                INSTR_NOT_F,
-                (ast_expression*)ast_binary_new(
-                    intrin_ctx(intrin),
-                    INSTR_OR,
-                    (ast_expression*)callisnan,
-                    (ast_expression*)callisinf
-                )
-            )
-        )
-    );
-
-    vec_push(func->blocks, block);
-    intrin_reg(intrin, value, func);
-
-    return (ast_expression*)value;;
-}
-
-static ast_expression *intrin_isinf(intrin_t *intrin) {
-    /*
-     * float isinf(float x) {
-     *     return (x != 0.0) && (x + x == x);
-     * }
-     */
-    ast_value    *value = NULL;
-    ast_value    *x     = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT);
-    ast_block    *body  = ast_block_new(intrin_ctx(intrin));
-    ast_function *func  = intrin_value(intrin, &value, "isinf", TYPE_FLOAT);
-
-    vec_push(body->exprs,
-        (ast_expression*)ast_return_new(
-            intrin_ctx(intrin),
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_AND,
-                (ast_expression*)ast_binary_new(
-                    intrin_ctx(intrin),
-                    INSTR_NE_F,
-                    (ast_expression*)x,
-                    (ast_expression*)intrin->fold->imm_float[0]
-                ),
-                (ast_expression*)ast_binary_new(
-                    intrin_ctx(intrin),
-                    INSTR_EQ_F,
-                    (ast_expression*)ast_binary_new(
-                        intrin_ctx(intrin),
-                        INSTR_ADD_F,
-                        (ast_expression*)x,
-                        (ast_expression*)x
-                    ),
-                    (ast_expression*)x
-                )
-            )
-        )
-    );
-
-    vec_push(value->expression.params, x);
-    vec_push(func->blocks, body);
-
-    intrin_reg(intrin, value, func);
-
-    return (ast_expression*)value;
-}
-
-static ast_expression *intrin_isnan(intrin_t *intrin) {
-    /*
-     * float isnan(float x) {
-     *   float local;
-     *   local = x;
-     *
-     *   return (x != local);
-     * }
-     */
-    ast_value    *value  = NULL;
-    ast_value    *arg1   = ast_value_new(intrin_ctx(intrin), "x",     TYPE_FLOAT);
-    ast_value    *local  = ast_value_new(intrin_ctx(intrin), "local", TYPE_FLOAT);
-    ast_block    *body   = ast_block_new(intrin_ctx(intrin));
-    ast_function *func   = intrin_value(intrin, &value, "isnan", TYPE_FLOAT);
-
-    vec_push(body->locals, local);
-    vec_push(body->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)local,
-            (ast_expression*)arg1
-        )
-    );
-
-    vec_push(body->exprs,
-        (ast_expression*)ast_return_new(
-            intrin_ctx(intrin),
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_NE_F,
-                (ast_expression*)arg1,
-                (ast_expression*)local
-            )
-        )
-    );
-
-    vec_push(value->expression.params, arg1);
-    vec_push(func->blocks, body);
-
-    intrin_reg(intrin, value, func);
-
-    return (ast_expression*)value;
-}
-
-static ast_expression *intrin_isnormal(intrin_t *intrin) {
-    /*
-     * float isnormal(float x) {
-     *     return isfinite(x);
-     * }
-     */
-    ast_value    *value         = NULL;
-    ast_call     *callisfinite  = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "isfinite", "isnormal"));
-    ast_value    *x             = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT);
-    ast_block    *body          = ast_block_new(intrin_ctx(intrin));
-    ast_function *func          = intrin_value(intrin, &value, "isnormal", TYPE_FLOAT);
-
-    vec_push(value->expression.params, x);
-    vec_push(callisfinite->params, (ast_expression*)x);
-
-    /* return <callisfinite> */
-    vec_push(body->exprs,
-        (ast_expression*)ast_return_new(
-            intrin_ctx(intrin),
-            (ast_expression*)callisfinite
-        )
-    );
-
-    vec_push(func->blocks, body);
-    intrin_reg(intrin, value, func);
-    return (ast_expression*)value;
-}
-
-static ast_expression *intrin_signbit(intrin_t *intrin) {
-    /*
-     * float signbit(float x) {
-     *     return (x < 0);
-     * }
-     */
-    ast_value    *value  = NULL;
-    ast_value    *x      = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT);
-    ast_block    *body   = ast_block_new(intrin_ctx(intrin));
-    ast_function *func   = intrin_value(intrin, &value, "signbit", TYPE_FLOAT);
-
-    vec_push(value->expression.params, x);
-
-    /* return (x < 0); */
-    vec_push(body->exprs,
-        (ast_expression*)ast_return_new(
-            intrin_ctx(intrin),
-            (ast_expression*)ast_ternary_new(
-                intrin_ctx(intrin),
-                (ast_expression*)ast_binary_new(
-                    intrin_ctx(intrin),
-                    INSTR_LT,
-                    (ast_expression*)x,
-                    (ast_expression*)intrin->fold->imm_float[0]
-                ),
-                (ast_expression*)intrin->fold->imm_float[1],
-                (ast_expression*)intrin->fold->imm_float[0]
-            )
-        )
-    );
-
-    vec_push(func->blocks, body);
-    intrin_reg(intrin, value, func);
-    return (ast_expression*)value;
-}
-
-static ast_expression *intrin_acosh(intrin_t *intrin) {
-    /*
-     * float acosh(float x) {
-     *     return log(x + sqrt((x * x) - 1));
-     * }
-     */
-    ast_value    *value    = NULL;
-    ast_value    *x        = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT);
-    ast_call     *calllog  = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "log", "acosh"));
-    ast_call     *callsqrt = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "sqrt", "acosh"));
-    ast_block    *body     = ast_block_new(intrin_ctx(intrin));
-    ast_function *func     = intrin_value(intrin, &value, "acosh", TYPE_FLOAT);
-
-    vec_push(value->expression.params, x);
-
-    /* <callsqrt> = sqrt((x * x) - 1); */
-    vec_push(callsqrt->params,
-        (ast_expression*)ast_binary_new(
-            intrin_ctx(intrin),
-            INSTR_SUB_F,
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_MUL_F,
-                (ast_expression*)x,
-                (ast_expression*)x
-            ),
-            (ast_expression*)intrin->fold->imm_float[1]
-        )
-    );
-
-    /* <calllog> = log(x + <callsqrt>); */
-    vec_push(calllog->params,
-        (ast_expression*)ast_binary_new(
-            intrin_ctx(intrin),
-            INSTR_ADD_F,
-            (ast_expression*)x,
-            (ast_expression*)callsqrt
-        )
-    );
-
-    /* return <calllog>; */
-    vec_push(body->exprs,
-        (ast_expression*)ast_return_new(
-            intrin_ctx(intrin),
-            (ast_expression*)calllog
-        )
-    );
-
-    vec_push(func->blocks, body);
-    intrin_reg(intrin, value, func);
-    return (ast_expression*)value;
-}
-
-static ast_expression *intrin_asinh(intrin_t *intrin) {
-    /*
-     * float asinh(float x) {
-     *     return log(x + sqrt((x * x) + 1));
-     * }
-     */
-    ast_value    *value    = NULL;
-    ast_value    *x        = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT);
-    ast_call     *calllog  = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "log", "asinh"));
-    ast_call     *callsqrt = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "sqrt", "asinh"));
-    ast_block    *body     = ast_block_new(intrin_ctx(intrin));
-    ast_function *func     = intrin_value(intrin, &value, "asinh", TYPE_FLOAT);
-
-    vec_push(value->expression.params, x);
-
-    /* <callsqrt> = sqrt((x * x) + 1); */
-    vec_push(callsqrt->params,
-        (ast_expression*)ast_binary_new(
-            intrin_ctx(intrin),
-            INSTR_ADD_F,
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_MUL_F,
-                (ast_expression*)x,
-                (ast_expression*)x
-            ),
-            (ast_expression*)intrin->fold->imm_float[1]
-        )
-    );
-
-    /* <calllog> = log(x + <callsqrt>); */
-    vec_push(calllog->params,
-        (ast_expression*)ast_binary_new(
-            intrin_ctx(intrin),
-            INSTR_ADD_F,
-            (ast_expression*)x,
-            (ast_expression*)callsqrt
-        )
-    );
-
-    /* return <calllog>; */
-    vec_push(body->exprs,
-        (ast_expression*)ast_return_new(
-            intrin_ctx(intrin),
-            (ast_expression*)calllog
-        )
-    );
-
-    vec_push(func->blocks, body);
-    intrin_reg(intrin, value, func);
-    return (ast_expression*)value;
-}
-
-static ast_expression *intrin_atanh(intrin_t *intrin) {
-    /*
-     * float atanh(float x) {
-     *     return 0.5 * log((1 + x) / (1 - x))
-     * }
-     */
-    ast_value    *value   = NULL;
-    ast_value    *x       = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT);
-    ast_call     *calllog = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "log", "atanh"));
-    ast_block    *body    = ast_block_new(intrin_ctx(intrin));
-    ast_function *func    = intrin_value(intrin, &value, "atanh", TYPE_FLOAT);
-
-    vec_push(value->expression.params, x);
-
-    /* <callog> = log((1 + x) / (1 - x)); */
-    vec_push(calllog->params,
-        (ast_expression*)ast_binary_new(
-            intrin_ctx(intrin),
-            INSTR_DIV_F,
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_ADD_F,
-                (ast_expression*)intrin->fold->imm_float[1],
-                (ast_expression*)x
-            ),
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_SUB_F,
-                (ast_expression*)intrin->fold->imm_float[1],
-                (ast_expression*)x
-            )
-        )
-    );
-
-    /* return 0.5 * <calllog>; */
-    vec_push(body->exprs,
-        (ast_expression*)ast_binary_new(
-            intrin_ctx(intrin),
-            INSTR_MUL_F,
-            (ast_expression*)fold_constgen_float(intrin->fold, 0.5, false),
-            (ast_expression*)calllog
-        )
-    );
-
-    vec_push(func->blocks, body);
-    intrin_reg(intrin, value, func);
-    return (ast_expression*)value;
-}
-
-static ast_expression *intrin_exp(intrin_t *intrin) {
-    /*
-     * float exp(float x) {
-     *     float sum = 1.0;
-     *     float acc = 1.0;
-     *     float i;
-     *     for (i = 1; i < 200; ++i)
-     *         sum += (acc *= x / i);
-     *
-     *     return sum;
-     * }
-     */
-    ast_value    *value = NULL;
-    ast_value    *x     = ast_value_new(intrin_ctx(intrin), "x",   TYPE_FLOAT);
-    ast_value    *sum   = ast_value_new(intrin_ctx(intrin), "sum", TYPE_FLOAT);
-    ast_value    *acc   = ast_value_new(intrin_ctx(intrin), "acc", TYPE_FLOAT);
-    ast_value    *i     = ast_value_new(intrin_ctx(intrin), "i",   TYPE_FLOAT);
-    ast_block    *body  = ast_block_new(intrin_ctx(intrin));
-    ast_function *func  = intrin_value(intrin, &value, "exp", TYPE_FLOAT);
-
-    vec_push(value->expression.params, x);
-    vec_push(body->locals, sum);
-    vec_push(body->locals, acc);
-    vec_push(body->locals, i);
-
-    /* sum = 1.0; */
-    vec_push(body->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)sum,
-            (ast_expression*)intrin->fold->imm_float[1]
-        )
-    );
-
-    /* acc = 1.0; */
-    vec_push(body->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)acc,
-            (ast_expression*)intrin->fold->imm_float[1]
-        )
-    );
-
-    /*
-     * for (i = 1; i < 200; ++i)
-     *     sum += (acc *= x / i);
-     */
-    vec_push(body->exprs,
-        (ast_expression*)ast_loop_new(
-            intrin_ctx(intrin),
-            /* i = 1; */
-            (ast_expression*)ast_store_new(
-                intrin_ctx(intrin),
-                INSTR_STORE_F,
-                (ast_expression*)i,
-                (ast_expression*)intrin->fold->imm_float[1]
-            ),
-            /* i < 200; */
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_LT,
-                (ast_expression*)i,
-                (ast_expression*)fold_constgen_float(intrin->fold, 200.0f, false)
-            ),
-            false,
-            NULL,
-            false,
-            /* ++i; */
-            (ast_expression*)ast_binstore_new(
-                intrin_ctx(intrin),
-                INSTR_STORE_F,
-                INSTR_ADD_F,
-                (ast_expression*)i,
-                (ast_expression*)intrin->fold->imm_float[1]
-            ),
-            /* sum += (acc *= (x / i)) */
-            (ast_expression*)ast_binstore_new(
-                intrin_ctx(intrin),
-                INSTR_STORE_F,
-                INSTR_ADD_F,
-                (ast_expression*)sum,
-                (ast_expression*)ast_binstore_new(
-                    intrin_ctx(intrin),
-                    INSTR_STORE_F,
-                    INSTR_MUL_F,
-                    (ast_expression*)acc,
-                    (ast_expression*)ast_binary_new(
-                        intrin_ctx(intrin),
-                        INSTR_DIV_F,
-                        (ast_expression*)x,
-                        (ast_expression*)i
-                    )
-                )
-            )
-        )
-    );
-
-    /* return sum; */
-    vec_push(body->exprs,
-        (ast_expression*)ast_return_new(
-            intrin_ctx(intrin),
-            (ast_expression*)sum
-        )
-    );
-
-    vec_push(func->blocks, body);
-
-    intrin_reg(intrin, value, func);
-    return (ast_expression*)value;
-}
-
-static ast_expression *intrin_exp2(intrin_t *intrin) {
-    /*
-     * float exp2(float x) {
-     *     return pow(2, x);
-     * }
-     */
-    ast_value    *value     = NULL;
-    ast_call     *callpow   = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "pow", "exp2"));
-    ast_value    *arg1      = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT);
-    ast_block    *body      = ast_block_new(intrin_ctx(intrin));
-    ast_function *func      = intrin_value(intrin, &value, "exp2", TYPE_FLOAT);
-
-    vec_push(value->expression.params, arg1);
-
-    vec_push(callpow->params, (ast_expression*)intrin->fold->imm_float[3]);
-    vec_push(callpow->params, (ast_expression*)arg1);
-
-    /* return <callpow> */
-    vec_push(body->exprs,
-        (ast_expression*)ast_return_new(
-            intrin_ctx(intrin),
-            (ast_expression*)callpow
-        )
-    );
-
-    vec_push(func->blocks, body);
-
-    intrin_reg(intrin, value, func);
-    return (ast_expression*)value;
-}
-
-static ast_expression *intrin_expm1(intrin_t *intrin) {
-    /*
-     * float expm1(float x) {
-     *     return exp(x) - 1;
-     * }
-     */
-    ast_value    *value    = NULL;
-    ast_call     *callexp  = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "exp", "expm1"));
-    ast_value    *x        = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT);
-    ast_block    *body     = ast_block_new(intrin_ctx(intrin));
-    ast_function *func     = intrin_value(intrin, &value, "expm1", TYPE_FLOAT);
-
-    vec_push(value->expression.params, x);
-
-    /* <callexp> = exp(x); */
-    vec_push(callexp->params, (ast_expression*)x);
-
-    /* return <callexp> - 1; */
-    vec_push(body->exprs,
-        (ast_expression*)ast_return_new(
-            intrin_ctx(intrin),
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_SUB_F,
-                (ast_expression*)callexp,
-                (ast_expression*)intrin->fold->imm_float[1]
-            )
-        )
-    );
-
-    vec_push(func->blocks, body);
-    intrin_reg(intrin, value, func);
-    return (ast_expression*)value;
-}
-
-static ast_expression *intrin_pow(intrin_t *intrin) {
-    /*
-     *
-     * float pow(float base, float exp) {
-     *     float result;
-     *     float low;
-     *     float high;
-     *     float mid;
-     *     float square;
-     *     float accumulate;
-     *
-     *     if (exp == 0.0)
-     *         return 1;
-     *     if (exp == 1.0)
-     *         return base;
-     *     if (exp < 0)
-     *         return 1.0 / pow(base, -exp);
-     *     if (exp >= 1) {
-     *         result = pow(base, exp / 2);
-     *         return result * result;
-     *     }
-     *
-     *     low        = 0.0f;
-     *     high       = 1.0f;
-     *     square     = sqrt(base);
-     *     accumulate = square;
-     *     mid        = high / 2.0f
-     *
-     *     while (fabs(mid - exp) > QC_POW_EPSILON) {
-     *         square = sqrt(square);
-     *         if (mid < exp) {
-     *             low         = mid;
-     *             accumulate *= square;
-     *         } else {
-     *             high        = mid;
-     *             accumulate *= (1.0f / square);
-     *         }
-     *         mid = (low + high) / 2;
-     *     }
-     *     return accumulate;
-     * }
-     */
-    ast_value    *value = NULL;
-    ast_function *func = intrin_value(intrin, &value, "pow", TYPE_FLOAT);
-
-    /* prepare some calls for later */
-    ast_call *callpow1  = ast_call_new(intrin_ctx(intrin), (ast_expression*)value);                  /* for pow(base, -exp)    */
-    ast_call *callpow2  = ast_call_new(intrin_ctx(intrin), (ast_expression*)value);                  /* for pow(vase, exp / 2) */
-    ast_call *callsqrt1 = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "sqrt", "pow")); /* for sqrt(base)         */
-    ast_call *callsqrt2 = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "sqrt", "pow")); /* for sqrt(square)       */
-    ast_call *callfabs  = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "fabs", "pow")); /* for fabs(mid - exp)    */
-
-    /* prepare some blocks for later */
-    ast_block *expgt1       = ast_block_new(intrin_ctx(intrin));
-    ast_block *midltexp     = ast_block_new(intrin_ctx(intrin));
-    ast_block *midltexpelse = ast_block_new(intrin_ctx(intrin));
-    ast_block *whileblock   = ast_block_new(intrin_ctx(intrin));
-
-    /* float pow(float base, float exp) */
-    ast_value    *base = ast_value_new(intrin_ctx(intrin), "base", TYPE_FLOAT);
-    ast_value    *exp  = ast_value_new(intrin_ctx(intrin), "exp",  TYPE_FLOAT);
-    /* { */
-    ast_block    *body = ast_block_new(intrin_ctx(intrin));
-
-    /*
-     * float result;
-     * float low;
-     * float high;
-     * float square;
-     * float accumulate;
-     * float mid;
-     */
-    ast_value *result     = ast_value_new(intrin_ctx(intrin), "result",     TYPE_FLOAT);
-    ast_value *low        = ast_value_new(intrin_ctx(intrin), "low",        TYPE_FLOAT);
-    ast_value *high       = ast_value_new(intrin_ctx(intrin), "high",       TYPE_FLOAT);
-    ast_value *square     = ast_value_new(intrin_ctx(intrin), "square",     TYPE_FLOAT);
-    ast_value *accumulate = ast_value_new(intrin_ctx(intrin), "accumulate", TYPE_FLOAT);
-    ast_value *mid        = ast_value_new(intrin_ctx(intrin), "mid",        TYPE_FLOAT);
-    vec_push(body->locals, result);
-    vec_push(body->locals, low);
-    vec_push(body->locals, high);
-    vec_push(body->locals, square);
-    vec_push(body->locals, accumulate);
-    vec_push(body->locals, mid);
-
-    vec_push(value->expression.params, base);
-    vec_push(value->expression.params, exp);
-
-    /*
-     * if (exp == 0.0)
-     *     return 1;
-     */
-    vec_push(body->exprs,
-        (ast_expression*)ast_ifthen_new(
-            intrin_ctx(intrin),
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_EQ_F,
-                (ast_expression*)exp,
-                (ast_expression*)intrin->fold->imm_float[0]
-            ),
-            (ast_expression*)ast_return_new(
-                intrin_ctx(intrin),
-                (ast_expression*)intrin->fold->imm_float[1]
-            ),
-            NULL
-        )
-    );
-
-    /*
-     * if (exp == 1.0)
-     *     return base;
-     */
-    vec_push(body->exprs,
-        (ast_expression*)ast_ifthen_new(
-            intrin_ctx(intrin),
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_EQ_F,
-                (ast_expression*)exp,
-                (ast_expression*)intrin->fold->imm_float[1]
-            ),
-            (ast_expression*)ast_return_new(
-                intrin_ctx(intrin),
-                (ast_expression*)base
-            ),
-            NULL
-        )
-    );
-
-    /* <callpow1> = pow(base, -exp) */
-    vec_push(callpow1->params, (ast_expression*)base);
-    vec_push(callpow1->params,
-        (ast_expression*)ast_unary_new(
-            intrin_ctx(intrin),
-            VINSTR_NEG_F,
-            (ast_expression*)exp
-        )
-    );
-
-    /*
-     * if (exp < 0)
-     *     return 1.0 / <callpow1>;
-     */
-    vec_push(body->exprs,
-        (ast_expression*)ast_ifthen_new(
-            intrin_ctx(intrin),
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_LT,
-                (ast_expression*)exp,
-                (ast_expression*)intrin->fold->imm_float[0]
-            ),
-            (ast_expression*)ast_return_new(
-                intrin_ctx(intrin),
-                (ast_expression*)ast_binary_new(
-                    intrin_ctx(intrin),
-                    INSTR_DIV_F,
-                    (ast_expression*)intrin->fold->imm_float[1],
-                    (ast_expression*)callpow1
-                )
-            ),
-            NULL
-        )
-    );
-
-    /* <callpow2> = pow(base, exp / 2) */
-    vec_push(callpow2->params, (ast_expression*)base);
-    vec_push(callpow2->params,
-        (ast_expression*)ast_binary_new(
-            intrin_ctx(intrin),
-            INSTR_DIV_F,
-            (ast_expression*)exp,
-            (ast_expression*)intrin->fold->imm_float[3] /* 2.0f */
-        )
-    );
-
-    /*
-     * <expgt1> = {
-     *     result = <callpow2>;
-     *     return result * result;
-     * }
-     */
-    vec_push(expgt1->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)result,
-            (ast_expression*)callpow2
-        )
-    );
-    vec_push(expgt1->exprs,
-        (ast_expression*)ast_return_new(
-            intrin_ctx(intrin),
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_MUL_F,
-                (ast_expression*)result,
-                (ast_expression*)result
-            )
-        )
-    );
-
-    /*
-     * if (exp >= 1) {
-     *     <expgt1>
-     * }
-     */
-    vec_push(body->exprs,
-        (ast_expression*)ast_ifthen_new(
-            intrin_ctx(intrin),
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_GE,
-                (ast_expression*)exp,
-                (ast_expression*)intrin->fold->imm_float[1]
-            ),
-            (ast_expression*)expgt1,
-            NULL
-        )
-    );
-
-    /*
-     * <callsqrt1> = sqrt(base)
-     */
-    vec_push(callsqrt1->params, (ast_expression*)base);
-
-    /*
-     * low        = 0.0f;
-     * high       = 1.0f;
-     * square     = sqrt(base);
-     * accumulate = square;
-     * mid        = high / 2.0f;
-     */
-    vec_push(body->exprs,
-        (ast_expression*)ast_store_new(intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)low,
-            (ast_expression*)intrin->fold->imm_float[0]
-        )
-    );
-    vec_push(body->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)high,
-            (ast_expression*)intrin->fold->imm_float[1]
-        )
-    );
-
-    vec_push(body->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)square,
-            (ast_expression*)callsqrt1
-        )
-    );
-
-    vec_push(body->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)accumulate,
-            (ast_expression*)square
-        )
-    );
-    vec_push(body->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)mid,
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_DIV_F,
-                (ast_expression*)high,
-                (ast_expression*)intrin->fold->imm_float[3] /* 2.0f */
-            )
-        )
-    );
-
-    /*
-     * <midltexp> = {
-     *     low         = mid;
-     *     accumulate *= square;
-     * }
-     */
-    vec_push(midltexp->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)low,
-            (ast_expression*)mid
-        )
-    );
-    vec_push(midltexp->exprs,
-        (ast_expression*)ast_binstore_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            INSTR_MUL_F,
-            (ast_expression*)accumulate,
-            (ast_expression*)square
-        )
-    );
-
-    /*
-     * <midltexpelse> = {
-     *     high        = mid;
-     *     accumulate *= (1.0 / square);
-     * }
-     */
-    vec_push(midltexpelse->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)high,
-            (ast_expression*)mid
-        )
-    );
-    vec_push(midltexpelse->exprs,
-        (ast_expression*)ast_binstore_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            INSTR_MUL_F,
-            (ast_expression*)accumulate,
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_DIV_F,
-                (ast_expression*)intrin->fold->imm_float[1],
-                (ast_expression*)square
-            )
-        )
-    );
-
-    /*
-     * <callsqrt2> = sqrt(square)
-     */
-    vec_push(callsqrt2->params, (ast_expression*)square);
-
-    /*
-     * <whileblock> = {
-     *     square = <callsqrt2>;
-     *     if (mid < exp)
-     *          <midltexp>;
-     *     else
-     *          <midltexpelse>;
-     *
-     *     mid = (low + high) / 2;
-     * }
-     */
-    vec_push(whileblock->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)square,
-            (ast_expression*)callsqrt2
-        )
-    );
-    vec_push(whileblock->exprs,
-        (ast_expression*)ast_ifthen_new(
-            intrin_ctx(intrin),
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_LT,
-                (ast_expression*)mid,
-                (ast_expression*)exp
-            ),
-            (ast_expression*)midltexp,
-            (ast_expression*)midltexpelse
-        )
-    );
-    vec_push(whileblock->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)mid,
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_DIV_F,
-                (ast_expression*)ast_binary_new(
-                    intrin_ctx(intrin),
-                    INSTR_ADD_F,
-                    (ast_expression*)low,
-                    (ast_expression*)high
-                ),
-                (ast_expression*)intrin->fold->imm_float[3] /* 2.0f */
-            )
-        )
-    );
-
-    /*
-     * <callabs> = fabs(mid - exp)
-     */
-    vec_push(callfabs->params,
-        (ast_expression*)ast_binary_new(
-            intrin_ctx(intrin),
-            INSTR_SUB_F,
-            (ast_expression*)mid,
-            (ast_expression*)exp
-        )
-    );
-
-    /*
-     * while (<callfabs>  > epsilon)
-     *     <whileblock>
-     */
-    vec_push(body->exprs,
-        (ast_expression*)ast_loop_new(
-            intrin_ctx(intrin),
-            /* init */
-            NULL,
-            /* pre condition */
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_GT,
-                (ast_expression*)callfabs,
-                (ast_expression*)fold_constgen_float(intrin->fold, QC_POW_EPSILON, false)
-            ),
-            /* pre not */
-            false,
-            /* post condition */
-            NULL,
-            /* post not */
-            false,
-            /* increment expression */
-            NULL,
-            /* code block */
-            (ast_expression*)whileblock
-        )
-    );
-
-    /* return accumulate */
-    vec_push(body->exprs,
-        (ast_expression*)ast_return_new(
-            intrin_ctx(intrin),
-            (ast_expression*)accumulate
-        )
-    );
-
-    /* } */
-    vec_push(func->blocks, body);
-
-    intrin_reg(intrin, value, func);
-    return (ast_expression*)value;
-}
-
-static ast_expression *intrin_mod(intrin_t *intrin) {
-    /*
-     * float mod(float a, float b) {
-     *     float div = a / b;
-     *     float sign = (div < 0.0f) ? -1 : 1;
-     *     return a - b * sign * floor(sign * div);
-     * }
-     */
-    ast_value    *value = NULL;
-    ast_call     *call  = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "floor", "mod"));
-    ast_value    *a     = ast_value_new(intrin_ctx(intrin), "a",    TYPE_FLOAT);
-    ast_value    *b     = ast_value_new(intrin_ctx(intrin), "b",    TYPE_FLOAT);
-    ast_value    *div   = ast_value_new(intrin_ctx(intrin), "div",  TYPE_FLOAT);
-    ast_value    *sign  = ast_value_new(intrin_ctx(intrin), "sign", TYPE_FLOAT);
-    ast_block    *body  = ast_block_new(intrin_ctx(intrin));
-    ast_function *func  = intrin_value(intrin, &value, "mod", TYPE_FLOAT);
-
-    vec_push(value->expression.params, a);
-    vec_push(value->expression.params, b);
-
-    vec_push(body->locals, div);
-    vec_push(body->locals, sign);
-
-    /* div = a / b; */
-    vec_push(body->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)div,
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_DIV_F,
-                (ast_expression*)a,
-                (ast_expression*)b
-            )
-        )
-    );
-
-    /* sign = (div < 0.0f) ? -1 : 1; */
-    vec_push(body->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)sign,
-            (ast_expression*)ast_ternary_new(
-                intrin_ctx(intrin),
-                (ast_expression*)ast_binary_new(
-                    intrin_ctx(intrin),
-                    INSTR_LT,
-                    (ast_expression*)div,
-                    (ast_expression*)intrin->fold->imm_float[0]
-                ),
-                (ast_expression*)intrin->fold->imm_float[2],
-                (ast_expression*)intrin->fold->imm_float[1]
-            )
-        )
-    );
-
-    /* floor(sign * div) */
-    vec_push(call->params,
-        (ast_expression*)ast_binary_new(
-            intrin_ctx(intrin),
-            INSTR_MUL_F,
-            (ast_expression*)sign,
-            (ast_expression*)div
-        )
-    );
-
-    /* return a - b * sign * <call> */
-    vec_push(body->exprs,
-        (ast_expression*)ast_return_new(
-            intrin_ctx(intrin),
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_SUB_F,
-                (ast_expression*)a,
-                (ast_expression*)ast_binary_new(
-                    intrin_ctx(intrin),
-                    INSTR_MUL_F,
-                    (ast_expression*)b,
-                    (ast_expression*)ast_binary_new(
-                        intrin_ctx(intrin),
-                        INSTR_MUL_F,
-                        (ast_expression*)sign,
-                        (ast_expression*)call
-                    )
-                )
-            )
-        )
-    );
-
-    vec_push(func->blocks, body);
-    intrin_reg(intrin, value, func);
-
-    return (ast_expression*)value;
-}
-
-static ast_expression *intrin_fabs(intrin_t *intrin) {
-    /*
-     * float fabs(float x) {
-     *     return x < 0 ? -x : x;
-     * }
-     */
-    ast_value    *value  = NULL;
-    ast_value    *arg1   = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT);
-    ast_block    *body   = ast_block_new(intrin_ctx(intrin));
-    ast_function *func   = intrin_value(intrin, &value, "fabs", TYPE_FLOAT);
-
-    vec_push(body->exprs,
-        (ast_expression*)ast_return_new(
-            intrin_ctx(intrin),
-            (ast_expression*)ast_ternary_new(
-                intrin_ctx(intrin),
-                (ast_expression*)ast_binary_new(
-                    intrin_ctx(intrin),
-                    INSTR_LE,
-                    (ast_expression*)arg1,
-                    (ast_expression*)intrin->fold->imm_float[0]
-                ),
-                (ast_expression*)ast_unary_new(
-                    intrin_ctx(intrin),
-                    VINSTR_NEG_F,
-                    (ast_expression*)arg1
-                ),
-                (ast_expression*)arg1
-            )
-        )
-    );
-
-    vec_push(value->expression.params, arg1);
-    vec_push(func->blocks, body);
-
-    intrin_reg(intrin, value, func);
-
-    return (ast_expression*)value;
-}
-
-static ast_expression *intrin_epsilon(intrin_t *intrin) {
-    /*
-     * float epsilon(void) {
-     *     float eps = 1.0f;
-     *     do { eps /= 2.0f; } while ((1.0f + (eps / 2.0f)) != 1.0f);
-     *     return eps;
-     * }
-     */
-    ast_value    *value  = NULL;
-    ast_value    *eps    = ast_value_new(intrin_ctx(intrin), "eps", TYPE_FLOAT);
-    ast_block    *body   = ast_block_new(intrin_ctx(intrin));
-    ast_function *func   = intrin_value(intrin, &value, "epsilon", TYPE_FLOAT);
-
-    vec_push(body->locals, eps);
-
-    /* eps = 1.0f; */
-    vec_push(body->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)eps,
-            (ast_expression*)intrin->fold->imm_float[0]
-        )
-    );
-
-    vec_push(body->exprs,
-        (ast_expression*)ast_loop_new(
-            intrin_ctx(intrin),
-            NULL,
-            NULL,
-            false,
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_NE_F,
-                (ast_expression*)ast_binary_new(
-                    intrin_ctx(intrin),
-                    INSTR_ADD_F,
-                    (ast_expression*)intrin->fold->imm_float[1],
-                    (ast_expression*)ast_binary_new(
-                        intrin_ctx(intrin),
-                        INSTR_MUL_F,
-                        (ast_expression*)eps,
-                        (ast_expression*)intrin->fold->imm_float[3] /* 2.0f */
-                    )
-                ),
-                (ast_expression*)intrin->fold->imm_float[1]
-            ),
-            false,
-            NULL,
-            (ast_expression*)ast_binstore_new(
-                intrin_ctx(intrin),
-                INSTR_STORE_F,
-                INSTR_DIV_F,
-                (ast_expression*)eps,
-                (ast_expression*)intrin->fold->imm_float[3] /* 2.0f */
-            )
-        )
-    );
-
-    /* return eps; */
-    vec_push(body->exprs,
-        (ast_expression*)ast_return_new(
-            intrin_ctx(intrin),
-            (ast_expression*)eps
-        )
-    );
-
-    vec_push(func->blocks, body);
-    intrin_reg(intrin, value, func);
-
-    return (ast_expression*)value;
-}
-
-static ast_expression *intrin_nan(intrin_t *intrin) {
-    /*
-     * float nan(void) {
-     *     float x = 0.0f;
-     *     return x / x;
-     * }
-     */
-    ast_value    *value  = NULL;
-    ast_value    *x      = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT);
-    ast_function *func   = intrin_value(intrin, &value, "nan", TYPE_FLOAT);
-    ast_block    *block  = ast_block_new(intrin_ctx(intrin));
-
-    vec_push(block->locals, x);
-
-    vec_push(block->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)x,
-            (ast_expression*)intrin->fold->imm_float[0]
-        )
-    );
-
-    vec_push(block->exprs,
-        (ast_expression*)ast_return_new(
-            intrin_ctx(intrin),
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_DIV_F,
-                (ast_expression*)x,
-                (ast_expression*)x
-            )
-        )
-    );
-
-    vec_push(func->blocks, block);
-    intrin_reg(intrin, value, func);
-
-    return (ast_expression*)value;
-}
-
-static ast_expression *intrin_inf(intrin_t *intrin) {
-    /*
-     * float inf(void) {
-     *     float x = 1.0f;
-     *     float y = 0.0f;
-     *     return x / y;
-     * }
-     */
-    ast_value    *value  = NULL;
-    ast_value    *x      = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT);
-    ast_value    *y      = ast_value_new(intrin_ctx(intrin), "y", TYPE_FLOAT);
-    ast_function *func   = intrin_value(intrin, &value, "inf", TYPE_FLOAT);
-    ast_block    *block  = ast_block_new(intrin_ctx(intrin));
-    size_t        i;
-
-    vec_push(block->locals, x);
-    vec_push(block->locals, y);
-
-    /* to keep code size down */
-    for (i = 0; i <= 1; i++) {
-        vec_push(block->exprs,
-            (ast_expression*)ast_store_new(
-                intrin_ctx(intrin),
-                INSTR_STORE_F,
-                (ast_expression*)((i == 0) ? x : y),
-                (ast_expression*)intrin->fold->imm_float[i]
-            )
-        );
-    }
-
-    vec_push(block->exprs,
-        (ast_expression*)ast_return_new(
-            intrin_ctx(intrin),
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_DIV_F,
-                (ast_expression*)x,
-                (ast_expression*)y
-            )
-        )
-    );
-
-    vec_push(func->blocks, block);
-    intrin_reg(intrin, value, func);
-
-    return (ast_expression*)value;
-}
-
-static ast_expression *intrin_ln(intrin_t *intrin) {
-    /*
-     * float log(float power, float base) {
-     *   float whole;
-     *   float nth
-     *   float sign = 1.0f;
-     *   float eps  = epsilon();
-     *
-     *   if (power <= 1.0f || bbase <= 1.0) {
-     *       if (power <= 0.0f || base <= 0.0f)
-     *           return nan();
-     *
-     *       if (power < 1.0f) {
-     *           power = 1.0f / power;
-     *           sign *= -1.0f;
-     *       }
-     *
-     *       if (base < 1.0f) {
-     *           sign *= -1.0f;
-     *           base  = 1.0f / base;
-     *       }
-     *   }
-     *
-     *   float A_i       = 1;
-     *   float B_i       = 0;
-     *   float A_iminus1 = 0;
-     *   float B_iminus1 = 1;
-     *
-     *   for (;;) {
-     *       whole = power;
-     *       nth   = 0.0f;
-     *
-     *       while (whole >= base) {
-     *           float base2    = base;
-     *           float n2       = 1.0f;
-     *           float newbase2 = base2 * base2;
-     *
-     *           while (whole >= newbase2) {
-     *               base2     = newbase2;
-     *               n2       *= 2;
-     *               newbase2 *= newbase2;
-     *           }
-     *
-     *           whole /= base2;
-     *           nth += n2;
-     *       }
-     *
-     *       float b_iplus1 = n;
-     *       float A_iplus1 = b_iplus1 * A_i + A_iminus1;
-     *       float B_iplus1 = b_iplus1 * B_i + B_iminus1;
-     *
-     *       A_iminus1 = A_i;
-     *       B_iminus1 = B_i;
-     *       A_i       = A_iplus1;
-     *       B_i       = B_iplus1;
-     *
-     *       if (whole <= 1.0f + eps)
-     *           break;
-     *
-     *       power = base;
-     *       bower = whole;
-     *   }
-     *   return sign * A_i / B_i;
-     * }
-     */
-
-    ast_value    *value      = NULL;
-    ast_value    *power      = ast_value_new(intrin_ctx(intrin), "power",     TYPE_FLOAT);
-    ast_value    *base       = ast_value_new(intrin_ctx(intrin), "base",      TYPE_FLOAT);
-    ast_value    *whole      = ast_value_new(intrin_ctx(intrin), "whole",     TYPE_FLOAT);
-    ast_value    *nth        = ast_value_new(intrin_ctx(intrin), "nth",       TYPE_FLOAT);
-    ast_value    *sign       = ast_value_new(intrin_ctx(intrin), "sign",      TYPE_FLOAT);
-    ast_value    *A_i        = ast_value_new(intrin_ctx(intrin), "A_i",       TYPE_FLOAT);
-    ast_value    *B_i        = ast_value_new(intrin_ctx(intrin), "B_i",       TYPE_FLOAT);
-    ast_value    *A_iminus1  = ast_value_new(intrin_ctx(intrin), "A_iminus1", TYPE_FLOAT);
-    ast_value    *B_iminus1  = ast_value_new(intrin_ctx(intrin), "B_iminus1", TYPE_FLOAT);
-    ast_value    *b_iplus1   = ast_value_new(intrin_ctx(intrin), "b_iplus1",  TYPE_FLOAT);
-    ast_value    *A_iplus1   = ast_value_new(intrin_ctx(intrin), "A_iplus1",  TYPE_FLOAT);
-    ast_value    *B_iplus1   = ast_value_new(intrin_ctx(intrin), "B_iplus1",  TYPE_FLOAT);
-    ast_value    *eps        = ast_value_new(intrin_ctx(intrin), "eps",       TYPE_FLOAT);
-    ast_value    *base2      = ast_value_new(intrin_ctx(intrin), "base2",     TYPE_FLOAT);
-    ast_value    *n2         = ast_value_new(intrin_ctx(intrin), "n2",        TYPE_FLOAT);
-    ast_value    *newbase2   = ast_value_new(intrin_ctx(intrin), "newbase2",  TYPE_FLOAT);
-    ast_block    *block      = ast_block_new(intrin_ctx(intrin));
-    ast_block    *plt1orblt1 = ast_block_new(intrin_ctx(intrin)); /* (power <= 1.0f || base <= 1.0f) */
-    ast_block    *plt1       = ast_block_new(intrin_ctx(intrin)); /* (power < 1.0f) */
-    ast_block    *blt1       = ast_block_new(intrin_ctx(intrin)); /* (base  < 1.0f) */
-    ast_block    *forloop    = ast_block_new(intrin_ctx(intrin)); /* for(;;) */
-    ast_block    *whileloop  = ast_block_new(intrin_ctx(intrin)); /* while (whole >= base) */
-    ast_block    *nestwhile  = ast_block_new(intrin_ctx(intrin)); /* while (whole >= newbase2) */
-    ast_function *func       = intrin_value(intrin, &value, "ln", TYPE_FLOAT);
-    size_t        i;
-
-    vec_push(value->expression.params, power);
-    vec_push(value->expression.params, base);
-
-    vec_push(block->locals, whole);
-    vec_push(block->locals, nth);
-    vec_push(block->locals, sign);
-    vec_push(block->locals, eps);
-    vec_push(block->locals, A_i);
-    vec_push(block->locals, B_i);
-    vec_push(block->locals, A_iminus1);
-    vec_push(block->locals, B_iminus1);
-
-    /* sign = 1.0f; */
-    vec_push(block->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)sign,
-            (ast_expression*)intrin->fold->imm_float[1]
-        )
-    );
-
-    /* eps = __builtin_epsilon(); */
-    vec_push(block->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)eps,
-            (ast_expression*)ast_call_new(
-                intrin_ctx(intrin),
-                intrin_func_self(intrin, "__builtin_epsilon", "ln")
-            )
-        )
-    );
-
-    /*
-     * A_i       = 1;
-     * B_i       = 0;
-     * A_iminus1 = 0;
-     * B_iminus1 = 1;
-     */
-    for (i = 0; i <= 1; i++) {
-        int j;
-        for (j = 1; j >= 0; j--) {
-            vec_push(block->exprs,
-                (ast_expression*)ast_store_new(
-                    intrin_ctx(intrin),
-                    INSTR_STORE_F,
-                    (ast_expression*)((j) ? ((i) ? B_iminus1 : A_i)
-                                          : ((i) ? A_iminus1 : B_i)),
-                    (ast_expression*)intrin->fold->imm_float[j]
-                )
-            );
-        }
-    }
-
-    /*
-     * <plt1> = {
-     *     power = 1.0f / power;
-     *     sign *= -1.0f;
-     * }
-     * <blt1> = {
-     *     base  = 1.0f / base;
-     *     sign *= -1.0f;
-     * }
-     */
-    for (i = 0; i <= 1; i++) {
-        vec_push(((i) ? blt1 : plt1)->exprs,
-            (ast_expression*)ast_store_new(
-                intrin_ctx(intrin),
-                INSTR_STORE_F,
-                (ast_expression*)((i) ? base : power),
-                (ast_expression*)ast_binary_new(
-                    intrin_ctx(intrin),
-                    INSTR_DIV_F,
-                    (ast_expression*)intrin->fold->imm_float[1],
-                    (ast_expression*)((i) ? base : power)
-                )
-            )
-        );
-        vec_push(plt1->exprs,
-            (ast_expression*)ast_binstore_new(
-                intrin_ctx(intrin),
-                INSTR_STORE_F,
-                INSTR_MUL_F,
-                (ast_expression*)sign,
-                (ast_expression*)intrin->fold->imm_float[2]
-            )
-        );
-    }
-
-    /*
-     * <plt1orblt1> = {
-     *     if (power <= 0.0 || base <= 0.0f)
-     *         return __builtin_nan();
-     *     if (power < 1.0f)
-     *         <plt1>
-     *     if (base < 1.0f)
-     *         <blt1>
-     * }
-     */
-    vec_push(plt1orblt1->exprs,
-        (ast_expression*)ast_ifthen_new(
-            intrin_ctx(intrin),
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_OR,
-                (ast_expression*)ast_binary_new(
-                    intrin_ctx(intrin),
-                    INSTR_LE,
-                    (ast_expression*)power,
-                    (ast_expression*)intrin->fold->imm_float[0]
-                ),
-                (ast_expression*)ast_binary_new(
-                    intrin_ctx(intrin),
-                    INSTR_LE,
-                    (ast_expression*)base,
-                    (ast_expression*)intrin->fold->imm_float[0]
-                )
-            ),
-            (ast_expression*)ast_return_new(
-                intrin_ctx(intrin),
-                (ast_expression*)ast_call_new(
-                    intrin_ctx(intrin),
-                    intrin_func_self(intrin, "__builtin_nan", "ln")
-                )
-            ),
-            NULL
-        )
-    );
-
-    for (i = 0; i <= 1; i++) {
-        vec_push(plt1orblt1->exprs,
-            (ast_expression*)ast_ifthen_new(
-                intrin_ctx(intrin),
-                (ast_expression*)ast_binary_new(
-                    intrin_ctx(intrin),
-                    INSTR_LT,
-                    (ast_expression*)((i) ? base : power),
-                    (ast_expression*)intrin->fold->imm_float[1]
-                ),
-                (ast_expression*)((i) ? blt1 : plt1),
-                NULL
-            )
-        );
-    }
-
-    vec_push(block->exprs, (ast_expression*)plt1orblt1);
-
-
-    /* whole = power; */
-    vec_push(forloop->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)whole,
-            (ast_expression*)power
-        )
-    );
-
-    /* nth = 0.0f; */
-    vec_push(forloop->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)nth,
-            (ast_expression*)intrin->fold->imm_float[0]
-        )
-    );
-
-    /* base2 = base; */
-    vec_push(whileloop->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)base2,
-            (ast_expression*)base
-        )
-    );
-
-    /* n2 = 1.0f; */
-    vec_push(whileloop->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)n2,
-            (ast_expression*)intrin->fold->imm_float[1]
-        )
-    );
-
-    /* newbase2 = base2 * base2; */
-    vec_push(whileloop->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)newbase2,
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_MUL_F,
-                (ast_expression*)base2,
-                (ast_expression*)base2
-            )
-        )
-    );
-
-    /* while loop locals */
-    vec_push(whileloop->locals, base2);
-    vec_push(whileloop->locals, n2);
-    vec_push(whileloop->locals, newbase2);
-
-    /* base2 = newbase2; */
-    vec_push(nestwhile->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)base2,
-            (ast_expression*)newbase2
-        )
-    );
-
-    /* n2 *= 2; */
-    vec_push(nestwhile->exprs,
-        (ast_expression*)ast_binstore_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            INSTR_MUL_F,
-            (ast_expression*)n2,
-            (ast_expression*)intrin->fold->imm_float[3] /* 2.0f */
-        )
-    );
-
-    /* newbase2 *= newbase2; */
-    vec_push(nestwhile->exprs,
-        (ast_expression*)ast_binstore_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            INSTR_MUL_F,
-            (ast_expression*)newbase2,
-            (ast_expression*)newbase2
-        )
-    );
-
-    /* while (whole >= newbase2) */
-    vec_push(whileloop->exprs,
-        (ast_expression*)ast_loop_new(
-            intrin_ctx(intrin),
-            NULL,
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_GE,
-                (ast_expression*)whole,
-                (ast_expression*)newbase2
-            ),
-            false,
-            NULL,
-            false,
-            NULL,
-            (ast_expression*)nestwhile
-        )
-    );
-
-    /* whole /= base2; */
-    vec_push(whileloop->exprs,
-        (ast_expression*)ast_binstore_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            INSTR_DIV_F,
-            (ast_expression*)whole,
-            (ast_expression*)base2
-        )
-    );
-
-    /* nth += n2; */
-    vec_push(whileloop->exprs,
-        (ast_expression*)ast_binstore_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            INSTR_ADD_F,
-            (ast_expression*)nth,
-            (ast_expression*)n2
-        )
-    );
-
-    /* while (whole >= base) */
-    vec_push(forloop->exprs,
-        (ast_expression*)ast_loop_new(
-            intrin_ctx(intrin),
-            NULL,
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_GE,
-                (ast_expression*)whole,
-                (ast_expression*)base
-            ),
-            false,
-            NULL,
-            false,
-            NULL,
-            (ast_expression*)whileloop
-        )
-    );
-
-    vec_push(forloop->locals, b_iplus1);
-    vec_push(forloop->locals, A_iplus1);
-    vec_push(forloop->locals, B_iplus1);
-
-    /* b_iplus1 = nth; */
-    vec_push(forloop->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)b_iplus1,
-            (ast_expression*)nth
-        )
-    );
-
-    /*
-     * A_iplus1 = b_iplus1 * A_i + A_iminus1;
-     * B_iplus1 = b_iplus1 * B_i + B_iminus1;
-     */
-    for (i = 0; i <= 1; i++) {
-        vec_push(forloop->exprs,
-            (ast_expression*)ast_store_new(
-                intrin_ctx(intrin),
-                INSTR_STORE_F,
-                (ast_expression*)((i) ? B_iplus1 : A_iplus1),
-                (ast_expression*)ast_binary_new(
-                    intrin_ctx(intrin),
-                    INSTR_ADD_F,
-                    (ast_expression*)ast_binary_new(
-                        intrin_ctx(intrin),
-                        INSTR_MUL_F,
-                        (ast_expression*)b_iplus1,
-                        (ast_expression*) ((i) ? B_i : A_i)
-                    ),
-                    (ast_expression*)((i) ? B_iminus1 : A_iminus1)
-                )
-            )
-        );
-    }
-
-    /*
-     * A_iminus1 = A_i;
-     * B_iminus1 = B_i;
-     */
-    for (i = 0; i <= 1; i++) {
-        vec_push(forloop->exprs,
-            (ast_expression*)ast_store_new(
-                intrin_ctx(intrin),
-                INSTR_STORE_F,
-                (ast_expression*)((i) ? B_iminus1 : A_iminus1),
-                (ast_expression*)((i) ? B_i       : A_i)
-            )
-        );
-    }
-
-    /*
-     * A_i = A_iplus1;
-     * B_i = B_iplus1;
-     */
-    for (i = 0; i <= 1; i++) {
-        vec_push(forloop->exprs,
-            (ast_expression*)ast_store_new(
-                intrin_ctx(intrin),
-                INSTR_STORE_F,
-                (ast_expression*)((i) ? B_i      : A_i),
-                (ast_expression*)((i) ? B_iplus1 : A_iplus1)
-            )
-        );
-    }
-
-    /*
-     * if (whole <= 1.0f + eps)
-     *     break;
-     */
-    vec_push(forloop->exprs,
-        (ast_expression*)ast_ifthen_new(
-            intrin_ctx(intrin),
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_LE,
-                (ast_expression*)whole,
-                (ast_expression*)ast_binary_new(
-                    intrin_ctx(intrin),
-                    INSTR_ADD_F,
-                    (ast_expression*)intrin->fold->imm_float[1],
-                    (ast_expression*)eps
-                )
-            ),
-            (ast_expression*)ast_breakcont_new(
-                intrin_ctx(intrin),
-                false,
-                0
-            ),
-            NULL
-        )
-    );
-
-    /*
-     * power = base;
-     * base  = whole;
-     */
-    for (i = 0; i <= 1; i++) {
-        vec_push(forloop->exprs,
-            (ast_expression*)ast_store_new(
-                intrin_ctx(intrin),
-                INSTR_STORE_F,
-                (ast_expression*)((i) ? base  : power),
-                (ast_expression*)((i) ? whole : base)
-            )
-        );
-    }
-
-    /* add the for loop block */
-    vec_push(block->exprs,
-        (ast_expression*)ast_loop_new(
-            intrin_ctx(intrin),
-            NULL,
-            /* for(; 1; ) ?? (can this be NULL too?) */
-            (ast_expression*)intrin->fold->imm_float[1],
-            false,
-            NULL,
-            false,
-            NULL,
-            (ast_expression*)forloop
-        )
-    );
-
-    /* return sign * A_i / B_il */
-    vec_push(block->exprs,
-        (ast_expression*)ast_return_new(
-            intrin_ctx(intrin),
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_MUL_F,
-                (ast_expression*)sign,
-                (ast_expression*)ast_binary_new(
-                    intrin_ctx(intrin),
-                    INSTR_DIV_F,
-                    (ast_expression*)A_i,
-                    (ast_expression*)B_i
-                )
-            )
-        )
-    );
-
-    vec_push(func->blocks, block);
-    intrin_reg(intrin, value, func);
-
-    return (ast_expression*)value;
-}
-
-static ast_expression *intrin_log_variant(intrin_t *intrin, const char *name, float base) {
-    ast_value    *value  = NULL;
-    ast_call     *callln = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "__builtin_ln", name));
-    ast_value    *arg1   = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT);
-    ast_block    *body   = ast_block_new(intrin_ctx(intrin));
-    ast_function *func   = intrin_value(intrin, &value, name, TYPE_FLOAT);
-
-    vec_push(value->expression.params, arg1);
-
-    vec_push(callln->params, (ast_expression*)arg1);
-    vec_push(callln->params, (ast_expression*)fold_constgen_float(intrin->fold, base, false));
-
-    vec_push(body->exprs,
-        (ast_expression*)ast_return_new(
-            intrin_ctx(intrin),
-            (ast_expression*)callln
-        )
-    );
-
-    vec_push(func->blocks, body);
-    intrin_reg(intrin, value, func);
-    return (ast_expression*)value;
-}
-
-static ast_expression *intrin_log(intrin_t *intrin) {
-    return intrin_log_variant(intrin, "log", 2.7182818284590452354);
-}
-static ast_expression *intrin_log10(intrin_t *intrin) {
-    return intrin_log_variant(intrin, "log10", 10);
-}
-static ast_expression *intrin_log2(intrin_t *intrin) {
-    return intrin_log_variant(intrin, "log2", 2);
-}
-static ast_expression *intrin_logb(intrin_t *intrin) {
-    /* FLT_RADIX == 2 for now */
-    return intrin_log_variant(intrin, "log2", 2);
-}
-
-static ast_expression *intrin_shift_variant(intrin_t *intrin, const char *name, size_t instr) {
-    /*
-     * float [shift] (float a, float b) {
-     *   return floor(a [instr] pow(2, b));
-     */
-    ast_value    *value     = NULL;
-    ast_call     *callpow   = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "pow", name));
-    ast_call     *callfloor = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "floor", name));
-    ast_value    *a         = ast_value_new(intrin_ctx(intrin), "a", TYPE_FLOAT);
-    ast_value    *b         = ast_value_new(intrin_ctx(intrin), "b", TYPE_FLOAT);
-    ast_block    *body      = ast_block_new(intrin_ctx(intrin));
-    ast_function *func      = intrin_value(intrin, &value, name, TYPE_FLOAT);
-
-    vec_push(value->expression.params, a);
-    vec_push(value->expression.params, b);
-
-    /* <callpow> = pow(2, b) */
-    vec_push(callpow->params, (ast_expression*)intrin->fold->imm_float[3]);
-    vec_push(callpow->params, (ast_expression*)b);
-
-    /* <callfloor> = floor(a [instr] <callpow>) */
-    vec_push(
-        callfloor->params,
-        (ast_expression*)ast_binary_new(
-            intrin_ctx(intrin),
-            instr,
-            (ast_expression*)a,
-            (ast_expression*)callpow
-        )
-    );
-
-    /* return <callfloor> */
-    vec_push(body->exprs,
-        (ast_expression*)ast_return_new(
-            intrin_ctx(intrin),
-            (ast_expression*)callfloor
-        )
-    );
-
-    vec_push(func->blocks, body);
-    intrin_reg(intrin, value, func);
-    return (ast_expression*)value;
-}
-
-static ast_expression *intrin_lshift(intrin_t *intrin) {
-    return intrin_shift_variant(intrin, "lshift", INSTR_MUL_F);
-}
-
-static ast_expression *intrin_rshift(intrin_t *intrin) {
-    return intrin_shift_variant(intrin, "rshift", INSTR_DIV_F);
-}
-
-/*
- * TODO: make static (and handle ast_type_string) here for the builtin
- * instead of in SYA parse close.
- */
-ast_expression *intrin_debug_typestring(intrin_t *intrin) {
-    (void)intrin;
-    return (ast_expression*)0x1;
-}
-
-static const intrin_func_t intrinsics[] = {
-    {&intrin_isfinite,         "__builtin_isfinite",         "isfinite", 1},
-    {&intrin_isinf,            "__builtin_isinf",            "isinf",    1},
-    {&intrin_isnan,            "__builtin_isnan",            "isnan",    1},
-    {&intrin_isnormal,         "__builtin_isnormal",         "isnormal", 1},
-    {&intrin_signbit,          "__builtin_signbit",          "signbit",  1},
-    {&intrin_acosh,            "__builtin_acosh",            "acosh",    1},
-    {&intrin_asinh,            "__builtin_asinh",            "asinh",    1},
-    {&intrin_atanh,            "__builtin_atanh",            "atanh",    1},
-    {&intrin_exp,              "__builtin_exp",              "exp",      1},
-    {&intrin_exp2,             "__builtin_exp2",             "exp2",     1},
-    {&intrin_expm1,            "__builtin_expm1",            "expm1",    1},
-    {&intrin_mod,              "__builtin_mod",              "mod",      2},
-    {&intrin_pow,              "__builtin_pow",              "pow",      2},
-    {&intrin_fabs,             "__builtin_fabs",             "fabs",     1},
-    {&intrin_log,              "__builtin_log",              "log",      1},
-    {&intrin_log10,            "__builtin_log10",            "log10",    1},
-    {&intrin_log2,             "__builtin_log2",             "log2",     1},
-    {&intrin_logb,             "__builtin_logb",             "logb",     1},
-    {&intrin_lshift,           "__builtin_lshift",           "",         2},
-    {&intrin_rshift,           "__builtin_rshift",           "",         2},
-    {&intrin_epsilon,          "__builtin_epsilon",          "",         0},
-    {&intrin_nan,              "__builtin_nan",              "",         0},
-    {&intrin_inf,              "__builtin_inf",              "",         0},
-    {&intrin_ln,               "__builtin_ln",               "",         2},
-    {&intrin_debug_typestring, "__builtin_debug_typestring", "",         0},
-    {&intrin_nullfunc,         "#nullfunc",                  "",         0}
-};
-
-static void intrin_error(intrin_t *intrin, const char *fmt, ...) {
-    va_list ap;
-    va_start(ap, fmt);
-    vcompile_error(intrin->parser->lex->tok.ctx, fmt, ap);
-    va_end(ap);
-}
-
-/* exposed */
-intrin_t *intrin_init(parser_t *parser) {
-    intrin_t *intrin = (intrin_t*)mem_a(sizeof(intrin_t));
-    size_t    i;
-
-    intrin->parser     = parser;
-    intrin->fold       = parser->fold;
-    intrin->intrinsics = NULL;
-    intrin->generated  = NULL;
-
-    vec_append(intrin->intrinsics, GMQCC_ARRAY_COUNT(intrinsics), intrinsics);
-
-    /* populate with null pointers for tracking generation */
-    for (i = 0; i < GMQCC_ARRAY_COUNT(intrinsics); i++)
-        vec_push(intrin->generated, NULL);
-
-    return intrin;
-}
-
-void intrin_cleanup(intrin_t *intrin) {
-    vec_free(intrin->intrinsics);
-    vec_free(intrin->generated);
-    mem_d(intrin);
-}
-
-ast_expression *intrin_fold(intrin_t *intrin, ast_value *value, ast_expression **exprs) {
-    size_t i;
-    if (!value || !value->name)
-        return NULL;
-    for (i = 0; i < vec_size(intrin->intrinsics); i++)
-        if (!strcmp(value->name, intrin->intrinsics[i].name))
-            return (vec_size(exprs) != intrin->intrinsics[i].args)
-                        ? NULL
-                        : fold_intrin(intrin->fold, value->name + 10, exprs);
-    return NULL;
-}
-
-static GMQCC_INLINE ast_expression *intrin_func_try(intrin_t *intrin, size_t offset, const char *compare) {
-    size_t i;
-    for (i = 0; i < vec_size(intrin->intrinsics); i++) {
-        if (strcmp(*(char **)((char *)&intrin->intrinsics[i] + offset), compare))
-            continue;
-        if (intrin->generated[i])
-            return intrin->generated[i];
-        return intrin->generated[i] = intrin->intrinsics[i].intrin(intrin);
-    }
-    return NULL;
-}
-
-static ast_expression *intrin_func_self(intrin_t *intrin, const char *name, const char *from) {
-    size_t           i;
-    ast_expression  *find;
-
-    /* try current first */
-    if ((find = parser_find_global(intrin->parser, name)) && ((ast_value*)find)->expression.vtype == TYPE_FUNCTION)
-        for (i = 0; i < vec_size(intrin->parser->functions); ++i)
-            if (((ast_value*)find)->name && !strcmp(intrin->parser->functions[i]->name, ((ast_value*)find)->name) && intrin->parser->functions[i]->builtin < 0)
-                return find;
-    /* try name second */
-    if ((find = intrin_func_try(intrin, offsetof(intrin_func_t, name),  name)))
-        return find;
-    /* try alias third */
-    if ((find = intrin_func_try(intrin, offsetof(intrin_func_t, alias), name)))
-        return find;
-
-    if (from) {
-        intrin_error(intrin, "need function `%s', compiler depends on it for `__builtin_%s'", name, from);
-        return intrin_func_self(intrin, "#nullfunc", NULL);
-    }
-    return NULL;
-}
-
-ast_expression *intrin_func(intrin_t *intrin, const char *name) {
-    return intrin_func_self(intrin, name, NULL);
-}
diff --git a/intrin.cpp b/intrin.cpp
new file mode 100644 (file)
index 0000000..b37b50f
--- /dev/null
@@ -0,0 +1,2048 @@
+#include <string.h>
+
+#include "ast.h"
+#include "fold.h"
+#include "parser.h"
+
+lex_ctx_t intrin::ctx() const {
+    return parser_ctx(m_parser);
+}
+
+ast_function *intrin::value(ast_value **out, const char *name, qc_type vtype) {
+    ast_value *value = nullptr;
+    ast_function *func  = nullptr;
+    char buffer[1024];
+    char stype [1024];
+
+    util_snprintf(buffer, sizeof(buffer), "__builtin_%s", name);
+    util_snprintf(stype,  sizeof(stype),   "<%s>",        type_name[vtype]);
+
+    value = new ast_value(ctx(), buffer, TYPE_FUNCTION);
+    value->m_intrinsic = true;
+    value->m_next = new ast_value(ctx(), stype, vtype);
+    func = ast_function::make(ctx(), buffer, value);
+    value->m_flags |= AST_FLAG_ERASEABLE;
+
+    *out = value;
+    return func;
+}
+
+void intrin::reg(ast_value *const value, ast_function *const func) {
+    m_parser->functions.push_back(func);
+    m_parser->globals.push_back(value);
+}
+
+#define QC_POW_EPSILON 0.00001f
+
+ast_expression *intrin::nullfunc() {
+    ast_value *val = nullptr;
+    ast_function *func = value(&val, nullptr, TYPE_VOID);
+    reg(val, func);
+    return val;
+}
+
+ast_expression *intrin::isfinite_() {
+    /*
+     * float isfinite(float x) {
+     *     return !(isnan(x) || isinf(x));
+     * }
+     */
+    ast_value    *val     = nullptr;
+    ast_value    *x         = new ast_value(ctx(), "x", TYPE_FLOAT);
+    ast_function *func      = value(&val, "isfinite", TYPE_FLOAT);
+    ast_call     *callisnan = ast_call::make(ctx(), func_self("isnan", "isfinite"));
+    ast_call     *callisinf = ast_call::make(ctx(), func_self("isinf", "isfinite"));
+    ast_block    *block     = new ast_block(ctx());
+
+    /* float x; */
+    val->m_type_params.emplace_back(x);
+
+    /* <callisnan> = isnan(x); */
+    callisnan->m_params.push_back(x);
+
+    /* <callisinf> = isinf(x); */
+    callisinf->m_params.push_back(x);
+
+    /* return (!<callisnan> || <callisinf>); */
+    block->m_exprs.push_back(
+        new ast_return(
+            ctx(),
+            ast_unary::make(
+                ctx(),
+                INSTR_NOT_F,
+                new ast_binary(
+                    ctx(),
+                    INSTR_OR,
+                    callisnan,
+                    callisinf
+                )
+            )
+        )
+    );
+
+    func->m_blocks.emplace_back(block);
+    reg(val, func);
+
+    return val;;
+}
+
+ast_expression *intrin::isinf_() {
+    /*
+     * float isinf(float x) {
+     *     return (x != 0.0) && (x + x == x);
+     * }
+     */
+    ast_value *val = nullptr;
+    ast_value *x = new ast_value(ctx(), "x", TYPE_FLOAT);
+    ast_block *body = new ast_block(ctx());
+    ast_function *func = value(&val, "isinf", TYPE_FLOAT);
+
+    body->m_exprs.push_back(
+        new ast_return(
+            ctx(),
+            new ast_binary(
+                ctx(),
+                INSTR_AND,
+                new ast_binary(
+                    ctx(),
+                    INSTR_NE_F,
+                    x,
+                    m_fold->m_imm_float[0]
+                ),
+                new ast_binary(
+                    ctx(),
+                    INSTR_EQ_F,
+                    new ast_binary(
+                        ctx(),
+                        INSTR_ADD_F,
+                        x,
+                        x
+                    ),
+                    x
+                )
+            )
+        )
+    );
+
+    val->m_type_params.emplace_back(x);
+    func->m_blocks.emplace_back(body);
+
+    reg(val, func);
+
+    return val;
+}
+
+ast_expression *intrin::isnan_() {
+    /*
+     * float isnan(float x) {
+     *   float local;
+     *   local = x;
+     *
+     *   return (x != local);
+     * }
+     */
+    ast_value *val = nullptr;
+    ast_value *arg1 = new ast_value(ctx(), "x",TYPE_FLOAT);
+    ast_value *local = new ast_value(ctx(), "local", TYPE_FLOAT);
+    ast_block *body  = new ast_block(ctx());
+    ast_function *func = value(&val, "isnan", TYPE_FLOAT);
+
+    body->m_locals.push_back(local);
+    body->m_exprs.push_back(
+        new ast_store(
+            ctx(),
+            INSTR_STORE_F,
+            local,
+            arg1
+        )
+    );
+
+    body->m_exprs.push_back(
+        new ast_return(
+            ctx(),
+            new ast_binary(
+                ctx(),
+                INSTR_NE_F,
+                arg1,
+                local
+            )
+        )
+    );
+
+    val->m_type_params.emplace_back(arg1);
+    func->m_blocks.emplace_back(body);
+
+    reg(val, func);
+
+    return val;
+}
+
+ast_expression *intrin::isnormal_() {
+    /*
+     * float isnormal(float x) {
+     *     return isfinite(x);
+     * }
+     */
+    ast_value *val = nullptr;
+    ast_call *callisfinite = ast_call::make(ctx(), func_self("isfinite", "isnormal"));
+    ast_value *x = new ast_value(ctx(), "x", TYPE_FLOAT);
+    ast_block *body = new ast_block(ctx());
+    ast_function *func = value(&val, "isnormal", TYPE_FLOAT);
+
+    val->m_type_params.emplace_back(x);
+    callisfinite->m_params.push_back(x);
+
+    /* return <callisfinite> */
+    body->m_exprs.push_back(
+        new ast_return(
+            ctx(),
+            callisfinite
+        )
+    );
+
+    func->m_blocks.emplace_back(body);
+    reg(val, func);
+    return val;
+}
+
+ast_expression *intrin::signbit_() {
+    /*
+     * float signbit(float x) {
+     *     return (x < 0);
+     * }
+     */
+    ast_value *val = nullptr;
+    ast_value *x = new ast_value(ctx(), "x", TYPE_FLOAT);
+    ast_block *body = new ast_block(ctx());
+    ast_function *func = value(&val, "signbit", TYPE_FLOAT);
+
+    val->m_type_params.emplace_back(x);
+
+    /* return (x < 0); */
+    body->m_exprs.push_back(
+        new ast_return(
+            ctx(),
+            new ast_ternary(
+                ctx(),
+                new ast_binary(
+                    ctx(),
+                    INSTR_LT,
+                    x,
+                    m_fold->m_imm_float[0]
+                ),
+                m_fold->m_imm_float[1],
+                m_fold->m_imm_float[0]
+            )
+        )
+    );
+
+    func->m_blocks.emplace_back(body);
+    reg(val, func);
+    return val;
+}
+
+ast_expression *intrin::acosh_() {
+    /*
+     * float acosh(float x) {
+     *     return log(x + sqrt((x * x) - 1));
+     * }
+     */
+    ast_value    *val    = nullptr;
+    ast_value    *x        = new ast_value(ctx(), "x", TYPE_FLOAT);
+    ast_call     *calllog  = ast_call::make(ctx(), func_self("log", "acosh"));
+    ast_call     *callsqrt = ast_call::make(ctx(), func_self("sqrt", "acosh"));
+    ast_block    *body     = new ast_block(ctx());
+    ast_function *func     = value(&val, "acosh", TYPE_FLOAT);
+
+    val->m_type_params.emplace_back(x);
+
+    /* <callsqrt> = sqrt((x * x) - 1); */
+    callsqrt->m_params.push_back(
+        new ast_binary(
+            ctx(),
+            INSTR_SUB_F,
+            new ast_binary(
+                ctx(),
+                INSTR_MUL_F,
+                x,
+                x
+            ),
+            m_fold->m_imm_float[1]
+        )
+    );
+
+    /* <calllog> = log(x + <callsqrt>); */
+    calllog->m_params.push_back(
+        new ast_binary(
+            ctx(),
+            INSTR_ADD_F,
+            x,
+            callsqrt
+        )
+    );
+
+    /* return <calllog>; */
+    body->m_exprs.push_back(
+        new ast_return(
+            ctx(),
+            calllog
+        )
+    );
+
+    func->m_blocks.emplace_back(body);
+    reg(val, func);
+    return val;
+}
+
+ast_expression *intrin::asinh_() {
+    /*
+     * float asinh(float x) {
+     *     return log(x + sqrt((x * x) + 1));
+     * }
+     */
+    ast_value *val = nullptr;
+    ast_value *x = new ast_value(ctx(), "x", TYPE_FLOAT);
+    ast_call *calllog = ast_call::make(ctx(), func_self("log", "asinh"));
+    ast_call *callsqrt = ast_call::make(ctx(), func_self("sqrt", "asinh"));
+    ast_block *body = new ast_block(ctx());
+    ast_function *func = value(&val, "asinh", TYPE_FLOAT);
+
+    val->m_type_params.emplace_back(x);
+
+    /* <callsqrt> = sqrt((x * x) + 1); */
+    callsqrt->m_params.push_back(
+        new ast_binary(
+            ctx(),
+            INSTR_ADD_F,
+            new ast_binary(
+                ctx(),
+                INSTR_MUL_F,
+                x,
+                x
+            ),
+            m_fold->m_imm_float[1]
+        )
+    );
+
+    /* <calllog> = log(x + <callsqrt>); */
+    calllog->m_params.push_back(
+        new ast_binary(
+            ctx(),
+            INSTR_ADD_F,
+            x,
+            callsqrt
+        )
+    );
+
+    /* return <calllog>; */
+    body->m_exprs.push_back(
+        new ast_return(
+            ctx(),
+            calllog
+        )
+    );
+
+    func->m_blocks.emplace_back(body);
+    reg(val, func);
+    return val;
+}
+
+ast_expression *intrin::atanh_() {
+    /*
+     * float atanh(float x) {
+     *     return 0.5 * log((1 + x) / (1 - x))
+     * }
+     */
+    ast_value    *val   = nullptr;
+    ast_value    *x       = new ast_value(ctx(), "x", TYPE_FLOAT);
+    ast_call     *calllog = ast_call::make(ctx(), func_self("log", "atanh"));
+    ast_block    *body    = new ast_block(ctx());
+    ast_function *func    = value(&val, "atanh", TYPE_FLOAT);
+
+    val->m_type_params.emplace_back(x);
+
+    /* <callog> = log((1 + x) / (1 - x)); */
+    calllog->m_params.push_back(
+        new ast_binary(
+            ctx(),
+            INSTR_DIV_F,
+            new ast_binary(
+                ctx(),
+                INSTR_ADD_F,
+                m_fold->m_imm_float[1],
+                x
+            ),
+            new ast_binary(
+                ctx(),
+                INSTR_SUB_F,
+                m_fold->m_imm_float[1],
+                x
+            )
+        )
+    );
+
+    /* return 0.5 * <calllog>; */
+    body->m_exprs.push_back(
+        new ast_binary(
+            ctx(),
+            INSTR_MUL_F,
+            m_fold->constgen_float(0.5, false),
+            calllog
+        )
+    );
+
+    func->m_blocks.emplace_back(body);
+    reg(val, func);
+    return val;
+}
+
+ast_expression *intrin::exp_() {
+    /*
+     * float exp(float x) {
+     *     float sum = 1.0;
+     *     float acc = 1.0;
+     *     float i;
+     *     for (i = 1; i < 200; ++i)
+     *         sum += (acc *= x / i);
+     *
+     *     return sum;
+     * }
+     */
+    ast_value *val = nullptr;
+    ast_value *x = new ast_value(ctx(), "x", TYPE_FLOAT);
+    ast_value *sum = new ast_value(ctx(), "sum", TYPE_FLOAT);
+    ast_value *acc = new ast_value(ctx(), "acc", TYPE_FLOAT);
+    ast_value *i = new ast_value(ctx(), "i",   TYPE_FLOAT);
+    ast_block *body = new ast_block(ctx());
+    ast_function *func = value(&val, "exp", TYPE_FLOAT);
+
+    val->m_type_params.emplace_back(x);
+
+    body->m_locals.push_back(sum);
+    body->m_locals.push_back(acc);
+    body->m_locals.push_back(i);
+
+    /* sum = 1.0; */
+    body->m_exprs.push_back(
+        new ast_store(
+            ctx(),
+            INSTR_STORE_F,
+            sum,
+            m_fold->m_imm_float[1]
+        )
+    );
+
+    /* acc = 1.0; */
+    body->m_exprs.push_back(
+        new ast_store(
+            ctx(),
+            INSTR_STORE_F,
+            acc,
+            m_fold->m_imm_float[1]
+        )
+    );
+
+    /*
+     * for (i = 1; i < 200; ++i)
+     *     sum += (acc *= x / i);
+     */
+    body->m_exprs.push_back(
+        new ast_loop(
+            ctx(),
+            /* i = 1; */
+            new ast_store(
+                ctx(),
+                INSTR_STORE_F,
+                i,
+                m_fold->m_imm_float[1]
+            ),
+            /* i < 200; */
+            new ast_binary(
+                ctx(),
+                INSTR_LT,
+                i,
+                m_fold->constgen_float(200.0f, false)
+            ),
+            false,
+            nullptr,
+            false,
+            /* ++i; */
+            new ast_binstore(
+                ctx(),
+                INSTR_STORE_F,
+                INSTR_ADD_F,
+                i,
+                m_fold->m_imm_float[1]
+            ),
+            /* sum += (acc *= (x / i)) */
+            new ast_binstore(
+                ctx(),
+                INSTR_STORE_F,
+                INSTR_ADD_F,
+                sum,
+                new ast_binstore(
+                    ctx(),
+                    INSTR_STORE_F,
+                    INSTR_MUL_F,
+                    acc,
+                    new ast_binary(
+                        ctx(),
+                        INSTR_DIV_F,
+                        x,
+                        i
+                    )
+                )
+            )
+        )
+    );
+
+    /* return sum; */
+    body->m_exprs.push_back(
+        new ast_return(
+            ctx(),
+            sum
+        )
+    );
+
+    func->m_blocks.emplace_back(body);
+    reg(val, func);
+    return val;
+}
+
+ast_expression *intrin::exp2_() {
+    /*
+     * float exp2(float x) {
+     *     return pow(2, x);
+     * }
+     */
+    ast_value *val = nullptr;
+    ast_call  *callpow = ast_call::make(ctx(), func_self("pow", "exp2"));
+    ast_value *arg1 = new ast_value(ctx(), "x", TYPE_FLOAT);
+    ast_block *body = new ast_block(ctx());
+    ast_function *func = value(&val, "exp2", TYPE_FLOAT);
+
+    val->m_type_params.emplace_back(arg1);
+
+    callpow->m_params.push_back(m_fold->m_imm_float[3]);
+    callpow->m_params.push_back(arg1);
+
+    /* return <callpow> */
+    body->m_exprs.push_back(
+        new ast_return(
+            ctx(),
+            callpow
+        )
+    );
+
+    func->m_blocks.emplace_back(body);
+    reg(val, func);
+    return val;
+}
+
+ast_expression *intrin::expm1_() {
+    /*
+     * float expm1(float x) {
+     *     return exp(x) - 1;
+     * }
+     */
+    ast_value *val = nullptr;
+    ast_call *callexp = ast_call::make(ctx(), func_self("exp", "expm1"));
+    ast_value *x = new ast_value(ctx(), "x", TYPE_FLOAT);
+    ast_block *body = new ast_block(ctx());
+    ast_function *func = value(&val, "expm1", TYPE_FLOAT);
+
+    val->m_type_params.emplace_back(x);
+
+    /* <callexp> = exp(x); */
+    callexp->m_params.push_back(x);
+
+    /* return <callexp> - 1; */
+    body->m_exprs.push_back(
+        new ast_return(
+            ctx(),
+            new ast_binary(
+                ctx(),
+                INSTR_SUB_F,
+                callexp,
+                m_fold->m_imm_float[1]
+            )
+        )
+    );
+
+    func->m_blocks.emplace_back(body);
+    reg(val, func);
+    return val;
+}
+
+ast_expression *intrin::pow_() {
+    /*
+     *
+     * float pow(float base, float exp) {
+     *     float result;
+     *     float low;
+     *     float high;
+     *     float mid;
+     *     float square;
+     *     float accumulate;
+     *
+     *     if (exp == 0.0)
+     *         return 1;
+     *     if (exp == 1.0)
+     *         return base;
+     *     if (exp < 0)
+     *         return 1.0 / pow(base, -exp);
+     *     if (exp >= 1) {
+     *         result = pow(base, exp / 2);
+     *         return result * result;
+     *     }
+     *
+     *     low        = 0.0f;
+     *     high       = 1.0f;
+     *     square     = sqrt(base);
+     *     accumulate = square;
+     *     mid        = high / 2.0f
+     *
+     *     while (fabs(mid - exp) > QC_POW_EPSILON) {
+     *         square = sqrt(square);
+     *         if (mid < exp) {
+     *             low         = mid;
+     *             accumulate *= square;
+     *         } else {
+     *             high        = mid;
+     *             accumulate *= (1.0f / square);
+     *         }
+     *         mid = (low + high) / 2;
+     *     }
+     *     return accumulate;
+     * }
+     */
+    ast_value    *val = nullptr;
+    ast_function *func = value(&val, "pow", TYPE_FLOAT);
+
+    /* prepare some calls for later */
+    ast_call *callpow1  = ast_call::make(ctx(), val);                  /* for pow(base, -exp)    */
+    ast_call *callpow2  = ast_call::make(ctx(), val);                  /* for pow(vase, exp / 2) */
+    ast_call *callsqrt1 = ast_call::make(ctx(), func_self("sqrt", "pow")); /* for sqrt(base)         */
+    ast_call *callsqrt2 = ast_call::make(ctx(), func_self("sqrt", "pow")); /* for sqrt(square)       */
+    ast_call *callfabs  = ast_call::make(ctx(), func_self("fabs", "pow")); /* for fabs(mid - exp)    */
+
+    /* prepare some blocks for later */
+    ast_block *expgt1       = new ast_block(ctx());
+    ast_block *midltexp     = new ast_block(ctx());
+    ast_block *midltexpelse = new ast_block(ctx());
+    ast_block *whileblock   = new ast_block(ctx());
+
+    /* float pow(float base, float exp) */
+    ast_value    *base = new ast_value(ctx(), "base", TYPE_FLOAT);
+    ast_value    *exp  = new ast_value(ctx(), "exp",  TYPE_FLOAT);
+    /* { */
+    ast_block    *body = new ast_block(ctx());
+
+    /*
+     * float result;
+     * float low;
+     * float high;
+     * float square;
+     * float accumulate;
+     * float mid;
+     */
+    ast_value *result     = new ast_value(ctx(), "result",     TYPE_FLOAT);
+    ast_value *low        = new ast_value(ctx(), "low",        TYPE_FLOAT);
+    ast_value *high       = new ast_value(ctx(), "high",       TYPE_FLOAT);
+    ast_value *square     = new ast_value(ctx(), "square",     TYPE_FLOAT);
+    ast_value *accumulate = new ast_value(ctx(), "accumulate", TYPE_FLOAT);
+    ast_value *mid        = new ast_value(ctx(), "mid",        TYPE_FLOAT);
+    body->m_locals.push_back(result);
+    body->m_locals.push_back(low);
+    body->m_locals.push_back(high);
+    body->m_locals.push_back(square);
+    body->m_locals.push_back(accumulate);
+    body->m_locals.push_back(mid);
+
+    val->m_type_params.emplace_back(base);
+    val->m_type_params.emplace_back(exp);
+
+    /*
+     * if (exp == 0.0)
+     *     return 1;
+     */
+    body->m_exprs.push_back(
+        new ast_ifthen(
+            ctx(),
+            new ast_binary(
+                ctx(),
+                INSTR_EQ_F,
+                exp,
+                m_fold->m_imm_float[0]
+            ),
+            new ast_return(
+                ctx(),
+                m_fold->m_imm_float[1]
+            ),
+            nullptr
+        )
+    );
+
+    /*
+     * if (exp == 1.0)
+     *     return base;
+     */
+    body->m_exprs.push_back(
+        new ast_ifthen(
+            ctx(),
+            new ast_binary(
+                ctx(),
+                INSTR_EQ_F,
+                exp,
+                m_fold->m_imm_float[1]
+            ),
+            new ast_return(
+                ctx(),
+                base
+            ),
+            nullptr
+        )
+    );
+
+    /* <callpow1> = pow(base, -exp) */
+    callpow1->m_params.push_back(base);
+    callpow1->m_params.push_back(
+        ast_unary::make(
+            ctx(),
+            VINSTR_NEG_F,
+            exp
+        )
+    );
+
+    /*
+     * if (exp < 0)
+     *     return 1.0 / <callpow1>;
+     */
+    body->m_exprs.push_back(
+        new ast_ifthen(
+            ctx(),
+            new ast_binary(
+                ctx(),
+                INSTR_LT,
+                exp,
+                m_fold->m_imm_float[0]
+            ),
+            new ast_return(
+                ctx(),
+                new ast_binary(
+                    ctx(),
+                    INSTR_DIV_F,
+                    m_fold->m_imm_float[1],
+                    callpow1
+                )
+            ),
+            nullptr
+        )
+    );
+
+    /* <callpow2> = pow(base, exp / 2) */
+    callpow2->m_params.push_back(base);
+    callpow2->m_params.push_back(
+        new ast_binary(
+            ctx(),
+            INSTR_DIV_F,
+            exp,
+            m_fold->m_imm_float[3] /* 2.0f */
+        )
+    );
+
+    /*
+     * <expgt1> = {
+     *     result = <callpow2>;
+     *     return result * result;
+     * }
+     */
+    expgt1->m_exprs.push_back(
+        new ast_store(
+            ctx(),
+            INSTR_STORE_F,
+            result,
+            callpow2
+        )
+    );
+    expgt1->m_exprs.push_back(
+        new ast_return(
+            ctx(),
+            new ast_binary(
+                ctx(),
+                INSTR_MUL_F,
+                result,
+                result
+            )
+        )
+    );
+
+    /*
+     * if (exp >= 1) {
+     *     <expgt1>
+     * }
+     */
+    body->m_exprs.push_back(
+        new ast_ifthen(
+            ctx(),
+            new ast_binary(
+                ctx(),
+                INSTR_GE,
+                exp,
+                m_fold->m_imm_float[1]
+            ),
+            expgt1,
+            nullptr
+        )
+    );
+
+    /*
+     * <callsqrt1> = sqrt(base)
+     */
+    callsqrt1->m_params.push_back(base);
+
+    /*
+     * low        = 0.0f;
+     * high       = 1.0f;
+     * square     = sqrt(base);
+     * accumulate = square;
+     * mid        = high / 2.0f;
+     */
+    body->m_exprs.push_back(
+        new ast_store(ctx(),
+            INSTR_STORE_F,
+            low,
+            m_fold->m_imm_float[0]
+        )
+    );
+    body->m_exprs.push_back(
+        new ast_store(
+            ctx(),
+            INSTR_STORE_F,
+            high,
+            m_fold->m_imm_float[1]
+        )
+    );
+
+    body->m_exprs.push_back(
+        new ast_store(
+            ctx(),
+            INSTR_STORE_F,
+            square,
+            callsqrt1
+        )
+    );
+
+    body->m_exprs.push_back(
+        new ast_store(
+            ctx(),
+            INSTR_STORE_F,
+            accumulate,
+            square
+        )
+    );
+    body->m_exprs.push_back(
+        new ast_store(
+            ctx(),
+            INSTR_STORE_F,
+            mid,
+            new ast_binary(
+                ctx(),
+                INSTR_DIV_F,
+                high,
+                m_fold->m_imm_float[3] /* 2.0f */
+            )
+        )
+    );
+
+    /*
+     * <midltexp> = {
+     *     low         = mid;
+     *     accumulate *= square;
+     * }
+     */
+    midltexp->m_exprs.push_back(
+        new ast_store(
+            ctx(),
+            INSTR_STORE_F,
+            low,
+            mid
+        )
+    );
+    midltexp->m_exprs.push_back(
+        new ast_binstore(
+            ctx(),
+            INSTR_STORE_F,
+            INSTR_MUL_F,
+            accumulate,
+            square
+        )
+    );
+
+    /*
+     * <midltexpelse> = {
+     *     high        = mid;
+     *     accumulate *= (1.0 / square);
+     * }
+     */
+    midltexpelse->m_exprs.push_back(
+        new ast_store(
+            ctx(),
+            INSTR_STORE_F,
+            high,
+            mid
+        )
+    );
+    midltexpelse->m_exprs.push_back(
+        new ast_binstore(
+            ctx(),
+            INSTR_STORE_F,
+            INSTR_MUL_F,
+            accumulate,
+            new ast_binary(
+                ctx(),
+                INSTR_DIV_F,
+                m_fold->m_imm_float[1],
+                square
+            )
+        )
+    );
+
+    /*
+     * <callsqrt2> = sqrt(square)
+     */
+    callsqrt2->m_params.push_back(square);
+
+    /*
+     * <whileblock> = {
+     *     square = <callsqrt2>;
+     *     if (mid < exp)
+     *          <midltexp>;
+     *     else
+     *          <midltexpelse>;
+     *
+     *     mid = (low + high) / 2;
+     * }
+     */
+    whileblock->m_exprs.push_back(
+        new ast_store(
+            ctx(),
+            INSTR_STORE_F,
+            square,
+            callsqrt2
+        )
+    );
+    whileblock->m_exprs.push_back(
+        new ast_ifthen(
+            ctx(),
+            new ast_binary(
+                ctx(),
+                INSTR_LT,
+                mid,
+                exp
+            ),
+            midltexp,
+            midltexpelse
+        )
+    );
+    whileblock->m_exprs.push_back(
+        new ast_store(
+            ctx(),
+            INSTR_STORE_F,
+            mid,
+            new ast_binary(
+                ctx(),
+                INSTR_DIV_F,
+                new ast_binary(
+                    ctx(),
+                    INSTR_ADD_F,
+                    low,
+                    high
+                ),
+                m_fold->m_imm_float[3] /* 2.0f */
+            )
+        )
+    );
+
+    /*
+     * <callabs> = fabs(mid - exp)
+     */
+    callfabs->m_params.push_back(
+        new ast_binary(
+            ctx(),
+            INSTR_SUB_F,
+            mid,
+            exp
+        )
+    );
+
+    /*
+     * while (<callfabs>  > epsilon)
+     *     <whileblock>
+     */
+    body->m_exprs.push_back(
+        new ast_loop(
+            ctx(),
+            /* init */
+            nullptr,
+            /* pre condition */
+            new ast_binary(
+                ctx(),
+                INSTR_GT,
+                callfabs,
+                m_fold->constgen_float(QC_POW_EPSILON, false)
+            ),
+            /* pre not */
+            false,
+            /* post condition */
+            nullptr,
+            /* post not */
+            false,
+            /* increment expression */
+            nullptr,
+            /* code block */
+            whileblock
+        )
+    );
+
+    /* return accumulate */
+    body->m_exprs.push_back(
+        new ast_return(
+            ctx(),
+            accumulate
+        )
+    );
+
+    /* } */
+    func->m_blocks.emplace_back(body);
+    reg(val, func);
+    return val;
+}
+
+ast_expression *intrin::mod_() {
+    /*
+     * float mod(float a, float b) {
+     *     float div = a / b;
+     *     float sign = (div < 0.0f) ? -1 : 1;
+     *     return a - b * sign * floor(sign * div);
+     * }
+     */
+    ast_value    *val = nullptr;
+    ast_call     *call  = ast_call::make(ctx(), func_self("floor", "mod"));
+    ast_value    *a     = new ast_value(ctx(), "a",    TYPE_FLOAT);
+    ast_value    *b     = new ast_value(ctx(), "b",    TYPE_FLOAT);
+    ast_value    *div   = new ast_value(ctx(), "div",  TYPE_FLOAT);
+    ast_value    *sign  = new ast_value(ctx(), "sign", TYPE_FLOAT);
+    ast_block    *body  = new ast_block(ctx());
+    ast_function *func  = value(&val, "mod", TYPE_FLOAT);
+
+    val->m_type_params.emplace_back(a);
+    val->m_type_params.emplace_back(b);
+
+    body->m_locals.push_back(div);
+    body->m_locals.push_back(sign);
+
+    /* div = a / b; */
+    body->m_exprs.push_back(
+        new ast_store(
+            ctx(),
+            INSTR_STORE_F,
+            div,
+            new ast_binary(
+                ctx(),
+                INSTR_DIV_F,
+                a,
+                b
+            )
+        )
+    );
+
+    /* sign = (div < 0.0f) ? -1 : 1; */
+    body->m_exprs.push_back(
+        new ast_store(
+            ctx(),
+            INSTR_STORE_F,
+            sign,
+            new ast_ternary(
+                ctx(),
+                new ast_binary(
+                    ctx(),
+                    INSTR_LT,
+                    div,
+                    m_fold->m_imm_float[0]
+                ),
+                m_fold->m_imm_float[2],
+                m_fold->m_imm_float[1]
+            )
+        )
+    );
+
+    /* floor(sign * div) */
+    call->m_params.push_back(
+        new ast_binary(
+            ctx(),
+            INSTR_MUL_F,
+            sign,
+            div
+        )
+    );
+
+    /* return a - b * sign * <call> */
+    body->m_exprs.push_back(
+        new ast_return(
+            ctx(),
+            new ast_binary(
+                ctx(),
+                INSTR_SUB_F,
+                a,
+                new ast_binary(
+                    ctx(),
+                    INSTR_MUL_F,
+                    b,
+                    new ast_binary(
+                        ctx(),
+                        INSTR_MUL_F,
+                        sign,
+                        call
+                    )
+                )
+            )
+        )
+    );
+
+    func->m_blocks.emplace_back(body);
+    reg(val, func);
+    return val;
+}
+
+ast_expression *intrin::fabs_() {
+    /*
+     * float fabs(float x) {
+     *     return x < 0 ? -x : x;
+     * }
+     */
+    ast_value    *val  = nullptr;
+    ast_value    *arg1   = new ast_value(ctx(), "x", TYPE_FLOAT);
+    ast_block    *body   = new ast_block(ctx());
+    ast_function *func   = value(&val, "fabs", TYPE_FLOAT);
+
+    body->m_exprs.push_back(
+        new ast_return(
+            ctx(),
+            new ast_ternary(
+                ctx(),
+                new ast_binary(
+                    ctx(),
+                    INSTR_LE,
+                    arg1,
+                    m_fold->m_imm_float[0]
+                ),
+                ast_unary::make(
+                    ctx(),
+                    VINSTR_NEG_F,
+                    arg1
+                ),
+                arg1
+            )
+        )
+    );
+
+    val->m_type_params.emplace_back(arg1);
+
+    func->m_blocks.emplace_back(body);
+    reg(val, func);
+    return val;
+}
+
+ast_expression *intrin::epsilon_() {
+    /*
+     * float epsilon(void) {
+     *     float eps = 1.0f;
+     *     do { eps /= 2.0f; } while ((1.0f + (eps / 2.0f)) != 1.0f);
+     *     return eps;
+     * }
+     */
+    ast_value    *val  = nullptr;
+    ast_value    *eps    = new ast_value(ctx(), "eps", TYPE_FLOAT);
+    ast_block    *body   = new ast_block(ctx());
+    ast_function *func   = value(&val, "epsilon", TYPE_FLOAT);
+
+    body->m_locals.push_back(eps);
+
+    /* eps = 1.0f; */
+    body->m_exprs.push_back(
+        new ast_store(
+            ctx(),
+            INSTR_STORE_F,
+            eps,
+            m_fold->m_imm_float[0]
+        )
+    );
+
+    body->m_exprs.push_back(
+        new ast_loop(
+            ctx(),
+            nullptr,
+            nullptr,
+            false,
+            new ast_binary(
+                ctx(),
+                INSTR_NE_F,
+                new ast_binary(
+                    ctx(),
+                    INSTR_ADD_F,
+                    m_fold->m_imm_float[1],
+                    new ast_binary(
+                        ctx(),
+                        INSTR_MUL_F,
+                        eps,
+                        m_fold->m_imm_float[3] /* 2.0f */
+                    )
+                ),
+                m_fold->m_imm_float[1]
+            ),
+            false,
+            nullptr,
+            new ast_binstore(
+                ctx(),
+                INSTR_STORE_F,
+                INSTR_DIV_F,
+                eps,
+                m_fold->m_imm_float[3] /* 2.0f */
+            )
+        )
+    );
+
+    /* return eps; */
+    body->m_exprs.push_back(
+        new ast_return(
+            ctx(),
+            eps
+        )
+    );
+
+    func->m_blocks.emplace_back(body);
+    reg(val, func);
+    return val;
+}
+
+ast_expression *intrin::nan_() {
+    /*
+     * float nan(void) {
+     *     float x = 0.0f;
+     *     return x / x;
+     * }
+     */
+    ast_value    *val  = nullptr;
+    ast_value    *x      = new ast_value(ctx(), "x", TYPE_FLOAT);
+    ast_function *func   = value(&val, "nan", TYPE_FLOAT);
+    ast_block    *block  = new ast_block(ctx());
+
+    block->m_locals.push_back(x);
+
+    block->m_exprs.push_back(
+        new ast_store(
+            ctx(),
+            INSTR_STORE_F,
+            x,
+            m_fold->m_imm_float[0]
+        )
+    );
+
+    block->m_exprs.push_back(
+        new ast_return(
+            ctx(),
+            new ast_binary(
+                ctx(),
+                INSTR_DIV_F,
+                x,
+                x
+            )
+        )
+    );
+
+    func->m_blocks.emplace_back(block);
+    reg(val, func);
+    return val;
+}
+
+ast_expression *intrin::inf_() {
+    /*
+     * float inf(void) {
+     *     float x = 1.0f;
+     *     float y = 0.0f;
+     *     return x / y;
+     * }
+     */
+    ast_value    *val  = nullptr;
+    ast_value    *x      = new ast_value(ctx(), "x", TYPE_FLOAT);
+    ast_value    *y      = new ast_value(ctx(), "y", TYPE_FLOAT);
+    ast_function *func   = value(&val, "inf", TYPE_FLOAT);
+    ast_block    *block  = new ast_block(ctx());
+    size_t        i;
+
+    block->m_locals.push_back(x);
+    block->m_locals.push_back(y);
+
+    /* to keep code size down */
+    for (i = 0; i <= 1; i++) {
+        block->m_exprs.push_back(
+            new ast_store(
+                ctx(),
+                INSTR_STORE_F,
+                ((i == 0) ? x : y),
+                m_fold->m_imm_float[i]
+            )
+        );
+    }
+
+    block->m_exprs.push_back(
+        new ast_return(
+            ctx(),
+            new ast_binary(
+                ctx(),
+                INSTR_DIV_F,
+                x,
+                y
+            )
+        )
+    );
+
+    func->m_blocks.emplace_back(block);
+    reg(val, func);
+    return val;
+}
+
+ast_expression *intrin::ln_() {
+    /*
+     * float log(float power, float base) {
+     *   float whole;
+     *   float nth
+     *   float sign = 1.0f;
+     *   float eps  = epsilon();
+     *
+     *   if (power <= 1.0f || bbase <= 1.0) {
+     *       if (power <= 0.0f || base <= 0.0f)
+     *           return nan();
+     *
+     *       if (power < 1.0f) {
+     *           power = 1.0f / power;
+     *           sign *= -1.0f;
+     *       }
+     *
+     *       if (base < 1.0f) {
+     *           sign *= -1.0f;
+     *           base  = 1.0f / base;
+     *       }
+     *   }
+     *
+     *   float A_i       = 1;
+     *   float B_i       = 0;
+     *   float A_iminus1 = 0;
+     *   float B_iminus1 = 1;
+     *
+     *   for (;;) {
+     *       whole = power;
+     *       nth   = 0.0f;
+     *
+     *       while (whole >= base) {
+     *           float base2    = base;
+     *           float n2       = 1.0f;
+     *           float newbase2 = base2 * base2;
+     *
+     *           while (whole >= newbase2) {
+     *               base2     = newbase2;
+     *               n2       *= 2;
+     *               newbase2 *= newbase2;
+     *           }
+     *
+     *           whole /= base2;
+     *           nth += n2;
+     *       }
+     *
+     *       float b_iplus1 = n;
+     *       float A_iplus1 = b_iplus1 * A_i + A_iminus1;
+     *       float B_iplus1 = b_iplus1 * B_i + B_iminus1;
+     *
+     *       A_iminus1 = A_i;
+     *       B_iminus1 = B_i;
+     *       A_i       = A_iplus1;
+     *       B_i       = B_iplus1;
+     *
+     *       if (whole <= 1.0f + eps)
+     *           break;
+     *
+     *       power = base;
+     *       bower = whole;
+     *   }
+     *   return sign * A_i / B_i;
+     * }
+     */
+
+    ast_value *val = nullptr;
+    ast_value *power = new ast_value(ctx(), "power", TYPE_FLOAT);
+    ast_value *base = new ast_value(ctx(), "base",TYPE_FLOAT);
+    ast_value *whole= new ast_value(ctx(), "whole", TYPE_FLOAT);
+    ast_value *nth = new ast_value(ctx(), "nth", TYPE_FLOAT);
+    ast_value *sign = new ast_value(ctx(), "sign", TYPE_FLOAT);
+    ast_value *A_i = new ast_value(ctx(), "A_i", TYPE_FLOAT);
+    ast_value *B_i = new ast_value(ctx(), "B_i", TYPE_FLOAT);
+    ast_value *A_iminus1 = new ast_value(ctx(), "A_iminus1", TYPE_FLOAT);
+    ast_value *B_iminus1 = new ast_value(ctx(), "B_iminus1", TYPE_FLOAT);
+    ast_value *b_iplus1 = new ast_value(ctx(), "b_iplus1", TYPE_FLOAT);
+    ast_value *A_iplus1 = new ast_value(ctx(), "A_iplus1", TYPE_FLOAT);
+    ast_value *B_iplus1 = new ast_value(ctx(), "B_iplus1", TYPE_FLOAT);
+    ast_value *eps = new ast_value(ctx(), "eps", TYPE_FLOAT);
+    ast_value *base2 = new ast_value(ctx(), "base2", TYPE_FLOAT);
+    ast_value *n2 = new ast_value(ctx(), "n2",TYPE_FLOAT);
+    ast_value *newbase2 = new ast_value(ctx(), "newbase2", TYPE_FLOAT);
+    ast_block *block = new ast_block(ctx());
+    ast_block *plt1orblt1 = new ast_block(ctx()); // (power <= 1.0f || base <= 1.0f)
+    ast_block *plt1 = new ast_block(ctx()); // (power < 1.0f)
+    ast_block *blt1 = new ast_block(ctx()); // (base< 1.0f)
+    ast_block *forloop = new ast_block(ctx()); // for(;;)
+    ast_block *whileloop = new ast_block(ctx()); // while (whole >= base)
+    ast_block *nestwhile= new ast_block(ctx()); // while (whole >= newbase2)
+    ast_function *func = value(&val, "ln", TYPE_FLOAT);
+    size_t i;
+
+    val->m_type_params.emplace_back(power);
+    val->m_type_params.emplace_back(base);
+
+    block->m_locals.push_back(whole);
+    block->m_locals.push_back(nth);
+    block->m_locals.push_back(sign);
+    block->m_locals.push_back(eps);
+    block->m_locals.push_back(A_i);
+    block->m_locals.push_back(B_i);
+    block->m_locals.push_back(A_iminus1);
+    block->m_locals.push_back(B_iminus1);
+
+    /* sign = 1.0f; */
+    block->m_exprs.push_back(
+        new ast_store(
+            ctx(),
+            INSTR_STORE_F,
+            sign,
+            m_fold->m_imm_float[1]
+        )
+    );
+
+    /* eps = __builtin_epsilon(); */
+    block->m_exprs.push_back(
+        new ast_store(
+            ctx(),
+            INSTR_STORE_F,
+            eps,
+            ast_call::make(
+                ctx(),
+                func_self("__builtin_epsilon", "ln")
+            )
+        )
+    );
+
+    /*
+     * A_i       = 1;
+     * B_i       = 0;
+     * A_iminus1 = 0;
+     * B_iminus1 = 1;
+     */
+    for (i = 0; i <= 1; i++) {
+        int j;
+        for (j = 1; j >= 0; j--) {
+            block->m_exprs.push_back(
+                new ast_store(
+                    ctx(),
+                    INSTR_STORE_F,
+                    ((j) ? ((i) ? B_iminus1 : A_i)
+                                          : ((i) ? A_iminus1 : B_i)),
+                    m_fold->m_imm_float[j]
+                )
+            );
+        }
+    }
+
+    /*
+     * <plt1> = {
+     *     power = 1.0f / power;
+     *     sign *= -1.0f;
+     * }
+     * <blt1> = {
+     *     base  = 1.0f / base;
+     *     sign *= -1.0f;
+     * }
+     */
+    for (i = 0; i <= 1; i++) {
+        ((i) ? blt1 : plt1)->m_exprs.push_back(
+            new ast_store(
+                ctx(),
+                INSTR_STORE_F,
+                ((i) ? base : power),
+                new ast_binary(
+                    ctx(),
+                    INSTR_DIV_F,
+                    m_fold->m_imm_float[1],
+                    ((i) ? base : power)
+                )
+            )
+        );
+        plt1->m_exprs.push_back(
+            new ast_binstore(
+                ctx(),
+                INSTR_STORE_F,
+                INSTR_MUL_F,
+                sign,
+                m_fold->m_imm_float[2]
+            )
+        );
+    }
+
+    /*
+     * <plt1orblt1> = {
+     *     if (power <= 0.0 || base <= 0.0f)
+     *         return __builtin_nan();
+     *     if (power < 1.0f)
+     *         <plt1>
+     *     if (base < 1.0f)
+     *         <blt1>
+     * }
+     */
+    plt1orblt1->m_exprs.push_back(
+        new ast_ifthen(
+            ctx(),
+            new ast_binary(
+                ctx(),
+                INSTR_OR,
+                new ast_binary(
+                    ctx(),
+                    INSTR_LE,
+                    power,
+                    m_fold->m_imm_float[0]
+                ),
+                new ast_binary(
+                    ctx(),
+                    INSTR_LE,
+                    base,
+                    m_fold->m_imm_float[0]
+                )
+            ),
+            new ast_return(
+                ctx(),
+                ast_call::make(
+                    ctx(),
+                    func_self("__builtin_nan", "ln")
+                )
+            ),
+            nullptr
+        )
+    );
+
+    for (i = 0; i <= 1; i++) {
+        plt1orblt1->m_exprs.push_back(
+            new ast_ifthen(
+                ctx(),
+                new ast_binary(
+                    ctx(),
+                    INSTR_LT,
+                    ((i) ? base : power),
+                    m_fold->m_imm_float[1]
+                ),
+                ((i) ? blt1 : plt1),
+                nullptr
+            )
+        );
+    }
+
+    block->m_exprs.push_back(plt1orblt1);
+
+
+    /* whole = power; */
+    forloop->m_exprs.push_back(
+        new ast_store(
+            ctx(),
+            INSTR_STORE_F,
+            whole,
+            power
+        )
+    );
+
+    /* nth = 0.0f; */
+    forloop->m_exprs.push_back(
+        new ast_store(
+            ctx(),
+            INSTR_STORE_F,
+            nth,
+            m_fold->m_imm_float[0]
+        )
+    );
+
+    /* base2 = base; */
+    whileloop->m_exprs.push_back(
+        new ast_store(
+            ctx(),
+            INSTR_STORE_F,
+            base2,
+            base
+        )
+    );
+
+    /* n2 = 1.0f; */
+    whileloop->m_exprs.push_back(
+        new ast_store(
+            ctx(),
+            INSTR_STORE_F,
+            n2,
+            m_fold->m_imm_float[1]
+        )
+    );
+
+    /* newbase2 = base2 * base2; */
+    whileloop->m_exprs.push_back(
+        new ast_store(
+            ctx(),
+            INSTR_STORE_F,
+            newbase2,
+            new ast_binary(
+                ctx(),
+                INSTR_MUL_F,
+                base2,
+                base2
+            )
+        )
+    );
+
+    /* while loop locals */
+    whileloop->m_locals.push_back(base2);
+    whileloop->m_locals.push_back(n2);
+    whileloop->m_locals.push_back(newbase2);
+
+    /* base2 = newbase2; */
+    nestwhile->m_exprs.push_back(
+        new ast_store(
+            ctx(),
+            INSTR_STORE_F,
+            base2,
+            newbase2
+        )
+    );
+
+    /* n2 *= 2; */
+    nestwhile->m_exprs.push_back(
+        new ast_binstore(
+            ctx(),
+            INSTR_STORE_F,
+            INSTR_MUL_F,
+            n2,
+            m_fold->m_imm_float[3] /* 2.0f */
+        )
+    );
+
+    /* newbase2 *= newbase2; */
+    nestwhile->m_exprs.push_back(
+        new ast_binstore(
+            ctx(),
+            INSTR_STORE_F,
+            INSTR_MUL_F,
+            newbase2,
+            newbase2
+        )
+    );
+
+    /* while (whole >= newbase2) */
+    whileloop->m_exprs.push_back(
+        new ast_loop(
+            ctx(),
+            nullptr,
+            new ast_binary(
+                ctx(),
+                INSTR_GE,
+                whole,
+                newbase2
+            ),
+            false,
+            nullptr,
+            false,
+            nullptr,
+            nestwhile
+        )
+    );
+
+    /* whole /= base2; */
+    whileloop->m_exprs.push_back(
+        new ast_binstore(
+            ctx(),
+            INSTR_STORE_F,
+            INSTR_DIV_F,
+            whole,
+            base2
+        )
+    );
+
+    /* nth += n2; */
+    whileloop->m_exprs.push_back(
+        new ast_binstore(
+            ctx(),
+            INSTR_STORE_F,
+            INSTR_ADD_F,
+            nth,
+            n2
+        )
+    );
+
+    /* while (whole >= base) */
+    forloop->m_exprs.push_back(
+        new ast_loop(
+            ctx(),
+            nullptr,
+            new ast_binary(
+                ctx(),
+                INSTR_GE,
+                whole,
+                base
+            ),
+            false,
+            nullptr,
+            false,
+            nullptr,
+            whileloop
+        )
+    );
+
+    forloop->m_locals.push_back(b_iplus1);
+    forloop->m_locals.push_back(A_iplus1);
+    forloop->m_locals.push_back(B_iplus1);
+
+    /* b_iplus1 = nth; */
+    forloop->m_exprs.push_back(
+        new ast_store(
+            ctx(),
+            INSTR_STORE_F,
+            b_iplus1,
+            nth
+        )
+    );
+
+    /*
+     * A_iplus1 = b_iplus1 * A_i + A_iminus1;
+     * B_iplus1 = b_iplus1 * B_i + B_iminus1;
+     */
+    for (i = 0; i <= 1; i++) {
+        forloop->m_exprs.push_back(
+            new ast_store(
+                ctx(),
+                INSTR_STORE_F,
+                ((i) ? B_iplus1 : A_iplus1),
+                new ast_binary(
+                    ctx(),
+                    INSTR_ADD_F,
+                    new ast_binary(
+                        ctx(),
+                        INSTR_MUL_F,
+                        b_iplus1,
+                         ((i) ? B_i : A_i)
+                    ),
+                    ((i) ? B_iminus1 : A_iminus1)
+                )
+            )
+        );
+    }
+
+    /*
+     * A_iminus1 = A_i;
+     * B_iminus1 = B_i;
+     */
+    for (i = 0; i <= 1; i++) {
+        forloop->m_exprs.push_back(
+            new ast_store(
+                ctx(),
+                INSTR_STORE_F,
+                ((i) ? B_iminus1 : A_iminus1),
+                ((i) ? B_i       : A_i)
+            )
+        );
+    }
+
+    /*
+     * A_i = A_iplus1;
+     * B_i = B_iplus1;
+     */
+    for (i = 0; i <= 1; i++) {
+        forloop->m_exprs.push_back(
+            new ast_store(
+                ctx(),
+                INSTR_STORE_F,
+                ((i) ? B_i      : A_i),
+                ((i) ? B_iplus1 : A_iplus1)
+            )
+        );
+    }
+
+    /*
+     * if (whole <= 1.0f + eps)
+     *     break;
+     */
+    forloop->m_exprs.push_back(
+        new ast_ifthen(
+            ctx(),
+            new ast_binary(
+                ctx(),
+                INSTR_LE,
+                whole,
+                new ast_binary(
+                    ctx(),
+                    INSTR_ADD_F,
+                    m_fold->m_imm_float[1],
+                    eps
+                )
+            ),
+            new ast_breakcont(
+                ctx(),
+                false,
+                0
+            ),
+            nullptr
+        )
+    );
+
+    /*
+     * power = base;
+     * base  = whole;
+     */
+    for (i = 0; i <= 1; i++) {
+        forloop->m_exprs.push_back(
+            new ast_store(
+                ctx(),
+                INSTR_STORE_F,
+                ((i) ? base  : power),
+                ((i) ? whole : base)
+            )
+        );
+    }
+
+    /* add the for loop block */
+    block->m_exprs.push_back(
+        new ast_loop(
+            ctx(),
+            nullptr,
+            /* for(; 1; ) ?? (can this be nullptr too?) */
+            m_fold->m_imm_float[1],
+            false,
+            nullptr,
+            false,
+            nullptr,
+            forloop
+        )
+    );
+
+    /* return sign * A_i / B_il */
+    block->m_exprs.push_back(
+        new ast_return(
+            ctx(),
+            new ast_binary(
+                ctx(),
+                INSTR_MUL_F,
+                sign,
+                new ast_binary(
+                    ctx(),
+                    INSTR_DIV_F,
+                    A_i,
+                    B_i
+                )
+            )
+        )
+    );
+
+    func->m_blocks.emplace_back(block);
+    reg(val, func);
+    return val;
+}
+
+ast_expression *intrin::log_variant(const char *name, float base) {
+    ast_value *val = nullptr;
+    ast_call *callln = ast_call::make(ctx(), func_self("__builtin_ln", name));
+    ast_value *arg1 = new ast_value(ctx(), "x", TYPE_FLOAT);
+    ast_block *body = new ast_block(ctx());
+    ast_function *func = value(&val, name, TYPE_FLOAT);
+
+    val->m_type_params.emplace_back(arg1);
+
+    callln->m_params.push_back(arg1);
+    callln->m_params.push_back(m_fold->constgen_float(base, false));
+
+    body->m_exprs.push_back(
+        new ast_return(
+            ctx(),
+            callln
+        )
+    );
+
+    func->m_blocks.emplace_back(body);
+    reg(val, func);
+    return val;
+}
+
+ast_expression *intrin::log_() {
+    return log_variant("log", 2.7182818284590452354);
+}
+ast_expression *intrin::log10_() {
+    return log_variant("log10", 10);
+}
+ast_expression *intrin::log2_() {
+    return log_variant("log2", 2);
+}
+ast_expression *intrin::logb_() {
+    /* FLT_RADIX == 2 for now */
+    return log_variant("log2", 2);
+}
+
+ast_expression *intrin::shift_variant(const char *name, size_t instr) {
+    /*
+     * float [shift] (float a, float b) {
+     *   return floor(a [instr] pow(2, b));
+     */
+    ast_value *val = nullptr;
+    ast_call *callpow = ast_call::make(ctx(), func_self("pow", name));
+    ast_call *callfloor = ast_call::make(ctx(), func_self("floor", name));
+    ast_value *a = new ast_value(ctx(), "a", TYPE_FLOAT);
+    ast_value *b = new ast_value(ctx(), "b", TYPE_FLOAT);
+    ast_block *body = new ast_block(ctx());
+    ast_function *func = value(&val, name, TYPE_FLOAT);
+
+    val->m_type_params.emplace_back(a);
+    val->m_type_params.emplace_back(b);
+
+    /* <callpow> = pow(2, b) */
+    callpow->m_params.push_back(m_fold->m_imm_float[3]);
+    callpow->m_params.push_back(b);
+
+    /* <callfloor> = floor(a [instr] <callpow>) */
+    callfloor->m_params.push_back(
+        new ast_binary(
+            ctx(),
+            instr,
+            a,
+            callpow
+        )
+    );
+
+    /* return <callfloor> */
+    body->m_exprs.push_back(
+        new ast_return(
+            ctx(),
+            callfloor
+        )
+    );
+
+    func->m_blocks.emplace_back(body);
+    reg(val, func);
+    return val;
+}
+
+ast_expression *intrin::lshift() {
+    return shift_variant("lshift", INSTR_MUL_F);
+}
+
+ast_expression *intrin::rshift() {
+    return shift_variant("rshift", INSTR_DIV_F);
+}
+
+void intrin::error(const char *fmt, ...) {
+    va_list ap;
+    va_start(ap, fmt);
+    vcompile_error(ctx(), fmt, ap);
+    va_end(ap);
+}
+
+/* exposed */
+ast_expression *intrin::debug_typestring() {
+    return (ast_expression*)0x1;
+}
+
+intrin::intrin(parser_t *parser)
+    : m_parser(parser)
+    , m_fold(&parser->m_fold)
+{
+    static const intrin_func_t intrinsics[] = {
+        {&intrin::isfinite_,        "__builtin_isfinite",         "isfinite", 1},
+        {&intrin::isinf_,           "__builtin_isinf",            "isinf",    1},
+        {&intrin::isnan_,           "__builtin_isnan",            "isnan",    1},
+        {&intrin::isnormal_,        "__builtin_isnormal",         "isnormal", 1},
+        {&intrin::signbit_,         "__builtin_signbit",          "signbit",  1},
+        {&intrin::acosh_,           "__builtin_acosh",            "acosh",    1},
+        {&intrin::asinh_,           "__builtin_asinh",            "asinh",    1},
+        {&intrin::atanh_,           "__builtin_atanh",            "atanh",    1},
+        {&intrin::exp_,             "__builtin_exp",              "exp",      1},
+        {&intrin::exp2_,            "__builtin_exp2",             "exp2",     1},
+        {&intrin::expm1_,           "__builtin_expm1",            "expm1",    1},
+        {&intrin::mod_,             "__builtin_mod",              "mod",      2},
+        {&intrin::pow_,             "__builtin_pow",              "pow",      2},
+        {&intrin::fabs_,            "__builtin_fabs",             "fabs",     1},
+        {&intrin::log_,             "__builtin_log",              "log",      1},
+        {&intrin::log10_,           "__builtin_log10",            "log10",    1},
+        {&intrin::log2_,            "__builtin_log2",             "log2",     1},
+        {&intrin::logb_,            "__builtin_logb",             "logb",     1},
+        {&intrin::lshift,           "__builtin_lshift",           "",         2},
+        {&intrin::rshift,           "__builtin_rshift",           "",         2},
+        {&intrin::epsilon_,         "__builtin_epsilon",          "",         0},
+        {&intrin::nan_,             "__builtin_nan",              "",         0},
+        {&intrin::inf_,             "__builtin_inf",              "",         0},
+        {&intrin::ln_,              "__builtin_ln",               "",         2},
+        {&intrin::debug_typestring, "__builtin_debug_typestring", "",         0},
+        {&intrin::nullfunc,         "#nullfunc",                  "",         0}
+    };
+
+    for (auto &it : intrinsics) {
+        m_intrinsics.push_back(it);
+        m_generated.push_back(nullptr);
+    }
+}
+
+ast_expression *intrin::do_fold(ast_value *val, ast_expression **exprs) {
+    if (!val || !val->m_name.length())
+        return nullptr;
+    static constexpr size_t kPrefixLength = 10; // "__builtin_"
+    for (auto &it : m_intrinsics) {
+        if (val->m_name == it.name)
+            return (vec_size(exprs) != it.args)
+                        ? nullptr
+                        : m_fold->intrinsic(val->m_name.c_str() + kPrefixLength, exprs);
+    }
+    return nullptr;
+}
+
+ast_expression *intrin::func_try(size_t offset, const char *compare) {
+    for (auto &it : m_intrinsics) {
+        const size_t index = &it - &m_intrinsics[0];
+        if (strcmp(*(char **)((char *)&it + offset), compare))
+            continue;
+        if (m_generated[index])
+            return m_generated[index];
+        return m_generated[index] = (this->*it.intrin_func_t::function)();
+    }
+    return nullptr;
+}
+
+ast_expression *intrin::func_self(const char *name, const char *from) {
+    ast_expression *find;
+    /* try current first */
+    if ((find = parser_find_global(m_parser, name)) && ((ast_value*)find)->m_vtype == TYPE_FUNCTION)
+        for (auto &it : m_parser->functions)
+            if (reinterpret_cast<ast_value*>(find)->m_name.length() && it->m_name == reinterpret_cast<ast_value*>(find)->m_name && it->m_builtin < 0)
+                return find;
+    /* try name second */
+    if ((find = func_try(offsetof(intrin_func_t, name),  name)))
+        return find;
+    /* try alias third */
+    if ((find = func_try(offsetof(intrin_func_t, alias), name)))
+        return find;
+
+    if (from) {
+        error("need function `%s', compiler depends on it for `__builtin_%s'", name, from);
+        return func_self("#nullfunc", nullptr);
+    }
+    return nullptr;
+}
+
+ast_expression *intrin::func(const char *name) {
+    return func_self(name, nullptr);
+}
diff --git a/intrin.h b/intrin.h
new file mode 100644 (file)
index 0000000..fe9ee78
--- /dev/null
+++ b/intrin.h
@@ -0,0 +1,74 @@
+#ifndef GMQCC_INTRIN_HDR
+#define GMQCC_INTRIN_HDR
+#include "gmqcc.h"
+
+struct fold;
+struct parser_t;
+
+struct ast_function;
+struct ast_expression;
+struct ast_value;
+
+struct intrin;
+
+struct intrin_func_t {
+    ast_expression *(intrin::*function)();
+    const char *name;
+    const char *alias;
+    size_t args;
+};
+
+struct intrin {
+    intrin() = default;
+    intrin(parser_t *parser);
+
+    ast_expression *debug_typestring();
+    ast_expression *do_fold(ast_value *val, ast_expression **exprs);
+    ast_expression *func_try(size_t offset, const char *compare);
+    ast_expression *func_self(const char *name, const char *from);
+    ast_expression *func(const char *name);
+
+protected:
+    lex_ctx_t ctx() const;
+    ast_function *value(ast_value **out, const char *name, qc_type vtype);
+    void reg(ast_value *const value, ast_function *const func);
+
+    ast_expression *nullfunc();
+    ast_expression *isfinite_();
+    ast_expression *isinf_();
+    ast_expression *isnan_();
+    ast_expression *isnormal_();
+    ast_expression *signbit_();
+    ast_expression *acosh_();
+    ast_expression *asinh_();
+    ast_expression *atanh_();
+    ast_expression *exp_();
+    ast_expression *exp2_();
+    ast_expression *expm1_();
+    ast_expression *pow_();
+    ast_expression *mod_();
+    ast_expression *fabs_();
+    ast_expression *epsilon_();
+    ast_expression *nan_();
+    ast_expression *inf_();
+    ast_expression *ln_();
+    ast_expression *log_variant(const char *name, float base);
+    ast_expression *log_();
+    ast_expression *log10_();
+    ast_expression *log2_();
+    ast_expression *logb_();
+    ast_expression *shift_variant(const char *name, size_t instr);
+    ast_expression *lshift();
+    ast_expression *rshift();
+
+    void error(const char *fmt, ...);
+
+private:
+    parser_t *m_parser;
+    fold *m_fold;
+    std::vector<intrin_func_t> m_intrinsics;
+    std::vector<ast_expression*> m_generated;
+};
+
+
+#endif
diff --git a/ir.c b/ir.c
deleted file mode 100644 (file)
index 73cbc9a..0000000
--- a/ir.c
+++ /dev/null
@@ -1,4468 +0,0 @@
-/*
- * Copyright (C) 2012, 2013, 2014, 2015
- *     Wolfgang Bumiller
- *     Dale Weiler
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
- * this software and associated documentation files (the "Software"), to deal in
- * the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is furnished to do
- * so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-#include <stdlib.h>
-#include <string.h>
-
-#include "gmqcc.h"
-#include "ir.h"
-
-/***********************************************************************
- * Type sizes used at multiple points in the IR codegen
- */
-
-const char *type_name[TYPE_COUNT] = {
-    "void",
-    "string",
-    "float",
-    "vector",
-    "entity",
-    "field",
-    "function",
-    "pointer",
-    "integer",
-    "variant",
-    "struct",
-    "union",
-    "array",
-
-    "nil",
-    "<no-expression>"
-};
-
-static size_t type_sizeof_[TYPE_COUNT] = {
-    1, /* TYPE_VOID     */
-    1, /* TYPE_STRING   */
-    1, /* TYPE_FLOAT    */
-    3, /* TYPE_VECTOR   */
-    1, /* TYPE_ENTITY   */
-    1, /* TYPE_FIELD    */
-    1, /* TYPE_FUNCTION */
-    1, /* TYPE_POINTER  */
-    1, /* TYPE_INTEGER  */
-    3, /* TYPE_VARIANT  */
-    0, /* TYPE_STRUCT   */
-    0, /* TYPE_UNION    */
-    0, /* TYPE_ARRAY    */
-    0, /* TYPE_NIL      */
-    0, /* TYPE_NOESPR   */
-};
-
-const uint16_t type_store_instr[TYPE_COUNT] = {
-    INSTR_STORE_F, /* should use I when having integer support */
-    INSTR_STORE_S,
-    INSTR_STORE_F,
-    INSTR_STORE_V,
-    INSTR_STORE_ENT,
-    INSTR_STORE_FLD,
-    INSTR_STORE_FNC,
-    INSTR_STORE_ENT, /* should use I */
-#if 0
-    INSTR_STORE_I, /* integer type */
-#else
-    INSTR_STORE_F,
-#endif
-
-    INSTR_STORE_V, /* variant, should never be accessed */
-
-    VINSTR_END, /* struct */
-    VINSTR_END, /* union  */
-    VINSTR_END, /* array  */
-    VINSTR_END, /* nil    */
-    VINSTR_END, /* noexpr */
-};
-
-const uint16_t field_store_instr[TYPE_COUNT] = {
-    INSTR_STORE_FLD,
-    INSTR_STORE_FLD,
-    INSTR_STORE_FLD,
-    INSTR_STORE_V,
-    INSTR_STORE_FLD,
-    INSTR_STORE_FLD,
-    INSTR_STORE_FLD,
-    INSTR_STORE_FLD,
-#if 0
-    INSTR_STORE_FLD, /* integer type */
-#else
-    INSTR_STORE_FLD,
-#endif
-
-    INSTR_STORE_V, /* variant, should never be accessed */
-
-    VINSTR_END, /* struct */
-    VINSTR_END, /* union  */
-    VINSTR_END, /* array  */
-    VINSTR_END, /* nil    */
-    VINSTR_END, /* noexpr */
-};
-
-const uint16_t type_storep_instr[TYPE_COUNT] = {
-    INSTR_STOREP_F, /* should use I when having integer support */
-    INSTR_STOREP_S,
-    INSTR_STOREP_F,
-    INSTR_STOREP_V,
-    INSTR_STOREP_ENT,
-    INSTR_STOREP_FLD,
-    INSTR_STOREP_FNC,
-    INSTR_STOREP_ENT, /* should use I */
-#if 0
-    INSTR_STOREP_ENT, /* integer type */
-#else
-    INSTR_STOREP_F,
-#endif
-
-    INSTR_STOREP_V, /* variant, should never be accessed */
-
-    VINSTR_END, /* struct */
-    VINSTR_END, /* union  */
-    VINSTR_END, /* array  */
-    VINSTR_END, /* nil    */
-    VINSTR_END, /* noexpr */
-};
-
-const uint16_t type_eq_instr[TYPE_COUNT] = {
-    INSTR_EQ_F, /* should use I when having integer support */
-    INSTR_EQ_S,
-    INSTR_EQ_F,
-    INSTR_EQ_V,
-    INSTR_EQ_E,
-    INSTR_EQ_E, /* FLD has no comparison */
-    INSTR_EQ_FNC,
-    INSTR_EQ_E, /* should use I */
-#if 0
-    INSTR_EQ_I,
-#else
-    INSTR_EQ_F,
-#endif
-
-    INSTR_EQ_V, /* variant, should never be accessed */
-
-    VINSTR_END, /* struct */
-    VINSTR_END, /* union  */
-    VINSTR_END, /* array  */
-    VINSTR_END, /* nil    */
-    VINSTR_END, /* noexpr */
-};
-
-const uint16_t type_ne_instr[TYPE_COUNT] = {
-    INSTR_NE_F, /* should use I when having integer support */
-    INSTR_NE_S,
-    INSTR_NE_F,
-    INSTR_NE_V,
-    INSTR_NE_E,
-    INSTR_NE_E, /* FLD has no comparison */
-    INSTR_NE_FNC,
-    INSTR_NE_E, /* should use I */
-#if 0
-    INSTR_NE_I,
-#else
-    INSTR_NE_F,
-#endif
-
-    INSTR_NE_V, /* variant, should never be accessed */
-
-    VINSTR_END, /* struct */
-    VINSTR_END, /* union  */
-    VINSTR_END, /* array  */
-    VINSTR_END, /* nil    */
-    VINSTR_END, /* noexpr */
-};
-
-const uint16_t type_not_instr[TYPE_COUNT] = {
-    INSTR_NOT_F, /* should use I when having integer support */
-    VINSTR_END,  /* not to be used, depends on string related -f flags */
-    INSTR_NOT_F,
-    INSTR_NOT_V,
-    INSTR_NOT_ENT,
-    INSTR_NOT_ENT,
-    INSTR_NOT_FNC,
-    INSTR_NOT_ENT, /* should use I */
-#if 0
-    INSTR_NOT_I, /* integer type */
-#else
-    INSTR_NOT_F,
-#endif
-
-    INSTR_NOT_V, /* variant, should never be accessed */
-
-    VINSTR_END, /* struct */
-    VINSTR_END, /* union  */
-    VINSTR_END, /* array  */
-    VINSTR_END, /* nil    */
-    VINSTR_END, /* noexpr */
-};
-
-/* protos */
-static ir_value*       ir_value_var(const char *name, int st, int vtype);
-static bool            ir_value_set_name(ir_value*, const char *name);
-static void            ir_value_dump(ir_value*, int (*oprintf)(const char*,...));
-
-static ir_value*       ir_gen_extparam_proto(ir_builder *ir);
-static void            ir_gen_extparam      (ir_builder *ir);
-
-static bool            ir_builder_set_name(ir_builder *self, const char *name);
-
-static ir_function*    ir_function_new(struct ir_builder_s *owner, int returntype);
-static bool            ir_function_set_name(ir_function*, const char *name);
-static void            ir_function_delete(ir_function*);
-static void            ir_function_dump(ir_function*, char *ind, int (*oprintf)(const char*,...));
-
-static ir_value*       ir_block_create_general_instr(ir_block *self, lex_ctx_t, const char *label,
-                                        int op, ir_value *a, ir_value *b, int outype);
-static void            ir_block_delete(ir_block*);
-static ir_block*       ir_block_new(struct ir_function_s *owner, const char *label);
-static bool GMQCC_WARN ir_block_create_store(ir_block*, lex_ctx_t, ir_value *target, ir_value *what);
-static bool            ir_block_set_label(ir_block*, const char *label);
-static void            ir_block_dump(ir_block*, char *ind, int (*oprintf)(const char*,...));
-
-static bool            ir_instr_op(ir_instr*, int op, ir_value *value, bool writing);
-static void            ir_instr_delete(ir_instr*);
-static void            ir_instr_dump(ir_instr* in, char *ind, int (*oprintf)(const char*,...));
-/* error functions */
-
-static void irerror(lex_ctx_t ctx, const char *msg, ...)
-{
-    va_list ap;
-    va_start(ap, msg);
-    con_cvprintmsg(ctx, LVL_ERROR, "internal error", msg, ap);
-    va_end(ap);
-}
-
-static bool GMQCC_WARN irwarning(lex_ctx_t ctx, int warntype, const char *fmt, ...)
-{
-    bool    r;
-    va_list ap;
-    va_start(ap, fmt);
-    r = vcompile_warning(ctx, warntype, fmt, ap);
-    va_end(ap);
-    return r;
-}
-
-/***********************************************************************
- * Vector utility functions
- */
-
-static bool GMQCC_WARN vec_ir_value_find(ir_value **vec, const ir_value *what, size_t *idx)
-{
-    size_t i;
-    size_t len = vec_size(vec);
-    for (i = 0; i < len; ++i) {
-        if (vec[i] == what) {
-            if (idx) *idx = i;
-            return true;
-        }
-    }
-    return false;
-}
-
-static bool GMQCC_WARN vec_ir_block_find(ir_block **vec, ir_block *what, size_t *idx)
-{
-    size_t i;
-    size_t len = vec_size(vec);
-    for (i = 0; i < len; ++i) {
-        if (vec[i] == what) {
-            if (idx) *idx = i;
-            return true;
-        }
-    }
-    return false;
-}
-
-static bool GMQCC_WARN vec_ir_instr_find(ir_instr **vec, ir_instr *what, size_t *idx)
-{
-    size_t i;
-    size_t len = vec_size(vec);
-    for (i = 0; i < len; ++i) {
-        if (vec[i] == what) {
-            if (idx) *idx = i;
-            return true;
-        }
-    }
-    return false;
-}
-
-/***********************************************************************
- * IR Builder
- */
-
-static void ir_block_delete_quick(ir_block* self);
-static void ir_instr_delete_quick(ir_instr *self);
-static void ir_function_delete_quick(ir_function *self);
-
-ir_builder* ir_builder_new(const char *modulename)
-{
-    ir_builder* self;
-    size_t      i;
-
-    self = (ir_builder*)mem_a(sizeof(*self));
-    if (!self)
-        return NULL;
-
-    self->functions   = NULL;
-    self->globals     = NULL;
-    self->fields      = NULL;
-    self->filenames   = NULL;
-    self->filestrings = NULL;
-    self->htglobals   = util_htnew(IR_HT_SIZE);
-    self->htfields    = util_htnew(IR_HT_SIZE);
-    self->htfunctions = util_htnew(IR_HT_SIZE);
-
-    self->extparams       = NULL;
-    self->extparam_protos = NULL;
-
-    self->first_common_globaltemp = 0;
-    self->max_globaltemps         = 0;
-    self->first_common_local      = 0;
-    self->max_locals              = 0;
-
-    self->str_immediate = 0;
-    self->name = NULL;
-    if (!ir_builder_set_name(self, modulename)) {
-        mem_d(self);
-        return NULL;
-    }
-
-    self->nil = ir_value_var("nil", store_value, TYPE_NIL);
-    self->nil->cvq = CV_CONST;
-
-    for (i = 0; i != IR_MAX_VINSTR_TEMPS; ++i) {
-        /* we write to them, but they're not supposed to be used outside the IR, so
-         * let's not allow the generation of ir_instrs which use these.
-         * So it's a constant noexpr.
-         */
-        self->vinstr_temp[i] = ir_value_var("vinstr_temp", store_value, TYPE_NOEXPR);
-        self->vinstr_temp[i]->cvq = CV_CONST;
-    }
-
-    self->reserved_va_count = NULL;
-    self->coverage_func     = NULL;
-
-    self->code              = code_init();
-
-    return self;
-}
-
-void ir_builder_delete(ir_builder* self)
-{
-    size_t i;
-    util_htdel(self->htglobals);
-    util_htdel(self->htfields);
-    util_htdel(self->htfunctions);
-    mem_d((void*)self->name);
-    for (i = 0; i != vec_size(self->functions); ++i) {
-        ir_function_delete_quick(self->functions[i]);
-    }
-    vec_free(self->functions);
-    for (i = 0; i != vec_size(self->extparams); ++i) {
-        ir_value_delete(self->extparams[i]);
-    }
-    vec_free(self->extparams);
-    vec_free(self->extparam_protos);
-    for (i = 0; i != vec_size(self->globals); ++i) {
-        ir_value_delete(self->globals[i]);
-    }
-    vec_free(self->globals);
-    for (i = 0; i != vec_size(self->fields); ++i) {
-        ir_value_delete(self->fields[i]);
-    }
-    ir_value_delete(self->nil);
-    for (i = 0; i != IR_MAX_VINSTR_TEMPS; ++i) {
-        ir_value_delete(self->vinstr_temp[i]);
-    }
-    vec_free(self->fields);
-    vec_free(self->filenames);
-    vec_free(self->filestrings);
-
-    code_cleanup(self->code);
-    mem_d(self);
-}
-
-bool ir_builder_set_name(ir_builder *self, const char *name)
-{
-    if (self->name)
-        mem_d((void*)self->name);
-    self->name = util_strdup(name);
-    return !!self->name;
-}
-
-static ir_function* ir_builder_get_function(ir_builder *self, const char *name)
-{
-    return (ir_function*)util_htget(self->htfunctions, name);
-}
-
-ir_function* ir_builder_create_function(ir_builder *self, const char *name, int outtype)
-{
-    ir_function *fn = ir_builder_get_function(self, name);
-    if (fn) {
-        return NULL;
-    }
-
-    fn = ir_function_new(self, outtype);
-    if (!ir_function_set_name(fn, name))
-    {
-        ir_function_delete(fn);
-        return NULL;
-    }
-    vec_push(self->functions, fn);
-    util_htset(self->htfunctions, name, fn);
-
-    fn->value = ir_builder_create_global(self, fn->name, TYPE_FUNCTION);
-    if (!fn->value) {
-        ir_function_delete(fn);
-        return NULL;
-    }
-
-    fn->value->hasvalue = true;
-    fn->value->outtype = outtype;
-    fn->value->constval.vfunc = fn;
-    fn->value->context = fn->context;
-
-    return fn;
-}
-
-static ir_value* ir_builder_get_global(ir_builder *self, const char *name)
-{
-    return (ir_value*)util_htget(self->htglobals, name);
-}
-
-ir_value* ir_builder_create_global(ir_builder *self, const char *name, int vtype)
-{
-    ir_value *ve;
-
-    if (name[0] != '#')
-    {
-        ve = ir_builder_get_global(self, name);
-        if (ve) {
-            return NULL;
-        }
-    }
-
-    ve = ir_value_var(name, store_global, vtype);
-    vec_push(self->globals, ve);
-    util_htset(self->htglobals, name, ve);
-    return ve;
-}
-
-ir_value* ir_builder_get_va_count(ir_builder *self)
-{
-    if (self->reserved_va_count)
-        return self->reserved_va_count;
-    return (self->reserved_va_count = ir_builder_create_global(self, "reserved:va_count", TYPE_FLOAT));
-}
-
-static ir_value* ir_builder_get_field(ir_builder *self, const char *name)
-{
-    return (ir_value*)util_htget(self->htfields, name);
-}
-
-
-ir_value* ir_builder_create_field(ir_builder *self, const char *name, int vtype)
-{
-    ir_value *ve = ir_builder_get_field(self, name);
-    if (ve) {
-        return NULL;
-    }
-
-    ve = ir_value_var(name, store_global, TYPE_FIELD);
-    ve->fieldtype = vtype;
-    vec_push(self->fields, ve);
-    util_htset(self->htfields, name, ve);
-    return ve;
-}
-
-/***********************************************************************
- *IR Function
- */
-
-static bool ir_function_naive_phi(ir_function*);
-static void ir_function_enumerate(ir_function*);
-static bool ir_function_calculate_liferanges(ir_function*);
-static bool ir_function_allocate_locals(ir_function*);
-
-ir_function* ir_function_new(ir_builder* owner, int outtype)
-{
-    ir_function *self;
-    self = (ir_function*)mem_a(sizeof(*self));
-
-    if (!self)
-        return NULL;
-
-    memset(self, 0, sizeof(*self));
-
-    self->name = NULL;
-    if (!ir_function_set_name(self, "<@unnamed>")) {
-        mem_d(self);
-        return NULL;
-    }
-    self->flags = 0;
-
-    self->owner = owner;
-    self->context.file = "<@no context>";
-    self->context.line = 0;
-    self->outtype = outtype;
-    self->value = NULL;
-    self->builtin = 0;
-
-    self->params = NULL;
-    self->blocks = NULL;
-    self->values = NULL;
-    self->locals = NULL;
-
-    self->max_varargs = 0;
-
-    self->code_function_def = -1;
-    self->allocated_locals = 0;
-    self->globaltemps      = 0;
-
-    self->run_id = 0;
-    return self;
-}
-
-bool ir_function_set_name(ir_function *self, const char *name)
-{
-    if (self->name)
-        mem_d((void*)self->name);
-    self->name = util_strdup(name);
-    return !!self->name;
-}
-
-static void ir_function_delete_quick(ir_function *self)
-{
-    size_t i;
-    mem_d((void*)self->name);
-
-    for (i = 0; i != vec_size(self->blocks); ++i)
-        ir_block_delete_quick(self->blocks[i]);
-    vec_free(self->blocks);
-
-    vec_free(self->params);
-
-    for (i = 0; i != vec_size(self->values); ++i)
-        ir_value_delete(self->values[i]);
-    vec_free(self->values);
-
-    for (i = 0; i != vec_size(self->locals); ++i)
-        ir_value_delete(self->locals[i]);
-    vec_free(self->locals);
-
-    /* self->value is deleted by the builder */
-
-    mem_d(self);
-}
-
-void ir_function_delete(ir_function *self)
-{
-    size_t i;
-    mem_d((void*)self->name);
-
-    for (i = 0; i != vec_size(self->blocks); ++i)
-        ir_block_delete(self->blocks[i]);
-    vec_free(self->blocks);
-
-    vec_free(self->params);
-
-    for (i = 0; i != vec_size(self->values); ++i)
-        ir_value_delete(self->values[i]);
-    vec_free(self->values);
-
-    for (i = 0; i != vec_size(self->locals); ++i)
-        ir_value_delete(self->locals[i]);
-    vec_free(self->locals);
-
-    /* self->value is deleted by the builder */
-
-    mem_d(self);
-}
-
-static void ir_function_collect_value(ir_function *self, ir_value *v)
-{
-    vec_push(self->values, v);
-}
-
-ir_block* ir_function_create_block(lex_ctx_t ctx, ir_function *self, const char *label)
-{
-    ir_block* bn = ir_block_new(self, label);
-    bn->context = ctx;
-    vec_push(self->blocks, bn);
-
-    if ((self->flags & IR_FLAG_BLOCK_COVERAGE) && self->owner->coverage_func)
-        (void)ir_block_create_call(bn, ctx, NULL, self->owner->coverage_func, false);
-
-    return bn;
-}
-
-static bool instr_is_operation(uint16_t op)
-{
-    return ( (op >= INSTR_MUL_F  && op <= INSTR_GT) ||
-             (op >= INSTR_LOAD_F && op <= INSTR_LOAD_FNC) ||
-             (op == INSTR_ADDRESS) ||
-             (op >= INSTR_NOT_F  && op <= INSTR_NOT_FNC) ||
-             (op >= INSTR_AND    && op <= INSTR_BITOR) ||
-             (op >= INSTR_CALL0  && op <= INSTR_CALL8) ||
-             (op >= VINSTR_BITAND_V && op <= VINSTR_NEG_V) );
-}
-
-static bool ir_function_pass_peephole(ir_function *self)
-{
-    size_t b;
-
-    for (b = 0; b < vec_size(self->blocks); ++b) {
-        size_t    i;
-        ir_block *block = self->blocks[b];
-
-        for (i = 0; i < vec_size(block->instr); ++i) {
-            ir_instr *inst;
-            inst = block->instr[i];
-
-            if (i >= 1 &&
-                (inst->opcode >= INSTR_STORE_F &&
-                 inst->opcode <= INSTR_STORE_FNC))
-            {
-                ir_instr *store;
-                ir_instr *oper;
-                ir_value *value;
-
-                store = inst;
-
-                oper  = block->instr[i-1];
-                if (!instr_is_operation(oper->opcode))
-                    continue;
-
-                /* Don't change semantics of MUL_VF in engines where these may not alias. */
-                if (OPTS_FLAG(LEGACY_VECTOR_MATHS)) {
-                    if (oper->opcode == INSTR_MUL_VF && oper->_ops[2]->memberof == oper->_ops[1])
-                        continue;
-                    if (oper->opcode == INSTR_MUL_FV && oper->_ops[1]->memberof == oper->_ops[2])
-                        continue;
-                }
-
-                value = oper->_ops[0];
-
-                /* only do it for SSA values */
-                if (value->store != store_value)
-                    continue;
-
-                /* don't optimize out the temp if it's used later again */
-                if (vec_size(value->reads) != 1)
-                    continue;
-
-                /* The very next store must use this value */
-                if (value->reads[0] != store)
-                    continue;
-
-                /* And of course the store must _read_ from it, so it's in
-                 * OP 1 */
-                if (store->_ops[1] != value)
-                    continue;
-
-                ++opts_optimizationcount[OPTIM_PEEPHOLE];
-                (void)!ir_instr_op(oper, 0, store->_ops[0], true);
-
-                vec_remove(block->instr, i, 1);
-                ir_instr_delete(store);
-            }
-            else if (inst->opcode == VINSTR_COND)
-            {
-                /* COND on a value resulting from a NOT could
-                 * remove the NOT and swap its operands
-                 */
-                while (true) {
-                    ir_block *tmp;
-                    size_t    inotid;
-                    ir_instr *inot;
-                    ir_value *value;
-                    value = inst->_ops[0];
-
-                    if (value->store != store_value ||
-                        vec_size(value->reads) != 1 ||
-                        value->reads[0] != inst)
-                    {
-                        break;
-                    }
-
-                    inot = value->writes[0];
-                    if (inot->_ops[0] != value ||
-                        inot->opcode < INSTR_NOT_F ||
-                        inot->opcode > INSTR_NOT_FNC ||
-                        inot->opcode == INSTR_NOT_V || /* can't do these */
-                        inot->opcode == INSTR_NOT_S)
-                    {
-                        break;
-                    }
-
-                    /* count */
-                    ++opts_optimizationcount[OPTIM_PEEPHOLE];
-                    /* change operand */
-                    (void)!ir_instr_op(inst, 0, inot->_ops[1], false);
-                    /* remove NOT */
-                    tmp = inot->owner;
-                    for (inotid = 0; inotid < vec_size(tmp->instr); ++inotid) {
-                        if (tmp->instr[inotid] == inot)
-                            break;
-                    }
-                    if (inotid >= vec_size(tmp->instr)) {
-                        compile_error(inst->context, "sanity-check failed: failed to find instruction to optimize out");
-                        return false;
-                    }
-                    vec_remove(tmp->instr, inotid, 1);
-                    ir_instr_delete(inot);
-                    /* swap ontrue/onfalse */
-                    tmp = inst->bops[0];
-                    inst->bops[0] = inst->bops[1];
-                    inst->bops[1] = tmp;
-                }
-                continue;
-            }
-        }
-    }
-
-    return true;
-}
-
-static bool ir_function_pass_tailrecursion(ir_function *self)
-{
-    size_t b, p;
-
-    for (b = 0; b < vec_size(self->blocks); ++b) {
-        ir_value *funcval;
-        ir_instr *ret, *call, *store = NULL;
-        ir_block *block = self->blocks[b];
-
-        if (!block->final || vec_size(block->instr) < 2)
-            continue;
-
-        ret = block->instr[vec_size(block->instr)-1];
-        if (ret->opcode != INSTR_DONE && ret->opcode != INSTR_RETURN)
-            continue;
-
-        call = block->instr[vec_size(block->instr)-2];
-        if (call->opcode >= INSTR_STORE_F && call->opcode <= INSTR_STORE_FNC) {
-            /* account for the unoptimized
-             * CALL
-             * STORE %return, %tmp
-             * RETURN %tmp
-             * version
-             */
-            if (vec_size(block->instr) < 3)
-                continue;
-
-            store = call;
-            call = block->instr[vec_size(block->instr)-3];
-        }
-
-        if (call->opcode < INSTR_CALL0 || call->opcode > INSTR_CALL8)
-            continue;
-
-        if (store) {
-            /* optimize out the STORE */
-            if (ret->_ops[0]   &&
-                ret->_ops[0]   == store->_ops[0] &&
-                store->_ops[1] == call->_ops[0])
-            {
-                ++opts_optimizationcount[OPTIM_PEEPHOLE];
-                call->_ops[0] = store->_ops[0];
-                vec_remove(block->instr, vec_size(block->instr) - 2, 1);
-                ir_instr_delete(store);
-            }
-            else
-                continue;
-        }
-
-        if (!call->_ops[0])
-            continue;
-
-        funcval = call->_ops[1];
-        if (!funcval)
-            continue;
-        if (funcval->vtype != TYPE_FUNCTION || funcval->constval.vfunc != self)
-            continue;
-
-        /* now we have a CALL and a RET, check if it's a tailcall */
-        if (ret->_ops[0] && call->_ops[0] != ret->_ops[0])
-            continue;
-
-        ++opts_optimizationcount[OPTIM_TAIL_RECURSION];
-        vec_shrinkby(block->instr, 2);
-
-        block->final = false; /* open it back up */
-
-        /* emite parameter-stores */
-        for (p = 0; p < vec_size(call->params); ++p) {
-            /* assert(call->params_count <= self->locals_count); */
-            if (!ir_block_create_store(block, call->context, self->locals[p], call->params[p])) {
-                irerror(call->context, "failed to create tailcall store instruction for parameter %i", (int)p);
-                return false;
-            }
-        }
-        if (!ir_block_create_jump(block, call->context, self->blocks[0])) {
-            irerror(call->context, "failed to create tailcall jump");
-            return false;
-        }
-
-        ir_instr_delete(call);
-        ir_instr_delete(ret);
-    }
-
-    return true;
-}
-
-bool ir_function_finalize(ir_function *self)
-{
-    size_t i;
-
-    if (self->builtin)
-        return true;
-
-    if (OPTS_OPTIMIZATION(OPTIM_PEEPHOLE)) {
-        if (!ir_function_pass_peephole(self)) {
-            irerror(self->context, "generic optimization pass broke something in `%s`", self->name);
-            return false;
-        }
-    }
-
-    if (OPTS_OPTIMIZATION(OPTIM_TAIL_RECURSION)) {
-        if (!ir_function_pass_tailrecursion(self)) {
-            irerror(self->context, "tail-recursion optimization pass broke something in `%s`", self->name);
-            return false;
-        }
-    }
-
-    if (!ir_function_naive_phi(self)) {
-        irerror(self->context, "internal error: ir_function_naive_phi failed");
-        return false;
-    }
-
-    for (i = 0; i < vec_size(self->locals); ++i) {
-        ir_value *v = self->locals[i];
-        if (v->vtype == TYPE_VECTOR ||
-            (v->vtype == TYPE_FIELD && v->outtype == TYPE_VECTOR))
-        {
-            ir_value_vector_member(v, 0);
-            ir_value_vector_member(v, 1);
-            ir_value_vector_member(v, 2);
-        }
-    }
-    for (i = 0; i < vec_size(self->values); ++i) {
-        ir_value *v = self->values[i];
-        if (v->vtype == TYPE_VECTOR ||
-            (v->vtype == TYPE_FIELD && v->outtype == TYPE_VECTOR))
-        {
-            ir_value_vector_member(v, 0);
-            ir_value_vector_member(v, 1);
-            ir_value_vector_member(v, 2);
-        }
-    }
-
-    ir_function_enumerate(self);
-
-    if (!ir_function_calculate_liferanges(self))
-        return false;
-    if (!ir_function_allocate_locals(self))
-        return false;
-    return true;
-}
-
-ir_value* ir_function_create_local(ir_function *self, const char *name, int vtype, bool param)
-{
-    ir_value *ve;
-
-    if (param &&
-        vec_size(self->locals) &&
-        self->locals[vec_size(self->locals)-1]->store != store_param) {
-        irerror(self->context, "cannot add parameters after adding locals");
-        return NULL;
-    }
-
-    ve = ir_value_var(name, (param ? store_param : store_local), vtype);
-    if (param)
-        ve->locked = true;
-    vec_push(self->locals, ve);
-    return ve;
-}
-
-/***********************************************************************
- *IR Block
- */
-
-ir_block* ir_block_new(ir_function* owner, const char *name)
-{
-    ir_block *self;
-    self = (ir_block*)mem_a(sizeof(*self));
-    if (!self)
-        return NULL;
-
-    memset(self, 0, sizeof(*self));
-
-    self->label = NULL;
-    if (name && !ir_block_set_label(self, name)) {
-        mem_d(self);
-        return NULL;
-    }
-    self->owner = owner;
-    self->context.file = "<@no context>";
-    self->context.line = 0;
-    self->final = false;
-
-    self->instr   = NULL;
-    self->entries = NULL;
-    self->exits   = NULL;
-
-    self->eid = 0;
-    self->is_return = false;
-
-    self->living = NULL;
-
-    self->generated = false;
-
-    return self;
-}
-
-static void ir_block_delete_quick(ir_block* self)
-{
-    size_t i;
-    if (self->label) mem_d(self->label);
-    for (i = 0; i != vec_size(self->instr); ++i)
-        ir_instr_delete_quick(self->instr[i]);
-    vec_free(self->instr);
-    vec_free(self->entries);
-    vec_free(self->exits);
-    vec_free(self->living);
-    mem_d(self);
-}
-
-void ir_block_delete(ir_block* self)
-{
-    size_t i;
-    if (self->label) mem_d(self->label);
-    for (i = 0; i != vec_size(self->instr); ++i)
-        ir_instr_delete(self->instr[i]);
-    vec_free(self->instr);
-    vec_free(self->entries);
-    vec_free(self->exits);
-    vec_free(self->living);
-    mem_d(self);
-}
-
-bool ir_block_set_label(ir_block *self, const char *name)
-{
-    if (self->label)
-        mem_d((void*)self->label);
-    self->label = util_strdup(name);
-    return !!self->label;
-}
-
-/***********************************************************************
- *IR Instructions
- */
-
-static ir_instr* ir_instr_new(lex_ctx_t ctx, ir_block* owner, int op)
-{
-    ir_instr *self;
-    self = (ir_instr*)mem_a(sizeof(*self));
-    if (!self)
-        return NULL;
-
-    self->owner = owner;
-    self->context = ctx;
-    self->opcode = op;
-    self->_ops[0] = NULL;
-    self->_ops[1] = NULL;
-    self->_ops[2] = NULL;
-    self->bops[0] = NULL;
-    self->bops[1] = NULL;
-
-    self->phi    = NULL;
-    self->params = NULL;
-
-    self->eid = 0;
-
-    self->likely = true;
-    return self;
-}
-
-static void ir_instr_delete_quick(ir_instr *self)
-{
-    vec_free(self->phi);
-    vec_free(self->params);
-    mem_d(self);
-}
-
-static void ir_instr_delete(ir_instr *self)
-{
-    size_t i;
-    /* The following calls can only delete from
-     * vectors, we still want to delete this instruction
-     * so ignore the return value. Since with the warn_unused_result attribute
-     * gcc doesn't care about an explicit: (void)foo(); to ignore the result,
-     * I have to improvise here and use if(foo());
-     */
-    for (i = 0; i < vec_size(self->phi); ++i) {
-        size_t idx;
-        if (vec_ir_instr_find(self->phi[i].value->writes, self, &idx))
-            vec_remove(self->phi[i].value->writes, idx, 1);
-        if (vec_ir_instr_find(self->phi[i].value->reads, self, &idx))
-            vec_remove(self->phi[i].value->reads, idx, 1);
-    }
-    vec_free(self->phi);
-    for (i = 0; i < vec_size(self->params); ++i) {
-        size_t idx;
-        if (vec_ir_instr_find(self->params[i]->writes, self, &idx))
-            vec_remove(self->params[i]->writes, idx, 1);
-        if (vec_ir_instr_find(self->params[i]->reads, self, &idx))
-            vec_remove(self->params[i]->reads, idx, 1);
-    }
-    vec_free(self->params);
-    (void)!ir_instr_op(self, 0, NULL, false);
-    (void)!ir_instr_op(self, 1, NULL, false);
-    (void)!ir_instr_op(self, 2, NULL, false);
-    mem_d(self);
-}
-
-static bool ir_instr_op(ir_instr *self, int op, ir_value *v, bool writing)
-{
-    if (v && v->vtype == TYPE_NOEXPR) {
-        irerror(self->context, "tried to use a NOEXPR value");
-        return false;
-    }
-
-    if (self->_ops[op]) {
-        size_t idx;
-        if (writing && vec_ir_instr_find(self->_ops[op]->writes, self, &idx))
-            vec_remove(self->_ops[op]->writes, idx, 1);
-        else if (vec_ir_instr_find(self->_ops[op]->reads, self, &idx))
-            vec_remove(self->_ops[op]->reads, idx, 1);
-    }
-    if (v) {
-        if (writing)
-            vec_push(v->writes, self);
-        else
-            vec_push(v->reads, self);
-    }
-    self->_ops[op] = v;
-    return true;
-}
-
-/***********************************************************************
- *IR Value
- */
-
-static void ir_value_code_setaddr(ir_value *self, int32_t gaddr)
-{
-    self->code.globaladdr = gaddr;
-    if (self->members[0]) self->members[0]->code.globaladdr = gaddr;
-    if (self->members[1]) self->members[1]->code.globaladdr = gaddr;
-    if (self->members[2]) self->members[2]->code.globaladdr = gaddr;
-}
-
-static int32_t ir_value_code_addr(const ir_value *self)
-{
-    if (self->store == store_return)
-        return OFS_RETURN + self->code.addroffset;
-    return self->code.globaladdr + self->code.addroffset;
-}
-
-ir_value* ir_value_var(const char *name, int storetype, int vtype)
-{
-    ir_value *self;
-    self = (ir_value*)mem_a(sizeof(*self));
-    self->vtype = vtype;
-    self->fieldtype = TYPE_VOID;
-    self->outtype = TYPE_VOID;
-    self->store = storetype;
-    self->flags = 0;
-
-    self->reads  = NULL;
-    self->writes = NULL;
-
-    self->cvq          = CV_NONE;
-    self->hasvalue     = false;
-    self->context.file = "<@no context>";
-    self->context.line = 0;
-    self->name = NULL;
-    if (name && !ir_value_set_name(self, name)) {
-        irerror(self->context, "out of memory");
-        mem_d(self);
-        return NULL;
-    }
-
-    memset(&self->constval, 0, sizeof(self->constval));
-    memset(&self->code,     0, sizeof(self->code));
-
-    self->members[0] = NULL;
-    self->members[1] = NULL;
-    self->members[2] = NULL;
-    self->memberof = NULL;
-
-    self->unique_life = false;
-    self->locked      = false;
-    self->callparam   = false;
-
-    self->life = NULL;
-    return self;
-}
-
-/*  helper function */
-static ir_value* ir_builder_imm_float(ir_builder *self, float value, bool add_to_list) {
-    ir_value *v = ir_value_var("#IMMEDIATE", store_global, TYPE_FLOAT);
-    v->flags |= IR_FLAG_ERASABLE;
-    v->hasvalue = true;
-    v->cvq = CV_CONST;
-    v->constval.vfloat = value;
-
-    vec_push(self->globals, v);
-    if (add_to_list)
-        vec_push(self->const_floats, v);
-    return v;
-}
-
-ir_value* ir_value_vector_member(ir_value *self, unsigned int member)
-{
-    char     *name;
-    size_t    len;
-    ir_value *m;
-    if (member >= 3)
-        return NULL;
-
-    if (self->members[member])
-        return self->members[member];
-
-    if (self->name) {
-        len = strlen(self->name);
-        name = (char*)mem_a(len + 3);
-        memcpy(name, self->name, len);
-        name[len+0] = '_';
-        name[len+1] = 'x' + member;
-        name[len+2] = '\0';
-    }
-    else
-        name = NULL;
-
-    if (self->vtype == TYPE_VECTOR)
-    {
-        m = ir_value_var(name, self->store, TYPE_FLOAT);
-        if (name)
-            mem_d(name);
-        if (!m)
-            return NULL;
-        m->context = self->context;
-
-        self->members[member] = m;
-        m->code.addroffset = member;
-    }
-    else if (self->vtype == TYPE_FIELD)
-    {
-        if (self->fieldtype != TYPE_VECTOR)
-            return NULL;
-        m = ir_value_var(name, self->store, TYPE_FIELD);
-        if (name)
-            mem_d(name);
-        if (!m)
-            return NULL;
-        m->fieldtype = TYPE_FLOAT;
-        m->context = self->context;
-
-        self->members[member] = m;
-        m->code.addroffset = member;
-    }
-    else
-    {
-        irerror(self->context, "invalid member access on %s", self->name);
-        return NULL;
-    }
-
-    m->memberof = self;
-    return m;
-}
-
-static GMQCC_INLINE size_t ir_value_sizeof(const ir_value *self)
-{
-    if (self->vtype == TYPE_FIELD && self->fieldtype == TYPE_VECTOR)
-        return type_sizeof_[TYPE_VECTOR];
-    return type_sizeof_[self->vtype];
-}
-
-static ir_value* ir_value_out(ir_function *owner, const char *name, int storetype, int vtype)
-{
-    ir_value *v = ir_value_var(name, storetype, vtype);
-    if (!v)
-        return NULL;
-    ir_function_collect_value(owner, v);
-    return v;
-}
-
-void ir_value_delete(ir_value* self)
-{
-    size_t i;
-    if (self->name)
-        mem_d((void*)self->name);
-    if (self->hasvalue)
-    {
-        if (self->vtype == TYPE_STRING)
-            mem_d((void*)self->constval.vstring);
-    }
-    if (!(self->flags & IR_FLAG_SPLIT_VECTOR)) {
-        for (i = 0; i < 3; ++i) {
-            if (self->members[i])
-                ir_value_delete(self->members[i]);
-        }
-    }
-    vec_free(self->reads);
-    vec_free(self->writes);
-    vec_free(self->life);
-    mem_d(self);
-}
-
-bool ir_value_set_name(ir_value *self, const char *name)
-{
-    if (self->name)
-        mem_d((void*)self->name);
-    self->name = util_strdup(name);
-    return !!self->name;
-}
-
-bool ir_value_set_float(ir_value *self, float f)
-{
-    if (self->vtype != TYPE_FLOAT)
-        return false;
-    self->constval.vfloat = f;
-    self->hasvalue = true;
-    return true;
-}
-
-bool ir_value_set_func(ir_value *self, int f)
-{
-    if (self->vtype != TYPE_FUNCTION)
-        return false;
-    self->constval.vint = f;
-    self->hasvalue = true;
-    return true;
-}
-
-bool ir_value_set_vector(ir_value *self, vec3_t v)
-{
-    if (self->vtype != TYPE_VECTOR)
-        return false;
-    self->constval.vvec = v;
-    self->hasvalue = true;
-    return true;
-}
-
-bool ir_value_set_field(ir_value *self, ir_value *fld)
-{
-    if (self->vtype != TYPE_FIELD)
-        return false;
-    self->constval.vpointer = fld;
-    self->hasvalue = true;
-    return true;
-}
-
-bool ir_value_set_string(ir_value *self, const char *str)
-{
-    if (self->vtype != TYPE_STRING)
-        return false;
-    self->constval.vstring = util_strdupe(str);
-    self->hasvalue = true;
-    return true;
-}
-
-#if 0
-bool ir_value_set_int(ir_value *self, int i)
-{
-    if (self->vtype != TYPE_INTEGER)
-        return false;
-    self->constval.vint = i;
-    self->hasvalue = true;
-    return true;
-}
-#endif
-
-bool ir_value_lives(ir_value *self, size_t at)
-{
-    size_t i;
-    for (i = 0; i < vec_size(self->life); ++i)
-    {
-        ir_life_entry_t *life = &self->life[i];
-        if (life->start <= at && at <= life->end)
-            return true;
-        if (life->start > at) /* since it's ordered */
-            return false;
-    }
-    return false;
-}
-
-static bool ir_value_life_insert(ir_value *self, size_t idx, ir_life_entry_t e)
-{
-    size_t k;
-    vec_push(self->life, e);
-    for (k = vec_size(self->life)-1; k > idx; --k)
-        self->life[k] = self->life[k-1];
-    self->life[idx] = e;
-    return true;
-}
-
-static bool ir_value_life_merge(ir_value *self, size_t s)
-{
-    size_t i;
-    const size_t vs = vec_size(self->life);
-    ir_life_entry_t *life = NULL;
-    ir_life_entry_t *before = NULL;
-    ir_life_entry_t new_entry;
-
-    /* Find the first range >= s */
-    for (i = 0; i < vs; ++i)
-    {
-        before = life;
-        life = &self->life[i];
-        if (life->start > s)
-            break;
-    }
-    /* nothing found? append */
-    if (i == vs) {
-        ir_life_entry_t e;
-        if (life && life->end+1 == s)
-        {
-            /* previous life range can be merged in */
-            life->end++;
-            return true;
-        }
-        if (life && life->end >= s)
-            return false;
-        e.start = e.end = s;
-        vec_push(self->life, e);
-        return true;
-    }
-    /* found */
-    if (before)
-    {
-        if (before->end + 1 == s &&
-            life->start - 1 == s)
-        {
-            /* merge */
-            before->end = life->end;
-            vec_remove(self->life, i, 1);
-            return true;
-        }
-        if (before->end + 1 == s)
-        {
-            /* extend before */
-            before->end++;
-            return true;
-        }
-        /* already contained */
-        if (before->end >= s)
-            return false;
-    }
-    /* extend */
-    if (life->start - 1 == s)
-    {
-        life->start--;
-        return true;
-    }
-    /* insert a new entry */
-    new_entry.start = new_entry.end = s;
-    return ir_value_life_insert(self, i, new_entry);
-}
-
-static bool ir_value_life_merge_into(ir_value *self, const ir_value *other)
-{
-    size_t i, myi;
-
-    if (!vec_size(other->life))
-        return true;
-
-    if (!vec_size(self->life)) {
-        size_t count = vec_size(other->life);
-        ir_life_entry_t *life = vec_add(self->life, count);
-        memcpy(life, other->life, count * sizeof(*life));
-        return true;
-    }
-
-    myi = 0;
-    for (i = 0; i < vec_size(other->life); ++i)
-    {
-        const ir_life_entry_t *life = &other->life[i];
-        while (true)
-        {
-            ir_life_entry_t *entry = &self->life[myi];
-
-            if (life->end+1 < entry->start)
-            {
-                /* adding an interval before entry */
-                if (!ir_value_life_insert(self, myi, *life))
-                    return false;
-                ++myi;
-                break;
-            }
-
-            if (life->start <  entry->start &&
-                life->end+1 >= entry->start)
-            {
-                /* starts earlier and overlaps */
-                entry->start = life->start;
-            }
-
-            if (life->end   >  entry->end &&
-                life->start <= entry->end+1)
-            {
-                /* ends later and overlaps */
-                entry->end = life->end;
-            }
-
-            /* see if our change combines it with the next ranges */
-            while (myi+1 < vec_size(self->life) &&
-                   entry->end+1 >= self->life[1+myi].start)
-            {
-                /* overlaps with (myi+1) */
-                if (entry->end < self->life[1+myi].end)
-                    entry->end = self->life[1+myi].end;
-                vec_remove(self->life, myi+1, 1);
-                entry = &self->life[myi];
-            }
-
-            /* see if we're after the entry */
-            if (life->start > entry->end)
-            {
-                ++myi;
-                /* append if we're at the end */
-                if (myi >= vec_size(self->life)) {
-                    vec_push(self->life, *life);
-                    break;
-                }
-                /* otherweise check the next range */
-                continue;
-            }
-            break;
-        }
-    }
-    return true;
-}
-
-static bool ir_values_overlap(const ir_value *a, const ir_value *b)
-{
-    /* For any life entry in A see if it overlaps with
-     * any life entry in B.
-     * Note that the life entries are orderes, so we can make a
-     * more efficient algorithm there than naively translating the
-     * statement above.
-     */
-
-    ir_life_entry_t *la, *lb, *enda, *endb;
-
-    /* first of all, if either has no life range, they cannot clash */
-    if (!vec_size(a->life) || !vec_size(b->life))
-        return false;
-
-    la = a->life;
-    lb = b->life;
-    enda = la + vec_size(a->life);
-    endb = lb + vec_size(b->life);
-    while (true)
-    {
-        /* check if the entries overlap, for that,
-         * both must start before the other one ends.
-         */
-        if (la->start < lb->end &&
-            lb->start < la->end)
-        {
-            return true;
-        }
-
-        /* entries are ordered
-         * one entry is earlier than the other
-         * that earlier entry will be moved forward
-         */
-        if (la->start < lb->start)
-        {
-            /* order: A B, move A forward
-             * check if we hit the end with A
-             */
-            if (++la == enda)
-                break;
-        }
-        else /* if (lb->start < la->start)  actually <= */
-        {
-            /* order: B A, move B forward
-             * check if we hit the end with B
-             */
-            if (++lb == endb)
-                break;
-        }
-    }
-    return false;
-}
-
-/***********************************************************************
- *IR main operations
- */
-
-static bool ir_check_unreachable(ir_block *self)
-{
-    /* The IR should never have to deal with unreachable code */
-    if (!self->final/* || OPTS_FLAG(ALLOW_UNREACHABLE_CODE)*/)
-        return true;
-    irerror(self->context, "unreachable statement (%s)", self->label);
-    return false;
-}
-
-bool ir_block_create_store_op(ir_block *self, lex_ctx_t ctx, int op, ir_value *target, ir_value *what)
-{
-    ir_instr *in;
-    if (!ir_check_unreachable(self))
-        return false;
-
-    if (target->store == store_value &&
-        (op < INSTR_STOREP_F || op > INSTR_STOREP_FNC))
-    {
-        irerror(self->context, "cannot store to an SSA value");
-        irerror(self->context, "trying to store: %s <- %s", target->name, what->name);
-        irerror(self->context, "instruction: %s", util_instr_str[op]);
-        return false;
-    }
-
-    in = ir_instr_new(ctx, self, op);
-    if (!in)
-        return false;
-
-    if (!ir_instr_op(in, 0, target, (op < INSTR_STOREP_F || op > INSTR_STOREP_FNC)) ||
-        !ir_instr_op(in, 1, what, false))
-    {
-        ir_instr_delete(in);
-        return false;
-    }
-    vec_push(self->instr, in);
-    return true;
-}
-
-bool ir_block_create_state_op(ir_block *self, lex_ctx_t ctx, ir_value *frame, ir_value *think)
-{
-    ir_instr *in;
-    if (!ir_check_unreachable(self))
-        return false;
-
-    in = ir_instr_new(ctx, self, INSTR_STATE);
-    if (!in)
-        return false;
-
-    if (!ir_instr_op(in, 0, frame, false) ||
-        !ir_instr_op(in, 1, think, false))
-    {
-        ir_instr_delete(in);
-        return false;
-    }
-    vec_push(self->instr, in);
-    return true;
-}
-
-static bool ir_block_create_store(ir_block *self, lex_ctx_t ctx, ir_value *target, ir_value *what)
-{
-    int op = 0;
-    int vtype;
-    if (target->vtype == TYPE_VARIANT)
-        vtype = what->vtype;
-    else
-        vtype = target->vtype;
-
-#if 0
-    if      (vtype == TYPE_FLOAT   && what->vtype == TYPE_INTEGER)
-        op = INSTR_CONV_ITOF;
-    else if (vtype == TYPE_INTEGER && what->vtype == TYPE_FLOAT)
-        op = INSTR_CONV_FTOI;
-#endif
-        op = type_store_instr[vtype];
-
-    if (OPTS_FLAG(ADJUST_VECTOR_FIELDS)) {
-        if (op == INSTR_STORE_FLD && what->fieldtype == TYPE_VECTOR)
-            op = INSTR_STORE_V;
-    }
-
-    return ir_block_create_store_op(self, ctx, op, target, what);
-}
-
-bool ir_block_create_storep(ir_block *self, lex_ctx_t ctx, ir_value *target, ir_value *what)
-{
-    int op = 0;
-    int vtype;
-
-    if (target->vtype != TYPE_POINTER)
-        return false;
-
-    /* storing using pointer - target is a pointer, type must be
-     * inferred from source
-     */
-    vtype = what->vtype;
-
-    op = type_storep_instr[vtype];
-    if (OPTS_FLAG(ADJUST_VECTOR_FIELDS)) {
-        if (op == INSTR_STOREP_FLD && what->fieldtype == TYPE_VECTOR)
-            op = INSTR_STOREP_V;
-    }
-
-    return ir_block_create_store_op(self, ctx, op, target, what);
-}
-
-bool ir_block_create_return(ir_block *self, lex_ctx_t ctx, ir_value *v)
-{
-    ir_instr *in;
-    if (!ir_check_unreachable(self))
-        return false;
-
-    self->final = true;
-
-    self->is_return = true;
-    in = ir_instr_new(ctx, self, INSTR_RETURN);
-    if (!in)
-        return false;
-
-    if (v && !ir_instr_op(in, 0, v, false)) {
-        ir_instr_delete(in);
-        return false;
-    }
-
-    vec_push(self->instr, in);
-    return true;
-}
-
-bool ir_block_create_if(ir_block *self, lex_ctx_t ctx, ir_value *v,
-                        ir_block *ontrue, ir_block *onfalse)
-{
-    ir_instr *in;
-    if (!ir_check_unreachable(self))
-        return false;
-    self->final = true;
-    /*in = ir_instr_new(ctx, self, (v->vtype == TYPE_STRING ? INSTR_IF_S : INSTR_IF_F));*/
-    in = ir_instr_new(ctx, self, VINSTR_COND);
-    if (!in)
-        return false;
-
-    if (!ir_instr_op(in, 0, v, false)) {
-        ir_instr_delete(in);
-        return false;
-    }
-
-    in->bops[0] = ontrue;
-    in->bops[1] = onfalse;
-
-    vec_push(self->instr, in);
-
-    vec_push(self->exits, ontrue);
-    vec_push(self->exits, onfalse);
-    vec_push(ontrue->entries,  self);
-    vec_push(onfalse->entries, self);
-    return true;
-}
-
-bool ir_block_create_jump(ir_block *self, lex_ctx_t ctx, ir_block *to)
-{
-    ir_instr *in;
-    if (!ir_check_unreachable(self))
-        return false;
-    self->final = true;
-    in = ir_instr_new(ctx, self, VINSTR_JUMP);
-    if (!in)
-        return false;
-
-    in->bops[0] = to;
-    vec_push(self->instr, in);
-
-    vec_push(self->exits, to);
-    vec_push(to->entries, self);
-    return true;
-}
-
-bool ir_block_create_goto(ir_block *self, lex_ctx_t ctx, ir_block *to)
-{
-    self->owner->flags |= IR_FLAG_HAS_GOTO;
-    return ir_block_create_jump(self, ctx, to);
-}
-
-ir_instr* ir_block_create_phi(ir_block *self, lex_ctx_t ctx, const char *label, int ot)
-{
-    ir_value *out;
-    ir_instr *in;
-    if (!ir_check_unreachable(self))
-        return NULL;
-    in = ir_instr_new(ctx, self, VINSTR_PHI);
-    if (!in)
-        return NULL;
-    out = ir_value_out(self->owner, label, store_value, ot);
-    if (!out) {
-        ir_instr_delete(in);
-        return NULL;
-    }
-    if (!ir_instr_op(in, 0, out, true)) {
-        ir_instr_delete(in);
-        ir_value_delete(out);
-        return NULL;
-    }
-    vec_push(self->instr, in);
-    return in;
-}
-
-ir_value* ir_phi_value(ir_instr *self)
-{
-    return self->_ops[0];
-}
-
-void ir_phi_add(ir_instr* self, ir_block *b, ir_value *v)
-{
-    ir_phi_entry_t pe;
-
-    if (!vec_ir_block_find(self->owner->entries, b, NULL)) {
-        /* Must not be possible to cause this, otherwise the AST
-         * is doing something wrong.
-         */
-        irerror(self->context, "Invalid entry block for PHI");
-        exit(EXIT_FAILURE);
-    }
-
-    pe.value = v;
-    pe.from = b;
-    vec_push(v->reads, self);
-    vec_push(self->phi, pe);
-}
-
-/* call related code */
-ir_instr* ir_block_create_call(ir_block *self, lex_ctx_t ctx, const char *label, ir_value *func, bool noreturn)
-{
-    ir_value *out;
-    ir_instr *in;
-    if (!ir_check_unreachable(self))
-        return NULL;
-    in = ir_instr_new(ctx, self, (noreturn ? VINSTR_NRCALL : INSTR_CALL0));
-    if (!in)
-        return NULL;
-    if (noreturn) {
-        self->final = true;
-        self->is_return = true;
-    }
-    out = ir_value_out(self->owner, label, (func->outtype == TYPE_VOID) ? store_return : store_value, func->outtype);
-    if (!out) {
-        ir_instr_delete(in);
-        return NULL;
-    }
-    if (!ir_instr_op(in, 0, out, true) ||
-        !ir_instr_op(in, 1, func, false))
-    {
-        ir_instr_delete(in);
-        ir_value_delete(out);
-        return NULL;
-    }
-    vec_push(self->instr, in);
-    /*
-    if (noreturn) {
-        if (!ir_block_create_return(self, ctx, NULL)) {
-            compile_error(ctx, "internal error: failed to generate dummy-return instruction");
-            ir_instr_delete(in);
-            return NULL;
-        }
-    }
-    */
-    return in;
-}
-
-ir_value* ir_call_value(ir_instr *self)
-{
-    return self->_ops[0];
-}
-
-void ir_call_param(ir_instr* self, ir_value *v)
-{
-    vec_push(self->params, v);
-    vec_push(v->reads, self);
-}
-
-/* binary op related code */
-
-ir_value* ir_block_create_binop(ir_block *self, lex_ctx_t ctx,
-                                const char *label, int opcode,
-                                ir_value *left, ir_value *right)
-{
-    int ot = TYPE_VOID;
-    switch (opcode) {
-        case INSTR_ADD_F:
-        case INSTR_SUB_F:
-        case INSTR_DIV_F:
-        case INSTR_MUL_F:
-        case INSTR_MUL_V:
-        case INSTR_AND:
-        case INSTR_OR:
-#if 0
-        case INSTR_AND_I:
-        case INSTR_AND_IF:
-        case INSTR_AND_FI:
-        case INSTR_OR_I:
-        case INSTR_OR_IF:
-        case INSTR_OR_FI:
-#endif
-        case INSTR_BITAND:
-        case INSTR_BITOR:
-        case VINSTR_BITXOR:
-#if 0
-        case INSTR_SUB_S: /* -- offset of string as float */
-        case INSTR_MUL_IF:
-        case INSTR_MUL_FI:
-        case INSTR_DIV_IF:
-        case INSTR_DIV_FI:
-        case INSTR_BITOR_IF:
-        case INSTR_BITOR_FI:
-        case INSTR_BITAND_FI:
-        case INSTR_BITAND_IF:
-        case INSTR_EQ_I:
-        case INSTR_NE_I:
-#endif
-            ot = TYPE_FLOAT;
-            break;
-#if 0
-        case INSTR_ADD_I:
-        case INSTR_ADD_IF:
-        case INSTR_ADD_FI:
-        case INSTR_SUB_I:
-        case INSTR_SUB_FI:
-        case INSTR_SUB_IF:
-        case INSTR_MUL_I:
-        case INSTR_DIV_I:
-        case INSTR_BITAND_I:
-        case INSTR_BITOR_I:
-        case INSTR_XOR_I:
-        case INSTR_RSHIFT_I:
-        case INSTR_LSHIFT_I:
-            ot = TYPE_INTEGER;
-            break;
-#endif
-        case INSTR_ADD_V:
-        case INSTR_SUB_V:
-        case INSTR_MUL_VF:
-        case INSTR_MUL_FV:
-        case VINSTR_BITAND_V:
-        case VINSTR_BITOR_V:
-        case VINSTR_BITXOR_V:
-        case VINSTR_BITAND_VF:
-        case VINSTR_BITOR_VF:
-        case VINSTR_BITXOR_VF:
-        case VINSTR_CROSS:
-#if 0
-        case INSTR_DIV_VF:
-        case INSTR_MUL_IV:
-        case INSTR_MUL_VI:
-#endif
-            ot = TYPE_VECTOR;
-            break;
-#if 0
-        case INSTR_ADD_SF:
-            ot = TYPE_POINTER;
-            break;
-#endif
-    /*
-     * after the following default case, the value of opcode can never
-     * be 1, 2, 3, 4, 5, 6, 7, 8, 9, 62, 63, 64, 65
-     */
-        default:
-            /* ranges: */
-            /* boolean operations result in floats */
-
-            /*
-             * opcode >= 10 takes true branch opcode is at least 10
-             * opcode <= 23 takes false branch opcode is at least 24
-             */
-            if (opcode >= INSTR_EQ_F && opcode <= INSTR_GT)
-                ot = TYPE_FLOAT;
-
-            /*
-             * At condition "opcode <= 23", the value of "opcode" must be
-             * at least 24.
-             * At condition "opcode <= 23", the value of "opcode" cannot be
-             * equal to any of {1, 2, 3, 4, 5, 6, 7, 8, 9, 62, 63, 64, 65}.
-             * The condition "opcode <= 23" cannot be true.
-             *
-             * Thus ot=2 (TYPE_FLOAT) can never be true
-             */
-#if 0
-            else if (opcode >= INSTR_LE && opcode <= INSTR_GT)
-                ot = TYPE_FLOAT;
-            else if (opcode >= INSTR_LE_I && opcode <= INSTR_EQ_FI)
-                ot = TYPE_FLOAT;
-#endif
-            break;
-    };
-    if (ot == TYPE_VOID) {
-        /* The AST or parser were supposed to check this! */
-        return NULL;
-    }
-
-    return ir_block_create_general_instr(self, ctx, label, opcode, left, right, ot);
-}
-
-ir_value* ir_block_create_unary(ir_block *self, lex_ctx_t ctx,
-                                const char *label, int opcode,
-                                ir_value *operand)
-{
-    int ot = TYPE_FLOAT;
-    ir_value *minus_1 = NULL;
-    if (opcode == VINSTR_NEG_F || opcode == VINSTR_NEG_V)
-        minus_1 = ir_builder_imm_float(self->owner->owner, -1.0f, false);
-    switch (opcode) {
-        case INSTR_NOT_F:
-        case INSTR_NOT_V:
-        case INSTR_NOT_S:
-        case INSTR_NOT_ENT:
-        case INSTR_NOT_FNC: /*
-        case INSTR_NOT_I:   */
-            ot = TYPE_FLOAT;
-            break;
-        /* Negation is implemented as -1 * <operand> */
-        case VINSTR_NEG_F:
-            return ir_block_create_general_instr(self, ctx, label, INSTR_MUL_F, minus_1, operand, TYPE_FLOAT);
-        case VINSTR_NEG_V:
-            return ir_block_create_general_instr(self, ctx, label, INSTR_MUL_FV, minus_1, operand, TYPE_VECTOR);
-
-        default:
-            ot = operand->vtype;
-            break;
-    };
-    if (ot == TYPE_VOID) {
-        /* The AST or parser were supposed to check this! */
-        return NULL;
-    }
-
-    /* let's use the general instruction creator and pass NULL for OPB */
-    return ir_block_create_general_instr(self, ctx, label, opcode, operand, NULL, ot);
-}
-
-static ir_value* ir_block_create_general_instr(ir_block *self, lex_ctx_t ctx, const char *label,
-                                        int op, ir_value *a, ir_value *b, int outype)
-{
-    ir_instr *instr;
-    ir_value *out;
-
-    out = ir_value_out(self->owner, label, store_value, outype);
-    if (!out)
-        return NULL;
-
-    instr = ir_instr_new(ctx, self, op);
-    if (!instr) {
-        ir_value_delete(out);
-        return NULL;
-    }
-
-    if (!ir_instr_op(instr, 0, out, true) ||
-        !ir_instr_op(instr, 1, a, false) ||
-        !ir_instr_op(instr, 2, b, false) )
-    {
-        goto on_error;
-    }
-
-    vec_push(self->instr, instr);
-
-    return out;
-on_error:
-    ir_instr_delete(instr);
-    ir_value_delete(out);
-    return NULL;
-}
-
-ir_value* ir_block_create_fieldaddress(ir_block *self, lex_ctx_t ctx, const char *label, ir_value *ent, ir_value *field)
-{
-    ir_value *v;
-
-    /* Support for various pointer types todo if so desired */
-    if (ent->vtype != TYPE_ENTITY)
-        return NULL;
-
-    if (field->vtype != TYPE_FIELD)
-        return NULL;
-
-    v = ir_block_create_general_instr(self, ctx, label, INSTR_ADDRESS, ent, field, TYPE_POINTER);
-    v->fieldtype = field->fieldtype;
-    return v;
-}
-
-ir_value* ir_block_create_load_from_ent(ir_block *self, lex_ctx_t ctx, const char *label, ir_value *ent, ir_value *field, int outype)
-{
-    int op;
-    if (ent->vtype != TYPE_ENTITY)
-        return NULL;
-
-    /* at some point we could redirect for TYPE_POINTER... but that could lead to carelessness */
-    if (field->vtype != TYPE_FIELD)
-        return NULL;
-
-    switch (outype)
-    {
-        case TYPE_FLOAT:    op = INSTR_LOAD_F;   break;
-        case TYPE_VECTOR:   op = INSTR_LOAD_V;   break;
-        case TYPE_STRING:   op = INSTR_LOAD_S;   break;
-        case TYPE_FIELD:    op = INSTR_LOAD_FLD; break;
-        case TYPE_ENTITY:   op = INSTR_LOAD_ENT; break;
-        case TYPE_FUNCTION: op = INSTR_LOAD_FNC; break;
-#if 0
-        case TYPE_POINTER: op = INSTR_LOAD_I;   break;
-        case TYPE_INTEGER: op = INSTR_LOAD_I;   break;
-#endif
-        default:
-            irerror(self->context, "invalid type for ir_block_create_load_from_ent: %s", type_name[outype]);
-            return NULL;
-    }
-
-    return ir_block_create_general_instr(self, ctx, label, op, ent, field, outype);
-}
-
-/* PHI resolving breaks the SSA, and must thus be the last
- * step before life-range calculation.
- */
-
-static bool ir_block_naive_phi(ir_block *self);
-bool ir_function_naive_phi(ir_function *self)
-{
-    size_t i;
-
-    for (i = 0; i < vec_size(self->blocks); ++i)
-    {
-        if (!ir_block_naive_phi(self->blocks[i]))
-            return false;
-    }
-    return true;
-}
-
-static bool ir_block_naive_phi(ir_block *self)
-{
-    size_t i, p; /*, w;*/
-    /* FIXME: optionally, create_phi can add the phis
-     * to a list so we don't need to loop through blocks
-     * - anyway: "don't optimize YET"
-     */
-    for (i = 0; i < vec_size(self->instr); ++i)
-    {
-        ir_instr *instr = self->instr[i];
-        if (instr->opcode != VINSTR_PHI)
-            continue;
-
-        vec_remove(self->instr, i, 1);
-        --i; /* NOTE: i+1 below */
-
-        for (p = 0; p < vec_size(instr->phi); ++p)
-        {
-            ir_value *v = instr->phi[p].value;
-            ir_block *b = instr->phi[p].from;
-
-            if (v->store == store_value &&
-                vec_size(v->reads) == 1 &&
-                vec_size(v->writes) == 1)
-            {
-                /* replace the value */
-                if (!ir_instr_op(v->writes[0], 0, instr->_ops[0], true))
-                    return false;
-            }
-            else
-            {
-                /* force a move instruction */
-                ir_instr *prevjump = vec_last(b->instr);
-                vec_pop(b->instr);
-                b->final = false;
-                instr->_ops[0]->store = store_global;
-                if (!ir_block_create_store(b, instr->context, instr->_ops[0], v))
-                    return false;
-                instr->_ops[0]->store = store_value;
-                vec_push(b->instr, prevjump);
-                b->final = true;
-            }
-        }
-        ir_instr_delete(instr);
-    }
-    return true;
-}
-
-/***********************************************************************
- *IR Temp allocation code
- * Propagating value life ranges by walking through the function backwards
- * until no more changes are made.
- * In theory this should happen once more than once for every nested loop
- * level.
- * Though this implementation might run an additional time for if nests.
- */
-
-/* Enumerate instructions used by value's life-ranges
- */
-static void ir_block_enumerate(ir_block *self, size_t *_eid)
-{
-    size_t i;
-    size_t eid = *_eid;
-    for (i = 0; i < vec_size(self->instr); ++i)
-    {
-        self->instr[i]->eid = eid++;
-    }
-    *_eid = eid;
-}
-
-/* Enumerate blocks and instructions.
- * The block-enumeration is unordered!
- * We do not really use the block enumreation, however
- * the instruction enumeration is important for life-ranges.
- */
-void ir_function_enumerate(ir_function *self)
-{
-    size_t i;
-    size_t instruction_id = 0;
-    for (i = 0; i < vec_size(self->blocks); ++i)
-    {
-        /* each block now gets an additional "entry" instruction id
-         * we can use to avoid point-life issues
-         */
-        self->blocks[i]->entry_id = instruction_id;
-        ++instruction_id;
-
-        self->blocks[i]->eid = i;
-        ir_block_enumerate(self->blocks[i], &instruction_id);
-    }
-}
-
-/* Local-value allocator
- * After finishing creating the liferange of all values used in a function
- * we can allocate their global-positions.
- * This is the counterpart to register-allocation in register machines.
- */
-typedef struct {
-    ir_value **locals;
-    size_t    *sizes;
-    size_t    *positions;
-    bool      *unique;
-} function_allocator;
-
-static bool function_allocator_alloc(function_allocator *alloc, ir_value *var)
-{
-    ir_value *slot;
-    size_t vsize = ir_value_sizeof(var);
-
-    var->code.local = vec_size(alloc->locals);
-
-    slot = ir_value_var("reg", store_global, var->vtype);
-    if (!slot)
-        return false;
-
-    if (!ir_value_life_merge_into(slot, var))
-        goto localerror;
-
-    vec_push(alloc->locals, slot);
-    vec_push(alloc->sizes, vsize);
-    vec_push(alloc->unique, var->unique_life);
-
-    return true;
-
-localerror:
-    ir_value_delete(slot);
-    return false;
-}
-
-static bool ir_function_allocator_assign(ir_function *self, function_allocator *alloc, ir_value *v)
-{
-    size_t a;
-    ir_value *slot;
-
-    if (v->unique_life)
-        return function_allocator_alloc(alloc, v);
-
-    for (a = 0; a < vec_size(alloc->locals); ++a)
-    {
-        /* if it's reserved for a unique liferange: skip */
-        if (alloc->unique[a])
-            continue;
-
-        slot = alloc->locals[a];
-
-        /* never resize parameters
-         * will be required later when overlapping temps + locals
-         */
-        if (a < vec_size(self->params) &&
-            alloc->sizes[a] < ir_value_sizeof(v))
-        {
-            continue;
-        }
-
-        if (ir_values_overlap(v, slot))
-            continue;
-
-        if (!ir_value_life_merge_into(slot, v))
-            return false;
-
-        /* adjust size for this slot */
-        if (alloc->sizes[a] < ir_value_sizeof(v))
-            alloc->sizes[a] = ir_value_sizeof(v);
-
-        v->code.local = a;
-        return true;
-    }
-    if (a >= vec_size(alloc->locals)) {
-        if (!function_allocator_alloc(alloc, v))
-            return false;
-    }
-    return true;
-}
-
-bool ir_function_allocate_locals(ir_function *self)
-{
-    size_t i;
-    bool   retval = true;
-    size_t pos;
-    bool   opt_gt = OPTS_OPTIMIZATION(OPTIM_GLOBAL_TEMPS);
-
-    ir_value *v;
-
-    function_allocator lockalloc, globalloc;
-
-    if (!vec_size(self->locals) && !vec_size(self->values))
-        return true;
-
-    globalloc.locals    = NULL;
-    globalloc.sizes     = NULL;
-    globalloc.positions = NULL;
-    globalloc.unique    = NULL;
-    lockalloc.locals    = NULL;
-    lockalloc.sizes     = NULL;
-    lockalloc.positions = NULL;
-    lockalloc.unique    = NULL;
-
-    for (i = 0; i < vec_size(self->locals); ++i)
-    {
-        v = self->locals[i];
-        if ((self->flags & IR_FLAG_MASK_NO_LOCAL_TEMPS) || !OPTS_OPTIMIZATION(OPTIM_LOCAL_TEMPS)) {
-            v->locked      = true;
-            v->unique_life = true;
-        }
-        else if (i >= vec_size(self->params))
-            break;
-        else
-            v->locked = true; /* lock parameters locals */
-        if (!function_allocator_alloc((v->locked || !opt_gt ? &lockalloc : &globalloc), v))
-            goto error;
-    }
-    for (; i < vec_size(self->locals); ++i)
-    {
-        v = self->locals[i];
-        if (!vec_size(v->life))
-            continue;
-        if (!ir_function_allocator_assign(self, (v->locked || !opt_gt ? &lockalloc : &globalloc), v))
-            goto error;
-    }
-
-    /* Allocate a slot for any value that still exists */
-    for (i = 0; i < vec_size(self->values); ++i)
-    {
-        v = self->values[i];
-
-        if (!vec_size(v->life))
-            continue;
-
-        /* CALL optimization:
-         * If the value is a parameter-temp: 1 write, 1 read from a CALL
-         * and it's not "locked", write it to the OFS_PARM directly.
-         */
-        if (OPTS_OPTIMIZATION(OPTIM_CALL_STORES) && !v->locked && !v->unique_life) {
-            if (vec_size(v->reads) == 1 && vec_size(v->writes) == 1 &&
-                (v->reads[0]->opcode == VINSTR_NRCALL ||
-                 (v->reads[0]->opcode >= INSTR_CALL0 && v->reads[0]->opcode <= INSTR_CALL8)
-                )
-               )
-            {
-                size_t    param;
-                ir_instr *call = v->reads[0];
-                if (!vec_ir_value_find(call->params, v, &param)) {
-                    irerror(call->context, "internal error: unlocked parameter %s not found", v->name);
-                    goto error;
-                }
-                ++opts_optimizationcount[OPTIM_CALL_STORES];
-                v->callparam = true;
-                if (param < 8)
-                    ir_value_code_setaddr(v, OFS_PARM0 + 3*param);
-                else {
-                    size_t nprotos = vec_size(self->owner->extparam_protos);
-                    ir_value *ep;
-                    param -= 8;
-                    if (nprotos > param)
-                        ep = self->owner->extparam_protos[param];
-                    else
-                    {
-                        ep = ir_gen_extparam_proto(self->owner);
-                        while (++nprotos <= param)
-                            ep = ir_gen_extparam_proto(self->owner);
-                    }
-                    ir_instr_op(v->writes[0], 0, ep, true);
-                    call->params[param+8] = ep;
-                }
-                continue;
-            }
-            if (vec_size(v->writes) == 1 && v->writes[0]->opcode == INSTR_CALL0)
-            {
-                v->store = store_return;
-                if (v->members[0]) v->members[0]->store = store_return;
-                if (v->members[1]) v->members[1]->store = store_return;
-                if (v->members[2]) v->members[2]->store = store_return;
-                ++opts_optimizationcount[OPTIM_CALL_STORES];
-                continue;
-            }
-        }
-
-        if (!ir_function_allocator_assign(self, (v->locked || !opt_gt ? &lockalloc : &globalloc), v))
-            goto error;
-    }
-
-    if (!lockalloc.sizes && !globalloc.sizes) {
-        goto cleanup;
-    }
-    vec_push(lockalloc.positions, 0);
-    vec_push(globalloc.positions, 0);
-
-    /* Adjust slot positions based on sizes */
-    if (lockalloc.sizes) {
-        pos = (vec_size(lockalloc.sizes) ? lockalloc.positions[0] : 0);
-        for (i = 1; i < vec_size(lockalloc.sizes); ++i)
-        {
-            pos = lockalloc.positions[i-1] + lockalloc.sizes[i-1];
-            vec_push(lockalloc.positions, pos);
-        }
-        self->allocated_locals = pos + vec_last(lockalloc.sizes);
-    }
-    if (globalloc.sizes) {
-        pos = (vec_size(globalloc.sizes) ? globalloc.positions[0] : 0);
-        for (i = 1; i < vec_size(globalloc.sizes); ++i)
-        {
-            pos = globalloc.positions[i-1] + globalloc.sizes[i-1];
-            vec_push(globalloc.positions, pos);
-        }
-        self->globaltemps = pos + vec_last(globalloc.sizes);
-    }
-
-    /* Locals need to know their new position */
-    for (i = 0; i < vec_size(self->locals); ++i) {
-        v = self->locals[i];
-        if (v->locked || !opt_gt)
-            v->code.local = lockalloc.positions[v->code.local];
-        else
-            v->code.local = globalloc.positions[v->code.local];
-    }
-    /* Take over the actual slot positions on values */
-    for (i = 0; i < vec_size(self->values); ++i) {
-        v = self->values[i];
-        if (v->locked || !opt_gt)
-            v->code.local = lockalloc.positions[v->code.local];
-        else
-            v->code.local = globalloc.positions[v->code.local];
-    }
-
-    goto cleanup;
-
-error:
-    retval = false;
-cleanup:
-    for (i = 0; i < vec_size(lockalloc.locals); ++i)
-        ir_value_delete(lockalloc.locals[i]);
-    for (i = 0; i < vec_size(globalloc.locals); ++i)
-        ir_value_delete(globalloc.locals[i]);
-    vec_free(globalloc.unique);
-    vec_free(globalloc.locals);
-    vec_free(globalloc.sizes);
-    vec_free(globalloc.positions);
-    vec_free(lockalloc.unique);
-    vec_free(lockalloc.locals);
-    vec_free(lockalloc.sizes);
-    vec_free(lockalloc.positions);
-    return retval;
-}
-
-/* Get information about which operand
- * is read from, or written to.
- */
-static void ir_op_read_write(int op, size_t *read, size_t *write)
-{
-    switch (op)
-    {
-    case VINSTR_JUMP:
-    case INSTR_GOTO:
-        *write = 0;
-        *read = 0;
-        break;
-    case INSTR_IF:
-    case INSTR_IFNOT:
-#if 0
-    case INSTR_IF_S:
-    case INSTR_IFNOT_S:
-#endif
-    case INSTR_RETURN:
-    case VINSTR_COND:
-        *write = 0;
-        *read = 1;
-        break;
-    case INSTR_STOREP_F:
-    case INSTR_STOREP_V:
-    case INSTR_STOREP_S:
-    case INSTR_STOREP_ENT:
-    case INSTR_STOREP_FLD:
-    case INSTR_STOREP_FNC:
-        *write = 0;
-        *read  = 7;
-        break;
-    default:
-        *write = 1;
-        *read = 6;
-        break;
-    };
-}
-
-static bool ir_block_living_add_instr(ir_block *self, size_t eid)
-{
-    size_t       i;
-    const size_t vs = vec_size(self->living);
-    bool         changed = false;
-    for (i = 0; i != vs; ++i)
-    {
-        if (ir_value_life_merge(self->living[i], eid))
-            changed = true;
-    }
-    return changed;
-}
-
-static bool ir_block_living_lock(ir_block *self)
-{
-    size_t i;
-    bool changed = false;
-    for (i = 0; i != vec_size(self->living); ++i)
-    {
-        if (!self->living[i]->locked) {
-            self->living[i]->locked = true;
-            changed = true;
-        }
-    }
-    return changed;
-}
-
-static bool ir_block_life_propagate(ir_block *self, bool *changed)
-{
-    ir_instr *instr;
-    ir_value *value;
-    size_t i, o, p, mem, cnt;
-    /* bitmasks which operands are read from or written to */
-    size_t read, write;
-    char dbg_ind[16];
-    dbg_ind[0] = '#';
-    dbg_ind[1] = '0';
-    (void)dbg_ind;
-
-    vec_free(self->living);
-
-    p = vec_size(self->exits);
-    for (i = 0; i < p; ++i) {
-        ir_block *prev = self->exits[i];
-        cnt = vec_size(prev->living);
-        for (o = 0; o < cnt; ++o) {
-            if (!vec_ir_value_find(self->living, prev->living[o], NULL))
-                vec_push(self->living, prev->living[o]);
-        }
-    }
-
-    i = vec_size(self->instr);
-    while (i)
-    { --i;
-        instr = self->instr[i];
-
-        /* See which operands are read and write operands */
-        ir_op_read_write(instr->opcode, &read, &write);
-
-        /* Go through the 3 main operands
-         * writes first, then reads
-         */
-        for (o = 0; o < 3; ++o)
-        {
-            if (!instr->_ops[o]) /* no such operand */
-                continue;
-
-            value = instr->_ops[o];
-
-            /* We only care about locals */
-            /* we also calculate parameter liferanges so that locals
-             * can take up parameter slots */
-            if (value->store != store_value &&
-                value->store != store_local &&
-                value->store != store_param)
-                continue;
-
-            /* write operands */
-            /* When we write to a local, we consider it "dead" for the
-             * remaining upper part of the function, since in SSA a value
-             * can only be written once (== created)
-             */
-            if (write & (1<<o))
-            {
-                size_t idx;
-                bool in_living = vec_ir_value_find(self->living, value, &idx);
-                if (!in_living)
-                {
-                    /* If the value isn't alive it hasn't been read before... */
-                    /* TODO: See if the warning can be emitted during parsing or AST processing
-                     * otherwise have warning printed here.
-                     * IF printing a warning here: include filecontext_t,
-                     * and make sure it's only printed once
-                     * since this function is run multiple times.
-                     */
-                    /* con_err( "Value only written %s\n", value->name); */
-                    if (ir_value_life_merge(value, instr->eid))
-                        *changed = true;
-                } else {
-                    /* since 'living' won't contain it
-                     * anymore, merge the value, since
-                     * (A) doesn't.
-                     */
-                    if (ir_value_life_merge(value, instr->eid))
-                        *changed = true;
-                    /* Then remove */
-                    vec_remove(self->living, idx, 1);
-                }
-                /* Removing a vector removes all members */
-                for (mem = 0; mem < 3; ++mem) {
-                    if (value->members[mem] && vec_ir_value_find(self->living, value->members[mem], &idx)) {
-                        if (ir_value_life_merge(value->members[mem], instr->eid))
-                            *changed = true;
-                        vec_remove(self->living, idx, 1);
-                    }
-                }
-                /* Removing the last member removes the vector */
-                if (value->memberof) {
-                    value = value->memberof;
-                    for (mem = 0; mem < 3; ++mem) {
-                        if (value->members[mem] && vec_ir_value_find(self->living, value->members[mem], NULL))
-                            break;
-                    }
-                    if (mem == 3 && vec_ir_value_find(self->living, value, &idx)) {
-                        if (ir_value_life_merge(value, instr->eid))
-                            *changed = true;
-                        vec_remove(self->living, idx, 1);
-                    }
-                }
-            }
-        }
-
-        /* These operations need a special case as they can break when using
-         * same source and destination operand otherwise, as the engine may
-         * read the source multiple times. */
-        if (instr->opcode == INSTR_MUL_VF ||
-            instr->opcode == VINSTR_BITAND_VF ||
-            instr->opcode == VINSTR_BITOR_VF ||
-            instr->opcode == VINSTR_BITXOR ||
-            instr->opcode == VINSTR_BITXOR_VF ||
-            instr->opcode == VINSTR_BITXOR_V ||
-            instr->opcode == VINSTR_CROSS)
-        {
-            value = instr->_ops[2];
-            /* the float source will get an additional lifetime */
-            if (ir_value_life_merge(value, instr->eid+1))
-                *changed = true;
-            if (value->memberof && ir_value_life_merge(value->memberof, instr->eid+1))
-                *changed = true;
-        }
-
-        if (instr->opcode == INSTR_MUL_FV ||
-            instr->opcode == INSTR_LOAD_V ||
-            instr->opcode == VINSTR_BITXOR ||
-            instr->opcode == VINSTR_BITXOR_VF ||
-            instr->opcode == VINSTR_BITXOR_V ||
-            instr->opcode == VINSTR_CROSS)
-        {
-            value = instr->_ops[1];
-            /* the float source will get an additional lifetime */
-            if (ir_value_life_merge(value, instr->eid+1))
-                *changed = true;
-            if (value->memberof && ir_value_life_merge(value->memberof, instr->eid+1))
-                *changed = true;
-        }
-
-        for (o = 0; o < 3; ++o)
-        {
-            if (!instr->_ops[o]) /* no such operand */
-                continue;
-
-            value = instr->_ops[o];
-
-            /* We only care about locals */
-            /* we also calculate parameter liferanges so that locals
-             * can take up parameter slots */
-            if (value->store != store_value &&
-                value->store != store_local &&
-                value->store != store_param)
-                continue;
-
-            /* read operands */
-            if (read & (1<<o))
-            {
-                if (!vec_ir_value_find(self->living, value, NULL))
-                    vec_push(self->living, value);
-                /* reading adds the full vector */
-                if (value->memberof && !vec_ir_value_find(self->living, value->memberof, NULL))
-                    vec_push(self->living, value->memberof);
-                for (mem = 0; mem < 3; ++mem) {
-                    if (value->members[mem] && !vec_ir_value_find(self->living, value->members[mem], NULL))
-                        vec_push(self->living, value->members[mem]);
-                }
-            }
-        }
-        /* PHI operands are always read operands */
-        for (p = 0; p < vec_size(instr->phi); ++p)
-        {
-            value = instr->phi[p].value;
-            if (!vec_ir_value_find(self->living, value, NULL))
-                vec_push(self->living, value);
-            /* reading adds the full vector */
-            if (value->memberof && !vec_ir_value_find(self->living, value->memberof, NULL))
-                vec_push(self->living, value->memberof);
-            for (mem = 0; mem < 3; ++mem) {
-                if (value->members[mem] && !vec_ir_value_find(self->living, value->members[mem], NULL))
-                    vec_push(self->living, value->members[mem]);
-            }
-        }
-
-        /* on a call, all these values must be "locked" */
-        if (instr->opcode >= INSTR_CALL0 && instr->opcode <= INSTR_CALL8) {
-            if (ir_block_living_lock(self))
-                *changed = true;
-        }
-        /* call params are read operands too */
-        for (p = 0; p < vec_size(instr->params); ++p)
-        {
-            value = instr->params[p];
-            if (!vec_ir_value_find(self->living, value, NULL))
-                vec_push(self->living, value);
-            /* reading adds the full vector */
-            if (value->memberof && !vec_ir_value_find(self->living, value->memberof, NULL))
-                vec_push(self->living, value->memberof);
-            for (mem = 0; mem < 3; ++mem) {
-                if (value->members[mem] && !vec_ir_value_find(self->living, value->members[mem], NULL))
-                    vec_push(self->living, value->members[mem]);
-            }
-        }
-
-        /* (A) */
-        if (ir_block_living_add_instr(self, instr->eid))
-            *changed = true;
-    }
-    /* the "entry" instruction ID */
-    if (ir_block_living_add_instr(self, self->entry_id))
-        *changed = true;
-
-    return true;
-}
-
-bool ir_function_calculate_liferanges(ir_function *self)
-{
-    size_t i, s;
-    bool changed;
-
-    /* parameters live at 0 */
-    for (i = 0; i < vec_size(self->params); ++i)
-        if (!ir_value_life_merge(self->locals[i], 0))
-            compile_error(self->context, "internal error: failed value-life merging");
-
-    do {
-        self->run_id++;
-        changed = false;
-        i = vec_size(self->blocks);
-        while (i--) {
-            ir_block_life_propagate(self->blocks[i], &changed);
-        }
-    } while (changed);
-
-    if (vec_size(self->blocks)) {
-        ir_block *block = self->blocks[0];
-        for (i = 0; i < vec_size(block->living); ++i) {
-            ir_value *v = block->living[i];
-            if (v->store != store_local)
-                continue;
-            if (v->vtype == TYPE_VECTOR)
-                continue;
-            self->flags |= IR_FLAG_HAS_UNINITIALIZED;
-            /* find the instruction reading from it */
-            for (s = 0; s < vec_size(v->reads); ++s) {
-                if (v->reads[s]->eid == v->life[0].end)
-                    break;
-            }
-            if (s < vec_size(v->reads)) {
-                if (irwarning(v->context, WARN_USED_UNINITIALIZED,
-                              "variable `%s` may be used uninitialized in this function\n"
-                              " -> %s:%i",
-                              v->name,
-                              v->reads[s]->context.file, v->reads[s]->context.line)
-                   )
-                {
-                    return false;
-                }
-                continue;
-            }
-            if (v->memberof) {
-                ir_value *vec = v->memberof;
-                for (s = 0; s < vec_size(vec->reads); ++s) {
-                    if (vec->reads[s]->eid == v->life[0].end)
-                        break;
-                }
-                if (s < vec_size(vec->reads)) {
-                    if (irwarning(v->context, WARN_USED_UNINITIALIZED,
-                                  "variable `%s` may be used uninitialized in this function\n"
-                                  " -> %s:%i",
-                                  v->name,
-                                  vec->reads[s]->context.file, vec->reads[s]->context.line)
-                       )
-                    {
-                        return false;
-                    }
-                    continue;
-                }
-            }
-            if (irwarning(v->context, WARN_USED_UNINITIALIZED,
-                          "variable `%s` may be used uninitialized in this function", v->name))
-            {
-                return false;
-            }
-        }
-    }
-    return true;
-}
-
-/***********************************************************************
- *IR Code-Generation
- *
- * Since the IR has the convention of putting 'write' operands
- * at the beginning, we have to rotate the operands of instructions
- * properly in order to generate valid QCVM code.
- *
- * Having destinations at a fixed position is more convenient. In QC
- * this is *mostly* OPC,  but FTE adds at least 2 instructions which
- * read from from OPA,  and store to OPB rather than OPC.   Which is
- * partially the reason why the implementation of these instructions
- * in darkplaces has been delayed for so long.
- *
- * Breaking conventions is annoying...
- */
-static bool ir_builder_gen_global(ir_builder *self, ir_value *global, bool islocal);
-
-static bool gen_global_field(code_t *code, ir_value *global)
-{
-    if (global->hasvalue)
-    {
-        ir_value *fld = global->constval.vpointer;
-        if (!fld) {
-            irerror(global->context, "Invalid field constant with no field: %s", global->name);
-            return false;
-        }
-
-        /* copy the field's value */
-        ir_value_code_setaddr(global, vec_size(code->globals));
-        vec_push(code->globals, fld->code.fieldaddr);
-        if (global->fieldtype == TYPE_VECTOR) {
-            vec_push(code->globals, fld->code.fieldaddr+1);
-            vec_push(code->globals, fld->code.fieldaddr+2);
-        }
-    }
-    else
-    {
-        ir_value_code_setaddr(global, vec_size(code->globals));
-        vec_push(code->globals, 0);
-        if (global->fieldtype == TYPE_VECTOR) {
-            vec_push(code->globals, 0);
-            vec_push(code->globals, 0);
-        }
-    }
-    if (global->code.globaladdr < 0)
-        return false;
-    return true;
-}
-
-static bool gen_global_pointer(code_t *code, ir_value *global)
-{
-    if (global->hasvalue)
-    {
-        ir_value *target = global->constval.vpointer;
-        if (!target) {
-            irerror(global->context, "Invalid pointer constant: %s", global->name);
-            /* NULL pointers are pointing to the NULL constant, which also
-             * sits at address 0, but still has an ir_value for itself.
-             */
-            return false;
-        }
-
-        /* Here, relocations ARE possible - in fteqcc-enhanced-qc:
-         * void() foo; <- proto
-         * void() *fooptr = &foo;
-         * void() foo = { code }
-         */
-        if (!target->code.globaladdr) {
-            /* FIXME: Check for the constant nullptr ir_value!
-             * because then code.globaladdr being 0 is valid.
-             */
-            irerror(global->context, "FIXME: Relocation support");
-            return false;
-        }
-
-        ir_value_code_setaddr(global, vec_size(code->globals));
-        vec_push(code->globals, target->code.globaladdr);
-    }
-    else
-    {
-        ir_value_code_setaddr(global, vec_size(code->globals));
-        vec_push(code->globals, 0);
-    }
-    if (global->code.globaladdr < 0)
-        return false;
-    return true;
-}
-
-static bool gen_blocks_recursive(code_t *code, ir_function *func, ir_block *block)
-{
-    prog_section_statement_t stmt;
-    ir_instr *instr;
-    ir_block *target;
-    ir_block *ontrue;
-    ir_block *onfalse;
-    size_t    stidx;
-    size_t    i;
-    int       j;
-
-    block->generated = true;
-    block->code_start = vec_size(code->statements);
-    for (i = 0; i < vec_size(block->instr); ++i)
-    {
-        instr = block->instr[i];
-
-        if (instr->opcode == VINSTR_PHI) {
-            irerror(block->context, "cannot generate virtual instruction (phi)");
-            return false;
-        }
-
-        if (instr->opcode == VINSTR_JUMP) {
-            target = instr->bops[0];
-            /* for uncoditional jumps, if the target hasn't been generated
-             * yet, we generate them right here.
-             */
-            if (!target->generated)
-                return gen_blocks_recursive(code, func, target);
-
-            /* otherwise we generate a jump instruction */
-            stmt.opcode = INSTR_GOTO;
-            stmt.o1.s1 = (target->code_start) - vec_size(code->statements);
-            stmt.o2.s1 = 0;
-            stmt.o3.s1 = 0;
-            if (stmt.o1.s1 != 1)
-                code_push_statement(code, &stmt, instr->context);
-
-            /* no further instructions can be in this block */
-            return true;
-        }
-
-        if (instr->opcode == VINSTR_BITXOR) {
-            stmt.opcode = INSTR_BITOR;
-            stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]);
-            stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]);
-            stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]);
-            code_push_statement(code, &stmt, instr->context);
-            stmt.opcode = INSTR_BITAND;
-            stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]);
-            stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]);
-            stmt.o3.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]);
-            code_push_statement(code, &stmt, instr->context);
-            stmt.opcode = INSTR_SUB_F;
-            stmt.o1.s1 = ir_value_code_addr(instr->_ops[0]);
-            stmt.o2.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]);
-            stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]);
-            code_push_statement(code, &stmt, instr->context);
-
-            /* instruction generated */
-            continue;
-        }
-
-        if (instr->opcode == VINSTR_BITAND_V) {
-            stmt.opcode = INSTR_BITAND;
-            stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]);
-            stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]);
-            stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]);
-            code_push_statement(code, &stmt, instr->context);
-            ++stmt.o1.s1;
-            ++stmt.o2.s1;
-            ++stmt.o3.s1;
-            code_push_statement(code, &stmt, instr->context);
-            ++stmt.o1.s1;
-            ++stmt.o2.s1;
-            ++stmt.o3.s1;
-            code_push_statement(code, &stmt, instr->context);
-
-            /* instruction generated */
-            continue;
-        }
-
-        if (instr->opcode == VINSTR_BITOR_V) {
-            stmt.opcode = INSTR_BITOR;
-            stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]);
-            stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]);
-            stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]);
-            code_push_statement(code, &stmt, instr->context);
-            ++stmt.o1.s1;
-            ++stmt.o2.s1;
-            ++stmt.o3.s1;
-            code_push_statement(code, &stmt, instr->context);
-            ++stmt.o1.s1;
-            ++stmt.o2.s1;
-            ++stmt.o3.s1;
-            code_push_statement(code, &stmt, instr->context);
-
-            /* instruction generated */
-            continue;
-        }
-
-        if (instr->opcode == VINSTR_BITXOR_V) {
-            for (j = 0; j < 3; ++j) {
-                stmt.opcode = INSTR_BITOR;
-                stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]) + j;
-                stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]) + j;
-                stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]) + j;
-                code_push_statement(code, &stmt, instr->context);
-                stmt.opcode = INSTR_BITAND;
-                stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]) + j;
-                stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]) + j;
-                stmt.o3.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]) + j;
-                code_push_statement(code, &stmt, instr->context);
-            }
-            stmt.opcode = INSTR_SUB_V;
-            stmt.o1.s1 = ir_value_code_addr(instr->_ops[0]);
-            stmt.o2.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]);
-            stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]);
-            code_push_statement(code, &stmt, instr->context);
-
-            /* instruction generated */
-            continue;
-        }
-
-        if (instr->opcode == VINSTR_BITAND_VF) {
-            stmt.opcode = INSTR_BITAND;
-            stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]);
-            stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]);
-            stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]);
-            code_push_statement(code, &stmt, instr->context);
-            ++stmt.o1.s1;
-            ++stmt.o3.s1;
-            code_push_statement(code, &stmt, instr->context);
-            ++stmt.o1.s1;
-            ++stmt.o3.s1;
-            code_push_statement(code, &stmt, instr->context);
-
-            /* instruction generated */
-            continue;
-        }
-
-        if (instr->opcode == VINSTR_BITOR_VF) {
-            stmt.opcode = INSTR_BITOR;
-            stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]);
-            stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]);
-            stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]);
-            code_push_statement(code, &stmt, instr->context);
-            ++stmt.o1.s1;
-            ++stmt.o3.s1;
-            code_push_statement(code, &stmt, instr->context);
-            ++stmt.o1.s1;
-            ++stmt.o3.s1;
-            code_push_statement(code, &stmt, instr->context);
-
-            /* instruction generated */
-            continue;
-        }
-
-        if (instr->opcode == VINSTR_BITXOR_VF) {
-            for (j = 0; j < 3; ++j) {
-                stmt.opcode = INSTR_BITOR;
-                stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]) + j;
-                stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]);
-                stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]) + j;
-                code_push_statement(code, &stmt, instr->context);
-                stmt.opcode = INSTR_BITAND;
-                stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]) + j;
-                stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]);
-                stmt.o3.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]) + j;
-                code_push_statement(code, &stmt, instr->context);
-            }
-            stmt.opcode = INSTR_SUB_V;
-            stmt.o1.s1 = ir_value_code_addr(instr->_ops[0]);
-            stmt.o2.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]);
-            stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]);
-            code_push_statement(code, &stmt, instr->context);
-
-            /* instruction generated */
-            continue;
-        }
-
-        if (instr->opcode == VINSTR_CROSS) {
-            stmt.opcode = INSTR_MUL_F;
-            for (j = 0; j < 3; ++j) {
-                stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]) + (j + 1) % 3;
-                stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]) + (j + 2) % 3;
-                stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]) + j;
-                code_push_statement(code, &stmt, instr->context);
-                stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]) + (j + 2) % 3;
-                stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]) + (j + 1) % 3;
-                stmt.o3.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]) + j;
-                code_push_statement(code, &stmt, instr->context);
-            }
-            stmt.opcode = INSTR_SUB_V;
-            stmt.o1.s1 = ir_value_code_addr(instr->_ops[0]);
-            stmt.o2.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]);
-            stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]);
-            code_push_statement(code, &stmt, instr->context);
-
-            /* instruction generated */
-            continue;
-        }
-
-        if (instr->opcode == VINSTR_COND) {
-            ontrue  = instr->bops[0];
-            onfalse = instr->bops[1];
-            /* TODO: have the AST signal which block should
-             * come first: eg. optimize IFs without ELSE...
-             */
-
-            stmt.o1.u1 = ir_value_code_addr(instr->_ops[0]);
-            stmt.o2.u1 = 0;
-            stmt.o3.s1 = 0;
-
-            if (ontrue->generated) {
-                stmt.opcode = INSTR_IF;
-                stmt.o2.s1 = (ontrue->code_start) - vec_size(code->statements);
-                if (stmt.o2.s1 != 1)
-                    code_push_statement(code, &stmt, instr->context);
-            }
-            if (onfalse->generated) {
-                stmt.opcode = INSTR_IFNOT;
-                stmt.o2.s1 = (onfalse->code_start) - vec_size(code->statements);
-                if (stmt.o2.s1 != 1)
-                    code_push_statement(code, &stmt, instr->context);
-            }
-            if (!ontrue->generated) {
-                if (onfalse->generated)
-                    return gen_blocks_recursive(code, func, ontrue);
-            }
-            if (!onfalse->generated) {
-                if (ontrue->generated)
-                    return gen_blocks_recursive(code, func, onfalse);
-            }
-            /* neither ontrue nor onfalse exist */
-            stmt.opcode = INSTR_IFNOT;
-            if (!instr->likely) {
-                /* Honor the likelyhood hint */
-                ir_block *tmp = onfalse;
-                stmt.opcode = INSTR_IF;
-                onfalse = ontrue;
-                ontrue = tmp;
-            }
-            stidx = vec_size(code->statements);
-            code_push_statement(code, &stmt, instr->context);
-            /* on false we jump, so add ontrue-path */
-            if (!gen_blocks_recursive(code, func, ontrue))
-                return false;
-            /* fixup the jump address */
-            code->statements[stidx].o2.s1 = vec_size(code->statements) - stidx;
-            /* generate onfalse path */
-            if (onfalse->generated) {
-                /* fixup the jump address */
-                code->statements[stidx].o2.s1 = (onfalse->code_start) - (stidx);
-                if (stidx+2 == vec_size(code->statements) && code->statements[stidx].o2.s1 == 1) {
-                    code->statements[stidx] = code->statements[stidx+1];
-                    if (code->statements[stidx].o1.s1 < 0)
-                        code->statements[stidx].o1.s1++;
-                    code_pop_statement(code);
-                }
-                stmt.opcode = vec_last(code->statements).opcode;
-                if (stmt.opcode == INSTR_GOTO ||
-                    stmt.opcode == INSTR_IF ||
-                    stmt.opcode == INSTR_IFNOT ||
-                    stmt.opcode == INSTR_RETURN ||
-                    stmt.opcode == INSTR_DONE)
-                {
-                    /* no use jumping from here */
-                    return true;
-                }
-                /* may have been generated in the previous recursive call */
-                stmt.opcode = INSTR_GOTO;
-                stmt.o1.s1 = (onfalse->code_start) - vec_size(code->statements);
-                stmt.o2.s1 = 0;
-                stmt.o3.s1 = 0;
-                if (stmt.o1.s1 != 1)
-                    code_push_statement(code, &stmt, instr->context);
-                return true;
-            }
-            else if (stidx+2 == vec_size(code->statements) && code->statements[stidx].o2.s1 == 1) {
-                code->statements[stidx] = code->statements[stidx+1];
-                if (code->statements[stidx].o1.s1 < 0)
-                    code->statements[stidx].o1.s1++;
-                code_pop_statement(code);
-            }
-            /* if not, generate now */
-            return gen_blocks_recursive(code, func, onfalse);
-        }
-
-        if ( (instr->opcode >= INSTR_CALL0 && instr->opcode <= INSTR_CALL8)
-           || instr->opcode == VINSTR_NRCALL)
-        {
-            size_t p, first;
-            ir_value *retvalue;
-
-            first = vec_size(instr->params);
-            if (first > 8)
-                first = 8;
-            for (p = 0; p < first; ++p)
-            {
-                ir_value *param = instr->params[p];
-                if (param->callparam)
-                    continue;
-
-                stmt.opcode = INSTR_STORE_F;
-                stmt.o3.u1 = 0;
-
-                if (param->vtype == TYPE_FIELD)
-                    stmt.opcode = field_store_instr[param->fieldtype];
-                else if (param->vtype == TYPE_NIL)
-                    stmt.opcode = INSTR_STORE_V;
-                else
-                    stmt.opcode = type_store_instr[param->vtype];
-                stmt.o1.u1 = ir_value_code_addr(param);
-                stmt.o2.u1 = OFS_PARM0 + 3 * p;
-
-                if (param->vtype == TYPE_VECTOR && (param->flags & IR_FLAG_SPLIT_VECTOR)) {
-                    /* fetch 3 separate floats */
-                    stmt.opcode = INSTR_STORE_F;
-                    stmt.o1.u1 = ir_value_code_addr(param->members[0]);
-                    code_push_statement(code, &stmt, instr->context);
-                    stmt.o2.u1++;
-                    stmt.o1.u1 = ir_value_code_addr(param->members[1]);
-                    code_push_statement(code, &stmt, instr->context);
-                    stmt.o2.u1++;
-                    stmt.o1.u1 = ir_value_code_addr(param->members[2]);
-                    code_push_statement(code, &stmt, instr->context);
-                }
-                else
-                    code_push_statement(code, &stmt, instr->context);
-            }
-            /* Now handle extparams */
-            first = vec_size(instr->params);
-            for (; p < first; ++p)
-            {
-                ir_builder *ir = func->owner;
-                ir_value *param = instr->params[p];
-                ir_value *targetparam;
-
-                if (param->callparam)
-                    continue;
-
-                if (p-8 >= vec_size(ir->extparams))
-                    ir_gen_extparam(ir);
-
-                targetparam = ir->extparams[p-8];
-
-                stmt.opcode = INSTR_STORE_F;
-                stmt.o3.u1 = 0;
-
-                if (param->vtype == TYPE_FIELD)
-                    stmt.opcode = field_store_instr[param->fieldtype];
-                else if (param->vtype == TYPE_NIL)
-                    stmt.opcode = INSTR_STORE_V;
-                else
-                    stmt.opcode = type_store_instr[param->vtype];
-                stmt.o1.u1 = ir_value_code_addr(param);
-                stmt.o2.u1 = ir_value_code_addr(targetparam);
-                if (param->vtype == TYPE_VECTOR && (param->flags & IR_FLAG_SPLIT_VECTOR)) {
-                    /* fetch 3 separate floats */
-                    stmt.opcode = INSTR_STORE_F;
-                    stmt.o1.u1 = ir_value_code_addr(param->members[0]);
-                    code_push_statement(code, &stmt, instr->context);
-                    stmt.o2.u1++;
-                    stmt.o1.u1 = ir_value_code_addr(param->members[1]);
-                    code_push_statement(code, &stmt, instr->context);
-                    stmt.o2.u1++;
-                    stmt.o1.u1 = ir_value_code_addr(param->members[2]);
-                    code_push_statement(code, &stmt, instr->context);
-                }
-                else
-                    code_push_statement(code, &stmt, instr->context);
-            }
-
-            stmt.opcode = INSTR_CALL0 + vec_size(instr->params);
-            if (stmt.opcode > INSTR_CALL8)
-                stmt.opcode = INSTR_CALL8;
-            stmt.o1.u1 = ir_value_code_addr(instr->_ops[1]);
-            stmt.o2.u1 = 0;
-            stmt.o3.u1 = 0;
-            code_push_statement(code, &stmt, instr->context);
-
-            retvalue = instr->_ops[0];
-            if (retvalue && retvalue->store != store_return &&
-                (retvalue->store == store_global || vec_size(retvalue->life)))
-            {
-                /* not to be kept in OFS_RETURN */
-                if (retvalue->vtype == TYPE_FIELD && OPTS_FLAG(ADJUST_VECTOR_FIELDS))
-                    stmt.opcode = field_store_instr[retvalue->fieldtype];
-                else
-                    stmt.opcode = type_store_instr[retvalue->vtype];
-                stmt.o1.u1 = OFS_RETURN;
-                stmt.o2.u1 = ir_value_code_addr(retvalue);
-                stmt.o3.u1 = 0;
-                code_push_statement(code, &stmt, instr->context);
-            }
-            continue;
-        }
-
-        if (instr->opcode == INSTR_STATE) {
-            stmt.opcode = instr->opcode;
-            if (instr->_ops[0])
-                stmt.o1.u1 = ir_value_code_addr(instr->_ops[0]);
-            if (instr->_ops[1])
-                stmt.o2.u1 = ir_value_code_addr(instr->_ops[1]);
-            stmt.o3.u1 = 0;
-            code_push_statement(code, &stmt, instr->context);
-            continue;
-        }
-
-        stmt.opcode = instr->opcode;
-        stmt.o1.u1 = 0;
-        stmt.o2.u1 = 0;
-        stmt.o3.u1 = 0;
-
-        /* This is the general order of operands */
-        if (instr->_ops[0])
-            stmt.o3.u1 = ir_value_code_addr(instr->_ops[0]);
-
-        if (instr->_ops[1])
-            stmt.o1.u1 = ir_value_code_addr(instr->_ops[1]);
-
-        if (instr->_ops[2])
-            stmt.o2.u1 = ir_value_code_addr(instr->_ops[2]);
-
-        if (stmt.opcode == INSTR_RETURN || stmt.opcode == INSTR_DONE)
-        {
-            stmt.o1.u1 = stmt.o3.u1;
-            stmt.o3.u1 = 0;
-        }
-        else if ((stmt.opcode >= INSTR_STORE_F &&
-                  stmt.opcode <= INSTR_STORE_FNC) ||
-                 (stmt.opcode >= INSTR_STOREP_F &&
-                  stmt.opcode <= INSTR_STOREP_FNC))
-        {
-            /* 2-operand instructions with A -> B */
-            stmt.o2.u1 = stmt.o3.u1;
-            stmt.o3.u1 = 0;
-
-            /* tiny optimization, don't output
-             * STORE a, a
-             */
-            if (stmt.o2.u1 == stmt.o1.u1 &&
-                OPTS_OPTIMIZATION(OPTIM_PEEPHOLE))
-            {
-                ++opts_optimizationcount[OPTIM_PEEPHOLE];
-                continue;
-            }
-        }
-        code_push_statement(code, &stmt, instr->context);
-    }
-    return true;
-}
-
-static bool gen_function_code(code_t *code, ir_function *self)
-{
-    ir_block *block;
-    prog_section_statement_t stmt, *retst;
-
-    /* Starting from entry point, we generate blocks "as they come"
-     * for now. Dead blocks will not be translated obviously.
-     */
-    if (!vec_size(self->blocks)) {
-        irerror(self->context, "Function '%s' declared without body.", self->name);
-        return false;
-    }
-
-    block = self->blocks[0];
-    if (block->generated)
-        return true;
-
-    if (!gen_blocks_recursive(code, self, block)) {
-        irerror(self->context, "failed to generate blocks for '%s'", self->name);
-        return false;
-    }
-
-    /* code_write and qcvm -disasm need to know that the function ends here */
-    retst = &vec_last(code->statements);
-    if (OPTS_OPTIMIZATION(OPTIM_VOID_RETURN) &&
-        self->outtype == TYPE_VOID &&
-        retst->opcode == INSTR_RETURN &&
-        !retst->o1.u1 && !retst->o2.u1 && !retst->o3.u1)
-    {
-        retst->opcode = INSTR_DONE;
-        ++opts_optimizationcount[OPTIM_VOID_RETURN];
-    } else {
-        lex_ctx_t last;
-
-        stmt.opcode = INSTR_DONE;
-        stmt.o1.u1  = 0;
-        stmt.o2.u1  = 0;
-        stmt.o3.u1  = 0;
-        last.line   = vec_last(code->linenums);
-        last.column = vec_last(code->columnnums);
-
-        code_push_statement(code, &stmt, last);
-    }
-    return true;
-}
-
-static qcint_t ir_builder_filestring(ir_builder *ir, const char *filename)
-{
-    /* NOTE: filename pointers are copied, we never strdup them,
-     * thus we can use pointer-comparison to find the string.
-     */
-    size_t i;
-    qcint_t  str;
-
-    for (i = 0; i < vec_size(ir->filenames); ++i) {
-        if (ir->filenames[i] == filename)
-            return ir->filestrings[i];
-    }
-
-    str = code_genstring(ir->code, filename);
-    vec_push(ir->filenames, filename);
-    vec_push(ir->filestrings, str);
-    return str;
-}
-
-static bool gen_global_function(ir_builder *ir, ir_value *global)
-{
-    prog_section_function_t fun;
-    ir_function            *irfun;
-
-    size_t i;
-
-    if (!global->hasvalue || (!global->constval.vfunc))
-    {
-        irerror(global->context, "Invalid state of function-global: not constant: %s", global->name);
-        return false;
-    }
-
-    irfun = global->constval.vfunc;
-
-    fun.name    = global->code.name;
-    fun.file    = ir_builder_filestring(ir, global->context.file);
-    fun.profile = 0; /* always 0 */
-    fun.nargs   = vec_size(irfun->params);
-    if (fun.nargs > 8)
-        fun.nargs = 8;
-
-    for (i = 0;i < 8; ++i) {
-        if ((int32_t)i >= fun.nargs)
-            fun.argsize[i] = 0;
-        else
-            fun.argsize[i] = type_sizeof_[irfun->params[i]];
-    }
-
-    fun.firstlocal = 0;
-    fun.locals     = irfun->allocated_locals;
-
-    if (irfun->builtin)
-        fun.entry = irfun->builtin+1;
-    else {
-        irfun->code_function_def = vec_size(ir->code->functions);
-        fun.entry                = vec_size(ir->code->statements);
-    }
-
-    vec_push(ir->code->functions, fun);
-    return true;
-}
-
-static ir_value* ir_gen_extparam_proto(ir_builder *ir)
-{
-    ir_value *global;
-    char      name[128];
-
-    util_snprintf(name, sizeof(name), "EXTPARM#%i", (int)(vec_size(ir->extparam_protos)));
-    global = ir_value_var(name, store_global, TYPE_VECTOR);
-
-    vec_push(ir->extparam_protos, global);
-    return global;
-}
-
-static void ir_gen_extparam(ir_builder *ir)
-{
-    prog_section_def_t def;
-    ir_value          *global;
-
-    if (vec_size(ir->extparam_protos) < vec_size(ir->extparams)+1)
-        global = ir_gen_extparam_proto(ir);
-    else
-        global = ir->extparam_protos[vec_size(ir->extparams)];
-
-    def.name   = code_genstring(ir->code, global->name);
-    def.type   = TYPE_VECTOR;
-    def.offset = vec_size(ir->code->globals);
-
-    vec_push(ir->code->defs, def);
-
-    ir_value_code_setaddr(global, def.offset);
-
-    vec_push(ir->code->globals, 0);
-    vec_push(ir->code->globals, 0);
-    vec_push(ir->code->globals, 0);
-
-    vec_push(ir->extparams, global);
-}
-
-static bool gen_function_extparam_copy(code_t *code, ir_function *self)
-{
-    size_t i, ext, numparams;
-
-    ir_builder *ir = self->owner;
-    ir_value   *ep;
-    prog_section_statement_t stmt;
-
-    numparams = vec_size(self->params);
-    if (!numparams)
-        return true;
-
-    stmt.opcode = INSTR_STORE_F;
-    stmt.o3.s1 = 0;
-    for (i = 8; i < numparams; ++i) {
-        ext = i - 8;
-        if (ext >= vec_size(ir->extparams))
-            ir_gen_extparam(ir);
-
-        ep = ir->extparams[ext];
-
-        stmt.opcode = type_store_instr[self->locals[i]->vtype];
-        if (self->locals[i]->vtype == TYPE_FIELD &&
-            self->locals[i]->fieldtype == TYPE_VECTOR)
-        {
-            stmt.opcode = INSTR_STORE_V;
-        }
-        stmt.o1.u1 = ir_value_code_addr(ep);
-        stmt.o2.u1 = ir_value_code_addr(self->locals[i]);
-        code_push_statement(code, &stmt, self->context);
-    }
-
-    return true;
-}
-
-static bool gen_function_varargs_copy(code_t *code, ir_function *self)
-{
-    size_t i, ext, numparams, maxparams;
-
-    ir_builder *ir = self->owner;
-    ir_value   *ep;
-    prog_section_statement_t stmt;
-
-    numparams = vec_size(self->params);
-    if (!numparams)
-        return true;
-
-    stmt.opcode = INSTR_STORE_V;
-    stmt.o3.s1 = 0;
-    maxparams = numparams + self->max_varargs;
-    for (i = numparams; i < maxparams; ++i) {
-        if (i < 8) {
-            stmt.o1.u1 = OFS_PARM0 + 3*i;
-            stmt.o2.u1 = ir_value_code_addr(self->locals[i]);
-            code_push_statement(code, &stmt, self->context);
-            continue;
-        }
-        ext = i - 8;
-        while (ext >= vec_size(ir->extparams))
-            ir_gen_extparam(ir);
-
-        ep = ir->extparams[ext];
-
-        stmt.o1.u1 = ir_value_code_addr(ep);
-        stmt.o2.u1 = ir_value_code_addr(self->locals[i]);
-        code_push_statement(code, &stmt, self->context);
-    }
-
-    return true;
-}
-
-static bool gen_function_locals(ir_builder *ir, ir_value *global)
-{
-    prog_section_function_t *def;
-    ir_function             *irfun;
-    size_t                   i;
-    uint32_t                 firstlocal, firstglobal;
-
-    irfun = global->constval.vfunc;
-    def   = ir->code->functions + irfun->code_function_def;
-
-    if (OPTS_OPTION_BOOL(OPTION_G) ||
-        !OPTS_OPTIMIZATION(OPTIM_OVERLAP_LOCALS)        ||
-        (irfun->flags & IR_FLAG_MASK_NO_OVERLAP))
-    {
-        firstlocal = def->firstlocal = vec_size(ir->code->globals);
-    } else {
-        firstlocal = def->firstlocal = ir->first_common_local;
-        ++opts_optimizationcount[OPTIM_OVERLAP_LOCALS];
-    }
-
-    firstglobal = (OPTS_OPTIMIZATION(OPTIM_GLOBAL_TEMPS) ? ir->first_common_globaltemp : firstlocal);
-
-    for (i = vec_size(ir->code->globals); i < firstlocal + irfun->allocated_locals; ++i)
-        vec_push(ir->code->globals, 0);
-    for (i = 0; i < vec_size(irfun->locals); ++i) {
-        ir_value *v = irfun->locals[i];
-        if (v->locked || !OPTS_OPTIMIZATION(OPTIM_GLOBAL_TEMPS)) {
-            ir_value_code_setaddr(v, firstlocal + v->code.local);
-            if (!ir_builder_gen_global(ir, irfun->locals[i], true)) {
-                irerror(irfun->locals[i]->context, "failed to generate local %s", irfun->locals[i]->name);
-                return false;
-            }
-        }
-        else
-            ir_value_code_setaddr(v, firstglobal + v->code.local);
-    }
-    for (i = 0; i < vec_size(irfun->values); ++i)
-    {
-        ir_value *v = irfun->values[i];
-        if (v->callparam)
-            continue;
-        if (v->locked)
-            ir_value_code_setaddr(v, firstlocal + v->code.local);
-        else
-            ir_value_code_setaddr(v, firstglobal + v->code.local);
-    }
-    return true;
-}
-
-static bool gen_global_function_code(ir_builder *ir, ir_value *global)
-{
-    prog_section_function_t *fundef;
-    ir_function             *irfun;
-
-    (void)ir;
-
-    irfun = global->constval.vfunc;
-    if (!irfun) {
-        if (global->cvq == CV_NONE) {
-            if (irwarning(global->context, WARN_IMPLICIT_FUNCTION_POINTER,
-                          "function `%s` has no body and in QC implicitly becomes a function-pointer",
-                          global->name))
-            {
-                /* Not bailing out just now. If this happens a lot you don't want to have
-                 * to rerun gmqcc for each such function.
-                 */
-
-                /* return false; */
-            }
-        }
-        /* this was a function pointer, don't generate code for those */
-        return true;
-    }
-
-    if (irfun->builtin)
-        return true;
-
-    /*
-     * If there is no definition and the thing is eraseable, we can ignore
-     * outputting the function to begin with.
-     */
-    if (global->flags & IR_FLAG_ERASABLE && irfun->code_function_def < 0) {
-        return true;
-    }
-
-    if (irfun->code_function_def < 0) {
-        irerror(irfun->context, "`%s`: IR global wasn't generated, failed to access function-def", irfun->name);
-        return false;
-    }
-    fundef = &ir->code->functions[irfun->code_function_def];
-
-    fundef->entry = vec_size(ir->code->statements);
-    if (!gen_function_locals(ir, global)) {
-        irerror(irfun->context, "Failed to generate locals for function %s", irfun->name);
-        return false;
-    }
-    if (!gen_function_extparam_copy(ir->code, irfun)) {
-        irerror(irfun->context, "Failed to generate extparam-copy code for function %s", irfun->name);
-        return false;
-    }
-    if (irfun->max_varargs && !gen_function_varargs_copy(ir->code, irfun)) {
-        irerror(irfun->context, "Failed to generate vararg-copy code for function %s", irfun->name);
-        return false;
-    }
-    if (!gen_function_code(ir->code, irfun)) {
-        irerror(irfun->context, "Failed to generate code for function %s", irfun->name);
-        return false;
-    }
-    return true;
-}
-
-static void gen_vector_defs(code_t *code, prog_section_def_t def, const char *name)
-{
-    char  *component;
-    size_t len, i;
-
-    if (!name || name[0] == '#' || OPTS_FLAG(SINGLE_VECTOR_DEFS))
-        return;
-
-    def.type = TYPE_FLOAT;
-
-    len = strlen(name);
-
-    component = (char*)mem_a(len+3);
-    memcpy(component, name, len);
-    len += 2;
-    component[len-0] = 0;
-    component[len-2] = '_';
-
-    component[len-1] = 'x';
-
-    for (i = 0; i < 3; ++i) {
-        def.name = code_genstring(code, component);
-        vec_push(code->defs, def);
-        def.offset++;
-        component[len-1]++;
-    }
-
-    mem_d(component);
-}
-
-static void gen_vector_fields(code_t *code, prog_section_field_t fld, const char *name)
-{
-    char  *component;
-    size_t len, i;
-
-    if (!name || OPTS_FLAG(SINGLE_VECTOR_DEFS))
-        return;
-
-    fld.type = TYPE_FLOAT;
-
-    len = strlen(name);
-
-    component = (char*)mem_a(len+3);
-    memcpy(component, name, len);
-    len += 2;
-    component[len-0] = 0;
-    component[len-2] = '_';
-
-    component[len-1] = 'x';
-
-    for (i = 0; i < 3; ++i) {
-        fld.name = code_genstring(code, component);
-        vec_push(code->fields, fld);
-        fld.offset++;
-        component[len-1]++;
-    }
-
-    mem_d(component);
-}
-
-static bool ir_builder_gen_global(ir_builder *self, ir_value *global, bool islocal)
-{
-    size_t             i;
-    int32_t           *iptr;
-    prog_section_def_t def;
-    bool               pushdef = opts.optimizeoff;
-
-    /* we don't generate split-vectors */
-    if (global->vtype == TYPE_VECTOR && (global->flags & IR_FLAG_SPLIT_VECTOR))
-        return true;
-
-    def.type   = global->vtype;
-    def.offset = vec_size(self->code->globals);
-    def.name   = 0;
-    if (OPTS_OPTION_BOOL(OPTION_G) || !islocal)
-    {
-        pushdef = true;
-
-        /*
-         * if we're eraseable and the function isn't referenced ignore outputting
-         * the function.
-         */
-        if (global->flags & IR_FLAG_ERASABLE && vec_size(global->reads) == 0) {
-            return true;
-        }
-
-        if (OPTS_OPTIMIZATION(OPTIM_STRIP_CONSTANT_NAMES) &&
-            !(global->flags & IR_FLAG_INCLUDE_DEF) &&
-            (global->name[0] == '#' || global->cvq == CV_CONST))
-        {
-            pushdef = false;
-        }
-
-        if (pushdef) {
-            if (global->name[0] == '#') {
-                if (!self->str_immediate)
-                    self->str_immediate = code_genstring(self->code, "IMMEDIATE");
-                def.name = global->code.name = self->str_immediate;
-            }
-            else
-                def.name = global->code.name = code_genstring(self->code, global->name);
-        }
-        else
-            def.name   = 0;
-        if (islocal) {
-            def.offset = ir_value_code_addr(global);
-            vec_push(self->code->defs, def);
-            if (global->vtype == TYPE_VECTOR)
-                gen_vector_defs(self->code, def, global->name);
-            else if (global->vtype == TYPE_FIELD && global->fieldtype == TYPE_VECTOR)
-                gen_vector_defs(self->code, def, global->name);
-            return true;
-        }
-    }
-    if (islocal)
-        return true;
-
-    switch (global->vtype)
-    {
-    case TYPE_VOID:
-        if (!strcmp(global->name, "end_sys_globals")) {
-            /* TODO: remember this point... all the defs before this one
-             * should be checksummed and added to progdefs.h when we generate it.
-             */
-        }
-        else if (!strcmp(global->name, "end_sys_fields")) {
-            /* TODO: same as above but for entity-fields rather than globsl
-             */
-        }
-        else if(irwarning(global->context, WARN_VOID_VARIABLES, "unrecognized variable of type void `%s`",
-                          global->name))
-        {
-            /* Not bailing out */
-            /* return false; */
-        }
-        /* I'd argue setting it to 0 is sufficient, but maybe some depend on knowing how far
-         * the system fields actually go? Though the engine knows this anyway...
-         * Maybe this could be an -foption
-         * fteqcc creates data for end_sys_* - of size 1, so let's do the same
-         */
-        ir_value_code_setaddr(global, vec_size(self->code->globals));
-        vec_push(self->code->globals, 0);
-        /* Add the def */
-        if (pushdef) vec_push(self->code->defs, def);
-        return true;
-    case TYPE_POINTER:
-        if (pushdef) vec_push(self->code->defs, def);
-        return gen_global_pointer(self->code, global);
-    case TYPE_FIELD:
-        if (pushdef) {
-            vec_push(self->code->defs, def);
-            if (global->fieldtype == TYPE_VECTOR)
-                gen_vector_defs(self->code, def, global->name);
-        }
-        return gen_global_field(self->code, global);
-    case TYPE_ENTITY:
-        /* fall through */
-    case TYPE_FLOAT:
-    {
-        ir_value_code_setaddr(global, vec_size(self->code->globals));
-        if (global->hasvalue) {
-            if (global->cvq == CV_CONST && !vec_size(global->reads))
-                return true;
-            iptr = (int32_t*)&global->constval.ivec[0];
-            vec_push(self->code->globals, *iptr);
-        } else {
-            vec_push(self->code->globals, 0);
-        }
-        if (!islocal && global->cvq != CV_CONST)
-            def.type |= DEF_SAVEGLOBAL;
-        if (pushdef) vec_push(self->code->defs, def);
-
-        return global->code.globaladdr >= 0;
-    }
-    case TYPE_STRING:
-    {
-        ir_value_code_setaddr(global, vec_size(self->code->globals));
-        if (global->hasvalue) {
-            uint32_t load;
-            if (global->cvq == CV_CONST && !vec_size(global->reads))
-                return true;
-            load = code_genstring(self->code, global->constval.vstring);
-            vec_push(self->code->globals, load);
-        } else {
-            vec_push(self->code->globals, 0);
-        }
-        if (!islocal && global->cvq != CV_CONST)
-            def.type |= DEF_SAVEGLOBAL;
-        if (pushdef) vec_push(self->code->defs, def);
-        return global->code.globaladdr >= 0;
-    }
-    case TYPE_VECTOR:
-    {
-        size_t d;
-        ir_value_code_setaddr(global, vec_size(self->code->globals));
-        if (global->hasvalue) {
-            iptr = (int32_t*)&global->constval.ivec[0];
-            vec_push(self->code->globals, iptr[0]);
-            if (global->code.globaladdr < 0)
-                return false;
-            for (d = 1; d < type_sizeof_[global->vtype]; ++d) {
-                vec_push(self->code->globals, iptr[d]);
-            }
-        } else {
-            vec_push(self->code->globals, 0);
-            if (global->code.globaladdr < 0)
-                return false;
-            for (d = 1; d < type_sizeof_[global->vtype]; ++d) {
-                vec_push(self->code->globals, 0);
-            }
-        }
-        if (!islocal && global->cvq != CV_CONST)
-            def.type |= DEF_SAVEGLOBAL;
-
-        if (pushdef) {
-            vec_push(self->code->defs, def);
-            def.type &= ~DEF_SAVEGLOBAL;
-            gen_vector_defs(self->code, def, global->name);
-        }
-        return global->code.globaladdr >= 0;
-    }
-    case TYPE_FUNCTION:
-        ir_value_code_setaddr(global, vec_size(self->code->globals));
-        if (!global->hasvalue) {
-            vec_push(self->code->globals, 0);
-            if (global->code.globaladdr < 0)
-                return false;
-        } else {
-            vec_push(self->code->globals, vec_size(self->code->functions));
-            if (!gen_global_function(self, global))
-                return false;
-        }
-        if (!islocal && global->cvq != CV_CONST)
-            def.type |= DEF_SAVEGLOBAL;
-        if (pushdef) vec_push(self->code->defs, def);
-        return true;
-    case TYPE_VARIANT:
-        /* assume biggest type */
-            ir_value_code_setaddr(global, vec_size(self->code->globals));
-            vec_push(self->code->globals, 0);
-            for (i = 1; i < type_sizeof_[TYPE_VARIANT]; ++i)
-                vec_push(self->code->globals, 0);
-            return true;
-    default:
-        /* refuse to create 'void' type or any other fancy business. */
-        irerror(global->context, "Invalid type for global variable `%s`: %s",
-                global->name, type_name[global->vtype]);
-        return false;
-    }
-}
-
-static GMQCC_INLINE void ir_builder_prepare_field(code_t *code, ir_value *field)
-{
-    field->code.fieldaddr = code_alloc_field(code, type_sizeof_[field->fieldtype]);
-}
-
-static bool ir_builder_gen_field(ir_builder *self, ir_value *field)
-{
-    prog_section_def_t def;
-    prog_section_field_t fld;
-
-    (void)self;
-
-    def.type   = (uint16_t)field->vtype;
-    def.offset = (uint16_t)vec_size(self->code->globals);
-
-    /* create a global named the same as the field */
-    if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_GMQCC) {
-        /* in our standard, the global gets a dot prefix */
-        size_t len = strlen(field->name);
-        char name[1024];
-
-        /* we really don't want to have to allocate this, and 1024
-         * bytes is more than enough for a variable/field name
-         */
-        if (len+2 >= sizeof(name)) {
-            irerror(field->context, "invalid field name size: %u", (unsigned int)len);
-            return false;
-        }
-
-        name[0] = '.';
-        memcpy(name+1, field->name, len); /* no strncpy - we used strlen above */
-        name[len+1] = 0;
-
-        def.name = code_genstring(self->code, name);
-        fld.name = def.name + 1; /* we reuse that string table entry */
-    } else {
-        /* in plain QC, there cannot be a global with the same name,
-         * and so we also name the global the same.
-         * FIXME: fteqcc should create a global as well
-         * check if it actually uses the same name. Probably does
-         */
-        def.name = code_genstring(self->code, field->name);
-        fld.name = def.name;
-    }
-
-    field->code.name = def.name;
-
-    vec_push(self->code->defs, def);
-
-    fld.type = field->fieldtype;
-
-    if (fld.type == TYPE_VOID) {
-        irerror(field->context, "field is missing a type: %s - don't know its size", field->name);
-        return false;
-    }
-
-    fld.offset = field->code.fieldaddr;
-
-    vec_push(self->code->fields, fld);
-
-    ir_value_code_setaddr(field, vec_size(self->code->globals));
-    vec_push(self->code->globals, fld.offset);
-    if (fld.type == TYPE_VECTOR) {
-        vec_push(self->code->globals, fld.offset+1);
-        vec_push(self->code->globals, fld.offset+2);
-    }
-
-    if (field->fieldtype == TYPE_VECTOR) {
-        gen_vector_defs  (self->code, def, field->name);
-        gen_vector_fields(self->code, fld, field->name);
-    }
-
-    return field->code.globaladdr >= 0;
-}
-
-static void ir_builder_collect_reusables(ir_builder *builder) {
-    size_t i;
-    ir_value **reusables = NULL;
-    for (i = 0; i < vec_size(builder->globals); ++i) {
-        ir_value *value = builder->globals[i];
-        if (value->vtype != TYPE_FLOAT || !value->hasvalue)
-            continue;
-        if (value->cvq == CV_CONST || (value->name && value->name[0] == '#')) {
-            vec_push(reusables, value);
-        }
-    }
-    builder->const_floats = reusables;
-}
-
-static void ir_builder_split_vector(ir_builder *self, ir_value *vec) {
-    size_t i, count;
-    ir_value* found[3] = { NULL, NULL, NULL };
-
-    /* must not be written to */
-    if (vec_size(vec->writes))
-        return;
-    /* must not be trying to access individual members */
-    if (vec->members[0] || vec->members[1] || vec->members[2])
-        return;
-    /* should be actually used otherwise it won't be generated anyway */
-    count = vec_size(vec->reads);
-    if (!count)
-        return;
-
-    /* may only be used directly as function parameters, so if we find some other instruction cancel */
-    for (i = 0; i != count; ++i) {
-        /* we only split vectors if they're used directly as parameter to a call only! */
-        ir_instr *user = vec->reads[i];
-        if ((user->opcode < INSTR_CALL0 || user->opcode > INSTR_CALL8) && user->opcode != VINSTR_NRCALL)
-            return;
-    }
-
-    vec->flags |= IR_FLAG_SPLIT_VECTOR;
-
-    /* find existing floats making up the split */
-    count = vec_size(self->const_floats);
-    for (i = 0; i != count; ++i) {
-        ir_value *c = self->const_floats[i];
-        if (!found[0] && c->constval.vfloat == vec->constval.vvec.x)
-            found[0] = c;
-        if (!found[1] && c->constval.vfloat == vec->constval.vvec.y)
-            found[1] = c;
-        if (!found[2] && c->constval.vfloat == vec->constval.vvec.z)
-            found[2] = c;
-        if (found[0] && found[1] && found[2])
-            break;
-    }
-
-    /* generate floats for not yet found components */
-    if (!found[0])
-        found[0] = ir_builder_imm_float(self, vec->constval.vvec.x, true);
-    if (!found[1]) {
-        if (vec->constval.vvec.y == vec->constval.vvec.x)
-            found[1] = found[0];
-        else
-            found[1] = ir_builder_imm_float(self, vec->constval.vvec.y, true);
-    }
-    if (!found[2]) {
-        if (vec->constval.vvec.z == vec->constval.vvec.x)
-            found[2] = found[0];
-        else if (vec->constval.vvec.z == vec->constval.vvec.y)
-            found[2] = found[1];
-        else
-            found[2] = ir_builder_imm_float(self, vec->constval.vvec.z, true);
-    }
-
-    /* the .members array should be safe to use here. */
-    vec->members[0] = found[0];
-    vec->members[1] = found[1];
-    vec->members[2] = found[2];
-
-    /* register the readers for these floats */
-    count = vec_size(vec->reads);
-    for (i = 0; i != count; ++i) {
-        vec_push(found[0]->reads, vec->reads[i]);
-        vec_push(found[1]->reads, vec->reads[i]);
-        vec_push(found[2]->reads, vec->reads[i]);
-    }
-}
-
-static void ir_builder_split_vectors(ir_builder *self) {
-    size_t i, count = vec_size(self->globals);
-    for (i = 0; i != count; ++i) {
-        ir_value *v = self->globals[i];
-        if (v->vtype != TYPE_VECTOR || !v->name || v->name[0] != '#')
-            continue;
-        ir_builder_split_vector(self, self->globals[i]);
-    }
-}
-
-bool ir_builder_generate(ir_builder *self, const char *filename)
-{
-    prog_section_statement_t stmt;
-    size_t i;
-    char  *lnofile = NULL;
-
-    if (OPTS_FLAG(SPLIT_VECTOR_PARAMETERS)) {
-        ir_builder_collect_reusables(self);
-        if (vec_size(self->const_floats) > 0)
-            ir_builder_split_vectors(self);
-    }
-
-    for (i = 0; i < vec_size(self->fields); ++i)
-    {
-        ir_builder_prepare_field(self->code, self->fields[i]);
-    }
-
-    for (i = 0; i < vec_size(self->globals); ++i)
-    {
-        if (!ir_builder_gen_global(self, self->globals[i], false)) {
-            return false;
-        }
-        if (self->globals[i]->vtype == TYPE_FUNCTION) {
-            ir_function *func = self->globals[i]->constval.vfunc;
-            if (func && self->max_locals < func->allocated_locals &&
-                !(func->flags & IR_FLAG_MASK_NO_OVERLAP))
-            {
-                self->max_locals = func->allocated_locals;
-            }
-            if (func && self->max_globaltemps < func->globaltemps)
-                self->max_globaltemps = func->globaltemps;
-        }
-    }
-
-    for (i = 0; i < vec_size(self->fields); ++i)
-    {
-        if (!ir_builder_gen_field(self, self->fields[i])) {
-            return false;
-        }
-    }
-
-    /* generate nil */
-    ir_value_code_setaddr(self->nil, vec_size(self->code->globals));
-    vec_push(self->code->globals, 0);
-    vec_push(self->code->globals, 0);
-    vec_push(self->code->globals, 0);
-
-    /* generate virtual-instruction temps */
-    for (i = 0; i < IR_MAX_VINSTR_TEMPS; ++i) {
-        ir_value_code_setaddr(self->vinstr_temp[i], vec_size(self->code->globals));
-        vec_push(self->code->globals, 0);
-        vec_push(self->code->globals, 0);
-        vec_push(self->code->globals, 0);
-    }
-
-    /* generate global temps */
-    self->first_common_globaltemp = vec_size(self->code->globals);
-    for (i = 0; i < self->max_globaltemps; ++i) {
-        vec_push(self->code->globals, 0);
-    }
-    /* generate common locals */
-    self->first_common_local = vec_size(self->code->globals);
-    for (i = 0; i < self->max_locals; ++i) {
-        vec_push(self->code->globals, 0);
-    }
-
-    /* generate function code */
-    for (i = 0; i < vec_size(self->globals); ++i)
-    {
-        if (self->globals[i]->vtype == TYPE_FUNCTION) {
-            if (!gen_global_function_code(self, self->globals[i])) {
-                return false;
-            }
-        }
-    }
-
-    if (vec_size(self->code->globals) >= 65536) {
-        irerror(vec_last(self->globals)->context, "This progs file would require more globals than the metadata can handle (%u). Bailing out.", (unsigned int)vec_size(self->code->globals));
-        return false;
-    }
-
-    /* DP errors if the last instruction is not an INSTR_DONE. */
-    if (vec_last(self->code->statements).opcode != INSTR_DONE)
-    {
-        lex_ctx_t last;
-
-        stmt.opcode = INSTR_DONE;
-        stmt.o1.u1  = 0;
-        stmt.o2.u1  = 0;
-        stmt.o3.u1  = 0;
-        last.line   = vec_last(self->code->linenums);
-        last.column = vec_last(self->code->columnnums);
-
-        code_push_statement(self->code, &stmt, last);
-    }
-
-    if (OPTS_OPTION_BOOL(OPTION_PP_ONLY))
-        return true;
-
-    if (vec_size(self->code->statements) != vec_size(self->code->linenums)) {
-        con_err("Linecounter wrong: %lu != %lu\n",
-                (unsigned long)vec_size(self->code->statements),
-                (unsigned long)vec_size(self->code->linenums));
-    } else if (OPTS_FLAG(LNO)) {
-        char  *dot;
-        size_t filelen = strlen(filename);
-
-        memcpy(vec_add(lnofile, filelen+1), filename, filelen+1);
-        dot = strrchr(lnofile, '.');
-        if (!dot) {
-            vec_pop(lnofile);
-        } else {
-            vec_shrinkto(lnofile, dot - lnofile);
-        }
-        memcpy(vec_add(lnofile, 5), ".lno", 5);
-    }
-
-    if (!code_write(self->code, filename, lnofile)) {
-        vec_free(lnofile);
-        return false;
-    }
-
-    vec_free(lnofile);
-    return true;
-}
-
-/***********************************************************************
- *IR DEBUG Dump functions...
- */
-
-#define IND_BUFSZ 1024
-
-static const char *qc_opname(int op)
-{
-    if (op < 0) return "<INVALID>";
-    if (op < VINSTR_END)
-        return util_instr_str[op];
-    switch (op) {
-        case VINSTR_END:       return "END";
-        case VINSTR_PHI:       return "PHI";
-        case VINSTR_JUMP:      return "JUMP";
-        case VINSTR_COND:      return "COND";
-        case VINSTR_BITXOR:    return "BITXOR";
-        case VINSTR_BITAND_V:  return "BITAND_V";
-        case VINSTR_BITOR_V:   return "BITOR_V";
-        case VINSTR_BITXOR_V:  return "BITXOR_V";
-        case VINSTR_BITAND_VF: return "BITAND_VF";
-        case VINSTR_BITOR_VF:  return "BITOR_VF";
-        case VINSTR_BITXOR_VF: return "BITXOR_VF";
-        case VINSTR_CROSS:     return "CROSS";
-        case VINSTR_NEG_F:     return "NEG_F";
-        case VINSTR_NEG_V:     return "NEG_V";
-        default:               return "<UNK>";
-    }
-}
-
-void ir_builder_dump(ir_builder *b, int (*oprintf)(const char*, ...))
-{
-    size_t i;
-    char indent[IND_BUFSZ];
-    indent[0] = '\t';
-    indent[1] = 0;
-
-    oprintf("module %s\n", b->name);
-    for (i = 0; i < vec_size(b->globals); ++i)
-    {
-        oprintf("global ");
-        if (b->globals[i]->hasvalue)
-            oprintf("%s = ", b->globals[i]->name);
-        ir_value_dump(b->globals[i], oprintf);
-        oprintf("\n");
-    }
-    for (i = 0; i < vec_size(b->functions); ++i)
-        ir_function_dump(b->functions[i], indent, oprintf);
-    oprintf("endmodule %s\n", b->name);
-}
-
-static const char *storenames[] = {
-    "[global]", "[local]", "[param]", "[value]", "[return]"
-};
-
-void ir_function_dump(ir_function *f, char *ind,
-                      int (*oprintf)(const char*, ...))
-{
-    size_t i;
-    if (f->builtin != 0) {
-        oprintf("%sfunction %s = builtin %i\n", ind, f->name, -f->builtin);
-        return;
-    }
-    oprintf("%sfunction %s\n", ind, f->name);
-    util_strncat(ind, "\t", IND_BUFSZ-1);
-    if (vec_size(f->locals))
-    {
-        oprintf("%s%i locals:\n", ind, (int)vec_size(f->locals));
-        for (i = 0; i < vec_size(f->locals); ++i) {
-            oprintf("%s\t", ind);
-            ir_value_dump(f->locals[i], oprintf);
-            oprintf("\n");
-        }
-    }
-    oprintf("%sliferanges:\n", ind);
-    for (i = 0; i < vec_size(f->locals); ++i) {
-        const char *attr = "";
-        size_t l, m;
-        ir_value *v = f->locals[i];
-        if (v->unique_life && v->locked)
-            attr = "unique,locked ";
-        else if (v->unique_life)
-            attr = "unique ";
-        else if (v->locked)
-            attr = "locked ";
-        oprintf("%s\t%s: %s %s %s%s@%i ", ind, v->name, type_name[v->vtype],
-                storenames[v->store],
-                attr, (v->callparam ? "callparam " : ""),
-                (int)v->code.local);
-        if (!v->life)
-            oprintf("[null]");
-        for (l = 0; l < vec_size(v->life); ++l) {
-            oprintf("[%i,%i] ", v->life[l].start, v->life[l].end);
-        }
-        oprintf("\n");
-        for (m = 0; m < 3; ++m) {
-            ir_value *vm = v->members[m];
-            if (!vm)
-                continue;
-            oprintf("%s\t%s: @%i ", ind, vm->name, (int)vm->code.local);
-            for (l = 0; l < vec_size(vm->life); ++l) {
-                oprintf("[%i,%i] ", vm->life[l].start, vm->life[l].end);
-            }
-            oprintf("\n");
-        }
-    }
-    for (i = 0; i < vec_size(f->values); ++i) {
-        const char *attr = "";
-        size_t l, m;
-        ir_value *v = f->values[i];
-        if (v->unique_life && v->locked)
-            attr = "unique,locked ";
-        else if (v->unique_life)
-            attr = "unique ";
-        else if (v->locked)
-            attr = "locked ";
-        oprintf("%s\t%s: %s %s %s%s@%i ", ind, v->name, type_name[v->vtype],
-                storenames[v->store],
-                attr, (v->callparam ? "callparam " : ""),
-                (int)v->code.local);
-        if (!v->life)
-            oprintf("[null]");
-        for (l = 0; l < vec_size(v->life); ++l) {
-            oprintf("[%i,%i] ", v->life[l].start, v->life[l].end);
-        }
-        oprintf("\n");
-        for (m = 0; m < 3; ++m) {
-            ir_value *vm = v->members[m];
-            if (!vm)
-                continue;
-            if (vm->unique_life && vm->locked)
-                attr = "unique,locked ";
-            else if (vm->unique_life)
-                attr = "unique ";
-            else if (vm->locked)
-                attr = "locked ";
-            oprintf("%s\t%s: %s@%i ", ind, vm->name, attr, (int)vm->code.local);
-            for (l = 0; l < vec_size(vm->life); ++l) {
-                oprintf("[%i,%i] ", vm->life[l].start, vm->life[l].end);
-            }
-            oprintf("\n");
-        }
-    }
-    if (vec_size(f->blocks))
-    {
-        oprintf("%slife passes: %i\n", ind, (int)f->run_id);
-        for (i = 0; i < vec_size(f->blocks); ++i) {
-            ir_block_dump(f->blocks[i], ind, oprintf);
-        }
-
-    }
-    ind[strlen(ind)-1] = 0;
-    oprintf("%sendfunction %s\n", ind, f->name);
-}
-
-void ir_block_dump(ir_block* b, char *ind,
-                   int (*oprintf)(const char*, ...))
-{
-    size_t i;
-    oprintf("%s:%s\n", ind, b->label);
-    util_strncat(ind, "\t", IND_BUFSZ-1);
-
-    if (b->instr && b->instr[0])
-        oprintf("%s (%i) [entry]\n", ind, (int)(b->instr[0]->eid-1));
-    for (i = 0; i < vec_size(b->instr); ++i)
-        ir_instr_dump(b->instr[i], ind, oprintf);
-    ind[strlen(ind)-1] = 0;
-}
-
-static void dump_phi(ir_instr *in, int (*oprintf)(const char*, ...))
-{
-    size_t i;
-    oprintf("%s <- phi ", in->_ops[0]->name);
-    for (i = 0; i < vec_size(in->phi); ++i)
-    {
-        oprintf("([%s] : %s) ", in->phi[i].from->label,
-                                in->phi[i].value->name);
-    }
-    oprintf("\n");
-}
-
-void ir_instr_dump(ir_instr *in, char *ind,
-                       int (*oprintf)(const char*, ...))
-{
-    size_t i;
-    const char *comma = NULL;
-
-    oprintf("%s (%i) ", ind, (int)in->eid);
-
-    if (in->opcode == VINSTR_PHI) {
-        dump_phi(in, oprintf);
-        return;
-    }
-
-    util_strncat(ind, "\t", IND_BUFSZ-1);
-
-    if (in->_ops[0] && (in->_ops[1] || in->_ops[2])) {
-        ir_value_dump(in->_ops[0], oprintf);
-        if (in->_ops[1] || in->_ops[2])
-            oprintf(" <- ");
-    }
-    if (in->opcode == INSTR_CALL0 || in->opcode == VINSTR_NRCALL) {
-        oprintf("CALL%i\t", vec_size(in->params));
-    } else
-        oprintf("%s\t", qc_opname(in->opcode));
-
-    if (in->_ops[0] && !(in->_ops[1] || in->_ops[2])) {
-        ir_value_dump(in->_ops[0], oprintf);
-        comma = ",\t";
-    }
-    else
-    {
-        for (i = 1; i != 3; ++i) {
-            if (in->_ops[i]) {
-                if (comma)
-                    oprintf(comma);
-                ir_value_dump(in->_ops[i], oprintf);
-                comma = ",\t";
-            }
-        }
-    }
-    if (in->bops[0]) {
-        if (comma)
-            oprintf(comma);
-        oprintf("[%s]", in->bops[0]->label);
-        comma = ",\t";
-    }
-    if (in->bops[1])
-        oprintf("%s[%s]", comma, in->bops[1]->label);
-    if (vec_size(in->params)) {
-        oprintf("\tparams: ");
-        for (i = 0; i != vec_size(in->params); ++i) {
-            oprintf("%s, ", in->params[i]->name);
-        }
-    }
-    oprintf("\n");
-    ind[strlen(ind)-1] = 0;
-}
-
-static void ir_value_dump_string(const char *str, int (*oprintf)(const char*, ...))
-{
-    oprintf("\"");
-    for (; *str; ++str) {
-        switch (*str) {
-            case '\n': oprintf("\\n"); break;
-            case '\r': oprintf("\\r"); break;
-            case '\t': oprintf("\\t"); break;
-            case '\v': oprintf("\\v"); break;
-            case '\f': oprintf("\\f"); break;
-            case '\b': oprintf("\\b"); break;
-            case '\a': oprintf("\\a"); break;
-            case '\\': oprintf("\\\\"); break;
-            case '"': oprintf("\\\""); break;
-            default: oprintf("%c", *str); break;
-        }
-    }
-    oprintf("\"");
-}
-
-void ir_value_dump(ir_value* v, int (*oprintf)(const char*, ...))
-{
-    if (v->hasvalue) {
-        switch (v->vtype) {
-            default:
-            case TYPE_VOID:
-                oprintf("(void)");
-                break;
-            case TYPE_FUNCTION:
-                oprintf("fn:%s", v->name);
-                break;
-            case TYPE_FLOAT:
-                oprintf("%g", v->constval.vfloat);
-                break;
-            case TYPE_VECTOR:
-                oprintf("'%g %g %g'",
-                        v->constval.vvec.x,
-                        v->constval.vvec.y,
-                        v->constval.vvec.z);
-                break;
-            case TYPE_ENTITY:
-                oprintf("(entity)");
-                break;
-            case TYPE_STRING:
-                ir_value_dump_string(v->constval.vstring, oprintf);
-                break;
-#if 0
-            case TYPE_INTEGER:
-                oprintf("%i", v->constval.vint);
-                break;
-#endif
-            case TYPE_POINTER:
-                oprintf("&%s",
-                    v->constval.vpointer->name);
-                break;
-        }
-    } else {
-        oprintf("%s", v->name);
-    }
-}
-
-void ir_value_dump_life(const ir_value *self, int (*oprintf)(const char*,...))
-{
-    size_t i;
-    oprintf("Life of %12s:", self->name);
-    for (i = 0; i < vec_size(self->life); ++i)
-    {
-        oprintf(" + [%i, %i]\n", self->life[i].start, self->life[i].end);
-    }
-}
diff --git a/ir.cpp b/ir.cpp
new file mode 100644 (file)
index 0000000..7ac8477
--- /dev/null
+++ b/ir.cpp
@@ -0,0 +1,4101 @@
+#include <stdlib.h>
+#include <string.h>
+
+#include "gmqcc.h"
+#include "ir.h"
+
+/***********************************************************************
+ * Type sizes used at multiple points in the IR codegen
+ */
+
+const char *type_name[TYPE_COUNT] = {
+    "void",
+    "string",
+    "float",
+    "vector",
+    "entity",
+    "field",
+    "function",
+    "pointer",
+    "integer",
+    "variant",
+    "struct",
+    "union",
+    "array",
+
+    "nil",
+    "<no-expression>"
+};
+
+static size_t type_sizeof_[TYPE_COUNT] = {
+    1, /* TYPE_VOID     */
+    1, /* TYPE_STRING   */
+    1, /* TYPE_FLOAT    */
+    3, /* TYPE_VECTOR   */
+    1, /* TYPE_ENTITY   */
+    1, /* TYPE_FIELD    */
+    1, /* TYPE_FUNCTION */
+    1, /* TYPE_POINTER  */
+    1, /* TYPE_INTEGER  */
+    3, /* TYPE_VARIANT  */
+    0, /* TYPE_STRUCT   */
+    0, /* TYPE_UNION    */
+    0, /* TYPE_ARRAY    */
+    0, /* TYPE_NIL      */
+    0, /* TYPE_NOESPR   */
+};
+
+const uint16_t type_store_instr[TYPE_COUNT] = {
+    INSTR_STORE_F, /* should use I when having integer support */
+    INSTR_STORE_S,
+    INSTR_STORE_F,
+    INSTR_STORE_V,
+    INSTR_STORE_ENT,
+    INSTR_STORE_FLD,
+    INSTR_STORE_FNC,
+    INSTR_STORE_ENT, /* should use I */
+#if 0
+    INSTR_STORE_I, /* integer type */
+#else
+    INSTR_STORE_F,
+#endif
+
+    INSTR_STORE_V, /* variant, should never be accessed */
+
+    VINSTR_END, /* struct */
+    VINSTR_END, /* union  */
+    VINSTR_END, /* array  */
+    VINSTR_END, /* nil    */
+    VINSTR_END, /* noexpr */
+};
+
+const uint16_t field_store_instr[TYPE_COUNT] = {
+    INSTR_STORE_FLD,
+    INSTR_STORE_FLD,
+    INSTR_STORE_FLD,
+    INSTR_STORE_V,
+    INSTR_STORE_FLD,
+    INSTR_STORE_FLD,
+    INSTR_STORE_FLD,
+    INSTR_STORE_FLD,
+#if 0
+    INSTR_STORE_FLD, /* integer type */
+#else
+    INSTR_STORE_FLD,
+#endif
+
+    INSTR_STORE_V, /* variant, should never be accessed */
+
+    VINSTR_END, /* struct */
+    VINSTR_END, /* union  */
+    VINSTR_END, /* array  */
+    VINSTR_END, /* nil    */
+    VINSTR_END, /* noexpr */
+};
+
+const uint16_t type_storep_instr[TYPE_COUNT] = {
+    INSTR_STOREP_F, /* should use I when having integer support */
+    INSTR_STOREP_S,
+    INSTR_STOREP_F,
+    INSTR_STOREP_V,
+    INSTR_STOREP_ENT,
+    INSTR_STOREP_FLD,
+    INSTR_STOREP_FNC,
+    INSTR_STOREP_ENT, /* should use I */
+#if 0
+    INSTR_STOREP_ENT, /* integer type */
+#else
+    INSTR_STOREP_F,
+#endif
+
+    INSTR_STOREP_V, /* variant, should never be accessed */
+
+    VINSTR_END, /* struct */
+    VINSTR_END, /* union  */
+    VINSTR_END, /* array  */
+    VINSTR_END, /* nil    */
+    VINSTR_END, /* noexpr */
+};
+
+const uint16_t type_eq_instr[TYPE_COUNT] = {
+    INSTR_EQ_F, /* should use I when having integer support */
+    INSTR_EQ_S,
+    INSTR_EQ_F,
+    INSTR_EQ_V,
+    INSTR_EQ_E,
+    INSTR_EQ_E, /* FLD has no comparison */
+    INSTR_EQ_FNC,
+    INSTR_EQ_E, /* should use I */
+#if 0
+    INSTR_EQ_I,
+#else
+    INSTR_EQ_F,
+#endif
+
+    INSTR_EQ_V, /* variant, should never be accessed */
+
+    VINSTR_END, /* struct */
+    VINSTR_END, /* union  */
+    VINSTR_END, /* array  */
+    VINSTR_END, /* nil    */
+    VINSTR_END, /* noexpr */
+};
+
+const uint16_t type_ne_instr[TYPE_COUNT] = {
+    INSTR_NE_F, /* should use I when having integer support */
+    INSTR_NE_S,
+    INSTR_NE_F,
+    INSTR_NE_V,
+    INSTR_NE_E,
+    INSTR_NE_E, /* FLD has no comparison */
+    INSTR_NE_FNC,
+    INSTR_NE_E, /* should use I */
+#if 0
+    INSTR_NE_I,
+#else
+    INSTR_NE_F,
+#endif
+
+    INSTR_NE_V, /* variant, should never be accessed */
+
+    VINSTR_END, /* struct */
+    VINSTR_END, /* union  */
+    VINSTR_END, /* array  */
+    VINSTR_END, /* nil    */
+    VINSTR_END, /* noexpr */
+};
+
+const uint16_t type_not_instr[TYPE_COUNT] = {
+    INSTR_NOT_F, /* should use I when having integer support */
+    VINSTR_END,  /* not to be used, depends on string related -f flags */
+    INSTR_NOT_F,
+    INSTR_NOT_V,
+    INSTR_NOT_ENT,
+    INSTR_NOT_ENT,
+    INSTR_NOT_FNC,
+    INSTR_NOT_ENT, /* should use I */
+#if 0
+    INSTR_NOT_I, /* integer type */
+#else
+    INSTR_NOT_F,
+#endif
+
+    INSTR_NOT_V, /* variant, should never be accessed */
+
+    VINSTR_END, /* struct */
+    VINSTR_END, /* union  */
+    VINSTR_END, /* array  */
+    VINSTR_END, /* nil    */
+    VINSTR_END, /* noexpr */
+};
+
+/* protos */
+static void            ir_function_dump(ir_function*, char *ind, int (*oprintf)(const char*,...));
+
+static ir_value*       ir_block_create_general_instr(ir_block *self, lex_ctx_t, const char *label,
+                                                     int op, ir_value *a, ir_value *b, qc_type outype);
+static bool GMQCC_WARN ir_block_create_store(ir_block*, lex_ctx_t, ir_value *target, ir_value *what);
+static void            ir_block_dump(ir_block*, char *ind, int (*oprintf)(const char*,...));
+
+static bool            ir_instr_op(ir_instr*, int op, ir_value *value, bool writing);
+static void            ir_instr_dump(ir_instr* in, char *ind, int (*oprintf)(const char*,...));
+/* error functions */
+
+static void irerror(lex_ctx_t ctx, const char *msg, ...)
+{
+    va_list ap;
+    va_start(ap, msg);
+    con_cvprintmsg(ctx, LVL_ERROR, "internal error", msg, ap);
+    va_end(ap);
+}
+
+static bool GMQCC_WARN irwarning(lex_ctx_t ctx, int warntype, const char *fmt, ...)
+{
+    bool    r;
+    va_list ap;
+    va_start(ap, fmt);
+    r = vcompile_warning(ctx, warntype, fmt, ap);
+    va_end(ap);
+    return r;
+}
+
+/***********************************************************************
+ * Vector utility functions
+ */
+
+static bool GMQCC_WARN vec_ir_value_find(std::vector<ir_value *> &vec, const ir_value *what, size_t *idx)
+{
+    for (auto &it : vec) {
+        if (it != what)
+            continue;
+        if (idx)
+            *idx = &it - &vec[0];
+        return true;
+    }
+    return false;
+}
+
+static bool GMQCC_WARN vec_ir_block_find(ir_block **vec, ir_block *what, size_t *idx)
+{
+    size_t i;
+    size_t len = vec_size(vec);
+    for (i = 0; i < len; ++i) {
+        if (vec[i] == what) {
+            if (idx) *idx = i;
+            return true;
+        }
+    }
+    return false;
+}
+
+static bool GMQCC_WARN vec_ir_instr_find(std::vector<ir_instr *> &vec, ir_instr *what, size_t *idx)
+{
+    for (auto &it : vec) {
+        if (it != what)
+            continue;
+        if (idx)
+            *idx = &it - &vec[0];
+        return true;
+    }
+    return false;
+}
+
+/***********************************************************************
+ * IR Builder
+ */
+
+static void ir_block_delete_quick(ir_block* self);
+static void ir_instr_delete_quick(ir_instr *self);
+static void ir_function_delete_quick(ir_function *self);
+
+ir_builder::ir_builder(const std::string& modulename)
+: m_name(modulename),
+  m_code(new code_t)
+{
+    m_htglobals   = util_htnew(IR_HT_SIZE);
+    m_htfields    = util_htnew(IR_HT_SIZE);
+    m_htfunctions = util_htnew(IR_HT_SIZE);
+
+    m_nil = new ir_value("nil", store_value, TYPE_NIL);
+    m_nil->m_cvq = CV_CONST;
+
+    for (size_t i = 0; i != IR_MAX_VINSTR_TEMPS; ++i) {
+        /* we write to them, but they're not supposed to be used outside the IR, so
+         * let's not allow the generation of ir_instrs which use these.
+         * So it's a constant noexpr.
+         */
+        m_vinstr_temp[i] = new ir_value("vinstr_temp", store_value, TYPE_NOEXPR);
+        m_vinstr_temp[i]->m_cvq = CV_CONST;
+    }
+}
+
+ir_builder::~ir_builder()
+{
+    util_htdel(m_htglobals);
+    util_htdel(m_htfields);
+    util_htdel(m_htfunctions);
+    for (auto& f : m_functions)
+        ir_function_delete_quick(f.release());
+    m_functions.clear(); // delete them now before deleting the rest:
+
+    delete m_nil;
+
+    for (size_t i = 0; i != IR_MAX_VINSTR_TEMPS; ++i) {
+        delete m_vinstr_temp[i];
+    }
+
+    m_extparams.clear();
+    m_extparam_protos.clear();
+}
+
+ir_function* ir_builder::createFunction(const std::string& name, qc_type outtype)
+{
+    ir_function *fn = (ir_function*)util_htget(m_htfunctions, name.c_str());
+    if (fn)
+        return nullptr;
+
+    fn = new ir_function(this, outtype);
+    fn->m_name = name;
+    m_functions.emplace_back(fn);
+    util_htset(m_htfunctions, name.c_str(), fn);
+
+    fn->m_value = createGlobal(fn->m_name, TYPE_FUNCTION);
+    if (!fn->m_value) {
+        delete fn;
+        return nullptr;
+    }
+
+    fn->m_value->m_hasvalue = true;
+    fn->m_value->m_outtype = outtype;
+    fn->m_value->m_constval.vfunc = fn;
+    fn->m_value->m_context = fn->m_context;
+
+    return fn;
+}
+
+ir_value* ir_builder::createGlobal(const std::string& name, qc_type vtype)
+{
+    ir_value *ve;
+
+    if (name[0] != '#')
+    {
+        ve = (ir_value*)util_htget(m_htglobals, name.c_str());
+        if (ve) {
+            return nullptr;
+        }
+    }
+
+    ve = new ir_value(std::string(name), store_global, vtype);
+    m_globals.emplace_back(ve);
+    util_htset(m_htglobals, name.c_str(), ve);
+    return ve;
+}
+
+ir_value* ir_builder::get_va_count()
+{
+    if (m_reserved_va_count)
+        return m_reserved_va_count;
+    return (m_reserved_va_count = createGlobal("reserved:va_count", TYPE_FLOAT));
+}
+
+ir_value* ir_builder::createField(const std::string& name, qc_type vtype)
+{
+    ir_value *ve = (ir_value*)util_htget(m_htfields, name.c_str());
+    if (ve) {
+        return nullptr;
+    }
+
+    ve = new ir_value(std::string(name), store_global, TYPE_FIELD);
+    ve->m_fieldtype = vtype;
+    m_fields.emplace_back(ve);
+    util_htset(m_htfields, name.c_str(), ve);
+    return ve;
+}
+
+/***********************************************************************
+ *IR Function
+ */
+
+static bool ir_function_naive_phi(ir_function*);
+static void ir_function_enumerate(ir_function*);
+static bool ir_function_calculate_liferanges(ir_function*);
+static bool ir_function_allocate_locals(ir_function*);
+
+ir_function::ir_function(ir_builder* owner_, qc_type outtype_)
+: m_owner(owner_),
+  m_name("<@unnamed>"),
+  m_outtype(outtype_)
+{
+    m_context.file = "<@no context>";
+    m_context.line = 0;
+}
+
+ir_function::~ir_function()
+{
+}
+
+static void ir_function_delete_quick(ir_function *self)
+{
+    for (auto& b : self->m_blocks)
+        ir_block_delete_quick(b.release());
+    delete self;
+}
+
+static void ir_function_collect_value(ir_function *self, ir_value *v)
+{
+    self->m_values.emplace_back(v);
+}
+
+ir_block* ir_function_create_block(lex_ctx_t ctx, ir_function *self, const char *label)
+{
+    ir_block* bn = new ir_block(self, label ? std::string(label) : std::string());
+    bn->m_context = ctx;
+    self->m_blocks.emplace_back(bn);
+
+    if ((self->m_flags & IR_FLAG_BLOCK_COVERAGE) && self->m_owner->m_coverage_func)
+        (void)ir_block_create_call(bn, ctx, nullptr, self->m_owner->m_coverage_func, false);
+
+    return bn;
+}
+
+static bool instr_is_operation(uint16_t op)
+{
+    return ( (op >= INSTR_MUL_F  && op <= INSTR_GT) ||
+             (op >= INSTR_LOAD_F && op <= INSTR_LOAD_FNC) ||
+             (op == INSTR_ADDRESS) ||
+             (op >= INSTR_NOT_F  && op <= INSTR_NOT_FNC) ||
+             (op >= INSTR_AND    && op <= INSTR_BITOR) ||
+             (op >= INSTR_CALL0  && op <= INSTR_CALL8) ||
+             (op >= VINSTR_BITAND_V && op <= VINSTR_NEG_V) );
+}
+
+static bool ir_function_pass_peephole(ir_function *self)
+{
+    for (auto& bp : self->m_blocks) {
+        ir_block *block = bp.get();
+        for (size_t i = 0; i < vec_size(block->m_instr); ++i) {
+            ir_instr *inst;
+            inst = block->m_instr[i];
+
+            if (i >= 1 &&
+                (inst->m_opcode >= INSTR_STORE_F &&
+                 inst->m_opcode <= INSTR_STORE_FNC))
+            {
+                ir_instr *store;
+                ir_instr *oper;
+                ir_value *value;
+
+                store = inst;
+
+                oper  = block->m_instr[i-1];
+                if (!instr_is_operation(oper->m_opcode))
+                    continue;
+
+                /* Don't change semantics of MUL_VF in engines where these may not alias. */
+                if (OPTS_FLAG(LEGACY_VECTOR_MATHS)) {
+                    if (oper->m_opcode == INSTR_MUL_VF && oper->_m_ops[2]->m_memberof == oper->_m_ops[1])
+                        continue;
+                    if (oper->m_opcode == INSTR_MUL_FV && oper->_m_ops[1]->m_memberof == oper->_m_ops[2])
+                        continue;
+                }
+
+                value = oper->_m_ops[0];
+
+                /* only do it for SSA values */
+                if (value->m_store != store_value)
+                    continue;
+
+                /* don't optimize out the temp if it's used later again */
+                if (value->m_reads.size() != 1)
+                    continue;
+
+                /* The very next store must use this value */
+                if (value->m_reads[0] != store)
+                    continue;
+
+                /* And of course the store must _read_ from it, so it's in
+                 * OP 1 */
+                if (store->_m_ops[1] != value)
+                    continue;
+
+                ++opts_optimizationcount[OPTIM_PEEPHOLE];
+                (void)!ir_instr_op(oper, 0, store->_m_ops[0], true);
+
+                vec_remove(block->m_instr, i, 1);
+                delete store;
+            }
+            else if (inst->m_opcode == VINSTR_COND)
+            {
+                /* COND on a value resulting from a NOT could
+                 * remove the NOT and swap its operands
+                 */
+                while (true) {
+                    ir_block *tmp;
+                    size_t    inotid;
+                    ir_instr *inot;
+                    ir_value *value;
+                    value = inst->_m_ops[0];
+
+                    if (value->m_store != store_value || value->m_reads.size() != 1 || value->m_reads[0] != inst)
+                        break;
+
+                    inot = value->m_writes[0];
+                    if (inot->_m_ops[0] != value ||
+                        inot->m_opcode < INSTR_NOT_F ||
+                        inot->m_opcode > INSTR_NOT_FNC ||
+                        inot->m_opcode == INSTR_NOT_V || /* can't do these */
+                        inot->m_opcode == INSTR_NOT_S)
+                    {
+                        break;
+                    }
+
+                    /* count */
+                    ++opts_optimizationcount[OPTIM_PEEPHOLE];
+                    /* change operand */
+                    (void)!ir_instr_op(inst, 0, inot->_m_ops[1], false);
+                    /* remove NOT */
+                    tmp = inot->m_owner;
+                    for (inotid = 0; inotid < vec_size(tmp->m_instr); ++inotid) {
+                        if (tmp->m_instr[inotid] == inot)
+                            break;
+                    }
+                    if (inotid >= vec_size(tmp->m_instr)) {
+                        compile_error(inst->m_context, "sanity-check failed: failed to find instruction to optimize out");
+                        return false;
+                    }
+                    vec_remove(tmp->m_instr, inotid, 1);
+                    delete inot;
+                    /* swap ontrue/onfalse */
+                    tmp = inst->m_bops[0];
+                    inst->m_bops[0] = inst->m_bops[1];
+                    inst->m_bops[1] = tmp;
+                }
+                continue;
+            }
+        }
+    }
+
+    return true;
+}
+
+static bool ir_function_pass_tailrecursion(ir_function *self)
+{
+    size_t p;
+
+    for (auto& bp : self->m_blocks) {
+        ir_block *block = bp.get();
+
+        ir_value *funcval;
+        ir_instr *ret, *call, *store = nullptr;
+
+        if (!block->m_final || vec_size(block->m_instr) < 2)
+            continue;
+
+        ret = block->m_instr[vec_size(block->m_instr)-1];
+        if (ret->m_opcode != INSTR_DONE && ret->m_opcode != INSTR_RETURN)
+            continue;
+
+        call = block->m_instr[vec_size(block->m_instr)-2];
+        if (call->m_opcode >= INSTR_STORE_F && call->m_opcode <= INSTR_STORE_FNC) {
+            /* account for the unoptimized
+             * CALL
+             * STORE %return, %tmp
+             * RETURN %tmp
+             * version
+             */
+            if (vec_size(block->m_instr) < 3)
+                continue;
+
+            store = call;
+            call = block->m_instr[vec_size(block->m_instr)-3];
+        }
+
+        if (call->m_opcode < INSTR_CALL0 || call->m_opcode > INSTR_CALL8)
+            continue;
+
+        if (store) {
+            /* optimize out the STORE */
+            if (ret->_m_ops[0]   &&
+                ret->_m_ops[0]   == store->_m_ops[0] &&
+                store->_m_ops[1] == call->_m_ops[0])
+            {
+                ++opts_optimizationcount[OPTIM_PEEPHOLE];
+                call->_m_ops[0] = store->_m_ops[0];
+                vec_remove(block->m_instr, vec_size(block->m_instr) - 2, 1);
+                delete store;
+            }
+            else
+                continue;
+        }
+
+        if (!call->_m_ops[0])
+            continue;
+
+        funcval = call->_m_ops[1];
+        if (!funcval)
+            continue;
+        if (funcval->m_vtype != TYPE_FUNCTION || funcval->m_constval.vfunc != self)
+            continue;
+
+        /* now we have a CALL and a RET, check if it's a tailcall */
+        if (ret->_m_ops[0] && call->_m_ops[0] != ret->_m_ops[0])
+            continue;
+
+        ++opts_optimizationcount[OPTIM_TAIL_RECURSION];
+        vec_shrinkby(block->m_instr, 2);
+
+        block->m_final = false; /* open it back up */
+
+        /* emite parameter-stores */
+        for (p = 0; p < call->m_params.size(); ++p) {
+            /* assert(call->params_count <= self->locals_count); */
+            if (!ir_block_create_store(block, call->m_context, self->m_locals[p].get(), call->m_params[p])) {
+                irerror(call->m_context, "failed to create tailcall store instruction for parameter %i", (int)p);
+                return false;
+            }
+        }
+        if (!ir_block_create_jump(block, call->m_context, self->m_blocks[0].get())) {
+            irerror(call->m_context, "failed to create tailcall jump");
+            return false;
+        }
+
+        delete call;
+        delete ret;
+    }
+
+    return true;
+}
+
+bool ir_function_finalize(ir_function *self)
+{
+    if (self->m_builtin)
+        return true;
+
+    if (OPTS_OPTIMIZATION(OPTIM_PEEPHOLE)) {
+        if (!ir_function_pass_peephole(self)) {
+            irerror(self->m_context, "generic optimization pass broke something in `%s`", self->m_name.c_str());
+            return false;
+        }
+    }
+
+    if (OPTS_OPTIMIZATION(OPTIM_TAIL_RECURSION)) {
+        if (!ir_function_pass_tailrecursion(self)) {
+            irerror(self->m_context, "tail-recursion optimization pass broke something in `%s`", self->m_name.c_str());
+            return false;
+        }
+    }
+
+    if (!ir_function_naive_phi(self)) {
+        irerror(self->m_context, "internal error: ir_function_naive_phi failed");
+        return false;
+    }
+
+    for (auto& lp : self->m_locals) {
+        ir_value *v = lp.get();
+        if (v->m_vtype == TYPE_VECTOR ||
+            (v->m_vtype == TYPE_FIELD && v->m_outtype == TYPE_VECTOR))
+        {
+            v->vectorMember(0);
+            v->vectorMember(1);
+            v->vectorMember(2);
+        }
+    }
+    for (auto& vp : self->m_values) {
+        ir_value *v = vp.get();
+        if (v->m_vtype == TYPE_VECTOR ||
+            (v->m_vtype == TYPE_FIELD && v->m_outtype == TYPE_VECTOR))
+        {
+            v->vectorMember(0);
+            v->vectorMember(1);
+            v->vectorMember(2);
+        }
+    }
+
+    ir_function_enumerate(self);
+
+    if (!ir_function_calculate_liferanges(self))
+        return false;
+    if (!ir_function_allocate_locals(self))
+        return false;
+    return true;
+}
+
+ir_value* ir_function_create_local(ir_function *self, const std::string& name, qc_type vtype, bool param)
+{
+    ir_value *ve;
+
+    if (param &&
+        !self->m_locals.empty() &&
+        self->m_locals.back()->m_store != store_param)
+    {
+        irerror(self->m_context, "cannot add parameters after adding locals");
+        return nullptr;
+    }
+
+    ve = new ir_value(std::string(name), (param ? store_param : store_local), vtype);
+    if (param)
+        ve->m_locked = true;
+    self->m_locals.emplace_back(ve);
+    return ve;
+}
+
+/***********************************************************************
+ *IR Block
+ */
+
+ir_block::ir_block(ir_function* owner, const std::string& name)
+: m_owner(owner),
+  m_label(name)
+{
+    m_context.file = "<@no context>";
+    m_context.line = 0;
+}
+
+ir_block::~ir_block()
+{
+    for (size_t i = 0; i != vec_size(m_instr); ++i)
+        delete m_instr[i];
+    vec_free(m_instr);
+    vec_free(m_entries);
+    vec_free(m_exits);
+}
+
+static void ir_block_delete_quick(ir_block* self)
+{
+    size_t i;
+    for (i = 0; i != vec_size(self->m_instr); ++i)
+        ir_instr_delete_quick(self->m_instr[i]);
+    vec_free(self->m_instr);
+    delete self;
+}
+
+/***********************************************************************
+ *IR Instructions
+ */
+
+ir_instr::ir_instr(lex_ctx_t ctx, ir_block* owner_, int op)
+: m_opcode(op),
+  m_context(ctx),
+  m_owner(owner_)
+{
+}
+
+ir_instr::~ir_instr()
+{
+    // The following calls can only delete from
+    // vectors, we still want to delete this instruction
+    // so ignore the return value. Since with the warn_unused_result attribute
+    // gcc doesn't care about an explicit: (void)foo(); to ignore the result,
+    // I have to improvise here and use if(foo());
+    for (auto &it : m_phi) {
+        size_t idx;
+        if (vec_ir_instr_find(it.value->m_writes, this, &idx))
+            it.value->m_writes.erase(it.value->m_writes.begin() + idx);
+        if (vec_ir_instr_find(it.value->m_reads, this, &idx))
+            it.value->m_reads.erase(it.value->m_reads.begin() + idx);
+    }
+    for (auto &it : m_params) {
+        size_t idx;
+        if (vec_ir_instr_find(it->m_writes, this, &idx))
+            it->m_writes.erase(it->m_writes.begin() + idx);
+        if (vec_ir_instr_find(it->m_reads, this, &idx))
+            it->m_reads.erase(it->m_reads.begin() + idx);
+    }
+    (void)!ir_instr_op(this, 0, nullptr, false);
+    (void)!ir_instr_op(this, 1, nullptr, false);
+    (void)!ir_instr_op(this, 2, nullptr, false);
+}
+
+static void ir_instr_delete_quick(ir_instr *self)
+{
+    self->m_phi.clear();
+    self->m_params.clear();
+    self->_m_ops[0] = nullptr;
+    self->_m_ops[1] = nullptr;
+    self->_m_ops[2] = nullptr;
+    delete self;
+}
+
+static bool ir_instr_op(ir_instr *self, int op, ir_value *v, bool writing)
+{
+    if (v && v->m_vtype == TYPE_NOEXPR) {
+        irerror(self->m_context, "tried to use a NOEXPR value");
+        return false;
+    }
+
+    if (self->_m_ops[op]) {
+        size_t idx;
+        if (writing && vec_ir_instr_find(self->_m_ops[op]->m_writes, self, &idx))
+            self->_m_ops[op]->m_writes.erase(self->_m_ops[op]->m_writes.begin() + idx);
+        else if (vec_ir_instr_find(self->_m_ops[op]->m_reads, self, &idx))
+            self->_m_ops[op]->m_reads.erase(self->_m_ops[op]->m_reads.begin() + idx);
+    }
+    if (v) {
+        if (writing)
+            v->m_writes.push_back(self);
+        else
+            v->m_reads.push_back(self);
+    }
+    self->_m_ops[op] = v;
+    return true;
+}
+
+/***********************************************************************
+ *IR Value
+ */
+
+void ir_value::setCodeAddress(int32_t gaddr)
+{
+    m_code.globaladdr = gaddr;
+    if (m_members[0]) m_members[0]->m_code.globaladdr = gaddr;
+    if (m_members[1]) m_members[1]->m_code.globaladdr = gaddr;
+    if (m_members[2]) m_members[2]->m_code.globaladdr = gaddr;
+}
+
+int32_t ir_value::codeAddress() const
+{
+    if (m_store == store_return)
+        return OFS_RETURN + m_code.addroffset;
+    return m_code.globaladdr + m_code.addroffset;
+}
+
+ir_value::ir_value(std::string&& name_, store_type store_, qc_type vtype_)
+    : m_name(move(name_))
+    , m_vtype(vtype_)
+    , m_store(store_)
+{
+    m_fieldtype = TYPE_VOID;
+    m_outtype = TYPE_VOID;
+    m_flags = 0;
+
+    m_cvq          = CV_NONE;
+    m_hasvalue     = false;
+    m_context.file = "<@no context>";
+    m_context.line = 0;
+
+    memset(&m_constval, 0, sizeof(m_constval));
+    memset(&m_code,     0, sizeof(m_code));
+
+    m_members[0] = nullptr;
+    m_members[1] = nullptr;
+    m_members[2] = nullptr;
+    m_memberof = nullptr;
+
+    m_unique_life = false;
+    m_locked = false;
+    m_callparam  = false;
+}
+
+ir_value::ir_value(ir_function *owner, std::string&& name, store_type storetype, qc_type vtype)
+    : ir_value(move(name), storetype, vtype)
+{
+    ir_function_collect_value(owner, this);
+}
+
+ir_value::~ir_value()
+{
+    size_t i;
+    if (m_hasvalue) {
+        if (m_vtype == TYPE_STRING)
+            mem_d((void*)m_constval.vstring);
+    }
+    if (!(m_flags & IR_FLAG_SPLIT_VECTOR)) {
+        for (i = 0; i < 3; ++i) {
+            if (m_members[i])
+                delete m_members[i];
+        }
+    }
+}
+
+
+/*  helper function */
+ir_value* ir_builder::literalFloat(float value, bool add_to_list) {
+    ir_value *v = new ir_value("#IMMEDIATE", store_global, TYPE_FLOAT);
+    v->m_flags |= IR_FLAG_ERASABLE;
+    v->m_hasvalue = true;
+    v->m_cvq = CV_CONST;
+    v->m_constval.vfloat = value;
+
+    m_globals.emplace_back(v);
+    if (add_to_list)
+        m_const_floats.emplace_back(v);
+    return v;
+}
+
+ir_value* ir_value::vectorMember(unsigned int member)
+{
+    std::string name;
+    ir_value *m;
+    if (member >= 3)
+        return nullptr;
+
+    if (m_members[member])
+        return m_members[member];
+
+    if (!m_name.empty()) {
+        char member_name[3] = { '_', char('x' + member), 0 };
+        name = m_name + member_name;
+    }
+
+    if (m_vtype == TYPE_VECTOR)
+    {
+        m = new ir_value(move(name), m_store, TYPE_FLOAT);
+        if (!m)
+            return nullptr;
+        m->m_context = m_context;
+
+        m_members[member] = m;
+        m->m_code.addroffset = member;
+    }
+    else if (m_vtype == TYPE_FIELD)
+    {
+        if (m_fieldtype != TYPE_VECTOR)
+            return nullptr;
+        m = new ir_value(move(name), m_store, TYPE_FIELD);
+        if (!m)
+            return nullptr;
+        m->m_fieldtype = TYPE_FLOAT;
+        m->m_context = m_context;
+
+        m_members[member] = m;
+        m->m_code.addroffset = member;
+    }
+    else
+    {
+        irerror(m_context, "invalid member access on %s", m_name.c_str());
+        return nullptr;
+    }
+
+    m->m_memberof = this;
+    return m;
+}
+
+size_t ir_value::size() const {
+    if (m_vtype == TYPE_FIELD && m_fieldtype == TYPE_VECTOR)
+        return type_sizeof_[TYPE_VECTOR];
+    return type_sizeof_[m_vtype];
+}
+
+bool ir_value::setFloat(float f)
+{
+    if (m_vtype != TYPE_FLOAT)
+        return false;
+    m_constval.vfloat = f;
+    m_hasvalue = true;
+    return true;
+}
+
+bool ir_value::setFunc(int f)
+{
+    if (m_vtype != TYPE_FUNCTION)
+        return false;
+    m_constval.vint = f;
+    m_hasvalue = true;
+    return true;
+}
+
+bool ir_value::setVector(vec3_t v)
+{
+    if (m_vtype != TYPE_VECTOR)
+        return false;
+    m_constval.vvec = v;
+    m_hasvalue = true;
+    return true;
+}
+
+bool ir_value::setField(ir_value *fld)
+{
+    if (m_vtype != TYPE_FIELD)
+        return false;
+    m_constval.vpointer = fld;
+    m_hasvalue = true;
+    return true;
+}
+
+bool ir_value::setString(const char *str)
+{
+    if (m_vtype != TYPE_STRING)
+        return false;
+    m_constval.vstring = util_strdupe(str);
+    m_hasvalue = true;
+    return true;
+}
+
+#if 0
+bool ir_value::setInt(int i)
+{
+    if (m_vtype != TYPE_INTEGER)
+        return false;
+    m_constval.vint = i;
+    m_hasvalue = true;
+    return true;
+}
+#endif
+
+bool ir_value::lives(size_t at)
+{
+    for (auto& l : m_life) {
+        if (l.start <= at && at <= l.end)
+            return true;
+        if (l.start > at) /* since it's ordered */
+            return false;
+    }
+    return false;
+}
+
+bool ir_value::insertLife(size_t idx, ir_life_entry_t e)
+{
+    m_life.insert(m_life.begin() + idx, e);
+    return true;
+}
+
+bool ir_value::setAlive(size_t s)
+{
+    size_t i;
+    const size_t vs = m_life.size();
+    ir_life_entry_t *life_found = nullptr;
+    ir_life_entry_t *before = nullptr;
+    ir_life_entry_t new_entry;
+
+    /* Find the first range >= s */
+    for (i = 0; i < vs; ++i)
+    {
+        before = life_found;
+        life_found = &m_life[i];
+        if (life_found->start > s)
+            break;
+    }
+    /* nothing found? append */
+    if (i == vs) {
+        ir_life_entry_t e;
+        if (life_found && life_found->end+1 == s)
+        {
+            /* previous life range can be merged in */
+            life_found->end++;
+            return true;
+        }
+        if (life_found && life_found->end >= s)
+            return false;
+        e.start = e.end = s;
+        m_life.emplace_back(e);
+        return true;
+    }
+    /* found */
+    if (before)
+    {
+        if (before->end + 1 == s &&
+            life_found->start - 1 == s)
+        {
+            /* merge */
+            before->end = life_found->end;
+            m_life.erase(m_life.begin()+i);
+            return true;
+        }
+        if (before->end + 1 == s)
+        {
+            /* extend before */
+            before->end++;
+            return true;
+        }
+        /* already contained */
+        if (before->end >= s)
+            return false;
+    }
+    /* extend */
+    if (life_found->start - 1 == s)
+    {
+        life_found->start--;
+        return true;
+    }
+    /* insert a new entry */
+    new_entry.start = new_entry.end = s;
+    return insertLife(i, new_entry);
+}
+
+bool ir_value::mergeLife(const ir_value *other)
+{
+    size_t i, myi;
+
+    if (other->m_life.empty())
+        return true;
+
+    if (m_life.empty()) {
+        m_life = other->m_life;
+        return true;
+    }
+
+    myi = 0;
+    for (i = 0; i < other->m_life.size(); ++i)
+    {
+        const ir_life_entry_t &otherlife = other->m_life[i];
+        while (true)
+        {
+            ir_life_entry_t *entry = &m_life[myi];
+
+            if (otherlife.end+1 < entry->start)
+            {
+                /* adding an interval before entry */
+                if (!insertLife(myi, otherlife))
+                    return false;
+                ++myi;
+                break;
+            }
+
+            if (otherlife.start <  entry->start &&
+                otherlife.end+1 >= entry->start)
+            {
+                /* starts earlier and overlaps */
+                entry->start = otherlife.start;
+            }
+
+            if (otherlife.end   >  entry->end &&
+                otherlife.start <= entry->end+1)
+            {
+                /* ends later and overlaps */
+                entry->end = otherlife.end;
+            }
+
+            /* see if our change combines it with the next ranges */
+            while (myi+1 < m_life.size() &&
+                   entry->end+1 >= m_life[1+myi].start)
+            {
+                /* overlaps with (myi+1) */
+                if (entry->end < m_life[1+myi].end)
+                    entry->end = m_life[1+myi].end;
+                m_life.erase(m_life.begin() + (myi + 1));
+                entry = &m_life[myi];
+            }
+
+            /* see if we're after the entry */
+            if (otherlife.start > entry->end)
+            {
+                ++myi;
+                /* append if we're at the end */
+                if (myi >= m_life.size()) {
+                    m_life.emplace_back(otherlife);
+                    break;
+                }
+                /* otherweise check the next range */
+                continue;
+            }
+            break;
+        }
+    }
+    return true;
+}
+
+static bool ir_values_overlap(const ir_value *a, const ir_value *b)
+{
+    /* For any life entry in A see if it overlaps with
+     * any life entry in B.
+     * Note that the life entries are orderes, so we can make a
+     * more efficient algorithm there than naively translating the
+     * statement above.
+     */
+
+    const ir_life_entry_t *la, *lb, *enda, *endb;
+
+    /* first of all, if either has no life range, they cannot clash */
+    if (a->m_life.empty() || b->m_life.empty())
+        return false;
+
+    la = &a->m_life.front();
+    lb = &b->m_life.front();
+    enda = &a->m_life.back() + 1;
+    endb = &b->m_life.back() + 1;
+    while (true)
+    {
+        /* check if the entries overlap, for that,
+         * both must start before the other one ends.
+         */
+        if (la->start < lb->end &&
+            lb->start < la->end)
+        {
+            return true;
+        }
+
+        /* entries are ordered
+         * one entry is earlier than the other
+         * that earlier entry will be moved forward
+         */
+        if (la->start < lb->start)
+        {
+            /* order: A B, move A forward
+             * check if we hit the end with A
+             */
+            if (++la == enda)
+                break;
+        }
+        else /* if (lb->start < la->start)  actually <= */
+        {
+            /* order: B A, move B forward
+             * check if we hit the end with B
+             */
+            if (++lb == endb)
+                break;
+        }
+    }
+    return false;
+}
+
+/***********************************************************************
+ *IR main operations
+ */
+
+static bool ir_check_unreachable(ir_block *self)
+{
+    /* The IR should never have to deal with unreachable code */
+    if (!self->m_final/* || OPTS_FLAG(ALLOW_UNREACHABLE_CODE)*/)
+        return true;
+    irerror(self->m_context, "unreachable statement (%s)", self->m_label.c_str());
+    return false;
+}
+
+bool ir_block_create_store_op(ir_block *self, lex_ctx_t ctx, int op, ir_value *target, ir_value *what)
+{
+    ir_instr *in;
+    if (!ir_check_unreachable(self))
+        return false;
+
+    if (target->m_store == store_value &&
+        (op < INSTR_STOREP_F || op > INSTR_STOREP_FNC))
+    {
+        irerror(self->m_context, "cannot store to an SSA value");
+        irerror(self->m_context, "trying to store: %s <- %s", target->m_name.c_str(), what->m_name.c_str());
+        irerror(self->m_context, "instruction: %s", util_instr_str[op]);
+        return false;
+    }
+
+    in = new ir_instr(ctx, self, op);
+    if (!in)
+        return false;
+
+    if (!ir_instr_op(in, 0, target, (op < INSTR_STOREP_F || op > INSTR_STOREP_FNC)) ||
+        !ir_instr_op(in, 1, what, false))
+    {
+        delete in;
+        return false;
+    }
+    vec_push(self->m_instr, in);
+    return true;
+}
+
+bool ir_block_create_state_op(ir_block *self, lex_ctx_t ctx, ir_value *frame, ir_value *think)
+{
+    ir_instr *in;
+    if (!ir_check_unreachable(self))
+        return false;
+
+    in = new ir_instr(ctx, self, INSTR_STATE);
+    if (!in)
+        return false;
+
+    if (!ir_instr_op(in, 0, frame, false) ||
+        !ir_instr_op(in, 1, think, false))
+    {
+        delete in;
+        return false;
+    }
+    vec_push(self->m_instr, in);
+    return true;
+}
+
+static bool ir_block_create_store(ir_block *self, lex_ctx_t ctx, ir_value *target, ir_value *what)
+{
+    int op = 0;
+    qc_type vtype;
+    if (target->m_vtype == TYPE_VARIANT)
+        vtype = what->m_vtype;
+    else
+        vtype = target->m_vtype;
+
+#if 0
+    if      (vtype == TYPE_FLOAT   && what->m_vtype == TYPE_INTEGER)
+        op = INSTR_CONV_ITOF;
+    else if (vtype == TYPE_INTEGER && what->m_vtype == TYPE_FLOAT)
+        op = INSTR_CONV_FTOI;
+#endif
+        op = type_store_instr[vtype];
+
+    if (OPTS_FLAG(ADJUST_VECTOR_FIELDS)) {
+        if (op == INSTR_STORE_FLD && what->m_fieldtype == TYPE_VECTOR)
+            op = INSTR_STORE_V;
+    }
+
+    return ir_block_create_store_op(self, ctx, op, target, what);
+}
+
+bool ir_block_create_storep(ir_block *self, lex_ctx_t ctx, ir_value *target, ir_value *what)
+{
+    int op = 0;
+    qc_type vtype;
+
+    if (target->m_vtype != TYPE_POINTER)
+        return false;
+
+    /* storing using pointer - target is a pointer, type must be
+     * inferred from source
+     */
+    vtype = what->m_vtype;
+
+    op = type_storep_instr[vtype];
+    if (OPTS_FLAG(ADJUST_VECTOR_FIELDS)) {
+        if (op == INSTR_STOREP_FLD && what->m_fieldtype == TYPE_VECTOR)
+            op = INSTR_STOREP_V;
+    }
+
+    return ir_block_create_store_op(self, ctx, op, target, what);
+}
+
+bool ir_block_create_return(ir_block *self, lex_ctx_t ctx, ir_value *v)
+{
+    ir_instr *in;
+    if (!ir_check_unreachable(self))
+        return false;
+
+    self->m_final = true;
+
+    self->m_is_return = true;
+    in = new ir_instr(ctx, self, INSTR_RETURN);
+    if (!in)
+        return false;
+
+    if (v && !ir_instr_op(in, 0, v, false)) {
+        delete in;
+        return false;
+    }
+
+    vec_push(self->m_instr, in);
+    return true;
+}
+
+bool ir_block_create_if(ir_block *self, lex_ctx_t ctx, ir_value *v,
+                        ir_block *ontrue, ir_block *onfalse)
+{
+    ir_instr *in;
+    if (!ir_check_unreachable(self))
+        return false;
+    self->m_final = true;
+    /*in = new ir_instr(ctx, self, (v->m_vtype == TYPE_STRING ? INSTR_IF_S : INSTR_IF_F));*/
+    in = new ir_instr(ctx, self, VINSTR_COND);
+    if (!in)
+        return false;
+
+    if (!ir_instr_op(in, 0, v, false)) {
+        delete in;
+        return false;
+    }
+
+    in->m_bops[0] = ontrue;
+    in->m_bops[1] = onfalse;
+
+    vec_push(self->m_instr, in);
+
+    vec_push(self->m_exits, ontrue);
+    vec_push(self->m_exits, onfalse);
+    vec_push(ontrue->m_entries,  self);
+    vec_push(onfalse->m_entries, self);
+    return true;
+}
+
+bool ir_block_create_jump(ir_block *self, lex_ctx_t ctx, ir_block *to)
+{
+    ir_instr *in;
+    if (!ir_check_unreachable(self))
+        return false;
+    self->m_final = true;
+    in = new ir_instr(ctx, self, VINSTR_JUMP);
+    if (!in)
+        return false;
+
+    in->m_bops[0] = to;
+    vec_push(self->m_instr, in);
+
+    vec_push(self->m_exits, to);
+    vec_push(to->m_entries, self);
+    return true;
+}
+
+bool ir_block_create_goto(ir_block *self, lex_ctx_t ctx, ir_block *to)
+{
+    self->m_owner->m_flags |= IR_FLAG_HAS_GOTO;
+    return ir_block_create_jump(self, ctx, to);
+}
+
+ir_instr* ir_block_create_phi(ir_block *self, lex_ctx_t ctx, const char *label, qc_type ot)
+{
+    ir_value *out;
+    ir_instr *in;
+    if (!ir_check_unreachable(self))
+        return nullptr;
+    in = new ir_instr(ctx, self, VINSTR_PHI);
+    if (!in)
+        return nullptr;
+    out = new ir_value(self->m_owner, label ? label : "", store_value, ot);
+    if (!out) {
+        delete in;
+        return nullptr;
+    }
+    if (!ir_instr_op(in, 0, out, true)) {
+        delete in;
+        return nullptr;
+    }
+    vec_push(self->m_instr, in);
+    return in;
+}
+
+ir_value* ir_phi_value(ir_instr *self)
+{
+    return self->_m_ops[0];
+}
+
+void ir_phi_add(ir_instr* self, ir_block *b, ir_value *v)
+{
+    ir_phi_entry_t pe;
+
+    if (!vec_ir_block_find(self->m_owner->m_entries, b, nullptr)) {
+        // Must not be possible to cause this, otherwise the AST
+        // is doing something wrong.
+        irerror(self->m_context, "Invalid entry block for PHI");
+        exit(EXIT_FAILURE);
+    }
+
+    pe.value = v;
+    pe.from = b;
+    v->m_reads.push_back(self);
+    self->m_phi.push_back(pe);
+}
+
+/* call related code */
+ir_instr* ir_block_create_call(ir_block *self, lex_ctx_t ctx, const char *label, ir_value *func, bool noreturn)
+{
+    ir_value *out;
+    ir_instr *in;
+    if (!ir_check_unreachable(self))
+        return nullptr;
+    in = new ir_instr(ctx, self, (noreturn ? VINSTR_NRCALL : INSTR_CALL0));
+    if (!in)
+        return nullptr;
+    if (noreturn) {
+        self->m_final = true;
+        self->m_is_return = true;
+    }
+    out = new ir_value(self->m_owner, label ? label : "", (func->m_outtype == TYPE_VOID) ? store_return : store_value, func->m_outtype);
+    if (!out) {
+        delete in;
+        return nullptr;
+    }
+    if (!ir_instr_op(in, 0, out, true) ||
+        !ir_instr_op(in, 1, func, false))
+    {
+        delete in;
+        return nullptr;
+    }
+    vec_push(self->m_instr, in);
+    /*
+    if (noreturn) {
+        if (!ir_block_create_return(self, ctx, nullptr)) {
+            compile_error(ctx, "internal error: failed to generate dummy-return instruction");
+            delete in;
+            return nullptr;
+        }
+    }
+    */
+    return in;
+}
+
+ir_value* ir_call_value(ir_instr *self)
+{
+    return self->_m_ops[0];
+}
+
+void ir_call_param(ir_instr* self, ir_value *v)
+{
+    self->m_params.push_back(v);
+    v->m_reads.push_back(self);
+}
+
+/* binary op related code */
+
+ir_value* ir_block_create_binop(ir_block *self, lex_ctx_t ctx,
+                                const char *label, int opcode,
+                                ir_value *left, ir_value *right)
+{
+    qc_type ot = TYPE_VOID;
+    switch (opcode) {
+        case INSTR_ADD_F:
+        case INSTR_SUB_F:
+        case INSTR_DIV_F:
+        case INSTR_MUL_F:
+        case INSTR_MUL_V:
+        case INSTR_AND:
+        case INSTR_OR:
+#if 0
+        case INSTR_AND_I:
+        case INSTR_AND_IF:
+        case INSTR_AND_FI:
+        case INSTR_OR_I:
+        case INSTR_OR_IF:
+        case INSTR_OR_FI:
+#endif
+        case INSTR_BITAND:
+        case INSTR_BITOR:
+        case VINSTR_BITXOR:
+#if 0
+        case INSTR_SUB_S: /* -- offset of string as float */
+        case INSTR_MUL_IF:
+        case INSTR_MUL_FI:
+        case INSTR_DIV_IF:
+        case INSTR_DIV_FI:
+        case INSTR_BITOR_IF:
+        case INSTR_BITOR_FI:
+        case INSTR_BITAND_FI:
+        case INSTR_BITAND_IF:
+        case INSTR_EQ_I:
+        case INSTR_NE_I:
+#endif
+            ot = TYPE_FLOAT;
+            break;
+#if 0
+        case INSTR_ADD_I:
+        case INSTR_ADD_IF:
+        case INSTR_ADD_FI:
+        case INSTR_SUB_I:
+        case INSTR_SUB_FI:
+        case INSTR_SUB_IF:
+        case INSTR_MUL_I:
+        case INSTR_DIV_I:
+        case INSTR_BITAND_I:
+        case INSTR_BITOR_I:
+        case INSTR_XOR_I:
+        case INSTR_RSHIFT_I:
+        case INSTR_LSHIFT_I:
+            ot = TYPE_INTEGER;
+            break;
+#endif
+        case INSTR_ADD_V:
+        case INSTR_SUB_V:
+        case INSTR_MUL_VF:
+        case INSTR_MUL_FV:
+        case VINSTR_BITAND_V:
+        case VINSTR_BITOR_V:
+        case VINSTR_BITXOR_V:
+        case VINSTR_BITAND_VF:
+        case VINSTR_BITOR_VF:
+        case VINSTR_BITXOR_VF:
+        case VINSTR_CROSS:
+#if 0
+        case INSTR_DIV_VF:
+        case INSTR_MUL_IV:
+        case INSTR_MUL_VI:
+#endif
+            ot = TYPE_VECTOR;
+            break;
+#if 0
+        case INSTR_ADD_SF:
+            ot = TYPE_POINTER;
+            break;
+#endif
+    /*
+     * after the following default case, the value of opcode can never
+     * be 1, 2, 3, 4, 5, 6, 7, 8, 9, 62, 63, 64, 65
+     */
+        default:
+            /* ranges: */
+            /* boolean operations result in floats */
+
+            /*
+             * opcode >= 10 takes true branch opcode is at least 10
+             * opcode <= 23 takes false branch opcode is at least 24
+             */
+            if (opcode >= INSTR_EQ_F && opcode <= INSTR_GT)
+                ot = TYPE_FLOAT;
+
+            /*
+             * At condition "opcode <= 23", the value of "opcode" must be
+             * at least 24.
+             * At condition "opcode <= 23", the value of "opcode" cannot be
+             * equal to any of {1, 2, 3, 4, 5, 6, 7, 8, 9, 62, 63, 64, 65}.
+             * The condition "opcode <= 23" cannot be true.
+             *
+             * Thus ot=2 (TYPE_FLOAT) can never be true
+             */
+#if 0
+            else if (opcode >= INSTR_LE && opcode <= INSTR_GT)
+                ot = TYPE_FLOAT;
+            else if (opcode >= INSTR_LE_I && opcode <= INSTR_EQ_FI)
+                ot = TYPE_FLOAT;
+#endif
+            break;
+    };
+    if (ot == TYPE_VOID) {
+        /* The AST or parser were supposed to check this! */
+        return nullptr;
+    }
+
+    return ir_block_create_general_instr(self, ctx, label, opcode, left, right, ot);
+}
+
+ir_value* ir_block_create_unary(ir_block *self, lex_ctx_t ctx,
+                                const char *label, int opcode,
+                                ir_value *operand)
+{
+    qc_type ot = TYPE_FLOAT;
+    switch (opcode) {
+        case INSTR_NOT_F:
+        case INSTR_NOT_V:
+        case INSTR_NOT_S:
+        case INSTR_NOT_ENT:
+        case INSTR_NOT_FNC: /*
+        case INSTR_NOT_I:   */
+            ot = TYPE_FLOAT;
+            break;
+
+        /*
+         * Negation for virtual instructions is emulated with 0-value. Thankfully
+         * the operand for 0 already exists so we just source it from here.
+         */
+        case VINSTR_NEG_F:
+            return ir_block_create_general_instr(self, ctx, label, INSTR_SUB_F, nullptr, operand, ot);
+        case VINSTR_NEG_V:
+            return ir_block_create_general_instr(self, ctx, label, INSTR_SUB_V, nullptr, operand, TYPE_VECTOR);
+
+        default:
+            ot = operand->m_vtype;
+            break;
+    };
+    if (ot == TYPE_VOID) {
+        /* The AST or parser were supposed to check this! */
+        return nullptr;
+    }
+
+    /* let's use the general instruction creator and pass nullptr for OPB */
+    return ir_block_create_general_instr(self, ctx, label, opcode, operand, nullptr, ot);
+}
+
+static ir_value* ir_block_create_general_instr(ir_block *self, lex_ctx_t ctx, const char *label,
+                                        int op, ir_value *a, ir_value *b, qc_type outype)
+{
+    ir_instr *instr;
+    ir_value *out;
+
+    out = new ir_value(self->m_owner, label ? label : "", store_value, outype);
+    if (!out)
+        return nullptr;
+
+    instr = new ir_instr(ctx, self, op);
+    if (!instr) {
+        return nullptr;
+    }
+
+    if (!ir_instr_op(instr, 0, out, true) ||
+        !ir_instr_op(instr, 1, a, false) ||
+        !ir_instr_op(instr, 2, b, false) )
+    {
+        goto on_error;
+    }
+
+    vec_push(self->m_instr, instr);
+
+    return out;
+on_error:
+    delete instr;
+    return nullptr;
+}
+
+ir_value* ir_block_create_fieldaddress(ir_block *self, lex_ctx_t ctx, const char *label, ir_value *ent, ir_value *field)
+{
+    ir_value *v;
+
+    /* Support for various pointer types todo if so desired */
+    if (ent->m_vtype != TYPE_ENTITY)
+        return nullptr;
+
+    if (field->m_vtype != TYPE_FIELD)
+        return nullptr;
+
+    v = ir_block_create_general_instr(self, ctx, label, INSTR_ADDRESS, ent, field, TYPE_POINTER);
+    v->m_fieldtype = field->m_fieldtype;
+    return v;
+}
+
+ir_value* ir_block_create_load_from_ent(ir_block *self, lex_ctx_t ctx, const char *label, ir_value *ent, ir_value *field, qc_type outype)
+{
+    int op;
+    if (ent->m_vtype != TYPE_ENTITY)
+        return nullptr;
+
+    /* at some point we could redirect for TYPE_POINTER... but that could lead to carelessness */
+    if (field->m_vtype != TYPE_FIELD)
+        return nullptr;
+
+    switch (outype)
+    {
+        case TYPE_FLOAT:    op = INSTR_LOAD_F;   break;
+        case TYPE_VECTOR:   op = INSTR_LOAD_V;   break;
+        case TYPE_STRING:   op = INSTR_LOAD_S;   break;
+        case TYPE_FIELD:    op = INSTR_LOAD_FLD; break;
+        case TYPE_ENTITY:   op = INSTR_LOAD_ENT; break;
+        case TYPE_FUNCTION: op = INSTR_LOAD_FNC; break;
+#if 0
+        case TYPE_POINTER: op = INSTR_LOAD_I;   break;
+        case TYPE_INTEGER: op = INSTR_LOAD_I;   break;
+#endif
+        default:
+            irerror(self->m_context, "invalid type for ir_block_create_load_from_ent: %s", type_name[outype]);
+            return nullptr;
+    }
+
+    return ir_block_create_general_instr(self, ctx, label, op, ent, field, outype);
+}
+
+/* PHI resolving breaks the SSA, and must thus be the last
+ * step before life-range calculation.
+ */
+
+static bool ir_block_naive_phi(ir_block *self);
+bool ir_function_naive_phi(ir_function *self)
+{
+    for (auto& b : self->m_blocks)
+        if (!ir_block_naive_phi(b.get()))
+            return false;
+    return true;
+}
+
+static bool ir_block_naive_phi(ir_block *self)
+{
+    size_t i;
+    /* FIXME: optionally, create_phi can add the phis
+     * to a list so we don't need to loop through blocks
+     * - anyway: "don't optimize YET"
+     */
+    for (i = 0; i < vec_size(self->m_instr); ++i)
+    {
+        ir_instr *instr = self->m_instr[i];
+        if (instr->m_opcode != VINSTR_PHI)
+            continue;
+
+        vec_remove(self->m_instr, i, 1);
+        --i; /* NOTE: i+1 below */
+
+        for (auto &it : instr->m_phi) {
+            ir_value *v = it.value;
+            ir_block *b = it.from;
+            if (v->m_store == store_value && v->m_reads.size() == 1 && v->m_writes.size() == 1) {
+                /* replace the value */
+                if (!ir_instr_op(v->m_writes[0], 0, instr->_m_ops[0], true))
+                    return false;
+            } else {
+                /* force a move instruction */
+                ir_instr *prevjump = vec_last(b->m_instr);
+                vec_pop(b->m_instr);
+                b->m_final = false;
+                instr->_m_ops[0]->m_store = store_global;
+                if (!ir_block_create_store(b, instr->m_context, instr->_m_ops[0], v))
+                    return false;
+                instr->_m_ops[0]->m_store = store_value;
+                vec_push(b->m_instr, prevjump);
+                b->m_final = true;
+            }
+        }
+        delete instr;
+    }
+    return true;
+}
+
+/***********************************************************************
+ *IR Temp allocation code
+ * Propagating value life ranges by walking through the function backwards
+ * until no more changes are made.
+ * In theory this should happen once more than once for every nested loop
+ * level.
+ * Though this implementation might run an additional time for if nests.
+ */
+
+/* Enumerate instructions used by value's life-ranges
+ */
+static void ir_block_enumerate(ir_block *self, size_t *_eid)
+{
+    size_t i;
+    size_t eid = *_eid;
+    for (i = 0; i < vec_size(self->m_instr); ++i)
+    {
+        self->m_instr[i]->m_eid = eid++;
+    }
+    *_eid = eid;
+}
+
+/* Enumerate blocks and instructions.
+ * The block-enumeration is unordered!
+ * We do not really use the block enumreation, however
+ * the instruction enumeration is important for life-ranges.
+ */
+void ir_function_enumerate(ir_function *self)
+{
+    size_t instruction_id = 0;
+    size_t block_eid = 0;
+    for (auto& block : self->m_blocks)
+    {
+        /* each block now gets an additional "entry" instruction id
+         * we can use to avoid point-life issues
+         */
+        block->m_entry_id = instruction_id;
+        block->m_eid      = block_eid;
+        ++instruction_id;
+        ++block_eid;
+
+        ir_block_enumerate(block.get(), &instruction_id);
+    }
+}
+
+/* Local-value allocator
+ * After finishing creating the liferange of all values used in a function
+ * we can allocate their global-positions.
+ * This is the counterpart to register-allocation in register machines.
+ */
+struct function_allocator {
+    ir_value **locals;
+    size_t *sizes;
+    size_t *positions;
+    bool *unique;
+};
+
+static bool function_allocator_alloc(function_allocator *alloc, ir_value *var)
+{
+    ir_value *slot;
+    size_t vsize = var->size();
+
+    var->m_code.local = vec_size(alloc->locals);
+
+    slot = new ir_value("reg", store_global, var->m_vtype);
+    if (!slot)
+        return false;
+
+    if (!slot->mergeLife(var))
+        goto localerror;
+
+    vec_push(alloc->locals, slot);
+    vec_push(alloc->sizes, vsize);
+    vec_push(alloc->unique, var->m_unique_life);
+
+    return true;
+
+localerror:
+    delete slot;
+    return false;
+}
+
+static bool ir_function_allocator_assign(ir_function *self, function_allocator *alloc, ir_value *v)
+{
+    size_t a;
+    ir_value *slot;
+
+    if (v->m_unique_life)
+        return function_allocator_alloc(alloc, v);
+
+    for (a = 0; a < vec_size(alloc->locals); ++a)
+    {
+        /* if it's reserved for a unique liferange: skip */
+        if (alloc->unique[a])
+            continue;
+
+        slot = alloc->locals[a];
+
+        /* never resize parameters
+         * will be required later when overlapping temps + locals
+         */
+        if (a < vec_size(self->m_params) &&
+            alloc->sizes[a] < v->size())
+        {
+            continue;
+        }
+
+        if (ir_values_overlap(v, slot))
+            continue;
+
+        if (!slot->mergeLife(v))
+            return false;
+
+        /* adjust size for this slot */
+        if (alloc->sizes[a] < v->size())
+            alloc->sizes[a] = v->size();
+
+        v->m_code.local = a;
+        return true;
+    }
+    if (a >= vec_size(alloc->locals)) {
+        if (!function_allocator_alloc(alloc, v))
+            return false;
+    }
+    return true;
+}
+
+bool ir_function_allocate_locals(ir_function *self)
+{
+    bool   retval = true;
+    size_t pos;
+    bool   opt_gt = OPTS_OPTIMIZATION(OPTIM_GLOBAL_TEMPS);
+
+    function_allocator lockalloc, globalloc;
+
+    if (self->m_locals.empty() && self->m_values.empty())
+        return true;
+
+    globalloc.locals    = nullptr;
+    globalloc.sizes     = nullptr;
+    globalloc.positions = nullptr;
+    globalloc.unique    = nullptr;
+    lockalloc.locals    = nullptr;
+    lockalloc.sizes     = nullptr;
+    lockalloc.positions = nullptr;
+    lockalloc.unique    = nullptr;
+
+    size_t i;
+    for (i = 0; i < self->m_locals.size(); ++i)
+    {
+        ir_value *v = self->m_locals[i].get();
+        if ((self->m_flags & IR_FLAG_MASK_NO_LOCAL_TEMPS) || !OPTS_OPTIMIZATION(OPTIM_LOCAL_TEMPS)) {
+            v->m_locked      = true;
+            v->m_unique_life = true;
+        }
+        else if (i >= vec_size(self->m_params))
+            break;
+        else
+            v->m_locked = true; /* lock parameters locals */
+        if (!function_allocator_alloc((v->m_locked || !opt_gt ? &lockalloc : &globalloc), v))
+            goto error;
+    }
+    for (; i < self->m_locals.size(); ++i)
+    {
+        ir_value *v = self->m_locals[i].get();
+        if (v->m_life.empty())
+            continue;
+        if (!ir_function_allocator_assign(self, (v->m_locked || !opt_gt ? &lockalloc : &globalloc), v))
+            goto error;
+    }
+
+    /* Allocate a slot for any value that still exists */
+    for (i = 0; i < self->m_values.size(); ++i)
+    {
+        ir_value *v = self->m_values[i].get();
+
+        if (v->m_life.empty())
+            continue;
+
+        /* CALL optimization:
+         * If the value is a parameter-temp: 1 write, 1 read from a CALL
+         * and it's not "locked", write it to the OFS_PARM directly.
+         */
+        if (OPTS_OPTIMIZATION(OPTIM_CALL_STORES) && !v->m_locked && !v->m_unique_life) {
+            if (v->m_reads.size() == 1 && v->m_writes.size() == 1 &&
+                (v->m_reads[0]->m_opcode == VINSTR_NRCALL ||
+                 (v->m_reads[0]->m_opcode >= INSTR_CALL0 && v->m_reads[0]->m_opcode <= INSTR_CALL8)
+                )
+               )
+            {
+                size_t param;
+                ir_instr *call = v->m_reads[0];
+                if (!vec_ir_value_find(call->m_params, v, &param)) {
+                    irerror(call->m_context, "internal error: unlocked parameter %s not found", v->m_name.c_str());
+                    goto error;
+                }
+                ++opts_optimizationcount[OPTIM_CALL_STORES];
+                v->m_callparam = true;
+                if (param < 8)
+                    v->setCodeAddress(OFS_PARM0 + 3*param);
+                else {
+                    size_t nprotos = self->m_owner->m_extparam_protos.size();
+                    ir_value *ep;
+                    param -= 8;
+                    if (nprotos > param)
+                        ep = self->m_owner->m_extparam_protos[param].get();
+                    else
+                    {
+                        ep = self->m_owner->generateExtparamProto();
+                        while (++nprotos <= param)
+                            ep = self->m_owner->generateExtparamProto();
+                    }
+                    ir_instr_op(v->m_writes[0], 0, ep, true);
+                    call->m_params[param+8] = ep;
+                }
+                continue;
+            }
+            if (v->m_writes.size() == 1 && v->m_writes[0]->m_opcode == INSTR_CALL0) {
+                v->m_store = store_return;
+                if (v->m_members[0]) v->m_members[0]->m_store = store_return;
+                if (v->m_members[1]) v->m_members[1]->m_store = store_return;
+                if (v->m_members[2]) v->m_members[2]->m_store = store_return;
+                ++opts_optimizationcount[OPTIM_CALL_STORES];
+                continue;
+            }
+        }
+
+        if (!ir_function_allocator_assign(self, (v->m_locked || !opt_gt ? &lockalloc : &globalloc), v))
+            goto error;
+    }
+
+    if (!lockalloc.sizes && !globalloc.sizes) {
+        goto cleanup;
+    }
+    vec_push(lockalloc.positions, 0);
+    vec_push(globalloc.positions, 0);
+
+    /* Adjust slot positions based on sizes */
+    if (lockalloc.sizes) {
+        pos = (vec_size(lockalloc.sizes) ? lockalloc.positions[0] : 0);
+        for (i = 1; i < vec_size(lockalloc.sizes); ++i)
+        {
+            pos = lockalloc.positions[i-1] + lockalloc.sizes[i-1];
+            vec_push(lockalloc.positions, pos);
+        }
+        self->m_allocated_locals = pos + vec_last(lockalloc.sizes);
+    }
+    if (globalloc.sizes) {
+        pos = (vec_size(globalloc.sizes) ? globalloc.positions[0] : 0);
+        for (i = 1; i < vec_size(globalloc.sizes); ++i)
+        {
+            pos = globalloc.positions[i-1] + globalloc.sizes[i-1];
+            vec_push(globalloc.positions, pos);
+        }
+        self->m_globaltemps = pos + vec_last(globalloc.sizes);
+    }
+
+    /* Locals need to know their new position */
+    for (auto& local : self->m_locals) {
+        if (local->m_locked || !opt_gt)
+            local->m_code.local = lockalloc.positions[local->m_code.local];
+        else
+            local->m_code.local = globalloc.positions[local->m_code.local];
+    }
+    /* Take over the actual slot positions on values */
+    for (auto& value : self->m_values) {
+        if (value->m_locked || !opt_gt)
+            value->m_code.local = lockalloc.positions[value->m_code.local];
+        else
+            value->m_code.local = globalloc.positions[value->m_code.local];
+    }
+
+    goto cleanup;
+
+error:
+    retval = false;
+cleanup:
+    for (i = 0; i < vec_size(lockalloc.locals); ++i)
+        delete lockalloc.locals[i];
+    for (i = 0; i < vec_size(globalloc.locals); ++i)
+        delete globalloc.locals[i];
+    vec_free(globalloc.unique);
+    vec_free(globalloc.locals);
+    vec_free(globalloc.sizes);
+    vec_free(globalloc.positions);
+    vec_free(lockalloc.unique);
+    vec_free(lockalloc.locals);
+    vec_free(lockalloc.sizes);
+    vec_free(lockalloc.positions);
+    return retval;
+}
+
+/* Get information about which operand
+ * is read from, or written to.
+ */
+static void ir_op_read_write(int op, size_t *read, size_t *write)
+{
+    switch (op)
+    {
+    case VINSTR_JUMP:
+    case INSTR_GOTO:
+        *write = 0;
+        *read = 0;
+        break;
+    case INSTR_IF:
+    case INSTR_IFNOT:
+#if 0
+    case INSTR_IF_S:
+    case INSTR_IFNOT_S:
+#endif
+    case INSTR_RETURN:
+    case VINSTR_COND:
+        *write = 0;
+        *read = 1;
+        break;
+    case INSTR_STOREP_F:
+    case INSTR_STOREP_V:
+    case INSTR_STOREP_S:
+    case INSTR_STOREP_ENT:
+    case INSTR_STOREP_FLD:
+    case INSTR_STOREP_FNC:
+        *write = 0;
+        *read  = 7;
+        break;
+    default:
+        *write = 1;
+        *read = 6;
+        break;
+    };
+}
+
+static bool ir_block_living_add_instr(ir_block *self, size_t eid) {
+    bool changed = false;
+    for (auto &it : self->m_living)
+        if (it->setAlive(eid))
+            changed = true;
+    return changed;
+}
+
+static bool ir_block_living_lock(ir_block *self) {
+    bool changed = false;
+    for (auto &it : self->m_living) {
+        if (it->m_locked)
+            continue;
+        it->m_locked = true;
+        changed = true;
+    }
+    return changed;
+}
+
+static bool ir_block_life_propagate(ir_block *self, bool *changed)
+{
+    ir_instr *instr;
+    ir_value *value;
+    size_t i, o, p, mem;
+    // bitmasks which operands are read from or written to
+    size_t read, write;
+
+    self->m_living.clear();
+
+    p = vec_size(self->m_exits);
+    for (i = 0; i < p; ++i) {
+        ir_block *prev = self->m_exits[i];
+        for (auto &it : prev->m_living)
+            if (!vec_ir_value_find(self->m_living, it, nullptr))
+                self->m_living.push_back(it);
+    }
+
+    i = vec_size(self->m_instr);
+    while (i)
+    { --i;
+        instr = self->m_instr[i];
+
+        /* See which operands are read and write operands */
+        ir_op_read_write(instr->m_opcode, &read, &write);
+
+        /* Go through the 3 main operands
+         * writes first, then reads
+         */
+        for (o = 0; o < 3; ++o)
+        {
+            if (!instr->_m_ops[o]) /* no such operand */
+                continue;
+
+            value = instr->_m_ops[o];
+
+            /* We only care about locals */
+            /* we also calculate parameter liferanges so that locals
+             * can take up parameter slots */
+            if (value->m_store != store_value &&
+                value->m_store != store_local &&
+                value->m_store != store_param)
+                continue;
+
+            /* write operands */
+            /* When we write to a local, we consider it "dead" for the
+             * remaining upper part of the function, since in SSA a value
+             * can only be written once (== created)
+             */
+            if (write & (1<<o))
+            {
+                size_t idx;
+                bool in_living = vec_ir_value_find(self->m_living, value, &idx);
+                if (!in_living)
+                {
+                    /* If the value isn't alive it hasn't been read before... */
+                    /* TODO: See if the warning can be emitted during parsing or AST processing
+                     * otherwise have warning printed here.
+                     * IF printing a warning here: include filecontext_t,
+                     * and make sure it's only printed once
+                     * since this function is run multiple times.
+                     */
+                    /* con_err( "Value only written %s\n", value->m_name); */
+                    if (value->setAlive(instr->m_eid))
+                        *changed = true;
+                } else {
+                    /* since 'living' won't contain it
+                     * anymore, merge the value, since
+                     * (A) doesn't.
+                     */
+                    if (value->setAlive(instr->m_eid))
+                        *changed = true;
+                    // Then remove
+                    self->m_living.erase(self->m_living.begin() + idx);
+                }
+                /* Removing a vector removes all members */
+                for (mem = 0; mem < 3; ++mem) {
+                    if (value->m_members[mem] && vec_ir_value_find(self->m_living, value->m_members[mem], &idx)) {
+                        if (value->m_members[mem]->setAlive(instr->m_eid))
+                            *changed = true;
+                        self->m_living.erase(self->m_living.begin() + idx);
+                    }
+                }
+                /* Removing the last member removes the vector */
+                if (value->m_memberof) {
+                    value = value->m_memberof;
+                    for (mem = 0; mem < 3; ++mem) {
+                        if (value->m_members[mem] && vec_ir_value_find(self->m_living, value->m_members[mem], nullptr))
+                            break;
+                    }
+                    if (mem == 3 && vec_ir_value_find(self->m_living, value, &idx)) {
+                        if (value->setAlive(instr->m_eid))
+                            *changed = true;
+                        self->m_living.erase(self->m_living.begin() + idx);
+                    }
+                }
+            }
+        }
+
+        /* These operations need a special case as they can break when using
+         * same source and destination operand otherwise, as the engine may
+         * read the source multiple times. */
+        if (instr->m_opcode == INSTR_MUL_VF ||
+            instr->m_opcode == VINSTR_BITAND_VF ||
+            instr->m_opcode == VINSTR_BITOR_VF ||
+            instr->m_opcode == VINSTR_BITXOR ||
+            instr->m_opcode == VINSTR_BITXOR_VF ||
+            instr->m_opcode == VINSTR_BITXOR_V ||
+            instr->m_opcode == VINSTR_CROSS)
+        {
+            value = instr->_m_ops[2];
+            /* the float source will get an additional lifetime */
+            if (value->setAlive(instr->m_eid+1))
+                *changed = true;
+            if (value->m_memberof && value->m_memberof->setAlive(instr->m_eid+1))
+                *changed = true;
+        }
+
+        if (instr->m_opcode == INSTR_MUL_FV ||
+            instr->m_opcode == INSTR_LOAD_V ||
+            instr->m_opcode == VINSTR_BITXOR ||
+            instr->m_opcode == VINSTR_BITXOR_VF ||
+            instr->m_opcode == VINSTR_BITXOR_V ||
+            instr->m_opcode == VINSTR_CROSS)
+        {
+            value = instr->_m_ops[1];
+            /* the float source will get an additional lifetime */
+            if (value->setAlive(instr->m_eid+1))
+                *changed = true;
+            if (value->m_memberof && value->m_memberof->setAlive(instr->m_eid+1))
+                *changed = true;
+        }
+
+        for (o = 0; o < 3; ++o)
+        {
+            if (!instr->_m_ops[o]) /* no such operand */
+                continue;
+
+            value = instr->_m_ops[o];
+
+            /* We only care about locals */
+            /* we also calculate parameter liferanges so that locals
+             * can take up parameter slots */
+            if (value->m_store != store_value &&
+                value->m_store != store_local &&
+                value->m_store != store_param)
+                continue;
+
+            /* read operands */
+            if (read & (1<<o))
+            {
+                if (!vec_ir_value_find(self->m_living, value, nullptr))
+                    self->m_living.push_back(value);
+                /* reading adds the full vector */
+                if (value->m_memberof && !vec_ir_value_find(self->m_living, value->m_memberof, nullptr))
+                    self->m_living.push_back(value->m_memberof);
+                for (mem = 0; mem < 3; ++mem) {
+                    if (value->m_members[mem] && !vec_ir_value_find(self->m_living, value->m_members[mem], nullptr))
+                        self->m_living.push_back(value->m_members[mem]);
+                }
+            }
+        }
+        /* PHI operands are always read operands */
+        for (auto &it : instr->m_phi) {
+            value = it.value;
+            if (!vec_ir_value_find(self->m_living, value, nullptr))
+                self->m_living.push_back(value);
+            /* reading adds the full vector */
+            if (value->m_memberof && !vec_ir_value_find(self->m_living, value->m_memberof, nullptr))
+                self->m_living.push_back(value->m_memberof);
+            for (mem = 0; mem < 3; ++mem) {
+                if (value->m_members[mem] && !vec_ir_value_find(self->m_living, value->m_members[mem], nullptr))
+                    self->m_living.push_back(value->m_members[mem]);
+            }
+        }
+
+        /* on a call, all these values must be "locked" */
+        if (instr->m_opcode >= INSTR_CALL0 && instr->m_opcode <= INSTR_CALL8) {
+            if (ir_block_living_lock(self))
+                *changed = true;
+        }
+        /* call params are read operands too */
+        for (auto &it : instr->m_params) {
+            value = it;
+            if (!vec_ir_value_find(self->m_living, value, nullptr))
+                self->m_living.push_back(value);
+            /* reading adds the full vector */
+            if (value->m_memberof && !vec_ir_value_find(self->m_living, value->m_memberof, nullptr))
+                self->m_living.push_back(value->m_memberof);
+            for (mem = 0; mem < 3; ++mem) {
+                if (value->m_members[mem] && !vec_ir_value_find(self->m_living, value->m_members[mem], nullptr))
+                    self->m_living.push_back(value->m_members[mem]);
+            }
+        }
+
+        /* (A) */
+        if (ir_block_living_add_instr(self, instr->m_eid))
+            *changed = true;
+    }
+    /* the "entry" instruction ID */
+    if (ir_block_living_add_instr(self, self->m_entry_id))
+        *changed = true;
+
+    return true;
+}
+
+bool ir_function_calculate_liferanges(ir_function *self)
+{
+    /* parameters live at 0 */
+    for (size_t i = 0; i < vec_size(self->m_params); ++i)
+        if (!self->m_locals[i].get()->setAlive(0))
+            compile_error(self->m_context, "internal error: failed value-life merging");
+
+    bool changed;
+    do {
+        self->m_run_id++;
+        changed = false;
+        for (auto i = self->m_blocks.rbegin(); i != self->m_blocks.rend(); ++i)
+            ir_block_life_propagate(i->get(), &changed);
+    } while (changed);
+
+    if (self->m_blocks.size()) {
+        ir_block *block = self->m_blocks[0].get();
+        for (auto &it : block->m_living) {
+            ir_value *v = it;
+            if (v->m_store != store_local)
+                continue;
+            if (v->m_vtype == TYPE_VECTOR)
+                continue;
+            self->m_flags |= IR_FLAG_HAS_UNINITIALIZED;
+            /* find the instruction reading from it */
+            size_t s = 0;
+            for (; s < v->m_reads.size(); ++s) {
+                if (v->m_reads[s]->m_eid == v->m_life[0].end)
+                    break;
+            }
+            if (s < v->m_reads.size()) {
+                if (irwarning(v->m_context, WARN_USED_UNINITIALIZED,
+                              "variable `%s` may be used uninitialized in this function\n"
+                              " -> %s:%i",
+                              v->m_name.c_str(),
+                              v->m_reads[s]->m_context.file, v->m_reads[s]->m_context.line)
+                   )
+                {
+                    return false;
+                }
+                continue;
+            }
+            if (v->m_memberof) {
+                ir_value *vec = v->m_memberof;
+                for (s = 0; s < vec->m_reads.size(); ++s) {
+                    if (vec->m_reads[s]->m_eid == v->m_life[0].end)
+                        break;
+                }
+                if (s < vec->m_reads.size()) {
+                    if (irwarning(v->m_context, WARN_USED_UNINITIALIZED,
+                                  "variable `%s` may be used uninitialized in this function\n"
+                                  " -> %s:%i",
+                                  v->m_name.c_str(),
+                                  vec->m_reads[s]->m_context.file, vec->m_reads[s]->m_context.line)
+                       )
+                    {
+                        return false;
+                    }
+                    continue;
+                }
+            }
+            if (irwarning(v->m_context, WARN_USED_UNINITIALIZED,
+                          "variable `%s` may be used uninitialized in this function", v->m_name.c_str()))
+            {
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
+/***********************************************************************
+ *IR Code-Generation
+ *
+ * Since the IR has the convention of putting 'write' operands
+ * at the beginning, we have to rotate the operands of instructions
+ * properly in order to generate valid QCVM code.
+ *
+ * Having destinations at a fixed position is more convenient. In QC
+ * this is *mostly* OPC,  but FTE adds at least 2 instructions which
+ * read from from OPA,  and store to OPB rather than OPC.   Which is
+ * partially the reason why the implementation of these instructions
+ * in darkplaces has been delayed for so long.
+ *
+ * Breaking conventions is annoying...
+ */
+static bool gen_global_field(code_t *code, ir_value *global)
+{
+    if (global->m_hasvalue)
+    {
+        ir_value *fld = global->m_constval.vpointer;
+        if (!fld) {
+            irerror(global->m_context, "Invalid field constant with no field: %s", global->m_name.c_str());
+            return false;
+        }
+
+        /* copy the field's value */
+        global->setCodeAddress(code->globals.size());
+        code->globals.push_back(fld->m_code.fieldaddr);
+        if (global->m_fieldtype == TYPE_VECTOR) {
+            code->globals.push_back(fld->m_code.fieldaddr+1);
+            code->globals.push_back(fld->m_code.fieldaddr+2);
+        }
+    }
+    else
+    {
+        global->setCodeAddress(code->globals.size());
+        code->globals.push_back(0);
+        if (global->m_fieldtype == TYPE_VECTOR) {
+            code->globals.push_back(0);
+            code->globals.push_back(0);
+        }
+    }
+    if (global->m_code.globaladdr < 0)
+        return false;
+    return true;
+}
+
+static bool gen_global_pointer(code_t *code, ir_value *global)
+{
+    if (global->m_hasvalue)
+    {
+        ir_value *target = global->m_constval.vpointer;
+        if (!target) {
+            irerror(global->m_context, "Invalid pointer constant: %s", global->m_name.c_str());
+            /* nullptr pointers are pointing to the nullptr constant, which also
+             * sits at address 0, but still has an ir_value for itself.
+             */
+            return false;
+        }
+
+        /* Here, relocations ARE possible - in fteqcc-enhanced-qc:
+         * void() foo; <- proto
+         * void() *fooptr = &foo;
+         * void() foo = { code }
+         */
+        if (!target->m_code.globaladdr) {
+            /* FIXME: Check for the constant nullptr ir_value!
+             * because then code.globaladdr being 0 is valid.
+             */
+            irerror(global->m_context, "FIXME: Relocation support");
+            return false;
+        }
+
+        global->setCodeAddress(code->globals.size());
+        code->globals.push_back(target->m_code.globaladdr);
+    }
+    else
+    {
+        global->setCodeAddress(code->globals.size());
+        code->globals.push_back(0);
+    }
+    if (global->m_code.globaladdr < 0)
+        return false;
+    return true;
+}
+
+static bool gen_blocks_recursive(code_t *code, ir_function *func, ir_block *block)
+{
+    prog_section_statement_t stmt;
+    ir_instr *instr;
+    ir_block *target;
+    ir_block *ontrue;
+    ir_block *onfalse;
+    size_t    stidx;
+    size_t    i;
+    int       j;
+
+    block->m_generated = true;
+    block->m_code_start = code->statements.size();
+    for (i = 0; i < vec_size(block->m_instr); ++i)
+    {
+        instr = block->m_instr[i];
+
+        if (instr->m_opcode == VINSTR_PHI) {
+            irerror(block->m_context, "cannot generate virtual instruction (phi)");
+            return false;
+        }
+
+        if (instr->m_opcode == VINSTR_JUMP) {
+            target = instr->m_bops[0];
+            /* for uncoditional jumps, if the target hasn't been generated
+             * yet, we generate them right here.
+             */
+            if (!target->m_generated)
+                return gen_blocks_recursive(code, func, target);
+
+            /* otherwise we generate a jump instruction */
+            stmt.opcode = INSTR_GOTO;
+            stmt.o1.s1 = target->m_code_start - code->statements.size();
+            stmt.o2.s1 = 0;
+            stmt.o3.s1 = 0;
+            if (stmt.o1.s1 != 1)
+                code_push_statement(code, &stmt, instr->m_context);
+
+            /* no further instructions can be in this block */
+            return true;
+        }
+
+        if (instr->m_opcode == VINSTR_BITXOR) {
+            stmt.opcode = INSTR_BITOR;
+            stmt.o1.s1 = instr->_m_ops[1]->codeAddress();
+            stmt.o2.s1 = instr->_m_ops[2]->codeAddress();
+            stmt.o3.s1 = instr->_m_ops[0]->codeAddress();
+            code_push_statement(code, &stmt, instr->m_context);
+            stmt.opcode = INSTR_BITAND;
+            stmt.o1.s1 = instr->_m_ops[1]->codeAddress();
+            stmt.o2.s1 = instr->_m_ops[2]->codeAddress();
+            stmt.o3.s1 = func->m_owner->m_vinstr_temp[0]->codeAddress();
+            code_push_statement(code, &stmt, instr->m_context);
+            stmt.opcode = INSTR_SUB_F;
+            stmt.o1.s1 = instr->_m_ops[0]->codeAddress();
+            stmt.o2.s1 = func->m_owner->m_vinstr_temp[0]->codeAddress();
+            stmt.o3.s1 = instr->_m_ops[0]->codeAddress();
+            code_push_statement(code, &stmt, instr->m_context);
+
+            /* instruction generated */
+            continue;
+        }
+
+        if (instr->m_opcode == VINSTR_BITAND_V) {
+            stmt.opcode = INSTR_BITAND;
+            stmt.o1.s1 = instr->_m_ops[1]->codeAddress();
+            stmt.o2.s1 = instr->_m_ops[2]->codeAddress();
+            stmt.o3.s1 = instr->_m_ops[0]->codeAddress();
+            code_push_statement(code, &stmt, instr->m_context);
+            ++stmt.o1.s1;
+            ++stmt.o2.s1;
+            ++stmt.o3.s1;
+            code_push_statement(code, &stmt, instr->m_context);
+            ++stmt.o1.s1;
+            ++stmt.o2.s1;
+            ++stmt.o3.s1;
+            code_push_statement(code, &stmt, instr->m_context);
+
+            /* instruction generated */
+            continue;
+        }
+
+        if (instr->m_opcode == VINSTR_BITOR_V) {
+            stmt.opcode = INSTR_BITOR;
+            stmt.o1.s1 = instr->_m_ops[1]->codeAddress();
+            stmt.o2.s1 = instr->_m_ops[2]->codeAddress();
+            stmt.o3.s1 = instr->_m_ops[0]->codeAddress();
+            code_push_statement(code, &stmt, instr->m_context);
+            ++stmt.o1.s1;
+            ++stmt.o2.s1;
+            ++stmt.o3.s1;
+            code_push_statement(code, &stmt, instr->m_context);
+            ++stmt.o1.s1;
+            ++stmt.o2.s1;
+            ++stmt.o3.s1;
+            code_push_statement(code, &stmt, instr->m_context);
+
+            /* instruction generated */
+            continue;
+        }
+
+        if (instr->m_opcode == VINSTR_BITXOR_V) {
+            for (j = 0; j < 3; ++j) {
+                stmt.opcode = INSTR_BITOR;
+                stmt.o1.s1 = instr->_m_ops[1]->codeAddress() + j;
+                stmt.o2.s1 = instr->_m_ops[2]->codeAddress() + j;
+                stmt.o3.s1 = instr->_m_ops[0]->codeAddress() + j;
+                code_push_statement(code, &stmt, instr->m_context);
+                stmt.opcode = INSTR_BITAND;
+                stmt.o1.s1 = instr->_m_ops[1]->codeAddress() + j;
+                stmt.o2.s1 = instr->_m_ops[2]->codeAddress() + j;
+                stmt.o3.s1 = func->m_owner->m_vinstr_temp[0]->codeAddress() + j;
+                code_push_statement(code, &stmt, instr->m_context);
+            }
+            stmt.opcode = INSTR_SUB_V;
+            stmt.o1.s1 = instr->_m_ops[0]->codeAddress();
+            stmt.o2.s1 = func->m_owner->m_vinstr_temp[0]->codeAddress();
+            stmt.o3.s1 = instr->_m_ops[0]->codeAddress();
+            code_push_statement(code, &stmt, instr->m_context);
+
+            /* instruction generated */
+            continue;
+        }
+
+        if (instr->m_opcode == VINSTR_BITAND_VF) {
+            stmt.opcode = INSTR_BITAND;
+            stmt.o1.s1 = instr->_m_ops[1]->codeAddress();
+            stmt.o2.s1 = instr->_m_ops[2]->codeAddress();
+            stmt.o3.s1 = instr->_m_ops[0]->codeAddress();
+            code_push_statement(code, &stmt, instr->m_context);
+            ++stmt.o1.s1;
+            ++stmt.o3.s1;
+            code_push_statement(code, &stmt, instr->m_context);
+            ++stmt.o1.s1;
+            ++stmt.o3.s1;
+            code_push_statement(code, &stmt, instr->m_context);
+
+            /* instruction generated */
+            continue;
+        }
+
+        if (instr->m_opcode == VINSTR_BITOR_VF) {
+            stmt.opcode = INSTR_BITOR;
+            stmt.o1.s1 = instr->_m_ops[1]->codeAddress();
+            stmt.o2.s1 = instr->_m_ops[2]->codeAddress();
+            stmt.o3.s1 = instr->_m_ops[0]->codeAddress();
+            code_push_statement(code, &stmt, instr->m_context);
+            ++stmt.o1.s1;
+            ++stmt.o3.s1;
+            code_push_statement(code, &stmt, instr->m_context);
+            ++stmt.o1.s1;
+            ++stmt.o3.s1;
+            code_push_statement(code, &stmt, instr->m_context);
+
+            /* instruction generated */
+            continue;
+        }
+
+        if (instr->m_opcode == VINSTR_BITXOR_VF) {
+            for (j = 0; j < 3; ++j) {
+                stmt.opcode = INSTR_BITOR;
+                stmt.o1.s1 = instr->_m_ops[1]->codeAddress() + j;
+                stmt.o2.s1 = instr->_m_ops[2]->codeAddress();
+                stmt.o3.s1 = instr->_m_ops[0]->codeAddress() + j;
+                code_push_statement(code, &stmt, instr->m_context);
+                stmt.opcode = INSTR_BITAND;
+                stmt.o1.s1 = instr->_m_ops[1]->codeAddress() + j;
+                stmt.o2.s1 = instr->_m_ops[2]->codeAddress();
+                stmt.o3.s1 = func->m_owner->m_vinstr_temp[0]->codeAddress() + j;
+                code_push_statement(code, &stmt, instr->m_context);
+            }
+            stmt.opcode = INSTR_SUB_V;
+            stmt.o1.s1 = instr->_m_ops[0]->codeAddress();
+            stmt.o2.s1 = func->m_owner->m_vinstr_temp[0]->codeAddress();
+            stmt.o3.s1 = instr->_m_ops[0]->codeAddress();
+            code_push_statement(code, &stmt, instr->m_context);
+
+            /* instruction generated */
+            continue;
+        }
+
+        if (instr->m_opcode == VINSTR_CROSS) {
+            stmt.opcode = INSTR_MUL_F;
+            for (j = 0; j < 3; ++j) {
+                stmt.o1.s1 = instr->_m_ops[1]->codeAddress() + (j + 1) % 3;
+                stmt.o2.s1 = instr->_m_ops[2]->codeAddress() + (j + 2) % 3;
+                stmt.o3.s1 = instr->_m_ops[0]->codeAddress() + j;
+                code_push_statement(code, &stmt, instr->m_context);
+                stmt.o1.s1 = instr->_m_ops[1]->codeAddress() + (j + 2) % 3;
+                stmt.o2.s1 = instr->_m_ops[2]->codeAddress() + (j + 1) % 3;
+                stmt.o3.s1 = func->m_owner->m_vinstr_temp[0]->codeAddress() + j;
+                code_push_statement(code, &stmt, instr->m_context);
+            }
+            stmt.opcode = INSTR_SUB_V;
+            stmt.o1.s1 = instr->_m_ops[0]->codeAddress();
+            stmt.o2.s1 = func->m_owner->m_vinstr_temp[0]->codeAddress();
+            stmt.o3.s1 = instr->_m_ops[0]->codeAddress();
+            code_push_statement(code, &stmt, instr->m_context);
+
+            /* instruction generated */
+            continue;
+        }
+
+        if (instr->m_opcode == VINSTR_COND) {
+            ontrue  = instr->m_bops[0];
+            onfalse = instr->m_bops[1];
+            /* TODO: have the AST signal which block should
+             * come first: eg. optimize IFs without ELSE...
+             */
+
+            stmt.o1.u1 = instr->_m_ops[0]->codeAddress();
+            stmt.o2.u1 = 0;
+            stmt.o3.s1 = 0;
+
+            if (ontrue->m_generated) {
+                stmt.opcode = INSTR_IF;
+                stmt.o2.s1 = ontrue->m_code_start - code->statements.size();
+                if (stmt.o2.s1 != 1)
+                    code_push_statement(code, &stmt, instr->m_context);
+            }
+            if (onfalse->m_generated) {
+                stmt.opcode = INSTR_IFNOT;
+                stmt.o2.s1 = onfalse->m_code_start - code->statements.size();
+                if (stmt.o2.s1 != 1)
+                    code_push_statement(code, &stmt, instr->m_context);
+            }
+            if (!ontrue->m_generated) {
+                if (onfalse->m_generated)
+                    return gen_blocks_recursive(code, func, ontrue);
+            }
+            if (!onfalse->m_generated) {
+                if (ontrue->m_generated)
+                    return gen_blocks_recursive(code, func, onfalse);
+            }
+            /* neither ontrue nor onfalse exist */
+            stmt.opcode = INSTR_IFNOT;
+            if (!instr->m_likely) {
+                /* Honor the likelyhood hint */
+                ir_block *tmp = onfalse;
+                stmt.opcode = INSTR_IF;
+                onfalse = ontrue;
+                ontrue = tmp;
+            }
+            stidx = code->statements.size();
+            code_push_statement(code, &stmt, instr->m_context);
+            /* on false we jump, so add ontrue-path */
+            if (!gen_blocks_recursive(code, func, ontrue))
+                return false;
+            /* fixup the jump address */
+            code->statements[stidx].o2.s1 = code->statements.size() - stidx;
+            /* generate onfalse path */
+            if (onfalse->m_generated) {
+                /* fixup the jump address */
+                code->statements[stidx].o2.s1 = onfalse->m_code_start - stidx;
+                if (stidx+2 == code->statements.size() && code->statements[stidx].o2.s1 == 1) {
+                    code->statements[stidx] = code->statements[stidx+1];
+                    if (code->statements[stidx].o1.s1 < 0)
+                        code->statements[stidx].o1.s1++;
+                    code_pop_statement(code);
+                }
+                stmt.opcode = code->statements.back().opcode;
+                if (stmt.opcode == INSTR_GOTO ||
+                    stmt.opcode == INSTR_IF ||
+                    stmt.opcode == INSTR_IFNOT ||
+                    stmt.opcode == INSTR_RETURN ||
+                    stmt.opcode == INSTR_DONE)
+                {
+                    /* no use jumping from here */
+                    return true;
+                }
+                /* may have been generated in the previous recursive call */
+                stmt.opcode = INSTR_GOTO;
+                stmt.o1.s1 = onfalse->m_code_start - code->statements.size();
+                stmt.o2.s1 = 0;
+                stmt.o3.s1 = 0;
+                if (stmt.o1.s1 != 1)
+                    code_push_statement(code, &stmt, instr->m_context);
+                return true;
+            }
+            else if (stidx+2 == code->statements.size() && code->statements[stidx].o2.s1 == 1) {
+                code->statements[stidx] = code->statements[stidx+1];
+                if (code->statements[stidx].o1.s1 < 0)
+                    code->statements[stidx].o1.s1++;
+                code_pop_statement(code);
+            }
+            /* if not, generate now */
+            return gen_blocks_recursive(code, func, onfalse);
+        }
+
+        if ( (instr->m_opcode >= INSTR_CALL0 && instr->m_opcode <= INSTR_CALL8)
+           || instr->m_opcode == VINSTR_NRCALL)
+        {
+            size_t p, first;
+            ir_value *retvalue;
+
+            first = instr->m_params.size();
+            if (first > 8)
+                first = 8;
+            for (p = 0; p < first; ++p)
+            {
+                ir_value *param = instr->m_params[p];
+                if (param->m_callparam)
+                    continue;
+
+                stmt.opcode = INSTR_STORE_F;
+                stmt.o3.u1 = 0;
+
+                if (param->m_vtype == TYPE_FIELD)
+                    stmt.opcode = field_store_instr[param->m_fieldtype];
+                else if (param->m_vtype == TYPE_NIL)
+                    stmt.opcode = INSTR_STORE_V;
+                else
+                    stmt.opcode = type_store_instr[param->m_vtype];
+                stmt.o1.u1 = param->codeAddress();
+                stmt.o2.u1 = OFS_PARM0 + 3 * p;
+
+                if (param->m_vtype == TYPE_VECTOR && (param->m_flags & IR_FLAG_SPLIT_VECTOR)) {
+                    /* fetch 3 separate floats */
+                    stmt.opcode = INSTR_STORE_F;
+                    stmt.o1.u1 = param->m_members[0]->codeAddress();
+                    code_push_statement(code, &stmt, instr->m_context);
+                    stmt.o2.u1++;
+                    stmt.o1.u1 = param->m_members[1]->codeAddress();
+                    code_push_statement(code, &stmt, instr->m_context);
+                    stmt.o2.u1++;
+                    stmt.o1.u1 = param->m_members[2]->codeAddress();
+                    code_push_statement(code, &stmt, instr->m_context);
+                }
+                else
+                    code_push_statement(code, &stmt, instr->m_context);
+            }
+            /* Now handle extparams */
+            first = instr->m_params.size();
+            for (; p < first; ++p)
+            {
+                ir_builder *ir = func->m_owner;
+                ir_value *param = instr->m_params[p];
+                ir_value *targetparam;
+
+                if (param->m_callparam)
+                    continue;
+
+                if (p-8 >= ir->m_extparams.size())
+                    ir->generateExtparam();
+
+                targetparam = ir->m_extparams[p-8];
+
+                stmt.opcode = INSTR_STORE_F;
+                stmt.o3.u1 = 0;
+
+                if (param->m_vtype == TYPE_FIELD)
+                    stmt.opcode = field_store_instr[param->m_fieldtype];
+                else if (param->m_vtype == TYPE_NIL)
+                    stmt.opcode = INSTR_STORE_V;
+                else
+                    stmt.opcode = type_store_instr[param->m_vtype];
+                stmt.o1.u1 = param->codeAddress();
+                stmt.o2.u1 = targetparam->codeAddress();
+                if (param->m_vtype == TYPE_VECTOR && (param->m_flags & IR_FLAG_SPLIT_VECTOR)) {
+                    /* fetch 3 separate floats */
+                    stmt.opcode = INSTR_STORE_F;
+                    stmt.o1.u1 = param->m_members[0]->codeAddress();
+                    code_push_statement(code, &stmt, instr->m_context);
+                    stmt.o2.u1++;
+                    stmt.o1.u1 = param->m_members[1]->codeAddress();
+                    code_push_statement(code, &stmt, instr->m_context);
+                    stmt.o2.u1++;
+                    stmt.o1.u1 = param->m_members[2]->codeAddress();
+                    code_push_statement(code, &stmt, instr->m_context);
+                }
+                else
+                    code_push_statement(code, &stmt, instr->m_context);
+            }
+
+            stmt.opcode = INSTR_CALL0 + instr->m_params.size();
+            if (stmt.opcode > INSTR_CALL8)
+                stmt.opcode = INSTR_CALL8;
+            stmt.o1.u1 = instr->_m_ops[1]->codeAddress();
+            stmt.o2.u1 = 0;
+            stmt.o3.u1 = 0;
+            code_push_statement(code, &stmt, instr->m_context);
+
+            retvalue = instr->_m_ops[0];
+            if (retvalue && retvalue->m_store != store_return &&
+                (retvalue->m_store == store_global || retvalue->m_life.size()))
+            {
+                /* not to be kept in OFS_RETURN */
+                if (retvalue->m_vtype == TYPE_FIELD && OPTS_FLAG(ADJUST_VECTOR_FIELDS))
+                    stmt.opcode = field_store_instr[retvalue->m_fieldtype];
+                else
+                    stmt.opcode = type_store_instr[retvalue->m_vtype];
+                stmt.o1.u1 = OFS_RETURN;
+                stmt.o2.u1 = retvalue->codeAddress();
+                stmt.o3.u1 = 0;
+                code_push_statement(code, &stmt, instr->m_context);
+            }
+            continue;
+        }
+
+        if (instr->m_opcode == INSTR_STATE) {
+            stmt.opcode = instr->m_opcode;
+            if (instr->_m_ops[0])
+                stmt.o1.u1 = instr->_m_ops[0]->codeAddress();
+            if (instr->_m_ops[1])
+                stmt.o2.u1 = instr->_m_ops[1]->codeAddress();
+            stmt.o3.u1 = 0;
+            code_push_statement(code, &stmt, instr->m_context);
+            continue;
+        }
+
+        stmt.opcode = instr->m_opcode;
+        stmt.o1.u1 = 0;
+        stmt.o2.u1 = 0;
+        stmt.o3.u1 = 0;
+
+        /* This is the general order of operands */
+        if (instr->_m_ops[0])
+            stmt.o3.u1 = instr->_m_ops[0]->codeAddress();
+
+        if (instr->_m_ops[1])
+            stmt.o1.u1 = instr->_m_ops[1]->codeAddress();
+
+        if (instr->_m_ops[2])
+            stmt.o2.u1 = instr->_m_ops[2]->codeAddress();
+
+        if (stmt.opcode == INSTR_RETURN || stmt.opcode == INSTR_DONE)
+        {
+            stmt.o1.u1 = stmt.o3.u1;
+            stmt.o3.u1 = 0;
+        }
+        else if ((stmt.opcode >= INSTR_STORE_F &&
+                  stmt.opcode <= INSTR_STORE_FNC) ||
+                 (stmt.opcode >= INSTR_STOREP_F &&
+                  stmt.opcode <= INSTR_STOREP_FNC))
+        {
+            /* 2-operand instructions with A -> B */
+            stmt.o2.u1 = stmt.o3.u1;
+            stmt.o3.u1 = 0;
+
+            /* tiny optimization, don't output
+             * STORE a, a
+             */
+            if (stmt.o2.u1 == stmt.o1.u1 &&
+                OPTS_OPTIMIZATION(OPTIM_PEEPHOLE))
+            {
+                ++opts_optimizationcount[OPTIM_PEEPHOLE];
+                continue;
+            }
+        }
+        code_push_statement(code, &stmt, instr->m_context);
+    }
+    return true;
+}
+
+static bool gen_function_code(code_t *code, ir_function *self)
+{
+    ir_block *block;
+    prog_section_statement_t stmt, *retst;
+
+    /* Starting from entry point, we generate blocks "as they come"
+     * for now. Dead blocks will not be translated obviously.
+     */
+    if (self->m_blocks.empty()) {
+        irerror(self->m_context, "Function '%s' declared without body.", self->m_name.c_str());
+        return false;
+    }
+
+    block = self->m_blocks[0].get();
+    if (block->m_generated)
+        return true;
+
+    if (!gen_blocks_recursive(code, self, block)) {
+        irerror(self->m_context, "failed to generate blocks for '%s'", self->m_name.c_str());
+        return false;
+    }
+
+    /* code_write and qcvm -disasm need to know that the function ends here */
+    retst = &code->statements.back();
+    if (OPTS_OPTIMIZATION(OPTIM_VOID_RETURN) &&
+        self->m_outtype == TYPE_VOID &&
+        retst->opcode == INSTR_RETURN &&
+        !retst->o1.u1 && !retst->o2.u1 && !retst->o3.u1)
+    {
+        retst->opcode = INSTR_DONE;
+        ++opts_optimizationcount[OPTIM_VOID_RETURN];
+    } else {
+        lex_ctx_t last;
+
+        stmt.opcode = INSTR_DONE;
+        stmt.o1.u1  = 0;
+        stmt.o2.u1  = 0;
+        stmt.o3.u1  = 0;
+        last.line   = code->linenums.back();
+        last.column = code->columnnums.back();
+
+        code_push_statement(code, &stmt, last);
+    }
+    return true;
+}
+
+qcint_t ir_builder::filestring(const char *filename)
+{
+    /* NOTE: filename pointers are copied, we never strdup them,
+     * thus we can use pointer-comparison to find the string.
+     */
+    qcint_t  str;
+
+    for (size_t i = 0; i != m_filenames.size(); ++i) {
+        if (!strcmp(m_filenames[i], filename))
+            return i;
+    }
+
+    str = code_genstring(m_code.get(), filename);
+    m_filenames.push_back(filename);
+    m_filestrings.push_back(str);
+    return str;
+}
+
+bool ir_builder::generateGlobalFunction(ir_value *global)
+{
+    prog_section_function_t fun;
+    ir_function            *irfun;
+
+    size_t i;
+
+    if (!global->m_hasvalue || (!global->m_constval.vfunc)) {
+        irerror(global->m_context, "Invalid state of function-global: not constant: %s", global->m_name.c_str());
+        return false;
+    }
+
+    irfun = global->m_constval.vfunc;
+    fun.name = global->m_code.name;
+    fun.file = filestring(global->m_context.file);
+    fun.profile = 0; /* always 0 */
+    fun.nargs = vec_size(irfun->m_params);
+    if (fun.nargs > 8)
+        fun.nargs = 8;
+
+    for (i = 0; i < 8; ++i) {
+        if ((int32_t)i >= fun.nargs)
+            fun.argsize[i] = 0;
+        else
+            fun.argsize[i] = type_sizeof_[irfun->m_params[i]];
+    }
+
+    fun.firstlocal = 0;
+    fun.locals = irfun->m_allocated_locals;
+
+    if (irfun->m_builtin)
+        fun.entry = irfun->m_builtin+1;
+    else {
+        irfun->m_code_function_def = m_code->functions.size();
+        fun.entry = m_code->statements.size();
+    }
+
+    m_code->functions.push_back(fun);
+    return true;
+}
+
+ir_value* ir_builder::generateExtparamProto()
+{
+    char      name[128];
+
+    util_snprintf(name, sizeof(name), "EXTPARM#%i", (int)(m_extparam_protos.size()));
+    ir_value *global = new ir_value(name, store_global, TYPE_VECTOR);
+    m_extparam_protos.emplace_back(global);
+
+    return global;
+}
+
+void ir_builder::generateExtparam()
+{
+    prog_section_def_t def;
+    ir_value          *global;
+
+    if (m_extparam_protos.size() < m_extparams.size()+1)
+        global = generateExtparamProto();
+    else
+        global = m_extparam_protos[m_extparams.size()].get();
+
+    def.name = code_genstring(m_code.get(), global->m_name.c_str());
+    def.type = TYPE_VECTOR;
+    def.offset = m_code->globals.size();
+
+    m_code->defs.push_back(def);
+
+    global->setCodeAddress(def.offset);
+
+    m_code->globals.push_back(0);
+    m_code->globals.push_back(0);
+    m_code->globals.push_back(0);
+
+    m_extparams.emplace_back(global);
+}
+
+static bool gen_function_extparam_copy(code_t *code, ir_function *self)
+{
+    ir_builder *ir = self->m_owner;
+
+    size_t numparams = vec_size(self->m_params);
+    if (!numparams)
+        return true;
+
+    prog_section_statement_t stmt;
+    stmt.opcode = INSTR_STORE_F;
+    stmt.o3.s1 = 0;
+    for (size_t i = 8; i < numparams; ++i) {
+        size_t ext = i - 8;
+        if (ext >= ir->m_extparams.size())
+            ir->generateExtparam();
+
+        ir_value *ep = ir->m_extparams[ext];
+
+        stmt.opcode = type_store_instr[self->m_locals[i]->m_vtype];
+        if (self->m_locals[i]->m_vtype == TYPE_FIELD &&
+            self->m_locals[i]->m_fieldtype == TYPE_VECTOR)
+        {
+            stmt.opcode = INSTR_STORE_V;
+        }
+        stmt.o1.u1 = ep->codeAddress();
+        stmt.o2.u1 = self->m_locals[i].get()->codeAddress();
+        code_push_statement(code, &stmt, self->m_context);
+    }
+
+    return true;
+}
+
+static bool gen_function_varargs_copy(code_t *code, ir_function *self)
+{
+    size_t i, ext, numparams, maxparams;
+
+    ir_builder *ir = self->m_owner;
+    ir_value   *ep;
+    prog_section_statement_t stmt;
+
+    numparams = vec_size(self->m_params);
+    if (!numparams)
+        return true;
+
+    stmt.opcode = INSTR_STORE_V;
+    stmt.o3.s1 = 0;
+    maxparams = numparams + self->m_max_varargs;
+    for (i = numparams; i < maxparams; ++i) {
+        if (i < 8) {
+            stmt.o1.u1 = OFS_PARM0 + 3*i;
+            stmt.o2.u1 = self->m_locals[i].get()->codeAddress();
+            code_push_statement(code, &stmt, self->m_context);
+            continue;
+        }
+        ext = i - 8;
+        while (ext >= ir->m_extparams.size())
+            ir->generateExtparam();
+
+        ep = ir->m_extparams[ext];
+
+        stmt.o1.u1 = ep->codeAddress();
+        stmt.o2.u1 = self->m_locals[i].get()->codeAddress();
+        code_push_statement(code, &stmt, self->m_context);
+    }
+
+    return true;
+}
+
+bool ir_builder::generateFunctionLocals(ir_value *global)
+{
+    prog_section_function_t *def;
+    ir_function             *irfun;
+    uint32_t                 firstlocal, firstglobal;
+
+    irfun = global->m_constval.vfunc;
+    def   = &m_code->functions[0] + irfun->m_code_function_def;
+
+    if (OPTS_OPTION_BOOL(OPTION_G) ||
+        !OPTS_OPTIMIZATION(OPTIM_OVERLAP_LOCALS)        ||
+        (irfun->m_flags & IR_FLAG_MASK_NO_OVERLAP))
+    {
+        firstlocal = def->firstlocal = m_code->globals.size();
+    } else {
+        firstlocal = def->firstlocal = m_first_common_local;
+        ++opts_optimizationcount[OPTIM_OVERLAP_LOCALS];
+    }
+
+    firstglobal = (OPTS_OPTIMIZATION(OPTIM_GLOBAL_TEMPS) ? m_first_common_globaltemp : firstlocal);
+
+    for (size_t i = m_code->globals.size(); i < firstlocal + irfun->m_allocated_locals; ++i)
+        m_code->globals.push_back(0);
+
+    for (auto& lp : irfun->m_locals) {
+        ir_value *v = lp.get();
+        if (v->m_locked || !OPTS_OPTIMIZATION(OPTIM_GLOBAL_TEMPS)) {
+            v->setCodeAddress(firstlocal + v->m_code.local);
+            if (!generateGlobal(v, true)) {
+                irerror(v->m_context, "failed to generate local %s", v->m_name.c_str());
+                return false;
+            }
+        }
+        else
+            v->setCodeAddress(firstglobal + v->m_code.local);
+    }
+    for (auto& vp : irfun->m_values) {
+        ir_value *v = vp.get();
+        if (v->m_callparam)
+            continue;
+        if (v->m_locked)
+            v->setCodeAddress(firstlocal + v->m_code.local);
+        else
+            v->setCodeAddress(firstglobal + v->m_code.local);
+    }
+    return true;
+}
+
+bool ir_builder::generateGlobalFunctionCode(ir_value *global)
+{
+    prog_section_function_t *fundef;
+    ir_function             *irfun;
+
+    irfun = global->m_constval.vfunc;
+    if (!irfun) {
+        if (global->m_cvq == CV_NONE) {
+            if (irwarning(global->m_context, WARN_IMPLICIT_FUNCTION_POINTER,
+                          "function `%s` has no body and in QC implicitly becomes a function-pointer",
+                          global->m_name.c_str()))
+            {
+                /* Not bailing out just now. If this happens a lot you don't want to have
+                 * to rerun gmqcc for each such function.
+                 */
+
+                /* return false; */
+            }
+        }
+        /* this was a function pointer, don't generate code for those */
+        return true;
+    }
+
+    if (irfun->m_builtin)
+        return true;
+
+    /*
+     * If there is no definition and the thing is eraseable, we can ignore
+     * outputting the function to begin with.
+     */
+    if (global->m_flags & IR_FLAG_ERASABLE && irfun->m_code_function_def < 0) {
+        return true;
+    }
+
+    if (irfun->m_code_function_def < 0) {
+        irerror(irfun->m_context, "`%s`: IR global wasn't generated, failed to access function-def", irfun->m_name.c_str());
+        return false;
+    }
+    fundef = &m_code->functions[irfun->m_code_function_def];
+
+    fundef->entry = m_code->statements.size();
+    if (!generateFunctionLocals(global)) {
+        irerror(irfun->m_context, "Failed to generate locals for function %s", irfun->m_name.c_str());
+        return false;
+    }
+    if (!gen_function_extparam_copy(m_code.get(), irfun)) {
+        irerror(irfun->m_context, "Failed to generate extparam-copy code for function %s", irfun->m_name.c_str());
+        return false;
+    }
+    if (irfun->m_max_varargs && !gen_function_varargs_copy(m_code.get(), irfun)) {
+        irerror(irfun->m_context, "Failed to generate vararg-copy code for function %s", irfun->m_name.c_str());
+        return false;
+    }
+    if (!gen_function_code(m_code.get(), irfun)) {
+        irerror(irfun->m_context, "Failed to generate code for function %s", irfun->m_name.c_str());
+        return false;
+    }
+    return true;
+}
+
+static void gen_vector_defs(code_t *code, prog_section_def_t def, const char *name)
+{
+    char  *component;
+    size_t len, i;
+
+    if (!name || name[0] == '#' || OPTS_FLAG(SINGLE_VECTOR_DEFS))
+        return;
+
+    def.type = TYPE_FLOAT;
+
+    len = strlen(name);
+
+    component = (char*)mem_a(len+3);
+    memcpy(component, name, len);
+    len += 2;
+    component[len-0] = 0;
+    component[len-2] = '_';
+
+    component[len-1] = 'x';
+
+    for (i = 0; i < 3; ++i) {
+        def.name = code_genstring(code, component);
+        code->defs.push_back(def);
+        def.offset++;
+        component[len-1]++;
+    }
+
+    mem_d(component);
+}
+
+static void gen_vector_fields(code_t *code, prog_section_field_t fld, const char *name)
+{
+    char  *component;
+    size_t len, i;
+
+    if (!name || OPTS_FLAG(SINGLE_VECTOR_DEFS))
+        return;
+
+    fld.type = TYPE_FLOAT;
+
+    len = strlen(name);
+
+    component = (char*)mem_a(len+3);
+    memcpy(component, name, len);
+    len += 2;
+    component[len-0] = 0;
+    component[len-2] = '_';
+
+    component[len-1] = 'x';
+
+    for (i = 0; i < 3; ++i) {
+        fld.name = code_genstring(code, component);
+        code->fields.push_back(fld);
+        fld.offset++;
+        component[len-1]++;
+    }
+
+    mem_d(component);
+}
+
+bool ir_builder::generateGlobal(ir_value *global, bool islocal)
+{
+    size_t             i;
+    int32_t           *iptr;
+    prog_section_def_t def;
+    bool               pushdef = opts.optimizeoff;
+
+    /* we don't generate split-vectors */
+    if (global->m_vtype == TYPE_VECTOR && (global->m_flags & IR_FLAG_SPLIT_VECTOR))
+        return true;
+
+    def.type = global->m_vtype;
+    def.offset = m_code->globals.size();
+    def.name = 0;
+    if (OPTS_OPTION_BOOL(OPTION_G) || !islocal)
+    {
+        pushdef = true;
+
+        /*
+         * if we're eraseable and the function isn't referenced ignore outputting
+         * the function.
+         */
+        if (global->m_flags & IR_FLAG_ERASABLE && global->m_reads.empty()) {
+            return true;
+        }
+
+        if (OPTS_OPTIMIZATION(OPTIM_STRIP_CONSTANT_NAMES) &&
+            !(global->m_flags & IR_FLAG_INCLUDE_DEF) &&
+            (global->m_name[0] == '#' || global->m_cvq == CV_CONST))
+        {
+            pushdef = false;
+        }
+
+        if (pushdef) {
+            if (global->m_name[0] == '#') {
+                if (!m_str_immediate)
+                    m_str_immediate = code_genstring(m_code.get(), "IMMEDIATE");
+                def.name = global->m_code.name = m_str_immediate;
+            }
+            else
+                def.name = global->m_code.name = code_genstring(m_code.get(), global->m_name.c_str());
+        }
+        else
+            def.name   = 0;
+        if (islocal) {
+            def.offset = global->codeAddress();
+            m_code->defs.push_back(def);
+            if (global->m_vtype == TYPE_VECTOR)
+                gen_vector_defs(m_code.get(), def, global->m_name.c_str());
+            else if (global->m_vtype == TYPE_FIELD && global->m_fieldtype == TYPE_VECTOR)
+                gen_vector_defs(m_code.get(), def, global->m_name.c_str());
+            return true;
+        }
+    }
+    if (islocal)
+        return true;
+
+    switch (global->m_vtype)
+    {
+    case TYPE_VOID:
+        if (0 == global->m_name.compare("end_sys_globals")) {
+            // TODO: remember this point... all the defs before this one
+            // should be checksummed and added to progdefs.h when we generate it.
+        }
+        else if (0 == global->m_name.compare("end_sys_fields")) {
+            // TODO: same as above but for entity-fields rather than globsl
+        }
+        else if(irwarning(global->m_context, WARN_VOID_VARIABLES, "unrecognized variable of type void `%s`",
+                          global->m_name.c_str()))
+        {
+            /* Not bailing out */
+            /* return false; */
+        }
+        /* I'd argue setting it to 0 is sufficient, but maybe some depend on knowing how far
+         * the system fields actually go? Though the engine knows this anyway...
+         * Maybe this could be an -foption
+         * fteqcc creates data for end_sys_* - of size 1, so let's do the same
+         */
+        global->setCodeAddress(m_code->globals.size());
+        m_code->globals.push_back(0);
+        /* Add the def */
+        if (pushdef)
+            m_code->defs.push_back(def);
+        return true;
+    case TYPE_POINTER:
+        if (pushdef)
+            m_code->defs.push_back(def);
+        return gen_global_pointer(m_code.get(), global);
+    case TYPE_FIELD:
+        if (pushdef) {
+            m_code->defs.push_back(def);
+            if (global->m_fieldtype == TYPE_VECTOR)
+                gen_vector_defs(m_code.get(), def, global->m_name.c_str());
+        }
+        return gen_global_field(m_code.get(), global);
+    case TYPE_ENTITY:
+        /* fall through */
+    case TYPE_FLOAT:
+    {
+        global->setCodeAddress(m_code->globals.size());
+        if (global->m_hasvalue) {
+            if (global->m_cvq == CV_CONST && global->m_reads.empty())
+                return true;
+            iptr = (int32_t*)&global->m_constval.ivec[0];
+            m_code->globals.push_back(*iptr);
+        } else {
+            m_code->globals.push_back(0);
+        }
+        if (!islocal && global->m_cvq != CV_CONST)
+            def.type |= DEF_SAVEGLOBAL;
+        if (pushdef)
+            m_code->defs.push_back(def);
+
+        return global->m_code.globaladdr >= 0;
+    }
+    case TYPE_STRING:
+    {
+        global->setCodeAddress(m_code->globals.size());
+        if (global->m_hasvalue) {
+            if (global->m_cvq == CV_CONST && global->m_reads.empty())
+                return true;
+            uint32_t load = code_genstring(m_code.get(), global->m_constval.vstring);
+            m_code->globals.push_back(load);
+        } else {
+            m_code->globals.push_back(0);
+        }
+        if (!islocal && global->m_cvq != CV_CONST)
+            def.type |= DEF_SAVEGLOBAL;
+        if (pushdef)
+            m_code->defs.push_back(def);
+        return global->m_code.globaladdr >= 0;
+    }
+    case TYPE_VECTOR:
+    {
+        size_t d;
+        global->setCodeAddress(m_code->globals.size());
+        if (global->m_hasvalue) {
+            iptr = (int32_t*)&global->m_constval.ivec[0];
+            m_code->globals.push_back(iptr[0]);
+            if (global->m_code.globaladdr < 0)
+                return false;
+            for (d = 1; d < type_sizeof_[global->m_vtype]; ++d) {
+                m_code->globals.push_back(iptr[d]);
+            }
+        } else {
+            m_code->globals.push_back(0);
+            if (global->m_code.globaladdr < 0)
+                return false;
+            for (d = 1; d < type_sizeof_[global->m_vtype]; ++d) {
+                m_code->globals.push_back(0);
+            }
+        }
+        if (!islocal && global->m_cvq != CV_CONST)
+            def.type |= DEF_SAVEGLOBAL;
+
+        if (pushdef) {
+            m_code->defs.push_back(def);
+            def.type &= ~DEF_SAVEGLOBAL;
+            gen_vector_defs(m_code.get(), def, global->m_name.c_str());
+        }
+        return global->m_code.globaladdr >= 0;
+    }
+    case TYPE_FUNCTION:
+        global->setCodeAddress(m_code->globals.size());
+        if (!global->m_hasvalue) {
+            m_code->globals.push_back(0);
+            if (global->m_code.globaladdr < 0)
+                return false;
+        } else {
+            m_code->globals.push_back(m_code->functions.size());
+            if (!generateGlobalFunction(global))
+                return false;
+        }
+        if (!islocal && global->m_cvq != CV_CONST)
+            def.type |= DEF_SAVEGLOBAL;
+        if (pushdef)
+            m_code->defs.push_back(def);
+        return true;
+    case TYPE_VARIANT:
+        /* assume biggest type */
+            global->setCodeAddress(m_code->globals.size());
+            m_code->globals.push_back(0);
+            for (i = 1; i < type_sizeof_[TYPE_VARIANT]; ++i)
+                m_code->globals.push_back(0);
+            return true;
+    default:
+        /* refuse to create 'void' type or any other fancy business. */
+        irerror(global->m_context, "Invalid type for global variable `%s`: %s",
+                global->m_name.c_str(), type_name[global->m_vtype]);
+        return false;
+    }
+}
+
+static GMQCC_INLINE void ir_builder_prepare_field(code_t *code, ir_value *field)
+{
+    field->m_code.fieldaddr = code_alloc_field(code, type_sizeof_[field->m_fieldtype]);
+}
+
+static bool ir_builder_gen_field(ir_builder *self, ir_value *field)
+{
+    prog_section_def_t def;
+    prog_section_field_t fld;
+
+    (void)self;
+
+    def.type   = (uint16_t)field->m_vtype;
+    def.offset = (uint16_t)self->m_code->globals.size();
+
+    /* create a global named the same as the field */
+    if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_GMQCC) {
+        /* in our standard, the global gets a dot prefix */
+        size_t len = field->m_name.length();
+        char name[1024];
+
+        /* we really don't want to have to allocate this, and 1024
+         * bytes is more than enough for a variable/field name
+         */
+        if (len+2 >= sizeof(name)) {
+            irerror(field->m_context, "invalid field name size: %u", (unsigned int)len);
+            return false;
+        }
+
+        name[0] = '.';
+        memcpy(name+1, field->m_name.c_str(), len); // no strncpy - we used strlen above
+        name[len+1] = 0;
+
+        def.name = code_genstring(self->m_code.get(), name);
+        fld.name = def.name + 1; /* we reuse that string table entry */
+    } else {
+        /* in plain QC, there cannot be a global with the same name,
+         * and so we also name the global the same.
+         * FIXME: fteqcc should create a global as well
+         * check if it actually uses the same name. Probably does
+         */
+        def.name = code_genstring(self->m_code.get(), field->m_name.c_str());
+        fld.name = def.name;
+    }
+
+    field->m_code.name = def.name;
+
+    self->m_code->defs.push_back(def);
+
+    fld.type = field->m_fieldtype;
+
+    if (fld.type == TYPE_VOID) {
+        irerror(field->m_context, "field is missing a type: %s - don't know its size", field->m_name.c_str());
+        return false;
+    }
+
+    fld.offset = field->m_code.fieldaddr;
+
+    self->m_code->fields.push_back(fld);
+
+    field->setCodeAddress(self->m_code->globals.size());
+    self->m_code->globals.push_back(fld.offset);
+    if (fld.type == TYPE_VECTOR) {
+        self->m_code->globals.push_back(fld.offset+1);
+        self->m_code->globals.push_back(fld.offset+2);
+    }
+
+    if (field->m_fieldtype == TYPE_VECTOR) {
+        gen_vector_defs  (self->m_code.get(), def, field->m_name.c_str());
+        gen_vector_fields(self->m_code.get(), fld, field->m_name.c_str());
+    }
+
+    return field->m_code.globaladdr >= 0;
+}
+
+static void ir_builder_collect_reusables(ir_builder *builder) {
+    std::vector<ir_value*> reusables;
+
+    for (auto& gp : builder->m_globals) {
+        ir_value *value = gp.get();
+        if (value->m_vtype != TYPE_FLOAT || !value->m_hasvalue)
+            continue;
+        if (value->m_cvq == CV_CONST || (value->m_name.length() >= 1 && value->m_name[0] == '#'))
+            reusables.emplace_back(value);
+    }
+    builder->m_const_floats = move(reusables);
+}
+
+static void ir_builder_split_vector(ir_builder *self, ir_value *vec) {
+    ir_value* found[3] = { nullptr, nullptr, nullptr };
+
+    // must not be written to
+    if (vec->m_writes.size())
+        return;
+    // must not be trying to access individual members
+    if (vec->m_members[0] || vec->m_members[1] || vec->m_members[2])
+        return;
+    // should be actually used otherwise it won't be generated anyway
+    if (vec->m_reads.empty())
+        return;
+    //size_t count = vec->m_reads.size();
+    //if (!count)
+    //    return;
+
+    // may only be used directly as function parameters, so if we find some other instruction cancel
+    for (ir_instr *user : vec->m_reads) {
+        // we only split vectors if they're used directly as parameter to a call only!
+        if ((user->m_opcode < INSTR_CALL0 || user->m_opcode > INSTR_CALL8) && user->m_opcode != VINSTR_NRCALL)
+            return;
+    }
+
+    vec->m_flags |= IR_FLAG_SPLIT_VECTOR;
+
+    // find existing floats making up the split
+    for (ir_value *c : self->m_const_floats) {
+        if (!found[0] && c->m_constval.vfloat == vec->m_constval.vvec.x)
+            found[0] = c;
+        if (!found[1] && c->m_constval.vfloat == vec->m_constval.vvec.y)
+            found[1] = c;
+        if (!found[2] && c->m_constval.vfloat == vec->m_constval.vvec.z)
+            found[2] = c;
+        if (found[0] && found[1] && found[2])
+            break;
+    }
+
+    // generate floats for not yet found components
+    if (!found[0])
+        found[0] = self->literalFloat(vec->m_constval.vvec.x, true);
+    if (!found[1]) {
+        if (vec->m_constval.vvec.y == vec->m_constval.vvec.x)
+            found[1] = found[0];
+        else
+            found[1] = self->literalFloat(vec->m_constval.vvec.y, true);
+    }
+    if (!found[2]) {
+        if (vec->m_constval.vvec.z == vec->m_constval.vvec.x)
+            found[2] = found[0];
+        else if (vec->m_constval.vvec.z == vec->m_constval.vvec.y)
+            found[2] = found[1];
+        else
+            found[2] = self->literalFloat(vec->m_constval.vvec.z, true);
+    }
+
+    // the .members array should be safe to use here
+    vec->m_members[0] = found[0];
+    vec->m_members[1] = found[1];
+    vec->m_members[2] = found[2];
+
+    // register the readers for these floats
+    found[0]->m_reads.insert(found[0]->m_reads.end(), vec->m_reads.begin(), vec->m_reads.end());
+    found[1]->m_reads.insert(found[1]->m_reads.end(), vec->m_reads.begin(), vec->m_reads.end());
+    found[2]->m_reads.insert(found[2]->m_reads.end(), vec->m_reads.begin(), vec->m_reads.end());
+}
+
+static void ir_builder_split_vectors(ir_builder *self) {
+    // member values may be added to self->m_globals during this operation, but
+    // no new vectors will be added, we need to iterate via an index as
+    // c++ iterators would be invalidated
+    const size_t count = self->m_globals.size();
+    for (size_t i = 0; i != count; ++i) {
+        ir_value *v = self->m_globals[i].get();
+        if (v->m_vtype != TYPE_VECTOR || !v->m_name.length() || v->m_name[0] != '#')
+            continue;
+        ir_builder_split_vector(self, v);
+    }
+}
+
+bool ir_builder::generate(const char *filename)
+{
+    prog_section_statement_t stmt;
+    char  *lnofile = nullptr;
+
+    if (OPTS_FLAG(SPLIT_VECTOR_PARAMETERS)) {
+        ir_builder_collect_reusables(this);
+        if (!m_const_floats.empty())
+            ir_builder_split_vectors(this);
+    }
+
+    for (auto& fp : m_fields)
+        ir_builder_prepare_field(m_code.get(), fp.get());
+
+    for (auto& gp : m_globals) {
+        ir_value *global = gp.get();
+        if (!generateGlobal(global, false)) {
+            return false;
+        }
+        if (global->m_vtype == TYPE_FUNCTION) {
+            ir_function *func = global->m_constval.vfunc;
+            if (func && m_max_locals < func->m_allocated_locals &&
+                !(func->m_flags & IR_FLAG_MASK_NO_OVERLAP))
+            {
+                m_max_locals = func->m_allocated_locals;
+            }
+            if (func && m_max_globaltemps < func->m_globaltemps)
+                m_max_globaltemps = func->m_globaltemps;
+        }
+    }
+
+    for (auto& fp : m_fields) {
+        if (!ir_builder_gen_field(this, fp.get()))
+            return false;
+    }
+
+    // generate nil
+    m_nil->setCodeAddress(m_code->globals.size());
+    m_code->globals.push_back(0);
+    m_code->globals.push_back(0);
+    m_code->globals.push_back(0);
+
+    // generate virtual-instruction temps
+    for (size_t i = 0; i < IR_MAX_VINSTR_TEMPS; ++i) {
+        m_vinstr_temp[i]->setCodeAddress(m_code->globals.size());
+        m_code->globals.push_back(0);
+        m_code->globals.push_back(0);
+        m_code->globals.push_back(0);
+    }
+
+    // generate global temps
+    m_first_common_globaltemp = m_code->globals.size();
+    m_code->globals.insert(m_code->globals.end(), m_max_globaltemps, 0);
+    // FIXME:DELME:
+    //for (size_t i = 0; i < m_max_globaltemps; ++i) {
+    //    m_code->globals.push_back(0);
+    //}
+    // generate common locals
+    m_first_common_local = m_code->globals.size();
+    m_code->globals.insert(m_code->globals.end(), m_max_locals, 0);
+    // FIXME:DELME:
+    //for (i = 0; i < m_max_locals; ++i) {
+    //    m_code->globals.push_back(0);
+    //}
+
+    // generate function code
+
+    for (auto& gp : m_globals) {
+        ir_value *global = gp.get();
+        if (global->m_vtype == TYPE_FUNCTION) {
+            if (!this->generateGlobalFunctionCode(global))
+                return false;
+        }
+    }
+
+    if (m_code->globals.size() >= 65536) {
+        irerror(m_globals.back()->m_context,
+            "This progs file would require more globals than the metadata can handle (%zu). Bailing out.",
+            m_code->globals.size());
+        return false;
+    }
+
+    /* DP errors if the last instruction is not an INSTR_DONE. */
+    if (m_code->statements.back().opcode != INSTR_DONE)
+    {
+        lex_ctx_t last;
+
+        stmt.opcode = INSTR_DONE;
+        stmt.o1.u1  = 0;
+        stmt.o2.u1  = 0;
+        stmt.o3.u1  = 0;
+        last.line   = m_code->linenums.back();
+        last.column = m_code->columnnums.back();
+
+        code_push_statement(m_code.get(), &stmt, last);
+    }
+
+    if (OPTS_OPTION_BOOL(OPTION_PP_ONLY))
+        return true;
+
+    if (m_code->statements.size() != m_code->linenums.size()) {
+        con_err("Linecounter wrong: %lu != %lu\n",
+                m_code->statements.size(),
+                m_code->linenums.size());
+    } else if (OPTS_FLAG(LNO)) {
+        char  *dot;
+        size_t filelen = strlen(filename);
+
+        memcpy(vec_add(lnofile, filelen+1), filename, filelen+1);
+        dot = strrchr(lnofile, '.');
+        if (!dot) {
+            vec_pop(lnofile);
+        } else {
+            vec_shrinkto(lnofile, dot - lnofile);
+        }
+        memcpy(vec_add(lnofile, 5), ".lno", 5);
+    }
+
+    if (!code_write(m_code.get(), filename, lnofile)) {
+        vec_free(lnofile);
+        return false;
+    }
+
+    vec_free(lnofile);
+    return true;
+}
+
+/***********************************************************************
+ *IR DEBUG Dump functions...
+ */
+
+#define IND_BUFSZ 1024
+
+static const char *qc_opname(int op)
+{
+    if (op < 0) return "<INVALID>";
+    if (op < VINSTR_END)
+        return util_instr_str[op];
+    switch (op) {
+        case VINSTR_END:       return "END";
+        case VINSTR_PHI:       return "PHI";
+        case VINSTR_JUMP:      return "JUMP";
+        case VINSTR_COND:      return "COND";
+        case VINSTR_BITXOR:    return "BITXOR";
+        case VINSTR_BITAND_V:  return "BITAND_V";
+        case VINSTR_BITOR_V:   return "BITOR_V";
+        case VINSTR_BITXOR_V:  return "BITXOR_V";
+        case VINSTR_BITAND_VF: return "BITAND_VF";
+        case VINSTR_BITOR_VF:  return "BITOR_VF";
+        case VINSTR_BITXOR_VF: return "BITXOR_VF";
+        case VINSTR_CROSS:     return "CROSS";
+        case VINSTR_NEG_F:     return "NEG_F";
+        case VINSTR_NEG_V:     return "NEG_V";
+        default:               return "<UNK>";
+    }
+}
+
+void ir_builder::dump(int (*oprintf)(const char*, ...)) const
+{
+    size_t i;
+    char indent[IND_BUFSZ];
+    indent[0] = '\t';
+    indent[1] = 0;
+
+    oprintf("module %s\n", m_name.c_str());
+    for (i = 0; i < m_globals.size(); ++i)
+    {
+        oprintf("global ");
+        if (m_globals[i]->m_hasvalue)
+            oprintf("%s = ", m_globals[i]->m_name.c_str());
+        m_globals[i].get()->dump(oprintf);
+        oprintf("\n");
+    }
+    for (i = 0; i < m_functions.size(); ++i)
+        ir_function_dump(m_functions[i].get(), indent, oprintf);
+    oprintf("endmodule %s\n", m_name.c_str());
+}
+
+static const char *storenames[] = {
+    "[global]", "[local]", "[param]", "[value]", "[return]"
+};
+
+void ir_function_dump(ir_function *f, char *ind,
+                      int (*oprintf)(const char*, ...))
+{
+    size_t i;
+    if (f->m_builtin != 0) {
+        oprintf("%sfunction %s = builtin %i\n", ind, f->m_name.c_str(), -f->m_builtin);
+        return;
+    }
+    oprintf("%sfunction %s\n", ind, f->m_name.c_str());
+    util_strncat(ind, "\t", IND_BUFSZ-1);
+    if (f->m_locals.size())
+    {
+        oprintf("%s%i locals:\n", ind, (int)f->m_locals.size());
+        for (i = 0; i < f->m_locals.size(); ++i) {
+            oprintf("%s\t", ind);
+            f->m_locals[i].get()->dump(oprintf);
+            oprintf("\n");
+        }
+    }
+    oprintf("%sliferanges:\n", ind);
+    for (i = 0; i < f->m_locals.size(); ++i) {
+        const char *attr = "";
+        size_t l, m;
+        ir_value *v = f->m_locals[i].get();
+        if (v->m_unique_life && v->m_locked)
+            attr = "unique,locked ";
+        else if (v->m_unique_life)
+            attr = "unique ";
+        else if (v->m_locked)
+            attr = "locked ";
+        oprintf("%s\t%s: %s %s %s%s@%i ", ind, v->m_name.c_str(), type_name[v->m_vtype],
+                storenames[v->m_store],
+                attr, (v->m_callparam ? "callparam " : ""),
+                (int)v->m_code.local);
+        if (v->m_life.empty())
+            oprintf("[null]");
+        for (l = 0; l < v->m_life.size(); ++l) {
+            oprintf("[%i,%i] ", v->m_life[l].start, v->m_life[l].end);
+        }
+        oprintf("\n");
+        for (m = 0; m < 3; ++m) {
+            ir_value *vm = v->m_members[m];
+            if (!vm)
+                continue;
+            oprintf("%s\t%s: @%i ", ind, vm->m_name.c_str(), (int)vm->m_code.local);
+            for (l = 0; l < vm->m_life.size(); ++l) {
+                oprintf("[%i,%i] ", vm->m_life[l].start, vm->m_life[l].end);
+            }
+            oprintf("\n");
+        }
+    }
+    for (i = 0; i < f->m_values.size(); ++i) {
+        const char *attr = "";
+        size_t l, m;
+        ir_value *v = f->m_values[i].get();
+        if (v->m_unique_life && v->m_locked)
+            attr = "unique,locked ";
+        else if (v->m_unique_life)
+            attr = "unique ";
+        else if (v->m_locked)
+            attr = "locked ";
+        oprintf("%s\t%s: %s %s %s%s@%i ", ind, v->m_name.c_str(), type_name[v->m_vtype],
+                storenames[v->m_store],
+                attr, (v->m_callparam ? "callparam " : ""),
+                (int)v->m_code.local);
+        if (v->m_life.empty())
+            oprintf("[null]");
+        for (l = 0; l < v->m_life.size(); ++l) {
+            oprintf("[%i,%i] ", v->m_life[l].start, v->m_life[l].end);
+        }
+        oprintf("\n");
+        for (m = 0; m < 3; ++m) {
+            ir_value *vm = v->m_members[m];
+            if (!vm)
+                continue;
+            if (vm->m_unique_life && vm->m_locked)
+                attr = "unique,locked ";
+            else if (vm->m_unique_life)
+                attr = "unique ";
+            else if (vm->m_locked)
+                attr = "locked ";
+            oprintf("%s\t%s: %s@%i ", ind, vm->m_name.c_str(), attr, (int)vm->m_code.local);
+            for (l = 0; l < vm->m_life.size(); ++l) {
+                oprintf("[%i,%i] ", vm->m_life[l].start, vm->m_life[l].end);
+            }
+            oprintf("\n");
+        }
+    }
+    if (f->m_blocks.size())
+    {
+        oprintf("%slife passes: %i\n", ind, (int)f->m_run_id);
+        for (i = 0; i < f->m_blocks.size(); ++i) {
+            ir_block_dump(f->m_blocks[i].get(), ind, oprintf);
+        }
+
+    }
+    ind[strlen(ind)-1] = 0;
+    oprintf("%sendfunction %s\n", ind, f->m_name.c_str());
+}
+
+void ir_block_dump(ir_block* b, char *ind,
+                   int (*oprintf)(const char*, ...))
+{
+    size_t i;
+    oprintf("%s:%s\n", ind, b->m_label.c_str());
+    util_strncat(ind, "\t", IND_BUFSZ-1);
+
+    if (b->m_instr && b->m_instr[0])
+        oprintf("%s (%i) [entry]\n", ind, (int)(b->m_instr[0]->m_eid-1));
+    for (i = 0; i < vec_size(b->m_instr); ++i)
+        ir_instr_dump(b->m_instr[i], ind, oprintf);
+    ind[strlen(ind)-1] = 0;
+}
+
+static void dump_phi(ir_instr *in, int (*oprintf)(const char*, ...))
+{
+    oprintf("%s <- phi ", in->_m_ops[0]->m_name.c_str());
+    for (auto &it : in->m_phi) {
+        oprintf("([%s] : %s) ", it.from->m_label.c_str(),
+                                it.value->m_name.c_str());
+    }
+    oprintf("\n");
+}
+
+void ir_instr_dump(ir_instr *in, char *ind,
+                       int (*oprintf)(const char*, ...))
+{
+    size_t i;
+    const char *comma = nullptr;
+
+    oprintf("%s (%i) ", ind, (int)in->m_eid);
+
+    if (in->m_opcode == VINSTR_PHI) {
+        dump_phi(in, oprintf);
+        return;
+    }
+
+    util_strncat(ind, "\t", IND_BUFSZ-1);
+
+    if (in->_m_ops[0] && (in->_m_ops[1] || in->_m_ops[2])) {
+        in->_m_ops[0]->dump(oprintf);
+        if (in->_m_ops[1] || in->_m_ops[2])
+            oprintf(" <- ");
+    }
+    if (in->m_opcode == INSTR_CALL0 || in->m_opcode == VINSTR_NRCALL) {
+        oprintf("CALL%i\t", in->m_params.size());
+    } else
+        oprintf("%s\t", qc_opname(in->m_opcode));
+
+    if (in->_m_ops[0] && !(in->_m_ops[1] || in->_m_ops[2])) {
+        in->_m_ops[0]->dump(oprintf);
+        comma = ",\t";
+    }
+    else
+    {
+        for (i = 1; i != 3; ++i) {
+            if (in->_m_ops[i]) {
+                if (comma)
+                    oprintf(comma);
+                in->_m_ops[i]->dump(oprintf);
+                comma = ",\t";
+            }
+        }
+    }
+    if (in->m_bops[0]) {
+        if (comma)
+            oprintf(comma);
+        oprintf("[%s]", in->m_bops[0]->m_label.c_str());
+        comma = ",\t";
+    }
+    if (in->m_bops[1])
+        oprintf("%s[%s]", comma, in->m_bops[1]->m_label.c_str());
+    if (in->m_params.size()) {
+        oprintf("\tparams: ");
+        for (auto &it : in->m_params)
+            oprintf("%s, ", it->m_name.c_str());
+    }
+    oprintf("\n");
+    ind[strlen(ind)-1] = 0;
+}
+
+static void ir_value_dump_string(const char *str, int (*oprintf)(const char*, ...))
+{
+    oprintf("\"");
+    for (; *str; ++str) {
+        switch (*str) {
+            case '\n': oprintf("\\n"); break;
+            case '\r': oprintf("\\r"); break;
+            case '\t': oprintf("\\t"); break;
+            case '\v': oprintf("\\v"); break;
+            case '\f': oprintf("\\f"); break;
+            case '\b': oprintf("\\b"); break;
+            case '\a': oprintf("\\a"); break;
+            case '\\': oprintf("\\\\"); break;
+            case '"': oprintf("\\\""); break;
+            default: oprintf("%c", *str); break;
+        }
+    }
+    oprintf("\"");
+}
+
+void ir_value::dump(int (*oprintf)(const char*, ...)) const
+{
+    if (m_hasvalue) {
+        switch (m_vtype) {
+            default:
+            case TYPE_VOID:
+                oprintf("(void)");
+                break;
+            case TYPE_FUNCTION:
+                oprintf("fn:%s", m_name.c_str());
+                break;
+            case TYPE_FLOAT:
+                oprintf("%g", m_constval.vfloat);
+                break;
+            case TYPE_VECTOR:
+                oprintf("'%g %g %g'",
+                        m_constval.vvec.x,
+                        m_constval.vvec.y,
+                        m_constval.vvec.z);
+                break;
+            case TYPE_ENTITY:
+                oprintf("(entity)");
+                break;
+            case TYPE_STRING:
+                ir_value_dump_string(m_constval.vstring, oprintf);
+                break;
+#if 0
+            case TYPE_INTEGER:
+                oprintf("%i", m_constval.vint);
+                break;
+#endif
+            case TYPE_POINTER:
+                oprintf("&%s",
+                    m_constval.vpointer->m_name.c_str());
+                break;
+        }
+    } else {
+        oprintf("%s", m_name.c_str());
+    }
+}
+
+void ir_value::dumpLife(int (*oprintf)(const char*,...)) const
+{
+    oprintf("Life of %12s:", m_name.c_str());
+    for (size_t i = 0; i < m_life.size(); ++i)
+    {
+        oprintf(" + [%i, %i]\n", m_life[i].start, m_life[i].end);
+    }
+}
diff --git a/ir.h b/ir.h
index cdf097bed4e17d31a2da7fc41a28e16497a7b93a..9fa8ab5ab62d2ee49323af529e7ed2a1e6508410 100644 (file)
--- a/ir.h
+++ b/ir.h
@@ -1,25 +1,3 @@
-/*
- * Copyright (C) 2012, 2013, 2014, 2015
- *     Wolfgang Bumiller
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
- * this software and associated documentation files (the "Software"), to deal in
- * the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is furnished to do
- * so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
 #ifndef GMQCC_IR_HDR
 #define GMQCC_IR_HDR
 #include "gmqcc.h"
  */
 typedef uint8_t ir_flag_t;
 
-typedef struct ir_value_s    ir_value;
-typedef struct ir_instr_s    ir_instr;
-typedef struct ir_block_s    ir_block;
-typedef struct ir_function_s ir_function;
-typedef struct ir_builder_s  ir_builder;
+struct ir_value;
+struct ir_instr;
+struct ir_block;
+struct ir_function;
+struct ir_builder;
 
-typedef struct {
+struct ir_life_entry_t {
     /* both inclusive */
     size_t start;
     size_t end;
-} ir_life_entry_t;
+};
 
 enum {
     IR_FLAG_HAS_ARRAYS              = 1 << 0,
@@ -57,127 +35,145 @@ enum {
     IR_FLAG_MASK_NO_LOCAL_TEMPS  = (IR_FLAG_HAS_ARRAYS | IR_FLAG_HAS_UNINITIALIZED)
 };
 
-struct ir_value_s {
-    char      *name;
-    int        vtype;
-    int        store;
-    lex_ctx_t  context;
+struct ir_value {
+    ir_value(std::string&& name, store_type storetype, qc_type vtype);
+    ir_value(ir_function *owner, std::string&& name, store_type storetype, qc_type vtype);
+    ~ir_value();
+
+    ir_value *vectorMember(unsigned int member);
+
+    bool GMQCC_WARN setFloat(float);
+    bool GMQCC_WARN setFunc(int);
+    bool GMQCC_WARN setString(const char*);
+    bool GMQCC_WARN setVector(vec3_t);
+    bool GMQCC_WARN setField(ir_value*);
+#if 0
+    bool GMQCC_WARN setInt(int);
+#endif
+
+    bool lives(size_t at);
+    void dumpLife(int (*oprintf)(const char*, ...)) const;
+
+    void setCodeAddress(int32_t gaddr);
+    int32_t codeAddress() const;
+
+    bool insertLife(size_t idx, ir_life_entry_t);
+    bool setAlive(size_t position);
+    bool mergeLife(const ir_value *other);
 
+    std::string m_name;
 
-    int       fieldtype; /* even the IR knows the subtype of a field */
-    int       outtype;   /* and the output type of a function        */
-    int       cvq;       /* 'const' vs 'var' qualifier               */
-    ir_flag_t flags;
+    qc_type m_vtype;
+    store_type m_store;
+    lex_ctx_t m_context;
+    qc_type m_fieldtype; // even the IR knows the subtype of a field
+    qc_type m_outtype;   // and the output type of a function
+    int m_cvq;       // 'const' vs 'var' qualifier
+    ir_flag_t m_flags;
 
-    ir_instr **reads;
-    ir_instr **writes;
+    std::vector<ir_instr *> m_reads;
+    std::vector<ir_instr *> m_writes;
 
-    /* constantvalues */
-    bool hasvalue;
+    // constant values
+    bool m_hasvalue;
     union {
-        qcfloat_t   vfloat;
-        int         vint;
-        vec3_t      vvec;
-        int32_t     ivec[3];
+        qcfloat_t    vfloat;
+        int          vint;
+        vec3_t       vvec;
+        int32_t      ivec[3];
         char        *vstring;
         ir_value    *vpointer;
         ir_function *vfunc;
-    } constval;
+    } m_constval;
 
     struct {
         int32_t globaladdr;
         int32_t name;
-        int32_t local;         /* filled by the local-allocator     */
-        int32_t addroffset;    /* added for members                 */
-        int32_t fieldaddr;     /* to generate field-addresses early */
-    } code;
+        int32_t local;         // filled by the local-allocator
+        int32_t addroffset;    // added for members
+        int32_t fieldaddr;     // to generate field-addresses early
+    } m_code;
 
-    /* for acessing vectors */
-    ir_value *members[3];
-    ir_value *memberof;
+    // for accessing vectors
+    ir_value *m_members[3];
+    ir_value *m_memberof;
 
+    bool m_unique_life;      // arrays will never overlap with temps
+    bool m_locked;           // temps living during a CALL must be locked
+    bool m_callparam;
 
-    bool unique_life;      /* arrays will never overlap with temps      */
-    bool locked;           /* temps living during a CALL must be locked */
-    bool callparam;
+    std::vector<ir_life_entry_t> m_life; // For the temp allocator
 
-    ir_life_entry_t *life; /* For the temp allocator */
-};
+    size_t size() const;
 
-/*
- * ir_value can be a variable, or created by an operation
- * if a result of an operation: the function should store
- * it to remember to delete it / garbage collect it
- */
-void            ir_value_delete(ir_value*);
-ir_value*       ir_value_vector_member(ir_value*, unsigned int member);
-bool GMQCC_WARN ir_value_set_float(ir_value*, float f);
-bool GMQCC_WARN ir_value_set_func(ir_value*, int f);
-bool GMQCC_WARN ir_value_set_string(ir_value*, const char *s);
-bool GMQCC_WARN ir_value_set_vector(ir_value*, vec3_t v);
-bool GMQCC_WARN ir_value_set_field(ir_value*, ir_value *fld);
-bool            ir_value_lives(ir_value*, size_t);
-void            ir_value_dump_life(const ir_value *self, int (*oprintf)(const char*,...));
+    void dump(int (*oprintf)(const char*, ...)) const;
+};
 
 /* PHI data */
-typedef struct ir_phi_entry_s {
+struct ir_phi_entry_t {
     ir_value *value;
     ir_block *from;
-} ir_phi_entry_t;
+};
 
 /* instruction */
-struct ir_instr_s {
-    int        opcode;
-    lex_ctx_t  context;
-    ir_value* (_ops[3]);
-    ir_block* (bops[2]);
+struct ir_instr {
+    ir_instr(lex_ctx_t, ir_block *owner, int opcode);
+    ~ir_instr();
 
-    ir_phi_entry_t *phi;
-    ir_value      **params;
+    int m_opcode;
+    lex_ctx_t m_context;
+    ir_value *(_m_ops[3]) = { nullptr, nullptr, nullptr };
+    ir_block *(m_bops[2]) = { nullptr, nullptr };
 
-    /* For the temp-allocation */
-    size_t eid;
+    std::vector<ir_phi_entry_t> m_phi;
+    std::vector<ir_value *> m_params;
+
+    // For the temp-allocation
+    size_t m_eid = 0;
 
-    /* For IFs */
-    bool   likely;
+    // For IFs
+    bool m_likely = true;
 
-    ir_block *owner;
+    ir_block *m_owner;
 };
 
 /* block */
-struct ir_block_s {
-    char      *label;
-    lex_ctx_t  context;
-    bool       final; /* once a jump is added we're done */
+struct ir_block {
+    ir_block(ir_function *owner, const std::string& name);
+    ~ir_block();
 
-    ir_instr **instr;
-    ir_block **entries;
-    ir_block **exits;
-    ir_value **living;
+    ir_function *m_owner;
+    std::string m_label;
 
-    /* For the temp-allocation */
-    size_t entry_id;
-    size_t eid;
-    bool   is_return;
+    lex_ctx_t m_context;
+    bool m_final = false; /* once a jump is added we're done */
+
+    ir_instr **m_instr = nullptr;
+    ir_block **m_entries = nullptr;
+    ir_block **m_exits = nullptr;
+    std::vector<ir_value *> m_living;
 
-    ir_function *owner;
+    /* For the temp-allocation */
+    size_t m_entry_id  = 0;
+    size_t m_eid       = 0;
+    bool   m_is_return = false;
 
-    bool   generated;
-    size_t code_start;
+    bool m_generated = false;
+    size_t m_code_start = 0;
 };
 
 ir_value*       ir_block_create_binop(ir_block*, lex_ctx_t, const char *label, int op, ir_value *left, ir_value *right);
 ir_value*       ir_block_create_unary(ir_block*, lex_ctx_t, const char *label, int op, ir_value *operand);
 bool GMQCC_WARN ir_block_create_store_op(ir_block*, lex_ctx_t, int op, ir_value *target, ir_value *what);
 bool GMQCC_WARN ir_block_create_storep(ir_block*, lex_ctx_t, ir_value *target, ir_value *what);
-ir_value*       ir_block_create_load_from_ent(ir_block*, lex_ctx_t, const char *label, ir_value *ent, ir_value *field, int outype);
+ir_value*       ir_block_create_load_from_ent(ir_block*, lex_ctx_t, const char *label, ir_value *ent, ir_value *field, qc_type outype);
 ir_value*       ir_block_create_fieldaddress(ir_block*, lex_ctx_t, const char *label, ir_value *entity, ir_value *field);
 bool GMQCC_WARN ir_block_create_state_op(ir_block*, lex_ctx_t, ir_value *frame, ir_value *think);
 
 /* This is to create an instruction of the form
  * <outtype>%label := opcode a, b
  */
-ir_instr* ir_block_create_phi(ir_block*, lex_ctx_t, const char *label, int vtype);
+ir_instr* ir_block_create_phi(ir_block*, lex_ctx_t, const char *label, qc_type vtype);
 ir_value* ir_phi_value(ir_instr*);
 void ir_phi_add(ir_instr*, ir_block *b, ir_value *v);
 ir_instr* ir_block_create_call(ir_block*, lex_ctx_t, const char *label, ir_value *func, bool noreturn);
@@ -199,30 +195,36 @@ bool GMQCC_WARN ir_block_create_jump(ir_block*, lex_ctx_t, ir_block *to);
 bool GMQCC_WARN ir_block_create_goto(ir_block*, lex_ctx_t, ir_block *to);
 
 /* function */
-struct ir_function_s {
-    char      *name;
-    int        outtype;
-    int       *params;
-    ir_block **blocks;
-    ir_flag_t  flags;
-    int        builtin;
+struct ir_function {
+    ir_function(ir_builder *owner, qc_type returntype);
+    ~ir_function();
+
+    ir_builder *m_owner;
+
+    std::string m_name;
+    qc_type     m_outtype;
+    int        *m_params  = nullptr;
+    ir_flag_t   m_flags   = 0;
+    int         m_builtin = 0;
+
+    std::vector<std::unique_ptr<ir_block>> m_blocks;
 
     /*
      * values generated from operations
      * which might get optimized away, so anything
      * in there needs to be deleted in the dtor.
      */
-    ir_value **values;
-    ir_value **locals;     /* locally defined variables */
-    ir_value *value;
+    std::vector<std::unique_ptr<ir_value>> m_values;
+    std::vector<std::unique_ptr<ir_value>> m_locals;     /* locally defined variables */
+    ir_value *m_value = nullptr;
 
-    size_t allocated_locals;
-    size_t globaltemps;
+    size_t m_allocated_locals = 0;
+    size_t m_globaltemps      = 0;
 
-    ir_block*  first;
-    ir_block*  last;
+    ir_block*  m_first = nullptr;
+    ir_block*  m_last  = nullptr;
 
-    lex_ctx_t  context;
+    lex_ctx_t  m_context;
 
     /*
      * for prototypes - first we generate all the
@@ -231,19 +233,17 @@ struct ir_function_s {
      *
      * remember the ID:
      */
-    qcint_t code_function_def;
+    qcint_t m_code_function_def = -1;
 
     /* for temp allocation */
-    size_t run_id;
-
-    ir_builder *owner;
+    size_t m_run_id = 0;
 
     /* vararg support: */
-    size_t max_varargs;
+    size_t m_max_varargs = 0;
 };
 
 
-ir_value*       ir_function_create_local(ir_function *self, const char *name, int vtype, bool param);
+ir_value*       ir_function_create_local(ir_function *self, const std::string& name, qc_type vtype, bool param);
 bool GMQCC_WARN ir_function_finalize(ir_function*);
 ir_block*       ir_function_create_block(lex_ctx_t ctx, ir_function*, const char *label);
 
@@ -251,52 +251,70 @@ ir_block*       ir_function_create_block(lex_ctx_t ctx, ir_function*, const char
 #define IR_HT_SIZE          1024
 #define IR_MAX_VINSTR_TEMPS 1
 
-struct ir_builder_s {
-    char *name;
-    ir_function **functions;
-    ir_value    **globals;
-    ir_value    **fields;
-    ir_value    **const_floats; /* for reusing them in vector-splits, TODO: sort this or use a radix-tree */
-
-    ht            htfunctions;
-    ht            htglobals;
-    ht            htfields;
-
-    ir_value    **extparams;
-    ir_value    **extparam_protos;
-
-    /* the highest func->allocated_locals */
-    size_t        max_locals;
-    size_t        max_globaltemps;
-    uint32_t      first_common_local;
-    uint32_t      first_common_globaltemp;
-
-    const char **filenames;
-    qcint_t     *filestrings;
-    /* we cache the #IMMEDIATE string here */
-    qcint_t      str_immediate;
-    /* there should just be this one nil */
-    ir_value    *nil;
-    ir_value    *reserved_va_count;
-    ir_value    *coverage_func;
+struct ir_builder {
+    ir_builder(const std::string& modulename);
+    ~ir_builder();
+
+    ir_function *createFunction(const std::string &name, qc_type outtype);
+    ir_value *createGlobal(const std::string &name, qc_type vtype);
+    ir_value *createField(const std::string &name, qc_type vtype);
+    ir_value *get_va_count();
+    bool generate(const char *filename);
+    void dump(int (*oprintf)(const char*, ...)) const;
+
+    ir_value *generateExtparamProto();
+    void generateExtparam();
+
+    ir_value *literalFloat(float value, bool add_to_list);
+
+    std::string m_name;
+    std::vector<std::unique_ptr<ir_function>> m_functions;
+    std::vector<std::unique_ptr<ir_value>>    m_globals;
+    std::vector<std::unique_ptr<ir_value>>    m_fields;
+    // for reusing them in vector-splits, TODO: sort this or use a radix-tree
+    std::vector<ir_value*>                    m_const_floats;
+
+    ht            m_htfunctions;
+    ht            m_htglobals;
+    ht            m_htfields;
+
+    // extparams' ir_values reference the ones from extparam_protos
+    std::vector<std::unique_ptr<ir_value>> m_extparam_protos;
+    std::vector<ir_value*>                 m_extparams;
+
+    // the highest func->allocated_locals
+    size_t        m_max_locals              = 0;
+    size_t        m_max_globaltemps         = 0;
+    uint32_t      m_first_common_local      = 0;
+    uint32_t      m_first_common_globaltemp = 0;
+
+    std::vector<const char*> m_filenames;
+    std::vector<qcint_t>     m_filestrings;
+
+    // we cache the #IMMEDIATE string here
+    qcint_t      m_str_immediate = 0;
+
+    // there should just be this one nil
+    ir_value    *m_nil;
+    ir_value    *m_reserved_va_count = nullptr;
+    ir_value    *m_coverage_func = nullptr;
+
     /* some virtual instructions require temps, and their code is isolated
      * so that we don't need to keep track of their liveness.
      */
-    ir_value    *vinstr_temp[IR_MAX_VINSTR_TEMPS];
+    ir_value    *m_vinstr_temp[IR_MAX_VINSTR_TEMPS];
 
     /* code generator */
-    code_t      *code;
+    std::unique_ptr<code_t> m_code;
+
+private:
+    qcint_t filestring(const char *filename);
+    bool generateGlobal(ir_value*, bool is_local);
+    bool generateGlobalFunction(ir_value*);
+    bool generateGlobalFunctionCode(ir_value*);
+    bool generateFunctionLocals(ir_value*);
 };
 
-ir_builder*  ir_builder_new(const char *modulename);
-void         ir_builder_delete(ir_builder*);
-ir_function* ir_builder_create_function(ir_builder*, const char *name, int outtype);
-ir_value*    ir_builder_create_global(ir_builder*, const char *name, int vtype);
-ir_value*    ir_builder_create_field(ir_builder*, const char *name, int vtype);
-ir_value*    ir_builder_get_va_count(ir_builder*);
-bool         ir_builder_generate(ir_builder *self, const char *filename);
-void         ir_builder_dump(ir_builder*, int (*oprintf)(const char*, ...));
-
 /*
  * This code assumes 32 bit floats while generating binary
  * Blub: don't use extern here, it's annoying and shows up in nm
diff --git a/lexer.c b/lexer.c
deleted file mode 100644 (file)
index a495d27..0000000
--- a/lexer.c
+++ /dev/null
@@ -1,1445 +0,0 @@
-/*
- * Copyright (C) 2012, 2013, 2014, 2015
- *     Wolfgang Bumiller
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
- * this software and associated documentation files (the "Software"), to deal in
- * the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is furnished to do
- * so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-#include <string.h>
-#include <stdlib.h>
-
-#include "gmqcc.h"
-#include "lexer.h"
-
-/*
- * List of Keywords
- */
-
-/* original */
-static const char *keywords_qc[] = {
-    "for", "do", "while",
-    "if", "else",
-    "local",
-    "return",
-    "const"
-};
-/* For fte/gmgqcc */
-static const char *keywords_fg[] = {
-    "switch", "case", "default",
-    "struct", "union",
-    "break", "continue",
-    "typedef",
-    "goto",
-
-    "__builtin_debug_printtype"
-};
-
-/*
- * Lexer code
- */
-static char* *lex_filenames;
-
-static void lexerror(lex_file *lex, const char *fmt, ...)
-{
-    va_list ap;
-
-    va_start(ap, fmt);
-    if (lex)
-        con_vprintmsg(LVL_ERROR, lex->name, lex->sline, lex->column, "parse error", fmt, ap);
-    else
-        con_vprintmsg(LVL_ERROR, "", 0, 0, "parse error", fmt, ap);
-    va_end(ap);
-}
-
-static bool lexwarn(lex_file *lex, int warntype, const char *fmt, ...)
-{
-    bool      r;
-    lex_ctx_t ctx;
-    va_list   ap;
-
-    ctx.file   = lex->name;
-    ctx.line   = lex->sline;
-    ctx.column = lex->column;
-
-    va_start(ap, fmt);
-    r = vcompile_warning(ctx, warntype, fmt, ap);
-    va_end(ap);
-    return r;
-}
-
-static void lex_token_new(lex_file *lex)
-{
-    if (lex->tok.value)
-        vec_shrinkto(lex->tok.value, 0);
-
-    lex->tok.constval.t  = 0;
-    lex->tok.ctx.line    = lex->sline;
-    lex->tok.ctx.file    = lex->name;
-    lex->tok.ctx.column  = lex->column;
-}
-
-static void lex_ungetch(lex_file *lex, int ch);
-static int lex_getch(lex_file *lex);
-
-lex_file* lex_open(const char *file)
-{
-    lex_file  *lex;
-    fs_file_t *in = fs_file_open(file, "rb");
-    uint32_t   read;
-
-    if (!in) {
-        lexerror(NULL, "open failed: '%s'\n", file);
-        return NULL;
-    }
-
-    lex = (lex_file*)mem_a(sizeof(*lex));
-    if (!lex) {
-        fs_file_close(in);
-        lexerror(NULL, "out of memory\n");
-        return NULL;
-    }
-
-    memset(lex, 0, sizeof(*lex));
-
-    lex->file    = in;
-    lex->name    = util_strdup(file);
-    lex->line    = 1; /* we start counting at 1 */
-    lex->column  = 0;
-    lex->peekpos = 0;
-    lex->eof     = false;
-
-    /* handle BOM */
-    if ((read = (lex_getch(lex) << 16) | (lex_getch(lex) << 8) | lex_getch(lex)) != 0xEFBBBF) {
-        lex_ungetch(lex, (read & 0x0000FF));
-        lex_ungetch(lex, (read & 0x00FF00) >> 8);
-        lex_ungetch(lex, (read & 0xFF0000) >> 16);
-    } else {
-        /*
-         * otherwise the lexer has advanced 3 bytes for the BOM, we need
-         * to set the column back to 0
-         */
-        lex->column = 0;
-    }
-
-    vec_push(lex_filenames, lex->name);
-    return lex;
-}
-
-lex_file* lex_open_string(const char *str, size_t len, const char *name)
-{
-    lex_file *lex;
-
-    lex = (lex_file*)mem_a(sizeof(*lex));
-    if (!lex) {
-        lexerror(NULL, "out of memory\n");
-        return NULL;
-    }
-
-    memset(lex, 0, sizeof(*lex));
-
-    lex->file = NULL;
-    lex->open_string        = str;
-    lex->open_string_length = len;
-    lex->open_string_pos    = 0;
-
-    lex->name    = util_strdup(name ? name : "<string-source>");
-    lex->line    = 1; /* we start counting at 1 */
-    lex->peekpos = 0;
-    lex->eof     = false;
-    lex->column  = 0;
-
-    vec_push(lex_filenames, lex->name);
-
-    return lex;
-}
-
-void lex_cleanup(void)
-{
-    size_t i;
-    for (i = 0; i < vec_size(lex_filenames); ++i)
-        mem_d(lex_filenames[i]);
-    vec_free(lex_filenames);
-}
-
-void lex_close(lex_file *lex)
-{
-    size_t i;
-    for (i = 0; i < vec_size(lex->frames); ++i)
-        mem_d(lex->frames[i].name);
-    vec_free(lex->frames);
-
-    if (lex->modelname)
-        vec_free(lex->modelname);
-
-    if (lex->file)
-        fs_file_close(lex->file);
-
-    vec_free(lex->tok.value);
-
-    /* mem_d(lex->name); collected in lex_filenames */
-    mem_d(lex);
-}
-
-
-
-static int lex_fgetc(lex_file *lex)
-{
-    if (lex->file) {
-        lex->column++;
-        return fs_file_getc(lex->file);
-    }
-    if (lex->open_string) {
-        if (lex->open_string_pos >= lex->open_string_length)
-            return FS_FILE_EOF;
-        lex->column++;
-        return lex->open_string[lex->open_string_pos++];
-    }
-    return FS_FILE_EOF;
-}
-
-/* Get or put-back data
- * The following to functions do NOT understand what kind of data they
- * are working on.
- * The are merely wrapping get/put in order to count line numbers.
- */
-static int lex_try_trigraph(lex_file *lex, int old)
-{
-    int c2, c3;
-    c2 = lex_fgetc(lex);
-    if (!lex->push_line && c2 == '\n') {
-        lex->line++;
-        lex->column = 0;
-    }
-
-    if (c2 != '?') {
-        lex_ungetch(lex, c2);
-        return old;
-    }
-
-    c3 = lex_fgetc(lex);
-    if (!lex->push_line && c3 == '\n') {
-        lex->line++;
-        lex->column = 0;
-    }
-
-    switch (c3) {
-        case '=': return '#';
-        case '/': return '\\';
-        case '\'': return '^';
-        case '(': return '[';
-        case ')': return ']';
-        case '!': return '|';
-        case '<': return '{';
-        case '>': return '}';
-        case '-': return '~';
-        default:
-            lex_ungetch(lex, c3);
-            lex_ungetch(lex, c2);
-            return old;
-    }
-}
-
-static int lex_try_digraph(lex_file *lex, int ch)
-{
-    int c2;
-    c2 = lex_fgetc(lex);
-    /* we just used fgetc() so count lines
-     * need to offset a \n the ungetch would recognize
-     */
-    if (!lex->push_line && c2 == '\n')
-        lex->line++;
-    if      (ch == '<' && c2 == ':')
-        return '[';
-    else if (ch == ':' && c2 == '>')
-        return ']';
-    else if (ch == '<' && c2 == '%')
-        return '{';
-    else if (ch == '%' && c2 == '>')
-        return '}';
-    else if (ch == '%' && c2 == ':')
-        return '#';
-    lex_ungetch(lex, c2);
-    return ch;
-}
-
-static int lex_getch(lex_file *lex)
-{
-    int ch;
-
-    if (lex->peekpos) {
-        lex->peekpos--;
-        if (!lex->push_line && lex->peek[lex->peekpos] == '\n') {
-            lex->line++;
-            lex->column = 0;
-        }
-        return lex->peek[lex->peekpos];
-    }
-
-    ch = lex_fgetc(lex);
-    if (!lex->push_line && ch == '\n') {
-        lex->line++;
-        lex->column = 0;
-    }
-    else if (ch == '?')
-        return lex_try_trigraph(lex, ch);
-    else if (!lex->flags.nodigraphs && (ch == '<' || ch == ':' || ch == '%'))
-        return lex_try_digraph(lex, ch);
-    return ch;
-}
-
-static void lex_ungetch(lex_file *lex, int ch)
-{
-    lex->peek[lex->peekpos++] = ch;
-    lex->column--;
-    if (!lex->push_line && ch == '\n') {
-        lex->line--;
-        lex->column = 0;
-    }
-}
-
-/* classify characters
- * some additions to the is*() functions of ctype.h
- */
-
-/* Idents are alphanumberic, but they start with alpha or _ */
-static bool isident_start(int ch)
-{
-    return util_isalpha(ch) || ch == '_';
-}
-
-static bool isident(int ch)
-{
-    return isident_start(ch) || util_isdigit(ch);
-}
-
-/* isxdigit_only is used when we already know it's not a digit
- * and want to see if it's a hex digit anyway.
- */
-static bool isxdigit_only(int ch)
-{
-    return (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F');
-}
-
-/* Append a character to the token buffer */
-static void lex_tokench(lex_file *lex, int ch)
-{
-    vec_push(lex->tok.value, ch);
-}
-
-/* Append a trailing null-byte */
-static void lex_endtoken(lex_file *lex)
-{
-    vec_push(lex->tok.value, 0);
-    vec_shrinkby(lex->tok.value, 1);
-}
-
-static bool lex_try_pragma(lex_file *lex)
-{
-    int ch;
-    char *pragma  = NULL;
-    char *command = NULL;
-    char *param   = NULL;
-    size_t line;
-
-    if (lex->flags.preprocessing)
-        return false;
-
-    line = lex->line;
-
-    ch = lex_getch(lex);
-    if (ch != '#') {
-        lex_ungetch(lex, ch);
-        return false;
-    }
-
-    for (ch = lex_getch(lex); vec_size(pragma) < 8 && ch >= 'a' && ch <= 'z'; ch = lex_getch(lex))
-        vec_push(pragma, ch);
-    vec_push(pragma, 0);
-
-    if (ch != ' ' || strcmp(pragma, "pragma")) {
-        lex_ungetch(lex, ch);
-        goto unroll;
-    }
-
-    for (ch = lex_getch(lex); vec_size(command) < 32 && ch >= 'a' && ch <= 'z'; ch = lex_getch(lex))
-        vec_push(command, ch);
-    vec_push(command, 0);
-
-    if (ch != '(') {
-        lex_ungetch(lex, ch);
-        goto unroll;
-    }
-
-    for (ch = lex_getch(lex); vec_size(param) < 1024 && ch != ')' && ch != '\n'; ch = lex_getch(lex))
-        vec_push(param, ch);
-    vec_push(param, 0);
-
-    if (ch != ')') {
-        lex_ungetch(lex, ch);
-        goto unroll;
-    }
-
-    if (!strcmp(command, "push")) {
-        if (!strcmp(param, "line")) {
-            lex->push_line++;
-            if (lex->push_line == 1)
-                --line;
-        }
-        else
-            goto unroll;
-    }
-    else if (!strcmp(command, "pop")) {
-        if (!strcmp(param, "line")) {
-            if (lex->push_line)
-                lex->push_line--;
-            if (lex->push_line == 0)
-                --line;
-        }
-        else
-            goto unroll;
-    }
-    else if (!strcmp(command, "file")) {
-        lex->name = util_strdup(param);
-        vec_push(lex_filenames, lex->name);
-    }
-    else if (!strcmp(command, "line")) {
-        line = strtol(param, NULL, 0)-1;
-    }
-    else
-        goto unroll;
-
-    lex->line = line;
-    while (ch != '\n' && ch != FS_FILE_EOF)
-        ch = lex_getch(lex);
-    vec_free(command);
-    vec_free(param);
-    vec_free(pragma);
-    return true;
-
-unroll:
-    if (command) {
-        vec_pop(command);
-        while (vec_size(command)) {
-            lex_ungetch(lex, (unsigned char)vec_last(command));
-            vec_pop(command);
-        }
-        vec_free(command);
-        lex_ungetch(lex, ' ');
-    }
-    if (param) {
-        vec_pop(param);
-        while (vec_size(param)) {
-            lex_ungetch(lex, (unsigned char)vec_last(param));
-            vec_pop(param);
-        }
-        vec_free(param);
-        lex_ungetch(lex, ' ');
-    }
-    if (pragma) {
-        vec_pop(pragma);
-        while (vec_size(pragma)) {
-            lex_ungetch(lex, (unsigned char)vec_last(pragma));
-            vec_pop(pragma);
-        }
-        vec_free(pragma);
-    }
-    lex_ungetch(lex, '#');
-
-    lex->line = line;
-    return false;
-}
-
-/* Skip whitespace and comments and return the first
- * non-white character.
- * As this makes use of the above getch() ungetch() functions,
- * we don't need to care at all about line numbering anymore.
- *
- * In theory, this function should only be used at the beginning
- * of lexing, or when we *know* the next character is part of the token.
- * Otherwise, if the parser throws an error, the linenumber may not be
- * the line of the error, but the line of the next token AFTER the error.
- *
- * This is currently only problematic when using c-like string-continuation,
- * since comments and whitespaces are allowed between 2 such strings.
- * Example:
-printf(   "line one\n"
-// A comment
-          "A continuation of the previous string"
-// This line is skipped
-      , foo);
-
- * In this case, if the parse decides it didn't actually want a string,
- * and uses lex->line to print an error, it will show the ', foo);' line's
- * linenumber.
- *
- * On the other hand, the parser is supposed to remember the line of the next
- * token's beginning. In this case we would want skipwhite() to be called
- * AFTER reading a token, so that the parser, before reading the NEXT token,
- * doesn't store teh *comment's* linenumber, but the actual token's linenumber.
- *
- * THIS SOLUTION
- *    here is to store the line of the first character after skipping
- *    the initial whitespace in lex->sline, this happens in lex_do.
- */
-static int lex_skipwhite(lex_file *lex, bool hadwhite)
-{
-    int ch = 0;
-    bool haswhite = hadwhite;
-
-    do
-    {
-        ch = lex_getch(lex);
-        while (ch != FS_FILE_EOF && util_isspace(ch)) {
-            if (ch == '\n') {
-                if (lex_try_pragma(lex))
-                    continue;
-            }
-            if (lex->flags.preprocessing) {
-                if (ch == '\n') {
-                    /* end-of-line */
-                    /* see if there was whitespace first */
-                    if (haswhite) { /* (vec_size(lex->tok.value)) { */
-                        lex_ungetch(lex, ch);
-                        lex_endtoken(lex);
-                        return TOKEN_WHITE;
-                    }
-                    /* otherwise return EOL */
-                    return TOKEN_EOL;
-                }
-                haswhite = true;
-                lex_tokench(lex, ch);
-            }
-            ch = lex_getch(lex);
-        }
-
-        if (ch == '/') {
-            ch = lex_getch(lex);
-            if (ch == '/')
-            {
-                /* one line comment */
-                ch = lex_getch(lex);
-
-                if (lex->flags.preprocessing) {
-                    haswhite = true;
-                    lex_tokench(lex, ' ');
-                    lex_tokench(lex, ' ');
-                }
-
-                while (ch != FS_FILE_EOF && ch != '\n') {
-                    if (lex->flags.preprocessing)
-                        lex_tokench(lex, ' '); /* ch); */
-                    ch = lex_getch(lex);
-                }
-                if (lex->flags.preprocessing) {
-                    lex_ungetch(lex, '\n');
-                    lex_endtoken(lex);
-                    return TOKEN_WHITE;
-                }
-                continue;
-            }
-            if (ch == '*')
-            {
-                /* multiline comment */
-                if (lex->flags.preprocessing) {
-                    haswhite = true;
-                    lex_tokench(lex, ' ');
-                    lex_tokench(lex, ' ');
-                }
-
-                while (ch != FS_FILE_EOF)
-                {
-                    ch = lex_getch(lex);
-                    if (ch == '*') {
-                        ch = lex_getch(lex);
-                        if (ch == '/') {
-                            if (lex->flags.preprocessing) {
-                                lex_tokench(lex, ' ');
-                                lex_tokench(lex, ' ');
-                            }
-                            break;
-                        }
-                        lex_ungetch(lex, ch);
-                    }
-                    if (lex->flags.preprocessing) {
-                        if (ch == '\n')
-                            lex_tokench(lex, '\n');
-                        else
-                            lex_tokench(lex, ' ');
-                    }
-                }
-                ch = ' '; /* cause TRUE in the isspace check */
-                continue;
-            }
-            /* Otherwise roll back to the slash and break out of the loop */
-            lex_ungetch(lex, ch);
-            ch = '/';
-            break;
-        }
-    } while (ch != FS_FILE_EOF && util_isspace(ch));
-
-    if (haswhite) {
-        lex_endtoken(lex);
-        lex_ungetch(lex, ch);
-        return TOKEN_WHITE;
-    }
-    return ch;
-}
-
-/* Get a token */
-static bool GMQCC_WARN lex_finish_ident(lex_file *lex)
-{
-    int ch;
-
-    ch = lex_getch(lex);
-    while (ch != FS_FILE_EOF && isident(ch))
-    {
-        lex_tokench(lex, ch);
-        ch = lex_getch(lex);
-    }
-
-    /* last ch was not an ident ch: */
-    lex_ungetch(lex, ch);
-
-    return true;
-}
-
-/* read one ident for the frame list */
-static int lex_parse_frame(lex_file *lex)
-{
-    int ch;
-
-    lex_token_new(lex);
-
-    ch = lex_getch(lex);
-    while (ch != FS_FILE_EOF && ch != '\n' && util_isspace(ch))
-        ch = lex_getch(lex);
-
-    if (ch == '\n')
-        return 1;
-
-    if (!isident_start(ch)) {
-        lexerror(lex, "invalid framename, must start with one of a-z or _, got %c", ch);
-        return -1;
-    }
-
-    lex_tokench(lex, ch);
-    if (!lex_finish_ident(lex))
-        return -1;
-    lex_endtoken(lex);
-    return 0;
-}
-
-/* read a list of $frames */
-static bool lex_finish_frames(lex_file *lex)
-{
-    do {
-        size_t i;
-        int    rc;
-        frame_macro m;
-
-        rc = lex_parse_frame(lex);
-        if (rc > 0) /* end of line */
-            return true;
-        if (rc < 0) /* error */
-            return false;
-
-        for (i = 0; i < vec_size(lex->frames); ++i) {
-            if (!strcmp(lex->tok.value, lex->frames[i].name)) {
-                lex->frames[i].value = lex->framevalue++;
-                if (lexwarn(lex, WARN_FRAME_MACROS, "duplicate frame macro defined: `%s`", lex->tok.value))
-                    return false;
-                break;
-            }
-        }
-        if (i < vec_size(lex->frames))
-            continue;
-
-        m.value = lex->framevalue++;
-        m.name = util_strdup(lex->tok.value);
-        vec_shrinkto(lex->tok.value, 0);
-        vec_push(lex->frames, m);
-    } while (true);
-
-    return false;
-}
-
-static int GMQCC_WARN lex_finish_string(lex_file *lex, int quote)
-{
-    utf8ch_t chr = 0;
-    int ch = 0, texttype = 0;
-    int nextch;
-    bool hex;
-    bool oct;
-    char u8buf[8]; /* way more than enough */
-    int  u8len, uc;
-
-    while (ch != FS_FILE_EOF)
-    {
-        ch = lex_getch(lex);
-        if (ch == quote)
-            return TOKEN_STRINGCONST;
-
-        if (lex->flags.preprocessing && ch == '\\') {
-            lex_tokench(lex, ch);
-            ch = lex_getch(lex);
-            if (ch == FS_FILE_EOF) {
-                lexerror(lex, "unexpected end of file");
-                lex_ungetch(lex, FS_FILE_EOF); /* next token to be TOKEN_EOF */
-                return (lex->tok.ttype = TOKEN_ERROR);
-            }
-            lex_tokench(lex, ch);
-        }
-        else if (ch == '\\') {
-            ch = lex_getch(lex);
-            if (ch == FS_FILE_EOF) {
-                lexerror(lex, "unexpected end of file");
-                lex_ungetch(lex, FS_FILE_EOF); /* next token to be TOKEN_EOF */
-                return (lex->tok.ttype = TOKEN_ERROR);
-            }
-
-            switch (ch) {
-            case '\\': break;
-            case '\'': break;
-            case '"':  break;
-            case 'a': ch = '\a'; break;
-            case 'r': ch = '\r'; break;
-            case 'n': ch = '\n'; break;
-            case 't': ch = '\t'; break;
-            case 'f': ch = '\f'; break;
-            case 'v': ch = '\v'; break;
-            case 'x':
-            case 'X':
-                /* same procedure as in fteqcc */
-                ch = 0;
-                nextch = lex_getch(lex);
-                if      (nextch >= '0' && nextch <= '9')
-                    ch += nextch - '0';
-                else if (nextch >= 'a' && nextch <= 'f')
-                    ch += nextch - 'a' + 10;
-                else if (nextch >= 'A' && nextch <= 'F')
-                    ch += nextch - 'A' + 10;
-                else {
-                    lexerror(lex, "bad character code");
-                    lex_ungetch(lex, nextch);
-                    return (lex->tok.ttype = TOKEN_ERROR);
-                }
-
-                ch *= 0x10;
-                nextch = lex_getch(lex);
-                if      (nextch >= '0' && nextch <= '9')
-                    ch += nextch - '0';
-                else if (nextch >= 'a' && nextch <= 'f')
-                    ch += nextch - 'a' + 10;
-                else if (nextch >= 'A' && nextch <= 'F')
-                    ch += nextch - 'A' + 10;
-                else {
-                    lexerror(lex, "bad character code");
-                    lex_ungetch(lex, nextch);
-                    return (lex->tok.ttype = TOKEN_ERROR);
-                }
-                break;
-
-            /* fteqcc support */
-            case '0': case '1': case '2': case '3':
-            case '4': case '5': case '6': case '7':
-            case '8': case '9':
-                ch = 18 + ch - '0';
-                break;
-            case '<':  ch = 29; break;
-            case '-':  ch = 30; break;
-            case '>':  ch = 31; break;
-            case '[':  ch = 16; break;
-            case ']':  ch = 17; break;
-            case '{':
-                chr = 0;
-                nextch = lex_getch(lex);
-                hex = (nextch == 'x');
-                oct = (nextch == '0');
-                if (!hex && !oct)
-                    lex_ungetch(lex, nextch);
-                for (nextch = lex_getch(lex); nextch != '}'; nextch = lex_getch(lex)) {
-                    if (!hex && !oct) {
-                        if (nextch >= '0' && nextch <= '9')
-                            chr = chr * 10 + nextch - '0';
-                        else {
-                            lexerror(lex, "bad character code");
-                            return (lex->tok.ttype = TOKEN_ERROR);
-                        }
-                    } else if (!oct) {
-                        if (nextch >= '0' && nextch <= '9')
-                            chr = chr * 0x10 + nextch - '0';
-                        else if (nextch >= 'a' && nextch <= 'f')
-                            chr = chr * 0x10 + nextch - 'a' + 10;
-                        else if (nextch >= 'A' && nextch <= 'F')
-                            chr = chr * 0x10 + nextch - 'A' + 10;
-                        else {
-                            lexerror(lex, "bad character code");
-                            return (lex->tok.ttype = TOKEN_ERROR);
-                        }
-                    } else {
-                        if (nextch >= '0' && nextch <= '9')
-                            chr = chr * 8 + chr - '0';
-                        else {
-                            lexerror(lex, "bad character code");
-                            return (lex->tok.ttype = TOKEN_ERROR);
-                        }
-                    }
-                    if (chr > 0x10FFFF || (!OPTS_FLAG(UTF8) && chr > 255))
-                    {
-                        lexerror(lex, "character code out of range");
-                        return (lex->tok.ttype = TOKEN_ERROR);
-                    }
-                }
-                if (OPTS_FLAG(UTF8) && chr >= 128) {
-                    u8len = utf8_from(u8buf, chr);
-                    if (!u8len)
-                        ch = 0;
-                    else {
-                        --u8len;
-                        lex->column += u8len;
-                        for (uc = 0; uc < u8len; ++uc)
-                            lex_tokench(lex, u8buf[uc]);
-                        /*
-                         * the last character will be inserted with the tokench() call
-                         * below the switch
-                         */
-                        ch = u8buf[uc];
-                    }
-                }
-                else
-                    ch = chr;
-                break;
-
-            /* high bit text */
-            case 'b': case 's':
-                texttype ^= 128;
-                continue;
-
-            case '\n':
-                ch = '\n';
-                break;
-
-            default:
-                lexwarn(lex, WARN_UNKNOWN_CONTROL_SEQUENCE, "unrecognized control sequence: \\%c", ch);
-                /* so we just add the character plus backslash no matter what it actually is */
-                lex_tokench(lex, '\\');
-            }
-            /* add the character finally */
-            lex_tokench(lex, ch | texttype);
-        }
-        else
-            lex_tokench(lex, ch);
-    }
-    lexerror(lex, "unexpected end of file within string constant");
-    lex_ungetch(lex, FS_FILE_EOF); /* next token to be TOKEN_EOF */
-    return (lex->tok.ttype = TOKEN_ERROR);
-}
-
-static int GMQCC_WARN lex_finish_digit(lex_file *lex, int lastch)
-{
-    bool ishex = false;
-
-    int  ch = lastch;
-
-    /* parse a number... */
-    if (ch == '.')
-        lex->tok.ttype = TOKEN_FLOATCONST;
-    else
-        lex->tok.ttype = TOKEN_INTCONST;
-
-    lex_tokench(lex, ch);
-
-    ch = lex_getch(lex);
-    if (ch != '.' && !util_isdigit(ch))
-    {
-        if (lastch != '0' || ch != 'x')
-        {
-            /* end of the number or EOF */
-            lex_ungetch(lex, ch);
-            lex_endtoken(lex);
-
-            lex->tok.constval.i = lastch - '0';
-            return lex->tok.ttype;
-        }
-
-        ishex = true;
-    }
-
-    /* EOF would have been caught above */
-
-    if (ch != '.')
-    {
-        lex_tokench(lex, ch);
-        ch = lex_getch(lex);
-        while (util_isdigit(ch) || (ishex && isxdigit_only(ch)))
-        {
-            lex_tokench(lex, ch);
-            ch = lex_getch(lex);
-        }
-    }
-    /* NOT else, '.' can come from above as well */
-    if (lex->tok.ttype != TOKEN_FLOATCONST && ch == '.' && !ishex)
-    {
-        /* Allow floating comma in non-hex mode */
-        lex->tok.ttype = TOKEN_FLOATCONST;
-        lex_tokench(lex, ch);
-
-        /* continue digits-only */
-        ch = lex_getch(lex);
-        while (util_isdigit(ch))
-        {
-            lex_tokench(lex, ch);
-            ch = lex_getch(lex);
-        }
-    }
-    /* put back the last character */
-    /* but do not put back the trailing 'f' or a float */
-    if (lex->tok.ttype == TOKEN_FLOATCONST && ch == 'f')
-        ch = lex_getch(lex);
-
-    /* generally we don't want words to follow numbers: */
-    if (isident(ch)) {
-        lexerror(lex, "unexpected trailing characters after number");
-        return (lex->tok.ttype = TOKEN_ERROR);
-    }
-    lex_ungetch(lex, ch);
-
-    lex_endtoken(lex);
-    if (lex->tok.ttype == TOKEN_FLOATCONST)
-        lex->tok.constval.f = strtod(lex->tok.value, NULL);
-    else
-        lex->tok.constval.i = strtol(lex->tok.value, NULL, 0);
-    return lex->tok.ttype;
-}
-
-int lex_do(lex_file *lex)
-{
-    int ch, nextch, thirdch;
-    bool hadwhite = false;
-
-    lex_token_new(lex);
-
-    while (true) {
-        ch = lex_skipwhite(lex, hadwhite);
-        hadwhite = true;
-        if (!lex->flags.mergelines || ch != '\\')
-            break;
-        ch = lex_getch(lex);
-        if (ch == '\r')
-            ch = lex_getch(lex);
-        if (ch != '\n') {
-            lex_ungetch(lex, ch);
-            ch = '\\';
-            break;
-        }
-        /* we reached a linemerge */
-        lex_tokench(lex, '\n');
-        continue;
-    }
-
-    if (lex->flags.preprocessing && (ch == TOKEN_WHITE || ch == TOKEN_EOL || ch == TOKEN_FATAL)) {
-        return (lex->tok.ttype = ch);
-    }
-
-    lex->sline = lex->line;
-    lex->tok.ctx.line = lex->sline;
-    lex->tok.ctx.file = lex->name;
-
-    if (lex->eof)
-        return (lex->tok.ttype = TOKEN_FATAL);
-
-    if (ch == FS_FILE_EOF) {
-        lex->eof = true;
-        return (lex->tok.ttype = TOKEN_EOF);
-    }
-
-    /* modelgen / spiritgen commands */
-    if (ch == '$' && !lex->flags.preprocessing) {
-        const char *v;
-        size_t frame;
-
-        ch = lex_getch(lex);
-        if (!isident_start(ch)) {
-            lexerror(lex, "hanging '$' modelgen/spritegen command line");
-            return lex_do(lex);
-        }
-        lex_tokench(lex, ch);
-        if (!lex_finish_ident(lex))
-            return (lex->tok.ttype = TOKEN_ERROR);
-        lex_endtoken(lex);
-        /* skip the known commands */
-        v = lex->tok.value;
-
-        if (!strcmp(v, "frame") || !strcmp(v, "framesave"))
-        {
-            /* frame/framesave command works like an enum
-             * similar to fteqcc we handle this in the lexer.
-             * The reason for this is that it is sensitive to newlines,
-             * which the parser is unaware of
-             */
-            if (!lex_finish_frames(lex))
-                 return (lex->tok.ttype = TOKEN_ERROR);
-            return lex_do(lex);
-        }
-
-        if (!strcmp(v, "framevalue"))
-        {
-            ch = lex_getch(lex);
-            while (ch != FS_FILE_EOF && util_isspace(ch) && ch != '\n')
-                ch = lex_getch(lex);
-
-            if (!util_isdigit(ch)) {
-                lexerror(lex, "$framevalue requires an integer parameter");
-                return lex_do(lex);
-            }
-
-            lex_token_new(lex);
-            lex->tok.ttype = lex_finish_digit(lex, ch);
-            lex_endtoken(lex);
-            if (lex->tok.ttype != TOKEN_INTCONST) {
-                lexerror(lex, "$framevalue requires an integer parameter");
-                return lex_do(lex);
-            }
-            lex->framevalue = lex->tok.constval.i;
-            return lex_do(lex);
-        }
-
-        if (!strcmp(v, "framerestore"))
-        {
-            int rc;
-
-            lex_token_new(lex);
-
-            rc = lex_parse_frame(lex);
-
-            if (rc > 0) {
-                lexerror(lex, "$framerestore requires a framename parameter");
-                return lex_do(lex);
-            }
-            if (rc < 0)
-                return (lex->tok.ttype = TOKEN_FATAL);
-
-            v = lex->tok.value;
-            for (frame = 0; frame < vec_size(lex->frames); ++frame) {
-                if (!strcmp(v, lex->frames[frame].name)) {
-                    lex->framevalue = lex->frames[frame].value;
-                    return lex_do(lex);
-                }
-            }
-            lexerror(lex, "unknown framename `%s`", v);
-            return lex_do(lex);
-        }
-
-        if (!strcmp(v, "modelname"))
-        {
-            int rc;
-
-            lex_token_new(lex);
-
-            rc = lex_parse_frame(lex);
-
-            if (rc > 0) {
-                lexerror(lex, "$modelname requires a parameter");
-                return lex_do(lex);
-            }
-            if (rc < 0)
-                return (lex->tok.ttype = TOKEN_FATAL);
-
-            if (lex->modelname) {
-                frame_macro m;
-                m.value = lex->framevalue;
-                m.name = lex->modelname;
-                lex->modelname = NULL;
-                vec_push(lex->frames, m);
-            }
-            lex->modelname = lex->tok.value;
-            lex->tok.value = NULL;
-            return lex_do(lex);
-        }
-
-        if (!strcmp(v, "flush"))
-        {
-            size_t fi;
-            for (fi = 0; fi < vec_size(lex->frames); ++fi)
-                mem_d(lex->frames[fi].name);
-            vec_free(lex->frames);
-            /* skip line (fteqcc does it too) */
-            ch = lex_getch(lex);
-            while (ch != FS_FILE_EOF && ch != '\n')
-                ch = lex_getch(lex);
-            return lex_do(lex);
-        }
-
-        if (!strcmp(v, "cd") ||
-            !strcmp(v, "origin") ||
-            !strcmp(v, "base") ||
-            !strcmp(v, "flags") ||
-            !strcmp(v, "scale") ||
-            !strcmp(v, "skin"))
-        {
-            /* skip line */
-            ch = lex_getch(lex);
-            while (ch != FS_FILE_EOF && ch != '\n')
-                ch = lex_getch(lex);
-            return lex_do(lex);
-        }
-
-        for (frame = 0; frame < vec_size(lex->frames); ++frame) {
-            if (!strcmp(v, lex->frames[frame].name)) {
-                lex->tok.constval.i = lex->frames[frame].value;
-                return (lex->tok.ttype = TOKEN_INTCONST);
-            }
-        }
-
-        lexerror(lex, "invalid frame macro");
-        return lex_do(lex);
-    }
-
-    /* single-character tokens */
-    switch (ch)
-    {
-        case '[':
-            nextch = lex_getch(lex);
-            if (nextch == '[') {
-                lex_tokench(lex, ch);
-                lex_tokench(lex, nextch);
-                lex_endtoken(lex);
-                return (lex->tok.ttype = TOKEN_ATTRIBUTE_OPEN);
-            }
-            lex_ungetch(lex, nextch);
-            /* FALL THROUGH */
-        case '(':
-        case ':':
-        case '?':
-            lex_tokench(lex, ch);
-            lex_endtoken(lex);
-            if (lex->flags.noops)
-                return (lex->tok.ttype = ch);
-            else
-                return (lex->tok.ttype = TOKEN_OPERATOR);
-
-        case ']':
-            if (lex->flags.noops) {
-                nextch = lex_getch(lex);
-                if (nextch == ']') {
-                    lex_tokench(lex, ch);
-                    lex_tokench(lex, nextch);
-                    lex_endtoken(lex);
-                    return (lex->tok.ttype = TOKEN_ATTRIBUTE_CLOSE);
-                }
-                lex_ungetch(lex, nextch);
-            }
-            /* FALL THROUGH */
-        case ')':
-        case ';':
-        case '{':
-        case '}':
-
-        case '#':
-            lex_tokench(lex, ch);
-            lex_endtoken(lex);
-            return (lex->tok.ttype = ch);
-        default:
-            break;
-    }
-
-    if (ch == '.') {
-        nextch = lex_getch(lex);
-        /* digits starting with a dot */
-        if (util_isdigit(nextch)) {
-            lex_ungetch(lex, nextch);
-            lex->tok.ttype = lex_finish_digit(lex, ch);
-            lex_endtoken(lex);
-            return lex->tok.ttype;
-        }
-        lex_ungetch(lex, nextch);
-    }
-
-    if (lex->flags.noops)
-    {
-        /* Detect characters early which are normally
-         * operators OR PART of an operator.
-         */
-        switch (ch)
-        {
-            case '*':
-            case '/':
-            case '<':
-            case '>':
-            case '=':
-            case '&':
-            case '|':
-            case '^':
-            case '~':
-            case ',':
-            case '!':
-                lex_tokench(lex, ch);
-                lex_endtoken(lex);
-                return (lex->tok.ttype = ch);
-            default:
-                break;
-        }
-    }
-
-    if (ch == '.')
-    {
-        lex_tokench(lex, ch);
-        /* peak ahead once */
-        nextch = lex_getch(lex);
-        if (nextch != '.') {
-            lex_ungetch(lex, nextch);
-            lex_endtoken(lex);
-            if (lex->flags.noops)
-                return (lex->tok.ttype = ch);
-            else
-                return (lex->tok.ttype = TOKEN_OPERATOR);
-        }
-        /* peak ahead again */
-        nextch = lex_getch(lex);
-        if (nextch != '.') {
-            lex_ungetch(lex, nextch);
-            lex_ungetch(lex, '.');
-            lex_endtoken(lex);
-            if (lex->flags.noops)
-                return (lex->tok.ttype = ch);
-            else
-                return (lex->tok.ttype = TOKEN_OPERATOR);
-        }
-        /* fill the token to be "..." */
-        lex_tokench(lex, ch);
-        lex_tokench(lex, ch);
-        lex_endtoken(lex);
-        return (lex->tok.ttype = TOKEN_DOTS);
-    }
-
-    if (ch == ',' || ch == '.') {
-        lex_tokench(lex, ch);
-        lex_endtoken(lex);
-        return (lex->tok.ttype = TOKEN_OPERATOR);
-    }
-
-    if (ch == '+' || ch == '-' || /* ++, --, +=, -=  and -> as well! */
-        ch == '>' || ch == '<' || /* <<, >>, <=, >=  and >< as well! */
-        ch == '=' || ch == '!' || /* <=>, ==, !=                     */
-        ch == '&' || ch == '|' || /* &&, ||, &=, |=                  */
-        ch == '~' || ch == '^'    /* ~=, ~, ^                        */
-    )  {
-        lex_tokench(lex, ch);
-        nextch = lex_getch(lex);
-
-        if ((nextch == '=' && ch != '<') || (nextch == '<' && ch == '>'))
-            lex_tokench(lex, nextch);
-        else if (nextch == ch && ch != '!') {
-            lex_tokench(lex, nextch);
-            if ((thirdch = lex_getch(lex)) == '=')
-                lex_tokench(lex, thirdch);
-            else
-                lex_ungetch(lex, thirdch);
-        } else if (ch == '<' && nextch == '=') {
-            lex_tokench(lex, nextch);
-            if ((thirdch = lex_getch(lex)) == '>')
-                lex_tokench(lex, thirdch);
-            else
-                lex_ungetch(lex, thirdch);
-
-        } else if (ch == '-' && nextch == '>') {
-            lex_tokench(lex, nextch);
-        } else if (ch == '&' && nextch == '~') {
-            thirdch = lex_getch(lex);
-            if (thirdch != '=') {
-                lex_ungetch(lex, thirdch);
-                lex_ungetch(lex, nextch);
-            }
-            else {
-                lex_tokench(lex, nextch);
-                lex_tokench(lex, thirdch);
-            }
-        }
-        else if (lex->flags.preprocessing &&
-                 ch == '-' && util_isdigit(nextch))
-        {
-            lex->tok.ttype = lex_finish_digit(lex, nextch);
-            if (lex->tok.ttype == TOKEN_INTCONST)
-                lex->tok.constval.i = -lex->tok.constval.i;
-            else
-                lex->tok.constval.f = -lex->tok.constval.f;
-            lex_endtoken(lex);
-            return lex->tok.ttype;
-        } else {
-            lex_ungetch(lex, nextch);
-        }
-
-        lex_endtoken(lex);
-        return (lex->tok.ttype = TOKEN_OPERATOR);
-    }
-
-    if (ch == '*' || ch == '/') /* *=, /= */
-    {
-        lex_tokench(lex, ch);
-
-        nextch = lex_getch(lex);
-        if (nextch == '=' || nextch == '*') {
-            lex_tokench(lex, nextch);
-        } else
-            lex_ungetch(lex, nextch);
-
-        lex_endtoken(lex);
-        return (lex->tok.ttype = TOKEN_OPERATOR);
-    }
-
-    if (ch == '%') {
-        lex_tokench(lex, ch);
-        lex_endtoken(lex);
-        return (lex->tok.ttype = TOKEN_OPERATOR);
-    }
-
-    if (isident_start(ch))
-    {
-        const char *v;
-
-        lex_tokench(lex, ch);
-        if (!lex_finish_ident(lex)) {
-            /* error? */
-            return (lex->tok.ttype = TOKEN_ERROR);
-        }
-        lex_endtoken(lex);
-        lex->tok.ttype = TOKEN_IDENT;
-
-        v = lex->tok.value;
-        if (!strcmp(v, "void")) {
-            lex->tok.ttype = TOKEN_TYPENAME;
-            lex->tok.constval.t = TYPE_VOID;
-        } else if (!strcmp(v, "int")) {
-            lex->tok.ttype = TOKEN_TYPENAME;
-            lex->tok.constval.t = TYPE_INTEGER;
-        } else if (!strcmp(v, "float")) {
-            lex->tok.ttype = TOKEN_TYPENAME;
-            lex->tok.constval.t = TYPE_FLOAT;
-        } else if (!strcmp(v, "string")) {
-            lex->tok.ttype = TOKEN_TYPENAME;
-            lex->tok.constval.t = TYPE_STRING;
-        } else if (!strcmp(v, "entity")) {
-            lex->tok.ttype = TOKEN_TYPENAME;
-            lex->tok.constval.t = TYPE_ENTITY;
-        } else if (!strcmp(v, "vector")) {
-            lex->tok.ttype = TOKEN_TYPENAME;
-            lex->tok.constval.t = TYPE_VECTOR;
-        } else if (!strcmp(v, "_length")) {
-            lex->tok.ttype = TOKEN_OPERATOR;
-        } else {
-            size_t kw;
-            for (kw = 0; kw < GMQCC_ARRAY_COUNT(keywords_qc); ++kw) {
-                if (!strcmp(v, keywords_qc[kw]))
-                    return (lex->tok.ttype = TOKEN_KEYWORD);
-            }
-            if (OPTS_OPTION_U32(OPTION_STANDARD) != COMPILER_QCC) {
-                for (kw = 0; kw < GMQCC_ARRAY_COUNT(keywords_fg); ++kw) {
-                    if (!strcmp(v, keywords_fg[kw]))
-                        return (lex->tok.ttype = TOKEN_KEYWORD);
-                }
-            }
-        }
-
-        return lex->tok.ttype;
-    }
-
-    if (ch == '"')
-    {
-        lex->flags.nodigraphs = true;
-        if (lex->flags.preprocessing)
-            lex_tokench(lex, ch);
-        lex->tok.ttype = lex_finish_string(lex, '"');
-        if (lex->flags.preprocessing)
-            lex_tokench(lex, ch);
-        while (!lex->flags.preprocessing && lex->tok.ttype == TOKEN_STRINGCONST)
-        {
-            /* Allow c style "string" "continuation" */
-            ch = lex_skipwhite(lex, false);
-            if (ch != '"') {
-                lex_ungetch(lex, ch);
-                break;
-            }
-
-            lex->tok.ttype = lex_finish_string(lex, '"');
-        }
-        lex->flags.nodigraphs = false;
-        lex_endtoken(lex);
-        return lex->tok.ttype;
-    }
-
-    if (ch == '\'')
-    {
-        /* we parse character constants like string,
-         * but return TOKEN_CHARCONST, or a vector type if it fits...
-         * Likewise actual unescaping has to be done by the parser.
-         * The difference is we don't allow 'char' 'continuation'.
-         */
-        if (lex->flags.preprocessing)
-            lex_tokench(lex, ch);
-        lex->tok.ttype = lex_finish_string(lex, '\'');
-        if (lex->flags.preprocessing)
-            lex_tokench(lex, ch);
-        lex_endtoken(lex);
-
-        lex->tok.ttype = TOKEN_CHARCONST;
-
-        /* It's a vector if we can successfully scan 3 floats */
-        if (util_sscanf(lex->tok.value, " %f %f %f ",
-                   &lex->tok.constval.v.x, &lex->tok.constval.v.y, &lex->tok.constval.v.z) == 3)
-
-        {
-             lex->tok.ttype = TOKEN_VECTORCONST;
-        }
-        else
-        {
-            if (!lex->flags.preprocessing && strlen(lex->tok.value) > 1) {
-                utf8ch_t u8char;
-                /* check for a valid utf8 character */
-                if (!OPTS_FLAG(UTF8) || !utf8_to(&u8char, (const unsigned char *)lex->tok.value, 8)) {
-                    if (lexwarn(lex, WARN_MULTIBYTE_CHARACTER,
-                                ( OPTS_FLAG(UTF8) ? "invalid multibyte character sequence `%s`"
-                                                  : "multibyte character: `%s`" ),
-                                lex->tok.value))
-                        return (lex->tok.ttype = TOKEN_ERROR);
-                }
-                else
-                    lex->tok.constval.i = u8char;
-            }
-            else
-                lex->tok.constval.i = lex->tok.value[0];
-        }
-
-        return lex->tok.ttype;
-    }
-
-    if (util_isdigit(ch))
-    {
-        lex->tok.ttype = lex_finish_digit(lex, ch);
-        lex_endtoken(lex);
-        return lex->tok.ttype;
-    }
-
-    if (lex->flags.preprocessing) {
-        lex_tokench(lex, ch);
-        lex_endtoken(lex);
-        return (lex->tok.ttype = ch);
-    }
-
-    lexerror(lex, "unknown token: `%c`", ch);
-    return (lex->tok.ttype = TOKEN_ERROR);
-}
diff --git a/lexer.cpp b/lexer.cpp
new file mode 100644 (file)
index 0000000..34fc71b
--- /dev/null
+++ b/lexer.cpp
@@ -0,0 +1,1423 @@
+#include <string.h>
+#include <stdlib.h>
+
+#include "gmqcc.h"
+#include "lexer.h"
+
+/*
+ * List of Keywords
+ */
+
+/* original */
+static const char *keywords_qc[] = {
+    "for", "do", "while",
+    "if", "else",
+    "local",
+    "return",
+    "const"
+};
+/* For fte/gmgqcc */
+static const char *keywords_fg[] = {
+    "switch", "case", "default",
+    "struct", "union",
+    "break", "continue",
+    "typedef",
+    "goto",
+
+    "__builtin_debug_printtype"
+};
+
+/*
+ * Lexer code
+ */
+static char* *lex_filenames;
+
+static void lexerror(lex_file *lex, const char *fmt, ...)
+{
+    va_list ap;
+
+    va_start(ap, fmt);
+    if (lex)
+        con_vprintmsg(LVL_ERROR, lex->name, lex->sline, lex->column, "parse error", fmt, ap);
+    else
+        con_vprintmsg(LVL_ERROR, "", 0, 0, "parse error", fmt, ap);
+    va_end(ap);
+}
+
+static bool lexwarn(lex_file *lex, int warntype, const char *fmt, ...)
+{
+    bool      r;
+    lex_ctx_t ctx;
+    va_list   ap;
+
+    ctx.file   = lex->name;
+    ctx.line   = lex->sline;
+    ctx.column = lex->column;
+
+    va_start(ap, fmt);
+    r = vcompile_warning(ctx, warntype, fmt, ap);
+    va_end(ap);
+    return r;
+}
+
+static void lex_token_new(lex_file *lex)
+{
+    if (lex->tok.value)
+        vec_shrinkto(lex->tok.value, 0);
+
+    lex->tok.constval.t  = TYPE_VOID;
+    lex->tok.ctx.line    = lex->sline;
+    lex->tok.ctx.file    = lex->name;
+    lex->tok.ctx.column  = lex->column;
+}
+
+static void lex_ungetch(lex_file *lex, int ch);
+static int lex_getch(lex_file *lex);
+
+lex_file* lex_open(const char *file)
+{
+    lex_file  *lex;
+    FILE *in = fopen(file, "rb");
+    uint32_t   read;
+
+    if (!in) {
+        lexerror(nullptr, "open failed: '%s'\n", file);
+        return nullptr;
+    }
+
+    lex = (lex_file*)mem_a(sizeof(*lex));
+    if (!lex) {
+        fclose(in);
+        lexerror(nullptr, "out of memory\n");
+        return nullptr;
+    }
+
+    memset(lex, 0, sizeof(*lex));
+
+    lex->file    = in;
+    lex->name    = util_strdup(file);
+    lex->line    = 1; /* we start counting at 1 */
+    lex->column  = 0;
+    lex->peekpos = 0;
+    lex->eof     = false;
+
+    /* handle BOM */
+    if ((read = (lex_getch(lex) << 16) | (lex_getch(lex) << 8) | lex_getch(lex)) != 0xEFBBBF) {
+        lex_ungetch(lex, (read & 0x0000FF));
+        lex_ungetch(lex, (read & 0x00FF00) >> 8);
+        lex_ungetch(lex, (read & 0xFF0000) >> 16);
+    } else {
+        /*
+         * otherwise the lexer has advanced 3 bytes for the BOM, we need
+         * to set the column back to 0
+         */
+        lex->column = 0;
+    }
+
+    vec_push(lex_filenames, lex->name);
+    return lex;
+}
+
+lex_file* lex_open_string(const char *str, size_t len, const char *name)
+{
+    lex_file *lex;
+
+    lex = (lex_file*)mem_a(sizeof(*lex));
+    if (!lex) {
+        lexerror(nullptr, "out of memory\n");
+        return nullptr;
+    }
+
+    memset(lex, 0, sizeof(*lex));
+
+    lex->file = nullptr;
+    lex->open_string        = str;
+    lex->open_string_length = len;
+    lex->open_string_pos    = 0;
+
+    lex->name    = util_strdup(name ? name : "<string-source>");
+    lex->line    = 1; /* we start counting at 1 */
+    lex->peekpos = 0;
+    lex->eof     = false;
+    lex->column  = 0;
+
+    vec_push(lex_filenames, lex->name);
+
+    return lex;
+}
+
+void lex_cleanup(void)
+{
+    size_t i;
+    for (i = 0; i < vec_size(lex_filenames); ++i)
+        mem_d(lex_filenames[i]);
+    vec_free(lex_filenames);
+}
+
+void lex_close(lex_file *lex)
+{
+    size_t i;
+    for (i = 0; i < vec_size(lex->frames); ++i)
+        mem_d(lex->frames[i].name);
+    vec_free(lex->frames);
+
+    if (lex->modelname)
+        vec_free(lex->modelname);
+
+    if (lex->file)
+        fclose(lex->file);
+
+    vec_free(lex->tok.value);
+
+    /* mem_d(lex->name); collected in lex_filenames */
+    mem_d(lex);
+}
+
+
+
+static int lex_fgetc(lex_file *lex)
+{
+    if (lex->file) {
+        lex->column++;
+        return fgetc(lex->file);
+    }
+    if (lex->open_string) {
+        if (lex->open_string_pos >= lex->open_string_length)
+            return EOF;
+        lex->column++;
+        return lex->open_string[lex->open_string_pos++];
+    }
+    return EOF;
+}
+
+/* Get or put-back data
+ * The following to functions do NOT understand what kind of data they
+ * are working on.
+ * The are merely wrapping get/put in order to count line numbers.
+ */
+static int lex_try_trigraph(lex_file *lex, int old)
+{
+    int c2, c3;
+    c2 = lex_fgetc(lex);
+    if (!lex->push_line && c2 == '\n') {
+        lex->line++;
+        lex->column = 0;
+    }
+
+    if (c2 != '?') {
+        lex_ungetch(lex, c2);
+        return old;
+    }
+
+    c3 = lex_fgetc(lex);
+    if (!lex->push_line && c3 == '\n') {
+        lex->line++;
+        lex->column = 0;
+    }
+
+    switch (c3) {
+        case '=': return '#';
+        case '/': return '\\';
+        case '\'': return '^';
+        case '(': return '[';
+        case ')': return ']';
+        case '!': return '|';
+        case '<': return '{';
+        case '>': return '}';
+        case '-': return '~';
+        default:
+            lex_ungetch(lex, c3);
+            lex_ungetch(lex, c2);
+            return old;
+    }
+}
+
+static int lex_try_digraph(lex_file *lex, int ch)
+{
+    int c2;
+    c2 = lex_fgetc(lex);
+    /* we just used fgetc() so count lines
+     * need to offset a \n the ungetch would recognize
+     */
+    if (!lex->push_line && c2 == '\n')
+        lex->line++;
+    if      (ch == '<' && c2 == ':')
+        return '[';
+    else if (ch == ':' && c2 == '>')
+        return ']';
+    else if (ch == '<' && c2 == '%')
+        return '{';
+    else if (ch == '%' && c2 == '>')
+        return '}';
+    else if (ch == '%' && c2 == ':')
+        return '#';
+    lex_ungetch(lex, c2);
+    return ch;
+}
+
+static int lex_getch(lex_file *lex)
+{
+    int ch;
+
+    if (lex->peekpos) {
+        lex->peekpos--;
+        if (!lex->push_line && lex->peek[lex->peekpos] == '\n') {
+            lex->line++;
+            lex->column = 0;
+        }
+        return lex->peek[lex->peekpos];
+    }
+
+    ch = lex_fgetc(lex);
+    if (!lex->push_line && ch == '\n') {
+        lex->line++;
+        lex->column = 0;
+    }
+    else if (ch == '?')
+        return lex_try_trigraph(lex, ch);
+    else if (!lex->flags.nodigraphs && (ch == '<' || ch == ':' || ch == '%'))
+        return lex_try_digraph(lex, ch);
+    return ch;
+}
+
+static void lex_ungetch(lex_file *lex, int ch)
+{
+    lex->peek[lex->peekpos++] = ch;
+    lex->column--;
+    if (!lex->push_line && ch == '\n') {
+        lex->line--;
+        lex->column = 0;
+    }
+}
+
+/* classify characters
+ * some additions to the is*() functions of ctype.h
+ */
+
+/* Idents are alphanumberic, but they start with alpha or _ */
+static bool isident_start(int ch)
+{
+    return util_isalpha(ch) || ch == '_';
+}
+
+static bool isident(int ch)
+{
+    return isident_start(ch) || util_isdigit(ch);
+}
+
+/* isxdigit_only is used when we already know it's not a digit
+ * and want to see if it's a hex digit anyway.
+ */
+static bool isxdigit_only(int ch)
+{
+    return (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F');
+}
+
+/* Append a character to the token buffer */
+static void lex_tokench(lex_file *lex, int ch)
+{
+    vec_push(lex->tok.value, ch);
+}
+
+/* Append a trailing null-byte */
+static void lex_endtoken(lex_file *lex)
+{
+    vec_push(lex->tok.value, 0);
+    vec_shrinkby(lex->tok.value, 1);
+}
+
+static bool lex_try_pragma(lex_file *lex)
+{
+    int ch;
+    char *pragma  = nullptr;
+    char *command = nullptr;
+    char *param   = nullptr;
+    size_t line;
+
+    if (lex->flags.preprocessing)
+        return false;
+
+    line = lex->line;
+
+    ch = lex_getch(lex);
+    if (ch != '#') {
+        lex_ungetch(lex, ch);
+        return false;
+    }
+
+    for (ch = lex_getch(lex); vec_size(pragma) < 8 && ch >= 'a' && ch <= 'z'; ch = lex_getch(lex))
+        vec_push(pragma, ch);
+    vec_push(pragma, 0);
+
+    if (ch != ' ' || strcmp(pragma, "pragma")) {
+        lex_ungetch(lex, ch);
+        goto unroll;
+    }
+
+    for (ch = lex_getch(lex); vec_size(command) < 32 && ch >= 'a' && ch <= 'z'; ch = lex_getch(lex))
+        vec_push(command, ch);
+    vec_push(command, 0);
+
+    if (ch != '(') {
+        lex_ungetch(lex, ch);
+        goto unroll;
+    }
+
+    for (ch = lex_getch(lex); vec_size(param) < 1024 && ch != ')' && ch != '\n'; ch = lex_getch(lex))
+        vec_push(param, ch);
+    vec_push(param, 0);
+
+    if (ch != ')') {
+        lex_ungetch(lex, ch);
+        goto unroll;
+    }
+
+    if (!strcmp(command, "push")) {
+        if (!strcmp(param, "line")) {
+            lex->push_line++;
+            if (lex->push_line == 1)
+                --line;
+        }
+        else
+            goto unroll;
+    }
+    else if (!strcmp(command, "pop")) {
+        if (!strcmp(param, "line")) {
+            if (lex->push_line)
+                lex->push_line--;
+            if (lex->push_line == 0)
+                --line;
+        }
+        else
+            goto unroll;
+    }
+    else if (!strcmp(command, "file")) {
+        lex->name = util_strdup(param);
+        vec_push(lex_filenames, lex->name);
+    }
+    else if (!strcmp(command, "line")) {
+        line = strtol(param, nullptr, 0)-1;
+    }
+    else
+        goto unroll;
+
+    lex->line = line;
+    while (ch != '\n' && ch != EOF)
+        ch = lex_getch(lex);
+    vec_free(command);
+    vec_free(param);
+    vec_free(pragma);
+    return true;
+
+unroll:
+    if (command) {
+        vec_pop(command);
+        while (vec_size(command)) {
+            lex_ungetch(lex, (unsigned char)vec_last(command));
+            vec_pop(command);
+        }
+        vec_free(command);
+        lex_ungetch(lex, ' ');
+    }
+    if (param) {
+        vec_pop(param);
+        while (vec_size(param)) {
+            lex_ungetch(lex, (unsigned char)vec_last(param));
+            vec_pop(param);
+        }
+        vec_free(param);
+        lex_ungetch(lex, ' ');
+    }
+    if (pragma) {
+        vec_pop(pragma);
+        while (vec_size(pragma)) {
+            lex_ungetch(lex, (unsigned char)vec_last(pragma));
+            vec_pop(pragma);
+        }
+        vec_free(pragma);
+    }
+    lex_ungetch(lex, '#');
+
+    lex->line = line;
+    return false;
+}
+
+/* Skip whitespace and comments and return the first
+ * non-white character.
+ * As this makes use of the above getch() ungetch() functions,
+ * we don't need to care at all about line numbering anymore.
+ *
+ * In theory, this function should only be used at the beginning
+ * of lexing, or when we *know* the next character is part of the token.
+ * Otherwise, if the parser throws an error, the linenumber may not be
+ * the line of the error, but the line of the next token AFTER the error.
+ *
+ * This is currently only problematic when using c-like string-continuation,
+ * since comments and whitespaces are allowed between 2 such strings.
+ * Example:
+printf(   "line one\n"
+// A comment
+          "A continuation of the previous string"
+// This line is skipped
+      , foo);
+
+ * In this case, if the parse decides it didn't actually want a string,
+ * and uses lex->line to print an error, it will show the ', foo);' line's
+ * linenumber.
+ *
+ * On the other hand, the parser is supposed to remember the line of the next
+ * token's beginning. In this case we would want skipwhite() to be called
+ * AFTER reading a token, so that the parser, before reading the NEXT token,
+ * doesn't store teh *comment's* linenumber, but the actual token's linenumber.
+ *
+ * THIS SOLUTION
+ *    here is to store the line of the first character after skipping
+ *    the initial whitespace in lex->sline, this happens in lex_do.
+ */
+static int lex_skipwhite(lex_file *lex, bool hadwhite)
+{
+    int ch = 0;
+    bool haswhite = hadwhite;
+
+    do
+    {
+        ch = lex_getch(lex);
+        while (ch != EOF && util_isspace(ch)) {
+            if (ch == '\n') {
+                if (lex_try_pragma(lex))
+                    continue;
+            }
+            if (lex->flags.preprocessing) {
+                if (ch == '\n') {
+                    /* end-of-line */
+                    /* see if there was whitespace first */
+                    if (haswhite) { /* (vec_size(lex->tok.value)) { */
+                        lex_ungetch(lex, ch);
+                        lex_endtoken(lex);
+                        return TOKEN_WHITE;
+                    }
+                    /* otherwise return EOL */
+                    return TOKEN_EOL;
+                }
+                haswhite = true;
+                lex_tokench(lex, ch);
+            }
+            ch = lex_getch(lex);
+        }
+
+        if (ch == '/') {
+            ch = lex_getch(lex);
+            if (ch == '/')
+            {
+                /* one line comment */
+                ch = lex_getch(lex);
+
+                if (lex->flags.preprocessing) {
+                    haswhite = true;
+                    lex_tokench(lex, ' ');
+                    lex_tokench(lex, ' ');
+                }
+
+                while (ch != EOF && ch != '\n') {
+                    if (lex->flags.preprocessing)
+                        lex_tokench(lex, ' '); /* ch); */
+                    ch = lex_getch(lex);
+                }
+                if (lex->flags.preprocessing) {
+                    lex_ungetch(lex, '\n');
+                    lex_endtoken(lex);
+                    return TOKEN_WHITE;
+                }
+                continue;
+            }
+            if (ch == '*')
+            {
+                /* multiline comment */
+                if (lex->flags.preprocessing) {
+                    haswhite = true;
+                    lex_tokench(lex, ' ');
+                    lex_tokench(lex, ' ');
+                }
+
+                while (ch != EOF)
+                {
+                    ch = lex_getch(lex);
+                    if (ch == '*') {
+                        ch = lex_getch(lex);
+                        if (ch == '/') {
+                            if (lex->flags.preprocessing) {
+                                lex_tokench(lex, ' ');
+                                lex_tokench(lex, ' ');
+                            }
+                            break;
+                        }
+                        lex_ungetch(lex, ch);
+                    }
+                    if (lex->flags.preprocessing) {
+                        if (ch == '\n')
+                            lex_tokench(lex, '\n');
+                        else
+                            lex_tokench(lex, ' ');
+                    }
+                }
+                ch = ' '; /* cause TRUE in the isspace check */
+                continue;
+            }
+            /* Otherwise roll back to the slash and break out of the loop */
+            lex_ungetch(lex, ch);
+            ch = '/';
+            break;
+        }
+    } while (ch != EOF && util_isspace(ch));
+
+    if (haswhite) {
+        lex_endtoken(lex);
+        lex_ungetch(lex, ch);
+        return TOKEN_WHITE;
+    }
+    return ch;
+}
+
+/* Get a token */
+static bool GMQCC_WARN lex_finish_ident(lex_file *lex)
+{
+    int ch;
+
+    ch = lex_getch(lex);
+    while (ch != EOF && isident(ch))
+    {
+        lex_tokench(lex, ch);
+        ch = lex_getch(lex);
+    }
+
+    /* last ch was not an ident ch: */
+    lex_ungetch(lex, ch);
+
+    return true;
+}
+
+/* read one ident for the frame list */
+static int lex_parse_frame(lex_file *lex)
+{
+    int ch;
+
+    lex_token_new(lex);
+
+    ch = lex_getch(lex);
+    while (ch != EOF && ch != '\n' && util_isspace(ch))
+        ch = lex_getch(lex);
+
+    if (ch == '\n')
+        return 1;
+
+    if (!isident_start(ch)) {
+        lexerror(lex, "invalid framename, must start with one of a-z or _, got %c", ch);
+        return -1;
+    }
+
+    lex_tokench(lex, ch);
+    if (!lex_finish_ident(lex))
+        return -1;
+    lex_endtoken(lex);
+    return 0;
+}
+
+/* read a list of $frames */
+static bool lex_finish_frames(lex_file *lex)
+{
+    do {
+        size_t i;
+        int    rc;
+        frame_macro m;
+
+        rc = lex_parse_frame(lex);
+        if (rc > 0) /* end of line */
+            return true;
+        if (rc < 0) /* error */
+            return false;
+
+        for (i = 0; i < vec_size(lex->frames); ++i) {
+            if (!strcmp(lex->tok.value, lex->frames[i].name)) {
+                lex->frames[i].value = lex->framevalue++;
+                if (lexwarn(lex, WARN_FRAME_MACROS, "duplicate frame macro defined: `%s`", lex->tok.value))
+                    return false;
+                break;
+            }
+        }
+        if (i < vec_size(lex->frames))
+            continue;
+
+        m.value = lex->framevalue++;
+        m.name = util_strdup(lex->tok.value);
+        vec_shrinkto(lex->tok.value, 0);
+        vec_push(lex->frames, m);
+    } while (true);
+
+    return false;
+}
+
+static int GMQCC_WARN lex_finish_string(lex_file *lex, int quote)
+{
+    utf8ch_t chr = 0;
+    int ch = 0, texttype = 0;
+    int nextch;
+    bool hex;
+    bool oct;
+    char u8buf[8]; /* way more than enough */
+    int  u8len, uc;
+
+    while (ch != EOF)
+    {
+        ch = lex_getch(lex);
+        if (ch == quote)
+            return TOKEN_STRINGCONST;
+
+        if (lex->flags.preprocessing && ch == '\\') {
+            lex_tokench(lex, ch);
+            ch = lex_getch(lex);
+            if (ch == EOF) {
+                lexerror(lex, "unexpected end of file");
+                lex_ungetch(lex, EOF); /* next token to be TOKEN_EOF */
+                return (lex->tok.ttype = TOKEN_ERROR);
+            }
+            lex_tokench(lex, ch);
+        }
+        else if (ch == '\\') {
+            ch = lex_getch(lex);
+            if (ch == EOF) {
+                lexerror(lex, "unexpected end of file");
+                lex_ungetch(lex, EOF); /* next token to be TOKEN_EOF */
+                return (lex->tok.ttype = TOKEN_ERROR);
+            }
+
+            switch (ch) {
+            case '\\': break;
+            case '\'': break;
+            case '"':  break;
+            case 'a': ch = '\a'; break;
+            case 'r': ch = '\r'; break;
+            case 'n': ch = '\n'; break;
+            case 't': ch = '\t'; break;
+            case 'f': ch = '\f'; break;
+            case 'v': ch = '\v'; break;
+            case 'x':
+            case 'X':
+                /* same procedure as in fteqcc */
+                ch = 0;
+                nextch = lex_getch(lex);
+                if      (nextch >= '0' && nextch <= '9')
+                    ch += nextch - '0';
+                else if (nextch >= 'a' && nextch <= 'f')
+                    ch += nextch - 'a' + 10;
+                else if (nextch >= 'A' && nextch <= 'F')
+                    ch += nextch - 'A' + 10;
+                else {
+                    lexerror(lex, "bad character code");
+                    lex_ungetch(lex, nextch);
+                    return (lex->tok.ttype = TOKEN_ERROR);
+                }
+
+                ch *= 0x10;
+                nextch = lex_getch(lex);
+                if      (nextch >= '0' && nextch <= '9')
+                    ch += nextch - '0';
+                else if (nextch >= 'a' && nextch <= 'f')
+                    ch += nextch - 'a' + 10;
+                else if (nextch >= 'A' && nextch <= 'F')
+                    ch += nextch - 'A' + 10;
+                else {
+                    lexerror(lex, "bad character code");
+                    lex_ungetch(lex, nextch);
+                    return (lex->tok.ttype = TOKEN_ERROR);
+                }
+                break;
+
+            /* fteqcc support */
+            case '0': case '1': case '2': case '3':
+            case '4': case '5': case '6': case '7':
+            case '8': case '9':
+                ch = 18 + ch - '0';
+                break;
+            case '<':  ch = 29; break;
+            case '-':  ch = 30; break;
+            case '>':  ch = 31; break;
+            case '[':  ch = 16; break;
+            case ']':  ch = 17; break;
+            case '{':
+                chr = 0;
+                nextch = lex_getch(lex);
+                hex = (nextch == 'x');
+                oct = (nextch == '0');
+                if (!hex && !oct)
+                    lex_ungetch(lex, nextch);
+                for (nextch = lex_getch(lex); nextch != '}'; nextch = lex_getch(lex)) {
+                    if (!hex && !oct) {
+                        if (nextch >= '0' && nextch <= '9')
+                            chr = chr * 10 + nextch - '0';
+                        else {
+                            lexerror(lex, "bad character code");
+                            return (lex->tok.ttype = TOKEN_ERROR);
+                        }
+                    } else if (!oct) {
+                        if (nextch >= '0' && nextch <= '9')
+                            chr = chr * 0x10 + nextch - '0';
+                        else if (nextch >= 'a' && nextch <= 'f')
+                            chr = chr * 0x10 + nextch - 'a' + 10;
+                        else if (nextch >= 'A' && nextch <= 'F')
+                            chr = chr * 0x10 + nextch - 'A' + 10;
+                        else {
+                            lexerror(lex, "bad character code");
+                            return (lex->tok.ttype = TOKEN_ERROR);
+                        }
+                    } else {
+                        if (nextch >= '0' && nextch <= '9')
+                            chr = chr * 8 + chr - '0';
+                        else {
+                            lexerror(lex, "bad character code");
+                            return (lex->tok.ttype = TOKEN_ERROR);
+                        }
+                    }
+                    if (chr > 0x10FFFF || (!OPTS_FLAG(UTF8) && chr > 255))
+                    {
+                        lexerror(lex, "character code out of range");
+                        return (lex->tok.ttype = TOKEN_ERROR);
+                    }
+                }
+                if (OPTS_FLAG(UTF8) && chr >= 128) {
+                    u8len = utf8_from(u8buf, chr);
+                    if (!u8len)
+                        ch = 0;
+                    else {
+                        --u8len;
+                        lex->column += u8len;
+                        for (uc = 0; uc < u8len; ++uc)
+                            lex_tokench(lex, u8buf[uc]);
+                        /*
+                         * the last character will be inserted with the tokench() call
+                         * below the switch
+                         */
+                        ch = u8buf[uc];
+                    }
+                }
+                else
+                    ch = chr;
+                break;
+
+            /* high bit text */
+            case 'b': case 's':
+                texttype ^= 128;
+                continue;
+
+            case '\n':
+                ch = '\n';
+                break;
+
+            default:
+                lexwarn(lex, WARN_UNKNOWN_CONTROL_SEQUENCE, "unrecognized control sequence: \\%c", ch);
+                /* so we just add the character plus backslash no matter what it actually is */
+                lex_tokench(lex, '\\');
+            }
+            /* add the character finally */
+            lex_tokench(lex, ch | texttype);
+        }
+        else
+            lex_tokench(lex, ch);
+    }
+    lexerror(lex, "unexpected end of file within string constant");
+    lex_ungetch(lex, EOF); /* next token to be TOKEN_EOF */
+    return (lex->tok.ttype = TOKEN_ERROR);
+}
+
+static int GMQCC_WARN lex_finish_digit(lex_file *lex, int lastch)
+{
+    bool ishex = false;
+
+    int  ch = lastch;
+
+    /* parse a number... */
+    if (ch == '.')
+        lex->tok.ttype = TOKEN_FLOATCONST;
+    else
+        lex->tok.ttype = TOKEN_INTCONST;
+
+    lex_tokench(lex, ch);
+
+    ch = lex_getch(lex);
+    if (ch != '.' && !util_isdigit(ch))
+    {
+        if (lastch != '0' || ch != 'x')
+        {
+            /* end of the number or EOF */
+            lex_ungetch(lex, ch);
+            lex_endtoken(lex);
+
+            lex->tok.constval.i = lastch - '0';
+            return lex->tok.ttype;
+        }
+
+        ishex = true;
+    }
+
+    /* EOF would have been caught above */
+
+    if (ch != '.')
+    {
+        lex_tokench(lex, ch);
+        ch = lex_getch(lex);
+        while (util_isdigit(ch) || (ishex && isxdigit_only(ch)))
+        {
+            lex_tokench(lex, ch);
+            ch = lex_getch(lex);
+        }
+    }
+    /* NOT else, '.' can come from above as well */
+    if (lex->tok.ttype != TOKEN_FLOATCONST && ch == '.' && !ishex)
+    {
+        /* Allow floating comma in non-hex mode */
+        lex->tok.ttype = TOKEN_FLOATCONST;
+        lex_tokench(lex, ch);
+
+        /* continue digits-only */
+        ch = lex_getch(lex);
+        while (util_isdigit(ch))
+        {
+            lex_tokench(lex, ch);
+            ch = lex_getch(lex);
+        }
+    }
+    /* put back the last character */
+    /* but do not put back the trailing 'f' or a float */
+    if (lex->tok.ttype == TOKEN_FLOATCONST && ch == 'f')
+        ch = lex_getch(lex);
+
+    /* generally we don't want words to follow numbers: */
+    if (isident(ch)) {
+        lexerror(lex, "unexpected trailing characters after number");
+        return (lex->tok.ttype = TOKEN_ERROR);
+    }
+    lex_ungetch(lex, ch);
+
+    lex_endtoken(lex);
+    if (lex->tok.ttype == TOKEN_FLOATCONST)
+        lex->tok.constval.f = strtod(lex->tok.value, nullptr);
+    else
+        lex->tok.constval.i = strtol(lex->tok.value, nullptr, 0);
+    return lex->tok.ttype;
+}
+
+int lex_do(lex_file *lex)
+{
+    int ch, nextch, thirdch;
+    bool hadwhite = false;
+
+    lex_token_new(lex);
+
+    while (true) {
+        ch = lex_skipwhite(lex, hadwhite);
+        hadwhite = true;
+        if (!lex->flags.mergelines || ch != '\\')
+            break;
+        ch = lex_getch(lex);
+        if (ch == '\r')
+            ch = lex_getch(lex);
+        if (ch != '\n') {
+            lex_ungetch(lex, ch);
+            ch = '\\';
+            break;
+        }
+        /* we reached a linemerge */
+        lex_tokench(lex, '\n');
+        continue;
+    }
+
+    if (lex->flags.preprocessing && (ch == TOKEN_WHITE || ch == TOKEN_EOL || ch == TOKEN_FATAL)) {
+        return (lex->tok.ttype = ch);
+    }
+
+    lex->sline = lex->line;
+    lex->tok.ctx.line = lex->sline;
+    lex->tok.ctx.file = lex->name;
+
+    if (lex->eof)
+        return (lex->tok.ttype = TOKEN_FATAL);
+
+    if (ch == EOF) {
+        lex->eof = true;
+        return (lex->tok.ttype = TOKEN_EOF);
+    }
+
+    /* modelgen / spiritgen commands */
+    if (ch == '$' && !lex->flags.preprocessing) {
+        const char *v;
+        size_t frame;
+
+        ch = lex_getch(lex);
+        if (!isident_start(ch)) {
+            lexerror(lex, "hanging '$' modelgen/spritegen command line");
+            return lex_do(lex);
+        }
+        lex_tokench(lex, ch);
+        if (!lex_finish_ident(lex))
+            return (lex->tok.ttype = TOKEN_ERROR);
+        lex_endtoken(lex);
+        /* skip the known commands */
+        v = lex->tok.value;
+
+        if (!strcmp(v, "frame") || !strcmp(v, "framesave"))
+        {
+            /* frame/framesave command works like an enum
+             * similar to fteqcc we handle this in the lexer.
+             * The reason for this is that it is sensitive to newlines,
+             * which the parser is unaware of
+             */
+            if (!lex_finish_frames(lex))
+                 return (lex->tok.ttype = TOKEN_ERROR);
+            return lex_do(lex);
+        }
+
+        if (!strcmp(v, "framevalue"))
+        {
+            ch = lex_getch(lex);
+            while (ch != EOF && util_isspace(ch) && ch != '\n')
+                ch = lex_getch(lex);
+
+            if (!util_isdigit(ch)) {
+                lexerror(lex, "$framevalue requires an integer parameter");
+                return lex_do(lex);
+            }
+
+            lex_token_new(lex);
+            lex->tok.ttype = lex_finish_digit(lex, ch);
+            lex_endtoken(lex);
+            if (lex->tok.ttype != TOKEN_INTCONST) {
+                lexerror(lex, "$framevalue requires an integer parameter");
+                return lex_do(lex);
+            }
+            lex->framevalue = lex->tok.constval.i;
+            return lex_do(lex);
+        }
+
+        if (!strcmp(v, "framerestore"))
+        {
+            int rc;
+
+            lex_token_new(lex);
+
+            rc = lex_parse_frame(lex);
+
+            if (rc > 0) {
+                lexerror(lex, "$framerestore requires a framename parameter");
+                return lex_do(lex);
+            }
+            if (rc < 0)
+                return (lex->tok.ttype = TOKEN_FATAL);
+
+            v = lex->tok.value;
+            for (frame = 0; frame < vec_size(lex->frames); ++frame) {
+                if (!strcmp(v, lex->frames[frame].name)) {
+                    lex->framevalue = lex->frames[frame].value;
+                    return lex_do(lex);
+                }
+            }
+            lexerror(lex, "unknown framename `%s`", v);
+            return lex_do(lex);
+        }
+
+        if (!strcmp(v, "modelname"))
+        {
+            int rc;
+
+            lex_token_new(lex);
+
+            rc = lex_parse_frame(lex);
+
+            if (rc > 0) {
+                lexerror(lex, "$modelname requires a parameter");
+                return lex_do(lex);
+            }
+            if (rc < 0)
+                return (lex->tok.ttype = TOKEN_FATAL);
+
+            if (lex->modelname) {
+                frame_macro m;
+                m.value = lex->framevalue;
+                m.name = lex->modelname;
+                lex->modelname = nullptr;
+                vec_push(lex->frames, m);
+            }
+            lex->modelname = lex->tok.value;
+            lex->tok.value = nullptr;
+            return lex_do(lex);
+        }
+
+        if (!strcmp(v, "flush"))
+        {
+            size_t fi;
+            for (fi = 0; fi < vec_size(lex->frames); ++fi)
+                mem_d(lex->frames[fi].name);
+            vec_free(lex->frames);
+            /* skip line (fteqcc does it too) */
+            ch = lex_getch(lex);
+            while (ch != EOF && ch != '\n')
+                ch = lex_getch(lex);
+            return lex_do(lex);
+        }
+
+        if (!strcmp(v, "cd") ||
+            !strcmp(v, "origin") ||
+            !strcmp(v, "base") ||
+            !strcmp(v, "flags") ||
+            !strcmp(v, "scale") ||
+            !strcmp(v, "skin"))
+        {
+            /* skip line */
+            ch = lex_getch(lex);
+            while (ch != EOF && ch != '\n')
+                ch = lex_getch(lex);
+            return lex_do(lex);
+        }
+
+        for (frame = 0; frame < vec_size(lex->frames); ++frame) {
+            if (!strcmp(v, lex->frames[frame].name)) {
+                lex->tok.constval.i = lex->frames[frame].value;
+                return (lex->tok.ttype = TOKEN_INTCONST);
+            }
+        }
+
+        lexerror(lex, "invalid frame macro");
+        return lex_do(lex);
+    }
+
+    /* single-character tokens */
+    switch (ch)
+    {
+        case '[':
+            nextch = lex_getch(lex);
+            if (nextch == '[') {
+                lex_tokench(lex, ch);
+                lex_tokench(lex, nextch);
+                lex_endtoken(lex);
+                return (lex->tok.ttype = TOKEN_ATTRIBUTE_OPEN);
+            }
+            lex_ungetch(lex, nextch);
+            /* FALL THROUGH */
+        case '(':
+        case ':':
+        case '?':
+            lex_tokench(lex, ch);
+            lex_endtoken(lex);
+            if (lex->flags.noops)
+                return (lex->tok.ttype = ch);
+            else
+                return (lex->tok.ttype = TOKEN_OPERATOR);
+
+        case ']':
+            if (lex->flags.noops) {
+                nextch = lex_getch(lex);
+                if (nextch == ']') {
+                    lex_tokench(lex, ch);
+                    lex_tokench(lex, nextch);
+                    lex_endtoken(lex);
+                    return (lex->tok.ttype = TOKEN_ATTRIBUTE_CLOSE);
+                }
+                lex_ungetch(lex, nextch);
+            }
+            /* FALL THROUGH */
+        case ')':
+        case ';':
+        case '{':
+        case '}':
+
+        case '#':
+            lex_tokench(lex, ch);
+            lex_endtoken(lex);
+            return (lex->tok.ttype = ch);
+        default:
+            break;
+    }
+
+    if (ch == '.') {
+        nextch = lex_getch(lex);
+        /* digits starting with a dot */
+        if (util_isdigit(nextch)) {
+            lex_ungetch(lex, nextch);
+            lex->tok.ttype = lex_finish_digit(lex, ch);
+            lex_endtoken(lex);
+            return lex->tok.ttype;
+        }
+        lex_ungetch(lex, nextch);
+    }
+
+    if (lex->flags.noops)
+    {
+        /* Detect characters early which are normally
+         * operators OR PART of an operator.
+         */
+        switch (ch)
+        {
+            case '*':
+            case '/':
+            case '<':
+            case '>':
+            case '=':
+            case '&':
+            case '|':
+            case '^':
+            case '~':
+            case ',':
+            case '!':
+                lex_tokench(lex, ch);
+                lex_endtoken(lex);
+                return (lex->tok.ttype = ch);
+            default:
+                break;
+        }
+    }
+
+    if (ch == '.')
+    {
+        lex_tokench(lex, ch);
+        /* peak ahead once */
+        nextch = lex_getch(lex);
+        if (nextch != '.') {
+            lex_ungetch(lex, nextch);
+            lex_endtoken(lex);
+            if (lex->flags.noops)
+                return (lex->tok.ttype = ch);
+            else
+                return (lex->tok.ttype = TOKEN_OPERATOR);
+        }
+        /* peak ahead again */
+        nextch = lex_getch(lex);
+        if (nextch != '.') {
+            lex_ungetch(lex, nextch);
+            lex_ungetch(lex, '.');
+            lex_endtoken(lex);
+            if (lex->flags.noops)
+                return (lex->tok.ttype = ch);
+            else
+                return (lex->tok.ttype = TOKEN_OPERATOR);
+        }
+        /* fill the token to be "..." */
+        lex_tokench(lex, ch);
+        lex_tokench(lex, ch);
+        lex_endtoken(lex);
+        return (lex->tok.ttype = TOKEN_DOTS);
+    }
+
+    if (ch == ',' || ch == '.') {
+        lex_tokench(lex, ch);
+        lex_endtoken(lex);
+        return (lex->tok.ttype = TOKEN_OPERATOR);
+    }
+
+    if (ch == '+' || ch == '-' || /* ++, --, +=, -=  and -> as well! */
+        ch == '>' || ch == '<' || /* <<, >>, <=, >=  and >< as well! */
+        ch == '=' || ch == '!' || /* <=>, ==, !=                     */
+        ch == '&' || ch == '|' || /* &&, ||, &=, |=                  */
+        ch == '~' || ch == '^'    /* ~=, ~, ^                        */
+    )  {
+        lex_tokench(lex, ch);
+        nextch = lex_getch(lex);
+
+        if ((nextch == '=' && ch != '<') || (nextch == '<' && ch == '>'))
+            lex_tokench(lex, nextch);
+        else if (nextch == ch && ch != '!') {
+            lex_tokench(lex, nextch);
+            if ((thirdch = lex_getch(lex)) == '=')
+                lex_tokench(lex, thirdch);
+            else
+                lex_ungetch(lex, thirdch);
+        } else if (ch == '<' && nextch == '=') {
+            lex_tokench(lex, nextch);
+            if ((thirdch = lex_getch(lex)) == '>')
+                lex_tokench(lex, thirdch);
+            else
+                lex_ungetch(lex, thirdch);
+
+        } else if (ch == '-' && nextch == '>') {
+            lex_tokench(lex, nextch);
+        } else if (ch == '&' && nextch == '~') {
+            thirdch = lex_getch(lex);
+            if (thirdch != '=') {
+                lex_ungetch(lex, thirdch);
+                lex_ungetch(lex, nextch);
+            }
+            else {
+                lex_tokench(lex, nextch);
+                lex_tokench(lex, thirdch);
+            }
+        }
+        else if (lex->flags.preprocessing &&
+                 ch == '-' && util_isdigit(nextch))
+        {
+            lex->tok.ttype = lex_finish_digit(lex, nextch);
+            if (lex->tok.ttype == TOKEN_INTCONST)
+                lex->tok.constval.i = -lex->tok.constval.i;
+            else
+                lex->tok.constval.f = -lex->tok.constval.f;
+            lex_endtoken(lex);
+            return lex->tok.ttype;
+        } else {
+            lex_ungetch(lex, nextch);
+        }
+
+        lex_endtoken(lex);
+        return (lex->tok.ttype = TOKEN_OPERATOR);
+    }
+
+    if (ch == '*' || ch == '/') /* *=, /= */
+    {
+        lex_tokench(lex, ch);
+
+        nextch = lex_getch(lex);
+        if (nextch == '=' || nextch == '*') {
+            lex_tokench(lex, nextch);
+        } else
+            lex_ungetch(lex, nextch);
+
+        lex_endtoken(lex);
+        return (lex->tok.ttype = TOKEN_OPERATOR);
+    }
+
+    if (ch == '%') {
+        lex_tokench(lex, ch);
+        lex_endtoken(lex);
+        return (lex->tok.ttype = TOKEN_OPERATOR);
+    }
+
+    if (isident_start(ch))
+    {
+        const char *v;
+
+        lex_tokench(lex, ch);
+        if (!lex_finish_ident(lex)) {
+            /* error? */
+            return (lex->tok.ttype = TOKEN_ERROR);
+        }
+        lex_endtoken(lex);
+        lex->tok.ttype = TOKEN_IDENT;
+
+        v = lex->tok.value;
+        if (!strcmp(v, "void")) {
+            lex->tok.ttype = TOKEN_TYPENAME;
+            lex->tok.constval.t = TYPE_VOID;
+        } else if (!strcmp(v, "int")) {
+            lex->tok.ttype = TOKEN_TYPENAME;
+            lex->tok.constval.t = TYPE_INTEGER;
+        } else if (!strcmp(v, "float")) {
+            lex->tok.ttype = TOKEN_TYPENAME;
+            lex->tok.constval.t = TYPE_FLOAT;
+        } else if (!strcmp(v, "string")) {
+            lex->tok.ttype = TOKEN_TYPENAME;
+            lex->tok.constval.t = TYPE_STRING;
+        } else if (!strcmp(v, "entity")) {
+            lex->tok.ttype = TOKEN_TYPENAME;
+            lex->tok.constval.t = TYPE_ENTITY;
+        } else if (!strcmp(v, "vector")) {
+            lex->tok.ttype = TOKEN_TYPENAME;
+            lex->tok.constval.t = TYPE_VECTOR;
+        } else if (!strcmp(v, "_length")) {
+            lex->tok.ttype = TOKEN_OPERATOR;
+        } else {
+            size_t kw;
+            for (kw = 0; kw < GMQCC_ARRAY_COUNT(keywords_qc); ++kw) {
+                if (!strcmp(v, keywords_qc[kw]))
+                    return (lex->tok.ttype = TOKEN_KEYWORD);
+            }
+            if (OPTS_OPTION_U32(OPTION_STANDARD) != COMPILER_QCC) {
+                for (kw = 0; kw < GMQCC_ARRAY_COUNT(keywords_fg); ++kw) {
+                    if (!strcmp(v, keywords_fg[kw]))
+                        return (lex->tok.ttype = TOKEN_KEYWORD);
+                }
+            }
+        }
+
+        return lex->tok.ttype;
+    }
+
+    if (ch == '"')
+    {
+        lex->flags.nodigraphs = true;
+        if (lex->flags.preprocessing)
+            lex_tokench(lex, ch);
+        lex->tok.ttype = lex_finish_string(lex, '"');
+        if (lex->flags.preprocessing)
+            lex_tokench(lex, ch);
+        while (!lex->flags.preprocessing && lex->tok.ttype == TOKEN_STRINGCONST)
+        {
+            /* Allow c style "string" "continuation" */
+            ch = lex_skipwhite(lex, false);
+            if (ch != '"') {
+                lex_ungetch(lex, ch);
+                break;
+            }
+
+            lex->tok.ttype = lex_finish_string(lex, '"');
+        }
+        lex->flags.nodigraphs = false;
+        lex_endtoken(lex);
+        return lex->tok.ttype;
+    }
+
+    if (ch == '\'')
+    {
+        /* we parse character constants like string,
+         * but return TOKEN_CHARCONST, or a vector type if it fits...
+         * Likewise actual unescaping has to be done by the parser.
+         * The difference is we don't allow 'char' 'continuation'.
+         */
+        if (lex->flags.preprocessing)
+            lex_tokench(lex, ch);
+        lex->tok.ttype = lex_finish_string(lex, '\'');
+        if (lex->flags.preprocessing)
+            lex_tokench(lex, ch);
+        lex_endtoken(lex);
+
+        lex->tok.ttype = TOKEN_CHARCONST;
+
+        /* It's a vector if we can successfully scan 3 floats */
+        if (util_sscanf(lex->tok.value, " %f %f %f ",
+                   &lex->tok.constval.v.x, &lex->tok.constval.v.y, &lex->tok.constval.v.z) == 3)
+
+        {
+             lex->tok.ttype = TOKEN_VECTORCONST;
+        }
+        else
+        {
+            if (!lex->flags.preprocessing && strlen(lex->tok.value) > 1) {
+                utf8ch_t u8char;
+                /* check for a valid utf8 character */
+                if (!OPTS_FLAG(UTF8) || !utf8_to(&u8char, (const unsigned char *)lex->tok.value, 8)) {
+                    if (lexwarn(lex, WARN_MULTIBYTE_CHARACTER,
+                                ( OPTS_FLAG(UTF8) ? "invalid multibyte character sequence `%s`"
+                                                  : "multibyte character: `%s`" ),
+                                lex->tok.value))
+                        return (lex->tok.ttype = TOKEN_ERROR);
+                }
+                else
+                    lex->tok.constval.i = u8char;
+            }
+            else
+                lex->tok.constval.i = lex->tok.value[0];
+        }
+
+        return lex->tok.ttype;
+    }
+
+    if (util_isdigit(ch))
+    {
+        lex->tok.ttype = lex_finish_digit(lex, ch);
+        lex_endtoken(lex);
+        return lex->tok.ttype;
+    }
+
+    if (lex->flags.preprocessing) {
+        lex_tokench(lex, ch);
+        lex_endtoken(lex);
+        return (lex->tok.ttype = ch);
+    }
+
+    lexerror(lex, "unknown token: `%c`", ch);
+    return (lex->tok.ttype = TOKEN_ERROR);
+}
diff --git a/lexer.h b/lexer.h
index 07fa5d7480a8ad67edb9654187c2384d4adfff92..8ed85a97ab39607494378f048cd1ad4429d29d91 100644 (file)
--- a/lexer.h
+++ b/lexer.h
@@ -1,59 +1,19 @@
-/*
- * Copyright (C) 2012, 2013, 2014, 2015
- *     Wolfgang Bumiller
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
- * this software and associated documentation files (the "Software"), to deal in
- * the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is furnished to do
- * so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
 #ifndef GMQCC_LEXER_HDR
 #define GMQCC_LEXER_HDR
 #include "gmqcc.h"
 
-typedef struct token_s token;
-
-struct token_s {
+struct token {
     int ttype;
-
     char *value;
-
     union {
-        vec3_t    v;
-        int       i;
+        vec3_t v;
+        int i;
         qcfloat_t f;
-        int       t; /* type */
+        qc_type t; /* type */
     } constval;
-
-#if 0
-    struct token_s *next;
-    struct token_s *prev;
-#endif
-
     lex_ctx_t ctx;
 };
 
-#if 0
-token* token_new();
-void   token_delete(token*);
-token* token_copy(const token *cp);
-void   token_delete_all(token *t);
-token* token_copy_all(const token *cp);
-#endif
-
 /* Lexer
  *
  */
@@ -101,13 +61,13 @@ enum {
     TOKEN_FATAL /* internal error, eg out of memory */
 };
 
-typedef struct {
+struct frame_macro {
     char *name;
-    int   value;
-} frame_macro;
+    int value;
+};
 
-typedef struct lex_file_s {
-    fs_file_t  *file;
+struct lex_file {
+    FILE  *file;
     const char *open_string;
     size_t      open_string_length;
     size_t      open_string_pos;
@@ -136,7 +96,7 @@ typedef struct lex_file_s {
     char *modelname;
 
     size_t push_line;
-} lex_file;
+};
 
 lex_file* lex_open (const char *file);
 lex_file* lex_open_string(const char *str, size_t len, const char *name);
@@ -156,7 +116,7 @@ enum {
 #define OP_SUFFIX 1
 #define OP_PREFIX 2
 
-typedef struct {
+struct oper_info {
     const char   *op;
     unsigned int operands;
     unsigned int id;
@@ -164,7 +124,7 @@ typedef struct {
     signed int   prec;
     unsigned int flags;
     bool         folds;
-} oper_info;
+};
 
 /*
  * Explicit uint8_t casts since the left operand of shift operator cannot
diff --git a/main.c b/main.c
deleted file mode 100644 (file)
index 9a027bd..0000000
--- a/main.c
+++ /dev/null
@@ -1,807 +0,0 @@
-/*
- * Copyright (C) 2012, 2013, 2014, 2015
- *     Dale Weiler
- *     Wolfgang Bumiller
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
- * this software and associated documentation files (the "Software"), to deal in
- * the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is furnished to do
- * so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-#include <stdlib.h>
-#include <string.h>
-
-#include "gmqcc.h"
-#include "lexer.h"
-
-/* TODO: cleanup this whole file .. it's a fuckign mess */
-
-/* set by the standard */
-const oper_info *operators      = NULL;
-size_t           operator_count = 0;
-static bool      opts_output_wasset = false;
-
-typedef struct { char *filename; int   type;  } argitem;
-typedef struct { char *name;     char *value; } ppitem;
-static argitem *items = NULL;
-static ppitem  *ppems = NULL;
-
-#define TYPE_QC  0
-#define TYPE_ASM 1
-#define TYPE_SRC 2
-
-static const char *app_name;
-
-static void version(void) {
-    con_out("GMQCC %d.%d.%d Built %s %s\n" GMQCC_DEV_VERSION_STRING,
-        GMQCC_VERSION_MAJOR,
-        GMQCC_VERSION_MINOR,
-        GMQCC_VERSION_PATCH,
-        __DATE__,
-        __TIME__
-    );
-}
-
-static int usage(void) {
-    con_out("usage: %s [options] [files...]", app_name);
-    con_out("options:\n"
-            "  -h, --help             show this help message\n"
-            "  -debug                 turns on compiler debug messages\n"
-            "  -memchk                turns on compiler memory leak check\n");
-    con_out("  -o, --output=file      output file, defaults to progs.dat\n"
-            "  -s filename            add a progs.src file to be used\n");
-    con_out("  -E                     stop after preprocessing\n");
-    con_out("  -q, --quiet            be less verbose\n");
-    con_out("  -config file           use the specified ini file\n");
-    con_out("  -std=standard          select one of the following standards\n"
-            "       -std=qcc          original QuakeC\n"
-            "       -std=fteqcc       fteqcc QuakeC\n"
-            "       -std=gmqcc        this compiler (default)\n");
-    con_out("  -f<flag>               enable a flag\n"
-            "  -fno-<flag>            disable a flag\n"
-            "  -fhelp                 list possible flags\n");
-    con_out("  -W<warning>            enable a warning\n"
-            "  -Wno-<warning>         disable a warning\n"
-            "  -Wall                  enable all warnings\n");
-    con_out("  -Werror                treat warnings as errors\n"
-            "  -Werror-<warning>      treat a warning as error\n"
-            "  -Wno-error-<warning>   opposite of the above\n");
-    con_out("  -Whelp                 list possible warnings\n");
-    con_out("  -O<number>             optimization level\n"
-            "  -O<name>               enable specific optimization\n"
-            "  -Ono-<name>            disable specific optimization\n"
-            "  -Ohelp                 list optimizations\n");
-    con_out("  -force-crc=num         force a specific checksum into the header\n");
-    con_out("  -state-fps=num         emulate OP_STATE with the specified FPS\n");
-    con_out("  -coverage              add coverage support\n");
-    return -1;
-}
-
-/* command line parsing */
-static bool options_witharg(int *argc_, char ***argv_, char **out) {
-    int  argc   = *argc_;
-    char **argv = *argv_;
-
-    if (argv[0][2]) {
-        *out = argv[0]+2;
-        return true;
-    }
-    /* eat up the next */
-    if (argc < 2) /* no parameter was provided */
-        return false;
-
-    *out = argv[1];
-    --*argc_;
-    ++*argv_;
-    return true;
-}
-
-static bool options_long_witharg_all(const char *optname, int *argc_, char ***argv_, char **out, int ds, bool split) {
-    int  argc   = *argc_;
-    char **argv = *argv_;
-
-    size_t len = strlen(optname);
-
-    if (strncmp(argv[0]+ds, optname, len))
-        return false;
-
-    /* it's --optname, check how the parameter is supplied */
-    if (argv[0][ds+len] == '=') {
-        /* using --opt=param */
-        *out = argv[0]+ds+len+1;
-        return true;
-    }
-
-    if (!split || argc < ds) /* no parameter was provided, or only single-arg form accepted */
-        return false;
-
-    /* using --opt param */
-    *out = argv[1];
-    --*argc_;
-    ++*argv_;
-    return true;
-}
-static bool options_long_witharg(const char *optname, int *argc_, char ***argv_, char **out) {
-    return options_long_witharg_all(optname, argc_, argv_, out, 2, true);
-}
-static bool options_long_gcc(const char *optname, int *argc_, char ***argv_, char **out) {
-    return options_long_witharg_all(optname, argc_, argv_, out, 1, false);
-}
-
-static bool options_parse(int argc, char **argv) {
-    bool argend = false;
-    size_t itr;
-    char  buffer[1024];
-    char *redirout    = NULL;
-    char *redirerr    = NULL;
-    char *config      = NULL;
-    char *memdumpcols = NULL;
-
-    while (!argend && argc > 1) {
-        char *argarg;
-        argitem item;
-        ppitem  macro;
-
-        ++argv;
-        --argc;
-
-        if (argv[0][0] == '-') {
-            /* All gcc-type long options */
-            if (options_long_gcc("std", &argc, &argv, &argarg)) {
-                if (!strcmp(argarg, "gmqcc") || !strcmp(argarg, "default")) {
-
-                    opts_set(opts.flags, ADJUST_VECTOR_FIELDS,          true);
-                    opts_set(opts.flags, CORRECT_LOGIC,                 true);
-                    opts_set(opts.flags, SHORT_LOGIC,                   true);
-                    opts_set(opts.flags, UNTYPED_NIL,                   true);
-                    opts_set(opts.flags, VARIADIC_ARGS,                 true);
-                    opts_set(opts.flags, FALSE_EMPTY_STRINGS,           false);
-                    opts_set(opts.flags, TRUE_EMPTY_STRINGS,            true);
-                    opts_set(opts.flags, LOOP_LABELS,                   true);
-                    opts_set(opts.flags, TRANSLATABLE_STRINGS,          true);
-                    opts_set(opts.flags, INITIALIZED_NONCONSTANTS,      true);
-                    opts_set(opts.werror, WARN_INVALID_PARAMETER_COUNT, true);
-                    opts_set(opts.werror, WARN_MISSING_RETURN_VALUES,   true);
-                    opts_set(opts.flags,  EXPRESSIONS_FOR_BUILTINS,     true);
-                    opts_set(opts.warn,   WARN_BREAKDEF,                true);
-
-
-
-                    OPTS_OPTION_U32(OPTION_STANDARD) = COMPILER_GMQCC;
-
-                } else if (!strcmp(argarg, "qcc")) {
-
-                    opts_set(opts.flags, ADJUST_VECTOR_FIELDS,  false);
-                    opts_set(opts.flags, ASSIGN_FUNCTION_TYPES, true);
-
-                    OPTS_OPTION_U32(OPTION_STANDARD) = COMPILER_QCC;
-
-                } else if (!strcmp(argarg, "fte") || !strcmp(argarg, "fteqcc")) {
-
-                    opts_set(opts.flags, FTEPP,                    true);
-                    opts_set(opts.flags, TRANSLATABLE_STRINGS,     true);
-                    opts_set(opts.flags, ADJUST_VECTOR_FIELDS,     false);
-                    opts_set(opts.flags, ASSIGN_FUNCTION_TYPES,    true);
-                    opts_set(opts.flags, CORRECT_TERNARY,          false);
-                    opts_set(opts.warn, WARN_TERNARY_PRECEDENCE,   true);
-                    opts_set(opts.warn, WARN_BREAKDEF,             true);
-
-                    OPTS_OPTION_U32(OPTION_STANDARD) = COMPILER_FTEQCC;
-
-                } else if (!strcmp(argarg, "qccx")) {
-
-                    opts_set(opts.flags, ADJUST_VECTOR_FIELDS,  false);
-                    OPTS_OPTION_U32(OPTION_STANDARD) = COMPILER_QCCX;
-
-                } else {
-                    con_out("Unknown standard: %s\n", argarg);
-                    return false;
-                }
-                continue;
-            }
-            if (options_long_gcc("force-crc", &argc, &argv, &argarg)) {
-
-                OPTS_OPTION_BOOL(OPTION_FORCECRC)   = true;
-                OPTS_OPTION_U16 (OPTION_FORCED_CRC) = strtol(argarg, NULL, 0);
-                continue;
-            }
-            if (options_long_gcc("state-fps", &argc, &argv, &argarg)) {
-                OPTS_OPTION_U32(OPTION_STATE_FPS) = strtol(argarg, NULL, 0);
-                opts_set(opts.flags, EMULATE_STATE, true);
-                continue;
-            }
-            if (options_long_gcc("redirout", &argc, &argv, &redirout)) {
-                con_change(redirout, redirerr);
-                continue;
-            }
-            if (options_long_gcc("redirerr", &argc, &argv, &redirerr)) {
-                con_change(redirout, redirerr);
-                continue;
-            }
-            if (options_long_gcc("config", &argc, &argv, &argarg)) {
-                config = argarg;
-                continue;
-            }
-            if (options_long_gcc("memdumpcols", &argc, &argv, &memdumpcols)) {
-                OPTS_OPTION_U16(OPTION_MEMDUMPCOLS) = (uint16_t)strtol(memdumpcols, NULL, 10);
-                continue;
-            }
-            if (options_long_gcc("progsrc", &argc, &argv, &argarg)) {
-                OPTS_OPTION_STR(OPTION_PROGSRC) = argarg;
-                continue;
-            }
-
-            /* show defaults (like pathscale) */
-            if (!strcmp(argv[0]+1, "show-defaults")) {
-                for (itr = 0; itr < COUNT_FLAGS; ++itr) {
-                    if (!OPTS_FLAG(itr))
-                        continue;
-
-                    memset(buffer, 0, sizeof(buffer));
-                    util_strtononcmd(opts_flag_list[itr].name, buffer, strlen(opts_flag_list[itr].name) + 1);
-
-                    con_out("-f%s ", buffer);
-                }
-                for (itr = 0; itr < COUNT_WARNINGS; ++itr) {
-                    if (!OPTS_WARN(itr))
-                        continue;
-
-                    memset(buffer, 0, sizeof(buffer));
-                    util_strtononcmd(opts_warn_list[itr].name, buffer, strlen(opts_warn_list[itr].name) + 1);
-                    con_out("-W%s ", buffer);
-                }
-                con_out("\n");
-                exit(0);
-            }
-
-            if (!strcmp(argv[0]+1, "debug")) {
-                OPTS_OPTION_BOOL(OPTION_DEBUG) = true;
-                continue;
-            }
-            if (!strcmp(argv[0]+1, "dump")) {
-                OPTS_OPTION_BOOL(OPTION_DUMP)  = true;
-                continue;
-            }
-            if (!strcmp(argv[0]+1, "dumpfin")) {
-                OPTS_OPTION_BOOL(OPTION_DUMPFIN) = true;
-                continue;
-            }
-            if (!strcmp(argv[0]+1, "memchk")) {
-                OPTS_OPTION_BOOL(OPTION_MEMCHK) = true;
-                continue;
-            }
-            if (!strcmp(argv[0]+1, "nocolor")) {
-                con_color(0);
-                continue;
-            }
-            if (!strcmp(argv[0]+1, "coverage")) {
-                OPTS_OPTION_BOOL(OPTION_COVERAGE) = true;
-                continue;
-            }
-
-            switch (argv[0][1]) {
-                /* -h, show usage but exit with 0 */
-                case 'h':
-                    usage();
-                    exit(0);
-                    /* break; never reached because of exit(0) */
-
-                case 'v':
-                    version();
-                    exit(0);
-
-                case 'E':
-                    OPTS_OPTION_BOOL(OPTION_PP_ONLY) = true;
-                    opts_set(opts.flags, FTEPP_PREDEFS, true); /* predefs on for -E */
-                    break;
-
-                /* debug turns on -flno */
-                case 'g':
-                    opts_setflag("LNO", true);
-                    OPTS_OPTION_BOOL(OPTION_G) = true;
-                    break;
-
-                case 'q':
-                    OPTS_OPTION_BOOL(OPTION_QUIET) = true;
-                    break;
-
-                case 'D':
-                    if (!strlen(argv[0]+2)) {
-                        con_err("expected name after -D\n");
-                        exit(0);
-                    }
-
-                    if (!(argarg = strchr(argv[0] + 2, '='))) {
-                        macro.name  = util_strdup(argv[0]+2);
-                        macro.value = NULL;
-                    } else {
-                        *argarg='\0'; /* terminate for name */
-                        macro.name  = util_strdup(argv[0]+2);
-                        macro.value = util_strdup(argarg+1);
-                    }
-                    vec_push(ppems, macro);
-                    break;
-
-                /* handle all -fflags */
-                case 'f':
-                    util_strtocmd(argv[0]+2, argv[0]+2, strlen(argv[0]+2)+1);
-                    if (!strcmp(argv[0]+2, "HELP") || *(argv[0]+2) == '?') {
-                        con_out("Possible flags:\n\n");
-                        for (itr = 0; itr < COUNT_FLAGS; ++itr) {
-                            util_strtononcmd(opts_flag_list[itr].name, buffer, sizeof(buffer));
-                            con_out(" -f%s\n", buffer);
-                        }
-                        exit(0);
-                    }
-                    else if (!strncmp(argv[0]+2, "NO_", 3)) {
-                        if (!opts_setflag(argv[0]+5, false)) {
-                            con_out("unknown flag: %s\n", argv[0]+2);
-                            return false;
-                        }
-                    }
-                    else if (!opts_setflag(argv[0]+2, true)) {
-                        con_out("unknown flag: %s\n", argv[0]+2);
-                        return false;
-                    }
-                    break;
-                case 'W':
-                    util_strtocmd(argv[0]+2, argv[0]+2, strlen(argv[0]+2)+1);
-                    if (!strcmp(argv[0]+2, "HELP") || *(argv[0]+2) == '?') {
-                        con_out("Possible warnings:\n");
-                        for (itr = 0; itr < COUNT_WARNINGS; ++itr) {
-                            util_strtononcmd(opts_warn_list[itr].name, buffer, sizeof(buffer));
-                            con_out(" -W%s\n", buffer);
-                            if (itr == WARN_DEBUG)
-                                con_out("   Warnings included by -Wall:\n");
-                        }
-                        exit(0);
-                    }
-                    else if (!strcmp(argv[0]+2, "NO_ERROR") ||
-                             !strcmp(argv[0]+2, "NO_ERROR_ALL"))
-                    {
-                        for (itr = 0; itr < GMQCC_ARRAY_COUNT(opts.werror); ++itr)
-                            opts.werror[itr] = 0;
-                        break;
-                    }
-                    else if (!strcmp(argv[0]+2, "ERROR") ||
-                             !strcmp(argv[0]+2, "ERROR_ALL"))
-                    {
-                        opts_backup_non_Werror_all();
-                        for (itr = 0; itr < GMQCC_ARRAY_COUNT(opts.werror); ++itr)
-                            opts.werror[itr] = 0xFFFFFFFFL;
-                        opts_restore_non_Werror_all();
-                        break;
-                    }
-                    else if (!strcmp(argv[0]+2, "NONE")) {
-                        for (itr = 0; itr < GMQCC_ARRAY_COUNT(opts.warn); ++itr)
-                            opts.warn[itr] = 0;
-                        break;
-                    }
-                    else if (!strcmp(argv[0]+2, "ALL")) {
-                        opts_backup_non_Wall();
-                        for (itr = 0; itr < GMQCC_ARRAY_COUNT(opts.warn); ++itr)
-                            opts.warn[itr] = 0xFFFFFFFFL;
-                        opts_restore_non_Wall();
-                        break;
-                    }
-                    else if (!strncmp(argv[0]+2, "ERROR_", 6)) {
-                        if (!opts_setwerror(argv[0]+8, true)) {
-                            con_out("unknown warning: %s\n", argv[0]+2);
-                            return false;
-                        }
-                    }
-                    else if (!strncmp(argv[0]+2, "NO_ERROR_", 9)) {
-                        if (!opts_setwerror(argv[0]+11, false)) {
-                            con_out("unknown warning: %s\n", argv[0]+2);
-                            return false;
-                        }
-                    }
-                    else if (!strncmp(argv[0]+2, "NO_", 3)) {
-                        if (!opts_setwarn(argv[0]+5, false)) {
-                            con_out("unknown warning: %s\n", argv[0]+2);
-                            return false;
-                        }
-                    }
-                    else if (!opts_setwarn(argv[0]+2, true)) {
-                        con_out("unknown warning: %s\n", argv[0]+2);
-                        return false;
-                    }
-                    break;
-
-                case 'O':
-                    if (!options_witharg(&argc, &argv, &argarg)) {
-                        con_out("option -O requires a numerical argument, or optimization name with an optional 'no-' prefix\n");
-                        return false;
-                    }
-                    if (util_isdigit(argarg[0])) {
-                        uint32_t val = (uint32_t)strtol(argarg, NULL, 10);
-                        OPTS_OPTION_U32(OPTION_O) = val;
-                        opts_setoptimlevel(val);
-                    } else {
-                        util_strtocmd(argarg, argarg, strlen(argarg)+1);
-                        if (!strcmp(argarg, "HELP")) {
-                            con_out("Possible optimizations:\n");
-                            for (itr = 0; itr < COUNT_OPTIMIZATIONS; ++itr) {
-                                util_strtononcmd(opts_opt_list[itr].name, buffer, sizeof(buffer));
-                                con_out(" -O%-20s (-O%u)\n", buffer, opts_opt_oflag[itr]);
-                            }
-                            exit(0);
-                        }
-                        else if (!strcmp(argarg, "ALL"))
-                            opts_setoptimlevel(OPTS_OPTION_U32(OPTION_O) = 9999);
-                        else if (!strncmp(argarg, "NO_", 3)) {
-                            /* constant folding cannot be turned off for obvious reasons */
-                            if (!strcmp(argarg, "NO_CONST_FOLD") || !opts_setoptim(argarg+3, false)) {
-                                con_out("unknown optimization: %s\n", argarg+3);
-                                return false;
-                            }
-                        }
-                        else {
-                            if (!opts_setoptim(argarg, true)) {
-                                con_out("unknown optimization: %s\n", argarg);
-                                return false;
-                            }
-                        }
-                    }
-                    break;
-
-                case 'o':
-                    if (!options_witharg(&argc, &argv, &argarg)) {
-                        con_out("option -o requires an argument: the output file name\n");
-                        return false;
-                    }
-                    OPTS_OPTION_STR(OPTION_OUTPUT) = argarg;
-                    opts_output_wasset = true;
-                    break;
-
-                case 'a':
-                case 's':
-                    item.type = argv[0][1] == 'a' ? TYPE_ASM : TYPE_SRC;
-                    if (!options_witharg(&argc, &argv, &argarg)) {
-                        con_out("option -a requires a filename %s\n",
-                                (argv[0][1] == 'a' ? "containing QC-asm" : "containing a progs.src formatted list"));
-                        return false;
-                    }
-                    item.filename = argarg;
-                    vec_push(items, item);
-                    break;
-
-                case '-':
-                    if (!argv[0][2]) {
-                        /* anything following -- is considered a non-option argument */
-                        argend = true;
-                        break;
-                    }
-            /* All long options without arguments */
-                    else if (!strcmp(argv[0]+2, "help")) {
-                        usage();
-                        exit(0);
-                    }
-                    else if (!strcmp(argv[0]+2, "version")) {
-                        version();
-                        exit(0);
-                    }
-                    else if (!strcmp(argv[0]+2, "quiet")) {
-                        OPTS_OPTION_BOOL(OPTION_QUIET) = true;
-                        break;
-                    }
-                    else if (!strcmp(argv[0]+2, "correct")) {
-                        OPTS_OPTION_BOOL(OPTION_CORRECTION) = true;
-                        break;
-                    }
-                    else if (!strcmp(argv[0]+2, "no-correct")) {
-                        OPTS_OPTION_BOOL(OPTION_CORRECTION) = false;
-                        break;
-                    }
-                    else if (!strcmp(argv[0]+2, "add-info")) {
-                        OPTS_OPTION_BOOL(OPTION_ADD_INFO) = true;
-                        break;
-                    }
-                    else {
-            /* All long options with arguments */
-                        if (options_long_witharg("output", &argc, &argv, &argarg)) {
-                            OPTS_OPTION_STR(OPTION_OUTPUT) = argarg;
-                            opts_output_wasset = true;
-                        } else {
-                            con_out("Unknown parameter: %s\n", argv[0]);
-                            return false;
-                        }
-                    }
-                    break;
-
-                default:
-                    con_out("Unknown parameter: %s\n", argv[0]);
-                    return false;
-            }
-        }
-        else
-        {
-            /* it's a QC filename */
-            item.filename = argv[0];
-            item.type     = TYPE_QC;
-            vec_push(items, item);
-        }
-    }
-    opts_ini_init(config);
-    return true;
-}
-
-/* returns the line number, or -1 on error */
-static bool progs_nextline(char **out, size_t *alen, fs_file_t *src) {
-    int    len;
-    char  *line;
-    char  *start;
-    char  *end;
-
-    line = *out;
-    len  = fs_file_getline(&line, alen, src);
-    if (len == -1)
-        return false;
-
-    /* start at first non-blank */
-    for (start = line; util_isspace(*start); ++start) {}
-    /* end at the first non-blank */
-    for (end = start; *end && !util_isspace(*end);  ++end)   {}
-
-    *out = line;
-    /* move the actual filename to the beginning */
-    while (start != end) {
-        *line++ = *start++;
-    }
-    *line = 0;
-    return true;
-}
-
-int main(int argc, char **argv) {
-    size_t          itr;
-    int             retval           = 0;
-    bool            operators_free   = false;
-    bool            progs_src        = false;
-    fs_file_t       *outfile         = NULL;
-    struct parser_s *parser          = NULL;
-    struct ftepp_s  *ftepp           = NULL;
-
-    app_name = argv[0];
-    con_init ();
-    opts_init("progs.dat", COMPILER_QCC, (1024 << 3));
-
-    util_seed(time(0));
-
-    if (!options_parse(argc, argv)) {
-        return usage();
-    }
-
-    if (OPTS_FLAG(TRUE_EMPTY_STRINGS) && OPTS_FLAG(FALSE_EMPTY_STRINGS)) {
-        con_err("-ftrue-empty-strings and -ffalse-empty-strings are mutually exclusive");
-        exit(EXIT_FAILURE);
-    }
-
-    /* the standard decides which set of operators to use */
-    if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_GMQCC) {
-        operators      = c_operators;
-        operator_count = GMQCC_ARRAY_COUNT(c_operators);
-    } else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_FTEQCC) {
-        operators      = fte_operators;
-        operator_count = GMQCC_ARRAY_COUNT(fte_operators);
-    } else {
-        operators      = qcc_operators;
-        operator_count = GMQCC_ARRAY_COUNT(qcc_operators);
-    }
-
-    if (operators == fte_operators) {
-        /* fix ternary? */
-        if (OPTS_FLAG(CORRECT_TERNARY)) {
-            oper_info *newops;
-            if (operators[operator_count-2].id != opid1(',') ||
-                operators[operator_count-1].id != opid2(':','?'))
-            {
-                con_err("internal error: operator precedence table wasn't updated correctly!\n");
-                exit(EXIT_FAILURE);
-            }
-            operators_free = true;
-            newops = (oper_info*)mem_a(sizeof(operators[0]) * operator_count);
-            memcpy(newops, operators, sizeof(operators[0]) * operator_count);
-            memcpy(&newops[operator_count-2], &operators[operator_count-1], sizeof(newops[0]));
-            memcpy(&newops[operator_count-1], &operators[operator_count-2], sizeof(newops[0]));
-            newops[operator_count-2].prec = newops[operator_count-1].prec+1;
-            operators = newops;
-        }
-    }
-
-    if (OPTS_OPTION_BOOL(OPTION_DUMP)) {
-        for (itr = 0; itr < COUNT_FLAGS; ++itr)
-            con_out("Flag %s = %i\n",    opts_flag_list[itr].name, OPTS_FLAG(itr));
-        for (itr = 0; itr < COUNT_WARNINGS; ++itr)
-            con_out("Warning %s = %i\n", opts_warn_list[itr].name, OPTS_WARN(itr));
-
-        con_out("output             = %s\n", OPTS_OPTION_STR(OPTION_OUTPUT));
-        con_out("optimization level = %u\n", OPTS_OPTION_U32(OPTION_O));
-        con_out("standard           = %u\n", OPTS_OPTION_U32(OPTION_STANDARD));
-    }
-
-    if (OPTS_OPTION_BOOL(OPTION_PP_ONLY)) {
-        if (opts_output_wasset) {
-            outfile = fs_file_open(OPTS_OPTION_STR(OPTION_OUTPUT), "wb");
-            if (!outfile) {
-                con_err("failed to open `%s` for writing\n", OPTS_OPTION_STR(OPTION_OUTPUT));
-                retval = 1;
-                goto cleanup;
-            }
-        }
-        else {
-            outfile = con_default_out();
-        }
-    }
-
-    if (!OPTS_OPTION_BOOL(OPTION_PP_ONLY)) {
-        if (!(parser = parser_create())) {
-            con_err("failed to initialize parser\n");
-            retval = 1;
-            goto cleanup;
-        }
-    }
-
-    if (OPTS_OPTION_BOOL(OPTION_PP_ONLY) || OPTS_FLAG(FTEPP)) {
-        if (!(ftepp = ftepp_create())) {
-            con_err("failed to initialize parser\n");
-            retval = 1;
-            goto cleanup;
-        }
-    }
-
-    /* add macros */
-    if (OPTS_OPTION_BOOL(OPTION_PP_ONLY) || OPTS_FLAG(FTEPP)) {
-        for (itr = 0; itr < vec_size(ppems); itr++) {
-            ftepp_add_macro(ftepp, ppems[itr].name, ppems[itr].value);
-            mem_d(ppems[itr].name);
-
-            /* can be null */
-            if (ppems[itr].value)
-                mem_d(ppems[itr].value);
-        }
-    }
-
-    if (!vec_size(items)) {
-        fs_file_t *src;
-        char      *line    = NULL;
-        size_t     linelen = 0;
-        bool       hasline = false;
-
-        progs_src = true;
-
-        src = fs_file_open(OPTS_OPTION_STR(OPTION_PROGSRC), "rb");
-        if (!src) {
-            con_err("failed to open `%s` for reading\n", OPTS_OPTION_STR(OPTION_PROGSRC));
-            retval = 1;
-            goto cleanup;
-        }
-
-        while (progs_nextline(&line, &linelen, src)) {
-            argitem item;
-
-            if (!line[0] || (line[0] == '/' && line[1] == '/'))
-                continue;
-
-            if (hasline) {
-                item.filename = util_strdup(line);
-                item.type     = TYPE_QC;
-                vec_push(items, item);
-            } else if (!opts_output_wasset) {
-                OPTS_OPTION_DUP(OPTION_OUTPUT) = util_strdup(line);
-                hasline                        = true;
-            }
-        }
-
-        fs_file_close(src);
-        mem_d(line);
-    }
-
-    if (vec_size(items)) {
-        if (!OPTS_OPTION_BOOL(OPTION_QUIET) &&
-            !OPTS_OPTION_BOOL(OPTION_PP_ONLY))
-        {
-            con_out("Mode: %s\n", (progs_src ? "progs.src" : "manual"));
-            con_out("There are %lu items to compile:\n", (unsigned long)vec_size(items));
-        }
-
-        for (itr = 0; itr < vec_size(items); ++itr) {
-            if (!OPTS_OPTION_BOOL(OPTION_QUIET) &&
-                !OPTS_OPTION_BOOL(OPTION_PP_ONLY))
-            {
-                con_out("  item: %s (%s)\n",
-                       items[itr].filename,
-                       ( (items[itr].type == TYPE_QC ? "qc" :
-                         (items[itr].type == TYPE_ASM ? "asm" :
-                         (items[itr].type == TYPE_SRC ? "progs.src" :
-                         ("unknown"))))));
-            }
-
-            if (OPTS_OPTION_BOOL(OPTION_PP_ONLY)) {
-                const char *out;
-                if (!ftepp_preprocess_file(ftepp, items[itr].filename)) {
-                    retval = 1;
-                    goto cleanup;
-                }
-                out = ftepp_get(ftepp);
-                if (out)
-                    fs_file_printf(outfile, "%s", out);
-                ftepp_flush(ftepp);
-            }
-            else {
-                if (OPTS_FLAG(FTEPP)) {
-                    const char *data;
-                    if (!ftepp_preprocess_file(ftepp, items[itr].filename)) {
-                        retval = 1;
-                        goto cleanup;
-                    }
-                    data = ftepp_get(ftepp);
-                    if (vec_size(data)) {
-                        if (!parser_compile_string(parser, items[itr].filename, data, vec_size(data))) {
-                            retval = 1;
-                            goto cleanup;
-                        }
-                    }
-                    ftepp_flush(ftepp);
-                }
-                else {
-                    if (!parser_compile_file(parser, items[itr].filename)) {
-                        retval = 1;
-                        goto cleanup;
-                    }
-                }
-            }
-
-            if (progs_src) {
-                mem_d(items[itr].filename);
-                items[itr].filename = NULL;
-            }
-        }
-
-        ftepp_finish(ftepp);
-        ftepp = NULL;
-        if (!OPTS_OPTION_BOOL(OPTION_PP_ONLY)) {
-            if (!parser_finish(parser, OPTS_OPTION_STR(OPTION_OUTPUT))) {
-                retval = 1;
-                goto cleanup;
-            }
-        }
-    }
-
-cleanup:
-    if (ftepp)
-        ftepp_finish(ftepp);
-    con_close();
-    vec_free(items);
-    vec_free(ppems);
-
-    if (!OPTS_OPTION_BOOL(OPTION_PP_ONLY))
-        if(parser) parser_cleanup(parser);
-
-    /* free allocated option strings */
-    for (itr = 0; itr < OPTION_COUNT; itr++)
-        if (OPTS_OPTION_DUPED(itr))
-            mem_d(OPTS_OPTION_STR(itr));
-
-    if (operators_free)
-        mem_d((void*)operators);
-
-    lex_cleanup();
-    stat_info();
-
-    if (!retval && compile_errors)
-        retval = 1;
-    return retval;
-}
diff --git a/main.cpp b/main.cpp
new file mode 100644 (file)
index 0000000..449067d
--- /dev/null
+++ b/main.cpp
@@ -0,0 +1,754 @@
+#include <stdlib.h>
+#include <string.h>
+
+#include "gmqcc.h"
+#include "lexer.h"
+
+/* TODO: cleanup this whole file .. it's a fuckign mess */
+
+/* set by the standard */
+const oper_info *operators = nullptr;
+size_t operator_count = 0;
+static bool opts_output_wasset = false;
+struct argitem { char *filename; int type; };
+struct ppitem { char *name; char *value; };
+static argitem *items = nullptr;
+static ppitem  *ppems = nullptr;
+
+#define TYPE_QC  0
+#define TYPE_ASM 1
+#define TYPE_SRC 2
+
+static const char *app_name;
+
+static void version(void) {
+    con_out("GMQCC %d.%d.%d Built %s %s\n" GMQCC_DEV_VERSION_STRING,
+        GMQCC_VERSION_MAJOR,
+        GMQCC_VERSION_MINOR,
+        GMQCC_VERSION_PATCH,
+        __DATE__,
+        __TIME__
+    );
+}
+
+static int usage(void) {
+    con_out("usage: %s [options] [files...]", app_name);
+    con_out("options:\n"
+            "  -h, --help             show this help message\n"
+            "  -debug                 turns on compiler debug messages\n");
+    con_out("  -o, --output=file      output file, defaults to progs.dat\n"
+            "  -s filename            add a progs.src file to be used\n");
+    con_out("  -E                     stop after preprocessing\n");
+    con_out("  -q, --quiet            be less verbose\n");
+    con_out("  -config file           use the specified ini file\n");
+    con_out("  -std=standard          select one of the following standards\n"
+            "       -std=qcc          original QuakeC\n"
+            "       -std=fteqcc       fteqcc QuakeC\n"
+            "       -std=gmqcc        this compiler (default)\n");
+    con_out("  -f<flag>               enable a flag\n"
+            "  -fno-<flag>            disable a flag\n"
+            "  -fhelp                 list possible flags\n");
+    con_out("  -W<warning>            enable a warning\n"
+            "  -Wno-<warning>         disable a warning\n"
+            "  -Wall                  enable all warnings\n");
+    con_out("  -Werror                treat warnings as errors\n"
+            "  -Werror-<warning>      treat a warning as error\n"
+            "  -Wno-error-<warning>   opposite of the above\n");
+    con_out("  -Whelp                 list possible warnings\n");
+    con_out("  -O<number>             optimization level\n"
+            "  -O<name>               enable specific optimization\n"
+            "  -Ono-<name>            disable specific optimization\n"
+            "  -Ohelp                 list optimizations\n");
+    con_out("  -force-crc=num         force a specific checksum into the header\n");
+    con_out("  -state-fps=num         emulate OP_STATE with the specified FPS\n");
+    con_out("  -coverage              add coverage support\n");
+    return -1;
+}
+
+/* command line parsing */
+static bool options_witharg(int *argc_, char ***argv_, char **out) {
+    int  argc   = *argc_;
+    char **argv = *argv_;
+
+    if (argv[0][2]) {
+        *out = argv[0]+2;
+        return true;
+    }
+    /* eat up the next */
+    if (argc < 2) /* no parameter was provided */
+        return false;
+
+    *out = argv[1];
+    --*argc_;
+    ++*argv_;
+    return true;
+}
+
+static bool options_long_witharg_all(const char *optname, int *argc_, char ***argv_, char **out, int ds, bool split) {
+    int  argc   = *argc_;
+    char **argv = *argv_;
+
+    size_t len = strlen(optname);
+
+    if (strncmp(argv[0]+ds, optname, len))
+        return false;
+
+    /* it's --optname, check how the parameter is supplied */
+    if (argv[0][ds+len] == '=') {
+        /* using --opt=param */
+        *out = argv[0]+ds+len+1;
+        return true;
+    }
+
+    if (!split || argc < ds) /* no parameter was provided, or only single-arg form accepted */
+        return false;
+
+    /* using --opt param */
+    *out = argv[1];
+    --*argc_;
+    ++*argv_;
+    return true;
+}
+static bool options_long_witharg(const char *optname, int *argc_, char ***argv_, char **out) {
+    return options_long_witharg_all(optname, argc_, argv_, out, 2, true);
+}
+static bool options_long_gcc(const char *optname, int *argc_, char ***argv_, char **out) {
+    return options_long_witharg_all(optname, argc_, argv_, out, 1, false);
+}
+
+static bool options_parse(int argc, char **argv) {
+    bool argend = false;
+    size_t itr;
+    char buffer[1024];
+    char *config = nullptr;
+
+    while (!argend && argc > 1) {
+        char *argarg;
+        argitem item;
+        ppitem macro;
+
+        ++argv;
+        --argc;
+
+        if (argv[0][0] == '-') {
+            /* All gcc-type long options */
+            if (options_long_gcc("std", &argc, &argv, &argarg)) {
+                if (!strcmp(argarg, "gmqcc") || !strcmp(argarg, "default")) {
+
+                    opts_set(opts.flags, ADJUST_VECTOR_FIELDS,          true);
+                    opts_set(opts.flags, CORRECT_LOGIC,                 true);
+                    opts_set(opts.flags, SHORT_LOGIC,                   true);
+                    opts_set(opts.flags, UNTYPED_NIL,                   true);
+                    opts_set(opts.flags, VARIADIC_ARGS,                 true);
+                    opts_set(opts.flags, FALSE_EMPTY_STRINGS,           false);
+                    opts_set(opts.flags, TRUE_EMPTY_STRINGS,            true);
+                    opts_set(opts.flags, LOOP_LABELS,                   true);
+                    opts_set(opts.flags, TRANSLATABLE_STRINGS,          true);
+                    opts_set(opts.flags, INITIALIZED_NONCONSTANTS,      true);
+                    opts_set(opts.werror, WARN_INVALID_PARAMETER_COUNT, true);
+                    opts_set(opts.werror, WARN_MISSING_RETURN_VALUES,   true);
+                    opts_set(opts.flags,  EXPRESSIONS_FOR_BUILTINS,     true);
+                    opts_set(opts.warn,   WARN_BREAKDEF,                true);
+
+
+
+                    OPTS_OPTION_U32(OPTION_STANDARD) = COMPILER_GMQCC;
+
+                } else if (!strcmp(argarg, "qcc")) {
+
+                    opts_set(opts.flags, ADJUST_VECTOR_FIELDS,  false);
+                    opts_set(opts.flags, ASSIGN_FUNCTION_TYPES, true);
+
+                    OPTS_OPTION_U32(OPTION_STANDARD) = COMPILER_QCC;
+
+                } else if (!strcmp(argarg, "fte") || !strcmp(argarg, "fteqcc")) {
+
+                    opts_set(opts.flags, FTEPP,                    true);
+                    opts_set(opts.flags, TRANSLATABLE_STRINGS,     true);
+                    opts_set(opts.flags, ADJUST_VECTOR_FIELDS,     false);
+                    opts_set(opts.flags, ASSIGN_FUNCTION_TYPES,    true);
+                    opts_set(opts.flags, CORRECT_TERNARY,          false);
+                    opts_set(opts.warn, WARN_TERNARY_PRECEDENCE,   true);
+                    opts_set(opts.warn, WARN_BREAKDEF,             true);
+
+                    OPTS_OPTION_U32(OPTION_STANDARD) = COMPILER_FTEQCC;
+
+                } else if (!strcmp(argarg, "qccx")) {
+
+                    opts_set(opts.flags, ADJUST_VECTOR_FIELDS,  false);
+                    OPTS_OPTION_U32(OPTION_STANDARD) = COMPILER_QCCX;
+
+                } else {
+                    con_out("Unknown standard: %s\n", argarg);
+                    return false;
+                }
+                continue;
+            }
+            if (options_long_gcc("force-crc", &argc, &argv, &argarg)) {
+
+                OPTS_OPTION_BOOL(OPTION_FORCECRC)   = true;
+                OPTS_OPTION_U16 (OPTION_FORCED_CRC) = strtol(argarg, nullptr, 0);
+                continue;
+            }
+            if (options_long_gcc("state-fps", &argc, &argv, &argarg)) {
+                OPTS_OPTION_U32(OPTION_STATE_FPS) = strtol(argarg, nullptr, 0);
+                opts_set(opts.flags, EMULATE_STATE, true);
+                continue;
+            }
+            if (options_long_gcc("config", &argc, &argv, &argarg)) {
+                config = argarg;
+                continue;
+            }
+            if (options_long_gcc("progsrc", &argc, &argv, &argarg)) {
+                OPTS_OPTION_STR(OPTION_PROGSRC) = argarg;
+                continue;
+            }
+
+            /* show defaults (like pathscale) */
+            if (!strcmp(argv[0]+1, "show-defaults")) {
+                for (itr = 0; itr < COUNT_FLAGS; ++itr) {
+                    if (!OPTS_FLAG(itr))
+                        continue;
+
+                    memset(buffer, 0, sizeof(buffer));
+                    util_strtononcmd(opts_flag_list[itr].name, buffer, strlen(opts_flag_list[itr].name) + 1);
+
+                    con_out("-f%s ", buffer);
+                }
+                for (itr = 0; itr < COUNT_WARNINGS; ++itr) {
+                    if (!OPTS_WARN(itr))
+                        continue;
+
+                    memset(buffer, 0, sizeof(buffer));
+                    util_strtononcmd(opts_warn_list[itr].name, buffer, strlen(opts_warn_list[itr].name) + 1);
+                    con_out("-W%s ", buffer);
+                }
+                con_out("\n");
+                exit(0);
+            }
+
+            if (!strcmp(argv[0]+1, "debug")) {
+                OPTS_OPTION_BOOL(OPTION_DEBUG) = true;
+                continue;
+            }
+            if (!strcmp(argv[0]+1, "dump")) {
+                OPTS_OPTION_BOOL(OPTION_DUMP)  = true;
+                continue;
+            }
+            if (!strcmp(argv[0]+1, "dumpfin")) {
+                OPTS_OPTION_BOOL(OPTION_DUMPFIN) = true;
+                continue;
+            }
+            if (!strcmp(argv[0]+1, "nocolor")) {
+                con_color(0);
+                continue;
+            }
+            if (!strcmp(argv[0]+1, "coverage")) {
+                OPTS_OPTION_BOOL(OPTION_COVERAGE) = true;
+                continue;
+            }
+
+            switch (argv[0][1]) {
+                /* -h, show usage but exit with 0 */
+                case 'h':
+                    usage();
+                    exit(0);
+                    /* break; never reached because of exit(0) */
+
+                case 'v':
+                    version();
+                    exit(0);
+
+                case 'E':
+                    OPTS_OPTION_BOOL(OPTION_PP_ONLY) = true;
+                    opts_set(opts.flags, FTEPP_PREDEFS, true); /* predefs on for -E */
+                    break;
+
+                /* debug turns on -flno */
+                case 'g':
+                    opts_setflag("LNO", true);
+                    OPTS_OPTION_BOOL(OPTION_G) = true;
+                    break;
+
+                case 'q':
+                    OPTS_OPTION_BOOL(OPTION_QUIET) = true;
+                    break;
+
+                case 'D':
+                    if (!strlen(argv[0]+2)) {
+                        con_err("expected name after -D\n");
+                        exit(0);
+                    }
+
+                    if (!(argarg = strchr(argv[0] + 2, '='))) {
+                        macro.name  = util_strdup(argv[0]+2);
+                        macro.value = nullptr;
+                    } else {
+                        *argarg='\0'; /* terminate for name */
+                        macro.name  = util_strdup(argv[0]+2);
+                        macro.value = util_strdup(argarg+1);
+                    }
+                    vec_push(ppems, macro);
+                    break;
+
+                /* handle all -fflags */
+                case 'f':
+                    util_strtocmd(argv[0]+2, argv[0]+2, strlen(argv[0]+2)+1);
+                    if (!strcmp(argv[0]+2, "HELP") || *(argv[0]+2) == '?') {
+                        con_out("Possible flags:\n\n");
+                        for (itr = 0; itr < COUNT_FLAGS; ++itr) {
+                            util_strtononcmd(opts_flag_list[itr].name, buffer, sizeof(buffer));
+                            con_out(" -f%s\n", buffer);
+                        }
+                        exit(0);
+                    }
+                    else if (!strncmp(argv[0]+2, "NO_", 3)) {
+                        if (!opts_setflag(argv[0]+5, false)) {
+                            con_out("unknown flag: %s\n", argv[0]+2);
+                            return false;
+                        }
+                    }
+                    else if (!opts_setflag(argv[0]+2, true)) {
+                        con_out("unknown flag: %s\n", argv[0]+2);
+                        return false;
+                    }
+                    break;
+                case 'W':
+                    util_strtocmd(argv[0]+2, argv[0]+2, strlen(argv[0]+2)+1);
+                    if (!strcmp(argv[0]+2, "HELP") || *(argv[0]+2) == '?') {
+                        con_out("Possible warnings:\n");
+                        for (itr = 0; itr < COUNT_WARNINGS; ++itr) {
+                            util_strtononcmd(opts_warn_list[itr].name, buffer, sizeof(buffer));
+                            con_out(" -W%s\n", buffer);
+                            if (itr == WARN_DEBUG)
+                                con_out("   Warnings included by -Wall:\n");
+                        }
+                        exit(0);
+                    }
+                    else if (!strcmp(argv[0]+2, "NO_ERROR") ||
+                             !strcmp(argv[0]+2, "NO_ERROR_ALL"))
+                    {
+                        for (itr = 0; itr < GMQCC_ARRAY_COUNT(opts.werror); ++itr)
+                            opts.werror[itr] = 0;
+                        break;
+                    }
+                    else if (!strcmp(argv[0]+2, "ERROR") ||
+                             !strcmp(argv[0]+2, "ERROR_ALL"))
+                    {
+                        opts_backup_non_Werror_all();
+                        for (itr = 0; itr < GMQCC_ARRAY_COUNT(opts.werror); ++itr)
+                            opts.werror[itr] = 0xFFFFFFFFL;
+                        opts_restore_non_Werror_all();
+                        break;
+                    }
+                    else if (!strcmp(argv[0]+2, "NONE")) {
+                        for (itr = 0; itr < GMQCC_ARRAY_COUNT(opts.warn); ++itr)
+                            opts.warn[itr] = 0;
+                        break;
+                    }
+                    else if (!strcmp(argv[0]+2, "ALL")) {
+                        opts_backup_non_Wall();
+                        for (itr = 0; itr < GMQCC_ARRAY_COUNT(opts.warn); ++itr)
+                            opts.warn[itr] = 0xFFFFFFFFL;
+                        opts_restore_non_Wall();
+                        break;
+                    }
+                    else if (!strncmp(argv[0]+2, "ERROR_", 6)) {
+                        if (!opts_setwerror(argv[0]+8, true)) {
+                            con_out("unknown warning: %s\n", argv[0]+2);
+                            return false;
+                        }
+                    }
+                    else if (!strncmp(argv[0]+2, "NO_ERROR_", 9)) {
+                        if (!opts_setwerror(argv[0]+11, false)) {
+                            con_out("unknown warning: %s\n", argv[0]+2);
+                            return false;
+                        }
+                    }
+                    else if (!strncmp(argv[0]+2, "NO_", 3)) {
+                        if (!opts_setwarn(argv[0]+5, false)) {
+                            con_out("unknown warning: %s\n", argv[0]+2);
+                            return false;
+                        }
+                    }
+                    else if (!opts_setwarn(argv[0]+2, true)) {
+                        con_out("unknown warning: %s\n", argv[0]+2);
+                        return false;
+                    }
+                    break;
+
+                case 'O':
+                    if (!options_witharg(&argc, &argv, &argarg)) {
+                        con_out("option -O requires a numerical argument, or optimization name with an optional 'no-' prefix\n");
+                        return false;
+                    }
+                    if (util_isdigit(argarg[0])) {
+                        uint32_t val = (uint32_t)strtol(argarg, nullptr, 10);
+                        OPTS_OPTION_U32(OPTION_O) = val;
+                        opts_setoptimlevel(val);
+                    } else {
+                        util_strtocmd(argarg, argarg, strlen(argarg)+1);
+                        if (!strcmp(argarg, "HELP")) {
+                            con_out("Possible optimizations:\n");
+                            for (itr = 0; itr < COUNT_OPTIMIZATIONS; ++itr) {
+                                util_strtononcmd(opts_opt_list[itr].name, buffer, sizeof(buffer));
+                                con_out(" -O%-20s (-O%u)\n", buffer, opts_opt_oflag[itr]);
+                            }
+                            exit(0);
+                        }
+                        else if (!strcmp(argarg, "ALL"))
+                            opts_setoptimlevel(OPTS_OPTION_U32(OPTION_O) = 9999);
+                        else if (!strncmp(argarg, "NO_", 3)) {
+                            /* constant folding cannot be turned off for obvious reasons */
+                            if (!strcmp(argarg, "NO_CONST_FOLD") || !opts_setoptim(argarg+3, false)) {
+                                con_out("unknown optimization: %s\n", argarg+3);
+                                return false;
+                            }
+                        }
+                        else {
+                            if (!opts_setoptim(argarg, true)) {
+                                con_out("unknown optimization: %s\n", argarg);
+                                return false;
+                            }
+                        }
+                    }
+                    break;
+
+                case 'o':
+                    if (!options_witharg(&argc, &argv, &argarg)) {
+                        con_out("option -o requires an argument: the output file name\n");
+                        return false;
+                    }
+                    OPTS_OPTION_STR(OPTION_OUTPUT) = argarg;
+                    opts_output_wasset = true;
+                    break;
+
+                case 'a':
+                case 's':
+                    item.type = argv[0][1] == 'a' ? TYPE_ASM : TYPE_SRC;
+                    if (!options_witharg(&argc, &argv, &argarg)) {
+                        con_out("option -a requires a filename %s\n",
+                                (argv[0][1] == 'a' ? "containing QC-asm" : "containing a progs.src formatted list"));
+                        return false;
+                    }
+                    item.filename = argarg;
+                    vec_push(items, item);
+                    break;
+
+                case '-':
+                    if (!argv[0][2]) {
+                        /* anything following -- is considered a non-option argument */
+                        argend = true;
+                        break;
+                    }
+            /* All long options without arguments */
+                    else if (!strcmp(argv[0]+2, "help")) {
+                        usage();
+                        exit(0);
+                    }
+                    else if (!strcmp(argv[0]+2, "version")) {
+                        version();
+                        exit(0);
+                    }
+                    else if (!strcmp(argv[0]+2, "quiet")) {
+                        OPTS_OPTION_BOOL(OPTION_QUIET) = true;
+                        break;
+                    }
+                    else if (!strcmp(argv[0]+2, "add-info")) {
+                        OPTS_OPTION_BOOL(OPTION_ADD_INFO) = true;
+                        break;
+                    }
+                    else {
+            /* All long options with arguments */
+                        if (options_long_witharg("output", &argc, &argv, &argarg)) {
+                            OPTS_OPTION_STR(OPTION_OUTPUT) = argarg;
+                            opts_output_wasset = true;
+                        } else {
+                            con_out("Unknown parameter: %s\n", argv[0]);
+                            return false;
+                        }
+                    }
+                    break;
+
+                default:
+                    con_out("Unknown parameter: %s\n", argv[0]);
+                    return false;
+            }
+        }
+        else
+        {
+            /* it's a QC filename */
+            item.filename = argv[0];
+            item.type     = TYPE_QC;
+            vec_push(items, item);
+        }
+    }
+    opts_ini_init(config);
+    return true;
+}
+
+/* returns the line number, or -1 on error */
+static bool progs_nextline(char **out, size_t *alen, FILE *src) {
+    int    len;
+    char  *line;
+    char  *start;
+    char  *end;
+
+    line = *out;
+    len  = util_getline(&line, alen, src);
+    if (len == -1)
+        return false;
+
+    /* start at first non-blank */
+    for (start = line; util_isspace(*start); ++start) {}
+    /* end at the first non-blank */
+    for (end = start; *end && !util_isspace(*end);  ++end)   {}
+
+    *out = line;
+    /* move the actual filename to the beginning */
+    while (start != end) {
+        *line++ = *start++;
+    }
+    *line = 0;
+    return true;
+}
+
+int main(int argc, char **argv) {
+    size_t          itr;
+    int             retval           = 0;
+    bool            operators_free   = false;
+    bool            progs_src        = false;
+    FILE       *outfile         = nullptr;
+    parser_t *parser          = nullptr;
+    ftepp_t  *ftepp           = nullptr;
+
+    app_name = argv[0];
+    con_init ();
+    opts_init("progs.dat", COMPILER_QCC, (1024 << 3));
+
+    util_seed(time(0));
+
+    if (!options_parse(argc, argv)) {
+        return usage();
+    }
+
+    if (OPTS_FLAG(TRUE_EMPTY_STRINGS) && OPTS_FLAG(FALSE_EMPTY_STRINGS)) {
+        con_err("-ftrue-empty-strings and -ffalse-empty-strings are mutually exclusive");
+        exit(EXIT_FAILURE);
+    }
+
+    /* the standard decides which set of operators to use */
+    if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_GMQCC) {
+        operators      = c_operators;
+        operator_count = GMQCC_ARRAY_COUNT(c_operators);
+    } else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_FTEQCC) {
+        operators      = fte_operators;
+        operator_count = GMQCC_ARRAY_COUNT(fte_operators);
+    } else {
+        operators      = qcc_operators;
+        operator_count = GMQCC_ARRAY_COUNT(qcc_operators);
+    }
+
+    if (operators == fte_operators) {
+        /* fix ternary? */
+        if (OPTS_FLAG(CORRECT_TERNARY)) {
+            oper_info *newops;
+            if (operators[operator_count-2].id != opid1(',') ||
+                operators[operator_count-1].id != opid2(':','?'))
+            {
+                con_err("internal error: operator precedence table wasn't updated correctly!\n");
+                exit(EXIT_FAILURE);
+            }
+            operators_free = true;
+            newops = (oper_info*)mem_a(sizeof(operators[0]) * operator_count);
+            memcpy(newops, operators, sizeof(operators[0]) * operator_count);
+            memcpy(&newops[operator_count-2], &operators[operator_count-1], sizeof(newops[0]));
+            memcpy(&newops[operator_count-1], &operators[operator_count-2], sizeof(newops[0]));
+            newops[operator_count-2].prec = newops[operator_count-1].prec+1;
+            operators = newops;
+        }
+    }
+
+    if (OPTS_OPTION_BOOL(OPTION_DUMP)) {
+        for (itr = 0; itr < COUNT_FLAGS; ++itr)
+            con_out("Flag %s = %i\n",    opts_flag_list[itr].name, OPTS_FLAG(itr));
+        for (itr = 0; itr < COUNT_WARNINGS; ++itr)
+            con_out("Warning %s = %i\n", opts_warn_list[itr].name, OPTS_WARN(itr));
+
+        con_out("output             = %s\n", OPTS_OPTION_STR(OPTION_OUTPUT));
+        con_out("optimization level = %u\n", OPTS_OPTION_U32(OPTION_O));
+        con_out("standard           = %u\n", OPTS_OPTION_U32(OPTION_STANDARD));
+    }
+
+    if (OPTS_OPTION_BOOL(OPTION_PP_ONLY)) {
+        if (opts_output_wasset) {
+            outfile = fopen(OPTS_OPTION_STR(OPTION_OUTPUT), "wb");
+            if (!outfile) {
+                con_err("failed to open `%s` for writing\n", OPTS_OPTION_STR(OPTION_OUTPUT));
+                retval = 1;
+                goto cleanup;
+            }
+        }
+        else {
+            outfile = con_default_out();
+        }
+    }
+
+    if (!OPTS_OPTION_BOOL(OPTION_PP_ONLY)) {
+        if (!(parser = parser_create())) {
+            con_err("failed to initialize parser\n");
+            retval = 1;
+            goto cleanup;
+        }
+    }
+
+    if (OPTS_OPTION_BOOL(OPTION_PP_ONLY) || OPTS_FLAG(FTEPP)) {
+        if (!(ftepp = ftepp_create())) {
+            con_err("failed to initialize parser\n");
+            retval = 1;
+            goto cleanup;
+        }
+    }
+
+    /* add macros */
+    if (OPTS_OPTION_BOOL(OPTION_PP_ONLY) || OPTS_FLAG(FTEPP)) {
+        for (itr = 0; itr < vec_size(ppems); itr++) {
+            ftepp_add_macro(ftepp, ppems[itr].name, ppems[itr].value);
+            mem_d(ppems[itr].name);
+
+            /* can be null */
+            if (ppems[itr].value)
+                mem_d(ppems[itr].value);
+        }
+    }
+
+    if (!vec_size(items)) {
+        FILE *src;
+        char      *line    = nullptr;
+        size_t     linelen = 0;
+        bool       hasline = false;
+
+        progs_src = true;
+
+        src = fopen(OPTS_OPTION_STR(OPTION_PROGSRC), "rb");
+        if (!src) {
+            con_err("failed to open `%s` for reading\n", OPTS_OPTION_STR(OPTION_PROGSRC));
+            retval = 1;
+            goto cleanup;
+        }
+
+        while (progs_nextline(&line, &linelen, src)) {
+            argitem item;
+
+            if (!line[0] || (line[0] == '/' && line[1] == '/'))
+                continue;
+
+            if (hasline) {
+                item.filename = util_strdup(line);
+                item.type     = TYPE_QC;
+                vec_push(items, item);
+            } else if (!opts_output_wasset) {
+                OPTS_OPTION_DUP(OPTION_OUTPUT) = util_strdup(line);
+                hasline                        = true;
+            }
+        }
+
+        fclose(src);
+        mem_d(line);
+    }
+
+    if (vec_size(items)) {
+        if (!OPTS_OPTION_BOOL(OPTION_QUIET) &&
+            !OPTS_OPTION_BOOL(OPTION_PP_ONLY))
+        {
+            con_out("Mode: %s\n", (progs_src ? "progs.src" : "manual"));
+            con_out("There are %lu items to compile:\n", (unsigned long)vec_size(items));
+        }
+
+        for (itr = 0; itr < vec_size(items); ++itr) {
+            if (!OPTS_OPTION_BOOL(OPTION_QUIET) &&
+                !OPTS_OPTION_BOOL(OPTION_PP_ONLY))
+            {
+                con_out("  item: %s (%s)\n",
+                       items[itr].filename,
+                       ( (items[itr].type == TYPE_QC ? "qc" :
+                         (items[itr].type == TYPE_ASM ? "asm" :
+                         (items[itr].type == TYPE_SRC ? "progs.src" :
+                         ("unknown"))))));
+            }
+
+            if (OPTS_OPTION_BOOL(OPTION_PP_ONLY)) {
+                const char *out;
+                if (!ftepp_preprocess_file(ftepp, items[itr].filename)) {
+                    retval = 1;
+                    goto cleanup;
+                }
+                out = ftepp_get(ftepp);
+                if (out)
+                    fprintf(outfile, "%s", out);
+                ftepp_flush(ftepp);
+            }
+            else {
+                if (OPTS_FLAG(FTEPP)) {
+                    const char *data;
+                    if (!ftepp_preprocess_file(ftepp, items[itr].filename)) {
+                        retval = 1;
+                        goto cleanup;
+                    }
+                    data = ftepp_get(ftepp);
+                    if (vec_size(data)) {
+                        if (!parser_compile_string(parser, items[itr].filename, data, vec_size(data))) {
+                            retval = 1;
+                            goto cleanup;
+                        }
+                    }
+                    ftepp_flush(ftepp);
+                }
+                else {
+                    if (!parser_compile_file(parser, items[itr].filename)) {
+                        retval = 1;
+                        goto cleanup;
+                    }
+                }
+            }
+
+            if (progs_src) {
+                mem_d(items[itr].filename);
+                items[itr].filename = nullptr;
+            }
+        }
+
+        ftepp_finish(ftepp);
+        ftepp = nullptr;
+        if (!OPTS_OPTION_BOOL(OPTION_PP_ONLY)) {
+            if (!parser_finish(parser, OPTS_OPTION_STR(OPTION_OUTPUT))) {
+                retval = 1;
+                goto cleanup;
+            }
+        }
+    }
+
+cleanup:
+    if (ftepp)
+        ftepp_finish(ftepp);
+    con_close();
+    vec_free(items);
+    vec_free(ppems);
+
+    if (!OPTS_OPTION_BOOL(OPTION_PP_ONLY))
+        if(parser) parser_cleanup(parser);
+
+    /* free allocated option strings */
+    for (itr = 0; itr < OPTION_COUNT; itr++)
+        if (OPTS_OPTION_DUPED(itr))
+            mem_d(OPTS_OPTION_STR(itr));
+
+    if (operators_free)
+        mem_d((void*)operators);
+
+    lex_cleanup();
+
+    if (!retval && compile_errors)
+        retval = 1;
+    return retval;
+}
diff --git a/misc/check-proj.sh b/misc/check-proj.sh
deleted file mode 100755 (executable)
index b43ec7a..0000000
+++ /dev/null
@@ -1,140 +0,0 @@
-#!/bin/sh
-
-host="gmqcc.qc.to"
-location="$host/files"
-list="$location/files"
-hashes="$location/hashes"
-options="$location/options"
-
-#download required things
-download_list=$(wget -qO- ${list})
-download_hashes=$(wget -qO- ${hashes})
-download_options=$(wget -qO- ${options})
-
-download() {
-    local old="$PWD"
-    cd ~/.gmqcc/testsuite
-    echo "$download_list" | while read -r line
-    do
-        echo "downloading $line ..."
-        wget -q "${location}/$line"
-    done
-
-    echo "$download_hashes" > ~/.gmqcc/testsuite/hashes
-    echo "$download_options" > ~/.gmqcc/testsuite/options
-
-    cd "$old"
-}
-
-if [ -z "$download_list" -o -z "$download_hashes" -o -z "$download_options" ]; then
-    echo "failed to download required information to check projects."
-
-    if [ "$(ping -q -c1 "${host}")" ]; then
-        echo "host ${host} seems to be up but missing required files."
-        echo "please file bug report at: github.com/graphitemaster/gmqcc"
-    else
-        echo "host ${host} seems to be down, please try again later."
-    fi
-
-    echo "aborting"
-    exit 1
-fi
-
-# we have existing contents around
-if [ -f ~/.gmqcc/testsuite/hashes -a -f ~/.gmqcc/testsuite/options ]; then
-    echo "$download_hashes" > /tmp/gmqcc_download_hashes
-    echo "$download_options" > /tmp/gmqcc_download_options
-
-    diff -u ~/.gmqcc/testsuite/hashes /tmp/gmqcc_download_hashes > /dev/null
-    check_hash=$?
-    diff -u ~/.gmqcc/testsuite/options /tmp/gmqcc_download_options > /dev/null
-    check_opts=$?
-
-    if [ $check_hash -ne 0 -o $check_opts -ne 0 ]; then
-        echo "consistency errors in hashes (possible update), obtaining fresh contents"
-        rm -rf ~/.gmqcc/testsuite/projects
-        rm ~/.gmqcc/testsuite/*.zip
-
-        download
-    fi
-else
-    # do we even have the directory
-    echo "preparing project testsuite for the first time"
-    if [ ! -d ~/.gmqcc/testsuite ]; then
-        mkdir -p ~/.gmqcc/testsuite
-    fi
-
-    download
-fi
-
-if [ ! -d ~/.gmqcc/testsuite/projects ]; then
-    mkdir -p ~/.gmqcc/testsuite/projects
-    old="$PWD"
-    cd ~/.gmqcc/testsuite/projects
-    echo "$(ls ../ | grep -v '^hashes$' | grep -v '^projects$' | grep -v '^options$')" | while read -r line
-    do
-        echo "extracting project $line"
-        mkdir "$(echo "$line" | sed 's/\(.*\)\..*/\1/')"
-        unzip -qq "../$line" -d $(echo "$line" | sed 's/\(.*\)\..*/\1/')
-    done
-    cd "$old"
-else
-    echo "previous state exists, using it"
-fi
-
-# compile projects in those directories
-gmqcc_bin="gmqcc"
-env -i type gmqcc 1>/dev/null 2>&1 || {
-    if [ -f ../gmqcc ]; then
-        echo "previous build of gmqcc exists, using it"
-        gmqcc_bin="$(pwd)/../gmqcc"
-    elif [ -f ./gmqcc ]; then
-        echo "previous build of gmqcc exists, using it"
-        gmqcc_bin="$(pwd)/gmqcc"
-    else
-        echo "gmqcc not installed and previous build doesn't exist"
-        echo "please run make, or make install"
-        exit 1
-    fi
-}
-
-end_dir="$PWD"
-cd ~/.gmqcc/testsuite/projects
-start="$PWD"
-find . -maxdepth 1 -mindepth 1 -type d | while read -r line
-do
-    line="${line#./}"
-    echo -n "compiling $line... "
-    cd "${start}/${line}"
-
-    # does the project have multiple subprojects?
-    if [ -f dirs ]; then
-        echo ""
-        cat dirs | while read -r dir
-        do
-            # change to subproject
-            echo -n "    compiling $dir... "
-            old="$PWD"
-            cd "$dir"
-            cmd="$(cat ../../../options | grep "$line:" | awk '{print substr($0, index($0, $2))}')"
-            "$gmqcc_bin" $cmd > /dev/null 2>&1
-            if [ $? -ne 0 ]; then
-                echo "error"
-            else
-                echo "success"
-            fi
-            cd "$old"
-        done
-    # nope only one project
-    else
-        cmd="$(cat ../../options | grep "$line:" | awk '{print substr($0, index($0, $2))}')"
-        "$gmqcc_bin" $cmd > /dev/null 2>&1
-        if [ $? -ne 0 ]; then
-            echo "error"
-        else
-            echo "success"
-        fi
-    fi
-done
-
-cd "$end_dir"
diff --git a/misc/nexuiz_export.sh b/misc/nexuiz_export.sh
deleted file mode 100755 (executable)
index a07a7af..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-#!/bin/sh
-
-if [ ! -d qcsrc ]; then
-    echo "failed to find qcsrc directory in $(pwd), please run this script"
-    echo "from nexuiz data directory"
-    exit 1
-else
-    # ensure this is actually a xonotic repo
-    pushd qcsrc > /dev/null
-    if [ ! -d client -o ! -d common -o ! -d menu -o ! -d server -o ! -d warpzonelib ]; then
-        echo "this doesnt look like a nexuiz source tree, aborting"
-        popd > /dev/null
-        exit 1
-    fi
-fi
-
-echo -n "removing redundant files ..."
-rm -f nexuiz.ncb
-rm -f nexuiz.sln
-rm -f nexuiz.suo
-rm -f nexuiz.vcproj
-rm -f nexuiz.vcproj.user
-echo "complete"
-
-echo -n "creating projects ..."
-echo "client" >  dirs
-echo "server" >> dirs
-echo "menu"   >> dirs
-
-echo "complete"
-
-echo -n "creating zip archive ..."
-zip -r -9 ../nexuiz.zip * > /dev/null
-echo "complete"
-
-popd > /dev/null
-echo "finished!"
diff --git a/misc/xonotic_export.sh b/misc/xonotic_export.sh
deleted file mode 100755 (executable)
index 5cd3921..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-#!/bin/sh
-
-if [ ! -d qcsrc ]; then
-    echo "failed to find qcsrc directory in $(pwd), please run this script"
-    echo "from xonotic-data.pk3dir"
-    exit 1
-else
-    # ensure this is actually a xonotic repo
-    pushd qcsrc > /dev/null
-    if [ ! -d client -o ! -d common -o ! -d dpdefs -o ! -d menu -o ! -d server -o ! -d warpzonelib ]; then
-        echo "this doesnt look like a xonotic source tree, aborting"
-        popd > /dev/null
-        exit 1
-    fi
-fi
-
-# force reset and update
-git rev-parse
-if [ $? -ne 0 ]; then
-    echo "not a git directory, continuing without rebase"
-else
-    echo -n "resetting git state and updating ... "
-    git reset --hard HEAD > /dev/null 2>&1
-    git pull > /dev/null 2>&1
-    echo "complete"
-fi
-
-echo -n "generate precache for csqc ..."
-./collect-precache.sh > /dev/null 2>&1
-echo "complete"
-
-echo -n "removing redundant files ..."
-rm -f Makefile
-rm -f autocvarize-update.sh
-rm -f autocvarize.pl
-rm -f collect-precache.sh
-rm -f fteqcc-bugs.qc
-rm -f i18n-badwords.txt
-rm -f i18n-guide.txt
-echo "complete"
-
-echo -n "creating projects ..."
-echo "client" >  dirs
-echo "server" >> dirs
-echo "menu"   >> dirs
-
-echo "complete"
-
-echo -n "creating zip archive ..."
-zip -r -9 ../xonotic.zip * > /dev/null
-echo "complete"
-
-popd > /dev/null
-echo "finished!"
diff --git a/msvc.c b/msvc.c
deleted file mode 100644 (file)
index cb13cc2..0000000
--- a/msvc.c
+++ /dev/null
@@ -1,231 +0,0 @@
-/*
- * Copyright (C) 2012, 2013, 2014, 2015
- *     Dale Weiler
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
- * this software and associated documentation files (the "Software"), to deal in
- * the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is furnished to do
- * so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-#define GMQCC_PLATFORM_HEADER
-#include <string.h>
-#include <stdlib.h>
-
-#include "platform.h"
-
-#define CTIME_BUFFER    64
-#define GETENV_BUFFER   4096
-#define STRERROR_BUFFER 128
-
-static void **platform_mem_pool = NULL;
-static void platform_mem_atexit() {
-    size_t i;
-    for (i = 0; i < vec_size(platform_mem_pool); i++)
-        mem_d(platform_mem_pool[i]);
-    vec_free(platform_mem_pool);
-}
-
-static void *platform_mem_allocate(size_t bytes) {
-    void *mem = NULL;
-    if (!platform_mem_pool) {
-        atexit(&platform_mem_atexit);
-        vec_push(platform_mem_pool, NULL);
-    }
-
-    mem = mem_a(bytes);
-    vec_push(platform_mem_pool, mem);
-
-    return mem;
-}
-
-int platform_vsnprintf(char *buffer, size_t bytes, const char *format, va_list arg) {
-    return vsnprintf_s(buffer, bytes, bytes, format, arg);
-}
-
-int platform_vsscanf(const char *str, const char *format, va_list va) {
-    return vsscanf_s(str, format, va);
-}
-
-const struct tm *platform_localtime(const time_t *timer) {
-    struct tm *t;
-    t = (struct tm*)platform_mem_allocate(sizeof(struct tm));
-    localtime_s(&t, timer);
-    return t;
-}
-
-const char *platform_ctime(const time_t *timer) {
-    char *buffer = (char *)platform_mem_allocate(CTIME_BUFFER);
-    ctime_s(buffer, CTIME_BUFFER, timer);
-    return buffer;
-}
-
-char *platform_strncat(char *dest, const char *src, size_t num) {
-    return strncat_s(dest, num, src, _TRUNCATE);
-}
-
-const char *platform_getenv(const char *var) {
-    char  *buffer = (char *)platform_mem_allocate(GETENV_BUFFER);
-    size_t size;
-    getenv_s(&size, buffer, GETENV_BUFFER, var);
-    return buffer;
-}
-
-/*
- * TODO: this isn't exactly 'accurate' for MSVC but it seems to work,
- * at least to some extent.
- */
-int platform_vasprintf(char **dat, const char *fmt, va_list args) {
-    int   ret;
-    int   len;
-    char *tmp = NULL;
-
-    if ((len = _vscprintf(fmt, args)) < 0) {
-        *dat = NULL;
-        return -1;
-    }
-
-    tmp = (char*)mem_a(len + 1);
-    if ((ret = _vsnprintf_s(tmp, len+1, len+1, fmt, args)) != len) {
-        mem_d(tmp);
-        *dat = NULL;
-        return -1;
-    }
-    *dat = tmp;
-    return len;
-}
-
-char *platform_strcat(char *dest, const char *src) {
-    strcat_s(dest, strlen(src), src);
-    return dest;
-}
-
-char *platform_strncpy(char *dest, const char *src, size_t num) {
-    strncpy_s(dest, num, src, num);
-    return dest;
-}
-
-const char *platform_strerror(int err) {
-    char *buffer = (char*)platform_mem_allocate(STRERROR_BUFFER);
-    strerror_s(buffer, STRERROR_BUFFER, err);
-    return buffer;
-}
-
-FILE *platform_fopen(const char *filename, const char *mode) {
-    FILE *handle;
-    return (fopen_s(&handle, filename, mode) != 0) ? NULL : handle;
-}
-
-size_t platform_fread(void *ptr, size_t size, size_t count, FILE *stream) {
-    return fread_s(ptr, size, size, count, stream);
-}
-
-size_t platform_fwrite(const void *ptr, size_t size, size_t count, FILE *stream) {
-    return fwrite(ptr, size, count, stream);
-}
-
-int platform_fflush(FILE *stream) {
-    return fflush(stream);
-}
-
-int platform_vfprintf(FILE *stream, const char *format, va_list arg) {
-    return vfprintf_s(stream, format, arg);
-}
-
-int platform_fclose(FILE *stream) {
-    return fclose(stream);
-}
-
-int platform_ferror(FILE *stream) {
-    return ferror(stream);
-}
-
-int platform_fgetc(FILE *stream) {
-    return fgetc(stream);
-}
-
-int platform_fputs(const char *str, FILE *stream) {
-    return fputs(str, stream);
-}
-
-int platform_fseek(FILE *stream, long offset, int origin) {
-    return fseek(stream, offset, origin);
-}
-
-long platform_ftell(FILE *stream) {
-    return ftell(stream);
-}
-
-int platform_mkdir(const char *path, int mode) {
-    return mkdir(path, mode);
-}
-
-DIR *platform_opendir(const char *path) {
-    DIR *dir = (DIR*)mem_a(sizeof(DIR) + strlen(path));
-    if (!dir)
-        return NULL;
-
-    platform_strncpy(dir->dd_name, path, strlen(path));
-    return dir;
-}
-
-int platform_closedir(DIR *dir) {
-    FindClose((HANDLE)dir->dd_handle);
-    mem_d((void*)dir);
-    return 0;
-}
-
-struct dirent *platform_readdir(DIR *dir) {
-    WIN32_FIND_DATA info;
-    struct dirent  *data;
-    int             ret;
-
-    if (!dir->dd_handle) {
-        char *dirname;
-        if (*dir->dd_name) {
-            size_t n = strlen(dir->dd_name);
-            if ((dir = (char*)mem_a(n+5))) {
-                platform_strncpy(dirname,     dir->dd_name, n);
-                platform_strncpy(dirname + n, "\\*.*",      4);
-            }
-        } else {
-            if (!(dirname = util_strdup("\\*.*")))
-                return NULL;
-        }
-
-        dir->dd_handle = (long)FindFirstFile(dirname, &info);
-        mem_d(dirname);
-        ret = !(!dir->dd_handle);
-    } else if (dir->dd_handle != -11) {
-        ret = FindNextFile((HANDLE)dir->dd_handle, &info);
-    } else {
-        ret = 0;
-    }
-
-    if (!ret)
-        return NULL;
-
-    if ((data = (struct dirent*)mem_a(sizeof(struct dirent)))) {
-        platform_strncpy(data->d_name, info.cFileName, FILENAME_MAX - 1);
-        data->d_name[FILENAME_MAX - 1] = '\0';
-        data->d_namelen                = strlen(data->d_name);
-    }
-
-    return data;
-}
-
-int platform_istty(int fd) {
-    return _isatty(fd);
-}
diff --git a/msvc/gmqcc.sln b/msvc/gmqcc.sln
deleted file mode 100755 (executable)
index 39daed8..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-\r
-Microsoft Visual Studio Solution File, Format Version 11.00\r
-# Visual Studio 2010\r
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gmqcc", "gmqcc\gmqcc.vcxproj", "{A6BD74E1-31BB-4D00-A9E0-09FF1BC76ED6}"\r
-EndProject\r
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "qcvm", "qcvm\qcvm.vcxproj", "{DC980E20-C7A8-4112-A517-631DBDA788E7}"\r
-EndProject\r
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pak", "pak\pak.vcxproj", "{A6F66BE9-57EF-4E93-AA9D-6E0C8B0990AD}"\r
-EndProject\r
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "testsuite", "testsuite\testsuite.vcxproj", "{7E2839D9-9C1A-4489-9FF9-FDC854EBED3D}"\r
-EndProject\r
-Global\r
-       GlobalSection(SolutionConfigurationPlatforms) = preSolution\r
-               Debug|Win32 = Debug|Win32\r
-               Release|Win32 = Release|Win32\r
-       EndGlobalSection\r
-       GlobalSection(ProjectConfigurationPlatforms) = postSolution\r
-               {A6BD74E1-31BB-4D00-A9E0-09FF1BC76ED6}.Debug|Win32.ActiveCfg = Debug|Win32\r
-               {A6BD74E1-31BB-4D00-A9E0-09FF1BC76ED6}.Debug|Win32.Build.0 = Debug|Win32\r
-               {A6BD74E1-31BB-4D00-A9E0-09FF1BC76ED6}.Release|Win32.ActiveCfg = Release|Win32\r
-               {A6BD74E1-31BB-4D00-A9E0-09FF1BC76ED6}.Release|Win32.Build.0 = Release|Win32\r
-               {DC980E20-C7A8-4112-A517-631DBDA788E7}.Debug|Win32.ActiveCfg = Debug|Win32\r
-               {DC980E20-C7A8-4112-A517-631DBDA788E7}.Debug|Win32.Build.0 = Debug|Win32\r
-               {DC980E20-C7A8-4112-A517-631DBDA788E7}.Release|Win32.ActiveCfg = Release|Win32\r
-               {DC980E20-C7A8-4112-A517-631DBDA788E7}.Release|Win32.Build.0 = Release|Win32\r
-               {A6F66BE9-57EF-4E93-AA9D-6E0C8B0990AD}.Debug|Win32.ActiveCfg = Debug|Win32\r
-               {A6F66BE9-57EF-4E93-AA9D-6E0C8B0990AD}.Debug|Win32.Build.0 = Debug|Win32\r
-               {A6F66BE9-57EF-4E93-AA9D-6E0C8B0990AD}.Release|Win32.ActiveCfg = Release|Win32\r
-               {A6F66BE9-57EF-4E93-AA9D-6E0C8B0990AD}.Release|Win32.Build.0 = Release|Win32\r
-               {7E2839D9-9C1A-4489-9FF9-FDC854EBED3D}.Debug|Win32.ActiveCfg = Debug|Win32\r
-               {7E2839D9-9C1A-4489-9FF9-FDC854EBED3D}.Debug|Win32.Build.0 = Debug|Win32\r
-               {7E2839D9-9C1A-4489-9FF9-FDC854EBED3D}.Release|Win32.ActiveCfg = Release|Win32\r
-               {7E2839D9-9C1A-4489-9FF9-FDC854EBED3D}.Release|Win32.Build.0 = Release|Win32\r
-       EndGlobalSection\r
-       GlobalSection(SolutionProperties) = preSolution\r
-               HideSolutionNode = FALSE\r
-       EndGlobalSection\r
-EndGlobal\r
diff --git a/msvc/gmqcc/gmqcc.vcxproj b/msvc/gmqcc/gmqcc.vcxproj
deleted file mode 100755 (executable)
index 8335978..0000000
+++ /dev/null
@@ -1,95 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>\r
-<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
-  <ItemGroup Label="ProjectConfigurations">\r
-    <ProjectConfiguration Include="Debug|Win32">\r
-      <Configuration>Debug</Configuration>\r
-      <Platform>Win32</Platform>\r
-    </ProjectConfiguration>\r
-    <ProjectConfiguration Include="Release|Win32">\r
-      <Configuration>Release</Configuration>\r
-      <Platform>Win32</Platform>\r
-    </ProjectConfiguration>\r
-  </ItemGroup>\r
-  <ItemGroup>\r
-    <ClCompile Include="..\..\ast.c" />\r
-    <ClCompile Include="..\..\code.c" />\r
-    <ClCompile Include="..\..\conout.c" />\r
-    <ClCompile Include="..\..\correct.c" />\r
-    <ClCompile Include="..\..\fold.c" />\r
-    <ClCompile Include="..\..\fs.c" />\r
-    <ClCompile Include="..\..\ftepp.c" />\r
-    <ClCompile Include="..\..\intrin.c" />\r
-    <ClCompile Include="..\..\ir.c" />\r
-    <ClCompile Include="..\..\lexer.c" />\r
-    <ClCompile Include="..\..\main.c" />\r
-    <ClCompile Include="..\..\opts.c" />\r
-    <ClCompile Include="..\..\parser.c" />\r
-    <ClCompile Include="..\..\stat.c" />\r
-    <ClCompile Include="..\..\utf8.c" />\r
-    <ClCompile Include="..\..\util.c" />\r
-  </ItemGroup>\r
-  <ItemGroup>\r
-    <ClInclude Include="..\..\ast.h" />\r
-    <ClInclude Include="..\..\gmqcc.h" />\r
-    <ClInclude Include="..\..\ir.h" />\r
-    <ClInclude Include="..\..\lexer.h" />\r
-    <ClInclude Include="..\..\parser.h" />\r
-  </ItemGroup>\r
-  <ItemGroup>\r
-    <None Include="..\..\opts.def" />\r
-  </ItemGroup>\r
-  <PropertyGroup Label="Globals">\r
-    <ProjectGuid>{A6BD74E1-31BB-4D00-A9E0-09FF1BC76ED6}</ProjectGuid>\r
-    <RootNamespace>gmqcc</RootNamespace>\r
-  </PropertyGroup>\r
-  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />\r
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">\r
-    <ConfigurationType>Application</ConfigurationType>\r
-    <UseDebugLibraries>true</UseDebugLibraries>\r
-    <CharacterSet>MultiByte</CharacterSet>\r
-  </PropertyGroup>\r
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">\r
-    <ConfigurationType>Application</ConfigurationType>\r
-    <UseDebugLibraries>false</UseDebugLibraries>\r
-    <WholeProgramOptimization>true</WholeProgramOptimization>\r
-    <CharacterSet>MultiByte</CharacterSet>\r
-  </PropertyGroup>\r
-  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />\r
-  <ImportGroup Label="ExtensionSettings">\r
-  </ImportGroup>\r
-  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">\r
-    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
-  </ImportGroup>\r
-  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">\r
-    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
-  </ImportGroup>\r
-  <PropertyGroup Label="UserMacros" />\r
-  <PropertyGroup />\r
-  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">\r
-    <ClCompile>\r
-      <WarningLevel>Level3</WarningLevel>\r
-      <Optimization>Disabled</Optimization>\r
-      <PreprocessorDefinitions>NVALGRIND;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
-    </ClCompile>\r
-    <Link>\r
-      <GenerateDebugInformation>true</GenerateDebugInformation>\r
-    </Link>\r
-  </ItemDefinitionGroup>\r
-  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">\r
-    <ClCompile>\r
-      <WarningLevel>Level3</WarningLevel>\r
-      <Optimization>MaxSpeed</Optimization>\r
-      <FunctionLevelLinking>true</FunctionLevelLinking>\r
-      <IntrinsicFunctions>true</IntrinsicFunctions>\r
-      <PreprocessorDefinitions>NVALGRIND;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
-    </ClCompile>\r
-    <Link>\r
-      <GenerateDebugInformation>true</GenerateDebugInformation>\r
-      <EnableCOMDATFolding>true</EnableCOMDATFolding>\r
-      <OptimizeReferences>true</OptimizeReferences>\r
-    </Link>\r
-  </ItemDefinitionGroup>\r
-  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />\r
-  <ImportGroup Label="ExtensionTargets">\r
-  </ImportGroup>\r
-</Project>
\ No newline at end of file
diff --git a/msvc/gmqcc/gmqcc.vcxproj.filters b/msvc/gmqcc/gmqcc.vcxproj.filters
deleted file mode 100755 (executable)
index 7797df3..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>\r
-<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
-  <ItemGroup>\r
-    <ClCompile Include="..\..\ast.c" />\r
-    <ClCompile Include="..\..\code.c" />\r
-    <ClCompile Include="..\..\conout.c" />\r
-    <ClCompile Include="..\..\correct.c" />\r
-    <ClCompile Include="..\..\fold.c" />\r
-    <ClCompile Include="..\..\fs.c" />\r
-    <ClCompile Include="..\..\ftepp.c" />\r
-    <ClCompile Include="..\..\intrin.c" />\r
-    <ClCompile Include="..\..\ir.c" />\r
-    <ClCompile Include="..\..\lexer.c" />\r
-    <ClCompile Include="..\..\main.c" />\r
-    <ClCompile Include="..\..\opts.c" />\r
-    <ClCompile Include="..\..\parser.c" />\r
-    <ClCompile Include="..\..\stat.c" />\r
-    <ClCompile Include="..\..\utf8.c" />\r
-    <ClCompile Include="..\..\util.c" />\r
-  </ItemGroup>\r
-  <ItemGroup>\r
-    <ClInclude Include="..\..\ast.h" />\r
-    <ClInclude Include="..\..\gmqcc.h" />\r
-    <ClInclude Include="..\..\ir.h" />\r
-    <ClInclude Include="..\..\lexer.h" />\r
-    <ClInclude Include="..\..\parser.h" />\r
-  </ItemGroup>\r
-  <ItemGroup>\r
-    <None Include="..\..\opts.def" />\r
-  </ItemGroup>\r
-</Project>
\ No newline at end of file
diff --git a/msvc/pak/pak.vcxproj b/msvc/pak/pak.vcxproj
deleted file mode 100755 (executable)
index 4ab71bf..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>\r
-<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
-  <ItemGroup Label="ProjectConfigurations">\r
-    <ProjectConfiguration Include="Debug|Win32">\r
-      <Configuration>Debug</Configuration>\r
-      <Platform>Win32</Platform>\r
-    </ProjectConfiguration>\r
-    <ProjectConfiguration Include="Release|Win32">\r
-      <Configuration>Release</Configuration>\r
-      <Platform>Win32</Platform>\r
-    </ProjectConfiguration>\r
-  </ItemGroup>\r
-  <ItemGroup>\r
-    <ClCompile Include="..\..\conout.c" />\r
-    <ClCompile Include="..\..\fs.c" />\r
-    <ClCompile Include="..\..\opts.c" />\r
-    <ClCompile Include="..\..\pak.c" />\r
-    <ClCompile Include="..\..\stat.c" />\r
-    <ClCompile Include="..\..\util.c" />\r
-  </ItemGroup>\r
-  <ItemGroup>\r
-    <ClInclude Include="..\..\gmqcc.h" />\r
-  </ItemGroup>\r
-  <ItemGroup>\r
-    <None Include="..\..\opts.def" />\r
-  </ItemGroup>\r
-  <PropertyGroup Label="Globals">\r
-    <ProjectGuid>{A6F66BE9-57EF-4E93-AA9D-6E0C8B0990AD}</ProjectGuid>\r
-    <RootNamespace>pak</RootNamespace>\r
-  </PropertyGroup>\r
-  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />\r
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">\r
-    <ConfigurationType>Application</ConfigurationType>\r
-    <UseDebugLibraries>true</UseDebugLibraries>\r
-    <CharacterSet>MultiByte</CharacterSet>\r
-  </PropertyGroup>\r
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">\r
-    <ConfigurationType>Application</ConfigurationType>\r
-    <UseDebugLibraries>false</UseDebugLibraries>\r
-    <WholeProgramOptimization>true</WholeProgramOptimization>\r
-    <CharacterSet>MultiByte</CharacterSet>\r
-  </PropertyGroup>\r
-  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />\r
-  <ImportGroup Label="ExtensionSettings">\r
-  </ImportGroup>\r
-  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">\r
-    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
-  </ImportGroup>\r
-  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">\r
-    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
-  </ImportGroup>\r
-  <PropertyGroup Label="UserMacros" />\r
-  <PropertyGroup />\r
-  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">\r
-    <ClCompile>\r
-      <WarningLevel>Level3</WarningLevel>\r
-      <Optimization>Disabled</Optimization>\r
-      <PreprocessorDefinitions>NVALGRIND;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
-    </ClCompile>\r
-    <Link>\r
-      <GenerateDebugInformation>true</GenerateDebugInformation>\r
-    </Link>\r
-  </ItemDefinitionGroup>\r
-  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">\r
-    <ClCompile>\r
-      <WarningLevel>Level3</WarningLevel>\r
-      <Optimization>MaxSpeed</Optimization>\r
-      <FunctionLevelLinking>true</FunctionLevelLinking>\r
-      <IntrinsicFunctions>true</IntrinsicFunctions>\r
-      <PreprocessorDefinitions>NVALGRIND;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
-    </ClCompile>\r
-    <Link>\r
-      <GenerateDebugInformation>true</GenerateDebugInformation>\r
-      <EnableCOMDATFolding>true</EnableCOMDATFolding>\r
-      <OptimizeReferences>true</OptimizeReferences>\r
-    </Link>\r
-  </ItemDefinitionGroup>\r
-  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />\r
-  <ImportGroup Label="ExtensionTargets">\r
-  </ImportGroup>\r
-</Project>
\ No newline at end of file
diff --git a/msvc/pak/pak.vcxproj.filters b/msvc/pak/pak.vcxproj.filters
deleted file mode 100755 (executable)
index 8784b71..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>\r
-<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
-  <ItemGroup>\r
-    <ClCompile Include="..\..\conout.c" />\r
-    <ClCompile Include="..\..\fs.c" />\r
-    <ClCompile Include="..\..\opts.c" />\r
-    <ClCompile Include="..\..\pak.c" />\r
-    <ClCompile Include="..\..\stat.c" />\r
-    <ClCompile Include="..\..\util.c" />\r
-  </ItemGroup>\r
-  <ItemGroup>\r
-    <ClInclude Include="..\..\gmqcc.h" />\r
-  </ItemGroup>\r
-  <ItemGroup>\r
-    <None Include="..\..\opts.def" />\r
-  </ItemGroup>\r
-</Project>
\ No newline at end of file
diff --git a/msvc/qcvm/qcvm.vcxproj b/msvc/qcvm/qcvm.vcxproj
deleted file mode 100755 (executable)
index c7ce04f..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>\r
-<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
-  <ItemGroup Label="ProjectConfigurations">\r
-    <ProjectConfiguration Include="Debug|Win32">\r
-      <Configuration>Debug</Configuration>\r
-      <Platform>Win32</Platform>\r
-    </ProjectConfiguration>\r
-    <ProjectConfiguration Include="Release|Win32">\r
-      <Configuration>Release</Configuration>\r
-      <Platform>Win32</Platform>\r
-    </ProjectConfiguration>\r
-  </ItemGroup>\r
-  <ItemGroup>\r
-    <ClCompile Include="..\..\conout.c" />\r
-    <ClCompile Include="..\..\exec.c" />\r
-    <ClCompile Include="..\..\fs.c" />\r
-    <ClCompile Include="..\..\stat.c" />\r
-    <ClCompile Include="..\..\util.c" />\r
-  </ItemGroup>\r
-  <ItemGroup>\r
-    <ClInclude Include="..\..\gmqcc.h" />\r
-  </ItemGroup>\r
-  <PropertyGroup Label="Globals">\r
-    <ProjectGuid>{DC980E20-C7A8-4112-A517-631DBDA788E7}</ProjectGuid>\r
-    <RootNamespace>qcvm</RootNamespace>\r
-  </PropertyGroup>\r
-  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />\r
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">\r
-    <ConfigurationType>Application</ConfigurationType>\r
-    <UseDebugLibraries>true</UseDebugLibraries>\r
-    <CharacterSet>MultiByte</CharacterSet>\r
-  </PropertyGroup>\r
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">\r
-    <ConfigurationType>Application</ConfigurationType>\r
-    <UseDebugLibraries>false</UseDebugLibraries>\r
-    <WholeProgramOptimization>true</WholeProgramOptimization>\r
-    <CharacterSet>MultiByte</CharacterSet>\r
-  </PropertyGroup>\r
-  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />\r
-  <ImportGroup Label="ExtensionSettings">\r
-  </ImportGroup>\r
-  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">\r
-    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
-  </ImportGroup>\r
-  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">\r
-    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
-  </ImportGroup>\r
-  <PropertyGroup Label="UserMacros" />\r
-  <PropertyGroup />\r
-  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">\r
-    <ClCompile>\r
-      <WarningLevel>Level3</WarningLevel>\r
-      <Optimization>Disabled</Optimization>\r
-      <PreprocessorDefinitions>NVALGRIND;QCVM_EXECUTOR;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
-    </ClCompile>\r
-    <Link>\r
-      <GenerateDebugInformation>true</GenerateDebugInformation>\r
-    </Link>\r
-  </ItemDefinitionGroup>\r
-  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">\r
-    <ClCompile>\r
-      <WarningLevel>Level3</WarningLevel>\r
-      <Optimization>MaxSpeed</Optimization>\r
-      <FunctionLevelLinking>true</FunctionLevelLinking>\r
-      <IntrinsicFunctions>true</IntrinsicFunctions>\r
-      <PreprocessorDefinitions>NVALGRIND;QCVM_EXECUTOR;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
-    </ClCompile>\r
-    <Link>\r
-      <GenerateDebugInformation>true</GenerateDebugInformation>\r
-      <EnableCOMDATFolding>true</EnableCOMDATFolding>\r
-      <OptimizeReferences>true</OptimizeReferences>\r
-    </Link>\r
-  </ItemDefinitionGroup>\r
-  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />\r
-  <ImportGroup Label="ExtensionTargets">\r
-  </ImportGroup>\r
-</Project>
\ No newline at end of file
diff --git a/msvc/qcvm/qcvm.vcxproj.filters b/msvc/qcvm/qcvm.vcxproj.filters
deleted file mode 100755 (executable)
index cbdf8a3..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>\r
-<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
-  <ItemGroup>\r
-    <ClCompile Include="..\..\conout.c" />\r
-    <ClCompile Include="..\..\exec.c" />\r
-    <ClCompile Include="..\..\stat.c" />\r
-    <ClCompile Include="..\..\util.c" />\r
-    <ClCompile Include="..\..\fs.c" />\r
-  </ItemGroup>\r
-  <ItemGroup>\r
-    <ClInclude Include="..\..\gmqcc.h" />\r
-  </ItemGroup>\r
-</Project>
\ No newline at end of file
diff --git a/msvc/testsuite/testsuite.vcxproj b/msvc/testsuite/testsuite.vcxproj
deleted file mode 100755 (executable)
index bad2db1..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>\r
-<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
-  <ItemGroup Label="ProjectConfigurations">\r
-    <ProjectConfiguration Include="Debug|Win32">\r
-      <Configuration>Debug</Configuration>\r
-      <Platform>Win32</Platform>\r
-    </ProjectConfiguration>\r
-    <ProjectConfiguration Include="Release|Win32">\r
-      <Configuration>Release</Configuration>\r
-      <Platform>Win32</Platform>\r
-    </ProjectConfiguration>\r
-  </ItemGroup>\r
-  <ItemGroup>\r
-    <ClCompile Include="..\..\conout.c" />\r
-    <ClCompile Include="..\..\fs.c" />\r
-    <ClCompile Include="..\..\opts.c" />\r
-    <ClCompile Include="..\..\stat.c" />\r
-    <ClCompile Include="..\..\test.c" />\r
-    <ClCompile Include="..\..\util.c" />\r
-  </ItemGroup>\r
-  <ItemGroup>\r
-    <ClInclude Include="..\..\gmqcc.h" />\r
-  </ItemGroup>\r
-  <ItemGroup>\r
-    <None Include="..\..\opts.def" />\r
-  </ItemGroup>\r
-  <PropertyGroup Label="Globals">\r
-    <ProjectGuid>{7E2839D9-9C1A-4489-9FF9-FDC854EBED3D}</ProjectGuid>\r
-    <RootNamespace>testsuite</RootNamespace>\r
-  </PropertyGroup>\r
-  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />\r
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">\r
-    <ConfigurationType>Application</ConfigurationType>\r
-    <UseDebugLibraries>true</UseDebugLibraries>\r
-    <CharacterSet>MultiByte</CharacterSet>\r
-  </PropertyGroup>\r
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">\r
-    <ConfigurationType>Application</ConfigurationType>\r
-    <UseDebugLibraries>false</UseDebugLibraries>\r
-    <WholeProgramOptimization>true</WholeProgramOptimization>\r
-    <CharacterSet>MultiByte</CharacterSet>\r
-  </PropertyGroup>\r
-  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />\r
-  <ImportGroup Label="ExtensionSettings">\r
-  </ImportGroup>\r
-  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">\r
-    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
-  </ImportGroup>\r
-  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">\r
-    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
-  </ImportGroup>\r
-  <PropertyGroup Label="UserMacros" />\r
-  <PropertyGroup />\r
-  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">\r
-    <ClCompile>\r
-      <WarningLevel>Level3</WarningLevel>\r
-      <Optimization>Disabled</Optimization>\r
-      <PreprocessorDefinitions>NVALGRIND;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
-    </ClCompile>\r
-    <Link>\r
-      <GenerateDebugInformation>true</GenerateDebugInformation>\r
-    </Link>\r
-  </ItemDefinitionGroup>\r
-  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">\r
-    <ClCompile>\r
-      <WarningLevel>Level3</WarningLevel>\r
-      <Optimization>MaxSpeed</Optimization>\r
-      <FunctionLevelLinking>true</FunctionLevelLinking>\r
-      <IntrinsicFunctions>true</IntrinsicFunctions>\r
-      <PreprocessorDefinitions>NVALGRIND;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
-    </ClCompile>\r
-    <Link>\r
-      <GenerateDebugInformation>true</GenerateDebugInformation>\r
-      <EnableCOMDATFolding>true</EnableCOMDATFolding>\r
-      <OptimizeReferences>true</OptimizeReferences>\r
-    </Link>\r
-  </ItemDefinitionGroup>\r
-  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />\r
-  <ImportGroup Label="ExtensionTargets">\r
-  </ImportGroup>\r
-</Project>
\ No newline at end of file
diff --git a/msvc/testsuite/testsuite.vcxproj.filters b/msvc/testsuite/testsuite.vcxproj.filters
deleted file mode 100755 (executable)
index fccab25..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>\r
-<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
-  <ItemGroup>\r
-    <ClCompile Include="..\..\fs.c" />\r
-    <ClCompile Include="..\..\opts.c" />\r
-    <ClCompile Include="..\..\stat.c" />\r
-    <ClCompile Include="..\..\test.c" />\r
-    <ClCompile Include="..\..\util.c" />\r
-    <ClCompile Include="..\..\conout.c" />\r
-  </ItemGroup>\r
-  <ItemGroup>\r
-    <ClInclude Include="..\..\gmqcc.h" />\r
-  </ItemGroup>\r
-  <ItemGroup>\r
-    <None Include="..\..\opts.def" />\r
-  </ItemGroup>\r
-</Project>
\ No newline at end of file
diff --git a/opts.c b/opts.c
deleted file mode 100644 (file)
index 7c59713..0000000
--- a/opts.c
+++ /dev/null
@@ -1,449 +0,0 @@
-/*
- * Copyright (C) 2012, 2013, 2014, 2015
- *     Wolfgang Bumiller
- *     Dale Weiler
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
- * this software and associated documentation files (the "Software"), to deal in
- * the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is furnished to do
- * so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-#include <string.h>
-#include <stdlib.h>
-
-#include "gmqcc.h"
-
-const unsigned int opts_opt_oflag[COUNT_OPTIMIZATIONS+1] = {
-# define GMQCC_TYPE_OPTIMIZATIONS
-# define GMQCC_DEFINE_FLAG(NAME, MIN_O) MIN_O,
-#  include "opts.def"
-    0
-};
-
-const opts_flag_def_t opts_opt_list[COUNT_OPTIMIZATIONS+1] = {
-# define GMQCC_TYPE_OPTIMIZATIONS
-# define GMQCC_DEFINE_FLAG(NAME, MIN_O) { #NAME, LONGBIT(OPTIM_##NAME) },
-#  include "opts.def"
-    { NULL, LONGBIT(0) }
-};
-
-const opts_flag_def_t opts_warn_list[COUNT_WARNINGS+1] = {
-# define GMQCC_TYPE_WARNS
-# define GMQCC_DEFINE_FLAG(X) { #X, LONGBIT(WARN_##X) },
-#  include "opts.def"
-    { NULL, LONGBIT(0) }
-};
-
-const opts_flag_def_t opts_flag_list[COUNT_FLAGS+1] = {
-# define GMQCC_TYPE_FLAGS
-# define GMQCC_DEFINE_FLAG(X) { #X, LONGBIT(X) },
-#  include "opts.def"
-    { NULL, LONGBIT(0) }
-};
-
-unsigned int opts_optimizationcount[COUNT_OPTIMIZATIONS];
-opts_cmd_t   opts; /* command line options */
-
-static void opts_setdefault(void) {
-    memset(&opts, 0, sizeof(opts_cmd_t));
-    OPTS_OPTION_BOOL(OPTION_CORRECTION) = true;
-    OPTS_OPTION_STR(OPTION_PROGSRC)     = "progs.src";
-
-    /* warnings */
-    opts_set(opts.warn,  WARN_UNUSED_VARIABLE,           true);
-    opts_set(opts.warn,  WARN_USED_UNINITIALIZED,        true);
-    opts_set(opts.warn,  WARN_UNKNOWN_CONTROL_SEQUENCE,  true);
-    opts_set(opts.warn,  WARN_EXTENSIONS,                true);
-    opts_set(opts.warn,  WARN_FIELD_REDECLARED,          true);
-    opts_set(opts.warn,  WARN_MISSING_RETURN_VALUES,     true);
-    opts_set(opts.warn,  WARN_INVALID_PARAMETER_COUNT,   true);
-    opts_set(opts.warn,  WARN_LOCAL_CONSTANTS,           true);
-    opts_set(opts.warn,  WARN_VOID_VARIABLES,            true);
-    opts_set(opts.warn,  WARN_IMPLICIT_FUNCTION_POINTER, true);
-    opts_set(opts.warn,  WARN_VARIADIC_FUNCTION,         true);
-    opts_set(opts.warn,  WARN_FRAME_MACROS,              true);
-    opts_set(opts.warn,  WARN_EFFECTLESS_STATEMENT,      true);
-    opts_set(opts.warn,  WARN_END_SYS_FIELDS,            true);
-    opts_set(opts.warn,  WARN_ASSIGN_FUNCTION_TYPES,     true);
-    opts_set(opts.warn,  WARN_CPP,                       true);
-    opts_set(opts.warn,  WARN_MULTIFILE_IF,              true);
-    opts_set(opts.warn,  WARN_DOUBLE_DECLARATION,        true);
-    opts_set(opts.warn,  WARN_CONST_VAR,                 true);
-    opts_set(opts.warn,  WARN_MULTIBYTE_CHARACTER,       true);
-    opts_set(opts.warn,  WARN_UNKNOWN_PRAGMAS,           true);
-    opts_set(opts.warn,  WARN_UNREACHABLE_CODE,          true);
-    opts_set(opts.warn,  WARN_UNKNOWN_ATTRIBUTE,         true);
-    opts_set(opts.warn,  WARN_RESERVED_NAMES,            true);
-    opts_set(opts.warn,  WARN_UNINITIALIZED_CONSTANT,    true);
-    opts_set(opts.warn,  WARN_DEPRECATED,                true);
-    opts_set(opts.warn,  WARN_PARENTHESIS,               true);
-    opts_set(opts.warn,  WARN_CONST_OVERWRITE,           true);
-    opts_set(opts.warn,  WARN_DIRECTIVE_INMACRO,         true);
-    opts_set(opts.warn,  WARN_BUILTINS,                  true);
-    opts_set(opts.warn,  WARN_INEXACT_COMPARES,          true);
-
-    /* flags */
-    opts_set(opts.flags, ADJUST_VECTOR_FIELDS,           true);
-    opts_set(opts.flags, CORRECT_TERNARY,                true);
-    opts_set(opts.flags, BAIL_ON_WERROR,                 true);
-    opts_set(opts.flags, LEGACY_VECTOR_MATHS,            true);
-    opts_set(opts.flags, DARKPLACES_STRING_TABLE_BUG,    true);
-
-    /* options */
-    OPTS_OPTION_U32(OPTION_STATE_FPS) = 10;
-}
-
-void opts_backup_non_Wall() {
-    size_t i;
-    for (i = 0; i <= WARN_DEBUG; ++i)
-        opts_set(opts.warn_backup, i, OPTS_WARN(i));
-}
-
-void opts_restore_non_Wall() {
-    size_t i;
-    for (i = 0; i <= WARN_DEBUG; ++i)
-        opts_set(opts.warn, i, OPTS_GENERIC(opts.warn_backup, i));
-}
-
-void opts_backup_non_Werror_all() {
-    size_t i;
-    for (i = 0; i <= WARN_DEBUG; ++i)
-        opts_set(opts.werror_backup, i, OPTS_WERROR(i));
-}
-
-void opts_restore_non_Werror_all() {
-    size_t i;
-    for (i = 0; i <= WARN_DEBUG; ++i)
-        opts_set(opts.werror, i, OPTS_GENERIC(opts.werror_backup, i));
-}
-
-void opts_init(const char *output, int standard, size_t arraysize) {
-    opts_setdefault();
-
-    OPTS_OPTION_STR(OPTION_OUTPUT)         = output;
-    OPTS_OPTION_U32(OPTION_STANDARD)       = standard;
-    OPTS_OPTION_U32(OPTION_MAX_ARRAY_SIZE) = arraysize;
-    OPTS_OPTION_U16(OPTION_MEMDUMPCOLS)    = 16;
-}
-
-static bool opts_setflag_all(const char *name, bool on, uint32_t *flags, const opts_flag_def_t *list, size_t listsize) {
-    size_t i;
-
-    for (i = 0; i < listsize; ++i) {
-        if (!strcmp(name, list[i].name)) {
-            longbit lb = list[i].bit;
-
-            if (on)
-                flags[lb.idx] |= (1<<(lb.bit));
-            else
-                flags[lb.idx] &= ~(1<<(lb.bit));
-
-            return true;
-        }
-    }
-    return false;
-}
-bool opts_setflag  (const char *name, bool on) {
-    return opts_setflag_all(name, on, opts.flags,        opts_flag_list, COUNT_FLAGS);
-}
-bool opts_setwarn  (const char *name, bool on) {
-    return opts_setflag_all(name, on, opts.warn,         opts_warn_list, COUNT_WARNINGS);
-}
-bool opts_setwerror(const char *name, bool on) {
-    return opts_setflag_all(name, on, opts.werror,       opts_warn_list, COUNT_WARNINGS);
-}
-bool opts_setoptim (const char *name, bool on) {
-    return opts_setflag_all(name, on, opts.optimization, opts_opt_list,  COUNT_OPTIMIZATIONS);
-}
-
-void opts_set(uint32_t *flags, size_t idx, bool on) {
-    longbit lb;
-    LONGBIT_SET(lb, idx);
-
-    if (on)
-        flags[lb.idx] |= (1<<(lb.bit));
-    else
-        flags[lb.idx] &= ~(1<<(lb.bit));
-}
-
-void opts_setoptimlevel(unsigned int level) {
-    size_t i;
-    for (i = 0; i < COUNT_OPTIMIZATIONS; ++i)
-        opts_set(opts.optimization, i, level >= opts_opt_oflag[i]);
-
-    if (!level)
-        opts.optimizeoff = true;
-}
-
-/*
- * Standard configuration parser and subsystem.  Yes, optionally you may
- * create ini files or cfg (the driver accepts both) for a project opposed
- * to supplying just a progs.src (since you also may need to supply command
- * line arguments or set the options of the compiler) [which cannot be done
- * from a progs.src.
- */
-static char *opts_ini_rstrip(char *s) {
-    char *p = s + strlen(s) - 1;
-    while (p > s && util_isspace(*p))
-        *p = '\0', p--;
-    return s;
-}
-
-static char *opts_ini_lskip(const char *s) {
-    while (*s && util_isspace(*s))
-        s++;
-    return (char*)s;
-}
-
-static char *opts_ini_next(const char *s, char c) {
-    bool last = false;
-    while (*s && *s != c && !(last && *s == ';'))
-        last = !!util_isspace(*s), s++;
-
-    return (char*)s;
-}
-
-static size_t opts_ini_parse (
-    fs_file_t *filehandle,
-    char *(*loadhandle)(const char *, const char *, const char *, char **),
-    char **errorhandle,
-    char **parse_file
-) {
-    size_t linesize;
-    size_t lineno             = 1;
-    size_t error              = 0;
-    char  *line               = NULL;
-    char   section_data[2048] = "";
-    char   oldname_data[2048] = "";
-
-    /* parsing and reading variables */
-    char *parse_beg;
-    char *parse_end;
-    char *read_name;
-    char *read_value;
-
-    while (fs_file_getline(&line, &linesize, filehandle) != FS_FILE_EOF) {
-        parse_beg = line;
-
-        /* handle BOM */
-        if (lineno == 1 && (
-                (unsigned char)parse_beg[0] == 0xEF &&
-                (unsigned char)parse_beg[1] == 0xBB &&
-                (unsigned char)parse_beg[2] == 0xBF
-            )
-        ) {
-            parse_beg ++; /* 0xEF */
-            parse_beg ++; /* 0xBB */
-            parse_beg ++; /* 0xBF */
-        }
-
-        if (*(parse_beg = opts_ini_lskip(opts_ini_rstrip(parse_beg))) == ';' || *parse_beg == '#') {
-            /* ignore '#' is a perl extension */
-        } else if (*parse_beg == '[') {
-            /* section found */
-            if (*(parse_end = opts_ini_next(parse_beg + 1, ']')) == ']') {
-                * parse_end = '\0'; /* terminate bro */
-                util_strncpy(section_data, parse_beg + 1, sizeof(section_data));
-                section_data[sizeof(section_data) - 1] = '\0';
-                *oldname_data                          = '\0';
-            } else if (!error) {
-                /* otherwise set error to the current line number */
-                error = lineno;
-            }
-        } else if (*parse_beg && *parse_beg != ';') {
-            /* not a comment, must be a name value pair :) */
-            if (*(parse_end = opts_ini_next(parse_beg, '=')) != '=')
-                parse_end = opts_ini_next(parse_beg, ':');
-
-            if (*parse_end == '=' || *parse_end == ':') {
-                *parse_end = '\0'; /* terminate bro */
-                read_name  = opts_ini_rstrip(parse_beg);
-                read_value = opts_ini_lskip(parse_end + 1);
-                if (*(parse_end = opts_ini_next(read_value, '\0')) == ';')
-                    * parse_end = '\0';
-                opts_ini_rstrip(read_value);
-
-                /* valid name value pair, lets call down to handler */
-                util_strncpy(oldname_data, read_name, sizeof(oldname_data));
-                oldname_data[sizeof(oldname_data) - 1] ='\0';
-
-                if ((*errorhandle = loadhandle(section_data, read_name, read_value, parse_file)) && !error)
-                    error = lineno;
-            } else if (!strcmp(section_data, "includes")) {
-                /* Includes are special */
-                if (*(parse_end = opts_ini_next(parse_beg, '=')) == '='
-                ||  *(parse_end = opts_ini_next(parse_beg, ':')) == ':') {
-                    static const char *invalid_include = "invalid use of include";
-                    vec_append(*errorhandle, strlen(invalid_include), invalid_include);
-                    error = lineno;
-                } else {
-                    read_name = opts_ini_rstrip(parse_beg);
-                    if ((*errorhandle = loadhandle(section_data, read_name, read_name, parse_file)) && !error)
-                        error = lineno;
-                }
-            } else if (!error) {
-                /* otherwise set error to the current line number */
-                error = lineno;
-            }
-        }
-        lineno++;
-    }
-    mem_d(line);
-    return error;
-
-}
-
-/*
- * returns true/false for a char that contains ("true" or "false" or numeric 0/1)
- */
-static bool opts_ini_bool(const char *value) {
-    if (!strcmp(value, "true"))  return true;
-    if (!strcmp(value, "false")) return false;
-    return !!strtol(value, NULL, 10);
-}
-
-static char *opts_ini_load(const char *section, const char *name, const char *value, char **parse_file) {
-    char *error = NULL;
-    bool  found = false;
-
-    /*
-     * undef all of these because they may still be defined like in my
-     * case they where.
-     */
-    #undef GMQCC_TYPE_FLAGS
-    #undef GMQCC_TYPE_OPTIMIZATIONS
-    #undef GMQCC_TYPE_WARNS
-
-    /* deal with includes */
-    if (!strcmp(section, "includes")) {
-        static const char *include_error_beg = "failed to open file `";
-        static const char *include_error_end = "' for inclusion";
-        fs_file_t *file = fs_file_open(value, "r");
-        found = true;
-        if (!file) {
-            vec_append(error, strlen(include_error_beg), include_error_beg);
-            vec_append(error, strlen(value), value);
-            vec_append(error, strlen(include_error_end), include_error_end);
-        } else {
-            if (opts_ini_parse(file, &opts_ini_load, &error, parse_file) != 0)
-                found = false;
-            /* Change the file name */
-            mem_d(*parse_file);
-            *parse_file = util_strdup(value);
-            fs_file_close(file);
-        }
-    }
-
-    /* flags */
-    #define GMQCC_TYPE_FLAGS
-    #define GMQCC_DEFINE_FLAG(X)                                       \
-    if (!strcmp(section, "flags") && !strcmp(name, #X)) {              \
-        opts_set(opts.flags, X, opts_ini_bool(value));                 \
-        found = true;                                                  \
-    }
-    #include "opts.def"
-
-    /* warnings */
-    #define GMQCC_TYPE_WARNS
-    #define GMQCC_DEFINE_FLAG(X)                                       \
-    if (!strcmp(section, "warnings") && !strcmp(name, #X)) {           \
-        opts_set(opts.warn, WARN_##X, opts_ini_bool(value));           \
-        found = true;                                                  \
-    }
-    #include "opts.def"
-
-    /* Werror-individuals */
-    #define GMQCC_TYPE_WARNS
-    #define GMQCC_DEFINE_FLAG(X)                                       \
-    if (!strcmp(section, "errors") && !strcmp(name, #X)) {             \
-        opts_set(opts.werror, WARN_##X, opts_ini_bool(value));         \
-        found = true;                                                  \
-    }
-    #include "opts.def"
-
-    /* optimizations */
-    #define GMQCC_TYPE_OPTIMIZATIONS
-    #define GMQCC_DEFINE_FLAG(X,Y)                                     \
-    if (!strcmp(section, "optimizations") && !strcmp(name, #X)) {      \
-        opts_set(opts.optimization, OPTIM_##X, opts_ini_bool(value));  \
-        found = true;                                                  \
-    }
-    #include "opts.def"
-
-    /* nothing was found ever! */
-    if (!found) {
-        if (strcmp(section, "includes") &&
-            strcmp(section, "flags")    &&
-            strcmp(section, "warnings") &&
-            strcmp(section, "optimizations"))
-        {
-            static const char *invalid_section = "invalid_section `";
-            vec_append(error, strlen(invalid_section), invalid_section);
-            vec_append(error, strlen(section), section);
-            vec_push(error, '`');
-        } else if (strcmp(section, "includes")) {
-            static const char *invalid_variable = "invalid_variable `";
-            static const char *in_section = "` in section: `";
-            vec_append(error, strlen(invalid_variable), invalid_variable);
-            vec_append(error, strlen(name), name);
-            vec_append(error, strlen(in_section), in_section);
-            vec_append(error, strlen(section), section);
-            vec_push(error, '`');
-        } else {
-            static const char *expected_something = "expected something";
-            vec_append(error, strlen(expected_something), expected_something);
-        }
-    }
-    vec_push(error, '\0');
-    return error;
-}
-
-/*
- * Actual loading subsystem, this finds the ini or cfg file, and properly
- * loads it and executes it to set compiler options.
- */
-void opts_ini_init(const char *file) {
-    /*
-     * Possible matches are:
-     *  gmqcc.ini
-     *  gmqcc.cfg
-     */
-    char       *error = NULL;
-    char       *parse_file = NULL;
-    size_t     line;
-    fs_file_t  *ini;
-
-    if (!file) {
-        /* try ini */
-        if (!(ini = fs_file_open((file = "gmqcc.ini"), "r")))
-            /* try cfg */
-            if (!(ini = fs_file_open((file = "gmqcc.cfg"), "r")))
-                return;
-    } else if (!(ini = fs_file_open(file, "r")))
-        return;
-
-    con_out("found ini file `%s`\n", file);
-
-    parse_file = util_strdup(file);
-    if ((line = opts_ini_parse(ini, &opts_ini_load, &error, &parse_file)) != 0) {
-        /* there was a parse error with the ini file */
-        con_printmsg(LVL_ERROR, parse_file, line, 0 /*TODO: column for ini error*/, "error", error);
-        vec_free(error);
-    }
-    mem_d(parse_file);
-
-    fs_file_close(ini);
-}
diff --git a/opts.cpp b/opts.cpp
new file mode 100644 (file)
index 0000000..b2517a0
--- /dev/null
+++ b/opts.cpp
@@ -0,0 +1,424 @@
+#include <string.h>
+#include <stdlib.h>
+
+#include "gmqcc.h"
+
+const unsigned int opts_opt_oflag[COUNT_OPTIMIZATIONS+1] = {
+# define GMQCC_TYPE_OPTIMIZATIONS
+# define GMQCC_DEFINE_FLAG(NAME, MIN_O) MIN_O,
+#  include "opts.def"
+    0
+};
+
+const opts_flag_def_t opts_opt_list[COUNT_OPTIMIZATIONS+1] = {
+# define GMQCC_TYPE_OPTIMIZATIONS
+# define GMQCC_DEFINE_FLAG(NAME, MIN_O) { #NAME, LONGBIT(OPTIM_##NAME) },
+#  include "opts.def"
+    { nullptr, LONGBIT(0) }
+};
+
+const opts_flag_def_t opts_warn_list[COUNT_WARNINGS+1] = {
+# define GMQCC_TYPE_WARNS
+# define GMQCC_DEFINE_FLAG(X) { #X, LONGBIT(WARN_##X) },
+#  include "opts.def"
+    { nullptr, LONGBIT(0) }
+};
+
+const opts_flag_def_t opts_flag_list[COUNT_FLAGS+1] = {
+# define GMQCC_TYPE_FLAGS
+# define GMQCC_DEFINE_FLAG(X) { #X, LONGBIT(X) },
+#  include "opts.def"
+    { nullptr, LONGBIT(0) }
+};
+
+unsigned int opts_optimizationcount[COUNT_OPTIMIZATIONS];
+opts_cmd_t   opts; /* command line options */
+
+static void opts_setdefault(void) {
+    memset(&opts, 0, sizeof(opts_cmd_t));
+    OPTS_OPTION_STR(OPTION_PROGSRC)     = "progs.src";
+
+    /* warnings */
+    opts_set(opts.warn,  WARN_UNUSED_VARIABLE,           true);
+    opts_set(opts.warn,  WARN_USED_UNINITIALIZED,        true);
+    opts_set(opts.warn,  WARN_UNKNOWN_CONTROL_SEQUENCE,  true);
+    opts_set(opts.warn,  WARN_EXTENSIONS,                true);
+    opts_set(opts.warn,  WARN_FIELD_REDECLARED,          true);
+    opts_set(opts.warn,  WARN_MISSING_RETURN_VALUES,     true);
+    opts_set(opts.warn,  WARN_INVALID_PARAMETER_COUNT,   true);
+    opts_set(opts.warn,  WARN_LOCAL_CONSTANTS,           true);
+    opts_set(opts.warn,  WARN_VOID_VARIABLES,            true);
+    opts_set(opts.warn,  WARN_IMPLICIT_FUNCTION_POINTER, true);
+    opts_set(opts.warn,  WARN_VARIADIC_FUNCTION,         true);
+    opts_set(opts.warn,  WARN_FRAME_MACROS,              true);
+    opts_set(opts.warn,  WARN_EFFECTLESS_STATEMENT,      true);
+    opts_set(opts.warn,  WARN_END_SYS_FIELDS,            true);
+    opts_set(opts.warn,  WARN_ASSIGN_FUNCTION_TYPES,     true);
+    opts_set(opts.warn,  WARN_CPP,                       true);
+    opts_set(opts.warn,  WARN_MULTIFILE_IF,              true);
+    opts_set(opts.warn,  WARN_DOUBLE_DECLARATION,        true);
+    opts_set(opts.warn,  WARN_CONST_VAR,                 true);
+    opts_set(opts.warn,  WARN_MULTIBYTE_CHARACTER,       true);
+    opts_set(opts.warn,  WARN_UNKNOWN_PRAGMAS,           true);
+    opts_set(opts.warn,  WARN_UNREACHABLE_CODE,          true);
+    opts_set(opts.warn,  WARN_UNKNOWN_ATTRIBUTE,         true);
+    opts_set(opts.warn,  WARN_RESERVED_NAMES,            true);
+    opts_set(opts.warn,  WARN_UNINITIALIZED_CONSTANT,    true);
+    opts_set(opts.warn,  WARN_DEPRECATED,                true);
+    opts_set(opts.warn,  WARN_PARENTHESIS,               true);
+    opts_set(opts.warn,  WARN_CONST_OVERWRITE,           true);
+    opts_set(opts.warn,  WARN_DIRECTIVE_INMACRO,         true);
+    opts_set(opts.warn,  WARN_BUILTINS,                  true);
+    opts_set(opts.warn,  WARN_INEXACT_COMPARES,          true);
+
+    /* flags */
+    opts_set(opts.flags, ADJUST_VECTOR_FIELDS,           true);
+    opts_set(opts.flags, CORRECT_TERNARY,                true);
+    opts_set(opts.flags, BAIL_ON_WERROR,                 true);
+    opts_set(opts.flags, LEGACY_VECTOR_MATHS,            true);
+    opts_set(opts.flags, DARKPLACES_STRING_TABLE_BUG,    true);
+
+    /* options */
+    OPTS_OPTION_U32(OPTION_STATE_FPS) = 10;
+}
+
+void opts_backup_non_Wall() {
+    size_t i;
+    for (i = 0; i <= WARN_DEBUG; ++i)
+        opts_set(opts.warn_backup, i, OPTS_WARN(i));
+}
+
+void opts_restore_non_Wall() {
+    size_t i;
+    for (i = 0; i <= WARN_DEBUG; ++i)
+        opts_set(opts.warn, i, OPTS_GENERIC(opts.warn_backup, i));
+}
+
+void opts_backup_non_Werror_all() {
+    size_t i;
+    for (i = 0; i <= WARN_DEBUG; ++i)
+        opts_set(opts.werror_backup, i, OPTS_WERROR(i));
+}
+
+void opts_restore_non_Werror_all() {
+    size_t i;
+    for (i = 0; i <= WARN_DEBUG; ++i)
+        opts_set(opts.werror, i, OPTS_GENERIC(opts.werror_backup, i));
+}
+
+void opts_init(const char *output, int standard, size_t arraysize) {
+    opts_setdefault();
+
+    OPTS_OPTION_STR(OPTION_OUTPUT)         = output;
+    OPTS_OPTION_U32(OPTION_STANDARD)       = standard;
+    OPTS_OPTION_U32(OPTION_MAX_ARRAY_SIZE) = arraysize;
+}
+
+static bool opts_setflag_all(const char *name, bool on, uint32_t *flags, const opts_flag_def_t *list, size_t listsize) {
+    size_t i;
+
+    for (i = 0; i < listsize; ++i) {
+        if (!strcmp(name, list[i].name)) {
+            longbit lb = list[i].bit;
+
+            if (on)
+                flags[lb.idx] |= (1<<(lb.bit));
+            else
+                flags[lb.idx] &= ~(1<<(lb.bit));
+
+            return true;
+        }
+    }
+    return false;
+}
+bool opts_setflag  (const char *name, bool on) {
+    return opts_setflag_all(name, on, opts.flags,        opts_flag_list, COUNT_FLAGS);
+}
+bool opts_setwarn  (const char *name, bool on) {
+    return opts_setflag_all(name, on, opts.warn,         opts_warn_list, COUNT_WARNINGS);
+}
+bool opts_setwerror(const char *name, bool on) {
+    return opts_setflag_all(name, on, opts.werror,       opts_warn_list, COUNT_WARNINGS);
+}
+bool opts_setoptim (const char *name, bool on) {
+    return opts_setflag_all(name, on, opts.optimization, opts_opt_list,  COUNT_OPTIMIZATIONS);
+}
+
+void opts_set(uint32_t *flags, size_t idx, bool on) {
+    longbit lb;
+    LONGBIT_SET(lb, idx);
+
+    if (on)
+        flags[lb.idx] |= (1<<(lb.bit));
+    else
+        flags[lb.idx] &= ~(1<<(lb.bit));
+}
+
+void opts_setoptimlevel(unsigned int level) {
+    size_t i;
+    for (i = 0; i < COUNT_OPTIMIZATIONS; ++i)
+        opts_set(opts.optimization, i, level >= opts_opt_oflag[i]);
+
+    if (!level)
+        opts.optimizeoff = true;
+}
+
+/*
+ * Standard configuration parser and subsystem.  Yes, optionally you may
+ * create ini files or cfg (the driver accepts both) for a project opposed
+ * to supplying just a progs.src (since you also may need to supply command
+ * line arguments or set the options of the compiler) [which cannot be done
+ * from a progs.src.
+ */
+static char *opts_ini_rstrip(char *s) {
+    char *p = s + strlen(s) - 1;
+    while (p > s && util_isspace(*p))
+        *p = '\0', p--;
+    return s;
+}
+
+static char *opts_ini_lskip(const char *s) {
+    while (*s && util_isspace(*s))
+        s++;
+    return (char*)s;
+}
+
+static char *opts_ini_next(const char *s, char c) {
+    bool last = false;
+    while (*s && *s != c && !(last && *s == ';'))
+        last = !!util_isspace(*s), s++;
+
+    return (char*)s;
+}
+
+static size_t opts_ini_parse (
+    FILE *filehandle,
+    char *(*loadhandle)(const char *, const char *, const char *, char **),
+    char **errorhandle,
+    char **parse_file
+) {
+    size_t linesize;
+    size_t lineno             = 1;
+    size_t error              = 0;
+    char  *line               = nullptr;
+    char   section_data[2048] = "";
+    char   oldname_data[2048] = "";
+
+    /* parsing and reading variables */
+    char *parse_beg;
+    char *parse_end;
+    char *read_name;
+    char *read_value;
+
+    while (util_getline(&line, &linesize, filehandle) != EOF) {
+        parse_beg = line;
+
+        /* handle BOM */
+        if (lineno == 1 && (
+                (unsigned char)parse_beg[0] == 0xEF &&
+                (unsigned char)parse_beg[1] == 0xBB &&
+                (unsigned char)parse_beg[2] == 0xBF
+            )
+        ) {
+            parse_beg ++; /* 0xEF */
+            parse_beg ++; /* 0xBB */
+            parse_beg ++; /* 0xBF */
+        }
+
+        if (*(parse_beg = opts_ini_lskip(opts_ini_rstrip(parse_beg))) == ';' || *parse_beg == '#') {
+            /* ignore '#' is a perl extension */
+        } else if (*parse_beg == '[') {
+            /* section found */
+            if (*(parse_end = opts_ini_next(parse_beg + 1, ']')) == ']') {
+                * parse_end = '\0'; /* terminate bro */
+                util_strncpy(section_data, parse_beg + 1, sizeof(section_data));
+                section_data[sizeof(section_data) - 1] = '\0';
+                *oldname_data                          = '\0';
+            } else if (!error) {
+                /* otherwise set error to the current line number */
+                error = lineno;
+            }
+        } else if (*parse_beg && *parse_beg != ';') {
+            /* not a comment, must be a name value pair :) */
+            if (*(parse_end = opts_ini_next(parse_beg, '=')) != '=')
+                parse_end = opts_ini_next(parse_beg, ':');
+
+            if (*parse_end == '=' || *parse_end == ':') {
+                *parse_end = '\0'; /* terminate bro */
+                read_name  = opts_ini_rstrip(parse_beg);
+                read_value = opts_ini_lskip(parse_end + 1);
+                if (*(parse_end = opts_ini_next(read_value, '\0')) == ';')
+                    * parse_end = '\0';
+                opts_ini_rstrip(read_value);
+
+                /* valid name value pair, lets call down to handler */
+                util_strncpy(oldname_data, read_name, sizeof(oldname_data));
+                oldname_data[sizeof(oldname_data) - 1] ='\0';
+
+                if ((*errorhandle = loadhandle(section_data, read_name, read_value, parse_file)) && !error)
+                    error = lineno;
+            } else if (!strcmp(section_data, "includes")) {
+                /* Includes are special */
+                if (*(parse_end = opts_ini_next(parse_beg, '=')) == '='
+                ||  *(parse_end = opts_ini_next(parse_beg, ':')) == ':') {
+                    static const char *invalid_include = "invalid use of include";
+                    vec_append(*errorhandle, strlen(invalid_include), invalid_include);
+                    error = lineno;
+                } else {
+                    read_name = opts_ini_rstrip(parse_beg);
+                    if ((*errorhandle = loadhandle(section_data, read_name, read_name, parse_file)) && !error)
+                        error = lineno;
+                }
+            } else if (!error) {
+                /* otherwise set error to the current line number */
+                error = lineno;
+            }
+        }
+        lineno++;
+    }
+    mem_d(line);
+    return error;
+
+}
+
+/*
+ * returns true/false for a char that contains ("true" or "false" or numeric 0/1)
+ */
+static bool opts_ini_bool(const char *value) {
+    if (!strcmp(value, "true"))  return true;
+    if (!strcmp(value, "false")) return false;
+    return !!strtol(value, nullptr, 10);
+}
+
+static char *opts_ini_load(const char *section, const char *name, const char *value, char **parse_file) {
+    char *error = nullptr;
+    bool  found = false;
+
+    /*
+     * undef all of these because they may still be defined like in my
+     * case they where.
+     */
+    #undef GMQCC_TYPE_FLAGS
+    #undef GMQCC_TYPE_OPTIMIZATIONS
+    #undef GMQCC_TYPE_WARNS
+
+    /* deal with includes */
+    if (!strcmp(section, "includes")) {
+        static const char *include_error_beg = "failed to open file `";
+        static const char *include_error_end = "' for inclusion";
+        FILE *file = fopen(value, "r");
+        found = true;
+        if (!file) {
+            vec_append(error, strlen(include_error_beg), include_error_beg);
+            vec_append(error, strlen(value), value);
+            vec_append(error, strlen(include_error_end), include_error_end);
+        } else {
+            if (opts_ini_parse(file, &opts_ini_load, &error, parse_file) != 0)
+                found = false;
+            /* Change the file name */
+            mem_d(*parse_file);
+            *parse_file = util_strdup(value);
+            fclose(file);
+        }
+    }
+
+    /* flags */
+    #define GMQCC_TYPE_FLAGS
+    #define GMQCC_DEFINE_FLAG(X)                                       \
+    if (!strcmp(section, "flags") && !strcmp(name, #X)) {              \
+        opts_set(opts.flags, X, opts_ini_bool(value));                 \
+        found = true;                                                  \
+    }
+    #include "opts.def"
+
+    /* warnings */
+    #define GMQCC_TYPE_WARNS
+    #define GMQCC_DEFINE_FLAG(X)                                       \
+    if (!strcmp(section, "warnings") && !strcmp(name, #X)) {           \
+        opts_set(opts.warn, WARN_##X, opts_ini_bool(value));           \
+        found = true;                                                  \
+    }
+    #include "opts.def"
+
+    /* Werror-individuals */
+    #define GMQCC_TYPE_WARNS
+    #define GMQCC_DEFINE_FLAG(X)                                       \
+    if (!strcmp(section, "errors") && !strcmp(name, #X)) {             \
+        opts_set(opts.werror, WARN_##X, opts_ini_bool(value));         \
+        found = true;                                                  \
+    }
+    #include "opts.def"
+
+    /* optimizations */
+    #define GMQCC_TYPE_OPTIMIZATIONS
+    #define GMQCC_DEFINE_FLAG(X,Y)                                     \
+    if (!strcmp(section, "optimizations") && !strcmp(name, #X)) {      \
+        opts_set(opts.optimization, OPTIM_##X, opts_ini_bool(value));  \
+        found = true;                                                  \
+    }
+    #include "opts.def"
+
+    /* nothing was found ever! */
+    if (!found) {
+        if (strcmp(section, "includes") &&
+            strcmp(section, "flags")    &&
+            strcmp(section, "warnings") &&
+            strcmp(section, "optimizations"))
+        {
+            static const char *invalid_section = "invalid_section `";
+            vec_append(error, strlen(invalid_section), invalid_section);
+            vec_append(error, strlen(section), section);
+            vec_push(error, '`');
+        } else if (strcmp(section, "includes")) {
+            static const char *invalid_variable = "invalid_variable `";
+            static const char *in_section = "` in section: `";
+            vec_append(error, strlen(invalid_variable), invalid_variable);
+            vec_append(error, strlen(name), name);
+            vec_append(error, strlen(in_section), in_section);
+            vec_append(error, strlen(section), section);
+            vec_push(error, '`');
+        } else {
+            static const char *expected_something = "expected something";
+            vec_append(error, strlen(expected_something), expected_something);
+        }
+    }
+    vec_push(error, '\0');
+    return error;
+}
+
+/*
+ * Actual loading subsystem, this finds the ini or cfg file, and properly
+ * loads it and executes it to set compiler options.
+ */
+void opts_ini_init(const char *file) {
+    /*
+     * Possible matches are:
+     *  gmqcc.ini
+     *  gmqcc.cfg
+     */
+    char       *error = nullptr;
+    char       *parse_file = nullptr;
+    size_t     line;
+    FILE  *ini;
+
+    if (!file) {
+        /* try ini */
+        if (!(ini = fopen((file = "gmqcc.ini"), "r")))
+            /* try cfg */
+            if (!(ini = fopen((file = "gmqcc.cfg"), "r")))
+                return;
+    } else if (!(ini = fopen(file, "r")))
+        return;
+
+    con_out("found ini file `%s`\n", file);
+
+    parse_file = util_strdup(file);
+    if ((line = opts_ini_parse(ini, &opts_ini_load, &error, &parse_file)) != 0) {
+        /* there was a parse error with the ini file */
+        con_printmsg(LVL_ERROR, parse_file, line, 0 /*TODO: column for ini error*/, "error", error);
+        vec_free(error);
+    }
+    mem_d(parse_file);
+
+    fclose(ini);
+}
index 92cff1463513ad6e04a05e0f9e21fed3fc5bad24..f5ce0af60c58c0fb00a6866935ebabe3a482b0a5 100644 (file)
--- a/opts.def
+++ b/opts.def
@@ -1,26 +1,3 @@
-/*
- * Copyright (C) 2012, 2013, 2014
- *     Wolfgang Bumiller
- *     Dale Weiler
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
- * this software and associated documentation files (the "Software"), to deal in
- * the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is furnished to do
- * so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
 #ifndef GMQCC_DEFINE_FLAG
 #   error "bad opts.def usage"
 #endif
     GMQCC_DEFINE_FLAG(G)
     GMQCC_DEFINE_FLAG(STANDARD)
     GMQCC_DEFINE_FLAG(DEBUG)
-    GMQCC_DEFINE_FLAG(MEMDUMPCOLS)
-    GMQCC_DEFINE_FLAG(MEMCHK)
     GMQCC_DEFINE_FLAG(DUMPFIN)
     GMQCC_DEFINE_FLAG(DUMP)
     GMQCC_DEFINE_FLAG(FORCECRC)
     GMQCC_DEFINE_FLAG(PP_ONLY)
     GMQCC_DEFINE_FLAG(MAX_ARRAY_SIZE)
     GMQCC_DEFINE_FLAG(ADD_INFO)
-    GMQCC_DEFINE_FLAG(CORRECTION)
-    GMQCC_DEFINE_FLAG(STATISTICS)
     GMQCC_DEFINE_FLAG(PROGSRC)
     GMQCC_DEFINE_FLAG(COVERAGE)
     GMQCC_DEFINE_FLAG(STATE_FPS)
diff --git a/pak.c b/pak.c
deleted file mode 100644 (file)
index 9781c53..0000000
--- a/pak.c
+++ /dev/null
@@ -1,607 +0,0 @@
-/*
- * Copyright (C) 2013, 2014, 2015
- *     Dale Weiler
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
- * this software and associated documentation files (the "Software"), to deal in
- * the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is furnished to do
- * so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-#include <string.h>
-#include <stdlib.h>
-
-#include "gmqcc.h"
-
-/*
- * The PAK format uses a FOURCC concept for storing the magic ident within
- * the header as a uint32_t.
- */
-#define PAK_FOURCC ((uint32_t)(((uint8_t)'P'|((uint8_t)'A'<<8)|((uint8_t)'C'<<16)|((uint8_t)'K'<<24))))
-
-typedef struct {
-    uint32_t magic;  /* "PACK" */
-
-    /*
-     * Offset to first directory entry in PAK file.  It's often
-     * best to store the directories at the end of the file opposed
-     * to the front, since it allows easy insertion without having
-     * to load the entire file into memory again.
-     */
-    uint32_t diroff;
-    uint32_t dirlen;
-} pak_header_t;
-
-/*
- * A directory, is sort of a "file entry".  The concept of
- * a directory in Quake world is a "file entry/record". This
- * describes a file (with directories/nested ones too in it's
- * file name).  Hence it can be a file, file with directory, or
- * file with directories.
- */
-typedef struct {
-    char     name[56];
-    uint32_t pos;
-    uint32_t len;
-} pak_directory_t;
-
-/*
- * Used to get the next token from a string, where the
- * strings themselfs are seperated by chracters from
- * `sep`.  This is essentially strsep.
- */
-static char *pak_tree_sep(char **str, const char *sep) {
-    char *beg = *str;
-    char *end;
-
-    if (!beg)
-        return NULL;
-
-    if (*(end = beg + strcspn(beg, sep)))
-        * end++ = '\0'; /* null terminate */
-    else
-          end   = 0;
-
-    *str = end;
-    return beg;
-}
-
-/*
- * When given a string like "a/b/c/d/e/file"
- * this function will handle the creation of
- * the directory structure, included nested
- * directories.
- */
-static void pak_tree_build(const char *entry) {
-    char *directory;
-    char *elements[28];
-    char *pathsplit;
-    char *token;
-
-    size_t itr;
-    size_t jtr;
-
-    pathsplit = (char *)mem_a(56);
-    directory = (char *)mem_a(56);
-
-    memset(pathsplit, 0, 56);
-
-    util_strncpy(directory, entry, 56);
-    for (itr = 0; (token = pak_tree_sep(&directory, "/")) != NULL; itr++) {
-        elements[itr] = token;
-    }
-
-    for (jtr = 0; jtr < itr - 1; jtr++) {
-        util_strcat(pathsplit, elements[jtr]);
-        util_strcat(pathsplit, "/");
-
-        if (fs_dir_make(pathsplit)) {
-            mem_d(pathsplit);
-            mem_d(directory);
-
-            /* TODO: undo on fail */
-
-            return;
-        }
-    }
-
-    mem_d(pathsplit);
-    mem_d(directory);
-}
-
-typedef struct {
-    pak_directory_t *directories;
-    pak_header_t     header;
-    fs_file_t       *handle;
-    bool             insert;
-} pak_file_t;
-
-static pak_file_t *pak_open_read(const char *file) {
-    pak_file_t *pak;
-    size_t      itr;
-
-    if (!(pak = (pak_file_t*)mem_a(sizeof(pak_file_t))))
-        return NULL;
-
-    if (!(pak->handle = fs_file_open(file, "rb"))) {
-        mem_d(pak);
-        return NULL;
-    }
-
-    pak->directories = NULL;
-    pak->insert      = false; /* read doesn't allow insert */
-
-    memset         (&pak->header, 0, sizeof(pak_header_t));
-    fs_file_read   (&pak->header,    sizeof(pak_header_t), 1, pak->handle);
-
-    util_endianswap(&pak->header.magic,  1, sizeof(pak->header.magic));
-    util_endianswap(&pak->header.diroff, 1, sizeof(pak->header.diroff));
-    util_endianswap(&pak->header.dirlen, 1, sizeof(pak->header.dirlen));
-
-    /*
-     * Every PAK file has "PACK" stored as FOURCC data in the
-     * header.  If this data cannot compare (as checked here), it's
-     * probably not a PAK file.
-     */
-    if (pak->header.magic != PAK_FOURCC) {
-        fs_file_close(pak->handle);
-        mem_d        (pak);
-        return NULL;
-    }
-
-    /*
-     * Time to read in the directory handles and prepare the directories
-     * vector.  We're going to be reading some the file inwards soon.
-     */
-    fs_file_seek(pak->handle, pak->header.diroff, FS_FILE_SEEK_SET);
-
-    /*
-     * Read in all directories from the PAK file. These are considered
-     * to be the "file entries".
-     */
-    for (itr = 0; itr < pak->header.dirlen / 64; itr++) {
-        pak_directory_t dir;
-        fs_file_read   (&dir,    sizeof(pak_directory_t), 1, pak->handle);
-
-        /* Don't translate name - it's just an array of bytes. */
-        util_endianswap(&dir.pos, 1, sizeof(dir.pos));
-        util_endianswap(&dir.len, 1, sizeof(dir.len));
-
-        vec_push(pak->directories, dir);
-    }
-    return pak;
-}
-
-static pak_file_t *pak_open_write(const char *file) {
-    pak_file_t *pak;
-
-    if (!(pak = (pak_file_t*)mem_a(sizeof(pak_file_t))))
-        return NULL;
-
-    /*
-     * Generate the required directory structure / tree for
-     * writing this PAK file too.
-     */
-    pak_tree_build(file);
-
-    if (!(pak->handle = fs_file_open(file, "wb"))) {
-        /*
-         * The directory tree that was created, needs to be
-         * removed entierly if we failed to open a file.
-         */
-        /* TODO backup directory clean */
-
-        mem_d(pak);
-        return NULL;
-    }
-
-    memset(&(pak->header), 0, sizeof(pak_header_t));
-
-    /*
-     * We're in "insert" mode, we need to do things like header
-     * "patching" and writing the directories at the end of the
-     * file.
-     */
-    pak->insert       = true;
-    pak->header.magic = PAK_FOURCC;
-
-    /* on BE systems we need to swap the byte order of the FOURCC */
-    util_endianswap(&pak->header.magic, 1, sizeof(uint32_t));
-
-    /*
-     * We need to write out the header since files will be wrote out to
-     * this even with directory entries, and that not wrote.  The header
-     * will need to be patched in later with a file_seek, and overwrite,
-     * we could use offsets and other trickery.  This is just easier.
-     */
-    fs_file_write(&(pak->header), sizeof(pak_header_t), 1, pak->handle);
-
-    return pak;
-}
-
-static pak_file_t *pak_open(const char *file, const char *mode) {
-    if (!file || !mode)
-        return NULL;
-
-    switch (*mode) {
-        case 'r': return pak_open_read (file);
-        case 'w': return pak_open_write(file);
-    }
-
-    return NULL;
-}
-
-static bool pak_exists(pak_file_t *pak, const char *file, pak_directory_t **dir) {
-    size_t itr;
-
-    if (!pak || !file)
-        return false;
-
-    for (itr = 0; itr < vec_size(pak->directories); itr++) {
-        if (!strcmp(pak->directories[itr].name, file)) {
-            /*
-             * Store back a pointer to the directory that matches
-             * the request if requested (NULL is not allowed).
-             */
-            if (dir) {
-                *dir = &(pak->directories[itr]);
-            }
-            return true;
-        }
-    }
-
-    return false;
-}
-
-/*
- * Extraction abilities.  These work as you expect them to.
- */
-static bool pak_extract_one(pak_file_t *pak, const char *file, const char *outdir) {
-    pak_directory_t *dir   = NULL;
-    unsigned char   *dat   = NULL;
-    char            *local = NULL;
-    fs_file_t       *out   = NULL;
-
-    if (!pak_exists(pak, file, &dir)) {
-        return false;
-    }
-
-    if (!(dat = (unsigned char *)mem_a(dir->len)))
-        goto err;
-
-    /*
-     * Generate the directory structure / tree that will be required
-     * to store the extracted file.
-     */
-    pak_tree_build(file);
-
-    /* TODO portable path seperators */
-    util_asprintf(&local, "%s/%s", outdir, file);
-
-    /*
-     * Now create the file, if this operation fails.  Then abort
-     * It shouldn't fail though.
-     */
-    if (!(out = fs_file_open(local, "wb")))
-        goto err;
-
-    /* free memory for directory string */
-    mem_d(local);
-
-    /* read */
-    if (fs_file_seek (pak->handle, dir->pos, FS_FILE_SEEK_SET) != 0)
-        goto err;
-
-    fs_file_read (dat, 1, dir->len, pak->handle);
-    fs_file_write(dat, 1, dir->len, out);
-    fs_file_close(out);
-
-    mem_d(dat);
-    return true;
-
-err:
-    if (dat) mem_d(dat);
-    if (out) fs_file_close(out);
-    return false;
-}
-
-static bool pak_extract_all(pak_file_t *pak, const char *dir) {
-    size_t itr;
-
-    if (!fs_dir_make(dir))
-        return false;
-
-    for (itr = 0; itr < vec_size(pak->directories); itr++) {
-        if (!pak_extract_one(pak, pak->directories[itr].name, dir))
-            return false;
-    }
-
-    return true;
-}
-
-/*
- * Insertion functions (the opposite of extraction).  Yes for generating
- * PAKs.
- */
-static bool pak_insert_one(pak_file_t *pak, const char *file) {
-    pak_directory_t dir;
-    unsigned char  *dat;
-    long            len;
-    fs_file_t      *fp;
-
-    /*
-     * We don't allow insertion on files that already exist within the
-     * pak file.  Weird shit can happen if we allow that ;). We also
-     * don't allow insertion if the pak isn't opened in write mode.
-     */
-    if (!pak || !file || !pak->insert || pak_exists(pak, file, NULL))
-        return false;
-
-    if (!(fp = fs_file_open(file, "rb")))
-        return false;
-
-    /*
-     * Calculate the total file length, since it will be wrote to
-     * the directory entry, and the actual contents of the file
-     * to the PAK file itself.
-     */
-    if (fs_file_seek(fp, 0, FS_FILE_SEEK_END) != 0 || ((len = fs_file_tell(fp)) < 0))
-        goto err;
-    if (fs_file_seek(fp, 0, FS_FILE_SEEK_SET) != 0)
-        goto err;
-
-    dir.len = len;
-    dir.pos = fs_file_tell(pak->handle);
-
-    /*
-     * We're limited to 56 bytes for a file name string, that INCLUDES
-     * the directory and '/' seperators.
-     */
-    if (strlen(file) >= 56)
-        goto err;
-
-    util_strncpy(dir.name, file, strlen(file));
-
-    /*
-     * Allocate some memory for loading in the data that will be
-     * redirected into the PAK file.
-     */
-    if (!(dat = (unsigned char *)mem_a(dir.len)))
-        goto err;
-
-    fs_file_read (dat, dir.len, 1, fp);
-    fs_file_close(fp);
-    fs_file_write(dat, dir.len, 1, pak->handle);
-
-    /*
-     * Now add the directory to the directories vector, so pak_close
-     * can actually write it.
-     */
-    vec_push(pak->directories, dir);
-
-    return true;
-
-err:
-    fs_file_close(fp);
-    return false;
-}
-
-/*
- * Like pak_insert_one, except this collects files in all directories
- * from a root directory, and inserts them all.
- */
-#if 0
-static bool pak_insert_all(pak_file_t *pak, const char *dir) {
-    DIR           *dp;
-    struct dirent *dirp;
-
-    if (!(pak->insert))
-        return false;
-
-    if (!(dp = fs_dir_open(dir)))
-        return false;
-
-    while ((dirp = fs_dir_read(dp))) {
-        if (!(pak_insert_one(pak, dirp->d_name))) {
-            fs_dir_close(dp);
-            return false;
-        }
-    }
-
-    fs_dir_close(dp);
-    return true;
-}
-#endif /*!if 0 renable when ready to use */
-
-static bool pak_close(pak_file_t *pak) {
-    size_t itr;
-    long   tell;
-
-    if (!pak)
-        return false;
-
-    /*
-     * In insert mode we need to patch the header, and write
-     * our directory entries at the end of the file.
-     */
-    if (pak->insert) {
-        if ((tell = fs_file_tell(pak->handle)) != 0)
-            goto err;
-
-        pak->header.dirlen = vec_size(pak->directories) * 64;
-        pak->header.diroff = tell;
-
-        /* patch header */
-        if (fs_file_seek (pak->handle, 0, FS_FILE_SEEK_SET) != 0)
-            goto err;
-
-        fs_file_write(&(pak->header), sizeof(pak_header_t), 1, pak->handle);
-
-        /* write directories */
-        if (fs_file_seek (pak->handle, pak->header.diroff, FS_FILE_SEEK_SET) != 0)
-            goto err;
-
-        for (itr = 0; itr < vec_size(pak->directories); itr++)
-            fs_file_write(&(pak->directories[itr]), sizeof(pak_directory_t), 1, pak->handle);
-    }
-
-    vec_free     (pak->directories);
-    fs_file_close(pak->handle);
-    mem_d        (pak);
-
-    return true;
-
-err:
-    vec_free     (pak->directories);
-    fs_file_close(pak->handle);
-    mem_d        (pak);
-
-    return false;
-}
-
-/*
- * Fancy GCC-like LONG parsing allows things like --opt=param with
- * assignment operator.  This is used for redirecting stdout/stderr
- * console to specific files of your choice.
- */
-static bool parsecmd(const char *optname, int *argc_, char ***argv_, char **out, int ds, bool split) {
-    int  argc   = *argc_;
-    char **argv = *argv_;
-
-    size_t len = strlen(optname);
-
-    if (strncmp(argv[0]+ds, optname, len))
-        return false;
-
-    /* it's --optname, check how the parameter is supplied */
-    if (argv[0][ds+len] == '=') {
-        *out = argv[0]+ds+len+1;
-        return true;
-    }
-
-    if (!split || argc < ds) /* no parameter was provided, or only single-arg form accepted */
-        return false;
-
-    /* using --opt param */
-    *out = argv[1];
-    --*argc_;
-    ++*argv_;
-    return true;
-}
-
-#include <stdio.h>
-int main(int argc, char **argv) {
-    bool          extract   = true;
-    char         *redirout  = (char*)stdout;
-    char         *redirerr  = (char*)stderr;
-    char         *file      = NULL;
-    char        **files     = NULL;
-    pak_file_t   *pak       = NULL;
-    size_t        iter      = 0;
-
-    con_init();
-
-    /*
-     * Command line option parsing commences now We only need to support
-     * a few things in the test suite.
-     */
-    while (argc > 1) {
-        ++argv;
-        --argc;
-
-        if (argv[0][0] == '-') {
-            if (parsecmd("redirout",  &argc, &argv, &redirout,  1, false))
-                continue;
-            if (parsecmd("redirerr",  &argc, &argv, &redirerr,  1, false))
-                continue;
-            if (parsecmd("file",      &argc, &argv, &file,      1, false))
-                continue;
-
-            con_change(redirout, redirerr);
-
-            switch (argv[0][1]) {
-                case 'e': extract = true;  continue;
-                case 'c': extract = false; continue;
-            }
-
-            if (!strcmp(argv[0]+1, "debug")) {
-                OPTS_OPTION_BOOL(OPTION_DEBUG) = true;
-                continue;
-            }
-            if (!strcmp(argv[0]+1, "memchk")) {
-                OPTS_OPTION_BOOL(OPTION_MEMCHK) = true;
-                continue;
-            }
-            if (!strcmp(argv[0]+1, "nocolor")) {
-                con_color(0);
-                continue;
-            }
-        }
-
-        vec_push(files, argv[0]);
-    }
-    con_change(redirout, redirerr);
-
-
-    if (!file) {
-        con_err("-file must be specified for output/input PAK file\n");
-        vec_free(files);
-        return EXIT_FAILURE;
-    }
-
-    if (extract) {
-        if (!(pak = pak_open(file, "r"))) {
-            con_err("failed to open PAK file %s\n", file);
-            vec_free(files);
-            return EXIT_FAILURE;
-        }
-
-        if (!pak_extract_all(pak, "./")) {
-            con_err("failed to extract PAK %s (files may be missing)\n", file);
-            pak_close(pak);
-            vec_free(files);
-            return EXIT_FAILURE;
-        }
-
-        /* not possible */
-        pak_close(pak);
-        vec_free(files);
-        stat_info();
-
-        return EXIT_SUCCESS;
-    }
-
-    if (!(pak = pak_open(file, "w"))) {
-        con_err("failed to open PAK %s for writing\n", file);
-        vec_free(files);
-        return EXIT_FAILURE;
-    }
-
-    for (iter = 0; iter < vec_size(files); iter++) {
-        if (!(pak_insert_one(pak, files[iter]))) {
-            con_err("failed inserting %s for PAK %s\n", files[iter], file);
-            pak_close(pak);
-            vec_free(files);
-            return EXIT_FAILURE;
-        }
-    }
-
-    /* not possible */
-    pak_close(pak);
-    vec_free(files);
-
-    stat_info();
-    return EXIT_SUCCESS;
-}
diff --git a/parser.c b/parser.c
deleted file mode 100644 (file)
index 672ce86..0000000
--- a/parser.c
+++ /dev/null
@@ -1,6540 +0,0 @@
-/*
- * Copyright (C) 2012, 2013, 2014, 2015
- *     Wolfgang Bumiller
- *     Dale Weiler
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
- * this software and associated documentation files (the "Software"), to deal in
- * the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is furnished to do
- * so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-#include <string.h>
-#include <math.h>
-
-#include "parser.h"
-
-#define PARSER_HT_LOCALS  2
-#define PARSER_HT_SIZE    512
-#define TYPEDEF_HT_SIZE   512
-
-static void parser_enterblock(parser_t *parser);
-static bool parser_leaveblock(parser_t *parser);
-static void parser_addlocal(parser_t *parser, const char *name, ast_expression *e);
-static void parser_addglobal(parser_t *parser, const char *name, ast_expression *e);
-static bool parse_typedef(parser_t *parser);
-static bool parse_variable(parser_t *parser, ast_block *localblock, bool nofields, int qualifier, ast_value *cached_typedef, bool noref, bool is_static, uint32_t qflags, char *vstring);
-static ast_block* parse_block(parser_t *parser);
-static bool parse_block_into(parser_t *parser, ast_block *block);
-static bool parse_statement_or_block(parser_t *parser, ast_expression **out);
-static bool parse_statement(parser_t *parser, ast_block *block, ast_expression **out, bool allow_cases);
-static ast_expression* parse_expression_leave(parser_t *parser, bool stopatcomma, bool truthvalue, bool with_labels);
-static ast_expression* parse_expression(parser_t *parser, bool stopatcomma, bool with_labels);
-static ast_value* parser_create_array_setter_proto(parser_t *parser, ast_value *array, const char *funcname);
-static ast_value* parser_create_array_getter_proto(parser_t *parser, ast_value *array, const ast_expression *elemtype, const char *funcname);
-static ast_value *parse_typename(parser_t *parser, ast_value **storebase, ast_value *cached_typedef, bool *is_vararg);
-
-static void parseerror(parser_t *parser, const char *fmt, ...)
-{
-    va_list ap;
-    va_start(ap, fmt);
-    vcompile_error(parser->lex->tok.ctx, fmt, ap);
-    va_end(ap);
-}
-
-/* returns true if it counts as an error */
-static bool GMQCC_WARN parsewarning(parser_t *parser, int warntype, const char *fmt, ...)
-{
-    bool    r;
-    va_list ap;
-    va_start(ap, fmt);
-    r = vcompile_warning(parser->lex->tok.ctx, warntype, fmt, ap);
-    va_end(ap);
-    return r;
-}
-
-/**********************************************************************
- * parsing
- */
-
-static bool parser_next(parser_t *parser)
-{
-    /* lex_do kills the previous token */
-    parser->tok = lex_do(parser->lex);
-    if (parser->tok == TOKEN_EOF)
-        return true;
-    if (parser->tok >= TOKEN_ERROR) {
-        parseerror(parser, "lex error");
-        return false;
-    }
-    return true;
-}
-
-#define parser_tokval(p) ((p)->lex->tok.value)
-#define parser_token(p)  (&((p)->lex->tok))
-
-char *parser_strdup(const char *str)
-{
-    if (str && !*str) {
-        /* actually dup empty strings */
-        char *out = (char*)mem_a(1);
-        *out = 0;
-        return out;
-    }
-    return util_strdup(str);
-}
-
-static ast_expression* parser_find_field(parser_t *parser, const char *name)
-{
-    return ( ast_expression*)util_htget(parser->htfields, name);
-}
-
-static ast_expression* parser_find_label(parser_t *parser, const char *name)
-{
-    size_t i;
-    for(i = 0; i < vec_size(parser->labels); i++)
-        if (!strcmp(parser->labels[i]->name, name))
-            return (ast_expression*)parser->labels[i];
-    return NULL;
-}
-
-ast_expression* parser_find_global(parser_t *parser, const char *name)
-{
-    ast_expression *var = (ast_expression*)util_htget(parser->aliases, parser_tokval(parser));
-    if (var)
-        return var;
-    return (ast_expression*)util_htget(parser->htglobals, name);
-}
-
-static ast_expression* parser_find_param(parser_t *parser, const char *name)
-{
-    size_t i;
-    ast_value *fun;
-    if (!parser->function)
-        return NULL;
-    fun = parser->function->vtype;
-    for (i = 0; i < vec_size(fun->expression.params); ++i) {
-        if (!strcmp(fun->expression.params[i]->name, name))
-            return (ast_expression*)(fun->expression.params[i]);
-    }
-    return NULL;
-}
-
-static ast_expression* parser_find_local(parser_t *parser, const char *name, size_t upto, bool *isparam)
-{
-    size_t          i, hash;
-    ast_expression *e;
-
-    hash = util_hthash(parser->htglobals, name);
-
-    *isparam = false;
-    for (i = vec_size(parser->variables); i > upto;) {
-        --i;
-        if ( (e = (ast_expression*)util_htgeth(parser->variables[i], name, hash)) )
-            return e;
-    }
-    *isparam = true;
-    return parser_find_param(parser, name);
-}
-
-static ast_expression* parser_find_var(parser_t *parser, const char *name)
-{
-    bool dummy;
-    ast_expression *v;
-    v         = parser_find_local(parser, name, 0, &dummy);
-    if (!v) v = parser_find_global(parser, name);
-    return v;
-}
-
-static ast_value* parser_find_typedef(parser_t *parser, const char *name, size_t upto)
-{
-    size_t     i, hash;
-    ast_value *e;
-    hash = util_hthash(parser->typedefs[0], name);
-
-    for (i = vec_size(parser->typedefs); i > upto;) {
-        --i;
-        if ( (e = (ast_value*)util_htgeth(parser->typedefs[i], name, hash)) )
-            return e;
-    }
-    return NULL;
-}
-
-typedef struct
-{
-    size_t etype; /* 0 = expression, others are operators */
-    bool            isparen;
-    size_t          off;
-    ast_expression *out;
-    ast_block      *block; /* for commas and function calls */
-    lex_ctx_t ctx;
-} sy_elem;
-
-enum {
-    PAREN_EXPR,
-    PAREN_FUNC,
-    PAREN_INDEX,
-    PAREN_TERNARY1,
-    PAREN_TERNARY2
-};
-typedef struct
-{
-    sy_elem        *out;
-    sy_elem        *ops;
-    size_t         *argc;
-    unsigned int   *paren;
-} shunt;
-
-static sy_elem syexp(lex_ctx_t ctx, ast_expression *v) {
-    sy_elem e;
-    e.etype = 0;
-    e.off   = 0;
-    e.out   = v;
-    e.block = NULL;
-    e.ctx   = ctx;
-    e.isparen = false;
-    return e;
-}
-
-static sy_elem syblock(lex_ctx_t ctx, ast_block *v) {
-    sy_elem e;
-    e.etype = 0;
-    e.off   = 0;
-    e.out   = (ast_expression*)v;
-    e.block = v;
-    e.ctx   = ctx;
-    e.isparen = false;
-    return e;
-}
-
-static sy_elem syop(lex_ctx_t ctx, const oper_info *op) {
-    sy_elem e;
-    e.etype = 1 + (op - operators);
-    e.off   = 0;
-    e.out   = NULL;
-    e.block = NULL;
-    e.ctx   = ctx;
-    e.isparen = false;
-    return e;
-}
-
-static sy_elem syparen(lex_ctx_t ctx, size_t off) {
-    sy_elem e;
-    e.etype = 0;
-    e.off   = off;
-    e.out   = NULL;
-    e.block = NULL;
-    e.ctx   = ctx;
-    e.isparen = true;
-    return e;
-}
-
-/* With regular precedence rules, ent.foo[n] is the same as (ent.foo)[n],
- * so we need to rotate it to become ent.(foo[n]).
- */
-static bool rotate_entfield_array_index_nodes(ast_expression **out)
-{
-    ast_array_index *index, *oldindex;
-    ast_entfield    *entfield;
-
-    ast_value       *field;
-    ast_expression  *sub;
-    ast_expression  *entity;
-
-    lex_ctx_t ctx = ast_ctx(*out);
-
-    if (!ast_istype(*out, ast_array_index))
-        return false;
-    index = (ast_array_index*)*out;
-
-    if (!ast_istype(index->array, ast_entfield))
-        return false;
-    entfield = (ast_entfield*)index->array;
-
-    if (!ast_istype(entfield->field, ast_value))
-        return false;
-    field = (ast_value*)entfield->field;
-
-    sub    = index->index;
-    entity = entfield->entity;
-
-    oldindex = index;
-
-    index = ast_array_index_new(ctx, (ast_expression*)field, sub);
-    entfield = ast_entfield_new(ctx, entity, (ast_expression*)index);
-    *out = (ast_expression*)entfield;
-
-    oldindex->array = NULL;
-    oldindex->index = NULL;
-    ast_delete(oldindex);
-
-    return true;
-}
-
-static bool check_write_to(lex_ctx_t ctx, ast_expression *expr)
-{
-    if (ast_istype(expr, ast_value)) {
-        ast_value *val = (ast_value*)expr;
-        if (val->cvq == CV_CONST) {
-            if (val->name[0] == '#') {
-                compile_error(ctx, "invalid assignment to a literal constant");
-                return false;
-            }
-            /*
-             * To work around quakeworld we must elide the error and make it
-             * a warning instead.
-             */
-            if (OPTS_OPTION_U32(OPTION_STANDARD) != COMPILER_QCC)
-                compile_error(ctx, "assignment to constant `%s`", val->name);
-            else
-                (void)!compile_warning(ctx, WARN_CONST_OVERWRITE, "assignment to constant `%s`", val->name);
-            return false;
-        }
-    }
-    return true;
-}
-
-static bool parser_sy_apply_operator(parser_t *parser, shunt *sy)
-{
-    const oper_info *op;
-    lex_ctx_t ctx;
-    ast_expression *out = NULL;
-    ast_expression *exprs[3];
-    ast_block      *blocks[3];
-    ast_binstore   *asbinstore;
-    size_t i, assignop, addop, subop;
-    qcint_t  generated_op = 0;
-
-    char ty1[1024];
-    char ty2[1024];
-
-    if (!vec_size(sy->ops)) {
-        parseerror(parser, "internal error: missing operator");
-        return false;
-    }
-
-    if (vec_last(sy->ops).isparen) {
-        parseerror(parser, "unmatched parenthesis");
-        return false;
-    }
-
-    op = &operators[vec_last(sy->ops).etype - 1];
-    ctx = vec_last(sy->ops).ctx;
-
-    if (vec_size(sy->out) < op->operands) {
-        if (op->flags & OP_PREFIX)
-            compile_error(ctx, "expected expression after unary operator `%s`", op->op, (int)op->id);
-        else /* this should have errored previously already */
-            compile_error(ctx, "expected expression after operator `%s`", op->op, (int)op->id);
-        return false;
-    }
-
-    vec_shrinkby(sy->ops, 1);
-
-    /* op(:?) has no input and no output */
-    if (!op->operands)
-        return true;
-
-    vec_shrinkby(sy->out, op->operands);
-    for (i = 0; i < op->operands; ++i) {
-        exprs[i]  = sy->out[vec_size(sy->out)+i].out;
-        blocks[i] = sy->out[vec_size(sy->out)+i].block;
-
-        if (exprs[i]->vtype == TYPE_NOEXPR &&
-            !(i != 0 && op->id == opid2('?',':')) &&
-            !(i == 1 && op->id == opid1('.')))
-        {
-            if (ast_istype(exprs[i], ast_label))
-                compile_error(ast_ctx(exprs[i]), "expected expression, got an unknown identifier");
-            else
-                compile_error(ast_ctx(exprs[i]), "not an expression");
-            (void)!compile_warning(ast_ctx(exprs[i]), WARN_DEBUG, "expression %u\n", (unsigned int)i);
-        }
-    }
-
-    if (blocks[0] && !vec_size(blocks[0]->exprs) && op->id != opid1(',')) {
-        compile_error(ctx, "internal error: operator cannot be applied on empty blocks");
-        return false;
-    }
-
-#define NotSameType(T) \
-             (exprs[0]->vtype != exprs[1]->vtype || \
-              exprs[0]->vtype != T)
-
-    switch (op->id)
-    {
-        default:
-            compile_error(ctx, "internal error: unhandled operator: %s (%i)", op->op, (int)op->id);
-            return false;
-
-        case opid1('.'):
-            if (exprs[0]->vtype == TYPE_VECTOR &&
-                exprs[1]->vtype == TYPE_NOEXPR)
-            {
-                if      (exprs[1] == (ast_expression*)parser->const_vec[0])
-                    out = (ast_expression*)ast_member_new(ctx, exprs[0], 0, NULL);
-                else if (exprs[1] == (ast_expression*)parser->const_vec[1])
-                    out = (ast_expression*)ast_member_new(ctx, exprs[0], 1, NULL);
-                else if (exprs[1] == (ast_expression*)parser->const_vec[2])
-                    out = (ast_expression*)ast_member_new(ctx, exprs[0], 2, NULL);
-                else {
-                    compile_error(ctx, "access to invalid vector component");
-                    return false;
-                }
-            }
-            else if (exprs[0]->vtype == TYPE_ENTITY) {
-                if (exprs[1]->vtype != TYPE_FIELD) {
-                    compile_error(ast_ctx(exprs[1]), "type error: right hand of member-operand should be an entity-field");
-                    return false;
-                }
-                out = (ast_expression*)ast_entfield_new(ctx, exprs[0], exprs[1]);
-            }
-            else if (exprs[0]->vtype == TYPE_VECTOR) {
-                compile_error(ast_ctx(exprs[1]), "vectors cannot be accessed this way");
-                return false;
-            }
-            else {
-                compile_error(ast_ctx(exprs[1]), "type error: member-of operator on something that is not an entity or vector");
-                return false;
-            }
-            break;
-
-        case opid1('['):
-            if (exprs[0]->vtype != TYPE_ARRAY &&
-                !(exprs[0]->vtype == TYPE_FIELD &&
-                  exprs[0]->next->vtype == TYPE_ARRAY))
-            {
-                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
-                compile_error(ast_ctx(exprs[0]), "cannot index value of type %s", ty1);
-                return false;
-            }
-            if (exprs[1]->vtype != TYPE_FLOAT) {
-                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
-                compile_error(ast_ctx(exprs[1]), "index must be of type float, not %s", ty1);
-                return false;
-            }
-            out = (ast_expression*)ast_array_index_new(ctx, exprs[0], exprs[1]);
-            if (rotate_entfield_array_index_nodes(&out))
-            {
-#if 0
-                /* This is not broken in fteqcc anymore */
-                if (OPTS_OPTION_U32(OPTION_STANDARD) != COMPILER_GMQCC) {
-                    /* this error doesn't need to make us bail out */
-                    (void)!parsewarning(parser, WARN_EXTENSIONS,
-                                        "accessing array-field members of an entity without parenthesis\n"
-                                        " -> this is an extension from -std=gmqcc");
-                }
-#endif
-            }
-            break;
-
-        case opid1(','):
-            if (vec_size(sy->paren) && vec_last(sy->paren) == PAREN_FUNC) {
-                vec_push(sy->out, syexp(ctx, exprs[0]));
-                vec_push(sy->out, syexp(ctx, exprs[1]));
-                vec_last(sy->argc)++;
-                return true;
-            }
-            if (blocks[0]) {
-                if (!ast_block_add_expr(blocks[0], exprs[1]))
-                    return false;
-            } else {
-                blocks[0] = ast_block_new(ctx);
-                if (!ast_block_add_expr(blocks[0], exprs[0]) ||
-                    !ast_block_add_expr(blocks[0], exprs[1]))
-                {
-                    return false;
-                }
-            }
-            ast_block_set_type(blocks[0], exprs[1]);
-
-            vec_push(sy->out, syblock(ctx, blocks[0]));
-            return true;
-
-        case opid2('+','P'):
-            out = exprs[0];
-            break;
-        case opid2('-','P'):
-            if ((out = fold_op(parser->fold, op, exprs)))
-                break;
-
-            if (exprs[0]->vtype != TYPE_FLOAT &&
-                exprs[0]->vtype != TYPE_VECTOR) {
-                    compile_error(ctx, "invalid types used in unary expression: cannot negate type %s",
-                                  type_name[exprs[0]->vtype]);
-                return false;
-            }
-            if (exprs[0]->vtype == TYPE_FLOAT)
-                out = (ast_expression*)ast_unary_new(ctx, VINSTR_NEG_F, exprs[0]);
-            else
-                out = (ast_expression*)ast_unary_new(ctx, VINSTR_NEG_V, exprs[0]);
-            break;
-
-        case opid2('!','P'):
-            if (!(out = fold_op(parser->fold, op, exprs))) {
-                switch (exprs[0]->vtype) {
-                    case TYPE_FLOAT:
-                        out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_F, exprs[0]);
-                        break;
-                    case TYPE_VECTOR:
-                        out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_V, exprs[0]);
-                        break;
-                    case TYPE_STRING:
-                        if (OPTS_FLAG(TRUE_EMPTY_STRINGS))
-                            out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_F, exprs[0]);
-                        else
-                            out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_S, exprs[0]);
-                        break;
-                    /* we don't constant-fold NOT for these types */
-                    case TYPE_ENTITY:
-                        out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_ENT, exprs[0]);
-                        break;
-                    case TYPE_FUNCTION:
-                        out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_FNC, exprs[0]);
-                        break;
-                    default:
-                    compile_error(ctx, "invalid types used in expression: cannot logically negate type %s",
-                                  type_name[exprs[0]->vtype]);
-                    return false;
-                }
-            }
-            break;
-
-        case opid1('+'):
-            if (exprs[0]->vtype != exprs[1]->vtype ||
-               (exprs[0]->vtype != TYPE_VECTOR && exprs[0]->vtype != TYPE_FLOAT) )
-            {
-                compile_error(ctx, "invalid types used in expression: cannot add type %s and %s",
-                              type_name[exprs[0]->vtype],
-                              type_name[exprs[1]->vtype]);
-                return false;
-            }
-            if (!(out = fold_op(parser->fold, op, exprs))) {
-                switch (exprs[0]->vtype) {
-                    case TYPE_FLOAT:
-                        out = fold_binary(ctx, INSTR_ADD_F, exprs[0], exprs[1]);
-                        break;
-                    case TYPE_VECTOR:
-                        out = fold_binary(ctx, INSTR_ADD_V, exprs[0], exprs[1]);
-                        break;
-                    default:
-                        compile_error(ctx, "invalid types used in expression: cannot add type %s and %s",
-                                      type_name[exprs[0]->vtype],
-                                      type_name[exprs[1]->vtype]);
-                        return false;
-                }
-            }
-            break;
-        case opid1('-'):
-            if  (exprs[0]->vtype != exprs[1]->vtype ||
-                (exprs[0]->vtype != TYPE_VECTOR && exprs[0]->vtype != TYPE_FLOAT))
-            {
-                compile_error(ctx, "invalid types used in expression: cannot subtract type %s from %s",
-                              type_name[exprs[1]->vtype],
-                              type_name[exprs[0]->vtype]);
-                return false;
-            }
-            if (!(out = fold_op(parser->fold, op, exprs))) {
-                switch (exprs[0]->vtype) {
-                    case TYPE_FLOAT:
-                        out = fold_binary(ctx, INSTR_SUB_F, exprs[0], exprs[1]);
-                        break;
-                    case TYPE_VECTOR:
-                        out = fold_binary(ctx, INSTR_SUB_V, exprs[0], exprs[1]);
-                        break;
-                    default:
-                        compile_error(ctx, "invalid types used in expression: cannot subtract type %s from %s",
-                                      type_name[exprs[1]->vtype],
-                                      type_name[exprs[0]->vtype]);
-                        return false;
-                }
-            }
-            break;
-        case opid1('*'):
-            if (exprs[0]->vtype != exprs[1]->vtype &&
-                !(exprs[0]->vtype == TYPE_VECTOR &&
-                  exprs[1]->vtype == TYPE_FLOAT) &&
-                !(exprs[1]->vtype == TYPE_VECTOR &&
-                  exprs[0]->vtype == TYPE_FLOAT)
-                )
-            {
-                compile_error(ctx, "invalid types used in expression: cannot multiply types %s and %s",
-                              type_name[exprs[1]->vtype],
-                              type_name[exprs[0]->vtype]);
-                return false;
-            }
-            if (!(out = fold_op(parser->fold, op, exprs))) {
-                switch (exprs[0]->vtype) {
-                    case TYPE_FLOAT:
-                        if (exprs[1]->vtype == TYPE_VECTOR)
-                            out = fold_binary(ctx, INSTR_MUL_FV, exprs[0], exprs[1]);
-                        else
-                            out = fold_binary(ctx, INSTR_MUL_F, exprs[0], exprs[1]);
-                        break;
-                    case TYPE_VECTOR:
-                        if (exprs[1]->vtype == TYPE_FLOAT)
-                            out = fold_binary(ctx, INSTR_MUL_VF, exprs[0], exprs[1]);
-                        else
-                            out = fold_binary(ctx, INSTR_MUL_V, exprs[0], exprs[1]);
-                        break;
-                    default:
-                        compile_error(ctx, "invalid types used in expression: cannot multiply types %s and %s",
-                                      type_name[exprs[1]->vtype],
-                                      type_name[exprs[0]->vtype]);
-                        return false;
-                }
-            }
-            break;
-
-        case opid1('/'):
-            if (exprs[1]->vtype != TYPE_FLOAT) {
-                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
-                ast_type_to_string(exprs[1], ty2, sizeof(ty2));
-                compile_error(ctx, "invalid types used in expression: cannot divide types %s and %s", ty1, ty2);
-                return false;
-            }
-            if (!(out = fold_op(parser->fold, op, exprs))) {
-                if (exprs[0]->vtype == TYPE_FLOAT)
-                    out = fold_binary(ctx, INSTR_DIV_F, exprs[0], exprs[1]);
-                else {
-                    ast_type_to_string(exprs[0], ty1, sizeof(ty1));
-                    ast_type_to_string(exprs[1], ty2, sizeof(ty2));
-                    compile_error(ctx, "invalid types used in expression: cannot divide types %s and %s", ty1, ty2);
-                    return false;
-                }
-            }
-            break;
-
-        case opid1('%'):
-            if (NotSameType(TYPE_FLOAT)) {
-                compile_error(ctx, "invalid types used in expression: cannot perform modulo operation between types %s and %s",
-                    type_name[exprs[0]->vtype],
-                    type_name[exprs[1]->vtype]);
-                return false;
-            } else if (!(out = fold_op(parser->fold, op, exprs))) {
-                /* generate a call to __builtin_mod */
-                ast_expression *mod  = intrin_func(parser->intrin, "mod");
-                ast_call       *call = NULL;
-                if (!mod) return false; /* can return null for missing floor */
-
-                call = ast_call_new(parser_ctx(parser), mod);
-                vec_push(call->params, exprs[0]);
-                vec_push(call->params, exprs[1]);
-
-                out = (ast_expression*)call;
-            }
-            break;
-
-        case opid2('%','='):
-            compile_error(ctx, "%= is unimplemented");
-            return false;
-
-        case opid1('|'):
-        case opid1('&'):
-        case opid1('^'):
-            if ( !(exprs[0]->vtype == TYPE_FLOAT  && exprs[1]->vtype == TYPE_FLOAT) &&
-                 !(exprs[0]->vtype == TYPE_VECTOR && exprs[1]->vtype == TYPE_FLOAT) &&
-                 !(exprs[0]->vtype == TYPE_VECTOR && exprs[1]->vtype == TYPE_VECTOR))
-            {
-                compile_error(ctx, "invalid types used in expression: cannot perform bit operations between types %s and %s",
-                              type_name[exprs[0]->vtype],
-                              type_name[exprs[1]->vtype]);
-                return false;
-            }
-
-            if (!(out = fold_op(parser->fold, op, exprs))) {
-                /*
-                 * IF the first expression is float, the following will be too
-                 * since scalar ^ vector is not allowed.
-                 */
-                if (exprs[0]->vtype == TYPE_FLOAT) {
-                    out = fold_binary(ctx,
-                        (op->id == opid1('^') ? VINSTR_BITXOR : op->id == opid1('|') ? INSTR_BITOR : INSTR_BITAND),
-                        exprs[0], exprs[1]);
-                } else {
-                    /*
-                     * The first is a vector: vector is allowed to bitop with vector and
-                     * with scalar, branch here for the second operand.
-                     */
-                    if (exprs[1]->vtype == TYPE_VECTOR) {
-                        /*
-                         * Bitop all the values of the vector components against the
-                         * vectors components in question.
-                         */
-                        out = fold_binary(ctx,
-                            (op->id == opid1('^') ? VINSTR_BITXOR_V : op->id == opid1('|') ? VINSTR_BITOR_V : VINSTR_BITAND_V),
-                            exprs[0], exprs[1]);
-                    } else {
-                        out = fold_binary(ctx,
-                            (op->id == opid1('^') ? VINSTR_BITXOR_VF : op->id == opid1('|') ? VINSTR_BITOR_VF : VINSTR_BITAND_VF),
-                            exprs[0], exprs[1]);
-                    }
-                }
-            }
-            break;
-
-        case opid2('<','<'):
-        case opid2('>','>'):
-            if (NotSameType(TYPE_FLOAT)) {
-                compile_error(ctx, "invalid types used in expression: cannot perform shift between types %s and %s",
-                    type_name[exprs[0]->vtype],
-                    type_name[exprs[1]->vtype]);
-                return false;
-            }
-
-            if (!(out = fold_op(parser->fold, op, exprs))) {
-                ast_expression *shift = intrin_func(parser->intrin, (op->id == opid2('<','<')) ? "__builtin_lshift" : "__builtin_rshift");
-                ast_call       *call  = ast_call_new(parser_ctx(parser), shift);
-                vec_push(call->params, exprs[0]);
-                vec_push(call->params, exprs[1]);
-                out = (ast_expression*)call;
-            }
-            break;
-
-        case opid3('<','<','='):
-        case opid3('>','>','='):
-            if (NotSameType(TYPE_FLOAT)) {
-                compile_error(ctx, "invalid types used in expression: cannot perform shift operation between types %s and %s",
-                    type_name[exprs[0]->vtype],
-                    type_name[exprs[1]->vtype]);
-                return false;
-            }
-
-            if(!(out = fold_op(parser->fold, op, exprs))) {
-                ast_expression *shift = intrin_func(parser->intrin, (op->id == opid3('<','<','=')) ? "__builtin_lshift" : "__builtin_rshift");
-                ast_call       *call  = ast_call_new(parser_ctx(parser), shift);
-                vec_push(call->params, exprs[0]);
-                vec_push(call->params, exprs[1]);
-                out = (ast_expression*)ast_store_new(
-                    parser_ctx(parser),
-                    INSTR_STORE_F,
-                    exprs[0],
-                    (ast_expression*)call
-                );
-            }
-
-            break;
-
-        case opid2('|','|'):
-            generated_op += 1; /* INSTR_OR */
-        case opid2('&','&'):
-            generated_op += INSTR_AND;
-            if (!(out = fold_op(parser->fold, op, exprs))) {
-                if (OPTS_FLAG(PERL_LOGIC) && !ast_compare_type(exprs[0], exprs[1])) {
-                    ast_type_to_string(exprs[0], ty1, sizeof(ty1));
-                    ast_type_to_string(exprs[1], ty2, sizeof(ty2));
-                    compile_error(ctx, "invalid types for logical operation with -fperl-logic: %s and %s", ty1, ty2);
-                    return false;
-                }
-                for (i = 0; i < 2; ++i) {
-                    if (OPTS_FLAG(CORRECT_LOGIC) && exprs[i]->vtype == TYPE_VECTOR) {
-                        out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_V, exprs[i]);
-                        if (!out) break;
-                        out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_F, out);
-                        if (!out) break;
-                        exprs[i] = out; out = NULL;
-                        if (OPTS_FLAG(PERL_LOGIC)) {
-                            /* here we want to keep the right expressions' type */
-                            break;
-                        }
-                    }
-                    else if (OPTS_FLAG(FALSE_EMPTY_STRINGS) && exprs[i]->vtype == TYPE_STRING) {
-                        out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_S, exprs[i]);
-                        if (!out) break;
-                        out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_F, out);
-                        if (!out) break;
-                        exprs[i] = out; out = NULL;
-                        if (OPTS_FLAG(PERL_LOGIC)) {
-                            /* here we want to keep the right expressions' type */
-                            break;
-                        }
-                    }
-                }
-                out = fold_binary(ctx, generated_op, exprs[0], exprs[1]);
-            }
-            break;
-
-        case opid2('?',':'):
-            if (vec_last(sy->paren) != PAREN_TERNARY2) {
-                compile_error(ctx, "mismatched parenthesis/ternary");
-                return false;
-            }
-            vec_pop(sy->paren);
-            if (!ast_compare_type(exprs[1], exprs[2])) {
-                ast_type_to_string(exprs[1], ty1, sizeof(ty1));
-                ast_type_to_string(exprs[2], ty2, sizeof(ty2));
-                compile_error(ctx, "operands of ternary expression must have the same type, got %s and %s", ty1, ty2);
-                return false;
-            }
-            if (!(out = fold_op(parser->fold, op, exprs)))
-                out = (ast_expression*)ast_ternary_new(ctx, exprs[0], exprs[1], exprs[2]);
-            break;
-
-        case opid2('*', '*'):
-            if (NotSameType(TYPE_FLOAT)) {
-                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
-                ast_type_to_string(exprs[1], ty2, sizeof(ty2));
-                compile_error(ctx, "invalid types used in exponentiation: %s and %s",
-                    ty1, ty2);
-                return false;
-            }
-
-            if (!(out = fold_op(parser->fold, op, exprs))) {
-                ast_call *gencall = ast_call_new(parser_ctx(parser), intrin_func(parser->intrin, "pow"));
-                vec_push(gencall->params, exprs[0]);
-                vec_push(gencall->params, exprs[1]);
-                out = (ast_expression*)gencall;
-            }
-            break;
-
-        case opid2('>', '<'):
-            if (NotSameType(TYPE_VECTOR)) {
-                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
-                ast_type_to_string(exprs[1], ty2, sizeof(ty2));
-                compile_error(ctx, "invalid types used in cross product: %s and %s",
-                    ty1, ty2);
-                return false;
-            }
-
-            if (!(out = fold_op(parser->fold, op, exprs))) {
-                out = fold_binary(
-                        parser_ctx(parser),
-                        VINSTR_CROSS,
-                        exprs[0],
-                        exprs[1]
-                );
-            }
-
-            break;
-
-        case opid3('<','=','>'): /* -1, 0, or 1 */
-            if (NotSameType(TYPE_FLOAT)) {
-                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
-                ast_type_to_string(exprs[1], ty2, sizeof(ty2));
-                compile_error(ctx, "invalid types used in comparision: %s and %s",
-                    ty1, ty2);
-
-                return false;
-            }
-
-            if (!(out = fold_op(parser->fold, op, exprs))) {
-                /* This whole block is NOT fold_binary safe */
-                ast_binary *eq = ast_binary_new(ctx, INSTR_EQ_F, exprs[0], exprs[1]);
-
-                eq->refs = AST_REF_NONE;
-
-                    /* if (lt) { */
-                out = (ast_expression*)ast_ternary_new(ctx,
-                        (ast_expression*)ast_binary_new(ctx, INSTR_LT, exprs[0], exprs[1]),
-                        /* out = -1 */
-                        (ast_expression*)parser->fold->imm_float[2],
-                    /* } else { */
-                        /* if (eq) { */
-                        (ast_expression*)ast_ternary_new(ctx, (ast_expression*)eq,
-                            /* out = 0 */
-                            (ast_expression*)parser->fold->imm_float[0],
-                        /* } else { */
-                            /* out = 1 */
-                            (ast_expression*)parser->fold->imm_float[1]
-                        /* } */
-                        )
-                    /* } */
-                    );
-
-            }
-            break;
-
-        case opid1('>'):
-            generated_op += 1; /* INSTR_GT */
-        case opid1('<'):
-            generated_op += 1; /* INSTR_LT */
-        case opid2('>', '='):
-            generated_op += 1; /* INSTR_GE */
-        case opid2('<', '='):
-            generated_op += INSTR_LE;
-            if (NotSameType(TYPE_FLOAT)) {
-                compile_error(ctx, "invalid types used in expression: cannot perform comparison between types %s and %s",
-                              type_name[exprs[0]->vtype],
-                              type_name[exprs[1]->vtype]);
-                return false;
-            }
-            if (!(out = fold_op(parser->fold, op, exprs)))
-                out = fold_binary(ctx, generated_op, exprs[0], exprs[1]);
-            break;
-        case opid2('!', '='):
-            if (exprs[0]->vtype != exprs[1]->vtype) {
-                compile_error(ctx, "invalid types used in expression: cannot perform comparison between types %s and %s",
-                              type_name[exprs[0]->vtype],
-                              type_name[exprs[1]->vtype]);
-                return false;
-            }
-            if (!(out = fold_op(parser->fold, op, exprs)))
-                out = fold_binary(ctx, type_ne_instr[exprs[0]->vtype], exprs[0], exprs[1]);
-            break;
-        case opid2('=', '='):
-            if (exprs[0]->vtype != exprs[1]->vtype) {
-                compile_error(ctx, "invalid types used in expression: cannot perform comparison between types %s and %s",
-                              type_name[exprs[0]->vtype],
-                              type_name[exprs[1]->vtype]);
-                return false;
-            }
-            if (!(out = fold_op(parser->fold, op, exprs)))
-                out = fold_binary(ctx, type_eq_instr[exprs[0]->vtype], exprs[0], exprs[1]);
-            break;
-
-        case opid1('='):
-            if (ast_istype(exprs[0], ast_entfield)) {
-                ast_expression *field = ((ast_entfield*)exprs[0])->field;
-                if (OPTS_FLAG(ADJUST_VECTOR_FIELDS) &&
-                    exprs[0]->vtype == TYPE_FIELD &&
-                    exprs[0]->next->vtype == TYPE_VECTOR)
-                {
-                    assignop = type_storep_instr[TYPE_VECTOR];
-                }
-                else
-                    assignop = type_storep_instr[exprs[0]->vtype];
-                if (assignop == VINSTR_END || !ast_compare_type(field->next, exprs[1]))
-                {
-                    ast_type_to_string(field->next, ty1, sizeof(ty1));
-                    ast_type_to_string(exprs[1], ty2, sizeof(ty2));
-                    if (OPTS_FLAG(ASSIGN_FUNCTION_TYPES) &&
-                        field->next->vtype == TYPE_FUNCTION &&
-                        exprs[1]->vtype == TYPE_FUNCTION)
-                    {
-                        (void)!compile_warning(ctx, WARN_ASSIGN_FUNCTION_TYPES,
-                                               "invalid types in assignment: cannot assign %s to %s", ty2, ty1);
-                    }
-                    else
-                        compile_error(ctx, "invalid types in assignment: cannot assign %s to %s", ty2, ty1);
-                }
-            }
-            else
-            {
-                if (OPTS_FLAG(ADJUST_VECTOR_FIELDS) &&
-                    exprs[0]->vtype == TYPE_FIELD &&
-                    exprs[0]->next->vtype == TYPE_VECTOR)
-                {
-                    assignop = type_store_instr[TYPE_VECTOR];
-                }
-                else {
-                    assignop = type_store_instr[exprs[0]->vtype];
-                }
-
-                if (assignop == VINSTR_END) {
-                    ast_type_to_string(exprs[0], ty1, sizeof(ty1));
-                    ast_type_to_string(exprs[1], ty2, sizeof(ty2));
-                    compile_error(ctx, "invalid types in assignment: cannot assign %s to %s", ty2, ty1);
-                }
-                else if (!ast_compare_type(exprs[0], exprs[1]))
-                {
-                    ast_type_to_string(exprs[0], ty1, sizeof(ty1));
-                    ast_type_to_string(exprs[1], ty2, sizeof(ty2));
-                    if (OPTS_FLAG(ASSIGN_FUNCTION_TYPES) &&
-                        exprs[0]->vtype == TYPE_FUNCTION &&
-                        exprs[1]->vtype == TYPE_FUNCTION)
-                    {
-                        (void)!compile_warning(ctx, WARN_ASSIGN_FUNCTION_TYPES,
-                                               "invalid types in assignment: cannot assign %s to %s", ty2, ty1);
-                    }
-                    else
-                        compile_error(ctx, "invalid types in assignment: cannot assign %s to %s", ty2, ty1);
-                }
-            }
-            (void)check_write_to(ctx, exprs[0]);
-            /* When we're a vector of part of an entity field we use STOREP */
-            if (ast_istype(exprs[0], ast_member) && ast_istype(((ast_member*)exprs[0])->owner, ast_entfield))
-                assignop = INSTR_STOREP_F;
-            out = (ast_expression*)ast_store_new(ctx, assignop, exprs[0], exprs[1]);
-            break;
-        case opid3('+','+','P'):
-        case opid3('-','-','P'):
-            /* prefix ++ */
-            if (exprs[0]->vtype != TYPE_FLOAT) {
-                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
-                compile_error(ast_ctx(exprs[0]), "invalid type for prefix increment: %s", ty1);
-                return false;
-            }
-            if (op->id == opid3('+','+','P'))
-                addop = INSTR_ADD_F;
-            else
-                addop = INSTR_SUB_F;
-            (void)check_write_to(ast_ctx(exprs[0]), exprs[0]);
-            if (ast_istype(exprs[0], ast_entfield)) {
-                out = (ast_expression*)ast_binstore_new(ctx, INSTR_STOREP_F, addop,
-                                                        exprs[0],
-                                                        (ast_expression*)parser->fold->imm_float[1]);
-            } else {
-                out = (ast_expression*)ast_binstore_new(ctx, INSTR_STORE_F, addop,
-                                                        exprs[0],
-                                                        (ast_expression*)parser->fold->imm_float[1]);
-            }
-            break;
-        case opid3('S','+','+'):
-        case opid3('S','-','-'):
-            /* prefix ++ */
-            if (exprs[0]->vtype != TYPE_FLOAT) {
-                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
-                compile_error(ast_ctx(exprs[0]), "invalid type for suffix increment: %s", ty1);
-                return false;
-            }
-            if (op->id == opid3('S','+','+')) {
-                addop = INSTR_ADD_F;
-                subop = INSTR_SUB_F;
-            } else {
-                addop = INSTR_SUB_F;
-                subop = INSTR_ADD_F;
-            }
-            (void)check_write_to(ast_ctx(exprs[0]), exprs[0]);
-            if (ast_istype(exprs[0], ast_entfield)) {
-                out = (ast_expression*)ast_binstore_new(ctx, INSTR_STOREP_F, addop,
-                                                        exprs[0],
-                                                        (ast_expression*)parser->fold->imm_float[1]);
-            } else {
-                out = (ast_expression*)ast_binstore_new(ctx, INSTR_STORE_F, addop,
-                                                        exprs[0],
-                                                        (ast_expression*)parser->fold->imm_float[1]);
-            }
-            if (!out)
-                return false;
-            out = fold_binary(ctx, subop,
-                              out,
-                              (ast_expression*)parser->fold->imm_float[1]);
-
-            break;
-        case opid2('+','='):
-        case opid2('-','='):
-            if (exprs[0]->vtype != exprs[1]->vtype ||
-                (exprs[0]->vtype != TYPE_VECTOR && exprs[0]->vtype != TYPE_FLOAT) )
-            {
-                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
-                ast_type_to_string(exprs[1], ty2, sizeof(ty2));
-                compile_error(ctx, "invalid types used in expression: cannot add or subtract type %s and %s",
-                              ty1, ty2);
-                return false;
-            }
-            (void)check_write_to(ctx, exprs[0]);
-            if (ast_istype(exprs[0], ast_entfield))
-                assignop = type_storep_instr[exprs[0]->vtype];
-            else
-                assignop = type_store_instr[exprs[0]->vtype];
-            switch (exprs[0]->vtype) {
-                case TYPE_FLOAT:
-                    out = (ast_expression*)ast_binstore_new(ctx, assignop,
-                                                            (op->id == opid2('+','=') ? INSTR_ADD_F : INSTR_SUB_F),
-                                                            exprs[0], exprs[1]);
-                    break;
-                case TYPE_VECTOR:
-                    out = (ast_expression*)ast_binstore_new(ctx, assignop,
-                                                            (op->id == opid2('+','=') ? INSTR_ADD_V : INSTR_SUB_V),
-                                                            exprs[0], exprs[1]);
-                    break;
-                default:
-                    compile_error(ctx, "invalid types used in expression: cannot add or subtract type %s and %s",
-                                  type_name[exprs[0]->vtype],
-                                  type_name[exprs[1]->vtype]);
-                    return false;
-            };
-            break;
-        case opid2('*','='):
-        case opid2('/','='):
-            if (exprs[1]->vtype != TYPE_FLOAT ||
-                !(exprs[0]->vtype == TYPE_FLOAT ||
-                  exprs[0]->vtype == TYPE_VECTOR))
-            {
-                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
-                ast_type_to_string(exprs[1], ty2, sizeof(ty2));
-                compile_error(ctx, "invalid types used in expression: %s and %s",
-                              ty1, ty2);
-                return false;
-            }
-            (void)check_write_to(ctx, exprs[0]);
-            if (ast_istype(exprs[0], ast_entfield))
-                assignop = type_storep_instr[exprs[0]->vtype];
-            else
-                assignop = type_store_instr[exprs[0]->vtype];
-            switch (exprs[0]->vtype) {
-                case TYPE_FLOAT:
-                    out = (ast_expression*)ast_binstore_new(ctx, assignop,
-                                                            (op->id == opid2('*','=') ? INSTR_MUL_F : INSTR_DIV_F),
-                                                            exprs[0], exprs[1]);
-                    break;
-                case TYPE_VECTOR:
-                    if (op->id == opid2('*','=')) {
-                        out = (ast_expression*)ast_binstore_new(ctx, assignop, INSTR_MUL_VF,
-                                                                exprs[0], exprs[1]);
-                    } else {
-                        out = fold_binary(ctx, INSTR_DIV_F,
-                                         (ast_expression*)parser->fold->imm_float[1],
-                                         exprs[1]);
-                        if (!out) {
-                            compile_error(ctx, "internal error: failed to generate division");
-                            return false;
-                        }
-                        out = (ast_expression*)ast_binstore_new(ctx, assignop, INSTR_MUL_VF,
-                                                                exprs[0], out);
-                    }
-                    break;
-                default:
-                    compile_error(ctx, "invalid types used in expression: cannot add or subtract type %s and %s",
-                                  type_name[exprs[0]->vtype],
-                                  type_name[exprs[1]->vtype]);
-                    return false;
-            };
-            break;
-        case opid2('&','='):
-        case opid2('|','='):
-        case opid2('^','='):
-            if (NotSameType(TYPE_FLOAT) && NotSameType(TYPE_VECTOR)) {
-                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
-                ast_type_to_string(exprs[1], ty2, sizeof(ty2));
-                compile_error(ctx, "invalid types used in expression: %s and %s",
-                              ty1, ty2);
-                return false;
-            }
-            (void)check_write_to(ctx, exprs[0]);
-            if (ast_istype(exprs[0], ast_entfield))
-                assignop = type_storep_instr[exprs[0]->vtype];
-            else
-                assignop = type_store_instr[exprs[0]->vtype];
-            if (exprs[0]->vtype == TYPE_FLOAT)
-                out = (ast_expression*)ast_binstore_new(ctx, assignop,
-                                                        (op->id == opid2('^','=') ? VINSTR_BITXOR : op->id == opid2('&','=') ? INSTR_BITAND : INSTR_BITOR),
-                                                        exprs[0], exprs[1]);
-            else
-                out = (ast_expression*)ast_binstore_new(ctx, assignop,
-                                                        (op->id == opid2('^','=') ? VINSTR_BITXOR_V : op->id == opid2('&','=') ? VINSTR_BITAND_V : VINSTR_BITOR_V),
-                                                        exprs[0], exprs[1]);
-            break;
-        case opid3('&','~','='):
-            /* This is like: a &= ~(b);
-             * But QC has no bitwise-not, so we implement it as
-             * a -= a & (b);
-             */
-            if (NotSameType(TYPE_FLOAT) && NotSameType(TYPE_VECTOR)) {
-                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
-                ast_type_to_string(exprs[1], ty2, sizeof(ty2));
-                compile_error(ctx, "invalid types used in expression: %s and %s",
-                              ty1, ty2);
-                return false;
-            }
-            if (ast_istype(exprs[0], ast_entfield))
-                assignop = type_storep_instr[exprs[0]->vtype];
-            else
-                assignop = type_store_instr[exprs[0]->vtype];
-            if (exprs[0]->vtype == TYPE_FLOAT)
-                out = fold_binary(ctx, INSTR_BITAND, exprs[0], exprs[1]);
-            else
-                out = fold_binary(ctx, VINSTR_BITAND_V, exprs[0], exprs[1]);
-            if (!out)
-                return false;
-            (void)check_write_to(ctx, exprs[0]);
-            if (exprs[0]->vtype == TYPE_FLOAT)
-                asbinstore = ast_binstore_new(ctx, assignop, INSTR_SUB_F, exprs[0], out);
-            else
-                asbinstore = ast_binstore_new(ctx, assignop, INSTR_SUB_V, exprs[0], out);
-            asbinstore->keep_dest = true;
-            out = (ast_expression*)asbinstore;
-            break;
-
-        case opid3('l', 'e', 'n'):
-            if (exprs[0]->vtype != TYPE_STRING && exprs[0]->vtype != TYPE_ARRAY) {
-                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
-                compile_error(ast_ctx(exprs[0]), "invalid type for length operator: %s", ty1);
-                return false;
-            }
-            /* strings must be const, arrays are statically sized */
-            if (exprs[0]->vtype == TYPE_STRING &&
-                !(((ast_value*)exprs[0])->hasvalue && ((ast_value*)exprs[0])->cvq == CV_CONST))
-            {
-                compile_error(ast_ctx(exprs[0]), "operand of length operator not a valid constant expression");
-                return false;
-            }
-            out = fold_op(parser->fold, op, exprs);
-            break;
-
-        case opid2('~', 'P'):
-            if (exprs[0]->vtype != TYPE_FLOAT && exprs[0]->vtype != TYPE_VECTOR) {
-                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
-                compile_error(ast_ctx(exprs[0]), "invalid type for bit not: %s", ty1);
-                return false;
-            }
-            if (!(out = fold_op(parser->fold, op, exprs))) {
-                if (exprs[0]->vtype == TYPE_FLOAT) {
-                    out = fold_binary(ctx, INSTR_SUB_F, (ast_expression*)parser->fold->imm_float[2], exprs[0]);
-                } else {
-                    out = fold_binary(ctx, INSTR_SUB_V, (ast_expression*)parser->fold->imm_vector[1], exprs[0]);
-                }
-            }
-            break;
-    }
-#undef NotSameType
-    if (!out) {
-        compile_error(ctx, "failed to apply operator %s", op->op);
-        return false;
-    }
-
-    vec_push(sy->out, syexp(ctx, out));
-    return true;
-}
-
-static bool parser_close_call(parser_t *parser, shunt *sy)
-{
-    /* was a function call */
-    ast_expression *fun;
-    ast_value      *funval = NULL;
-    ast_call       *call;
-
-    size_t          fid;
-    size_t          paramcount, i;
-    bool            fold = true;
-
-    fid = vec_last(sy->ops).off;
-    vec_shrinkby(sy->ops, 1);
-
-    /* out[fid] is the function
-     * everything above is parameters...
-     */
-    if (!vec_size(sy->argc)) {
-        parseerror(parser, "internal error: no argument counter available");
-        return false;
-    }
-
-    paramcount = vec_last(sy->argc);
-    vec_pop(sy->argc);
-
-    if (vec_size(sy->out) < fid) {
-        parseerror(parser, "internal error: broken function call%lu < %lu+%lu\n",
-                   (unsigned long)vec_size(sy->out),
-                   (unsigned long)fid,
-                   (unsigned long)paramcount);
-        return false;
-    }
-
-    /*
-     * TODO handle this at the intrinsic level with an ast_intrinsic
-     * node and codegen.
-     */
-    if ((fun = sy->out[fid].out) == intrin_debug_typestring(parser->intrin)) {
-        char ty[1024];
-        if (fid+2 != vec_size(sy->out) ||
-            vec_last(sy->out).block)
-        {
-            parseerror(parser, "intrinsic __builtin_debug_typestring requires exactly 1 parameter");
-            return false;
-        }
-        ast_type_to_string(vec_last(sy->out).out, ty, sizeof(ty));
-        ast_unref(vec_last(sy->out).out);
-        sy->out[fid] = syexp(ast_ctx(vec_last(sy->out).out),
-                             (ast_expression*)fold_constgen_string(parser->fold, ty, false));
-        vec_shrinkby(sy->out, 1);
-        return true;
-    }
-
-    /*
-     * Now we need to determine if the function that is being called is
-     * an intrinsic so we can evaluate if the arguments to it are constant
-     * and than fruitfully fold them.
-     */
-#define fold_can_1(X)  \
-    (ast_istype(((ast_expression*)(X)), ast_value) && (X)->hasvalue && ((X)->cvq == CV_CONST) && \
-                ((ast_expression*)(X))->vtype != TYPE_FUNCTION)
-
-    if (fid + 1 < vec_size(sy->out))
-        ++paramcount;
-
-    for (i = 0; i < paramcount; ++i) {
-        if (!fold_can_1((ast_value*)sy->out[fid + 1 + i].out)) {
-            fold = false;
-            break;
-        }
-    }
-
-    /*
-     * All is well which ends well, if we make it into here we can ignore the
-     * intrinsic call and just evaluate it i.e constant fold it.
-     */
-    if (fold && ast_istype(fun, ast_value) && ((ast_value*)fun)->intrinsic) {
-        ast_expression **exprs  = NULL;
-        ast_expression *foldval = NULL;
-
-        for (i = 0; i < paramcount; i++)
-            vec_push(exprs, sy->out[fid+1 + i].out);
-
-        if (!(foldval = intrin_fold(parser->intrin, (ast_value*)fun, exprs))) {
-            vec_free(exprs);
-            goto fold_leave;
-        }
-
-        /*
-         * Blub: what sorts of unreffing and resizing of
-         * sy->out should I be doing here?
-         */
-        sy->out[fid] = syexp(foldval->node.context, foldval);
-        vec_shrinkby(sy->out, paramcount);
-        vec_free(exprs);
-
-        return true;
-    }
-
-    fold_leave:
-    call = ast_call_new(sy->ops[vec_size(sy->ops)].ctx, fun);
-
-    if (!call)
-        return false;
-
-    if (fid+1 + paramcount != vec_size(sy->out)) {
-        parseerror(parser, "internal error: parameter count mismatch: (%lu+1+%lu), %lu",
-                   (unsigned long)fid, (unsigned long)paramcount, (unsigned long)vec_size(sy->out));
-        return false;
-    }
-
-    for (i = 0; i < paramcount; ++i)
-        vec_push(call->params, sy->out[fid+1 + i].out);
-    vec_shrinkby(sy->out, paramcount);
-    (void)!ast_call_check_types(call, parser->function->vtype->expression.varparam);
-    if (parser->max_param_count < paramcount)
-        parser->max_param_count = paramcount;
-
-    if (ast_istype(fun, ast_value)) {
-        funval = (ast_value*)fun;
-        if ((fun->flags & AST_FLAG_VARIADIC) &&
-            !(/*funval->cvq == CV_CONST && */ funval->hasvalue && funval->constval.vfunc->builtin))
-        {
-            call->va_count = (ast_expression*)fold_constgen_float(parser->fold, (qcfloat_t)paramcount, false);
-        }
-    }
-
-    /* overwrite fid, the function, with a call */
-    sy->out[fid] = syexp(call->expression.node.context, (ast_expression*)call);
-
-    if (fun->vtype != TYPE_FUNCTION) {
-        parseerror(parser, "not a function (%s)", type_name[fun->vtype]);
-        return false;
-    }
-
-    if (!fun->next) {
-        parseerror(parser, "could not determine function return type");
-        return false;
-    } else {
-        ast_value *fval = (ast_istype(fun, ast_value) ? ((ast_value*)fun) : NULL);
-
-        if (fun->flags & AST_FLAG_DEPRECATED) {
-            if (!fval) {
-                return !parsewarning(parser, WARN_DEPRECATED,
-                        "call to function (which is marked deprecated)\n",
-                        "-> it has been declared here: %s:%i",
-                        ast_ctx(fun).file, ast_ctx(fun).line);
-            }
-            if (!fval->desc) {
-                return !parsewarning(parser, WARN_DEPRECATED,
-                        "call to `%s` (which is marked deprecated)\n"
-                        "-> `%s` declared here: %s:%i",
-                        fval->name, fval->name, ast_ctx(fun).file, ast_ctx(fun).line);
-            }
-            return !parsewarning(parser, WARN_DEPRECATED,
-                    "call to `%s` (deprecated: %s)\n"
-                    "-> `%s` declared here: %s:%i",
-                    fval->name, fval->desc, fval->name, ast_ctx(fun).file,
-                    ast_ctx(fun).line);
-        }
-
-        if (vec_size(fun->params) != paramcount &&
-            !((fun->flags & AST_FLAG_VARIADIC) &&
-              vec_size(fun->params) < paramcount))
-        {
-            const char *fewmany = (vec_size(fun->params) > paramcount) ? "few" : "many";
-            if (fval)
-                return !parsewarning(parser, WARN_INVALID_PARAMETER_COUNT,
-                                     "too %s parameters for call to %s: expected %i, got %i\n"
-                                     " -> `%s` has been declared here: %s:%i",
-                                     fewmany, fval->name, (int)vec_size(fun->params), (int)paramcount,
-                                     fval->name, ast_ctx(fun).file, (int)ast_ctx(fun).line);
-            else
-                return !parsewarning(parser, WARN_INVALID_PARAMETER_COUNT,
-                                     "too %s parameters for function call: expected %i, got %i\n"
-                                     " -> it has been declared here: %s:%i",
-                                     fewmany, (int)vec_size(fun->params), (int)paramcount,
-                                     ast_ctx(fun).file, (int)ast_ctx(fun).line);
-        }
-    }
-
-    return true;
-}
-
-static bool parser_close_paren(parser_t *parser, shunt *sy)
-{
-    if (!vec_size(sy->ops)) {
-        parseerror(parser, "unmatched closing paren");
-        return false;
-    }
-
-    while (vec_size(sy->ops)) {
-        if (vec_last(sy->ops).isparen) {
-            if (vec_last(sy->paren) == PAREN_FUNC) {
-                vec_pop(sy->paren);
-                if (!parser_close_call(parser, sy))
-                    return false;
-                break;
-            }
-            if (vec_last(sy->paren) == PAREN_EXPR) {
-                vec_pop(sy->paren);
-                if (!vec_size(sy->out)) {
-                    compile_error(vec_last(sy->ops).ctx, "empty paren expression");
-                    vec_shrinkby(sy->ops, 1);
-                    return false;
-                }
-                vec_shrinkby(sy->ops, 1);
-                break;
-            }
-            if (vec_last(sy->paren) == PAREN_INDEX) {
-                vec_pop(sy->paren);
-                /* pop off the parenthesis */
-                vec_shrinkby(sy->ops, 1);
-                /* then apply the index operator */
-                if (!parser_sy_apply_operator(parser, sy))
-                    return false;
-                break;
-            }
-            if (vec_last(sy->paren) == PAREN_TERNARY1) {
-                vec_last(sy->paren) = PAREN_TERNARY2;
-                /* pop off the parenthesis */
-                vec_shrinkby(sy->ops, 1);
-                break;
-            }
-            compile_error(vec_last(sy->ops).ctx, "invalid parenthesis");
-            return false;
-        }
-        if (!parser_sy_apply_operator(parser, sy))
-            return false;
-    }
-    return true;
-}
-
-static void parser_reclassify_token(parser_t *parser)
-{
-    size_t i;
-    if (parser->tok >= TOKEN_START)
-        return;
-    for (i = 0; i < operator_count; ++i) {
-        if (!strcmp(parser_tokval(parser), operators[i].op)) {
-            parser->tok = TOKEN_OPERATOR;
-            return;
-        }
-    }
-}
-
-static ast_expression* parse_vararg_do(parser_t *parser)
-{
-    ast_expression *idx, *out;
-    ast_value      *typevar;
-    ast_value      *funtype = parser->function->vtype;
-    lex_ctx_t         ctx     = parser_ctx(parser);
-
-    if (!parser->function->varargs) {
-        parseerror(parser, "function has no variable argument list");
-        return NULL;
-    }
-
-    if (!parser_next(parser) || parser->tok != '(') {
-        parseerror(parser, "expected parameter index and type in parenthesis");
-        return NULL;
-    }
-    if (!parser_next(parser)) {
-        parseerror(parser, "error parsing parameter index");
-        return NULL;
-    }
-
-    idx = parse_expression_leave(parser, true, false, false);
-    if (!idx)
-        return NULL;
-
-    if (parser->tok != ',') {
-        if (parser->tok != ')') {
-            ast_unref(idx);
-            parseerror(parser, "expected comma after parameter index");
-            return NULL;
-        }
-        /* vararg piping: ...(start) */
-        out = (ast_expression*)ast_argpipe_new(ctx, idx);
-        return out;
-    }
-
-    if (!parser_next(parser) || (parser->tok != TOKEN_IDENT && parser->tok != TOKEN_TYPENAME)) {
-        ast_unref(idx);
-        parseerror(parser, "expected typename for vararg");
-        return NULL;
-    }
-
-    typevar = parse_typename(parser, NULL, NULL, NULL);
-    if (!typevar) {
-        ast_unref(idx);
-        return NULL;
-    }
-
-    if (parser->tok != ')') {
-        ast_unref(idx);
-        ast_delete(typevar);
-        parseerror(parser, "expected closing paren");
-        return NULL;
-    }
-
-    if (funtype->expression.varparam &&
-        !ast_compare_type((ast_expression*)typevar, (ast_expression*)funtype->expression.varparam))
-    {
-        char ty1[1024];
-        char ty2[1024];
-        ast_type_to_string((ast_expression*)typevar, ty1, sizeof(ty1));
-        ast_type_to_string((ast_expression*)funtype->expression.varparam, ty2, sizeof(ty2));
-        compile_error(ast_ctx(typevar),
-                      "function was declared to take varargs of type `%s`, requested type is: %s",
-                      ty2, ty1);
-    }
-
-    out = (ast_expression*)ast_array_index_new(ctx, (ast_expression*)(parser->function->varargs), idx);
-    ast_type_adopt(out, typevar);
-    ast_delete(typevar);
-    return out;
-}
-
-static ast_expression* parse_vararg(parser_t *parser)
-{
-    bool           old_noops = parser->lex->flags.noops;
-
-    ast_expression *out;
-
-    parser->lex->flags.noops = true;
-    out = parse_vararg_do(parser);
-
-    parser->lex->flags.noops = old_noops;
-    return out;
-}
-
-/* not to be exposed */
-bool ftepp_predef_exists(const char *name);
-static bool parse_sya_operand(parser_t *parser, shunt *sy, bool with_labels)
-{
-    if (OPTS_FLAG(TRANSLATABLE_STRINGS) &&
-        parser->tok == TOKEN_IDENT &&
-        !strcmp(parser_tokval(parser), "_"))
-    {
-        /* a translatable string */
-        ast_value *val;
-
-        parser->lex->flags.noops = true;
-        if (!parser_next(parser) || parser->tok != '(') {
-            parseerror(parser, "use _(\"string\") to create a translatable string constant");
-            return false;
-        }
-        parser->lex->flags.noops = false;
-        if (!parser_next(parser) || parser->tok != TOKEN_STRINGCONST) {
-            parseerror(parser, "expected a constant string in translatable-string extension");
-            return false;
-        }
-        val = (ast_value*)fold_constgen_string(parser->fold, parser_tokval(parser), true);
-        if (!val)
-            return false;
-        vec_push(sy->out, syexp(parser_ctx(parser), (ast_expression*)val));
-
-        if (!parser_next(parser) || parser->tok != ')') {
-            parseerror(parser, "expected closing paren after translatable string");
-            return false;
-        }
-        return true;
-    }
-    else if (parser->tok == TOKEN_DOTS)
-    {
-        ast_expression *va;
-        if (!OPTS_FLAG(VARIADIC_ARGS)) {
-            parseerror(parser, "cannot access varargs (try -fvariadic-args)");
-            return false;
-        }
-        va = parse_vararg(parser);
-        if (!va)
-            return false;
-        vec_push(sy->out, syexp(parser_ctx(parser), va));
-        return true;
-    }
-    else if (parser->tok == TOKEN_FLOATCONST) {
-        ast_expression *val = fold_constgen_float(parser->fold, (parser_token(parser)->constval.f), false);
-        if (!val)
-            return false;
-        vec_push(sy->out, syexp(parser_ctx(parser), val));
-        return true;
-    }
-    else if (parser->tok == TOKEN_INTCONST || parser->tok == TOKEN_CHARCONST) {
-        ast_expression *val = fold_constgen_float(parser->fold, (qcfloat_t)(parser_token(parser)->constval.i), false);
-        if (!val)
-            return false;
-        vec_push(sy->out, syexp(parser_ctx(parser), val));
-        return true;
-    }
-    else if (parser->tok == TOKEN_STRINGCONST) {
-        ast_expression *val = fold_constgen_string(parser->fold, parser_tokval(parser), false);
-        if (!val)
-            return false;
-        vec_push(sy->out, syexp(parser_ctx(parser), val));
-        return true;
-    }
-    else if (parser->tok == TOKEN_VECTORCONST) {
-        ast_expression *val = fold_constgen_vector(parser->fold, parser_token(parser)->constval.v);
-        if (!val)
-            return false;
-        vec_push(sy->out, syexp(parser_ctx(parser), val));
-        return true;
-    }
-    else if (parser->tok == TOKEN_IDENT)
-    {
-        const char     *ctoken = parser_tokval(parser);
-        ast_expression *prev = vec_size(sy->out) ? vec_last(sy->out).out : NULL;
-        ast_expression *var;
-        /* a_vector.{x,y,z} */
-        if (!vec_size(sy->ops) ||
-            !vec_last(sy->ops).etype ||
-            operators[vec_last(sy->ops).etype-1].id != opid1('.'))
-        {
-            /* When adding more intrinsics, fix the above condition */
-            prev = NULL;
-        }
-        if (prev && prev->vtype == TYPE_VECTOR && ctoken[0] >= 'x' && ctoken[0] <= 'z' && !ctoken[1])
-        {
-            var = (ast_expression*)parser->const_vec[ctoken[0]-'x'];
-        } else {
-            var = parser_find_var(parser, parser_tokval(parser));
-            if (!var)
-                var = parser_find_field(parser, parser_tokval(parser));
-        }
-        if (!var && with_labels) {
-            var = (ast_expression*)parser_find_label(parser, parser_tokval(parser));
-            if (!with_labels) {
-                ast_label *lbl = ast_label_new(parser_ctx(parser), parser_tokval(parser), true);
-                var = (ast_expression*)lbl;
-                vec_push(parser->labels, lbl);
-            }
-        }
-        if (!var && !strcmp(parser_tokval(parser), "__FUNC__"))
-            var = (ast_expression*)fold_constgen_string(parser->fold, parser->function->name, false);
-        if (!var) {
-            /*
-             * now we try for the real intrinsic hashtable. If the string
-             * begins with __builtin, we simply skip past it, otherwise we
-             * use the identifier as is.
-             */
-            if (!strncmp(parser_tokval(parser), "__builtin_", 10)) {
-                var = intrin_func(parser->intrin, parser_tokval(parser));
-            }
-
-            /*
-             * Try it again, intrin_func deals with the alias method as well
-             * the first one masks for __builtin though, we emit warning here.
-             */
-            if (!var) {
-                if ((var = intrin_func(parser->intrin, parser_tokval(parser)))) {
-                    (void)!compile_warning(
-                        parser_ctx(parser),
-                        WARN_BUILTINS,
-                        "using implicitly defined builtin `__builtin_%s' for `%s'",
-                        parser_tokval(parser),
-                        parser_tokval(parser)
-                    );
-                }
-            }
-
-
-            if (!var) {
-                char *correct = NULL;
-                size_t i;
-
-                /*
-                 * sometimes people use preprocessing predefs without enabling them
-                 * i've done this thousands of times already myself.  Lets check for
-                 * it in the predef table.  And diagnose it better :)
-                 */
-                if (!OPTS_FLAG(FTEPP_PREDEFS) && ftepp_predef_exists(parser_tokval(parser))) {
-                    parseerror(parser, "unexpected identifier: %s (use -fftepp-predef to enable pre-defined macros)", parser_tokval(parser));
-                    return false;
-                }
-
-                /*
-                 * TODO: determine the best score for the identifier: be it
-                 * a variable, a field.
-                 *
-                 * We should also consider adding correction tables for
-                 * other things as well.
-                 */
-                if (OPTS_OPTION_BOOL(OPTION_CORRECTION) && strlen(parser_tokval(parser)) <= 16) {
-                    correction_t corr;
-                    correct_init(&corr);
-
-                    for (i = 0; i < vec_size(parser->correct_variables); i++) {
-                        correct = correct_str(&corr, parser->correct_variables[i], parser_tokval(parser));
-                        if (strcmp(correct, parser_tokval(parser))) {
-                            break;
-                        } else  {
-                            mem_d(correct);
-                            correct = NULL;
-                        }
-                    }
-                    correct_free(&corr);
-
-                    if (correct) {
-                        parseerror(parser, "unexpected identifier: %s (did you mean %s?)", parser_tokval(parser), correct);
-                        mem_d(correct);
-                        return false;
-                    }
-                }
-                parseerror(parser, "unexpected identifier: %s", parser_tokval(parser));
-                return false;
-            }
-        }
-        else
-        {
-            if (ast_istype(var, ast_value)) {
-                ((ast_value*)var)->uses++;
-            }
-            else if (ast_istype(var, ast_member)) {
-                ast_member *mem = (ast_member*)var;
-                if (ast_istype(mem->owner, ast_value))
-                    ((ast_value*)(mem->owner))->uses++;
-            }
-        }
-        vec_push(sy->out, syexp(parser_ctx(parser), var));
-        return true;
-    }
-    parseerror(parser, "unexpected token `%s`", parser_tokval(parser));
-    return false;
-}
-
-static ast_expression* parse_expression_leave(parser_t *parser, bool stopatcomma, bool truthvalue, bool with_labels)
-{
-    ast_expression *expr = NULL;
-    shunt sy;
-    size_t i;
-    bool wantop = false;
-    /* only warn once about an assignment in a truth value because the current code
-     * would trigger twice on: if(a = b && ...), once for the if-truth-value, once for the && part
-     */
-    bool warn_parenthesis = true;
-
-    /* count the parens because an if starts with one, so the
-     * end of a condition is an unmatched closing paren
-     */
-    int ternaries = 0;
-
-    memset(&sy, 0, sizeof(sy));
-
-    parser->lex->flags.noops = false;
-
-    parser_reclassify_token(parser);
-
-    while (true)
-    {
-        if (parser->tok == TOKEN_TYPENAME) {
-            parseerror(parser, "unexpected typename `%s`", parser_tokval(parser));
-            goto onerr;
-        }
-
-        if (parser->tok == TOKEN_OPERATOR)
-        {
-            /* classify the operator */
-            const oper_info *op;
-            const oper_info *olast = NULL;
-            size_t o;
-            for (o = 0; o < operator_count; ++o) {
-                if (((!(operators[o].flags & OP_PREFIX) == !!wantop)) &&
-                    /* !(operators[o].flags & OP_SUFFIX) && / * remove this */
-                    !strcmp(parser_tokval(parser), operators[o].op))
-                {
-                    break;
-                }
-            }
-            if (o == operator_count) {
-                compile_error(parser_ctx(parser), "unexpected operator: %s", parser_tokval(parser));
-                goto onerr;
-            }
-            /* found an operator */
-            op = &operators[o];
-
-            /* when declaring variables, a comma starts a new variable */
-            if (op->id == opid1(',') && !vec_size(sy.paren) && stopatcomma) {
-                /* fixup the token */
-                parser->tok = ',';
-                break;
-            }
-
-            /* a colon without a pervious question mark cannot be a ternary */
-            if (!ternaries && op->id == opid2(':','?')) {
-                parser->tok = ':';
-                break;
-            }
-
-            if (op->id == opid1(',')) {
-                if (vec_size(sy.paren) && vec_last(sy.paren) == PAREN_TERNARY2) {
-                    (void)!parsewarning(parser, WARN_TERNARY_PRECEDENCE, "suggesting parenthesis around ternary expression");
-                }
-            }
-
-            if (vec_size(sy.ops) && !vec_last(sy.ops).isparen)
-                olast = &operators[vec_last(sy.ops).etype-1];
-
-            /* first only apply higher precedences, assoc_left+equal comes after we warn about precedence rules */
-            while (olast && op->prec < olast->prec)
-            {
-                if (!parser_sy_apply_operator(parser, &sy))
-                    goto onerr;
-                if (vec_size(sy.ops) && !vec_last(sy.ops).isparen)
-                    olast = &operators[vec_last(sy.ops).etype-1];
-                else
-                    olast = NULL;
-            }
-
-#define IsAssignOp(x) (\
-                (x) == opid1('=') || \
-                (x) == opid2('+','=') || \
-                (x) == opid2('-','=') || \
-                (x) == opid2('*','=') || \
-                (x) == opid2('/','=') || \
-                (x) == opid2('%','=') || \
-                (x) == opid2('&','=') || \
-                (x) == opid2('|','=') || \
-                (x) == opid3('&','~','=') \
-                )
-            if (warn_parenthesis) {
-                if ( (olast && IsAssignOp(olast->id) && (op->id == opid2('&','&') || op->id == opid2('|','|'))) ||
-                     (olast && IsAssignOp(op->id) && (olast->id == opid2('&','&') || olast->id == opid2('|','|'))) ||
-                     (truthvalue && !vec_size(sy.paren) && IsAssignOp(op->id))
-                   )
-                {
-                    (void)!parsewarning(parser, WARN_PARENTHESIS, "suggesting parenthesis around assignment used as truth value");
-                    warn_parenthesis = false;
-                }
-
-                if (olast && olast->id != op->id) {
-                    if ((op->id    == opid1('&') || op->id    == opid1('|') || op->id    == opid1('^')) &&
-                        (olast->id == opid1('&') || olast->id == opid1('|') || olast->id == opid1('^')))
-                    {
-                        (void)!parsewarning(parser, WARN_PARENTHESIS, "suggesting parenthesis around bitwise operations");
-                        warn_parenthesis = false;
-                    }
-                    else if ((op->id    == opid2('&','&') || op->id    == opid2('|','|')) &&
-                             (olast->id == opid2('&','&') || olast->id == opid2('|','|')))
-                    {
-                        (void)!parsewarning(parser, WARN_PARENTHESIS, "suggesting parenthesis around logical operations");
-                        warn_parenthesis = false;
-                    }
-                }
-            }
-
-            while (olast && (
-                    (op->prec < olast->prec) ||
-                    (op->assoc == ASSOC_LEFT && op->prec <= olast->prec) ) )
-            {
-                if (!parser_sy_apply_operator(parser, &sy))
-                    goto onerr;
-                if (vec_size(sy.ops) && !vec_last(sy.ops).isparen)
-                    olast = &operators[vec_last(sy.ops).etype-1];
-                else
-                    olast = NULL;
-            }
-
-            if (op->id == opid1('(')) {
-                if (wantop) {
-                    size_t sycount = vec_size(sy.out);
-                    /* we expected an operator, this is the function-call operator */
-                    vec_push(sy.paren, PAREN_FUNC);
-                    vec_push(sy.ops, syparen(parser_ctx(parser), sycount-1));
-                    vec_push(sy.argc, 0);
-                } else {
-                    vec_push(sy.paren, PAREN_EXPR);
-                    vec_push(sy.ops, syparen(parser_ctx(parser), 0));
-                }
-                wantop = false;
-            } else if (op->id == opid1('[')) {
-                if (!wantop) {
-                    parseerror(parser, "unexpected array subscript");
-                    goto onerr;
-                }
-                vec_push(sy.paren, PAREN_INDEX);
-                /* push both the operator and the paren, this makes life easier */
-                vec_push(sy.ops, syop(parser_ctx(parser), op));
-                vec_push(sy.ops, syparen(parser_ctx(parser), 0));
-                wantop = false;
-            } else if (op->id == opid2('?',':')) {
-                vec_push(sy.ops, syop(parser_ctx(parser), op));
-                vec_push(sy.ops, syparen(parser_ctx(parser), 0));
-                wantop = false;
-                ++ternaries;
-                vec_push(sy.paren, PAREN_TERNARY1);
-            } else if (op->id == opid2(':','?')) {
-                if (!vec_size(sy.paren)) {
-                    parseerror(parser, "unexpected colon outside ternary expression (missing parenthesis?)");
-                    goto onerr;
-                }
-                if (vec_last(sy.paren) != PAREN_TERNARY1) {
-                    parseerror(parser, "unexpected colon outside ternary expression (missing parenthesis?)");
-                    goto onerr;
-                }
-                if (!parser_close_paren(parser, &sy))
-                    goto onerr;
-                vec_push(sy.ops, syop(parser_ctx(parser), op));
-                wantop = false;
-                --ternaries;
-            } else {
-                vec_push(sy.ops, syop(parser_ctx(parser), op));
-                wantop = !!(op->flags & OP_SUFFIX);
-            }
-        }
-        else if (parser->tok == ')') {
-            while (vec_size(sy.paren) && vec_last(sy.paren) == PAREN_TERNARY2) {
-                if (!parser_sy_apply_operator(parser, &sy))
-                    goto onerr;
-            }
-            if (!vec_size(sy.paren))
-                break;
-            if (wantop) {
-                if (vec_last(sy.paren) == PAREN_TERNARY1) {
-                    parseerror(parser, "mismatched parentheses (closing paren in ternary expression?)");
-                    goto onerr;
-                }
-                if (!parser_close_paren(parser, &sy))
-                    goto onerr;
-            } else {
-                /* must be a function call without parameters */
-                if (vec_last(sy.paren) != PAREN_FUNC) {
-                    parseerror(parser, "closing paren in invalid position");
-                    goto onerr;
-                }
-                if (!parser_close_paren(parser, &sy))
-                    goto onerr;
-            }
-            wantop = true;
-        }
-        else if (parser->tok == '(') {
-            parseerror(parser, "internal error: '(' should be classified as operator");
-            goto onerr;
-        }
-        else if (parser->tok == '[') {
-            parseerror(parser, "internal error: '[' should be classified as operator");
-            goto onerr;
-        }
-        else if (parser->tok == ']') {
-            while (vec_size(sy.paren) && vec_last(sy.paren) == PAREN_TERNARY2) {
-                if (!parser_sy_apply_operator(parser, &sy))
-                    goto onerr;
-            }
-            if (!vec_size(sy.paren))
-                break;
-            if (vec_last(sy.paren) != PAREN_INDEX) {
-                parseerror(parser, "mismatched parentheses, unexpected ']'");
-                goto onerr;
-            }
-            if (!parser_close_paren(parser, &sy))
-                goto onerr;
-            wantop = true;
-        }
-        else if (!wantop) {
-            if (!parse_sya_operand(parser, &sy, with_labels))
-                goto onerr;
-#if 0
-            if (vec_size(sy.paren) && vec_last(sy.ops).isparen && vec_last(sy.paren) == PAREN_FUNC)
-                vec_last(sy.argc)++;
-#endif
-            wantop = true;
-        }
-        else {
-            /* in this case we might want to allow constant string concatenation */
-            bool concatenated = false;
-            if (parser->tok == TOKEN_STRINGCONST && vec_size(sy.out)) {
-                ast_expression *lexpr = vec_last(sy.out).out;
-                if (ast_istype(lexpr, ast_value)) {
-                    ast_value *last = (ast_value*)lexpr;
-                    if (last->isimm == true && last->cvq == CV_CONST &&
-                        last->hasvalue && last->expression.vtype == TYPE_STRING)
-                    {
-                        char *newstr = NULL;
-                        util_asprintf(&newstr, "%s%s", last->constval.vstring, parser_tokval(parser));
-                        vec_last(sy.out).out = (ast_expression*)fold_constgen_string(parser->fold, newstr, false);
-                        mem_d(newstr);
-                        concatenated = true;
-                    }
-                }
-            }
-            if (!concatenated) {
-                parseerror(parser, "expected operator or end of statement");
-                goto onerr;
-            }
-        }
-
-        if (!parser_next(parser)) {
-            goto onerr;
-        }
-        if (parser->tok == ';' ||
-            ((!vec_size(sy.paren) || (vec_size(sy.paren) == 1 && vec_last(sy.paren) == PAREN_TERNARY2)) &&
-            (parser->tok == ']' || parser->tok == ')' || parser->tok == '}')))
-        {
-            break;
-        }
-    }
-
-    while (vec_size(sy.ops)) {
-        if (!parser_sy_apply_operator(parser, &sy))
-            goto onerr;
-    }
-
-    parser->lex->flags.noops = true;
-    if (vec_size(sy.out) != 1) {
-        parseerror(parser, "expression expected");
-        expr = NULL;
-    } else
-        expr = sy.out[0].out;
-    vec_free(sy.out);
-    vec_free(sy.ops);
-    if (vec_size(sy.paren)) {
-        parseerror(parser, "internal error: vec_size(sy.paren) = %lu", (unsigned long)vec_size(sy.paren));
-        return NULL;
-    }
-    vec_free(sy.paren);
-    vec_free(sy.argc);
-    return expr;
-
-onerr:
-    parser->lex->flags.noops = true;
-    for (i = 0; i < vec_size(sy.out); ++i) {
-        if (sy.out[i].out)
-            ast_unref(sy.out[i].out);
-    }
-    vec_free(sy.out);
-    vec_free(sy.ops);
-    vec_free(sy.paren);
-    vec_free(sy.argc);
-    return NULL;
-}
-
-static ast_expression* parse_expression(parser_t *parser, bool stopatcomma, bool with_labels)
-{
-    ast_expression *e = parse_expression_leave(parser, stopatcomma, false, with_labels);
-    if (!e)
-        return NULL;
-    if (parser->tok != ';') {
-        parseerror(parser, "semicolon expected after expression");
-        ast_unref(e);
-        return NULL;
-    }
-    if (!parser_next(parser)) {
-        ast_unref(e);
-        return NULL;
-    }
-    return e;
-}
-
-static void parser_enterblock(parser_t *parser)
-{
-    vec_push(parser->variables, util_htnew(PARSER_HT_SIZE));
-    vec_push(parser->_blocklocals, vec_size(parser->_locals));
-    vec_push(parser->typedefs, util_htnew(TYPEDEF_HT_SIZE));
-    vec_push(parser->_blocktypedefs, vec_size(parser->_typedefs));
-    vec_push(parser->_block_ctx, parser_ctx(parser));
-
-    /* corrector */
-    vec_push(parser->correct_variables, correct_trie_new());
-    vec_push(parser->correct_variables_score, NULL);
-}
-
-static bool parser_leaveblock(parser_t *parser)
-{
-    bool   rv = true;
-    size_t locals, typedefs;
-
-    if (vec_size(parser->variables) <= PARSER_HT_LOCALS) {
-        parseerror(parser, "internal error: parser_leaveblock with no block");
-        return false;
-    }
-
-    util_htdel(vec_last(parser->variables));
-    correct_del(vec_last(parser->correct_variables), vec_last(parser->correct_variables_score));
-
-    vec_pop(parser->variables);
-    vec_pop(parser->correct_variables);
-    vec_pop(parser->correct_variables_score);
-    if (!vec_size(parser->_blocklocals)) {
-        parseerror(parser, "internal error: parser_leaveblock with no block (2)");
-        return false;
-    }
-
-    locals = vec_last(parser->_blocklocals);
-    vec_pop(parser->_blocklocals);
-    while (vec_size(parser->_locals) != locals) {
-        ast_expression *e = vec_last(parser->_locals);
-        ast_value      *v = (ast_value*)e;
-        vec_pop(parser->_locals);
-        if (ast_istype(e, ast_value) && !v->uses) {
-            if (compile_warning(ast_ctx(v), WARN_UNUSED_VARIABLE, "unused variable: `%s`", v->name))
-                rv = false;
-        }
-    }
-
-    typedefs = vec_last(parser->_blocktypedefs);
-    while (vec_size(parser->_typedefs) != typedefs) {
-        ast_delete(vec_last(parser->_typedefs));
-        vec_pop(parser->_typedefs);
-    }
-    util_htdel(vec_last(parser->typedefs));
-    vec_pop(parser->typedefs);
-
-    vec_pop(parser->_block_ctx);
-
-    return rv;
-}
-
-static void parser_addlocal(parser_t *parser, const char *name, ast_expression *e)
-{
-    vec_push(parser->_locals, e);
-    util_htset(vec_last(parser->variables), name, (void*)e);
-
-    /* corrector */
-    correct_add (
-         vec_last(parser->correct_variables),
-        &vec_last(parser->correct_variables_score),
-        name
-    );
-}
-
-static void parser_addglobal(parser_t *parser, const char *name, ast_expression *e)
-{
-    vec_push(parser->globals, e);
-    util_htset(parser->htglobals, name, e);
-
-    /* corrector */
-    correct_add (
-         parser->correct_variables[0],
-        &parser->correct_variables_score[0],
-        name
-    );
-}
-
-static ast_expression* process_condition(parser_t *parser, ast_expression *cond, bool *_ifnot)
-{
-    bool       ifnot = false;
-    ast_unary *unary;
-    ast_expression *prev;
-
-    if (cond->vtype == TYPE_VOID || cond->vtype >= TYPE_VARIANT) {
-        char ty[1024];
-        ast_type_to_string(cond, ty, sizeof(ty));
-        compile_error(ast_ctx(cond), "invalid type for if() condition: %s", ty);
-    }
-
-    if (OPTS_FLAG(FALSE_EMPTY_STRINGS) && cond->vtype == TYPE_STRING)
-    {
-        prev = cond;
-        cond = (ast_expression*)ast_unary_new(ast_ctx(cond), INSTR_NOT_S, cond);
-        if (!cond) {
-            ast_unref(prev);
-            parseerror(parser, "internal error: failed to process condition");
-            return NULL;
-        }
-        ifnot = !ifnot;
-    }
-    else if (OPTS_FLAG(CORRECT_LOGIC) && cond->vtype == TYPE_VECTOR)
-    {
-        /* vector types need to be cast to true booleans */
-        ast_binary *bin = (ast_binary*)cond;
-        if (!OPTS_FLAG(PERL_LOGIC) || !ast_istype(cond, ast_binary) || !(bin->op == INSTR_AND || bin->op == INSTR_OR))
-        {
-            /* in perl-logic, AND and OR take care of the -fcorrect-logic */
-            prev = cond;
-            cond = (ast_expression*)ast_unary_new(ast_ctx(cond), INSTR_NOT_V, cond);
-            if (!cond) {
-                ast_unref(prev);
-                parseerror(parser, "internal error: failed to process condition");
-                return NULL;
-            }
-            ifnot = !ifnot;
-        }
-    }
-
-    unary = (ast_unary*)cond;
-    /* ast_istype dereferences cond, should test here for safety */
-    while (cond && ast_istype(cond, ast_unary) && unary->op == INSTR_NOT_F)
-    {
-        cond = unary->operand;
-        unary->operand = NULL;
-        ast_delete(unary);
-        ifnot = !ifnot;
-        unary = (ast_unary*)cond;
-    }
-
-    if (!cond)
-        parseerror(parser, "internal error: failed to process condition");
-
-    if (ifnot) *_ifnot = !*_ifnot;
-    return cond;
-}
-
-static bool parse_if(parser_t *parser, ast_block *block, ast_expression **out)
-{
-    ast_ifthen *ifthen;
-    ast_expression *cond, *ontrue = NULL, *onfalse = NULL;
-    bool ifnot = false;
-
-    lex_ctx_t ctx = parser_ctx(parser);
-
-    (void)block; /* not touching */
-
-    /* skip the 'if', parse an optional 'not' and check for an opening paren */
-    if (!parser_next(parser)) {
-        parseerror(parser, "expected condition or 'not'");
-        return false;
-    }
-    if (parser->tok == TOKEN_IDENT && !strcmp(parser_tokval(parser), "not")) {
-        ifnot = true;
-        if (!parser_next(parser)) {
-            parseerror(parser, "expected condition in parenthesis");
-            return false;
-        }
-    }
-    if (parser->tok != '(') {
-        parseerror(parser, "expected 'if' condition in parenthesis");
-        return false;
-    }
-    /* parse into the expression */
-    if (!parser_next(parser)) {
-        parseerror(parser, "expected 'if' condition after opening paren");
-        return false;
-    }
-    /* parse the condition */
-    cond = parse_expression_leave(parser, false, true, false);
-    if (!cond)
-        return false;
-    /* closing paren */
-    if (parser->tok != ')') {
-        parseerror(parser, "expected closing paren after 'if' condition");
-        ast_unref(cond);
-        return false;
-    }
-    /* parse into the 'then' branch */
-    if (!parser_next(parser)) {
-        parseerror(parser, "expected statement for on-true branch of 'if'");
-        ast_unref(cond);
-        return false;
-    }
-    if (!parse_statement_or_block(parser, &ontrue)) {
-        ast_unref(cond);
-        return false;
-    }
-    if (!ontrue)
-        ontrue = (ast_expression*)ast_block_new(parser_ctx(parser));
-    /* check for an else */
-    if (!strcmp(parser_tokval(parser), "else")) {
-        /* parse into the 'else' branch */
-        if (!parser_next(parser)) {
-            parseerror(parser, "expected on-false branch after 'else'");
-            ast_delete(ontrue);
-            ast_unref(cond);
-            return false;
-        }
-        if (!parse_statement_or_block(parser, &onfalse)) {
-            ast_delete(ontrue);
-            ast_unref(cond);
-            return false;
-        }
-    }
-
-    cond = process_condition(parser, cond, &ifnot);
-    if (!cond) {
-        if (ontrue)  ast_delete(ontrue);
-        if (onfalse) ast_delete(onfalse);
-        return false;
-    }
-
-    if (ifnot)
-        ifthen = ast_ifthen_new(ctx, cond, onfalse, ontrue);
-    else
-        ifthen = ast_ifthen_new(ctx, cond, ontrue, onfalse);
-    *out = (ast_expression*)ifthen;
-    return true;
-}
-
-static bool parse_while_go(parser_t *parser, ast_block *block, ast_expression **out);
-static bool parse_while(parser_t *parser, ast_block *block, ast_expression **out)
-{
-    bool rv;
-    char *label = NULL;
-
-    /* skip the 'while' and get the body */
-    if (!parser_next(parser)) {
-        if (OPTS_FLAG(LOOP_LABELS))
-            parseerror(parser, "expected loop label or 'while' condition in parenthesis");
-        else
-            parseerror(parser, "expected 'while' condition in parenthesis");
-        return false;
-    }
-
-    if (parser->tok == ':') {
-        if (!OPTS_FLAG(LOOP_LABELS))
-            parseerror(parser, "labeled loops not activated, try using -floop-labels");
-        if (!parser_next(parser) || parser->tok != TOKEN_IDENT) {
-            parseerror(parser, "expected loop label");
-            return false;
-        }
-        label = util_strdup(parser_tokval(parser));
-        if (!parser_next(parser)) {
-            mem_d(label);
-            parseerror(parser, "expected 'while' condition in parenthesis");
-            return false;
-        }
-    }
-
-    if (parser->tok != '(') {
-        parseerror(parser, "expected 'while' condition in parenthesis");
-        return false;
-    }
-
-    vec_push(parser->breaks, label);
-    vec_push(parser->continues, label);
-
-    rv = parse_while_go(parser, block, out);
-    if (label)
-        mem_d(label);
-    if (vec_last(parser->breaks) != label || vec_last(parser->continues) != label) {
-        parseerror(parser, "internal error: label stack corrupted");
-        rv = false;
-        ast_delete(*out);
-        *out = NULL;
-    }
-    else {
-        vec_pop(parser->breaks);
-        vec_pop(parser->continues);
-    }
-    return rv;
-}
-
-static bool parse_while_go(parser_t *parser, ast_block *block, ast_expression **out)
-{
-    ast_loop *aloop;
-    ast_expression *cond, *ontrue;
-
-    bool ifnot = false;
-
-    lex_ctx_t ctx = parser_ctx(parser);
-
-    (void)block; /* not touching */
-
-    /* parse into the expression */
-    if (!parser_next(parser)) {
-        parseerror(parser, "expected 'while' condition after opening paren");
-        return false;
-    }
-    /* parse the condition */
-    cond = parse_expression_leave(parser, false, true, false);
-    if (!cond)
-        return false;
-    /* closing paren */
-    if (parser->tok != ')') {
-        parseerror(parser, "expected closing paren after 'while' condition");
-        ast_unref(cond);
-        return false;
-    }
-    /* parse into the 'then' branch */
-    if (!parser_next(parser)) {
-        parseerror(parser, "expected while-loop body");
-        ast_unref(cond);
-        return false;
-    }
-    if (!parse_statement_or_block(parser, &ontrue)) {
-        ast_unref(cond);
-        return false;
-    }
-
-    cond = process_condition(parser, cond, &ifnot);
-    if (!cond) {
-        ast_unref(ontrue);
-        return false;
-    }
-    aloop = ast_loop_new(ctx, NULL, cond, ifnot, NULL, false, NULL, ontrue);
-    *out = (ast_expression*)aloop;
-    return true;
-}
-
-static bool parse_dowhile_go(parser_t *parser, ast_block *block, ast_expression **out);
-static bool parse_dowhile(parser_t *parser, ast_block *block, ast_expression **out)
-{
-    bool rv;
-    char *label = NULL;
-
-    /* skip the 'do' and get the body */
-    if (!parser_next(parser)) {
-        if (OPTS_FLAG(LOOP_LABELS))
-            parseerror(parser, "expected loop label or body");
-        else
-            parseerror(parser, "expected loop body");
-        return false;
-    }
-
-    if (parser->tok == ':') {
-        if (!OPTS_FLAG(LOOP_LABELS))
-            parseerror(parser, "labeled loops not activated, try using -floop-labels");
-        if (!parser_next(parser) || parser->tok != TOKEN_IDENT) {
-            parseerror(parser, "expected loop label");
-            return false;
-        }
-        label = util_strdup(parser_tokval(parser));
-        if (!parser_next(parser)) {
-            mem_d(label);
-            parseerror(parser, "expected loop body");
-            return false;
-        }
-    }
-
-    vec_push(parser->breaks, label);
-    vec_push(parser->continues, label);
-
-    rv = parse_dowhile_go(parser, block, out);
-    if (label)
-        mem_d(label);
-    if (vec_last(parser->breaks) != label || vec_last(parser->continues) != label) {
-        parseerror(parser, "internal error: label stack corrupted");
-        rv = false;
-        /*
-         * Test for NULL otherwise ast_delete dereferences null pointer
-         * and boom.
-         */
-        if (*out)
-            ast_delete(*out);
-        *out = NULL;
-    }
-    else {
-        vec_pop(parser->breaks);
-        vec_pop(parser->continues);
-    }
-    return rv;
-}
-
-static bool parse_dowhile_go(parser_t *parser, ast_block *block, ast_expression **out)
-{
-    ast_loop *aloop;
-    ast_expression *cond, *ontrue;
-
-    bool ifnot = false;
-
-    lex_ctx_t ctx = parser_ctx(parser);
-
-    (void)block; /* not touching */
-
-    if (!parse_statement_or_block(parser, &ontrue))
-        return false;
-
-    /* expect the "while" */
-    if (parser->tok != TOKEN_KEYWORD ||
-        strcmp(parser_tokval(parser), "while"))
-    {
-        parseerror(parser, "expected 'while' and condition");
-        ast_delete(ontrue);
-        return false;
-    }
-
-    /* skip the 'while' and check for opening paren */
-    if (!parser_next(parser) || parser->tok != '(') {
-        parseerror(parser, "expected 'while' condition in parenthesis");
-        ast_delete(ontrue);
-        return false;
-    }
-    /* parse into the expression */
-    if (!parser_next(parser)) {
-        parseerror(parser, "expected 'while' condition after opening paren");
-        ast_delete(ontrue);
-        return false;
-    }
-    /* parse the condition */
-    cond = parse_expression_leave(parser, false, true, false);
-    if (!cond)
-        return false;
-    /* closing paren */
-    if (parser->tok != ')') {
-        parseerror(parser, "expected closing paren after 'while' condition");
-        ast_delete(ontrue);
-        ast_unref(cond);
-        return false;
-    }
-    /* parse on */
-    if (!parser_next(parser) || parser->tok != ';') {
-        parseerror(parser, "expected semicolon after condition");
-        ast_delete(ontrue);
-        ast_unref(cond);
-        return false;
-    }
-
-    if (!parser_next(parser)) {
-        parseerror(parser, "parse error");
-        ast_delete(ontrue);
-        ast_unref(cond);
-        return false;
-    }
-
-    cond = process_condition(parser, cond, &ifnot);
-    if (!cond) {
-        ast_delete(ontrue);
-        return false;
-    }
-    aloop = ast_loop_new(ctx, NULL, NULL, false, cond, ifnot, NULL, ontrue);
-    *out = (ast_expression*)aloop;
-    return true;
-}
-
-static bool parse_for_go(parser_t *parser, ast_block *block, ast_expression **out);
-static bool parse_for(parser_t *parser, ast_block *block, ast_expression **out)
-{
-    bool rv;
-    char *label = NULL;
-
-    /* skip the 'for' and check for opening paren */
-    if (!parser_next(parser)) {
-        if (OPTS_FLAG(LOOP_LABELS))
-            parseerror(parser, "expected loop label or 'for' expressions in parenthesis");
-        else
-            parseerror(parser, "expected 'for' expressions in parenthesis");
-        return false;
-    }
-
-    if (parser->tok == ':') {
-        if (!OPTS_FLAG(LOOP_LABELS))
-            parseerror(parser, "labeled loops not activated, try using -floop-labels");
-        if (!parser_next(parser) || parser->tok != TOKEN_IDENT) {
-            parseerror(parser, "expected loop label");
-            return false;
-        }
-        label = util_strdup(parser_tokval(parser));
-        if (!parser_next(parser)) {
-            mem_d(label);
-            parseerror(parser, "expected 'for' expressions in parenthesis");
-            return false;
-        }
-    }
-
-    if (parser->tok != '(') {
-        parseerror(parser, "expected 'for' expressions in parenthesis");
-        return false;
-    }
-
-    vec_push(parser->breaks, label);
-    vec_push(parser->continues, label);
-
-    rv = parse_for_go(parser, block, out);
-    if (label)
-        mem_d(label);
-    if (vec_last(parser->breaks) != label || vec_last(parser->continues) != label) {
-        parseerror(parser, "internal error: label stack corrupted");
-        rv = false;
-        ast_delete(*out);
-        *out = NULL;
-    }
-    else {
-        vec_pop(parser->breaks);
-        vec_pop(parser->continues);
-    }
-    return rv;
-}
-static bool parse_for_go(parser_t *parser, ast_block *block, ast_expression **out)
-{
-    ast_loop       *aloop;
-    ast_expression *initexpr, *cond, *increment, *ontrue;
-    ast_value      *typevar;
-
-    bool ifnot  = false;
-
-    lex_ctx_t ctx = parser_ctx(parser);
-
-    parser_enterblock(parser);
-
-    initexpr  = NULL;
-    cond      = NULL;
-    increment = NULL;
-    ontrue    = NULL;
-
-    /* parse into the expression */
-    if (!parser_next(parser)) {
-        parseerror(parser, "expected 'for' initializer after opening paren");
-        goto onerr;
-    }
-
-    typevar = NULL;
-    if (parser->tok == TOKEN_IDENT)
-        typevar = parser_find_typedef(parser, parser_tokval(parser), 0);
-
-    if (typevar || parser->tok == TOKEN_TYPENAME) {
-#if 0
-        if (OPTS_OPTION_U32(OPTION_STANDARD) != COMPILER_GMQCC) {
-            if (parsewarning(parser, WARN_EXTENSIONS,
-                             "current standard does not allow variable declarations in for-loop initializers"))
-                goto onerr;
-        }
-#endif
-        if (!parse_variable(parser, block, true, CV_VAR, typevar, false, false, 0, NULL))
-            goto onerr;
-    }
-    else if (parser->tok != ';')
-    {
-        initexpr = parse_expression_leave(parser, false, false, false);
-        if (!initexpr)
-            goto onerr;
-
-        /* move on to condition */
-        if (parser->tok != ';') {
-            parseerror(parser, "expected semicolon after for-loop initializer");
-            goto onerr;
-        }
-
-        if (!parser_next(parser)) {
-            parseerror(parser, "expected for-loop condition");
-            goto onerr;
-        }
-    } else {
-        if (!parser_next(parser)) {
-            parseerror(parser, "expected for-loop condition");
-            goto onerr;
-        }
-    }
-
-    /* parse the condition */
-    if (parser->tok != ';') {
-        cond = parse_expression_leave(parser, false, true, false);
-        if (!cond)
-            goto onerr;
-    }
-    /* move on to incrementor */
-    if (parser->tok != ';') {
-        parseerror(parser, "expected semicolon after for-loop initializer");
-        goto onerr;
-    }
-    if (!parser_next(parser)) {
-        parseerror(parser, "expected for-loop condition");
-        goto onerr;
-    }
-
-    /* parse the incrementor */
-    if (parser->tok != ')') {
-        lex_ctx_t condctx = parser_ctx(parser);
-        increment = parse_expression_leave(parser, false, false, false);
-        if (!increment)
-            goto onerr;
-        if (!ast_side_effects(increment)) {
-            if (compile_warning(condctx, WARN_EFFECTLESS_STATEMENT, "statement has no effect"))
-                goto onerr;
-        }
-    }
-
-    /* closing paren */
-    if (parser->tok != ')') {
-        parseerror(parser, "expected closing paren after 'for-loop' incrementor");
-        goto onerr;
-    }
-    /* parse into the 'then' branch */
-    if (!parser_next(parser)) {
-        parseerror(parser, "expected for-loop body");
-        goto onerr;
-    }
-    if (!parse_statement_or_block(parser, &ontrue))
-        goto onerr;
-
-    if (cond) {
-        cond = process_condition(parser, cond, &ifnot);
-        if (!cond)
-            goto onerr;
-    }
-    aloop = ast_loop_new(ctx, initexpr, cond, ifnot, NULL, false, increment, ontrue);
-    *out = (ast_expression*)aloop;
-
-    if (!parser_leaveblock(parser)) {
-        ast_delete(aloop);
-        return false;
-    }
-    return true;
-onerr:
-    if (initexpr)  ast_unref(initexpr);
-    if (cond)      ast_unref(cond);
-    if (increment) ast_unref(increment);
-    (void)!parser_leaveblock(parser);
-    return false;
-}
-
-static bool parse_return(parser_t *parser, ast_block *block, ast_expression **out)
-{
-    ast_expression *exp      = NULL;
-    ast_expression *var      = NULL;
-    ast_return     *ret      = NULL;
-    ast_value      *retval   = parser->function->return_value;
-    ast_value      *expected = parser->function->vtype;
-
-    lex_ctx_t ctx = parser_ctx(parser);
-
-    (void)block; /* not touching */
-
-    if (!parser_next(parser)) {
-        parseerror(parser, "expected return expression");
-        return false;
-    }
-
-    /* return assignments */
-    if (parser->tok == '=') {
-        if (!OPTS_FLAG(RETURN_ASSIGNMENTS)) {
-            parseerror(parser, "return assignments not activated, try using -freturn-assigments");
-            return false;
-        }
-
-        if (type_store_instr[expected->expression.next->vtype] == VINSTR_END) {
-            char ty1[1024];
-            ast_type_to_string(expected->expression.next, ty1, sizeof(ty1));
-            parseerror(parser, "invalid return type: `%s'", ty1);
-            return false;
-        }
-
-        if (!parser_next(parser)) {
-            parseerror(parser, "expected return assignment expression");
-            return false;
-        }
-
-        if (!(exp = parse_expression_leave(parser, false, false, false)))
-            return false;
-
-        /* prepare the return value */
-        if (!retval) {
-            retval = ast_value_new(ctx, "#LOCAL_RETURN", TYPE_VOID);
-            ast_type_adopt(retval, expected->expression.next);
-            parser->function->return_value = retval;
-        }
-
-        if (!ast_compare_type(exp, (ast_expression*)retval)) {
-            char ty1[1024], ty2[1024];
-            ast_type_to_string(exp, ty1, sizeof(ty1));
-            ast_type_to_string(&retval->expression, ty2, sizeof(ty2));
-            parseerror(parser, "invalid type for return value: `%s', expected `%s'", ty1, ty2);
-        }
-
-        /* store to 'return' local variable */
-        var = (ast_expression*)ast_store_new(
-            ctx,
-            type_store_instr[expected->expression.next->vtype],
-            (ast_expression*)retval, exp);
-
-        if (!var) {
-            ast_unref(exp);
-            return false;
-        }
-
-        if (parser->tok != ';')
-            parseerror(parser, "missing semicolon after return assignment");
-        else if (!parser_next(parser))
-            parseerror(parser, "parse error after return assignment");
-
-        *out = var;
-        return true;
-    }
-
-    if (parser->tok != ';') {
-        exp = parse_expression(parser, false, false);
-        if (!exp)
-            return false;
-
-        if (exp->vtype != TYPE_NIL &&
-            exp->vtype != ((ast_expression*)expected)->next->vtype)
-        {
-            parseerror(parser, "return with invalid expression");
-        }
-
-        ret = ast_return_new(ctx, exp);
-        if (!ret) {
-            ast_unref(exp);
-            return false;
-        }
-    } else {
-        if (!parser_next(parser))
-            parseerror(parser, "parse error");
-
-        if (!retval && expected->expression.next->vtype != TYPE_VOID)
-        {
-            (void)!parsewarning(parser, WARN_MISSING_RETURN_VALUES, "return without value");
-        }
-        ret = ast_return_new(ctx, (ast_expression*)retval);
-    }
-    *out = (ast_expression*)ret;
-    return true;
-}
-
-static bool parse_break_continue(parser_t *parser, ast_block *block, ast_expression **out, bool is_continue)
-{
-    size_t       i;
-    unsigned int levels = 0;
-    lex_ctx_t      ctx = parser_ctx(parser);
-    const char **loops = (is_continue ? parser->continues : parser->breaks);
-
-    (void)block; /* not touching */
-    if (!parser_next(parser)) {
-        parseerror(parser, "expected semicolon or loop label");
-        return false;
-    }
-
-    if (!vec_size(loops)) {
-        if (is_continue)
-            parseerror(parser, "`continue` can only be used inside loops");
-        else
-            parseerror(parser, "`break` can only be used inside loops or switches");
-    }
-
-    if (parser->tok == TOKEN_IDENT) {
-        if (!OPTS_FLAG(LOOP_LABELS))
-            parseerror(parser, "labeled loops not activated, try using -floop-labels");
-        i = vec_size(loops);
-        while (i--) {
-            if (loops[i] && !strcmp(loops[i], parser_tokval(parser)))
-                break;
-            if (!i) {
-                parseerror(parser, "no such loop to %s: `%s`",
-                           (is_continue ? "continue" : "break out of"),
-                           parser_tokval(parser));
-                return false;
-            }
-            ++levels;
-        }
-        if (!parser_next(parser)) {
-            parseerror(parser, "expected semicolon");
-            return false;
-        }
-    }
-
-    if (parser->tok != ';') {
-        parseerror(parser, "expected semicolon");
-        return false;
-    }
-
-    if (!parser_next(parser))
-        parseerror(parser, "parse error");
-
-    *out = (ast_expression*)ast_breakcont_new(ctx, is_continue, levels);
-    return true;
-}
-
-/* returns true when it was a variable qualifier, false otherwise!
- * on error, cvq is set to CV_WRONG
- */
-typedef struct {
-    const char *name;
-    size_t      flag;
-} attribute_t;
-
-static bool parse_qualifiers(parser_t *parser, bool with_local, int *cvq, bool *noref, bool *is_static, uint32_t *_flags, char **message)
-{
-    bool had_const    = false;
-    bool had_var      = false;
-    bool had_noref    = false;
-    bool had_attrib   = false;
-    bool had_static   = false;
-    uint32_t flags    = 0;
-
-    static attribute_t attributes[] = {
-        { "noreturn",   AST_FLAG_NORETURN   },
-        { "inline",     AST_FLAG_INLINE     },
-        { "eraseable",  AST_FLAG_ERASEABLE  },
-        { "accumulate", AST_FLAG_ACCUMULATE },
-        { "last",       AST_FLAG_FINAL_DECL }
-    };
-
-   *cvq = CV_NONE;
-
-    for (;;) {
-        size_t i;
-        if (parser->tok == TOKEN_ATTRIBUTE_OPEN) {
-            had_attrib = true;
-            /* parse an attribute */
-            if (!parser_next(parser)) {
-                parseerror(parser, "expected attribute after `[[`");
-                *cvq = CV_WRONG;
-                return false;
-            }
-
-            for (i = 0; i < GMQCC_ARRAY_COUNT(attributes); i++) {
-                if (!strcmp(parser_tokval(parser), attributes[i].name)) {
-                    flags |= attributes[i].flag;
-                    if (!parser_next(parser) || parser->tok != TOKEN_ATTRIBUTE_CLOSE) {
-                        parseerror(parser, "`%s` attribute has no parameters, expected `]]`",
-                            attributes[i].name);
-                        *cvq = CV_WRONG;
-                        return false;
-                    }
-                    break;
-                }
-            }
-
-            if (i != GMQCC_ARRAY_COUNT(attributes))
-                goto leave;
-
-
-            if (!strcmp(parser_tokval(parser), "noref")) {
-                had_noref = true;
-                if (!parser_next(parser) || parser->tok != TOKEN_ATTRIBUTE_CLOSE) {
-                    parseerror(parser, "`noref` attribute has no parameters, expected `]]`");
-                    *cvq = CV_WRONG;
-                    return false;
-                }
-            }
-            else if (!strcmp(parser_tokval(parser), "alias") && !(flags & AST_FLAG_ALIAS)) {
-                flags   |= AST_FLAG_ALIAS;
-                *message = NULL;
-
-                if (!parser_next(parser)) {
-                    parseerror(parser, "parse error in attribute");
-                    goto argerr;
-                }
-
-                if (parser->tok == '(') {
-                    if (!parser_next(parser) || parser->tok != TOKEN_STRINGCONST) {
-                        parseerror(parser, "`alias` attribute missing parameter");
-                        goto argerr;
-                    }
-
-                    *message = util_strdup(parser_tokval(parser));
-
-                    if (!parser_next(parser)) {
-                        parseerror(parser, "parse error in attribute");
-                        goto argerr;
-                    }
-
-                    if (parser->tok != ')') {
-                        parseerror(parser, "`alias` attribute expected `)` after parameter");
-                        goto argerr;
-                    }
-
-                    if (!parser_next(parser)) {
-                        parseerror(parser, "parse error in attribute");
-                        goto argerr;
-                    }
-                }
-
-                if (parser->tok != TOKEN_ATTRIBUTE_CLOSE) {
-                    parseerror(parser, "`alias` attribute expected `]]`");
-                    goto argerr;
-                }
-            }
-            else if (!strcmp(parser_tokval(parser), "deprecated") && !(flags & AST_FLAG_DEPRECATED)) {
-                flags   |= AST_FLAG_DEPRECATED;
-                *message = NULL;
-
-                if (!parser_next(parser)) {
-                    parseerror(parser, "parse error in attribute");
-                    goto argerr;
-                }
-
-                if (parser->tok == '(') {
-                    if (!parser_next(parser) || parser->tok != TOKEN_STRINGCONST) {
-                        parseerror(parser, "`deprecated` attribute missing parameter");
-                        goto argerr;
-                    }
-
-                    *message = util_strdup(parser_tokval(parser));
-
-                    if (!parser_next(parser)) {
-                        parseerror(parser, "parse error in attribute");
-                        goto argerr;
-                    }
-
-                    if(parser->tok != ')') {
-                        parseerror(parser, "`deprecated` attribute expected `)` after parameter");
-                        goto argerr;
-                    }
-
-                    if (!parser_next(parser)) {
-                        parseerror(parser, "parse error in attribute");
-                        goto argerr;
-                    }
-                }
-                /* no message */
-                if (parser->tok != TOKEN_ATTRIBUTE_CLOSE) {
-                    parseerror(parser, "`deprecated` attribute expected `]]`");
-
-                    argerr: /* ugly */
-                    if (*message) mem_d(*message);
-                    *message = NULL;
-                    *cvq     = CV_WRONG;
-                    return false;
-                }
-            }
-            else if (!strcmp(parser_tokval(parser), "coverage") && !(flags & AST_FLAG_COVERAGE)) {
-                flags |= AST_FLAG_COVERAGE;
-                if (!parser_next(parser)) {
-                    error_in_coverage:
-                    parseerror(parser, "parse error in coverage attribute");
-                    *cvq = CV_WRONG;
-                    return false;
-                }
-                if (parser->tok == '(') {
-                    if (!parser_next(parser)) {
-                        bad_coverage_arg:
-                        parseerror(parser, "invalid parameter for coverage() attribute\n"
-                                           "valid are: block");
-                        *cvq = CV_WRONG;
-                        return false;
-                    }
-                    if (parser->tok != ')') {
-                        do {
-                            if (parser->tok != TOKEN_IDENT)
-                                goto bad_coverage_arg;
-                            if (!strcmp(parser_tokval(parser), "block"))
-                                flags |= AST_FLAG_BLOCK_COVERAGE;
-                            else if (!strcmp(parser_tokval(parser), "none"))
-                                flags &= ~(AST_FLAG_COVERAGE_MASK);
-                            else
-                                goto bad_coverage_arg;
-                            if (!parser_next(parser))
-                                goto error_in_coverage;
-                            if (parser->tok == ',') {
-                                if (!parser_next(parser))
-                                    goto error_in_coverage;
-                            }
-                        } while (parser->tok != ')');
-                    }
-                    if (parser->tok != ')' || !parser_next(parser))
-                        goto error_in_coverage;
-                } else {
-                    /* without parameter [[coverage]] equals [[coverage(block)]] */
-                    flags |= AST_FLAG_BLOCK_COVERAGE;
-                }
-            }
-            else
-            {
-                /* Skip tokens until we hit a ]] */
-                (void)!parsewarning(parser, WARN_UNKNOWN_ATTRIBUTE, "unknown attribute starting with `%s`", parser_tokval(parser));
-                while (parser->tok != TOKEN_ATTRIBUTE_CLOSE) {
-                    if (!parser_next(parser)) {
-                        parseerror(parser, "error inside attribute");
-                        *cvq = CV_WRONG;
-                        return false;
-                    }
-                }
-            }
-        }
-        else if (with_local && !strcmp(parser_tokval(parser), "static"))
-            had_static = true;
-        else if (!strcmp(parser_tokval(parser), "const"))
-            had_const = true;
-        else if (!strcmp(parser_tokval(parser), "var"))
-            had_var = true;
-        else if (with_local && !strcmp(parser_tokval(parser), "local"))
-            had_var = true;
-        else if (!strcmp(parser_tokval(parser), "noref"))
-            had_noref = true;
-        else if (!had_const && !had_var && !had_noref && !had_attrib && !had_static && !flags) {
-            return false;
-        }
-        else
-            break;
-
-        leave:
-        if (!parser_next(parser))
-            goto onerr;
-    }
-    if (had_const)
-        *cvq = CV_CONST;
-    else if (had_var)
-        *cvq = CV_VAR;
-    else
-        *cvq = CV_NONE;
-    *noref     = had_noref;
-    *is_static = had_static;
-    *_flags    = flags;
-    return true;
-onerr:
-    parseerror(parser, "parse error after variable qualifier");
-    *cvq = CV_WRONG;
-    return true;
-}
-
-static bool parse_switch_go(parser_t *parser, ast_block *block, ast_expression **out);
-static bool parse_switch(parser_t *parser, ast_block *block, ast_expression **out)
-{
-    bool rv;
-    char *label = NULL;
-
-    /* skip the 'while' and get the body */
-    if (!parser_next(parser)) {
-        if (OPTS_FLAG(LOOP_LABELS))
-            parseerror(parser, "expected loop label or 'switch' operand in parenthesis");
-        else
-            parseerror(parser, "expected 'switch' operand in parenthesis");
-        return false;
-    }
-
-    if (parser->tok == ':') {
-        if (!OPTS_FLAG(LOOP_LABELS))
-            parseerror(parser, "labeled loops not activated, try using -floop-labels");
-        if (!parser_next(parser) || parser->tok != TOKEN_IDENT) {
-            parseerror(parser, "expected loop label");
-            return false;
-        }
-        label = util_strdup(parser_tokval(parser));
-        if (!parser_next(parser)) {
-            mem_d(label);
-            parseerror(parser, "expected 'switch' operand in parenthesis");
-            return false;
-        }
-    }
-
-    if (parser->tok != '(') {
-        parseerror(parser, "expected 'switch' operand in parenthesis");
-        return false;
-    }
-
-    vec_push(parser->breaks, label);
-
-    rv = parse_switch_go(parser, block, out);
-    if (label)
-        mem_d(label);
-    if (vec_last(parser->breaks) != label) {
-        parseerror(parser, "internal error: label stack corrupted");
-        rv = false;
-        ast_delete(*out);
-        *out = NULL;
-    }
-    else {
-        vec_pop(parser->breaks);
-    }
-    return rv;
-}
-
-static bool parse_switch_go(parser_t *parser, ast_block *block, ast_expression **out)
-{
-    ast_expression *operand;
-    ast_value      *opval;
-    ast_value      *typevar;
-    ast_switch     *switchnode;
-    ast_switch_case swcase;
-
-    int  cvq;
-    bool noref, is_static;
-    uint32_t qflags = 0;
-
-    lex_ctx_t ctx = parser_ctx(parser);
-
-    (void)block; /* not touching */
-    (void)opval;
-
-    /* parse into the expression */
-    if (!parser_next(parser)) {
-        parseerror(parser, "expected switch operand");
-        return false;
-    }
-    /* parse the operand */
-    operand = parse_expression_leave(parser, false, false, false);
-    if (!operand)
-        return false;
-
-    switchnode = ast_switch_new(ctx, operand);
-
-    /* closing paren */
-    if (parser->tok != ')') {
-        ast_delete(switchnode);
-        parseerror(parser, "expected closing paren after 'switch' operand");
-        return false;
-    }
-
-    /* parse over the opening paren */
-    if (!parser_next(parser) || parser->tok != '{') {
-        ast_delete(switchnode);
-        parseerror(parser, "expected list of cases");
-        return false;
-    }
-
-    if (!parser_next(parser)) {
-        ast_delete(switchnode);
-        parseerror(parser, "expected 'case' or 'default'");
-        return false;
-    }
-
-    /* new block; allow some variables to be declared here */
-    parser_enterblock(parser);
-    while (true) {
-        typevar = NULL;
-        if (parser->tok == TOKEN_IDENT)
-            typevar = parser_find_typedef(parser, parser_tokval(parser), 0);
-        if (typevar || parser->tok == TOKEN_TYPENAME) {
-            if (!parse_variable(parser, block, true, CV_NONE, typevar, false, false, 0, NULL)) {
-                ast_delete(switchnode);
-                return false;
-            }
-            continue;
-        }
-        if (parse_qualifiers(parser, true, &cvq, &noref, &is_static, &qflags, NULL))
-        {
-            if (cvq == CV_WRONG) {
-                ast_delete(switchnode);
-                return false;
-            }
-            if (!parse_variable(parser, block, true, cvq, NULL, noref, is_static, qflags, NULL)) {
-                ast_delete(switchnode);
-                return false;
-            }
-            continue;
-        }
-        break;
-    }
-
-    /* case list! */
-    while (parser->tok != '}') {
-        ast_block *caseblock;
-
-        if (!strcmp(parser_tokval(parser), "case")) {
-            if (!parser_next(parser)) {
-                ast_delete(switchnode);
-                parseerror(parser, "expected expression for case");
-                return false;
-            }
-            swcase.value = parse_expression_leave(parser, false, false, false);
-            if (!swcase.value) {
-                ast_delete(switchnode);
-                parseerror(parser, "expected expression for case");
-                return false;
-            }
-            if (!OPTS_FLAG(RELAXED_SWITCH)) {
-                if (!ast_istype(swcase.value, ast_value)) { /* || ((ast_value*)swcase.value)->cvq != CV_CONST) { */
-                    parseerror(parser, "case on non-constant values need to be explicitly enabled via -frelaxed-switch");
-                    ast_unref(operand);
-                    return false;
-                }
-            }
-        }
-        else if (!strcmp(parser_tokval(parser), "default")) {
-            swcase.value = NULL;
-            if (!parser_next(parser)) {
-                ast_delete(switchnode);
-                parseerror(parser, "expected colon");
-                return false;
-            }
-        }
-        else {
-            ast_delete(switchnode);
-            parseerror(parser, "expected 'case' or 'default'");
-            return false;
-        }
-
-        /* Now the colon and body */
-        if (parser->tok != ':') {
-            if (swcase.value) ast_unref(swcase.value);
-            ast_delete(switchnode);
-            parseerror(parser, "expected colon");
-            return false;
-        }
-
-        if (!parser_next(parser)) {
-            if (swcase.value) ast_unref(swcase.value);
-            ast_delete(switchnode);
-            parseerror(parser, "expected statements or case");
-            return false;
-        }
-        caseblock = ast_block_new(parser_ctx(parser));
-        if (!caseblock) {
-            if (swcase.value) ast_unref(swcase.value);
-            ast_delete(switchnode);
-            return false;
-        }
-        swcase.code = (ast_expression*)caseblock;
-        vec_push(switchnode->cases, swcase);
-        while (true) {
-            ast_expression *expr;
-            if (parser->tok == '}')
-                break;
-            if (parser->tok == TOKEN_KEYWORD) {
-                if (!strcmp(parser_tokval(parser), "case") ||
-                    !strcmp(parser_tokval(parser), "default"))
-                {
-                    break;
-                }
-            }
-            if (!parse_statement(parser, caseblock, &expr, true)) {
-                ast_delete(switchnode);
-                return false;
-            }
-            if (!expr)
-                continue;
-            if (!ast_block_add_expr(caseblock, expr)) {
-                ast_delete(switchnode);
-                return false;
-            }
-        }
-    }
-
-    parser_leaveblock(parser);
-
-    /* closing paren */
-    if (parser->tok != '}') {
-        ast_delete(switchnode);
-        parseerror(parser, "expected closing paren of case list");
-        return false;
-    }
-    if (!parser_next(parser)) {
-        ast_delete(switchnode);
-        parseerror(parser, "parse error after switch");
-        return false;
-    }
-    *out = (ast_expression*)switchnode;
-    return true;
-}
-
-/* parse computed goto sides */
-static ast_expression *parse_goto_computed(parser_t *parser, ast_expression **side) {
-    ast_expression *on_true;
-    ast_expression *on_false;
-    ast_expression *cond;
-
-    if (!*side)
-        return NULL;
-
-    if (ast_istype(*side, ast_ternary)) {
-        ast_ternary *tern = (ast_ternary*)*side;
-        on_true  = parse_goto_computed(parser, &tern->on_true);
-        on_false = parse_goto_computed(parser, &tern->on_false);
-
-        if (!on_true || !on_false) {
-            parseerror(parser, "expected label or expression in ternary");
-            if (on_true) ast_unref(on_true);
-            if (on_false) ast_unref(on_false);
-            return NULL;
-        }
-
-        cond = tern->cond;
-        tern->cond = NULL;
-        ast_delete(tern);
-        *side = NULL;
-        return (ast_expression*)ast_ifthen_new(parser_ctx(parser), cond, on_true, on_false);
-    } else if (ast_istype(*side, ast_label)) {
-        ast_goto *gt = ast_goto_new(parser_ctx(parser), ((ast_label*)*side)->name);
-        ast_goto_set_label(gt, ((ast_label*)*side));
-        *side = NULL;
-        return (ast_expression*)gt;
-    }
-    return NULL;
-}
-
-static bool parse_goto(parser_t *parser, ast_expression **out)
-{
-    ast_goto       *gt = NULL;
-    ast_expression *lbl;
-
-    if (!parser_next(parser))
-        return false;
-
-    if (parser->tok != TOKEN_IDENT) {
-        ast_expression *expression;
-
-        /* could be an expression i.e computed goto :-) */
-        if (parser->tok != '(') {
-            parseerror(parser, "expected label name after `goto`");
-            return false;
-        }
-
-        /* failed to parse expression for goto */
-        if (!(expression = parse_expression(parser, false, true)) ||
-            !(*out = parse_goto_computed(parser, &expression))) {
-            parseerror(parser, "invalid goto expression");
-            if(expression)
-                ast_unref(expression);
-            return false;
-        }
-
-        return true;
-    }
-
-    /* not computed goto */
-    gt = ast_goto_new(parser_ctx(parser), parser_tokval(parser));
-    lbl = parser_find_label(parser, gt->name);
-    if (lbl) {
-        if (!ast_istype(lbl, ast_label)) {
-            parseerror(parser, "internal error: label is not an ast_label");
-            ast_delete(gt);
-            return false;
-        }
-        ast_goto_set_label(gt, (ast_label*)lbl);
-    }
-    else
-        vec_push(parser->gotos, gt);
-
-    if (!parser_next(parser) || parser->tok != ';') {
-        parseerror(parser, "semicolon expected after goto label");
-        return false;
-    }
-    if (!parser_next(parser)) {
-        parseerror(parser, "parse error after goto");
-        return false;
-    }
-
-    *out = (ast_expression*)gt;
-    return true;
-}
-
-static bool parse_skipwhite(parser_t *parser)
-{
-    do {
-        if (!parser_next(parser))
-            return false;
-    } while (parser->tok == TOKEN_WHITE && parser->tok < TOKEN_ERROR);
-    return parser->tok < TOKEN_ERROR;
-}
-
-static bool parse_eol(parser_t *parser)
-{
-    if (!parse_skipwhite(parser))
-        return false;
-    return parser->tok == TOKEN_EOL;
-}
-
-static bool parse_pragma_do(parser_t *parser)
-{
-    if (!parser_next(parser) ||
-        parser->tok != TOKEN_IDENT ||
-        strcmp(parser_tokval(parser), "pragma"))
-    {
-        parseerror(parser, "expected `pragma` keyword after `#`, got `%s`", parser_tokval(parser));
-        return false;
-    }
-    if (!parse_skipwhite(parser) || parser->tok != TOKEN_IDENT) {
-        parseerror(parser, "expected pragma, got `%s`", parser_tokval(parser));
-        return false;
-    }
-
-    if (!strcmp(parser_tokval(parser), "noref")) {
-        if (!parse_skipwhite(parser) || parser->tok != TOKEN_INTCONST) {
-            parseerror(parser, "`noref` pragma requires an argument: 0 or 1");
-            return false;
-        }
-        parser->noref = !!parser_token(parser)->constval.i;
-        if (!parse_eol(parser)) {
-            parseerror(parser, "parse error after `noref` pragma");
-            return false;
-        }
-    }
-    else
-    {
-        (void)!parsewarning(parser, WARN_UNKNOWN_PRAGMAS, "ignoring #pragma %s", parser_tokval(parser));
-
-        /* skip to eol */
-        while (!parse_eol(parser)) {
-            parser_next(parser);
-        }
-
-        return true;
-    }
-
-    return true;
-}
-
-static bool parse_pragma(parser_t *parser)
-{
-    bool rv;
-    parser->lex->flags.preprocessing = true;
-    parser->lex->flags.mergelines = true;
-    rv = parse_pragma_do(parser);
-    if (parser->tok != TOKEN_EOL) {
-        parseerror(parser, "junk after pragma");
-        rv = false;
-    }
-    parser->lex->flags.preprocessing = false;
-    parser->lex->flags.mergelines = false;
-    if (!parser_next(parser)) {
-        parseerror(parser, "parse error after pragma");
-        rv = false;
-    }
-    return rv;
-}
-
-static bool parse_statement(parser_t *parser, ast_block *block, ast_expression **out, bool allow_cases)
-{
-    bool       noref, is_static;
-    int        cvq     = CV_NONE;
-    uint32_t   qflags  = 0;
-    ast_value *typevar = NULL;
-    char      *vstring = NULL;
-
-    *out = NULL;
-
-    if (parser->tok == TOKEN_IDENT)
-        typevar = parser_find_typedef(parser, parser_tokval(parser), 0);
-
-    if (typevar || parser->tok == TOKEN_TYPENAME || parser->tok == '.' || parser->tok == TOKEN_DOTS)
-    {
-        /* local variable */
-        if (!block) {
-            parseerror(parser, "cannot declare a variable from here");
-            return false;
-        }
-        if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) {
-            if (parsewarning(parser, WARN_EXTENSIONS, "missing 'local' keyword when declaring a local variable"))
-                return false;
-        }
-        if (!parse_variable(parser, block, false, CV_NONE, typevar, false, false, 0, NULL))
-            return false;
-        return true;
-    }
-    else if (parse_qualifiers(parser, !!block, &cvq, &noref, &is_static, &qflags, &vstring))
-    {
-        if (cvq == CV_WRONG)
-            return false;
-        return parse_variable(parser, block, false, cvq, NULL, noref, is_static, qflags, vstring);
-    }
-    else if (parser->tok == TOKEN_KEYWORD)
-    {
-        if (!strcmp(parser_tokval(parser), "__builtin_debug_printtype"))
-        {
-            char ty[1024];
-            ast_value *tdef;
-
-            if (!parser_next(parser)) {
-                parseerror(parser, "parse error after __builtin_debug_printtype");
-                return false;
-            }
-
-            if (parser->tok == TOKEN_IDENT && (tdef = parser_find_typedef(parser, parser_tokval(parser), 0)))
-            {
-                ast_type_to_string((ast_expression*)tdef, ty, sizeof(ty));
-                con_out("__builtin_debug_printtype: `%s`=`%s`\n", tdef->name, ty);
-                if (!parser_next(parser)) {
-                    parseerror(parser, "parse error after __builtin_debug_printtype typename argument");
-                    return false;
-                }
-            }
-            else
-            {
-                if (!parse_statement(parser, block, out, allow_cases))
-                    return false;
-                if (!*out)
-                    con_out("__builtin_debug_printtype: got no output node\n");
-                else
-                {
-                    ast_type_to_string(*out, ty, sizeof(ty));
-                    con_out("__builtin_debug_printtype: `%s`\n", ty);
-                }
-            }
-            return true;
-        }
-        else if (!strcmp(parser_tokval(parser), "return"))
-        {
-            return parse_return(parser, block, out);
-        }
-        else if (!strcmp(parser_tokval(parser), "if"))
-        {
-            return parse_if(parser, block, out);
-        }
-        else if (!strcmp(parser_tokval(parser), "while"))
-        {
-            return parse_while(parser, block, out);
-        }
-        else if (!strcmp(parser_tokval(parser), "do"))
-        {
-            return parse_dowhile(parser, block, out);
-        }
-        else if (!strcmp(parser_tokval(parser), "for"))
-        {
-            if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) {
-                if (parsewarning(parser, WARN_EXTENSIONS, "for loops are not recognized in the original Quake C standard, to enable try an alternate standard --std=?"))
-                    return false;
-            }
-            return parse_for(parser, block, out);
-        }
-        else if (!strcmp(parser_tokval(parser), "break"))
-        {
-            return parse_break_continue(parser, block, out, false);
-        }
-        else if (!strcmp(parser_tokval(parser), "continue"))
-        {
-            return parse_break_continue(parser, block, out, true);
-        }
-        else if (!strcmp(parser_tokval(parser), "switch"))
-        {
-            return parse_switch(parser, block, out);
-        }
-        else if (!strcmp(parser_tokval(parser), "case") ||
-                 !strcmp(parser_tokval(parser), "default"))
-        {
-            if (!allow_cases) {
-                parseerror(parser, "unexpected 'case' label");
-                return false;
-            }
-            return true;
-        }
-        else if (!strcmp(parser_tokval(parser), "goto"))
-        {
-            return parse_goto(parser, out);
-        }
-        else if (!strcmp(parser_tokval(parser), "typedef"))
-        {
-            if (!parser_next(parser)) {
-                parseerror(parser, "expected type definition after 'typedef'");
-                return false;
-            }
-            return parse_typedef(parser);
-        }
-        parseerror(parser, "Unexpected keyword: `%s'", parser_tokval(parser));
-        return false;
-    }
-    else if (parser->tok == '{')
-    {
-        ast_block *inner;
-        inner = parse_block(parser);
-        if (!inner)
-            return false;
-        *out = (ast_expression*)inner;
-        return true;
-    }
-    else if (parser->tok == ':')
-    {
-        size_t i;
-        ast_label *label;
-        if (!parser_next(parser)) {
-            parseerror(parser, "expected label name");
-            return false;
-        }
-        if (parser->tok != TOKEN_IDENT) {
-            parseerror(parser, "label must be an identifier");
-            return false;
-        }
-        label = (ast_label*)parser_find_label(parser, parser_tokval(parser));
-        if (label) {
-            if (!label->undefined) {
-                parseerror(parser, "label `%s` already defined", label->name);
-                return false;
-            }
-            label->undefined = false;
-        }
-        else {
-            label = ast_label_new(parser_ctx(parser), parser_tokval(parser), false);
-            vec_push(parser->labels, label);
-        }
-        *out = (ast_expression*)label;
-        if (!parser_next(parser)) {
-            parseerror(parser, "parse error after label");
-            return false;
-        }
-        for (i = 0; i < vec_size(parser->gotos); ++i) {
-            if (!strcmp(parser->gotos[i]->name, label->name)) {
-                ast_goto_set_label(parser->gotos[i], label);
-                vec_remove(parser->gotos, i, 1);
-                --i;
-            }
-        }
-        return true;
-    }
-    else if (parser->tok == ';')
-    {
-        if (!parser_next(parser)) {
-            parseerror(parser, "parse error after empty statement");
-            return false;
-        }
-        return true;
-    }
-    else
-    {
-        lex_ctx_t ctx = parser_ctx(parser);
-        ast_expression *exp = parse_expression(parser, false, false);
-        if (!exp)
-            return false;
-        *out = exp;
-        if (!ast_side_effects(exp)) {
-            if (compile_warning(ctx, WARN_EFFECTLESS_STATEMENT, "statement has no effect"))
-                return false;
-        }
-        return true;
-    }
-}
-
-static bool parse_enum(parser_t *parser)
-{
-    bool        flag = false;
-    bool        reverse = false;
-    qcfloat_t     num = 0;
-    ast_value **values = NULL;
-    ast_value  *var = NULL;
-    ast_value  *asvalue;
-
-    ast_expression *old;
-
-    if (!parser_next(parser) || (parser->tok != '{' && parser->tok != ':')) {
-        parseerror(parser, "expected `{` or `:` after `enum` keyword");
-        return false;
-    }
-
-    /* enumeration attributes (can add more later) */
-    if (parser->tok == ':') {
-        if (!parser_next(parser) || parser->tok != TOKEN_IDENT){
-            parseerror(parser, "expected `flag` or `reverse` for enumeration attribute");
-            return false;
-        }
-
-        /* attributes? */
-        if (!strcmp(parser_tokval(parser), "flag")) {
-            num  = 1;
-            flag = true;
-        }
-        else if (!strcmp(parser_tokval(parser), "reverse")) {
-            reverse = true;
-        }
-        else {
-            parseerror(parser, "invalid attribute `%s` for enumeration", parser_tokval(parser));
-            return false;
-        }
-
-        if (!parser_next(parser) || parser->tok != '{') {
-            parseerror(parser, "expected `{` after enum attribute ");
-            return false;
-        }
-    }
-
-    while (true) {
-        if (!parser_next(parser) || parser->tok != TOKEN_IDENT) {
-            if (parser->tok == '}') {
-                /* allow an empty enum */
-                break;
-            }
-            parseerror(parser, "expected identifier or `}`");
-            goto onerror;
-        }
-
-        old = parser_find_field(parser, parser_tokval(parser));
-        if (!old)
-            old = parser_find_global(parser, parser_tokval(parser));
-        if (old) {
-            parseerror(parser, "value `%s` has already been declared here: %s:%i",
-                       parser_tokval(parser), ast_ctx(old).file, ast_ctx(old).line);
-            goto onerror;
-        }
-
-        var = ast_value_new(parser_ctx(parser), parser_tokval(parser), TYPE_FLOAT);
-        vec_push(values, var);
-        var->cvq             = CV_CONST;
-        var->hasvalue        = true;
-
-        /* for flagged enumerations increment in POTs of TWO */
-        var->constval.vfloat = (flag) ? (num *= 2) : (num ++);
-        parser_addglobal(parser, var->name, (ast_expression*)var);
-
-        if (!parser_next(parser)) {
-            parseerror(parser, "expected `=`, `}` or comma after identifier");
-            goto onerror;
-        }
-
-        if (parser->tok == ',')
-            continue;
-        if (parser->tok == '}')
-            break;
-        if (parser->tok != '=') {
-            parseerror(parser, "expected `=`, `}` or comma after identifier");
-            goto onerror;
-        }
-
-        if (!parser_next(parser)) {
-            parseerror(parser, "expected expression after `=`");
-            goto onerror;
-        }
-
-        /* We got a value! */
-        old = parse_expression_leave(parser, true, false, false);
-        asvalue = (ast_value*)old;
-        if (!ast_istype(old, ast_value) || asvalue->cvq != CV_CONST || !asvalue->hasvalue) {
-            compile_error(ast_ctx(var), "constant value or expression expected");
-            goto onerror;
-        }
-        num = (var->constval.vfloat = asvalue->constval.vfloat) + 1;
-
-        if (parser->tok == '}')
-            break;
-        if (parser->tok != ',') {
-            parseerror(parser, "expected `}` or comma after expression");
-            goto onerror;
-        }
-    }
-
-    /* patch them all (for reversed attribute) */
-    if (reverse) {
-        size_t i;
-        for (i = 0; i < vec_size(values); i++)
-            values[i]->constval.vfloat = vec_size(values) - i - 1;
-    }
-
-    if (parser->tok != '}') {
-        parseerror(parser, "internal error: breaking without `}`");
-        goto onerror;
-    }
-
-    if (!parser_next(parser) || parser->tok != ';') {
-        parseerror(parser, "expected semicolon after enumeration");
-        goto onerror;
-    }
-
-    if (!parser_next(parser)) {
-        parseerror(parser, "parse error after enumeration");
-        goto onerror;
-    }
-
-    vec_free(values);
-    return true;
-
-onerror:
-    vec_free(values);
-    return false;
-}
-
-static bool parse_block_into(parser_t *parser, ast_block *block)
-{
-    bool   retval = true;
-
-    parser_enterblock(parser);
-
-    if (!parser_next(parser)) { /* skip the '{' */
-        parseerror(parser, "expected function body");
-        goto cleanup;
-    }
-
-    while (parser->tok != TOKEN_EOF && parser->tok < TOKEN_ERROR)
-    {
-        ast_expression *expr = NULL;
-        if (parser->tok == '}')
-            break;
-
-        if (!parse_statement(parser, block, &expr, false)) {
-            /* parseerror(parser, "parse error"); */
-            block = NULL;
-            goto cleanup;
-        }
-        if (!expr)
-            continue;
-        if (!ast_block_add_expr(block, expr)) {
-            ast_delete(block);
-            block = NULL;
-            goto cleanup;
-        }
-    }
-
-    if (parser->tok != '}') {
-        block = NULL;
-    } else {
-        (void)parser_next(parser);
-    }
-
-cleanup:
-    if (!parser_leaveblock(parser))
-        retval = false;
-    return retval && !!block;
-}
-
-static ast_block* parse_block(parser_t *parser)
-{
-    ast_block *block;
-    block = ast_block_new(parser_ctx(parser));
-    if (!block)
-        return NULL;
-    if (!parse_block_into(parser, block)) {
-        ast_block_delete(block);
-        return NULL;
-    }
-    return block;
-}
-
-static bool parse_statement_or_block(parser_t *parser, ast_expression **out)
-{
-    if (parser->tok == '{') {
-        *out = (ast_expression*)parse_block(parser);
-        return !!*out;
-    }
-    return parse_statement(parser, NULL, out, false);
-}
-
-static bool create_vector_members(ast_value *var, ast_member **me)
-{
-    size_t i;
-    size_t len = strlen(var->name);
-
-    for (i = 0; i < 3; ++i) {
-        char *name = (char*)mem_a(len+3);
-        memcpy(name, var->name, len);
-        name[len+0] = '_';
-        name[len+1] = 'x'+i;
-        name[len+2] = 0;
-        me[i] = ast_member_new(ast_ctx(var), (ast_expression*)var, i, name);
-        mem_d(name);
-        if (!me[i])
-            break;
-    }
-    if (i == 3)
-        return true;
-
-    /* unroll */
-    do { ast_member_delete(me[--i]); } while(i);
-    return false;
-}
-
-static bool parse_function_body(parser_t *parser, ast_value *var)
-{
-    ast_block      *block = NULL;
-    ast_function   *func;
-    ast_function   *old;
-    size_t          parami;
-
-    ast_expression *framenum  = NULL;
-    ast_expression *nextthink = NULL;
-    /* None of the following have to be deleted */
-    ast_expression *fld_think = NULL, *fld_nextthink = NULL, *fld_frame = NULL;
-    ast_expression *gbl_time = NULL, *gbl_self = NULL;
-    bool            has_frame_think;
-
-    bool retval = true;
-
-    has_frame_think = false;
-    old = parser->function;
-
-    if (var->expression.flags & AST_FLAG_ALIAS) {
-        parseerror(parser, "function aliases cannot have bodies");
-        return false;
-    }
-
-    if (vec_size(parser->gotos) || vec_size(parser->labels)) {
-        parseerror(parser, "gotos/labels leaking");
-        return false;
-    }
-
-    if (!OPTS_FLAG(VARIADIC_ARGS) && var->expression.flags & AST_FLAG_VARIADIC) {
-        if (parsewarning(parser, WARN_VARIADIC_FUNCTION,
-                         "variadic function with implementation will not be able to access additional parameters (try -fvariadic-args)"))
-        {
-            return false;
-        }
-    }
-
-    if (parser->tok == '[') {
-        /* got a frame definition: [ framenum, nextthink ]
-         * this translates to:
-         * self.frame = framenum;
-         * self.nextthink = time + 0.1;
-         * self.think = nextthink;
-         */
-        nextthink = NULL;
-
-        fld_think     = parser_find_field(parser, "think");
-        fld_nextthink = parser_find_field(parser, "nextthink");
-        fld_frame     = parser_find_field(parser, "frame");
-        if (!fld_think || !fld_nextthink || !fld_frame) {
-            parseerror(parser, "cannot use [frame,think] notation without the required fields");
-            parseerror(parser, "please declare the following entityfields: `frame`, `think`, `nextthink`");
-            return false;
-        }
-        gbl_time      = parser_find_global(parser, "time");
-        gbl_self      = parser_find_global(parser, "self");
-        if (!gbl_time || !gbl_self) {
-            parseerror(parser, "cannot use [frame,think] notation without the required globals");
-            parseerror(parser, "please declare the following globals: `time`, `self`");
-            return false;
-        }
-
-        if (!parser_next(parser))
-            return false;
-
-        framenum = parse_expression_leave(parser, true, false, false);
-        if (!framenum) {
-            parseerror(parser, "expected a framenumber constant in[frame,think] notation");
-            return false;
-        }
-        if (!ast_istype(framenum, ast_value) || !( (ast_value*)framenum )->hasvalue) {
-            ast_unref(framenum);
-            parseerror(parser, "framenumber in [frame,think] notation must be a constant");
-            return false;
-        }
-
-        if (parser->tok != ',') {
-            ast_unref(framenum);
-            parseerror(parser, "expected comma after frame number in [frame,think] notation");
-            parseerror(parser, "Got a %i\n", parser->tok);
-            return false;
-        }
-
-        if (!parser_next(parser)) {
-            ast_unref(framenum);
-            return false;
-        }
-
-        if (parser->tok == TOKEN_IDENT && !parser_find_var(parser, parser_tokval(parser)))
-        {
-            /* qc allows the use of not-yet-declared functions here
-             * - this automatically creates a prototype */
-            ast_value      *thinkfunc;
-            ast_expression *functype = fld_think->next;
-
-            thinkfunc = ast_value_new(parser_ctx(parser), parser_tokval(parser), functype->vtype);
-            if (!thinkfunc) { /* || !ast_type_adopt(thinkfunc, functype)*/
-                ast_unref(framenum);
-                parseerror(parser, "failed to create implicit prototype for `%s`", parser_tokval(parser));
-                return false;
-            }
-            ast_type_adopt(thinkfunc, functype);
-
-            if (!parser_next(parser)) {
-                ast_unref(framenum);
-                ast_delete(thinkfunc);
-                return false;
-            }
-
-            parser_addglobal(parser, thinkfunc->name, (ast_expression*)thinkfunc);
-
-            nextthink = (ast_expression*)thinkfunc;
-
-        } else {
-            nextthink = parse_expression_leave(parser, true, false, false);
-            if (!nextthink) {
-                ast_unref(framenum);
-                parseerror(parser, "expected a think-function in [frame,think] notation");
-                return false;
-            }
-        }
-
-        if (!ast_istype(nextthink, ast_value)) {
-            parseerror(parser, "think-function in [frame,think] notation must be a constant");
-            retval = false;
-        }
-
-        if (retval && parser->tok != ']') {
-            parseerror(parser, "expected closing `]` for [frame,think] notation");
-            retval = false;
-        }
-
-        if (retval && !parser_next(parser)) {
-            retval = false;
-        }
-
-        if (retval && parser->tok != '{') {
-            parseerror(parser, "a function body has to be declared after a [frame,think] declaration");
-            retval = false;
-        }
-
-        if (!retval) {
-            ast_unref(nextthink);
-            ast_unref(framenum);
-            return false;
-        }
-
-        has_frame_think = true;
-    }
-
-    block = ast_block_new(parser_ctx(parser));
-    if (!block) {
-        parseerror(parser, "failed to allocate block");
-        if (has_frame_think) {
-            ast_unref(nextthink);
-            ast_unref(framenum);
-        }
-        return false;
-    }
-
-    if (has_frame_think) {
-        if (!OPTS_FLAG(EMULATE_STATE)) {
-            ast_state *state_op = ast_state_new(parser_ctx(parser), framenum, nextthink);
-            if (!ast_block_add_expr(block, (ast_expression*)state_op)) {
-                parseerror(parser, "failed to generate state op for [frame,think]");
-                ast_unref(nextthink);
-                ast_unref(framenum);
-                ast_delete(block);
-                return false;
-            }
-        } else {
-            /* emulate OP_STATE in code: */
-            lex_ctx_t ctx;
-            ast_expression *self_frame;
-            ast_expression *self_nextthink;
-            ast_expression *self_think;
-            ast_expression *time_plus_1;
-            ast_store *store_frame;
-            ast_store *store_nextthink;
-            ast_store *store_think;
-
-            float frame_delta = 1.0f / (float)OPTS_OPTION_U32(OPTION_STATE_FPS);
-
-            ctx = parser_ctx(parser);
-            self_frame     = (ast_expression*)ast_entfield_new(ctx, gbl_self, fld_frame);
-            self_nextthink = (ast_expression*)ast_entfield_new(ctx, gbl_self, fld_nextthink);
-            self_think     = (ast_expression*)ast_entfield_new(ctx, gbl_self, fld_think);
-
-            time_plus_1    = (ast_expression*)ast_binary_new(ctx, INSTR_ADD_F,
-                             gbl_time, (ast_expression*)fold_constgen_float(parser->fold, frame_delta, false));
-
-            if (!self_frame || !self_nextthink || !self_think || !time_plus_1) {
-                if (self_frame)     ast_delete(self_frame);
-                if (self_nextthink) ast_delete(self_nextthink);
-                if (self_think)     ast_delete(self_think);
-                if (time_plus_1)    ast_delete(time_plus_1);
-                retval = false;
-            }
-
-            if (retval)
-            {
-                store_frame     = ast_store_new(ctx, INSTR_STOREP_F,   self_frame,     framenum);
-                store_nextthink = ast_store_new(ctx, INSTR_STOREP_F,   self_nextthink, time_plus_1);
-                store_think     = ast_store_new(ctx, INSTR_STOREP_FNC, self_think,     nextthink);
-
-                if (!store_frame) {
-                    ast_delete(self_frame);
-                    retval = false;
-                }
-                if (!store_nextthink) {
-                    ast_delete(self_nextthink);
-                    retval = false;
-                }
-                if (!store_think) {
-                    ast_delete(self_think);
-                    retval = false;
-                }
-                if (!retval) {
-                    if (store_frame)     ast_delete(store_frame);
-                    if (store_nextthink) ast_delete(store_nextthink);
-                    if (store_think)     ast_delete(store_think);
-                    retval = false;
-                }
-                if (!ast_block_add_expr(block, (ast_expression*)store_frame) ||
-                    !ast_block_add_expr(block, (ast_expression*)store_nextthink) ||
-                    !ast_block_add_expr(block, (ast_expression*)store_think))
-                {
-                    retval = false;
-                }
-            }
-
-            if (!retval) {
-                parseerror(parser, "failed to generate code for [frame,think]");
-                ast_unref(nextthink);
-                ast_unref(framenum);
-                ast_delete(block);
-                return false;
-            }
-        }
-    }
-
-    if (var->hasvalue) {
-        if (!(var->expression.flags & AST_FLAG_ACCUMULATE)) {
-            parseerror(parser, "function `%s` declared with multiple bodies", var->name);
-            ast_block_delete(block);
-            goto enderr;
-        }
-        func = var->constval.vfunc;
-
-        if (!func) {
-            parseerror(parser, "internal error: NULL function: `%s`", var->name);
-            ast_block_delete(block);
-            goto enderr;
-        }
-    } else {
-        func = ast_function_new(ast_ctx(var), var->name, var);
-
-        if (!func) {
-            parseerror(parser, "failed to allocate function for `%s`", var->name);
-            ast_block_delete(block);
-            goto enderr;
-        }
-        vec_push(parser->functions, func);
-    }
-
-    parser_enterblock(parser);
-
-    for (parami = 0; parami < vec_size(var->expression.params); ++parami) {
-        size_t     e;
-        ast_value *param = var->expression.params[parami];
-        ast_member *me[3];
-
-        if (param->expression.vtype != TYPE_VECTOR &&
-            (param->expression.vtype != TYPE_FIELD ||
-             param->expression.next->vtype != TYPE_VECTOR))
-        {
-            continue;
-        }
-
-        if (!create_vector_members(param, me)) {
-            ast_block_delete(block);
-            goto enderrfn;
-        }
-
-        for (e = 0; e < 3; ++e) {
-            parser_addlocal(parser, me[e]->name, (ast_expression*)me[e]);
-            ast_block_collect(block, (ast_expression*)me[e]);
-        }
-    }
-
-    if (var->argcounter && !func->argc) {
-        ast_value *argc = ast_value_new(ast_ctx(var), var->argcounter, TYPE_FLOAT);
-        parser_addlocal(parser, argc->name, (ast_expression*)argc);
-        func->argc = argc;
-    }
-
-    if (OPTS_FLAG(VARIADIC_ARGS) && var->expression.flags & AST_FLAG_VARIADIC && !func->varargs) {
-        char name[1024];
-        ast_value *varargs = ast_value_new(ast_ctx(var), "reserved:va_args", TYPE_ARRAY);
-        varargs->expression.flags |= AST_FLAG_IS_VARARG;
-        varargs->expression.next = (ast_expression*)ast_value_new(ast_ctx(var), NULL, TYPE_VECTOR);
-        varargs->expression.count = 0;
-        util_snprintf(name, sizeof(name), "%s##va##SET", var->name);
-        if (!parser_create_array_setter_proto(parser, varargs, name)) {
-            ast_delete(varargs);
-            ast_block_delete(block);
-            goto enderrfn;
-        }
-        util_snprintf(name, sizeof(name), "%s##va##GET", var->name);
-        if (!parser_create_array_getter_proto(parser, varargs, varargs->expression.next, name)) {
-            ast_delete(varargs);
-            ast_block_delete(block);
-            goto enderrfn;
-        }
-        func->varargs     = varargs;
-        func->fixedparams = (ast_value*)fold_constgen_float(parser->fold, vec_size(var->expression.params), false);
-    }
-
-    parser->function = func;
-    if (!parse_block_into(parser, block)) {
-        ast_block_delete(block);
-        goto enderrfn;
-    }
-
-    vec_push(func->blocks, block);
-
-    parser->function = old;
-    if (!parser_leaveblock(parser))
-        retval = false;
-    if (vec_size(parser->variables) != PARSER_HT_LOCALS) {
-        parseerror(parser, "internal error: local scopes left");
-        retval = false;
-    }
-
-    if (parser->tok == ';')
-        return parser_next(parser);
-    else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC)
-        parseerror(parser, "missing semicolon after function body (mandatory with -std=qcc)");
-    return retval;
-
-enderrfn:
-    (void)!parser_leaveblock(parser);
-    vec_pop(parser->functions);
-    ast_function_delete(func);
-    var->constval.vfunc = NULL;
-
-enderr:
-    parser->function = old;
-    return false;
-}
-
-static ast_expression *array_accessor_split(
-    parser_t  *parser,
-    ast_value *array,
-    ast_value *index,
-    size_t     middle,
-    ast_expression *left,
-    ast_expression *right
-    )
-{
-    ast_ifthen *ifthen;
-    ast_binary *cmp;
-
-    lex_ctx_t ctx = ast_ctx(array);
-
-    if (!left || !right) {
-        if (left)  ast_delete(left);
-        if (right) ast_delete(right);
-        return NULL;
-    }
-
-    cmp = ast_binary_new(ctx, INSTR_LT,
-                         (ast_expression*)index,
-                         (ast_expression*)fold_constgen_float(parser->fold, middle, false));
-    if (!cmp) {
-        ast_delete(left);
-        ast_delete(right);
-        parseerror(parser, "internal error: failed to create comparison for array setter");
-        return NULL;
-    }
-
-    ifthen = ast_ifthen_new(ctx, (ast_expression*)cmp, left, right);
-    if (!ifthen) {
-        ast_delete(cmp); /* will delete left and right */
-        parseerror(parser, "internal error: failed to create conditional jump for array setter");
-        return NULL;
-    }
-
-    return (ast_expression*)ifthen;
-}
-
-static ast_expression *array_setter_node(parser_t *parser, ast_value *array, ast_value *index, ast_value *value, size_t from, size_t afterend)
-{
-    lex_ctx_t ctx = ast_ctx(array);
-
-    if (from+1 == afterend) {
-        /* set this value */
-        ast_block       *block;
-        ast_return      *ret;
-        ast_array_index *subscript;
-        ast_store       *st;
-        int assignop = type_store_instr[value->expression.vtype];
-
-        if (value->expression.vtype == TYPE_FIELD && value->expression.next->vtype == TYPE_VECTOR)
-            assignop = INSTR_STORE_V;
-
-        subscript = ast_array_index_new(ctx, (ast_expression*)array, (ast_expression*)fold_constgen_float(parser->fold, from, false));
-        if (!subscript)
-            return NULL;
-
-        st = ast_store_new(ctx, assignop, (ast_expression*)subscript, (ast_expression*)value);
-        if (!st) {
-            ast_delete(subscript);
-            return NULL;
-        }
-
-        block = ast_block_new(ctx);
-        if (!block) {
-            ast_delete(st);
-            return NULL;
-        }
-
-        if (!ast_block_add_expr(block, (ast_expression*)st)) {
-            ast_delete(block);
-            return NULL;
-        }
-
-        ret = ast_return_new(ctx, NULL);
-        if (!ret) {
-            ast_delete(block);
-            return NULL;
-        }
-
-        if (!ast_block_add_expr(block, (ast_expression*)ret)) {
-            ast_delete(block);
-            return NULL;
-        }
-
-        return (ast_expression*)block;
-    } else {
-        ast_expression *left, *right;
-        size_t diff = afterend - from;
-        size_t middle = from + diff/2;
-        left  = array_setter_node(parser, array, index, value, from, middle);
-        right = array_setter_node(parser, array, index, value, middle, afterend);
-        return array_accessor_split(parser, array, index, middle, left, right);
-    }
-}
-
-static ast_expression *array_field_setter_node(
-    parser_t  *parser,
-    ast_value *array,
-    ast_value *entity,
-    ast_value *index,
-    ast_value *value,
-    size_t     from,
-    size_t     afterend)
-{
-    lex_ctx_t ctx = ast_ctx(array);
-
-    if (from+1 == afterend) {
-        /* set this value */
-        ast_block       *block;
-        ast_return      *ret;
-        ast_entfield    *entfield;
-        ast_array_index *subscript;
-        ast_store       *st;
-        int assignop = type_storep_instr[value->expression.vtype];
-
-        if (value->expression.vtype == TYPE_FIELD && value->expression.next->vtype == TYPE_VECTOR)
-            assignop = INSTR_STOREP_V;
-
-        subscript = ast_array_index_new(ctx, (ast_expression*)array, (ast_expression*)fold_constgen_float(parser->fold, from, false));
-        if (!subscript)
-            return NULL;
-
-        subscript->expression.next = ast_type_copy(ast_ctx(subscript), (ast_expression*)subscript);
-        subscript->expression.vtype = TYPE_FIELD;
-
-        entfield = ast_entfield_new_force(ctx,
-                                          (ast_expression*)entity,
-                                          (ast_expression*)subscript,
-                                          (ast_expression*)subscript);
-        if (!entfield) {
-            ast_delete(subscript);
-            return NULL;
-        }
-
-        st = ast_store_new(ctx, assignop, (ast_expression*)entfield, (ast_expression*)value);
-        if (!st) {
-            ast_delete(entfield);
-            return NULL;
-        }
-
-        block = ast_block_new(ctx);
-        if (!block) {
-            ast_delete(st);
-            return NULL;
-        }
-
-        if (!ast_block_add_expr(block, (ast_expression*)st)) {
-            ast_delete(block);
-            return NULL;
-        }
-
-        ret = ast_return_new(ctx, NULL);
-        if (!ret) {
-            ast_delete(block);
-            return NULL;
-        }
-
-        if (!ast_block_add_expr(block, (ast_expression*)ret)) {
-            ast_delete(block);
-            return NULL;
-        }
-
-        return (ast_expression*)block;
-    } else {
-        ast_expression *left, *right;
-        size_t diff = afterend - from;
-        size_t middle = from + diff/2;
-        left  = array_field_setter_node(parser, array, entity, index, value, from, middle);
-        right = array_field_setter_node(parser, array, entity, index, value, middle, afterend);
-        return array_accessor_split(parser, array, index, middle, left, right);
-    }
-}
-
-static ast_expression *array_getter_node(parser_t *parser, ast_value *array, ast_value *index, size_t from, size_t afterend)
-{
-    lex_ctx_t ctx = ast_ctx(array);
-
-    if (from+1 == afterend) {
-        ast_return      *ret;
-        ast_array_index *subscript;
-
-        subscript = ast_array_index_new(ctx, (ast_expression*)array, (ast_expression*)fold_constgen_float(parser->fold, from, false));
-        if (!subscript)
-            return NULL;
-
-        ret = ast_return_new(ctx, (ast_expression*)subscript);
-        if (!ret) {
-            ast_delete(subscript);
-            return NULL;
-        }
-
-        return (ast_expression*)ret;
-    } else {
-        ast_expression *left, *right;
-        size_t diff = afterend - from;
-        size_t middle = from + diff/2;
-        left  = array_getter_node(parser, array, index, from, middle);
-        right = array_getter_node(parser, array, index, middle, afterend);
-        return array_accessor_split(parser, array, index, middle, left, right);
-    }
-}
-
-static bool parser_create_array_accessor(parser_t *parser, ast_value *array, const char *funcname, ast_value **out)
-{
-    ast_function   *func = NULL;
-    ast_value      *fval = NULL;
-    ast_block      *body = NULL;
-
-    fval = ast_value_new(ast_ctx(array), funcname, TYPE_FUNCTION);
-    if (!fval) {
-        parseerror(parser, "failed to create accessor function value");
-        return false;
-    }
-    fval->expression.flags &= ~(AST_FLAG_COVERAGE_MASK);
-
-    func = ast_function_new(ast_ctx(array), funcname, fval);
-    if (!func) {
-        ast_delete(fval);
-        parseerror(parser, "failed to create accessor function node");
-        return false;
-    }
-
-    body = ast_block_new(ast_ctx(array));
-    if (!body) {
-        parseerror(parser, "failed to create block for array accessor");
-        ast_delete(fval);
-        ast_delete(func);
-        return false;
-    }
-
-    vec_push(func->blocks, body);
-    *out = fval;
-
-    vec_push(parser->accessors, fval);
-
-    return true;
-}
-
-static ast_value* parser_create_array_setter_proto(parser_t *parser, ast_value *array, const char *funcname)
-{
-    ast_value      *index = NULL;
-    ast_value      *value = NULL;
-    ast_function   *func;
-    ast_value      *fval;
-
-    if (!ast_istype(array->expression.next, ast_value)) {
-        parseerror(parser, "internal error: array accessor needs to build an ast_value with a copy of the element type");
-        return NULL;
-    }
-
-    if (!parser_create_array_accessor(parser, array, funcname, &fval))
-        return NULL;
-    func = fval->constval.vfunc;
-    fval->expression.next = (ast_expression*)ast_value_new(ast_ctx(array), "<void>", TYPE_VOID);
-
-    index = ast_value_new(ast_ctx(array), "index", TYPE_FLOAT);
-    value = ast_value_copy((ast_value*)array->expression.next);
-
-    if (!index || !value) {
-        parseerror(parser, "failed to create locals for array accessor");
-        goto cleanup;
-    }
-    (void)!ast_value_set_name(value, "value"); /* not important */
-    vec_push(fval->expression.params, index);
-    vec_push(fval->expression.params, value);
-
-    array->setter = fval;
-    return fval;
-cleanup:
-    if (index) ast_delete(index);
-    if (value) ast_delete(value);
-    ast_delete(func);
-    ast_delete(fval);
-    return NULL;
-}
-
-static bool parser_create_array_setter_impl(parser_t *parser, ast_value *array)
-{
-    ast_expression *root = NULL;
-    root = array_setter_node(parser, array,
-                             array->setter->expression.params[0],
-                             array->setter->expression.params[1],
-                             0, array->expression.count);
-    if (!root) {
-        parseerror(parser, "failed to build accessor search tree");
-        return false;
-    }
-    if (!ast_block_add_expr(array->setter->constval.vfunc->blocks[0], root)) {
-        ast_delete(root);
-        return false;
-    }
-    return true;
-}
-
-static bool parser_create_array_setter(parser_t *parser, ast_value *array, const char *funcname)
-{
-    if (!parser_create_array_setter_proto(parser, array, funcname))
-        return false;
-    return parser_create_array_setter_impl(parser, array);
-}
-
-static bool parser_create_array_field_setter(parser_t *parser, ast_value *array, const char *funcname)
-{
-    ast_expression *root = NULL;
-    ast_value      *entity = NULL;
-    ast_value      *index = NULL;
-    ast_value      *value = NULL;
-    ast_function   *func;
-    ast_value      *fval;
-
-    if (!ast_istype(array->expression.next, ast_value)) {
-        parseerror(parser, "internal error: array accessor needs to build an ast_value with a copy of the element type");
-        return false;
-    }
-
-    if (!parser_create_array_accessor(parser, array, funcname, &fval))
-        return false;
-    func = fval->constval.vfunc;
-    fval->expression.next = (ast_expression*)ast_value_new(ast_ctx(array), "<void>", TYPE_VOID);
-
-    entity = ast_value_new(ast_ctx(array), "entity", TYPE_ENTITY);
-    index  = ast_value_new(ast_ctx(array), "index",  TYPE_FLOAT);
-    value  = ast_value_copy((ast_value*)array->expression.next);
-    if (!entity || !index || !value) {
-        parseerror(parser, "failed to create locals for array accessor");
-        goto cleanup;
-    }
-    (void)!ast_value_set_name(value, "value"); /* not important */
-    vec_push(fval->expression.params, entity);
-    vec_push(fval->expression.params, index);
-    vec_push(fval->expression.params, value);
-
-    root = array_field_setter_node(parser, array, entity, index, value, 0, array->expression.count);
-    if (!root) {
-        parseerror(parser, "failed to build accessor search tree");
-        goto cleanup;
-    }
-
-    array->setter = fval;
-    return ast_block_add_expr(func->blocks[0], root);
-cleanup:
-    if (entity) ast_delete(entity);
-    if (index)  ast_delete(index);
-    if (value)  ast_delete(value);
-    if (root)   ast_delete(root);
-    ast_delete(func);
-    ast_delete(fval);
-    return false;
-}
-
-static ast_value* parser_create_array_getter_proto(parser_t *parser, ast_value *array, const ast_expression *elemtype, const char *funcname)
-{
-    ast_value      *index = NULL;
-    ast_value      *fval;
-    ast_function   *func;
-
-    /* NOTE: checking array->expression.next rather than elemtype since
-     * for fields elemtype is a temporary fieldtype.
-     */
-    if (!ast_istype(array->expression.next, ast_value)) {
-        parseerror(parser, "internal error: array accessor needs to build an ast_value with a copy of the element type");
-        return NULL;
-    }
-
-    if (!parser_create_array_accessor(parser, array, funcname, &fval))
-        return NULL;
-    func = fval->constval.vfunc;
-    fval->expression.next = ast_type_copy(ast_ctx(array), elemtype);
-
-    index = ast_value_new(ast_ctx(array), "index", TYPE_FLOAT);
-
-    if (!index) {
-        parseerror(parser, "failed to create locals for array accessor");
-        goto cleanup;
-    }
-    vec_push(fval->expression.params, index);
-
-    array->getter = fval;
-    return fval;
-cleanup:
-    if (index) ast_delete(index);
-    ast_delete(func);
-    ast_delete(fval);
-    return NULL;
-}
-
-static bool parser_create_array_getter_impl(parser_t *parser, ast_value *array)
-{
-    ast_expression *root = NULL;
-
-    root = array_getter_node(parser, array, array->getter->expression.params[0], 0, array->expression.count);
-    if (!root) {
-        parseerror(parser, "failed to build accessor search tree");
-        return false;
-    }
-    if (!ast_block_add_expr(array->getter->constval.vfunc->blocks[0], root)) {
-        ast_delete(root);
-        return false;
-    }
-    return true;
-}
-
-static bool parser_create_array_getter(parser_t *parser, ast_value *array, const ast_expression *elemtype, const char *funcname)
-{
-    if (!parser_create_array_getter_proto(parser, array, elemtype, funcname))
-        return false;
-    return parser_create_array_getter_impl(parser, array);
-}
-
-static ast_value *parse_parameter_list(parser_t *parser, ast_value *var)
-{
-    lex_ctx_t     ctx;
-    size_t      i;
-    ast_value **params;
-    ast_value  *param;
-    ast_value  *fval;
-    bool        first = true;
-    bool        variadic = false;
-    ast_value  *varparam = NULL;
-    char       *argcounter = NULL;
-
-    ctx = parser_ctx(parser);
-
-    /* for the sake of less code we parse-in in this function */
-    if (!parser_next(parser)) {
-        ast_delete(var);
-        parseerror(parser, "expected parameter list");
-        return NULL;
-    }
-
-    params = NULL;
-
-    /* parse variables until we hit a closing paren */
-    while (parser->tok != ')') {
-        bool is_varargs = false;
-
-        if (!first) {
-            /* there must be commas between them */
-            if (parser->tok != ',') {
-                parseerror(parser, "expected comma or end of parameter list");
-                goto on_error;
-            }
-            if (!parser_next(parser)) {
-                parseerror(parser, "expected parameter");
-                goto on_error;
-            }
-        }
-        first = false;
-
-        param = parse_typename(parser, NULL, NULL, &is_varargs);
-        if (!param && !is_varargs)
-            goto on_error;
-        if (is_varargs) {
-            /* '...' indicates a varargs function */
-            variadic = true;
-            if (parser->tok != ')' && parser->tok != TOKEN_IDENT) {
-                parseerror(parser, "`...` must be the last parameter of a variadic function declaration");
-                goto on_error;
-            }
-            if (parser->tok == TOKEN_IDENT) {
-                argcounter = util_strdup(parser_tokval(parser));
-                if (!parser_next(parser) || parser->tok != ')') {
-                    parseerror(parser, "`...` must be the last parameter of a variadic function declaration");
-                    goto on_error;
-                }
-            }
-        } else {
-            vec_push(params, param);
-            if (param->expression.vtype >= TYPE_VARIANT) {
-                char tname[1024]; /* typename is reserved in C++ */
-                ast_type_to_string((ast_expression*)param, tname, sizeof(tname));
-                parseerror(parser, "type not supported as part of a parameter list: %s", tname);
-                goto on_error;
-            }
-            /* type-restricted varargs */
-            if (parser->tok == TOKEN_DOTS) {
-                variadic = true;
-                varparam = vec_last(params);
-                vec_pop(params);
-                if (!parser_next(parser) || (parser->tok != ')' && parser->tok != TOKEN_IDENT)) {
-                    parseerror(parser, "`...` must be the last parameter of a variadic function declaration");
-                    goto on_error;
-                }
-                if (parser->tok == TOKEN_IDENT) {
-                    argcounter = util_strdup(parser_tokval(parser));
-                    ast_value_set_name(param, argcounter);
-                    if (!parser_next(parser) || parser->tok != ')') {
-                        parseerror(parser, "`...` must be the last parameter of a variadic function declaration");
-                        goto on_error;
-                    }
-                }
-            }
-            if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_FTEQCC && param->name[0] == '<') {
-                parseerror(parser, "parameter name omitted");
-                goto on_error;
-            }
-        }
-    }
-
-    if (vec_size(params) == 1 && params[0]->expression.vtype == TYPE_VOID)
-        vec_free(params);
-
-    /* sanity check */
-    if (vec_size(params) > 8 && OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC)
-        (void)!parsewarning(parser, WARN_EXTENSIONS, "more than 8 parameters are not supported by this standard");
-
-    /* parse-out */
-    if (!parser_next(parser)) {
-        parseerror(parser, "parse error after typename");
-        goto on_error;
-    }
-
-    /* now turn 'var' into a function type */
-    fval = ast_value_new(ctx, "<type()>", TYPE_FUNCTION);
-    fval->expression.next     = (ast_expression*)var;
-    if (variadic)
-        fval->expression.flags |= AST_FLAG_VARIADIC;
-    var = fval;
-
-    var->expression.params   = params;
-    var->expression.varparam = (ast_expression*)varparam;
-    var->argcounter          = argcounter;
-    params = NULL;
-
-    return var;
-
-on_error:
-    if (argcounter)
-        mem_d(argcounter);
-    if (varparam)
-        ast_delete(varparam);
-    ast_delete(var);
-    for (i = 0; i < vec_size(params); ++i)
-        ast_delete(params[i]);
-    vec_free(params);
-    return NULL;
-}
-
-static ast_value *parse_arraysize(parser_t *parser, ast_value *var)
-{
-    ast_expression *cexp;
-    ast_value      *cval, *tmp;
-    lex_ctx_t ctx;
-
-    ctx = parser_ctx(parser);
-
-    if (!parser_next(parser)) {
-        ast_delete(var);
-        parseerror(parser, "expected array-size");
-        return NULL;
-    }
-
-    if (parser->tok != ']') {
-        cexp = parse_expression_leave(parser, true, false, false);
-
-        if (!cexp || !ast_istype(cexp, ast_value)) {
-            if (cexp)
-                ast_unref(cexp);
-            ast_delete(var);
-            parseerror(parser, "expected array-size as constant positive integer");
-            return NULL;
-        }
-        cval = (ast_value*)cexp;
-    }
-    else {
-        cexp = NULL;
-        cval = NULL;
-    }
-
-    tmp = ast_value_new(ctx, "<type[]>", TYPE_ARRAY);
-    tmp->expression.next = (ast_expression*)var;
-    var = tmp;
-
-    if (cval) {
-        if (cval->expression.vtype == TYPE_INTEGER)
-            tmp->expression.count = cval->constval.vint;
-        else if (cval->expression.vtype == TYPE_FLOAT)
-            tmp->expression.count = cval->constval.vfloat;
-        else {
-            ast_unref(cexp);
-            ast_delete(var);
-            parseerror(parser, "array-size must be a positive integer constant");
-            return NULL;
-        }
-
-        ast_unref(cexp);
-    } else {
-        var->expression.count = -1;
-        var->expression.flags |= AST_FLAG_ARRAY_INIT;
-    }
-
-    if (parser->tok != ']') {
-        ast_delete(var);
-        parseerror(parser, "expected ']' after array-size");
-        return NULL;
-    }
-    if (!parser_next(parser)) {
-        ast_delete(var);
-        parseerror(parser, "error after parsing array size");
-        return NULL;
-    }
-    return var;
-}
-
-/* Parse a complete typename.
- * for single-variables (ie. function parameters or typedefs) storebase should be NULL
- * but when parsing variables separated by comma
- * 'storebase' should point to where the base-type should be kept.
- * The base type makes up every bit of type information which comes *before* the
- * variable name.
- *
- * NOTE: The value must either be named, have a NULL name, or a name starting
- *       with '<'. In the first case, this will be the actual variable or type
- *       name, in the other cases it is assumed that the name will appear
- *       later, and an error is generated otherwise.
- *
- * The following will be parsed in its entirety:
- *     void() foo()
- * The 'basetype' in this case is 'void()'
- * and if there's a comma after it, say:
- *     void() foo(), bar
- * then the type-information 'void()' can be stored in 'storebase'
- */
-static ast_value *parse_typename(parser_t *parser, ast_value **storebase, ast_value *cached_typedef, bool *is_vararg)
-{
-    ast_value *var, *tmp;
-    lex_ctx_t    ctx;
-
-    const char *name = NULL;
-    bool        isfield  = false;
-    bool        wasarray = false;
-    size_t      morefields = 0;
-
-    bool        vararg = (parser->tok == TOKEN_DOTS);
-
-    ctx = parser_ctx(parser);
-
-    /* types may start with a dot */
-    if (parser->tok == '.' || parser->tok == TOKEN_DOTS) {
-        isfield = true;
-        if (parser->tok == TOKEN_DOTS)
-            morefields += 2;
-        /* if we parsed a dot we need a typename now */
-        if (!parser_next(parser)) {
-            parseerror(parser, "expected typename for field definition");
-            return NULL;
-        }
-
-        /* Further dots are handled seperately because they won't be part of the
-         * basetype
-         */
-        while (true) {
-            if (parser->tok == '.')
-                ++morefields;
-            else if (parser->tok == TOKEN_DOTS)
-                morefields += 3;
-            else
-                break;
-            vararg = false;
-            if (!parser_next(parser)) {
-                parseerror(parser, "expected typename for field definition");
-                return NULL;
-            }
-        }
-    }
-    if (parser->tok == TOKEN_IDENT)
-        cached_typedef = parser_find_typedef(parser, parser_tokval(parser), 0);
-    if (!cached_typedef && parser->tok != TOKEN_TYPENAME) {
-        if (vararg && is_vararg) {
-            *is_vararg = true;
-            return NULL;
-        }
-        parseerror(parser, "expected typename");
-        return NULL;
-    }
-
-    /* generate the basic type value */
-    if (cached_typedef) {
-        var = ast_value_copy(cached_typedef);
-        ast_value_set_name(var, "<type(from_def)>");
-    } else
-        var = ast_value_new(ctx, "<type>", parser_token(parser)->constval.t);
-
-    for (; morefields; --morefields) {
-        tmp = ast_value_new(ctx, "<.type>", TYPE_FIELD);
-        tmp->expression.next = (ast_expression*)var;
-        var = tmp;
-    }
-
-    /* do not yet turn into a field - remember:
-     * .void() foo; is a field too
-     * .void()() foo; is a function
-     */
-
-    /* parse on */
-    if (!parser_next(parser)) {
-        ast_delete(var);
-        parseerror(parser, "parse error after typename");
-        return NULL;
-    }
-
-    /* an opening paren now starts the parameter-list of a function
-     * this is where original-QC has parameter lists.
-     * We allow a single parameter list here.
-     * Much like fteqcc we don't allow `float()() x`
-     */
-    if (parser->tok == '(') {
-        var = parse_parameter_list(parser, var);
-        if (!var)
-            return NULL;
-    }
-
-    /* store the base if requested */
-    if (storebase) {
-        *storebase = ast_value_copy(var);
-        if (isfield) {
-            tmp = ast_value_new(ctx, "<type:f>", TYPE_FIELD);
-            tmp->expression.next = (ast_expression*)*storebase;
-            *storebase = tmp;
-        }
-    }
-
-    /* there may be a name now */
-    if (parser->tok == TOKEN_IDENT || parser->tok == TOKEN_KEYWORD) {
-        if (!strcmp(parser_tokval(parser), "break"))
-            (void)!parsewarning(parser, WARN_BREAKDEF, "break definition ignored (suggest removing it)");
-        else if (parser->tok == TOKEN_KEYWORD)
-            goto leave;
-
-        name = util_strdup(parser_tokval(parser));
-
-        /* parse on */
-        if (!parser_next(parser)) {
-            ast_delete(var);
-            mem_d(name);
-            parseerror(parser, "error after variable or field declaration");
-            return NULL;
-        }
-    }
-
-    leave:
-    /* now this may be an array */
-    if (parser->tok == '[') {
-        wasarray = true;
-        var = parse_arraysize(parser, var);
-        if (!var) {
-            if (name) mem_d(name);
-            return NULL;
-        }
-    }
-
-    /* This is the point where we can turn it into a field */
-    if (isfield) {
-        /* turn it into a field if desired */
-        tmp = ast_value_new(ctx, "<type:f>", TYPE_FIELD);
-        tmp->expression.next = (ast_expression*)var;
-        var = tmp;
-    }
-
-    /* now there may be function parens again */
-    if (parser->tok == '(' && OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC)
-        parseerror(parser, "C-style function syntax is not allowed in -std=qcc");
-    if (parser->tok == '(' && wasarray)
-        parseerror(parser, "arrays as part of a return type is not supported");
-    while (parser->tok == '(') {
-        var = parse_parameter_list(parser, var);
-        if (!var) {
-            if (name) mem_d(name);
-            return NULL;
-        }
-    }
-
-    /* finally name it */
-    if (name) {
-        if (!ast_value_set_name(var, name)) {
-            ast_delete(var);
-            mem_d(name);
-            parseerror(parser, "internal error: failed to set name");
-            return NULL;
-        }
-        /* free the name, ast_value_set_name duplicates */
-        mem_d(name);
-    }
-
-    return var;
-}
-
-static bool parse_typedef(parser_t *parser)
-{
-    ast_value      *typevar, *oldtype;
-    ast_expression *old;
-
-    typevar = parse_typename(parser, NULL, NULL, NULL);
-
-    if (!typevar)
-        return false;
-
-    /* while parsing types, the ast_value's get named '<something>' */
-    if (!typevar->name || typevar->name[0] == '<') {
-        parseerror(parser, "missing name in typedef");
-        ast_delete(typevar);
-        return false;
-    }
-
-    if ( (old = parser_find_var(parser, typevar->name)) ) {
-        parseerror(parser, "cannot define a type with the same name as a variable: %s\n"
-                   " -> `%s` has been declared here: %s:%i",
-                   typevar->name, ast_ctx(old).file, ast_ctx(old).line);
-        ast_delete(typevar);
-        return false;
-    }
-
-    if ( (oldtype = parser_find_typedef(parser, typevar->name, vec_last(parser->_blocktypedefs))) ) {
-        parseerror(parser, "type `%s` has already been declared here: %s:%i",
-                   typevar->name, ast_ctx(oldtype).file, ast_ctx(oldtype).line);
-        ast_delete(typevar);
-        return false;
-    }
-
-    vec_push(parser->_typedefs, typevar);
-    util_htset(vec_last(parser->typedefs), typevar->name, typevar);
-
-    if (parser->tok != ';') {
-        parseerror(parser, "expected semicolon after typedef");
-        return false;
-    }
-    if (!parser_next(parser)) {
-        parseerror(parser, "parse error after typedef");
-        return false;
-    }
-
-    return true;
-}
-
-static const char *cvq_to_str(int cvq) {
-    switch (cvq) {
-        case CV_NONE:  return "none";
-        case CV_VAR:   return "`var`";
-        case CV_CONST: return "`const`";
-        default:       return "<INVALID>";
-    }
-}
-
-static bool parser_check_qualifiers(parser_t *parser, const ast_value *var, const ast_value *proto)
-{
-    bool av, ao;
-    if (proto->cvq != var->cvq) {
-        if (!(proto->cvq == CV_CONST && var->cvq == CV_NONE &&
-              !OPTS_FLAG(INITIALIZED_NONCONSTANTS) &&
-              parser->tok == '='))
-        {
-            return !parsewarning(parser, WARN_DIFFERENT_QUALIFIERS,
-                                 "`%s` declared with different qualifiers: %s\n"
-                                 " -> previous declaration here: %s:%i uses %s",
-                                 var->name, cvq_to_str(var->cvq),
-                                 ast_ctx(proto).file, ast_ctx(proto).line,
-                                 cvq_to_str(proto->cvq));
-        }
-    }
-    av = (var  ->expression.flags & AST_FLAG_NORETURN);
-    ao = (proto->expression.flags & AST_FLAG_NORETURN);
-    if (!av != !ao) {
-        return !parsewarning(parser, WARN_DIFFERENT_ATTRIBUTES,
-                             "`%s` declared with different attributes%s\n"
-                             " -> previous declaration here: %s:%i",
-                             var->name, (av ? ": noreturn" : ""),
-                             ast_ctx(proto).file, ast_ctx(proto).line,
-                             (ao ? ": noreturn" : ""));
-    }
-    return true;
-}
-
-static bool create_array_accessors(parser_t *parser, ast_value *var)
-{
-    char name[1024];
-    util_snprintf(name, sizeof(name), "%s##SET", var->name);
-    if (!parser_create_array_setter(parser, var, name))
-        return false;
-    util_snprintf(name, sizeof(name), "%s##GET", var->name);
-    if (!parser_create_array_getter(parser, var, var->expression.next, name))
-        return false;
-    return true;
-}
-
-static bool parse_array(parser_t *parser, ast_value *array)
-{
-    size_t i;
-    if (array->initlist) {
-        parseerror(parser, "array already initialized elsewhere");
-        return false;
-    }
-    if (!parser_next(parser)) {
-        parseerror(parser, "parse error in array initializer");
-        return false;
-    }
-    i = 0;
-    while (parser->tok != '}') {
-        ast_value *v = (ast_value*)parse_expression_leave(parser, true, false, false);
-        if (!v)
-            return false;
-        if (!ast_istype(v, ast_value) || !v->hasvalue || v->cvq != CV_CONST) {
-            ast_unref(v);
-            parseerror(parser, "initializing element must be a compile time constant");
-            return false;
-        }
-        vec_push(array->initlist, v->constval);
-        if (v->expression.vtype == TYPE_STRING) {
-            array->initlist[i].vstring = util_strdupe(array->initlist[i].vstring);
-            ++i;
-        }
-        ast_unref(v);
-        if (parser->tok == '}')
-            break;
-        if (parser->tok != ',' || !parser_next(parser)) {
-            parseerror(parser, "expected comma or '}' in element list");
-            return false;
-        }
-    }
-    if (!parser_next(parser) || parser->tok != ';') {
-        parseerror(parser, "expected semicolon after initializer, got %s");
-        return false;
-    }
-    /*
-    if (!parser_next(parser)) {
-        parseerror(parser, "parse error after initializer");
-        return false;
-    }
-    */
-
-    if (array->expression.flags & AST_FLAG_ARRAY_INIT) {
-        if (array->expression.count != (size_t)-1) {
-            parseerror(parser, "array `%s' has already been initialized with %u elements",
-                       array->name, (unsigned)array->expression.count);
-        }
-        array->expression.count = vec_size(array->initlist);
-        if (!create_array_accessors(parser, array))
-            return false;
-    }
-    return true;
-}
-
-static bool parse_variable(parser_t *parser, ast_block *localblock, bool nofields, int qualifier, ast_value *cached_typedef, bool noref, bool is_static, uint32_t qflags, char *vstring)
-{
-    ast_value *var;
-    ast_value *proto;
-    ast_expression *old;
-    bool       was_end;
-    size_t     i;
-
-    ast_value *basetype = NULL;
-    bool      retval    = true;
-    bool      isparam   = false;
-    bool      isvector  = false;
-    bool      cleanvar  = true;
-    bool      wasarray  = false;
-
-    ast_member *me[3] = { NULL, NULL, NULL };
-    ast_member *last_me[3] = { NULL, NULL, NULL };
-
-    if (!localblock && is_static)
-        parseerror(parser, "`static` qualifier is not supported in global scope");
-
-    /* get the first complete variable */
-    var = parse_typename(parser, &basetype, cached_typedef, NULL);
-    if (!var) {
-        if (basetype)
-            ast_delete(basetype);
-        return false;
-    }
-
-    /* while parsing types, the ast_value's get named '<something>' */
-    if (!var->name || var->name[0] == '<') {
-        parseerror(parser, "declaration does not declare anything");
-        if (basetype)
-            ast_delete(basetype);
-        return false;
-    }
-
-    while (true) {
-        proto = NULL;
-        wasarray = false;
-
-        /* Part 0: finish the type */
-        if (parser->tok == '(') {
-            if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC)
-                parseerror(parser, "C-style function syntax is not allowed in -std=qcc");
-            var = parse_parameter_list(parser, var);
-            if (!var) {
-                retval = false;
-                goto cleanup;
-            }
-        }
-        /* we only allow 1-dimensional arrays */
-        if (parser->tok == '[') {
-            wasarray = true;
-            var = parse_arraysize(parser, var);
-            if (!var) {
-                retval = false;
-                goto cleanup;
-            }
-        }
-        if (parser->tok == '(' && wasarray) {
-            parseerror(parser, "arrays as part of a return type is not supported");
-            /* we'll still parse the type completely for now */
-        }
-        /* for functions returning functions */
-        while (parser->tok == '(') {
-            if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC)
-                parseerror(parser, "C-style function syntax is not allowed in -std=qcc");
-            var = parse_parameter_list(parser, var);
-            if (!var) {
-                retval = false;
-                goto cleanup;
-            }
-        }
-
-        var->cvq = qualifier;
-        if (qflags & AST_FLAG_COVERAGE) /* specified in QC, drop our default */
-            var->expression.flags &= ~(AST_FLAG_COVERAGE_MASK);
-        var->expression.flags |= qflags;
-
-        /*
-         * store the vstring back to var for alias and
-         * deprecation messages.
-         */
-        if (var->expression.flags & AST_FLAG_DEPRECATED ||
-            var->expression.flags & AST_FLAG_ALIAS)
-            var->desc = vstring;
-
-        if (parser_find_global(parser, var->name) && var->expression.flags & AST_FLAG_ALIAS) {
-            parseerror(parser, "function aliases cannot be forward declared");
-            retval = false;
-            goto cleanup;
-        }
-
-
-        /* Part 1:
-         * check for validity: (end_sys_..., multiple-definitions, prototypes, ...)
-         * Also: if there was a prototype, `var` will be deleted and set to `proto` which
-         * is then filled with the previous definition and the parameter-names replaced.
-         */
-        if (!strcmp(var->name, "nil")) {
-            if (OPTS_FLAG(UNTYPED_NIL)) {
-                if (!localblock || !OPTS_FLAG(PERMISSIVE))
-                    parseerror(parser, "name `nil` not allowed (try -fpermissive)");
-            } else
-                (void)!parsewarning(parser, WARN_RESERVED_NAMES, "variable name `nil` is reserved");
-        }
-        if (!localblock) {
-            /* Deal with end_sys_ vars */
-            was_end = false;
-            if (!strcmp(var->name, "end_sys_globals")) {
-                var->uses++;
-                parser->crc_globals = vec_size(parser->globals);
-                was_end = true;
-            }
-            else if (!strcmp(var->name, "end_sys_fields")) {
-                var->uses++;
-                parser->crc_fields = vec_size(parser->fields);
-                was_end = true;
-            }
-            if (was_end && var->expression.vtype == TYPE_FIELD) {
-                if (parsewarning(parser, WARN_END_SYS_FIELDS,
-                                 "global '%s' hint should not be a field",
-                                 parser_tokval(parser)))
-                {
-                    retval = false;
-                    goto cleanup;
-                }
-            }
-
-            if (!nofields && var->expression.vtype == TYPE_FIELD)
-            {
-                /* deal with field declarations */
-                old = parser_find_field(parser, var->name);
-                if (old) {
-                    if (parsewarning(parser, WARN_FIELD_REDECLARED, "field `%s` already declared here: %s:%i",
-                                     var->name, ast_ctx(old).file, (int)ast_ctx(old).line))
-                    {
-                        retval = false;
-                        goto cleanup;
-                    }
-                    ast_delete(var);
-                    var = NULL;
-                    goto skipvar;
-                    /*
-                    parseerror(parser, "field `%s` already declared here: %s:%i",
-                               var->name, ast_ctx(old).file, ast_ctx(old).line);
-                    retval = false;
-                    goto cleanup;
-                    */
-                }
-                if ((OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC || OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_FTEQCC) &&
-                    (old = parser_find_global(parser, var->name)))
-                {
-                    parseerror(parser, "cannot declare a field and a global of the same name with -std=qcc");
-                    parseerror(parser, "field `%s` already declared here: %s:%i",
-                               var->name, ast_ctx(old).file, ast_ctx(old).line);
-                    retval = false;
-                    goto cleanup;
-                }
-            }
-            else
-            {
-                /* deal with other globals */
-                old = parser_find_global(parser, var->name);
-                if (old && var->expression.vtype == TYPE_FUNCTION && old->vtype == TYPE_FUNCTION)
-                {
-                    /* This is a function which had a prototype */
-                    if (!ast_istype(old, ast_value)) {
-                        parseerror(parser, "internal error: prototype is not an ast_value");
-                        retval = false;
-                        goto cleanup;
-                    }
-                    proto = (ast_value*)old;
-                    proto->desc = var->desc;
-                    if (!ast_compare_type((ast_expression*)proto, (ast_expression*)var)) {
-                        parseerror(parser, "conflicting types for `%s`, previous declaration was here: %s:%i",
-                                   proto->name,
-                                   ast_ctx(proto).file, ast_ctx(proto).line);
-                        retval = false;
-                        goto cleanup;
-                    }
-                    /* we need the new parameter-names */
-                    for (i = 0; i < vec_size(proto->expression.params); ++i)
-                        ast_value_set_name(proto->expression.params[i], var->expression.params[i]->name);
-                    if (!parser_check_qualifiers(parser, var, proto)) {
-                        retval = false;
-                        if (proto->desc)
-                            mem_d(proto->desc);
-                        proto = NULL;
-                        goto cleanup;
-                    }
-                    proto->expression.flags |= var->expression.flags;
-                    ast_delete(var);
-                    var = proto;
-                }
-                else
-                {
-                    /* other globals */
-                    if (old) {
-                        if (parsewarning(parser, WARN_DOUBLE_DECLARATION,
-                                         "global `%s` already declared here: %s:%i",
-                                         var->name, ast_ctx(old).file, ast_ctx(old).line))
-                        {
-                            retval = false;
-                            goto cleanup;
-                        }
-                        if (old->flags & AST_FLAG_FINAL_DECL) {
-                            parseerror(parser, "cannot redeclare variable `%s`, declared final here: %s:%i",
-                                       var->name, ast_ctx(old).file, ast_ctx(old).line);
-                            retval = false;
-                            goto cleanup;
-                        }
-                        proto = (ast_value*)old;
-                        if (!ast_istype(old, ast_value)) {
-                            parseerror(parser, "internal error: not an ast_value");
-                            retval = false;
-                            proto = NULL;
-                            goto cleanup;
-                        }
-                        if (!parser_check_qualifiers(parser, var, proto)) {
-                            retval = false;
-                            proto = NULL;
-                            goto cleanup;
-                        }
-                        proto->expression.flags |= var->expression.flags;
-                        /* copy the context for finals,
-                         * so the error can show where it was actually made 'final'
-                         */
-                        if (proto->expression.flags & AST_FLAG_FINAL_DECL)
-                            ast_ctx(old) = ast_ctx(var);
-                        ast_delete(var);
-                        var = proto;
-                    }
-                    if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC &&
-                        (old = parser_find_field(parser, var->name)))
-                    {
-                        parseerror(parser, "cannot declare a field and a global of the same name with -std=qcc");
-                        parseerror(parser, "global `%s` already declared here: %s:%i",
-                                   var->name, ast_ctx(old).file, ast_ctx(old).line);
-                        retval = false;
-                        goto cleanup;
-                    }
-                }
-            }
-        }
-        else /* it's not a global */
-        {
-            old = parser_find_local(parser, var->name, vec_size(parser->variables)-1, &isparam);
-            if (old && !isparam) {
-                parseerror(parser, "local `%s` already declared here: %s:%i",
-                           var->name, ast_ctx(old).file, (int)ast_ctx(old).line);
-                retval = false;
-                goto cleanup;
-            }
-            /* doing this here as the above is just for a single scope */
-            old = parser_find_local(parser, var->name, 0, &isparam);
-            if (old && isparam) {
-                if (parsewarning(parser, WARN_LOCAL_SHADOWS,
-                                 "local `%s` is shadowing a parameter", var->name))
-                {
-                    parseerror(parser, "local `%s` already declared here: %s:%i",
-                               var->name, ast_ctx(old).file, (int)ast_ctx(old).line);
-                    retval = false;
-                    goto cleanup;
-                }
-                if (OPTS_OPTION_U32(OPTION_STANDARD) != COMPILER_GMQCC) {
-                    ast_delete(var);
-                    if (ast_istype(old, ast_value))
-                        var = proto = (ast_value*)old;
-                    else {
-                        var = NULL;
-                        goto skipvar;
-                    }
-                }
-            }
-        }
-
-        /* in a noref section we simply bump the usecount */
-        if (noref || parser->noref)
-            var->uses++;
-
-        /* Part 2:
-         * Create the global/local, and deal with vector types.
-         */
-        if (!proto) {
-            if (var->expression.vtype == TYPE_VECTOR)
-                isvector = true;
-            else if (var->expression.vtype == TYPE_FIELD &&
-                     var->expression.next->vtype == TYPE_VECTOR)
-                isvector = true;
-
-            if (isvector) {
-                if (!create_vector_members(var, me)) {
-                    retval = false;
-                    goto cleanup;
-                }
-            }
-
-            if (!localblock) {
-                /* deal with global variables, fields, functions */
-                if (!nofields && var->expression.vtype == TYPE_FIELD && parser->tok != '=') {
-                    var->isfield = true;
-                    vec_push(parser->fields, (ast_expression*)var);
-                    util_htset(parser->htfields, var->name, var);
-                    if (isvector) {
-                        for (i = 0; i < 3; ++i) {
-                            vec_push(parser->fields, (ast_expression*)me[i]);
-                            util_htset(parser->htfields, me[i]->name, me[i]);
-                        }
-                    }
-                }
-                else {
-                    if (!(var->expression.flags & AST_FLAG_ALIAS)) {
-                        parser_addglobal(parser, var->name, (ast_expression*)var);
-                        if (isvector) {
-                            for (i = 0; i < 3; ++i) {
-                                parser_addglobal(parser, me[i]->name, (ast_expression*)me[i]);
-                            }
-                        }
-                    } else {
-                        ast_expression *find  = parser_find_global(parser, var->desc);
-
-                        if (!find) {
-                            compile_error(parser_ctx(parser), "undeclared variable `%s` for alias `%s`", var->desc, var->name);
-                            return false;
-                        }
-
-                        if (!ast_compare_type((ast_expression*)var, find)) {
-                            char ty1[1024];
-                            char ty2[1024];
-
-                            ast_type_to_string(find,                  ty1, sizeof(ty1));
-                            ast_type_to_string((ast_expression*)var,  ty2, sizeof(ty2));
-
-                            compile_error(parser_ctx(parser), "incompatible types `%s` and `%s` for alias `%s`",
-                                ty1, ty2, var->name
-                            );
-                            return false;
-                        }
-
-                        /*
-                         * add alias to aliases table and to corrector
-                         * so corrections can apply for aliases as well.
-                         */
-                        util_htset(parser->aliases, var->name, find);
-
-                        /*
-                         * add to corrector so corrections can work
-                         * even for aliases too.
-                         */
-                        correct_add (
-                             vec_last(parser->correct_variables),
-                            &vec_last(parser->correct_variables_score),
-                            var->name
-                        );
-
-                        /* generate aliases for vector components */
-                        if (isvector) {
-                            char *buffer[3];
-
-                            util_asprintf(&buffer[0], "%s_x", var->desc);
-                            util_asprintf(&buffer[1], "%s_y", var->desc);
-                            util_asprintf(&buffer[2], "%s_z", var->desc);
-
-                            util_htset(parser->aliases, me[0]->name, parser_find_global(parser, buffer[0]));
-                            util_htset(parser->aliases, me[1]->name, parser_find_global(parser, buffer[1]));
-                            util_htset(parser->aliases, me[2]->name, parser_find_global(parser, buffer[2]));
-
-                            mem_d(buffer[0]);
-                            mem_d(buffer[1]);
-                            mem_d(buffer[2]);
-
-                            /*
-                             * add to corrector so corrections can work
-                             * even for aliases too.
-                             */
-                            correct_add (
-                                 vec_last(parser->correct_variables),
-                                &vec_last(parser->correct_variables_score),
-                                me[0]->name
-                            );
-                            correct_add (
-                                 vec_last(parser->correct_variables),
-                                &vec_last(parser->correct_variables_score),
-                                me[1]->name
-                            );
-                            correct_add (
-                                 vec_last(parser->correct_variables),
-                                &vec_last(parser->correct_variables_score),
-                                me[2]->name
-                            );
-                        }
-                    }
-                }
-            } else {
-                if (is_static) {
-                    /* a static adds itself to be generated like any other global
-                     * but is added to the local namespace instead
-                     */
-                    char   *defname = NULL;
-                    size_t  prefix_len, ln;
-                    size_t  sn, sn_size;
-
-                    ln = strlen(parser->function->name);
-                    vec_append(defname, ln, parser->function->name);
-
-                    vec_append(defname, 2, "::");
-                    /* remember the length up to here */
-                    prefix_len = vec_size(defname);
-
-                    /* Add it to the local scope */
-                    util_htset(vec_last(parser->variables), var->name, (void*)var);
-
-                    /* corrector */
-                    correct_add (
-                         vec_last(parser->correct_variables),
-                        &vec_last(parser->correct_variables_score),
-                        var->name
-                    );
-
-                    /* now rename the global */
-                    ln = strlen(var->name);
-                    vec_append(defname, ln, var->name);
-                    /* if a variable of that name already existed, add the
-                     * counter value.
-                     * The counter is incremented either way.
-                     */
-                    sn_size = vec_size(parser->function->static_names);
-                    for (sn = 0; sn != sn_size; ++sn) {
-                        if (strcmp(parser->function->static_names[sn], var->name) == 0)
-                            break;
-                    }
-                    if (sn != sn_size) {
-                        char *num = NULL;
-                        int   len = util_asprintf(&num, "#%u", parser->function->static_count);
-                        vec_append(defname, len, num);
-                        mem_d(num);
-                    }
-                    else
-                        vec_push(parser->function->static_names, util_strdup(var->name));
-                    parser->function->static_count++;
-                    ast_value_set_name(var, defname);
-
-                    /* push it to the to-be-generated globals */
-                    vec_push(parser->globals, (ast_expression*)var);
-
-                    /* same game for the vector members */
-                    if (isvector) {
-                        for (i = 0; i < 3; ++i) {
-                            util_htset(vec_last(parser->variables), me[i]->name, (void*)(me[i]));
-
-                            /* corrector */
-                            correct_add(
-                                 vec_last(parser->correct_variables),
-                                &vec_last(parser->correct_variables_score),
-                                me[i]->name
-                            );
-
-                            vec_shrinkto(defname, prefix_len);
-                            ln = strlen(me[i]->name);
-                            vec_append(defname, ln, me[i]->name);
-                            ast_member_set_name(me[i], defname);
-
-                            vec_push(parser->globals, (ast_expression*)me[i]);
-                        }
-                    }
-                    vec_free(defname);
-                } else {
-                    vec_push(localblock->locals, var);
-                    parser_addlocal(parser, var->name, (ast_expression*)var);
-                    if (isvector) {
-                        for (i = 0; i < 3; ++i) {
-                            parser_addlocal(parser, me[i]->name, (ast_expression*)me[i]);
-                            ast_block_collect(localblock, (ast_expression*)me[i]);
-                        }
-                    }
-                }
-            }
-        }
-        memcpy(last_me, me, sizeof(me));
-        me[0] = me[1] = me[2] = NULL;
-        cleanvar = false;
-        /* Part 2.2
-         * deal with arrays
-         */
-        if (var->expression.vtype == TYPE_ARRAY) {
-            if (var->expression.count != (size_t)-1) {
-                if (!create_array_accessors(parser, var))
-                    goto cleanup;
-            }
-        }
-        else if (!localblock && !nofields &&
-                 var->expression.vtype == TYPE_FIELD &&
-                 var->expression.next->vtype == TYPE_ARRAY)
-        {
-            char name[1024];
-            ast_expression *telem;
-            ast_value      *tfield;
-            ast_value      *array = (ast_value*)var->expression.next;
-
-            if (!ast_istype(var->expression.next, ast_value)) {
-                parseerror(parser, "internal error: field element type must be an ast_value");
-                goto cleanup;
-            }
-
-            util_snprintf(name, sizeof(name), "%s##SETF", var->name);
-            if (!parser_create_array_field_setter(parser, array, name))
-                goto cleanup;
-
-            telem = ast_type_copy(ast_ctx(var), array->expression.next);
-            tfield = ast_value_new(ast_ctx(var), "<.type>", TYPE_FIELD);
-            tfield->expression.next = telem;
-            util_snprintf(name, sizeof(name), "%s##GETFP", var->name);
-            if (!parser_create_array_getter(parser, array, (ast_expression*)tfield, name)) {
-                ast_delete(tfield);
-                goto cleanup;
-            }
-            ast_delete(tfield);
-        }
-
-skipvar:
-        if (parser->tok == ';') {
-            ast_delete(basetype);
-            if (!parser_next(parser)) {
-                parseerror(parser, "error after variable declaration");
-                return false;
-            }
-            return true;
-        }
-
-        if (parser->tok == ',')
-            goto another;
-
-        /*
-        if (!var || (!localblock && !nofields && basetype->expression.vtype == TYPE_FIELD)) {
-        */
-        if (!var) {
-            parseerror(parser, "missing comma or semicolon while parsing variables");
-            break;
-        }
-
-        if (localblock && OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) {
-            if (parsewarning(parser, WARN_LOCAL_CONSTANTS,
-                             "initializing expression turns variable `%s` into a constant in this standard",
-                             var->name) )
-            {
-                break;
-            }
-        }
-
-        if (parser->tok != '{' || var->expression.vtype != TYPE_FUNCTION) {
-            if (parser->tok != '=') {
-                parseerror(parser, "missing semicolon or initializer, got: `%s`", parser_tokval(parser));
-                break;
-            }
-
-            if (!parser_next(parser)) {
-                parseerror(parser, "error parsing initializer");
-                break;
-            }
-        }
-        else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) {
-            parseerror(parser, "expected '=' before function body in this standard");
-        }
-
-        if (parser->tok == '#') {
-            ast_function *func   = NULL;
-            ast_value    *number = NULL;
-            float         fractional;
-            float         integral;
-            int           builtin_num;
-
-            if (localblock) {
-                parseerror(parser, "cannot declare builtins within functions");
-                break;
-            }
-            if (var->expression.vtype != TYPE_FUNCTION) {
-                parseerror(parser, "unexpected builtin number, '%s' is not a function", var->name);
-                break;
-            }
-            if (!parser_next(parser)) {
-                parseerror(parser, "expected builtin number");
-                break;
-            }
-
-            if (OPTS_FLAG(EXPRESSIONS_FOR_BUILTINS)) {
-                number = (ast_value*)parse_expression_leave(parser, true, false, false);
-                if (!number) {
-                    parseerror(parser, "builtin number expected");
-                    break;
-                }
-                if (!ast_istype(number, ast_value) || !number->hasvalue || number->cvq != CV_CONST)
-                {
-                    ast_unref(number);
-                    parseerror(parser, "builtin number must be a compile time constant");
-                    break;
-                }
-                if (number->expression.vtype == TYPE_INTEGER)
-                    builtin_num = number->constval.vint;
-                else if (number->expression.vtype == TYPE_FLOAT)
-                    builtin_num = number->constval.vfloat;
-                else {
-                    ast_unref(number);
-                    parseerror(parser, "builtin number must be an integer constant");
-                    break;
-                }
-                ast_unref(number);
-
-                fractional = modff(builtin_num, &integral);
-                if (builtin_num < 0 || fractional != 0) {
-                    parseerror(parser, "builtin number must be an integer greater than zero");
-                    break;
-                }
-
-                /* we only want the integral part anyways */
-                builtin_num = integral;
-            } else if (parser->tok == TOKEN_INTCONST) {
-                builtin_num = parser_token(parser)->constval.i;
-            } else {
-                parseerror(parser, "builtin number must be a compile time constant");
-                break;
-            }
-
-            if (var->hasvalue) {
-                (void)!parsewarning(parser, WARN_DOUBLE_DECLARATION,
-                                    "builtin `%s` has already been defined\n"
-                                    " -> previous declaration here: %s:%i",
-                                    var->name, ast_ctx(var).file, (int)ast_ctx(var).line);
-            }
-            else
-            {
-                func = ast_function_new(ast_ctx(var), var->name, var);
-                if (!func) {
-                    parseerror(parser, "failed to allocate function for `%s`", var->name);
-                    break;
-                }
-                vec_push(parser->functions, func);
-
-                func->builtin = -builtin_num-1;
-            }
-
-            if (OPTS_FLAG(EXPRESSIONS_FOR_BUILTINS)
-                    ? (parser->tok != ',' && parser->tok != ';')
-                    : (!parser_next(parser)))
-            {
-                parseerror(parser, "expected comma or semicolon");
-                if (func)
-                    ast_function_delete(func);
-                var->constval.vfunc = NULL;
-                break;
-            }
-        }
-        else if (var->expression.vtype == TYPE_ARRAY && parser->tok == '{')
-        {
-            if (localblock) {
-                /* Note that fteqcc and most others don't even *have*
-                 * local arrays, so this is not a high priority.
-                 */
-                parseerror(parser, "TODO: initializers for local arrays");
-                break;
-            }
-
-            var->hasvalue = true;
-            if (!parse_array(parser, var))
-                break;
-        }
-        else if (var->expression.vtype == TYPE_FUNCTION && (parser->tok == '{' || parser->tok == '['))
-        {
-            if (localblock) {
-                parseerror(parser, "cannot declare functions within functions");
-                break;
-            }
-
-            if (proto)
-                ast_ctx(proto) = parser_ctx(parser);
-
-            if (!parse_function_body(parser, var))
-                break;
-            ast_delete(basetype);
-            for (i = 0; i < vec_size(parser->gotos); ++i)
-                parseerror(parser, "undefined label: `%s`", parser->gotos[i]->name);
-            vec_free(parser->gotos);
-            vec_free(parser->labels);
-            return true;
-        } else {
-            ast_expression *cexp;
-            ast_value      *cval;
-            bool            folded_const = false;
-
-            cexp = parse_expression_leave(parser, true, false, false);
-            if (!cexp)
-                break;
-            cval = ast_istype(cexp, ast_value) ? (ast_value*)cexp : NULL;
-
-            /* deal with foldable constants: */
-            if (localblock &&
-                var->cvq == CV_CONST && cval && cval->hasvalue && cval->cvq == CV_CONST && !cval->isfield)
-            {
-                /* remove it from the current locals */
-                if (isvector) {
-                    for (i = 0; i < 3; ++i) {
-                        vec_pop(parser->_locals);
-                        vec_pop(localblock->collect);
-                    }
-                }
-                /* do sanity checking, this function really needs refactoring */
-                if (vec_last(parser->_locals) != (ast_expression*)var)
-                    parseerror(parser, "internal error: unexpected change in local variable handling");
-                else
-                    vec_pop(parser->_locals);
-                if (vec_last(localblock->locals) != var)
-                    parseerror(parser, "internal error: unexpected change in local variable handling (2)");
-                else
-                    vec_pop(localblock->locals);
-                /* push it to the to-be-generated globals */
-                vec_push(parser->globals, (ast_expression*)var);
-                if (isvector)
-                    for (i = 0; i < 3; ++i)
-                        vec_push(parser->globals, (ast_expression*)last_me[i]);
-                folded_const = true;
-            }
-
-            if (folded_const || !localblock || is_static) {
-                if (cval != parser->nil &&
-                    (!cval || ((!cval->hasvalue || cval->cvq != CV_CONST) && !cval->isfield))
-                   )
-                {
-                    parseerror(parser, "initializer is non constant");
-                }
-                else
-                {
-                    if (!is_static &&
-                        !OPTS_FLAG(INITIALIZED_NONCONSTANTS) &&
-                        qualifier != CV_VAR)
-                    {
-                        var->cvq = CV_CONST;
-                    }
-                    if (cval == parser->nil)
-                        var->expression.flags |= AST_FLAG_INITIALIZED;
-                    else
-                    {
-                        var->hasvalue = true;
-                        if (cval->expression.vtype == TYPE_STRING)
-                            var->constval.vstring = parser_strdup(cval->constval.vstring);
-                        else if (cval->expression.vtype == TYPE_FIELD)
-                            var->constval.vfield = cval;
-                        else
-                            memcpy(&var->constval, &cval->constval, sizeof(var->constval));
-                        ast_unref(cval);
-                    }
-                }
-            } else {
-                int cvq;
-                shunt sy = { NULL, NULL, NULL, NULL };
-                cvq = var->cvq;
-                var->cvq = CV_NONE;
-                vec_push(sy.out, syexp(ast_ctx(var), (ast_expression*)var));
-                vec_push(sy.out, syexp(ast_ctx(cexp), (ast_expression*)cexp));
-                vec_push(sy.ops, syop(ast_ctx(var), parser->assign_op));
-                if (!parser_sy_apply_operator(parser, &sy))
-                    ast_unref(cexp);
-                else {
-                    if (vec_size(sy.out) != 1 && vec_size(sy.ops) != 0)
-                        parseerror(parser, "internal error: leaked operands");
-                    if (!ast_block_add_expr(localblock, (ast_expression*)sy.out[0].out))
-                        break;
-                }
-                vec_free(sy.out);
-                vec_free(sy.ops);
-                vec_free(sy.argc);
-                var->cvq = cvq;
-            }
-            /* a constant initialized to an inexact value should be marked inexact:
-             * const float x = <inexact>; should propagate the inexact flag
-             */
-            if (var->cvq == CV_CONST && var->expression.vtype == TYPE_FLOAT) {
-                if (cval && cval->hasvalue && cval->cvq == CV_CONST)
-                    var->inexact = cval->inexact;
-            }
-        }
-
-another:
-        if (parser->tok == ',') {
-            if (!parser_next(parser)) {
-                parseerror(parser, "expected another variable");
-                break;
-            }
-
-            if (parser->tok != TOKEN_IDENT) {
-                parseerror(parser, "expected another variable");
-                break;
-            }
-            var = ast_value_copy(basetype);
-            cleanvar = true;
-            ast_value_set_name(var, parser_tokval(parser));
-            if (!parser_next(parser)) {
-                parseerror(parser, "error parsing variable declaration");
-                break;
-            }
-            continue;
-        }
-
-        if (parser->tok != ';') {
-            parseerror(parser, "missing semicolon after variables");
-            break;
-        }
-
-        if (!parser_next(parser)) {
-            parseerror(parser, "parse error after variable declaration");
-            break;
-        }
-
-        ast_delete(basetype);
-        return true;
-    }
-
-    if (cleanvar && var)
-        ast_delete(var);
-    ast_delete(basetype);
-    return false;
-
-cleanup:
-    ast_delete(basetype);
-    if (cleanvar && var)
-        ast_delete(var);
-    if (me[0]) ast_member_delete(me[0]);
-    if (me[1]) ast_member_delete(me[1]);
-    if (me[2]) ast_member_delete(me[2]);
-    return retval;
-}
-
-static bool parser_global_statement(parser_t *parser)
-{
-    int        cvq       = CV_WRONG;
-    bool       noref     = false;
-    bool       is_static = false;
-    uint32_t   qflags    = 0;
-    ast_value *istype    = NULL;
-    char      *vstring   = NULL;
-
-    if (parser->tok == TOKEN_IDENT)
-        istype = parser_find_typedef(parser, parser_tokval(parser), 0);
-
-    if (istype || parser->tok == TOKEN_TYPENAME || parser->tok == '.' || parser->tok == TOKEN_DOTS)
-    {
-        return parse_variable(parser, NULL, false, CV_NONE, istype, false, false, 0, NULL);
-    }
-    else if (parse_qualifiers(parser, false, &cvq, &noref, &is_static, &qflags, &vstring))
-    {
-        if (cvq == CV_WRONG)
-            return false;
-        return parse_variable(parser, NULL, false, cvq, NULL, noref, is_static, qflags, vstring);
-    }
-    else if (parser->tok == TOKEN_IDENT && !strcmp(parser_tokval(parser), "enum"))
-    {
-        return parse_enum(parser);
-    }
-    else if (parser->tok == TOKEN_KEYWORD)
-    {
-        if (!strcmp(parser_tokval(parser), "typedef")) {
-            if (!parser_next(parser)) {
-                parseerror(parser, "expected type definition after 'typedef'");
-                return false;
-            }
-            return parse_typedef(parser);
-        }
-        parseerror(parser, "unrecognized keyword `%s`", parser_tokval(parser));
-        return false;
-    }
-    else if (parser->tok == '#')
-    {
-        return parse_pragma(parser);
-    }
-    else if (parser->tok == '$')
-    {
-        if (!parser_next(parser)) {
-            parseerror(parser, "parse error");
-            return false;
-        }
-    }
-    else
-    {
-        parseerror(parser, "unexpected token: `%s`", parser->lex->tok.value);
-        return false;
-    }
-    return true;
-}
-
-static uint16_t progdefs_crc_sum(uint16_t old, const char *str)
-{
-    return util_crc16(old, str, strlen(str));
-}
-
-static void progdefs_crc_file(const char *str)
-{
-    /* write to progdefs.h here */
-    (void)str;
-}
-
-static uint16_t progdefs_crc_both(uint16_t old, const char *str)
-{
-    old = progdefs_crc_sum(old, str);
-    progdefs_crc_file(str);
-    return old;
-}
-
-static void generate_checksum(parser_t *parser, ir_builder *ir)
-{
-    uint16_t   crc = 0xFFFF;
-    size_t     i;
-    ast_value *value;
-
-    crc = progdefs_crc_both(crc, "\n/* file generated by qcc, do not modify */\n\ntypedef struct\n{");
-    crc = progdefs_crc_sum(crc, "\tint\tpad[28];\n");
-    /*
-    progdefs_crc_file("\tint\tpad;\n");
-    progdefs_crc_file("\tint\tofs_return[3];\n");
-    progdefs_crc_file("\tint\tofs_parm0[3];\n");
-    progdefs_crc_file("\tint\tofs_parm1[3];\n");
-    progdefs_crc_file("\tint\tofs_parm2[3];\n");
-    progdefs_crc_file("\tint\tofs_parm3[3];\n");
-    progdefs_crc_file("\tint\tofs_parm4[3];\n");
-    progdefs_crc_file("\tint\tofs_parm5[3];\n");
-    progdefs_crc_file("\tint\tofs_parm6[3];\n");
-    progdefs_crc_file("\tint\tofs_parm7[3];\n");
-    */
-    for (i = 0; i < parser->crc_globals; ++i) {
-        if (!ast_istype(parser->globals[i], ast_value))
-            continue;
-        value = (ast_value*)(parser->globals[i]);
-        switch (value->expression.vtype) {
-            case TYPE_FLOAT:    crc = progdefs_crc_both(crc, "\tfloat\t"); break;
-            case TYPE_VECTOR:   crc = progdefs_crc_both(crc, "\tvec3_t\t"); break;
-            case TYPE_STRING:   crc = progdefs_crc_both(crc, "\tstring_t\t"); break;
-            case TYPE_FUNCTION: crc = progdefs_crc_both(crc, "\tfunc_t\t"); break;
-            default:
-                crc = progdefs_crc_both(crc, "\tint\t");
-                break;
-        }
-        crc = progdefs_crc_both(crc, value->name);
-        crc = progdefs_crc_both(crc, ";\n");
-    }
-    crc = progdefs_crc_both(crc, "} globalvars_t;\n\ntypedef struct\n{\n");
-    for (i = 0; i < parser->crc_fields; ++i) {
-        if (!ast_istype(parser->fields[i], ast_value))
-            continue;
-        value = (ast_value*)(parser->fields[i]);
-        switch (value->expression.next->vtype) {
-            case TYPE_FLOAT:    crc = progdefs_crc_both(crc, "\tfloat\t"); break;
-            case TYPE_VECTOR:   crc = progdefs_crc_both(crc, "\tvec3_t\t"); break;
-            case TYPE_STRING:   crc = progdefs_crc_both(crc, "\tstring_t\t"); break;
-            case TYPE_FUNCTION: crc = progdefs_crc_both(crc, "\tfunc_t\t"); break;
-            default:
-                crc = progdefs_crc_both(crc, "\tint\t");
-                break;
-        }
-        crc = progdefs_crc_both(crc, value->name);
-        crc = progdefs_crc_both(crc, ";\n");
-    }
-    crc = progdefs_crc_both(crc, "} entvars_t;\n\n");
-    ir->code->crc = crc;
-}
-
-parser_t *parser_create()
-{
-    parser_t *parser;
-    lex_ctx_t empty_ctx;
-    size_t i;
-
-    parser = (parser_t*)mem_a(sizeof(parser_t));
-    if (!parser)
-        return NULL;
-
-    memset(parser, 0, sizeof(*parser));
-
-    for (i = 0; i < operator_count; ++i) {
-        if (operators[i].id == opid1('=')) {
-            parser->assign_op = operators+i;
-            break;
-        }
-    }
-    if (!parser->assign_op) {
-        con_err("internal error: initializing parser: failed to find assign operator\n");
-        mem_d(parser);
-        return NULL;
-    }
-
-    vec_push(parser->variables, parser->htfields  = util_htnew(PARSER_HT_SIZE));
-    vec_push(parser->variables, parser->htglobals = util_htnew(PARSER_HT_SIZE));
-    vec_push(parser->typedefs, util_htnew(TYPEDEF_HT_SIZE));
-    vec_push(parser->_blocktypedefs, 0);
-
-    parser->aliases = util_htnew(PARSER_HT_SIZE);
-
-    /* corrector */
-    vec_push(parser->correct_variables, correct_trie_new());
-    vec_push(parser->correct_variables_score, NULL);
-
-    empty_ctx.file   = "<internal>";
-    empty_ctx.line   = 0;
-    empty_ctx.column = 0;
-    parser->nil = ast_value_new(empty_ctx, "nil", TYPE_NIL);
-    parser->nil->cvq = CV_CONST;
-    if (OPTS_FLAG(UNTYPED_NIL))
-        util_htset(parser->htglobals, "nil", (void*)parser->nil);
-
-    parser->max_param_count = 1;
-
-    parser->const_vec[0] = ast_value_new(empty_ctx, "<vector.x>", TYPE_NOEXPR);
-    parser->const_vec[1] = ast_value_new(empty_ctx, "<vector.y>", TYPE_NOEXPR);
-    parser->const_vec[2] = ast_value_new(empty_ctx, "<vector.z>", TYPE_NOEXPR);
-
-    if (OPTS_OPTION_BOOL(OPTION_ADD_INFO)) {
-        parser->reserved_version = ast_value_new(empty_ctx, "reserved:version", TYPE_STRING);
-        parser->reserved_version->cvq = CV_CONST;
-        parser->reserved_version->hasvalue = true;
-        parser->reserved_version->expression.flags |= AST_FLAG_INCLUDE_DEF;
-        parser->reserved_version->constval.vstring = util_strdup(GMQCC_FULL_VERSION_STRING);
-    } else {
-        parser->reserved_version = NULL;
-    }
-
-    parser->fold   = fold_init  (parser);
-    parser->intrin = intrin_init(parser);
-    return parser;
-}
-
-static bool parser_compile(parser_t *parser)
-{
-    /* initial lexer/parser state */
-    parser->lex->flags.noops = true;
-
-    if (parser_next(parser))
-    {
-        while (parser->tok != TOKEN_EOF && parser->tok < TOKEN_ERROR)
-        {
-            if (!parser_global_statement(parser)) {
-                if (parser->tok == TOKEN_EOF)
-                    parseerror(parser, "unexpected end of file");
-                else if (compile_errors)
-                    parseerror(parser, "there have been errors, bailing out");
-                lex_close(parser->lex);
-                parser->lex = NULL;
-                return false;
-            }
-        }
-    } else {
-        parseerror(parser, "parse error");
-        lex_close(parser->lex);
-        parser->lex = NULL;
-        return false;
-    }
-
-    lex_close(parser->lex);
-    parser->lex = NULL;
-
-    return !compile_errors;
-}
-
-bool parser_compile_file(parser_t *parser, const char *filename)
-{
-    parser->lex = lex_open(filename);
-    if (!parser->lex) {
-        con_err("failed to open file \"%s\"\n", filename);
-        return false;
-    }
-    return parser_compile(parser);
-}
-
-bool parser_compile_string(parser_t *parser, const char *name, const char *str, size_t len)
-{
-    parser->lex = lex_open_string(str, len, name);
-    if (!parser->lex) {
-        con_err("failed to create lexer for string \"%s\"\n", name);
-        return false;
-    }
-    return parser_compile(parser);
-}
-
-static void parser_remove_ast(parser_t *parser)
-{
-    size_t i;
-    if (parser->ast_cleaned)
-        return;
-    parser->ast_cleaned = true;
-    for (i = 0; i < vec_size(parser->accessors); ++i) {
-        ast_delete(parser->accessors[i]->constval.vfunc);
-        parser->accessors[i]->constval.vfunc = NULL;
-        ast_delete(parser->accessors[i]);
-    }
-    for (i = 0; i < vec_size(parser->functions); ++i) {
-        ast_delete(parser->functions[i]);
-    }
-    for (i = 0; i < vec_size(parser->fields); ++i) {
-        ast_delete(parser->fields[i]);
-    }
-    for (i = 0; i < vec_size(parser->globals); ++i) {
-        ast_delete(parser->globals[i]);
-    }
-    vec_free(parser->accessors);
-    vec_free(parser->functions);
-    vec_free(parser->globals);
-    vec_free(parser->fields);
-
-    for (i = 0; i < vec_size(parser->variables); ++i)
-        util_htdel(parser->variables[i]);
-    vec_free(parser->variables);
-    vec_free(parser->_blocklocals);
-    vec_free(parser->_locals);
-
-    /* corrector */
-    for (i = 0; i < vec_size(parser->correct_variables); ++i) {
-        correct_del(parser->correct_variables[i], parser->correct_variables_score[i]);
-    }
-    vec_free(parser->correct_variables);
-    vec_free(parser->correct_variables_score);
-
-    for (i = 0; i < vec_size(parser->_typedefs); ++i)
-        ast_delete(parser->_typedefs[i]);
-    vec_free(parser->_typedefs);
-    for (i = 0; i < vec_size(parser->typedefs); ++i)
-        util_htdel(parser->typedefs[i]);
-    vec_free(parser->typedefs);
-    vec_free(parser->_blocktypedefs);
-
-    vec_free(parser->_block_ctx);
-
-    vec_free(parser->labels);
-    vec_free(parser->gotos);
-    vec_free(parser->breaks);
-    vec_free(parser->continues);
-
-    ast_value_delete(parser->nil);
-
-    ast_value_delete(parser->const_vec[0]);
-    ast_value_delete(parser->const_vec[1]);
-    ast_value_delete(parser->const_vec[2]);
-
-    if (parser->reserved_version)
-        ast_value_delete(parser->reserved_version);
-
-    util_htdel(parser->aliases);
-    fold_cleanup(parser->fold);
-    intrin_cleanup(parser->intrin);
-}
-
-void parser_cleanup(parser_t *parser)
-{
-    parser_remove_ast(parser);
-    mem_d(parser);
-}
-
-static bool parser_set_coverage_func(parser_t *parser, ir_builder *ir) {
-    size_t          i;
-    ast_expression *expr;
-    ast_value      *cov;
-    ast_function   *func;
-
-    if (!OPTS_OPTION_BOOL(OPTION_COVERAGE))
-        return true;
-
-    func = NULL;
-    for (i = 0; i != vec_size(parser->functions); ++i) {
-        if (!strcmp(parser->functions[i]->name, "coverage")) {
-            func = parser->functions[i];
-            break;
-        }
-    }
-    if (!func) {
-        if (OPTS_OPTION_BOOL(OPTION_COVERAGE)) {
-            con_out("coverage support requested but no coverage() builtin declared\n");
-            ir_builder_delete(ir);
-            return false;
-        }
-        return true;
-    }
-
-    cov  = func->vtype;
-    expr = (ast_expression*)cov;
-
-    if (expr->vtype != TYPE_FUNCTION || vec_size(expr->params) != 0) {
-        char ty[1024];
-        ast_type_to_string(expr, ty, sizeof(ty));
-        con_out("invalid type for coverage(): %s\n", ty);
-        ir_builder_delete(ir);
-        return false;
-    }
-
-    ir->coverage_func = func->ir_func->value;
-    return true;
-}
-
-bool parser_finish(parser_t *parser, const char *output)
-{
-    size_t          i;
-    ir_builder     *ir;
-    bool            retval = true;
-
-    if (compile_errors) {
-        con_out("*** there were compile errors\n");
-        return false;
-    }
-
-    ir = ir_builder_new("gmqcc_out");
-    if (!ir) {
-        con_out("failed to allocate builder\n");
-        return false;
-    }
-
-    for (i = 0; i < vec_size(parser->fields); ++i) {
-        ast_value *field;
-        bool hasvalue;
-        if (!ast_istype(parser->fields[i], ast_value))
-            continue;
-        field = (ast_value*)parser->fields[i];
-        hasvalue = field->hasvalue;
-        field->hasvalue = false;
-        if (!ast_global_codegen((ast_value*)field, ir, true)) {
-            con_out("failed to generate field %s\n", field->name);
-            ir_builder_delete(ir);
-            return false;
-        }
-        if (hasvalue) {
-            ir_value *ifld;
-            ast_expression *subtype;
-            field->hasvalue = true;
-            subtype = field->expression.next;
-            ifld = ir_builder_create_field(ir, field->name, subtype->vtype);
-            if (subtype->vtype == TYPE_FIELD)
-                ifld->fieldtype = subtype->next->vtype;
-            else if (subtype->vtype == TYPE_FUNCTION)
-                ifld->outtype = subtype->next->vtype;
-            (void)!ir_value_set_field(field->ir_v, ifld);
-        }
-    }
-    for (i = 0; i < vec_size(parser->globals); ++i) {
-        ast_value *asvalue;
-        if (!ast_istype(parser->globals[i], ast_value))
-            continue;
-        asvalue = (ast_value*)(parser->globals[i]);
-        if (!asvalue->uses && !asvalue->hasvalue && asvalue->expression.vtype != TYPE_FUNCTION) {
-            retval = retval && !compile_warning(ast_ctx(asvalue), WARN_UNUSED_VARIABLE,
-                                                "unused global: `%s`", asvalue->name);
-        }
-        if (!ast_global_codegen(asvalue, ir, false)) {
-            con_out("failed to generate global %s\n", asvalue->name);
-            ir_builder_delete(ir);
-            return false;
-        }
-    }
-    /* Build function vararg accessor ast tree now before generating
-     * immediates, because the accessors may add new immediates
-     */
-    for (i = 0; i < vec_size(parser->functions); ++i) {
-        ast_function *f = parser->functions[i];
-        if (f->varargs) {
-            if (parser->max_param_count > vec_size(f->vtype->expression.params)) {
-                f->varargs->expression.count = parser->max_param_count - vec_size(f->vtype->expression.params);
-                if (!parser_create_array_setter_impl(parser, f->varargs)) {
-                    con_out("failed to generate vararg setter for %s\n", f->name);
-                    ir_builder_delete(ir);
-                    return false;
-                }
-                if (!parser_create_array_getter_impl(parser, f->varargs)) {
-                    con_out("failed to generate vararg getter for %s\n", f->name);
-                    ir_builder_delete(ir);
-                    return false;
-                }
-            } else {
-                ast_delete(f->varargs);
-                f->varargs = NULL;
-            }
-        }
-    }
-    /* Now we can generate immediates */
-    if (!fold_generate(parser->fold, ir))
-        return false;
-
-    /* before generating any functions we need to set the coverage_func */
-    if (!parser_set_coverage_func(parser, ir))
-        return false;
-
-    for (i = 0; i < vec_size(parser->globals); ++i) {
-        ast_value *asvalue;
-        if (!ast_istype(parser->globals[i], ast_value))
-            continue;
-        asvalue = (ast_value*)(parser->globals[i]);
-        if (!(asvalue->expression.flags & AST_FLAG_INITIALIZED))
-        {
-            if (asvalue->cvq == CV_CONST && !asvalue->hasvalue)
-                (void)!compile_warning(ast_ctx(asvalue), WARN_UNINITIALIZED_CONSTANT,
-                                       "uninitialized constant: `%s`",
-                                       asvalue->name);
-            else if ((asvalue->cvq == CV_NONE || asvalue->cvq == CV_CONST) && !asvalue->hasvalue)
-                (void)!compile_warning(ast_ctx(asvalue), WARN_UNINITIALIZED_GLOBAL,
-                                       "uninitialized global: `%s`",
-                                       asvalue->name);
-        }
-        if (!ast_generate_accessors(asvalue, ir)) {
-            ir_builder_delete(ir);
-            return false;
-        }
-    }
-    for (i = 0; i < vec_size(parser->fields); ++i) {
-        ast_value *asvalue;
-        asvalue = (ast_value*)(parser->fields[i]->next);
-
-        if (!ast_istype((ast_expression*)asvalue, ast_value))
-            continue;
-        if (asvalue->expression.vtype != TYPE_ARRAY)
-            continue;
-        if (!ast_generate_accessors(asvalue, ir)) {
-            ir_builder_delete(ir);
-            return false;
-        }
-    }
-    if (parser->reserved_version &&
-        !ast_global_codegen(parser->reserved_version, ir, false))
-    {
-        con_out("failed to generate reserved::version");
-        ir_builder_delete(ir);
-        return false;
-    }
-    for (i = 0; i < vec_size(parser->functions); ++i) {
-        ast_function *f = parser->functions[i];
-        if (!ast_function_codegen(f, ir)) {
-            con_out("failed to generate function %s\n", f->name);
-            ir_builder_delete(ir);
-            return false;
-        }
-    }
-
-    generate_checksum(parser, ir);
-
-    if (OPTS_OPTION_BOOL(OPTION_DUMP))
-        ir_builder_dump(ir, con_out);
-    for (i = 0; i < vec_size(parser->functions); ++i) {
-        if (!ir_function_finalize(parser->functions[i]->ir_func)) {
-            con_out("failed to finalize function %s\n", parser->functions[i]->name);
-            ir_builder_delete(ir);
-            return false;
-        }
-    }
-    parser_remove_ast(parser);
-
-    if (compile_Werrors) {
-        con_out("*** there were warnings treated as errors\n");
-        compile_show_werrors();
-        retval = false;
-    }
-
-    if (retval) {
-        if (OPTS_OPTION_BOOL(OPTION_DUMPFIN))
-            ir_builder_dump(ir, con_out);
-
-        if (!ir_builder_generate(ir, output)) {
-            con_out("*** failed to generate output file\n");
-            ir_builder_delete(ir);
-            return false;
-        }
-    }
-    ir_builder_delete(ir);
-    return retval;
-}
diff --git a/parser.cpp b/parser.cpp
new file mode 100644 (file)
index 0000000..51f7e1e
--- /dev/null
@@ -0,0 +1,6345 @@
+#include <string.h>
+#include <math.h>
+
+#include "intrin.h"
+#include "fold.h"
+#include "ast.h"
+#include "parser.h"
+
+#define PARSER_HT_LOCALS  2
+#define PARSER_HT_SIZE    512
+#define TYPEDEF_HT_SIZE   512
+
+static void parser_enterblock(parser_t *parser);
+static bool parser_leaveblock(parser_t *parser);
+static void parser_addlocal(parser_t *parser, const char *name, ast_expression *e);
+static void parser_addlocal(parser_t *parser, const std::string &name, ast_expression *e);
+static void parser_addglobal(parser_t *parser, const char *name, ast_expression *e);
+static void parser_addglobal(parser_t *parser, const std::string &name, ast_expression *e);
+static bool parse_typedef(parser_t *parser);
+static bool parse_variable(parser_t *parser, ast_block *localblock, bool nofields, int qualifier, ast_value *cached_typedef, bool noref, bool is_static, uint32_t qflags, char *vstring);
+static ast_block* parse_block(parser_t *parser);
+static bool parse_block_into(parser_t *parser, ast_block *block);
+static bool parse_statement_or_block(parser_t *parser, ast_expression **out);
+static bool parse_statement(parser_t *parser, ast_block *block, ast_expression **out, bool allow_cases);
+static ast_expression* parse_expression_leave(parser_t *parser, bool stopatcomma, bool truthvalue, bool with_labels);
+static ast_expression* parse_expression(parser_t *parser, bool stopatcomma, bool with_labels);
+static ast_value* parser_create_array_setter_proto(parser_t *parser, ast_value *array, const char *funcname);
+static ast_value* parser_create_array_getter_proto(parser_t *parser, ast_value *array, const ast_expression *elemtype, const char *funcname);
+static ast_value *parse_typename(parser_t *parser, ast_value **storebase, ast_value *cached_typedef, bool *is_vararg);
+
+static void parseerror_(parser_t *parser, const char *fmt, ...)
+{
+    va_list ap;
+    va_start(ap, fmt);
+    vcompile_error(parser->lex->tok.ctx, fmt, ap);
+    va_end(ap);
+}
+
+template<typename... Ts>
+static inline void parseerror(parser_t *parser, const char *fmt, const Ts&... ts) {
+    return parseerror_(parser, fmt, formatNormalize(ts)...);
+}
+
+// returns true if it counts as an error
+static bool GMQCC_WARN parsewarning_(parser_t *parser, int warntype, const char *fmt, ...)
+{
+    bool    r;
+    va_list ap;
+    va_start(ap, fmt);
+    r = vcompile_warning(parser->lex->tok.ctx, warntype, fmt, ap);
+    va_end(ap);
+    return r;
+}
+
+template<typename... Ts>
+static inline bool GMQCC_WARN parsewarning(parser_t *parser, int warntype, const char *fmt, const Ts&... ts) {
+    return parsewarning_(parser, warntype, fmt, formatNormalize(ts)...);
+}
+
+/**********************************************************************
+ * parsing
+ */
+
+static bool parser_next(parser_t *parser)
+{
+    /* lex_do kills the previous token */
+    parser->tok = lex_do(parser->lex);
+    if (parser->tok == TOKEN_EOF)
+        return true;
+    if (parser->tok >= TOKEN_ERROR) {
+        parseerror(parser, "lex error");
+        return false;
+    }
+    return true;
+}
+
+#define parser_tokval(p) ((p)->lex->tok.value)
+#define parser_token(p)  (&((p)->lex->tok))
+
+char *parser_strdup(const char *str)
+{
+    if (str && !*str) {
+        /* actually dup empty strings */
+        char *out = (char*)mem_a(1);
+        *out = 0;
+        return out;
+    }
+    return util_strdup(str);
+}
+
+static ast_expression* parser_find_field(parser_t *parser, const char *name) {
+    return (ast_expression*)util_htget(parser->htfields, name);
+}
+static ast_expression* parser_find_field(parser_t *parser, const std::string &name) {
+    return parser_find_field(parser, name.c_str());
+}
+
+static ast_expression* parser_find_label(parser_t *parser, const char *name)
+{
+    for (auto &it : parser->labels)
+        if (it->m_name == name)
+            return it;
+    return nullptr;
+}
+static inline ast_expression* parser_find_label(parser_t *parser, const std::string &name) {
+    return parser_find_label(parser, name.c_str());
+}
+
+ast_expression* parser_find_global(parser_t *parser, const char *name)
+{
+    ast_expression *var = (ast_expression*)util_htget(parser->aliases, parser_tokval(parser));
+    if (var)
+        return var;
+    return (ast_expression*)util_htget(parser->htglobals, name);
+}
+
+ast_expression* parser_find_global(parser_t *parser, const std::string &name) {
+    return parser_find_global(parser, name.c_str());
+}
+
+static ast_expression* parser_find_param(parser_t *parser, const char *name)
+{
+    ast_value *fun;
+    if (!parser->function)
+        return nullptr;
+    fun = parser->function->m_function_type;
+    for (auto &it : fun->m_type_params) {
+        if (it->m_name == name)
+            return it.get();
+    }
+    return nullptr;
+}
+
+static ast_expression* parser_find_local(parser_t *parser, const char *name, size_t upto, bool *isparam)
+{
+    size_t          i, hash;
+    ast_expression *e;
+
+    hash = util_hthash(parser->htglobals, name);
+
+    *isparam = false;
+    for (i = vec_size(parser->variables); i > upto;) {
+        --i;
+        if ( (e = (ast_expression*)util_htgeth(parser->variables[i], name, hash)) )
+            return e;
+    }
+    *isparam = true;
+    return parser_find_param(parser, name);
+}
+
+static ast_expression* parser_find_local(parser_t *parser, const std::string &name, size_t upto, bool *isparam) {
+    return parser_find_local(parser, name.c_str(), upto, isparam);
+}
+
+static ast_expression* parser_find_var(parser_t *parser, const char *name)
+{
+    bool dummy;
+    ast_expression *v;
+    v         = parser_find_local(parser, name, 0, &dummy);
+    if (!v) v = parser_find_global(parser, name);
+    return v;
+}
+
+static inline ast_expression* parser_find_var(parser_t *parser, const std::string &name) {
+    return parser_find_var(parser, name.c_str());
+}
+
+static ast_value* parser_find_typedef(parser_t *parser, const char *name, size_t upto)
+{
+    size_t     i, hash;
+    ast_value *e;
+    hash = util_hthash(parser->typedefs[0], name);
+
+    for (i = vec_size(parser->typedefs); i > upto;) {
+        --i;
+        if ( (e = (ast_value*)util_htgeth(parser->typedefs[i], name, hash)) )
+            return e;
+    }
+    return nullptr;
+}
+
+static ast_value* parser_find_typedef(parser_t *parser, const std::string &name, size_t upto) {
+    return parser_find_typedef(parser, name.c_str(), upto);
+}
+
+struct sy_elem {
+    size_t etype; /* 0 = expression, others are operators */
+    bool isparen;
+    size_t off;
+    ast_expression *out;
+    ast_block *block; /* for commas and function calls */
+    lex_ctx_t ctx;
+};
+
+enum {
+    PAREN_EXPR,
+    PAREN_FUNC,
+    PAREN_INDEX,
+    PAREN_TERNARY1,
+    PAREN_TERNARY2
+};
+
+struct shunt {
+    std::vector<sy_elem> out;
+    std::vector<sy_elem> ops;
+    std::vector<size_t> argc;
+    std::vector<unsigned int> paren;
+};
+
+static sy_elem syexp(lex_ctx_t ctx, ast_expression *v) {
+    sy_elem e;
+    e.etype = 0;
+    e.off   = 0;
+    e.out   = v;
+    e.block = nullptr;
+    e.ctx   = ctx;
+    e.isparen = false;
+    return e;
+}
+
+static sy_elem syblock(lex_ctx_t ctx, ast_block *v) {
+    sy_elem e;
+    e.etype = 0;
+    e.off   = 0;
+    e.out   = v;
+    e.block = v;
+    e.ctx   = ctx;
+    e.isparen = false;
+    return e;
+}
+
+static sy_elem syop(lex_ctx_t ctx, const oper_info *op) {
+    sy_elem e;
+    e.etype = 1 + (op - operators);
+    e.off   = 0;
+    e.out   = nullptr;
+    e.block = nullptr;
+    e.ctx   = ctx;
+    e.isparen = false;
+    return e;
+}
+
+static sy_elem syparen(lex_ctx_t ctx, size_t off) {
+    sy_elem e;
+    e.etype = 0;
+    e.off   = off;
+    e.out   = nullptr;
+    e.block = nullptr;
+    e.ctx   = ctx;
+    e.isparen = true;
+    return e;
+}
+
+/* With regular precedence rules, ent.foo[n] is the same as (ent.foo)[n],
+ * so we need to rotate it to become ent.(foo[n]).
+ */
+static bool rotate_entfield_array_index_nodes(ast_expression **out)
+{
+    ast_array_index *index, *oldindex;
+    ast_entfield    *entfield;
+
+    ast_value       *field;
+    ast_expression  *sub;
+    ast_expression  *entity;
+
+    lex_ctx_t ctx = (*out)->m_context;
+
+    if (!ast_istype(*out, ast_array_index))
+        return false;
+    index = (ast_array_index*)*out;
+
+    if (!ast_istype(index->m_array, ast_entfield))
+        return false;
+    entfield = (ast_entfield*)index->m_array;
+
+    if (!ast_istype(entfield->m_field, ast_value))
+        return false;
+    field = (ast_value*)entfield->m_field;
+
+    sub    = index->m_index;
+    entity = entfield->m_entity;
+
+    oldindex = index;
+
+    index = ast_array_index::make(ctx, field, sub);
+    entfield = new ast_entfield(ctx, entity, index);
+    *out = entfield;
+
+    oldindex->m_array = nullptr;
+    oldindex->m_index = nullptr;
+    delete oldindex;
+
+    return true;
+}
+
+static bool check_write_to(lex_ctx_t ctx, ast_expression *expr)
+{
+    if (ast_istype(expr, ast_value)) {
+        ast_value *val = (ast_value*)expr;
+        if (val->m_cvq == CV_CONST) {
+            if (val->m_name[0] == '#') {
+                compile_error(ctx, "invalid assignment to a literal constant");
+                return false;
+            }
+            /*
+             * To work around quakeworld we must elide the error and make it
+             * a warning instead.
+             */
+            if (OPTS_OPTION_U32(OPTION_STANDARD) != COMPILER_QCC)
+                compile_error(ctx, "assignment to constant `%s`", val->m_name);
+            else
+                (void)!compile_warning(ctx, WARN_CONST_OVERWRITE, "assignment to constant `%s`", val->m_name);
+            return false;
+        }
+    }
+    return true;
+}
+
+static bool parser_sy_apply_operator(parser_t *parser, shunt *sy)
+{
+    const oper_info *op;
+    lex_ctx_t ctx;
+    ast_expression *out = nullptr;
+    ast_expression *exprs[3];
+    ast_block      *blocks[3];
+    ast_binstore   *asbinstore;
+    size_t i, assignop, addop, subop;
+    qcint_t  generated_op = 0;
+
+    char ty1[1024];
+    char ty2[1024];
+
+    if (sy->ops.empty()) {
+        parseerror(parser, "internal error: missing operator");
+        return false;
+    }
+
+    if (sy->ops.back().isparen) {
+        parseerror(parser, "unmatched parenthesis");
+        return false;
+    }
+
+    op = &operators[sy->ops.back().etype - 1];
+    ctx = sy->ops.back().ctx;
+
+    if (sy->out.size() < op->operands) {
+        if (op->flags & OP_PREFIX)
+            compile_error(ctx, "expected expression after unary operator `%s`", op->op, (int)op->id);
+        else /* this should have errored previously already */
+            compile_error(ctx, "expected expression after operator `%s`", op->op, (int)op->id);
+        return false;
+    }
+
+    sy->ops.pop_back();
+
+    /* op(:?) has no input and no output */
+    if (!op->operands)
+        return true;
+
+    sy->out.erase(sy->out.end() - op->operands, sy->out.end());
+    for (i = 0; i < op->operands; ++i) {
+        exprs[i]  = sy->out[sy->out.size()+i].out;
+        blocks[i] = sy->out[sy->out.size()+i].block;
+
+        if (exprs[i]->m_vtype == TYPE_NOEXPR &&
+            !(i != 0 && op->id == opid2('?',':')) &&
+            !(i == 1 && op->id == opid1('.')))
+        {
+            if (ast_istype(exprs[i], ast_label))
+                compile_error(exprs[i]->m_context, "expected expression, got an unknown identifier");
+            else
+                compile_error(exprs[i]->m_context, "not an expression");
+            (void)!compile_warning(exprs[i]->m_context, WARN_DEBUG, "expression %u\n", (unsigned int)i);
+        }
+    }
+
+    if (blocks[0] && blocks[0]->m_exprs.empty() && op->id != opid1(',')) {
+        compile_error(ctx, "internal error: operator cannot be applied on empty blocks");
+        return false;
+    }
+
+#define NotSameType(T) \
+             (exprs[0]->m_vtype != exprs[1]->m_vtype || \
+              exprs[0]->m_vtype != T)
+
+    switch (op->id)
+    {
+        default:
+            compile_error(ctx, "internal error: unhandled operator: %s (%i)", op->op, (int)op->id);
+            return false;
+
+        case opid1('.'):
+            if (exprs[0]->m_vtype == TYPE_VECTOR &&
+                exprs[1]->m_vtype == TYPE_NOEXPR)
+            {
+                if      (exprs[1] == parser->const_vec[0])
+                    out = ast_member::make(ctx, exprs[0], 0, "");
+                else if (exprs[1] == parser->const_vec[1])
+                    out = ast_member::make(ctx, exprs[0], 1, "");
+                else if (exprs[1] == parser->const_vec[2])
+                    out = ast_member::make(ctx, exprs[0], 2, "");
+                else {
+                    compile_error(ctx, "access to invalid vector component");
+                    return false;
+                }
+            }
+            else if (exprs[0]->m_vtype == TYPE_ENTITY) {
+                if (exprs[1]->m_vtype != TYPE_FIELD) {
+                    compile_error(exprs[1]->m_context, "type error: right hand of member-operand should be an entity-field");
+                    return false;
+                }
+                out = new ast_entfield(ctx, exprs[0], exprs[1]);
+            }
+            else if (exprs[0]->m_vtype == TYPE_VECTOR) {
+                compile_error(exprs[1]->m_context, "vectors cannot be accessed this way");
+                return false;
+            }
+            else {
+                compile_error(exprs[1]->m_context, "type error: member-of operator on something that is not an entity or vector");
+                return false;
+            }
+            break;
+
+        case opid1('['):
+            if (exprs[0]->m_vtype != TYPE_ARRAY &&
+                !(exprs[0]->m_vtype == TYPE_FIELD &&
+                  exprs[0]->m_next->m_vtype == TYPE_ARRAY))
+            {
+                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
+                compile_error(exprs[0]->m_context, "cannot index value of type %s", ty1);
+                return false;
+            }
+            if (exprs[1]->m_vtype != TYPE_FLOAT) {
+                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
+                compile_error(exprs[1]->m_context, "index must be of type float, not %s", ty1);
+                return false;
+            }
+            out = ast_array_index::make(ctx, exprs[0], exprs[1]);
+            rotate_entfield_array_index_nodes(&out);
+            break;
+
+        case opid1(','):
+            if (sy->paren.size() && sy->paren.back() == PAREN_FUNC) {
+                sy->out.push_back(syexp(ctx, exprs[0]));
+                sy->out.push_back(syexp(ctx, exprs[1]));
+                sy->argc.back()++;
+                return true;
+            }
+            if (blocks[0]) {
+                if (!blocks[0]->addExpr(exprs[1]))
+                    return false;
+            } else {
+                blocks[0] = new ast_block(ctx);
+                if (!blocks[0]->addExpr(exprs[0]) ||
+                    !blocks[0]->addExpr(exprs[1]))
+                {
+                    return false;
+                }
+            }
+            blocks[0]->setType(*exprs[1]);
+
+            sy->out.push_back(syblock(ctx, blocks[0]));
+            return true;
+
+        case opid2('+','P'):
+            out = exprs[0];
+            break;
+        case opid2('-','P'):
+            if ((out = parser->m_fold.op(op, exprs)))
+                break;
+
+            if (exprs[0]->m_vtype != TYPE_FLOAT &&
+                exprs[0]->m_vtype != TYPE_VECTOR) {
+                    compile_error(ctx, "invalid types used in unary expression: cannot negate type %s",
+                                  type_name[exprs[0]->m_vtype]);
+                return false;
+            }
+            if (exprs[0]->m_vtype == TYPE_FLOAT)
+                out = ast_unary::make(ctx, VINSTR_NEG_F, exprs[0]);
+            else
+                out = ast_unary::make(ctx, VINSTR_NEG_V, exprs[0]);
+            break;
+
+        case opid2('!','P'):
+            if (!(out = parser->m_fold.op(op, exprs))) {
+                switch (exprs[0]->m_vtype) {
+                    case TYPE_FLOAT:
+                        out = ast_unary::make(ctx, INSTR_NOT_F, exprs[0]);
+                        break;
+                    case TYPE_VECTOR:
+                        out = ast_unary::make(ctx, INSTR_NOT_V, exprs[0]);
+                        break;
+                    case TYPE_STRING:
+                        if (OPTS_FLAG(TRUE_EMPTY_STRINGS))
+                            out = ast_unary::make(ctx, INSTR_NOT_F, exprs[0]);
+                        else
+                            out = ast_unary::make(ctx, INSTR_NOT_S, exprs[0]);
+                        break;
+                    /* we don't constant-fold NOT for these types */
+                    case TYPE_ENTITY:
+                        out = ast_unary::make(ctx, INSTR_NOT_ENT, exprs[0]);
+                        break;
+                    case TYPE_FUNCTION:
+                        out = ast_unary::make(ctx, INSTR_NOT_FNC, exprs[0]);
+                        break;
+                    default:
+                    compile_error(ctx, "invalid types used in expression: cannot logically negate type %s",
+                                  type_name[exprs[0]->m_vtype]);
+                    return false;
+                }
+            }
+            break;
+
+        case opid1('+'):
+            if (exprs[0]->m_vtype != exprs[1]->m_vtype ||
+               (exprs[0]->m_vtype != TYPE_VECTOR && exprs[0]->m_vtype != TYPE_FLOAT) )
+            {
+                compile_error(ctx, "invalid types used in expression: cannot add type %s and %s",
+                              type_name[exprs[0]->m_vtype],
+                              type_name[exprs[1]->m_vtype]);
+                return false;
+            }
+            if (!(out = parser->m_fold.op(op, exprs))) {
+                switch (exprs[0]->m_vtype) {
+                    case TYPE_FLOAT:
+                        out = fold::binary(ctx, INSTR_ADD_F, exprs[0], exprs[1]);
+                        break;
+                    case TYPE_VECTOR:
+                        out = fold::binary(ctx, INSTR_ADD_V, exprs[0], exprs[1]);
+                        break;
+                    default:
+                        compile_error(ctx, "invalid types used in expression: cannot add type %s and %s",
+                                      type_name[exprs[0]->m_vtype],
+                                      type_name[exprs[1]->m_vtype]);
+                        return false;
+                }
+            }
+            break;
+        case opid1('-'):
+            if  (exprs[0]->m_vtype != exprs[1]->m_vtype ||
+                (exprs[0]->m_vtype != TYPE_VECTOR && exprs[0]->m_vtype != TYPE_FLOAT))
+            {
+                compile_error(ctx, "invalid types used in expression: cannot subtract type %s from %s",
+                              type_name[exprs[1]->m_vtype],
+                              type_name[exprs[0]->m_vtype]);
+                return false;
+            }
+            if (!(out = parser->m_fold.op(op, exprs))) {
+                switch (exprs[0]->m_vtype) {
+                    case TYPE_FLOAT:
+                        out = fold::binary(ctx, INSTR_SUB_F, exprs[0], exprs[1]);
+                        break;
+                    case TYPE_VECTOR:
+                        out = fold::binary(ctx, INSTR_SUB_V, exprs[0], exprs[1]);
+                        break;
+                    default:
+                        compile_error(ctx, "invalid types used in expression: cannot subtract type %s from %s",
+                                      type_name[exprs[1]->m_vtype],
+                                      type_name[exprs[0]->m_vtype]);
+                        return false;
+                }
+            }
+            break;
+        case opid1('*'):
+            if (exprs[0]->m_vtype != exprs[1]->m_vtype &&
+                !(exprs[0]->m_vtype == TYPE_VECTOR &&
+                  exprs[1]->m_vtype == TYPE_FLOAT) &&
+                !(exprs[1]->m_vtype == TYPE_VECTOR &&
+                  exprs[0]->m_vtype == TYPE_FLOAT)
+                )
+            {
+                compile_error(ctx, "invalid types used in expression: cannot multiply types %s and %s",
+                              type_name[exprs[1]->m_vtype],
+                              type_name[exprs[0]->m_vtype]);
+                return false;
+            }
+            if (!(out = parser->m_fold.op(op, exprs))) {
+                switch (exprs[0]->m_vtype) {
+                    case TYPE_FLOAT:
+                        if (exprs[1]->m_vtype == TYPE_VECTOR)
+                            out = fold::binary(ctx, INSTR_MUL_FV, exprs[0], exprs[1]);
+                        else
+                            out = fold::binary(ctx, INSTR_MUL_F, exprs[0], exprs[1]);
+                        break;
+                    case TYPE_VECTOR:
+                        if (exprs[1]->m_vtype == TYPE_FLOAT)
+                            out = fold::binary(ctx, INSTR_MUL_VF, exprs[0], exprs[1]);
+                        else
+                            out = fold::binary(ctx, INSTR_MUL_V, exprs[0], exprs[1]);
+                        break;
+                    default:
+                        compile_error(ctx, "invalid types used in expression: cannot multiply types %s and %s",
+                                      type_name[exprs[1]->m_vtype],
+                                      type_name[exprs[0]->m_vtype]);
+                        return false;
+                }
+            }
+            break;
+
+        case opid1('/'):
+            if (exprs[1]->m_vtype != TYPE_FLOAT) {
+                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
+                ast_type_to_string(exprs[1], ty2, sizeof(ty2));
+                compile_error(ctx, "invalid types used in expression: cannot divide types %s and %s", ty1, ty2);
+                return false;
+            }
+            if (!(out = parser->m_fold.op(op, exprs))) {
+                if (exprs[0]->m_vtype == TYPE_FLOAT)
+                    out = fold::binary(ctx, INSTR_DIV_F, exprs[0], exprs[1]);
+                else {
+                    ast_type_to_string(exprs[0], ty1, sizeof(ty1));
+                    ast_type_to_string(exprs[1], ty2, sizeof(ty2));
+                    compile_error(ctx, "invalid types used in expression: cannot divide types %s and %s", ty1, ty2);
+                    return false;
+                }
+            }
+            break;
+
+        case opid1('%'):
+            if (NotSameType(TYPE_FLOAT)) {
+                compile_error(ctx, "invalid types used in expression: cannot perform modulo operation between types %s and %s",
+                    type_name[exprs[0]->m_vtype],
+                    type_name[exprs[1]->m_vtype]);
+                return false;
+            } else if (!(out = parser->m_fold.op(op, exprs))) {
+                /* generate a call to __builtin_mod */
+                ast_expression *mod  = parser->m_intrin.func("mod");
+                ast_call       *call = nullptr;
+                if (!mod) return false; /* can return null for missing floor */
+
+                call = ast_call::make(parser_ctx(parser), mod);
+                call->m_params.push_back(exprs[0]);
+                call->m_params.push_back(exprs[1]);
+
+                out = call;
+            }
+            break;
+
+        case opid2('%','='):
+            compile_error(ctx, "%= is unimplemented");
+            return false;
+
+        case opid1('|'):
+        case opid1('&'):
+        case opid1('^'):
+            if ( !(exprs[0]->m_vtype == TYPE_FLOAT  && exprs[1]->m_vtype == TYPE_FLOAT) &&
+                 !(exprs[0]->m_vtype == TYPE_VECTOR && exprs[1]->m_vtype == TYPE_FLOAT) &&
+                 !(exprs[0]->m_vtype == TYPE_VECTOR && exprs[1]->m_vtype == TYPE_VECTOR))
+            {
+                compile_error(ctx, "invalid types used in expression: cannot perform bit operations between types %s and %s",
+                              type_name[exprs[0]->m_vtype],
+                              type_name[exprs[1]->m_vtype]);
+                return false;
+            }
+
+            if (!(out = parser->m_fold.op(op, exprs))) {
+                /*
+                 * IF the first expression is float, the following will be too
+                 * since scalar ^ vector is not allowed.
+                 */
+                if (exprs[0]->m_vtype == TYPE_FLOAT) {
+                    out = fold::binary(ctx,
+                        (op->id == opid1('^') ? VINSTR_BITXOR : op->id == opid1('|') ? INSTR_BITOR : INSTR_BITAND),
+                        exprs[0], exprs[1]);
+                } else {
+                    /*
+                     * The first is a vector: vector is allowed to bitop with vector and
+                     * with scalar, branch here for the second operand.
+                     */
+                    if (exprs[1]->m_vtype == TYPE_VECTOR) {
+                        /*
+                         * Bitop all the values of the vector components against the
+                         * vectors components in question.
+                         */
+                        out = fold::binary(ctx,
+                            (op->id == opid1('^') ? VINSTR_BITXOR_V : op->id == opid1('|') ? VINSTR_BITOR_V : VINSTR_BITAND_V),
+                            exprs[0], exprs[1]);
+                    } else {
+                        out = fold::binary(ctx,
+                            (op->id == opid1('^') ? VINSTR_BITXOR_VF : op->id == opid1('|') ? VINSTR_BITOR_VF : VINSTR_BITAND_VF),
+                            exprs[0], exprs[1]);
+                    }
+                }
+            }
+            break;
+
+        case opid2('<','<'):
+        case opid2('>','>'):
+            if (NotSameType(TYPE_FLOAT)) {
+                compile_error(ctx, "invalid types used in expression: cannot perform shift between types %s and %s",
+                    type_name[exprs[0]->m_vtype],
+                    type_name[exprs[1]->m_vtype]);
+                return false;
+            }
+
+            if (!(out = parser->m_fold.op(op, exprs))) {
+                ast_expression *shift = parser->m_intrin.func((op->id == opid2('<','<')) ? "__builtin_lshift" : "__builtin_rshift");
+                ast_call *call  = ast_call::make(parser_ctx(parser), shift);
+                call->m_params.push_back(exprs[0]);
+                call->m_params.push_back(exprs[1]);
+                out = call;
+            }
+            break;
+
+        case opid3('<','<','='):
+        case opid3('>','>','='):
+            if (NotSameType(TYPE_FLOAT)) {
+                compile_error(ctx, "invalid types used in expression: cannot perform shift operation between types %s and %s",
+                    type_name[exprs[0]->m_vtype],
+                    type_name[exprs[1]->m_vtype]);
+                return false;
+            }
+
+            if(!(out = parser->m_fold.op(op, exprs))) {
+                ast_expression *shift = parser->m_intrin.func((op->id == opid3('<','<','=')) ? "__builtin_lshift" : "__builtin_rshift");
+                ast_call *call  = ast_call::make(parser_ctx(parser), shift);
+                call->m_params.push_back(exprs[0]);
+                call->m_params.push_back(exprs[1]);
+                out = new ast_store(
+                    parser_ctx(parser),
+                    INSTR_STORE_F,
+                    exprs[0],
+                    call
+                );
+            }
+
+            break;
+
+        case opid2('|','|'):
+            generated_op += 1; /* INSTR_OR */
+        case opid2('&','&'):
+            generated_op += INSTR_AND;
+            if (!(out = parser->m_fold.op(op, exprs))) {
+                if (OPTS_FLAG(PERL_LOGIC) && !exprs[0]->compareType(*exprs[1])) {
+                    ast_type_to_string(exprs[0], ty1, sizeof(ty1));
+                    ast_type_to_string(exprs[1], ty2, sizeof(ty2));
+                    compile_error(ctx, "invalid types for logical operation with -fperl-logic: %s and %s", ty1, ty2);
+                    return false;
+                }
+                for (i = 0; i < 2; ++i) {
+                    if (OPTS_FLAG(CORRECT_LOGIC) && exprs[i]->m_vtype == TYPE_VECTOR) {
+                        out = ast_unary::make(ctx, INSTR_NOT_V, exprs[i]);
+                        if (!out) break;
+                        out = ast_unary::make(ctx, INSTR_NOT_F, out);
+                        if (!out) break;
+                        exprs[i] = out; out = nullptr;
+                        if (OPTS_FLAG(PERL_LOGIC)) {
+                            /* here we want to keep the right expressions' type */
+                            break;
+                        }
+                    }
+                    else if (OPTS_FLAG(FALSE_EMPTY_STRINGS) && exprs[i]->m_vtype == TYPE_STRING) {
+                        out = ast_unary::make(ctx, INSTR_NOT_S, exprs[i]);
+                        if (!out) break;
+                        out = ast_unary::make(ctx, INSTR_NOT_F, out);
+                        if (!out) break;
+                        exprs[i] = out; out = nullptr;
+                        if (OPTS_FLAG(PERL_LOGIC)) {
+                            /* here we want to keep the right expressions' type */
+                            break;
+                        }
+                    }
+                }
+                out = fold::binary(ctx, generated_op, exprs[0], exprs[1]);
+            }
+            break;
+
+        case opid2('?',':'):
+            if (sy->paren.back() != PAREN_TERNARY2) {
+                compile_error(ctx, "mismatched parenthesis/ternary");
+                return false;
+            }
+            sy->paren.pop_back();
+            if (!exprs[1]->compareType(*exprs[2])) {
+                ast_type_to_string(exprs[1], ty1, sizeof(ty1));
+                ast_type_to_string(exprs[2], ty2, sizeof(ty2));
+                compile_error(ctx, "operands of ternary expression must have the same type, got %s and %s", ty1, ty2);
+                return false;
+            }
+            if (!(out = parser->m_fold.op(op, exprs)))
+                out = new ast_ternary(ctx, exprs[0], exprs[1], exprs[2]);
+            break;
+
+        case opid2('*', '*'):
+            if (NotSameType(TYPE_FLOAT)) {
+                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
+                ast_type_to_string(exprs[1], ty2, sizeof(ty2));
+                compile_error(ctx, "invalid types used in exponentiation: %s and %s",
+                    ty1, ty2);
+                return false;
+            }
+
+            if (!(out = parser->m_fold.op(op, exprs))) {
+                ast_call *gencall = ast_call::make(parser_ctx(parser), parser->m_intrin.func("pow"));
+                gencall->m_params.push_back(exprs[0]);
+                gencall->m_params.push_back(exprs[1]);
+                out = gencall;
+            }
+            break;
+
+        case opid2('>', '<'):
+            if (NotSameType(TYPE_VECTOR)) {
+                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
+                ast_type_to_string(exprs[1], ty2, sizeof(ty2));
+                compile_error(ctx, "invalid types used in cross product: %s and %s",
+                    ty1, ty2);
+                return false;
+            }
+
+            if (!(out = parser->m_fold.op(op, exprs))) {
+                out = fold::binary(
+                    parser_ctx(parser),
+                    VINSTR_CROSS,
+                    exprs[0],
+                    exprs[1]
+                );
+            }
+
+            break;
+
+        case opid3('<','=','>'): /* -1, 0, or 1 */
+            if (NotSameType(TYPE_FLOAT)) {
+                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
+                ast_type_to_string(exprs[1], ty2, sizeof(ty2));
+                compile_error(ctx, "invalid types used in comparision: %s and %s",
+                    ty1, ty2);
+
+                return false;
+            }
+
+            if (!(out = parser->m_fold.op(op, exprs))) {
+                /* This whole block is NOT fold_binary safe */
+                ast_binary *eq = new ast_binary(ctx, INSTR_EQ_F, exprs[0], exprs[1]);
+
+                eq->m_refs = AST_REF_NONE;
+
+                    /* if (lt) { */
+                out = new ast_ternary(ctx,
+                        new ast_binary(ctx, INSTR_LT, exprs[0], exprs[1]),
+                        /* out = -1 */
+                        parser->m_fold.imm_float(2),
+                    /* } else { */
+                        /* if (eq) { */
+                        new ast_ternary(ctx, eq,
+                            /* out = 0 */
+                            parser->m_fold.imm_float(0),
+                        /* } else { */
+                            /* out = 1 */
+                            parser->m_fold.imm_float(1)
+                        /* } */
+                        )
+                    /* } */
+                    );
+
+            }
+            break;
+
+        case opid1('>'):
+            generated_op += 1; /* INSTR_GT */
+        case opid1('<'):
+            generated_op += 1; /* INSTR_LT */
+        case opid2('>', '='):
+            generated_op += 1; /* INSTR_GE */
+        case opid2('<', '='):
+            generated_op += INSTR_LE;
+            if (NotSameType(TYPE_FLOAT)) {
+                compile_error(ctx, "invalid types used in expression: cannot perform comparison between types %s and %s",
+                              type_name[exprs[0]->m_vtype],
+                              type_name[exprs[1]->m_vtype]);
+                return false;
+            }
+            if (!(out = parser->m_fold.op(op, exprs)))
+                out = fold::binary(ctx, generated_op, exprs[0], exprs[1]);
+            break;
+        case opid2('!', '='):
+            if (exprs[0]->m_vtype != exprs[1]->m_vtype) {
+                compile_error(ctx, "invalid types used in expression: cannot perform comparison between types %s and %s",
+                              type_name[exprs[0]->m_vtype],
+                              type_name[exprs[1]->m_vtype]);
+                return false;
+            }
+            if (!(out = parser->m_fold.op(op, exprs)))
+                out = fold::binary(ctx, type_ne_instr[exprs[0]->m_vtype], exprs[0], exprs[1]);
+            break;
+        case opid2('=', '='):
+            if (exprs[0]->m_vtype != exprs[1]->m_vtype) {
+                compile_error(ctx, "invalid types used in expression: cannot perform comparison between types %s and %s",
+                              type_name[exprs[0]->m_vtype],
+                              type_name[exprs[1]->m_vtype]);
+                return false;
+            }
+            if (!(out = parser->m_fold.op(op, exprs)))
+                out = fold::binary(ctx, type_eq_instr[exprs[0]->m_vtype], exprs[0], exprs[1]);
+            break;
+
+        case opid1('='):
+            if (ast_istype(exprs[0], ast_entfield)) {
+                ast_expression *field = ((ast_entfield*)exprs[0])->m_field;
+                if (OPTS_FLAG(ADJUST_VECTOR_FIELDS) &&
+                    exprs[0]->m_vtype == TYPE_FIELD &&
+                    exprs[0]->m_next->m_vtype == TYPE_VECTOR)
+                {
+                    assignop = type_storep_instr[TYPE_VECTOR];
+                }
+                else
+                    assignop = type_storep_instr[exprs[0]->m_vtype];
+                if (assignop == VINSTR_END || !field->m_next->compareType(*exprs[1]))
+                {
+                    ast_type_to_string(field->m_next, ty1, sizeof(ty1));
+                    ast_type_to_string(exprs[1], ty2, sizeof(ty2));
+                    if (OPTS_FLAG(ASSIGN_FUNCTION_TYPES) &&
+                        field->m_next->m_vtype == TYPE_FUNCTION &&
+                        exprs[1]->m_vtype == TYPE_FUNCTION)
+                    {
+                        (void)!compile_warning(ctx, WARN_ASSIGN_FUNCTION_TYPES,
+                                               "invalid types in assignment: cannot assign %s to %s", ty2, ty1);
+                    }
+                    else
+                        compile_error(ctx, "invalid types in assignment: cannot assign %s to %s", ty2, ty1);
+                }
+            }
+            else
+            {
+                if (OPTS_FLAG(ADJUST_VECTOR_FIELDS) &&
+                    exprs[0]->m_vtype == TYPE_FIELD &&
+                    exprs[0]->m_next->m_vtype == TYPE_VECTOR)
+                {
+                    assignop = type_store_instr[TYPE_VECTOR];
+                }
+                else {
+                    assignop = type_store_instr[exprs[0]->m_vtype];
+                }
+
+                if (assignop == VINSTR_END) {
+                    ast_type_to_string(exprs[0], ty1, sizeof(ty1));
+                    ast_type_to_string(exprs[1], ty2, sizeof(ty2));
+                    compile_error(ctx, "invalid types in assignment: cannot assign %s to %s", ty2, ty1);
+                }
+                else if (!exprs[0]->compareType(*exprs[1]))
+                {
+                    ast_type_to_string(exprs[0], ty1, sizeof(ty1));
+                    ast_type_to_string(exprs[1], ty2, sizeof(ty2));
+                    if (OPTS_FLAG(ASSIGN_FUNCTION_TYPES) &&
+                        exprs[0]->m_vtype == TYPE_FUNCTION &&
+                        exprs[1]->m_vtype == TYPE_FUNCTION)
+                    {
+                        (void)!compile_warning(ctx, WARN_ASSIGN_FUNCTION_TYPES,
+                                               "invalid types in assignment: cannot assign %s to %s", ty2, ty1);
+                    }
+                    else
+                        compile_error(ctx, "invalid types in assignment: cannot assign %s to %s", ty2, ty1);
+                }
+            }
+            (void)check_write_to(ctx, exprs[0]);
+            /* When we're a vector of part of an entity field we use STOREP */
+            if (ast_istype(exprs[0], ast_member) && ast_istype(((ast_member*)exprs[0])->m_owner, ast_entfield))
+                assignop = INSTR_STOREP_F;
+            out = new ast_store(ctx, assignop, exprs[0], exprs[1]);
+            break;
+        case opid3('+','+','P'):
+        case opid3('-','-','P'):
+            /* prefix ++ */
+            if (exprs[0]->m_vtype != TYPE_FLOAT) {
+                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
+                compile_error(exprs[0]->m_context, "invalid type for prefix increment: %s", ty1);
+                return false;
+            }
+            if (op->id == opid3('+','+','P'))
+                addop = INSTR_ADD_F;
+            else
+                addop = INSTR_SUB_F;
+            (void)check_write_to(exprs[0]->m_context, exprs[0]);
+            if (ast_istype(exprs[0], ast_entfield)) {
+                out = new ast_binstore(ctx, INSTR_STOREP_F, addop,
+                                       exprs[0],
+                                       parser->m_fold.imm_float(1));
+            } else {
+                out = new ast_binstore(ctx, INSTR_STORE_F, addop,
+                                       exprs[0],
+                                       parser->m_fold.imm_float(1));
+            }
+            break;
+        case opid3('S','+','+'):
+        case opid3('S','-','-'):
+            /* prefix ++ */
+            if (exprs[0]->m_vtype != TYPE_FLOAT) {
+                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
+                compile_error(exprs[0]->m_context, "invalid type for suffix increment: %s", ty1);
+                return false;
+            }
+            if (op->id == opid3('S','+','+')) {
+                addop = INSTR_ADD_F;
+                subop = INSTR_SUB_F;
+            } else {
+                addop = INSTR_SUB_F;
+                subop = INSTR_ADD_F;
+            }
+            (void)check_write_to(exprs[0]->m_context, exprs[0]);
+            if (ast_istype(exprs[0], ast_entfield)) {
+                out = new ast_binstore(ctx, INSTR_STOREP_F, addop,
+                                       exprs[0],
+                                       parser->m_fold.imm_float(1));
+            } else {
+                out = new ast_binstore(ctx, INSTR_STORE_F, addop,
+                                       exprs[0],
+                                       parser->m_fold.imm_float(1));
+            }
+            if (!out)
+                return false;
+            out = fold::binary(ctx, subop,
+                              out,
+                              parser->m_fold.imm_float(1));
+
+            break;
+        case opid2('+','='):
+        case opid2('-','='):
+            if (exprs[0]->m_vtype != exprs[1]->m_vtype ||
+                (exprs[0]->m_vtype != TYPE_VECTOR && exprs[0]->m_vtype != TYPE_FLOAT) )
+            {
+                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
+                ast_type_to_string(exprs[1], ty2, sizeof(ty2));
+                compile_error(ctx, "invalid types used in expression: cannot add or subtract type %s and %s",
+                              ty1, ty2);
+                return false;
+            }
+            (void)check_write_to(ctx, exprs[0]);
+            if (ast_istype(exprs[0], ast_entfield))
+                assignop = type_storep_instr[exprs[0]->m_vtype];
+            else
+                assignop = type_store_instr[exprs[0]->m_vtype];
+            switch (exprs[0]->m_vtype) {
+                case TYPE_FLOAT:
+                    out = new ast_binstore(ctx, assignop,
+                                           (op->id == opid2('+','=') ? INSTR_ADD_F : INSTR_SUB_F),
+                                           exprs[0], exprs[1]);
+                    break;
+                case TYPE_VECTOR:
+                    out = new ast_binstore(ctx, assignop,
+                                           (op->id == opid2('+','=') ? INSTR_ADD_V : INSTR_SUB_V),
+                                           exprs[0], exprs[1]);
+                    break;
+                default:
+                    compile_error(ctx, "invalid types used in expression: cannot add or subtract type %s and %s",
+                                  type_name[exprs[0]->m_vtype],
+                                  type_name[exprs[1]->m_vtype]);
+                    return false;
+            };
+            break;
+        case opid2('*','='):
+        case opid2('/','='):
+            if (exprs[1]->m_vtype != TYPE_FLOAT ||
+                !(exprs[0]->m_vtype == TYPE_FLOAT ||
+                  exprs[0]->m_vtype == TYPE_VECTOR))
+            {
+                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
+                ast_type_to_string(exprs[1], ty2, sizeof(ty2));
+                compile_error(ctx, "invalid types used in expression: %s and %s",
+                              ty1, ty2);
+                return false;
+            }
+            (void)check_write_to(ctx, exprs[0]);
+            if (ast_istype(exprs[0], ast_entfield))
+                assignop = type_storep_instr[exprs[0]->m_vtype];
+            else
+                assignop = type_store_instr[exprs[0]->m_vtype];
+            switch (exprs[0]->m_vtype) {
+                case TYPE_FLOAT:
+                    out = new ast_binstore(ctx, assignop,
+                                           (op->id == opid2('*','=') ? INSTR_MUL_F : INSTR_DIV_F),
+                                           exprs[0], exprs[1]);
+                    break;
+                case TYPE_VECTOR:
+                    if (op->id == opid2('*','=')) {
+                        out = new ast_binstore(ctx, assignop, INSTR_MUL_VF,
+                                               exprs[0], exprs[1]);
+                    } else {
+                        out = fold::binary(ctx, INSTR_DIV_F,
+                                         parser->m_fold.imm_float(1),
+                                         exprs[1]);
+                        if (!out) {
+                            compile_error(ctx, "internal error: failed to generate division");
+                            return false;
+                        }
+                        out = new ast_binstore(ctx, assignop, INSTR_MUL_VF,
+                                               exprs[0], out);
+                    }
+                    break;
+                default:
+                    compile_error(ctx, "invalid types used in expression: cannot add or subtract type %s and %s",
+                                  type_name[exprs[0]->m_vtype],
+                                  type_name[exprs[1]->m_vtype]);
+                    return false;
+            };
+            break;
+        case opid2('&','='):
+        case opid2('|','='):
+        case opid2('^','='):
+            if (NotSameType(TYPE_FLOAT) && NotSameType(TYPE_VECTOR)) {
+                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
+                ast_type_to_string(exprs[1], ty2, sizeof(ty2));
+                compile_error(ctx, "invalid types used in expression: %s and %s",
+                              ty1, ty2);
+                return false;
+            }
+            (void)check_write_to(ctx, exprs[0]);
+            if (ast_istype(exprs[0], ast_entfield))
+                assignop = type_storep_instr[exprs[0]->m_vtype];
+            else
+                assignop = type_store_instr[exprs[0]->m_vtype];
+            if (exprs[0]->m_vtype == TYPE_FLOAT)
+                out = new ast_binstore(ctx, assignop,
+                                       (op->id == opid2('^','=') ? VINSTR_BITXOR : op->id == opid2('&','=') ? INSTR_BITAND : INSTR_BITOR),
+                                       exprs[0], exprs[1]);
+            else
+                out = new ast_binstore(ctx, assignop,
+                                       (op->id == opid2('^','=') ? VINSTR_BITXOR_V : op->id == opid2('&','=') ? VINSTR_BITAND_V : VINSTR_BITOR_V),
+                                       exprs[0], exprs[1]);
+            break;
+        case opid3('&','~','='):
+            /* This is like: a &= ~(b);
+             * But QC has no bitwise-not, so we implement it as
+             * a -= a & (b);
+             */
+            if (NotSameType(TYPE_FLOAT) && NotSameType(TYPE_VECTOR)) {
+                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
+                ast_type_to_string(exprs[1], ty2, sizeof(ty2));
+                compile_error(ctx, "invalid types used in expression: %s and %s",
+                              ty1, ty2);
+                return false;
+            }
+            if (ast_istype(exprs[0], ast_entfield))
+                assignop = type_storep_instr[exprs[0]->m_vtype];
+            else
+                assignop = type_store_instr[exprs[0]->m_vtype];
+            if (exprs[0]->m_vtype == TYPE_FLOAT)
+                out = fold::binary(ctx, INSTR_BITAND, exprs[0], exprs[1]);
+            else
+                out = fold::binary(ctx, VINSTR_BITAND_V, exprs[0], exprs[1]);
+            if (!out)
+                return false;
+            (void)check_write_to(ctx, exprs[0]);
+            if (exprs[0]->m_vtype == TYPE_FLOAT)
+                asbinstore = new ast_binstore(ctx, assignop, INSTR_SUB_F, exprs[0], out);
+            else
+                asbinstore = new ast_binstore(ctx, assignop, INSTR_SUB_V, exprs[0], out);
+            asbinstore->m_keep_dest = true;
+            out = asbinstore;
+            break;
+
+        case opid3('l', 'e', 'n'):
+            if (exprs[0]->m_vtype != TYPE_STRING && exprs[0]->m_vtype != TYPE_ARRAY) {
+                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
+                compile_error(exprs[0]->m_context, "invalid type for length operator: %s", ty1);
+                return false;
+            }
+            /* strings must be const, arrays are statically sized */
+            if (exprs[0]->m_vtype == TYPE_STRING &&
+                !(((ast_value*)exprs[0])->m_hasvalue && ((ast_value*)exprs[0])->m_cvq == CV_CONST))
+            {
+                compile_error(exprs[0]->m_context, "operand of length operator not a valid constant expression");
+                return false;
+            }
+            out = parser->m_fold.op(op, exprs);
+            break;
+
+        case opid2('~', 'P'):
+            if (exprs[0]->m_vtype != TYPE_FLOAT && exprs[0]->m_vtype != TYPE_VECTOR) {
+                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
+                compile_error(exprs[0]->m_context, "invalid type for bit not: %s", ty1);
+                return false;
+            }
+            if (!(out = parser->m_fold.op(op, exprs))) {
+                if (exprs[0]->m_vtype == TYPE_FLOAT) {
+                    out = fold::binary(ctx, INSTR_SUB_F, parser->m_fold.imm_float(2), exprs[0]);
+                } else {
+                    out = fold::binary(ctx, INSTR_SUB_V, parser->m_fold.imm_vector(1), exprs[0]);
+                }
+            }
+            break;
+    }
+#undef NotSameType
+    if (!out) {
+        compile_error(ctx, "failed to apply operator %s", op->op);
+        return false;
+    }
+
+    sy->out.push_back(syexp(ctx, out));
+    return true;
+}
+
+static bool parser_close_call(parser_t *parser, shunt *sy)
+{
+    /* was a function call */
+    ast_expression *fun;
+    ast_value      *funval = nullptr;
+    ast_call       *call;
+
+    size_t          fid;
+    size_t          paramcount, i;
+    bool            fold = true;
+
+    fid = sy->ops.back().off;
+    sy->ops.pop_back();
+
+    /* out[fid] is the function
+     * everything above is parameters...
+     */
+    if (sy->argc.empty()) {
+        parseerror(parser, "internal error: no argument counter available");
+        return false;
+    }
+
+    paramcount = sy->argc.back();
+    sy->argc.pop_back();
+
+    if (sy->out.size() < fid) {
+        parseerror(parser, "internal error: broken function call %zu < %zu+%zu\n",
+                   sy->out.size(),
+                   fid,
+                   paramcount);
+        return false;
+    }
+
+    /*
+     * TODO handle this at the intrinsic level with an ast_intrinsic
+     * node and codegen.
+     */
+    if ((fun = sy->out[fid].out) == parser->m_intrin.debug_typestring()) {
+        char ty[1024];
+        if (fid+2 != sy->out.size() || sy->out.back().block) {
+            parseerror(parser, "intrinsic __builtin_debug_typestring requires exactly 1 parameter");
+            return false;
+        }
+        ast_type_to_string(sy->out.back().out, ty, sizeof(ty));
+        ast_unref(sy->out.back().out);
+        sy->out[fid] = syexp(sy->out.back().out->m_context,
+                             parser->m_fold.constgen_string(ty, false));
+        sy->out.pop_back();
+        return true;
+    }
+
+    /*
+     * Now we need to determine if the function that is being called is
+     * an intrinsic so we can evaluate if the arguments to it are constant
+     * and than fruitfully fold them.
+     */
+#define fold_can_1(X)  \
+    (ast_istype(((X)), ast_value) && (X)->m_hasvalue && ((X)->m_cvq == CV_CONST) && \
+                ((X))->m_vtype != TYPE_FUNCTION)
+
+    if (fid + 1 < sy->out.size())
+        ++paramcount;
+
+    for (i = 0; i < paramcount; ++i) {
+        if (!fold_can_1((ast_value*)sy->out[fid + 1 + i].out)) {
+            fold = false;
+            break;
+        }
+    }
+
+    /*
+     * All is well which ends well, if we make it into here we can ignore the
+     * intrinsic call and just evaluate it i.e constant fold it.
+     */
+    if (fold && ast_istype(fun, ast_value) && ((ast_value*)fun)->m_intrinsic) {
+        ast_expression **exprs  = nullptr;
+        ast_expression *foldval = nullptr;
+
+        for (i = 0; i < paramcount; i++)
+            vec_push(exprs, sy->out[fid+1 + i].out);
+
+        if (!(foldval = parser->m_intrin.do_fold((ast_value*)fun, exprs))) {
+            vec_free(exprs);
+            goto fold_leave;
+        }
+
+        /*
+         * Blub: what sorts of unreffing and resizing of
+         * sy->out should I be doing here?
+         */
+        sy->out[fid] = syexp(foldval->m_context, foldval);
+        sy->out.erase(sy->out.end() - paramcount, sy->out.end());
+        vec_free(exprs);
+
+        return true;
+    }
+
+    fold_leave:
+    call = ast_call::make(sy->ops[sy->ops.size()].ctx, fun);
+
+    if (!call)
+        return false;
+
+    if (fid+1 + paramcount != sy->out.size()) {
+        parseerror(parser, "internal error: parameter count mismatch: (%zu+1+%zu), %zu",
+                   fid,
+                   paramcount,
+                   sy->out.size());
+        return false;
+    }
+
+    for (i = 0; i < paramcount; ++i)
+        call->m_params.push_back(sy->out[fid+1 + i].out);
+    sy->out.erase(sy->out.end() - paramcount, sy->out.end());
+    (void)!call->checkTypes(parser->function->m_function_type->m_varparam);
+    if (parser->max_param_count < paramcount)
+        parser->max_param_count = paramcount;
+
+    if (ast_istype(fun, ast_value)) {
+        funval = (ast_value*)fun;
+        if ((fun->m_flags & AST_FLAG_VARIADIC) &&
+            !(/*funval->m_cvq == CV_CONST && */ funval->m_hasvalue && funval->m_constval.vfunc->m_builtin))
+        {
+            call->m_va_count = parser->m_fold.constgen_float((qcfloat_t)paramcount, false);
+        }
+    }
+
+    /* overwrite fid, the function, with a call */
+    sy->out[fid] = syexp(call->m_context, call);
+
+    if (fun->m_vtype != TYPE_FUNCTION) {
+        parseerror(parser, "not a function (%s)", type_name[fun->m_vtype]);
+        return false;
+    }
+
+    if (!fun->m_next) {
+        parseerror(parser, "could not determine function return type");
+        return false;
+    } else {
+        ast_value *fval = (ast_istype(fun, ast_value) ? ((ast_value*)fun) : nullptr);
+
+        if (fun->m_flags & AST_FLAG_DEPRECATED) {
+            if (!fval) {
+                return !parsewarning(parser, WARN_DEPRECATED,
+                        "call to function (which is marked deprecated)\n",
+                        "-> it has been declared here: %s:%i",
+                        fun->m_context.file, fun->m_context.line);
+            }
+            if (!fval->m_desc.length()) {
+                return !parsewarning(parser, WARN_DEPRECATED,
+                        "call to `%s` (which is marked deprecated)\n"
+                        "-> `%s` declared here: %s:%i",
+                        fval->m_name, fval->m_name, fun->m_context.file, fun->m_context.line);
+            }
+            return !parsewarning(parser, WARN_DEPRECATED,
+                    "call to `%s` (deprecated: %s)\n"
+                    "-> `%s` declared here: %s:%i",
+                    fval->m_name, fval->m_desc, fval->m_name, fun->m_context.file,
+                    fun->m_context.line);
+        }
+
+        if (fun->m_type_params.size() != paramcount &&
+            !((fun->m_flags & AST_FLAG_VARIADIC) &&
+              fun->m_type_params.size() < paramcount))
+        {
+            const char *fewmany = (fun->m_type_params.size() > paramcount) ? "few" : "many";
+            if (fval)
+                return !parsewarning(parser, WARN_INVALID_PARAMETER_COUNT,
+                                     "too %s parameters for call to %s: expected %i, got %i\n"
+                                     " -> `%s` has been declared here: %s:%i",
+                                     fewmany, fval->m_name, (int)fun->m_type_params.size(), (int)paramcount,
+                                     fval->m_name, fun->m_context.file, (int)fun->m_context.line);
+            else
+                return !parsewarning(parser, WARN_INVALID_PARAMETER_COUNT,
+                                     "too %s parameters for function call: expected %i, got %i\n"
+                                     " -> it has been declared here: %s:%i",
+                                     fewmany, (int)fun->m_type_params.size(), (int)paramcount,
+                                     fun->m_context.file, (int)fun->m_context.line);
+        }
+    }
+
+    return true;
+}
+
+static bool parser_close_paren(parser_t *parser, shunt *sy)
+{
+    if (sy->ops.empty()) {
+        parseerror(parser, "unmatched closing paren");
+        return false;
+    }
+
+    while (sy->ops.size()) {
+        if (sy->ops.back().isparen) {
+            if (sy->paren.back() == PAREN_FUNC) {
+                sy->paren.pop_back();
+                if (!parser_close_call(parser, sy))
+                    return false;
+                break;
+            }
+            if (sy->paren.back() == PAREN_EXPR) {
+                sy->paren.pop_back();
+                if (sy->out.empty()) {
+                    compile_error(sy->ops.back().ctx, "empty paren expression");
+                    sy->ops.pop_back();
+                    return false;
+                }
+                sy->ops.pop_back();
+                break;
+            }
+            if (sy->paren.back() == PAREN_INDEX) {
+                sy->paren.pop_back();
+                // pop off the parenthesis
+                sy->ops.pop_back();
+                /* then apply the index operator */
+                if (!parser_sy_apply_operator(parser, sy))
+                    return false;
+                break;
+            }
+            if (sy->paren.back() == PAREN_TERNARY1) {
+                sy->paren.back() = PAREN_TERNARY2;
+                // pop off the parenthesis
+                sy->ops.pop_back();
+                break;
+            }
+            compile_error(sy->ops.back().ctx, "invalid parenthesis");
+            return false;
+        }
+        if (!parser_sy_apply_operator(parser, sy))
+            return false;
+    }
+    return true;
+}
+
+static void parser_reclassify_token(parser_t *parser)
+{
+    size_t i;
+    if (parser->tok >= TOKEN_START)
+        return;
+    for (i = 0; i < operator_count; ++i) {
+        if (!strcmp(parser_tokval(parser), operators[i].op)) {
+            parser->tok = TOKEN_OPERATOR;
+            return;
+        }
+    }
+}
+
+static ast_expression* parse_vararg_do(parser_t *parser)
+{
+    ast_expression *idx, *out;
+    ast_value      *typevar;
+    ast_value      *funtype = parser->function->m_function_type;
+    lex_ctx_t         ctx     = parser_ctx(parser);
+
+    if (!parser->function->m_varargs) {
+        parseerror(parser, "function has no variable argument list");
+        return nullptr;
+    }
+
+    if (!parser_next(parser) || parser->tok != '(') {
+        parseerror(parser, "expected parameter index and type in parenthesis");
+        return nullptr;
+    }
+    if (!parser_next(parser)) {
+        parseerror(parser, "error parsing parameter index");
+        return nullptr;
+    }
+
+    idx = parse_expression_leave(parser, true, false, false);
+    if (!idx)
+        return nullptr;
+
+    if (parser->tok != ',') {
+        if (parser->tok != ')') {
+            ast_unref(idx);
+            parseerror(parser, "expected comma after parameter index");
+            return nullptr;
+        }
+        // vararg piping: ...(start)
+        out = new ast_argpipe(ctx, idx);
+        return out;
+    }
+
+    if (!parser_next(parser) || (parser->tok != TOKEN_IDENT && parser->tok != TOKEN_TYPENAME)) {
+        ast_unref(idx);
+        parseerror(parser, "expected typename for vararg");
+        return nullptr;
+    }
+
+    typevar = parse_typename(parser, nullptr, nullptr, nullptr);
+    if (!typevar) {
+        ast_unref(idx);
+        return nullptr;
+    }
+
+    if (parser->tok != ')') {
+        ast_unref(idx);
+        delete typevar;
+        parseerror(parser, "expected closing paren");
+        return nullptr;
+    }
+
+    if (funtype->m_varparam &&
+        !typevar->compareType(*funtype->m_varparam))
+    {
+        char ty1[1024];
+        char ty2[1024];
+        ast_type_to_string(typevar, ty1, sizeof(ty1));
+        ast_type_to_string(funtype->m_varparam, ty2, sizeof(ty2));
+        compile_error(typevar->m_context,
+                      "function was declared to take varargs of type `%s`, requested type is: %s",
+                      ty2, ty1);
+    }
+
+    out = ast_array_index::make(ctx, parser->function->m_varargs.get(), idx);
+    out->adoptType(*typevar);
+    delete typevar;
+    return out;
+}
+
+static ast_expression* parse_vararg(parser_t *parser)
+{
+    bool           old_noops = parser->lex->flags.noops;
+
+    ast_expression *out;
+
+    parser->lex->flags.noops = true;
+    out = parse_vararg_do(parser);
+
+    parser->lex->flags.noops = old_noops;
+    return out;
+}
+
+/* not to be exposed */
+bool ftepp_predef_exists(const char *name);
+static bool parse_sya_operand(parser_t *parser, shunt *sy, bool with_labels)
+{
+    if (OPTS_FLAG(TRANSLATABLE_STRINGS) &&
+        parser->tok == TOKEN_IDENT &&
+        !strcmp(parser_tokval(parser), "_"))
+    {
+        /* a translatable string */
+        ast_value *val;
+
+        parser->lex->flags.noops = true;
+        if (!parser_next(parser) || parser->tok != '(') {
+            parseerror(parser, "use _(\"string\") to create a translatable string constant");
+            return false;
+        }
+        parser->lex->flags.noops = false;
+        if (!parser_next(parser) || parser->tok != TOKEN_STRINGCONST) {
+            parseerror(parser, "expected a constant string in translatable-string extension");
+            return false;
+        }
+        val = (ast_value*)parser->m_fold.constgen_string(parser_tokval(parser), true);
+        if (!val)
+            return false;
+        sy->out.push_back(syexp(parser_ctx(parser), val));
+
+        if (!parser_next(parser) || parser->tok != ')') {
+            parseerror(parser, "expected closing paren after translatable string");
+            return false;
+        }
+        return true;
+    }
+    else if (parser->tok == TOKEN_DOTS)
+    {
+        ast_expression *va;
+        if (!OPTS_FLAG(VARIADIC_ARGS)) {
+            parseerror(parser, "cannot access varargs (try -fvariadic-args)");
+            return false;
+        }
+        va = parse_vararg(parser);
+        if (!va)
+            return false;
+        sy->out.push_back(syexp(parser_ctx(parser), va));
+        return true;
+    }
+    else if (parser->tok == TOKEN_FLOATCONST) {
+        ast_expression *val = parser->m_fold.constgen_float((parser_token(parser)->constval.f), false);
+        if (!val)
+            return false;
+        sy->out.push_back(syexp(parser_ctx(parser), val));
+        return true;
+    }
+    else if (parser->tok == TOKEN_INTCONST || parser->tok == TOKEN_CHARCONST) {
+        ast_expression *val = parser->m_fold.constgen_float((qcfloat_t)(parser_token(parser)->constval.i), false);
+        if (!val)
+            return false;
+        sy->out.push_back(syexp(parser_ctx(parser), val));
+        return true;
+    }
+    else if (parser->tok == TOKEN_STRINGCONST) {
+        ast_expression *val = parser->m_fold.constgen_string(parser_tokval(parser), false);
+        if (!val)
+            return false;
+        sy->out.push_back(syexp(parser_ctx(parser), val));
+        return true;
+    }
+    else if (parser->tok == TOKEN_VECTORCONST) {
+        ast_expression *val = parser->m_fold.constgen_vector(parser_token(parser)->constval.v);
+        if (!val)
+            return false;
+        sy->out.push_back(syexp(parser_ctx(parser), val));
+        return true;
+    }
+    else if (parser->tok == TOKEN_IDENT)
+    {
+        const char     *ctoken = parser_tokval(parser);
+        ast_expression *prev = sy->out.size() ? sy->out.back().out : nullptr;
+        ast_expression *var;
+        /* a_vector.{x,y,z} */
+        if (sy->ops.empty() ||
+            !sy->ops.back().etype ||
+            operators[sy->ops.back().etype-1].id != opid1('.'))
+        {
+            /* When adding more intrinsics, fix the above condition */
+            prev = nullptr;
+        }
+        if (prev && prev->m_vtype == TYPE_VECTOR && ctoken[0] >= 'x' && ctoken[0] <= 'z' && !ctoken[1])
+        {
+            var = parser->const_vec[ctoken[0]-'x'];
+        } else {
+            var = parser_find_var(parser, parser_tokval(parser));
+            if (!var)
+                var = parser_find_field(parser, parser_tokval(parser));
+        }
+        if (!var && with_labels) {
+            var = parser_find_label(parser, parser_tokval(parser));
+            if (!with_labels) {
+                ast_label *lbl = new ast_label(parser_ctx(parser), parser_tokval(parser), true);
+                var = lbl;
+                parser->labels.push_back(lbl);
+            }
+        }
+        if (!var && !strcmp(parser_tokval(parser), "__FUNC__"))
+            var = parser->m_fold.constgen_string(parser->function->m_name, false);
+        if (!var) {
+            /*
+             * now we try for the real intrinsic hashtable. If the string
+             * begins with __builtin, we simply skip past it, otherwise we
+             * use the identifier as is.
+             */
+            if (!strncmp(parser_tokval(parser), "__builtin_", 10)) {
+                var = parser->m_intrin.func(parser_tokval(parser));
+            }
+
+            /*
+             * Try it again, intrin_func deals with the alias method as well
+             * the first one masks for __builtin though, we emit warning here.
+             */
+            if (!var) {
+                if ((var = parser->m_intrin.func(parser_tokval(parser)))) {
+                    (void)!compile_warning(
+                        parser_ctx(parser),
+                        WARN_BUILTINS,
+                        "using implicitly defined builtin `__builtin_%s' for `%s'",
+                        parser_tokval(parser),
+                        parser_tokval(parser)
+                    );
+                }
+            }
+
+
+            if (!var) {
+                /*
+                 * sometimes people use preprocessing predefs without enabling them
+                 * i've done this thousands of times already myself.  Lets check for
+                 * it in the predef table.  And diagnose it better :)
+                 */
+                if (!OPTS_FLAG(FTEPP_PREDEFS) && ftepp_predef_exists(parser_tokval(parser))) {
+                    parseerror(parser, "unexpected identifier: %s (use -fftepp-predef to enable pre-defined macros)", parser_tokval(parser));
+                    return false;
+                }
+
+                parseerror(parser, "unexpected identifier: %s", parser_tokval(parser));
+                return false;
+            }
+        }
+        else
+        {
+            if (ast_istype(var, ast_value)) {
+                ((ast_value*)var)->m_uses++;
+            }
+            else if (ast_istype(var, ast_member)) {
+                ast_member *mem = (ast_member*)var;
+                if (ast_istype(mem->m_owner, ast_value))
+                    ((ast_value*)(mem->m_owner))->m_uses++;
+            }
+        }
+        sy->out.push_back(syexp(parser_ctx(parser), var));
+        return true;
+    }
+    parseerror(parser, "unexpected token `%s`", parser_tokval(parser));
+    return false;
+}
+
+static ast_expression* parse_expression_leave(parser_t *parser, bool stopatcomma, bool truthvalue, bool with_labels)
+{
+    ast_expression *expr = nullptr;
+    shunt sy;
+    bool wantop = false;
+    /* only warn once about an assignment in a truth value because the current code
+     * would trigger twice on: if(a = b && ...), once for the if-truth-value, once for the && part
+     */
+    bool warn_parenthesis = true;
+
+    /* count the parens because an if starts with one, so the
+     * end of a condition is an unmatched closing paren
+     */
+    int ternaries = 0;
+
+    memset(&sy, 0, sizeof(sy));
+
+    parser->lex->flags.noops = false;
+
+    parser_reclassify_token(parser);
+
+    while (true)
+    {
+        if (parser->tok == TOKEN_TYPENAME) {
+            parseerror(parser, "unexpected typename `%s`", parser_tokval(parser));
+            goto onerr;
+        }
+
+        if (parser->tok == TOKEN_OPERATOR)
+        {
+            /* classify the operator */
+            const oper_info *op;
+            const oper_info *olast = nullptr;
+            size_t o;
+            for (o = 0; o < operator_count; ++o) {
+                if (((!(operators[o].flags & OP_PREFIX) == !!wantop)) &&
+                    /* !(operators[o].flags & OP_SUFFIX) && / * remove this */
+                    !strcmp(parser_tokval(parser), operators[o].op))
+                {
+                    break;
+                }
+            }
+            if (o == operator_count) {
+                compile_error(parser_ctx(parser), "unexpected operator: %s", parser_tokval(parser));
+                goto onerr;
+            }
+            /* found an operator */
+            op = &operators[o];
+
+            /* when declaring variables, a comma starts a new variable */
+            if (op->id == opid1(',') && sy.paren.empty() && stopatcomma) {
+                /* fixup the token */
+                parser->tok = ',';
+                break;
+            }
+
+            /* a colon without a pervious question mark cannot be a ternary */
+            if (!ternaries && op->id == opid2(':','?')) {
+                parser->tok = ':';
+                break;
+            }
+
+            if (op->id == opid1(',')) {
+                if (sy.paren.size() && sy.paren.back() == PAREN_TERNARY2) {
+                    (void)!parsewarning(parser, WARN_TERNARY_PRECEDENCE, "suggesting parenthesis around ternary expression");
+                }
+            }
+
+            if (sy.ops.size() && !sy.ops.back().isparen)
+                olast = &operators[sy.ops.back().etype-1];
+
+            /* first only apply higher precedences, assoc_left+equal comes after we warn about precedence rules */
+            while (olast && op->prec < olast->prec)
+            {
+                if (!parser_sy_apply_operator(parser, &sy))
+                    goto onerr;
+                if (sy.ops.size() && !sy.ops.back().isparen)
+                    olast = &operators[sy.ops.back().etype-1];
+                else
+                    olast = nullptr;
+            }
+
+#define IsAssignOp(x) (\
+                (x) == opid1('=') || \
+                (x) == opid2('+','=') || \
+                (x) == opid2('-','=') || \
+                (x) == opid2('*','=') || \
+                (x) == opid2('/','=') || \
+                (x) == opid2('%','=') || \
+                (x) == opid2('&','=') || \
+                (x) == opid2('|','=') || \
+                (x) == opid3('&','~','=') \
+                )
+            if (warn_parenthesis) {
+                if ( (olast && IsAssignOp(olast->id) && (op->id == opid2('&','&') || op->id == opid2('|','|'))) ||
+                     (olast && IsAssignOp(op->id) && (olast->id == opid2('&','&') || olast->id == opid2('|','|'))) ||
+                     (truthvalue && sy.paren.empty() && IsAssignOp(op->id))
+                   )
+                {
+                    (void)!parsewarning(parser, WARN_PARENTHESIS, "suggesting parenthesis around assignment used as truth value");
+                    warn_parenthesis = false;
+                }
+
+                if (olast && olast->id != op->id) {
+                    if ((op->id    == opid1('&') || op->id    == opid1('|') || op->id    == opid1('^')) &&
+                        (olast->id == opid1('&') || olast->id == opid1('|') || olast->id == opid1('^')))
+                    {
+                        (void)!parsewarning(parser, WARN_PARENTHESIS, "suggesting parenthesis around bitwise operations");
+                        warn_parenthesis = false;
+                    }
+                    else if ((op->id    == opid2('&','&') || op->id    == opid2('|','|')) &&
+                             (olast->id == opid2('&','&') || olast->id == opid2('|','|')))
+                    {
+                        (void)!parsewarning(parser, WARN_PARENTHESIS, "suggesting parenthesis around logical operations");
+                        warn_parenthesis = false;
+                    }
+                }
+            }
+
+            while (olast && (
+                    (op->prec < olast->prec) ||
+                    (op->assoc == ASSOC_LEFT && op->prec <= olast->prec) ) )
+            {
+                if (!parser_sy_apply_operator(parser, &sy))
+                    goto onerr;
+                if (sy.ops.size() && !sy.ops.back().isparen)
+                    olast = &operators[sy.ops.back().etype-1];
+                else
+                    olast = nullptr;
+            }
+
+            if (op->id == opid1('(')) {
+                if (wantop) {
+                    size_t sycount = sy.out.size();
+                    /* we expected an operator, this is the function-call operator */
+                    sy.paren.push_back(PAREN_FUNC);
+                    sy.ops.push_back(syparen(parser_ctx(parser), sycount-1));
+                    sy.argc.push_back(0);
+                } else {
+                    sy.paren.push_back(PAREN_EXPR);
+                    sy.ops.push_back(syparen(parser_ctx(parser), 0));
+                }
+                wantop = false;
+            } else if (op->id == opid1('[')) {
+                if (!wantop) {
+                    parseerror(parser, "unexpected array subscript");
+                    goto onerr;
+                }
+                sy.paren.push_back(PAREN_INDEX);
+                /* push both the operator and the paren, this makes life easier */
+                sy.ops.push_back(syop(parser_ctx(parser), op));
+                sy.ops.push_back(syparen(parser_ctx(parser), 0));
+                wantop = false;
+            } else if (op->id == opid2('?',':')) {
+                sy.ops.push_back(syop(parser_ctx(parser), op));
+                sy.ops.push_back(syparen(parser_ctx(parser), 0));
+                wantop = false;
+                ++ternaries;
+                sy.paren.push_back(PAREN_TERNARY1);
+            } else if (op->id == opid2(':','?')) {
+                if (sy.paren.empty()) {
+                    parseerror(parser, "unexpected colon outside ternary expression (missing parenthesis?)");
+                    goto onerr;
+                }
+                if (sy.paren.back() != PAREN_TERNARY1) {
+                    parseerror(parser, "unexpected colon outside ternary expression (missing parenthesis?)");
+                    goto onerr;
+                }
+                if (!parser_close_paren(parser, &sy))
+                    goto onerr;
+                sy.ops.push_back(syop(parser_ctx(parser), op));
+                wantop = false;
+                --ternaries;
+            } else {
+                sy.ops.push_back(syop(parser_ctx(parser), op));
+                wantop = !!(op->flags & OP_SUFFIX);
+            }
+        }
+        else if (parser->tok == ')') {
+            while (sy.paren.size() && sy.paren.back() == PAREN_TERNARY2) {
+                if (!parser_sy_apply_operator(parser, &sy))
+                    goto onerr;
+            }
+            if (sy.paren.empty())
+                break;
+            if (wantop) {
+                if (sy.paren.back() == PAREN_TERNARY1) {
+                    parseerror(parser, "mismatched parentheses (closing paren in ternary expression?)");
+                    goto onerr;
+                }
+                if (!parser_close_paren(parser, &sy))
+                    goto onerr;
+            } else {
+                /* must be a function call without parameters */
+                if (sy.paren.back() != PAREN_FUNC) {
+                    parseerror(parser, "closing paren in invalid position");
+                    goto onerr;
+                }
+                if (!parser_close_paren(parser, &sy))
+                    goto onerr;
+            }
+            wantop = true;
+        }
+        else if (parser->tok == '(') {
+            parseerror(parser, "internal error: '(' should be classified as operator");
+            goto onerr;
+        }
+        else if (parser->tok == '[') {
+            parseerror(parser, "internal error: '[' should be classified as operator");
+            goto onerr;
+        }
+        else if (parser->tok == ']') {
+            while (sy.paren.size() && sy.paren.back() == PAREN_TERNARY2) {
+                if (!parser_sy_apply_operator(parser, &sy))
+                    goto onerr;
+            }
+            if (sy.paren.empty())
+                break;
+            if (sy.paren.back() != PAREN_INDEX) {
+                parseerror(parser, "mismatched parentheses, unexpected ']'");
+                goto onerr;
+            }
+            if (!parser_close_paren(parser, &sy))
+                goto onerr;
+            wantop = true;
+        }
+        else if (!wantop) {
+            if (!parse_sya_operand(parser, &sy, with_labels))
+                goto onerr;
+            wantop = true;
+        }
+        else {
+            /* in this case we might want to allow constant string concatenation */
+            bool concatenated = false;
+            if (parser->tok == TOKEN_STRINGCONST && sy.out.size()) {
+                ast_expression *lexpr = sy.out.back().out;
+                if (ast_istype(lexpr, ast_value)) {
+                    ast_value *last = (ast_value*)lexpr;
+                    if (last->m_isimm == true && last->m_cvq == CV_CONST &&
+                        last->m_hasvalue && last->m_vtype == TYPE_STRING)
+                    {
+                        char *newstr = nullptr;
+                        util_asprintf(&newstr, "%s%s", last->m_constval.vstring, parser_tokval(parser));
+                        sy.out.back().out = parser->m_fold.constgen_string(newstr, false);
+                        mem_d(newstr);
+                        concatenated = true;
+                    }
+                }
+            }
+            if (!concatenated) {
+                parseerror(parser, "expected operator or end of statement");
+                goto onerr;
+            }
+        }
+
+        if (!parser_next(parser)) {
+            goto onerr;
+        }
+        if (parser->tok == ';' ||
+            ((sy.paren.empty() || (sy.paren.size() == 1 && sy.paren.back() == PAREN_TERNARY2)) &&
+            (parser->tok == ']' || parser->tok == ')' || parser->tok == '}')))
+        {
+            break;
+        }
+    }
+
+    while (sy.ops.size()) {
+        if (!parser_sy_apply_operator(parser, &sy))
+            goto onerr;
+    }
+
+    parser->lex->flags.noops = true;
+    if (sy.out.size() != 1) {
+        parseerror(parser, "expression expected");
+        expr = nullptr;
+    } else
+        expr = sy.out[0].out;
+    if (sy.paren.size()) {
+        parseerror(parser, "internal error: sy.paren.size() = %zu", sy.paren.size());
+        return nullptr;
+    }
+    return expr;
+
+onerr:
+    parser->lex->flags.noops = true;
+    for (auto &it : sy.out)
+        if (it.out) ast_unref(it.out);
+    return nullptr;
+}
+
+static ast_expression* parse_expression(parser_t *parser, bool stopatcomma, bool with_labels)
+{
+    ast_expression *e = parse_expression_leave(parser, stopatcomma, false, with_labels);
+    if (!e)
+        return nullptr;
+    if (parser->tok != ';') {
+        parseerror(parser, "semicolon expected after expression");
+        ast_unref(e);
+        return nullptr;
+    }
+    if (!parser_next(parser)) {
+        ast_unref(e);
+        return nullptr;
+    }
+    return e;
+}
+
+static void parser_enterblock(parser_t *parser)
+{
+    vec_push(parser->variables, util_htnew(PARSER_HT_SIZE));
+    vec_push(parser->_blocklocals, vec_size(parser->_locals));
+    vec_push(parser->typedefs, util_htnew(TYPEDEF_HT_SIZE));
+    vec_push(parser->_blocktypedefs, vec_size(parser->_typedefs));
+    vec_push(parser->_block_ctx, parser_ctx(parser));
+}
+
+static bool parser_leaveblock(parser_t *parser)
+{
+    bool   rv = true;
+    size_t locals, typedefs;
+
+    if (vec_size(parser->variables) <= PARSER_HT_LOCALS) {
+        parseerror(parser, "internal error: parser_leaveblock with no block");
+        return false;
+    }
+
+    util_htdel(vec_last(parser->variables));
+
+    vec_pop(parser->variables);
+    if (!vec_size(parser->_blocklocals)) {
+        parseerror(parser, "internal error: parser_leaveblock with no block (2)");
+        return false;
+    }
+
+    locals = vec_last(parser->_blocklocals);
+    vec_pop(parser->_blocklocals);
+    while (vec_size(parser->_locals) != locals) {
+        ast_expression *e = vec_last(parser->_locals);
+        ast_value      *v = (ast_value*)e;
+        vec_pop(parser->_locals);
+        if (ast_istype(e, ast_value) && !v->m_uses) {
+            if (compile_warning(v->m_context, WARN_UNUSED_VARIABLE, "unused variable: `%s`", v->m_name))
+                rv = false;
+        }
+    }
+
+    typedefs = vec_last(parser->_blocktypedefs);
+    while (vec_size(parser->_typedefs) != typedefs) {
+        delete vec_last(parser->_typedefs);
+        vec_pop(parser->_typedefs);
+    }
+    util_htdel(vec_last(parser->typedefs));
+    vec_pop(parser->typedefs);
+
+    vec_pop(parser->_block_ctx);
+
+    return rv;
+}
+
+static void parser_addlocal(parser_t *parser, const char *name, ast_expression *e)
+{
+    vec_push(parser->_locals, e);
+    util_htset(vec_last(parser->variables), name, (void*)e);
+}
+static void parser_addlocal(parser_t *parser, const std::string &name, ast_expression *e) {
+    return parser_addlocal(parser, name.c_str(), e);
+}
+
+static void parser_addglobal(parser_t *parser, const char *name, ast_expression *e)
+{
+    parser->globals.push_back(e);
+    util_htset(parser->htglobals, name, e);
+}
+static void parser_addglobal(parser_t *parser, const std::string &name, ast_expression *e) {
+    return parser_addglobal(parser, name.c_str(), e);
+}
+
+static ast_expression* process_condition(parser_t *parser, ast_expression *cond, bool *_ifnot)
+{
+    bool       ifnot = false;
+    ast_unary *unary;
+    ast_expression *prev;
+
+    if (cond->m_vtype == TYPE_VOID || cond->m_vtype >= TYPE_VARIANT) {
+        char ty[1024];
+        ast_type_to_string(cond, ty, sizeof(ty));
+        compile_error(cond->m_context, "invalid type for if() condition: %s", ty);
+    }
+
+    if (OPTS_FLAG(FALSE_EMPTY_STRINGS) && cond->m_vtype == TYPE_STRING)
+    {
+        prev = cond;
+        cond = ast_unary::make(cond->m_context, INSTR_NOT_S, cond);
+        if (!cond) {
+            ast_unref(prev);
+            parseerror(parser, "internal error: failed to process condition");
+            return nullptr;
+        }
+        ifnot = !ifnot;
+    }
+    else if (OPTS_FLAG(CORRECT_LOGIC) && cond->m_vtype == TYPE_VECTOR)
+    {
+        /* vector types need to be cast to true booleans */
+        ast_binary *bin = (ast_binary*)cond;
+        if (!OPTS_FLAG(PERL_LOGIC) || !ast_istype(cond, ast_binary) || !(bin->m_op == INSTR_AND || bin->m_op == INSTR_OR))
+        {
+            /* in perl-logic, AND and OR take care of the -fcorrect-logic */
+            prev = cond;
+            cond = ast_unary::make(cond->m_context, INSTR_NOT_V, cond);
+            if (!cond) {
+                ast_unref(prev);
+                parseerror(parser, "internal error: failed to process condition");
+                return nullptr;
+            }
+            ifnot = !ifnot;
+        }
+    }
+
+    unary = (ast_unary*)cond;
+    /* ast_istype dereferences cond, should test here for safety */
+    while (cond && ast_istype(cond, ast_unary) && unary->m_op == INSTR_NOT_F)
+    {
+        cond = unary->m_operand;
+        unary->m_operand = nullptr;
+        delete unary;
+        ifnot = !ifnot;
+        unary = (ast_unary*)cond;
+    }
+
+    if (!cond)
+        parseerror(parser, "internal error: failed to process condition");
+
+    if (ifnot) *_ifnot = !*_ifnot;
+    return cond;
+}
+
+static bool parse_if(parser_t *parser, ast_block *block, ast_expression **out)
+{
+    ast_ifthen *ifthen;
+    ast_expression *cond, *ontrue = nullptr, *onfalse = nullptr;
+    bool ifnot = false;
+
+    lex_ctx_t ctx = parser_ctx(parser);
+
+    (void)block; /* not touching */
+
+    /* skip the 'if', parse an optional 'not' and check for an opening paren */
+    if (!parser_next(parser)) {
+        parseerror(parser, "expected condition or 'not'");
+        return false;
+    }
+    if (parser->tok == TOKEN_IDENT && !strcmp(parser_tokval(parser), "not")) {
+        ifnot = true;
+        if (!parser_next(parser)) {
+            parseerror(parser, "expected condition in parenthesis");
+            return false;
+        }
+    }
+    if (parser->tok != '(') {
+        parseerror(parser, "expected 'if' condition in parenthesis");
+        return false;
+    }
+    /* parse into the expression */
+    if (!parser_next(parser)) {
+        parseerror(parser, "expected 'if' condition after opening paren");
+        return false;
+    }
+    /* parse the condition */
+    cond = parse_expression_leave(parser, false, true, false);
+    if (!cond)
+        return false;
+    /* closing paren */
+    if (parser->tok != ')') {
+        parseerror(parser, "expected closing paren after 'if' condition");
+        ast_unref(cond);
+        return false;
+    }
+    /* parse into the 'then' branch */
+    if (!parser_next(parser)) {
+        parseerror(parser, "expected statement for on-true branch of 'if'");
+        ast_unref(cond);
+        return false;
+    }
+    if (!parse_statement_or_block(parser, &ontrue)) {
+        ast_unref(cond);
+        return false;
+    }
+    if (!ontrue)
+        ontrue = new ast_block(parser_ctx(parser));
+    /* check for an else */
+    if (!strcmp(parser_tokval(parser), "else")) {
+        /* parse into the 'else' branch */
+        if (!parser_next(parser)) {
+            parseerror(parser, "expected on-false branch after 'else'");
+            delete ontrue;
+            ast_unref(cond);
+            return false;
+        }
+        if (!parse_statement_or_block(parser, &onfalse)) {
+            delete ontrue;
+            ast_unref(cond);
+            return false;
+        }
+    }
+
+    cond = process_condition(parser, cond, &ifnot);
+    if (!cond) {
+        if (ontrue)  delete ontrue;
+        if (onfalse) delete onfalse;
+        return false;
+    }
+
+    if (ifnot)
+        ifthen = new ast_ifthen(ctx, cond, onfalse, ontrue);
+    else
+        ifthen = new ast_ifthen(ctx, cond, ontrue, onfalse);
+    *out = ifthen;
+    return true;
+}
+
+static bool parse_while_go(parser_t *parser, ast_block *block, ast_expression **out);
+static bool parse_while(parser_t *parser, ast_block *block, ast_expression **out)
+{
+    bool rv;
+    char *label = nullptr;
+
+    /* skip the 'while' and get the body */
+    if (!parser_next(parser)) {
+        if (OPTS_FLAG(LOOP_LABELS))
+            parseerror(parser, "expected loop label or 'while' condition in parenthesis");
+        else
+            parseerror(parser, "expected 'while' condition in parenthesis");
+        return false;
+    }
+
+    if (parser->tok == ':') {
+        if (!OPTS_FLAG(LOOP_LABELS))
+            parseerror(parser, "labeled loops not activated, try using -floop-labels");
+        if (!parser_next(parser) || parser->tok != TOKEN_IDENT) {
+            parseerror(parser, "expected loop label");
+            return false;
+        }
+        label = util_strdup(parser_tokval(parser));
+        if (!parser_next(parser)) {
+            mem_d(label);
+            parseerror(parser, "expected 'while' condition in parenthesis");
+            return false;
+        }
+    }
+
+    if (parser->tok != '(') {
+        parseerror(parser, "expected 'while' condition in parenthesis");
+        return false;
+    }
+
+    parser->breaks.push_back(label);
+    parser->continues.push_back(label);
+
+    rv = parse_while_go(parser, block, out);
+    if (label)
+        mem_d(label);
+    if (parser->breaks.back() != label || parser->continues.back() != label) {
+        parseerror(parser, "internal error: label stack corrupted");
+        rv = false;
+        delete *out;
+        *out = nullptr;
+    }
+    else {
+        parser->breaks.pop_back();
+        parser->continues.pop_back();
+    }
+    return rv;
+}
+
+static bool parse_while_go(parser_t *parser, ast_block *block, ast_expression **out)
+{
+    ast_loop *aloop;
+    ast_expression *cond, *ontrue;
+
+    bool ifnot = false;
+
+    lex_ctx_t ctx = parser_ctx(parser);
+
+    (void)block; /* not touching */
+
+    /* parse into the expression */
+    if (!parser_next(parser)) {
+        parseerror(parser, "expected 'while' condition after opening paren");
+        return false;
+    }
+    /* parse the condition */
+    cond = parse_expression_leave(parser, false, true, false);
+    if (!cond)
+        return false;
+    /* closing paren */
+    if (parser->tok != ')') {
+        parseerror(parser, "expected closing paren after 'while' condition");
+        ast_unref(cond);
+        return false;
+    }
+    /* parse into the 'then' branch */
+    if (!parser_next(parser)) {
+        parseerror(parser, "expected while-loop body");
+        ast_unref(cond);
+        return false;
+    }
+    if (!parse_statement_or_block(parser, &ontrue)) {
+        ast_unref(cond);
+        return false;
+    }
+
+    cond = process_condition(parser, cond, &ifnot);
+    if (!cond) {
+        ast_unref(ontrue);
+        return false;
+    }
+    aloop = new ast_loop(ctx, nullptr, cond, ifnot, nullptr, false, nullptr, ontrue);
+    *out = aloop;
+    return true;
+}
+
+static bool parse_dowhile_go(parser_t *parser, ast_block *block, ast_expression **out);
+static bool parse_dowhile(parser_t *parser, ast_block *block, ast_expression **out)
+{
+    bool rv;
+    char *label = nullptr;
+
+    /* skip the 'do' and get the body */
+    if (!parser_next(parser)) {
+        if (OPTS_FLAG(LOOP_LABELS))
+            parseerror(parser, "expected loop label or body");
+        else
+            parseerror(parser, "expected loop body");
+        return false;
+    }
+
+    if (parser->tok == ':') {
+        if (!OPTS_FLAG(LOOP_LABELS))
+            parseerror(parser, "labeled loops not activated, try using -floop-labels");
+        if (!parser_next(parser) || parser->tok != TOKEN_IDENT) {
+            parseerror(parser, "expected loop label");
+            return false;
+        }
+        label = util_strdup(parser_tokval(parser));
+        if (!parser_next(parser)) {
+            mem_d(label);
+            parseerror(parser, "expected loop body");
+            return false;
+        }
+    }
+
+    parser->breaks.push_back(label);
+    parser->continues.push_back(label);
+
+    rv = parse_dowhile_go(parser, block, out);
+    if (label)
+        mem_d(label);
+    if (parser->breaks.back() != label || parser->continues.back() != label) {
+        parseerror(parser, "internal error: label stack corrupted");
+        rv = false;
+        delete *out;
+        *out = nullptr;
+    }
+    else {
+        parser->breaks.pop_back();
+        parser->continues.pop_back();
+    }
+    return rv;
+}
+
+static bool parse_dowhile_go(parser_t *parser, ast_block *block, ast_expression **out)
+{
+    ast_loop *aloop;
+    ast_expression *cond, *ontrue;
+
+    bool ifnot = false;
+
+    lex_ctx_t ctx = parser_ctx(parser);
+
+    (void)block; /* not touching */
+
+    if (!parse_statement_or_block(parser, &ontrue))
+        return false;
+
+    /* expect the "while" */
+    if (parser->tok != TOKEN_KEYWORD ||
+        strcmp(parser_tokval(parser), "while"))
+    {
+        parseerror(parser, "expected 'while' and condition");
+        delete ontrue;
+        return false;
+    }
+
+    /* skip the 'while' and check for opening paren */
+    if (!parser_next(parser) || parser->tok != '(') {
+        parseerror(parser, "expected 'while' condition in parenthesis");
+        delete ontrue;
+        return false;
+    }
+    /* parse into the expression */
+    if (!parser_next(parser)) {
+        parseerror(parser, "expected 'while' condition after opening paren");
+        delete ontrue;
+        return false;
+    }
+    /* parse the condition */
+    cond = parse_expression_leave(parser, false, true, false);
+    if (!cond)
+        return false;
+    /* closing paren */
+    if (parser->tok != ')') {
+        parseerror(parser, "expected closing paren after 'while' condition");
+        delete ontrue;
+        ast_unref(cond);
+        return false;
+    }
+    /* parse on */
+    if (!parser_next(parser) || parser->tok != ';') {
+        parseerror(parser, "expected semicolon after condition");
+        delete ontrue;
+        ast_unref(cond);
+        return false;
+    }
+
+    if (!parser_next(parser)) {
+        parseerror(parser, "parse error");
+        delete ontrue;
+        ast_unref(cond);
+        return false;
+    }
+
+    cond = process_condition(parser, cond, &ifnot);
+    if (!cond) {
+        delete ontrue;
+        return false;
+    }
+    aloop = new ast_loop(ctx, nullptr, nullptr, false, cond, ifnot, nullptr, ontrue);
+    *out = aloop;
+    return true;
+}
+
+static bool parse_for_go(parser_t *parser, ast_block *block, ast_expression **out);
+static bool parse_for(parser_t *parser, ast_block *block, ast_expression **out)
+{
+    bool rv;
+    char *label = nullptr;
+
+    /* skip the 'for' and check for opening paren */
+    if (!parser_next(parser)) {
+        if (OPTS_FLAG(LOOP_LABELS))
+            parseerror(parser, "expected loop label or 'for' expressions in parenthesis");
+        else
+            parseerror(parser, "expected 'for' expressions in parenthesis");
+        return false;
+    }
+
+    if (parser->tok == ':') {
+        if (!OPTS_FLAG(LOOP_LABELS))
+            parseerror(parser, "labeled loops not activated, try using -floop-labels");
+        if (!parser_next(parser) || parser->tok != TOKEN_IDENT) {
+            parseerror(parser, "expected loop label");
+            return false;
+        }
+        label = util_strdup(parser_tokval(parser));
+        if (!parser_next(parser)) {
+            mem_d(label);
+            parseerror(parser, "expected 'for' expressions in parenthesis");
+            return false;
+        }
+    }
+
+    if (parser->tok != '(') {
+        parseerror(parser, "expected 'for' expressions in parenthesis");
+        return false;
+    }
+
+    parser->breaks.push_back(label);
+    parser->continues.push_back(label);
+
+    rv = parse_for_go(parser, block, out);
+    if (label)
+        mem_d(label);
+    if (parser->breaks.back() != label || parser->continues.back() != label) {
+        parseerror(parser, "internal error: label stack corrupted");
+        rv = false;
+        delete *out;
+        *out = nullptr;
+    }
+    else {
+        parser->breaks.pop_back();
+        parser->continues.pop_back();
+    }
+    return rv;
+}
+static bool parse_for_go(parser_t *parser, ast_block *block, ast_expression **out)
+{
+    ast_loop       *aloop;
+    ast_expression *initexpr, *cond, *increment, *ontrue;
+    ast_value      *typevar;
+
+    bool ifnot  = false;
+
+    lex_ctx_t ctx = parser_ctx(parser);
+
+    parser_enterblock(parser);
+
+    initexpr  = nullptr;
+    cond      = nullptr;
+    increment = nullptr;
+    ontrue    = nullptr;
+
+    /* parse into the expression */
+    if (!parser_next(parser)) {
+        parseerror(parser, "expected 'for' initializer after opening paren");
+        goto onerr;
+    }
+
+    typevar = nullptr;
+    if (parser->tok == TOKEN_IDENT)
+        typevar = parser_find_typedef(parser, parser_tokval(parser), 0);
+
+    if (typevar || parser->tok == TOKEN_TYPENAME) {
+        if (!parse_variable(parser, block, true, CV_VAR, typevar, false, false, 0, nullptr))
+            goto onerr;
+    }
+    else if (parser->tok != ';')
+    {
+        initexpr = parse_expression_leave(parser, false, false, false);
+        if (!initexpr)
+            goto onerr;
+        /* move on to condition */
+        if (parser->tok != ';') {
+            parseerror(parser, "expected semicolon after for-loop initializer");
+            goto onerr;
+        }
+        if (!parser_next(parser)) {
+            parseerror(parser, "expected for-loop condition");
+            goto onerr;
+        }
+    } else if (!parser_next(parser)) {
+        parseerror(parser, "expected for-loop condition");
+        goto onerr;
+    }
+
+    /* parse the condition */
+    if (parser->tok != ';') {
+        cond = parse_expression_leave(parser, false, true, false);
+        if (!cond)
+            goto onerr;
+    }
+    /* move on to incrementor */
+    if (parser->tok != ';') {
+        parseerror(parser, "expected semicolon after for-loop initializer");
+        goto onerr;
+    }
+    if (!parser_next(parser)) {
+        parseerror(parser, "expected for-loop condition");
+        goto onerr;
+    }
+
+    /* parse the incrementor */
+    if (parser->tok != ')') {
+        lex_ctx_t condctx = parser_ctx(parser);
+        increment = parse_expression_leave(parser, false, false, false);
+        if (!increment)
+            goto onerr;
+        if (!increment->m_side_effects) {
+            if (compile_warning(condctx, WARN_EFFECTLESS_STATEMENT, "statement has no effect"))
+                goto onerr;
+        }
+    }
+
+    /* closing paren */
+    if (parser->tok != ')') {
+        parseerror(parser, "expected closing paren after 'for-loop' incrementor");
+        goto onerr;
+    }
+    /* parse into the 'then' branch */
+    if (!parser_next(parser)) {
+        parseerror(parser, "expected for-loop body");
+        goto onerr;
+    }
+    if (!parse_statement_or_block(parser, &ontrue))
+        goto onerr;
+
+    if (cond) {
+        cond = process_condition(parser, cond, &ifnot);
+        if (!cond)
+            goto onerr;
+    }
+    aloop = new ast_loop(ctx, initexpr, cond, ifnot, nullptr, false, increment, ontrue);
+    *out = aloop;
+
+    if (!parser_leaveblock(parser)) {
+        delete aloop;
+        return false;
+    }
+    return true;
+onerr:
+    if (initexpr)  ast_unref(initexpr);
+    if (cond)      ast_unref(cond);
+    if (increment) ast_unref(increment);
+    (void)!parser_leaveblock(parser);
+    return false;
+}
+
+static bool parse_return(parser_t *parser, ast_block *block, ast_expression **out)
+{
+    ast_expression *exp      = nullptr;
+    ast_expression *var      = nullptr;
+    ast_return     *ret      = nullptr;
+    ast_value      *retval   = parser->function->m_return_value;
+    ast_value      *expected = parser->function->m_function_type;
+
+    lex_ctx_t ctx = parser_ctx(parser);
+
+    (void)block; /* not touching */
+
+    if (!parser_next(parser)) {
+        parseerror(parser, "expected return expression");
+        return false;
+    }
+
+    /* return assignments */
+    if (parser->tok == '=') {
+        if (!OPTS_FLAG(RETURN_ASSIGNMENTS)) {
+            parseerror(parser, "return assignments not activated, try using -freturn-assigments");
+            return false;
+        }
+
+        if (type_store_instr[expected->m_next->m_vtype] == VINSTR_END) {
+            char ty1[1024];
+            ast_type_to_string(expected->m_next, ty1, sizeof(ty1));
+            parseerror(parser, "invalid return type: `%s'", ty1);
+            return false;
+        }
+
+        if (!parser_next(parser)) {
+            parseerror(parser, "expected return assignment expression");
+            return false;
+        }
+
+        if (!(exp = parse_expression_leave(parser, false, false, false)))
+            return false;
+
+        /* prepare the return value */
+        if (!retval) {
+            retval = new ast_value(ctx, "#LOCAL_RETURN", TYPE_VOID);
+            retval->adoptType(*expected->m_next);
+            parser->function->m_return_value = retval;
+        }
+
+        if (!exp->compareType(*retval)) {
+            char ty1[1024], ty2[1024];
+            ast_type_to_string(exp, ty1, sizeof(ty1));
+            ast_type_to_string(retval, ty2, sizeof(ty2));
+            parseerror(parser, "invalid type for return value: `%s', expected `%s'", ty1, ty2);
+        }
+
+        /* store to 'return' local variable */
+        var = new ast_store(
+            ctx,
+            type_store_instr[expected->m_next->m_vtype],
+            retval, exp);
+
+        if (!var) {
+            ast_unref(exp);
+            return false;
+        }
+
+        if (parser->tok != ';')
+            parseerror(parser, "missing semicolon after return assignment");
+        else if (!parser_next(parser))
+            parseerror(parser, "parse error after return assignment");
+
+        *out = var;
+        return true;
+    }
+
+    if (parser->tok != ';') {
+        exp = parse_expression(parser, false, false);
+        if (!exp)
+            return false;
+
+        if (exp->m_vtype != TYPE_NIL &&
+            exp->m_vtype != (expected)->m_next->m_vtype)
+        {
+            parseerror(parser, "return with invalid expression");
+        }
+
+        ret = new ast_return(ctx, exp);
+        if (!ret) {
+            ast_unref(exp);
+            return false;
+        }
+    } else {
+        if (!parser_next(parser))
+            parseerror(parser, "parse error");
+
+        if (!retval && expected->m_next->m_vtype != TYPE_VOID)
+        {
+            (void)!parsewarning(parser, WARN_MISSING_RETURN_VALUES, "return without value");
+        }
+        ret = new ast_return(ctx, retval);
+    }
+    *out = ret;
+    return true;
+}
+
+static bool parse_break_continue(parser_t *parser, ast_block *block, ast_expression **out, bool is_continue)
+{
+    size_t i;
+    unsigned int levels = 0;
+    lex_ctx_t ctx = parser_ctx(parser);
+    auto &loops = (is_continue ? parser->continues : parser->breaks);
+
+    (void)block; /* not touching */
+    if (!parser_next(parser)) {
+        parseerror(parser, "expected semicolon or loop label");
+        return false;
+    }
+
+    if (loops.empty()) {
+        if (is_continue)
+            parseerror(parser, "`continue` can only be used inside loops");
+        else
+            parseerror(parser, "`break` can only be used inside loops or switches");
+    }
+
+    if (parser->tok == TOKEN_IDENT) {
+        if (!OPTS_FLAG(LOOP_LABELS))
+            parseerror(parser, "labeled loops not activated, try using -floop-labels");
+        i = loops.size();
+        while (i--) {
+            if (loops[i] && !strcmp(loops[i], parser_tokval(parser)))
+                break;
+            if (!i) {
+                parseerror(parser, "no such loop to %s: `%s`",
+                           (is_continue ? "continue" : "break out of"),
+                           parser_tokval(parser));
+                return false;
+            }
+            ++levels;
+        }
+        if (!parser_next(parser)) {
+            parseerror(parser, "expected semicolon");
+            return false;
+        }
+    }
+
+    if (parser->tok != ';') {
+        parseerror(parser, "expected semicolon");
+        return false;
+    }
+
+    if (!parser_next(parser))
+        parseerror(parser, "parse error");
+
+    *out = new ast_breakcont(ctx, is_continue, levels);
+    return true;
+}
+
+/* returns true when it was a variable qualifier, false otherwise!
+ * on error, cvq is set to CV_WRONG
+ */
+struct attribute_t {
+    const char *name;
+    size_t      flag;
+};
+
+static bool parse_qualifiers(parser_t *parser, bool with_local, int *cvq, bool *noref, bool *is_static, uint32_t *_flags, char **message)
+{
+    bool had_const    = false;
+    bool had_var      = false;
+    bool had_noref    = false;
+    bool had_attrib   = false;
+    bool had_static   = false;
+    uint32_t flags    = 0;
+
+    static attribute_t attributes[] = {
+        { "noreturn",   AST_FLAG_NORETURN   },
+        { "inline",     AST_FLAG_INLINE     },
+        { "eraseable",  AST_FLAG_ERASEABLE  },
+        { "accumulate", AST_FLAG_ACCUMULATE },
+        { "last",       AST_FLAG_FINAL_DECL }
+    };
+
+   *cvq = CV_NONE;
+
+    for (;;) {
+        size_t i;
+        if (parser->tok == TOKEN_ATTRIBUTE_OPEN) {
+            had_attrib = true;
+            /* parse an attribute */
+            if (!parser_next(parser)) {
+                parseerror(parser, "expected attribute after `[[`");
+                *cvq = CV_WRONG;
+                return false;
+            }
+
+            for (i = 0; i < GMQCC_ARRAY_COUNT(attributes); i++) {
+                if (!strcmp(parser_tokval(parser), attributes[i].name)) {
+                    flags |= attributes[i].flag;
+                    if (!parser_next(parser) || parser->tok != TOKEN_ATTRIBUTE_CLOSE) {
+                        parseerror(parser, "`%s` attribute has no parameters, expected `]]`",
+                            attributes[i].name);
+                        *cvq = CV_WRONG;
+                        return false;
+                    }
+                    break;
+                }
+            }
+
+            if (i != GMQCC_ARRAY_COUNT(attributes))
+                goto leave;
+
+
+            if (!strcmp(parser_tokval(parser), "noref")) {
+                had_noref = true;
+                if (!parser_next(parser) || parser->tok != TOKEN_ATTRIBUTE_CLOSE) {
+                    parseerror(parser, "`noref` attribute has no parameters, expected `]]`");
+                    *cvq = CV_WRONG;
+                    return false;
+                }
+            }
+            else if (!strcmp(parser_tokval(parser), "alias") && !(flags & AST_FLAG_ALIAS)) {
+                flags   |= AST_FLAG_ALIAS;
+                *message = nullptr;
+
+                if (!parser_next(parser)) {
+                    parseerror(parser, "parse error in attribute");
+                    goto argerr;
+                }
+
+                if (parser->tok == '(') {
+                    if (!parser_next(parser) || parser->tok != TOKEN_STRINGCONST) {
+                        parseerror(parser, "`alias` attribute missing parameter");
+                        goto argerr;
+                    }
+
+                    *message = util_strdup(parser_tokval(parser));
+
+                    if (!parser_next(parser)) {
+                        parseerror(parser, "parse error in attribute");
+                        goto argerr;
+                    }
+
+                    if (parser->tok != ')') {
+                        parseerror(parser, "`alias` attribute expected `)` after parameter");
+                        goto argerr;
+                    }
+
+                    if (!parser_next(parser)) {
+                        parseerror(parser, "parse error in attribute");
+                        goto argerr;
+                    }
+                }
+
+                if (parser->tok != TOKEN_ATTRIBUTE_CLOSE) {
+                    parseerror(parser, "`alias` attribute expected `]]`");
+                    goto argerr;
+                }
+            }
+            else if (!strcmp(parser_tokval(parser), "deprecated") && !(flags & AST_FLAG_DEPRECATED)) {
+                flags   |= AST_FLAG_DEPRECATED;
+                *message = nullptr;
+
+                if (!parser_next(parser)) {
+                    parseerror(parser, "parse error in attribute");
+                    goto argerr;
+                }
+
+                if (parser->tok == '(') {
+                    if (!parser_next(parser) || parser->tok != TOKEN_STRINGCONST) {
+                        parseerror(parser, "`deprecated` attribute missing parameter");
+                        goto argerr;
+                    }
+
+                    *message = util_strdup(parser_tokval(parser));
+
+                    if (!parser_next(parser)) {
+                        parseerror(parser, "parse error in attribute");
+                        goto argerr;
+                    }
+
+                    if(parser->tok != ')') {
+                        parseerror(parser, "`deprecated` attribute expected `)` after parameter");
+                        goto argerr;
+                    }
+
+                    if (!parser_next(parser)) {
+                        parseerror(parser, "parse error in attribute");
+                        goto argerr;
+                    }
+                }
+                /* no message */
+                if (parser->tok != TOKEN_ATTRIBUTE_CLOSE) {
+                    parseerror(parser, "`deprecated` attribute expected `]]`");
+
+                    argerr: /* ugly */
+                    if (*message) mem_d(*message);
+                    *message = nullptr;
+                    *cvq     = CV_WRONG;
+                    return false;
+                }
+            }
+            else if (!strcmp(parser_tokval(parser), "coverage") && !(flags & AST_FLAG_COVERAGE)) {
+                flags |= AST_FLAG_COVERAGE;
+                if (!parser_next(parser)) {
+                    error_in_coverage:
+                    parseerror(parser, "parse error in coverage attribute");
+                    *cvq = CV_WRONG;
+                    return false;
+                }
+                if (parser->tok == '(') {
+                    if (!parser_next(parser)) {
+                        bad_coverage_arg:
+                        parseerror(parser, "invalid parameter for coverage() attribute\n"
+                                           "valid are: block");
+                        *cvq = CV_WRONG;
+                        return false;
+                    }
+                    if (parser->tok != ')') {
+                        do {
+                            if (parser->tok != TOKEN_IDENT)
+                                goto bad_coverage_arg;
+                            if (!strcmp(parser_tokval(parser), "block"))
+                                flags |= AST_FLAG_BLOCK_COVERAGE;
+                            else if (!strcmp(parser_tokval(parser), "none"))
+                                flags &= ~(AST_FLAG_COVERAGE_MASK);
+                            else
+                                goto bad_coverage_arg;
+                            if (!parser_next(parser))
+                                goto error_in_coverage;
+                            if (parser->tok == ',') {
+                                if (!parser_next(parser))
+                                    goto error_in_coverage;
+                            }
+                        } while (parser->tok != ')');
+                    }
+                    if (parser->tok != ')' || !parser_next(parser))
+                        goto error_in_coverage;
+                } else {
+                    /* without parameter [[coverage]] equals [[coverage(block)]] */
+                    flags |= AST_FLAG_BLOCK_COVERAGE;
+                }
+            }
+            else
+            {
+                /* Skip tokens until we hit a ]] */
+                (void)!parsewarning(parser, WARN_UNKNOWN_ATTRIBUTE, "unknown attribute starting with `%s`", parser_tokval(parser));
+                while (parser->tok != TOKEN_ATTRIBUTE_CLOSE) {
+                    if (!parser_next(parser)) {
+                        parseerror(parser, "error inside attribute");
+                        *cvq = CV_WRONG;
+                        return false;
+                    }
+                }
+            }
+        }
+        else if (with_local && !strcmp(parser_tokval(parser), "static"))
+            had_static = true;
+        else if (!strcmp(parser_tokval(parser), "const"))
+            had_const = true;
+        else if (!strcmp(parser_tokval(parser), "var"))
+            had_var = true;
+        else if (with_local && !strcmp(parser_tokval(parser), "local"))
+            had_var = true;
+        else if (!strcmp(parser_tokval(parser), "noref"))
+            had_noref = true;
+        else if (!had_const && !had_var && !had_noref && !had_attrib && !had_static && !flags) {
+            return false;
+        }
+        else
+            break;
+
+        leave:
+        if (!parser_next(parser))
+            goto onerr;
+    }
+    if (had_const)
+        *cvq = CV_CONST;
+    else if (had_var)
+        *cvq = CV_VAR;
+    else
+        *cvq = CV_NONE;
+    *noref     = had_noref;
+    *is_static = had_static;
+    *_flags    = flags;
+    return true;
+onerr:
+    parseerror(parser, "parse error after variable qualifier");
+    *cvq = CV_WRONG;
+    return true;
+}
+
+static bool parse_switch_go(parser_t *parser, ast_block *block, ast_expression **out);
+static bool parse_switch(parser_t *parser, ast_block *block, ast_expression **out)
+{
+    bool rv;
+    char *label = nullptr;
+
+    /* skip the 'while' and get the body */
+    if (!parser_next(parser)) {
+        if (OPTS_FLAG(LOOP_LABELS))
+            parseerror(parser, "expected loop label or 'switch' operand in parenthesis");
+        else
+            parseerror(parser, "expected 'switch' operand in parenthesis");
+        return false;
+    }
+
+    if (parser->tok == ':') {
+        if (!OPTS_FLAG(LOOP_LABELS))
+            parseerror(parser, "labeled loops not activated, try using -floop-labels");
+        if (!parser_next(parser) || parser->tok != TOKEN_IDENT) {
+            parseerror(parser, "expected loop label");
+            return false;
+        }
+        label = util_strdup(parser_tokval(parser));
+        if (!parser_next(parser)) {
+            mem_d(label);
+            parseerror(parser, "expected 'switch' operand in parenthesis");
+            return false;
+        }
+    }
+
+    if (parser->tok != '(') {
+        parseerror(parser, "expected 'switch' operand in parenthesis");
+        return false;
+    }
+
+    parser->breaks.push_back(label);
+
+    rv = parse_switch_go(parser, block, out);
+    if (label)
+        mem_d(label);
+    if (parser->breaks.back() != label) {
+        parseerror(parser, "internal error: label stack corrupted");
+        rv = false;
+        delete *out;
+        *out = nullptr;
+    }
+    else {
+        parser->breaks.pop_back();
+    }
+    return rv;
+}
+
+static bool parse_switch_go(parser_t *parser, ast_block *block, ast_expression **out)
+{
+    ast_expression *operand;
+    ast_value      *opval;
+    ast_value      *typevar;
+    ast_switch     *switchnode;
+    ast_switch_case swcase;
+
+    int  cvq;
+    bool noref, is_static;
+    uint32_t qflags = 0;
+
+    lex_ctx_t ctx = parser_ctx(parser);
+
+    (void)block; /* not touching */
+    (void)opval;
+
+    /* parse into the expression */
+    if (!parser_next(parser)) {
+        parseerror(parser, "expected switch operand");
+        return false;
+    }
+    /* parse the operand */
+    operand = parse_expression_leave(parser, false, false, false);
+    if (!operand)
+        return false;
+
+    switchnode = new ast_switch(ctx, operand);
+
+    /* closing paren */
+    if (parser->tok != ')') {
+        delete switchnode;
+        parseerror(parser, "expected closing paren after 'switch' operand");
+        return false;
+    }
+
+    /* parse over the opening paren */
+    if (!parser_next(parser) || parser->tok != '{') {
+        delete switchnode;
+        parseerror(parser, "expected list of cases");
+        return false;
+    }
+
+    if (!parser_next(parser)) {
+        delete switchnode;
+        parseerror(parser, "expected 'case' or 'default'");
+        return false;
+    }
+
+    /* new block; allow some variables to be declared here */
+    parser_enterblock(parser);
+    while (true) {
+        typevar = nullptr;
+        if (parser->tok == TOKEN_IDENT)
+            typevar = parser_find_typedef(parser, parser_tokval(parser), 0);
+        if (typevar || parser->tok == TOKEN_TYPENAME) {
+            if (!parse_variable(parser, block, true, CV_NONE, typevar, false, false, 0, nullptr)) {
+                delete switchnode;
+                return false;
+            }
+            continue;
+        }
+        if (parse_qualifiers(parser, true, &cvq, &noref, &is_static, &qflags, nullptr))
+        {
+            if (cvq == CV_WRONG) {
+                delete switchnode;
+                return false;
+            }
+            if (!parse_variable(parser, block, true, cvq, nullptr, noref, is_static, qflags, nullptr)) {
+                delete switchnode;
+                return false;
+            }
+            continue;
+        }
+        break;
+    }
+
+    /* case list! */
+    while (parser->tok != '}') {
+        ast_block *caseblock;
+
+        if (!strcmp(parser_tokval(parser), "case")) {
+            if (!parser_next(parser)) {
+                delete switchnode;
+                parseerror(parser, "expected expression for case");
+                return false;
+            }
+            swcase.m_value = parse_expression_leave(parser, false, false, false);
+            if (!swcase.m_value) {
+                delete switchnode;
+                parseerror(parser, "expected expression for case");
+                return false;
+            }
+            if (!OPTS_FLAG(RELAXED_SWITCH)) {
+                if (!ast_istype(swcase.m_value, ast_value)) { /* || ((ast_value*)swcase.m_value)->m_cvq != CV_CONST) { */
+                    parseerror(parser, "case on non-constant values need to be explicitly enabled via -frelaxed-switch");
+                    ast_unref(operand);
+                    return false;
+                }
+            }
+        }
+        else if (!strcmp(parser_tokval(parser), "default")) {
+            swcase.m_value = nullptr;
+            if (!parser_next(parser)) {
+                delete switchnode;
+                parseerror(parser, "expected colon");
+                return false;
+            }
+        }
+        else {
+            delete switchnode;
+            parseerror(parser, "expected 'case' or 'default'");
+            return false;
+        }
+
+        /* Now the colon and body */
+        if (parser->tok != ':') {
+            if (swcase.m_value) ast_unref(swcase.m_value);
+            delete switchnode;
+            parseerror(parser, "expected colon");
+            return false;
+        }
+
+        if (!parser_next(parser)) {
+            if (swcase.m_value) ast_unref(swcase.m_value);
+            delete switchnode;
+            parseerror(parser, "expected statements or case");
+            return false;
+        }
+        caseblock = new ast_block(parser_ctx(parser));
+        if (!caseblock) {
+            if (swcase.m_value) ast_unref(swcase.m_value);
+            delete switchnode;
+            return false;
+        }
+        swcase.m_code = caseblock;
+        switchnode->m_cases.push_back(swcase);
+        while (true) {
+            ast_expression *expr;
+            if (parser->tok == '}')
+                break;
+            if (parser->tok == TOKEN_KEYWORD) {
+                if (!strcmp(parser_tokval(parser), "case") ||
+                    !strcmp(parser_tokval(parser), "default"))
+                {
+                    break;
+                }
+            }
+            if (!parse_statement(parser, caseblock, &expr, true)) {
+                delete switchnode;
+                return false;
+            }
+            if (!expr)
+                continue;
+            if (!caseblock->addExpr(expr)) {
+                delete switchnode;
+                return false;
+            }
+        }
+    }
+
+    parser_leaveblock(parser);
+
+    /* closing paren */
+    if (parser->tok != '}') {
+        delete switchnode;
+        parseerror(parser, "expected closing paren of case list");
+        return false;
+    }
+    if (!parser_next(parser)) {
+        delete switchnode;
+        parseerror(parser, "parse error after switch");
+        return false;
+    }
+    *out = switchnode;
+    return true;
+}
+
+/* parse computed goto sides */
+static ast_expression *parse_goto_computed(parser_t *parser, ast_expression **side) {
+    ast_expression *on_true;
+    ast_expression *on_false;
+    ast_expression *cond;
+
+    if (!*side)
+        return nullptr;
+
+    if (ast_istype(*side, ast_ternary)) {
+        ast_ternary *tern = (ast_ternary*)*side;
+        on_true  = parse_goto_computed(parser, &tern->m_on_true);
+        on_false = parse_goto_computed(parser, &tern->m_on_false);
+
+        if (!on_true || !on_false) {
+            parseerror(parser, "expected label or expression in ternary");
+            if (on_true) ast_unref(on_true);
+            if (on_false) ast_unref(on_false);
+            return nullptr;
+        }
+
+        cond = tern->m_cond;
+        tern->m_cond = nullptr;
+        delete tern;
+        *side = nullptr;
+        return new ast_ifthen(parser_ctx(parser), cond, on_true, on_false);
+    } else if (ast_istype(*side, ast_label)) {
+        ast_goto *gt = new ast_goto(parser_ctx(parser), ((ast_label*)*side)->m_name);
+        gt->setLabel(reinterpret_cast<ast_label*>(*side));
+        *side = nullptr;
+        return gt;
+    }
+    return nullptr;
+}
+
+static bool parse_goto(parser_t *parser, ast_expression **out)
+{
+    ast_goto       *gt = nullptr;
+    ast_expression *lbl;
+
+    if (!parser_next(parser))
+        return false;
+
+    if (parser->tok != TOKEN_IDENT) {
+        ast_expression *expression;
+
+        /* could be an expression i.e computed goto :-) */
+        if (parser->tok != '(') {
+            parseerror(parser, "expected label name after `goto`");
+            return false;
+        }
+
+        /* failed to parse expression for goto */
+        if (!(expression = parse_expression(parser, false, true)) ||
+            !(*out = parse_goto_computed(parser, &expression))) {
+            parseerror(parser, "invalid goto expression");
+            if(expression)
+                ast_unref(expression);
+            return false;
+        }
+
+        return true;
+    }
+
+    /* not computed goto */
+    gt = new ast_goto(parser_ctx(parser), parser_tokval(parser));
+    lbl = parser_find_label(parser, gt->m_name);
+    if (lbl) {
+        if (!ast_istype(lbl, ast_label)) {
+            parseerror(parser, "internal error: label is not an ast_label");
+            delete gt;
+            return false;
+        }
+        gt->setLabel(reinterpret_cast<ast_label*>(lbl));
+    }
+    else
+        parser->gotos.push_back(gt);
+
+    if (!parser_next(parser) || parser->tok != ';') {
+        parseerror(parser, "semicolon expected after goto label");
+        return false;
+    }
+    if (!parser_next(parser)) {
+        parseerror(parser, "parse error after goto");
+        return false;
+    }
+
+    *out = gt;
+    return true;
+}
+
+static bool parse_skipwhite(parser_t *parser)
+{
+    do {
+        if (!parser_next(parser))
+            return false;
+    } while (parser->tok == TOKEN_WHITE && parser->tok < TOKEN_ERROR);
+    return parser->tok < TOKEN_ERROR;
+}
+
+static bool parse_eol(parser_t *parser)
+{
+    if (!parse_skipwhite(parser))
+        return false;
+    return parser->tok == TOKEN_EOL;
+}
+
+static bool parse_pragma_do(parser_t *parser)
+{
+    if (!parser_next(parser) ||
+        parser->tok != TOKEN_IDENT ||
+        strcmp(parser_tokval(parser), "pragma"))
+    {
+        parseerror(parser, "expected `pragma` keyword after `#`, got `%s`", parser_tokval(parser));
+        return false;
+    }
+    if (!parse_skipwhite(parser) || parser->tok != TOKEN_IDENT) {
+        parseerror(parser, "expected pragma, got `%s`", parser_tokval(parser));
+        return false;
+    }
+
+    if (!strcmp(parser_tokval(parser), "noref")) {
+        if (!parse_skipwhite(parser) || parser->tok != TOKEN_INTCONST) {
+            parseerror(parser, "`noref` pragma requires an argument: 0 or 1");
+            return false;
+        }
+        parser->noref = !!parser_token(parser)->constval.i;
+        if (!parse_eol(parser)) {
+            parseerror(parser, "parse error after `noref` pragma");
+            return false;
+        }
+    }
+    else
+    {
+        (void)!parsewarning(parser, WARN_UNKNOWN_PRAGMAS, "ignoring #pragma %s", parser_tokval(parser));
+
+        /* skip to eol */
+        while (!parse_eol(parser)) {
+            parser_next(parser);
+        }
+
+        return true;
+    }
+
+    return true;
+}
+
+static bool parse_pragma(parser_t *parser)
+{
+    bool rv;
+    parser->lex->flags.preprocessing = true;
+    parser->lex->flags.mergelines = true;
+    rv = parse_pragma_do(parser);
+    if (parser->tok != TOKEN_EOL) {
+        parseerror(parser, "junk after pragma");
+        rv = false;
+    }
+    parser->lex->flags.preprocessing = false;
+    parser->lex->flags.mergelines = false;
+    if (!parser_next(parser)) {
+        parseerror(parser, "parse error after pragma");
+        rv = false;
+    }
+    return rv;
+}
+
+static bool parse_statement(parser_t *parser, ast_block *block, ast_expression **out, bool allow_cases)
+{
+    bool       noref, is_static;
+    int        cvq     = CV_NONE;
+    uint32_t   qflags  = 0;
+    ast_value *typevar = nullptr;
+    char      *vstring = nullptr;
+
+    *out = nullptr;
+
+    if (parser->tok == TOKEN_IDENT)
+        typevar = parser_find_typedef(parser, parser_tokval(parser), 0);
+
+    if (typevar || parser->tok == TOKEN_TYPENAME || parser->tok == '.' || parser->tok == TOKEN_DOTS)
+    {
+        /* local variable */
+        if (!block) {
+            parseerror(parser, "cannot declare a variable from here");
+            return false;
+        }
+        if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) {
+            if (parsewarning(parser, WARN_EXTENSIONS, "missing 'local' keyword when declaring a local variable"))
+                return false;
+        }
+        if (!parse_variable(parser, block, false, CV_NONE, typevar, false, false, 0, nullptr))
+            return false;
+        return true;
+    }
+    else if (parse_qualifiers(parser, !!block, &cvq, &noref, &is_static, &qflags, &vstring))
+    {
+        if (cvq == CV_WRONG)
+            return false;
+        return parse_variable(parser, block, false, cvq, nullptr, noref, is_static, qflags, vstring);
+    }
+    else if (parser->tok == TOKEN_KEYWORD)
+    {
+        if (!strcmp(parser_tokval(parser), "__builtin_debug_printtype"))
+        {
+            char ty[1024];
+            ast_value *tdef;
+
+            if (!parser_next(parser)) {
+                parseerror(parser, "parse error after __builtin_debug_printtype");
+                return false;
+            }
+
+            if (parser->tok == TOKEN_IDENT && (tdef = parser_find_typedef(parser, parser_tokval(parser), 0)))
+            {
+                ast_type_to_string(tdef, ty, sizeof(ty));
+                con_out("__builtin_debug_printtype: `%s`=`%s`\n", tdef->m_name.c_str(), ty);
+                if (!parser_next(parser)) {
+                    parseerror(parser, "parse error after __builtin_debug_printtype typename argument");
+                    return false;
+                }
+            }
+            else
+            {
+                if (!parse_statement(parser, block, out, allow_cases))
+                    return false;
+                if (!*out)
+                    con_out("__builtin_debug_printtype: got no output node\n");
+                else
+                {
+                    ast_type_to_string(*out, ty, sizeof(ty));
+                    con_out("__builtin_debug_printtype: `%s`\n", ty);
+                }
+            }
+            return true;
+        }
+        else if (!strcmp(parser_tokval(parser), "return"))
+        {
+            return parse_return(parser, block, out);
+        }
+        else if (!strcmp(parser_tokval(parser), "if"))
+        {
+            return parse_if(parser, block, out);
+        }
+        else if (!strcmp(parser_tokval(parser), "while"))
+        {
+            return parse_while(parser, block, out);
+        }
+        else if (!strcmp(parser_tokval(parser), "do"))
+        {
+            return parse_dowhile(parser, block, out);
+        }
+        else if (!strcmp(parser_tokval(parser), "for"))
+        {
+            if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) {
+                if (parsewarning(parser, WARN_EXTENSIONS, "for loops are not recognized in the original Quake C standard, to enable try an alternate standard --std=?"))
+                    return false;
+            }
+            return parse_for(parser, block, out);
+        }
+        else if (!strcmp(parser_tokval(parser), "break"))
+        {
+            return parse_break_continue(parser, block, out, false);
+        }
+        else if (!strcmp(parser_tokval(parser), "continue"))
+        {
+            return parse_break_continue(parser, block, out, true);
+        }
+        else if (!strcmp(parser_tokval(parser), "switch"))
+        {
+            return parse_switch(parser, block, out);
+        }
+        else if (!strcmp(parser_tokval(parser), "case") ||
+                 !strcmp(parser_tokval(parser), "default"))
+        {
+            if (!allow_cases) {
+                parseerror(parser, "unexpected 'case' label");
+                return false;
+            }
+            return true;
+        }
+        else if (!strcmp(parser_tokval(parser), "goto"))
+        {
+            return parse_goto(parser, out);
+        }
+        else if (!strcmp(parser_tokval(parser), "typedef"))
+        {
+            if (!parser_next(parser)) {
+                parseerror(parser, "expected type definition after 'typedef'");
+                return false;
+            }
+            return parse_typedef(parser);
+        }
+        parseerror(parser, "Unexpected keyword: `%s'", parser_tokval(parser));
+        return false;
+    }
+    else if (parser->tok == '{')
+    {
+        ast_block *inner;
+        inner = parse_block(parser);
+        if (!inner)
+            return false;
+        *out = inner;
+        return true;
+    }
+    else if (parser->tok == ':')
+    {
+        size_t i;
+        ast_label *label;
+        if (!parser_next(parser)) {
+            parseerror(parser, "expected label name");
+            return false;
+        }
+        if (parser->tok != TOKEN_IDENT) {
+            parseerror(parser, "label must be an identifier");
+            return false;
+        }
+        label = (ast_label*)parser_find_label(parser, parser_tokval(parser));
+        if (label) {
+            if (!label->m_undefined) {
+                parseerror(parser, "label `%s` already defined", label->m_name);
+                return false;
+            }
+            label->m_undefined = false;
+        }
+        else {
+            label = new ast_label(parser_ctx(parser), parser_tokval(parser), false);
+            parser->labels.push_back(label);
+        }
+        *out = label;
+        if (!parser_next(parser)) {
+            parseerror(parser, "parse error after label");
+            return false;
+        }
+        for (i = 0; i < parser->gotos.size(); ++i) {
+            if (parser->gotos[i]->m_name == label->m_name) {
+                parser->gotos[i]->setLabel(label);
+                parser->gotos.erase(parser->gotos.begin() + i);
+                --i;
+            }
+        }
+        return true;
+    }
+    else if (parser->tok == ';')
+    {
+        if (!parser_next(parser)) {
+            parseerror(parser, "parse error after empty statement");
+            return false;
+        }
+        return true;
+    }
+    else
+    {
+        lex_ctx_t ctx = parser_ctx(parser);
+        ast_expression *exp = parse_expression(parser, false, false);
+        if (!exp)
+            return false;
+        *out = exp;
+        if (!exp->m_side_effects) {
+            if (compile_warning(ctx, WARN_EFFECTLESS_STATEMENT, "statement has no effect"))
+                return false;
+        }
+        return true;
+    }
+}
+
+static bool parse_enum(parser_t *parser)
+{
+    bool        flag = false;
+    bool        reverse = false;
+    qcfloat_t     num = 0;
+    ast_value **values = nullptr;
+    ast_value  *var = nullptr;
+    ast_value  *asvalue;
+
+    ast_expression *old;
+
+    if (!parser_next(parser) || (parser->tok != '{' && parser->tok != ':')) {
+        parseerror(parser, "expected `{` or `:` after `enum` keyword");
+        return false;
+    }
+
+    /* enumeration attributes (can add more later) */
+    if (parser->tok == ':') {
+        if (!parser_next(parser) || parser->tok != TOKEN_IDENT){
+            parseerror(parser, "expected `flag` or `reverse` for enumeration attribute");
+            return false;
+        }
+
+        /* attributes? */
+        if (!strcmp(parser_tokval(parser), "flag")) {
+            num  = 1;
+            flag = true;
+        }
+        else if (!strcmp(parser_tokval(parser), "reverse")) {
+            reverse = true;
+        }
+        else {
+            parseerror(parser, "invalid attribute `%s` for enumeration", parser_tokval(parser));
+            return false;
+        }
+
+        if (!parser_next(parser) || parser->tok != '{') {
+            parseerror(parser, "expected `{` after enum attribute ");
+            return false;
+        }
+    }
+
+    while (true) {
+        if (!parser_next(parser) || parser->tok != TOKEN_IDENT) {
+            if (parser->tok == '}') {
+                /* allow an empty enum */
+                break;
+            }
+            parseerror(parser, "expected identifier or `}`");
+            goto onerror;
+        }
+
+        old = parser_find_field(parser, parser_tokval(parser));
+        if (!old)
+            old = parser_find_global(parser, parser_tokval(parser));
+        if (old) {
+            parseerror(parser, "value `%s` has already been declared here: %s:%i",
+                       parser_tokval(parser), old->m_context.file, old->m_context.line);
+            goto onerror;
+        }
+
+        var = new ast_value(parser_ctx(parser), parser_tokval(parser), TYPE_FLOAT);
+        vec_push(values, var);
+        var->m_cvq             = CV_CONST;
+        var->m_hasvalue        = true;
+
+        /* for flagged enumerations increment in POTs of TWO */
+        var->m_constval.vfloat = (flag) ? (num *= 2) : (num ++);
+        parser_addglobal(parser, var->m_name, var);
+
+        if (!parser_next(parser)) {
+            parseerror(parser, "expected `=`, `}` or comma after identifier");
+            goto onerror;
+        }
+
+        if (parser->tok == ',')
+            continue;
+        if (parser->tok == '}')
+            break;
+        if (parser->tok != '=') {
+            parseerror(parser, "expected `=`, `}` or comma after identifier");
+            goto onerror;
+        }
+
+        if (!parser_next(parser)) {
+            parseerror(parser, "expected expression after `=`");
+            goto onerror;
+        }
+
+        /* We got a value! */
+        old = parse_expression_leave(parser, true, false, false);
+        asvalue = (ast_value*)old;
+        if (!ast_istype(old, ast_value) || asvalue->m_cvq != CV_CONST || !asvalue->m_hasvalue) {
+            compile_error(var->m_context, "constant value or expression expected");
+            goto onerror;
+        }
+        num = (var->m_constval.vfloat = asvalue->m_constval.vfloat) + 1;
+
+        if (parser->tok == '}')
+            break;
+        if (parser->tok != ',') {
+            parseerror(parser, "expected `}` or comma after expression");
+            goto onerror;
+        }
+    }
+
+    /* patch them all (for reversed attribute) */
+    if (reverse) {
+        size_t i;
+        for (i = 0; i < vec_size(values); i++)
+            values[i]->m_constval.vfloat = vec_size(values) - i - 1;
+    }
+
+    if (parser->tok != '}') {
+        parseerror(parser, "internal error: breaking without `}`");
+        goto onerror;
+    }
+
+    if (!parser_next(parser) || parser->tok != ';') {
+        parseerror(parser, "expected semicolon after enumeration");
+        goto onerror;
+    }
+
+    if (!parser_next(parser)) {
+        parseerror(parser, "parse error after enumeration");
+        goto onerror;
+    }
+
+    vec_free(values);
+    return true;
+
+onerror:
+    vec_free(values);
+    return false;
+}
+
+static bool parse_block_into(parser_t *parser, ast_block *block)
+{
+    bool   retval = true;
+
+    parser_enterblock(parser);
+
+    if (!parser_next(parser)) { /* skip the '{' */
+        parseerror(parser, "expected function body");
+        goto cleanup;
+    }
+
+    while (parser->tok != TOKEN_EOF && parser->tok < TOKEN_ERROR)
+    {
+        ast_expression *expr = nullptr;
+        if (parser->tok == '}')
+            break;
+
+        if (!parse_statement(parser, block, &expr, false)) {
+            /* parseerror(parser, "parse error"); */
+            block = nullptr;
+            goto cleanup;
+        }
+        if (!expr)
+            continue;
+        if (!block->addExpr(expr)) {
+            delete block;
+            block = nullptr;
+            goto cleanup;
+        }
+    }
+
+    if (parser->tok != '}') {
+        block = nullptr;
+    } else {
+        (void)parser_next(parser);
+    }
+
+cleanup:
+    if (!parser_leaveblock(parser))
+        retval = false;
+    return retval && !!block;
+}
+
+static ast_block* parse_block(parser_t *parser)
+{
+    ast_block *block;
+    block = new ast_block(parser_ctx(parser));
+    if (!block)
+        return nullptr;
+    if (!parse_block_into(parser, block)) {
+        delete block;
+        return nullptr;
+    }
+    return block;
+}
+
+static bool parse_statement_or_block(parser_t *parser, ast_expression **out)
+{
+    if (parser->tok == '{') {
+        *out = parse_block(parser);
+        return !!*out;
+    }
+    return parse_statement(parser, nullptr, out, false);
+}
+
+static bool create_vector_members(ast_value *var, ast_member **me)
+{
+    size_t i;
+    size_t len = var->m_name.length();
+
+    for (i = 0; i < 3; ++i) {
+        char *name = (char*)mem_a(len+3);
+        memcpy(name, var->m_name.c_str(), len);
+        name[len+0] = '_';
+        name[len+1] = 'x'+i;
+        name[len+2] = 0;
+        me[i] = ast_member::make(var->m_context, var, i, name);
+        mem_d(name);
+        if (!me[i])
+            break;
+    }
+    if (i == 3)
+        return true;
+
+    /* unroll */
+    do { delete me[--i]; } while(i);
+    return false;
+}
+
+static bool parse_function_body(parser_t *parser, ast_value *var)
+{
+    ast_block *block = nullptr;
+    ast_function *func;
+    ast_function *old;
+
+    ast_expression *framenum  = nullptr;
+    ast_expression *nextthink = nullptr;
+    /* None of the following have to be deleted */
+    ast_expression *fld_think = nullptr, *fld_nextthink = nullptr, *fld_frame = nullptr;
+    ast_expression *gbl_time = nullptr, *gbl_self = nullptr;
+    bool has_frame_think;
+
+    bool retval = true;
+
+    has_frame_think = false;
+    old = parser->function;
+
+    if (var->m_flags & AST_FLAG_ALIAS) {
+        parseerror(parser, "function aliases cannot have bodies");
+        return false;
+    }
+
+    if (parser->gotos.size() || parser->labels.size()) {
+        parseerror(parser, "gotos/labels leaking");
+        return false;
+    }
+
+    if (!OPTS_FLAG(VARIADIC_ARGS) && var->m_flags & AST_FLAG_VARIADIC) {
+        if (parsewarning(parser, WARN_VARIADIC_FUNCTION,
+                         "variadic function with implementation will not be able to access additional parameters (try -fvariadic-args)"))
+        {
+            return false;
+        }
+    }
+
+    if (parser->tok == '[') {
+        /* got a frame definition: [ framenum, nextthink ]
+         * this translates to:
+         * self.frame = framenum;
+         * self.nextthink = time + 0.1;
+         * self.think = nextthink;
+         */
+        nextthink = nullptr;
+
+        fld_think     = parser_find_field(parser, "think");
+        fld_nextthink = parser_find_field(parser, "nextthink");
+        fld_frame     = parser_find_field(parser, "frame");
+        if (!fld_think || !fld_nextthink || !fld_frame) {
+            parseerror(parser, "cannot use [frame,think] notation without the required fields");
+            parseerror(parser, "please declare the following entityfields: `frame`, `think`, `nextthink`");
+            return false;
+        }
+        gbl_time      = parser_find_global(parser, "time");
+        gbl_self      = parser_find_global(parser, "self");
+        if (!gbl_time || !gbl_self) {
+            parseerror(parser, "cannot use [frame,think] notation without the required globals");
+            parseerror(parser, "please declare the following globals: `time`, `self`");
+            return false;
+        }
+
+        if (!parser_next(parser))
+            return false;
+
+        framenum = parse_expression_leave(parser, true, false, false);
+        if (!framenum) {
+            parseerror(parser, "expected a framenumber constant in[frame,think] notation");
+            return false;
+        }
+        if (!ast_istype(framenum, ast_value) || !( (ast_value*)framenum )->m_hasvalue) {
+            ast_unref(framenum);
+            parseerror(parser, "framenumber in [frame,think] notation must be a constant");
+            return false;
+        }
+
+        if (parser->tok != ',') {
+            ast_unref(framenum);
+            parseerror(parser, "expected comma after frame number in [frame,think] notation");
+            parseerror(parser, "Got a %i\n", parser->tok);
+            return false;
+        }
+
+        if (!parser_next(parser)) {
+            ast_unref(framenum);
+            return false;
+        }
+
+        if (parser->tok == TOKEN_IDENT && !parser_find_var(parser, parser_tokval(parser)))
+        {
+            /* qc allows the use of not-yet-declared functions here
+             * - this automatically creates a prototype */
+            ast_value      *thinkfunc;
+            ast_expression *functype = fld_think->m_next;
+
+            thinkfunc = new ast_value(parser_ctx(parser), parser_tokval(parser), functype->m_vtype);
+            if (!thinkfunc) { /* || !thinkfunc->adoptType(*functype)*/
+                ast_unref(framenum);
+                parseerror(parser, "failed to create implicit prototype for `%s`", parser_tokval(parser));
+                return false;
+            }
+            thinkfunc->adoptType(*functype);
+
+            if (!parser_next(parser)) {
+                ast_unref(framenum);
+                delete thinkfunc;
+                return false;
+            }
+
+            parser_addglobal(parser, thinkfunc->m_name, thinkfunc);
+
+            nextthink = thinkfunc;
+
+        } else {
+            nextthink = parse_expression_leave(parser, true, false, false);
+            if (!nextthink) {
+                ast_unref(framenum);
+                parseerror(parser, "expected a think-function in [frame,think] notation");
+                return false;
+            }
+        }
+
+        if (!ast_istype(nextthink, ast_value)) {
+            parseerror(parser, "think-function in [frame,think] notation must be a constant");
+            retval = false;
+        }
+
+        if (retval && parser->tok != ']') {
+            parseerror(parser, "expected closing `]` for [frame,think] notation");
+            retval = false;
+        }
+
+        if (retval && !parser_next(parser)) {
+            retval = false;
+        }
+
+        if (retval && parser->tok != '{') {
+            parseerror(parser, "a function body has to be declared after a [frame,think] declaration");
+            retval = false;
+        }
+
+        if (!retval) {
+            ast_unref(nextthink);
+            ast_unref(framenum);
+            return false;
+        }
+
+        has_frame_think = true;
+    }
+
+    block = new ast_block(parser_ctx(parser));
+    if (!block) {
+        parseerror(parser, "failed to allocate block");
+        if (has_frame_think) {
+            ast_unref(nextthink);
+            ast_unref(framenum);
+        }
+        return false;
+    }
+
+    if (has_frame_think) {
+        if (!OPTS_FLAG(EMULATE_STATE)) {
+            ast_state *state_op = new ast_state(parser_ctx(parser), framenum, nextthink);
+            if (!block->addExpr(state_op)) {
+                parseerror(parser, "failed to generate state op for [frame,think]");
+                ast_unref(nextthink);
+                ast_unref(framenum);
+                delete block;
+                return false;
+            }
+        } else {
+            /* emulate OP_STATE in code: */
+            lex_ctx_t ctx;
+            ast_expression *self_frame;
+            ast_expression *self_nextthink;
+            ast_expression *self_think;
+            ast_expression *time_plus_1;
+            ast_store *store_frame;
+            ast_store *store_nextthink;
+            ast_store *store_think;
+
+            float frame_delta = 1.0f / (float)OPTS_OPTION_U32(OPTION_STATE_FPS);
+
+            ctx = parser_ctx(parser);
+            self_frame     = new ast_entfield(ctx, gbl_self, fld_frame);
+            self_nextthink = new ast_entfield(ctx, gbl_self, fld_nextthink);
+            self_think     = new ast_entfield(ctx, gbl_self, fld_think);
+
+            time_plus_1    = new ast_binary(ctx, INSTR_ADD_F,
+                             gbl_time, parser->m_fold.constgen_float(frame_delta, false));
+
+            if (!self_frame || !self_nextthink || !self_think || !time_plus_1) {
+                if (self_frame)     delete self_frame;
+                if (self_nextthink) delete self_nextthink;
+                if (self_think)     delete self_think;
+                if (time_plus_1)    delete time_plus_1;
+                retval = false;
+            }
+
+            if (retval)
+            {
+                store_frame     = new ast_store(ctx, INSTR_STOREP_F,   self_frame,     framenum);
+                store_nextthink = new ast_store(ctx, INSTR_STOREP_F,   self_nextthink, time_plus_1);
+                store_think     = new ast_store(ctx, INSTR_STOREP_FNC, self_think,     nextthink);
+
+                if (!store_frame) {
+                    delete self_frame;
+                    retval = false;
+                }
+                if (!store_nextthink) {
+                    delete self_nextthink;
+                    retval = false;
+                }
+                if (!store_think) {
+                    delete self_think;
+                    retval = false;
+                }
+                if (!retval) {
+                    if (store_frame)     delete store_frame;
+                    if (store_nextthink) delete store_nextthink;
+                    if (store_think)     delete store_think;
+                    retval = false;
+                }
+                if (!block->addExpr(store_frame) ||
+                    !block->addExpr(store_nextthink) ||
+                    !block->addExpr(store_think))
+                {
+                    retval = false;
+                }
+            }
+
+            if (!retval) {
+                parseerror(parser, "failed to generate code for [frame,think]");
+                ast_unref(nextthink);
+                ast_unref(framenum);
+                delete block;
+                return false;
+            }
+        }
+    }
+
+    if (var->m_hasvalue) {
+        if (!(var->m_flags & AST_FLAG_ACCUMULATE)) {
+            parseerror(parser, "function `%s` declared with multiple bodies", var->m_name);
+            delete block;
+            goto enderr;
+        }
+        func = var->m_constval.vfunc;
+
+        if (!func) {
+            parseerror(parser, "internal error: nullptr function: `%s`", var->m_name);
+            delete block;
+            goto enderr;
+        }
+    } else {
+        func = ast_function::make(var->m_context, var->m_name, var);
+
+        if (!func) {
+            parseerror(parser, "failed to allocate function for `%s`", var->m_name);
+            delete block;
+            goto enderr;
+        }
+        parser->functions.push_back(func);
+    }
+
+    parser_enterblock(parser);
+
+    for (auto &it : var->m_type_params) {
+        size_t e;
+        ast_member *me[3];
+
+        if (it->m_vtype != TYPE_VECTOR &&
+            (it->m_vtype != TYPE_FIELD ||
+             it->m_next->m_vtype != TYPE_VECTOR))
+        {
+            continue;
+        }
+
+        if (!create_vector_members(it.get(), me)) {
+            delete block;
+            goto enderrfn;
+        }
+
+        for (e = 0; e < 3; ++e) {
+            parser_addlocal(parser, me[e]->m_name, me[e]);
+            block->collect(me[e]);
+        }
+    }
+
+    if (var->m_argcounter && !func->m_argc) {
+        ast_value *argc = new ast_value(var->m_context, var->m_argcounter, TYPE_FLOAT);
+        parser_addlocal(parser, argc->m_name, argc);
+        func->m_argc.reset(argc);
+    }
+
+    if (OPTS_FLAG(VARIADIC_ARGS) && var->m_flags & AST_FLAG_VARIADIC && !func->m_varargs) {
+        char name[1024];
+        ast_value *varargs = new ast_value(var->m_context, "reserved:va_args", TYPE_ARRAY);
+        varargs->m_flags |= AST_FLAG_IS_VARARG;
+        varargs->m_next = new ast_value(var->m_context, "", TYPE_VECTOR);
+        varargs->m_count = 0;
+        util_snprintf(name, sizeof(name), "%s##va##SET", var->m_name.c_str());
+        if (!parser_create_array_setter_proto(parser, varargs, name)) {
+            delete varargs;
+            delete block;
+            goto enderrfn;
+        }
+        util_snprintf(name, sizeof(name), "%s##va##GET", var->m_name.c_str());
+        if (!parser_create_array_getter_proto(parser, varargs, varargs->m_next, name)) {
+            delete varargs;
+            delete block;
+            goto enderrfn;
+        }
+        func->m_varargs.reset(varargs);
+        func->m_fixedparams = (ast_value*)parser->m_fold.constgen_float(var->m_type_params.size(), false);
+    }
+
+    parser->function = func;
+    if (!parse_block_into(parser, block)) {
+        delete block;
+        goto enderrfn;
+    }
+
+    func->m_blocks.emplace_back(block);
+
+    parser->function = old;
+    if (!parser_leaveblock(parser))
+        retval = false;
+    if (vec_size(parser->variables) != PARSER_HT_LOCALS) {
+        parseerror(parser, "internal error: local scopes left");
+        retval = false;
+    }
+
+    if (parser->tok == ';')
+        return parser_next(parser);
+    else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC)
+        parseerror(parser, "missing semicolon after function body (mandatory with -std=qcc)");
+    return retval;
+
+enderrfn:
+    (void)!parser_leaveblock(parser);
+    parser->functions.pop_back();
+    delete func;
+    var->m_constval.vfunc = nullptr;
+
+enderr:
+    parser->function = old;
+    return false;
+}
+
+static ast_expression *array_accessor_split(
+    parser_t  *parser,
+    ast_value *array,
+    ast_value *index,
+    size_t     middle,
+    ast_expression *left,
+    ast_expression *right
+    )
+{
+    ast_ifthen *ifthen;
+    ast_binary *cmp;
+
+    lex_ctx_t ctx = array->m_context;
+
+    if (!left || !right) {
+        if (left)  delete left;
+        if (right) delete right;
+        return nullptr;
+    }
+
+    cmp = new ast_binary(ctx, INSTR_LT,
+                         index,
+                         parser->m_fold.constgen_float(middle, false));
+    if (!cmp) {
+        delete left;
+        delete right;
+        parseerror(parser, "internal error: failed to create comparison for array setter");
+        return nullptr;
+    }
+
+    ifthen = new ast_ifthen(ctx, cmp, left, right);
+    if (!ifthen) {
+        delete cmp; /* will delete left and right */
+        parseerror(parser, "internal error: failed to create conditional jump for array setter");
+        return nullptr;
+    }
+
+    return ifthen;
+}
+
+static ast_expression *array_setter_node(parser_t *parser, ast_value *array, ast_value *index, ast_value *value, size_t from, size_t afterend)
+{
+    lex_ctx_t ctx = array->m_context;
+
+    if (from+1 == afterend) {
+        /* set this value */
+        ast_block       *block;
+        ast_return      *ret;
+        ast_array_index *subscript;
+        ast_store       *st;
+        int assignop = type_store_instr[value->m_vtype];
+
+        if (value->m_vtype == TYPE_FIELD && value->m_next->m_vtype == TYPE_VECTOR)
+            assignop = INSTR_STORE_V;
+
+        subscript = ast_array_index::make(ctx, array, parser->m_fold.constgen_float(from, false));
+        if (!subscript)
+            return nullptr;
+
+        st = new ast_store(ctx, assignop, subscript, value);
+        if (!st) {
+            delete subscript;
+            return nullptr;
+        }
+
+        block = new ast_block(ctx);
+        if (!block) {
+            delete st;
+            return nullptr;
+        }
+
+        if (!block->addExpr(st)) {
+            delete block;
+            return nullptr;
+        }
+
+        ret = new ast_return(ctx, nullptr);
+        if (!ret) {
+            delete block;
+            return nullptr;
+        }
+
+        if (!block->addExpr(ret)) {
+            delete block;
+            return nullptr;
+        }
+
+        return block;
+    } else {
+        ast_expression *left, *right;
+        size_t diff = afterend - from;
+        size_t middle = from + diff/2;
+        left  = array_setter_node(parser, array, index, value, from, middle);
+        right = array_setter_node(parser, array, index, value, middle, afterend);
+        return array_accessor_split(parser, array, index, middle, left, right);
+    }
+}
+
+static ast_expression *array_field_setter_node(
+    parser_t  *parser,
+    ast_value *array,
+    ast_value *entity,
+    ast_value *index,
+    ast_value *value,
+    size_t     from,
+    size_t     afterend)
+{
+    lex_ctx_t ctx = array->m_context;
+
+    if (from+1 == afterend) {
+        /* set this value */
+        ast_block       *block;
+        ast_return      *ret;
+        ast_entfield    *entfield;
+        ast_array_index *subscript;
+        ast_store       *st;
+        int assignop = type_storep_instr[value->m_vtype];
+
+        if (value->m_vtype == TYPE_FIELD && value->m_next->m_vtype == TYPE_VECTOR)
+            assignop = INSTR_STOREP_V;
+
+        subscript = ast_array_index::make(ctx, array, parser->m_fold.constgen_float(from, false));
+        if (!subscript)
+            return nullptr;
+
+        subscript->m_next = new ast_expression(ast_copy_type, subscript->m_context, *subscript);
+        subscript->m_vtype = TYPE_FIELD;
+
+        entfield = new ast_entfield(ctx, entity, subscript, subscript);
+        if (!entfield) {
+            delete subscript;
+            return nullptr;
+        }
+
+        st = new ast_store(ctx, assignop, entfield, value);
+        if (!st) {
+            delete entfield;
+            return nullptr;
+        }
+
+        block = new ast_block(ctx);
+        if (!block) {
+            delete st;
+            return nullptr;
+        }
+
+        if (!block->addExpr(st)) {
+            delete block;
+            return nullptr;
+        }
+
+        ret = new ast_return(ctx, nullptr);
+        if (!ret) {
+            delete block;
+            return nullptr;
+        }
+
+        if (!block->addExpr(ret)) {
+            delete block;
+            return nullptr;
+        }
+
+        return block;
+    } else {
+        ast_expression *left, *right;
+        size_t diff = afterend - from;
+        size_t middle = from + diff/2;
+        left  = array_field_setter_node(parser, array, entity, index, value, from, middle);
+        right = array_field_setter_node(parser, array, entity, index, value, middle, afterend);
+        return array_accessor_split(parser, array, index, middle, left, right);
+    }
+}
+
+static ast_expression *array_getter_node(parser_t *parser, ast_value *array, ast_value *index, size_t from, size_t afterend)
+{
+    lex_ctx_t ctx = array->m_context;
+
+    if (from+1 == afterend) {
+        ast_return      *ret;
+        ast_array_index *subscript;
+
+        subscript = ast_array_index::make(ctx, array, parser->m_fold.constgen_float(from, false));
+        if (!subscript)
+            return nullptr;
+
+        ret = new ast_return(ctx, subscript);
+        if (!ret) {
+            delete subscript;
+            return nullptr;
+        }
+
+        return ret;
+    } else {
+        ast_expression *left, *right;
+        size_t diff = afterend - from;
+        size_t middle = from + diff/2;
+        left  = array_getter_node(parser, array, index, from, middle);
+        right = array_getter_node(parser, array, index, middle, afterend);
+        return array_accessor_split(parser, array, index, middle, left, right);
+    }
+}
+
+static bool parser_create_array_accessor(parser_t *parser, ast_value *array, const char *funcname, ast_value **out)
+{
+    ast_function   *func = nullptr;
+    ast_value      *fval = nullptr;
+    ast_block      *body = nullptr;
+
+    fval = new ast_value(array->m_context, funcname, TYPE_FUNCTION);
+    if (!fval) {
+        parseerror(parser, "failed to create accessor function value");
+        return false;
+    }
+    fval->m_flags &= ~(AST_FLAG_COVERAGE_MASK);
+
+    func = ast_function::make(array->m_context, funcname, fval);
+    if (!func) {
+        delete fval;
+        parseerror(parser, "failed to create accessor function node");
+        return false;
+    }
+
+    body = new ast_block(array->m_context);
+    if (!body) {
+        parseerror(parser, "failed to create block for array accessor");
+        delete fval;
+        delete func;
+        return false;
+    }
+
+    func->m_blocks.emplace_back(body);
+    *out = fval;
+
+    parser->accessors.push_back(fval);
+
+    return true;
+}
+
+static ast_value* parser_create_array_setter_proto(parser_t *parser, ast_value *array, const char *funcname)
+{
+    ast_value      *index = nullptr;
+    ast_value      *value = nullptr;
+    ast_function   *func;
+    ast_value      *fval;
+
+    if (!ast_istype(array->m_next, ast_value)) {
+        parseerror(parser, "internal error: array accessor needs to build an ast_value with a copy of the element type");
+        return nullptr;
+    }
+
+    if (!parser_create_array_accessor(parser, array, funcname, &fval))
+        return nullptr;
+    func = fval->m_constval.vfunc;
+    fval->m_next = new ast_value(array->m_context, "<void>", TYPE_VOID);
+
+    index = new ast_value(array->m_context, "index", TYPE_FLOAT);
+    value = new ast_value(ast_copy_type, *(ast_value*)array->m_next);
+
+    if (!index || !value) {
+        parseerror(parser, "failed to create locals for array accessor");
+        goto cleanup;
+    }
+    value->m_name = "value"; // not important
+    fval->m_type_params.emplace_back(index);
+    fval->m_type_params.emplace_back(value);
+
+    array->m_setter = fval;
+    return fval;
+cleanup:
+    if (index) delete index;
+    if (value) delete value;
+    delete func;
+    delete fval;
+    return nullptr;
+}
+
+static bool parser_create_array_setter_impl(parser_t *parser, ast_value *array)
+{
+    ast_expression *root = nullptr;
+    root = array_setter_node(parser, array,
+                             array->m_setter->m_type_params[0].get(),
+                             array->m_setter->m_type_params[1].get(),
+                             0, array->m_count);
+    if (!root) {
+        parseerror(parser, "failed to build accessor search tree");
+        return false;
+    }
+    if (!array->m_setter->m_constval.vfunc->m_blocks[0].get()->addExpr(root)) {
+        delete root;
+        return false;
+    }
+    return true;
+}
+
+static bool parser_create_array_setter(parser_t *parser, ast_value *array, const char *funcname)
+{
+    if (!parser_create_array_setter_proto(parser, array, funcname))
+        return false;
+    return parser_create_array_setter_impl(parser, array);
+}
+
+static bool parser_create_array_field_setter(parser_t *parser, ast_value *array, const char *funcname)
+{
+    ast_expression *root = nullptr;
+    ast_value      *entity = nullptr;
+    ast_value      *index = nullptr;
+    ast_value      *value = nullptr;
+    ast_function   *func;
+    ast_value      *fval;
+
+    if (!ast_istype(array->m_next, ast_value)) {
+        parseerror(parser, "internal error: array accessor needs to build an ast_value with a copy of the element type");
+        return false;
+    }
+
+    if (!parser_create_array_accessor(parser, array, funcname, &fval))
+        return false;
+    func = fval->m_constval.vfunc;
+    fval->m_next = new ast_value(array->m_context, "<void>", TYPE_VOID);
+
+    entity = new ast_value(array->m_context, "entity", TYPE_ENTITY);
+    index  = new ast_value(array->m_context, "index",  TYPE_FLOAT);
+    value  = new ast_value(ast_copy_type, *(ast_value*)array->m_next);
+    if (!entity || !index || !value) {
+        parseerror(parser, "failed to create locals for array accessor");
+        goto cleanup;
+    }
+    value->m_name = "value"; // not important
+    fval->m_type_params.emplace_back(entity);
+    fval->m_type_params.emplace_back(index);
+    fval->m_type_params.emplace_back(value);
+
+    root = array_field_setter_node(parser, array, entity, index, value, 0, array->m_count);
+    if (!root) {
+        parseerror(parser, "failed to build accessor search tree");
+        goto cleanup;
+    }
+
+    array->m_setter = fval;
+    return func->m_blocks[0].get()->addExpr(root);
+cleanup:
+    if (entity) delete entity;
+    if (index)  delete index;
+    if (value)  delete value;
+    if (root)   delete root;
+    delete func;
+    delete fval;
+    return false;
+}
+
+static ast_value* parser_create_array_getter_proto(parser_t *parser, ast_value *array, const ast_expression *elemtype, const char *funcname)
+{
+    ast_value      *index = nullptr;
+    ast_value      *fval;
+    ast_function   *func;
+
+    /* NOTE: checking array->m_next rather than elemtype since
+     * for fields elemtype is a temporary fieldtype.
+     */
+    if (!ast_istype(array->m_next, ast_value)) {
+        parseerror(parser, "internal error: array accessor needs to build an ast_value with a copy of the element type");
+        return nullptr;
+    }
+
+    if (!parser_create_array_accessor(parser, array, funcname, &fval))
+        return nullptr;
+    func = fval->m_constval.vfunc;
+    fval->m_next = new ast_expression(ast_copy_type, array->m_context, *elemtype);
+
+    index = new ast_value(array->m_context, "index", TYPE_FLOAT);
+
+    if (!index) {
+        parseerror(parser, "failed to create locals for array accessor");
+        goto cleanup;
+    }
+    fval->m_type_params.emplace_back(index);
+
+    array->m_getter = fval;
+    return fval;
+cleanup:
+    if (index) delete index;
+    delete func;
+    delete fval;
+    return nullptr;
+}
+
+static bool parser_create_array_getter_impl(parser_t *parser, ast_value *array)
+{
+    ast_expression *root = nullptr;
+
+    root = array_getter_node(parser, array, array->m_getter->m_type_params[0].get(), 0, array->m_count);
+    if (!root) {
+        parseerror(parser, "failed to build accessor search tree");
+        return false;
+    }
+    if (!array->m_getter->m_constval.vfunc->m_blocks[0].get()->addExpr(root)) {
+        delete root;
+        return false;
+    }
+    return true;
+}
+
+static bool parser_create_array_getter(parser_t *parser, ast_value *array, const ast_expression *elemtype, const char *funcname)
+{
+    if (!parser_create_array_getter_proto(parser, array, elemtype, funcname))
+        return false;
+    return parser_create_array_getter_impl(parser, array);
+}
+
+static ast_value *parse_parameter_list(parser_t *parser, ast_value *var)
+{
+    lex_ctx_t ctx = parser_ctx(parser);
+    std::vector<std::unique_ptr<ast_value>> params;
+    ast_value *fval;
+    bool first = true;
+    bool variadic = false;
+    ast_value *varparam = nullptr;
+    char *argcounter = nullptr;
+
+    /* for the sake of less code we parse-in in this function */
+    if (!parser_next(parser)) {
+        delete var;
+        parseerror(parser, "expected parameter list");
+        return nullptr;
+    }
+
+    /* parse variables until we hit a closing paren */
+    while (parser->tok != ')') {
+        bool is_varargs = false;
+
+        if (!first) {
+            /* there must be commas between them */
+            if (parser->tok != ',') {
+                parseerror(parser, "expected comma or end of parameter list");
+                goto on_error;
+            }
+            if (!parser_next(parser)) {
+                parseerror(parser, "expected parameter");
+                goto on_error;
+            }
+        }
+        first = false;
+
+        ast_value *param = parse_typename(parser, nullptr, nullptr, &is_varargs);
+        if (!param && !is_varargs)
+            goto on_error;
+        if (is_varargs) {
+            /* '...' indicates a varargs function */
+            variadic = true;
+            if (parser->tok != ')' && parser->tok != TOKEN_IDENT) {
+                parseerror(parser, "`...` must be the last parameter of a variadic function declaration");
+                goto on_error;
+            }
+            if (parser->tok == TOKEN_IDENT) {
+                argcounter = util_strdup(parser_tokval(parser));
+                if (!parser_next(parser) || parser->tok != ')') {
+                    parseerror(parser, "`...` must be the last parameter of a variadic function declaration");
+                    goto on_error;
+                }
+            }
+        } else {
+            params.emplace_back(param);
+            if (param->m_vtype >= TYPE_VARIANT) {
+                char tname[1024]; /* typename is reserved in C++ */
+                ast_type_to_string(param, tname, sizeof(tname));
+                parseerror(parser, "type not supported as part of a parameter list: %s", tname);
+                goto on_error;
+            }
+            /* type-restricted varargs */
+            if (parser->tok == TOKEN_DOTS) {
+                variadic = true;
+                varparam = params.back().release();
+                params.pop_back();
+                if (!parser_next(parser) || (parser->tok != ')' && parser->tok != TOKEN_IDENT)) {
+                    parseerror(parser, "`...` must be the last parameter of a variadic function declaration");
+                    goto on_error;
+                }
+                if (parser->tok == TOKEN_IDENT) {
+                    argcounter = util_strdup(parser_tokval(parser));
+                    param->m_name = argcounter;
+                    if (!parser_next(parser) || parser->tok != ')') {
+                        parseerror(parser, "`...` must be the last parameter of a variadic function declaration");
+                        goto on_error;
+                    }
+                }
+            }
+            if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_FTEQCC && param->m_name[0] == '<') {
+                parseerror(parser, "parameter name omitted");
+                goto on_error;
+            }
+        }
+    }
+
+    if (params.size() == 1 && params[0]->m_vtype == TYPE_VOID)
+        params.clear();
+
+    /* sanity check */
+    if (params.size() > 8 && OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC)
+        (void)!parsewarning(parser, WARN_EXTENSIONS, "more than 8 parameters are not supported by this standard");
+
+    /* parse-out */
+    if (!parser_next(parser)) {
+        parseerror(parser, "parse error after typename");
+        goto on_error;
+    }
+
+    /* now turn 'var' into a function type */
+    fval = new ast_value(ctx, "<type()>", TYPE_FUNCTION);
+    fval->m_next = var;
+    if (variadic)
+        fval->m_flags |= AST_FLAG_VARIADIC;
+    var = fval;
+
+    var->m_type_params = move(params);
+    var->m_varparam = varparam;
+    var->m_argcounter = argcounter;
+
+    return var;
+
+on_error:
+    if (argcounter)
+        mem_d(argcounter);
+    if (varparam)
+        delete varparam;
+    delete var;
+    return nullptr;
+}
+
+static ast_value *parse_arraysize(parser_t *parser, ast_value *var)
+{
+    ast_expression *cexp;
+    ast_value      *cval, *tmp;
+    lex_ctx_t ctx;
+
+    ctx = parser_ctx(parser);
+
+    if (!parser_next(parser)) {
+        delete var;
+        parseerror(parser, "expected array-size");
+        return nullptr;
+    }
+
+    if (parser->tok != ']') {
+        cexp = parse_expression_leave(parser, true, false, false);
+
+        if (!cexp || !ast_istype(cexp, ast_value)) {
+            if (cexp)
+                ast_unref(cexp);
+            delete var;
+            parseerror(parser, "expected array-size as constant positive integer");
+            return nullptr;
+        }
+        cval = (ast_value*)cexp;
+    }
+    else {
+        cexp = nullptr;
+        cval = nullptr;
+    }
+
+    tmp = new ast_value(ctx, "<type[]>", TYPE_ARRAY);
+    tmp->m_next = var;
+    var = tmp;
+
+    if (cval) {
+        if (cval->m_vtype == TYPE_INTEGER)
+            tmp->m_count = cval->m_constval.vint;
+        else if (cval->m_vtype == TYPE_FLOAT)
+            tmp->m_count = cval->m_constval.vfloat;
+        else {
+            ast_unref(cexp);
+            delete var;
+            parseerror(parser, "array-size must be a positive integer constant");
+            return nullptr;
+        }
+
+        ast_unref(cexp);
+    } else {
+        var->m_count = -1;
+        var->m_flags |= AST_FLAG_ARRAY_INIT;
+    }
+
+    if (parser->tok != ']') {
+        delete var;
+        parseerror(parser, "expected ']' after array-size");
+        return nullptr;
+    }
+    if (!parser_next(parser)) {
+        delete var;
+        parseerror(parser, "error after parsing array size");
+        return nullptr;
+    }
+    return var;
+}
+
+/* Parse a complete typename.
+ * for single-variables (ie. function parameters or typedefs) storebase should be nullptr
+ * but when parsing variables separated by comma
+ * 'storebase' should point to where the base-type should be kept.
+ * The base type makes up every bit of type information which comes *before* the
+ * variable name.
+ *
+ * NOTE: The value must either be named, have a nullptr name, or a name starting
+ *       with '<'. In the first case, this will be the actual variable or type
+ *       name, in the other cases it is assumed that the name will appear
+ *       later, and an error is generated otherwise.
+ *
+ * The following will be parsed in its entirety:
+ *     void() foo()
+ * The 'basetype' in this case is 'void()'
+ * and if there's a comma after it, say:
+ *     void() foo(), bar
+ * then the type-information 'void()' can be stored in 'storebase'
+ */
+static ast_value *parse_typename(parser_t *parser, ast_value **storebase, ast_value *cached_typedef, bool *is_vararg)
+{
+    ast_value *var, *tmp;
+    lex_ctx_t    ctx;
+
+    const char *name = nullptr;
+    bool        isfield  = false;
+    bool        wasarray = false;
+    size_t      morefields = 0;
+
+    bool        vararg = (parser->tok == TOKEN_DOTS);
+
+    ctx = parser_ctx(parser);
+
+    /* types may start with a dot */
+    if (parser->tok == '.' || parser->tok == TOKEN_DOTS) {
+        isfield = true;
+        if (parser->tok == TOKEN_DOTS)
+            morefields += 2;
+        /* if we parsed a dot we need a typename now */
+        if (!parser_next(parser)) {
+            parseerror(parser, "expected typename for field definition");
+            return nullptr;
+        }
+
+        /* Further dots are handled seperately because they won't be part of the
+         * basetype
+         */
+        while (true) {
+            if (parser->tok == '.')
+                ++morefields;
+            else if (parser->tok == TOKEN_DOTS)
+                morefields += 3;
+            else
+                break;
+            vararg = false;
+            if (!parser_next(parser)) {
+                parseerror(parser, "expected typename for field definition");
+                return nullptr;
+            }
+        }
+    }
+    if (parser->tok == TOKEN_IDENT)
+        cached_typedef = parser_find_typedef(parser, parser_tokval(parser), 0);
+    if (!cached_typedef && parser->tok != TOKEN_TYPENAME) {
+        if (vararg && is_vararg) {
+            *is_vararg = true;
+            return nullptr;
+        }
+        parseerror(parser, "expected typename");
+        return nullptr;
+    }
+
+    /* generate the basic type value */
+    if (cached_typedef) {
+        var = new ast_value(ast_copy_type, *cached_typedef);
+        var->m_name = "<type(from_def)>";
+    } else
+        var = new ast_value(ctx, "<type>", parser_token(parser)->constval.t);
+
+    for (; morefields; --morefields) {
+        tmp = new ast_value(ctx, "<.type>", TYPE_FIELD);
+        tmp->m_next = var;
+        var = tmp;
+    }
+
+    /* do not yet turn into a field - remember:
+     * .void() foo; is a field too
+     * .void()() foo; is a function
+     */
+
+    /* parse on */
+    if (!parser_next(parser)) {
+        delete var;
+        parseerror(parser, "parse error after typename");
+        return nullptr;
+    }
+
+    /* an opening paren now starts the parameter-list of a function
+     * this is where original-QC has parameter lists.
+     * We allow a single parameter list here.
+     * Much like fteqcc we don't allow `float()() x`
+     */
+    if (parser->tok == '(') {
+        var = parse_parameter_list(parser, var);
+        if (!var)
+            return nullptr;
+    }
+
+    /* store the base if requested */
+    if (storebase) {
+        *storebase = new ast_value(ast_copy_type, *var);
+        if (isfield) {
+            tmp = new ast_value(ctx, "<type:f>", TYPE_FIELD);
+            tmp->m_next = *storebase;
+            *storebase = tmp;
+        }
+    }
+
+    /* there may be a name now */
+    if (parser->tok == TOKEN_IDENT || parser->tok == TOKEN_KEYWORD) {
+        if (!strcmp(parser_tokval(parser), "break"))
+            (void)!parsewarning(parser, WARN_BREAKDEF, "break definition ignored (suggest removing it)");
+        else if (parser->tok == TOKEN_KEYWORD)
+            goto leave;
+
+        name = util_strdup(parser_tokval(parser));
+
+        /* parse on */
+        if (!parser_next(parser)) {
+            delete var;
+            mem_d(name);
+            parseerror(parser, "error after variable or field declaration");
+            return nullptr;
+        }
+    }
+
+    leave:
+    /* now this may be an array */
+    if (parser->tok == '[') {
+        wasarray = true;
+        var = parse_arraysize(parser, var);
+        if (!var) {
+            if (name) mem_d(name);
+            return nullptr;
+        }
+    }
+
+    /* This is the point where we can turn it into a field */
+    if (isfield) {
+        /* turn it into a field if desired */
+        tmp = new ast_value(ctx, "<type:f>", TYPE_FIELD);
+        tmp->m_next = var;
+        var = tmp;
+    }
+
+    /* now there may be function parens again */
+    if (parser->tok == '(' && OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC)
+        parseerror(parser, "C-style function syntax is not allowed in -std=qcc");
+    if (parser->tok == '(' && wasarray)
+        parseerror(parser, "arrays as part of a return type is not supported");
+    while (parser->tok == '(') {
+        var = parse_parameter_list(parser, var);
+        if (!var) {
+            if (name) mem_d(name);
+            return nullptr;
+        }
+    }
+
+    /* finally name it */
+    if (name) {
+        var->m_name = name;
+        // free the name, ast_value_set_name duplicates
+        mem_d(name);
+    }
+
+    return var;
+}
+
+static bool parse_typedef(parser_t *parser)
+{
+    ast_value      *typevar, *oldtype;
+    ast_expression *old;
+
+    typevar = parse_typename(parser, nullptr, nullptr, nullptr);
+
+    if (!typevar)
+        return false;
+
+    // while parsing types, the ast_value's get named '<something>'
+    if (!typevar->m_name.length() || typevar->m_name[0] == '<') {
+        parseerror(parser, "missing name in typedef");
+        delete typevar;
+        return false;
+    }
+
+    if ( (old = parser_find_var(parser, typevar->m_name)) ) {
+        parseerror(parser, "cannot define a type with the same name as a variable: %s\n"
+                   " -> `%s` has been declared here: %s:%i",
+                   typevar->m_name, old->m_context.file, old->m_context.line);
+        delete typevar;
+        return false;
+    }
+
+    if ( (oldtype = parser_find_typedef(parser, typevar->m_name, vec_last(parser->_blocktypedefs))) ) {
+        parseerror(parser, "type `%s` has already been declared here: %s:%i",
+                   typevar->m_name, oldtype->m_context.file, oldtype->m_context.line);
+        delete typevar;
+        return false;
+    }
+
+    vec_push(parser->_typedefs, typevar);
+    util_htset(vec_last(parser->typedefs), typevar->m_name.c_str(), typevar);
+
+    if (parser->tok != ';') {
+        parseerror(parser, "expected semicolon after typedef");
+        return false;
+    }
+    if (!parser_next(parser)) {
+        parseerror(parser, "parse error after typedef");
+        return false;
+    }
+
+    return true;
+}
+
+static const char *cvq_to_str(int cvq) {
+    switch (cvq) {
+        case CV_NONE:  return "none";
+        case CV_VAR:   return "`var`";
+        case CV_CONST: return "`const`";
+        default:       return "<INVALID>";
+    }
+}
+
+static bool parser_check_qualifiers(parser_t *parser, const ast_value *var, const ast_value *proto)
+{
+    bool av, ao;
+    if (proto->m_cvq != var->m_cvq) {
+        if (!(proto->m_cvq == CV_CONST && var->m_cvq == CV_NONE &&
+              !OPTS_FLAG(INITIALIZED_NONCONSTANTS) &&
+              parser->tok == '='))
+        {
+            return !parsewarning(parser, WARN_DIFFERENT_QUALIFIERS,
+                                 "`%s` declared with different qualifiers: %s\n"
+                                 " -> previous declaration here: %s:%i uses %s",
+                                 var->m_name, cvq_to_str(var->m_cvq),
+                                 proto->m_context.file, proto->m_context.line,
+                                 cvq_to_str(proto->m_cvq));
+        }
+    }
+    av = (var  ->m_flags & AST_FLAG_NORETURN);
+    ao = (proto->m_flags & AST_FLAG_NORETURN);
+    if (!av != !ao) {
+        return !parsewarning(parser, WARN_DIFFERENT_ATTRIBUTES,
+                             "`%s` declared with different attributes%s\n"
+                             " -> previous declaration here: %s:%i",
+                             var->m_name, (av ? ": noreturn" : ""),
+                             proto->m_context.file, proto->m_context.line,
+                             (ao ? ": noreturn" : ""));
+    }
+    return true;
+}
+
+static bool create_array_accessors(parser_t *parser, ast_value *var)
+{
+    char name[1024];
+    util_snprintf(name, sizeof(name), "%s##SET", var->m_name.c_str());
+    if (!parser_create_array_setter(parser, var, name))
+        return false;
+    util_snprintf(name, sizeof(name), "%s##GET", var->m_name.c_str());
+    if (!parser_create_array_getter(parser, var, var->m_next, name))
+        return false;
+    return true;
+}
+
+static bool parse_array(parser_t *parser, ast_value *array)
+{
+    size_t i;
+    if (array->m_initlist.size()) {
+        parseerror(parser, "array already initialized elsewhere");
+        return false;
+    }
+    if (!parser_next(parser)) {
+        parseerror(parser, "parse error in array initializer");
+        return false;
+    }
+    i = 0;
+    while (parser->tok != '}') {
+        ast_value *v = (ast_value*)parse_expression_leave(parser, true, false, false);
+        if (!v)
+            return false;
+        if (!ast_istype(v, ast_value) || !v->m_hasvalue || v->m_cvq != CV_CONST) {
+            ast_unref(v);
+            parseerror(parser, "initializing element must be a compile time constant");
+            return false;
+        }
+        array->m_initlist.push_back(v->m_constval);
+        if (v->m_vtype == TYPE_STRING) {
+            array->m_initlist[i].vstring = util_strdupe(array->m_initlist[i].vstring);
+            ++i;
+        }
+        ast_unref(v);
+        if (parser->tok == '}')
+            break;
+        if (parser->tok != ',' || !parser_next(parser)) {
+            parseerror(parser, "expected comma or '}' in element list");
+            return false;
+        }
+    }
+    if (!parser_next(parser) || parser->tok != ';') {
+        parseerror(parser, "expected semicolon after initializer, got %s");
+        return false;
+    }
+    /*
+    if (!parser_next(parser)) {
+        parseerror(parser, "parse error after initializer");
+        return false;
+    }
+    */
+
+    if (array->m_flags & AST_FLAG_ARRAY_INIT) {
+        if (array->m_count != (size_t)-1) {
+            parseerror(parser, "array `%s' has already been initialized with %u elements",
+                       array->m_name, (unsigned)array->m_count);
+        }
+        array->m_count = array->m_initlist.size();
+        if (!create_array_accessors(parser, array))
+            return false;
+    }
+    return true;
+}
+
+static bool parse_variable(parser_t *parser, ast_block *localblock, bool nofields, int qualifier, ast_value *cached_typedef, bool noref, bool is_static, uint32_t qflags, char *vstring)
+{
+    ast_value *var;
+    ast_value *proto;
+    ast_expression *old;
+    bool       was_end;
+    size_t     i;
+
+    ast_value *basetype = nullptr;
+    bool      retval    = true;
+    bool      isparam   = false;
+    bool      isvector  = false;
+    bool      cleanvar  = true;
+    bool      wasarray  = false;
+
+    ast_member *me[3] = { nullptr, nullptr, nullptr };
+    ast_member *last_me[3] = { nullptr, nullptr, nullptr };
+
+    if (!localblock && is_static)
+        parseerror(parser, "`static` qualifier is not supported in global scope");
+
+    /* get the first complete variable */
+    var = parse_typename(parser, &basetype, cached_typedef, nullptr);
+    if (!var) {
+        if (basetype)
+            delete basetype;
+        return false;
+    }
+
+    /* while parsing types, the ast_value's get named '<something>' */
+    if (!var->m_name.length() || var->m_name[0] == '<') {
+        parseerror(parser, "declaration does not declare anything");
+        if (basetype)
+            delete basetype;
+        return false;
+    }
+
+    while (true) {
+        proto = nullptr;
+        wasarray = false;
+
+        /* Part 0: finish the type */
+        if (parser->tok == '(') {
+            if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC)
+                parseerror(parser, "C-style function syntax is not allowed in -std=qcc");
+            var = parse_parameter_list(parser, var);
+            if (!var) {
+                retval = false;
+                goto cleanup;
+            }
+        }
+        /* we only allow 1-dimensional arrays */
+        if (parser->tok == '[') {
+            wasarray = true;
+            var = parse_arraysize(parser, var);
+            if (!var) {
+                retval = false;
+                goto cleanup;
+            }
+        }
+        if (parser->tok == '(' && wasarray) {
+            parseerror(parser, "arrays as part of a return type is not supported");
+            /* we'll still parse the type completely for now */
+        }
+        /* for functions returning functions */
+        while (parser->tok == '(') {
+            if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC)
+                parseerror(parser, "C-style function syntax is not allowed in -std=qcc");
+            var = parse_parameter_list(parser, var);
+            if (!var) {
+                retval = false;
+                goto cleanup;
+            }
+        }
+
+        var->m_cvq = qualifier;
+        if (qflags & AST_FLAG_COVERAGE) /* specified in QC, drop our default */
+            var->m_flags &= ~(AST_FLAG_COVERAGE_MASK);
+        var->m_flags |= qflags;
+
+        /*
+         * store the vstring back to var for alias and
+         * deprecation messages.
+         */
+        if (var->m_flags & AST_FLAG_DEPRECATED ||
+            var->m_flags & AST_FLAG_ALIAS)
+            var->m_desc = vstring;
+
+        if (parser_find_global(parser, var->m_name) && var->m_flags & AST_FLAG_ALIAS) {
+            parseerror(parser, "function aliases cannot be forward declared");
+            retval = false;
+            goto cleanup;
+        }
+
+
+        /* Part 1:
+         * check for validity: (end_sys_..., multiple-definitions, prototypes, ...)
+         * Also: if there was a prototype, `var` will be deleted and set to `proto` which
+         * is then filled with the previous definition and the parameter-names replaced.
+         */
+        if (var->m_name == "nil") {
+            if (OPTS_FLAG(UNTYPED_NIL)) {
+                if (!localblock || !OPTS_FLAG(PERMISSIVE))
+                    parseerror(parser, "name `nil` not allowed (try -fpermissive)");
+            } else
+                (void)!parsewarning(parser, WARN_RESERVED_NAMES, "variable name `nil` is reserved");
+        }
+        if (!localblock) {
+            /* Deal with end_sys_ vars */
+            was_end = false;
+            if (var->m_name == "end_sys_globals") {
+                var->m_uses++;
+                parser->crc_globals = parser->globals.size();
+                was_end = true;
+            }
+            else if (var->m_name == "end_sys_fields") {
+                var->m_uses++;
+                parser->crc_fields = parser->fields.size();
+                was_end = true;
+            }
+            if (was_end && var->m_vtype == TYPE_FIELD) {
+                if (parsewarning(parser, WARN_END_SYS_FIELDS,
+                                 "global '%s' hint should not be a field",
+                                 parser_tokval(parser)))
+                {
+                    retval = false;
+                    goto cleanup;
+                }
+            }
+
+            if (!nofields && var->m_vtype == TYPE_FIELD)
+            {
+                /* deal with field declarations */
+                old = parser_find_field(parser, var->m_name);
+                if (old) {
+                    if (parsewarning(parser, WARN_FIELD_REDECLARED, "field `%s` already declared here: %s:%i",
+                                     var->m_name, old->m_context.file, (int)old->m_context.line))
+                    {
+                        retval = false;
+                        goto cleanup;
+                    }
+                    delete var;
+                    var = nullptr;
+                    goto skipvar;
+                    /*
+                    parseerror(parser, "field `%s` already declared here: %s:%i",
+                               var->m_name, old->m_context.file, old->m_context.line);
+                    retval = false;
+                    goto cleanup;
+                    */
+                }
+                if ((OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC || OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_FTEQCC) &&
+                    (old = parser_find_global(parser, var->m_name)))
+                {
+                    parseerror(parser, "cannot declare a field and a global of the same name with -std=qcc");
+                    parseerror(parser, "field `%s` already declared here: %s:%i",
+                               var->m_name, old->m_context.file, old->m_context.line);
+                    retval = false;
+                    goto cleanup;
+                }
+            }
+            else
+            {
+                /* deal with other globals */
+                old = parser_find_global(parser, var->m_name);
+                if (old && var->m_vtype == TYPE_FUNCTION && old->m_vtype == TYPE_FUNCTION)
+                {
+                    /* This is a function which had a prototype */
+                    if (!ast_istype(old, ast_value)) {
+                        parseerror(parser, "internal error: prototype is not an ast_value");
+                        retval = false;
+                        goto cleanup;
+                    }
+                    proto = (ast_value*)old;
+                    proto->m_desc = var->m_desc;
+                    if (!proto->compareType(*var)) {
+                        parseerror(parser, "conflicting types for `%s`, previous declaration was here: %s:%i",
+                                   proto->m_name,
+                                   proto->m_context.file, proto->m_context.line);
+                        retval = false;
+                        goto cleanup;
+                    }
+                    /* we need the new parameter-names */
+                    for (i = 0; i < proto->m_type_params.size(); ++i)
+                        proto->m_type_params[i]->m_name = var->m_type_params[i]->m_name;
+                    if (!parser_check_qualifiers(parser, var, proto)) {
+                        retval = false;
+                        proto = nullptr;
+                        goto cleanup;
+                    }
+                    proto->m_flags |= var->m_flags;
+                    delete var;
+                    var = proto;
+                }
+                else
+                {
+                    /* other globals */
+                    if (old) {
+                        if (parsewarning(parser, WARN_DOUBLE_DECLARATION,
+                                         "global `%s` already declared here: %s:%i",
+                                         var->m_name, old->m_context.file, old->m_context.line))
+                        {
+                            retval = false;
+                            goto cleanup;
+                        }
+                        if (old->m_flags & AST_FLAG_FINAL_DECL) {
+                            parseerror(parser, "cannot redeclare variable `%s`, declared final here: %s:%i",
+                                       var->m_name, old->m_context.file, old->m_context.line);
+                            retval = false;
+                            goto cleanup;
+                        }
+                        proto = (ast_value*)old;
+                        if (!ast_istype(old, ast_value)) {
+                            parseerror(parser, "internal error: not an ast_value");
+                            retval = false;
+                            proto = nullptr;
+                            goto cleanup;
+                        }
+                        if (!parser_check_qualifiers(parser, var, proto)) {
+                            retval = false;
+                            proto = nullptr;
+                            goto cleanup;
+                        }
+                        proto->m_flags |= var->m_flags;
+                        /* copy the context for finals,
+                         * so the error can show where it was actually made 'final'
+                         */
+                        if (proto->m_flags & AST_FLAG_FINAL_DECL)
+                            old->m_context = var->m_context;
+                        delete var;
+                        var = proto;
+                    }
+                    if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC &&
+                        (old = parser_find_field(parser, var->m_name)))
+                    {
+                        parseerror(parser, "cannot declare a field and a global of the same name with -std=qcc");
+                        parseerror(parser, "global `%s` already declared here: %s:%i",
+                                   var->m_name, old->m_context.file, old->m_context.line);
+                        retval = false;
+                        goto cleanup;
+                    }
+                }
+            }
+        }
+        else /* it's not a global */
+        {
+            old = parser_find_local(parser, var->m_name, vec_size(parser->variables)-1, &isparam);
+            if (old && !isparam) {
+                parseerror(parser, "local `%s` already declared here: %s:%i",
+                           var->m_name, old->m_context.file, (int)old->m_context.line);
+                retval = false;
+                goto cleanup;
+            }
+            /* doing this here as the above is just for a single scope */
+            old = parser_find_local(parser, var->m_name, 0, &isparam);
+            if (old && isparam) {
+                if (parsewarning(parser, WARN_LOCAL_SHADOWS,
+                                 "local `%s` is shadowing a parameter", var->m_name))
+                {
+                    parseerror(parser, "local `%s` already declared here: %s:%i",
+                               var->m_name, old->m_context.file, (int)old->m_context.line);
+                    retval = false;
+                    goto cleanup;
+                }
+                if (OPTS_OPTION_U32(OPTION_STANDARD) != COMPILER_GMQCC) {
+                    delete var;
+                    if (ast_istype(old, ast_value))
+                        var = proto = (ast_value*)old;
+                    else {
+                        var = nullptr;
+                        goto skipvar;
+                    }
+                }
+            }
+        }
+
+        /* in a noref section we simply bump the usecount */
+        if (noref || parser->noref)
+            var->m_uses++;
+
+        /* Part 2:
+         * Create the global/local, and deal with vector types.
+         */
+        if (!proto) {
+            if (var->m_vtype == TYPE_VECTOR)
+                isvector = true;
+            else if (var->m_vtype == TYPE_FIELD &&
+                     var->m_next->m_vtype == TYPE_VECTOR)
+                isvector = true;
+
+            if (isvector) {
+                if (!create_vector_members(var, me)) {
+                    retval = false;
+                    goto cleanup;
+                }
+            }
+
+            if (!localblock) {
+                /* deal with global variables, fields, functions */
+                if (!nofields && var->m_vtype == TYPE_FIELD && parser->tok != '=') {
+                    var->m_isfield = true;
+                    parser->fields.push_back(var);
+                    util_htset(parser->htfields, var->m_name.c_str(), var);
+                    if (isvector) {
+                        for (i = 0; i < 3; ++i) {
+                            parser->fields.push_back(me[i]);
+                            util_htset(parser->htfields, me[i]->m_name.c_str(), me[i]);
+                        }
+                    }
+                }
+                else {
+                    if (!(var->m_flags & AST_FLAG_ALIAS)) {
+                        parser_addglobal(parser, var->m_name, var);
+                        if (isvector) {
+                            for (i = 0; i < 3; ++i) {
+                                parser_addglobal(parser, me[i]->m_name.c_str(), me[i]);
+                            }
+                        }
+                    } else {
+                        ast_expression *find  = parser_find_global(parser, var->m_desc);
+
+                        if (!find) {
+                            compile_error(parser_ctx(parser), "undeclared variable `%s` for alias `%s`", var->m_desc, var->m_name);
+                            return false;
+                        }
+
+                        if (!var->compareType(*find)) {
+                            char ty1[1024];
+                            char ty2[1024];
+
+                            ast_type_to_string(find, ty1, sizeof(ty1));
+                            ast_type_to_string(var,  ty2, sizeof(ty2));
+
+                            compile_error(parser_ctx(parser), "incompatible types `%s` and `%s` for alias `%s`",
+                                ty1, ty2, var->m_name
+                            );
+                            return false;
+                        }
+
+                        util_htset(parser->aliases, var->m_name.c_str(), find);
+
+                        /* generate aliases for vector components */
+                        if (isvector) {
+                            char *buffer[3];
+
+                            util_asprintf(&buffer[0], "%s_x", var->m_desc.c_str());
+                            util_asprintf(&buffer[1], "%s_y", var->m_desc.c_str());
+                            util_asprintf(&buffer[2], "%s_z", var->m_desc.c_str());
+
+                            util_htset(parser->aliases, me[0]->m_name.c_str(), parser_find_global(parser, buffer[0]));
+                            util_htset(parser->aliases, me[1]->m_name.c_str(), parser_find_global(parser, buffer[1]));
+                            util_htset(parser->aliases, me[2]->m_name.c_str(), parser_find_global(parser, buffer[2]));
+
+                            mem_d(buffer[0]);
+                            mem_d(buffer[1]);
+                            mem_d(buffer[2]);
+                        }
+                    }
+                }
+            } else {
+                if (is_static) {
+                    // a static adds itself to be generated like any other global
+                    // but is added to the local namespace instead
+                    std::string defname;
+                    size_t  prefix_len;
+                    size_t  sn, sn_size;
+
+                    defname = parser->function->m_name;
+                    defname.append(2, ':');
+
+                    // remember the length up to here
+                    prefix_len = defname.length();
+
+                    // Add it to the local scope
+                    util_htset(vec_last(parser->variables), var->m_name.c_str(), (void*)var);
+
+                    // now rename the global
+                    defname.append(var->m_name);
+                    // if a variable of that name already existed, add the
+                    // counter value.
+                    // The counter is incremented either way.
+                    sn_size = parser->function->m_static_names.size();
+                    for (sn = 0; sn != sn_size; ++sn) {
+                        if (parser->function->m_static_names[sn] == var->m_name.c_str())
+                            break;
+                    }
+                    if (sn != sn_size) {
+                        char *num = nullptr;
+                        int   len = util_asprintf(&num, "#%u", parser->function->m_static_count);
+                        defname.append(num, 0, len);
+                        mem_d(num);
+                    }
+                    else
+                        parser->function->m_static_names.emplace_back(var->m_name);
+                    parser->function->m_static_count++;
+                    var->m_name = defname;
+
+                    // push it to the to-be-generated globals
+                    parser->globals.push_back(var);
+
+                    // same game for the vector members
+                    if (isvector) {
+                        defname.erase(prefix_len);
+                        for (i = 0; i < 3; ++i) {
+                            util_htset(vec_last(parser->variables), me[i]->m_name.c_str(), (void*)(me[i]));
+                            me[i]->m_name = move(defname + me[i]->m_name);
+                            parser->globals.push_back(me[i]);
+                        }
+                    }
+                } else {
+                    localblock->m_locals.push_back(var);
+                    parser_addlocal(parser, var->m_name, var);
+                    if (isvector) {
+                        for (i = 0; i < 3; ++i) {
+                            parser_addlocal(parser, me[i]->m_name, me[i]);
+                            localblock->collect(me[i]);
+                        }
+                    }
+                }
+            }
+        }
+        memcpy(last_me, me, sizeof(me));
+        me[0] = me[1] = me[2] = nullptr;
+        cleanvar = false;
+        /* Part 2.2
+         * deal with arrays
+         */
+        if (var->m_vtype == TYPE_ARRAY) {
+            if (var->m_count != (size_t)-1) {
+                if (!create_array_accessors(parser, var))
+                    goto cleanup;
+            }
+        }
+        else if (!localblock && !nofields &&
+                 var->m_vtype == TYPE_FIELD &&
+                 var->m_next->m_vtype == TYPE_ARRAY)
+        {
+            char name[1024];
+            ast_expression *telem;
+            ast_value      *tfield;
+            ast_value      *array = (ast_value*)var->m_next;
+
+            if (!ast_istype(var->m_next, ast_value)) {
+                parseerror(parser, "internal error: field element type must be an ast_value");
+                goto cleanup;
+            }
+
+            util_snprintf(name, sizeof(name), "%s##SETF", var->m_name.c_str());
+            if (!parser_create_array_field_setter(parser, array, name))
+                goto cleanup;
+
+            telem = new ast_expression(ast_copy_type, var->m_context, *array->m_next);
+            tfield = new ast_value(var->m_context, "<.type>", TYPE_FIELD);
+            tfield->m_next = telem;
+            util_snprintf(name, sizeof(name), "%s##GETFP", var->m_name.c_str());
+            if (!parser_create_array_getter(parser, array, tfield, name)) {
+                delete tfield;
+                goto cleanup;
+            }
+            delete tfield;
+        }
+
+skipvar:
+        if (parser->tok == ';') {
+            delete basetype;
+            if (!parser_next(parser)) {
+                parseerror(parser, "error after variable declaration");
+                return false;
+            }
+            return true;
+        }
+
+        if (parser->tok == ',')
+            goto another;
+
+        /*
+        if (!var || (!localblock && !nofields && basetype->m_vtype == TYPE_FIELD)) {
+        */
+        if (!var) {
+            parseerror(parser, "missing comma or semicolon while parsing variables");
+            break;
+        }
+
+        if (localblock && OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) {
+            if (parsewarning(parser, WARN_LOCAL_CONSTANTS,
+                             "initializing expression turns variable `%s` into a constant in this standard",
+                             var->m_name) )
+            {
+                break;
+            }
+        }
+
+        if (parser->tok != '{' || var->m_vtype != TYPE_FUNCTION) {
+            if (parser->tok != '=') {
+                parseerror(parser, "missing semicolon or initializer, got: `%s`", parser_tokval(parser));
+                break;
+            }
+
+            if (!parser_next(parser)) {
+                parseerror(parser, "error parsing initializer");
+                break;
+            }
+        }
+        else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) {
+            parseerror(parser, "expected '=' before function body in this standard");
+        }
+
+        if (parser->tok == '#') {
+            ast_function *func   = nullptr;
+            ast_value    *number = nullptr;
+            float         fractional;
+            float         integral;
+            int           builtin_num;
+
+            if (localblock) {
+                parseerror(parser, "cannot declare builtins within functions");
+                break;
+            }
+            if (var->m_vtype != TYPE_FUNCTION) {
+                parseerror(parser, "unexpected builtin number, '%s' is not a function", var->m_name);
+                break;
+            }
+            if (!parser_next(parser)) {
+                parseerror(parser, "expected builtin number");
+                break;
+            }
+
+            if (OPTS_FLAG(EXPRESSIONS_FOR_BUILTINS)) {
+                number = (ast_value*)parse_expression_leave(parser, true, false, false);
+                if (!number) {
+                    parseerror(parser, "builtin number expected");
+                    break;
+                }
+                if (!ast_istype(number, ast_value) || !number->m_hasvalue || number->m_cvq != CV_CONST)
+                {
+                    ast_unref(number);
+                    parseerror(parser, "builtin number must be a compile time constant");
+                    break;
+                }
+                if (number->m_vtype == TYPE_INTEGER)
+                    builtin_num = number->m_constval.vint;
+                else if (number->m_vtype == TYPE_FLOAT)
+                    builtin_num = number->m_constval.vfloat;
+                else {
+                    ast_unref(number);
+                    parseerror(parser, "builtin number must be an integer constant");
+                    break;
+                }
+                ast_unref(number);
+
+                fractional = modff(builtin_num, &integral);
+                if (builtin_num < 0 || fractional != 0) {
+                    parseerror(parser, "builtin number must be an integer greater than zero");
+                    break;
+                }
+
+                /* we only want the integral part anyways */
+                builtin_num = integral;
+            } else if (parser->tok == TOKEN_INTCONST) {
+                builtin_num = parser_token(parser)->constval.i;
+            } else {
+                parseerror(parser, "builtin number must be a compile time constant");
+                break;
+            }
+
+            if (var->m_hasvalue) {
+                (void)!parsewarning(parser, WARN_DOUBLE_DECLARATION,
+                                    "builtin `%s` has already been defined\n"
+                                    " -> previous declaration here: %s:%i",
+                                    var->m_name, var->m_context.file, (int)var->m_context.line);
+            }
+            else
+            {
+                func = ast_function::make(var->m_context, var->m_name, var);
+                if (!func) {
+                    parseerror(parser, "failed to allocate function for `%s`", var->m_name);
+                    break;
+                }
+                parser->functions.push_back(func);
+
+                func->m_builtin = -builtin_num-1;
+            }
+
+            if (OPTS_FLAG(EXPRESSIONS_FOR_BUILTINS)
+                    ? (parser->tok != ',' && parser->tok != ';')
+                    : (!parser_next(parser)))
+            {
+                parseerror(parser, "expected comma or semicolon");
+                delete func;
+                var->m_constval.vfunc = nullptr;
+                break;
+            }
+        }
+        else if (var->m_vtype == TYPE_ARRAY && parser->tok == '{')
+        {
+            if (localblock) {
+                /* Note that fteqcc and most others don't even *have*
+                 * local arrays, so this is not a high priority.
+                 */
+                parseerror(parser, "TODO: initializers for local arrays");
+                break;
+            }
+
+            var->m_hasvalue = true;
+            if (!parse_array(parser, var))
+                break;
+        }
+        else if (var->m_vtype == TYPE_FUNCTION && (parser->tok == '{' || parser->tok == '['))
+        {
+            if (localblock) {
+                parseerror(parser, "cannot declare functions within functions");
+                break;
+            }
+
+            if (proto)
+                proto->m_context = parser_ctx(parser);
+
+            if (!parse_function_body(parser, var))
+                break;
+            delete basetype;
+            for (auto &it : parser->gotos)
+                parseerror(parser, "undefined label: `%s`", it->m_name);
+            parser->gotos.clear();
+            parser->labels.clear();
+            return true;
+        } else {
+            ast_expression *cexp;
+            ast_value      *cval;
+            bool            folded_const = false;
+
+            cexp = parse_expression_leave(parser, true, false, false);
+            if (!cexp)
+                break;
+            cval = ast_istype(cexp, ast_value) ? (ast_value*)cexp : nullptr;
+
+            /* deal with foldable constants: */
+            if (localblock &&
+                var->m_cvq == CV_CONST && cval && cval->m_hasvalue && cval->m_cvq == CV_CONST && !cval->m_isfield)
+            {
+                /* remove it from the current locals */
+                if (isvector) {
+                    for (i = 0; i < 3; ++i) {
+                        vec_pop(parser->_locals);
+                        localblock->m_collect.pop_back();
+                    }
+                }
+                /* do sanity checking, this function really needs refactoring */
+                if (vec_last(parser->_locals) != var)
+                    parseerror(parser, "internal error: unexpected change in local variable handling");
+                else
+                    vec_pop(parser->_locals);
+                if (localblock->m_locals.back() != var)
+                    parseerror(parser, "internal error: unexpected change in local variable handling (2)");
+                else
+                    localblock->m_locals.pop_back();
+                /* push it to the to-be-generated globals */
+                parser->globals.push_back(var);
+                if (isvector)
+                    for (i = 0; i < 3; ++i)
+                        parser->globals.push_back(last_me[i]);
+                folded_const = true;
+            }
+
+            if (folded_const || !localblock || is_static) {
+                if (cval != parser->nil &&
+                    (!cval || ((!cval->m_hasvalue || cval->m_cvq != CV_CONST) && !cval->m_isfield))
+                   )
+                {
+                    parseerror(parser, "initializer is non constant");
+                }
+                else
+                {
+                    if (!is_static &&
+                        !OPTS_FLAG(INITIALIZED_NONCONSTANTS) &&
+                        qualifier != CV_VAR)
+                    {
+                        var->m_cvq = CV_CONST;
+                    }
+                    if (cval == parser->nil)
+                        var->m_flags |= AST_FLAG_INITIALIZED;
+                    else
+                    {
+                        var->m_hasvalue = true;
+                        if (cval->m_vtype == TYPE_STRING)
+                            var->m_constval.vstring = parser_strdup(cval->m_constval.vstring);
+                        else if (cval->m_vtype == TYPE_FIELD)
+                            var->m_constval.vfield = cval;
+                        else
+                            memcpy(&var->m_constval, &cval->m_constval, sizeof(var->m_constval));
+                        ast_unref(cval);
+                    }
+                }
+            } else {
+                int cvq;
+                shunt sy;
+                cvq = var->m_cvq;
+                var->m_cvq = CV_NONE;
+                sy.out.push_back(syexp(var->m_context, var));
+                sy.out.push_back(syexp(cexp->m_context, cexp));
+                sy.ops.push_back(syop(var->m_context, parser->assign_op));
+                if (!parser_sy_apply_operator(parser, &sy))
+                    ast_unref(cexp);
+                else {
+                    if (sy.out.size() != 1 && sy.ops.size() != 0)
+                        parseerror(parser, "internal error: leaked operands");
+                    if (!localblock->addExpr(sy.out[0].out))
+                        break;
+                }
+                var->m_cvq = cvq;
+            }
+            /* a constant initialized to an inexact value should be marked inexact:
+             * const float x = <inexact>; should propagate the inexact flag
+             */
+            if (var->m_cvq == CV_CONST && var->m_vtype == TYPE_FLOAT) {
+                if (cval && cval->m_hasvalue && cval->m_cvq == CV_CONST)
+                    var->m_inexact = cval->m_inexact;
+            }
+        }
+
+another:
+        if (parser->tok == ',') {
+            if (!parser_next(parser)) {
+                parseerror(parser, "expected another variable");
+                break;
+            }
+
+            if (parser->tok != TOKEN_IDENT) {
+                parseerror(parser, "expected another variable");
+                break;
+            }
+            var = new ast_value(ast_copy_type, *basetype);
+            cleanvar = true;
+            var->m_name = parser_tokval(parser);
+            if (!parser_next(parser)) {
+                parseerror(parser, "error parsing variable declaration");
+                break;
+            }
+            continue;
+        }
+
+        if (parser->tok != ';') {
+            parseerror(parser, "missing semicolon after variables");
+            break;
+        }
+
+        if (!parser_next(parser)) {
+            parseerror(parser, "parse error after variable declaration");
+            break;
+        }
+
+        delete basetype;
+        return true;
+    }
+
+    if (cleanvar && var)
+        delete var;
+    delete basetype;
+    return false;
+
+cleanup:
+    delete basetype;
+    if (cleanvar && var)
+        delete var;
+    delete me[0];
+    delete me[1];
+    delete me[2];
+    return retval;
+}
+
+static bool parser_global_statement(parser_t *parser)
+{
+    int        cvq       = CV_WRONG;
+    bool       noref     = false;
+    bool       is_static = false;
+    uint32_t   qflags    = 0;
+    ast_value *istype    = nullptr;
+    char      *vstring   = nullptr;
+
+    if (parser->tok == TOKEN_IDENT)
+        istype = parser_find_typedef(parser, parser_tokval(parser), 0);
+
+    if (istype || parser->tok == TOKEN_TYPENAME || parser->tok == '.' || parser->tok == TOKEN_DOTS)
+    {
+        return parse_variable(parser, nullptr, false, CV_NONE, istype, false, false, 0, nullptr);
+    }
+    else if (parse_qualifiers(parser, false, &cvq, &noref, &is_static, &qflags, &vstring))
+    {
+        if (cvq == CV_WRONG)
+            return false;
+        return parse_variable(parser, nullptr, false, cvq, nullptr, noref, is_static, qflags, vstring);
+    }
+    else if (parser->tok == TOKEN_IDENT && !strcmp(parser_tokval(parser), "enum"))
+    {
+        return parse_enum(parser);
+    }
+    else if (parser->tok == TOKEN_KEYWORD)
+    {
+        if (!strcmp(parser_tokval(parser), "typedef")) {
+            if (!parser_next(parser)) {
+                parseerror(parser, "expected type definition after 'typedef'");
+                return false;
+            }
+            return parse_typedef(parser);
+        }
+        parseerror(parser, "unrecognized keyword `%s`", parser_tokval(parser));
+        return false;
+    }
+    else if (parser->tok == '#')
+    {
+        return parse_pragma(parser);
+    }
+    else if (parser->tok == '$')
+    {
+        if (!parser_next(parser)) {
+            parseerror(parser, "parse error");
+            return false;
+        }
+    }
+    else
+    {
+        parseerror(parser, "unexpected token: `%s`", parser->lex->tok.value);
+        return false;
+    }
+    return true;
+}
+
+static uint16_t progdefs_crc_sum(uint16_t old, const char *str)
+{
+    return util_crc16(old, str, strlen(str));
+}
+
+static void progdefs_crc_file(const char *str)
+{
+    /* write to progdefs.h here */
+    (void)str;
+}
+
+static uint16_t progdefs_crc_both(uint16_t old, const char *str)
+{
+    old = progdefs_crc_sum(old, str);
+    progdefs_crc_file(str);
+    return old;
+}
+
+static void generate_checksum(parser_t *parser, ir_builder *ir)
+{
+    uint16_t   crc = 0xFFFF;
+    size_t     i;
+    ast_value *value;
+
+    crc = progdefs_crc_both(crc, "\n/* file generated by qcc, do not modify */\n\ntypedef struct\n{");
+    crc = progdefs_crc_sum(crc, "\tint\tpad[28];\n");
+    /*
+    progdefs_crc_file("\tint\tpad;\n");
+    progdefs_crc_file("\tint\tofs_return[3];\n");
+    progdefs_crc_file("\tint\tofs_parm0[3];\n");
+    progdefs_crc_file("\tint\tofs_parm1[3];\n");
+    progdefs_crc_file("\tint\tofs_parm2[3];\n");
+    progdefs_crc_file("\tint\tofs_parm3[3];\n");
+    progdefs_crc_file("\tint\tofs_parm4[3];\n");
+    progdefs_crc_file("\tint\tofs_parm5[3];\n");
+    progdefs_crc_file("\tint\tofs_parm6[3];\n");
+    progdefs_crc_file("\tint\tofs_parm7[3];\n");
+    */
+    for (i = 0; i < parser->crc_globals; ++i) {
+        if (!ast_istype(parser->globals[i], ast_value))
+            continue;
+        value = (ast_value*)(parser->globals[i]);
+        switch (value->m_vtype) {
+            case TYPE_FLOAT:    crc = progdefs_crc_both(crc, "\tfloat\t"); break;
+            case TYPE_VECTOR:   crc = progdefs_crc_both(crc, "\tvec3_t\t"); break;
+            case TYPE_STRING:   crc = progdefs_crc_both(crc, "\tstring_t\t"); break;
+            case TYPE_FUNCTION: crc = progdefs_crc_both(crc, "\tfunc_t\t"); break;
+            default:
+                crc = progdefs_crc_both(crc, "\tint\t");
+                break;
+        }
+        crc = progdefs_crc_both(crc, value->m_name.c_str());
+        crc = progdefs_crc_both(crc, ";\n");
+    }
+    crc = progdefs_crc_both(crc, "} globalvars_t;\n\ntypedef struct\n{\n");
+    for (i = 0; i < parser->crc_fields; ++i) {
+        if (!ast_istype(parser->fields[i], ast_value))
+            continue;
+        value = (ast_value*)(parser->fields[i]);
+        switch (value->m_next->m_vtype) {
+            case TYPE_FLOAT:    crc = progdefs_crc_both(crc, "\tfloat\t"); break;
+            case TYPE_VECTOR:   crc = progdefs_crc_both(crc, "\tvec3_t\t"); break;
+            case TYPE_STRING:   crc = progdefs_crc_both(crc, "\tstring_t\t"); break;
+            case TYPE_FUNCTION: crc = progdefs_crc_both(crc, "\tfunc_t\t"); break;
+            default:
+                crc = progdefs_crc_both(crc, "\tint\t");
+                break;
+        }
+        crc = progdefs_crc_both(crc, value->m_name.c_str());
+        crc = progdefs_crc_both(crc, ";\n");
+    }
+    crc = progdefs_crc_both(crc, "} entvars_t;\n\n");
+    ir->m_code->crc = crc;
+}
+
+parser_t *parser_create()
+{
+    parser_t *parser;
+    lex_ctx_t empty_ctx;
+    size_t i;
+
+    parser = (parser_t*)mem_a(sizeof(parser_t));
+    if (!parser)
+        return nullptr;
+
+    memset(parser, 0, sizeof(*parser));
+
+    // TODO: remove
+    new (parser) parser_t();
+
+    for (i = 0; i < operator_count; ++i) {
+        if (operators[i].id == opid1('=')) {
+            parser->assign_op = operators+i;
+            break;
+        }
+    }
+    if (!parser->assign_op) {
+        con_err("internal error: initializing parser: failed to find assign operator\n");
+        mem_d(parser);
+        return nullptr;
+    }
+
+    vec_push(parser->variables, parser->htfields  = util_htnew(PARSER_HT_SIZE));
+    vec_push(parser->variables, parser->htglobals = util_htnew(PARSER_HT_SIZE));
+    vec_push(parser->typedefs, util_htnew(TYPEDEF_HT_SIZE));
+    vec_push(parser->_blocktypedefs, 0);
+
+    parser->aliases = util_htnew(PARSER_HT_SIZE);
+
+    empty_ctx.file   = "<internal>";
+    empty_ctx.line   = 0;
+    empty_ctx.column = 0;
+    parser->nil = new ast_value(empty_ctx, "nil", TYPE_NIL);
+    parser->nil->m_cvq = CV_CONST;
+    if (OPTS_FLAG(UNTYPED_NIL))
+        util_htset(parser->htglobals, "nil", (void*)parser->nil);
+
+    parser->max_param_count = 1;
+
+    parser->const_vec[0] = new ast_value(empty_ctx, "<vector.x>", TYPE_NOEXPR);
+    parser->const_vec[1] = new ast_value(empty_ctx, "<vector.y>", TYPE_NOEXPR);
+    parser->const_vec[2] = new ast_value(empty_ctx, "<vector.z>", TYPE_NOEXPR);
+
+    if (OPTS_OPTION_BOOL(OPTION_ADD_INFO)) {
+        parser->reserved_version = new ast_value(empty_ctx, "reserved:version", TYPE_STRING);
+        parser->reserved_version->m_cvq = CV_CONST;
+        parser->reserved_version->m_hasvalue = true;
+        parser->reserved_version->m_flags |= AST_FLAG_INCLUDE_DEF;
+        parser->reserved_version->m_constval.vstring = util_strdup(GMQCC_FULL_VERSION_STRING);
+    } else {
+        parser->reserved_version = nullptr;
+    }
+
+    parser->m_fold = fold(parser);
+    parser->m_intrin = intrin(parser);
+    return parser;
+}
+
+static bool parser_compile(parser_t *parser)
+{
+    /* initial lexer/parser state */
+    parser->lex->flags.noops = true;
+
+    if (parser_next(parser))
+    {
+        while (parser->tok != TOKEN_EOF && parser->tok < TOKEN_ERROR)
+        {
+            if (!parser_global_statement(parser)) {
+                if (parser->tok == TOKEN_EOF)
+                    parseerror(parser, "unexpected end of file");
+                else if (compile_errors)
+                    parseerror(parser, "there have been errors, bailing out");
+                lex_close(parser->lex);
+                parser->lex = nullptr;
+                return false;
+            }
+        }
+    } else {
+        parseerror(parser, "parse error");
+        lex_close(parser->lex);
+        parser->lex = nullptr;
+        return false;
+    }
+
+    lex_close(parser->lex);
+    parser->lex = nullptr;
+
+    return !compile_errors;
+}
+
+bool parser_compile_file(parser_t *parser, const char *filename)
+{
+    parser->lex = lex_open(filename);
+    if (!parser->lex) {
+        con_err("failed to open file \"%s\"\n", filename);
+        return false;
+    }
+    return parser_compile(parser);
+}
+
+bool parser_compile_string(parser_t *parser, const char *name, const char *str, size_t len)
+{
+    parser->lex = lex_open_string(str, len, name);
+    if (!parser->lex) {
+        con_err("failed to create lexer for string \"%s\"\n", name);
+        return false;
+    }
+    return parser_compile(parser);
+}
+
+static void parser_remove_ast(parser_t *parser)
+{
+    size_t i;
+    if (parser->ast_cleaned)
+        return;
+    parser->ast_cleaned = true;
+    for (auto &it : parser->accessors) {
+        delete it->m_constval.vfunc;
+        it->m_constval.vfunc = nullptr;
+        delete it;
+    }
+    for (auto &it : parser->functions) delete it;
+    for (auto &it : parser->globals) delete it;
+    for (auto &it : parser->fields) delete it;
+
+    for (i = 0; i < vec_size(parser->variables); ++i)
+        util_htdel(parser->variables[i]);
+    vec_free(parser->variables);
+    vec_free(parser->_blocklocals);
+    vec_free(parser->_locals);
+
+    for (i = 0; i < vec_size(parser->_typedefs); ++i)
+        delete parser->_typedefs[i];
+    vec_free(parser->_typedefs);
+    for (i = 0; i < vec_size(parser->typedefs); ++i)
+        util_htdel(parser->typedefs[i]);
+    vec_free(parser->typedefs);
+    vec_free(parser->_blocktypedefs);
+
+    vec_free(parser->_block_ctx);
+
+    delete parser->nil;
+
+    delete parser->const_vec[0];
+    delete parser->const_vec[1];
+    delete parser->const_vec[2];
+
+    if (parser->reserved_version)
+        delete parser->reserved_version;
+
+    util_htdel(parser->aliases);
+}
+
+void parser_cleanup(parser_t *parser)
+{
+    parser_remove_ast(parser);
+    parser->~parser_t();
+    mem_d(parser);
+}
+
+static bool parser_set_coverage_func(parser_t *parser, ir_builder *ir) {
+    ast_expression *expr;
+    ast_value      *cov;
+    ast_function   *func;
+
+    if (!OPTS_OPTION_BOOL(OPTION_COVERAGE))
+        return true;
+
+    func = nullptr;
+    for (auto &it : parser->functions) {
+        if (it->m_name == "coverage") {
+            func = it;
+            break;
+        }
+    }
+    if (!func) {
+        if (OPTS_OPTION_BOOL(OPTION_COVERAGE)) {
+            con_out("coverage support requested but no coverage() builtin declared\n");
+            delete ir;
+            return false;
+        }
+        return true;
+    }
+
+    cov  = func->m_function_type;
+    expr = cov;
+
+    if (expr->m_vtype != TYPE_FUNCTION || expr->m_type_params.size()) {
+        char ty[1024];
+        ast_type_to_string(expr, ty, sizeof(ty));
+        con_out("invalid type for coverage(): %s\n", ty);
+        delete ir;
+        return false;
+    }
+
+    ir->m_coverage_func = func->m_ir_func->m_value;
+    return true;
+}
+
+bool parser_finish(parser_t *parser, const char *output)
+{
+    ir_builder *ir;
+    bool retval = true;
+
+    if (compile_errors) {
+        con_out("*** there were compile errors\n");
+        return false;
+    }
+
+    ir = new ir_builder("gmqcc_out");
+    if (!ir) {
+        con_out("failed to allocate builder\n");
+        return false;
+    }
+
+    for (auto &it : parser->fields) {
+        bool hasvalue;
+        if (!ast_istype(it, ast_value))
+            continue;
+        ast_value *field = (ast_value*)it;
+        hasvalue = field->m_hasvalue;
+        field->m_hasvalue = false;
+        if (!reinterpret_cast<ast_value*>(field)->generateGlobal(ir, true)) {
+            con_out("failed to generate field %s\n", field->m_name.c_str());
+            delete ir;
+            return false;
+        }
+        if (hasvalue) {
+            ir_value *ifld;
+            ast_expression *subtype;
+            field->m_hasvalue = true;
+            subtype = field->m_next;
+            ifld = ir->createField(field->m_name, subtype->m_vtype);
+            if (subtype->m_vtype == TYPE_FIELD)
+                ifld->m_fieldtype = subtype->m_next->m_vtype;
+            else if (subtype->m_vtype == TYPE_FUNCTION)
+                ifld->m_outtype = subtype->m_next->m_vtype;
+            (void)!field->m_ir_v->setField(ifld);
+        }
+    }
+    for (auto &it : parser->globals) {
+        ast_value *asvalue;
+        if (!ast_istype(it, ast_value))
+            continue;
+        asvalue = (ast_value*)it;
+        if (!asvalue->m_uses && !asvalue->m_hasvalue && asvalue->m_vtype != TYPE_FUNCTION) {
+            retval = retval && !compile_warning(asvalue->m_context, WARN_UNUSED_VARIABLE,
+                                                "unused global: `%s`", asvalue->m_name);
+        }
+        if (!asvalue->generateGlobal(ir, false)) {
+            con_out("failed to generate global %s\n", asvalue->m_name.c_str());
+            delete ir;
+            return false;
+        }
+    }
+    /* Build function vararg accessor ast tree now before generating
+     * immediates, because the accessors may add new immediates
+     */
+    for (auto &f : parser->functions) {
+        if (f->m_varargs) {
+            if (parser->max_param_count > f->m_function_type->m_type_params.size()) {
+                f->m_varargs->m_count = parser->max_param_count - f->m_function_type->m_type_params.size();
+                if (!parser_create_array_setter_impl(parser, f->m_varargs.get())) {
+                    con_out("failed to generate vararg setter for %s\n", f->m_name.c_str());
+                    delete ir;
+                    return false;
+                }
+                if (!parser_create_array_getter_impl(parser, f->m_varargs.get())) {
+                    con_out("failed to generate vararg getter for %s\n", f->m_name.c_str());
+                    delete ir;
+                    return false;
+                }
+            } else {
+                f->m_varargs = nullptr;
+            }
+        }
+    }
+    /* Now we can generate immediates */
+    if (!parser->m_fold.generate(ir))
+        return false;
+
+    /* before generating any functions we need to set the coverage_func */
+    if (!parser_set_coverage_func(parser, ir))
+        return false;
+    for (auto &it : parser->globals) {
+        if (!ast_istype(it, ast_value))
+            continue;
+        ast_value *asvalue = (ast_value*)it;
+        if (!(asvalue->m_flags & AST_FLAG_INITIALIZED))
+        {
+            if (asvalue->m_cvq == CV_CONST && !asvalue->m_hasvalue)
+                (void)!compile_warning(asvalue->m_context, WARN_UNINITIALIZED_CONSTANT,
+                                       "uninitialized constant: `%s`",
+                                       asvalue->m_name);
+            else if ((asvalue->m_cvq == CV_NONE || asvalue->m_cvq == CV_CONST) && !asvalue->m_hasvalue)
+                (void)!compile_warning(asvalue->m_context, WARN_UNINITIALIZED_GLOBAL,
+                                       "uninitialized global: `%s`",
+                                       asvalue->m_name);
+        }
+        if (!asvalue->generateAccessors(ir)) {
+            delete ir;
+            return false;
+        }
+    }
+    for (auto &it : parser->fields) {
+        ast_value *asvalue = (ast_value*)it->m_next;
+        if (!ast_istype(asvalue, ast_value))
+            continue;
+        if (asvalue->m_vtype != TYPE_ARRAY)
+            continue;
+        if (!asvalue->generateAccessors(ir)) {
+            delete ir;
+            return false;
+        }
+    }
+    if (parser->reserved_version &&
+        !parser->reserved_version->generateGlobal(ir, false))
+    {
+        con_out("failed to generate reserved::version");
+        delete ir;
+        return false;
+    }
+    for (auto &f : parser->functions) {
+        if (!f->generateFunction(ir)) {
+            con_out("failed to generate function %s\n", f->m_name.c_str());
+            delete ir;
+            return false;
+        }
+    }
+
+    generate_checksum(parser, ir);
+
+    if (OPTS_OPTION_BOOL(OPTION_DUMP))
+        ir->dump(con_out);
+    for (auto &it : parser->functions) {
+        if (!ir_function_finalize(it->m_ir_func)) {
+            con_out("failed to finalize function %s\n", it->m_name.c_str());
+            delete ir;
+            return false;
+        }
+    }
+    parser_remove_ast(parser);
+
+    if (compile_Werrors) {
+        con_out("*** there were warnings treated as errors\n");
+        compile_show_werrors();
+        retval = false;
+    }
+
+    if (retval) {
+        if (OPTS_OPTION_BOOL(OPTION_DUMPFIN))
+            ir->dump(con_out);
+
+        if (!ir->generate(output)) {
+            con_out("*** failed to generate output file\n");
+            delete ir;
+            return false;
+        }
+    }
+    delete ir;
+    return retval;
+}
index c6fe91d86ac0c8f26159740485407ffefffb8afe..a09238df68d768dbd1d0f2fbee2bc5165c0d5686 100644 (file)
--- a/parser.h
+++ b/parser.h
@@ -1,73 +1,31 @@
-/*
- * Copyright (C) 2012, 2013, 2014, 2015
- *     Wolfgang Bumiller
- *     Dale Weiler
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
- * this software and associated documentation files (the "Software"), to deal in
- * the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is furnished to do
- * so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
 #ifndef GMQCC_PARSER_HDR
 #define GMQCC_PARSER_HDR
 #include "gmqcc.h"
 #include "lexer.h"
-#include "ast.h"
-
-typedef struct intrin_s intrin_t;
-typedef struct parser_s parser_t;
-
-typedef struct {
-    struct parser_s *parser;
-    ast_value      **imm_float;              /* vector<ast_value*> */
-    ast_value      **imm_vector;             /* vector<ast_value*> */
-    ast_value      **imm_string;             /* vector<ast_value*> */
-    hash_table_t    *imm_string_untranslate; /* map<string, ast_value*> */
-    hash_table_t    *imm_string_dotranslate; /* map<string, ast_value*> */
-} fold_t;
-
-typedef struct {
-    ast_expression *(*intrin)(intrin_t *);
-    const char       *name;
-    const char       *alias;
-    size_t            args;
-} intrin_func_t;
-
-struct intrin_s {
-    intrin_func_t  *intrinsics;              /* vector<intrin_func_t>   */
-    ast_expression **generated;              /* vector<ast_expression*> */
-    parser_t       *parser;
-    fold_t         *fold;
-};
+//#include "ast.h"
+
+#include "intrin.h"
+#include "fold.h"
+
+struct parser_t;
 
 #define parser_ctx(p) ((p)->lex->tok.ctx)
 
-struct parser_s {
+struct parser_t {
+    parser_t() { }
+
     lex_file *lex;
-    int      tok;
+    int tok;
 
-    bool     ast_cleaned;
+    bool ast_cleaned;
 
-    ast_expression **globals;
-    ast_expression **fields;
-    ast_function **functions;
-    size_t         translated;
+    std::vector<ast_expression *> globals;
+    std::vector<ast_expression *> fields;
+    std::vector<ast_function *> functions;
+    size_t translated;
 
     /* must be deleted first, they reference immediates and values */
-    ast_value    **accessors;
+    std::vector<ast_value *> accessors;
 
     ast_value *nil;
     ast_value *reserved_version;
@@ -76,15 +34,15 @@ struct parser_s {
     size_t crc_fields;
 
     ast_function *function;
-    ht            aliases;
+    ht aliases;
 
     /* All the labels the function defined...
      * Should they be in ast_function instead?
      */
-    ast_label  **labels;
-    ast_goto   **gotos;
-    const char **breaks;
-    const char **continues;
+    std::vector<ast_label*> labels;
+    std::vector<ast_goto*> gotos;
+    std::vector<const char *> breaks;
+    std::vector<const char *> continues;
 
     /* A list of hashtables for each scope */
     ht *variables;
@@ -92,16 +50,12 @@ struct parser_s {
     ht htglobals;
     ht *typedefs;
 
-    /* same as above but for the spelling corrector */
-    correct_trie_t  **correct_variables;
-    size_t         ***correct_variables_score;  /* vector of vector of size_t* */
-
     /* not to be used directly, we use the hash table */
     ast_expression **_locals;
-    size_t          *_blocklocals;
-    ast_value      **_typedefs;
-    size_t          *_blocktypedefs;
-    lex_ctx_t         *_block_ctx;
+    size_t *_blocklocals;
+    ast_value **_typedefs;
+    size_t *_blocktypedefs;
+    lex_ctx_t *_block_ctx;
 
     /* we store the '=' operator info */
     const oper_info *assign_op;
@@ -113,10 +67,10 @@ struct parser_s {
     bool noref;
 
     /* collected information */
-    size_t     max_param_count;
+    size_t max_param_count;
 
-    fold_t   *fold;
-    intrin_t *intrin;
+    fold m_fold;
+    intrin m_intrin;
 };
 
 
@@ -124,25 +78,4 @@ struct parser_s {
 char           *parser_strdup     (const char *str);
 ast_expression *parser_find_global(parser_t *parser, const char *name);
 
-/* fold.c */
-fold_t         *fold_init           (parser_t *);
-void            fold_cleanup        (fold_t *);
-ast_expression *fold_constgen_float (fold_t *, qcfloat_t, bool);
-ast_expression *fold_constgen_vector(fold_t *, vec3_t);
-ast_expression *fold_constgen_string(fold_t *, const char *, bool);
-bool            fold_generate       (fold_t *, ir_builder *);
-ast_expression *fold_op             (fold_t *, const oper_info *, ast_expression **);
-ast_expression *fold_intrin         (fold_t *, const char      *, ast_expression **);
-
-ast_expression *fold_binary         (lex_ctx_t ctx, int, ast_expression *, ast_expression *);
-int             fold_cond_ifthen    (ir_value *, ast_function *, ast_ifthen  *);
-int             fold_cond_ternary   (ir_value *, ast_function *, ast_ternary *);
-
-/* intrin.c */
-intrin_t       *intrin_init            (parser_t *parser);
-void            intrin_cleanup         (intrin_t *intrin);
-ast_expression *intrin_fold            (intrin_t *intrin, ast_value *, ast_expression **);
-ast_expression *intrin_func            (intrin_t *intrin, const char *name);
-ast_expression *intrin_debug_typestring(intrin_t *intrin);
-
 #endif
diff --git a/platform.h b/platform.h
deleted file mode 100644 (file)
index 57ba352..0000000
+++ /dev/null
@@ -1,554 +0,0 @@
-/*
- * Copyright (C) 2012, 2013, 2014, 2015
- *     Dale Weiler
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
- * this software and associated documentation files (the "Software"), to deal in
- * the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is furnished to do
- * so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-#ifndef GMQCC_PLATFORM_HDR
-#define GMQCC_PLATFORM_HDR
-
-#ifndef GMQCC_PLATFORM_HEADER
-#   error "This header shouldn't be included!"
-#endif
-
-#undef GMQCC_PLATFORM_HEADER
-#include <stdarg.h>
-#include <time.h>
-#include <stdio.h>
-
-#ifdef _WIN32
-#   ifndef STDERR_FILENO
-#       define STDERR_FILENO 2
-#   endif
-#   ifndef STDOUT_FILENO
-#       define STDOUT_FILENO 1
-#   endif
-#   ifndef __MINGW32__
-#       define _WIN32_LEAN_AND_MEAN
-#       include <windows.h>
-#       include <io.h>
-#       include <fcntl.h>
-
-        struct dirent {
-            long               d_ino;
-            unsigned short     d_reclen;
-            unsigned short     d_namlen;
-            char               d_name[FILENAME_MAX];
-        };
-
-        typedef struct {
-            struct _finddata_t dd_dta;
-            struct dirent      dd_dir;
-            long               dd_handle;
-            int                dd_stat;
-            char               dd_name[1];
-        } DIR;
-#   else
-#       include <dirent.h>
-#   endif /*!__MINGW32__*/
-
-#   ifndef S_ISDIR
-#       define S_ISDIR(X) ((X)&_S_IFDIR)
-#   endif
-#else
-#   include <sys/types.h>
-#   include <sys/stat.h>
-#   include <unistd.h>
-#   include <dirent.h>
-#endif /*!_WIN32*/
-
-/*
- * Function: platform_vsnprintf
- *  Write formatted output using a pointer to a lis of arguments.
- *
- * Parameters:
- *  buffer - Storage location for output.
- *  bytes  - Maximum number of characters to write.
- *  format - Format specification.
- *  arg    - Variable argument list.
- *
- * Returns:
- *  The number of characters written if the number of characters to write
- *  is less than or equal to `bytes`; if the number of characters to write
- *  is greater than `bytes`, this function returns -1 indicating that the
- *  output has been truncated. The return value does not include the
- *  terminating null, if one is written.
- *
- * Remarks:
- *  Function takes pointer to an argument list, then formats the data,
- *  and writes up to `bytes` characters to the memory pointed to by
- *  `buffer`. If there is room at the end (that is, if the number of
- *  character to write is less than `bytes`), the buffer will be null-terminated.
- */
-int platform_vsnprintf(char *buffer, size_t bytes, const char *format, va_list arg);
-
-/*
- * Function: platform_vsscanf
- *  Reads formatted data from a string.
- *
- * Parameters:
- *  buffer - Stored data to read.
- *  format - Format specification.
- *  arg    - Variable argument list.
- *
- * Returns:
- *  The number of fields that are successfully converted and assigned;
- *  the return value does not include fields that were read but not
- *  assigned. A return vlaue of 0 indicated that no fields were assigned.
- *  The return value if EOF for error or if the end of the string is
- *  reached before the first conversion.
- *
- * Remarks:
- *  Reads data from `buffer` into the locations that are given by each
- *  argument in the `arg` argument list. Every argument in the list must
- *  be a pointer to a variable that has a type that corresponds to a
- *  type specifier in `format`. The `format` argument controls th
- *  interpretation of the input fields and has the same form and function
- *  as the `format` argument for the *scanf* function. If copying takes
- *  place between strings that overlap, the behaviour is undefined.
- */
-int platform_vsscanf(const char *buffer, const char *format, va_list arg);
-
-/*
- * Function: platform_localtime
- *  Convert a time value and correct for the local time zone.
- *
- * Parameters
- *  timer - Pointer to stored time.
- *
- * Returns:
- *  A pointer to a structure result, or NULL if the date passed to
- *  the function is before midnight, January 1, 1970.
- */
-const struct tm *platform_localtime(const time_t *timer);
-
-/*
- * Function: platform_ctime
- *  Convert a time value to a string and adjust for local time zone
- *  settings.
- *
- * Parameters:
- *  timer - Pointer to stored time.
- *
- * Returns:
- *  Pointer to the character string result. NULL will be returned if time
- *  represents a date before midnight, January 1, 1970, UTC.
- *
- * Remarks:
- *  Converts a time value stored as a `time_t` value into a chracter string.
- *  The `timer` value is usually obtained from a call to *time*, which returns
- *  the number of seconds since midnight, January 1, 1970 UTC. The return
- *  value of the string contains exactly 26 characters. A 24-hour clock is used.
- *  All fields have constant width. The newline chracter and the null character
- *  occupy the last two positions of the string. The converted character string
- *  is also adjusted according to the local time zone settings.
- */
-const char *platform_ctime(const time_t *timer);
-
-/*
- * Function: platform_strncat
- *  Append characters of a string.
- *
- * Parameters:
- *  dest - Null terminated destination string
- *  src  - Source string
- *  num  - Number of characters to append
- *
- * Returns:
- *  Pointer to the destination string. No return value is used to indicate
- *  an error.
- *
- * Remarks:
- *  Function appends, at mode, the first `num` characters of `src` to
- *  `dest`. The initial character of `src` overwrites the terminating
- *  null chracter of `dest`. If a null character appears in `src` before
- *  `num` chracters are appended, `platform_strncat` appends all chracters
- *  from `src`, up to the null chracter. If `num` is greater than the
- *  length of `src`, the length of `src` is used in place of `num`.
- */
-char *platform_strncat(char *dest, const char *src, size_t num);
-
-/*
- * Function: platform_getenv
- *  Get a value from the current enviroment.
- *
- * Parameters:
- *  var - Enviroment variable name
- *
- * Returns:
- *  A pointer to the enviroment table entry containing `var. It's not
- *  safe to modify the value of the enviroment variable using the returned
- *  pointer. The return value is *NULL* if `var` is not found in the
- *  enviroment table.
- */
-const char *platform_getenv(const char *var);
-
-/*
- * Function: platform_vasprintf
- *  Print to allocated string
- *
- * Parameters:
- *  dat  - Pointer to pointer to store allocated data.
- *  fmt  - Format specification.
- *  args - Variable argument list.
- *
- * Returns:
- *  Number of character written, -1 is used to indicate an error.
- *
- * Remarks:
- *  Allocate a string large enough to hold the output including
- *  the terminating null character than write formatted output
- *  to it using format specification.
- */
-int platform_vasprintf(char **dat, const char *fmt, va_list args);
-
-/*
- * Function: platform_vfprintf
- *  Write formatted output using a pointer to a list of arguments.
- *
- * Parameters:
- *  stream - Pointer to stream.
- *  format - Format specification.
- *  atrg   - Variable argument list.
- *
- * Returns:
- *  Number of characters written, not including the terminating null
- *  character, or a negitive value if an output error occurs. -1 is
- *  also used to indicate an error.
- *
- * Remarks:
- *  Takes a pointer to an argument list, then formats and writes the
- *  given data to `stream`.
- */
-int platform_vfprintf(FILE *stream, const char *format, va_list arg);
-
-/*
- * Function: platform_strcat
- *  Append characters of a string.
- *
- * Parameters:
- *  dest - Null terminated destination string
- *  src  - Source string
- *
- * Returns:
- *  Pointer to the destination string. No return value is used to indicate
- *  an error.
- *
- * Remarks:
- *  Appens `src` to `dest` and terminates with resulting null character.
- *  The initial character of `src` overwrites the terminating null
- *  character of `dest`. The behaviour of platform_strcat is undefined
- *  if the source and destination string overlap.
- */
-char *platform_strcat(char *dest, const char *src);
-
-/*
- * Function: platform_strncpy
- *  Copys characters of one string to another.
- *
- * Parameters:
- *  dest - Destination string.
- *  src  - Source string.
- *  num  - Number of characters to be copied.
- *
- * Returns:
- *  `dest`. No return value is reserved to indicate an error.
- *
- * Remarks:
- *  Copies the initial characters of `src` to `dest` and returns `dest`.
- *  If `num` is less than or equal to the length of `src1 a null character
- *  is not appended automatically to the copied string. If `num` is greater
- *  than the length of `src`, the destination string is padded with null
- *  characters up to length `num`. The behaviour of this function is undefined
- *  if the source and destination strings overlap.
- */
-char *platform_strncpy(char *dest, const char *src, size_t num);
-
-/*
- * Function: platform_strerror
- *  Get a system error message
- *
- * Parameters:
- *  err - Error number.
- *
- * Returns:
- *  A pointer to the error message
- */
-const char *platform_strerror(int err);
-
-/*
- * Function: platform_fopen
- *  Opens a file
- *
- * Parameters:
- *  filename - File name.
- *  mode     - Kind of access that's enabled.
- *
- * Returns:
- *  A pointer to the open file. A null pointer value indicates an error.
- */
-FILE *platform_fopen(const char *filename, const char *mode);
-
-/*
- * Function: platform_fread
- *  Reads data from a stream
- *
- * Parameters:
- *  ptr    - Storage location for data.
- *  size   - Item size in bytes.
- *  count  - Maximum number of items to be read.
- *  stream - Pointer to stream
- *
- * Returns:
- *  The number of full items actually read, which may be less than `count`
- *  if an error occurs or if the end of the file is encountered before
- *  reaching `count`. If `size` or `count` is 0, `platform_fread`
- *  returns 0 and the buffer contents are unchanged.
- */
-size_t platform_fread(void *ptr, size_t size, size_t count, FILE *stream);
-
-/*
- * Function: platform_fwrite
- *  Writes data to a stream
- *
- * Parameters:
- *  ptr    - Pointer to data to be written.
- *  size   - Item size in bytes.
- *  count  - Maximum number of items to be read.
- *  stream - Pointer to stream
- *
- * Returns:
- *  The number of full items actually written, which may be less than
- *  `count` if an error occurs. Also, if an error occurs, the
- *  file-position indicator cannot be determined.
- *
- * Remarks:
- *  Writes up to `count` items, of `size` length each, from `ptr` to the
- *  output stream. The file pointer associated with stream (if there is one)
- *  is incremented by the number of bytes actually written.
- */
-size_t platform_fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
-
-/*
- * Function: platform_fflush
- *  Flushes a stream
- *
- * Parameters:
- *  stream - Pointer to stream
- *
- * Returns:
- *  0 value if the buffer was succesffuly flushed. The value 0 is also
- *  returned in cases in which the specified stream has no buffer or is
- *  open for reading only. A return value of *EOF* indicates an error.
- *
- * Remarks:
- *  Flushes a stream. If the file associated with stream is open for output,
- *  platform_fflush writes to that file the contents of the buffer
- *  associated with the stream. If the stream is open for input,
- *  platform_fflush clears the contents of the buffer. platform_fflush
- *  negates the effect of any prior call to ungetc against stream. Also,
- *  platform_fflush(NULL) flushes all streams opened for output.
- *  The stream remains open after the call. platform_fflush has no effect
- *  on an unbuffered stream.
- */
-int platform_fflush(FILE *stream);
-
-/*
- * Function: platform_fclose
- *  Closes a stream.
- *
- * Parameters:
- *  stream - Pointer to stream.
- *
- * Returns:
- *  0 value. *EOF* is used to indicate an error.
- *
- * Remarks:
- *  Closes a stream.
- */
-int platform_fclose(FILE *stream);
-
-/*
- * Function: platform_ferror
- *  Tests for an error on a stream.
- *
- * Parameters:
- *  stream - Pointer to stream.
- *
- * Returns:
- *  If not error has occured on `stream`, 0 value is returned, otherwise
- *  it returns a nonzero value.
- *
- * Remarks:
- *  Tests for a reading or writing error on the file associated with `stream`.
- *  If an error has occured, the error indicator for the stream remains set
- *  until the stream is closed or rewound.
- */
-int platform_ferror(FILE *stream);
-
-/*
- * Function: platform_fgetc
- *  Read a character from a stream.
- *
- * Parameters:
- *  stream - Pointer to a stream.
- *
- * Returns:
- *  The chracter read as an int or EOF to indicate an error or end-of-file.
- *
- * Remarks:
- *  Reads a single chracter from the current position of the file associated
- *  with `stream`. Than increments that position. If the steam is at the end
- *  of the file, the end-of-file indicator for the stream is set.
- */
-int platform_fgetc(FILE *stream);
-
-/*
- * Function: platform_fputs
- *  Write a string to a stream
- *
- * Parameters:
- *  str    - Output string.
- *  stream - Pointer to stream.
- *
- * Returns:
- *  Non-negative value if successful. EOF is used to indicate an error.
- *
- * Remarks:
- *  Copies `str` to the output stream at the current position.
- */
-int platform_fputs(const char *str, FILE *stream);
-
-/*
- * Function: platform_fseek
- *  Moves the file pointer to a specified location.
- *
- * Parameters:
- *  stream - Pointer to stream.
- *  offset - Number of bytes from origin to offset.
- *  origin - Initital position.
- *
- * Returns:
- *  0 value, nonzero values are used to indicate an error.
- *
- * Remarks:
- *  Moves the file pointer (if any) associated with stream to a new
- *  location that is offset bytes from origin.
- *  The next operation on the stream takes place at the new location.
- *  On a stream open for update, the next operation can be either a
- *  read or a write.
- */
-int platform_fseek(FILE *stream, long offset, int origin);
-
-/*
- * Function: platform_ftell
- *  Gets the current position of a file pointer
- *
- * Parameters:
- *  stream - Pointer to stream
- *
- * Returns:
- *  Current file position. May not reflect physical byte offset, e.g
- *  systems where read-mode does carriage return-linefeed translation.
- *  -1 value is used to indivate an error.
- */
-long platform_ftell(FILE *stream);
-
-/*
- * Function: platform_mkdir
- *  Make a directory
- *
- * Parameters:
- *  path    - Path to create
- *  mode    - The mode of the directory (implementation defined)
- *
- * Returns:
- *  0 value. -1 value is used to indicate an error. On error no
- *  directory shall be created.
- *
- * Remarks:
- *  Shall create a new empty directory with with the name path specified
- *  by argument `path.
- */
-int platform_mkdir(const char *path, int mode);
-
-/*
- * Function: platform_opendir
- *  Open a directory
- *
- * Parameters:
- *  path - Path to the directory to open
- *
- * Returns:
- *  Pointer to an object of type *DIR*. A null pointer value indicates
- *  an error.
- *
- * Remarks:
- *  Shall open a directory stream corresponding to the directory named by
- *  the `path` argument. The directory stream is positioned at the first entry.
- */
-DIR *platform_opendir(const char *path);
-
-/*
- * Function: platform_closedir
- *  Close a directory stream
- *
- * Parameters:
- *  dir - Pointer to directory stream
- *
- * Returns:
- *  0 value. A -1 value indicated an error.
- *
- * Remarks:
- *  Shall close the directory stream referred to by the argument
- *  `dir`. Upon return, the value of `dir` may no longer point to
- *  an accessible object of the type *DIR*.
- */
-int platform_closedir(DIR *dir);
-
-/*
- * Function: platform_readdir
- *  Read directory
- *
- * Parameters:
- *  dir - Pointer to directory stream
- *
- * Returns:
- *  Pointer to an object of type `struct dirent`. A null pointer value
- *  indicates an error.
- *
- * Remarks:
- *  When the end of the directory is encountered, a null pointer is
- *  returned.
- */
-struct dirent *platform_readdir(DIR *dir);
-
-/*
- * Function: platform_isatty
- *  Determines whether a file descriptor is associated with a character
- *  device.
- *
- * Returns:
- *  A nonzero value if the descriptor is associated with a character
- *  device. Otherwise `platform_isatty` returns 0.
- */
-int platform_isatty(int fd);
-
-#endif
diff --git a/stat.c b/stat.c
deleted file mode 100644 (file)
index 544ae96..0000000
--- a/stat.c
+++ /dev/null
@@ -1,769 +0,0 @@
-/*
- * Copyright (C) 2012, 2013, 2014, 2015
- *     Dale Weiler
- *     Wolfgang Bumiller
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
- * this software and associated documentation files (the "Software"), to deal in
- * the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is furnished to do
- * so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-#include <string.h>
-#include <stdlib.h>
-
-#include "gmqcc.h"
-
-typedef struct stat_mem_block_s stat_mem_block_t;
-
-#define IDENT_SIZE 4
-#define IDENT_VEC  "vec"
-#define IDENT_MEM  "mem"
-#define IDENT_VEC_TOP (sizeof(vector_t) + IDENT_SIZE)
-#define IDENT_MEM_TOP (sizeof(stat_mem_block_t) + IDENT_SIZE)
-
-/*
- * For the valgrind integration of our allocator. This allows us to have
- * more `accurate` valgrind output for our allocator, and also secures the
- * possible underflows (where one could obtain access to the redzone that
- * represents info about that allocation).
- */
-#ifndef NVALGRIND
-#   include <valgrind/valgrind.h>
-#   include <valgrind/memcheck.h>
-#else
-#   define VALGRIND_MALLOCLIKE_BLOCK(PTR, ALLOC_SIZE, REDZONE_SIZE, ZEROED)
-#   define VALGRIND_FREELIKE_BLOCK(PTR, REDZONE_SIZE)
-#   define VALGRIND_MAKE_MEM_DEFINED(PTR, REDZONE_SIZE)
-#   define VALGRIND_MAKE_MEM_NOACCESS(PTR, REDZONE_SIZE)
-#endif
-
-/*
- * GMQCC performs tons of allocations, constructions, and crazyness
- * all around. When trying to optimizes systems, or just get fancy
- * statistics out of the compiler, it's often printf mess. This file
- * implements the statistics system of the compiler. I.E the allocator
- * we use to track allocations, and other systems of interest.
- */
-#define ST_SIZE 1024
-
-struct stat_mem_block_s {
-    const char              *file;
-    size_t                   line;
-    size_t                   size;
-    const char              *expr;
-    struct stat_mem_block_s *next;
-    struct stat_mem_block_s *prev;
-};
-
-typedef struct {
-    size_t key;
-    size_t value;
-} stat_size_entry_t, **stat_size_table_t;
-
-static uint64_t          stat_mem_allocated         = 0;
-static uint64_t          stat_mem_deallocated       = 0;
-static uint64_t          stat_mem_allocated_total   = 0;
-static uint64_t          stat_mem_deallocated_total = 0;
-static uint64_t          stat_mem_high              = 0;
-static uint64_t          stat_mem_peak              = 0;
-static uint64_t          stat_mem_strdups           = 0;
-static uint64_t          stat_used_strdups          = 0;
-static uint64_t          stat_used_vectors          = 0;
-static uint64_t          stat_used_hashtables       = 0;
-static uint64_t          stat_type_vectors          = 0;
-static uint64_t          stat_type_hashtables       = 0;
-static stat_size_table_t stat_size_vectors          = NULL;
-static stat_size_table_t stat_size_hashtables       = NULL;
-static stat_mem_block_t *stat_mem_block_root        = NULL;
-
-/*
- * A tiny size_t key-value hashtbale for tracking vector and hashtable
- * sizes. We can use it for other things too, if we need to. This is
- * very TIGHT, and efficent in terms of space though.
- */
-static stat_size_table_t stat_size_new(void) {
-    return (stat_size_table_t)memset(
-        mem_a(sizeof(stat_size_entry_t*) * ST_SIZE),
-        0, ST_SIZE * sizeof(stat_size_entry_t*)
-    );
-}
-
-static void stat_size_del(stat_size_table_t table) {
-    size_t i = 0;
-    for (; i < ST_SIZE; i++) if(table[i]) mem_d(table[i]);
-    mem_d(table);
-}
-
-static stat_size_entry_t *stat_size_get(stat_size_table_t table, size_t key) {
-    size_t hash = (key % ST_SIZE);
-    while (table[hash] && table[hash]->key != key)
-        hash = (hash + 1) % ST_SIZE;
-    return table[hash];
-}
-static void stat_size_put(stat_size_table_t table, size_t key, size_t value) {
-    size_t hash = (key % ST_SIZE);
-    while (table[hash] && table[hash]->key != key)
-        hash = (hash + 1) % ST_SIZE;
-    table[hash]        = (stat_size_entry_t*)mem_a(sizeof(stat_size_entry_t));
-    table[hash]->key   = key;
-    table[hash]->value = value;
-}
-
-/*
- * A basic header of information wrapper allocator. Simply stores
- * information as a header, returns the memory + 1 past it, can be
- * retrieved again with - 1. Where type is stat_mem_block_t*.
- */
-void *stat_mem_allocate(size_t size, size_t line, const char *file, const char *expr) {
-    stat_mem_block_t *info = (stat_mem_block_t*)malloc(size + IDENT_MEM_TOP);
-    void             *data = (void *)((char*)info + IDENT_MEM_TOP);
-
-    if(GMQCC_UNLIKELY(!info))
-        return NULL;
-
-    info->line = line;
-    info->size = size;
-    info->file = file;
-    info->expr = expr;
-    info->prev = NULL;
-    info->next = stat_mem_block_root;
-
-    /* Write identifier */
-    memcpy(info + 1, IDENT_MEM, IDENT_SIZE);
-
-    /* likely since it only happens once */
-    if (GMQCC_LIKELY(stat_mem_block_root != NULL)) {
-        VALGRIND_MAKE_MEM_DEFINED(stat_mem_block_root, IDENT_MEM_TOP);
-        stat_mem_block_root->prev = info;
-        VALGRIND_MAKE_MEM_NOACCESS(stat_mem_block_root, IDENT_MEM_TOP);
-    }
-
-    stat_mem_block_root       = info;
-    stat_mem_allocated       += size;
-    stat_mem_high            += size;
-    stat_mem_allocated_total ++;
-
-    if (stat_mem_high > stat_mem_peak)
-        stat_mem_peak = stat_mem_high;
-
-    VALGRIND_MALLOCLIKE_BLOCK(data, size, IDENT_MEM_TOP, 0);
-    return data;
-}
-
-void stat_mem_deallocate(void *ptr, size_t line, const char *file) {
-    stat_mem_block_t *info  = NULL;
-    char             *ident = (char *)ptr - IDENT_SIZE;
-
-    if (GMQCC_UNLIKELY(!ptr))
-        return;
-
-    /* Validate usage */
-    VALGRIND_MAKE_MEM_DEFINED(ident, IDENT_SIZE);
-    if (!strcmp(ident, IDENT_VEC)) {
-        vector_t         *vec   = (vector_t*)((char *)ptr - IDENT_VEC_TOP);
-        stat_mem_block_t *block = (stat_mem_block_t*)((char *)vec - IDENT_MEM_TOP);
-
-        VALGRIND_MAKE_MEM_DEFINED(block, sizeof(stat_mem_block_t));
-        con_err("internal warning: invalid use of mem_d:\n");
-        con_err("internal warning:    vector (used elements: %u, allocated elements: %u)\n",
-            (unsigned)vec->used,
-            (unsigned)vec->allocated
-        );
-        con_err("internal warning:    vector was last (re)allocated with (size: %u (bytes), at location: %s:%u)\n",
-            (unsigned)block->size,
-            block->file,
-            (unsigned)block->line
-        );
-        con_err("internal warning:    released with wrong routine at %s:%u\n", file, (unsigned)line);
-        con_err("internal warning:    forwarding to vec_free, please fix it\n");
-        VALGRIND_MAKE_MEM_NOACCESS(block, sizeof(stat_mem_block_t));
-        VALGRIND_MAKE_MEM_NOACCESS(ident, IDENT_SIZE);
-        vec_free(ptr);
-        return;
-    }
-    VALGRIND_MAKE_MEM_NOACCESS(ident, IDENT_SIZE);
-    info = (stat_mem_block_t*)((char *)ptr - IDENT_MEM_TOP);
-
-    /*
-     * we need access to the redzone that represents the info block
-     * so lets do that.
-     */
-    VALGRIND_MAKE_MEM_DEFINED(info, IDENT_MEM_TOP);
-
-    stat_mem_deallocated       += info->size;
-    stat_mem_high              -= info->size;
-    stat_mem_deallocated_total ++;
-
-    if (info->prev) {
-        /* just need access for a short period */
-        VALGRIND_MAKE_MEM_DEFINED(info->prev, IDENT_MEM_TOP);
-        info->prev->next = info->next;
-        /* don't need access anymore */
-        VALGRIND_MAKE_MEM_NOACCESS(info->prev, IDENT_MEM_TOP);
-    }
-    if (info->next) {
-        /* just need access for a short period */
-        VALGRIND_MAKE_MEM_DEFINED(info->next, IDENT_MEM_TOP);
-        info->next->prev = info->prev;
-        /* don't need access anymore */
-        VALGRIND_MAKE_MEM_NOACCESS(info->next, IDENT_MEM_TOP);
-    }
-
-    /* move ahead */
-    if (info == stat_mem_block_root)
-        stat_mem_block_root = info->next;
-
-    free(info);
-    VALGRIND_MAKE_MEM_NOACCESS(info, IDENT_MEM_TOP);
-    VALGRIND_FREELIKE_BLOCK(ptr, IDENT_MEM_TOP);
-}
-
-void *stat_mem_reallocate(void *ptr, size_t size, size_t line, const char *file, const char *expr) {
-    stat_mem_block_t *oldinfo = NULL;
-    stat_mem_block_t *newinfo;
-
-    if (GMQCC_UNLIKELY(!ptr))
-        return stat_mem_allocate(size, line, file, expr);
-
-    /* stay consistent with glibc */
-    if (GMQCC_UNLIKELY(!size)) {
-        stat_mem_deallocate(ptr, line, file);
-        return NULL;
-    }
-
-    oldinfo = (stat_mem_block_t*)((char *)ptr - IDENT_MEM_TOP);
-    newinfo = (stat_mem_block_t*)malloc(size + IDENT_MEM_TOP);
-
-    if (GMQCC_UNLIKELY(!newinfo)) {
-        stat_mem_deallocate(ptr, line, file);
-        return NULL;
-    }
-
-    VALGRIND_MALLOCLIKE_BLOCK((char *)newinfo + IDENT_MEM_TOP, size, IDENT_MEM_TOP, 0);
-
-    /* we need access to the old info redzone */
-    VALGRIND_MAKE_MEM_DEFINED(oldinfo, IDENT_MEM_TOP);
-
-    /* We need access to the new info redzone */
-    VALGRIND_MAKE_MEM_DEFINED(newinfo, IDENT_MEM_TOP);
-    memcpy((char *)(newinfo + 1), IDENT_MEM, IDENT_SIZE);
-    memcpy((char *)newinfo + IDENT_MEM_TOP, (char *)oldinfo + IDENT_MEM_TOP, oldinfo->size);
-    VALGRIND_MAKE_MEM_NOACCESS(newinfo, IDENT_MEM_TOP);
-
-    if (oldinfo->prev) {
-        /* just need access for a short period */
-        VALGRIND_MAKE_MEM_DEFINED(oldinfo->prev, IDENT_MEM_TOP);
-        oldinfo->prev->next = oldinfo->next;
-        /* don't need access anymore */
-        VALGRIND_MAKE_MEM_NOACCESS(oldinfo->prev, IDENT_MEM_TOP);
-    }
-
-    if (oldinfo->next) {
-        /* just need access for a short period */
-        VALGRIND_MAKE_MEM_DEFINED(oldinfo->next, IDENT_MEM_TOP);
-        oldinfo->next->prev = oldinfo->prev;
-        /* don't need access anymore */
-        VALGRIND_MAKE_MEM_NOACCESS(oldinfo->next, IDENT_MEM_TOP);
-    }
-
-    /* move ahead */
-    if (oldinfo == stat_mem_block_root)
-        stat_mem_block_root = oldinfo->next;
-
-    /* we need access to the redzone for the newinfo block */
-    VALGRIND_MAKE_MEM_DEFINED(newinfo, IDENT_MEM_TOP);
-
-    newinfo->line = line;
-    newinfo->size = size;
-    newinfo->file = file;
-    newinfo->expr = expr;
-    newinfo->prev = NULL;
-    newinfo->next = stat_mem_block_root;
-
-    /*
-     * likely since the only time there is no root is when it's
-     * being initialized first.
-     */
-    if (GMQCC_LIKELY(stat_mem_block_root != NULL)) {
-        /* we need access to the root */
-        VALGRIND_MAKE_MEM_DEFINED(stat_mem_block_root, IDENT_MEM_TOP);
-        stat_mem_block_root->prev = newinfo;
-        /* kill access */
-        VALGRIND_MAKE_MEM_NOACCESS(stat_mem_block_root, IDENT_MEM_TOP);
-    }
-
-    stat_mem_block_root = newinfo;
-    stat_mem_allocated -= oldinfo->size;
-    stat_mem_high      -= oldinfo->size;
-    stat_mem_allocated += newinfo->size;
-    stat_mem_high      += newinfo->size;
-
-    /*
-     * we're finished with the redzones, lets kill the access
-     * to them.
-     */
-    VALGRIND_MAKE_MEM_NOACCESS(newinfo, IDENT_MEM_TOP);
-    VALGRIND_MAKE_MEM_NOACCESS(oldinfo, IDENT_MEM_TOP);
-
-    if (stat_mem_high > stat_mem_peak)
-        stat_mem_peak = stat_mem_high;
-
-    free(oldinfo);
-    VALGRIND_FREELIKE_BLOCK(ptr, IDENT_MEM_TOP);
-    return (char *)newinfo + IDENT_MEM_TOP;
-}
-
-/*
- * strdup does it's own malloc, we need to track malloc. We don't want
- * to overwrite malloc though, infact, we can't really hook it at all
- * without library specific assumptions. So we re implement strdup.
- */
-char *stat_mem_strdup(const char *src, size_t line, const char *file, bool empty) {
-    size_t len = 0;
-    char  *ptr = NULL;
-
-    if (!src)
-        return NULL;
-
-    len = strlen(src);
-    if (((!empty) ? len : true) && (ptr = (char*)stat_mem_allocate(len + 1, line, file, "strdup"))) {
-        memcpy(ptr, src, len);
-        ptr[len] = '\0';
-    }
-
-    stat_used_strdups ++;
-    stat_mem_strdups  += len;
-    return ptr;
-}
-
-/*
- * The reallocate function for resizing vectors.
- */
-void _util_vec_grow(void **a, size_t i, size_t s) {
-    vector_t          *d = (vector_t*)((char *)*a - IDENT_VEC_TOP);
-    size_t             m = 0;
-    stat_size_entry_t *e = NULL;
-    void              *p = NULL;
-
-    if (*a) {
-        m = 2 * d->allocated + i;
-        p = mem_r(d, s * m + IDENT_VEC_TOP);
-    } else {
-        m = i + 1;
-        p = mem_a(s * m + IDENT_VEC_TOP);
-        ((vector_t*)p)->used = 0;
-        stat_used_vectors++;
-    }
-
-    if (!stat_size_vectors)
-        stat_size_vectors = stat_size_new();
-
-    if ((e = stat_size_get(stat_size_vectors, s))) {
-        e->value ++;
-    } else {
-        stat_size_put(stat_size_vectors, s, 1); /* start off with 1 */
-        stat_type_vectors++;
-    }
-
-    d = (vector_t*)p;
-    d->allocated = m;
-    memcpy(d + 1, IDENT_VEC, IDENT_SIZE);
-    *a = (void *)((char *)d + IDENT_VEC_TOP);
-}
-
-void _util_vec_delete(void *data, size_t line, const char *file) {
-    char *ident = (char *)data - IDENT_SIZE;
-    if (!strcmp(ident, IDENT_MEM)) {
-        stat_mem_block_t *block = (stat_mem_block_t*)((char *)data - IDENT_MEM_TOP);
-        VALGRIND_MAKE_MEM_DEFINED(block, sizeof(stat_mem_block_t));
-        con_err("internal warning: invalid use of vec_free:\n");
-        con_err("internal warning:    memory block last allocated (size: %u (bytes), at %s:%u)\n",
-            (unsigned)block->size,
-            block->file,
-            (unsigned)block->line);
-        con_err("internal warning:    released with with wrong routine at %s:%u\n", file, (unsigned)line);
-        con_err("internal warning:    forwarding to mem_d, please fix it\n");
-        VALGRIND_MAKE_MEM_NOACCESS(block, sizeof(stat_mem_block_t));
-        mem_d(data);
-        return;
-    }
-    /* forward */
-    stat_mem_deallocate((void*)(ident - sizeof(vector_t)), line, file);
-}
-
-/*
- * Hash table for generic data, based on dynamic memory allocations
- * all around.  This is the internal interface, please look for
- * EXPOSED INTERFACE comment below
- */
-typedef struct hash_node_t {
-    char               *key;   /* the key for this node in table */
-    void               *value; /* pointer to the data as void*   */
-    struct hash_node_t *next;  /* next node (linked list)        */
-} hash_node_t;
-
-
-size_t hash(const char *key);
-size_t util_hthash(hash_table_t *ht, const char *key) {
-    return hash(key) % ht->size;
-}
-
-static hash_node_t *_util_htnewpair(const char *key, void *value) {
-    hash_node_t *node;
-    if (!(node = (hash_node_t*)mem_a(sizeof(hash_node_t))))
-        return NULL;
-
-    if (!(node->key = util_strdupe(key))) {
-        mem_d(node);
-        return NULL;
-    }
-
-    node->value = value;
-    node->next  = NULL;
-
-    return node;
-}
-
-/*
- * EXPOSED INTERFACE for the hashtable implementation
- * util_htnew(size)                             -- to make a new hashtable
- * util_htset(table, key, value, sizeof(value)) -- to set something in the table
- * util_htget(table, key)                       -- to get something from the table
- * util_htdel(table)                            -- to delete the table
- */
-hash_table_t *util_htnew(size_t size) {
-    hash_table_t      *hashtable = NULL;
-    stat_size_entry_t *find      = NULL;
-
-    if (size < 1)
-        return NULL;
-
-    if (!stat_size_hashtables)
-        stat_size_hashtables = stat_size_new();
-
-    if (!(hashtable = (hash_table_t*)mem_a(sizeof(hash_table_t))))
-        return NULL;
-
-    if (!(hashtable->table = (hash_node_t**)mem_a(sizeof(hash_node_t*) * size))) {
-        mem_d(hashtable);
-        return NULL;
-    }
-
-    if ((find = stat_size_get(stat_size_hashtables, size)))
-        find->value++;
-    else {
-        stat_type_hashtables++;
-        stat_size_put(stat_size_hashtables, size, 1);
-    }
-
-    hashtable->size = size;
-    memset(hashtable->table, 0, sizeof(hash_node_t*) * size);
-
-    stat_used_hashtables++;
-    return hashtable;
-}
-
-void util_htseth(hash_table_t *ht, const char *key, size_t bin, void *value) {
-    hash_node_t *newnode = NULL;
-    hash_node_t *next    = NULL;
-    hash_node_t *last    = NULL;
-
-    next = ht->table[bin];
-
-    while (next && next->key && strcmp(key, next->key) > 0)
-        last = next, next = next->next;
-
-    /* already in table, do a replace */
-    if (next && next->key && strcmp(key, next->key) == 0) {
-        next->value = value;
-    } else {
-        /* not found, grow a pair man :P */
-        newnode = _util_htnewpair(key, value);
-        if (next == ht->table[bin]) {
-            newnode->next  = next;
-            ht->table[bin] = newnode;
-        } else if (!next) {
-            last->next = newnode;
-        } else {
-            newnode->next = next;
-            last->next = newnode;
-        }
-    }
-}
-
-void util_htset(hash_table_t *ht, const char *key, void *value) {
-    util_htseth(ht, key, util_hthash(ht, key), value);
-}
-
-void *util_htgeth(hash_table_t *ht, const char *key, size_t bin) {
-    hash_node_t *pair = ht->table[bin];
-
-    while (pair && pair->key && strcmp(key, pair->key) > 0)
-        pair = pair->next;
-
-    if (!pair || !pair->key || strcmp(key, pair->key) != 0)
-        return NULL;
-
-    return pair->value;
-}
-
-void *util_htget(hash_table_t *ht, const char *key) {
-    return util_htgeth(ht, key, util_hthash(ht, key));
-}
-
-void *code_util_str_htgeth(hash_table_t *ht, const char *key, size_t bin);
-void *code_util_str_htgeth(hash_table_t *ht, const char *key, size_t bin) {
-    hash_node_t *pair;
-    size_t len, keylen;
-    int cmp;
-
-    keylen = strlen(key);
-
-    pair = ht->table[bin];
-    while (pair && pair->key) {
-        len = strlen(pair->key);
-        if (len < keylen) {
-            pair = pair->next;
-            continue;
-        }
-        if (keylen == len) {
-            cmp = strcmp(key, pair->key);
-            if (cmp == 0)
-                return pair->value;
-            if (cmp < 0)
-                return NULL;
-            pair = pair->next;
-            continue;
-        }
-        cmp = strcmp(key, pair->key + len - keylen);
-        if (cmp == 0) {
-            uintptr_t up = (uintptr_t)pair->value;
-            up += len - keylen;
-            return (void*)up;
-        }
-        pair = pair->next;
-    }
-    return NULL;
-}
-
-/*
- * Free all allocated data in a hashtable, this is quite the amount
- * of work.
- */
-void util_htrem(hash_table_t *ht, void (*callback)(void *data)) {
-    size_t i = 0;
-
-    for (; i < ht->size; ++i) {
-        hash_node_t *n = ht->table[i];
-        hash_node_t *p;
-
-        /* free in list */
-        while (n) {
-            if (n->key)
-                mem_d(n->key);
-            if (callback)
-                callback(n->value);
-            p = n;
-            n = p->next;
-            mem_d(p);
-        }
-
-    }
-    /* free table */
-    mem_d(ht->table);
-    mem_d(ht);
-}
-
-void util_htrmh(hash_table_t *ht, const char *key, size_t bin, void (*cb)(void*)) {
-    hash_node_t **pair = &ht->table[bin];
-    hash_node_t *tmp;
-
-    while (*pair && (*pair)->key && strcmp(key, (*pair)->key) > 0)
-        pair = &(*pair)->next;
-
-    tmp = *pair;
-    if (!tmp || !tmp->key || strcmp(key, tmp->key) != 0)
-        return;
-
-    if (cb)
-        (*cb)(tmp->value);
-
-    *pair = tmp->next;
-    mem_d(tmp->key);
-    mem_d(tmp);
-}
-
-void util_htrm(hash_table_t *ht, const char *key, void (*cb)(void*)) {
-    util_htrmh(ht, key, util_hthash(ht, key), cb);
-}
-
-void util_htdel(hash_table_t *ht) {
-    util_htrem(ht, NULL);
-}
-
-/*
- * The following functions below implement printing / dumping of statistical
- * information.
- */
-static void stat_dump_mem_contents(stat_mem_block_t *block, uint16_t cols) {
-    unsigned char *buffer = (unsigned char *)mem_a(cols);
-    unsigned char *memory = (unsigned char *)(block + 1);
-    size_t         i;
-
-    for (i = 0; i < block->size; i++) {
-        if (!(i % 16)) {
-            if (i != 0)
-                con_out(" %s\n", buffer);
-            con_out(" 0x%08X: ", i);
-        }
-
-        con_out(" %02X", memory[i]);
-
-        buffer[i % cols] = ((memory[i] < 0x20) || (memory[i] > 0x7E))
-                            ? '.'
-                            : memory[i];
-
-        buffer[(i % cols) + 1] = '\0';
-    }
-
-    while ((i % cols) != 0) {
-        con_out("   ");
-        i++;
-    }
-
-    con_out(" %s\n", buffer);
-    mem_d(buffer);
-}
-
-static void stat_dump_mem_leaks(void) {
-    stat_mem_block_t *info;
-    /* we need access to the root for this */
-    VALGRIND_MAKE_MEM_DEFINED(stat_mem_block_root, sizeof(stat_mem_block_t));
-    for (info = stat_mem_block_root; info; info = info->next) {
-        /* we need access to the block */
-        VALGRIND_MAKE_MEM_DEFINED(info, sizeof(stat_mem_block_t));
-        con_out("lost: %u (bytes) at %s:%u from expression `%s`\n",
-            info->size,
-            info->file,
-            info->line,
-            info->expr
-        );
-
-        stat_dump_mem_contents(info, OPTS_OPTION_U16(OPTION_MEMDUMPCOLS));
-
-        /*
-         * we're finished with the access, the redzone should be marked
-         * inaccesible so that invalid read/writes that could 'step-into'
-         * those redzones will show up as invalid read/writes in valgrind.
-         */
-        VALGRIND_MAKE_MEM_NOACCESS(info, sizeof(stat_mem_block_t));
-    }
-    VALGRIND_MAKE_MEM_NOACCESS(stat_mem_block_root, sizeof(stat_mem_block_t));
-}
-
-static void stat_dump_mem_info(void) {
-    con_out("Memory Information:\n\
-    Total allocations:   %llu\n\
-    Total deallocations: %llu\n\
-    Total allocated:     %f (MB)\n\
-    Total deallocated:   %f (MB)\n\
-    Total peak memory:   %f (MB)\n\
-    Total leaked memory: %f (MB) in %llu allocations\n",
-        stat_mem_allocated_total,
-        stat_mem_deallocated_total,
-        (float)(stat_mem_allocated)                        / 1048576.0f,
-        (float)(stat_mem_deallocated)                      / 1048576.0f,
-        (float)(stat_mem_peak)                             / 1048576.0f,
-        (float)(stat_mem_allocated - stat_mem_deallocated) / 1048576.0f,
-        stat_mem_allocated_total - stat_mem_deallocated_total
-    );
-}
-
-static void stat_dump_stats_table(stat_size_table_t table, const char *string, uint64_t *size) {
-    size_t i,j;
-
-    if (!table)
-        return;
-
-    for (i = 0, j = 1; i < ST_SIZE; i++) {
-        stat_size_entry_t *entry;
-
-        if (!(entry = table[i]))
-            continue;
-
-        con_out(string, (unsigned)j, (unsigned)entry->key, (unsigned)entry->value);
-        j++;
-
-        if (size)
-            *size += entry->key * entry->value;
-    }
-}
-
-void stat_info() {
-    if (OPTS_OPTION_BOOL(OPTION_MEMCHK) ||
-        OPTS_OPTION_BOOL(OPTION_STATISTICS)) {
-        uint64_t mem = 0;
-
-        con_out("Memory Statistics:\n\
-    Total vectors allocated:       %llu\n\
-    Total string duplicates:       %llu\n\
-    Total string duplicate memory: %f (MB)\n\
-    Total hashtables allocated:    %llu\n\
-    Total unique vector sizes:     %llu\n",
-            stat_used_vectors,
-            stat_used_strdups,
-            (float)(stat_mem_strdups) / 1048576.0f,
-            stat_used_hashtables,
-            stat_type_vectors
-        );
-
-        stat_dump_stats_table (
-            stat_size_vectors,
-            "        %2u| # of %5u byte vectors: %u\n",
-            &mem
-        );
-
-        con_out (
-            "    Total unique hashtable sizes: %llu\n",
-            stat_type_hashtables
-        );
-
-        stat_dump_stats_table (
-            stat_size_hashtables,
-            "        %2u| # of %5u element hashtables: %u\n",
-            NULL
-        );
-
-        con_out (
-            "    Total vector memory:          %f (MB)\n\n",
-            (float)(mem) / 1048576.0f
-        );
-    }
-
-    if (stat_size_vectors)
-        stat_size_del(stat_size_vectors);
-    if (stat_size_hashtables)
-        stat_size_del(stat_size_hashtables);
-
-    if (OPTS_OPTION_BOOL(OPTION_DEBUG) ||
-        OPTS_OPTION_BOOL(OPTION_MEMCHK))
-        stat_dump_mem_info();
-
-    if (OPTS_OPTION_BOOL(OPTION_DEBUG))
-        stat_dump_mem_leaks();
-}
-#undef ST_SIZE
diff --git a/stat.cpp b/stat.cpp
new file mode 100644 (file)
index 0000000..1797536
--- /dev/null
+++ b/stat.cpp
@@ -0,0 +1,249 @@
+#include <string.h>
+#include <stdlib.h>
+
+#include "gmqcc.h"
+
+/*
+ * strdup does it's own malloc, we need to track malloc. We don't want
+ * to overwrite malloc though, infact, we can't really hook it at all
+ * without library specific assumptions. So we re implement strdup.
+ */
+char *stat_mem_strdup(const char *src, bool empty) {
+    size_t len = 0;
+    char *ptr = nullptr;
+
+    if (!src)
+        return nullptr;
+
+    len = strlen(src);
+    if ((!empty ? len : true) && (ptr = (char*)mem_a(len + 1))) {
+        memcpy(ptr, src, len);
+        ptr[len] = '\0';
+    }
+
+    return ptr;
+}
+
+/*
+ * The reallocate function for resizing vectors.
+ */
+void _util_vec_grow(void **a, size_t i, size_t s) {
+    vector_t *d = vec_meta(*a);
+    size_t m = 0;
+    void *p = nullptr;
+
+    if (*a) {
+        m = 2 * d->allocated + i;
+        p = mem_r(d, s * m + sizeof(vector_t));
+    } else {
+        m = i + 1;
+        p = mem_a(s * m + sizeof(vector_t));
+        ((vector_t*)p)->used = 0;
+    }
+
+    d = (vector_t*)p;
+    d->allocated = m;
+    *a = d + 1;
+}
+
+void _util_vec_delete(void *data) {
+    mem_d(vec_meta(data));
+}
+
+/*
+ * Hash table for generic data, based on dynamic memory allocations
+ * all around.  This is the internal interface, please look for
+ * EXPOSED INTERFACE comment below
+ */
+struct hash_node_t {
+    char *key;   /* the key for this node in table */
+    void *value; /* pointer to the data as void*   */
+    hash_node_t *next;  /* next node (linked list)        */
+};
+
+size_t hash(const char *key);
+
+size_t util_hthash(hash_table_t *ht, const char *key) {
+    return hash(key) % ht->size;
+}
+
+static hash_node_t *_util_htnewpair(const char *key, void *value) {
+    hash_node_t *node;
+    if (!(node = (hash_node_t*)mem_a(sizeof(hash_node_t))))
+        return nullptr;
+
+    if (!(node->key = util_strdupe(key))) {
+        mem_d(node);
+        return nullptr;
+    }
+
+    node->value = value;
+    node->next  = nullptr;
+
+    return node;
+}
+
+/*
+ * EXPOSED INTERFACE for the hashtable implementation
+ * util_htnew(size)                             -- to make a new hashtable
+ * util_htset(table, key, value, sizeof(value)) -- to set something in the table
+ * util_htget(table, key)                       -- to get something from the table
+ * util_htdel(table)                            -- to delete the table
+ */
+hash_table_t *util_htnew(size_t size) {
+    hash_table_t *hashtable = nullptr;
+
+    if (size < 1)
+        return nullptr;
+
+    if (!(hashtable = (hash_table_t*)mem_a(sizeof(hash_table_t))))
+        return nullptr;
+
+    if (!(hashtable->table = (hash_node_t**)mem_a(sizeof(hash_node_t*) * size))) {
+        mem_d(hashtable);
+        return nullptr;
+    }
+
+    hashtable->size = size;
+    memset(hashtable->table, 0, sizeof(hash_node_t*) * size);
+
+    return hashtable;
+}
+
+void util_htseth(hash_table_t *ht, const char *key, size_t bin, void *value) {
+    hash_node_t *newnode = nullptr;
+    hash_node_t *next    = nullptr;
+    hash_node_t *last    = nullptr;
+
+    next = ht->table[bin];
+
+    while (next && next->key && strcmp(key, next->key) > 0)
+        last = next, next = next->next;
+
+    /* already in table, do a replace */
+    if (next && next->key && strcmp(key, next->key) == 0) {
+        next->value = value;
+    } else {
+        /* not found, grow a pair man :P */
+        newnode = _util_htnewpair(key, value);
+        if (next == ht->table[bin]) {
+            newnode->next  = next;
+            ht->table[bin] = newnode;
+        } else if (!next) {
+            last->next = newnode;
+        } else {
+            newnode->next = next;
+            last->next = newnode;
+        }
+    }
+}
+
+void util_htset(hash_table_t *ht, const char *key, void *value) {
+    util_htseth(ht, key, util_hthash(ht, key), value);
+}
+
+void *util_htgeth(hash_table_t *ht, const char *key, size_t bin) {
+    hash_node_t *pair = ht->table[bin];
+
+    while (pair && pair->key && strcmp(key, pair->key) > 0)
+        pair = pair->next;
+
+    if (!pair || !pair->key || strcmp(key, pair->key) != 0)
+        return nullptr;
+
+    return pair->value;
+}
+
+void *util_htget(hash_table_t *ht, const char *key) {
+    return util_htgeth(ht, key, util_hthash(ht, key));
+}
+
+void *code_util_str_htgeth(hash_table_t *ht, const char *key, size_t bin);
+void *code_util_str_htgeth(hash_table_t *ht, const char *key, size_t bin) {
+    hash_node_t *pair;
+    size_t len, keylen;
+    int cmp;
+
+    keylen = strlen(key);
+
+    pair = ht->table[bin];
+    while (pair && pair->key) {
+        len = strlen(pair->key);
+        if (len < keylen) {
+            pair = pair->next;
+            continue;
+        }
+        if (keylen == len) {
+            cmp = strcmp(key, pair->key);
+            if (cmp == 0)
+                return pair->value;
+            if (cmp < 0)
+                return nullptr;
+            pair = pair->next;
+            continue;
+        }
+        cmp = strcmp(key, pair->key + len - keylen);
+        if (cmp == 0) {
+            uintptr_t up = (uintptr_t)pair->value;
+            up += len - keylen;
+            return (void*)up;
+        }
+        pair = pair->next;
+    }
+    return nullptr;
+}
+
+/*
+ * Free all allocated data in a hashtable, this is quite the amount
+ * of work.
+ */
+void util_htrem(hash_table_t *ht, void (*callback)(void *data)) {
+    size_t i = 0;
+
+    for (; i < ht->size; ++i) {
+        hash_node_t *n = ht->table[i];
+        hash_node_t *p;
+
+        /* free in list */
+        while (n) {
+            if (n->key)
+                mem_d(n->key);
+            if (callback)
+                callback(n->value);
+            p = n;
+            n = p->next;
+            mem_d(p);
+        }
+
+    }
+    /* free table */
+    mem_d(ht->table);
+    mem_d(ht);
+}
+
+void util_htrmh(hash_table_t *ht, const char *key, size_t bin, void (*cb)(void*)) {
+    hash_node_t **pair = &ht->table[bin];
+    hash_node_t *tmp;
+
+    while (*pair && (*pair)->key && strcmp(key, (*pair)->key) > 0)
+        pair = &(*pair)->next;
+
+    tmp = *pair;
+    if (!tmp || !tmp->key || strcmp(key, tmp->key) != 0)
+        return;
+
+    if (cb)
+        (*cb)(tmp->value);
+
+    *pair = tmp->next;
+    mem_d(tmp->key);
+    mem_d(tmp);
+}
+
+void util_htrm(hash_table_t *ht, const char *key, void (*cb)(void*)) {
+    util_htrmh(ht, key, util_hthash(ht, key), cb);
+}
+
+void util_htdel(hash_table_t *ht) {
+    util_htrem(ht, nullptr);
+}
diff --git a/syntax/README b/syntax/README
deleted file mode 100644 (file)
index 5a5c4b4..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-Here exists some syntax highlighting configuration files for various
-text editors. Inside each directory exists some documentaiton on how
-you can install the configuration file correctly.
-
-Currently the supported text editors:
-    geany
-    kate
-    kwrite        - uses kate syntax highlighting
-    kdevelop      - uses kate syntax highlighting
-    QtCreator     - supports kate syntax highlighting
-    gtksourceview - main source viewer in GNOME
-    gedit         - uses gtksourceview
-    sandy         - uses gtksourceview
-    nano
-    jedit
-
-
-Other text editors we plan to provide syntax highlighting configuration
-files for (but never got around to figuring out)
-    vim
-    emacs
-
-If your text editor is not supported and you'd like to create syntax
-highlighting support for it, don't hesitate to share it with us.
diff --git a/syntax/geany/README b/syntax/geany/README
deleted file mode 100644 (file)
index c527d3f..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-To use the geany syntax highlighting install filetypes.qc to the syntax
-directory for geany.
-
-# Can be installed globally to
-/usr/share/geany/
-
-# Can be installed locally to
-~/.config/geany/filedefs/
diff --git a/syntax/geany/filetypes.qc b/syntax/geany/filetypes.qc
deleted file mode 100644 (file)
index d84bb7e..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-[styling]
-default=default
-comment=comment
-commentline=comment_line
-commentdoc=comment_doc
-preprocessorcomment=comment
-number=number_1
-word=keyword_1
-word2=keyword_2
-string=string_1
-stringraw=string_2
-character=character
-uuid=other
-preprocessor=preprocessor
-operator=operator
-identifier=identifier_1
-stringeol=string_eol
-verbatim=string_2
-regex=regex
-commentlinedoc=comment_line_doc
-commentdockeyword=comment_doc_keyword
-commentdockeyworderror=comment_doc_keyword_error
-globalclass=class
-tripleverbatim=string_2
-hashquotedstring=string_2
-
-[keywords]
-primary=break case const continue string default do else enum float for goto if return switch typedef void while false nil true
-secondary=
-docComment=
-
-[lexer_properties]
-styling.within.preprocessor=1
-lexer.cpp.track.preprocessor=0
-preprocessor.symbol.$(file.patterns.cpp)=#
-preprocessor.start.$(file.patterns.cpp)=if ifdef ifndef
-preprocessor.middle.$(file.patterns.cpp)=else elif
-preprocessor.end.$(file.patterns.cpp)=endif
-
-[settings]
-extension=qc
-comment_single=//
-comment_open=/*
-comment_close=*/
-comment_use_indent=true
-context_action_cmd=
-
-[indentation]
-width=4
-type=0
-
-[build_settings]
-compiler=gmqcc -Wall "%f" -o "%e"
-linker=
-run_cmd=qcvm "./%e"
diff --git a/syntax/gtksourceview/README b/syntax/gtksourceview/README
deleted file mode 100644 (file)
index 73a6072..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-To use the gtksourceview syntax highlighting install qc.lang to the syntax
-directory for gtksourceview
-
-# Can be installed globally to
-/usr/share/gtksourceview-[version]/language-specs/
diff --git a/syntax/gtksourceview/qc.lang b/syntax/gtksourceview/qc.lang
deleted file mode 100644 (file)
index 827290f..0000000
+++ /dev/null
@@ -1,173 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<language id="qc" _name="QuakeC" version="1.0" _section="Sources">
-  <metadata>
-    <property name="globs">*.qc</property>
-    <property name="line-comment-start">//</property>
-    <property name="block-comment-start">/*</property>
-    <property name="block-comment-end">*/</property>
-  </metadata>
-
-  <styles>
-    <style id="comment"           _name="Comment"               map-to="def:comment"/>
-    <style id="string"            _name="String"                map-to="def:string"/>
-    <style id="preprocessor"      _name="Preprocessor"          map-to="def:preprocessor"/>
-    <style id="common-defines"    _name="Common Defines"        map-to="def:special-constant"/>
-    <style id="included-file"     _name="Included File"         map-to="def:string"/>
-    <style id="keyword"           _name="Keyword"               map-to="def:keyword"/>
-    <style id="type"              _name="Data Type"             map-to="def:type"/>
-    <style id="escaped-character" _name="Escaped Character"     map-to="def:special-char"/>
-    <style id="floating-point"    _name="Floating point number" map-to="def:floating-point"/>
-    <style id="decimal"           _name="Decimal number"        map-to="def:decimal"/>
-    <style id="hexadecimal"       _name="Hexadecimal number"    map-to="def:base-n-integer"/>
-    <style id="boolean"           _name="Boolean value"         map-to="def:boolean"/>
-  </styles>
-
-  <definitions>
-
-    <!--regexs-->
-    <define-regex id="preproc-start">^\s*#\s*</define-regex>
-    <define-regex id="escaped-character" extended="true">
-      \\(                   # leading backslash
-      [\\\"\'nrbtfav\?] |   # escaped character
-      [0-7]{1,3} |          # one, two, or three octal digits
-      x[0-9A-Fa-f]+         # 'x' followed by hex digits
-      )
-    </define-regex>
-
-    <!-- Preprocessor -->
-    <context id="if0-comment" style-ref="comment">
-      <start>\%{preproc-start}if\b\s*0\b</start>
-      <end>\%{preproc-start}(endif|else|elif)\b</end>
-      <include>
-        <context id="if-in-if0">
-          <start>\%{preproc-start}if(n?def)?\b</start>
-          <end>\%{preproc-start}endif\b</end>
-          <include>
-            <context ref="if-in-if0"/>
-            <context ref="def:in-comment"/>
-          </include>
-        </context>
-        <context ref="def:in-comment"/>
-      </include>
-    </context>
-    <context id="include" style-ref="preprocessor">
-      <match extended="true">
-        \%{preproc-start}
-        (include|import)\s*
-        (".*?"|&lt;.*&gt;)
-      </match>
-      <include>
-        <context id="included-file" sub-pattern="2" style-ref="included-file"/>
-      </include>
-    </context>
-    <context id="preprocessor" style-ref="preprocessor" end-at-line-end="true">
-      <start extended="true">
-        \%{preproc-start}
-        (define|undef|error|pragma|ident|if(n?def)?|else|elif|endif|line|warning)
-        \b
-      </start>
-      <include>
-        <context ref="def:line-continue" ignore-style="true"/>
-        <context ref="string" ignore-style="true"/>
-        <context ref="def:qc-like-comment"/>
-        <context ref="def:qc-like-comment-multiline"/>
-      </include>
-    </context>
-
-    <context id="float" style-ref="floating-point">
-      <match extended="true">
-        (?&lt;![\w\.])
-        ((\.[0-9]+ | [0-9]+\.[0-9]*) ([Ee][+-]?[0-9]*)? |
-         ([0-9]+[Ee][+-]?[0-9]*))
-        [fFlL]?
-        (?![\w\.])
-      </match>
-    </context>
-
-    <context id="hexadecimal" style-ref="hexadecimal">
-      <match extended="true">
-        (?&lt;![\w\.])
-        0[xX][a-fA-F0-9]+[uUlL]*
-        (?![\w\.])
-      </match>
-    </context>
-
-    <context id="invalid-hexadecimal" style-ref="error">
-      <match extended="true">
-        (?&lt;![\w\.])
-        0[xX][a-fA-F0-9]*[g-zG-Z][a-zA-Z0-9]*[uUlL]*
-        (?![\w\.])
-      </match>
-    </context>
-
-    <context id="decimal" style-ref="decimal">
-      <match extended="true">
-        (?&lt;![\w\.])
-        (0|[1-9][0-9]*)[uUlL]*
-        (?![\w\.])
-      </match>
-    </context>
-
-    <context id="keywords" style-ref="keyword">
-      <keyword>break</keyword>
-      <keyword>case</keyword>
-      <keyword>continue</keyword>
-      <keyword>default</keyword>
-      <keyword>do</keyword>
-      <keyword>else</keyword>
-      <keyword>enum</keyword>
-      <keyword>for</keyword>
-      <keyword>goto</keyword>
-      <keyword>if</keyword>
-      <keyword>return</keyword>
-      <keyword>switch</keyword>
-      <keyword>typedef</keyword>
-      <keyword>while</keyword>
-      <keyword>nil</keyword>
-    </context>
-
-    <context id="types" style-ref="type">
-      <keyword>bool</keyword>
-      <keyword>string</keyword>
-      <keyword>vector</keyword>
-      <keyword>float</keyword>
-      <keyword>void</keyword>
-    </context>
-
-    <context id="boolean" style-ref="boolean">
-      <keyword>true</keyword>
-      <keyword>false</keyword>
-    </context>
-
-    <context id="common-defines" style-ref="common-defines">
-      <keyword>__LINE__</keyword>
-      <keyword>__FILE__</keyword>
-      <keyword>__TIME__</keyword>
-      <keyword>__RANDOM__</keyword>
-      <keyword>__RANDOM_LAST__</keyword>
-      <keyword>__COUNTER__</keyword>
-      <keyword>__COUNTER_LAST__</keyword>
-      <keyword>__DATE__</keyword>
-    </context>
-
-    <context id="qc" class="no-spell-check">
-      <include>
-        <context ref="def:qc-like-comment"/>
-        <context ref="def:qc-like-comment-multiline"/>
-        <context ref="def:qc-like-close-comment-outside-comment"/>
-        <context ref="if0-comment"/>
-        <context ref="include"/>
-        <context ref="preprocessor"/>
-        <context ref="string"/>
-        <context ref="float"/>
-        <context ref="hexadecimal"/>
-        <context ref="invalid-hexadecimal"/>
-        <context ref="decimal"/>
-        <context ref="keywords"/>
-        <context ref="types"/>
-        <context ref="boolean"/>
-        <context ref="common-defines"/>
-      </include>
-    </context>
-  </definitions>
-</language>
diff --git a/syntax/jedit/README b/syntax/jedit/README
deleted file mode 100644 (file)
index cfdf523..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-To use the jedit syntax highlighting install qc.xml to the syntax
-directory for jedit
-
-# For Windows Users that directory is
-C:\Users\username\.jedit\modes
-
-# For Linux users that directory is
-/home/username/.jedit/modes
-
-# For Mac users that directory is
-/Users/username/Library/jEdit/modes
-
-After the file is installed, a mode line needs to be added to
-a file caled catalog in that same directory.
-
-Add the following line:
-<MODE NAME="QuakeC Code" FILE="qc.xml" FILE_NAME_GLOB="*.qc" />
-
-inside the <MODES> block before the end tag </MODES>. If the file
-does not exist, you can simply make one and use the following:
-
-<?xml version="1.0"?>
-<!DOCTYPE MODES SYSTEM "catalog.dtd">
-<MODES>
-    <MODE NAME="QuakeC Code" FILE="qc.xml" FILE_NAME_GLOB="*.qc" />
-</MODES>
diff --git a/syntax/jedit/qc.xml b/syntax/jedit/qc.xml
deleted file mode 100644 (file)
index 94b2d66..0000000
+++ /dev/null
@@ -1,271 +0,0 @@
-<?xml version="1.0"?>
-
-<!DOCTYPE MODE SYSTEM "xmode.dtd">
-
-<MODE>
-    <PROPS>
-        <PROPERTY NAME="commentStart" VALUE="/*" />
-        <PROPERTY NAME="commentEnd" VALUE="*/" />
-        <PROPERTY NAME="lineComment" VALUE="//" />
-        <PROPERTY NAME="wordBreakChars" VALUE=",+-=&lt;&gt;/?^&amp;*" />
-
-        <!-- Auto indent -->
-        <PROPERTY NAME="indentOpenBrackets" VALUE="{" />
-        <PROPERTY NAME="indentCloseBrackets" VALUE="}" />
-        <PROPERTY NAME="unalignedOpenBrackets" VALUE="(" />
-        <PROPERTY NAME="unalignedCloseBrackets" VALUE=")" />
-        <PROPERTY NAME="indentNextLine"
-            VALUE="(?!^\s*(#|//)).*(\b(if|while|for)\s*\(.*\)|\b(else|do)\b)[^{;]*$" />
-        <PROPERTY NAME="unindentThisLine"
-            VALUE="^\s*((case\b.*|[\p{Alpha}_][\p{Alnum}_]*)\s*:(?!:)).*$" />
-        <PROPERTY NAME="electricKeys" VALUE=":" />
-    </PROPS>
-
-    <RULES
-        IGNORE_CASE="FALSE"
-        HIGHLIGHT_DIGITS="TRUE" DIGIT_RE="[0-9][0-9a-zA-Z]*">
-        <EOL_SPAN TYPE="KEYWORD2" AT_WHITESPACE_END="TRUE" DELEGATE="CPP">#</EOL_SPAN>
-
-        <IMPORT DELEGATE="LEX"/>
-        <IMPORT DELEGATE="CORE"/>
-    </RULES>
-
-    <RULES SET="LEX" IGNORE_CASE="FALSE">
-        <IMPORT DELEGATE="COMMENTS" />
-        <IMPORT DELEGATE="C_LEXER" />
-    </RULES>
-
-    <!-- Comments, Trigraph, Alternate-Tokens -->
-    <RULES SET="C_LEXER"
-        IGNORE_CASE="FALSE"
-        HIGHLIGHT_DIGITS="TRUE" DIGIT_RE="[0-9][0-9a-zA-Z]*">
-
-        <SPAN TYPE="LITERAL1" NO_LINE_BREAK="TRUE" ESCAPE="\">
-            <BEGIN>L"</BEGIN>
-            <END>"</END>
-        </SPAN>
-        <SPAN TYPE="LITERAL1" NO_LINE_BREAK="TRUE" ESCAPE="\">
-            <BEGIN>"</BEGIN>
-            <END>"</END>
-        </SPAN>
-        <SPAN TYPE="LITERAL1" NO_LINE_BREAK="TRUE" ESCAPE="\">
-            <BEGIN>L'</BEGIN>
-            <END>'</END>
-        </SPAN>
-        <SPAN TYPE="LITERAL1" NO_LINE_BREAK="TRUE" ESCAPE="\">
-            <BEGIN>'</BEGIN>
-            <END>'</END>
-        </SPAN>
-
-        <!-- Trigraphs -->
-        <SEQ TYPE="LITERAL4">??(</SEQ>
-        <SEQ TYPE="LITERAL4">??/</SEQ>
-        <SEQ TYPE="LITERAL4">??)</SEQ>
-        <SEQ TYPE="LITERAL4">??'</SEQ>
-        <SEQ TYPE="LITERAL4">??&lt;</SEQ>
-        <SEQ TYPE="LITERAL4">??!</SEQ>
-        <SEQ TYPE="LITERAL4">??&gt;</SEQ>
-        <SEQ TYPE="LITERAL4">??-</SEQ>
-        <SEQ TYPE="LITERAL4">??=</SEQ>
-
-        <!-- Alternate tokens -->
-        <SEQ TYPE="LITERAL4">&lt;:</SEQ>
-        <SEQ TYPE="LITERAL4">:&gt;</SEQ>
-        <SEQ TYPE="LITERAL4">&lt;%</SEQ>
-        <SEQ TYPE="LITERAL4">%&gt;</SEQ>
-        <SEQ TYPE="LITERAL4">%:</SEQ>
-
-        <!-- Labels.
-            This is a part of core language syntax, but must be here
-            because it can't work after SEQ for ':'. -->
-        <MARK_PREVIOUS AT_WHITESPACE_END="TRUE"
-            MATCH_TYPE="OPERATOR"
-            TYPE="LABEL">:</MARK_PREVIOUS>
-
-        <!-- Function-like macro or function calls.
-            This can't work after SEQ for '('. -->
-        <MARK_PREVIOUS
-            TYPE="FUNCTION"
-            MATCH_TYPE="OPERATOR">(</MARK_PREVIOUS>
-
-        <SEQ TYPE="OPERATOR">=</SEQ>
-        <SEQ TYPE="OPERATOR">!</SEQ>
-        <SEQ TYPE="OPERATOR">+</SEQ>
-        <SEQ TYPE="OPERATOR">-</SEQ>
-        <SEQ TYPE="OPERATOR">/</SEQ>
-        <SEQ TYPE="OPERATOR">*</SEQ>
-        <SEQ TYPE="OPERATOR">&gt;</SEQ>
-        <SEQ TYPE="OPERATOR">&lt;</SEQ>
-        <SEQ TYPE="OPERATOR">%</SEQ>
-        <SEQ TYPE="OPERATOR">&amp;</SEQ>
-        <SEQ TYPE="OPERATOR">|</SEQ>
-        <SEQ TYPE="OPERATOR">^</SEQ>
-        <SEQ TYPE="OPERATOR">~</SEQ>
-        <SEQ TYPE="OPERATOR">?</SEQ>
-        <SEQ TYPE="OPERATOR">:</SEQ>
-        <SEQ TYPE="OPERATOR">.</SEQ>
-        <SEQ TYPE="OPERATOR">,</SEQ>
-        <SEQ TYPE="OPERATOR">[</SEQ>
-        <SEQ TYPE="OPERATOR">]</SEQ>
-        <SEQ TYPE="OPERATOR">)</SEQ>
-        <SEQ TYPE="OPERATOR">}</SEQ>
-        <SEQ TYPE="OPERATOR">{</SEQ>
-        <SEQ TYPE="OPERATOR">;</SEQ>
-
-        <KEYWORDS>
-            <LITERAL2>__FILE__</LITERAL2>
-            <LITERAL2>__LINE__</LITERAL2>
-            <LITERAL2>__DATE__</LITERAL2>
-            <LITERAL2>__RANDOM__</LITERAL2>
-            <LITERAL2>__RANDOM_LAST</LITERAL2>
-            <LITERAL2>__COUNT__</LITERAL2>
-            <LITERAL2>__COUNT_LAST</LITERAL2>
-        </KEYWORDS>
-    </RULES>
-
-    <!-- Core language -->
-    <RULES SET="CORE"
-        IGNORE_CASE="FALSE"
-        HIGHLIGHT_DIGITS="TRUE" DIGIT_RE="[0-9][0-9a-zA-Z]*">
-        <KEYWORDS>
-            <!-- Types -->
-            <KEYWORD3>float</KEYWORD3>
-            <KEYWORD3>vector</KEYWORD3>
-            <KEYWORD3>string</KEYWORD3>
-            <KEYWORD3>entity</KEYWORD3>
-            <KEYWORD3>enum</KEYWORD3>
-            <KEYWORD3>.float</KEYWORD3>
-            <KEYWORD3>.int</KEYWORD3>
-            <KEYWORD3>.vector</KEYWORD3>
-            <KEYWORD3>.string</KEYWORD3>
-            <KEYWORD3>.entity</KEYWORD3>
-            <KEYWORD3>.void</KEYWORD3>
-            <KEYWORD3>typedef</KEYWORD3>
-
-            <KEYWORD1>break</KEYWORD1>
-            <KEYWORD1>case</KEYWORD1>
-            <KEYWORD1>continue</KEYWORD1>
-            <KEYWORD1>default</KEYWORD1>
-            <KEYWORD1>do</KEYWORD1>
-            <KEYWORD1>else</KEYWORD1>
-            <KEYWORD1>for</KEYWORD1>
-            <KEYWORD1>goto</KEYWORD1>
-            <KEYWORD1>if</KEYWORD1>
-            <KEYWORD1>return</KEYWORD1>
-            <KEYWORD1>switch</KEYWORD1>
-            <KEYWORD1>void</KEYWORD1>
-            <KEYWORD1>while</KEYWORD1>
-            <KEYWORD1>nil</KEYWORD1>
-
-            <LITERAL2>FALSE</LITERAL2>
-            <LITERAL2>TRUE</LITERAL2>
-            <LITERAL2>...</LITERAL2>
-        </KEYWORDS>
-    </RULES>
-
-    <!-- Different comment styles. -->
-    <RULES SET="COMMENTS">
-        <!-- Doxygen comment, Javadoc style -->
-        <SEQ TYPE="COMMENT1">/**/</SEQ>
-        <SPAN TYPE="COMMENT3" DELEGATE="doxygen::DOXYGEN">
-            <BEGIN>/**&lt;</BEGIN>
-            <END>*/</END>
-        </SPAN>
-        <SPAN TYPE="COMMENT3" DELEGATE="doxygen::DOXYGEN">
-            <BEGIN>/**</BEGIN>
-            <END>*/</END>
-        </SPAN>
-        <EOL_SPAN TYPE="COMMENT3" DELEGATE="doxygen::DOXYGEN">///&lt;</EOL_SPAN>
-        <EOL_SPAN TYPE="COMMENT3" DELEGATE="doxygen::DOXYGEN">///</EOL_SPAN>
-
-        <!-- Doxygen comment, Qt style -->
-        <SPAN TYPE="COMMENT3" DELEGATE="doxygen::DOXYGEN">
-            <BEGIN>/*!&lt;</BEGIN>
-            <END>*/</END>
-        </SPAN>
-        <SPAN TYPE="COMMENT3" DELEGATE="doxygen::DOXYGEN">
-            <BEGIN>/*!</BEGIN>
-            <END>*/</END>
-        </SPAN>
-        <EOL_SPAN TYPE="COMMENT3" DELEGATE="doxygen::DOXYGEN">//!&lt;</EOL_SPAN>
-        <EOL_SPAN TYPE="COMMENT3" DELEGATE="doxygen::DOXYGEN">//!</EOL_SPAN>
-
-        <!-- C style comment -->
-        <SPAN TYPE="COMMENT1">
-            <BEGIN>/*</BEGIN>
-            <END>*/</END>
-        </SPAN>
-        <EOL_SPAN TYPE="COMMENT1">//</EOL_SPAN>
-    </RULES>
-
-    <!-- Preprocessor specific rules -->
-    <RULES SET="CPP"
-        IGNORE_CASE="FALSE"
-        HIGHLIGHT_DIGITS="TRUE" DIGIT_RE="[0-9][0-9a-zA-Z]*">
-
-        <EOL_SPAN_REGEXP HASH_CHAR="include" TYPE="MARKUP" DELEGATE="INCLUDE">include\b</EOL_SPAN_REGEXP>
-        <EOL_SPAN_REGEXP HASH_CHAR="define" TYPE="MARKUP" DELEGATE="DEFINE">define\b</EOL_SPAN_REGEXP>
-        <EOL_SPAN_REGEXP HASH_CHAR="endif" TYPE="MARKUP" DELEGATE="LEX">endif\b</EOL_SPAN_REGEXP>
-        <EOL_SPAN_REGEXP HASH_CHAR="elif" TYPE="MARKUP" DELEGATE="CONDITION">elif\b</EOL_SPAN_REGEXP>
-        <EOL_SPAN_REGEXP HASH_CHAR="if" TYPE="MARKUP" DELEGATE="CONDITION">if\b</EOL_SPAN_REGEXP>
-
-        <IMPORT DELEGATE="LEX"/>
-
-        <!-- Directives -->
-        <KEYWORDS>
-            <MARKUP>undef</MARKUP>
-            <MARKUP>ifdef</MARKUP>
-            <MARKUP>ifndef</MARKUP>
-            <MARKUP>else</MARKUP>
-            <MARKUP>error</MARKUP>
-            <MARKUP>warning</MARKUP>
-            <MARKUP>pragma</MARKUP>
-            <MARKUP>$frame</MARKUP>
-            <MARKUP>$model</MARKUP>
-        </KEYWORDS>
-    </RULES>
-
-    <!-- After #include directive -->
-    <!-- "\"s are not escaped. -->
-    <RULES SET="INCLUDE"
-        IGNORE_CASE="FALSE"
-        HIGHLIGHT_DIGITS="TRUE" DIGIT_RE="[0-9][0-9a-zA-Z]*">
-        <SPAN TYPE="LITERAL1" NO_LINE_BREAK="TRUE">
-            <BEGIN>&lt;</BEGIN>
-            <END>&gt;</END>
-        </SPAN>
-        <SPAN TYPE="LITERAL1" NO_LINE_BREAK="TRUE">
-            <BEGIN>"</BEGIN>
-            <END>"</END>
-        </SPAN>
-        <IMPORT DELEGATE="LEX"/>
-    </RULES>
-
-    <!-- After #define directive -->
-    <!-- Almost same as the normal code,
-        except two additional operators # and ##. -->
-    <RULES SET="DEFINE"
-        IGNORE_CASE="FALSE"
-        HIGHLIGHT_DIGITS="TRUE" DIGIT_RE="[0-9][0-9a-zA-Z]*">
-        <SEQ TYPE="OPERATOR">#</SEQ>
-        <IMPORT DELEGATE="LEX"/>
-        <IMPORT DELEGATE="CORE"/>
-    </RULES>
-
-    <!-- After #if or #elif directive -->
-    <!-- All constant expressions and a special operator
-        'defined' is available. But the core language elements
-        (such as operator 'sizeof', type casting, etc...) are not. -->
-    <RULES SET="CONDITION"
-        IGNORE_CASE="FALSE"
-        HIGHLIGHT_DIGITS="TRUE" DIGIT_RE="[0-9][0-9a-zA-Z]*">
-        <IMPORT DELEGATE="LEX"/>
-        <KEYWORDS>
-            <KEYWORD2>defined</KEYWORD2>
-            <KEYWORD2>TRUE</KEYWORD2>
-            <KEYWORD2>FALSE</KEYWORD2>
-            <KEYWORD2>true</KEYWORD2>
-            <KEYWORD2>false</KEYWORD2>
-        </KEYWORDS>
-    </RULES>
-</MODE>
diff --git a/syntax/kate/README b/syntax/kate/README
deleted file mode 100644 (file)
index 888d6d2..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-To use the Kate syntax highlighting install qc.xml to the syntax
-directory for kate.
-
-# Can be installed globally to
-$KDEDIR/share/apps/katepart/syntax
-
-if $KDEDIR is unset you can lookup the folder directory with
-kde4-config --prefix if that doesn't work chances are KDEDIR is
-/usr
diff --git a/syntax/kate/qc.xml b/syntax/kate/qc.xml
deleted file mode 100644 (file)
index 2f49dba..0000000
+++ /dev/null
@@ -1,155 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE language SYSTEM "language.dtd">
-<language name="QuakeC" section="Sources"
-          version="1.00" kateversion="2.4"
-          indenter="cstyle"
-          extensions="*.qc;*.QC;*.qh"
-          mimetype=""
-          priority="5"
-          author="Dale Weiler">
-  <highlighting>
-    <list name="keywords">
-      <item> break </item>
-      <item> case </item>
-      <item> continue </item>
-      <item> default </item>
-      <item> do </item>
-      <item> else </item>
-      <item> enum </item>
-      <item> for </item>
-      <item> goto </item>
-      <item> if </item>
-      <item> return </item>
-      <item> switch </item>
-      <item> typedef </item>
-      <item> while </item>
-      <item> nil </item>
-    </list>
-    <list name="types">
-      <item> const </item>
-      <item> vector </item>
-      <item> float </item>
-      <item> void </item>
-      <item> string </item>
-    </list>
-    <contexts>
-      <context attribute="Normal Text" lineEndContext="#stay" name="Normal">
-        <DetectSpaces />
-    <RegExpr attribute="Preprocessor" context="Outscoped" String="#\s*if\s+0\s*$" beginRegion="PP" firstNonSpace="true" />
-        <DetectChar context="AfterHash" char="#" firstNonSpace="true" lookAhead="true" />
-        <StringDetect attribute="Region Marker" context="Region Marker" String="//BEGIN" beginRegion="Region1" firstNonSpace="true" />
-        <StringDetect attribute="Region Marker" context="Region Marker" String="//END" endRegion="Region1" firstNonSpace="true" />
-        <keyword attribute="Keyword" context="#stay" String="keywords"/>
-        <keyword attribute="Data Type" context="#stay" String="types"/>
-        <DetectIdentifier />
-        <DetectChar attribute="Symbol" context="#stay" char="{" beginRegion="Brace1" />
-        <DetectChar attribute="Symbol" context="#stay" char="}" endRegion="Brace1" />
-        <Float attribute="Float" context="#stay">
-          <AnyChar String="fF" attribute="Float" context="#stay"/>
-        </Float>
-        <HlCHex attribute="Hex" context="#stay"/>
-        <Int attribute="Decimal" context="#stay" >
-          <StringDetect attribute="Decimal" context="#stay" String="ULL" insensitive="TRUE"/>
-          <StringDetect attribute="Decimal" context="#stay" String="LUL" insensitive="TRUE"/>
-          <StringDetect attribute="Decimal" context="#stay" String="LLU" insensitive="TRUE"/>
-          <StringDetect attribute="Decimal" context="#stay" String="UL" insensitive="TRUE"/>
-          <StringDetect attribute="Decimal" context="#stay" String="LU" insensitive="TRUE"/>
-          <StringDetect attribute="Decimal" context="#stay" String="LL" insensitive="TRUE"/>
-          <StringDetect attribute="Decimal" context="#stay" String="U" insensitive="TRUE"/>
-          <StringDetect attribute="Decimal" context="#stay" String="L" insensitive="TRUE"/>
-        </Int>
-        <HlCChar attribute="Char" context="#stay"/>
-        <DetectChar attribute="String" context="String" char="&quot;"/>
-        <Detect2Chars attribute="Comment" context="Commentar 1" char="/" char1="/"/>
-        <Detect2Chars attribute="Comment" context="Commentar 2" char="/" char1="*" beginRegion="Comment"/>
-        <AnyChar attribute="Symbol" context="#stay" String=":!%&amp;()+,-/.*&lt;=&gt;?[]|~^&#59;"/>
-      </context>
-
-      <context attribute="String" lineEndContext="#pop" name="String">
-        <LineContinue attribute="String" context="#stay"/>
-        <HlCStringChar attribute="String Char" context="#stay"/>
-        <DetectChar attribute="String" context="#pop" char="&quot;"/>
-      </context>
-
-      <context attribute="Region Marker" lineEndContext="#pop" name="Region Marker">
-      </context>
-
-      <context attribute="Comment" lineEndContext="#pop" name="Commentar 1">
-        <LineContinue attribute="Comment" context="#stay"/>
-        <IncludeRules context="##Alerts" />
-      </context>
-
-      <context attribute="Comment" lineEndContext="#stay" name="Commentar 2">
-        <Detect2Chars attribute="Comment" context="#pop" char="*" char1="/" endRegion="Comment"/>
-        <IncludeRules context="##Alerts" />
-      </context>
-
-      <context attribute="Error" lineEndContext="#pop" name="AfterHash">
-        <!-- define, elif, else, endif, error, if, ifdef, ifndef, include, include_next, line, pragma, undef, warning -->
-        <RegExpr attribute="Preprocessor" context="Preprocessor" String="#\s*if(?:def|ndef)?(?=\s+\S)" insensitive="true" beginRegion="PP" firstNonSpace="true" />
-        <RegExpr attribute="Preprocessor" context="Preprocessor" String="#\s*endif" insensitive="true" endRegion="PP" firstNonSpace="true" />
-        <RegExpr attribute="Preprocessor" context="Define" String="#\s*define.*((?=\\))" insensitive="true" firstNonSpace="true" />
-        <RegExpr attribute="Preprocessor" context="Preprocessor" String="#\s*(?:el(?:se|if)|include(?:_next)?|define|undef|line|error|warning|pragma)" insensitive="true" firstNonSpace="true" />
-        <RegExpr attribute="Preprocessor" context="Preprocessor" String="#\s+[0-9]+" insensitive="true" firstNonSpace="true" />
-      </context>
-
-      <context attribute="Preprocessor" lineEndContext="#pop" name="Preprocessor">
-        <LineContinue attribute="Preprocessor" context="#stay"/>
-        <RangeDetect attribute="Prep. Lib" context="#stay" char="&quot;" char1="&quot;"/>
-        <RangeDetect attribute="Prep. Lib" context="#stay" char="&lt;" char1="&gt;"/>
-        <IncludeRules context="##Doxygen" />
-        <Detect2Chars attribute="Comment" context="Commentar/Preprocessor" char="/" char1="*" beginRegion="Comment2" />
-        <Detect2Chars attribute="Comment" context="Commentar 1" char="/" char1="/" />
-      </context>
-
-      <context attribute="Preprocessor" lineEndContext="#pop" name="Define">
-        <LineContinue attribute="Preprocessor" context="#stay"/>
-      </context>
-
-      <context attribute="Comment" lineEndContext="#stay" name="Commentar/Preprocessor">
-        <Detect2Chars attribute="Comment" context="#pop" char="*" char1="/" endRegion="Comment2" />
-      </context>
-
-      <context attribute="Comment" lineEndContext="#stay" name="Outscoped" >
-        <DetectSpaces />
-        <DetectIdentifier />
-        <DetectChar attribute="String" context="String" char="&quot;"/>
-        <Detect2Chars attribute="Comment" context="Commentar 1" char="/" char1="/"/>
-        <Detect2Chars attribute="Comment" context="Commentar 2" char="/" char1="*" beginRegion="Comment"/>
-        <RegExpr attribute="Comment" context="Outscoped intern" String="#\s*if" beginRegion="PP" firstNonSpace="true" />
-        <RegExpr attribute="Preprocessor" context="#pop" String="#\s*el(?:se|if)" firstNonSpace="true" />
-        <RegExpr attribute="Preprocessor" context="#pop" String="#\s*endif" endRegion="PP" firstNonSpace="true" />
-      </context>
-
-      <context attribute="Comment" lineEndContext="#stay" name="Outscoped intern">
-        <DetectSpaces />
-        <DetectIdentifier />
-        <DetectChar attribute="String" context="String" char="&quot;"/>
-        <Detect2Chars attribute="Comment" context="Commentar 1" char="/" char1="/"/>
-        <Detect2Chars attribute="Comment" context="Commentar 2" char="/" char1="*" beginRegion="Comment"/>
-        <RegExpr attribute="Comment" context="Outscoped intern" String="#\s*if" beginRegion="PP" firstNonSpace="true" />
-        <RegExpr attribute="Comment" context="#pop" String="#\s*endif" endRegion="PP" firstNonSpace="true" />
-      </context>
-    </contexts>
-    <itemDatas>
-      <itemData name="Normal Text"  defStyleNum="dsNormal" spellChecking="false"/>
-      <itemData name="Keyword"      defStyleNum="dsKeyword" spellChecking="false"/>
-      <itemData name="Data Type"    defStyleNum="dsDataType" spellChecking="false"/>
-      <itemData name="Decimal"      defStyleNum="dsDecVal" spellChecking="false"/>
-      <itemData name="Hex"          defStyleNum="dsBaseN" spellChecking="false"/>
-      <itemData name="Float"        defStyleNum="dsFloat" spellChecking="false"/>
-      <itemData name="String"       defStyleNum="dsString"/>
-      <itemData name="String Char"  defStyleNum="dsChar"/>
-      <itemData name="Comment"      defStyleNum="dsComment"/>
-      <itemData name="Symbol"       defStyleNum="dsNormal" spellChecking="false"/>
-      <itemData name="Preprocessor" defStyleNum="dsOthers" spellChecking="false"/>
-    </itemDatas>
-  </highlighting>
-  <general>
-    <comments>
-      <comment name="singleLine" start="//" />
-      <comment name="multiLine" start="/*" end="*/" />
-    </comments>
-    <keywords casesensitive="1" additionalDeliminator="'&quot;" />
-  </general>
-</language>
diff --git a/syntax/nano/README b/syntax/nano/README
deleted file mode 100644 (file)
index edd9d3f..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-To use the nano syntax highlighting install qc.nanorc somewhere and
-add:
-
-include /directory/qc.nanorc
-
-to your nanorc file located at ~/.nanorc. If the file doesn't exist
-create it.
-
-Optionally you can install it globally by installing qc.nanorc to
-/usr/share/nano
-
-However you still need to provide the include to your ~/.nanorc
diff --git a/syntax/nano/qc.nanorc b/syntax/nano/qc.nanorc
deleted file mode 100644 (file)
index 5222485..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-# Language:   QuakeC
-# Maintainer: Dale Weiler
-
-syntax "qc" "\.(qc|QC)$" "\.(qh|QH)$"
-color brightred "\<[A-Z_][0-9A-Z_]+\>" 
-color green "\<(float|string|enum|void|const|typedef|nil)\>"
-color brightyellow "\<(for|if|while|do|else|case|default|switch)\>"
-color magenta "\<(goto|continue|break|return)\>"
-color brightcyan "^[[:space:]]*#[[:space:]]*(define|include|(un|ifn?)def|endif|el(if|se)|if|warning|error|pragma)"
-color brightmagenta "'([^'\]|(\\["'abfnrtv\\]))'" "'\\(([0-3]?[0-7]{1,2}))'" "'\\x[0-9A-Fa-f]{1,2}'"
-
-color brightyellow "<[^=       ]*>" ""(\\.|[^"])*""
-
-## This string is VERY resource intensive!
-color brightyellow start=""(\\.|[^"])*\\[[:space:]]*$" end="^(\\.|[^"])*""
-
-## Comment highlighting
-color brightblue "//.*"
-color brightblue start="/\*" end="\*/"
-
-## Trailing whitespace
-color ,green "[[:space:]]+$"
diff --git a/test.c b/test.c
deleted file mode 100644 (file)
index 0e1a466..0000000
--- a/test.c
+++ /dev/null
@@ -1,1416 +0,0 @@
-/*
- * Copyright (C) 2012, 2013, 2014, 2015
- *     Dale Weiler
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
- * this software and associated documentation files (the "Software"), to deal in
- * the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is furnished to do
- * so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-#define GMQCC_PLATFORM_HEADER /* TODO: eliminate! */
-#include <stdlib.h>
-#include <string.h>
-
-#include "gmqcc.h"
-#include "platform.h"
-
-static const char *task_bins[] = {
-    "./gmqcc",
-    "./qcvm"
-};
-
-/*
- * TODO: Windows version
- * this implements a unique bi-directional popen-like function that
- * allows reading data from both stdout and stderr. And writing to
- * stdin :)
- *
- * Example of use:
- * FILE *handles[3] = task_popen("ls", "-l", "r");
- * if (!handles) { perror("failed to open stdin/stdout/stderr to ls");
- * // handles[0] = stdin
- * // handles[1] = stdout
- * // handles[2] = stderr
- *
- * task_pclose(handles); // to close
- */
-#ifndef _WIN32
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <dirent.h>
-#include <unistd.h>
-typedef struct {
-    fs_file_t *handles[3];
-    int        pipes  [3];
-
-    int stderr_fd;
-    int stdout_fd;
-    int pid;
-} popen_t;
-
-static fs_file_t **task_popen(const char *command, const char *mode) {
-    int     inhandle  [2];
-    int     outhandle [2];
-    int     errhandle [2];
-    int     trypipe;
-
-    popen_t *data = (popen_t*)mem_a(sizeof(popen_t));
-
-    /*
-     * Parse the command now into a list for execv, this is a pain
-     * in the ass.
-     */
-    char  *line = (char*)command;
-    char **argv = NULL;
-    {
-
-        while (*line != '\0') {
-            while (*line == ' ' || *line == '\t' || *line == '\n')
-                *line++ = '\0';
-            vec_push(argv, line);
-
-            while (*line != '\0' && *line != ' ' &&
-                   *line != '\t' && *line != '\n') line++;
-        }
-        vec_push(argv, (char *)0);
-    }
-
-
-    if ((trypipe = pipe(inhandle))  < 0) goto task_popen_error_0;
-    if ((trypipe = pipe(outhandle)) < 0) goto task_popen_error_1;
-    if ((trypipe = pipe(errhandle)) < 0) goto task_popen_error_2;
-
-    if ((data->pid = fork()) > 0) {
-        /* parent */
-        close(inhandle  [0]);
-        close(outhandle [1]);
-        close(errhandle [1]);
-
-        data->pipes  [0] = inhandle [1];
-        data->pipes  [1] = outhandle[0];
-        data->pipes  [2] = errhandle[0];
-
-        data->handles[0] = (fs_file_t*)fdopen(inhandle [1], "w");
-        data->handles[1] = (fs_file_t*)fdopen(outhandle[0], mode);
-        data->handles[2] = (fs_file_t*)fdopen(errhandle[0], mode);
-
-        /* sigh */
-        vec_free(argv);
-        return data->handles;
-    } else if (data->pid == 0) {
-        /* child */
-        close(inhandle [1]);
-        close(outhandle[0]);
-        close(errhandle[0]);
-
-        /* see piping documentation for this sillyness :P */
-        dup2(inhandle [0], 0);
-        dup2(outhandle[1], 1);
-        dup2(errhandle[1], 2);
-
-        execvp(*argv, argv);
-        exit(EXIT_FAILURE);
-    } else {
-        /* fork failed */
-        goto task_popen_error_3;
-    }
-
-task_popen_error_3: close(errhandle[0]), close(errhandle[1]);
-task_popen_error_2: close(outhandle[0]), close(outhandle[1]);
-task_popen_error_1: close(inhandle [0]), close(inhandle [1]);
-task_popen_error_0:
-
-    vec_free(argv);
-    return NULL;
-}
-
-static int task_pclose(fs_file_t **handles) {
-    popen_t *data   = (popen_t*)handles;
-    int      status = 0;
-
-    close(data->pipes[0]); /* stdin  */
-    close(data->pipes[1]); /* stdout */
-    close(data->pipes[2]); /* stderr */
-
-    waitpid(data->pid, &status, 0);
-
-    mem_d(data);
-
-    return status;
-}
-#else
-    #include <sys/stat.h>
-    typedef struct {
-        fs_file_t *handles[3];
-        char       name_err[L_tmpnam];
-        char       name_out[L_tmpnam];
-    } popen_t;
-
-    static fs_file_t **task_popen(const char *command, const char *mode) {
-        char    *cmd  = NULL;
-        popen_t *open = (popen_t*)mem_a(sizeof(popen_t));
-
-        tmpnam(open->name_err);
-        tmpnam(open->name_out);
-
-        (void)mode; /* excluded */
-
-        util_asprintf(&cmd, "%s -redirout=%s -redirerr=%s", command, open->name_out, open->name_err);
-
-        system(cmd); /* HACK */
-        open->handles[0] = NULL;
-        open->handles[1] = fs_file_open(open->name_out, "r");
-        open->handles[2] = fs_file_open(open->name_err, "r");
-
-        mem_d(cmd);
-
-        return open->handles;
-    }
-
-    static int task_pclose(fs_file_t **files) {
-        popen_t *open = ((popen_t*)files);
-
-        fs_file_close(files[1]);
-        fs_file_close(files[2]);
-
-        remove(open->name_err);
-        remove(open->name_out);
-
-        mem_d(open);
-
-        return EXIT_SUCCESS;
-    }
-#   define popen _popen
-#   define pclose _pclose
-#endif /*! _WIN32 */
-
-#define TASK_COMPILE    0
-#define TASK_EXECUTE    1
-/*
- * Task template system:
- *  templates are rules for a specific test, used to create a "task" that
- *  is executed with those set of rules (arguments, and what not). Tests
- *  that don't have a template with them cannot become tasks, since without
- *  the information for that test there is no way to properly "test" them.
- *  Rules for these templates are described in a template file, using a
- *  task template language.
- *
- *  The language is a basic finite statemachine, top-down single-line
- *  description language.
- *
- *  The languge is composed entierly of "tags" which describe a string of
- *  text for a task.  Think of it much like a configuration file.  Except
- *  it's been designed to allow flexibility and future support for prodecual
- *  semantics.
- *
- *  The following "tags" are suported by the language
- *
- *      D:
- *          Used to set a description of the current test, this must be
- *          provided, this tag is NOT optional.
- *
- *      T:
- *          Used to set the procedure for the given task, there are two
- *          options for this:
- *              -compile
- *                  This simply performs compilation only
- *              -execute
- *                  This will perform compilation and execution
- *              -fail
- *                  This will perform compilation, but requires
- *                  the compilation to fail in order to succeed.
- *
- *          This must be provided, this tag is NOT optional.
- *
- *      C:
- *          Used to set the compilation flags for the given task, this
- *          must be provided, this tag is NOT optional.
- *
- *      F:  Used to set some test suite flags, currently the only option
- *          is -no-defs (to including of defs.qh)
- *
- *      E:
- *          Used to set the execution flags for the given task. This tag
- *          must be provided if T == -execute, otherwise it's erroneous
- *          as compilation only takes place.
- *
- *      M:
- *          Used to describe a string of text that should be matched from
- *          the output of executing the task.  If this doesn't match the
- *          task fails.  This tag must be provided if T == -execute, otherwise
- *          it's erroneous as compilation only takes place.
- *
- *      I:
- *          Used to specify the INPUT source file to operate on, this must be
- *          provided, this tag is NOT optional
- *
- *
- *  Notes:
- *      These tags have one-time use, using them more than once will result
- *      in template compilation errors.
- *
- *      Lines beginning with # or // in the template file are comments and
- *      are ignored by the template parser.
- *
- *      Whitespace is optional, with exception to the colon ':' between the
- *      tag and it's assignment value/
- *
- *      The template compiler will detect erronrous tags (optional tags
- *      that need not be set), as well as missing tags, and error accordingly
- *      this will result in the task failing.
- */
-typedef struct {
-    char  *description;
-    char  *compileflags;
-    char  *executeflags;
-    char  *proceduretype;
-    char  *sourcefile;
-    char  *tempfilename;
-    char **comparematch;
-    char  *rulesfile;
-    char  *testflags;
-} task_template_t;
-
-/*
- * This is very much like a compiler code generator :-).  This generates
- * a value from some data observed from the compiler.
- */
-static bool task_template_generate(task_template_t *tmpl, char tag, const char *file, size_t line, char *value, size_t *pad) {
-    size_t desclen = 0;
-    size_t filelen = 0;
-    char **destval = NULL;
-
-    if (!tmpl)
-        return false;
-
-    switch(tag) {
-        case 'D': destval = &tmpl->description;    break;
-        case 'T': destval = &tmpl->proceduretype;  break;
-        case 'C': destval = &tmpl->compileflags;   break;
-        case 'E': destval = &tmpl->executeflags;   break;
-        case 'I': destval = &tmpl->sourcefile;     break;
-        case 'F': destval = &tmpl->testflags;      break;
-        default:
-            con_printmsg(LVL_ERROR, __FILE__, __LINE__, 0, "internal error",
-                "invalid tag `%c:` during code generation\n",
-                tag
-            );
-            return false;
-    }
-
-    /*
-     * Ensure if for the given tag, there already exists a
-     * assigned value.
-     */
-    if (*destval) {
-        con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "compile error",
-            "tag `%c:` already assigned value: %s\n",
-            tag, *destval
-        );
-        return false;
-    }
-
-    /*
-     * Strip any whitespace that might exist in the value for assignments
-     * like "D:      foo"
-     */
-    if (value && *value && (*value == ' ' || *value == '\t'))
-        value++;
-    else if (!value)
-        exit(EXIT_FAILURE);
-
-    /*
-     * Value will contain a newline character at the end, we need to strip
-     * this otherwise kaboom, seriously, kaboom :P
-     */
-    if (strchr(value, '\n'))
-        *strrchr(value, '\n')='\0';
-
-    /*
-     * Now allocate and set the actual value for the specific tag. Which
-     * was properly selected and can be accessed with *destval.
-     */
-    *destval = util_strdup(value);
-
-
-    if (*destval == tmpl->description) {
-        /*
-         * Create some padding for the description to align the
-         * printing of the rules file.
-         */
-        if ((desclen = strlen(tmpl->description)) > pad[0])
-            pad[0] = desclen;
-    }
-
-    if ((filelen = strlen(file)) > pad[2])
-        pad[2] = filelen;
-
-    return true;
-}
-
-static bool task_template_parse(const char *file, task_template_t *tmpl, fs_file_t *fp, size_t *pad) {
-    char  *data = NULL;
-    char  *back = NULL;
-    size_t size = 0;
-    size_t line = 1;
-
-    if (!tmpl)
-        return false;
-
-    /* top down parsing */
-    while (fs_file_getline(&back, &size, fp) != FS_FILE_EOF) {
-        /* skip whitespace */
-        data = back;
-        if (*data && (*data == ' ' || *data == '\t'))
-            data++;
-
-        switch (*data) {
-            /*
-             * Handle comments inside task tmpl files.  We're strict
-             * about the language for fun :-)
-             */
-            case '/':
-                if (data[1] != '/') {
-                    con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "tmpl parse error",
-                        "invalid character `/`, perhaps you meant `//` ?");
-
-                    mem_d(back);
-                    return false;
-                }
-            case '#':
-                break;
-
-            /*
-             * Empty newlines are acceptable as well, so we handle that here
-             * despite being just odd since there should't be that many
-             * empty lines to begin with.
-             */
-            case '\r':
-            case '\n':
-                break;
-
-
-            /*
-             * Now begin the actual "tag" stuff.  This works as you expect
-             * it to.
-             */
-            case 'D':
-            case 'T':
-            case 'C':
-            case 'E':
-            case 'I':
-            case 'F':
-                if (data[1] != ':') {
-                    con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "tmpl parse error",
-                        "expected `:` after `%c`",
-                        *data
-                    );
-                    goto failure;
-                }
-                if (!task_template_generate(tmpl, *data, file, line, &data[3], pad)) {
-                    con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "tmpl compile error",
-                        "failed to generate for given task\n"
-                    );
-                    goto failure;
-                }
-                break;
-
-            /*
-             * Match requires it's own system since we allow multiple M's
-             * for multi-line matching.
-             */
-            case 'M':
-            {
-                char *value = &data[3];
-                if (data[1] != ':') {
-                    con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "tmpl parse error",
-                        "expected `:` after `%c`",
-                        *data
-                    );
-                    goto failure;
-                }
-
-                /*
-                 * Value will contain a newline character at the end, we need to strip
-                 * this otherwise kaboom, seriously, kaboom :P
-                 */
-                if (strrchr(value, '\n'))
-                    *strrchr(value, '\n')='\0';
-                else /* cppcheck: possible null pointer dereference */
-                    exit(EXIT_FAILURE);
-
-                vec_push(tmpl->comparematch, util_strdup(value));
-
-                break;
-            }
-
-            default:
-                con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "tmpl parse error",
-                    "invalid tag `%c`", *data
-                );
-                goto failure;
-            /* no break required */
-        }
-
-        /* update line and free old sata */
-        line++;
-        mem_d(back);
-        back = NULL;
-    }
-    if (back)
-        mem_d(back);
-    return true;
-
-failure:
-    mem_d (back);
-    return false;
-}
-
-/*
- * Nullifies the template data: used during initialization of a new
- * template and free.
- */
-static void task_template_nullify(task_template_t *tmpl) {
-    if (!tmpl)
-        return;
-
-    tmpl->description    = NULL;
-    tmpl->proceduretype  = NULL;
-    tmpl->compileflags   = NULL;
-    tmpl->executeflags   = NULL;
-    tmpl->comparematch   = NULL;
-    tmpl->sourcefile     = NULL;
-    tmpl->tempfilename   = NULL;
-    tmpl->rulesfile      = NULL;
-    tmpl->testflags      = NULL;
-}
-
-static task_template_t *task_template_compile(const char *file, const char *dir, size_t *pad) {
-    /* a page should be enough */
-    char             fullfile[4096];
-    size_t           filepadd = 0;
-    fs_file_t       *tempfile = NULL;
-    task_template_t *tmpl     = NULL;
-
-    util_snprintf(fullfile,    sizeof(fullfile), "%s/%s", dir, file);
-
-    tempfile = fs_file_open(fullfile, "r");
-    tmpl     = (task_template_t*)mem_a(sizeof(task_template_t));
-    task_template_nullify(tmpl);
-
-    /*
-     * Create some padding for the printing to align the
-     * printing of the rules file to the console.
-     */
-    if ((filepadd = strlen(fullfile)) > pad[1])
-        pad[1] = filepadd;
-
-    tmpl->rulesfile = util_strdup(fullfile);
-
-    /*
-     * Esnure the file even exists for the task, this is pretty useless
-     * to even do.
-     */
-    if (!tempfile) {
-        con_err("template file: %s does not exist or invalid permissions\n",
-            file
-        );
-        goto failure;
-    }
-
-    if (!task_template_parse(file, tmpl, tempfile, pad)) {
-        con_err("template parse error: error during parsing\n");
-        goto failure;
-    }
-
-    /*
-     * Regardless procedure type, the following tags must exist:
-     *  D
-     *  T
-     *  C
-     *  I
-     */
-    if (!tmpl->description) {
-        con_err("template compile error: %s missing `D:` tag\n", file);
-        goto failure;
-    }
-    if (!tmpl->proceduretype) {
-        con_err("template compile error: %s missing `T:` tag\n", file);
-        goto failure;
-    }
-    if (!tmpl->compileflags) {
-        con_err("template compile error: %s missing `C:` tag\n", file);
-        goto failure;
-    }
-    if (!tmpl->sourcefile) {
-        con_err("template compile error: %s missing `I:` tag\n", file);
-        goto failure;
-    }
-
-    /*
-     * Now lets compile the template, compilation is really just
-     * the process of validating the input.
-     */
-    if (!strcmp(tmpl->proceduretype, "-compile")) {
-        if (tmpl->executeflags)
-            con_err("template compile warning: %s erroneous tag `E:` when only compiling\n", file);
-        if (tmpl->comparematch)
-            con_err("template compile warning: %s erroneous tag `M:` when only compiling\n", file);
-        goto success;
-    } else if (!strcmp(tmpl->proceduretype, "-execute")) {
-        if (!tmpl->executeflags) {
-            /* default to $null */
-            tmpl->executeflags = util_strdup("$null");
-        }
-        if (!tmpl->comparematch) {
-            con_err("template compile error: %s missing `M:` tag (use `$null` for exclude)\n", file);
-            goto failure;
-        }
-    } else if (!strcmp(tmpl->proceduretype, "-fail")) {
-        if (tmpl->executeflags)
-            con_err("template compile warning: %s erroneous tag `E:` when only failing\n", file);
-        if (tmpl->comparematch)
-            con_err("template compile warning: %s erroneous tag `M:` when only failing\n", file);
-    } else if (!strcmp(tmpl->proceduretype, "-diagnostic")) {
-        if (tmpl->executeflags)
-            con_err("template compile warning: %s erroneous tag `E:` when only diagnostic\n", file);
-        if (!tmpl->comparematch) {
-            con_err("template compile error: %s missing `M:` tag (use `$null` for exclude)\n", file);
-            goto failure;
-        }
-    } else if (!strcmp(tmpl->proceduretype, "-pp")) {
-        if (tmpl->executeflags)
-            con_err("template compile warning: %s erroneous tag `E:` when only preprocessing\n", file);
-        if (!tmpl->comparematch) {
-            con_err("template compile error: %s missing `M:` tag (use `$null` for exclude)\n", file);
-            goto failure;
-        }
-    } else {
-        con_err("template compile error: %s invalid procedure type: %s\n", file, tmpl->proceduretype);
-        goto failure;
-    }
-
-success:
-    fs_file_close(tempfile);
-    return tmpl;
-
-failure:
-    /*
-     * The file might not exist and we jump here when that doesn't happen
-     * so the check to see if it's not null here is required.
-     */
-    if (tempfile)
-        fs_file_close(tempfile);
-    mem_d (tmpl);
-
-    return NULL;
-}
-
-static void task_template_destroy(task_template_t *tmpl) {
-    if (!tmpl)
-        return;
-
-    if (tmpl->description)    mem_d(tmpl->description);
-    if (tmpl->proceduretype)  mem_d(tmpl->proceduretype);
-    if (tmpl->compileflags)   mem_d(tmpl->compileflags);
-    if (tmpl->executeflags)   mem_d(tmpl->executeflags);
-    if (tmpl->sourcefile)     mem_d(tmpl->sourcefile);
-    if (tmpl->rulesfile)      mem_d(tmpl->rulesfile);
-    if (tmpl->testflags)      mem_d(tmpl->testflags);
-
-    /*
-     * Delete all allocated string for task tmpl then destroy the
-     * main vector.
-     */
-    {
-        size_t i = 0;
-        for (; i < vec_size(tmpl->comparematch); i++)
-            mem_d(tmpl->comparematch[i]);
-
-        vec_free(tmpl->comparematch);
-    }
-
-    /*
-     * Nullify all the template members otherwise NULL comparision
-     * checks will fail if tmpl pointer is reused.
-     */
-    mem_d(tmpl->tempfilename);
-    mem_d(tmpl);
-}
-
-/*
- * Now comes the task manager, this system allows adding tasks in and out
- * of a task list.  This is the executor of the tasks essentially as well.
- */
-typedef struct {
-    task_template_t *tmpl;
-    fs_file_t       **runhandles;
-    fs_file_t       *stderrlog;
-    fs_file_t       *stdoutlog;
-    char            *stdoutlogfile;
-    char            *stderrlogfile;
-    bool             compiled;
-} task_t;
-
-static task_t *task_tasks = NULL;
-
-/*
- * Read a directory and searches for all template files in it
- * which is later used to run all tests.
- */
-static bool task_propagate(const char *curdir, size_t *pad, const char *defs) {
-    bool             success = true;
-    fs_dir_t        *dir;
-    fs_dirent_t     *files;
-    struct stat      directory;
-    char             buffer[4096];
-    size_t           found = 0;
-    char           **directories = NULL;
-    char            *claim = util_strdup(curdir);
-    size_t           i;
-
-    vec_push(directories, claim);
-    dir = fs_dir_open(claim);
-
-    /*
-     * Generate a list of subdirectories since we'll be checking them too
-     * for tmpl files.
-     */
-    while ((files = fs_dir_read(dir))) {
-        util_asprintf(&claim, "%s/%s", curdir, files->d_name);
-        if (stat(claim, &directory) == -1) {
-            fs_dir_close(dir);
-            mem_d(claim);
-            return false;
-        }
-
-        if (S_ISDIR(directory.st_mode) && files->d_name[0] != '.') {
-            vec_push(directories, claim);
-        } else {
-            mem_d(claim);
-            claim = NULL;
-        }
-    }
-    fs_dir_close(dir);
-
-    /*
-     * Now do all the work, by touching all the directories inside
-     * test as well and compile the task templates into data we can
-     * use to run the tests.
-     */
-    for (i = 0; i < vec_size(directories); i++) {
-        dir = fs_dir_open(directories[i]);
-
-        while ((files = fs_dir_read(dir))) {
-            util_snprintf(buffer, sizeof(buffer), "%s/%s", directories[i], files->d_name);
-            if (stat(buffer, &directory) == -1) {
-                con_err("internal error: stat failed, aborting\n");
-                abort();
-            }
-
-            if (S_ISDIR(directory.st_mode))
-                continue;
-
-            /*
-             * We made it here, which concludes the file/directory is not
-             * actually a directory, so it must be a file :)
-             */
-            if (strcmp(files->d_name + strlen(files->d_name) - 5, ".tmpl") == 0) {
-                task_template_t *tmpl = task_template_compile(files->d_name, directories[i], pad);
-                char             buf[4096]; /* one page should be enough */
-                const char      *qcflags = NULL;
-                task_t           task;
-
-                memset(&task, 0, sizeof(task));
-
-                found ++;
-                if (!tmpl) {
-                    con_err("error compiling task template: %s\n", files->d_name);
-                    success = false;
-                    continue;
-                }
-                /*
-                 * Generate a temportary file name for the output binary
-                 * so we don't trample over an existing one.
-                 */
-                tmpl->tempfilename = NULL;
-                util_asprintf(&tmpl->tempfilename, "%s/TMPDAT.%s.dat", directories[i], files->d_name);
-
-                /*
-                 * Additional QCFLAGS enviroment variable may be used
-                 * to test compile flags for all tests.  This needs to be
-                 * BEFORE other flags (so that the .tmpl can override them)
-                 */
-                qcflags = platform_getenv("QCFLAGS");
-
-                /*
-                 * Generate the command required to open a pipe to a process
-                 * which will be refered to with a handle in the task for
-                 * reading the data from the pipe.
-                 */
-                if (strcmp(tmpl->proceduretype, "-pp")) {
-                    if (qcflags) {
-                        if (tmpl->testflags && !strcmp(tmpl->testflags, "-no-defs")) {
-                            util_snprintf(buf, sizeof(buf), "%s %s/%s %s %s -o %s",
-                                task_bins[TASK_COMPILE],
-                                directories[i],
-                                tmpl->sourcefile,
-                                qcflags,
-                                tmpl->compileflags,
-                                tmpl->tempfilename
-                            );
-                        } else {
-                            util_snprintf(buf, sizeof(buf), "%s %s/%s %s/%s %s %s -o %s",
-                                task_bins[TASK_COMPILE],
-                                curdir,
-                                defs,
-                                directories[i],
-                                tmpl->sourcefile,
-                                qcflags,
-                                tmpl->compileflags,
-                                tmpl->tempfilename
-                            );
-                        }
-                    } else {
-                        if (tmpl->testflags && !strcmp(tmpl->testflags, "-no-defs")) {
-                            util_snprintf(buf, sizeof(buf), "%s %s/%s %s -o %s",
-                                task_bins[TASK_COMPILE],
-                                directories[i],
-                                tmpl->sourcefile,
-                                tmpl->compileflags,
-                                tmpl->tempfilename
-                            );
-                        } else {
-                            util_snprintf(buf, sizeof(buf), "%s %s/%s %s/%s %s -o %s",
-                                task_bins[TASK_COMPILE],
-                                curdir,
-                                defs,
-                                directories[i],
-                                tmpl->sourcefile,
-                                tmpl->compileflags,
-                                tmpl->tempfilename
-                            );
-                        }
-                    }
-                } else {
-                    /* Preprocessing (qcflags mean shit all here we don't allow them) */
-                    if (tmpl->testflags && !strcmp(tmpl->testflags, "-no-defs")) {
-                        util_snprintf(buf, sizeof(buf), "%s -E %s/%s %s -o %s",
-                            task_bins[TASK_COMPILE],
-                            directories[i],
-                            tmpl->sourcefile,
-                            tmpl->compileflags,
-                            tmpl->tempfilename
-                        );
-                    } else {
-                        util_snprintf(buf, sizeof(buf), "%s -E %s/%s %s/%s %s -o %s",
-                            task_bins[TASK_COMPILE],
-                            curdir,
-                            defs,
-                            directories[i],
-                            tmpl->sourcefile,
-                            tmpl->compileflags,
-                            tmpl->tempfilename
-                        );
-                    }
-                }
-
-                /*
-                 * The task template was compiled, now lets create a task from
-                 * the template data which has now been propagated.
-                 */
-                task.tmpl = tmpl;
-                if (!(task.runhandles = task_popen(buf, "r"))) {
-                    con_err("error opening pipe to process for test: %s\n", tmpl->description);
-                    success = false;
-                    continue;
-                }
-
-                /*
-                 * Open up some file desciptors for logging the stdout/stderr
-                 * to our own.
-                 */
-                util_snprintf(buf,  sizeof(buf), "%s.stdout", tmpl->tempfilename);
-                task.stdoutlogfile = util_strdup(buf);
-                if (!(task.stdoutlog     = fs_file_open(buf, "w"))) {
-                    con_err("error opening %s for stdout\n", buf);
-                    continue;
-                }
-
-                util_snprintf(buf,  sizeof(buf), "%s.stderr", tmpl->tempfilename);
-                task.stderrlogfile = util_strdup(buf);
-                if (!(task.stderrlog = fs_file_open(buf, "w"))) {
-                    con_err("error opening %s for stderr\n", buf);
-                    continue;
-                }
-
-                vec_push(task_tasks, task);
-            }
-        }
-
-        fs_dir_close(dir);
-        mem_d(directories[i]); /* free claimed memory */
-    }
-    vec_free(directories);
-
-    return success;
-}
-
-/*
- * Task precleanup removes any existing temporary files or log files
- * left behind from a previous invoke of the test-suite.
- */
-static void task_precleanup(const char *curdir) {
-    fs_dir_t     *dir;
-    fs_dirent_t  *files;
-    char          buffer[4096];
-
-    dir = fs_dir_open(curdir);
-
-    while ((files = fs_dir_read(dir))) {
-        if (strstr(files->d_name, "TMP")     ||
-            strstr(files->d_name, ".stdout") ||
-            strstr(files->d_name, ".stderr") ||
-            strstr(files->d_name, ".dat"))
-        {
-            util_snprintf(buffer, sizeof(buffer), "%s/%s", curdir, files->d_name);
-            if (remove(buffer))
-                con_err("error removing temporary file: %s\n", buffer);
-        }
-    }
-
-    fs_dir_close(dir);
-}
-
-static void task_destroy(void) {
-    /*
-     * Free all the data in the task list and finally the list itself
-     * then proceed to cleanup anything else outside the program like
-     * temporary files.
-     */
-    size_t i;
-    for (i = 0; i < vec_size(task_tasks); i++) {
-        /*
-         * Close any open handles to files or processes here.  It's mighty
-         * annoying to have to do all this cleanup work.
-         */
-        if (task_tasks[i].stdoutlog)  fs_file_close (task_tasks[i].stdoutlog);
-        if (task_tasks[i].stderrlog)  fs_file_close (task_tasks[i].stderrlog);
-
-        /*
-         * Only remove the log files if the test actually compiled otherwise
-         * forget about it (or if it didn't compile, and the procedure type
-         * was set to -fail (meaning it shouldn't compile) .. stil remove)
-         */
-        if (task_tasks[i].compiled || !strcmp(task_tasks[i].tmpl->proceduretype, "-fail")) {
-            if (remove(task_tasks[i].stdoutlogfile))
-                con_err("error removing stdout log file: %s\n", task_tasks[i].stdoutlogfile);
-            if (remove(task_tasks[i].stderrlogfile))
-                con_err("error removing stderr log file: %s\n", task_tasks[i].stderrlogfile);
-
-            (void)!remove(task_tasks[i].tmpl->tempfilename);
-        }
-
-        /* free util_strdup data for log files */
-        mem_d(task_tasks[i].stdoutlogfile);
-        mem_d(task_tasks[i].stderrlogfile);
-
-        task_template_destroy(task_tasks[i].tmpl);
-    }
-    vec_free(task_tasks);
-}
-
-/*
- * This executes the QCVM task for a specificly compiled progs.dat
- * using the template passed into it for call-flags and user defined
- * messages IF the procedure type is -execute, otherwise it matches
- * the preprocessor output.
- */
-static bool task_trymatch(size_t i, char ***line) {
-    bool             success = true;
-    bool             process = true;
-    int              retval  = EXIT_SUCCESS;
-    fs_file_t       *execute;
-    char             buffer[4096];
-    task_template_t *tmpl = task_tasks[i].tmpl;
-
-    memset  (buffer,0,sizeof(buffer));
-
-    if (!strcmp(tmpl->proceduretype, "-execute")) {
-        /*
-         * Drop the execution flags for the QCVM if none where
-         * actually specified.
-         */
-        if (!strcmp(tmpl->executeflags, "$null")) {
-            util_snprintf(buffer,  sizeof(buffer), "%s %s",
-                task_bins[TASK_EXECUTE],
-                tmpl->tempfilename
-            );
-        } else {
-            util_snprintf(buffer,  sizeof(buffer), "%s %s %s",
-                task_bins[TASK_EXECUTE],
-                tmpl->executeflags,
-                tmpl->tempfilename
-            );
-        }
-
-        execute = (fs_file_t*)popen(buffer, "r");
-        if (!execute)
-            return false;
-    } else if (!strcmp(tmpl->proceduretype, "-pp")) {
-        /*
-         * we're preprocessing, which means we need to read int
-         * the produced file and do some really weird shit.
-         */
-        if (!(execute = fs_file_open(tmpl->tempfilename, "r")))
-            return false;
-
-        process = false;
-    } else {
-        /*
-         * we're testing diagnostic output, which means it will be
-         * in runhandles[2] (stderr) since that is where the compiler
-         * puts it's errors.
-         */
-        if (!(execute = fs_file_open(task_tasks[i].stderrlogfile, "r")))
-            return false;
-
-        process = false;
-    }
-
-    /*
-     * Now lets read the lines and compare them to the matches we expect
-     * and handle accordingly.
-     */
-    {
-        char  *data    = NULL;
-        size_t size    = 0;
-        size_t compare = 0;
-
-        while (fs_file_getline(&data, &size, execute) != FS_FILE_EOF) {
-            if (!strcmp(data, "No main function found\n")) {
-                con_err("test failure: `%s` (No main function found) [%s]\n",
-                    tmpl->description,
-                    tmpl->rulesfile
-                );
-                if (!process)
-                    fs_file_close(execute);
-                else
-                    pclose((FILE*)execute);
-                return false;
-            }
-
-            /*
-             * Trim newlines from data since they will just break our
-             * ability to properly validate matches.
-             */
-            if  (strrchr(data, '\n'))
-                *strrchr(data, '\n') = '\0';
-
-            /*
-             * We remove the file/directory and stuff from the error
-             * match messages when testing diagnostics.
-             */
-            if(!strcmp(tmpl->proceduretype, "-diagnostic")) {
-                if (strstr(data, "there have been errors, bailing out"))
-                    continue; /* ignore it */
-                if (strstr(data, ": error: ")) {
-                    char *claim = util_strdup(data + (strstr(data, ": error: ") - data) + 9);
-                    mem_d(data);
-                    data = claim;
-                }
-            }
-
-            /*
-             * We need to ignore null lines for when -pp is used (preprocessor), since
-             * the preprocessor is likely to create empty newlines in certain macro
-             * instantations, otherwise it's in the wrong nature to ignore empty newlines.
-             */
-            if (!strcmp(tmpl->proceduretype, "-pp") && !*data)
-                continue;
-
-            if (vec_size(tmpl->comparematch) > compare) {
-                if (strcmp(data, tmpl->comparematch[compare++])) {
-                    success = false;
-                }
-            } else {
-                success = false;
-            }
-
-            /*
-             * Copy to output vector for diagnostics if execution match
-             * fails.
-             */
-            vec_push(*line, data);
-
-            /* reset */
-            data = NULL;
-            size = 0;
-        }
-
-        if (compare != vec_size(tmpl->comparematch))
-            success = false;
-
-        mem_d(data);
-        data = NULL;
-    }
-
-    if (process)
-        retval = pclose((FILE*)execute);
-    else
-        fs_file_close(execute);
-
-    return success && retval == EXIT_SUCCESS;
-}
-
-static const char *task_type(task_template_t *tmpl) {
-    if (!strcmp(tmpl->proceduretype, "-pp"))
-        return "type: preprocessor";
-    if (!strcmp(tmpl->proceduretype, "-execute"))
-        return "type: execution";
-    if (!strcmp(tmpl->proceduretype, "-compile"))
-        return "type: compile";
-    if (!strcmp(tmpl->proceduretype, "-diagnostic"))
-        return "type: diagnostic";
-    return "type: fail";
-}
-
-/*
- * This schedualizes all tasks and actually runs them individually
- * this is generally easy for just -compile variants.  For compile and
- * execution this takes more work since a task needs to be generated
- * from thin air and executed INLINE.
- */
-#include <math.h>
-static size_t task_schedualize(size_t *pad) {
-    char   space[2][64];
-    bool   execute  = false;
-    char  *data     = NULL;
-    char **match    = NULL;
-    size_t size     = 0;
-    size_t i        = 0;
-    size_t j        = 0;
-    size_t failed   = 0;
-    int    status   = 0;
-
-    util_snprintf(space[0], sizeof(space[0]), "%d", (int)vec_size(task_tasks));
-
-    for (; i < vec_size(task_tasks); i++) {
-        memset(space[1], 0, sizeof(space[1]));
-        util_snprintf(space[1], sizeof(space[1]), "%d", (int)(i + 1));
-
-        con_out("test #%u %*s", i + 1, strlen(space[0]) - strlen(space[1]), "");
-
-        /*
-         * Generate a task from thin air if it requires execution in
-         * the QCVM.
-         */
-
-        /* diagnostic is not executed, but compare tested instead, like preproessor */
-        execute = !! (!strcmp(task_tasks[i].tmpl->proceduretype, "-execute")) ||
-                     (!strcmp(task_tasks[i].tmpl->proceduretype, "-pp"))      ||
-                     (!strcmp(task_tasks[i].tmpl->proceduretype, "-diagnostic"));
-
-        /*
-         * We assume it compiled before we actually compiled :).  On error
-         * we change the value
-         */
-        task_tasks[i].compiled = true;
-
-        /*
-         * Read data from stdout first and pipe that stuff into a log file
-         * then we do the same for stderr.
-         */
-        while (fs_file_getline(&data, &size, task_tasks[i].runhandles[1]) != FS_FILE_EOF) {
-            fs_file_puts(task_tasks[i].stdoutlog, data);
-
-            if (strstr(data, "failed to open file")) {
-                task_tasks[i].compiled = false;
-                execute                = false;
-            }
-        }
-        while (fs_file_getline(&data, &size, task_tasks[i].runhandles[2]) != FS_FILE_EOF) {
-            /*
-             * If a string contains an error we just dissalow execution
-             * of it in the vm.
-             *
-             * TODO: make this more percise, e.g if we print a warning
-             * that refers to a variable named error, or something like
-             * that .. then this will blowup :P
-             */
-            if (strstr(data, "error") && strcmp(task_tasks[i].tmpl->proceduretype, "-diagnostic")) {
-                execute                = false;
-                task_tasks[i].compiled = false;
-            }
-
-            fs_file_puts (task_tasks[i].stderrlog, data);
-            fs_file_flush(task_tasks[i].stderrlog); /* fast flush for read */
-        }
-
-        if (!task_tasks[i].compiled && strcmp(task_tasks[i].tmpl->proceduretype, "-fail")) {
-            con_out("failure:   `%s` %*s %*s\n",
-                task_tasks[i].tmpl->description,
-                (pad[0] + pad[1] - strlen(task_tasks[i].tmpl->description)) + (strlen(task_tasks[i].tmpl->rulesfile) - pad[1]),
-                task_tasks[i].tmpl->rulesfile,
-                (pad[1] + pad[2] - strlen(task_tasks[i].tmpl->rulesfile)) + (strlen("(failed to compile)") - pad[2]),
-                "(failed to compile)"
-            );
-            failed++;
-            continue;
-        }
-
-        status = task_pclose(task_tasks[i].runhandles);
-        if ((!strcmp(task_tasks[i].tmpl->proceduretype, "-fail") && status == EXIT_SUCCESS)
-        ||  ( strcmp(task_tasks[i].tmpl->proceduretype, "-fail") && status == EXIT_FAILURE)) {
-            con_out("failure:   `%s` %*s %*s\n",
-                task_tasks[i].tmpl->description,
-                (pad[0] + pad[1] - strlen(task_tasks[i].tmpl->description)) + (strlen(task_tasks[i].tmpl->rulesfile) - pad[1]),
-                task_tasks[i].tmpl->rulesfile,
-                (pad[1] + pad[2] - strlen(task_tasks[i].tmpl->rulesfile)) + (strlen("(compiler didn't return exit success)") - pad[2]),
-                "(compiler didn't return exit success)"
-            );
-            failed++;
-            continue;
-        }
-
-        if (!execute) {
-            con_out("succeeded: `%s` %*s %*s\n",
-                task_tasks[i].tmpl->description,
-                (pad[0] + pad[1] - strlen(task_tasks[i].tmpl->description)) + (strlen(task_tasks[i].tmpl->rulesfile) - pad[1]),
-                task_tasks[i].tmpl->rulesfile,
-                (pad[1] + pad[2] - strlen(task_tasks[i].tmpl->rulesfile)) + (strlen(task_type(task_tasks[i].tmpl)) - pad[2]),
-                task_type(task_tasks[i].tmpl)
-
-            );
-            continue;
-        }
-
-        /*
-         * If we made it here that concludes the task is to be executed
-         * in the virtual machine (or the preprocessor output needs to
-         * be matched).
-         */
-        if (!task_trymatch(i, &match)) {
-            size_t d = 0;
-
-            con_out("failure:   `%s` %*s %*s\n",
-                task_tasks[i].tmpl->description,
-                (pad[0] + pad[1] - strlen(task_tasks[i].tmpl->description)) + (strlen(task_tasks[i].tmpl->rulesfile) - pad[1]),
-                task_tasks[i].tmpl->rulesfile,
-                (pad[1] + pad[2] - strlen(task_tasks[i].tmpl->rulesfile)) + (strlen(
-                    (strcmp(task_tasks[i].tmpl->proceduretype, "-pp"))
-                        ? "(invalid results from execution)"
-                        : (strcmp(task_tasks[i].tmpl->proceduretype, "-diagnostic"))
-                            ? "(invalid results from preprocessing)"
-                            : "(invalid results from compiler diagnsotics)"
-                ) - pad[2]),
-                (strcmp(task_tasks[i].tmpl->proceduretype, "-pp"))
-                    ? "(invalid results from execution)"
-                    : (strcmp(task_tasks[i].tmpl->proceduretype, "-diagnostic"))
-                            ? "(invalid results from preprocessing)"
-                            : "(invalid results from compiler diagnsotics)"
-            );
-
-            /*
-             * Print nicely formatted expected match lists to console error
-             * handler for the all the given matches in the template file and
-             * what was actually returned from executing.
-             */
-            con_out("    Expected From %u Matches: (got %u Matches)\n",
-                vec_size(task_tasks[i].tmpl->comparematch),
-                vec_size(match)
-            );
-            for (; d < vec_size(task_tasks[i].tmpl->comparematch); d++) {
-                char  *select = task_tasks[i].tmpl->comparematch[d];
-                size_t length = 60 - strlen(select);
-
-                con_out("        Expected: \"%s\"", select);
-                while (length --)
-                    con_out(" ");
-                con_out("| Got: \"%s\"\n", (d >= vec_size(match)) ? "<<nothing else to compare>>" : match[d]);
-            }
-
-            /*
-             * Print the non-expected out (since we are simply not expecting it)
-             * This will help track down bugs in template files that fail to match
-             * something.
-             */
-            if (vec_size(match) > vec_size(task_tasks[i].tmpl->comparematch)) {
-                for (d = 0; d < vec_size(match) - vec_size(task_tasks[i].tmpl->comparematch); d++) {
-                    con_out("        Expected: Nothing                                                       | Got: \"%s\"\n",
-                        match[d + vec_size(task_tasks[i].tmpl->comparematch)]
-                    );
-                }
-            }
-
-
-            for (j = 0; j < vec_size(match); j++)
-                mem_d(match[j]);
-            vec_free(match);
-            failed++;
-            continue;
-        }
-
-        for (j = 0; j < vec_size(match); j++)
-            mem_d(match[j]);
-        vec_free(match);
-
-        con_out("succeeded: `%s` %*s %*s\n",
-            task_tasks[i].tmpl->description,
-            (pad[0] + pad[1] - strlen(task_tasks[i].tmpl->description)) + (strlen(task_tasks[i].tmpl->rulesfile) - pad[1]),
-            task_tasks[i].tmpl->rulesfile,
-            (pad[1] + pad[2] - strlen(task_tasks[i].tmpl->rulesfile)) + (strlen(task_type(task_tasks[i].tmpl))- pad[2]),
-            task_type(task_tasks[i].tmpl)
-
-        );
-    }
-    mem_d(data);
-    return failed;
-}
-
-/*
- * This is the heart of the whole test-suite process.  This cleans up
- * any existing temporary files left behind as well as log files left
- * behind.  Then it propagates a list of tests from `curdir` by scaning
- * it for template files and compiling them into tasks, in which it
- * schedualizes them (executes them) and actually reports errors and
- * what not.  It then proceeds to destroy the tasks and return memory
- * it's the engine :)
- *
- * It returns true of tests could be propagated, otherwise it returns
- * false.
- *
- * It expects con_init() was called before hand.
- */
-static GMQCC_WARN bool test_perform(const char *curdir, const char *defs) {
-    size_t             failed       = false;
-    static const char *default_defs = "defs.qh";
-
-    size_t pad[] = {
-        /* test ### [succeed/fail]: `description`      [tests/template.tmpl]     [type] */
-                    0,                                 0,                        0
-    };
-
-    /*
-     * If the default definition file isn't set to anything.  We will
-     * use the default_defs here, which is "defs.qc"
-     */
-    if (!defs) {
-        defs = default_defs;
-    }
-
-
-    task_precleanup(curdir);
-    if (!task_propagate(curdir, pad, defs)) {
-        con_err("error: failed to propagate tasks\n");
-        task_destroy();
-        return false;
-    }
-    /*
-     * If we made it here all tasks where propagated from their resultant
-     * template file.  So we can start the FILO scheduler, this has been
-     * designed in the most thread-safe way possible for future threading
-     * it's designed to prevent lock contention, and possible syncronization
-     * issues.
-     */
-    failed = task_schedualize(pad);
-    if (failed)
-        con_out("%u out of %u tests failed\n", failed, vec_size(task_tasks));
-    task_destroy();
-
-    return (failed) ? false : true;
-}
-
-/*
- * Fancy GCC-like LONG parsing allows things like --opt=param with
- * assignment operator.  This is used for redirecting stdout/stderr
- * console to specific files of your choice.
- */
-static bool parsecmd(const char *optname, int *argc_, char ***argv_, char **out, int ds, bool split) {
-    int  argc   = *argc_;
-    char **argv = *argv_;
-
-    size_t len = strlen(optname);
-
-    if (strncmp(argv[0]+ds, optname, len))
-        return false;
-
-    /* it's --optname, check how the parameter is supplied */
-    if (argv[0][ds+len] == '=') {
-        *out = argv[0]+ds+len+1;
-        return true;
-    }
-
-    if (!split || argc < ds) /* no parameter was provided, or only single-arg form accepted */
-        return false;
-
-    /* using --opt param */
-    *out = argv[1];
-    --*argc_;
-    ++*argv_;
-    return true;
-}
-
-int main(int argc, char **argv) {
-    bool          succeed  = false;
-    char         *redirout = (char*)stdout;
-    char         *redirerr = (char*)stderr;
-    char         *defs     = NULL;
-
-    con_init();
-    OPTS_OPTION_U16(OPTION_MEMDUMPCOLS) = 16;
-
-    /*
-     * Command line option parsing commences now We only need to support
-     * a few things in the test suite.
-     */
-    while (argc > 1) {
-        ++argv;
-        --argc;
-
-        if (argv[0][0] == '-') {
-            if (parsecmd("redirout", &argc, &argv, &redirout, 1, false))
-                continue;
-            if (parsecmd("redirerr", &argc, &argv, &redirerr, 1, false))
-                continue;
-            if (parsecmd("defs",     &argc, &argv, &defs,     1, false))
-                continue;
-
-            con_change(redirout, redirerr);
-
-            if (!strcmp(argv[0]+1, "debug")) {
-                OPTS_OPTION_BOOL(OPTION_DEBUG) = true;
-                continue;
-            }
-            if (!strcmp(argv[0]+1, "memchk")) {
-                OPTS_OPTION_BOOL(OPTION_MEMCHK) = true;
-                continue;
-            }
-            if (!strcmp(argv[0]+1, "nocolor")) {
-                con_color(0);
-                continue;
-            }
-
-            con_err("invalid argument %s\n", argv[0]+1);
-            return -1;
-        }
-    }
-    con_change(redirout, redirerr);
-    succeed = test_perform("tests", defs);
-    stat_info();
-
-    return (succeed) ? EXIT_SUCCESS : EXIT_FAILURE;
-}
diff --git a/test.cpp b/test.cpp
new file mode 100644 (file)
index 0000000..2614c7f
--- /dev/null
+++ b/test.cpp
@@ -0,0 +1,1305 @@
+#include <vector>
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+
+#include <dirent.h>
+#include <unistd.h>
+
+#include "gmqcc.h"
+
+static const char *task_bins[] = {
+    "./gmqcc",
+    "./qcvm"
+};
+
+struct popen_t {
+    FILE *handles[3];
+    int pipes[3];
+    int stderr_fd;
+    int stdout_fd;
+    int pid;
+};
+
+static FILE **task_popen(const char *command, const char *mode) {
+    int     inhandle  [2];
+    int     outhandle [2];
+    int     errhandle [2];
+    int     trypipe;
+
+    popen_t *data = (popen_t*)mem_a(sizeof(popen_t));
+
+    char *line = (char*)command;
+    std::vector<char *> argv;
+    {
+
+        while (*line != '\0') {
+            while (*line == ' ' || *line == '\t' || *line == '\n')
+                *line++ = '\0';
+            argv.push_back(line);
+            while (*line != '\0' && *line != ' ' &&
+                   *line != '\t' && *line != '\n') line++;
+        }
+        argv.push_back((char *)0);
+    }
+
+    if ((trypipe = pipe(inhandle))  < 0) goto task_popen_error_0;
+    if ((trypipe = pipe(outhandle)) < 0) goto task_popen_error_1;
+    if ((trypipe = pipe(errhandle)) < 0) goto task_popen_error_2;
+
+    if ((data->pid = fork()) > 0) {
+        /* parent */
+        close(inhandle [0]);
+        close(outhandle [1]);
+        close(errhandle [1]);
+        data->pipes[0] = inhandle [1];
+        data->pipes[1] = outhandle[0];
+        data->pipes[2] = errhandle[0];
+        data->handles[0] = fdopen(inhandle [1], "w");
+        data->handles[1] = fdopen(outhandle[0], mode);
+        data->handles[2] = fdopen(errhandle[0], mode);
+        return data->handles;
+    } else if (data->pid == 0) {
+        /* child */
+        close(inhandle [1]);
+        close(outhandle[0]);
+        close(errhandle[0]);
+
+        /* see piping documentation for this sillyness :P */
+        dup2(inhandle [0], 0);
+        dup2(outhandle[1], 1);
+        dup2(errhandle[1], 2);
+
+        execvp(argv[0], &argv[0]);
+        exit(95);
+    } else {
+        /* fork failed */
+        goto task_popen_error_3;
+    }
+
+task_popen_error_3: close(errhandle[0]), close(errhandle[1]);
+task_popen_error_2: close(outhandle[0]), close(outhandle[1]);
+task_popen_error_1: close(inhandle [0]), close(inhandle [1]);
+task_popen_error_0:
+
+    return nullptr;
+}
+
+static int task_pclose(FILE **handles) {
+    popen_t *data   = (popen_t*)handles;
+    int      status = 0;
+
+    close(data->pipes[0]); /* stdin  */
+    close(data->pipes[1]); /* stdout */
+    close(data->pipes[2]); /* stderr */
+
+    if (data->pid != waitpid(data->pid, &status, 0)) {
+      abort();
+    }
+    if (!WIFEXITED(status))
+      return -1;
+    if (WIFSIGNALED(status))
+      con_out("got signaled!\n");
+
+    mem_d(data);
+
+    return status ? 1 : 0;
+}
+
+#define TASK_COMPILE    0
+#define TASK_EXECUTE    1
+/*
+ * Task template system:
+ *  templates are rules for a specific test, used to create a "task" that
+ *  is executed with those set of rules (arguments, and what not). Tests
+ *  that don't have a template with them cannot become tasks, since without
+ *  the information for that test there is no way to properly "test" them.
+ *  Rules for these templates are described in a template file, using a
+ *  task template language.
+ *
+ *  The language is a basic finite statemachine, top-down single-line
+ *  description language.
+ *
+ *  The languge is composed entierly of "tags" which describe a string of
+ *  text for a task.  Think of it much like a configuration file.  Except
+ *  it's been designed to allow flexibility and future support for prodecual
+ *  semantics.
+ *
+ *  The following "tags" are suported by the language
+ *
+ *      D:
+ *          Used to set a description of the current test, this must be
+ *          provided, this tag is NOT optional.
+ *
+ *      T:
+ *          Used to set the procedure for the given task, there are two
+ *          options for this:
+ *              -compile
+ *                  This simply performs compilation only
+ *              -execute
+ *                  This will perform compilation and execution
+ *              -fail
+ *                  This will perform compilation, but requires
+ *                  the compilation to fail in order to succeed.
+ *
+ *          This must be provided, this tag is NOT optional.
+ *
+ *      C:
+ *          Used to set the compilation flags for the given task, this
+ *          must be provided, this tag is NOT optional.
+ *
+ *      F:  Used to set some test suite flags, currently the only option
+ *          is -no-defs (to including of defs.qh)
+ *
+ *      E:
+ *          Used to set the execution flags for the given task. This tag
+ *          must be provided if T == -execute, otherwise it's erroneous
+ *          as compilation only takes place.
+ *
+ *      M:
+ *          Used to describe a string of text that should be matched from
+ *          the output of executing the task.  If this doesn't match the
+ *          task fails.  This tag must be provided if T == -execute, otherwise
+ *          it's erroneous as compilation only takes place.
+ *
+ *      I:
+ *          Used to specify the INPUT source file to operate on, this must be
+ *          provided, this tag is NOT optional
+ *
+ *
+ *  Notes:
+ *      These tags have one-time use, using them more than once will result
+ *      in template compilation errors.
+ *
+ *      Lines beginning with # or // in the template file are comments and
+ *      are ignored by the template parser.
+ *
+ *      Whitespace is optional, with exception to the colon ':' between the
+ *      tag and it's assignment value/
+ *
+ *      The template compiler will detect erronrous tags (optional tags
+ *      that need not be set), as well as missing tags, and error accordingly
+ *      this will result in the task failing.
+ */
+struct task_template_t {
+    char *description;
+    char *compileflags;
+    char *executeflags;
+    char *proceduretype;
+    char *sourcefile;
+    char *tempfilename;
+    std::vector<char *> comparematch;
+    char *rulesfile;
+    char *testflags;
+};
+
+/*
+ * This is very much like a compiler code generator :-).  This generates
+ * a value from some data observed from the compiler.
+ */
+static bool task_template_generate(task_template_t *tmpl, char tag, const char *file, size_t line, char *value, size_t *pad) {
+    size_t desclen = 0;
+    size_t filelen = 0;
+    char **destval = nullptr;
+
+    if (!tmpl)
+        return false;
+
+    switch(tag) {
+        case 'D': destval = &tmpl->description;    break;
+        case 'T': destval = &tmpl->proceduretype;  break;
+        case 'C': destval = &tmpl->compileflags;   break;
+        case 'E': destval = &tmpl->executeflags;   break;
+        case 'I': destval = &tmpl->sourcefile;     break;
+        case 'F': destval = &tmpl->testflags;      break;
+        default:
+            con_printmsg(LVL_ERROR, __FILE__, __LINE__, 0, "internal error",
+                "invalid tag `%c:` during code generation\n",
+                tag
+            );
+            return false;
+    }
+
+    /*
+     * Ensure if for the given tag, there already exists a
+     * assigned value.
+     */
+    if (*destval) {
+        con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "compile error",
+            "tag `%c:` already assigned value: %s\n",
+            tag, *destval
+        );
+        return false;
+    }
+
+    /*
+     * Strip any whitespace that might exist in the value for assignments
+     * like "D:      foo"
+     */
+    if (value && *value && (*value == ' ' || *value == '\t'))
+        value++;
+    else if (!value)
+        exit(EXIT_FAILURE);
+
+    /*
+     * Value will contain a newline character at the end, we need to strip
+     * this otherwise kaboom, seriously, kaboom :P
+     */
+    if (strchr(value, '\n'))
+        *strrchr(value, '\n')='\0';
+
+    /*
+     * Now allocate and set the actual value for the specific tag. Which
+     * was properly selected and can be accessed with *destval.
+     */
+    *destval = util_strdup(value);
+
+
+    if (*destval == tmpl->description) {
+        /*
+         * Create some padding for the description to align the
+         * printing of the rules file.
+         */
+        if ((desclen = strlen(tmpl->description)) > pad[0])
+            pad[0] = desclen;
+    }
+
+    if ((filelen = strlen(file)) > pad[2])
+        pad[2] = filelen;
+
+    return true;
+}
+
+static bool task_template_parse(const char *file, task_template_t *tmpl, FILE *fp, size_t *pad) {
+    char  *data = nullptr;
+    char  *back = nullptr;
+    size_t size = 0;
+    size_t line = 1;
+
+    if (!tmpl)
+        return false;
+
+    /* top down parsing */
+    while (util_getline(&back, &size, fp) != EOF) {
+        /* skip whitespace */
+        data = back;
+        if (*data && (*data == ' ' || *data == '\t'))
+            data++;
+
+        switch (*data) {
+            /*
+             * Handle comments inside task tmpl files.  We're strict
+             * about the language for fun :-)
+             */
+            case '/':
+                if (data[1] != '/') {
+                    con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "tmpl parse error",
+                        "invalid character `/`, perhaps you meant `//` ?");
+
+                    mem_d(back);
+                    return false;
+                }
+            case '#':
+                break;
+
+            /*
+             * Empty newlines are acceptable as well, so we handle that here
+             * despite being just odd since there should't be that many
+             * empty lines to begin with.
+             */
+            case '\r':
+            case '\n':
+                break;
+
+
+            /*
+             * Now begin the actual "tag" stuff.  This works as you expect
+             * it to.
+             */
+            case 'D':
+            case 'T':
+            case 'C':
+            case 'E':
+            case 'I':
+            case 'F':
+                if (data[1] != ':') {
+                    con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "tmpl parse error",
+                        "expected `:` after `%c`",
+                        *data
+                    );
+                    goto failure;
+                }
+                if (!task_template_generate(tmpl, *data, file, line, &data[3], pad)) {
+                    con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "tmpl compile error",
+                        "failed to generate for given task\n"
+                    );
+                    goto failure;
+                }
+                break;
+
+            /*
+             * Match requires it's own system since we allow multiple M's
+             * for multi-line matching.
+             */
+            case 'M':
+            {
+                char *value = &data[3];
+                if (data[1] != ':') {
+                    con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "tmpl parse error",
+                        "expected `:` after `%c`",
+                        *data
+                    );
+                    goto failure;
+                }
+
+                /*
+                 * Value will contain a newline character at the end, we need to strip
+                 * this otherwise kaboom, seriously, kaboom :P
+                 */
+                if (strrchr(value, '\n'))
+                    *strrchr(value, '\n')='\0';
+                else /* cppcheck: possible null pointer dereference */
+                    exit(EXIT_FAILURE);
+
+                tmpl->comparematch.push_back(util_strdup(value));
+
+                break;
+            }
+
+            default:
+                con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "tmpl parse error",
+                    "invalid tag `%c`", *data
+                );
+                goto failure;
+            /* no break required */
+        }
+
+        /* update line and free old sata */
+        line++;
+        mem_d(back);
+        back = nullptr;
+    }
+    if (back)
+        mem_d(back);
+    return true;
+
+failure:
+    mem_d (back);
+    return false;
+}
+
+/*
+ * Nullifies the template data: used during initialization of a new
+ * template and free.
+ */
+static void task_template_nullify(task_template_t *tmpl) {
+    if (!tmpl)
+        return;
+
+    tmpl->description = nullptr;
+    tmpl->proceduretype = nullptr;
+    tmpl->compileflags = nullptr;
+    tmpl->executeflags = nullptr;
+    tmpl->sourcefile = nullptr;
+    tmpl->tempfilename = nullptr;
+    tmpl->rulesfile = nullptr;
+    tmpl->testflags = nullptr;
+}
+
+static task_template_t *task_template_compile(const char *file, const char *dir, size_t *pad) {
+    /* a page should be enough */
+    char             fullfile[4096];
+    size_t           filepadd = 0;
+    FILE       *tempfile = nullptr;
+    task_template_t *tmpl     = nullptr;
+
+    util_snprintf(fullfile, sizeof(fullfile), "%s/%s", dir, file);
+
+    tempfile = fopen(fullfile, "r");
+    tmpl = (task_template_t*)mem_a(sizeof(task_template_t));
+    new (tmpl) task_template_t();
+    task_template_nullify(tmpl);
+
+    /*
+     * Create some padding for the printing to align the
+     * printing of the rules file to the console.
+     */
+    if ((filepadd = strlen(fullfile)) > pad[1])
+        pad[1] = filepadd;
+
+    tmpl->rulesfile = util_strdup(fullfile);
+
+    /*
+     * Esnure the file even exists for the task, this is pretty useless
+     * to even do.
+     */
+    if (!tempfile) {
+        con_err("template file: %s does not exist or invalid permissions\n",
+            file
+        );
+        goto failure;
+    }
+
+    if (!task_template_parse(file, tmpl, tempfile, pad)) {
+        con_err("template parse error: error during parsing\n");
+        goto failure;
+    }
+
+    /*
+     * Regardless procedure type, the following tags must exist:
+     *  D
+     *  T
+     *  C
+     *  I
+     */
+    if (!tmpl->description) {
+        con_err("template compile error: %s missing `D:` tag\n", file);
+        goto failure;
+    }
+    if (!tmpl->proceduretype) {
+        con_err("template compile error: %s missing `T:` tag\n", file);
+        goto failure;
+    }
+    if (!tmpl->compileflags) {
+        con_err("template compile error: %s missing `C:` tag\n", file);
+        goto failure;
+    }
+    if (!tmpl->sourcefile) {
+        con_err("template compile error: %s missing `I:` tag\n", file);
+        goto failure;
+    }
+
+    /*
+     * Now lets compile the template, compilation is really just
+     * the process of validating the input.
+     */
+    if (!strcmp(tmpl->proceduretype, "-compile")) {
+        if (tmpl->executeflags)
+            con_err("template compile warning: %s erroneous tag `E:` when only compiling\n", file);
+        if (tmpl->comparematch.size())
+            con_err("template compile warning: %s erroneous tag `M:` when only compiling\n", file);
+        goto success;
+    } else if (!strcmp(tmpl->proceduretype, "-execute")) {
+        if (!tmpl->executeflags) {
+            /* default to $null */
+            tmpl->executeflags = util_strdup("$null");
+        }
+        if (tmpl->comparematch.empty()) {
+            con_err("template compile error: %s missing `M:` tag (use `$null` for exclude)\n", file);
+            goto failure;
+        }
+    } else if (!strcmp(tmpl->proceduretype, "-fail")) {
+        if (tmpl->executeflags)
+            con_err("template compile warning: %s erroneous tag `E:` when only failing\n", file);
+        if (tmpl->comparematch.size())
+            con_err("template compile warning: %s erroneous tag `M:` when only failing\n", file);
+    } else if (!strcmp(tmpl->proceduretype, "-diagnostic")) {
+        if (tmpl->executeflags)
+            con_err("template compile warning: %s erroneous tag `E:` when only diagnostic\n", file);
+        if (tmpl->comparematch.empty()) {
+            con_err("template compile error: %s missing `M:` tag (use `$null` for exclude)\n", file);
+            goto failure;
+        }
+    } else if (!strcmp(tmpl->proceduretype, "-pp")) {
+        if (tmpl->executeflags)
+            con_err("template compile warning: %s erroneous tag `E:` when only preprocessing\n", file);
+        if (tmpl->comparematch.empty()) {
+            con_err("template compile error: %s missing `M:` tag (use `$null` for exclude)\n", file);
+            goto failure;
+        }
+    } else {
+        con_err("template compile error: %s invalid procedure type: %s\n", file, tmpl->proceduretype);
+        goto failure;
+    }
+
+success:
+    fclose(tempfile);
+    return tmpl;
+
+failure:
+    /*
+     * The file might not exist and we jump here when that doesn't happen
+     * so the check to see if it's not null here is required.
+     */
+    if (tempfile)
+        fclose(tempfile);
+    mem_d(tmpl);
+
+    return nullptr;
+}
+
+static void task_template_destroy(task_template_t *tmpl) {
+    if (!tmpl)
+        return;
+
+    if (tmpl->description)    mem_d(tmpl->description);
+    if (tmpl->proceduretype)  mem_d(tmpl->proceduretype);
+    if (tmpl->compileflags)   mem_d(tmpl->compileflags);
+    if (tmpl->executeflags)   mem_d(tmpl->executeflags);
+    if (tmpl->sourcefile)     mem_d(tmpl->sourcefile);
+    if (tmpl->rulesfile)      mem_d(tmpl->rulesfile);
+    if (tmpl->testflags)      mem_d(tmpl->testflags);
+
+
+    for (auto &it : tmpl->comparematch)
+        mem_d(it);
+
+    /*
+     * Nullify all the template members otherwise nullptr comparision
+     * checks will fail if tmpl pointer is reused.
+     */
+    mem_d(tmpl->tempfilename);
+    mem_d(tmpl);
+}
+
+/*
+ * Now comes the task manager, this system allows adding tasks in and out
+ * of a task list.  This is the executor of the tasks essentially as well.
+ */
+struct task_t {
+    task_template_t *tmpl;
+    FILE **runhandles;
+    FILE *stderrlog;
+    FILE *stdoutlog;
+    char *stdoutlogfile;
+    char *stderrlogfile;
+    bool compiled;
+};
+
+static std::vector<task_t> task_tasks;
+
+/*
+ * Read a directory and searches for all template files in it
+ * which is later used to run all tests.
+ */
+static bool task_propagate(const char *curdir, size_t *pad, const char *defs) {
+    bool  success = true;
+    DIR *dir;
+    struct dirent *files;
+    struct stat directory;
+    char buffer[4096];
+    size_t found = 0;
+    std::vector<char *> directories;
+    char *claim = util_strdup(curdir);
+
+    directories.push_back(claim);
+    dir = opendir(claim);
+
+    /*
+     * Generate a list of subdirectories since we'll be checking them too
+     * for tmpl files.
+     */
+    while ((files = readdir(dir))) {
+        util_asprintf(&claim, "%s/%s", curdir, files->d_name);
+        if (stat(claim, &directory) == -1) {
+            closedir(dir);
+            mem_d(claim);
+            return false;
+        }
+
+        if (S_ISDIR(directory.st_mode) && files->d_name[0] != '.') {
+            directories.push_back(claim);
+        } else {
+            mem_d(claim);
+            claim = nullptr;
+        }
+    }
+    closedir(dir);
+
+    /*
+     * Now do all the work, by touching all the directories inside
+     * test as well and compile the task templates into data we can
+     * use to run the tests.
+     */
+    for (auto &it : directories) {
+        dir = opendir(it);
+        while ((files = readdir(dir))) {
+            util_snprintf(buffer, sizeof(buffer), "%s/%s", it, files->d_name);
+            if (stat(buffer, &directory) == -1) {
+                con_err("internal error: stat failed, aborting\n");
+                abort();
+            }
+
+            if (S_ISDIR(directory.st_mode))
+                continue;
+
+            /*
+             * We made it here, which concludes the file/directory is not
+             * actually a directory, so it must be a file :)
+             */
+            if (strcmp(files->d_name + strlen(files->d_name) - 5, ".tmpl") == 0) {
+                task_template_t *tmpl = task_template_compile(files->d_name, it, pad);
+                char             buf[4096]; /* one page should be enough */
+                const char      *qcflags = nullptr;
+                task_t           task;
+
+                memset(&task, 0, sizeof(task));
+
+                found ++;
+                if (!tmpl) {
+                    con_err("error compiling task template: %s\n", files->d_name);
+                    success = false;
+                    continue;
+                }
+                /*
+                 * Generate a temportary file name for the output binary
+                 * so we don't trample over an existing one.
+                 */
+                tmpl->tempfilename = nullptr;
+                util_asprintf(&tmpl->tempfilename, "%s/TMPDAT.%s.dat", it, files->d_name);
+
+                /*
+                 * Additional QCFLAGS enviroment variable may be used
+                 * to test compile flags for all tests.  This needs to be
+                 * BEFORE other flags (so that the .tmpl can override them)
+                 */
+                qcflags = getenv("QCFLAGS");
+
+                /*
+                 * Generate the command required to open a pipe to a process
+                 * which will be refered to with a handle in the task for
+                 * reading the data from the pipe.
+                 */
+                if (strcmp(tmpl->proceduretype, "-pp")) {
+                    if (qcflags) {
+                        if (tmpl->testflags && !strcmp(tmpl->testflags, "-no-defs")) {
+                            util_snprintf(buf, sizeof(buf), "%s %s/%s %s %s -o %s",
+                                task_bins[TASK_COMPILE],
+                                it,
+                                tmpl->sourcefile,
+                                qcflags,
+                                tmpl->compileflags,
+                                tmpl->tempfilename
+                            );
+                        } else {
+                            util_snprintf(buf, sizeof(buf), "%s %s/%s %s/%s %s %s -o %s",
+                                task_bins[TASK_COMPILE],
+                                curdir,
+                                defs,
+                                it,
+                                tmpl->sourcefile,
+                                qcflags,
+                                tmpl->compileflags,
+                                tmpl->tempfilename
+                            );
+                        }
+                    } else {
+                        if (tmpl->testflags && !strcmp(tmpl->testflags, "-no-defs")) {
+                            util_snprintf(buf, sizeof(buf), "%s %s/%s %s -o %s",
+                                task_bins[TASK_COMPILE],
+                                it,
+                                tmpl->sourcefile,
+                                tmpl->compileflags,
+                                tmpl->tempfilename
+                            );
+                        } else {
+                            util_snprintf(buf, sizeof(buf), "%s %s/%s %s/%s %s -o %s",
+                                task_bins[TASK_COMPILE],
+                                curdir,
+                                defs,
+                                it,
+                                tmpl->sourcefile,
+                                tmpl->compileflags,
+                                tmpl->tempfilename
+                            );
+                        }
+                    }
+                } else {
+                    /* Preprocessing (qcflags mean shit all here we don't allow them) */
+                    if (tmpl->testflags && !strcmp(tmpl->testflags, "-no-defs")) {
+                        util_snprintf(buf, sizeof(buf), "%s -E %s/%s %s -o %s",
+                            task_bins[TASK_COMPILE],
+                            it,
+                            tmpl->sourcefile,
+                            tmpl->compileflags,
+                            tmpl->tempfilename
+                        );
+                    } else {
+                        util_snprintf(buf, sizeof(buf), "%s -E %s/%s %s/%s %s -o %s",
+                            task_bins[TASK_COMPILE],
+                            curdir,
+                            defs,
+                            it,
+                            tmpl->sourcefile,
+                            tmpl->compileflags,
+                            tmpl->tempfilename
+                        );
+                    }
+                }
+
+                /*
+                 * The task template was compiled, now lets create a task from
+                 * the template data which has now been propagated.
+                 */
+                task.tmpl = tmpl;
+                if (!(task.runhandles = task_popen(buf, "r"))) {
+                    con_err("error opening pipe to process for test: %s\n", tmpl->description);
+                    success = false;
+                    continue;
+                }
+
+                /*
+                 * Open up some file desciptors for logging the stdout/stderr
+                 * to our own.
+                 */
+                util_snprintf(buf, sizeof(buf), "%s.stdout", tmpl->tempfilename);
+                task.stdoutlogfile = util_strdup(buf);
+                if (!(task.stdoutlog = fopen(buf, "w"))) {
+                    con_err("error opening %s for stdout\n", buf);
+                    continue;
+                }
+
+                util_snprintf(buf, sizeof(buf), "%s.stderr", tmpl->tempfilename);
+                task.stderrlogfile = util_strdup(buf);
+                if (!(task.stderrlog = fopen(buf, "w"))) {
+                    con_err("error opening %s for stderr\n", buf);
+                    continue;
+                }
+                task_tasks.push_back(task);
+            }
+        }
+        closedir(dir);
+        mem_d(it); /* free claimed memory */
+    }
+    return success;
+}
+
+/*
+ * Task precleanup removes any existing temporary files or log files
+ * left behind from a previous invoke of the test-suite.
+ */
+static void task_precleanup(const char *curdir) {
+    DIR     *dir;
+    struct dirent  *files;
+    char          buffer[4096];
+
+    dir = opendir(curdir);
+
+    while ((files = readdir(dir))) {
+        if (strstr(files->d_name, "TMP")     ||
+            strstr(files->d_name, ".stdout") ||
+            strstr(files->d_name, ".stderr") ||
+            strstr(files->d_name, ".dat"))
+        {
+            util_snprintf(buffer, sizeof(buffer), "%s/%s", curdir, files->d_name);
+            if (remove(buffer))
+                con_err("error removing temporary file: %s\n", buffer);
+        }
+    }
+
+    closedir(dir);
+}
+
+static void task_destroy(void) {
+    /*
+     * Free all the data in the task list and finally the list itself
+     * then proceed to cleanup anything else outside the program like
+     * temporary files.
+     */
+    for (auto &it : task_tasks) {
+        /*
+         * Close any open handles to files or processes here.  It's mighty
+         * annoying to have to do all this cleanup work.
+         */
+        if (it.stdoutlog) fclose(it.stdoutlog);
+        if (it.stderrlog) fclose(it.stderrlog);
+
+        /*
+         * Only remove the log files if the test actually compiled otherwise
+         * forget about it (or if it didn't compile, and the procedure type
+         * was set to -fail (meaning it shouldn't compile) .. stil remove)
+         */
+        if (it.compiled || !strcmp(it.tmpl->proceduretype, "-fail")) {
+            if (remove(it.stdoutlogfile))
+                con_err("error removing stdout log file: %s\n", it.stdoutlogfile);
+            if (remove(it.stderrlogfile))
+                con_err("error removing stderr log file: %s\n", it.stderrlogfile);
+
+            (void)!remove(it.tmpl->tempfilename);
+        }
+
+        /* free util_strdup data for log files */
+        mem_d(it.stdoutlogfile);
+        mem_d(it.stderrlogfile);
+
+        task_template_destroy(it.tmpl);
+    }
+}
+
+/*
+ * This executes the QCVM task for a specificly compiled progs.dat
+ * using the template passed into it for call-flags and user defined
+ * messages IF the procedure type is -execute, otherwise it matches
+ * the preprocessor output.
+ */
+static bool task_trymatch(task_t &task, std::vector<char *> &line) {
+    bool success = true;
+    bool process = true;
+    int retval = EXIT_SUCCESS;
+    FILE *execute;
+    char buffer[4096];
+    task_template_t *tmpl = task.tmpl;
+
+    memset(buffer,0,sizeof(buffer));
+
+    if (!strcmp(tmpl->proceduretype, "-execute")) {
+        /*
+         * Drop the execution flags for the QCVM if none where
+         * actually specified.
+         */
+        if (!strcmp(tmpl->executeflags, "$null")) {
+            util_snprintf(buffer,  sizeof(buffer), "%s %s",
+                task_bins[TASK_EXECUTE],
+                tmpl->tempfilename
+            );
+        } else {
+            util_snprintf(buffer,  sizeof(buffer), "%s %s %s",
+                task_bins[TASK_EXECUTE],
+                tmpl->executeflags,
+                tmpl->tempfilename
+            );
+        }
+
+        execute = popen(buffer, "r");
+        if (!execute)
+            return false;
+    } else if (!strcmp(tmpl->proceduretype, "-pp")) {
+        /*
+         * we're preprocessing, which means we need to read int
+         * the produced file and do some really weird shit.
+         */
+        if (!(execute = fopen(tmpl->tempfilename, "r")))
+            return false;
+        process = false;
+    } else {
+        /*
+         * we're testing diagnostic output, which means it will be
+         * in runhandles[2] (stderr) since that is where the compiler
+         * puts it's errors.
+         */
+        if (!(execute = fopen(task.stderrlogfile, "r")))
+            return false;
+        process = false;
+    }
+
+    /*
+     * Now lets read the lines and compare them to the matches we expect
+     * and handle accordingly.
+     */
+    {
+        char  *data    = nullptr;
+        size_t size    = 0;
+        size_t compare = 0;
+
+        while (util_getline(&data, &size, execute) != EOF) {
+            if (!strcmp(data, "No main function found\n")) {
+                con_err("test failure: `%s` (No main function found) [%s]\n",
+                    tmpl->description,
+                    tmpl->rulesfile
+                );
+                if (!process)
+                    fclose(execute);
+                else
+                    pclose((FILE*)execute);
+                return false;
+            }
+
+            /*
+             * Trim newlines from data since they will just break our
+             * ability to properly validate matches.
+             */
+            if  (strrchr(data, '\n'))
+                *strrchr(data, '\n') = '\0';
+
+            /*
+             * We remove the file/directory and stuff from the error
+             * match messages when testing diagnostics.
+             */
+            if(!strcmp(tmpl->proceduretype, "-diagnostic")) {
+                if (strstr(data, "there have been errors, bailing out"))
+                    continue; /* ignore it */
+                if (strstr(data, ": error: ")) {
+                    char *claim = util_strdup(data + (strstr(data, ": error: ") - data) + 9);
+                    mem_d(data);
+                    data = claim;
+                }
+            }
+
+            /*
+             * We need to ignore null lines for when -pp is used (preprocessor), since
+             * the preprocessor is likely to create empty newlines in certain macro
+             * instantations, otherwise it's in the wrong nature to ignore empty newlines.
+             */
+            if (!strcmp(tmpl->proceduretype, "-pp") && !*data)
+                continue;
+
+            if (tmpl->comparematch.size() > compare) {
+                if (strcmp(data, tmpl->comparematch[compare++])) {
+                    success = false;
+                }
+            } else {
+                success = false;
+            }
+
+            line.push_back(data);
+
+            /* reset */
+            data = nullptr;
+            size = 0;
+        }
+
+        if (compare != tmpl->comparematch.size())
+            success = false;
+
+        mem_d(data);
+        data = nullptr;
+    }
+
+    if (process)
+        retval = pclose((FILE*)execute);
+    else
+        fclose(execute);
+
+    return success && retval == EXIT_SUCCESS;
+}
+
+static const char *task_type(task_template_t *tmpl) {
+    if (!strcmp(tmpl->proceduretype, "-pp"))
+        return "type: preprocessor";
+    if (!strcmp(tmpl->proceduretype, "-execute"))
+        return "type: execution";
+    if (!strcmp(tmpl->proceduretype, "-compile"))
+        return "type: compile";
+    if (!strcmp(tmpl->proceduretype, "-diagnostic"))
+        return "type: diagnostic";
+    return "type: fail";
+}
+
+/*
+ * This schedualizes all tasks and actually runs them individually
+ * this is generally easy for just -compile variants.  For compile and
+ * execution this takes more work since a task needs to be generated
+ * from thin air and executed INLINE.
+ */
+#include <math.h>
+static size_t task_schedualize(size_t *pad) {
+    char space[2][64];
+    bool execute = false;
+    char *data = nullptr;
+    std::vector<char *> match;
+    size_t size = 0;
+    size_t i = 0;
+    size_t failed = 0;
+    int status = 0;
+
+    util_snprintf(space[0], sizeof(space[0]), "%d", (int)task_tasks.size());
+
+    for (auto &it : task_tasks) {
+        i++;
+        memset(space[1], 0, sizeof(space[1]));
+        util_snprintf(space[1], sizeof(space[1]), "%d", (int)(i));
+
+        con_out("test #%u %*s", i, strlen(space[0]) - strlen(space[1]), "");
+            //con_out("[[%*s]]",
+            //    (pad[0] + pad[1] - strlen(it.tmpl->description)) + (strlen(it.tmpl->rulesfile) - pad[1]),
+            //    it.tmpl->rulesfile);
+            //fflush(stdout);
+
+        /*
+         * Generate a task from thin air if it requires execution in
+         * the QCVM.
+         */
+
+        /* diagnostic is not executed, but compare tested instead, like preproessor */
+        execute = !! (!strcmp(it.tmpl->proceduretype, "-execute")) ||
+                     (!strcmp(it.tmpl->proceduretype, "-pp"))      ||
+                     (!strcmp(it.tmpl->proceduretype, "-diagnostic"));
+
+        /*
+         * We assume it compiled before we actually compiled :).  On error
+         * we change the value
+         */
+        it.compiled = true;
+
+        /*
+         * Read data from stdout first and pipe that stuff into a log file
+         * then we do the same for stderr.
+         */
+        while (util_getline(&data, &size, it.runhandles[1]) != EOF) {
+            fputs(data, it.stdoutlog);
+
+            if (strstr(data, "failed to open file")) {
+                it.compiled = false;
+                execute                = false;
+            }
+        }
+        while (util_getline(&data, &size, it.runhandles[2]) != EOF) {
+            /*
+             * If a string contains an error we just dissalow execution
+             * of it in the vm.
+             *
+             * TODO: make this more percise, e.g if we print a warning
+             * that refers to a variable named error, or something like
+             * that .. then this will blowup :P
+             */
+            if (strstr(data, "error") && strcmp(it.tmpl->proceduretype, "-diagnostic")) {
+                execute                = false;
+                it.compiled = false;
+            }
+
+            fputs(data, it.stderrlog);
+            fflush(it.stderrlog); /* fast flush for read */
+        }
+
+        if (!it.compiled && strcmp(it.tmpl->proceduretype, "-fail")) {
+            con_out("failure:   `%s` %*s %*s\n",
+                it.tmpl->description,
+                (pad[0] + pad[1] - strlen(it.tmpl->description)) + (strlen(it.tmpl->rulesfile) - pad[1]),
+                it.tmpl->rulesfile,
+                (pad[1] + pad[2] - strlen(it.tmpl->rulesfile)) + (strlen("(failed to compile)") - pad[2]),
+                "(failed to compile)"
+            );
+            failed++;
+            continue;
+        }
+
+        status = task_pclose(it.runhandles);
+        if (status != 0 && status != 1) {
+            con_out("compiler failure (returned: %i):   `%s` %*s\n",
+                status,
+                it.tmpl->description,
+                (pad[0] + pad[1] - strlen(it.tmpl->description)) + (strlen(it.tmpl->rulesfile) - pad[1]),
+                it.tmpl->rulesfile
+            );
+            failed++;
+            continue;
+        }
+        if ((!strcmp(it.tmpl->proceduretype, "-fail") && status == EXIT_SUCCESS)
+        ||  ( strcmp(it.tmpl->proceduretype, "-fail") && status == EXIT_FAILURE)) {
+            con_out("failure:   `%s` %*s %*s\n",
+                it.tmpl->description,
+                (pad[0] + pad[1] - strlen(it.tmpl->description)) + (strlen(it.tmpl->rulesfile) - pad[1]),
+                it.tmpl->rulesfile,
+                (pad[1] + pad[2] - strlen(it.tmpl->rulesfile)) + (strlen("(compiler didn't return exit success)") - pad[2]),
+                "(compiler didn't return exit success)"
+            );
+            failed++;
+            continue;
+        }
+
+        if (!execute) {
+            con_out("succeeded: `%s` %*s %*s\n",
+                it.tmpl->description,
+                (pad[0] + pad[1] - strlen(it.tmpl->description)) + (strlen(it.tmpl->rulesfile) - pad[1]),
+                it.tmpl->rulesfile,
+                (pad[1] + pad[2] - strlen(it.tmpl->rulesfile)) + (strlen(task_type(it.tmpl)) - pad[2]),
+                task_type(it.tmpl)
+
+            );
+            continue;
+        }
+
+        /*
+         * If we made it here that concludes the task is to be executed
+         * in the virtual machine (or the preprocessor output needs to
+         * be matched).
+         */
+        if (!task_trymatch(it, match)) {
+            size_t d = 0;
+
+            con_out("failure:   `%s` %*s %*s\n",
+                it.tmpl->description,
+                (pad[0] + pad[1] - strlen(it.tmpl->description)) + (strlen(it.tmpl->rulesfile) - pad[1]),
+                it.tmpl->rulesfile,
+                (pad[1] + pad[2] - strlen(it.tmpl->rulesfile)) + (strlen(
+                    (strcmp(it.tmpl->proceduretype, "-pp"))
+                        ? "(invalid results from execution)"
+                        : (strcmp(it.tmpl->proceduretype, "-diagnostic"))
+                            ? "(invalid results from preprocessing)"
+                            : "(invalid results from compiler diagnsotics)"
+                ) - pad[2]),
+                (strcmp(it.tmpl->proceduretype, "-pp"))
+                    ? "(invalid results from execution)"
+                    : (strcmp(it.tmpl->proceduretype, "-diagnostic"))
+                            ? "(invalid results from preprocessing)"
+                            : "(invalid results from compiler diagnsotics)"
+            );
+
+            /*
+             * Print nicely formatted expected match lists to console error
+             * handler for the all the given matches in the template file and
+             * what was actually returned from executing.
+             */
+            con_out("    Expected From %u Matches: (got %u Matches)\n",
+                it.tmpl->comparematch.size(),
+                match.size()
+            );
+            for (; d < it.tmpl->comparematch.size(); d++) {
+                char *select = it.tmpl->comparematch[d];
+                size_t length = 60 - strlen(select);
+                con_out("        Expected: \"%s\"", select);
+                while (length --)
+                    con_out(" ");
+                con_out("| Got: \"%s\"\n", (d >= match.size()) ? "<<nothing else to compare>>" : match[d]);
+            }
+
+            /*
+             * Print the non-expected out (since we are simply not expecting it)
+             * This will help track down bugs in template files that fail to match
+             * something.
+             */
+            if (match.size() > it.tmpl->comparematch.size()) {
+                for (d = 0; d < match.size() - it.tmpl->comparematch.size(); d++) {
+                    con_out("        Expected: Nothing                                                       | Got: \"%s\"\n",
+                        match[d + it.tmpl->comparematch.size()]
+                    );
+                }
+            }
+
+            for (auto &it : match)
+                mem_d(it);
+            match.clear();
+            failed++;
+            continue;
+        }
+
+        for (auto &it : match)
+            mem_d(it);
+        match.clear();
+
+        con_out("succeeded: `%s` %*s %*s\n",
+            it.tmpl->description,
+            (pad[0] + pad[1] - strlen(it.tmpl->description)) + (strlen(it.tmpl->rulesfile) - pad[1]),
+            it.tmpl->rulesfile,
+            (pad[1] + pad[2] - strlen(it.tmpl->rulesfile)) + (strlen(task_type(it.tmpl))- pad[2]),
+            task_type(it.tmpl)
+
+        );
+    }
+    mem_d(data);
+    return failed;
+}
+
+/*
+ * This is the heart of the whole test-suite process.  This cleans up
+ * any existing temporary files left behind as well as log files left
+ * behind.  Then it propagates a list of tests from `curdir` by scaning
+ * it for template files and compiling them into tasks, in which it
+ * schedualizes them (executes them) and actually reports errors and
+ * what not.  It then proceeds to destroy the tasks and return memory
+ * it's the engine :)
+ *
+ * It returns true of tests could be propagated, otherwise it returns
+ * false.
+ *
+ * It expects con_init() was called before hand.
+ */
+static GMQCC_WARN bool test_perform(const char *curdir, const char *defs) {
+    size_t             failed       = false;
+    static const char *default_defs = "defs.qh";
+
+    size_t pad[] = {
+        /* test ### [succeed/fail]: `description`      [tests/template.tmpl]     [type] */
+                    0,                                 0,                        0
+    };
+
+    /*
+     * If the default definition file isn't set to anything.  We will
+     * use the default_defs here, which is "defs.qc"
+     */
+    if (!defs) {
+        defs = default_defs;
+    }
+
+
+    task_precleanup(curdir);
+    if (!task_propagate(curdir, pad, defs)) {
+        con_err("error: failed to propagate tasks\n");
+        task_destroy();
+        return false;
+    }
+    /*
+     * If we made it here all tasks where propagated from their resultant
+     * template file.  So we can start the FILO scheduler, this has been
+     * designed in the most thread-safe way possible for future threading
+     * it's designed to prevent lock contention, and possible syncronization
+     * issues.
+     */
+    failed = task_schedualize(pad);
+    if (failed)
+        con_out("%u out of %u tests failed\n", failed, task_tasks.size());
+    task_destroy();
+
+    return (failed) ? false : true;
+}
+
+/*
+ * Fancy GCC-like LONG parsing allows things like --opt=param with
+ * assignment operator.  This is used for redirecting stdout/stderr
+ * console to specific files of your choice.
+ */
+static bool parsecmd(const char *optname, int *argc_, char ***argv_, char **out, int ds, bool split) {
+    int  argc   = *argc_;
+    char **argv = *argv_;
+
+    size_t len = strlen(optname);
+
+    if (strncmp(argv[0]+ds, optname, len))
+        return false;
+
+    /* it's --optname, check how the parameter is supplied */
+    if (argv[0][ds+len] == '=') {
+        *out = argv[0]+ds+len+1;
+        return true;
+    }
+
+    if (!split || argc < ds) /* no parameter was provided, or only single-arg form accepted */
+        return false;
+
+    /* using --opt param */
+    *out = argv[1];
+    --*argc_;
+    ++*argv_;
+    return true;
+}
+
+int main(int argc, char **argv) {
+    bool succeed  = false;
+    char *defs = nullptr;
+
+    con_init();
+
+    /*
+     * Command line option parsing commences now We only need to support
+     * a few things in the test suite.
+     */
+    while (argc > 1) {
+        ++argv;
+        --argc;
+
+        if (argv[0][0] == '-') {
+            if (parsecmd("defs", &argc, &argv, &defs, 1, false))
+                continue;
+
+            if (!strcmp(argv[0]+1, "debug")) {
+                OPTS_OPTION_BOOL(OPTION_DEBUG) = true;
+                continue;
+            }
+            if (!strcmp(argv[0]+1, "nocolor")) {
+                con_color(0);
+                continue;
+            }
+
+            con_err("invalid argument %s\n", argv[0]+1);
+            return -1;
+        }
+    }
+    succeed = test_perform("tests", defs);
+
+    return (succeed) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/tests/fieldfuncs.qc b/tests/fieldfuncs.qc
new file mode 100644 (file)
index 0000000..4228f33
--- /dev/null
@@ -0,0 +1,13 @@
+.float field;
+
+.float getfield() {
+  return field;
+}
+
+void() main = {
+  entity e = spawn();
+  e.field = 42;
+  print(ftos(e.(getfield())), "\n");
+  .float memptr = getfield();
+  print(ftos(e.memptr), "\n");
+}
diff --git a/tests/fieldfuncs.tmpl b/tests/fieldfuncs.tmpl
new file mode 100644 (file)
index 0000000..1199b9b
--- /dev/null
@@ -0,0 +1,6 @@
+I: fieldfuncs.qc
+D: test fields with functions
+T: -compile
+C: -std=fte
+M: 42
+M: 42
diff --git a/tests/forloop.qc b/tests/forloop.qc
new file mode 100644 (file)
index 0000000..17512bd
--- /dev/null
@@ -0,0 +1,13 @@
+void main() {
+    float j;
+    for (j = 0; j < 2; ++j)
+        print("+");
+
+    for (float i = 0; i < 5; ++i)
+        print("*");
+
+    for (;;) {
+        print("\n");
+        break;
+    }
+}
diff --git a/tests/forloop.tmpl b/tests/forloop.tmpl
new file mode 100644 (file)
index 0000000..2718dfc
--- /dev/null
@@ -0,0 +1,5 @@
+I: forloop.qc
+D: test for loops
+T: -execute
+C: -std=gmqcc
+M: ++*****
diff --git a/utf8.c b/utf8.c
deleted file mode 100644 (file)
index e9c56bc..0000000
--- a/utf8.c
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2012, 2013, 2014, 2015
- *     Dale Weiler
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
- * this software and associated documentation files (the "Software"), to deal in
- * the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is furnished to do
- * so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-#include "gmqcc.h"
-
-/*
- * Based on the flexible and economical utf8 decoder:
- * http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
- *
- * This is slightly more economical, the fastest way to decode utf8 is
- * with a lookup table as in:
- *
- * first 1-byte lookup
- * if that fails, 2-byte lookup
- * if that fails, 3-byte lookup
- * if that fails, 4-byte lookup
- *
- * The following table can be generated with some interval trickery.
- * consider an interval [a, b):
- *
- *      a must be 0x80 or b must be 0xc0, lower 3 bits
- *      are clear, thus:
- *          interval(a,b) = ((uint32_t)((a==0x80?0x40-b:-a)<<23))
- *
- * The failstate can be represented as interval(0x80,0x80), it's
- * odd to see but this is a full state machine.
- *
- * The table than maps the corresponding sections as a serise of
- * intervals.
- *
- * In this table the transition values are pre-multiplied with 16 to
- * save a shift instruction for every byte, we throw away fillers
- * which makes the table smaller.
- *
- * The first section of the table handles bytes with leading C
- * The second section of the table handles bytes with leading D
- * The third section of the table handles bytes with leading E
- * The last section of the table handles bytes with leading F
- *
- * The values themselfs in the table are arranged so that when you
- * left shift them by 6 to shift continuation characters into place, the
- * new top bits tell you:
- *
- *  1 - if you keep going
- *  2 - the range of valid values for the next byte
- */
-static const uint32_t utf8_tab[] = {
-    0xC0000002, 0xC0000003, 0xC0000004, 0xC0000005, 0xC0000006,
-    0xC0000007, 0xC0000008, 0xC0000009, 0xC000000A, 0xC000000B,
-    0xC000000C, 0xC000000D, 0xC000000E, 0xC000000F, 0xC0000010,
-    0xC0000011, 0xC0000012, 0xC0000013, 0xC0000014, 0xC0000015,
-    0xC0000016, 0xC0000017, 0xC0000018, 0xC0000019, 0xC000001A,
-    0xC000001B, 0xC000001C, 0xC000001D, 0xC000001E, 0xC000001F,
-    0xB3000000, 0xC3000001, 0xC3000002, 0xC3000003, 0xC3000004,
-    0xC3000005, 0xC3000006, 0xC3000007, 0xC3000008, 0xC3000009,
-    0xC300000A, 0xC300000B, 0xC300000C, 0xD300000D, 0xC300000E,
-    0xC300000F, 0xBB0C0000, 0xC30C0001, 0xC30C0002, 0xC30C0003,
-    0xD30C0004
-};
-
-int utf8_from(char *s, utf8ch_t ch) {
-    if (!s)
-        return 0;
-
-    if ((unsigned)ch < 0x80) {
-        *s = ch;
-        return 1;
-    } else if ((unsigned)ch < 0x800) {
-        *s++ = 0xC0 | (ch >> 6);
-        *s   = 0x80 | (ch & 0x3F);
-        return 2;
-    } else if ((unsigned)ch < 0xD800 || (unsigned)ch - 0xE000 < 0x2000) {
-        *s++ = 0xE0 | (ch >> 12);
-        *s++ = 0x80 | ((ch >> 6) & 0x3F);
-        *s   = 0x80 | (ch & 0x3F);
-        return 3;
-    } else if ((unsigned)ch - 0x10000 < 0x100000) {
-        *s++ = 0xF0 | (ch >> 18);
-        *s++ = 0x80 | ((ch >> 12) & 0x3F);
-        *s++ = 0x80 | ((ch >> 6) & 0x3F);
-        *s   = 0x80 | (ch & 0x3F);
-        return 4;
-    }
-    return 0;
-}
-
-int utf8_to(utf8ch_t *i, const unsigned char *s, size_t n) {
-    unsigned c,j;
-
-    if (!s || !n)
-        return 0;
-
-    /* This is consistent with mbtowc behaviour. */
-    if (!i)
-        i = (utf8ch_t*)(void*)&i;
-
-    if (*s < 0x80)
-        return !!(*i = *s);
-    if (*s-0xC2U > 0x32)
-        return 0;
-
-    c = utf8_tab[*s++-0xC2U];
-
-    /*
-     * Avoid excessive checks against n.
-     *
-     * When shifting state `n-1` times does not clear the high bit,
-     * then the value of `n` won't satisfy the condition to read a
-     * character as it will be insufficent.
-     */
-    if (n < 4 && ((c<<(6*n-6)) & (1U << 31)))
-        return 0;
-
-    /*
-     * The upper 6 state bits are negitive integer offset to a bound-check
-     * next byte equivlant to: ((b-0x80)+(b+offset))&~0x3f
-     */
-    if ((((*s>>3)-0x10)|((*s>>3)+((int32_t)c>>26))) & ~7)
-        return 0;
-
-    for (j=2; j<3; j++) {
-        if (!((c = c<<6 | (*s++-0x80))&(1U<<31))) {
-            *i = c;
-            return j;
-        }
-        if (*s-0x80U >= 0x40)
-            return 0;
-    }
-
-    *i = c<<6 | (*s++-0x80);
-    return 4;
-}
diff --git a/utf8.cpp b/utf8.cpp
new file mode 100644 (file)
index 0000000..622363c
--- /dev/null
+++ b/utf8.cpp
@@ -0,0 +1,129 @@
+#include "gmqcc.h"
+
+/*
+ * Based on the flexible and economical utf8 decoder:
+ * http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
+ *
+ * This is slightly more economical, the fastest way to decode utf8 is
+ * with a lookup table as in:
+ *
+ * first 1-byte lookup
+ * if that fails, 2-byte lookup
+ * if that fails, 3-byte lookup
+ * if that fails, 4-byte lookup
+ *
+ * The following table can be generated with some interval trickery.
+ * consider an interval [a, b):
+ *
+ *      a must be 0x80 or b must be 0xc0, lower 3 bits
+ *      are clear, thus:
+ *          interval(a,b) = ((uint32_t)((a==0x80?0x40-b:-a)<<23))
+ *
+ * The failstate can be represented as interval(0x80,0x80), it's
+ * odd to see but this is a full state machine.
+ *
+ * The table than maps the corresponding sections as a serise of
+ * intervals.
+ *
+ * In this table the transition values are pre-multiplied with 16 to
+ * save a shift instruction for every byte, we throw away fillers
+ * which makes the table smaller.
+ *
+ * The first section of the table handles bytes with leading C
+ * The second section of the table handles bytes with leading D
+ * The third section of the table handles bytes with leading E
+ * The last section of the table handles bytes with leading F
+ *
+ * The values themselfs in the table are arranged so that when you
+ * left shift them by 6 to shift continuation characters into place, the
+ * new top bits tell you:
+ *
+ *  1 - if you keep going
+ *  2 - the range of valid values for the next byte
+ */
+static const uint32_t utf8_tab[] = {
+    0xC0000002, 0xC0000003, 0xC0000004, 0xC0000005, 0xC0000006,
+    0xC0000007, 0xC0000008, 0xC0000009, 0xC000000A, 0xC000000B,
+    0xC000000C, 0xC000000D, 0xC000000E, 0xC000000F, 0xC0000010,
+    0xC0000011, 0xC0000012, 0xC0000013, 0xC0000014, 0xC0000015,
+    0xC0000016, 0xC0000017, 0xC0000018, 0xC0000019, 0xC000001A,
+    0xC000001B, 0xC000001C, 0xC000001D, 0xC000001E, 0xC000001F,
+    0xB3000000, 0xC3000001, 0xC3000002, 0xC3000003, 0xC3000004,
+    0xC3000005, 0xC3000006, 0xC3000007, 0xC3000008, 0xC3000009,
+    0xC300000A, 0xC300000B, 0xC300000C, 0xD300000D, 0xC300000E,
+    0xC300000F, 0xBB0C0000, 0xC30C0001, 0xC30C0002, 0xC30C0003,
+    0xD30C0004
+};
+
+int utf8_from(char *s, utf8ch_t ch) {
+    if (!s)
+        return 0;
+
+    if ((unsigned)ch < 0x80) {
+        *s = ch;
+        return 1;
+    } else if ((unsigned)ch < 0x800) {
+        *s++ = 0xC0 | (ch >> 6);
+        *s   = 0x80 | (ch & 0x3F);
+        return 2;
+    } else if ((unsigned)ch < 0xD800 || (unsigned)ch - 0xE000 < 0x2000) {
+        *s++ = 0xE0 | (ch >> 12);
+        *s++ = 0x80 | ((ch >> 6) & 0x3F);
+        *s   = 0x80 | (ch & 0x3F);
+        return 3;
+    } else if ((unsigned)ch - 0x10000 < 0x100000) {
+        *s++ = 0xF0 | (ch >> 18);
+        *s++ = 0x80 | ((ch >> 12) & 0x3F);
+        *s++ = 0x80 | ((ch >> 6) & 0x3F);
+        *s   = 0x80 | (ch & 0x3F);
+        return 4;
+    }
+    return 0;
+}
+
+int utf8_to(utf8ch_t *i, const unsigned char *s, size_t n) {
+    unsigned c,j;
+
+    if (!s || !n)
+        return 0;
+
+    /* This is consistent with mbtowc behaviour. */
+    if (!i)
+        i = (utf8ch_t*)(void*)&i;
+
+    if (*s < 0x80)
+        return !!(*i = *s);
+    if (*s-0xC2U > 0x32)
+        return 0;
+
+    c = utf8_tab[*s++-0xC2U];
+
+    /*
+     * Avoid excessive checks against n.
+     *
+     * When shifting state `n-1` times does not clear the high bit,
+     * then the value of `n` won't satisfy the condition to read a
+     * character as it will be insufficent.
+     */
+    if (n < 4 && ((c<<(6*n-6)) & (1U << 31)))
+        return 0;
+
+    /*
+     * The upper 6 state bits are negitive integer offset to a bound-check
+     * next byte equivlant to: ((b-0x80)+(b+offset))&~0x3f
+     */
+    if ((((*s>>3)-0x10)|((*s>>3)+((int32_t)c>>26))) & ~7)
+        return 0;
+
+    for (j=2; j<3; j++) {
+        if (!((c = c<<6 | (*s++-0x80))&(1U<<31))) {
+            *i = c;
+            return j;
+        }
+        if (*s-0x80U >= 0x40)
+            return 0;
+    }
+
+    *i = c<<6 | (*s++-0x80);
+    return 4;
+}
diff --git a/util.c b/util.c
deleted file mode 100644 (file)
index 4adcb4a..0000000
--- a/util.c
+++ /dev/null
@@ -1,681 +0,0 @@
-/*
- * Copyright (C) 2012, 2013, 2014, 2015
- *     Dale Weiler
- *     Wolfgang Bumiller
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
- * this software and associated documentation files (the "Software"), to deal in
- * the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is furnished to do
- * so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-#define GMQCC_PLATFORM_HEADER
-#include <stdlib.h>
-#include "gmqcc.h"
-#include "platform.h"
-
-/*
- * Initially this was handled with a table in the gmqcc.h header, but
- * much to my surprise the contents of the table was duplicated for
- * each translation unit, causing all these strings to be duplicated
- * for every .c file it was included into. This method culls back on
- * it. This is a 'utility' function because the executor also depends
- * on this for disassembled byte-code.
- */
-const char *util_instr_str[VINSTR_END] = {
-    "DONE",       "MUL_F",      "MUL_V",      "MUL_FV",
-    "MUL_VF",     "DIV_F",      "ADD_F",      "ADD_V",
-    "SUB_F",      "SUB_V",      "EQ_F",       "EQ_V",
-    "EQ_S",       "EQ_E",       "EQ_FNC",     "NE_F",
-    "NE_V",       "NE_S",       "NE_E",       "NE_FNC",
-    "LE",         "GE",         "LT",         "GT",
-    "LOAD_F",     "LOAD_V",     "LOAD_S",     "LOAD_ENT",
-    "LOAD_FLD",   "LOAD_FNC",   "ADDRESS",    "STORE_F",
-    "STORE_V",    "STORE_S",    "STORE_ENT",  "STORE_FLD",
-    "STORE_FNC",  "STOREP_F",   "STOREP_V",   "STOREP_S",
-    "STOREP_ENT", "STOREP_FLD", "STOREP_FNC", "RETURN",
-    "NOT_F",      "NOT_V",      "NOT_S",      "NOT_ENT",
-    "NOT_FNC",    "IF",         "IFNOT",      "CALL0",
-    "CALL1",      "CALL2",      "CALL3",      "CALL4",
-    "CALL5",      "CALL6",      "CALL7",      "CALL8",
-    "STATE",      "GOTO",       "AND",        "OR",
-    "BITAND",     "BITOR"
-};
-
-/*
- * only required if big endian .. otherwise no need to swap
- * data.
- */
-#if PLATFORM_BYTE_ORDER == GMQCC_BYTE_ORDER_BIG || PLATFORM_BYTE_ORDER == -1
-    static GMQCC_INLINE void util_swap16(uint16_t *d, size_t l) {
-        while (l--) {
-            d[l] = (d[l] << 8) | (d[l] >> 8);
-        }
-    }
-
-    static GMQCC_INLINE void util_swap32(uint32_t *d, size_t l) {
-        while (l--) {
-            uint32_t v;
-            v = ((d[l] << 8) & 0xFF00FF00) | ((d[l] >> 8) & 0x00FF00FF);
-            d[l] = (v << 16) | (v >> 16);
-        }
-    }
-
-    /* Some strange system doesn't like constants that big, AND doesn't recognize an ULL suffix
-     * so let's go the safe way
-     */
-    static GMQCC_INLINE void util_swap64(uint32_t *d, size_t l) {
-        /*
-        while (l--) {
-            uint64_t v;
-            v = ((d[l] << 8) & 0xFF00FF00FF00FF00) | ((d[l] >> 8) & 0x00FF00FF00FF00FF);
-            v = ((v << 16) & 0xFFFF0000FFFF0000) | ((v >> 16) & 0x0000FFFF0000FFFF);
-            d[l] = (v << 32) | (v >> 32);
-        }
-        */
-        size_t i;
-        l *= 2;
-        for (i = 0; i < l; i += 2) {
-            uint32_t v1 = d[i];
-            d[i] = d[i+1];
-            d[i+1] = v1;
-            util_swap32(d+i, 2);
-        }
-    }
-#endif
-
-void util_endianswap(void *_data, size_t count, unsigned int typesize) {
-#   if PLATFORM_BYTE_ORDER == -1 /* runtime check */
-    if (*((char*)&typesize))
-        return;
-#else
-
-#   if PLATFORM_BYTE_ORDER == GMQCC_BYTE_ORDER_LITTLE
-        /* prevent unused warnings */
-        (void) _data;
-        (void) count;
-        (void) typesize;
-        return;
-#   else
-        switch (typesize) {
-            case 1: return;
-            case 2:
-                util_swap16((uint16_t*)_data, count);
-                return;
-            case 4:
-                util_swap32((uint32_t*)_data, count);
-                return;
-            case 8:
-                util_swap64((uint32_t*)_data, count);
-                return;
-
-            default:
-                con_err ("util_endianswap: I don't know how to swap a %u byte structure!\n", typesize);
-                exit(EXIT_FAILURE); /* please blow the fuck up! */
-        }
-#   endif
-#endif
-}
-
-void util_swap_header(prog_header_t *code_header) {
-    util_endianswap(&code_header->version,              1, sizeof(code_header->version));
-    util_endianswap(&code_header->crc16,                1, sizeof(code_header->crc16));
-    util_endianswap(&code_header->statements.offset,    1, sizeof(code_header->statements.offset));
-    util_endianswap(&code_header->statements.length,    1, sizeof(code_header->statements.length));
-    util_endianswap(&code_header->defs.offset,          1, sizeof(code_header->defs.offset));
-    util_endianswap(&code_header->defs.length,          1, sizeof(code_header->defs.length));
-    util_endianswap(&code_header->fields.offset,        1, sizeof(code_header->fields.offset));
-    util_endianswap(&code_header->fields.length,        1, sizeof(code_header->fields.length));
-    util_endianswap(&code_header->functions.offset,     1, sizeof(code_header->functions.offset));
-    util_endianswap(&code_header->functions.length,     1, sizeof(code_header->functions.length));
-    util_endianswap(&code_header->strings.offset,       1, sizeof(code_header->strings.offset));
-    util_endianswap(&code_header->strings.length,       1, sizeof(code_header->strings.length));
-    util_endianswap(&code_header->globals.offset,       1, sizeof(code_header->globals.offset));
-    util_endianswap(&code_header->globals.length,       1, sizeof(code_header->globals.length));
-    util_endianswap(&code_header->entfield,             1, sizeof(code_header->entfield));
-}
-
-void util_swap_statements(prog_section_statement_t *statements) {
-    size_t i;
-
-    for (i = 0; i < vec_size(statements); ++i) {
-        util_endianswap(&statements[i].opcode,  1, sizeof(statements[i].opcode));
-        util_endianswap(&statements[i].o1,      1, sizeof(statements[i].o1));
-        util_endianswap(&statements[i].o2,      1, sizeof(statements[i].o2));
-        util_endianswap(&statements[i].o3,      1, sizeof(statements[i].o3));
-    }
-}
-
-void util_swap_defs_fields(prog_section_both_t *section) {
-    size_t i;
-
-    for (i = 0; i < vec_size(section); ++i) {
-        util_endianswap(&section[i].type,   1, sizeof(section[i].type));
-        util_endianswap(&section[i].offset, 1, sizeof(section[i].offset));
-        util_endianswap(&section[i].name,   1, sizeof(section[i].name));
-    }
-}
-
-void util_swap_functions(prog_section_function_t *functions) {
-    size_t i;
-
-    for (i = 0; i < vec_size(functions); ++i) {
-        util_endianswap(&functions[i].entry,        1, sizeof(functions[i].entry));
-        util_endianswap(&functions[i].firstlocal,   1, sizeof(functions[i].firstlocal));
-        util_endianswap(&functions[i].locals,       1, sizeof(functions[i].locals));
-        util_endianswap(&functions[i].profile,      1, sizeof(functions[i].profile));
-        util_endianswap(&functions[i].name,         1, sizeof(functions[i].name));
-        util_endianswap(&functions[i].file,         1, sizeof(functions[i].file));
-        util_endianswap(&functions[i].nargs,        1, sizeof(functions[i].nargs));
-        /* Don't swap argsize[] - it's just a byte array, which Quake uses only as such. */
-    }
-}
-
-void util_swap_globals(int32_t *globals) {
-    util_endianswap(globals, vec_size(globals), sizeof(int32_t));
-}
-
-/*
-* Based On:
-*   Slicing-by-8 algorithms by Michael E.
-*       Kounavis and Frank L. Berry from Intel Corp.
-*       http://www.intel.com/technology/comms/perfnet/download/CRC_generators.pdf
-*
-*   This code was made to be slightly less confusing with macros, which
-*   I suppose is somewhat ironic.
-*
-*   The code had to be changed for non reflected on the output register
-*   since that's the method Quake uses.
-*
-*   The code also had to be changed for CRC16, which is slightly harder
-*   since the CRC32 method in the original Intel paper used a different
-*   bit order convention.
-*
-* Notes about the table:
-*   - It's exactly 4K in size
-*   - 64 elements fit in a cache line
-*   - can do 8 iterations unrolled 8 times for free
-*   - The first 256 elements of the table are standard CRC16 table
-*
-* Table can be generated with the following utility:
-*/
-#if 0
-#include <stdio.h>
-#include <stdint.h>
-int main(void) {
-    for (unsigned i = 0; i < 0x100; ++i) {
-        uint16_t x = i << 8;
-        for (int j = 0; j < 8; ++j)
-            x = (x << 1) ^ ((x & 0x8000) ? 0x1021 : 0);
-        tab[0][i] = x;
-    }
-    for (unsigned i = 0; i < 0x100; ++i) {
-        uint16_t c = tab[0][i];
-        for (unsigned j = 1; j < 8; ++j) {
-            c = tab[0][c >> 8] ^ (c << 8);
-            tab[j][i] = c;
-        }
-    }
-    printf("static const uint16_t util_crc16_table[8][256] = {");
-    for (int i = 0; i < 8; ++i) {
-        printf("{\n");
-        for (int j = 0; j < 0x100; ++j) {
-            printf((j & 7) ? " " : "    ");
-            printf((j != 0x100-1) ? "0x%04X," : "0x%04X", tab[i][j]);
-            if ((j & 7) == 7)
-                printf("\n");
-        }
-        printf((i != 7) ? "}," : "}");
-    }
-    printf("};\n");
-    return 0;
-}
-#endif
-/*
- * Non-Reflective version is present as well as a reference.
- *
- * TODO:
- *  combine the crc16 into u32s and mask off low high for byte order
- *  to make the arrays smaller.
- */
-
-static const uint16_t util_crc16_table[8][256] = {{
-    0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
-    0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
-    0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
-    0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
-    0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
-    0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
-    0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
-    0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
-    0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
-    0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
-    0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
-    0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
-    0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
-    0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
-    0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
-    0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
-    0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
-    0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
-    0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
-    0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
-    0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
-    0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
-    0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
-    0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
-    0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
-    0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
-    0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
-    0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
-    0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
-    0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
-    0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
-    0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
-},{
-    0x0000, 0x3331, 0x6662, 0x5553, 0xCCC4, 0xFFF5, 0xAAA6, 0x9997,
-    0x89A9, 0xBA98, 0xEFCB, 0xDCFA, 0x456D, 0x765C, 0x230F, 0x103E,
-    0x0373, 0x3042, 0x6511, 0x5620, 0xCFB7, 0xFC86, 0xA9D5, 0x9AE4,
-    0x8ADA, 0xB9EB, 0xECB8, 0xDF89, 0x461E, 0x752F, 0x207C, 0x134D,
-    0x06E6, 0x35D7, 0x6084, 0x53B5, 0xCA22, 0xF913, 0xAC40, 0x9F71,
-    0x8F4F, 0xBC7E, 0xE92D, 0xDA1C, 0x438B, 0x70BA, 0x25E9, 0x16D8,
-    0x0595, 0x36A4, 0x63F7, 0x50C6, 0xC951, 0xFA60, 0xAF33, 0x9C02,
-    0x8C3C, 0xBF0D, 0xEA5E, 0xD96F, 0x40F8, 0x73C9, 0x269A, 0x15AB,
-    0x0DCC, 0x3EFD, 0x6BAE, 0x589F, 0xC108, 0xF239, 0xA76A, 0x945B,
-    0x8465, 0xB754, 0xE207, 0xD136, 0x48A1, 0x7B90, 0x2EC3, 0x1DF2,
-    0x0EBF, 0x3D8E, 0x68DD, 0x5BEC, 0xC27B, 0xF14A, 0xA419, 0x9728,
-    0x8716, 0xB427, 0xE174, 0xD245, 0x4BD2, 0x78E3, 0x2DB0, 0x1E81,
-    0x0B2A, 0x381B, 0x6D48, 0x5E79, 0xC7EE, 0xF4DF, 0xA18C, 0x92BD,
-    0x8283, 0xB1B2, 0xE4E1, 0xD7D0, 0x4E47, 0x7D76, 0x2825, 0x1B14,
-    0x0859, 0x3B68, 0x6E3B, 0x5D0A, 0xC49D, 0xF7AC, 0xA2FF, 0x91CE,
-    0x81F0, 0xB2C1, 0xE792, 0xD4A3, 0x4D34, 0x7E05, 0x2B56, 0x1867,
-    0x1B98, 0x28A9, 0x7DFA, 0x4ECB, 0xD75C, 0xE46D, 0xB13E, 0x820F,
-    0x9231, 0xA100, 0xF453, 0xC762, 0x5EF5, 0x6DC4, 0x3897, 0x0BA6,
-    0x18EB, 0x2BDA, 0x7E89, 0x4DB8, 0xD42F, 0xE71E, 0xB24D, 0x817C,
-    0x9142, 0xA273, 0xF720, 0xC411, 0x5D86, 0x6EB7, 0x3BE4, 0x08D5,
-    0x1D7E, 0x2E4F, 0x7B1C, 0x482D, 0xD1BA, 0xE28B, 0xB7D8, 0x84E9,
-    0x94D7, 0xA7E6, 0xF2B5, 0xC184, 0x5813, 0x6B22, 0x3E71, 0x0D40,
-    0x1E0D, 0x2D3C, 0x786F, 0x4B5E, 0xD2C9, 0xE1F8, 0xB4AB, 0x879A,
-    0x97A4, 0xA495, 0xF1C6, 0xC2F7, 0x5B60, 0x6851, 0x3D02, 0x0E33,
-    0x1654, 0x2565, 0x7036, 0x4307, 0xDA90, 0xE9A1, 0xBCF2, 0x8FC3,
-    0x9FFD, 0xACCC, 0xF99F, 0xCAAE, 0x5339, 0x6008, 0x355B, 0x066A,
-    0x1527, 0x2616, 0x7345, 0x4074, 0xD9E3, 0xEAD2, 0xBF81, 0x8CB0,
-    0x9C8E, 0xAFBF, 0xFAEC, 0xC9DD, 0x504A, 0x637B, 0x3628, 0x0519,
-    0x10B2, 0x2383, 0x76D0, 0x45E1, 0xDC76, 0xEF47, 0xBA14, 0x8925,
-    0x991B, 0xAA2A, 0xFF79, 0xCC48, 0x55DF, 0x66EE, 0x33BD, 0x008C,
-    0x13C1, 0x20F0, 0x75A3, 0x4692, 0xDF05, 0xEC34, 0xB967, 0x8A56,
-    0x9A68, 0xA959, 0xFC0A, 0xCF3B, 0x56AC, 0x659D, 0x30CE, 0x03FF
-},{
-    0x0000, 0x3730, 0x6E60, 0x5950, 0xDCC0, 0xEBF0, 0xB2A0, 0x8590,
-    0xA9A1, 0x9E91, 0xC7C1, 0xF0F1, 0x7561, 0x4251, 0x1B01, 0x2C31,
-    0x4363, 0x7453, 0x2D03, 0x1A33, 0x9FA3, 0xA893, 0xF1C3, 0xC6F3,
-    0xEAC2, 0xDDF2, 0x84A2, 0xB392, 0x3602, 0x0132, 0x5862, 0x6F52,
-    0x86C6, 0xB1F6, 0xE8A6, 0xDF96, 0x5A06, 0x6D36, 0x3466, 0x0356,
-    0x2F67, 0x1857, 0x4107, 0x7637, 0xF3A7, 0xC497, 0x9DC7, 0xAAF7,
-    0xC5A5, 0xF295, 0xABC5, 0x9CF5, 0x1965, 0x2E55, 0x7705, 0x4035,
-    0x6C04, 0x5B34, 0x0264, 0x3554, 0xB0C4, 0x87F4, 0xDEA4, 0xE994,
-    0x1DAD, 0x2A9D, 0x73CD, 0x44FD, 0xC16D, 0xF65D, 0xAF0D, 0x983D,
-    0xB40C, 0x833C, 0xDA6C, 0xED5C, 0x68CC, 0x5FFC, 0x06AC, 0x319C,
-    0x5ECE, 0x69FE, 0x30AE, 0x079E, 0x820E, 0xB53E, 0xEC6E, 0xDB5E,
-    0xF76F, 0xC05F, 0x990F, 0xAE3F, 0x2BAF, 0x1C9F, 0x45CF, 0x72FF,
-    0x9B6B, 0xAC5B, 0xF50B, 0xC23B, 0x47AB, 0x709B, 0x29CB, 0x1EFB,
-    0x32CA, 0x05FA, 0x5CAA, 0x6B9A, 0xEE0A, 0xD93A, 0x806A, 0xB75A,
-    0xD808, 0xEF38, 0xB668, 0x8158, 0x04C8, 0x33F8, 0x6AA8, 0x5D98,
-    0x71A9, 0x4699, 0x1FC9, 0x28F9, 0xAD69, 0x9A59, 0xC309, 0xF439,
-    0x3B5A, 0x0C6A, 0x553A, 0x620A, 0xE79A, 0xD0AA, 0x89FA, 0xBECA,
-    0x92FB, 0xA5CB, 0xFC9B, 0xCBAB, 0x4E3B, 0x790B, 0x205B, 0x176B,
-    0x7839, 0x4F09, 0x1659, 0x2169, 0xA4F9, 0x93C9, 0xCA99, 0xFDA9,
-    0xD198, 0xE6A8, 0xBFF8, 0x88C8, 0x0D58, 0x3A68, 0x6338, 0x5408,
-    0xBD9C, 0x8AAC, 0xD3FC, 0xE4CC, 0x615C, 0x566C, 0x0F3C, 0x380C,
-    0x143D, 0x230D, 0x7A5D, 0x4D6D, 0xC8FD, 0xFFCD, 0xA69D, 0x91AD,
-    0xFEFF, 0xC9CF, 0x909F, 0xA7AF, 0x223F, 0x150F, 0x4C5F, 0x7B6F,
-    0x575E, 0x606E, 0x393E, 0x0E0E, 0x8B9E, 0xBCAE, 0xE5FE, 0xD2CE,
-    0x26F7, 0x11C7, 0x4897, 0x7FA7, 0xFA37, 0xCD07, 0x9457, 0xA367,
-    0x8F56, 0xB866, 0xE136, 0xD606, 0x5396, 0x64A6, 0x3DF6, 0x0AC6,
-    0x6594, 0x52A4, 0x0BF4, 0x3CC4, 0xB954, 0x8E64, 0xD734, 0xE004,
-    0xCC35, 0xFB05, 0xA255, 0x9565, 0x10F5, 0x27C5, 0x7E95, 0x49A5,
-    0xA031, 0x9701, 0xCE51, 0xF961, 0x7CF1, 0x4BC1, 0x1291, 0x25A1,
-    0x0990, 0x3EA0, 0x67F0, 0x50C0, 0xD550, 0xE260, 0xBB30, 0x8C00,
-    0xE352, 0xD462, 0x8D32, 0xBA02, 0x3F92, 0x08A2, 0x51F2, 0x66C2,
-    0x4AF3, 0x7DC3, 0x2493, 0x13A3, 0x9633, 0xA103, 0xF853, 0xCF63
-},{
-    0x0000, 0x76B4, 0xED68, 0x9BDC, 0xCAF1, 0xBC45, 0x2799, 0x512D,
-    0x85C3, 0xF377, 0x68AB, 0x1E1F, 0x4F32, 0x3986, 0xA25A, 0xD4EE,
-    0x1BA7, 0x6D13, 0xF6CF, 0x807B, 0xD156, 0xA7E2, 0x3C3E, 0x4A8A,
-    0x9E64, 0xE8D0, 0x730C, 0x05B8, 0x5495, 0x2221, 0xB9FD, 0xCF49,
-    0x374E, 0x41FA, 0xDA26, 0xAC92, 0xFDBF, 0x8B0B, 0x10D7, 0x6663,
-    0xB28D, 0xC439, 0x5FE5, 0x2951, 0x787C, 0x0EC8, 0x9514, 0xE3A0,
-    0x2CE9, 0x5A5D, 0xC181, 0xB735, 0xE618, 0x90AC, 0x0B70, 0x7DC4,
-    0xA92A, 0xDF9E, 0x4442, 0x32F6, 0x63DB, 0x156F, 0x8EB3, 0xF807,
-    0x6E9C, 0x1828, 0x83F4, 0xF540, 0xA46D, 0xD2D9, 0x4905, 0x3FB1,
-    0xEB5F, 0x9DEB, 0x0637, 0x7083, 0x21AE, 0x571A, 0xCCC6, 0xBA72,
-    0x753B, 0x038F, 0x9853, 0xEEE7, 0xBFCA, 0xC97E, 0x52A2, 0x2416,
-    0xF0F8, 0x864C, 0x1D90, 0x6B24, 0x3A09, 0x4CBD, 0xD761, 0xA1D5,
-    0x59D2, 0x2F66, 0xB4BA, 0xC20E, 0x9323, 0xE597, 0x7E4B, 0x08FF,
-    0xDC11, 0xAAA5, 0x3179, 0x47CD, 0x16E0, 0x6054, 0xFB88, 0x8D3C,
-    0x4275, 0x34C1, 0xAF1D, 0xD9A9, 0x8884, 0xFE30, 0x65EC, 0x1358,
-    0xC7B6, 0xB102, 0x2ADE, 0x5C6A, 0x0D47, 0x7BF3, 0xE02F, 0x969B,
-    0xDD38, 0xAB8C, 0x3050, 0x46E4, 0x17C9, 0x617D, 0xFAA1, 0x8C15,
-    0x58FB, 0x2E4F, 0xB593, 0xC327, 0x920A, 0xE4BE, 0x7F62, 0x09D6,
-    0xC69F, 0xB02B, 0x2BF7, 0x5D43, 0x0C6E, 0x7ADA, 0xE106, 0x97B2,
-    0x435C, 0x35E8, 0xAE34, 0xD880, 0x89AD, 0xFF19, 0x64C5, 0x1271,
-    0xEA76, 0x9CC2, 0x071E, 0x71AA, 0x2087, 0x5633, 0xCDEF, 0xBB5B,
-    0x6FB5, 0x1901, 0x82DD, 0xF469, 0xA544, 0xD3F0, 0x482C, 0x3E98,
-    0xF1D1, 0x8765, 0x1CB9, 0x6A0D, 0x3B20, 0x4D94, 0xD648, 0xA0FC,
-    0x7412, 0x02A6, 0x997A, 0xEFCE, 0xBEE3, 0xC857, 0x538B, 0x253F,
-    0xB3A4, 0xC510, 0x5ECC, 0x2878, 0x7955, 0x0FE1, 0x943D, 0xE289,
-    0x3667, 0x40D3, 0xDB0F, 0xADBB, 0xFC96, 0x8A22, 0x11FE, 0x674A,
-    0xA803, 0xDEB7, 0x456B, 0x33DF, 0x62F2, 0x1446, 0x8F9A, 0xF92E,
-    0x2DC0, 0x5B74, 0xC0A8, 0xB61C, 0xE731, 0x9185, 0x0A59, 0x7CED,
-    0x84EA, 0xF25E, 0x6982, 0x1F36, 0x4E1B, 0x38AF, 0xA373, 0xD5C7,
-    0x0129, 0x779D, 0xEC41, 0x9AF5, 0xCBD8, 0xBD6C, 0x26B0, 0x5004,
-    0x9F4D, 0xE9F9, 0x7225, 0x0491, 0x55BC, 0x2308, 0xB8D4, 0xCE60,
-    0x1A8E, 0x6C3A, 0xF7E6, 0x8152, 0xD07F, 0xA6CB, 0x3D17, 0x4BA3
-},{
-    0x0000, 0xAA51, 0x4483, 0xEED2, 0x8906, 0x2357, 0xCD85, 0x67D4,
-    0x022D, 0xA87C, 0x46AE, 0xECFF, 0x8B2B, 0x217A, 0xCFA8, 0x65F9,
-    0x045A, 0xAE0B, 0x40D9, 0xEA88, 0x8D5C, 0x270D, 0xC9DF, 0x638E,
-    0x0677, 0xAC26, 0x42F4, 0xE8A5, 0x8F71, 0x2520, 0xCBF2, 0x61A3,
-    0x08B4, 0xA2E5, 0x4C37, 0xE666, 0x81B2, 0x2BE3, 0xC531, 0x6F60,
-    0x0A99, 0xA0C8, 0x4E1A, 0xE44B, 0x839F, 0x29CE, 0xC71C, 0x6D4D,
-    0x0CEE, 0xA6BF, 0x486D, 0xE23C, 0x85E8, 0x2FB9, 0xC16B, 0x6B3A,
-    0x0EC3, 0xA492, 0x4A40, 0xE011, 0x87C5, 0x2D94, 0xC346, 0x6917,
-    0x1168, 0xBB39, 0x55EB, 0xFFBA, 0x986E, 0x323F, 0xDCED, 0x76BC,
-    0x1345, 0xB914, 0x57C6, 0xFD97, 0x9A43, 0x3012, 0xDEC0, 0x7491,
-    0x1532, 0xBF63, 0x51B1, 0xFBE0, 0x9C34, 0x3665, 0xD8B7, 0x72E6,
-    0x171F, 0xBD4E, 0x539C, 0xF9CD, 0x9E19, 0x3448, 0xDA9A, 0x70CB,
-    0x19DC, 0xB38D, 0x5D5F, 0xF70E, 0x90DA, 0x3A8B, 0xD459, 0x7E08,
-    0x1BF1, 0xB1A0, 0x5F72, 0xF523, 0x92F7, 0x38A6, 0xD674, 0x7C25,
-    0x1D86, 0xB7D7, 0x5905, 0xF354, 0x9480, 0x3ED1, 0xD003, 0x7A52,
-    0x1FAB, 0xB5FA, 0x5B28, 0xF179, 0x96AD, 0x3CFC, 0xD22E, 0x787F,
-    0x22D0, 0x8881, 0x6653, 0xCC02, 0xABD6, 0x0187, 0xEF55, 0x4504,
-    0x20FD, 0x8AAC, 0x647E, 0xCE2F, 0xA9FB, 0x03AA, 0xED78, 0x4729,
-    0x268A, 0x8CDB, 0x6209, 0xC858, 0xAF8C, 0x05DD, 0xEB0F, 0x415E,
-    0x24A7, 0x8EF6, 0x6024, 0xCA75, 0xADA1, 0x07F0, 0xE922, 0x4373,
-    0x2A64, 0x8035, 0x6EE7, 0xC4B6, 0xA362, 0x0933, 0xE7E1, 0x4DB0,
-    0x2849, 0x8218, 0x6CCA, 0xC69B, 0xA14F, 0x0B1E, 0xE5CC, 0x4F9D,
-    0x2E3E, 0x846F, 0x6ABD, 0xC0EC, 0xA738, 0x0D69, 0xE3BB, 0x49EA,
-    0x2C13, 0x8642, 0x6890, 0xC2C1, 0xA515, 0x0F44, 0xE196, 0x4BC7,
-    0x33B8, 0x99E9, 0x773B, 0xDD6A, 0xBABE, 0x10EF, 0xFE3D, 0x546C,
-    0x3195, 0x9BC4, 0x7516, 0xDF47, 0xB893, 0x12C2, 0xFC10, 0x5641,
-    0x37E2, 0x9DB3, 0x7361, 0xD930, 0xBEE4, 0x14B5, 0xFA67, 0x5036,
-    0x35CF, 0x9F9E, 0x714C, 0xDB1D, 0xBCC9, 0x1698, 0xF84A, 0x521B,
-    0x3B0C, 0x915D, 0x7F8F, 0xD5DE, 0xB20A, 0x185B, 0xF689, 0x5CD8,
-    0x3921, 0x9370, 0x7DA2, 0xD7F3, 0xB027, 0x1A76, 0xF4A4, 0x5EF5,
-    0x3F56, 0x9507, 0x7BD5, 0xD184, 0xB650, 0x1C01, 0xF2D3, 0x5882,
-    0x3D7B, 0x972A, 0x79F8, 0xD3A9, 0xB47D, 0x1E2C, 0xF0FE, 0x5AAF
-},{
-    0x0000, 0x45A0, 0x8B40, 0xCEE0, 0x06A1, 0x4301, 0x8DE1, 0xC841,
-    0x0D42, 0x48E2, 0x8602, 0xC3A2, 0x0BE3, 0x4E43, 0x80A3, 0xC503,
-    0x1A84, 0x5F24, 0x91C4, 0xD464, 0x1C25, 0x5985, 0x9765, 0xD2C5,
-    0x17C6, 0x5266, 0x9C86, 0xD926, 0x1167, 0x54C7, 0x9A27, 0xDF87,
-    0x3508, 0x70A8, 0xBE48, 0xFBE8, 0x33A9, 0x7609, 0xB8E9, 0xFD49,
-    0x384A, 0x7DEA, 0xB30A, 0xF6AA, 0x3EEB, 0x7B4B, 0xB5AB, 0xF00B,
-    0x2F8C, 0x6A2C, 0xA4CC, 0xE16C, 0x292D, 0x6C8D, 0xA26D, 0xE7CD,
-    0x22CE, 0x676E, 0xA98E, 0xEC2E, 0x246F, 0x61CF, 0xAF2F, 0xEA8F,
-    0x6A10, 0x2FB0, 0xE150, 0xA4F0, 0x6CB1, 0x2911, 0xE7F1, 0xA251,
-    0x6752, 0x22F2, 0xEC12, 0xA9B2, 0x61F3, 0x2453, 0xEAB3, 0xAF13,
-    0x7094, 0x3534, 0xFBD4, 0xBE74, 0x7635, 0x3395, 0xFD75, 0xB8D5,
-    0x7DD6, 0x3876, 0xF696, 0xB336, 0x7B77, 0x3ED7, 0xF037, 0xB597,
-    0x5F18, 0x1AB8, 0xD458, 0x91F8, 0x59B9, 0x1C19, 0xD2F9, 0x9759,
-    0x525A, 0x17FA, 0xD91A, 0x9CBA, 0x54FB, 0x115B, 0xDFBB, 0x9A1B,
-    0x459C, 0x003C, 0xCEDC, 0x8B7C, 0x433D, 0x069D, 0xC87D, 0x8DDD,
-    0x48DE, 0x0D7E, 0xC39E, 0x863E, 0x4E7F, 0x0BDF, 0xC53F, 0x809F,
-    0xD420, 0x9180, 0x5F60, 0x1AC0, 0xD281, 0x9721, 0x59C1, 0x1C61,
-    0xD962, 0x9CC2, 0x5222, 0x1782, 0xDFC3, 0x9A63, 0x5483, 0x1123,
-    0xCEA4, 0x8B04, 0x45E4, 0x0044, 0xC805, 0x8DA5, 0x4345, 0x06E5,
-    0xC3E6, 0x8646, 0x48A6, 0x0D06, 0xC547, 0x80E7, 0x4E07, 0x0BA7,
-    0xE128, 0xA488, 0x6A68, 0x2FC8, 0xE789, 0xA229, 0x6CC9, 0x2969,
-    0xEC6A, 0xA9CA, 0x672A, 0x228A, 0xEACB, 0xAF6B, 0x618B, 0x242B,
-    0xFBAC, 0xBE0C, 0x70EC, 0x354C, 0xFD0D, 0xB8AD, 0x764D, 0x33ED,
-    0xF6EE, 0xB34E, 0x7DAE, 0x380E, 0xF04F, 0xB5EF, 0x7B0F, 0x3EAF,
-    0xBE30, 0xFB90, 0x3570, 0x70D0, 0xB891, 0xFD31, 0x33D1, 0x7671,
-    0xB372, 0xF6D2, 0x3832, 0x7D92, 0xB5D3, 0xF073, 0x3E93, 0x7B33,
-    0xA4B4, 0xE114, 0x2FF4, 0x6A54, 0xA215, 0xE7B5, 0x2955, 0x6CF5,
-    0xA9F6, 0xEC56, 0x22B6, 0x6716, 0xAF57, 0xEAF7, 0x2417, 0x61B7,
-    0x8B38, 0xCE98, 0x0078, 0x45D8, 0x8D99, 0xC839, 0x06D9, 0x4379,
-    0x867A, 0xC3DA, 0x0D3A, 0x489A, 0x80DB, 0xC57B, 0x0B9B, 0x4E3B,
-    0x91BC, 0xD41C, 0x1AFC, 0x5F5C, 0x971D, 0xD2BD, 0x1C5D, 0x59FD,
-    0x9CFE, 0xD95E, 0x17BE, 0x521E, 0x9A5F, 0xDFFF, 0x111F, 0x54BF
-},{
-    0x0000, 0xB861, 0x60E3, 0xD882, 0xC1C6, 0x79A7, 0xA125, 0x1944,
-    0x93AD, 0x2BCC, 0xF34E, 0x4B2F, 0x526B, 0xEA0A, 0x3288, 0x8AE9,
-    0x377B, 0x8F1A, 0x5798, 0xEFF9, 0xF6BD, 0x4EDC, 0x965E, 0x2E3F,
-    0xA4D6, 0x1CB7, 0xC435, 0x7C54, 0x6510, 0xDD71, 0x05F3, 0xBD92,
-    0x6EF6, 0xD697, 0x0E15, 0xB674, 0xAF30, 0x1751, 0xCFD3, 0x77B2,
-    0xFD5B, 0x453A, 0x9DB8, 0x25D9, 0x3C9D, 0x84FC, 0x5C7E, 0xE41F,
-    0x598D, 0xE1EC, 0x396E, 0x810F, 0x984B, 0x202A, 0xF8A8, 0x40C9,
-    0xCA20, 0x7241, 0xAAC3, 0x12A2, 0x0BE6, 0xB387, 0x6B05, 0xD364,
-    0xDDEC, 0x658D, 0xBD0F, 0x056E, 0x1C2A, 0xA44B, 0x7CC9, 0xC4A8,
-    0x4E41, 0xF620, 0x2EA2, 0x96C3, 0x8F87, 0x37E6, 0xEF64, 0x5705,
-    0xEA97, 0x52F6, 0x8A74, 0x3215, 0x2B51, 0x9330, 0x4BB2, 0xF3D3,
-    0x793A, 0xC15B, 0x19D9, 0xA1B8, 0xB8FC, 0x009D, 0xD81F, 0x607E,
-    0xB31A, 0x0B7B, 0xD3F9, 0x6B98, 0x72DC, 0xCABD, 0x123F, 0xAA5E,
-    0x20B7, 0x98D6, 0x4054, 0xF835, 0xE171, 0x5910, 0x8192, 0x39F3,
-    0x8461, 0x3C00, 0xE482, 0x5CE3, 0x45A7, 0xFDC6, 0x2544, 0x9D25,
-    0x17CC, 0xAFAD, 0x772F, 0xCF4E, 0xD60A, 0x6E6B, 0xB6E9, 0x0E88,
-    0xABF9, 0x1398, 0xCB1A, 0x737B, 0x6A3F, 0xD25E, 0x0ADC, 0xB2BD,
-    0x3854, 0x8035, 0x58B7, 0xE0D6, 0xF992, 0x41F3, 0x9971, 0x2110,
-    0x9C82, 0x24E3, 0xFC61, 0x4400, 0x5D44, 0xE525, 0x3DA7, 0x85C6,
-    0x0F2F, 0xB74E, 0x6FCC, 0xD7AD, 0xCEE9, 0x7688, 0xAE0A, 0x166B,
-    0xC50F, 0x7D6E, 0xA5EC, 0x1D8D, 0x04C9, 0xBCA8, 0x642A, 0xDC4B,
-    0x56A2, 0xEEC3, 0x3641, 0x8E20, 0x9764, 0x2F05, 0xF787, 0x4FE6,
-    0xF274, 0x4A15, 0x9297, 0x2AF6, 0x33B2, 0x8BD3, 0x5351, 0xEB30,
-    0x61D9, 0xD9B8, 0x013A, 0xB95B, 0xA01F, 0x187E, 0xC0FC, 0x789D,
-    0x7615, 0xCE74, 0x16F6, 0xAE97, 0xB7D3, 0x0FB2, 0xD730, 0x6F51,
-    0xE5B8, 0x5DD9, 0x855B, 0x3D3A, 0x247E, 0x9C1F, 0x449D, 0xFCFC,
-    0x416E, 0xF90F, 0x218D, 0x99EC, 0x80A8, 0x38C9, 0xE04B, 0x582A,
-    0xD2C3, 0x6AA2, 0xB220, 0x0A41, 0x1305, 0xAB64, 0x73E6, 0xCB87,
-    0x18E3, 0xA082, 0x7800, 0xC061, 0xD925, 0x6144, 0xB9C6, 0x01A7,
-    0x8B4E, 0x332F, 0xEBAD, 0x53CC, 0x4A88, 0xF2E9, 0x2A6B, 0x920A,
-    0x2F98, 0x97F9, 0x4F7B, 0xF71A, 0xEE5E, 0x563F, 0x8EBD, 0x36DC,
-    0xBC35, 0x0454, 0xDCD6, 0x64B7, 0x7DF3, 0xC592, 0x1D10, 0xA571
-},{
-    0x0000, 0x47D3, 0x8FA6, 0xC875, 0x0F6D, 0x48BE, 0x80CB, 0xC718,
-    0x1EDA, 0x5909, 0x917C, 0xD6AF, 0x11B7, 0x5664, 0x9E11, 0xD9C2,
-    0x3DB4, 0x7A67, 0xB212, 0xF5C1, 0x32D9, 0x750A, 0xBD7F, 0xFAAC,
-    0x236E, 0x64BD, 0xACC8, 0xEB1B, 0x2C03, 0x6BD0, 0xA3A5, 0xE476,
-    0x7B68, 0x3CBB, 0xF4CE, 0xB31D, 0x7405, 0x33D6, 0xFBA3, 0xBC70,
-    0x65B2, 0x2261, 0xEA14, 0xADC7, 0x6ADF, 0x2D0C, 0xE579, 0xA2AA,
-    0x46DC, 0x010F, 0xC97A, 0x8EA9, 0x49B1, 0x0E62, 0xC617, 0x81C4,
-    0x5806, 0x1FD5, 0xD7A0, 0x9073, 0x576B, 0x10B8, 0xD8CD, 0x9F1E,
-    0xF6D0, 0xB103, 0x7976, 0x3EA5, 0xF9BD, 0xBE6E, 0x761B, 0x31C8,
-    0xE80A, 0xAFD9, 0x67AC, 0x207F, 0xE767, 0xA0B4, 0x68C1, 0x2F12,
-    0xCB64, 0x8CB7, 0x44C2, 0x0311, 0xC409, 0x83DA, 0x4BAF, 0x0C7C,
-    0xD5BE, 0x926D, 0x5A18, 0x1DCB, 0xDAD3, 0x9D00, 0x5575, 0x12A6,
-    0x8DB8, 0xCA6B, 0x021E, 0x45CD, 0x82D5, 0xC506, 0x0D73, 0x4AA0,
-    0x9362, 0xD4B1, 0x1CC4, 0x5B17, 0x9C0F, 0xDBDC, 0x13A9, 0x547A,
-    0xB00C, 0xF7DF, 0x3FAA, 0x7879, 0xBF61, 0xF8B2, 0x30C7, 0x7714,
-    0xAED6, 0xE905, 0x2170, 0x66A3, 0xA1BB, 0xE668, 0x2E1D, 0x69CE,
-    0xFD81, 0xBA52, 0x7227, 0x35F4, 0xF2EC, 0xB53F, 0x7D4A, 0x3A99,
-    0xE35B, 0xA488, 0x6CFD, 0x2B2E, 0xEC36, 0xABE5, 0x6390, 0x2443,
-    0xC035, 0x87E6, 0x4F93, 0x0840, 0xCF58, 0x888B, 0x40FE, 0x072D,
-    0xDEEF, 0x993C, 0x5149, 0x169A, 0xD182, 0x9651, 0x5E24, 0x19F7,
-    0x86E9, 0xC13A, 0x094F, 0x4E9C, 0x8984, 0xCE57, 0x0622, 0x41F1,
-    0x9833, 0xDFE0, 0x1795, 0x5046, 0x975E, 0xD08D, 0x18F8, 0x5F2B,
-    0xBB5D, 0xFC8E, 0x34FB, 0x7328, 0xB430, 0xF3E3, 0x3B96, 0x7C45,
-    0xA587, 0xE254, 0x2A21, 0x6DF2, 0xAAEA, 0xED39, 0x254C, 0x629F,
-    0x0B51, 0x4C82, 0x84F7, 0xC324, 0x043C, 0x43EF, 0x8B9A, 0xCC49,
-    0x158B, 0x5258, 0x9A2D, 0xDDFE, 0x1AE6, 0x5D35, 0x9540, 0xD293,
-    0x36E5, 0x7136, 0xB943, 0xFE90, 0x3988, 0x7E5B, 0xB62E, 0xF1FD,
-    0x283F, 0x6FEC, 0xA799, 0xE04A, 0x2752, 0x6081, 0xA8F4, 0xEF27,
-    0x7039, 0x37EA, 0xFF9F, 0xB84C, 0x7F54, 0x3887, 0xF0F2, 0xB721,
-    0x6EE3, 0x2930, 0xE145, 0xA696, 0x618E, 0x265D, 0xEE28, 0xA9FB,
-    0x4D8D, 0x0A5E, 0xC22B, 0x85F8, 0x42E0, 0x0533, 0xCD46, 0x8A95,
-    0x5357, 0x1484, 0xDCF1, 0x9B22, 0x5C3A, 0x1BE9, 0xD39C, 0x944F
-}};
-
-/* Non - Reflected */
-uint16_t util_crc16(uint16_t current, const char *GMQCC_RESTRICT k, size_t len) {
-    register uint16_t h = current;
-
-    /* don't load twice */
-    const uint8_t *GMQCC_RESTRICT data = (const uint8_t *GMQCC_RESTRICT)k;
-    size_t n;
-
-    /* deal with the first bytes as bytes until we reach an 8 byte boundary */
-    while (len & 7) {
-        h = (uint16_t)(h << 8) ^ (*util_crc16_table)[(h >> 8) ^ *data++];
-        --len;
-    }
-
-    #define SELECT_BULK(X, MOD) util_crc16_table[(X)][data[7-(X)] ^ (MOD)]
-    #define SELECT_DATA(X)      util_crc16_table[(X)][data[7-(X)]]
-
-    for (n = len / 8; n; --n) {
-        h = SELECT_BULK(7, (h >> 8))   ^
-            SELECT_BULK(6, (h & 0xFF)) ^
-            SELECT_DATA(5) ^
-            SELECT_DATA(4) ^
-            SELECT_DATA(3) ^
-            SELECT_DATA(2) ^
-            SELECT_DATA(1) ^
-            SELECT_DATA(0);
-        data += 8;
-        len  -= 8;
-    }
-
-    #undef SELECT_BULK
-    #undef SELECT_DATA
-
-    /* deal with the rest with the byte method */
-    for (n = len & 7; n; --n)
-        h = (uint16_t)(h << 8) ^ (*util_crc16_table)[(h >> 8) ^ *data++];
-
-    return h;
-}
-
-/*
- * modifier is the match to make and the transposition from it, while add is the upper-value that determines the
- * transposition from uppercase to lower case.
- */
-static size_t util_strtransform(const char *in, char *out, size_t outsz, const char *mod, int add) {
-    size_t sz = 1;
-    for (; *in && sz < outsz; ++in, ++out, ++sz) {
-        *out = (*in == mod[0])
-                    ? mod[1]
-                    : (util_isalpha(*in) && ((add > 0) ? util_isupper(*in) : !util_isupper(*in)))
-                        ? *in + add
-                        : *in;
-    }
-    *out = 0;
-    return sz-1;
-}
-
-size_t util_strtocmd(const char *in, char *out, size_t outsz) {
-    return util_strtransform(in, out, outsz, "-_", 'A'-'a');
-}
-size_t util_strtononcmd(const char *in, char *out, size_t outsz) {
-    return util_strtransform(in, out, outsz, "_-", 'a'-'A');
-}
-size_t util_optimizationtostr(const char *in, char *out, size_t outsz) {
-    return util_strtransform(in, out, outsz, "_ ", 'a'-'A');
-}
-
-int util_snprintf(char *str, size_t size, const char *fmt, ...) {
-    va_list  arg;
-    int      ret;
-
-    va_start(arg, fmt);
-    ret = platform_vsnprintf(str, size, fmt, arg);
-    va_end(arg);
-
-    return ret;
-}
-
-int util_asprintf(char **ret, const char *fmt, ...) {
-    va_list  args;
-    int      read;
-
-    va_start(args, fmt);
-    read = platform_vasprintf(ret, fmt, args);
-    va_end  (args);
-
-    return read;
-}
-
-int util_sscanf(const char *str, const char *format, ...) {
-    va_list  args;
-    int      read;
-
-    va_start(args, format);
-    read = platform_vsscanf(str, format, args);
-    va_end(args);
-
-    return read;
-}
-
-char *util_strncpy(char *dest, const char *src, size_t n) {
-    return platform_strncpy(dest, src, n);
-}
-char *util_strncat(char *dest, const char *src, size_t n) {
-    return platform_strncat(dest, src, n);
-}
-char *util_strcat(char *dest, const char *src) {
-    return platform_strcat(dest, src);
-}
-const char *util_strerror(int err) {
-    return platform_strerror(err);
-}
-
-const struct tm *util_localtime(const time_t *timer) {
-    return platform_localtime(timer);
-}
-const char *util_ctime(const time_t *timer) {
-    return platform_ctime(timer);
-}
-
-bool util_isatty(fs_file_t *file) {
-    if (file == (fs_file_t*)stdout) return !!platform_isatty(STDOUT_FILENO);
-    if (file == (fs_file_t*)stderr) return !!platform_isatty(STDERR_FILENO);
-    return false;
-}
-/*
- * A small noncryptographic PRNG based on:
- * http://burtleburtle.net/bob/rand/smallprng.html
- */
-static uint32_t util_rand_state[4] = {
-    0xF1EA5EED, 0x00000000,
-    0x00000000, 0x00000000
-};
-
-#define util_rand_rot(X, Y) (((X)<<(Y))|((X)>>(32-(Y))))
-
-uint32_t util_rand() {
-    uint32_t last;
-
-    last               = util_rand_state[0] - util_rand_rot(util_rand_state[1], 27);
-    util_rand_state[0] = util_rand_state[1] ^ util_rand_rot(util_rand_state[2], 17);
-    util_rand_state[1] = util_rand_state[2] + util_rand_state[3];
-    util_rand_state[2] = util_rand_state[3] + last;
-    util_rand_state[3] = util_rand_state[0] + last;
-
-    return util_rand_state[3];
-}
-
-#undef util_rand_rot
-
-void util_seed(uint32_t value) {
-    size_t i;
-
-    util_rand_state[0] = 0xF1EA5EED;
-    util_rand_state[1] = value;
-    util_rand_state[2] = value;
-    util_rand_state[3] = value;
-
-    for (i = 0; i < 20; ++i)
-        (void)util_rand();
-}
-
diff --git a/util.cpp b/util.cpp
new file mode 100644 (file)
index 0000000..ead50dd
--- /dev/null
+++ b/util.cpp
@@ -0,0 +1,726 @@
+#include <stdlib.h>
+#include <string.h>
+#include "gmqcc.h"
+
+const char *util_instr_str[VINSTR_END] = {
+    "DONE",       "MUL_F",      "MUL_V",      "MUL_FV",
+    "MUL_VF",     "DIV_F",      "ADD_F",      "ADD_V",
+    "SUB_F",      "SUB_V",      "EQ_F",       "EQ_V",
+    "EQ_S",       "EQ_E",       "EQ_FNC",     "NE_F",
+    "NE_V",       "NE_S",       "NE_E",       "NE_FNC",
+    "LE",         "GE",         "LT",         "GT",
+    "LOAD_F",     "LOAD_V",     "LOAD_S",     "LOAD_ENT",
+    "LOAD_FLD",   "LOAD_FNC",   "ADDRESS",    "STORE_F",
+    "STORE_V",    "STORE_S",    "STORE_ENT",  "STORE_FLD",
+    "STORE_FNC",  "STOREP_F",   "STOREP_V",   "STOREP_S",
+    "STOREP_ENT", "STOREP_FLD", "STOREP_FNC", "RETURN",
+    "NOT_F",      "NOT_V",      "NOT_S",      "NOT_ENT",
+    "NOT_FNC",    "IF",         "IFNOT",      "CALL0",
+    "CALL1",      "CALL2",      "CALL3",      "CALL4",
+    "CALL5",      "CALL6",      "CALL7",      "CALL8",
+    "STATE",      "GOTO",       "AND",        "OR",
+    "BITAND",     "BITOR"
+};
+
+/*
+ * only required if big endian .. otherwise no need to swap
+ * data.
+ */
+#if PLATFORM_BYTE_ORDER == GMQCC_BYTE_ORDER_BIG || PLATFORM_BYTE_ORDER == -1
+    static GMQCC_INLINE void util_swap16(uint16_t *d, size_t l) {
+        while (l--) {
+            d[l] = (d[l] << 8) | (d[l] >> 8);
+        }
+    }
+
+    static GMQCC_INLINE void util_swap32(uint32_t *d, size_t l) {
+        while (l--) {
+            uint32_t v;
+            v = ((d[l] << 8) & 0xFF00FF00) | ((d[l] >> 8) & 0x00FF00FF);
+            d[l] = (v << 16) | (v >> 16);
+        }
+    }
+
+    /* Some strange system doesn't like constants that big, AND doesn't recognize an ULL suffix
+     * so let's go the safe way
+     */
+    static GMQCC_INLINE void util_swap64(uint32_t *d, size_t l) {
+        while (l--) {
+            uint64_t v;
+            v = ((d[l] << 8) & 0xFF00FF00FF00FF00) | ((d[l] >> 8) & 0x00FF00FF00FF00FF);
+            v = ((v << 16) & 0xFFFF0000FFFF0000) | ((v >> 16) & 0x0000FFFF0000FFFF);
+            d[l] = (v << 32) | (v >> 32);
+        }
+    }
+#endif
+
+void util_endianswap(void *_data, size_t count, unsigned int typesize) {
+#   if PLATFORM_BYTE_ORDER == -1 /* runtime check */
+    if (*((char*)&typesize))
+        return;
+#else
+
+#   if PLATFORM_BYTE_ORDER == GMQCC_BYTE_ORDER_LITTLE
+        /* prevent unused warnings */
+        (void) _data;
+        (void) count;
+        (void) typesize;
+        return;
+#   else
+        switch (typesize) {
+            case 1: return;
+            case 2:
+                util_swap16((uint16_t*)_data, count);
+                return;
+            case 4:
+                util_swap32((uint32_t*)_data, count);
+                return;
+            case 8:
+                util_swap64((uint32_t*)_data, count);
+                return;
+
+            default:
+                con_err ("util_endianswap: I don't know how to swap a %u byte structure!\n", typesize);
+                exit(EXIT_FAILURE); /* please blow the fuck up! */
+        }
+#   endif
+#endif
+}
+
+void util_swap_header(prog_header_t &code_header) {
+    util_endianswap(&code_header.version,              1, sizeof(code_header.version));
+    util_endianswap(&code_header.crc16,                1, sizeof(code_header.crc16));
+    util_endianswap(&code_header.statements.offset,    1, sizeof(code_header.statements.offset));
+    util_endianswap(&code_header.statements.length,    1, sizeof(code_header.statements.length));
+    util_endianswap(&code_header.defs.offset,          1, sizeof(code_header.defs.offset));
+    util_endianswap(&code_header.defs.length,          1, sizeof(code_header.defs.length));
+    util_endianswap(&code_header.fields.offset,        1, sizeof(code_header.fields.offset));
+    util_endianswap(&code_header.fields.length,        1, sizeof(code_header.fields.length));
+    util_endianswap(&code_header.functions.offset,     1, sizeof(code_header.functions.offset));
+    util_endianswap(&code_header.functions.length,     1, sizeof(code_header.functions.length));
+    util_endianswap(&code_header.strings.offset,       1, sizeof(code_header.strings.offset));
+    util_endianswap(&code_header.strings.length,       1, sizeof(code_header.strings.length));
+    util_endianswap(&code_header.globals.offset,       1, sizeof(code_header.globals.offset));
+    util_endianswap(&code_header.globals.length,       1, sizeof(code_header.globals.length));
+    util_endianswap(&code_header.entfield,             1, sizeof(code_header.entfield));
+}
+
+void util_swap_statements(std::vector<prog_section_statement_t> &statements) {
+    for (auto &it : statements) {
+        util_endianswap(&it.opcode,  1, sizeof(it.opcode));
+        util_endianswap(&it.o1,      1, sizeof(it.o1));
+        util_endianswap(&it.o2,      1, sizeof(it.o2));
+        util_endianswap(&it.o3,      1, sizeof(it.o3));
+    }
+}
+
+void util_swap_defs_fields(std::vector<prog_section_both_t> &section) {
+    for (auto &it : section) {
+        util_endianswap(&it.type,   1, sizeof(it.type));
+        util_endianswap(&it.offset, 1, sizeof(it.offset));
+        util_endianswap(&it.name,   1, sizeof(it.name));
+    }
+}
+
+void util_swap_functions(std::vector<prog_section_function_t> &functions) {
+    for (auto &it : functions) {
+        util_endianswap(&it.entry,        1, sizeof(it.entry));
+        util_endianswap(&it.firstlocal,   1, sizeof(it.firstlocal));
+        util_endianswap(&it.locals,       1, sizeof(it.locals));
+        util_endianswap(&it.profile,      1, sizeof(it.profile));
+        util_endianswap(&it.name,         1, sizeof(it.name));
+        util_endianswap(&it.file,         1, sizeof(it.file));
+        util_endianswap(&it.nargs,        1, sizeof(it.nargs));
+        /* Don't swap argsize[] - it's just a byte array, which Quake uses only as such. */
+    }
+}
+
+void util_swap_globals(std::vector<int32_t> &globals) {
+    util_endianswap(&globals[0], globals.size(), sizeof(int32_t));
+}
+
+/*
+* Based On:
+*   Slicing-by-8 algorithms by Michael E.
+*       Kounavis and Frank L. Berry from Intel Corp.
+*       http://www.intel.com/technology/comms/perfnet/download/CRC_generators.pdf
+*
+*   This code was made to be slightly less confusing with macros, which
+*   I suppose is somewhat ironic.
+*
+*   The code had to be changed for non reflected on the output register
+*   since that's the method Quake uses.
+*
+*   The code also had to be changed for CRC16, which is slightly harder
+*   since the CRC32 method in the original Intel paper used a different
+*   bit order convention.
+*
+* Notes about the table:
+*   - It's exactly 4K in size
+*   - 64 elements fit in a cache line
+*   - can do 8 iterations unrolled 8 times for free
+*   - The first 256 elements of the table are standard CRC16 table
+*
+* Table can be generated with the following utility:
+*/
+#if 0
+#include <stdio.h>
+#include <stdint.h>
+int main(void) {
+    for (unsigned i = 0; i < 0x100; ++i) {
+        uint16_t x = i << 8;
+        for (int j = 0; j < 8; ++j)
+            x = (x << 1) ^ ((x & 0x8000) ? 0x1021 : 0);
+        tab[0][i] = x;
+    }
+    for (unsigned i = 0; i < 0x100; ++i) {
+        uint16_t c = tab[0][i];
+        for (unsigned j = 1; j < 8; ++j) {
+            c = tab[0][c >> 8] ^ (c << 8);
+            tab[j][i] = c;
+        }
+    }
+    printf("static const uint16_t util_crc16_table[8][256] = {");
+    for (int i = 0; i < 8; ++i) {
+        printf("{\n");
+        for (int j = 0; j < 0x100; ++j) {
+            printf((j & 7) ? " " : "    ");
+            printf((j != 0x100-1) ? "0x%04X," : "0x%04X", tab[i][j]);
+            if ((j & 7) == 7)
+                printf("\n");
+        }
+        printf((i != 7) ? "}," : "}");
+    }
+    printf("};\n");
+    return 0;
+}
+#endif
+/*
+ * Non-Reflective version is present as well as a reference.
+ *
+ * TODO:
+ *  combine the crc16 into u32s and mask off low high for byte order
+ *  to make the arrays smaller.
+ */
+
+static const uint16_t util_crc16_table[8][256] = {{
+    0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
+    0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
+    0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
+    0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
+    0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
+    0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
+    0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
+    0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
+    0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
+    0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
+    0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
+    0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
+    0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
+    0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
+    0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
+    0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
+    0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
+    0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
+    0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
+    0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
+    0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
+    0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
+    0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
+    0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
+    0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
+    0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
+    0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
+    0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
+    0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
+    0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
+    0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
+    0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
+},{
+    0x0000, 0x3331, 0x6662, 0x5553, 0xCCC4, 0xFFF5, 0xAAA6, 0x9997,
+    0x89A9, 0xBA98, 0xEFCB, 0xDCFA, 0x456D, 0x765C, 0x230F, 0x103E,
+    0x0373, 0x3042, 0x6511, 0x5620, 0xCFB7, 0xFC86, 0xA9D5, 0x9AE4,
+    0x8ADA, 0xB9EB, 0xECB8, 0xDF89, 0x461E, 0x752F, 0x207C, 0x134D,
+    0x06E6, 0x35D7, 0x6084, 0x53B5, 0xCA22, 0xF913, 0xAC40, 0x9F71,
+    0x8F4F, 0xBC7E, 0xE92D, 0xDA1C, 0x438B, 0x70BA, 0x25E9, 0x16D8,
+    0x0595, 0x36A4, 0x63F7, 0x50C6, 0xC951, 0xFA60, 0xAF33, 0x9C02,
+    0x8C3C, 0xBF0D, 0xEA5E, 0xD96F, 0x40F8, 0x73C9, 0x269A, 0x15AB,
+    0x0DCC, 0x3EFD, 0x6BAE, 0x589F, 0xC108, 0xF239, 0xA76A, 0x945B,
+    0x8465, 0xB754, 0xE207, 0xD136, 0x48A1, 0x7B90, 0x2EC3, 0x1DF2,
+    0x0EBF, 0x3D8E, 0x68DD, 0x5BEC, 0xC27B, 0xF14A, 0xA419, 0x9728,
+    0x8716, 0xB427, 0xE174, 0xD245, 0x4BD2, 0x78E3, 0x2DB0, 0x1E81,
+    0x0B2A, 0x381B, 0x6D48, 0x5E79, 0xC7EE, 0xF4DF, 0xA18C, 0x92BD,
+    0x8283, 0xB1B2, 0xE4E1, 0xD7D0, 0x4E47, 0x7D76, 0x2825, 0x1B14,
+    0x0859, 0x3B68, 0x6E3B, 0x5D0A, 0xC49D, 0xF7AC, 0xA2FF, 0x91CE,
+    0x81F0, 0xB2C1, 0xE792, 0xD4A3, 0x4D34, 0x7E05, 0x2B56, 0x1867,
+    0x1B98, 0x28A9, 0x7DFA, 0x4ECB, 0xD75C, 0xE46D, 0xB13E, 0x820F,
+    0x9231, 0xA100, 0xF453, 0xC762, 0x5EF5, 0x6DC4, 0x3897, 0x0BA6,
+    0x18EB, 0x2BDA, 0x7E89, 0x4DB8, 0xD42F, 0xE71E, 0xB24D, 0x817C,
+    0x9142, 0xA273, 0xF720, 0xC411, 0x5D86, 0x6EB7, 0x3BE4, 0x08D5,
+    0x1D7E, 0x2E4F, 0x7B1C, 0x482D, 0xD1BA, 0xE28B, 0xB7D8, 0x84E9,
+    0x94D7, 0xA7E6, 0xF2B5, 0xC184, 0x5813, 0x6B22, 0x3E71, 0x0D40,
+    0x1E0D, 0x2D3C, 0x786F, 0x4B5E, 0xD2C9, 0xE1F8, 0xB4AB, 0x879A,
+    0x97A4, 0xA495, 0xF1C6, 0xC2F7, 0x5B60, 0x6851, 0x3D02, 0x0E33,
+    0x1654, 0x2565, 0x7036, 0x4307, 0xDA90, 0xE9A1, 0xBCF2, 0x8FC3,
+    0x9FFD, 0xACCC, 0xF99F, 0xCAAE, 0x5339, 0x6008, 0x355B, 0x066A,
+    0x1527, 0x2616, 0x7345, 0x4074, 0xD9E3, 0xEAD2, 0xBF81, 0x8CB0,
+    0x9C8E, 0xAFBF, 0xFAEC, 0xC9DD, 0x504A, 0x637B, 0x3628, 0x0519,
+    0x10B2, 0x2383, 0x76D0, 0x45E1, 0xDC76, 0xEF47, 0xBA14, 0x8925,
+    0x991B, 0xAA2A, 0xFF79, 0xCC48, 0x55DF, 0x66EE, 0x33BD, 0x008C,
+    0x13C1, 0x20F0, 0x75A3, 0x4692, 0xDF05, 0xEC34, 0xB967, 0x8A56,
+    0x9A68, 0xA959, 0xFC0A, 0xCF3B, 0x56AC, 0x659D, 0x30CE, 0x03FF
+},{
+    0x0000, 0x3730, 0x6E60, 0x5950, 0xDCC0, 0xEBF0, 0xB2A0, 0x8590,
+    0xA9A1, 0x9E91, 0xC7C1, 0xF0F1, 0x7561, 0x4251, 0x1B01, 0x2C31,
+    0x4363, 0x7453, 0x2D03, 0x1A33, 0x9FA3, 0xA893, 0xF1C3, 0xC6F3,
+    0xEAC2, 0xDDF2, 0x84A2, 0xB392, 0x3602, 0x0132, 0x5862, 0x6F52,
+    0x86C6, 0xB1F6, 0xE8A6, 0xDF96, 0x5A06, 0x6D36, 0x3466, 0x0356,
+    0x2F67, 0x1857, 0x4107, 0x7637, 0xF3A7, 0xC497, 0x9DC7, 0xAAF7,
+    0xC5A5, 0xF295, 0xABC5, 0x9CF5, 0x1965, 0x2E55, 0x7705, 0x4035,
+    0x6C04, 0x5B34, 0x0264, 0x3554, 0xB0C4, 0x87F4, 0xDEA4, 0xE994,
+    0x1DAD, 0x2A9D, 0x73CD, 0x44FD, 0xC16D, 0xF65D, 0xAF0D, 0x983D,
+    0xB40C, 0x833C, 0xDA6C, 0xED5C, 0x68CC, 0x5FFC, 0x06AC, 0x319C,
+    0x5ECE, 0x69FE, 0x30AE, 0x079E, 0x820E, 0xB53E, 0xEC6E, 0xDB5E,
+    0xF76F, 0xC05F, 0x990F, 0xAE3F, 0x2BAF, 0x1C9F, 0x45CF, 0x72FF,
+    0x9B6B, 0xAC5B, 0xF50B, 0xC23B, 0x47AB, 0x709B, 0x29CB, 0x1EFB,
+    0x32CA, 0x05FA, 0x5CAA, 0x6B9A, 0xEE0A, 0xD93A, 0x806A, 0xB75A,
+    0xD808, 0xEF38, 0xB668, 0x8158, 0x04C8, 0x33F8, 0x6AA8, 0x5D98,
+    0x71A9, 0x4699, 0x1FC9, 0x28F9, 0xAD69, 0x9A59, 0xC309, 0xF439,
+    0x3B5A, 0x0C6A, 0x553A, 0x620A, 0xE79A, 0xD0AA, 0x89FA, 0xBECA,
+    0x92FB, 0xA5CB, 0xFC9B, 0xCBAB, 0x4E3B, 0x790B, 0x205B, 0x176B,
+    0x7839, 0x4F09, 0x1659, 0x2169, 0xA4F9, 0x93C9, 0xCA99, 0xFDA9,
+    0xD198, 0xE6A8, 0xBFF8, 0x88C8, 0x0D58, 0x3A68, 0x6338, 0x5408,
+    0xBD9C, 0x8AAC, 0xD3FC, 0xE4CC, 0x615C, 0x566C, 0x0F3C, 0x380C,
+    0x143D, 0x230D, 0x7A5D, 0x4D6D, 0xC8FD, 0xFFCD, 0xA69D, 0x91AD,
+    0xFEFF, 0xC9CF, 0x909F, 0xA7AF, 0x223F, 0x150F, 0x4C5F, 0x7B6F,
+    0x575E, 0x606E, 0x393E, 0x0E0E, 0x8B9E, 0xBCAE, 0xE5FE, 0xD2CE,
+    0x26F7, 0x11C7, 0x4897, 0x7FA7, 0xFA37, 0xCD07, 0x9457, 0xA367,
+    0x8F56, 0xB866, 0xE136, 0xD606, 0x5396, 0x64A6, 0x3DF6, 0x0AC6,
+    0x6594, 0x52A4, 0x0BF4, 0x3CC4, 0xB954, 0x8E64, 0xD734, 0xE004,
+    0xCC35, 0xFB05, 0xA255, 0x9565, 0x10F5, 0x27C5, 0x7E95, 0x49A5,
+    0xA031, 0x9701, 0xCE51, 0xF961, 0x7CF1, 0x4BC1, 0x1291, 0x25A1,
+    0x0990, 0x3EA0, 0x67F0, 0x50C0, 0xD550, 0xE260, 0xBB30, 0x8C00,
+    0xE352, 0xD462, 0x8D32, 0xBA02, 0x3F92, 0x08A2, 0x51F2, 0x66C2,
+    0x4AF3, 0x7DC3, 0x2493, 0x13A3, 0x9633, 0xA103, 0xF853, 0xCF63
+},{
+    0x0000, 0x76B4, 0xED68, 0x9BDC, 0xCAF1, 0xBC45, 0x2799, 0x512D,
+    0x85C3, 0xF377, 0x68AB, 0x1E1F, 0x4F32, 0x3986, 0xA25A, 0xD4EE,
+    0x1BA7, 0x6D13, 0xF6CF, 0x807B, 0xD156, 0xA7E2, 0x3C3E, 0x4A8A,
+    0x9E64, 0xE8D0, 0x730C, 0x05B8, 0x5495, 0x2221, 0xB9FD, 0xCF49,
+    0x374E, 0x41FA, 0xDA26, 0xAC92, 0xFDBF, 0x8B0B, 0x10D7, 0x6663,
+    0xB28D, 0xC439, 0x5FE5, 0x2951, 0x787C, 0x0EC8, 0x9514, 0xE3A0,
+    0x2CE9, 0x5A5D, 0xC181, 0xB735, 0xE618, 0x90AC, 0x0B70, 0x7DC4,
+    0xA92A, 0xDF9E, 0x4442, 0x32F6, 0x63DB, 0x156F, 0x8EB3, 0xF807,
+    0x6E9C, 0x1828, 0x83F4, 0xF540, 0xA46D, 0xD2D9, 0x4905, 0x3FB1,
+    0xEB5F, 0x9DEB, 0x0637, 0x7083, 0x21AE, 0x571A, 0xCCC6, 0xBA72,
+    0x753B, 0x038F, 0x9853, 0xEEE7, 0xBFCA, 0xC97E, 0x52A2, 0x2416,
+    0xF0F8, 0x864C, 0x1D90, 0x6B24, 0x3A09, 0x4CBD, 0xD761, 0xA1D5,
+    0x59D2, 0x2F66, 0xB4BA, 0xC20E, 0x9323, 0xE597, 0x7E4B, 0x08FF,
+    0xDC11, 0xAAA5, 0x3179, 0x47CD, 0x16E0, 0x6054, 0xFB88, 0x8D3C,
+    0x4275, 0x34C1, 0xAF1D, 0xD9A9, 0x8884, 0xFE30, 0x65EC, 0x1358,
+    0xC7B6, 0xB102, 0x2ADE, 0x5C6A, 0x0D47, 0x7BF3, 0xE02F, 0x969B,
+    0xDD38, 0xAB8C, 0x3050, 0x46E4, 0x17C9, 0x617D, 0xFAA1, 0x8C15,
+    0x58FB, 0x2E4F, 0xB593, 0xC327, 0x920A, 0xE4BE, 0x7F62, 0x09D6,
+    0xC69F, 0xB02B, 0x2BF7, 0x5D43, 0x0C6E, 0x7ADA, 0xE106, 0x97B2,
+    0x435C, 0x35E8, 0xAE34, 0xD880, 0x89AD, 0xFF19, 0x64C5, 0x1271,
+    0xEA76, 0x9CC2, 0x071E, 0x71AA, 0x2087, 0x5633, 0xCDEF, 0xBB5B,
+    0x6FB5, 0x1901, 0x82DD, 0xF469, 0xA544, 0xD3F0, 0x482C, 0x3E98,
+    0xF1D1, 0x8765, 0x1CB9, 0x6A0D, 0x3B20, 0x4D94, 0xD648, 0xA0FC,
+    0x7412, 0x02A6, 0x997A, 0xEFCE, 0xBEE3, 0xC857, 0x538B, 0x253F,
+    0xB3A4, 0xC510, 0x5ECC, 0x2878, 0x7955, 0x0FE1, 0x943D, 0xE289,
+    0x3667, 0x40D3, 0xDB0F, 0xADBB, 0xFC96, 0x8A22, 0x11FE, 0x674A,
+    0xA803, 0xDEB7, 0x456B, 0x33DF, 0x62F2, 0x1446, 0x8F9A, 0xF92E,
+    0x2DC0, 0x5B74, 0xC0A8, 0xB61C, 0xE731, 0x9185, 0x0A59, 0x7CED,
+    0x84EA, 0xF25E, 0x6982, 0x1F36, 0x4E1B, 0x38AF, 0xA373, 0xD5C7,
+    0x0129, 0x779D, 0xEC41, 0x9AF5, 0xCBD8, 0xBD6C, 0x26B0, 0x5004,
+    0x9F4D, 0xE9F9, 0x7225, 0x0491, 0x55BC, 0x2308, 0xB8D4, 0xCE60,
+    0x1A8E, 0x6C3A, 0xF7E6, 0x8152, 0xD07F, 0xA6CB, 0x3D17, 0x4BA3
+},{
+    0x0000, 0xAA51, 0x4483, 0xEED2, 0x8906, 0x2357, 0xCD85, 0x67D4,
+    0x022D, 0xA87C, 0x46AE, 0xECFF, 0x8B2B, 0x217A, 0xCFA8, 0x65F9,
+    0x045A, 0xAE0B, 0x40D9, 0xEA88, 0x8D5C, 0x270D, 0xC9DF, 0x638E,
+    0x0677, 0xAC26, 0x42F4, 0xE8A5, 0x8F71, 0x2520, 0xCBF2, 0x61A3,
+    0x08B4, 0xA2E5, 0x4C37, 0xE666, 0x81B2, 0x2BE3, 0xC531, 0x6F60,
+    0x0A99, 0xA0C8, 0x4E1A, 0xE44B, 0x839F, 0x29CE, 0xC71C, 0x6D4D,
+    0x0CEE, 0xA6BF, 0x486D, 0xE23C, 0x85E8, 0x2FB9, 0xC16B, 0x6B3A,
+    0x0EC3, 0xA492, 0x4A40, 0xE011, 0x87C5, 0x2D94, 0xC346, 0x6917,
+    0x1168, 0xBB39, 0x55EB, 0xFFBA, 0x986E, 0x323F, 0xDCED, 0x76BC,
+    0x1345, 0xB914, 0x57C6, 0xFD97, 0x9A43, 0x3012, 0xDEC0, 0x7491,
+    0x1532, 0xBF63, 0x51B1, 0xFBE0, 0x9C34, 0x3665, 0xD8B7, 0x72E6,
+    0x171F, 0xBD4E, 0x539C, 0xF9CD, 0x9E19, 0x3448, 0xDA9A, 0x70CB,
+    0x19DC, 0xB38D, 0x5D5F, 0xF70E, 0x90DA, 0x3A8B, 0xD459, 0x7E08,
+    0x1BF1, 0xB1A0, 0x5F72, 0xF523, 0x92F7, 0x38A6, 0xD674, 0x7C25,
+    0x1D86, 0xB7D7, 0x5905, 0xF354, 0x9480, 0x3ED1, 0xD003, 0x7A52,
+    0x1FAB, 0xB5FA, 0x5B28, 0xF179, 0x96AD, 0x3CFC, 0xD22E, 0x787F,
+    0x22D0, 0x8881, 0x6653, 0xCC02, 0xABD6, 0x0187, 0xEF55, 0x4504,
+    0x20FD, 0x8AAC, 0x647E, 0xCE2F, 0xA9FB, 0x03AA, 0xED78, 0x4729,
+    0x268A, 0x8CDB, 0x6209, 0xC858, 0xAF8C, 0x05DD, 0xEB0F, 0x415E,
+    0x24A7, 0x8EF6, 0x6024, 0xCA75, 0xADA1, 0x07F0, 0xE922, 0x4373,
+    0x2A64, 0x8035, 0x6EE7, 0xC4B6, 0xA362, 0x0933, 0xE7E1, 0x4DB0,
+    0x2849, 0x8218, 0x6CCA, 0xC69B, 0xA14F, 0x0B1E, 0xE5CC, 0x4F9D,
+    0x2E3E, 0x846F, 0x6ABD, 0xC0EC, 0xA738, 0x0D69, 0xE3BB, 0x49EA,
+    0x2C13, 0x8642, 0x6890, 0xC2C1, 0xA515, 0x0F44, 0xE196, 0x4BC7,
+    0x33B8, 0x99E9, 0x773B, 0xDD6A, 0xBABE, 0x10EF, 0xFE3D, 0x546C,
+    0x3195, 0x9BC4, 0x7516, 0xDF47, 0xB893, 0x12C2, 0xFC10, 0x5641,
+    0x37E2, 0x9DB3, 0x7361, 0xD930, 0xBEE4, 0x14B5, 0xFA67, 0x5036,
+    0x35CF, 0x9F9E, 0x714C, 0xDB1D, 0xBCC9, 0x1698, 0xF84A, 0x521B,
+    0x3B0C, 0x915D, 0x7F8F, 0xD5DE, 0xB20A, 0x185B, 0xF689, 0x5CD8,
+    0x3921, 0x9370, 0x7DA2, 0xD7F3, 0xB027, 0x1A76, 0xF4A4, 0x5EF5,
+    0x3F56, 0x9507, 0x7BD5, 0xD184, 0xB650, 0x1C01, 0xF2D3, 0x5882,
+    0x3D7B, 0x972A, 0x79F8, 0xD3A9, 0xB47D, 0x1E2C, 0xF0FE, 0x5AAF
+},{
+    0x0000, 0x45A0, 0x8B40, 0xCEE0, 0x06A1, 0x4301, 0x8DE1, 0xC841,
+    0x0D42, 0x48E2, 0x8602, 0xC3A2, 0x0BE3, 0x4E43, 0x80A3, 0xC503,
+    0x1A84, 0x5F24, 0x91C4, 0xD464, 0x1C25, 0x5985, 0x9765, 0xD2C5,
+    0x17C6, 0x5266, 0x9C86, 0xD926, 0x1167, 0x54C7, 0x9A27, 0xDF87,
+    0x3508, 0x70A8, 0xBE48, 0xFBE8, 0x33A9, 0x7609, 0xB8E9, 0xFD49,
+    0x384A, 0x7DEA, 0xB30A, 0xF6AA, 0x3EEB, 0x7B4B, 0xB5AB, 0xF00B,
+    0x2F8C, 0x6A2C, 0xA4CC, 0xE16C, 0x292D, 0x6C8D, 0xA26D, 0xE7CD,
+    0x22CE, 0x676E, 0xA98E, 0xEC2E, 0x246F, 0x61CF, 0xAF2F, 0xEA8F,
+    0x6A10, 0x2FB0, 0xE150, 0xA4F0, 0x6CB1, 0x2911, 0xE7F1, 0xA251,
+    0x6752, 0x22F2, 0xEC12, 0xA9B2, 0x61F3, 0x2453, 0xEAB3, 0xAF13,
+    0x7094, 0x3534, 0xFBD4, 0xBE74, 0x7635, 0x3395, 0xFD75, 0xB8D5,
+    0x7DD6, 0x3876, 0xF696, 0xB336, 0x7B77, 0x3ED7, 0xF037, 0xB597,
+    0x5F18, 0x1AB8, 0xD458, 0x91F8, 0x59B9, 0x1C19, 0xD2F9, 0x9759,
+    0x525A, 0x17FA, 0xD91A, 0x9CBA, 0x54FB, 0x115B, 0xDFBB, 0x9A1B,
+    0x459C, 0x003C, 0xCEDC, 0x8B7C, 0x433D, 0x069D, 0xC87D, 0x8DDD,
+    0x48DE, 0x0D7E, 0xC39E, 0x863E, 0x4E7F, 0x0BDF, 0xC53F, 0x809F,
+    0xD420, 0x9180, 0x5F60, 0x1AC0, 0xD281, 0x9721, 0x59C1, 0x1C61,
+    0xD962, 0x9CC2, 0x5222, 0x1782, 0xDFC3, 0x9A63, 0x5483, 0x1123,
+    0xCEA4, 0x8B04, 0x45E4, 0x0044, 0xC805, 0x8DA5, 0x4345, 0x06E5,
+    0xC3E6, 0x8646, 0x48A6, 0x0D06, 0xC547, 0x80E7, 0x4E07, 0x0BA7,
+    0xE128, 0xA488, 0x6A68, 0x2FC8, 0xE789, 0xA229, 0x6CC9, 0x2969,
+    0xEC6A, 0xA9CA, 0x672A, 0x228A, 0xEACB, 0xAF6B, 0x618B, 0x242B,
+    0xFBAC, 0xBE0C, 0x70EC, 0x354C, 0xFD0D, 0xB8AD, 0x764D, 0x33ED,
+    0xF6EE, 0xB34E, 0x7DAE, 0x380E, 0xF04F, 0xB5EF, 0x7B0F, 0x3EAF,
+    0xBE30, 0xFB90, 0x3570, 0x70D0, 0xB891, 0xFD31, 0x33D1, 0x7671,
+    0xB372, 0xF6D2, 0x3832, 0x7D92, 0xB5D3, 0xF073, 0x3E93, 0x7B33,
+    0xA4B4, 0xE114, 0x2FF4, 0x6A54, 0xA215, 0xE7B5, 0x2955, 0x6CF5,
+    0xA9F6, 0xEC56, 0x22B6, 0x6716, 0xAF57, 0xEAF7, 0x2417, 0x61B7,
+    0x8B38, 0xCE98, 0x0078, 0x45D8, 0x8D99, 0xC839, 0x06D9, 0x4379,
+    0x867A, 0xC3DA, 0x0D3A, 0x489A, 0x80DB, 0xC57B, 0x0B9B, 0x4E3B,
+    0x91BC, 0xD41C, 0x1AFC, 0x5F5C, 0x971D, 0xD2BD, 0x1C5D, 0x59FD,
+    0x9CFE, 0xD95E, 0x17BE, 0x521E, 0x9A5F, 0xDFFF, 0x111F, 0x54BF
+},{
+    0x0000, 0xB861, 0x60E3, 0xD882, 0xC1C6, 0x79A7, 0xA125, 0x1944,
+    0x93AD, 0x2BCC, 0xF34E, 0x4B2F, 0x526B, 0xEA0A, 0x3288, 0x8AE9,
+    0x377B, 0x8F1A, 0x5798, 0xEFF9, 0xF6BD, 0x4EDC, 0x965E, 0x2E3F,
+    0xA4D6, 0x1CB7, 0xC435, 0x7C54, 0x6510, 0xDD71, 0x05F3, 0xBD92,
+    0x6EF6, 0xD697, 0x0E15, 0xB674, 0xAF30, 0x1751, 0xCFD3, 0x77B2,
+    0xFD5B, 0x453A, 0x9DB8, 0x25D9, 0x3C9D, 0x84FC, 0x5C7E, 0xE41F,
+    0x598D, 0xE1EC, 0x396E, 0x810F, 0x984B, 0x202A, 0xF8A8, 0x40C9,
+    0xCA20, 0x7241, 0xAAC3, 0x12A2, 0x0BE6, 0xB387, 0x6B05, 0xD364,
+    0xDDEC, 0x658D, 0xBD0F, 0x056E, 0x1C2A, 0xA44B, 0x7CC9, 0xC4A8,
+    0x4E41, 0xF620, 0x2EA2, 0x96C3, 0x8F87, 0x37E6, 0xEF64, 0x5705,
+    0xEA97, 0x52F6, 0x8A74, 0x3215, 0x2B51, 0x9330, 0x4BB2, 0xF3D3,
+    0x793A, 0xC15B, 0x19D9, 0xA1B8, 0xB8FC, 0x009D, 0xD81F, 0x607E,
+    0xB31A, 0x0B7B, 0xD3F9, 0x6B98, 0x72DC, 0xCABD, 0x123F, 0xAA5E,
+    0x20B7, 0x98D6, 0x4054, 0xF835, 0xE171, 0x5910, 0x8192, 0x39F3,
+    0x8461, 0x3C00, 0xE482, 0x5CE3, 0x45A7, 0xFDC6, 0x2544, 0x9D25,
+    0x17CC, 0xAFAD, 0x772F, 0xCF4E, 0xD60A, 0x6E6B, 0xB6E9, 0x0E88,
+    0xABF9, 0x1398, 0xCB1A, 0x737B, 0x6A3F, 0xD25E, 0x0ADC, 0xB2BD,
+    0x3854, 0x8035, 0x58B7, 0xE0D6, 0xF992, 0x41F3, 0x9971, 0x2110,
+    0x9C82, 0x24E3, 0xFC61, 0x4400, 0x5D44, 0xE525, 0x3DA7, 0x85C6,
+    0x0F2F, 0xB74E, 0x6FCC, 0xD7AD, 0xCEE9, 0x7688, 0xAE0A, 0x166B,
+    0xC50F, 0x7D6E, 0xA5EC, 0x1D8D, 0x04C9, 0xBCA8, 0x642A, 0xDC4B,
+    0x56A2, 0xEEC3, 0x3641, 0x8E20, 0x9764, 0x2F05, 0xF787, 0x4FE6,
+    0xF274, 0x4A15, 0x9297, 0x2AF6, 0x33B2, 0x8BD3, 0x5351, 0xEB30,
+    0x61D9, 0xD9B8, 0x013A, 0xB95B, 0xA01F, 0x187E, 0xC0FC, 0x789D,
+    0x7615, 0xCE74, 0x16F6, 0xAE97, 0xB7D3, 0x0FB2, 0xD730, 0x6F51,
+    0xE5B8, 0x5DD9, 0x855B, 0x3D3A, 0x247E, 0x9C1F, 0x449D, 0xFCFC,
+    0x416E, 0xF90F, 0x218D, 0x99EC, 0x80A8, 0x38C9, 0xE04B, 0x582A,
+    0xD2C3, 0x6AA2, 0xB220, 0x0A41, 0x1305, 0xAB64, 0x73E6, 0xCB87,
+    0x18E3, 0xA082, 0x7800, 0xC061, 0xD925, 0x6144, 0xB9C6, 0x01A7,
+    0x8B4E, 0x332F, 0xEBAD, 0x53CC, 0x4A88, 0xF2E9, 0x2A6B, 0x920A,
+    0x2F98, 0x97F9, 0x4F7B, 0xF71A, 0xEE5E, 0x563F, 0x8EBD, 0x36DC,
+    0xBC35, 0x0454, 0xDCD6, 0x64B7, 0x7DF3, 0xC592, 0x1D10, 0xA571
+},{
+    0x0000, 0x47D3, 0x8FA6, 0xC875, 0x0F6D, 0x48BE, 0x80CB, 0xC718,
+    0x1EDA, 0x5909, 0x917C, 0xD6AF, 0x11B7, 0x5664, 0x9E11, 0xD9C2,
+    0x3DB4, 0x7A67, 0xB212, 0xF5C1, 0x32D9, 0x750A, 0xBD7F, 0xFAAC,
+    0x236E, 0x64BD, 0xACC8, 0xEB1B, 0x2C03, 0x6BD0, 0xA3A5, 0xE476,
+    0x7B68, 0x3CBB, 0xF4CE, 0xB31D, 0x7405, 0x33D6, 0xFBA3, 0xBC70,
+    0x65B2, 0x2261, 0xEA14, 0xADC7, 0x6ADF, 0x2D0C, 0xE579, 0xA2AA,
+    0x46DC, 0x010F, 0xC97A, 0x8EA9, 0x49B1, 0x0E62, 0xC617, 0x81C4,
+    0x5806, 0x1FD5, 0xD7A0, 0x9073, 0x576B, 0x10B8, 0xD8CD, 0x9F1E,
+    0xF6D0, 0xB103, 0x7976, 0x3EA5, 0xF9BD, 0xBE6E, 0x761B, 0x31C8,
+    0xE80A, 0xAFD9, 0x67AC, 0x207F, 0xE767, 0xA0B4, 0x68C1, 0x2F12,
+    0xCB64, 0x8CB7, 0x44C2, 0x0311, 0xC409, 0x83DA, 0x4BAF, 0x0C7C,
+    0xD5BE, 0x926D, 0x5A18, 0x1DCB, 0xDAD3, 0x9D00, 0x5575, 0x12A6,
+    0x8DB8, 0xCA6B, 0x021E, 0x45CD, 0x82D5, 0xC506, 0x0D73, 0x4AA0,
+    0x9362, 0xD4B1, 0x1CC4, 0x5B17, 0x9C0F, 0xDBDC, 0x13A9, 0x547A,
+    0xB00C, 0xF7DF, 0x3FAA, 0x7879, 0xBF61, 0xF8B2, 0x30C7, 0x7714,
+    0xAED6, 0xE905, 0x2170, 0x66A3, 0xA1BB, 0xE668, 0x2E1D, 0x69CE,
+    0xFD81, 0xBA52, 0x7227, 0x35F4, 0xF2EC, 0xB53F, 0x7D4A, 0x3A99,
+    0xE35B, 0xA488, 0x6CFD, 0x2B2E, 0xEC36, 0xABE5, 0x6390, 0x2443,
+    0xC035, 0x87E6, 0x4F93, 0x0840, 0xCF58, 0x888B, 0x40FE, 0x072D,
+    0xDEEF, 0x993C, 0x5149, 0x169A, 0xD182, 0x9651, 0x5E24, 0x19F7,
+    0x86E9, 0xC13A, 0x094F, 0x4E9C, 0x8984, 0xCE57, 0x0622, 0x41F1,
+    0x9833, 0xDFE0, 0x1795, 0x5046, 0x975E, 0xD08D, 0x18F8, 0x5F2B,
+    0xBB5D, 0xFC8E, 0x34FB, 0x7328, 0xB430, 0xF3E3, 0x3B96, 0x7C45,
+    0xA587, 0xE254, 0x2A21, 0x6DF2, 0xAAEA, 0xED39, 0x254C, 0x629F,
+    0x0B51, 0x4C82, 0x84F7, 0xC324, 0x043C, 0x43EF, 0x8B9A, 0xCC49,
+    0x158B, 0x5258, 0x9A2D, 0xDDFE, 0x1AE6, 0x5D35, 0x9540, 0xD293,
+    0x36E5, 0x7136, 0xB943, 0xFE90, 0x3988, 0x7E5B, 0xB62E, 0xF1FD,
+    0x283F, 0x6FEC, 0xA799, 0xE04A, 0x2752, 0x6081, 0xA8F4, 0xEF27,
+    0x7039, 0x37EA, 0xFF9F, 0xB84C, 0x7F54, 0x3887, 0xF0F2, 0xB721,
+    0x6EE3, 0x2930, 0xE145, 0xA696, 0x618E, 0x265D, 0xEE28, 0xA9FB,
+    0x4D8D, 0x0A5E, 0xC22B, 0x85F8, 0x42E0, 0x0533, 0xCD46, 0x8A95,
+    0x5357, 0x1484, 0xDCF1, 0x9B22, 0x5C3A, 0x1BE9, 0xD39C, 0x944F
+}};
+
+/* Non - Reflected */
+uint16_t util_crc16(uint16_t current, const char *GMQCC_RESTRICT k, size_t len) {
+    uint16_t h = current;
+
+    /* don't load twice */
+    const uint8_t *GMQCC_RESTRICT data = (const uint8_t *GMQCC_RESTRICT)k;
+    size_t n;
+
+    /* deal with the first bytes as bytes until we reach an 8 byte boundary */
+    while (len & 7) {
+        h = (uint16_t)(h << 8) ^ (*util_crc16_table)[(h >> 8) ^ *data++];
+        --len;
+    }
+
+    #define SELECT_BULK(X, MOD) util_crc16_table[(X)][data[7-(X)] ^ (MOD)]
+    #define SELECT_DATA(X)      util_crc16_table[(X)][data[7-(X)]]
+
+    for (n = len / 8; n; --n) {
+        h = SELECT_BULK(7, (h >> 8))   ^
+            SELECT_BULK(6, (h & 0xFF)) ^
+            SELECT_DATA(5) ^
+            SELECT_DATA(4) ^
+            SELECT_DATA(3) ^
+            SELECT_DATA(2) ^
+            SELECT_DATA(1) ^
+            SELECT_DATA(0);
+        data += 8;
+        len  -= 8;
+    }
+
+    #undef SELECT_BULK
+    #undef SELECT_DATA
+
+    /* deal with the rest with the byte method */
+    for (n = len & 7; n; --n)
+        h = (uint16_t)(h << 8) ^ (*util_crc16_table)[(h >> 8) ^ *data++];
+
+    return h;
+}
+
+/*
+ * modifier is the match to make and the transposition from it, while add is the upper-value that determines the
+ * transposition from uppercase to lower case.
+ */
+static size_t util_strtransform(const char *in, char *out, size_t outsz, const char *mod, int add) {
+    size_t sz = 1;
+    for (; *in && sz < outsz; ++in, ++out, ++sz) {
+        *out = (*in == mod[0])
+                    ? mod[1]
+                    : (util_isalpha(*in) && ((add > 0) ? util_isupper(*in) : !util_isupper(*in)))
+                        ? *in + add
+                        : *in;
+    }
+    *out = 0;
+    return sz-1;
+}
+
+size_t util_strtocmd(const char *in, char *out, size_t outsz) {
+    return util_strtransform(in, out, outsz, "-_", 'A'-'a');
+}
+size_t util_strtononcmd(const char *in, char *out, size_t outsz) {
+    return util_strtransform(in, out, outsz, "_-", 'a'-'A');
+}
+size_t util_optimizationtostr(const char *in, char *out, size_t outsz) {
+    return util_strtransform(in, out, outsz, "_ ", 'a'-'A');
+}
+
+static int util_vasprintf(char **dat, const char *fmt, va_list args) {
+    int     ret;
+    int     len;
+    char   *tmp = nullptr;
+    char    buf[128];
+    va_list cpy;
+
+    va_copy(cpy, args);
+    len = vsnprintf(buf, sizeof(buf), fmt, cpy);
+    va_end (cpy);
+
+    if (len < 0)
+        return len;
+
+    if (len < (int)sizeof(buf)) {
+        *dat = util_strdup(buf);
+        return len;
+    }
+
+    tmp = (char*)mem_a(len + 1);
+    if ((ret = vsnprintf(tmp, len + 1, fmt, args)) != len) {
+        mem_d(tmp);
+        *dat = nullptr;
+        return -1;
+    }
+
+    *dat = tmp;
+    return len;
+}
+
+int util_snprintf(char *str, size_t size, const char *fmt, ...) {
+    va_list  arg;
+    int ret;
+    va_start(arg, fmt);
+    ret = vsnprintf(str, size, fmt, arg);
+    va_end(arg);
+    return ret;
+}
+
+int util_asprintf(char **ret, const char *fmt, ...) {
+    va_list  args;
+    int read;
+    va_start(args, fmt);
+    read = util_vasprintf(ret, fmt, args);
+    va_end(args);
+    return read;
+}
+
+int util_sscanf(const char *str, const char *format, ...) {
+    va_list  args;
+    int read;
+    va_start(args, format);
+    read = vsscanf(str, format, args);
+    va_end(args);
+    return read;
+}
+
+char *util_strncpy(char *dest, const char *src, size_t n) {
+    return strncpy(dest, src, n);
+}
+
+char *util_strncat(char *dest, const char *src, size_t n) {
+    return strncat(dest, src, n);
+}
+
+char *util_strcat(char *dest, const char *src) {
+    return strcat(dest, src);
+}
+
+const char *util_strerror(int err) {
+    return strerror(err);
+}
+
+const struct tm *util_localtime(const time_t *timer) {
+    return localtime(timer);
+}
+
+const char *util_ctime(const time_t *timer) {
+    return ctime(timer);
+}
+
+int util_getline(char **lineptr, size_t *n, FILE *stream) {
+    int   chr;
+    int   ret;
+    char *pos;
+
+    if (!lineptr || !n || !stream)
+        return -1;
+    if (!*lineptr) {
+        if (!(*lineptr = (char*)mem_a((*n=64))))
+            return -1;
+    }
+
+    chr = *n;
+    pos = *lineptr;
+
+    for (;;) {
+        int c = getc(stream);
+
+        if (chr < 2) {
+            *n += (*n > 16) ? *n : 64;
+            chr = *n + *lineptr - pos;
+            if (!(*lineptr = (char*)mem_r(*lineptr,*n)))
+                return -1;
+            pos = *n - chr + *lineptr;
+        }
+
+        if (ferror(stream))
+            return -1;
+        if (c == EOF) {
+            if (pos == *lineptr)
+                return -1;
+            else
+                break;
+        }
+
+        *pos++ = c;
+        chr--;
+        if (c == '\n')
+            break;
+    }
+    *pos = '\0';
+    return (ret = pos - *lineptr);
+}
+
+#ifndef _WIN32
+#include <unistd.h>
+bool util_isatty(FILE *file) {
+    if (file == stdout) return !!isatty(STDOUT_FILENO);
+    if (file == stderr) return !!isatty(STDERR_FILENO);
+    return false;
+}
+#else
+bool util_isatty(FILE *file) {
+    return false;
+}
+#endif
+
+/*
+ * A small noncryptographic PRNG based on:
+ * http://burtleburtle.net/bob/rand/smallprng.html
+ */
+static uint32_t util_rand_state[4] = {
+    0xF1EA5EED, 0x00000000,
+    0x00000000, 0x00000000
+};
+
+#define util_rand_rot(X, Y) (((X)<<(Y))|((X)>>(32-(Y))))
+
+uint32_t util_rand() {
+    uint32_t last;
+
+    last               = util_rand_state[0] - util_rand_rot(util_rand_state[1], 27);
+    util_rand_state[0] = util_rand_state[1] ^ util_rand_rot(util_rand_state[2], 17);
+    util_rand_state[1] = util_rand_state[2] + util_rand_state[3];
+    util_rand_state[2] = util_rand_state[3] + last;
+    util_rand_state[3] = util_rand_state[0] + last;
+
+    return util_rand_state[3];
+}
+
+#undef util_rand_rot
+
+void util_seed(uint32_t value) {
+    size_t i;
+
+    util_rand_state[0] = 0xF1EA5EED;
+    util_rand_state[1] = value;
+    util_rand_state[2] = value;
+    util_rand_state[3] = value;
+
+    for (i = 0; i < 20; ++i)
+        (void)util_rand();
+}
+
+size_t hash(const char *string) {
+    size_t hash = 0;
+    for(; *string; ++string) {
+        hash += *string;
+        hash += (hash << 10);
+        hash ^= (hash >> 6);
+    }
+    hash += hash << 3;
+    hash ^= hash >> 11;
+    hash += hash << 15;
+    return hash;
+}
+