diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..526c8a38 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.sh text eol=lf \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index dd996c71..a514c329 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ v5.0 (currently in beta) * Added `JsonBuffer::strdup()` to make a copy of a string (issues #10, #57) * Implicitly call `strdup()` for `String` but not for `char*` (issues #84, #87) * Added support of non standard JSON input (issue #44) +* Added support of comments in JSON input (issue #88) * Redesigned `JsonVariant` to leverage converting constructors instead of assignment operators (issue #66) * Switched to new the library layout (requires Arduino 1.0.6 or above) diff --git a/include/ArduinoJson/Internals/Comments.hpp b/include/ArduinoJson/Internals/Comments.hpp new file mode 100644 index 00000000..bd55b16f --- /dev/null +++ b/include/ArduinoJson/Internals/Comments.hpp @@ -0,0 +1,13 @@ +// Copyright Benoit Blanchon 2014-2015 +// MIT License +// +// Arduino JSON library +// https://github.com/bblanchon/ArduinoJson + +#pragma once + +namespace ArduinoJson { +namespace Internals { +const char *skipSpacesAndComments(const char *ptr); +} +} diff --git a/scripts/build-arduino-package.sh b/scripts/build-arduino-package.sh index a28d0023..a5d1d1b5 100755 --- a/scripts/build-arduino-package.sh +++ b/scripts/build-arduino-package.sh @@ -1,22 +1,21 @@ -#!/bin/bash - -ZIP="C:\Program Files\7-Zip\7z.exe" -TAG=$(git describe) -OUTPUT="ArduinoJson-$TAG.zip" - -cd ../.. - -# remove existing file -rm -f $OUTPUT - -# create zip -"$ZIP" a $OUTPUT \ - ArduinoJson/CHANGELOG.md \ - ArduinoJson/examples \ - ArduinoJson/include \ - ArduinoJson/keywords.txt \ - ArduinoJson/library.properties \ - ArduinoJson/LICENSE.md \ - ArduinoJson/README.md \ - ArduinoJson/src \ - -x!ArduinoJson/src/CMakeLists.txt +#!/bin/bash + +TAG=$(git describe) +OUTPUT="ArduinoJson-$TAG.zip" + +cd $(dirname $0)/../.. + +# remove existing file +rm -f $OUTPUT + +# create zip +7z a $OUTPUT \ + ArduinoJson/CHANGELOG.md \ + ArduinoJson/examples \ + ArduinoJson/include \ + ArduinoJson/keywords.txt \ + ArduinoJson/library.properties \ + ArduinoJson/LICENSE.md \ + ArduinoJson/README.md \ + ArduinoJson/src \ + -x!ArduinoJson/src/CMakeLists.txt diff --git a/src/Arduino/Print.cpp b/src/Arduino/Print.cpp index dbcd9a0a..afcea126 100644 --- a/src/Arduino/Print.cpp +++ b/src/Arduino/Print.cpp @@ -11,6 +11,12 @@ #include // for isnan() and isinf() #include // for sprintf() +// only for GCC 4.9+ +#if defined(__GNUC__) && \ + (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9)) +#pragma GCC diagnostic ignored "-Wfloat-conversion" +#endif + size_t Print::print(const char s[]) { size_t n = 0; while (*s) { diff --git a/src/Internals/Comments.cpp b/src/Internals/Comments.cpp new file mode 100644 index 00000000..103d0473 --- /dev/null +++ b/src/Internals/Comments.cpp @@ -0,0 +1,51 @@ +// Copyright Benoit Blanchon 2014-2015 +// MIT License +// +// Arduino JSON library +// https://github.com/bblanchon/ArduinoJson + +#include "../../include/ArduinoJson/Internals/Comments.hpp" + +inline static const char *skipCStyleComment(const char *ptr) { + ptr += 2; + for (;;) { + if (ptr[0] == '\0') return ptr; + if (ptr[0] == '*' && ptr[1] == '/') return ptr + 2; + ptr++; + } +} + +inline static const char *skipCppStyleComment(const char *ptr) { + ptr += 2; + for (;;) { + if (ptr[0] == '\0' || ptr[0] == '\n') return ptr; + ptr++; + } +} + +const char *ArduinoJson::Internals::skipSpacesAndComments(const char *ptr) { + for (;;) { + switch (ptr[0]) { + case ' ': + case '\t': + case '\r': + case '\n': + ptr++; + continue; + case '/': + switch (ptr[1]) { + case '*': + ptr = skipCStyleComment(ptr); + break; + case '/': + ptr = skipCppStyleComment(ptr); + break; + default: + return ptr; + } + break; + default: + return ptr; + } + } +} diff --git a/src/Internals/JsonParser.cpp b/src/Internals/JsonParser.cpp index 553e2f88..5ae4a23c 100644 --- a/src/Internals/JsonParser.cpp +++ b/src/Internals/JsonParser.cpp @@ -9,6 +9,7 @@ #include // for strtol, strtod #include +#include "../../include/ArduinoJson/Internals/Comments.hpp" #include "../../include/ArduinoJson/Internals/Encoding.hpp" #include "../../include/ArduinoJson/JsonArray.hpp" #include "../../include/ArduinoJson/JsonBuffer.hpp" @@ -17,16 +18,11 @@ using namespace ArduinoJson; using namespace ArduinoJson::Internals; -static const char *skipSpaces(const char *ptr) { - while (isspace(*ptr)) ptr++; - return ptr; -} - bool JsonParser::skip(char charToSkip) { - register const char *ptr = skipSpaces(_readPtr); + register const char *ptr = skipSpacesAndComments(_readPtr); if (*ptr != charToSkip) return false; ptr++; - _readPtr = skipSpaces(ptr); + _readPtr = skipSpacesAndComments(ptr); return true; } @@ -50,7 +46,7 @@ bool JsonParser::parseAnythingTo(JsonVariant *destination) { } inline bool JsonParser::parseAnythingToUnsafe(JsonVariant *destination) { - _readPtr = skipSpaces(_readPtr); + _readPtr = skipSpacesAndComments(_readPtr); switch (*_readPtr) { case '[': @@ -212,8 +208,13 @@ bool JsonParser::parseNullTo(JsonVariant *destination) { return true; } -static bool isStopChar(char c) { - return c == '\0' || c == ':' || c == '}' || c == ']' || c == ','; +static inline bool isInRange(char c, char min, char max) { + return min <= c && c <= max; +} + +static inline bool isLetterOrNumber(char c) { + return isInRange(c, '0', '9') || isInRange(c, 'a', 'z') || + isInRange(c, 'A', 'Z'); } const char *JsonParser::parseString() { @@ -222,7 +223,7 @@ const char *JsonParser::parseString() { char c = *readPtr; - if (c == '\'' || c == '\"') { + if (c == '\'' || c == '\"') { // quotes char stopChar = c; for (;;) { c = *++readPtr; @@ -241,9 +242,9 @@ const char *JsonParser::parseString() { *writePtr++ = c; } - } else { + } else { // no quotes for (;;) { - if (isStopChar(c)) break; + if (!isLetterOrNumber(c)) break; *writePtr++ = c; c = *++readPtr; } diff --git a/test/JsonParser_Array_Tests.cpp b/test/JsonParser_Array_Tests.cpp index f392516f..fa2a51f4 100644 --- a/test/JsonParser_Array_Tests.cpp +++ b/test/JsonParser_Array_Tests.cpp @@ -164,8 +164,26 @@ TEST_F(JsonParser_Array_Tests, MixedTrueFalse) { parseMustFail(); } -TEST_F(JsonParser_Array_Tests, TwoStrings) { - whenInputIs("[\"hello\",\"world\"]"); +TEST_F(JsonParser_Array_Tests, TwoStringsDoubleQuotes) { + whenInputIs("[ \"hello\" , \"world\" ]"); + + parseMustSucceed(); + sizeMustBe(2); + firstElementMustBe("hello"); + secondElementMustBe("world"); +} + +TEST_F(JsonParser_Array_Tests, TwoStringsSingleQuotes) { + whenInputIs("[ 'hello' , 'world' ]"); + + parseMustSucceed(); + sizeMustBe(2); + firstElementMustBe("hello"); + secondElementMustBe("world"); +} + +TEST_F(JsonParser_Array_Tests, TwoStringsNoQuotes) { + whenInputIs("[ hello , world ]"); parseMustSucceed(); sizeMustBe(2); @@ -224,3 +242,118 @@ TEST_F(JsonParser_Array_Tests, StringWithUnterminatedEscapeSequence) { whenInputIs("\"\\\0\"", 4); parseMustFail(); } + +TEST_F(JsonParser_Array_Tests, CCommentBeforeOpeningBracket) { + whenInputIs("/*COMMENT*/[\"hello\"]"); + + parseMustSucceed(); + sizeMustBe(1); + firstElementMustBe("hello"); +} + +TEST_F(JsonParser_Array_Tests, CCommentAfterOpeningBracket) { + whenInputIs("[/*COMMENT*/\"hello\"]"); + + parseMustSucceed(); + sizeMustBe(1); + firstElementMustBe("hello"); +} + +TEST_F(JsonParser_Array_Tests, CCommentBeforeClosingBracket) { + whenInputIs("[\"hello\"/*COMMENT*/]"); + + parseMustSucceed(); + sizeMustBe(1); + firstElementMustBe("hello"); +} + +TEST_F(JsonParser_Array_Tests, CCommentAfterClosingBracket) { + whenInputIs("[\"hello\"]/*COMMENT*/"); + + parseMustSucceed(); + sizeMustBe(1); + firstElementMustBe("hello"); +} + +TEST_F(JsonParser_Array_Tests, CCommentBeforeComma) { + whenInputIs("[\"hello\"/*COMMENT*/,\"world\"]"); + + parseMustSucceed(); + sizeMustBe(2); + firstElementMustBe("hello"); + secondElementMustBe("world"); +} + +TEST_F(JsonParser_Array_Tests, CCommentAfterComma) { + whenInputIs("[\"hello\",/*COMMENT*/\"world\"]"); + + parseMustSucceed(); + sizeMustBe(2); + firstElementMustBe("hello"); + secondElementMustBe("world"); +} + +TEST_F(JsonParser_Array_Tests, CppCommentBeforeOpeningBracket) { + whenInputIs("//COMMENT\n[\"hello\"]"); + + parseMustSucceed(); + sizeMustBe(1); + firstElementMustBe("hello"); +} + +TEST_F(JsonParser_Array_Tests, CppCommentAfterOpeningBracket) { + whenInputIs("[//COMMENT\n\"hello\"]"); + + parseMustSucceed(); + sizeMustBe(1); + firstElementMustBe("hello"); +} + +TEST_F(JsonParser_Array_Tests, CppCommentBeforeClosingBracket) { + whenInputIs("[\"hello\"//COMMENT\n]"); + + parseMustSucceed(); + sizeMustBe(1); + firstElementMustBe("hello"); +} + +TEST_F(JsonParser_Array_Tests, CppCommentAfterClosingBracket) { + whenInputIs("[\"hello\"]//COMMENT\n"); + + parseMustSucceed(); + sizeMustBe(1); + firstElementMustBe("hello"); +} + +TEST_F(JsonParser_Array_Tests, CppCommentBeforeComma) { + whenInputIs("[\"hello\"//COMMENT\n,\"world\"]"); + + parseMustSucceed(); + sizeMustBe(2); + firstElementMustBe("hello"); + secondElementMustBe("world"); +} + +TEST_F(JsonParser_Array_Tests, CppCommentAfterComma) { + whenInputIs("[\"hello\",//COMMENT\n\"world\"]"); + + parseMustSucceed(); + sizeMustBe(2); + firstElementMustBe("hello"); + secondElementMustBe("world"); +} + +TEST_F(JsonParser_Array_Tests, InvalidCppComment) { + whenInputIs("[/COMMENT\n]"); + parseMustFail(); +} + +TEST_F(JsonParser_Array_Tests, InvalidComment) { + whenInputIs("[/*/\n]"); + parseMustFail(); +} + +TEST_F(JsonParser_Array_Tests, UnfinishedCComment) { + whenInputIs("[/*COMMENT]"); + parseMustFail(); +}