# HG changeset patch # User Fabien Ninoles # Date 1349634652 14400 # Node ID ef7abb48573b9d970709ef94b7153c21fa677815 # Parent d187e7fc99707187dc40e1ba9ccedc3b5f6daf5a Convert to C api. diff -r d187e7fc9970 -r ef7abb48573b Makefile --- a/Makefile Sat Oct 06 21:08:55 2012 -0400 +++ b/Makefile Sun Oct 07 14:30:52 2012 -0400 @@ -1,6 +1,7 @@ all: tests -CC=g++ +clean: + rm -f *.o tests tests: tests.o jsonsax.o diff -r d187e7fc9970 -r ef7abb48573b jsonsax.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jsonsax.c Sun Oct 07 14:30:52 2012 -0400 @@ -0,0 +1,341 @@ +/* +Copyright (c) Fabien Ninoles +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of the University nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. +*/ + +#include "jsonsax.h" + +#include + +#define CASE_IS_SPACE \ + case ' ': \ +case '\n': \ +case '\t': \ +case '\r' + +#define CASE_IS_DIGIT \ + case '0': \ +case '1': \ +case '2': \ +case '3': \ +case '4': \ +case '5': \ +case '6': \ +case '7': \ +case '8': \ +case '9' + + +struct _jsonsax_state +{ + struct jsonsax_backend m_backend; + char m_lastchar; + void* m_data; +}; + +#define JSONSAX_GETCHAR(state) \ + (state)->m_backend.m_getchar((state)->m_data) +#define JSONSAX_ONERROR(state, err) \ + (state)->m_backend.m_onerror((state)->m_data, err) +#define JSONSAX_ONSTART(state,type) \ + (state)->m_backend.m_onstart((state)->m_data, type) +#define JSONSAX_ONSTOP(state,type) \ + (state)->m_backend.m_onstop((state)->m_data, type) + +static int jsonsax_parsevalue(struct _jsonsax_state* state); + +static int _jsonsax_getc(struct _jsonsax_state* state) +{ + int c = JSONSAX_GETCHAR(state); + if (c > 0) + { + state->m_lastchar = c; + return 0; + }; + return -1; +} + +static int _jsonsax_trim(struct _jsonsax_state* state) +{ + do + { + switch(state->m_lastchar) + { + CASE_IS_SPACE: + break; + default: + return 0; + } + } + while(_jsonsax_getc(state) == 0); + return -1; +} + +static int jsonsax_getc(struct _jsonsax_state* state) +{ + int err = _jsonsax_getc(state); + if (err == 0) + { + return _jsonsax_trim(state); + } + return err; +} + +int jsonsax_parsearray(struct _jsonsax_state* state) +{ + assert(state->m_lastchar == '['); + int err = jsonsax_getc(state); + if (err != 0) return err; + while(jsonsax_parsevalue(state)) + { + switch(state->m_lastchar) + { + case ',': + jsonsax_getc(state); + break; + case ']': + JSONSAX_ONSTOP(state, EJSONSAX_ARRAY); + jsonsax_getc(state); + return 0; + default: + JSONSAX_ONERROR(state, -1); + return -1; + } + } + if (state->m_lastchar != ']') + { + JSONSAX_ONERROR(state, -1); + return -1; + } + JSONSAX_ONSTOP(state, EJSONSAX_ARRAY); + jsonsax_getc(state); + return 0; +} + +int jsonsax_parsestring(struct _jsonsax_state* state) +{ + assert(state->m_lastchar == '"'); + JSONSAX_ONSTART(state, EJSONSAX_STRING); + while(jsonsax_getc(state) == 0) + { + switch(state->m_lastchar) + { + case '\\': + jsonsax_getc(state); + break; + case '"': + JSONSAX_ONSTOP(state, EJSONSAX_STRING); + jsonsax_getc(state); + return 0; + } + } + return -1; +} + +int jsonsax_parsenumber(struct _jsonsax_state* state) +{ + assert(state->m_lastchar == '-' || + state->m_lastchar == '0' || + state->m_lastchar == '1' || + state->m_lastchar == '2' || + state->m_lastchar == '3' || + state->m_lastchar == '4' || + state->m_lastchar == '5' || + state->m_lastchar == '6' || + state->m_lastchar == '7' || + state->m_lastchar == '8' || + state->m_lastchar == '9'); + + JSONSAX_ONSTART(state, EJSONSAX_NUMBER); + while(jsonsax_getc(state) == 0) + { + switch(state->m_lastchar) + { + // TODO: lookup for correct number format. + case 'e': + case '-': + case '.': + CASE_IS_DIGIT: + break; + default: + JSONSAX_ONSTOP(state, EJSONSAX_NUMBER); + return -1; + } + } + return 0; +} + +int jsonsax_parseconstant(struct _jsonsax_state* state, + const char* constant, + enum EJSONSAX_TYPE etype) +{ + assert(state->m_lastchar == constant[0]); + ++constant; + while(jsonsax_getc(state) == 0) + { + if (*constant == '\0') break; + if (*constant != state->m_lastchar) break; + constant++; + } + + if (*constant == '\0') + { + JSONSAX_ONSTOP(state, etype); + return 0; + } + else + { + JSONSAX_ONERROR(state, -1); + return -1; + } +} + +int jsonsax_parsenull(struct _jsonsax_state* state) +{ + return jsonsax_parseconstant(state, "null", EJSONSAX_NULL); +} + +int jsonsax_parsetrue(struct _jsonsax_state* state) +{ + return jsonsax_parseconstant(state, "true", EJSONSAX_TRUE); +} + +int jsonsax_parsefalse(struct _jsonsax_state* state) +{ + return jsonsax_parseconstant(state, "false", EJSONSAX_FALSE); +} + +int jsonsax_parsepair(struct _jsonsax_state* state) +{ + if (state->m_lastchar != '"') + { + JSONSAX_ONERROR(state, -1); + return -1; + } + if (jsonsax_parsestring(state) != 0) + { + JSONSAX_ONERROR(state, -1); + return -1; + } + if (state->m_lastchar != ':') + { + JSONSAX_ONERROR(state, -1); + return -1; + } + if (jsonsax_getc(state) != 0) + { + JSONSAX_ONERROR(state, -1); + return -1; + } + return jsonsax_parsevalue(state); +} + +static int jsonsax_parseobject(struct _jsonsax_state* state) +{ + assert(state->m_lastchar == '{'); + JSONSAX_ONSTART(state, EJSONSAX_OBJECT); + jsonsax_getc(state); + while(jsonsax_parsepair(state) == 0) + { + switch(state->m_lastchar) + { + case ',': + jsonsax_getc(state); + break; + case '}': + JSONSAX_ONSTOP(state, EJSONSAX_OBJECT); + jsonsax_getc(state); + return 0; + default: + JSONSAX_ONERROR(state, -1); + return -1; + } + } + if (state->m_lastchar != '}') + { + JSONSAX_ONERROR(state, -1); + return -1; + } + JSONSAX_ONSTOP(state, EJSONSAX_OBJECT); + jsonsax_getc(state); + return 0; +} + +static int jsonsax_parsevalue(struct _jsonsax_state* state) +{ + switch(state->m_lastchar) + { + case '[': + return jsonsax_parsearray(state); + case '{': + return jsonsax_parseobject(state); + case '"': + return jsonsax_parsestring(state); + case '-': + CASE_IS_DIGIT: + return jsonsax_parsenumber(state); + case 'n': + return jsonsax_parsenull(state); + case 't': + return jsonsax_parsetrue(state); + case 'f': + return jsonsax_parsefalse(state); + default: + return -1; + } +} + +static int default_onevent(void* data, enum EJSONSAX_TYPE type) +{ + return 0; +} + +static void default_onerror(void* data, int error) +{ + (void)data; + (void)error; +} + +int jsonsax_parse(struct jsonsax_backend* backend, void* data) +{ + assert(backend != 0); + assert(backend->m_getchar != 0); + struct _jsonsax_state state; + state.m_backend.m_getchar = backend->m_getchar; + state.m_backend.m_onstart = backend->m_onstart ? backend->m_onstart : default_onevent; + state.m_backend.m_onstop = backend->m_onstop ? backend->m_onstop : default_onevent; + state.m_backend.m_onerror = backend->m_onerror ? backend->m_onerror : default_onerror; + state.m_data = data; + state.m_lastchar = '\0'; + int err = jsonsax_getc(&state); + if (err != 0) return err; + err = jsonsax_parsevalue(&state); + return err; +} + +#undef CASE_IS_DIGIT +#undef CASE_IS_SPACE diff -r d187e7fc9970 -r ef7abb48573b jsonsax.cpp --- a/jsonsax.cpp Sat Oct 06 21:08:55 2012 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,276 +0,0 @@ -#include "jsonsax.hpp" - -#include - -#define CASE_IS_SPACE \ - case ' ': \ -case '\n': \ -case '\t': \ -case '\r' - -#define CASE_IS_DIGIT \ - case '0': \ -case '1': \ -case '2': \ -case '3': \ -case '4': \ -case '5': \ -case '6': \ -case '7': \ -case '8': \ -case '9' - -JSLXParser::JSLXParser(IJSLXBackend* pBackend) - : m_pBackend(pBackend) -{} -bool JSLXParser::Parse() -{ - return GetNextChar() && ParseValue(); -} - -bool JSLXParser::_GetNextChar() -{ - if (!m_pBackend->Eof()) - { - m_lastChar = m_pBackend->GetChar(); - return true; - } - return false; -} - -bool JSLXParser::TrimWhiteSpace() -{ - do - { - switch(m_lastChar) - { - CASE_IS_SPACE: - break; - default: - return true; - } - } - while(_GetNextChar()); - return false; -} - -bool JSLXParser::GetNextChar(bool trim /* = true */) -{ - if (trim) - { - return _GetNextChar() && TrimWhiteSpace(); - } - return _GetNextChar(); -} - -bool JSLXParser::ParseValue() -{ - switch(m_lastChar) - { - case '[': - return ParseArray(); - case '{': - return ParseObject(); - case '"': - return ParseString(); - case '-': - CASE_IS_DIGIT: - return ParseNumber(); - case 'n': - return ParseNull(); - case 't': - return ParseTrue(); - case 'f': - return ParseFalse(); - default: - return false; - } -} - -bool JSLXParser::ParseObject() -{ - assert(m_lastChar == '{'); - m_pBackend->OnStartObject(); - GetNextChar(); - while(ParsePair()) - { - switch(m_lastChar) - { - case ',': - GetNextChar(); - break; - case '}': - m_pBackend->OnStopObject(); - GetNextChar(); - return true; - default: - m_pBackend->OnError("ParseObject: invalid char"); - return false; - } - } - if (m_lastChar != '}') - { - m_pBackend->OnError("ParseObject: invalid char"); - return false; - } - m_pBackend->OnStopObject(); - GetNextChar(); - return true; -} - -bool JSLXParser::ParsePair() -{ - if (m_lastChar != '"') - { - m_pBackend->OnError("ParsePair: Invalid Key"); - return false; - } - if (!ParseString()) - { - m_pBackend->OnError("ParsePair: Invalid Key"); - return false; - } - if (m_lastChar != ':') - { - m_pBackend->OnError("ParsePair: Missing pair separator"); - return false; - } - if (!GetNextChar()) - { - m_pBackend->OnError("ParsePair: Missing value"); - return false; - } - return ParseValue(); -} - -bool JSLXParser::ParseArray() -{ - assert(m_lastChar == '['); - if (!GetNextChar()) return false; - while(ParseValue()) - { - switch(m_lastChar) - { - case ',': - GetNextChar(); - break; - case ']': - m_pBackend->OnStopArray(); - GetNextChar(); - return true; - default: - m_pBackend->OnError("ParseArray: Missing seperator in array"); - return false; - } - } - if (m_lastChar != ']') - { - m_pBackend->OnError("ParseArray: Bad value in array"); - return false; - } - m_pBackend->OnStopArray(); - GetNextChar(); - return true; -} - -bool JSLXParser::ParseString() -{ - assert(m_lastChar == '"'); - m_pBackend->OnStartString(); - while(GetNextChar()) - { - switch(m_lastChar) - { - case '\\': - GetNextChar(); - break; - case '"': - m_pBackend->OnStopString(); - GetNextChar(); - return true; - } - } - return false; -} - -bool JSLXParser::ParseNumber() -{ - assert(m_lastChar == '-' || - m_lastChar == '0' || - m_lastChar == '1' || - m_lastChar == '2' || - m_lastChar == '3' || - m_lastChar == '4' || - m_lastChar == '5' || - m_lastChar == '6' || - m_lastChar == '7' || - m_lastChar == '8' || - m_lastChar == '9'); - - m_pBackend->OnStartNumber(); - while(GetNextChar()) - { - switch(m_lastChar) - { - // TODO: lookup for correct number format. - case 'e': - case '-': - case '.': - CASE_IS_DIGIT: - break; - default: - m_pBackend->OnStopNumber(); - return true; - } - } - return true; -} - -bool JSLXParser::ParseNull() -{ - if (ParseConstant("null")) - { - m_pBackend->OnNull(); - return true; - } - return false; -} - -bool JSLXParser::ParseTrue() -{ - if (ParseConstant("true")) - { - m_pBackend->OnTrue(); - return true; - } - return false; -} - -bool JSLXParser::ParseFalse() -{ - if (ParseConstant("false")) - { - m_pBackend->OnFalse(); - return true; - } - return false; -} - -bool JSLXParser::ParseConstant(const char* constant) -{ - assert(m_lastChar == constant[0]); - ++constant; - while(GetNextChar()) - { - if (*constant == '\0') - return true; - if (*constant != m_lastChar) - return false; - constant++; - } - return *constant == '\0'; -} - -#undef CASE_IS_DIGIT -#undef CASE_IS_SPACE - diff -r d187e7fc9970 -r ef7abb48573b jsonsax.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jsonsax.h Sun Oct 07 14:30:52 2012 -0400 @@ -0,0 +1,65 @@ +/* +Copyright (c) Fabien Ninoles +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of the University nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. +*/ + +#ifndef _JSONSAX_H_ +#define _JSONSAX_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +enum EJSONSAX_TYPE { + EJSONSAX_OBJECT, + EJSONSAX_ARRAY, + EJSONSAX_STRING, + EJSONSAX_NUMBER, + EJSONSAX_NULL, + EJSONSAX_TRUE, + EJSONSAX_FALSE +}; + +struct jsonsax_backend +{ + int (*m_getchar)(void* data); + int (*m_onstart)(void* data, enum EJSONSAX_TYPE type); + int (*m_onstop)(void* data, enum EJSONSAX_TYPE type); + void (*m_onerror)(void* data, int error); +}; + +// Note: the structure members would be copied before +// being used. You cannot change the functions called after +// it. +int jsonsax_parse(struct jsonsax_backend* backend, void* data); + +#ifdef __cplusplus +} +#endif + +#endif diff -r d187e7fc9970 -r ef7abb48573b tests.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests.c Sun Oct 07 14:30:52 2012 -0400 @@ -0,0 +1,127 @@ +#include "jsonsax.h" + +#include +#include +#include +#include + +#define DEBUGTEST + +void debug(const char* msg, ...) +{ +#ifdef DEBUGTEST + fprintf(stderr, "\n-*- "); + va_list ap; + va_start(ap, msg); + vfprintf(stderr, msg, ap); + va_end(ap); + fprintf(stderr, " -*-\n"); +#else + (void)msg; +#endif +} + +const char* TEST_JSONTYPE_STR[] = { + "EJSONSAX_OBJECT", + "EJSONSAX_ARRAY", + "EJSONSAX_STRING", + "EJSONSAX_NUMBER", + "EJSONSAX_NULL", + "EJSONSAX_TRUE", + "EJSONSAX_FALSE" +}; + +struct test_data +{ + const char* m_str; +}; + +int test_getchar(void* data) +{ + assert(data); + struct test_data* t = (struct test_data*)(data); + if (t->m_str[0] == 0) return -1; +#ifdef DEBUGTEST + char c = t->m_str[0]; + fprintf(stderr, "%c", c == ' ' ? '_' : c); +#endif + return *(t->m_str++); +} + +int test_on_start(void* data, enum EJSONSAX_TYPE etype) +{ + assert(data); + struct test_data* t = (struct test_data*)(data); + debug("start %s", TEST_JSONTYPE_STR[etype]); + + switch (etype) + { +case EJSONSAX_OBJECT: + assert(t->m_str[-1] == '{'); + break; +case EJSONSAX_ARRAY: + assert(t->m_str[-1] == '['); + break; +case EJSONSAX_STRING: + assert(t->m_str[-1] == '"'); + break; +case EJSONSAX_NUMBER: + break; +case EJSONSAX_NULL: +case EJSONSAX_TRUE: +case EJSONSAX_FALSE: + assert(0); +} + return 0; +} + +int test_on_stop(void* data, enum EJSONSAX_TYPE etype) +{ + assert(data); + struct test_data* t = (struct test_data*)(data); + debug("stop %s", TEST_JSONTYPE_STR[etype]); + + switch (etype) + { +case EJSONSAX_OBJECT: + assert(t->m_str[-1] == '}'); + break; +case EJSONSAX_ARRAY: + assert(t->m_str[-1] == ']'); + break; +case EJSONSAX_STRING: + assert(t->m_str[-1] == '"'); + break; +case EJSONSAX_NUMBER: +case EJSONSAX_NULL: +case EJSONSAX_TRUE: +case EJSONSAX_FALSE: + break; +} + return 0; +} + +void test_on_error(void* data, int error) +{ + assert(data); + debug("Error %d", error); +} + +int main() +{ + const char json[] = + "{ \"hello\" : [ 11, 12 ,1" + "3 ], \n \"world\" : -3.45e2 }"; + struct test_data data; + data.m_str = json; + struct jsonsax_backend tester; + memset(&tester, 0, sizeof(tester)); + tester.m_getchar = &test_getchar; + tester.m_onstart = &test_on_start; + tester.m_onstop = &test_on_stop; + + int err = jsonsax_parse(&tester, &data); + assert(err == 0); + + return err; +}; diff -r d187e7fc9970 -r ef7abb48573b tests.cpp --- a/tests.cpp Sat Oct 06 21:08:55 2012 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,138 +0,0 @@ -#include "jsonsax.hpp" - -#include -#include - -// #define DEBUGTEST -class JSLXTester : public IJSLXBackend -{ -public: - explicit JSLXTester(const char* json) - : m_json(json) - {} - - void debug(const char* data) - { -#ifdef DEBUGTEST - std::cerr << std::endl << "-*- " << data << " -*-" << std::endl; -#endif - } - - virtual bool Eof() - { - return *m_json == '\0'; - } - - virtual char GetChar() - { - assert(!Eof()); -#ifdef DEBUGTEST - std::cerr.put(*m_json == ' ' ? '_' : *m_json); -#endif - return *m_json++; - } - - virtual bool OnStartObject() - { - assert(m_json[-1] == '{'); - debug("OnStartObject"); - return true; - } - - virtual bool OnStopObject() - { - assert(m_json[-1] == '}'); - debug("OnStopObject"); - return true; - } - - virtual bool OnStartArray() - { - assert(m_json[-1] == '['); - debug("OnStartArray"); - return true; - } - - virtual bool OnStopArray() - { - assert(m_json[-1] == ']'); - debug("OnStopArray"); - return true; - } - - virtual bool OnStartNumber() - { - // assert(m_json[-1] == ']'); - debug("OnStartNumber"); - return true; - } - - virtual bool OnStopNumber() - { - // assert(m_json[-1] == ']'); - debug("OnStopNumber"); - return true; - } - - virtual bool OnStartString() - { - assert(m_json[-1] == '"'); - debug("OnStartString"); - return true; - } - - virtual bool OnStopString() - { - assert(m_json[-1] == '"'); - debug("OnStopString"); - return true; - } - - virtual bool OnNull() - { - // assert(m_json[-1] == ']'); - debug("OnNull"); - return true; - } - - virtual bool OnTrue() - { - // assert(m_json[-1] == ']'); - debug("OnTrue"); - return true; - } - - virtual bool OnFalse() - { - // assert(m_json[-1] == ']'); - debug("OnFalse"); - return true; - } - - virtual void OnError(const char* err) - { - debug("Error"); - debug(err); - } - - bool Check() - { - return Eof(); - } - -private: - const char* m_json; -}; - -int main() -{ - const char json[] = - "{ \"hello\" : [ 11, 12 ,1" - "3 ], \n \"world\" : -3.45e2 }"; - JSLXTester tester(json); - JSLXParser parser(&tester); - assert(parser.Parse()); - assert(tester.Check()); - - return 0; -}