diff --git a/tests/Makefile b/tests/Makefile
new file mode 100644
index 0000000..cd4587f
--- /dev/null
+++ b/tests/Makefile
@@ -0,0 +1,81 @@
+include Makefile.sources
+
+TARGET_NAME = geargrafx-test
+UNAME_S := $(shell uname -s)
+PLATFORM = "undefined"
+GEARGRAFX_TESTING ?= 1
+
+OBJECTS += $(SOURCES_C:.c=.o) $(SOURCES_CXX:.cpp=.o)
+
+USE_CLANG ?= 0
+ifeq ($(USE_CLANG), 1)
+ CXX = clang++
+ CC = clang
+else
+ CXX = g++
+ CC = gcc
+endif
+
+CPPFLAGS += -I../ -I../../
+CPPFLAGS += -Wall -Wextra -Wformat
+CXXFLAGS += -std=c++11
+CFLAGS += -std=c99
+
+DEBUG ?= 0
+ifeq ($(DEBUG), 1)
+ CPPFLAGS +=-DDEBUG -g3
+else
+ CPPFLAGS +=-DNDEBUG -O3 -flto=auto
+ LDFLAGS += -O3 -flto=auto
+endif
+
+SANITIZE ?= 0
+ifeq ($(SANITIZE), 1)
+ CPPFLAGS +=-fsanitize=address
+ LDFLAGS += -lasan
+endif
+
+ifeq ($(UNAME_S), Linux) #LINUX
+ PLATFORM = "Linux"
+ LDFLAGS += -lGL -lGLEW -ldl `sdl2-config --libs`
+ CPPFLAGS += `sdl2-config --cflags`
+ TARGET := $(TARGET_NAME)
+else ifeq ($(UNAME_S), Darwin) #APPLE
+ PLATFORM = "macOS"
+ LDFLAGS += -framework OpenGL -framework Cocoa -framework IOKit -framework CoreVideo `sdl2-config --libs`
+ LDFLAGS += -L/usr/local/lib
+ CPPFLAGS += `sdl2-config --cflags`
+ CPPFLAGS += -I/usr/local/include -I/opt/local/include
+ TARGET := $(TARGET_NAME)
+else ifeq ($(findstring MINGW,$(UNAME_S)),MINGW)
+ PLATFORM = "MinGW"
+ LDFLAGS += -lgdi32 -lopengl32 -lglew32 -limm32 `pkg-config --static --libs sdl2`
+ CPPFLAGS += `pkg-config --cflags sdl2`
+ TARGET := $(TARGET_NAME).exe
+else
+ PLATFORM = "Generic Unix-like/BSD"
+ LDFLAGS += `sdl2-config --libs` -lSDL2
+ LDFLAGS += `pkg-config --libs glew` -lGLEW
+ CXXFLAGS += -std=gnu++11
+ CPPFLAGS += `sdl2-config --cflags`
+ CPPFLAGS += `pkg-config --cflags glew`
+ TARGET := $(TARGET_NAME)
+endif
+
+all: $(TARGET)
+ @echo Build complete for $(PLATFORM)
+
+$(TARGET): $(OBJECTS)
+ $(CXX) -o $@ $(OBJECTS) $(LDFLAGS)
+
+%.o: %.mm
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $<
+
+%.o: %.cpp
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $<
+
+%.o: %.c
+ $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
+
+clean:
+ rm -f $(OBJECTS) $(TARGET)
diff --git a/tests/Makefile.sources b/tests/Makefile.sources
new file mode 100644
index 0000000..457ac1e
--- /dev/null
+++ b/tests/Makefile.sources
@@ -0,0 +1,18 @@
+SRC_DIR = ../src
+TEST_SRC_DIR=.
+
+SOURCES_C :=
+
+SOURCES_CXX := \
+ $(TEST_SRC_DIR)/main.cpp \
+ $(SRC_DIR)/geargrafx_core.cpp \
+ $(SRC_DIR)/audio.cpp \
+ $(SRC_DIR)/cartridge.cpp \
+ $(SRC_DIR)/huc6260.cpp \
+ $(SRC_DIR)/huc6270.cpp \
+ $(SRC_DIR)/huc6280.cpp \
+ $(SRC_DIR)/huc6280_functors.cpp \
+ $(SRC_DIR)/huc6280_opcodes.cpp \
+ $(SRC_DIR)/huc6280_psg.cpp \
+ $(SRC_DIR)/input.cpp \
+ $(SRC_DIR)/memory.cpp \
diff --git a/tests/RSJparser.tcc b/tests/RSJparser.tcc
new file mode 100644
index 0000000..6c32ff9
--- /dev/null
+++ b/tests/RSJparser.tcc
@@ -0,0 +1,786 @@
+/** **************************************************************************************
+* *
+* A Ridiculously Simple JSON Parser for C++ (RSJp-cpp) *
+* Version 2.x *
+* ---------------------------------------------------------- *
+* Copyright (C) 2018 Subhrajit Bhattacharya *
+* *
+* This program is free software: you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation, either version 3 of the License, or *
+* (at your option) any later version. *
+* *
+* This program is distributed in the hope that it will be useful, *
+* but WITHOUT ANY WARRANTY; without even the implied warranty of *
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+* GNU General Public License for more details . *
+* *
+* *
+* Contact: subhrajit@gmail.com *
+* https://www.lehigh.edu/~sub216/ , http://subhrajit.net/ *
+* *
+* *
+*************************************************************************************** **/
+
+#ifndef __DOSL_RSJPARSE_TCC
+#define __DOSL_RSJPARSE_TCC
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+static char const* RSJobjectbrackets = "{}";
+static char const* RSJarraybrackets = "[]";
+static char RSJobjectassignment = ':';
+static char RSJarraydelimiter = ',';
+
+static std::vector RSJbrackets = {RSJobjectbrackets, RSJarraybrackets};
+static std::vector RSJstringquotes = {"\"\"", "''"};
+static char RSJcharescape = '\\';
+static std::string RSJlinecommentstart = "//";
+
+static std::string RSJprinttab = " ";
+
+enum RSJresourceType { RSJ_UNINITIATED, RSJ_UNKNOWN, RSJ_OBJECT, RSJ_ARRAY, RSJ_LEAF };
+
+// ============================================================
+// Direct string manipulation functions
+
+inline
+std::string to_string (RSJresourceType rt) {
+ switch (rt) {
+ case RSJ_UNINITIATED: return("RSJ_UNINITIATED");
+ case RSJ_UNKNOWN: return("RSJ_UNKNOWN");
+ case RSJ_OBJECT: return("RSJ_OBJECT");
+ case RSJ_ARRAY: return("RSJ_ARRAY");
+ case RSJ_LEAF: return("RSJ_LEAF");
+ }
+}
+
+enum StrTrimDir { STRTRIM_L=1, STRTRIM_R=2, STRTRIM_LR=3 };
+
+inline
+std::string strtrim (std::string str, std::string chars=" \t\n\r", int max_count=-1, StrTrimDir dirs=STRTRIM_LR) {
+ if (str.empty()) return(str);
+ if (max_count<0) max_count = str.length();
+
+ if (dirs & STRTRIM_L) { // left trim
+ int p;
+ for (p=0; p& bracks, int indx=0) {
+ for (int b=0; b split_RSJ_array (const std::string& str) { // TODO: Make efficient. This function is speed bottleneck.
+ // splits, while respecting brackets and escapes
+ std::vector ret;
+
+ std::string current;
+ std::vector bracket_stack;
+ std::vector quote_stack;
+ bool escape_active = false;
+ int bi;
+
+ for (int a=0; a 0) { // already inside string
+ if (str[a]==RSJcharescape) // an escape character
+ escape_active = !escape_active;
+ else if (!escape_active && str[a]==RSJstringquotes[quote_stack.back()][1] ) { // close quote
+ quote_stack.pop_back();
+ escape_active = false;
+ }
+ else
+ escape_active = false;
+
+ current.push_back (str[a]);
+ continue; // to *
+ }
+
+ if (quote_stack.size()==0) { // check for start of string
+ if ((bi = is_bracket (str[a], RSJstringquotes)) >= 0) {
+ quote_stack.push_back (bi);
+ current.push_back (str[a]);
+ continue; // to *
+ }
+ }
+
+ // ------------------------------------
+ // checks for comments
+
+ if (quote_stack.size()==0) { // comment cannot start inside string
+
+ // single-line commenst
+ if (str.compare (a, RSJlinecommentstart.length(), RSJlinecommentstart) == 0) {
+ // ignore until end of line
+ int newline_pos = str.find ("\n", a);
+ if (newline_pos == std::string::npos)
+ newline_pos = str.find ("\r", a);
+
+ if (newline_pos != std::string::npos)
+ a = newline_pos; // point to the newline character (a will be incremented)
+ else // the comment continues until EOF
+ a = str.length();
+ continue;
+ }
+ }
+
+ // ------------------------------------
+ // checks for brackets
+
+ if ( bracket_stack.size()>0 && str[a]==RSJbrackets[bracket_stack.back()][1] ) { // check for closing bracket
+ bracket_stack.pop_back();
+ current.push_back (str[a]);
+ continue;
+ }
+
+ if ((bi = is_bracket (str[a], RSJbrackets)) >= 0) {
+ bracket_stack.push_back (bi);
+ current.push_back (str[a]);
+ continue; // to *
+ }
+
+ // ------------------------------------
+ // otherwise
+ current.push_back (str[a]);
+ }
+
+ if (current.length() > 0)
+ ret.push_back (current);
+
+ return (ret);
+}
+
+inline
+std::string insert_tab_after_newlines (std::string str) {
+ for (int a=0; a RSJobject;
+typedef std::vector RSJarray;
+
+// ------------------------------------
+// Main classes
+
+class RSJresource {
+/* Use: RSJresource("RSJ_string_data").as()["keyName"].as()[2].as()
+ RSJresource("RSJ_string_data")["keyName"][2].as() */
+private:
+ // main data
+ std::string data; // can be object, vector or leaf data
+ bool _exists; // whether the RSJ resource exists.
+
+ // parsed data
+ RSJparsedData* parsed_data_p;
+
+public:
+ // constructor
+ RSJresource () : _exists (false), parsed_data_p (NULL) { } // no data field.
+
+ RSJresource (std::string str) : data (str), _exists (true), parsed_data_p (NULL) { }
+ RSJresource (const char* str) : RSJresource(std::string(str)) { }
+
+ // other convertion
+ template
+ RSJresource (dataType d) : RSJresource(std::to_string(d)) { }
+
+ // read from file and stream
+ RSJresource (std::istream& is) : _exists (true), parsed_data_p (NULL) {
+ data = std::string ( (std::istreambuf_iterator(is)), (std::istreambuf_iterator()) );
+ }
+ RSJresource (std::ifstream& ifs) : _exists (true), parsed_data_p (NULL) {
+ std::istream& is = ifs;
+ data = std::string ( (std::istreambuf_iterator(is)), (std::istreambuf_iterator()) );
+ }
+
+ // free allocated memory for parsed data
+ ~RSJresource();
+
+ // deep copy
+ RSJresource (const RSJresource& r);
+ RSJresource& operator= (const RSJresource& r);
+
+ // ------------------------------------
+ // parsers (old)
+ RSJresourceType parse (bool force=false);
+ void parse_full (bool force=false, int max_depth=INT_MAX, int* parse_count_for_verbose_p=NULL); // recursively parse the entire JSON text
+ // parser (new)
+ void fast_parse (std::string* str_p=NULL, bool copy_string=false, int max_depth=INT_MAX, int* parse_start_str_pos=NULL); // TODO: finish.
+
+ RSJobject& as_object (bool force=false);
+ RSJarray& as_array (bool force=false);
+
+ // ------------------------------------
+
+ // access raw data and other attributes
+ int size(void);
+ std::string& raw_data (void) { return (data); }
+ bool exists (void) { return (_exists); }
+ bool is_parsed (void) { return (parsed_data_p!=NULL); }
+ RSJresourceType type (void);
+ // emitter
+ std::string as_str (bool print_comments=false, bool update_data=true);
+ void print (bool print_comments=false, bool update_data=true)
+ { std::cout << as_str(print_comments,update_data) << std::endl; }
+
+ // opertor[]
+ RSJresource& operator[] (std::string key); // object
+ RSJresource& operator[] (int indx); // array
+
+ // ------------------------------------
+
+ // as
+ template
+ dataType as (const dataType& def = dataType()) { // specialized outside class declaration
+ if (!exists()) return (def);
+ return dataType (data); // default behavior for unknown types: invoke 'dataType(std::string)'
+ }
+
+ // as_vector
+ template > // vectorType should have push_back method
+ vectorType as_vector (const vectorType& def = vectorType());
+
+ // as_map
+ template > // mapType should have operator[] defined
+ mapType as_map (const mapType& def = mapType());
+};
+
+// ------------------------------------------------------------
+
+class RSJparsedData {
+public:
+ RSJobject object;
+ RSJarray array;
+
+ RSJresourceType type;
+ RSJparsedData() : type(RSJ_UNKNOWN) {}
+
+ // parser (single-level)
+ void parse (const std::string& data, RSJresourceType typ = RSJ_UNKNOWN) {
+ std::string content = strtrim(data);
+
+ if (typ==RSJ_OBJECT || typ==RSJ_UNKNOWN) {
+ // parse as object:
+ content = strtrim (strtrim (content, "{", 1, STRTRIM_L ), "}", 1, STRTRIM_R );
+ if (content.length() != data.length()) { // a valid object
+ std::vector nvPairs = split_RSJ_array (content);
+ for (int a=0; a 0) {
+ type = RSJ_OBJECT;
+ return;
+ }
+ }
+ }
+
+ if (typ==RSJ_ARRAY || typ==RSJ_UNKNOWN) {
+ // parse as array
+ content = strtrim (strtrim (content, "[", 1, STRTRIM_L ), "]", 1, STRTRIM_R );
+ if (content.length() != data.length()) { // a valid array
+ std::vector nvPairs = split_RSJ_array (content);
+ for (int a=0; a 0) {
+ type = RSJ_ARRAY;
+ return;
+ }
+ }
+ }
+
+ if (typ==RSJ_UNKNOWN)
+ type = RSJ_LEAF;
+ }
+
+
+ // remove non-existing items inserted due to accessing
+ int cleanup(void) {
+
+ if (type==RSJ_OBJECT) {
+ bool found = true;
+ while (found) {
+ found = false;
+ for (auto it=object.begin(); it!=object.end(); ++it)
+ if (!(it->second.exists())) {
+ object.erase(it);
+ found = true;
+ break; // break for loop since it is now invalid
+ }
+ }
+ return (object.size());
+ }
+
+ if (type==RSJ_ARRAY) { // erases only the non-existent elements at the tail
+ while (!(array[array.size()-1].exists()))
+ array.pop_back();
+ return (array.size());
+ }
+
+ if (type==RSJ_LEAF)
+ return (1);
+
+ return (0);
+ }
+
+ // size
+ int size(void) { return (cleanup()); }
+};
+
+
+// ------------------------------------------------------------
+// RSJresource member functions
+
+inline
+RSJresource::~RSJresource (){
+ if (parsed_data_p) delete parsed_data_p;
+}
+
+inline
+RSJresource::RSJresource (const RSJresource& r) {
+ data=r.data;
+ _exists = r._exists;
+ if(r.parsed_data_p) parsed_data_p = new RSJparsedData(*(r.parsed_data_p));
+ else parsed_data_p = NULL;
+}
+
+inline
+RSJresource& RSJresource::operator= (const RSJresource& r) {
+ data=r.data;
+ _exists = r._exists;
+ if(r.parsed_data_p) parsed_data_p = new RSJparsedData(*(r.parsed_data_p));
+ else parsed_data_p = NULL;
+ return *this;
+}
+
+inline
+int RSJresource::size (void) {
+ if (!exists()) return (0);
+ parse(); // parse if not parsed
+ return (parsed_data_p->size());
+}
+
+inline
+RSJresourceType RSJresource::type (void) {
+ if (!exists()) return (RSJ_UNINITIATED);
+ parse(); // parse if not parsed
+ return (parsed_data_p->type);
+}
+
+inline
+std::string RSJresource::as_str (bool print_comments, bool update_data) {
+ if (exists()) {
+ std::string ret;
+ parse(); // parse if not parsed
+ parsed_data_p->cleanup();
+
+ if (parsed_data_p->type==RSJ_OBJECT) {
+ ret = "{\n";
+ for (auto it=parsed_data_p->object.begin(); it!=parsed_data_p->object.end(); ++it) {
+ ret += RSJprinttab + "'" + it->first + "': " + insert_tab_after_newlines( it->second.as_str (print_comments, update_data) );
+ if (std::next(it) != parsed_data_p->object.end()) ret += ",";
+ if (print_comments)
+ ret += " // " + to_string(it->second.type());
+ ret += "\n";
+ }
+ ret += "}";
+ }
+ else if (parsed_data_p->type==RSJ_ARRAY) {
+ ret = "[\n";
+ for (auto it=parsed_data_p->array.begin(); it!=parsed_data_p->array.end(); ++it) {
+ ret += RSJprinttab + insert_tab_after_newlines( it->as_str (print_comments, update_data) );
+ if (std::next(it) != parsed_data_p->array.end()) ret += ",";
+ if (print_comments)
+ ret += " // " + to_string(it->type());
+ ret += "\n";
+ }
+ ret += "]";
+ }
+ else // RSJ_LEAF or RSJ_UNKNOWN
+ ret = strtrim (data);
+
+ if (update_data) data = ret;
+ return (ret);
+ }
+ else
+ return ("");
+}
+
+// Parsers
+
+inline
+RSJresourceType RSJresource::parse (bool force) {
+ if (!parsed_data_p) parsed_data_p = new RSJparsedData;
+ if (parsed_data_p->type==RSJ_UNKNOWN || force) parsed_data_p->parse (data, RSJ_UNKNOWN);
+ return (parsed_data_p->type);
+}
+
+inline
+void RSJresource::parse_full (bool force, int max_depth, int* parse_count_for_verbose_p) { // recursive parsing (slow)
+ if (max_depth==0) return;
+ if (!parsed_data_p) parsed_data_p = new RSJparsedData;
+ if (parsed_data_p->type==RSJ_UNKNOWN || force) parsed_data_p->parse (data, RSJ_UNKNOWN);
+ // verbose
+ if (parse_count_for_verbose_p) {
+ (*parse_count_for_verbose_p)++;
+ if ( (*parse_count_for_verbose_p) % 100 == 0)
+ std::cout << "parse_full: " << (*parse_count_for_verbose_p) << " calls." << std::endl;
+ }
+ // recursive parse children if not already parsed
+ if (parsed_data_p->type==RSJ_OBJECT)
+ for (auto it=parsed_data_p->object.begin(); it!=parsed_data_p->object.end(); ++it)
+ it->second.parse_full (force, max_depth-1, parse_count_for_verbose_p);
+ else if (parsed_data_p->type==RSJ_ARRAY)
+ for (auto it=parsed_data_p->array.begin(); it!=parsed_data_p->array.end(); ++it)
+ it->parse_full (force, max_depth-1, parse_count_for_verbose_p);
+}
+
+// ------------------------------------------------------------
+// ============================================================
+// FAST PARSER (Under construction. DO NOT use the following functions in your application.)
+
+inline
+int seek_next (std::string* str_p, int start_pos, char character) {
+
+}
+
+inline
+void RSJresource::fast_parse (std::string* str_p, bool copy_string, int max_depth, int* parse_start_str_pos) {
+ // TODO: UNDER CONSTRUCTION...
+
+ if (!str_p)
+ str_p = &data;
+ std::string& str = *str_p;
+
+ // splits, while respecting brackets and escapes
+ //std::vector ret;
+
+ //std::string current;
+ std::vector bracket_stack;
+ std::vector quote_stack;
+ bool escape_active = false;
+ int bi;
+
+ bool initial_whitespaces = true;
+ bool isroot = false;
+
+ if (!parse_start_str_pos) {
+ parse_start_str_pos = new int;
+ *parse_start_str_pos = 0;
+ isroot = true;
+ }
+
+ int a = *parse_start_str_pos;
+
+ while (*parse_start_str_pos < str_p->length()) { // *
+
+ // initial whitespace characters
+ if (initial_whitespaces) {
+ if (str[a] == ' ' || str[a] == '\n' || str[a] == '\r' || str[a] == '\t' ) {
+ ++a;
+ continue;
+ }
+ else {
+ if (str[a] == '{') // start of object
+ // ... TODO: seek_next ':'
+
+ initial_whitespaces = false;
+ }
+ }
+
+
+ // delimiter
+ if ( bracket_stack.size()==0 && quote_stack.size()==0 && str[a]==RSJarraydelimiter ) {
+ //ret.push_back (current);
+
+ //current.clear();
+ bracket_stack.clear(); quote_stack.clear(); escape_active = false;
+ continue; // to *
+ }
+
+ // ------------------------------------
+ // checks for string
+
+ if (quote_stack.size() > 0) { // already inside string
+ if (str[a]==RSJcharescape) // an escape character
+ escape_active = !escape_active;
+ else if (!escape_active && str[a]==RSJstringquotes[quote_stack.back()][1] ) { // close quote
+ quote_stack.pop_back();
+ escape_active = false;
+ }
+ else
+ escape_active = false;
+
+ //current.push_back (str[a]);
+ continue; // to *
+ }
+
+ if (quote_stack.size()==0) { // check for start of string
+ if ((bi = is_bracket (str[a], RSJstringquotes)) >= 0) {
+ quote_stack.push_back (bi);
+ //current.push_back (str[a]);
+ continue; // to *
+ }
+ }
+
+ // ------------------------------------
+ // checks for comments
+
+ if (quote_stack.size()==0) { // comment cannot start inside string
+
+ // single-line commenst
+ if (str.compare (a, RSJlinecommentstart.length(), RSJlinecommentstart) == 0) {
+ // ignore until end of line
+ int newline_pos = str.find ("\n", a);
+ if (newline_pos == std::string::npos)
+ newline_pos = str.find ("\r", a);
+
+ if (newline_pos != std::string::npos)
+ a = newline_pos; // point to the newline character (a will be incremented)
+ else // the comment continues until EOF
+ a = str.length();
+ continue;
+ }
+ }
+
+ // ------------------------------------
+ // checks for brackets
+
+ if ( bracket_stack.size()>0 && str[a]==RSJbrackets[bracket_stack.back()][1] ) { // check for closing bracket
+ bracket_stack.pop_back();
+ //current.push_back (str[a]);
+ continue;
+ }
+
+ if ((bi = is_bracket (str[a], RSJbrackets)) >= 0) {
+ bracket_stack.push_back (bi);
+ //current.push_back (str[a]);
+ continue; // to *
+ }
+
+ // ------------------------------------
+ // otherwise
+ //current.push_back (str[a]);
+ }
+
+ /*if (current.length() > 0)
+ ret.push_back (current); */
+
+ if (isroot)
+ delete parse_start_str_pos;
+
+ // return (ret);
+}
+
+// ============================================================
+
+// ------------------------------------------------------------
+
+inline
+RSJobject& RSJresource::as_object (bool force) {
+ if (!parsed_data_p) parsed_data_p = new RSJparsedData;
+ if (parsed_data_p->type==RSJ_UNKNOWN || force) parsed_data_p->parse (data, RSJ_OBJECT);
+ return (parsed_data_p->object);
+}
+
+inline
+RSJresource& RSJresource::operator[] (std::string key) { // returns reference
+ return ( (as_object())[key] ); // will return empty resource (with _exists==false) if
+ // either this resource does not exist, is not an object, or the key does not exist
+}
+
+inline
+RSJarray& RSJresource::as_array (bool force) {
+ if (!parsed_data_p) parsed_data_p = new RSJparsedData;
+ if (parsed_data_p->type==RSJ_UNKNOWN || force) parsed_data_p->parse (data, RSJ_ARRAY);
+ return (parsed_data_p->array);
+}
+
+inline
+RSJresource& RSJresource::operator[] (int indx) { // returns reference
+ as_array();
+ if (indx >= parsed_data_p->array.size())
+ parsed_data_p->array.resize(indx+1); // insert empty resources
+ return (parsed_data_p->array[indx]); // will return empty resource (with _exists==false) if
+ // either this resource does not exist, is not an object, or the key does not exist
+}
+
+// ------------------------------------------------------------
+// special 'as':
+
+template inline
+vectorType RSJresource::as_vector (const vectorType& def) { // returns copy -- for being consistent with other 'as' specializations
+ if (!exists()) return (def);
+ vectorType ret;
+ as_array();
+ for (auto it=parsed_data_p->array.begin(); it!=parsed_data_p->array.end(); ++it)
+ ret.push_back (it->as());
+ return (ret);
+}
+
+template inline
+mapType RSJresource::as_map (const mapType& def) { // returns copy -- for being consistent with other 'as' specializations
+ if (!exists()) return (def);
+ mapType ret;
+ as_object();
+ for (auto it=parsed_data_p->object.begin(); it!=parsed_data_p->object.end(); ++it)
+ ret[it->first] = it->second.as();
+ return (ret);
+}
+
+// ============================================================
+// Specialized .as() member functions
+
+// Helper preprocessor directives
+#define rsjObject as()
+#define rsjArray as()
+#define rsjAs(t) as()
+
+
+// RSJobject
+template <> inline
+RSJobject RSJresource::as (const RSJobject& def) { // returns copy -- for being consistent with other 'as' specializations
+ if (!exists()) return (def);
+ return (as_object());
+}
+
+// RSJarray
+template <> inline
+RSJarray RSJresource::as (const RSJarray& def) { // returns copy -- for being consistent with other 'as' specializations
+ if (!exists()) return (def);
+ return (as_array());
+}
+
+// ------------------------------------
+// Elementary types
+
+// String
+template <> inline
+std::string RSJresource::as (const std::string& def) {
+ if (!exists()) return (def);
+
+ char qq = '\0';
+ std::string ret = strip_outer_quotes (data, &qq);
+
+ std::vector< std::vector > escapes = { {"\\n","\n"}, {"\\r","\r"}, {"\\t","\t"}, {"\\\\","\\"} };
+ if (qq=='"')
+ escapes.push_back ({"\\\"","\""});
+ else if (qq=='\'')
+ escapes.push_back ({"\\'","'"});
+
+ for (int a=0; a inline
+int RSJresource::as (const int& def) {
+ if (!exists()) return (def);
+ return (atoi (strip_outer_quotes(data).c_str() ) );
+}
+
+// double
+template <> inline
+double RSJresource::as (const double& def) {
+ if (!exists()) return (def);
+ return (atof (strip_outer_quotes(data).c_str() ) );
+}
+
+// bool
+template <> inline
+bool RSJresource::as (const bool& def) {
+ if (!exists()) return (def);
+ std::string cleanData = strip_outer_quotes (data);
+ if (cleanData=="true" || cleanData=="TRUE" || cleanData=="True" || atoi(cleanData.c_str())!=0) return (true);
+ return (false);
+}
+
+// ------------------------------------
+// Other types
+
+/*template <> template inline
+bool RSJresource::as< std::vector > (const std::vector& def) {
+ return as_vector (def);
+}
+
+template <> template inline
+std::unordered_map RSJresource::as< std::unordered_map >
+ (const std::unordered_map& def) {
+ return as_map (def);
+}*/
+
+#endif
\ No newline at end of file
diff --git a/tests/geargrafx-test b/tests/geargrafx-test
new file mode 100755
index 0000000..d392845
Binary files /dev/null and b/tests/geargrafx-test differ
diff --git a/tests/main.cpp b/tests/main.cpp
new file mode 100644
index 0000000..cfea9c6
--- /dev/null
+++ b/tests/main.cpp
@@ -0,0 +1,28 @@
+/*
+ * Geargrafx - PC Engine / TurboGrafx Emulator
+ * Copyright (C) 2024 Ignacio Sanchez
+
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see http://www.gnu.org/licenses/
+ *
+ */
+
+#include "../src/geargrafx.h"
+#include "RSJparser.tcc"
+
+int main(int argc, char* argv[])
+{
+ int ret = 0;
+
+ return ret;
+}
\ No newline at end of file