diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2aef9c9423e10b6671bbddb86c4f159335b69f26
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,19 @@
+platform:
+- x86
+- x64
+version: 1.0.{build}
+os: Visual Studio 2015
+build_script:
+- '@echo off'
+- setlocal
+- ps: >-
+    If ($env:Platform -Match "x86") {
+      $env:VCVARS_PLATFORM="x86"
+    } Else {
+      $env:VCVARS_PLATFORM="amd64"
+    }
+- call "%VS140COMNTOOLS%\..\..\VC\vcvarsall.bat" %VCVARS_PLATFORM%
+- call tools\windows\build.bat
+# FIXME(windows) TODO(uucidl): reactivate examples
+# - call tools\windows\build_examples.bat
+- exit /b 0
diff --git a/src/SConscript b/src/SConscript
index e192b05e182b0020ac7f931f68244b300b93b9bc..05ffa983674488db41cc0c6f32f642c2f43e30f8 100644
--- a/src/SConscript
+++ b/src/SConscript
@@ -5,6 +5,7 @@ Import('env testruns')
 dist_headers = [
     "hammer.h",
     "allocator.h",
+    "compiler_specifics.h",
     "glue.h",
     "internal.h"
 ]
@@ -61,6 +62,7 @@ misc_hammer_parts = [
     'desugar.c',
     'glue.c',
     'hammer.c',
+    'platform_bsdlike.c',
     'pprint.c',
     'registry.c',
     'system_allocator.c']
diff --git a/src/backends/contextfree.h b/src/backends/contextfree.h
index 9105636f1d19a06bf9c69fc1484338c485904655..29b51a08ac3c39251170ce50f6c85448f5adf65f 100644
--- a/src/backends/contextfree.h
+++ b/src/backends/contextfree.h
@@ -22,7 +22,7 @@ struct HCFStack_ {
 };
 
 #ifndef UNUSED
-#define UNUSED __attribute__((unused))
+#define UNUSED H_GCC_ATTRIBUTE((unused))
 #endif
 
 static inline HCFChoice* h_cfstack_new_choice_raw(HAllocator *mm__, HCFStack *stk__) UNUSED;
diff --git a/src/backends/packrat.c b/src/backends/packrat.c
index 33082c6c278beb09b2abf767e5314d18ab471db4..e6f86f2957e5988dd94557534ab66b6050fda915 100644
--- a/src/backends/packrat.c
+++ b/src/backends/packrat.c
@@ -126,7 +126,7 @@ HParseResult* grow(HParserCacheKey *k, HParseState *state, HRecursionHead *head)
   h_hashtable_put(state->recursion_heads, &k->input_pos, head);
   HParserCacheValue *old_cached = h_hashtable_get(state->cache, k);
   if (!old_cached || PC_LEFT == old_cached->value_type)
-    errx(1, "impossible match");
+    h_platform_errx(1, "impossible match");
   HParseResult *old_res = old_cached->right;
 
   // rewind the input
@@ -148,7 +148,7 @@ HParseResult* grow(HParserCacheKey *k, HParseState *state, HRecursionHead *head)
         state->input_stream = cached->input_stream;
 	return cached->right;
       } else {
-	errx(1, "impossible match");
+	h_platform_errx(1, "impossible match");
       }
     }
   } else {
@@ -173,7 +173,7 @@ HParseResult* lr_answer(HParserCacheKey *k, HParseState *state, HLeftRec *growab
 	return grow(k, state, growable->head);
     }
   } else {
-    errx(1, "lrAnswer with no head");
+    h_platform_errx(1, "lrAnswer with no head");
   }
 }
 
diff --git a/src/bindings/cpp/hammer/hammer_test.hpp b/src/bindings/cpp/hammer/hammer_test.hpp
index 77e6daa3fccd31d296d3d364a75bcc0cce5d7354..f3ab77a377d569a3b42255b050e7d6d8324837aa 100644
--- a/src/bindings/cpp/hammer/hammer_test.hpp
+++ b/src/bindings/cpp/hammer/hammer_test.hpp
@@ -5,7 +5,7 @@
 #include <gtest/gtest.h>
 #include <hammer/hammer.hpp>
 
-#define HAMMER_DECL_UNUSED __attribute__((unused))
+#define HAMMER_DECL_UNUSED H_GCC_ATTRIBUTE((unused))
 
 static ::testing::AssertionResult ParseFails (hammer::Parser parser,
 					      const std::string &input) HAMMER_DECL_UNUSED;
diff --git a/src/bindings/desugar-header.pl b/src/bindings/desugar-header.pl
index 5bdd11e665b86af623583a94002551795d7b9ade..e836ad7a8bd0af6299008696f22f2f9a549d4b82 100644
--- a/src/bindings/desugar-header.pl
+++ b/src/bindings/desugar-header.pl
@@ -11,7 +11,7 @@ while(<>) {
   } elsif (/^HAMMER_FN_DECL\(([^,]*), ([^,]*), ([^)]*)\);/) {
     print "$1 $2($3);\n";
     print "$1 $2__m(HAllocator* mm__, $3);\n";
-  } elsif (/^HAMMER_FN_DECL_VARARGS_ATTR\((__attribute__\(\([^)]*\)\)), ([^,]*), ([^,]*), ([^)]*)\);/) {
+  } elsif (/^HAMMER_FN_DECL_VARARGS_ATTR\((H_GCC_ATTRIBUTE\(\([^)]*\)\)), ([^,]*), ([^,]*), ([^)]*)\);/) {
     print "$2 $3($4, ...);\n";
     print "$2 $3__m(HAllocator *mm__, $4, ...);\n";
     print "$2 $3__a(void* args);\n";
diff --git a/src/compiler_specifics.h b/src/compiler_specifics.h
new file mode 100644
index 0000000000000000000000000000000000000000..ed09d664fa52557ce5505f789c37ffb881a5f753
--- /dev/null
+++ b/src/compiler_specifics.h
@@ -0,0 +1,16 @@
+#ifndef HAMMER_COMPILER_SPECIFICS__H
+#define HAMMER_COMPILER_SPECIFICS__H
+
+#if defined(__clang__) || defined(__GNUC__)
+#define H_GCC_ATTRIBUTE(x) __attribute__(x)
+#else
+#define H_GCC_ATTRIBUTE(x)
+#endif
+
+#if defined(_MSC_VER)
+#define H_MSVC_DECLSPEC(x) __declspec(x)
+#else
+#define H_MSVC_DECLSPEC(x)
+#endif
+
+#endif
diff --git a/src/hammer.c b/src/hammer.c
index 6bb9ebb4febe53668a91ae9617ba05f2c158023d..443c77b790b10b5592958a55e7d457bc695c030a 100644
--- a/src/hammer.c
+++ b/src/hammer.c
@@ -17,7 +17,6 @@
 
 #include <assert.h>
 #include <ctype.h>
-#include <err.h>
 #include <limits.h>
 #include <stdarg.h>
 #include <string.h>
diff --git a/src/hammer.h b/src/hammer.h
index 50b3de5c939e94084f73499b031962029104c136..42c73458a4d0e513f4400e1a3c6790e9cc736a9e 100644
--- a/src/hammer.h
+++ b/src/hammer.h
@@ -17,6 +17,9 @@
 
 #ifndef HAMMER_HAMMER__H
 #define HAMMER_HAMMER__H
+
+#include "compiler_specifics.h"
+
 #ifndef HAMMER_INTERNAL__NO_STDARG_H
 #include <stdarg.h>
 #endif // HAMMER_INTERNAL__NO_STDARG_H
@@ -434,7 +437,7 @@ HAMMER_FN_DECL_NOARG(HParser*, h_nothing_p);
  *
  * Result token type: TT_SEQUENCE
  */
-HAMMER_FN_DECL_VARARGS_ATTR(__attribute__((sentinel)), HParser*, h_sequence, HParser* p);
+HAMMER_FN_DECL_VARARGS_ATTR(H_GCC_ATTRIBUTE((sentinel)), HParser*, h_sequence, HParser* p);
 
 /**
  * Given an array of parsers, p_array, apply each parser in order. The 
@@ -443,7 +446,7 @@ HAMMER_FN_DECL_VARARGS_ATTR(__attribute__((sentinel)), HParser*, h_sequence, HPa
  *
  * Result token type: The type of the first successful parser's result.
  */
-HAMMER_FN_DECL_VARARGS_ATTR(__attribute__((sentinel)), HParser*, h_choice, HParser* p);
+HAMMER_FN_DECL_VARARGS_ATTR(H_GCC_ATTRIBUTE((sentinel)), HParser*, h_choice, HParser* p);
 
 /**
  * Given a null-terminated list of parsers, match a permutation phrase of these
@@ -469,7 +472,7 @@ HAMMER_FN_DECL_VARARGS_ATTR(__attribute__((sentinel)), HParser*, h_choice, HPars
  *
  * Result token type: TT_SEQUENCE
  */
-HAMMER_FN_DECL_VARARGS_ATTR(__attribute__((sentinel)), HParser*, h_permutation, HParser* p);
+HAMMER_FN_DECL_VARARGS_ATTR(H_GCC_ATTRIBUTE((sentinel)), HParser*, h_permutation, HParser* p);
 
 /**
  * Given two parsers, p1 and p2, this parser succeeds in the following 
diff --git a/src/internal.h b/src/internal.h
index ed1bd0856febf3fa422a471a21051d75d33d5297..9aac4ee7dbaa4c4a1b8e87785b98f22265f37c71 100644
--- a/src/internal.h
+++ b/src/internal.h
@@ -24,9 +24,9 @@
 #define HAMMER_INTERNAL__H
 #include <stdint.h>
 #include <assert.h>
-#include <err.h>
 #include <string.h>
 #include "hammer.h"
+#include "platform.h"
 
 /* "Internal" in this case means "we're not ready to commit
  * to a public API." Many structures and routines here will be
@@ -38,7 +38,7 @@
 #else
 #define assert_message(check, message) do {				\
     if (!(check))							\
-      errx(1, "Assertion failed (programmer error): %s", message);	\
+      h_platform_errx(1, "Assertion failed (programmer error): %s", message);	\
   } while(0)
 #endif
 
diff --git a/src/parsers/many.c b/src/parsers/many.c
index 1e3b0221ceae76c782a317e1c5c17b21a496f4a1..51d733fcf87e3191e6f413a9513ac7900d29d8f2 100644
--- a/src/parsers/many.c
+++ b/src/parsers/many.c
@@ -246,7 +246,7 @@ static HParseResult* parse_length_value(void *env, HParseState *state) {
   if (!len)
     return NULL;
   if (len->ast->token_type != TT_UINT)
-    errx(1, "Length parser must return an unsigned integer");
+    h_platform_errx(1, "Length parser must return an unsigned integer");
   // TODO: allocate this using public functions
   HRepeat repeat = {
     .p = lv->value,
diff --git a/src/platform.h b/src/platform.h
new file mode 100644
index 0000000000000000000000000000000000000000..0c05bfe2e7ada4e7f1c5f48c3d705a839a78730a
--- /dev/null
+++ b/src/platform.h
@@ -0,0 +1,18 @@
+#ifndef HAMMER_PLATFORM__H
+#define HAMMER_PLATFORM__H
+
+/**
+ * @file interface between hammer and the operating system /
+ * underlying platform.
+ */
+
+#include "compiler_specifics.h"
+
+/* Error Reporting */
+
+/* BSD errx function, seen in err.h */
+H_MSVC_DECLSPEC(noreturn) \
+void h_platform_errx(int err, const char* format, ...)	\
+  H_GCC_ATTRIBUTE((noreturn, format (printf,2,3)));
+
+#endif
diff --git a/src/platform_bsdlike.c b/src/platform_bsdlike.c
new file mode 100644
index 0000000000000000000000000000000000000000..ebb38d9df55243c75774dbfd9789932ee8d9c650
--- /dev/null
+++ b/src/platform_bsdlike.c
@@ -0,0 +1,10 @@
+#include "platform.h"
+
+#include <err.h>
+#include <stdarg.h>
+
+void h_platform_errx(int err, const char* format, ...) {
+  va_list ap;
+  va_start(ap, format);
+  verrx(err, format, ap);
+}
diff --git a/src/platform_win32.c b/src/platform_win32.c
new file mode 100644
index 0000000000000000000000000000000000000000..30af168170933456eb64370bd3cfd632b0558b50
--- /dev/null
+++ b/src/platform_win32.c
@@ -0,0 +1,10 @@
+#include "platform.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include "windows.h"
+
+void h_platform_errx(int err, const char* format, ...) {
+  // FIXME(windows) TODO(uucidl): to be implemented
+  ExitProcess(err);
+}
+
diff --git a/tools/windows/README.md b/tools/windows/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..3b28eea4af4d8c436d747e3117e947c38411453c
--- /dev/null
+++ b/tools/windows/README.md
@@ -0,0 +1 @@
+Support tools for the Windows (win32/win64) port.
\ No newline at end of file
diff --git a/tools/windows/build.bat b/tools/windows/build.bat
new file mode 100644
index 0000000000000000000000000000000000000000..20f878acab296420cb8b29fa36df74aad26eeb44
--- /dev/null
+++ b/tools/windows/build.bat
@@ -0,0 +1,47 @@
+@echo off
+setlocal
+
+REM This script must be run after vcvarsall.bat has been run,
+REM so that cl.exe is in your path.
+where cl.exe || goto vsmissing_err
+
+REM HEREPATH is <drive_letter>:<script_directory>
+set HEREPATH=%~d0%~p0
+
+REM Set up SRC, BUILD and CLFLAGS
+call %HEREPATH%\env.bat
+call %HEREPATH%\clvars.bat
+
+echo SRC=%SRC%, BUILD=%BUILD%
+echo Building with flags: %CLFLAGS%
+
+pushd %SRC%
+mkdir %BUILD%\obj
+del /Q %BUILD%\obj\
+
+cl.exe -nologo -FC -EHsc -Z7 -Oi -GR- -Gm- %CLFLAGS% -c ^
+       @%HEREPATH%\hammer_lib_src_list ^
+       -Fo%BUILD%\obj\
+if %errorlevel% neq 0 goto err
+
+lib.exe %BUILD%\obj\*.obj -OUT:%BUILD%\hammer.lib
+echo STATIC_LIBRARY %BUILD%\hammer.lib
+if %errorlevel% neq 0 goto err
+popd
+
+REM TODO(uucidl): how to build and run the tests? They are written with glib.h
+REM which might be a challenge on windows. On the other hand the API of glib.h
+REM does not seem too hard to reimplement.
+
+echo SUCCESS: Successfully built
+endlocal
+exit /b 0
+
+:vsmissing_err
+echo ERROR: CL.EXE missing. Have you run vcvarsall.bat?
+exit /b 1
+
+:err
+endlocal
+echo ERROR: Failed to build
+exit /b %errorlevel%
diff --git a/tools/windows/build_examples.bat b/tools/windows/build_examples.bat
new file mode 100644
index 0000000000000000000000000000000000000000..c431faebcd29d7b1a1aaeaa77558b948fc3454f0
--- /dev/null
+++ b/tools/windows/build_examples.bat
@@ -0,0 +1,53 @@
+@echo off
+setlocal
+
+REM This script must be run after vcvarsall.bat has been run,
+REM so that cl.exe is in your path.
+where cl.exe || goto vsmissing_err
+
+REM HEREPATH is <drive_letter>:<script_directory>
+set HEREPATH=%~d0%~p0
+
+REM Set up SRC, BUILD and CLFLAGS
+call %HEREPATH%\env.bat
+call %HEREPATH%\clvars.bat
+
+echo SRC=%SRC%, BUILD=%BUILD%
+echo CLFLAGS=%CLFLAGS%
+
+set HAMMERLIB=%BUILD%\hammer.lib
+
+REM Now let's build some example programs
+
+cl.exe -nologo %CLFLAGS% examples\base64.c %HAMMERLIB% -Fo%BUILD%\ -Fe%BUILD%\
+if %errorlevel% neq 0 goto err
+echo PROGRAM build\base64.exe
+cl.exe -nologo %CLFLAGS% examples\base64_sem1.c %HAMMERLIB%  -Fo%BUILD%\ -Fe%BUILD%\
+if %errorlevel% neq 0 goto err
+echo PROGRAM build\base64_sem1.exe
+cl.exe -nologo %CLFLAGS% examples\base64_sem2.c %HAMMERLIB%  -Fo%BUILD%\ -Fe%BUILD%\
+if %errorlevel% neq 0 goto err
+echo PROGRAM build\base64_sem2.exe
+
+REM FIXME(windows) TODO(uucidl): dns.c only works on posix
+REM cl.exe -nologo %CLFLAGS% examples\dns.c %HAMMERLIB% -Fo%BUILD%\ -Fe%BUILD%\
+REM if %errorlevel% neq 0 goto err
+REM echo PROGRAM build\dns.exe
+
+REM FIXME(windows) TODO(uucidl): grammar.c needs to be fixed
+cl.exe -nologo %CLFLAGS% examples\ties.c examples\grammar.c %HAMMERLIB% -Fo%BUILD%\ -Fe%BUILD%\
+if %errorlevel% neq 0 goto err
+echo PROGRAM build\ties.exe
+
+echo SUCCESS: Successfully built
+endlocal
+exit /b 0
+
+:vsmissing_err
+echo ERROR: CL.EXE missing. Have you run vcvarsall.bat?
+exit /b 1
+
+:err
+echo ERROR: Failed to build
+endlocal
+exit /b %errorlevel%
diff --git a/tools/windows/clvars.bat b/tools/windows/clvars.bat
new file mode 100644
index 0000000000000000000000000000000000000000..8e29226871988207083b61197b5efea11c1ebb69
--- /dev/null
+++ b/tools/windows/clvars.bat
@@ -0,0 +1,59 @@
+REM Don't call me directly
+REM Exports CLFLAGS
+
+REM Start with the most strict warning level
+set WARNINGS=-W4 -Wall -WX
+
+REM c4457 (declaration shadowing function parameter)
+REM FIXME(windows) TODO(uucidl): remove occurence of c4457 and reactivate
+REM FIXME(windows) TODO(uucidl): remove occurence of c4456 and reactivate
+REM see -Wshadow
+set WARNINGS=%WARNINGS% -wd4457 -wd4456
+
+REM c4701 (potentially unitialized local variable)
+REM FIXME(windows) TODO(uucidl): remove occurence of c4701 if possible
+set WARNINGS=%WARNINGS% -wd4701
+
+REM We disable implicit casting warnings (c4244), as they occur too often here.
+REM Its gcc/clang counterpart is Wconversion which does not seem to
+REM be enabled by default.
+REM See: [[https://gcc.gnu.org/wiki/NewWconversion#Frequently_Asked_Questions]]
+REM
+REM Likewise for c4242 (conversion with potential loss of data) and c4267
+REM (conversion away from size_t to a smaller type) and c4245 (conversion
+REM from int to size_t signed/unsigned mismatch)
+set WARNINGS=%WARNINGS% -wd4242 -wd4244 -wd4245 -wd4267
+
+REM c4100 (unreferenced formal parameter) is equivalent to -Wno-unused-parameter
+set WARNINGS=%WARNINGS% -wd4100
+
+REM c4200 (zero-sized array) is a C idiom supported by C99
+set WARNINGS=%WARNINGS% -wd4200
+
+REM c4204 (non-constant aggregate initializers) ressembles C99 support
+set WARNINGS=%WARNINGS% -wd4204
+
+REM c4201 (anonymous unions) ressembles C11 support.
+REM see -std=gnu99 vs -std=c99
+set WARNINGS=%WARNINGS% -wd4201
+
+REM c4820 (warnings about padding) and c4324 (intentional padding) are
+REM not useful
+set WARNINGS=%WARNINGS% -wd4820 -wd4324
+
+REM c4710 (inlining could not be performed) is not useful
+set WARNINGS=%WARNINGS% -wd4710
+
+REM c4255 ( () vs (void) ambiguity) is not useful
+set WARNINGS=%WARNINGS% -wd4255
+
+REM c4127 (conditional expression is constant) is not useful
+set WARNINGS=%WARNINGS% -wd4127
+
+REM c4668 (an undefined symbol in a preprocessor directive) is not useful
+set WARNINGS=%WARNINGS% -wd4668
+
+REM we use sprintf so this should be enabled
+set DEFINES=-D_CRT_SECURE_NO_WARNINGS
+
+set CLFLAGS=-Od -Z7 %DEFINES% %WARNINGS% -Debug
diff --git a/tools/windows/env.bat b/tools/windows/env.bat
new file mode 100644
index 0000000000000000000000000000000000000000..4037578cccb96202cdd20541cb84932e22a663ab
--- /dev/null
+++ b/tools/windows/env.bat
@@ -0,0 +1,16 @@
+REM Don't call me directly.
+REM
+REM Expects HEREPATH (this directory)
+REM Exports SRC (hammer's src directory)
+REM Exports BUILD (hammer's build directory)
+
+set TOP=%HEREPATH%..\..
+
+REM Get canonical path for TOP
+pushd .
+cd %TOP%
+set TOP=%CD%
+popd
+
+set SRC=%TOP%\src
+set BUILD=%TOP%\build
diff --git a/tools/windows/hammer_lib_src_list b/tools/windows/hammer_lib_src_list
new file mode 100644
index 0000000000000000000000000000000000000000..4c85a43ca3ee36f9810d8bf02bbfee1abfd5951c
--- /dev/null
+++ b/tools/windows/hammer_lib_src_list
@@ -0,0 +1,38 @@
+platform_win32.c 
+allocator.c 
+bitreader.c 
+bitwriter.c 
+cfgrammar.c 
+desugar.c 
+glue.c 
+hammer.c 
+parsers/action.c 
+parsers/and.c 
+parsers/attr_bool.c 
+parsers/butnot.c 
+parsers/ch.c 
+parsers/charset.c 
+parsers/difference.c 
+parsers/end.c 
+parsers/endianness.c 
+parsers/epsilon.c 
+parsers/ignore.c 
+parsers/ignoreseq.c 
+parsers/indirect.c 
+parsers/int_range.c 
+parsers/many.c 
+parsers/not.c 
+parsers/optional.c 
+parsers/permutation.c 	
+parsers/sequence.c 
+parsers/token.c 
+parsers/unimplemented.c 
+parsers/whitespace.c 
+parsers/xor.c 
+parsers/value.c 
+backends/packrat.c
+backends/llk.c
+backends/glr.c
+backends/lalr.c
+backends/lr.c
+backends/lr0.c
diff --git a/tools/windows/status.bat b/tools/windows/status.bat
new file mode 100644
index 0000000000000000000000000000000000000000..4f8bd11f9f7567f32cd17f843d2918ac6fd1e14d
--- /dev/null
+++ b/tools/windows/status.bat
@@ -0,0 +1 @@
+git grep "FIXME(windows)"