]> git.localhorst.tv Git - blobs.git/commitdiff
more stuff from blank
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Mon, 13 Nov 2017 19:55:16 +0000 (20:55 +0100)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Mon, 13 Nov 2017 19:55:16 +0000 (20:55 +0100)
16 files changed:
src/io/Token.hpp [new file with mode: 0644]
src/io/TokenStreamReader.hpp [new file with mode: 0644]
src/io/Tokenizer.hpp [new file with mode: 0644]
src/io/event.cpp [new file with mode: 0644]
src/io/event.hpp [new file with mode: 0644]
src/io/filesystem.cpp [new file with mode: 0644]
src/io/filesystem.hpp [new file with mode: 0644]
src/io/token.cpp [new file with mode: 0644]
tst/io/EventTest.cpp [new file with mode: 0644]
tst/io/EventTest.hpp [new file with mode: 0644]
tst/io/FilesystemTest.cpp [new file with mode: 0644]
tst/io/FilesystemTest.hpp [new file with mode: 0644]
tst/io/TokenTest.cpp [new file with mode: 0644]
tst/io/TokenTest.hpp [new file with mode: 0644]
tst/world/OrbitTest.cpp
tst/world/OrbitTest.hpp

diff --git a/src/io/Token.hpp b/src/io/Token.hpp
new file mode 100644 (file)
index 0000000..0c94683
--- /dev/null
@@ -0,0 +1,40 @@
+#ifndef BLOBS_IO_TOKEN_HPP_
+#define BLOBS_IO_TOKEN_HPP_
+
+#include <iosfwd>
+#include <string>
+
+
+namespace blobs {
+namespace io {
+
+struct Token {
+       enum Type {
+               UNKNOWN = 0,
+               ANGLE_BRACKET_OPEN = '{',
+               ANGLE_BRACKET_CLOSE = '}',
+               CHEVRON_OPEN = '<',
+               CHEVRON_CLOSE = '>',
+               BRACKET_OPEN = '[',
+               BRACKET_CLOSE = ']',
+               PARENTHESIS_OPEN = '(',
+               PARENTHESIS_CLOSE = ')',
+               COLON = ':',
+               SEMICOLON = ';',
+               COMMA = ',',
+               EQUALS = '=',
+               NUMBER = '0',
+               STRING = '"',
+               IDENTIFIER = 'a',
+               COMMENT = '#',
+       } type = UNKNOWN;
+       std::string value;
+};
+
+std::ostream &operator <<(std::ostream &, Token::Type);
+std::ostream &operator <<(std::ostream &, const Token &);
+
+}
+}
+
+#endif
diff --git a/src/io/TokenStreamReader.hpp b/src/io/TokenStreamReader.hpp
new file mode 100644 (file)
index 0000000..e2945c4
--- /dev/null
@@ -0,0 +1,71 @@
+#ifndef BLOBS_IO_TOKENSTREAMREADER_HPP_
+#define BLOBS_IO_TOKENSTREAMREADER_HPP_
+
+#include "Token.hpp"
+#include "Tokenizer.hpp"
+#include "../graphics/glm.hpp"
+
+#include <iosfwd>
+#include <string>
+
+
+namespace blobs {
+namespace io {
+
+class TokenStreamReader {
+
+public:
+       explicit TokenStreamReader(std::istream &);
+
+       bool HasMore();
+       const Token &Next();
+       const Token &Peek();
+
+       void Skip(Token::Type);
+
+       void ReadBoolean(bool &);
+       void ReadIdentifier(std::string &);
+       void ReadNumber(float &);
+       void ReadNumber(int &);
+       void ReadNumber(unsigned long &);
+       void ReadString(std::string &);
+
+       void ReadVec(glm::vec2 &);
+       void ReadVec(glm::vec3 &);
+       void ReadVec(glm::vec4 &);
+
+       void ReadVec(glm::ivec2 &);
+       void ReadVec(glm::ivec3 &);
+       void ReadVec(glm::ivec4 &);
+
+       void ReadQuat(glm::quat &);
+
+       // the Get* functions advance to the next token
+       // the As* functions try to cast the current token
+       // if the value could not be converted, a std::runtime_error is thrown
+
+       bool GetBool();
+       bool AsBool() const;
+       float GetFloat();
+       float AsFloat() const;
+       int GetInt();
+       int AsInt() const;
+       unsigned long GetULong();
+       unsigned long AsULong() const;
+
+private:
+       void SkipComments();
+
+       void Assert(Token::Type) const;
+       Token::Type GetType() const noexcept;
+       const std::string &GetValue() const noexcept;
+
+       Tokenizer in;
+       bool cached;
+
+};
+
+}
+}
+
+#endif
diff --git a/src/io/Tokenizer.hpp b/src/io/Tokenizer.hpp
new file mode 100644 (file)
index 0000000..c717780
--- /dev/null
@@ -0,0 +1,39 @@
+#ifndef BLOBS_IO_TOKENIZER_HPP_
+#define BLOBS_IO_TOKENIZER_HPP_
+
+#include "Token.hpp"
+
+#include <iosfwd>
+
+
+namespace blobs {
+namespace io {
+
+class Tokenizer {
+
+public:
+
+public:
+       explicit Tokenizer(std::istream &in);
+
+       bool HasMore();
+       const Token &Next();
+       const Token &Current() const noexcept { return current; }
+
+private:
+       void ReadToken();
+
+       void ReadNumber();
+       void ReadString();
+       void ReadComment();
+       void ReadIdentifier();
+
+       std::istream &in;
+       Token current;
+
+};
+
+}
+}
+
+#endif
diff --git a/src/io/event.cpp b/src/io/event.cpp
new file mode 100644 (file)
index 0000000..e7ee526
--- /dev/null
@@ -0,0 +1,458 @@
+#include "event.hpp"
+
+#include <cctype>
+#include <ostream>
+
+using std::ostream;
+
+
+namespace blobs {
+namespace io {
+
+ostream &operator <<(ostream &out, const SDL_Event &evt) {
+       switch (evt.type) {
+#if SDL_VERSION_ATLEAST(2, 0, 4)
+               case SDL_AUDIODEVICEADDED:
+                       out << "audio device added: " << evt.adevice;
+                       break;
+               case SDL_AUDIODEVICEREMOVED:
+                       out << "audio device removed: " << evt.adevice;
+                       break;
+#endif
+               case SDL_CONTROLLERAXISMOTION:
+                       out << "controller axis motion: " << evt.caxis;
+                       break;
+               case SDL_CONTROLLERBUTTONDOWN:
+                       out << "controller button down: " << evt.cbutton;
+                       break;
+               case SDL_CONTROLLERBUTTONUP:
+                       out << "controller button up: " << evt.cbutton;
+                       break;
+               case SDL_CONTROLLERDEVICEADDED:
+                       out << "controller device added: " << evt.cdevice;
+                       break;
+               case SDL_CONTROLLERDEVICEREMOVED:
+                       out << "controller device removed: " << evt.cdevice;
+                       break;
+               case SDL_CONTROLLERDEVICEREMAPPED:
+                       out << "controller device remapped: " << evt.cdevice;
+                       break;
+               case SDL_DOLLARGESTURE:
+                       out << "dollar gesture: " << evt.dgesture;
+                       break;
+               case SDL_DOLLARRECORD:
+                       out << "dollar record: " << evt.dgesture;
+                       break;
+               case SDL_DROPFILE:
+                       out << "drop file: " << evt.drop;
+                       break;
+               case SDL_FINGERMOTION:
+                       out << "finger motion: " << evt.tfinger;
+                       break;
+               case SDL_FINGERDOWN:
+                       out << "finger down: " << evt.tfinger;
+                       break;
+               case SDL_FINGERUP:
+                       out << "finger up: " << evt.tfinger;
+                       break;
+               case SDL_KEYDOWN:
+                       out << "key down: " << evt.key;
+                       break;
+               case SDL_KEYUP:
+                       out << "key up: " << evt.key;
+                       break;
+               case SDL_JOYAXISMOTION:
+                       out << "joystick axis motion: " << evt.jaxis;
+                       break;
+               case SDL_JOYBALLMOTION:
+                       out << "joystick ball motion: " << evt.jball;
+                       break;
+               case SDL_JOYHATMOTION:
+                       out << "joystick hat motion: " << evt.jhat;
+                       break;
+               case SDL_JOYBUTTONDOWN:
+                       out << "joystick button down: " << evt.jbutton;
+                       break;
+               case SDL_JOYBUTTONUP:
+                       out << "joystick button up: " << evt.jbutton;
+                       break;
+               case SDL_JOYDEVICEADDED:
+                       out << "joystick device added: " << evt.jdevice;
+                       break;
+               case SDL_JOYDEVICEREMOVED:
+                       out << "joystick device removed: " << evt.jdevice;
+                       break;
+               case SDL_MOUSEMOTION:
+                       out << "mouse motion: " << evt.motion;
+                       break;
+               case SDL_MOUSEBUTTONDOWN:
+                       out << "mouse button down: " << evt.button;
+                       break;
+               case SDL_MOUSEBUTTONUP:
+                       out << "mouse button up: " << evt.button;
+                       break;
+               case SDL_MOUSEWHEEL:
+                       out << "mouse wheel: " << evt.wheel;
+                       break;
+               case SDL_MULTIGESTURE:
+                       out << "multi gesture: " << evt.mgesture;
+                       break;
+               case SDL_QUIT:
+                       out << "quit: " << evt.quit;
+                       break;
+               case SDL_SYSWMEVENT:
+                       out << "sys wm: " << evt.syswm;
+                       break;
+               case SDL_TEXTEDITING:
+                       out << "text editing: " << evt.edit;
+                       break;
+               case SDL_TEXTINPUT:
+                       out << "text input: " << evt.text;
+                       break;
+               case SDL_USEREVENT:
+                       out << "user: " << evt.user;
+                       break;
+               case SDL_WINDOWEVENT:
+                       out << "window: " << evt.window;
+                       break;
+               default:
+                       out << "unknown";
+                       break;
+       }
+       return out;
+}
+
+ostream &operator <<(ostream &out, const SDL_WindowEvent &evt) {
+       switch (evt.event) {
+               case SDL_WINDOWEVENT_SHOWN:
+                       out << "shown, window ID: " << evt.windowID;
+                       break;
+               case SDL_WINDOWEVENT_HIDDEN:
+                       out << "hidden, window ID: " << evt.windowID;
+                       break;
+               case SDL_WINDOWEVENT_EXPOSED:
+                       out << "exposed, window ID: " << evt.windowID;
+                       break;
+               case SDL_WINDOWEVENT_MOVED:
+                       out << "moved, window ID: " << evt.windowID
+                               << ", position: " << evt.data1 << ' ' << evt.data2;
+                       break;
+               case SDL_WINDOWEVENT_RESIZED:
+                       out << "resized, window ID: " << evt.windowID
+                               << ", size: " << evt.data1 << 'x' << evt.data2;
+                       break;
+               case SDL_WINDOWEVENT_SIZE_CHANGED:
+                       out << "size changed, window ID: " << evt.windowID
+                               << ", size: " << evt.data1 << 'x' << evt.data2;
+                       break;
+               case SDL_WINDOWEVENT_MINIMIZED:
+                       out << "minimized, window ID: " << evt.windowID;
+                       break;
+               case SDL_WINDOWEVENT_MAXIMIZED:
+                       out << "maximized, window ID: " << evt.windowID;
+                       break;
+               case SDL_WINDOWEVENT_RESTORED:
+                       out << "restored, window ID: " << evt.windowID;
+                       break;
+               case SDL_WINDOWEVENT_ENTER:
+                       out << "mouse entered, window ID: " << evt.windowID;
+                       break;
+               case SDL_WINDOWEVENT_LEAVE:
+                       out << "mouse left, window ID: " << evt.windowID;
+                       break;
+               case SDL_WINDOWEVENT_FOCUS_GAINED:
+                       out << "focus gained, window ID: " << evt.windowID;
+                       break;
+               case SDL_WINDOWEVENT_FOCUS_LOST:
+                       out << "focus lost, window ID: " << evt.windowID;
+                       break;
+               case SDL_WINDOWEVENT_CLOSE:
+                       out << "closed, window ID: " << evt.windowID;
+                       break;
+               default:
+                       out << "unknown";
+                       break;
+       }
+       return out;
+}
+
+ostream &operator <<(ostream &out, const SDL_KeyboardEvent &evt) {
+       out << "window ID: " << evt.windowID
+               << ", state: " << (evt.state == SDL_PRESSED ? "pressed" : "released")
+               << ", repeat: " << (evt.repeat ? "yes" : "no")
+               << ", keysym: " << evt.keysym;
+       return out;
+}
+
+ostream &operator <<(ostream &out, const SDL_Keysym &keysym) {
+       out << "scancode: " << int(keysym.scancode)
+               << ", sym: " << int(keysym.sym)
+               << " (\"" << SDL_GetKeyName(keysym.sym) << "\")";
+       if (keysym.mod) {
+               out << ", mod:";
+               if (keysym.mod & KMOD_LSHIFT) {
+                       out << " LSHIFT";
+               }
+               if (keysym.mod & KMOD_RSHIFT) {
+                       out << " RSHIFT";
+               }
+               if (keysym.mod & KMOD_LCTRL) {
+                       out << " LCTRL";
+               }
+               if (keysym.mod & KMOD_RCTRL) {
+                       out << " RCTRL";
+               }
+               if (keysym.mod & KMOD_LALT) {
+                       out << " LALT";
+               }
+               if (keysym.mod & KMOD_RALT) {
+                       out << " RALT";
+               }
+               if (keysym.mod & KMOD_LGUI) {
+                       out << " LSUPER";
+               }
+               if (keysym.mod & KMOD_RGUI) {
+                       out << " RSUPER";
+               }
+               if (keysym.mod & KMOD_NUM) {
+                       out << " NUM";
+               }
+               if (keysym.mod & KMOD_CAPS) {
+                       out << " CAPS";
+               }
+               if (keysym.mod & KMOD_MODE) {
+                       out << " ALTGR";
+               }
+       }
+       return out;
+}
+
+ostream &operator <<(ostream &out, const SDL_TextEditingEvent &evt) {
+       out << "window ID: " << evt.windowID
+               << ", text: \"" << evt.text
+               << "\", start: " << evt.start
+               << ", length: " << evt.length;
+       return out;
+}
+
+ostream &operator <<(ostream &out, const SDL_TextInputEvent &evt) {
+       out << "window ID: " << evt.windowID
+               << ", text: \"" << evt.text << '"';
+       return out;
+}
+
+ostream &operator <<(ostream &out, const SDL_MouseMotionEvent &evt) {
+       out << "window ID: " << evt.windowID
+               << ", mouse ID: " << evt.which
+               << ", position: " << evt.x << ' ' << evt.y
+               << ", delta: " << evt.xrel << ' ' << evt.yrel;
+       if (evt.state) {
+               out << ", buttons:";
+               if (evt.state & SDL_BUTTON_LMASK) {
+                       out << " left";
+               }
+               if (evt.state & SDL_BUTTON_MMASK) {
+                       out << " middle";
+               }
+               if (evt.state & SDL_BUTTON_RMASK) {
+                       out << " right";
+               }
+               if (evt.state & SDL_BUTTON_X1MASK) {
+                       out << " X1";
+               }
+               if (evt.state & SDL_BUTTON_X2MASK) {
+                       out << " X2";
+               }
+       }
+       return out;
+}
+
+ostream &operator <<(ostream &out, const SDL_MouseButtonEvent &evt) {
+       out << "window ID: " << evt.windowID
+               << ", mouse ID: " << evt.which
+               << ", button: ";
+       switch (evt.button) {
+               case SDL_BUTTON_LEFT:
+                       out << "left";
+                       break;
+               case SDL_BUTTON_MIDDLE:
+                       out << "middle";
+                       break;
+               case SDL_BUTTON_RIGHT:
+                       out << "right";
+                       break;
+               case SDL_BUTTON_X1:
+                       out << "X1";
+                       break;
+               case SDL_BUTTON_X2:
+                       out << "X2";
+                       break;
+               default:
+                       out << int(evt.button);
+                       break;
+       }
+       out << ", state: " << (evt.state == SDL_PRESSED ? "pressed" : "released")
+               << ", clicks: " << int(evt.clicks)
+               << ", position: " << evt.x << ' ' << evt.y;
+       return out;
+}
+
+ostream &operator <<(ostream &out, const SDL_MouseWheelEvent &evt) {
+       out << "window ID: " << evt.windowID
+               << ", mouse ID: " << evt.which
+               << ", delta: " << evt.x << ' ' << evt.y
+#if SDL_VERSION_ATLEAST(2, 0, 4)
+               << ", direction: " << (evt.direction == SDL_MOUSEWHEEL_NORMAL ? "normal" : "flipped")
+#endif
+               ;
+       return out;
+}
+
+ostream &operator <<(ostream &out, const SDL_JoyAxisEvent &evt) {
+       out << "joystick ID: " << evt.which
+               << ", axis ID: " << int(evt.axis)
+               << ", value: " << (float(evt.value) / 32768.0f);
+       return out;
+}
+
+ostream &operator <<(ostream &out, const SDL_JoyBallEvent &evt) {
+       out << "joystick ID: " << evt.which
+               << ", ball ID: " << int(evt.ball)
+               << ", delta: " << evt.xrel << ' ' << evt.yrel;
+       return out;
+}
+
+ostream &operator <<(ostream &out, const SDL_JoyHatEvent &evt) {
+       out << "joystick ID: " << evt.which
+               << ", hat ID: " << int(evt.hat)
+               << ", value: ";
+       switch (evt.value) {
+               case SDL_HAT_LEFTUP:
+                       out << "left up";
+                       break;
+               case SDL_HAT_UP:
+                       out << "up";
+                       break;
+               case SDL_HAT_RIGHTUP:
+                       out << "right up";
+                       break;
+               case SDL_HAT_LEFT:
+                       out << "left";
+                       break;
+               case SDL_HAT_CENTERED:
+                       out << "center";
+                       break;
+               case SDL_HAT_RIGHT:
+                       out << "right";
+                       break;
+               case SDL_HAT_LEFTDOWN:
+                       out << "left down";
+                       break;
+               case SDL_HAT_DOWN:
+                       out << "down";
+                       break;
+               case SDL_HAT_RIGHTDOWN:
+                       out << "right down";
+                       break;
+               default:
+                       out << "unknown";
+                       break;
+       }
+       return out;
+}
+
+ostream &operator <<(ostream &out, const SDL_JoyButtonEvent &evt) {
+       out << "joystick ID: " << evt.which
+               << ", button ID: " << int(evt.button)
+               << ", state: " << (evt.state == SDL_PRESSED ? "pressed" : "released");
+       return out;
+}
+
+ostream &operator <<(ostream &out, const SDL_JoyDeviceEvent &evt) {
+       out << "joystick ID: " << evt.which;
+       return out;
+}
+
+ostream &operator <<(ostream &out, const SDL_ControllerAxisEvent &evt) {
+       out << "controller ID: " << evt.which
+               << ", axis ID: " << int(evt.axis)
+               << ", value: " << (float(evt.value) / 32768.0f);
+       return out;
+}
+
+ostream &operator <<(ostream &out, const SDL_ControllerButtonEvent &evt) {
+       out << "controller ID: " << evt.which
+               << ", button ID: " << int(evt.button)
+               << ", state: " << (evt.state == SDL_PRESSED ? "pressed" : "released");
+       return out;
+}
+
+ostream &operator <<(ostream &out, const SDL_ControllerDeviceEvent &evt) {
+       out << "controller ID: " << evt.which;
+       return out;
+}
+
+#if SDL_VERSION_ATLEAST(2, 0, 4)
+ostream &operator <<(ostream &out, const SDL_AudioDeviceEvent &evt) {
+       out << "device ID: " << evt.which
+               << ", capture: " << (evt.iscapture ? "yes" : "no");
+       return out;
+}
+#endif
+
+ostream &operator <<(ostream &out, const SDL_QuitEvent &evt) {
+       out << "quit";
+       return out;
+}
+
+ostream &operator <<(ostream &out, const SDL_UserEvent &evt) {
+       out << "window ID: " << evt.windowID
+               << ", code: " << evt.code
+               << ", data 1: " << evt.data1
+               << ", data 2: " << evt.data2;
+       return out;
+}
+
+ostream &operator <<(ostream &out, const SDL_SysWMEvent &evt) {
+       if (evt.msg) {
+               out << "with message";
+       } else {
+               out << "without message";
+       }
+       return out;
+}
+
+ostream &operator <<(ostream &out, const SDL_TouchFingerEvent &evt) {
+       out << "device ID: " << evt.touchId
+               << ", finger ID: " << evt.fingerId
+               << ", position: " << evt.x << ' ' << evt.y
+               << ", delta: " << evt.dx << ' ' << evt.dy
+               << ", pressure: " << evt.pressure;
+       return out;
+}
+
+ostream &operator <<(ostream &out, const SDL_MultiGestureEvent &evt) {
+       out << "device ID: " << evt.touchId
+               << ", theta: " << evt.dTheta
+               << ", distance: " << evt.dDist
+               << ", position: " << evt.x << ' ' << evt.y
+               << ", fingers: " << evt.numFingers;
+       return out;
+}
+
+ostream &operator <<(ostream &out, const SDL_DollarGestureEvent &evt) {
+       out << "device ID: " << evt.touchId
+               << ", gesture ID: " << evt.gestureId
+               << ", fingers: " << evt.numFingers
+               << ", error: " << evt.error
+               << ", position: " << evt.x << ' ' << evt.y;
+       return out;
+}
+
+ostream &operator <<(ostream &out, const SDL_DropEvent &evt) {
+       out << "file: " << evt.file;
+       return out;
+}
+
+}
+}
diff --git a/src/io/event.hpp b/src/io/event.hpp
new file mode 100644 (file)
index 0000000..187cfa2
--- /dev/null
@@ -0,0 +1,45 @@
+#ifndef BLOBS_IO_EVENT_HPP_
+#define BLOBS_IO_EVENT_HPP_
+
+#include <iosfwd>
+#include <SDL.h>
+#include <SDL_version.h>
+
+
+namespace blobs {
+namespace io {
+
+std::ostream &operator <<(std::ostream &, const SDL_Event &);
+
+std::ostream &operator <<(std::ostream &, const SDL_WindowEvent &);
+std::ostream &operator <<(std::ostream &, const SDL_KeyboardEvent &);
+std::ostream &operator <<(std::ostream &, const SDL_TextEditingEvent &);
+std::ostream &operator <<(std::ostream &, const SDL_TextInputEvent &);
+std::ostream &operator <<(std::ostream &, const SDL_MouseMotionEvent &);
+std::ostream &operator <<(std::ostream &, const SDL_MouseButtonEvent &);
+std::ostream &operator <<(std::ostream &, const SDL_MouseWheelEvent &);
+std::ostream &operator <<(std::ostream &, const SDL_JoyAxisEvent &);
+std::ostream &operator <<(std::ostream &, const SDL_JoyBallEvent &);
+std::ostream &operator <<(std::ostream &, const SDL_JoyHatEvent &);
+std::ostream &operator <<(std::ostream &, const SDL_JoyButtonEvent &);
+std::ostream &operator <<(std::ostream &, const SDL_JoyDeviceEvent &);
+std::ostream &operator <<(std::ostream &, const SDL_ControllerAxisEvent &);
+std::ostream &operator <<(std::ostream &, const SDL_ControllerButtonEvent &);
+std::ostream &operator <<(std::ostream &, const SDL_ControllerDeviceEvent &);
+#if SDL_VERSION_ATLEAST(2, 0, 4)
+std::ostream &operator <<(std::ostream &, const SDL_AudioDeviceEvent &);
+#endif
+std::ostream &operator <<(std::ostream &, const SDL_QuitEvent &);
+std::ostream &operator <<(std::ostream &, const SDL_UserEvent &);
+std::ostream &operator <<(std::ostream &, const SDL_SysWMEvent &);
+std::ostream &operator <<(std::ostream &, const SDL_TouchFingerEvent &);
+std::ostream &operator <<(std::ostream &, const SDL_MultiGestureEvent &);
+std::ostream &operator <<(std::ostream &, const SDL_DollarGestureEvent &);
+std::ostream &operator <<(std::ostream &, const SDL_DropEvent &);
+
+std::ostream &operator <<(std::ostream &, const SDL_Keysym &);
+
+}
+}
+
+#endif
diff --git a/src/io/filesystem.cpp b/src/io/filesystem.cpp
new file mode 100644 (file)
index 0000000..feb1cac
--- /dev/null
@@ -0,0 +1,213 @@
+#include "filesystem.hpp"
+
+#include <cerrno>
+#include <cstdio>
+#include <cstring>
+#ifdef _WIN32
+#  include <conio.h>
+#  include <direct.h>
+#  include <windows.h>
+#else
+#  include <dirent.h>
+#  include <sys/types.h>
+#endif
+#include <sys/stat.h>
+
+
+namespace blobs {
+namespace io {
+
+namespace {
+#ifdef _WIN32
+       using Stat = struct _stat;
+       int do_stat(const char *path, Stat &info) {
+               return _stat(path, &info);
+       }
+       bool is_dir(const Stat &info) {
+               return (info.st_mode & _S_IFDIR) != 0;
+       }
+       bool is_file(const Stat &info) {
+               return (info.st_mode & _S_IFEG) != 0;
+       }
+#else
+       using Stat = struct stat;
+       int do_stat(const char *path, Stat &info) {
+               return stat(path, &info);
+       }
+       bool is_dir(const Stat &info) {
+               return S_ISDIR(info.st_mode);
+       }
+       bool is_file(const Stat &info) {
+               return S_ISREG(info.st_mode);
+       }
+#endif
+       std::time_t get_mtime(const Stat &info) {
+#ifdef __APPLE__
+               return info.st_mtimespec.tv_sec;
+#else
+       return info.st_mtime;
+#endif
+       }
+}
+
+bool is_dir(const char *path) {
+       Stat info;
+       if (do_stat(path, info) != 0) {
+               return false;
+       }
+       return is_dir(info);
+}
+
+bool is_file(const char *path) {
+       Stat info;
+       if (do_stat(path, info) != 0) {
+               return false;
+       }
+       return is_file(info);
+}
+
+std::time_t file_mtime(const char *path) {
+       Stat info;
+       if (do_stat(path, info) != 0) {
+               return 0;
+       }
+       return get_mtime(info);
+}
+
+
+bool make_dir(const char *path) {
+#ifdef _WIN32
+       int ret = _mkdir(path);
+#else
+       int ret = mkdir(path, 0777);
+#endif
+       return ret == 0;
+}
+
+
+bool make_dirs(const std::string &path) {
+       if (make_dir(path)) {
+               return true;
+       }
+
+       switch (errno) {
+
+               case ENOENT:
+                       // missing component
+                       {
+#ifdef _WIN32
+                               auto pos = path.find_last_of("\\/");
+#else
+                               auto pos = path.find_last_of('/');
+#endif
+                               if (pos == std::string::npos) {
+                                       return false;
+                               }
+                               if (pos == path.length() - 1) {
+                                       // trailing separator, would make final make_dir fail
+#ifdef _WIN32
+                                        pos = path.find_last_of("\\/", pos - 1);
+#else
+                                        pos = path.find_last_of('/', pos - 1);
+#endif
+                                       if (pos == std::string::npos) {
+                                               return false;
+                                       }
+                               }
+                               if (!make_dirs(path.substr(0, pos))) {
+                                       return false;
+                               }
+                       }
+                       // try again
+                       return make_dir(path);
+
+               case EEXIST:
+                       // something's there, check if it's a dir and we're good
+                       return is_dir(path);
+
+               default:
+                       // whatever else went wrong, it can't be good
+                       return false;
+
+       }
+}
+
+
+bool remove_file(const std::string &path) {
+       return remove(path.c_str()) == 0;
+}
+
+
+bool remove_dir(const std::string &path) {
+#ifdef _WIN32
+
+       // shamelessly stolen from http://www.codeguru.com/forum/showthread.php?t=239271
+       const std::string pattern = path + "\\*.*";
+       WIN32_FIND_DATA info;
+       HANDLE file = FindFirstFile(pattern.c_str(), &info);
+       if (file == INVALID_HANDLE_VALUE) {
+               // already non-existing
+               return true;
+       }
+
+       do {
+               if (
+                       strncmp(info.cFileName, ".", 2) == 0 ||
+                       strncmp(info.cFileName, "..", 3) == 0
+               ) {
+                       continue;
+               }
+               const std::string sub_path = path + '\\' + info.cFileName;
+               if ((info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
+                       if (!remove_dir(sub_path)) {
+                               return false;
+                       }
+               } else {
+                       if (!SetFileAttributes(sub_path.c_str(), FILE_ATTRIBUTE_NORMAL)) {
+                               return false;
+                       }
+                       if (!remove_file(sub_path)) {
+                               return false;
+                       }
+               }
+       } while (FindNextFile(file, &info));
+       FindClose(file);
+
+       DWORD error = GetLastError();
+       if (error != ERROR_NO_MORE_FILES) {
+               return false;
+       }
+       // is this (NORMAL vs DIRECTORY) really correct?
+       if (!SetFileAttributes(path.c_str(), FILE_ATTRIBUTE_NORMAL)) {
+               return false;
+       }
+       return RemoveDirectory(path.c_str());
+
+#else
+
+       DIR *dir = opendir(path.c_str());
+       for (dirent *entry = readdir(dir); entry != nullptr; entry = readdir(dir)) {
+               if (
+                       strncmp(entry->d_name, ".", 2) == 0 ||
+                       strncmp(entry->d_name, "..", 3) == 0
+               ) {
+                       continue;
+               }
+               const std::string sub_path = path + '/' + entry->d_name;
+               if (is_dir(sub_path)) {
+                       if (!remove_dir(sub_path)) {
+                               return false;
+                       }
+               } else {
+                       if (!remove_file(sub_path)) {
+                               return false;
+                       }
+               }
+       }
+       return remove(path.c_str()) == 0;
+
+#endif
+}
+
+}
+}
diff --git a/src/io/filesystem.hpp b/src/io/filesystem.hpp
new file mode 100644 (file)
index 0000000..154b812
--- /dev/null
@@ -0,0 +1,49 @@
+#ifndef BLOBS_IO_FILESYSTEM_HPP_
+#define BLOBS_IO_FILESYSTEM_HPP_
+
+#include <ctime>
+#include <string>
+
+
+namespace blobs {
+namespace io {
+
+/// check if give path points to an existing directory
+bool is_dir(const char *);
+inline bool is_dir(const std::string &s) {
+       return is_dir(s.c_str());
+}
+/// check if give path points to an existing file
+bool is_file(const char *);
+inline bool is_file(const std::string &s) {
+       return is_file(s.c_str());
+}
+/// get timestamp of last modification
+std::time_t file_mtime(const char *);
+inline std::time_t file_mtime(const std::string &s) {
+       return file_mtime(s.c_str());
+}
+
+/// create given directory
+/// @return true if the directory was created
+///         the directory might already exist, see errno
+bool make_dir(const char *);
+inline bool make_dir(const std::string &s) {
+       return make_dir(s.c_str());
+}
+/// create given directory and all parents
+/// @return true if the directory was created or already exists
+bool make_dirs(const std::string &);
+
+/// remove given file
+/// @return true on success
+bool remove_file(const std::string &);
+/// recursively remove given directory
+/// may leave the directory partially removed on failure
+/// @return true if the directory was completely removed
+bool remove_dir(const std::string &);
+
+}
+}
+
+#endif
diff --git a/src/io/token.cpp b/src/io/token.cpp
new file mode 100644 (file)
index 0000000..c15d65a
--- /dev/null
@@ -0,0 +1,472 @@
+#include "Token.hpp"
+#include "Tokenizer.hpp"
+#include "TokenStreamReader.hpp"
+
+#include <cctype>
+#include <istream>
+#include <ostream>
+#include <sstream>
+#include <stdexcept>
+#include <glm/gtc/quaternion.hpp>
+
+using namespace std;
+
+
+namespace blobs {
+namespace io {
+
+ostream &operator <<(ostream &out, Token::Type t) {
+       switch (t) {
+               case Token::ANGLE_BRACKET_OPEN:
+                       return out << "ANGLE_BRACKET_OPEN";
+               case Token::ANGLE_BRACKET_CLOSE:
+                       return out << "ANGLE_BRACKET_CLOSE";
+               case Token::CHEVRON_OPEN:
+                       return out << "CHEVRON_OPEN";
+               case Token::CHEVRON_CLOSE:
+                       return out << "CHEVRON_CLOSE";
+               case Token::BRACKET_OPEN:
+                       return out << "BRACKET_OPEN";
+               case Token::BRACKET_CLOSE:
+                       return out << "BRACKET_CLOSE";
+               case Token::PARENTHESIS_OPEN:
+                       return out << "PARENTHESIS_OPEN";
+               case Token::PARENTHESIS_CLOSE:
+                       return out << "PARENTHESIS_CLOSE";
+               case Token::COLON:
+                       return out << "COLON";
+               case Token::SEMICOLON:
+                       return out << "SEMICOLON";
+               case Token::COMMA:
+                       return out << "COMMA";
+               case Token::EQUALS:
+                       return out << "EQUALS";
+               case Token::NUMBER:
+                       return out << "NUMBER";
+               case Token::STRING:
+                       return out << "STRING";
+               case Token::IDENTIFIER:
+                       return out << "IDENTIFIER";
+               case Token::COMMENT:
+                       return out << "COMMENT";
+               default:
+                       return out << "UNKNOWN";
+       }
+}
+
+ostream &operator <<(ostream &out, const Token &t) {
+       out << t.type;
+       switch (t.type) {
+               case Token::UNKNOWN:
+               case Token::NUMBER:
+               case Token::STRING:
+               case Token::IDENTIFIER:
+               case Token::COMMENT:
+                       return out << '(' << t.value << ')';
+               default:
+                       return out;
+       }
+}
+
+Tokenizer::Tokenizer(istream &in)
+: in(in)
+, current() {
+
+}
+
+
+bool Tokenizer::HasMore() {
+       return bool(istream::sentry(in));
+}
+
+const Token &Tokenizer::Next() {
+       ReadToken();
+       return Current();
+}
+
+void Tokenizer::ReadToken() {
+       current.type = Token::UNKNOWN;
+       current.value.clear();
+
+       istream::sentry s(in);
+       if (!s) {
+               throw runtime_error("read past the end of stream");
+               return;
+       }
+
+       istream::char_type c;
+       in.get(c);
+       switch (c) {
+               case '{': case '}':
+               case '<': case '>':
+               case '[': case ']':
+               case '(': case ')':
+               case ';': case ':':
+               case ',': case '=':
+                       current.type = Token::Type(c);
+                       break;
+               case '+': case '-': case '.':
+               case '0': case '1': case '2': case '3': case '4':
+               case '5': case '6': case '7': case '8': case '9':
+                       in.putback(c);
+                       ReadNumber();
+                       break;
+               case '"':
+                       ReadString();
+                       break;
+               case '#':
+               case '/':
+                       in.putback(c);
+                       ReadComment();
+                       break;
+               default:
+                       in.putback(c);
+                       ReadIdentifier();
+                       break;
+       }
+}
+
+namespace {
+
+bool is_num_char(istream::char_type c) {
+       return isxdigit(c)
+               || c == '.'
+               || c == '-'
+               || c == '+'
+       ;
+}
+
+}
+
+void Tokenizer::ReadNumber() {
+       current.type = Token::NUMBER;
+       istream::char_type c;
+       while (in.get(c)) {
+               if (is_num_char(c)) {
+                       current.value += c;
+               } else {
+                       in.putback(c);
+                       break;
+               }
+       }
+}
+
+void Tokenizer::ReadString() {
+       current.type = Token::STRING;
+       bool escape = false;
+
+       istream::char_type c;
+       while (in.get(c)) {
+               if (escape) {
+                       escape = false;
+                       switch (c) {
+                               case 'n':
+                                       current.value += '\n';
+                                       break;
+                               case 'r':
+                                       current.value += '\r';
+                                       break;
+                               case 't':
+                                       current.value += '\t';
+                                       break;
+                               default:
+                                       current.value += c;
+                                       break;
+                       }
+               } else if (c == '"') {
+                       break;
+               } else if (c == '\\') {
+                       escape = true;
+               } else {
+                       current.value += c;
+               }
+       }
+}
+
+void Tokenizer::ReadComment() {
+       current.type = Token::COMMENT;
+       istream::char_type c;
+       in.get(c);
+
+       if (c == '#') {
+               while (in.get(c) && c != '\n') {
+                       current.value += c;
+               }
+               return;
+       }
+
+       // c is guaranteed to be '/' now
+       if (!in.get(c)) {
+               throw runtime_error("unexpected end of stream");
+       }
+       if (c == '/') {
+               while (in.get(c) && c != '\n') {
+                       current.value += c;
+               }
+               return;
+       } else if (c != '*') {
+               throw runtime_error("invalid character after /");
+       }
+
+       while (in.get(c)) {
+               if (c == '*') {
+                       istream::char_type c2;
+                       if (!in.get(c2)) {
+                               throw runtime_error("unexpected end of stream");
+                       }
+                       if (c2 == '/') {
+                               break;
+                       } else {
+                               current.value += c;
+                               current.value += c2;
+                       }
+               } else {
+                       current.value += c;
+               }
+       }
+}
+
+void Tokenizer::ReadIdentifier() {
+       current.type = Token::IDENTIFIER;
+
+       istream::char_type c;
+       while (in.get(c)) {
+               if (isalnum(c) || c == '_' || c == '.') {
+                       current.value += c;
+               } else {
+                       in.putback(c);
+                       break;
+               }
+       }
+}
+
+
+TokenStreamReader::TokenStreamReader(istream &in)
+: in(in)
+, cached(false) {
+
+}
+
+
+bool TokenStreamReader::HasMore() {
+       if (cached) {
+               return true;
+       }
+       SkipComments();
+       return cached;
+}
+
+const Token &TokenStreamReader::Next() {
+       SkipComments();
+       cached = false;
+       return in.Current();
+}
+
+void TokenStreamReader::SkipComments() {
+       if (cached) {
+               if (in.Current().type == Token::COMMENT) {
+                       cached = false;
+               } else {
+                       return;
+               }
+       }
+       while (in.HasMore()) {
+               if (in.Next().type != Token::COMMENT) {
+                       cached = true;
+                       return;
+               }
+       }
+}
+
+const Token &TokenStreamReader::Peek() {
+       if (!cached) {
+               Next();
+               cached = true;
+       }
+       return in.Current();
+}
+
+
+void TokenStreamReader::Assert(Token::Type t) const {
+       if (GetType() != t) {
+               stringstream s;
+               s << "unexpected token in input stream: expected " << t << ", but got " << in.Current();
+               throw runtime_error(s.str());
+       }
+}
+
+Token::Type TokenStreamReader::GetType() const noexcept {
+       return in.Current().type;
+}
+
+const std::string &TokenStreamReader::GetValue() const noexcept {
+       return in.Current().value;
+}
+
+void TokenStreamReader::Skip(Token::Type t) {
+       Next();
+       Assert(t);
+}
+
+
+void TokenStreamReader::ReadBoolean(bool &b) {
+       b = GetBool();
+}
+
+void TokenStreamReader::ReadIdentifier(string &out) {
+       Next();
+       Assert(Token::IDENTIFIER);
+       out = GetValue();
+}
+
+void TokenStreamReader::ReadNumber(float &n) {
+       n = GetFloat();
+}
+
+void TokenStreamReader::ReadNumber(int &n) {
+       n = GetInt();
+}
+
+void TokenStreamReader::ReadNumber(unsigned long &n) {
+       n = GetULong();
+}
+
+void TokenStreamReader::ReadString(string &out) {
+       Next();
+       Assert(Token::STRING);
+       out = GetValue();
+}
+
+
+void TokenStreamReader::ReadVec(glm::vec2 &v) {
+       Skip(Token::BRACKET_OPEN);
+       ReadNumber(v.x);
+       Skip(Token::COMMA);
+       ReadNumber(v.y);
+       Skip(Token::BRACKET_CLOSE);
+}
+
+void TokenStreamReader::ReadVec(glm::vec3 &v) {
+       Skip(Token::BRACKET_OPEN);
+       ReadNumber(v.x);
+       Skip(Token::COMMA);
+       ReadNumber(v.y);
+       Skip(Token::COMMA);
+       ReadNumber(v.z);
+       Skip(Token::BRACKET_CLOSE);
+}
+
+void TokenStreamReader::ReadVec(glm::vec4 &v) {
+       Skip(Token::BRACKET_OPEN);
+       ReadNumber(v.x);
+       Skip(Token::COMMA);
+       ReadNumber(v.y);
+       Skip(Token::COMMA);
+       ReadNumber(v.z);
+       Skip(Token::COMMA);
+       ReadNumber(v.w);
+       Skip(Token::BRACKET_CLOSE);
+}
+
+void TokenStreamReader::ReadVec(glm::ivec2 &v) {
+       Skip(Token::BRACKET_OPEN);
+       ReadNumber(v.x);
+       Skip(Token::COMMA);
+       ReadNumber(v.y);
+       Skip(Token::BRACKET_CLOSE);
+}
+
+void TokenStreamReader::ReadVec(glm::ivec3 &v) {
+       Skip(Token::BRACKET_OPEN);
+       ReadNumber(v.x);
+       Skip(Token::COMMA);
+       ReadNumber(v.y);
+       Skip(Token::COMMA);
+       ReadNumber(v.z);
+       Skip(Token::BRACKET_CLOSE);
+}
+
+void TokenStreamReader::ReadVec(glm::ivec4 &v) {
+       Skip(Token::BRACKET_OPEN);
+       ReadNumber(v.x);
+       Skip(Token::COMMA);
+       ReadNumber(v.y);
+       Skip(Token::COMMA);
+       ReadNumber(v.z);
+       Skip(Token::COMMA);
+       ReadNumber(v.w);
+       Skip(Token::BRACKET_CLOSE);
+}
+
+void TokenStreamReader::ReadQuat(glm::quat &q) {
+       Skip(Token::BRACKET_OPEN);
+       ReadNumber(q.w);
+       Skip(Token::COMMA);
+       ReadNumber(q.x);
+       Skip(Token::COMMA);
+       ReadNumber(q.y);
+       Skip(Token::COMMA);
+       ReadNumber(q.z);
+       Skip(Token::BRACKET_CLOSE);
+}
+
+
+bool TokenStreamReader::GetBool() {
+       Next();
+       return AsBool();
+}
+
+bool TokenStreamReader::AsBool() const {
+       switch (GetType()) {
+               case Token::NUMBER:
+                       return AsInt() != 0;
+               case Token::IDENTIFIER:
+               case Token::STRING:
+                       if (GetValue() == "true" || GetValue() == "yes" || GetValue() == "on") {
+                               return true;
+                       } else if (GetValue() == "false" || GetValue() == "no" || GetValue() == "off") {
+                               return false;
+                       } else {
+                               throw runtime_error("unexpected value in input stream: cannot cast " + GetValue() + " to bool");
+                       }
+               default:
+                       {
+                               stringstream s;
+                               s << "unexpected token in input stream: cannot cast " << in.Current() << " to bool";
+                               throw runtime_error(s.str());
+                       }
+       }
+}
+
+float TokenStreamReader::GetFloat() {
+       Next();
+       return AsFloat();
+}
+
+float TokenStreamReader::AsFloat() const {
+       Assert(Token::NUMBER);
+       return stof(GetValue());
+}
+
+int TokenStreamReader::GetInt() {
+       Next();
+       return AsInt();
+}
+
+int TokenStreamReader::AsInt() const {
+       Assert(Token::NUMBER);
+       return stoi(GetValue());
+}
+
+unsigned long TokenStreamReader::GetULong() {
+       Next();
+       return AsULong();
+}
+
+unsigned long TokenStreamReader::AsULong() const {
+       Assert(Token::NUMBER);
+       return stoul(GetValue());
+}
+
+}
+}
diff --git a/tst/io/EventTest.cpp b/tst/io/EventTest.cpp
new file mode 100644 (file)
index 0000000..e0becd1
--- /dev/null
@@ -0,0 +1,604 @@
+#include "EventTest.hpp"
+
+#include "io/event.hpp"
+
+#include <sstream>
+#include <string>
+#include <SDL_syswm.h>
+
+
+CPPUNIT_TEST_SUITE_REGISTRATION(blobs::io::test::EventTest);
+
+using namespace std;
+
+namespace blobs {
+namespace io {
+namespace test {
+
+void EventTest::setUp() {
+
+}
+
+void EventTest::tearDown() {
+
+}
+
+
+namespace {
+
+template<class T>
+string string_cast(const T &val) {
+       stringstream str;
+       str << val;
+       return str.str();
+}
+
+}
+
+#if SDL_VERSION_ATLEAST(2, 0, 4)
+
+void EventTest::testAudioDevice() {
+       SDL_Event event;
+       event.type = SDL_AUDIODEVICEADDED;
+       event.adevice.which = 1;
+       event.adevice.iscapture = false;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL audio device event",
+               string("audio device added: device ID: 1, capture: no"), string_cast(event));
+       event.adevice.which = 2;
+       event.adevice.iscapture = true;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL audio device event",
+               string("audio device added: device ID: 2, capture: yes"), string_cast(event));
+               event.type = SDL_AUDIODEVICEREMOVED;
+       event.adevice.which = 3;
+       event.adevice.iscapture = false;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL audio device event",
+               string("audio device removed: device ID: 3, capture: no"), string_cast(event));
+       event.adevice.which = 4;
+       event.adevice.iscapture = true;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL audio device event",
+               string("audio device removed: device ID: 4, capture: yes"), string_cast(event));
+}
+
+#endif
+
+void EventTest::testController() {
+       SDL_Event event;
+       event.type = SDL_CONTROLLERAXISMOTION;
+       event.caxis.which = 0;
+       event.caxis.axis = 1;
+       event.caxis.value = 16384;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL controller axis event",
+               string("controller axis motion: controller ID: 0, axis ID: 1, value: 0.5"), string_cast(event));
+
+       event.type = SDL_CONTROLLERBUTTONDOWN;
+       event.cbutton.which = 2;
+       event.cbutton.button = 3;
+       event.cbutton.state = SDL_PRESSED;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL controller button event",
+               string("controller button down: controller ID: 2, button ID: 3, state: pressed"), string_cast(event));
+       event.type = SDL_CONTROLLERBUTTONUP;
+       event.cbutton.which = 4;
+       event.cbutton.button = 5;
+       event.cbutton.state = SDL_RELEASED;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL controller button event",
+               string("controller button up: controller ID: 4, button ID: 5, state: released"), string_cast(event));
+
+       event.type = SDL_CONTROLLERDEVICEADDED;
+       event.cdevice.which = 6;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL controller device event",
+               string("controller device added: controller ID: 6"), string_cast(event));
+       event.type = SDL_CONTROLLERDEVICEREMOVED;
+       event.cdevice.which = 7;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL controller device event",
+               string("controller device removed: controller ID: 7"), string_cast(event));
+       event.type = SDL_CONTROLLERDEVICEREMAPPED;
+       event.cdevice.which = 8;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL controller device event",
+               string("controller device remapped: controller ID: 8"), string_cast(event));
+}
+
+void EventTest::testDollar() {
+       SDL_Event event;
+       event.type = SDL_DOLLARGESTURE;
+       event.dgesture.touchId = 0;
+       event.dgesture.gestureId = 1;
+       event.dgesture.numFingers = 2;
+       event.dgesture.error = 3;
+       event.dgesture.x = 4;
+       event.dgesture.y = 5;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL dollar gesture event",
+               string("dollar gesture: device ID: 0, gesture ID: 1, fingers: 2, error: 3, position: 4 5"), string_cast(event));
+
+       event.type = SDL_DOLLARRECORD;
+       event.dgesture.touchId = 6;
+       event.dgesture.gestureId = 7;
+       event.dgesture.numFingers = 8;
+       event.dgesture.error = 9;
+       event.dgesture.x = 10;
+       event.dgesture.y = 11;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL dollar record event",
+               string("dollar record: device ID: 6, gesture ID: 7, fingers: 8, error: 9, position: 10 11"), string_cast(event));
+}
+
+void EventTest::testDrop() {
+       char filename[] = "/dev/random";
+       SDL_Event event;
+       event.type = SDL_DROPFILE;
+       event.drop.file = filename;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL drop file event",
+               string("drop file: file: ") + filename, string_cast(event));
+}
+
+void EventTest::testFinger() {
+       SDL_Event event;
+       event.type = SDL_FINGERMOTION;
+       event.tfinger.touchId = 0;
+       event.tfinger.fingerId = 1;
+       event.tfinger.x = 2;
+       event.tfinger.y = 3;
+       event.tfinger.dx = 4;
+       event.tfinger.dy = 5;
+       event.tfinger.pressure = 6;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL finger motion event",
+               string("finger motion: device ID: 0, finger ID: 1, position: 2 3, delta: 4 5, pressure: 6"), string_cast(event));
+
+       event.type = SDL_FINGERDOWN;
+       event.tfinger.touchId = 7;
+       event.tfinger.fingerId = 8;
+       event.tfinger.x = 9;
+       event.tfinger.y = 10;
+       event.tfinger.dx = 11;
+       event.tfinger.dy = 12;
+       event.tfinger.pressure = 13;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL finger down event",
+               string("finger down: device ID: 7, finger ID: 8, position: 9 10, delta: 11 12, pressure: 13"), string_cast(event));
+
+       event.type = SDL_FINGERUP;
+       event.tfinger.touchId = 14;
+       event.tfinger.fingerId = 15;
+       event.tfinger.x = 16;
+       event.tfinger.y = 17;
+       event.tfinger.dx = 18;
+       event.tfinger.dy = 19;
+       event.tfinger.pressure = 20;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL finger up event",
+               string("finger up: device ID: 14, finger ID: 15, position: 16 17, delta: 18 19, pressure: 20"), string_cast(event));
+}
+
+void EventTest::testKey() {
+       SDL_Event event;
+       event.type = SDL_KEYDOWN;
+       event.key.windowID = 0;
+       event.key.state = SDL_PRESSED;
+       event.key.repeat = 0;
+       event.key.keysym.scancode = SDL_SCANCODE_0;
+       event.key.keysym.sym = SDLK_0;
+       event.key.keysym.mod = KMOD_NONE;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL key down event",
+               string("key down: window ID: 0, state: pressed, repeat: no, keysym: "
+                       "scancode: ") + to_string(int(SDL_SCANCODE_0)) + ", sym: "
+                       + to_string(int(SDLK_0)) +" (\"0\")", string_cast(event));
+       event.key.windowID = 2;
+       event.key.repeat = 1;
+       event.key.keysym.scancode = SDL_SCANCODE_BACKSPACE;
+       event.key.keysym.sym = SDLK_BACKSPACE;
+       event.key.keysym.mod = KMOD_LCTRL | KMOD_LALT;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL key down event",
+               string("key down: window ID: 2, state: pressed, repeat: yes, keysym: "
+                       "scancode: ") + to_string(int(SDL_SCANCODE_BACKSPACE)) + ", sym: "
+                       + to_string(int(SDLK_BACKSPACE)) +" (\"Backspace\"), mod: LCTRL LALT", string_cast(event));
+
+       event.type = SDL_KEYUP;
+       event.key.windowID = 1;
+       event.key.state = SDL_RELEASED;
+       event.key.repeat = 0;
+       event.key.keysym.scancode = SDL_SCANCODE_SYSREQ;
+       event.key.keysym.sym = SDLK_SYSREQ;
+       event.key.keysym.mod = KMOD_LSHIFT | KMOD_RALT;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL key up event",
+               string("key up: window ID: 1, state: released, repeat: no, keysym: "
+                       "scancode: ") + to_string(int(SDL_SCANCODE_SYSREQ)) + ", sym: "
+                       + to_string(int(SDLK_SYSREQ)) +" (\"SysReq\"), mod: LSHIFT RALT", string_cast(event));
+       event.key.windowID = 3;
+       event.key.repeat = 1;
+       event.key.keysym.scancode = SDL_SCANCODE_L;
+       event.key.keysym.sym = SDLK_l;
+       event.key.keysym.mod = KMOD_RSHIFT | KMOD_RCTRL | KMOD_LGUI;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL key up event",
+               string("key up: window ID: 3, state: released, repeat: yes, keysym: "
+                       "scancode: ") + to_string(int(SDL_SCANCODE_L)) + ", sym: "
+                       + to_string(int(SDLK_l)) +" (\"L\"), mod: RSHIFT RCTRL LSUPER", string_cast(event));
+       event.key.windowID = 4;
+       event.key.repeat = 2;
+       event.key.keysym.scancode = SDL_SCANCODE_VOLUMEUP;
+       event.key.keysym.sym = SDLK_VOLUMEUP;
+       event.key.keysym.mod = KMOD_RGUI | KMOD_NUM | KMOD_CAPS | KMOD_MODE;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL key up event",
+               string("key up: window ID: 4, state: released, repeat: yes, keysym: "
+                       "scancode: ") + to_string(int(SDL_SCANCODE_VOLUMEUP)) + ", sym: "
+                       + to_string(int(SDLK_VOLUMEUP)) +" (\"VolumeUp\"), mod: RSUPER NUM CAPS ALTGR", string_cast(event));
+}
+
+void EventTest::testJoystick() {
+       SDL_Event event;
+       event.type = SDL_JOYAXISMOTION;
+       event.jaxis.which = 0;
+       event.jaxis.axis = 1;
+       event.jaxis.value = 16384;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL joystick axis motion event",
+               string("joystick axis motion: joystick ID: 0, axis ID: 1, value: 0.5"), string_cast(event));
+
+       event.type = SDL_JOYBALLMOTION;
+       event.jball.which = 2;
+       event.jball.ball = 3;
+       event.jball.xrel = 4;
+       event.jball.yrel = 5;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL joystick ball motion event",
+               string("joystick ball motion: joystick ID: 2, ball ID: 3, delta: 4 5"), string_cast(event));
+
+       event.type = SDL_JOYHATMOTION;
+       event.jhat.which = 6;
+       event.jhat.hat = 7;
+       event.jhat.value = SDL_HAT_LEFTUP;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL joystick hat motion event",
+               string("joystick hat motion: joystick ID: 6, hat ID: 7, value: left up"), string_cast(event));
+       event.jhat.value = SDL_HAT_UP;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL joystick hat motion event",
+               string("joystick hat motion: joystick ID: 6, hat ID: 7, value: up"), string_cast(event));
+       event.jhat.value = SDL_HAT_RIGHTUP;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL joystick hat motion event",
+               string("joystick hat motion: joystick ID: 6, hat ID: 7, value: right up"), string_cast(event));
+       event.jhat.value = SDL_HAT_LEFT;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL joystick hat motion event",
+               string("joystick hat motion: joystick ID: 6, hat ID: 7, value: left"), string_cast(event));
+       event.jhat.value = SDL_HAT_CENTERED;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL joystick hat motion event",
+               string("joystick hat motion: joystick ID: 6, hat ID: 7, value: center"), string_cast(event));
+       event.jhat.value = SDL_HAT_RIGHT;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL joystick hat motion event",
+               string("joystick hat motion: joystick ID: 6, hat ID: 7, value: right"), string_cast(event));
+       event.jhat.value = SDL_HAT_LEFTDOWN;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL joystick hat motion event",
+               string("joystick hat motion: joystick ID: 6, hat ID: 7, value: left down"), string_cast(event));
+       event.jhat.value = SDL_HAT_DOWN;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL joystick hat motion event",
+               string("joystick hat motion: joystick ID: 6, hat ID: 7, value: down"), string_cast(event));
+       event.jhat.value = SDL_HAT_RIGHTDOWN;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL joystick hat motion event",
+               string("joystick hat motion: joystick ID: 6, hat ID: 7, value: right down"), string_cast(event));
+       event.jhat.value = -1;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL joystick hat motion event",
+               string("joystick hat motion: joystick ID: 6, hat ID: 7, value: unknown"), string_cast(event));
+
+       event.type = SDL_JOYBUTTONDOWN;
+       event.jbutton.which = 8;
+       event.jbutton.button = 9;
+       event.jbutton.state = SDL_PRESSED;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL joystick button down event",
+               string("joystick button down: joystick ID: 8, button ID: 9, state: pressed"), string_cast(event));
+       event.type = SDL_JOYBUTTONUP;
+       event.jbutton.which = 10;
+       event.jbutton.button = 11;
+       event.jbutton.state = SDL_RELEASED;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL joystick button up event",
+               string("joystick button up: joystick ID: 10, button ID: 11, state: released"), string_cast(event));
+
+       event.type = SDL_JOYDEVICEADDED;
+       event.jdevice.which = 12;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL joystick device added event",
+               string("joystick device added: joystick ID: 12"), string_cast(event));
+       event.type = SDL_JOYDEVICEREMOVED;
+       event.jdevice.which = 13;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL joystick device removed event",
+               string("joystick device removed: joystick ID: 13"), string_cast(event));
+}
+
+void EventTest::testMouse() {
+       SDL_Event event;
+       event.type = SDL_MOUSEMOTION;
+       event.motion.windowID = 0;
+       event.motion.which = 1;
+       event.motion.x = 2;
+       event.motion.y = 3;
+       event.motion.xrel = 4;
+       event.motion.yrel = 5;
+       event.motion.state = 0;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL mouse motion event",
+               string("mouse motion: window ID: 0, mouse ID: 1, position: 2 3, delta: 4 5"), string_cast(event));
+       event.motion.windowID = 6;
+       event.motion.which = 7;
+       event.motion.x = 8;
+       event.motion.y = 9;
+       event.motion.xrel = 10;
+       event.motion.yrel = 11;
+       event.motion.state = SDL_BUTTON_LMASK | SDL_BUTTON_MMASK | SDL_BUTTON_RMASK;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL mouse motion event",
+               string("mouse motion: window ID: 6, mouse ID: 7, position: 8 9, delta: 10 11, buttons: left middle right"), string_cast(event));
+       event.motion.state = SDL_BUTTON_X1MASK | SDL_BUTTON_X2MASK;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL mouse motion event",
+               string("mouse motion: window ID: 6, mouse ID: 7, position: 8 9, delta: 10 11, buttons: X1 X2"), string_cast(event));
+
+       event.type = SDL_MOUSEBUTTONDOWN;
+       event.button.windowID = 0;
+       event.button.which = 1;
+       event.button.button = SDL_BUTTON_LEFT;
+       event.button.state = SDL_PRESSED;
+       event.button.clicks = 2;
+       event.button.x = 3;
+       event.button.y = 4;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL mouse button down event",
+               string("mouse button down: window ID: 0, mouse ID: 1, button: left, state: pressed, clicks: 2, position: 3 4"), string_cast(event));
+       event.type = SDL_MOUSEBUTTONUP;
+       event.button.windowID = 5;
+       event.button.which = 6;
+       event.button.button = SDL_BUTTON_MIDDLE;
+       event.button.clicks = 7;
+       event.button.x = 8;
+       event.button.y = 9;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL mouse button up event",
+               string("mouse button up: window ID: 5, mouse ID: 6, button: middle, state: pressed, clicks: 7, position: 8 9"), string_cast(event));
+       event.button.button = SDL_BUTTON_RIGHT;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL mouse button up event",
+               string("mouse button up: window ID: 5, mouse ID: 6, button: right, state: pressed, clicks: 7, position: 8 9"), string_cast(event));
+       event.button.button = SDL_BUTTON_X1;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL mouse button up event",
+               string("mouse button up: window ID: 5, mouse ID: 6, button: X1, state: pressed, clicks: 7, position: 8 9"), string_cast(event));
+       event.button.button = SDL_BUTTON_X2;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL mouse button up event",
+               string("mouse button up: window ID: 5, mouse ID: 6, button: X2, state: pressed, clicks: 7, position: 8 9"), string_cast(event));
+       event.button.button = SDL_BUTTON_X2 + 1;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL mouse button up event",
+               string("mouse button up: window ID: 5, mouse ID: 6, button: ") + to_string(int(SDL_BUTTON_X2 + 1)) + ", state: pressed, clicks: 7, position: 8 9", string_cast(event));
+
+       event.type = SDL_MOUSEWHEEL;
+       event.wheel.windowID = 0;
+       event.wheel.which = 1;
+       event.wheel.x = 2;
+       event.wheel.y = 3;
+#if SDL_VERSION_ATLEAST(2, 0, 4)
+       event.wheel.direction = SDL_MOUSEWHEEL_NORMAL;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL mouse wheel event",
+               string("mouse wheel: window ID: 0, mouse ID: 1, delta: 2 3, direction: normal"), string_cast(event));
+       event.wheel.windowID = 4;
+       event.wheel.which = 5;
+       event.wheel.x = 6;
+       event.wheel.y = 7;
+       event.wheel.direction = SDL_MOUSEWHEEL_FLIPPED;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL mouse wheel event",
+               string("mouse wheel: window ID: 4, mouse ID: 5, delta: 6 7, direction: flipped"), string_cast(event));
+#else
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL mouse wheel event",
+               string("mouse wheel: window ID: 0, mouse ID: 1, delta: 2 3"), string_cast(event));
+#endif
+}
+
+void EventTest::testMultiGesture() {
+       SDL_Event event;
+       event.type = SDL_MULTIGESTURE;
+       event.mgesture.touchId = 0;
+       event.mgesture.dTheta = 1;
+       event.mgesture.dDist = 2;
+       event.mgesture.x = 3;
+       event.mgesture.y = 4;
+       event.mgesture.numFingers = 5;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL multi gesture event",
+               string("multi gesture: device ID: 0, theta: 1, distance: 2, position: 3 4, fingers: 5"), string_cast(event));
+}
+
+void EventTest::testQuit() {
+       SDL_Event event;
+       event.type = SDL_QUIT;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL quit event",
+               string("quit: quit"), string_cast(event));
+}
+
+void EventTest::testSysWM() {
+       SDL_Event event;
+       event.type = SDL_SYSWMEVENT;
+       event.syswm.msg = nullptr;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL sys wm event",
+               string("sys wm: without message"), string_cast(event));
+       SDL_SysWMmsg msg;
+       event.syswm.msg = &msg;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL sys wm event",
+               string("sys wm: with message"), string_cast(event));
+}
+
+void EventTest::testText() {
+       SDL_Event event;
+       event.type = SDL_TEXTEDITING;
+       event.edit.windowID = 0;
+       event.edit.text[0] = '\303';
+       event.edit.text[1] = '\244';
+       event.edit.text[2] = '\0';
+       event.edit.start = 1;
+       event.edit.length = 2;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL text editing event",
+               string("text editing: window ID: 0, text: \"ä\", start: 1, length: 2"), string_cast(event));
+
+       event.type = SDL_TEXTINPUT;
+       event.text.windowID = 3;
+       event.text.text[0] = '\0';
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL text input event",
+               string("text input: window ID: 3, text: \"\""), string_cast(event));
+}
+
+void EventTest::testUser() {
+       SDL_Event event;
+       event.type = SDL_USEREVENT;
+       event.user.windowID = 0;
+       event.user.code = 1;
+       event.user.data1 = nullptr;
+       event.user.data2 = reinterpret_cast<void *>(1);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL user event",
+               string("user: window ID: 0, code: 1, data 1: 0, data 2: 0x1"), string_cast(event));
+}
+
+void EventTest::testWindow() {
+       SDL_Event event;
+       event.type = SDL_WINDOWEVENT;
+       event.window.event = SDL_WINDOWEVENT_SHOWN;
+       event.window.windowID = 0;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL window event",
+               string("window: shown, window ID: 0"), string_cast(event));
+
+       event.window.event = SDL_WINDOWEVENT_HIDDEN;
+       event.window.windowID = 1;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL window event",
+               string("window: hidden, window ID: 1"), string_cast(event));
+
+       event.window.event = SDL_WINDOWEVENT_EXPOSED;
+       event.window.windowID = 2;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL window event",
+               string("window: exposed, window ID: 2"), string_cast(event));
+
+       event.window.event = SDL_WINDOWEVENT_MOVED;
+       event.window.windowID = 3;
+       event.window.data1 = 4;
+       event.window.data2 = 5;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL window event",
+               string("window: moved, window ID: 3, position: 4 5"), string_cast(event));
+
+       event.window.event = SDL_WINDOWEVENT_RESIZED;
+       event.window.windowID = 6;
+       event.window.data1 = 7;
+       event.window.data2 = 8;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL window event",
+               string("window: resized, window ID: 6, size: 7x8"), string_cast(event));
+
+       event.window.event = SDL_WINDOWEVENT_SIZE_CHANGED;
+       event.window.windowID = 9;
+       event.window.data1 = 10;
+       event.window.data2 = 11;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL window event",
+               string("window: size changed, window ID: 9, size: 10x11"), string_cast(event));
+
+       event.window.event = SDL_WINDOWEVENT_MINIMIZED;
+       event.window.windowID = 12;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL window event",
+               string("window: minimized, window ID: 12"), string_cast(event));
+
+       event.window.event = SDL_WINDOWEVENT_MAXIMIZED;
+       event.window.windowID = 13;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL window event",
+               string("window: maximized, window ID: 13"), string_cast(event));
+
+       event.window.event = SDL_WINDOWEVENT_RESTORED;
+       event.window.windowID = 14;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL window event",
+               string("window: restored, window ID: 14"), string_cast(event));
+
+       event.window.event = SDL_WINDOWEVENT_ENTER;
+       event.window.windowID = 15;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL window event",
+               string("window: mouse entered, window ID: 15"), string_cast(event));
+
+       event.window.event = SDL_WINDOWEVENT_LEAVE;
+       event.window.windowID = 16;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL window event",
+               string("window: mouse left, window ID: 16"), string_cast(event));
+
+       event.window.event = SDL_WINDOWEVENT_FOCUS_GAINED;
+       event.window.windowID = 17;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL window event",
+               string("window: focus gained, window ID: 17"), string_cast(event));
+
+       event.window.event = SDL_WINDOWEVENT_FOCUS_LOST;
+       event.window.windowID = 18;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL window event",
+               string("window: focus lost, window ID: 18"), string_cast(event));
+
+       event.window.event = SDL_WINDOWEVENT_CLOSE;
+       event.window.windowID = 19;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL window event",
+               string("window: closed, window ID: 19"), string_cast(event));
+
+       event.window.event = SDL_WINDOWEVENT_NONE;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL window event",
+               string("window: unknown"), string_cast(event));
+}
+
+void EventTest::testUnknown() {
+       SDL_Event event;
+       // SDL_LASTEVENT holds the number of entries in the enum and therefore
+       // shouldn't be recognized as any valid event type
+       event.type = SDL_LASTEVENT;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "output format of SDL user event",
+               string("unknown"), string_cast(event));
+}
+
+}
+}
+}
diff --git a/tst/io/EventTest.hpp b/tst/io/EventTest.hpp
new file mode 100644 (file)
index 0000000..7849063
--- /dev/null
@@ -0,0 +1,68 @@
+#ifndef BLOBS_TEST_IO_EVENTTEST_HPP
+#define BLOBS_TEST_IO_EVENTTEST_HPP
+
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <SDL_version.h>
+
+namespace blobs {
+namespace io {
+namespace test {
+
+class EventTest
+: public CppUnit::TestFixture {
+
+CPPUNIT_TEST_SUITE(EventTest);
+
+#if SDL_VERSION_ATLEAST(2, 0, 4)
+CPPUNIT_TEST(testAudioDevice);
+#endif
+
+CPPUNIT_TEST(testController);
+CPPUNIT_TEST(testDollar);
+CPPUNIT_TEST(testDrop);
+CPPUNIT_TEST(testFinger);
+CPPUNIT_TEST(testKey);
+CPPUNIT_TEST(testJoystick);
+CPPUNIT_TEST(testMouse);
+CPPUNIT_TEST(testMultiGesture);
+CPPUNIT_TEST(testQuit);
+CPPUNIT_TEST(testSysWM);
+CPPUNIT_TEST(testText);
+CPPUNIT_TEST(testUser);
+CPPUNIT_TEST(testWindow);
+CPPUNIT_TEST(testUnknown);
+
+CPPUNIT_TEST_SUITE_END();
+
+public:
+       void setUp();
+       void tearDown();
+
+#if SDL_VERSION_ATLEAST(2, 0, 4)
+       void testAudioDevice();
+#endif
+
+       void testController();
+       void testDollar();
+       void testDrop();
+       void testFinger();
+       void testKey();
+       void testJoystick();
+       void testMouse();
+       void testMultiGesture();
+       void testQuit();
+       void testSysWM();
+       void testText();
+       void testUser();
+       void testWindow();
+       void testUnknown();
+
+};
+
+}
+}
+}
+
+
+#endif
diff --git a/tst/io/FilesystemTest.cpp b/tst/io/FilesystemTest.cpp
new file mode 100644 (file)
index 0000000..a098c2b
--- /dev/null
@@ -0,0 +1,165 @@
+#include "FilesystemTest.hpp"
+
+#include "io/filesystem.hpp"
+
+#include <algorithm>
+
+CPPUNIT_TEST_SUITE_REGISTRATION(blobs::io::test::FilesystemTest);
+
+using namespace std;
+
+
+namespace blobs {
+namespace io {
+namespace test {
+
+void FilesystemTest::setUp() {
+       test_dir = "test-dir";
+       CPPUNIT_ASSERT_MESSAGE(
+               "failed to create test dir",
+               make_dir(test_dir));
+}
+
+void FilesystemTest::tearDown() {
+       CPPUNIT_ASSERT_MESSAGE(
+               "failed to remove test dir",
+               remove_dir(test_dir));
+}
+
+
+void FilesystemTest::testFile() {
+#ifdef _WIN32
+       const string test_file = test_dir + "\\test-file.txt";
+#else
+       const string test_file = test_dir + "/test-file";
+#endif
+
+       CPPUNIT_ASSERT_MESSAGE(
+               "inexistant file is file",
+               !is_file(test_file));
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "mtime of inexistant file should be zero",
+               time_t(0), file_mtime(test_file));
+       CPPUNIT_ASSERT_MESSAGE(
+               "inexistant file is a directory",
+               !is_dir(test_file));
+
+       { // create file
+               ofstream file(test_file);
+               file << "hello" << endl;
+       }
+       time_t now = time(nullptr);
+       CPPUNIT_ASSERT_MESSAGE(
+               "existing file not a file",
+               is_file(test_file));
+       CPPUNIT_ASSERT_MESSAGE(
+               "mtime of existing file should be somewhere around now",
+               // let's assume that creating the file takes less than five seconds
+               abs(now - file_mtime(test_file) < 5));
+       CPPUNIT_ASSERT_MESSAGE(
+               "regular file is a directory",
+               !is_dir(test_file));
+
+       CPPUNIT_ASSERT_MESSAGE(
+               "failed to remove test file",
+               remove_file(test_file));
+
+       CPPUNIT_ASSERT_MESSAGE(
+               "removed file is still a file",
+               !is_file(test_file));
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "mtime of removed file should be zero",
+               time_t(0), file_mtime(test_file));
+       CPPUNIT_ASSERT_MESSAGE(
+               "removed file became a directory",
+               !is_dir(test_file));
+}
+
+void FilesystemTest::testDirectory() {
+#ifdef _WIN32
+       const string test_subdir = test_dir + "\\a";
+       const string test_subsubdir = test_subdir + "\\b";
+       const string test_file = test_subsubdir + "\\c.txt";
+#else
+       const string test_subdir = test_dir + "/a";
+       const string test_subsubdir = test_subdir + "/b";
+       const string test_file = test_subsubdir + "/c";
+#endif
+
+       CPPUNIT_ASSERT_MESSAGE(
+               "inexistant directory is a file",
+               !is_file(test_subdir));
+       CPPUNIT_ASSERT_MESSAGE(
+               "inexistant directory is a directory",
+               !is_dir(test_subdir));
+
+       CPPUNIT_ASSERT_MESSAGE(
+               "failed to create test subdir",
+               make_dir(test_subdir));
+       CPPUNIT_ASSERT_MESSAGE(
+               "created directory is a file",
+               !is_file(test_subdir));
+       CPPUNIT_ASSERT_MESSAGE(
+               "created directory is not a directory",
+               is_dir(test_subdir));
+
+       CPPUNIT_ASSERT_MESSAGE(
+               "failed to remove test subdir",
+               remove_dir(test_subdir));
+       CPPUNIT_ASSERT_MESSAGE(
+               "removed directory became a file",
+               !is_file(test_subdir));
+       CPPUNIT_ASSERT_MESSAGE(
+               "removed directory is still a directory",
+               !is_dir(test_subdir));
+
+       CPPUNIT_ASSERT_MESSAGE(
+               "failed to create test subdirs",
+               make_dirs(test_subsubdir));
+       CPPUNIT_ASSERT_MESSAGE(
+               "created directory is a file",
+               !is_file(test_subdir));
+       CPPUNIT_ASSERT_MESSAGE(
+               "created directory is not a directory",
+               is_dir(test_subdir));
+       CPPUNIT_ASSERT_MESSAGE(
+               "created directory is a file",
+               !is_file(test_subsubdir));
+       CPPUNIT_ASSERT_MESSAGE(
+               "created directory is not a directory",
+               is_dir(test_subsubdir));
+
+       { // create file
+               ofstream file(test_file);
+               file << "hello" << endl;
+       }
+       CPPUNIT_ASSERT_MESSAGE(
+               "failed to create test file",
+               is_file(test_file));
+
+       CPPUNIT_ASSERT_MESSAGE(
+               "failed to remove test subdir",
+               remove_dir(test_subdir));
+       CPPUNIT_ASSERT_MESSAGE(
+               "removed directory became a file",
+               !is_file(test_subdir));
+       CPPUNIT_ASSERT_MESSAGE(
+               "removed directory is still a directory",
+               !is_dir(test_subdir));
+       CPPUNIT_ASSERT_MESSAGE(
+               "removed directory became a file",
+               !is_file(test_subsubdir));
+       CPPUNIT_ASSERT_MESSAGE(
+               "removed directory is still a directory",
+               !is_dir(test_subsubdir));
+       CPPUNIT_ASSERT_MESSAGE(
+               "removed file became a directory",
+               !is_dir(test_file));
+       CPPUNIT_ASSERT_MESSAGE(
+               "removed file is still a file",
+               !is_file(test_file));
+}
+
+}
+}
+}
diff --git a/tst/io/FilesystemTest.hpp b/tst/io/FilesystemTest.hpp
new file mode 100644 (file)
index 0000000..65e203a
--- /dev/null
@@ -0,0 +1,37 @@
+#ifndef BLOBS_TEST_IO_FILESYSTEMTEST_HPP
+#define BLOBS_TEST_IO_FILESYSTEMTEST_HPP
+
+#include <string>
+#include <cppunit/extensions/HelperMacros.h>
+
+namespace blobs {
+namespace io {
+namespace test {
+
+class FilesystemTest
+: public CppUnit::TestFixture {
+
+CPPUNIT_TEST_SUITE(FilesystemTest);
+
+CPPUNIT_TEST(testFile);
+CPPUNIT_TEST(testDirectory);
+
+CPPUNIT_TEST_SUITE_END();
+
+public:
+       void setUp();
+       void tearDown();
+
+       void testFile();
+       void testDirectory();
+
+private:
+       std::string test_dir;
+
+};
+
+}
+}
+}
+
+#endif
diff --git a/tst/io/TokenTest.cpp b/tst/io/TokenTest.cpp
new file mode 100644 (file)
index 0000000..1a38b1e
--- /dev/null
@@ -0,0 +1,474 @@
+#include "TokenTest.hpp"
+
+#include "io/TokenStreamReader.hpp"
+
+#include <sstream>
+#include <stdexcept>
+#include <glm/gtx/io.hpp>
+
+CPPUNIT_TEST_SUITE_REGISTRATION(blobs::io::test::TokenTest);
+
+using namespace std;
+
+namespace blobs {
+namespace io {
+namespace test {
+
+void TokenTest::setUp() {
+
+}
+
+void TokenTest::tearDown() {
+
+}
+
+
+void TokenTest::testTypeIO() {
+       AssertStreamOutput(Token::UNKNOWN, "UNKNOWN");
+       AssertStreamOutput(Token::ANGLE_BRACKET_OPEN, "ANGLE_BRACKET_OPEN");
+       AssertStreamOutput(Token::ANGLE_BRACKET_CLOSE, "ANGLE_BRACKET_CLOSE");
+       AssertStreamOutput(Token::CHEVRON_OPEN, "CHEVRON_OPEN");
+       AssertStreamOutput(Token::CHEVRON_CLOSE, "CHEVRON_CLOSE");
+       AssertStreamOutput(Token::BRACKET_OPEN, "BRACKET_OPEN");
+       AssertStreamOutput(Token::BRACKET_CLOSE, "BRACKET_CLOSE");
+       AssertStreamOutput(Token::PARENTHESIS_OPEN, "PARENTHESIS_OPEN");
+       AssertStreamOutput(Token::PARENTHESIS_CLOSE, "PARENTHESIS_CLOSE");
+       AssertStreamOutput(Token::COLON, "COLON");
+       AssertStreamOutput(Token::SEMICOLON, "SEMICOLON");
+       AssertStreamOutput(Token::COMMA, "COMMA");
+       AssertStreamOutput(Token::EQUALS, "EQUALS");
+       AssertStreamOutput(Token::NUMBER, "NUMBER");
+       AssertStreamOutput(Token::STRING, "STRING");
+       AssertStreamOutput(Token::IDENTIFIER, "IDENTIFIER");
+       AssertStreamOutput(Token::COMMENT, "COMMENT");
+}
+
+void TokenTest::testTokenIO() {
+       Token t;
+       t.value = "why oh why";
+       AssertStreamOutput(t, "UNKNOWN(why oh why)");
+       t.type = Token::UNKNOWN;
+       t.value = "do I have no purpose";
+       AssertStreamOutput(t, "UNKNOWN(do I have no purpose)");
+       t.type = Token::ANGLE_BRACKET_OPEN;
+       AssertStreamOutput(t, "ANGLE_BRACKET_OPEN");
+       t.type = Token::ANGLE_BRACKET_CLOSE;
+       AssertStreamOutput(t, "ANGLE_BRACKET_CLOSE");
+       t.type = Token::CHEVRON_OPEN;
+       AssertStreamOutput(t, "CHEVRON_OPEN");
+       t.type = Token::CHEVRON_CLOSE;
+       AssertStreamOutput(t, "CHEVRON_CLOSE");
+       t.type = Token::BRACKET_OPEN;
+       AssertStreamOutput(t, "BRACKET_OPEN");
+       t.type = Token::BRACKET_CLOSE;
+       AssertStreamOutput(t, "BRACKET_CLOSE");
+       t.type = Token::PARENTHESIS_OPEN;
+       AssertStreamOutput(t, "PARENTHESIS_OPEN");
+       t.type = Token::PARENTHESIS_CLOSE;
+       AssertStreamOutput(t, "PARENTHESIS_CLOSE");
+       t.type = Token::COLON;
+       AssertStreamOutput(t, "COLON");
+       t.type = Token::SEMICOLON;
+       AssertStreamOutput(t, "SEMICOLON");
+       t.type = Token::COMMA;
+       AssertStreamOutput(t, "COMMA");
+       t.type = Token::EQUALS;
+       AssertStreamOutput(t, "EQUALS");
+       t.type = Token::NUMBER;
+       t.value = "15";
+       AssertStreamOutput(t, "NUMBER(15)");
+       t.type = Token::STRING;
+       t.value = "hello world";
+       AssertStreamOutput(t, "STRING(hello world)");
+       t.type = Token::IDENTIFIER;
+       t.value = "foo";
+       AssertStreamOutput(t, "IDENTIFIER(foo)");
+       t.type = Token::COMMENT;
+       t.value = "WITHOUT ANY WARRANTY";
+       AssertStreamOutput(t, "COMMENT(WITHOUT ANY WARRANTY)");
+}
+
+void TokenTest::testTokenizer() {
+       stringstream stream;
+       stream << "[{0},<.5>+3=/**\n * test\n */ (-1.5); foo_bar.baz:\"hello\\r\\n\\t\\\"world\\\"\" ] // this line\n#that line";
+       Tokenizer in(stream);
+
+       AssertHasMore(in);
+       Token token(in.Next());
+       AssertToken(token.type, token.value, in.Current());
+       AssertToken(Token::BRACKET_OPEN, token);
+
+       AssertHasMore(in);
+       AssertToken(Token::ANGLE_BRACKET_OPEN, in.Next());
+       AssertHasMore(in);
+       AssertToken(Token::NUMBER, "0", in.Next());
+       AssertHasMore(in);
+       AssertToken(Token::ANGLE_BRACKET_CLOSE, in.Next());
+       AssertHasMore(in);
+       AssertToken(Token::COMMA, in.Next());
+       AssertHasMore(in);
+       AssertToken(Token::CHEVRON_OPEN, in.Next());
+       AssertHasMore(in);
+       AssertToken(Token::NUMBER, ".5", in.Next());
+       AssertHasMore(in);
+       AssertToken(Token::CHEVRON_CLOSE, in.Next());
+       AssertHasMore(in);
+       AssertToken(Token::NUMBER, "+3", in.Next());
+       AssertHasMore(in);
+       AssertToken(Token::EQUALS, in.Next());
+       AssertHasMore(in);
+       AssertToken(Token::COMMENT, "*\n * test\n ", in.Next());
+       AssertHasMore(in);
+       AssertToken(Token::PARENTHESIS_OPEN, in.Next());
+       AssertHasMore(in);
+       AssertToken(Token::NUMBER, "-1.5", in.Next());
+       AssertHasMore(in);
+       AssertToken(Token::PARENTHESIS_CLOSE, in.Next());
+       AssertHasMore(in);
+       AssertToken(Token::SEMICOLON, in.Next());
+       AssertHasMore(in);
+       AssertToken(Token::IDENTIFIER, "foo_bar.baz", in.Next());
+       AssertHasMore(in);
+       AssertToken(Token::COLON, in.Next());
+       AssertHasMore(in);
+       AssertToken(Token::STRING, "hello\r\n\t\"world\"", in.Next());
+       AssertHasMore(in);
+       AssertToken(Token::BRACKET_CLOSE, in.Next());
+       AssertHasMore(in);
+       AssertToken(Token::COMMENT, " this line", in.Next());
+       AssertHasMore(in);
+       AssertToken(Token::COMMENT, "that line", in.Next());
+       CPPUNIT_ASSERT_MESSAGE("expected end of stream", !in.HasMore());
+       CPPUNIT_ASSERT_THROW_MESSAGE(
+               "extracting token after EOS",
+               in.Next(), std::runtime_error);
+}
+
+void TokenTest::testTokenizerBrokenComment() {
+       {
+               stringstream stream;
+               stream << "/* just one more thing…*";
+               Tokenizer in(stream);
+               AssertHasMore(in);
+               CPPUNIT_ASSERT_THROW_MESSAGE(
+                       "half-closed comment should throw",
+                       in.Next(), std::runtime_error);
+       }
+       {
+               stringstream stream;
+               stream << "  /";
+               Tokenizer in(stream);
+               AssertHasMore(in);
+               CPPUNIT_ASSERT_THROW_MESSAGE(
+                       "sole '/' at end of stream should throw",
+                       in.Next(), std::runtime_error);
+       }
+       {
+               stringstream stream;
+               stream << "/.";
+               Tokenizer in(stream);
+               AssertHasMore(in);
+               CPPUNIT_ASSERT_THROW_MESSAGE(
+                       "'/' followed by garbage should throw",
+                       in.Next(), std::runtime_error);
+       }
+}
+
+
+namespace {
+
+template<class T>
+void assert_read(std::string message, T expected, T actual, TokenStreamReader &in) {
+       stringstream msg;
+       msg << message << ", current token: " << in.Peek();
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               msg.str(),
+               expected, actual);
+}
+
+}
+
+void TokenTest::testReader() {
+       stringstream ss;
+       ss <<
+               "/* booleans */\n"
+               "true false yes no on off\n"
+               "\"true\" \"false\" \"yes\" \"no\" \"on\" \"off\"\n"
+               "1 0 -1\n"
+               "# identifiers\n"
+               "foo foo_bar vec.y\n"
+               "// numbers\n"
+               "0 1 +2 -3 4.5\n"
+               ".5 1.5 0.25 -1.75 0.625\n"
+               "0 1 -1 2.5\n"
+               // strings
+               "\"hello\" \"\" \"\\r\\n\\t\\\"\"\n"
+               // vectors
+               "[1,0] [ 0.707, 0.707 ] // vec2\n"
+               "[.577,.577 ,0.577] [ 1,-2,3] // vec3\n"
+               "[ 0, 0, 0, 1 ] [1,0,0,-1.0] // vec4\n"
+               "[640, 480] [3, 4, 5] [0, -10, 100, -1000] # ivecs\n"
+               "[ -0.945, 0, -0.326, 0] # quat\n"
+               ;
+       TokenStreamReader in(ss);
+
+       // booleans
+
+       bool value_bool;
+       in.ReadBoolean(value_bool);
+       assert_read("reading boolean true", true, value_bool, in);
+       in.ReadBoolean(value_bool);
+       assert_read("reading boolean false", false, value_bool, in);
+       in.ReadBoolean(value_bool);
+       assert_read("reading boolean yes", true, value_bool, in);
+       in.ReadBoolean(value_bool);
+       assert_read("reading boolean no", false, value_bool, in);
+       in.ReadBoolean(value_bool);
+       assert_read("reading boolean on", true, value_bool, in);
+       in.ReadBoolean(value_bool);
+       assert_read("reading boolean off", false, value_bool, in);
+
+       in.ReadBoolean(value_bool);
+       assert_read("reading boolean \"true\"", true, value_bool, in);
+       in.ReadBoolean(value_bool);
+       assert_read("reading boolean \"false\"", false, value_bool, in);
+       in.ReadBoolean(value_bool);
+       assert_read("reading boolean \"yes\"", true, value_bool, in);
+       in.ReadBoolean(value_bool);
+       assert_read("reading boolean \"no\"", false, value_bool, in);
+       in.ReadBoolean(value_bool);
+       assert_read("reading boolean \"on\"", true, value_bool, in);
+       in.ReadBoolean(value_bool);
+       assert_read("reading boolean \"off\"", false, value_bool, in);
+
+       in.ReadBoolean(value_bool);
+       assert_read("reading boolean 1", true, value_bool, in);
+       in.ReadBoolean(value_bool);
+       assert_read("reading boolean 0", false, value_bool, in);
+       in.ReadBoolean(value_bool);
+       assert_read("reading boolean -1", true, value_bool, in);
+
+       // identifiers
+
+       string value_ident;
+       in.ReadIdentifier(value_ident);
+       assert_read<string>("reading identifier foo", "foo", value_ident, in);
+       in.ReadIdentifier(value_ident);
+       assert_read<string>("reading identifier foo_bar", "foo_bar", value_ident, in);
+       in.ReadIdentifier(value_ident);
+       assert_read<string>("reading identifier vec.y", "vec.y", value_ident, in);
+
+       // numbers
+       int value_int;
+       in.ReadNumber(value_int);
+       assert_read("reading integer 0", 0, value_int, in);
+       in.ReadNumber(value_int);
+       assert_read("reading integer 1", 1, value_int, in);
+       in.ReadNumber(value_int);
+       assert_read("reading integer +2", 2, value_int, in);
+       in.ReadNumber(value_int);
+       assert_read("reading integer -3", -3, value_int, in);
+       in.ReadNumber(value_int);
+       assert_read("reading integer 4.5", 4, value_int, in);
+
+       float value_float;
+       in.ReadNumber(value_float);
+       assert_read("reading float .5", .5f, value_float, in);
+       in.ReadNumber(value_float);
+       assert_read("reading float 1.5", 1.5f, value_float, in);
+       in.ReadNumber(value_float);
+       assert_read("reading float 0.25", .25f, value_float, in);
+       in.ReadNumber(value_float);
+       assert_read("reading float -1.75", -1.75f, value_float, in);
+       in.ReadNumber(value_float);
+       assert_read("reading float 0.625", 0.625f, value_float, in);
+
+       unsigned long value_uint;
+       in.ReadNumber(value_uint);
+       assert_read("reading unsigned integer 0", 0ul, value_uint, in);
+       in.ReadNumber(value_uint);
+       assert_read("reading unsigned integer 1", 1ul, value_uint, in);
+       in.ReadNumber(value_uint);
+       assert_read("reading unsigned integer -1", -1ul, value_uint, in);
+       in.ReadNumber(value_uint);
+       assert_read("reading unsigned integer 2.5", 2ul, value_uint, in);
+
+       // strings
+
+       string value_string;
+       in.ReadString(value_string);
+       assert_read<string>(
+               "reading string \"hello\"",
+               "hello", value_string, in);
+       in.ReadString(value_string);
+       assert_read<string>(
+               "reading string \"\"",
+               "", value_string, in);
+       in.ReadString(value_string);
+       assert_read<string>(
+               "reading string \"\\r\\n\\t\\\"\"",
+               "\r\n\t\"", value_string, in);
+
+       // vectors
+
+       glm::vec2 value_vec2;
+       in.ReadVec(value_vec2);
+       assert_read(
+               "reading vector [1,0]",
+               glm::vec2(1, 0), value_vec2, in);
+       in.ReadVec(value_vec2);
+       assert_read(
+               "reading vector [ 0.707, 0.707 ]",
+               glm::vec2(.707, .707), value_vec2, in);
+
+       glm::vec3 value_vec3;
+       in.ReadVec(value_vec3);
+       assert_read(
+               "reading vector [.577,.577 ,0.577]",
+               glm::vec3(.577, .577, .577), value_vec3, in);
+       in.ReadVec(value_vec3);
+       assert_read(
+               "reading vector [ 1,-2,3]",
+               glm::vec3(1, -2, 3), value_vec3, in);
+
+       glm::vec4 value_vec4;
+       in.ReadVec(value_vec4);
+       assert_read(
+               "reading vector [ 0, 0, 0, 1 ]",
+               glm::vec4(0, 0, 0, 1), value_vec4, in);
+       in.ReadVec(value_vec4);
+       assert_read(
+               "reading vector [1,0,0,-1.0]",
+               glm::vec4(1, 0, 0, -1), value_vec4, in);
+
+       glm::ivec2 value_ivec2;
+       in.ReadVec(value_ivec2);
+       assert_read(
+               "reading integer vector [640, 480]",
+               glm::ivec2(640, 480), value_ivec2, in);
+       glm::ivec3 value_ivec3;
+       in.ReadVec(value_ivec3);
+       assert_read(
+               "reading integer vector [3, 4, 5]",
+               glm::ivec3(3, 4, 5), value_ivec3, in);
+       glm::ivec4 value_ivec4;
+       in.ReadVec(value_ivec4);
+       assert_read(
+               "reading integer vector [0, -10, 100, -1000]",
+               glm::ivec4(0, -10, 100, -1000), value_ivec4, in);
+
+       glm::quat value_quat;
+       in.ReadQuat(value_quat);
+       assert_read(
+               "reading quaternion [ -0.945, 0, -0.326, 0]",
+               glm::quat(-0.945, 0, -0.326, 0), value_quat, in);
+       // TODO: comment at end of stream makes it think there's more?
+       //CPPUNIT_ASSERT_MESSAGE("expected end of stream", !in.HasMore());
+       // TODO: and it even works??
+       //CPPUNIT_ASSERT_THROW_MESSAGE(
+       //      "extracting token after EOS",
+       //      in.Next(), std::runtime_error);
+}
+
+void TokenTest::testReaderEmpty() {
+       { // zero length stream
+               stringstream ss;
+               ss << "";
+               TokenStreamReader in(ss);
+               CPPUNIT_ASSERT_MESSAGE(
+                       "empty stream shouldn't have tokens",
+                       !in.HasMore());
+       }
+       { // stream consisting solely of comments
+               stringstream ss;
+               ss <<
+                       "/*\n"
+                       " * hello\n"
+                       " */\n"
+                       "#hello\n"
+                       "// is there anybody out there\n"
+                       ;
+               TokenStreamReader in(ss);
+               CPPUNIT_ASSERT_MESSAGE(
+                       "comment stream shouldn't have tokens",
+                       !in.HasMore());
+       }
+}
+
+void TokenTest::testReaderMalformed() {
+       {
+               stringstream ss;
+               ss << "a";
+               TokenStreamReader in(ss);
+               CPPUNIT_ASSERT_THROW_MESSAGE(
+                       "unexpected token type should throw",
+                       in.GetInt(), std::runtime_error);
+       }
+       {
+               stringstream ss;
+               ss << ":";
+               TokenStreamReader in(ss);
+               CPPUNIT_ASSERT_THROW_MESSAGE(
+                       "casting ':' to bool should throw",
+                       in.GetBool(), std::runtime_error);
+       }
+       {
+               stringstream ss;
+               ss << "hello";
+               TokenStreamReader in(ss);
+               CPPUNIT_ASSERT_THROW_MESSAGE(
+                       "casting \"hello\" to bool should throw",
+                       in.GetBool(), std::runtime_error);
+       }
+}
+
+
+void TokenTest::AssertStreamOutput(
+       Token::Type t,
+       string expected
+) {
+       stringstream conv;
+       conv << t;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "unexpected std::ostream << Token::Type result",
+               expected, conv.str());
+}
+
+void TokenTest::AssertStreamOutput(
+       const Token &t,
+       string expected
+) {
+       stringstream conv;
+       conv << t;
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "unexpected std::ostream << Token result",
+               expected, conv.str());
+}
+
+void TokenTest::AssertHasMore(Tokenizer &in) {
+       CPPUNIT_ASSERT_MESSAGE("unexpected end of stream", in.HasMore());
+}
+
+void TokenTest::AssertToken(
+       Token::Type expected_type,
+       const Token &actual_token
+) {
+       AssertToken(expected_type, "", actual_token);
+}
+
+void TokenTest::AssertToken(
+       Token::Type expected_type,
+       string expected_value,
+       const Token &actual_token
+) {
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "unexpected token type",
+               expected_type, actual_token.type);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "unexpected token value",
+               expected_value, actual_token.value);
+}
+
+}
+}
+}
diff --git a/tst/io/TokenTest.hpp b/tst/io/TokenTest.hpp
new file mode 100644 (file)
index 0000000..3f50050
--- /dev/null
@@ -0,0 +1,61 @@
+#ifndef BLOBS_TEST_IO_TOKENTEST_HPP
+#define BLOBS_TEST_IO_TOKENTEST_HPP
+
+#include "io/Token.hpp"
+#include "io/Tokenizer.hpp"
+
+#include <string>
+#include <cppunit/extensions/HelperMacros.h>
+
+
+namespace blobs {
+namespace io {
+namespace test {
+
+class TokenTest
+: public CppUnit::TestFixture {
+
+CPPUNIT_TEST_SUITE(TokenTest);
+
+CPPUNIT_TEST(testTypeIO);
+CPPUNIT_TEST(testTokenIO);
+CPPUNIT_TEST(testTokenizer);
+CPPUNIT_TEST(testTokenizerBrokenComment);
+CPPUNIT_TEST(testReader);
+CPPUNIT_TEST(testReaderEmpty);
+CPPUNIT_TEST(testReaderMalformed);
+
+CPPUNIT_TEST_SUITE_END();
+
+public:
+       void setUp();
+       void tearDown();
+
+       void testTypeIO();
+       void testTokenIO();
+       void testTokenizer();
+       void testTokenizerBrokenComment();
+
+       void testReader();
+       void testReaderEmpty();
+       void testReaderMalformed();
+
+       static void AssertStreamOutput(
+               Token::Type, std::string expected);
+       static void AssertStreamOutput(
+               const Token &, std::string expected);
+
+       static void AssertHasMore(Tokenizer &);
+       static void AssertToken(
+               Token::Type expected_type, const Token &actual_token);
+       static void AssertToken(
+               Token::Type expected_type, std::string expected_value,
+               const Token &actual_token);
+
+};
+
+}
+}
+}
+
+#endif
index 175da83db81e751fa30d5c7c1f3eb41b471b0792..ce1690902008155211af539cc8f5ee45a004e777 100644 (file)
@@ -5,14 +5,14 @@
 #include "const.hpp"
 #include "world/Orbit.hpp"
 
-CPPUNIT_TEST_SUITE_REGISTRATION(blobs::test::world::OrbitTest);
+CPPUNIT_TEST_SUITE_REGISTRATION(blobs::world::test::OrbitTest);
 
-using blobs::world::Orbit;
+using blobs::test::AssertEqual;
 
 
 namespace blobs {
-namespace test {
 namespace world {
+namespace test {
 
 void OrbitTest::setUp() {
 }
index f9c5238a19dfba6af81fb7230632ede0daa39026..58790500209499c060a001657352f8feacd1eb53 100644 (file)
@@ -5,8 +5,8 @@
 
 
 namespace blobs {
-namespace test {
 namespace world {
+namespace test {
 
 class OrbitTest
 : public CppUnit::TestFixture {