diff --git a/Plugins/MIDI/MIDI.xcodeproj/project.pbxproj b/Plugins/MIDI/MIDI.xcodeproj/project.pbxproj index ce3d91bcc..4ad91bb99 100644 --- a/Plugins/MIDI/MIDI.xcodeproj/project.pbxproj +++ b/Plugins/MIDI/MIDI.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 830C94D11D171B65000E404F /* sflist.c in Sources */ = {isa = PBXBuildFile; fileRef = 830C94CB1D171B65000E404F /* sflist.c */; }; + 830C94D21D171B65000E404F /* json-builder.c in Sources */ = {isa = PBXBuildFile; fileRef = 830C94CD1D171B65000E404F /* json-builder.c */; }; + 830C94D31D171B65000E404F /* json.c in Sources */ = {isa = PBXBuildFile; fileRef = 830C94CF1D171B65000E404F /* json.c */; }; 83686AAC1C5C69D400671C7A /* AUPlayerView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 83686AAB1C5C69D400671C7A /* AUPlayerView.mm */; }; 83686AB11C5C783000671C7A /* CoreAudioKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83686AB01C5C783000671C7A /* CoreAudioKit.framework */; }; 8398F2E01C438C7D00EB9639 /* AudioUnit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8398F2DF1C438C7D00EB9639 /* AudioUnit.framework */; }; @@ -83,6 +86,12 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 830C94CB1D171B65000E404F /* sflist.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = sflist.c; path = ../../../ThirdParty/BASS/sflist.c; sourceTree = ""; }; + 830C94CC1D171B65000E404F /* sflist.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = sflist.h; path = ../../../ThirdParty/BASS/sflist.h; sourceTree = ""; }; + 830C94CD1D171B65000E404F /* json-builder.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = "json-builder.c"; path = "../../../ThirdParty/json/json-builder.c"; sourceTree = ""; }; + 830C94CE1D171B65000E404F /* json-builder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "json-builder.h"; path = "../../../ThirdParty/json/json-builder.h"; sourceTree = ""; }; + 830C94CF1D171B65000E404F /* json.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = json.c; path = ../../../ThirdParty/json/json.c; sourceTree = ""; }; + 830C94D01D171B65000E404F /* json.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = json.h; path = ../../../ThirdParty/json/json.h; sourceTree = ""; }; 833F68431CDBCABE00AFB9F0 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; }; 83686AAB1C5C69D400671C7A /* AUPlayerView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AUPlayerView.mm; sourceTree = ""; }; 83686AAD1C5C6A2700671C7A /* AUPlayerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AUPlayerView.h; sourceTree = ""; }; @@ -160,6 +169,19 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 830C94CA1D171B30000E404F /* sflist */ = { + isa = PBXGroup; + children = ( + 830C94CB1D171B65000E404F /* sflist.c */, + 830C94CC1D171B65000E404F /* sflist.h */, + 830C94CD1D171B65000E404F /* json-builder.c */, + 830C94CE1D171B65000E404F /* json-builder.h */, + 830C94CF1D171B65000E404F /* json.c */, + 830C94D01D171B65000E404F /* json.h */, + ); + name = sflist; + sourceTree = ""; + }; 83A09F551CFA83F2001E7D2D /* synthlib_doom */ = { isa = PBXGroup; children = ( @@ -253,6 +275,7 @@ 83B06690180D5668008E3612 /* MIDI */ = { isa = PBXGroup; children = ( + 830C94CA1D171B30000E404F /* sflist */, 83A09F6E1CFA8D6B001E7D2D /* MSPlayer.cpp */, 83A09F6D1CFA8D6B001E7D2D /* MSPlayer.h */, 83A09F661CFA883D001E7D2D /* interface.h */, @@ -387,6 +410,7 @@ files = ( 83E973471C4378880007F413 /* AUPlayer.mm in Sources */, 83686AAC1C5C69D400671C7A /* AUPlayerView.mm in Sources */, + 830C94D31D171B65000E404F /* json.c in Sources */, 83DFEA071CBC87BB00BCC565 /* SCCore.cpp in Sources */, 83A09F621CFA83F2001E7D2D /* i_oplmusic.cpp in Sources */, 83B06709180D64DA008E3612 /* MIDIPlayer.cpp in Sources */, @@ -398,6 +422,8 @@ 83B0670C180D6665008E3612 /* BMPlayer.cpp in Sources */, 83A09F651CFA83F2001E7D2D /* opl3class.cpp in Sources */, 83C35705180EDD1C007E9DF0 /* MIDIMetadataReader.mm in Sources */, + 830C94D21D171B65000E404F /* json-builder.c in Sources */, + 830C94D11D171B65000E404F /* sflist.c in Sources */, 83A09F641CFA83F2001E7D2D /* opl3.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -501,6 +527,10 @@ COMBINE_HIDPI_IMAGES = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "MIDI/MIDI-Prefix.pch"; + HEADER_SEARCH_PATHS = ( + "$(SRCROOT)/../../ThirdParty/BASS", + "$(SRCROOT)/../../ThirdParty/json", + ); INFOPLIST_FILE = "MIDI/MIDI-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; LIBRARY_SEARCH_PATHS = ( @@ -521,6 +551,10 @@ COMBINE_HIDPI_IMAGES = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "MIDI/MIDI-Prefix.pch"; + HEADER_SEARCH_PATHS = ( + "$(SRCROOT)/../../ThirdParty/BASS", + "$(SRCROOT)/../../ThirdParty/json", + ); INFOPLIST_FILE = "MIDI/MIDI-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; LIBRARY_SEARCH_PATHS = ( diff --git a/Plugins/MIDI/MIDI/BMPlayer.cpp b/Plugins/MIDI/MIDI/BMPlayer.cpp index a74aa5132..141cf0e93 100644 --- a/Plugins/MIDI/MIDI/BMPlayer.cpp +++ b/Plugins/MIDI/MIDI/BMPlayer.cpp @@ -1,5 +1,7 @@ #include "BMPlayer.h" +#include + #include #include @@ -20,7 +22,8 @@ struct Cached_SoundFont unsigned long ref_count; std::chrono::steady_clock::time_point time_released; HSOUNDFONT handle; - Cached_SoundFont() : handle( 0 ) { } + sflist_presets * presetlist; + Cached_SoundFont() : handle( 0 ), presetlist( 0 ) { } }; static std::mutex Cache_Lock; @@ -46,11 +49,14 @@ static void cache_deinit() for ( auto it = Cache_List.begin(); it != Cache_List.end(); ++it ) { - BASS_MIDI_FontFree( it->second.handle ); + if ( it->second.handle ) + BASS_MIDI_FontFree( it->second.handle ); + if ( it->second.presetlist ) + sflist_free( it->second.presetlist ); } } -static HSOUNDFONT cache_open( const char * path ) +static HSOUNDFONT cache_open_font( const char * path ) { HSOUNDFONT font = NULL; @@ -80,7 +86,80 @@ static HSOUNDFONT cache_open( const char * path ) return font; } -static void cache_close( HSOUNDFONT handle ) +static sflist_presets * sflist_open_file( const char * path ) +{ + sflist_presets * presetlist; + FILE * f; + char * sflist_file; + char * separator; + size_t length; + char error[sflist_max_error]; + char base_path[32768]; + strcpy(base_path, path); + separator = strrchr(base_path, '/'); + if (separator) + *separator = '\0'; + else + base_path[0] = '\0'; + + f = fopen( path, "r" ); + if (!f) return 0; + + fseek(f, 0, SEEK_END); + length = ftell(f); + fseek(f, 0, SEEK_SET); + + sflist_file = (char *) malloc(length + 1); + if (!sflist_file) + { + fclose(f); + return 0; + } + + length = fread(sflist_file, 1, length, f); + fclose(f); + + sflist_file[length] = '\0'; + + presetlist = sflist_load(sflist_file, strlen(sflist_file), base_path, error); + + free(sflist_file); + + return presetlist; +} + +static sflist_presets * cache_open_list( const char * path ) +{ + sflist_presets * presetlist = NULL; + + std::lock_guard lock( Cache_Lock ); + + Cached_SoundFont & entry = Cache_List[ path ]; + + if ( !entry.presetlist ) + { + + presetlist = sflist_open_file( path ); + if ( presetlist ) + { + entry.presetlist = presetlist; + entry.ref_count = 1; + } + else + { + Cache_List.erase( path ); + } + } + else + { + presetlist = entry.presetlist; + ++entry.ref_count; + } + + return presetlist; +} + +static void cache_close_font( HSOUNDFONT handle ) { std::lock_guard lock( Cache_Lock ); @@ -95,6 +174,21 @@ static void cache_close( HSOUNDFONT handle ) } } +static void cache_close_list( sflist_presets * presetlist ) +{ + std::lock_guard lock( Cache_Lock ); + + for ( auto it = Cache_List.begin(); it != Cache_List.end(); ++it ) + { + if ( it->second.presetlist == presetlist ) + { + if ( --it->second.ref_count == 0 ) + it->second.time_released = std::chrono::steady_clock::now(); + break; + } + } +} + static void cache_run() { std::chrono::milliseconds dura( 250 ); @@ -114,7 +208,10 @@ static void cache_run() auto elapsed = std::chrono::duration_cast ( now - it->second.time_released ); if ( elapsed.count() >= 10 ) { - BASS_MIDI_FontFree( it->second.handle ); + if ( it->second.handle ) + BASS_MIDI_FontFree( it->second.handle ); + if ( it->second.presetlist ) + sflist_free( it->second.presetlist ); it = Cache_List.erase( it ); continue; } @@ -200,6 +297,7 @@ BMPlayer::BMPlayer() : MIDIPlayer() { memset(_stream, 0, sizeof(_stream)); bSincInterpolation = false; + _presetList = 0; if ( !g_initializer.initialize() ) throw std::runtime_error( "Unable to initialize BASS" ); } @@ -290,9 +388,14 @@ void BMPlayer::shutdown() memset(_stream, 0, sizeof(_stream)); for ( unsigned long i = 0; i < _soundFonts.size(); ++i ) { - cache_close( _soundFonts[i] ); + cache_close_font( _soundFonts[i] ); } _soundFonts.resize( 0 ); + if ( _presetList ) + { + cache_close_list( _presetList ); + _presetList = 0; + } } void BMPlayer::compound_presets( std::vector & out, std::vector & in, std::vector & channels ) @@ -335,7 +438,19 @@ bool BMPlayer::startup() } memset( bank_lsb_override, 0, sizeof( bank_lsb_override ) ); std::vector presetList; - if (sSoundFontName.length()) + if ( sFileSoundFontName.length() ) + { + HSOUNDFONT font = cache_open_font( sFileSoundFontName.c_str() ); + if ( !font ) + { + shutdown(); + return false; + } + _soundFonts.push_back( font ); + presetList.push_back( { font, -1, -1, -1, 0, 0 } ); + } + + if (sSoundFontName.length()) { std::string ext; size_t dot = sSoundFontName.find_last_of('.'); @@ -346,7 +461,7 @@ bool BMPlayer::startup() #endif ) { - HSOUNDFONT font = cache_open( sSoundFontName.c_str() ); + HSOUNDFONT font = cache_open_font( sSoundFontName.c_str() ); if ( !font ) { shutdown(); @@ -355,233 +470,24 @@ bool BMPlayer::startup() _soundFonts.push_back( font ); presetList.push_back( {font, -1, -1, -1, 0, 0} ); } - else if ( !strcasecmp( ext.c_str(), "sflist" ) ) + else if ( !strcasecmp( ext.c_str(), "sflist" ) || !strcasecmp( ext.c_str(), "json" ) ) { - FILE * fl = fopen( sSoundFontName.c_str(), "r" ); - if ( fl ) - { - std::string path, temp; - char name[32768]; - char *nameptr; - size_t slash = sSoundFontName.find_last_of('/'); - if ( slash != std::string::npos ) path.assign( sSoundFontName.begin(), sSoundFontName.begin() + slash + 1 ); - while ( !feof( fl ) ) - { - std::vector presets; - - if ( !fgets( name, 32767, fl ) ) break; - name[32767] = 0; - char * cr = strchr( name, '\n' ); - if ( cr ) *cr = 0; - cr = strchr( name, '\r' ); - if ( cr ) *cr = 0; - cr = strchr( name, '|' ); - if ( cr ) - { - std::vector nested_presets; - std::vector channels; - bool valid = true; - bool pushed_back = true; - char *endchr; - nameptr = cr + 1; - *cr = 0; - cr = name; - while ( *cr && valid ) - { - switch ( *cr++ ) - { - case 'p': - { - // patch override - "p[db#,]dp#=[sb#,]sp#" ex. "p0,5=0,1" - // may be used once per preset group - pushed_back = false; - long dbank = 0; - long dpreset = strtol( cr, &endchr, 10 ); - if ( endchr == cr ) - { - valid = false; - break; - } - if ( *endchr == ',' ) - { - dbank = dpreset; - cr = endchr + 1; - dpreset = strtol( cr, &endchr, 10 ); - if ( endchr == cr ) - { - valid = false; - break; - } - } - if ( *endchr != '=' ) - { - valid = false; - break; - } - cr = endchr + 1; - long sbank = -1; - long spreset = strtol( cr, &endchr, 10 ); - if ( endchr == cr ) - { - valid = false; - break; - } - if ( *endchr == ',' ) - { - sbank = spreset; - cr = endchr + 1; - spreset = strtol( cr, &endchr, 10 ); - if ( endchr == cr ) - { - valid = false; - break; - } - } - if ( *endchr && *endchr != ';' && *endchr != '&' ) - { - valid = false; - break; - } - cr = endchr; - nested_presets.push_back( { 0, (int) spreset, (int) sbank, (int) dpreset, (int) dbank, 0 } ); - } - break; - - case 'c': - { - // channel override - implemented using bank LSB, which is disabled from - // actual use. - format "c#[-#]" ex. "c16" (range is 1-48) - // may be used multiple times per preset group - pushed_back = false; - long channel_start = strtol(cr, &endchr, 10); - long channel_end; - if ( endchr == cr ) - { - valid = false; - break; - } - if ( channel_start < 1 || channel_start > 48 ) - { - valid = false; - break; - } - channel_end = channel_start; - if ( *endchr == '-' ) - { - channel_end = strtol(cr, &endchr, 10); - if ( channel_end <= channel_start || channel_end > 48 ) - { - valid = false; - break; - } - } - for ( auto it = channels.begin(); it != channels.end(); ++it ) - { - if ( *it >= channel_start || *it <= channel_end ) - { - valid = false; - break; - } - } - if ( *endchr && *endchr != ';' ) - { - valid = false; - break; - } - cr = endchr; - for ( long channel = channel_start; channel <= channel_end; ++channel ) - channels.push_back( channel ); - } - break; - - case '&': - { - // separates preset groups per SoundFont bank - if ( !pushed_back ) - { - compound_presets( presets, nested_presets, channels ); - nested_presets.clear(); channels.clear(); - pushed_back = true; - } - } - break; - - case ';': - // separates preset items - break; - - default: - // invalid command character - valid = false; - break; - } - } - if ( !pushed_back && valid ) - compound_presets( presets, nested_presets, channels ); - if ( !valid ) - { - presets.clear(); - presets.push_back( { 0, -1, -1, -1, 0, 0 } ); - memset( bank_lsb_override, 0, sizeof(bank_lsb_override) ); - } - } - else - { - presets.push_back( { 0, -1, -1, -1, 0, 0 } ); - nameptr = name; - } - if ( nameptr[0] == '/' ) - { - temp = nameptr; - } - else - { - temp = path; - temp += nameptr; - } - HSOUNDFONT font = cache_open( temp.c_str() ); - if ( !font ) - { - fclose( fl ); - shutdown(); - return false; - } - for ( auto it = presets.begin(); it != presets.end(); ++it ) - { - it->font = font; - presetList.push_back( *it ); - } - _soundFonts.push_back( font ); - } - fclose( fl ); - } - else - { - return false; - } + _presetList = cache_open_list( sSoundFontName.c_str() ); + if ( !_presetList ) + { + shutdown(); + return false; + } + for (unsigned int i = 0, j = _presetList->count; i < j; ++i) + { + presetList.push_back( _presetList->presets[i] ); + } } } - if ( sFileSoundFontName.length() ) - { - HSOUNDFONT font = cache_open( sFileSoundFontName.c_str() ); - if ( !font ) - { - shutdown(); - return false; - } - _soundFonts.push_back( font ); - presetList.push_back( { font, -1, -1, -1, 0, 0 } ); - } - - std::vector< BASS_MIDI_FONTEX > fonts; - for ( unsigned long i = 0, j = presetList.size(); i < j; ++i ) - { - fonts.push_back( presetList[j - i - 1] ); - } - BASS_MIDI_StreamSetFonts( _stream[0], &fonts[0], (unsigned int) fonts.size() | BASS_MIDI_FONT_EX ); - BASS_MIDI_StreamSetFonts( _stream[1], &fonts[0], (unsigned int) fonts.size() | BASS_MIDI_FONT_EX ); - BASS_MIDI_StreamSetFonts( _stream[2], &fonts[0], (unsigned int) fonts.size() | BASS_MIDI_FONT_EX ); + BASS_MIDI_StreamSetFonts( _stream[0], &presetList[0], (unsigned int) presetList.size() | BASS_MIDI_FONT_EX ); + BASS_MIDI_StreamSetFonts( _stream[1], &presetList[0], (unsigned int) presetList.size() | BASS_MIDI_FONT_EX ); + BASS_MIDI_StreamSetFonts( _stream[2], &presetList[0], (unsigned int) presetList.size() | BASS_MIDI_FONT_EX ); reset_parameters(); diff --git a/Plugins/MIDI/MIDI/BMPlayer.h b/Plugins/MIDI/MIDI/BMPlayer.h index 12567c712..f9c215368 100644 --- a/Plugins/MIDI/MIDI/BMPlayer.h +++ b/Plugins/MIDI/MIDI/BMPlayer.h @@ -3,7 +3,9 @@ #include "MIDIPlayer.h" -#include "../../../ThirdParty/BASS/bassmidi.h" +#include + +typedef struct sflist_presets sflist_presets; class BMPlayer : public MIDIPlayer { @@ -31,6 +33,7 @@ private: void reset_parameters(); std::vector _soundFonts; + sflist_presets * _presetList; std::string sSoundFontName; std::string sFileSoundFontName; diff --git a/Preferences/General/MIDIPane.m b/Preferences/General/MIDIPane.m index b1baceca6..e5dc98a4c 100644 --- a/Preferences/General/MIDIPane.m +++ b/Preferences/General/MIDIPane.m @@ -28,7 +28,7 @@ - (IBAction)setSoundFont:(id)sender { - NSArray *fileTypes = [NSArray arrayWithObjects:@"sf2",@"sf2pack",@"sflist",nil]; + NSArray *fileTypes = [NSArray arrayWithObjects:@"sf2",@"sf2pack",@"sflist",@"json",nil]; NSOpenPanel * panel = [NSOpenPanel openPanel]; [panel setAllowsMultipleSelection:NO]; [panel setCanChooseDirectories:NO]; diff --git a/ThirdParty/BASS/sflist.c b/ThirdParty/BASS/sflist.c new file mode 100644 index 000000000..81c3afc66 --- /dev/null +++ b/ThirdParty/BASS/sflist.c @@ -0,0 +1,894 @@ +/* vim: set et ts=3 sw=3 sts=3 ft=c: + * + * Copyright (C) 2016 Christopher Snowhill. All rights reserved. + * https://github.com/kode54/sflist + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 +#include +#include +#include +#include + +#include + +#include "sflist.h" + +/* Extras needed */ + +static int json_equal (const json_value * a, const json_value * b); + +static int json_equal_array(const json_value * a, const json_value * b) +{ + unsigned int i, j; + + if (a->u.array.length != b->u.array.length) return 0; + + for (i = 0, j = a->u.array.length; i < j; ++i) + { + if (!json_equal(a->u.array.values[i], b->u.array.values[i])) + return 0; + } + + return 1; +} + +static int json_equal_object(const json_value * a, const json_value * b) +{ + unsigned int i, j; + + if (a->u.object.length != b->u.object.length) return 0; + + for (i = 0, j = a->u.object.length; i < j; ++i) + { + if (strcmp(a->u.object.values[i].name, b->u.object.values[i].name)) + return 0; + if (!json_equal(a->u.object.values[i].value, b->u.object.values[i].value)) + return 0; + } + + return 1; +} + +static int json_equal (const json_value * a, const json_value * b) +{ + if (a->type != b->type) return 0; + + switch (a->type) + { + case json_none: + case json_null: + return 1; + + case json_integer: + return a->u.integer == b->u.integer; + + case json_double: + return a->u.dbl == b->u.dbl; + + case json_boolean: + return !a->u.boolean == !b->u.boolean; + + case json_string: + return !strcmp(a->u.string.ptr, b->u.string.ptr); + + case json_array: + return json_equal_array(a, b); + + case json_object: + return json_equal_object(a, b); + } + + return 0; +} + +static int json_signum (double val) +{ + return (val > 0.0) - (val < 0.0); +} + +#define json_compare_invalid -1000 + +static int json_compare (const json_value * a, const json_value * b) +{ + if (a->type != b->type) return json_compare_invalid; + + switch (a->type) + { + case json_none: + case json_null: + return 0; + + case json_integer: + return a->u.integer - b->u.integer; + + case json_double: + return json_signum(a->u.dbl - b->u.dbl); + + case json_boolean: + return !!a->u.boolean - !!b->u.boolean; + + case json_string: + return strcmp(a->u.string.ptr, b->u.string.ptr); + + case json_array: + case json_object: + return json_compare_invalid; + } + + return json_compare_invalid; +} + +static int json_array_contains_value (const json_value * array, const json_value * value) +{ + unsigned int i, j; + + for (i = 0, j = array->u.array.length; i < j; ++i) + { + if (json_equal(array->u.array.values[i], value)) + return 1; + } + + return 0; +} + +json_value * json_array_merge (json_value * arrayA, json_value * arrayB) +{ + unsigned int i, j; + + if (arrayA->type != json_array || arrayB->type != json_array) + return 0; + + for (i = 0, j = arrayB->u.array.length; i < j; ++i) + { + if (!json_array_contains_value(arrayA, arrayB->u.array.values[i])) + { + json_array_push(arrayA, arrayB->u.array.values[i]); + } + } + + json_builder_free(arrayB); + + return arrayA; +} + +static int json_compare_callback (const void * a, const void * b) +{ + const json_value * aa = (const json_value *) a; + const json_value * bb = (const json_value *) b; + return json_compare(aa, bb); +} + +json_value * json_array_sort (json_value * array) +{ + unsigned int i, j; + + json_type type; + + if (array->type != json_array) return 0; + + if (array->u.array.length < 2) return array; + + type = array->u.array.values[0]->type; + + for (i = 1, j = array->u.array.length; i < j; ++i) + { + if (array->u.array.values[i]->type != type) + return 0; + } + + qsort(array->u.array.values, j, sizeof(json_value *), json_compare_callback); + + return array; +} + + +/* Processing begins */ + +static size_t sflist_parse_int(const char * in, const char ** end) +{ + size_t rval = 0; + while (in < *end) { + if (isdigit(*in)) { + rval = (rval * 10) + (*in - '0'); + } + else break; + ++in; + } + *end = in; + return rval; +} + +static double sflist_parse_float(const char * in, const char ** end) +{ + size_t whole = 0; + size_t decimal = 0; + size_t decimal_places = 0; + double sign = 1.0; + const char * end_orig = *end; + const char * ptr = in; + if (*ptr == '-') { + ++ptr; + sign = -1.0; + } + whole = sflist_parse_int(ptr, end); + if (*end == ptr || (**end != '.' && *end < end_orig)) { + *end = in; + return 0.0; + } + if (*end < end_orig) { + ptr = *end + 1; + *end = end_orig; + decimal = sflist_parse_int(ptr, end); + if (*end == ptr || *end < end_orig) { + *end = in; + return 0.0; + } + decimal_places = *end - ptr; + } + return (((double)whole) + (((double)decimal) / pow(10.0, (double)decimal_places))) * sign; +} + +static json_value * sflist_load_v1(const char * sflist, size_t size, char * error_buf) +{ + json_value * rval = 0; + + json_value * arr = json_array_new(0); + + json_value * channels = 0; + json_value * patchMappings = 0; + double gain = 0.0; + + const char * ptr = sflist; + const char * end = sflist + size; + + unsigned int cur_line = 0; + + while (ptr < end) { + const char * line_start = ptr; + json_value * obj = 0; + const char * path = 0; + const char * pipe = 0; + const char * lend = ptr; + ++cur_line; + while (lend < end && *lend && *lend != '\r' && *lend != '\n') { + if (*lend == '|') + pipe = lend; + ++lend; + } + if (pipe) + path = pipe + 1; + else + path = ptr; + if (pipe) { + while (ptr < pipe) { + char c; + const char * fend = ptr; + const char * vend; + while (fend < pipe && *fend != '&') ++fend; + vend = fend; + switch (c = *ptr++) { + case '&': + continue; + + case 'c': { + json_value * this_channels; + size_t channel_low = sflist_parse_int(ptr, &vend); + size_t channel_high = 0; + size_t i; + if (vend == ptr || (*vend != '-' && *vend != '&' && *vend != '|')) { + sprintf(error_buf, "Invalid channel number (%u:%u)", cur_line, (int)(vend - line_start + 1)); + goto error; + } + if (*vend != '-') + channel_high = channel_low; + else { + ptr = vend + 1; + vend = fend; + channel_high = sflist_parse_int(ptr, &vend); + if (vend == ptr || (*vend != '&' && *vend != '|')) { + sprintf(error_buf, "Invalid channel range end value (%u:%u)", cur_line, (int)(vend - line_start + 1)); + goto error; + } + } + if (!channels) + channels = json_array_new(0); + this_channels = json_array_new(0); + for (i = channel_low; i <= channel_high; ++i) + json_array_push(this_channels, json_integer_new(i)); + channels = json_array_merge(channels, this_channels); + ptr = fend; + } + break; + + case 'p': { + json_value * mapping = 0; + json_value * mapping_destination = 0; + json_value * mapping_source = 0; + + long source_bank = -1; + long source_program = -1; + long dest_bank = -1; + long dest_program = -1; + + size_t val = sflist_parse_int(ptr, &vend); + if (vend == ptr || (*vend != '=' && *vend != ',' && *vend != '|')) { + sprintf(error_buf, "Invalid preset number (%u:%u)", cur_line, (int)(vend - line_start + 1)); + goto error; + } + dest_program = val; + if (*vend == ',') { + dest_bank = val; + ptr = vend + 1; + vend = fend; + val = sflist_parse_int(ptr, &vend); + if (vend == ptr || (*vend != '=' && *vend != '|')) { + sprintf(error_buf, "Invalid preset number (%u:%u)", cur_line, (int)(vend - line_start + 1)); + goto error; + } + dest_program = val; + } + if (*vend == '=') { + ptr = vend + 1; + vend = fend; + val = sflist_parse_int(ptr, &vend); + if (vend == ptr || (*vend != ',' && *vend != '|')) { + sprintf(error_buf, "Invalid preset number (%u:%u)", cur_line, (int)(vend - line_start + 1)); + goto error; + } + source_program = val; + if (*vend == ',') { + source_bank = val; + ptr = vend + 1; + vend = fend; + val = sflist_parse_int(ptr, &vend); + if (vend == ptr || (*vend != '&' && *vend != '|')) { + sprintf(error_buf, "Invalid preset number (%u:%u)", cur_line, (int)(vend - line_start + 1)); + goto error; + } + source_program = val; + } + } + + if (!patchMappings) + patchMappings = json_array_new(0); + mapping = json_object_new(0); + mapping_destination = json_object_new(0); + if (dest_bank != -1) { + json_object_push(mapping_destination, "bank", json_integer_new(dest_bank)); + } + json_object_push(mapping_destination, "program", json_integer_new(dest_program)); + json_object_push(mapping, "destination", mapping_destination); + if (source_program != -1) { + mapping_source = json_object_new(0); + if (source_bank != -1) { + json_object_push(mapping_source, "bank", json_integer_new(source_bank)); + } + json_object_push(mapping_source, "program", json_integer_new(source_program)); + json_object_push(mapping, "source", mapping_source); + } + json_array_push(patchMappings, mapping); + + ptr = fend; + } + break; + + case 'g': { + double val = sflist_parse_float(ptr, &vend); + if (vend == ptr || vend < fend) { + sprintf(error_buf, "Invalid gain value (%u:%u)", cur_line, (int)(vend - line_start + 1)); + goto error; + } + gain = val; + ptr = fend; + } + break; + + default: + sprintf(error_buf, "Invalid character in preset '%c' (%u:%u)", c, cur_line, (unsigned int)(ptr - line_start)); + goto error; + } + } + } + obj = json_object_new(0); + json_object_push(obj, "fileName", json_string_new_length(lend - path, path)); + if (gain != 0.0) { + json_object_push(obj, "gain", json_double_new(gain)); + gain = 0.0; + } + if (channels) { + channels = json_array_sort(channels); + json_object_push(obj, "channels", channels); + channels = 0; + } + if (patchMappings) { + json_object_push(obj, "patchMappings", patchMappings); + patchMappings = 0; + } + + json_array_push(arr, obj); + + ptr = lend; + + while (ptr < end && (*ptr == '\n' || *ptr == '\r')) ++ptr; + } + + rval = json_object_new(1); + json_object_push(rval, "soundFonts", arr); + + return rval; + +error: + if (channels) json_builder_free(channels); + if (patchMappings) json_builder_free(patchMappings); + if (arr) json_builder_free(arr); + return 0; +} + +static json_value * sflist_load_v2(const char * sflist, size_t size, char * error) +{ + json_value * rval = 0; + + json_settings settings = { 0 }; + settings.value_extra = json_builder_extra; + + rval = json_parse_ex( &settings, sflist, size, error); + + return rval; +} + +static const json_value * json_object_item(const json_value * object, const char * name) +{ + unsigned int i, j; + + if (object->type != json_object) return &json_value_none; + + for (i = 0, j = object->u.object.length; i < j; ++i) { + if (!strcmp(object->u.object.values[i].name, name)) + return object->u.object.values[i].value; + } + + return &json_value_none; +} + +static void sflist_process_patchmappings(BASS_MIDI_FONTEX * out, BASS_MIDI_FONTEX * fontex, const json_value * patchMappings, unsigned int channel) +{ + unsigned int i, j; + for (i = 0, j = patchMappings->u.array.length; i < j; ++i) { + json_value * preset = patchMappings->u.array.values[i]; + const json_value * destination = json_object_item(preset, "destination"); + const json_value * source = json_object_item(preset, "source"); + const json_value * destination_bank = json_object_item(destination, "bank"); + const json_value * destination_program = json_object_item(destination, "program"); + const json_value * source_bank = json_object_item(source, "bank"); + const json_value * source_program = json_object_item(source, "program"); + fontex->spreset = (source_program->type == json_none) ? -1 : source_program->u.integer; + fontex->sbank = (source_bank->type == json_none) ? -1 : source_bank->u.integer; + fontex->dpreset = (destination_program->type == json_none) ? -1 : destination_program->u.integer; + fontex->dbank = (destination_bank->type == json_none) ? 0 : destination_bank->u.integer; + fontex->dbanklsb = channel; + *out++ = *fontex; + } +} + +static sflist_presets * sflist_process(const json_value * sflist, const char * base_path, char * error_buf) +{ +#ifdef _WIN32 + wchar_t path16[32768]; +#endif + char path_temp[32768]; + const char * base_path_end = base_path + strlen(base_path) - 1; + unsigned int presets_to_allocate = 0; + sflist_presets * rval = calloc(1, sizeof(sflist_presets)); + json_value * arr; + unsigned int i, j, k, l, preset_number; + HSOUNDFONT hfont = 0; + BASS_MIDI_FONTEX fontex; + + if (!rval) + { + strcpy(error_buf, "Out of memory"); + goto error; + } + + if (sflist->type != json_object || sflist->u.object.length != 1 || + strcmp(sflist->u.object.values[0].name, "soundFonts")) + { + if (sflist->type != json_object) + strcpy(error_buf, "Base JSON item is not an object"); + else if (sflist->u.object.length != 1) + sprintf(error_buf, "Base JSON object contains unexpected number of items (wanted 1, got %u)", sflist->u.object.length); + else + sprintf(error_buf, "Base JSON object contains '%s' object instead of 'soundFonts'", sflist->u.object.values[0].name); + goto error; + } + + arr = sflist->u.object.values[0].value; + + if (arr->type != json_array) + { + strcpy(error_buf, "JSON 'soundFonts' object is not an array"); + goto error; + } + + for (i = 0, j = arr->u.array.length; i < j; ++i) { + const json_value * obj = arr->u.array.values[i]; + const json_value * path = 0; + const json_value * gain = 0; + const json_value * channels = 0; + const json_value * patchMappings = 0; + unsigned int patches_needed = 1; + if (obj->type != json_object) { + sprintf(error_buf, "soundFont item #%u is not an object", i + 1); + goto error; + } + path = json_object_item(obj, "fileName"); + gain = json_object_item(obj, "gain"); + channels = json_object_item(obj, "channels"); + patchMappings = json_object_item(obj, "patchMappings"); + if (path->type == json_none) { + sprintf(error_buf, "soundFont item #%u has no 'fileName'", i + 1); + goto error; + } + if (path->type != json_string) { + sprintf(error_buf, "soundFont item #%u 'fileName' is not a string", i + 1); + goto error; + } + if (gain->type != json_none && gain->type != json_integer && gain->type != json_double) { + sprintf(error_buf, "soundFont item #%u has an invalid gain value", i + 1); + goto error; + } + if (channels->type != json_none) { + if (channels->type != json_array) { + sprintf(error_buf, "soundFont item #%u 'channels' is not an array", i + 1); + goto error; + } + for (k = 0, l = channels->u.array.length; k < l; ++k) { + json_value * channel = channels->u.array.values[k]; + if (channel->type != json_integer) { + sprintf(error_buf, "soundFont item #%u 'channels' #%u is not an integer", i + 1, k + 1); + goto error; + } + if (channel->u.integer < 1 || channel->u.integer > 48) { + sprintf(error_buf, "soundFont item #%u 'channels' #%u is out of range (wanted 1-48, got %" PRId64 ")", i + 1, k + 1, channel->u.integer); + goto error; + } + } + patches_needed = l; + } + if (patchMappings->type != json_none) { + if (patchMappings->type != json_array) { + sprintf(error_buf, "soundFont item #%u 'patchMappings' is not an array", i + 1); + goto error; + } + for (k = 0, l = patchMappings->u.array.length; k < l; ++k) { + unsigned int m, n; + unsigned int source_found = 0; + unsigned int destination_found = 0; + json_value * mapping = patchMappings->u.array.values[k]; + if (mapping->type != json_object) { + sprintf(error_buf, "soundFont item #%u 'patchMappings' #%u is not an object", i + 1, k + 1); + goto error; + } + for (m = 0, n = mapping->u.object.length; m < n; ++m) { + unsigned int o, p; + json_value * item = mapping->u.object.values[m].value; + const char * name = mapping->u.object.values[m].name; + unsigned int bank_found = 0; + unsigned int program_found = 0; + if (strcmp(name, "source") && strcmp(name, "destination")) { + sprintf(error_buf, "soundFont item #%u 'patchMappings' #%u contains an invalid '%s' field", i + 1, k + 1, name); + goto error; + } + if (item->type != json_object) { + sprintf(error_buf, "soundFont item #%u 'patchMappings' #%u '%s' is not an object", i + 1, k + 1, name); + goto error; + } + if (!strcmp(name, "source")) + ++source_found; + else + ++destination_found; + for (o = 0, p = item->u.object.length; o < p; ++o) { + int range_min = 0; + int range_max = 128; + json_value * item2 = item->u.object.values[o].value; + const char * name2 = item->u.object.values[o].name; + if (strcmp(name2, "bank") && strcmp(name2, "program")) { + sprintf(error_buf, "soundFont item #%u 'patchMappings' #%u '%s' contains an invalid '%s' field", i + 1, k + 1, name, name2); + goto error; + } + if (item->type != json_integer) { + sprintf(error_buf, "soundFont item #%u 'patchMappings' #%u '%s' '%s' is not an integer", i + 1, k + 1, name, name2); + } + if (!strcmp(name2, "program")) { + if (!strcmp(name, "destination")) + range_max = 65535; + else + range_max = 127; + } + if (item->u.integer < range_min || item->u.integer > range_max) { + sprintf(error_buf, "soundFont item #%u 'patchMappings' #%u '%s' '%s' is out of range (expected %d-%d, got %" PRId64 ")", i + 1, k + 1, name, name2, range_min, range_max, item->u.integer); + goto error; + } + if (!strcmp(name2, "bank")) + ++bank_found; + else + ++program_found; + } + if (!bank_found && !program_found) { + sprintf(error_buf, "soundFont item #%u 'patchMappings' #%u '%s' contains no 'bank' or 'program'", i + 1, k + 1, name); + goto error; + } + if (bank_found > 1) { + sprintf(error_buf, "soundFont item #%u 'patchMappings' #%u '%s' contains more than one 'bank'", i + 1, k + 1, name); + goto error; + } + if (program_found > 1) { + sprintf(error_buf, "soundFont item #%u 'patchMappings' #%u '%s' contains more than one 'program'", i + 1, k + 1, name); + goto error; + } + } + if (!destination_found) { + sprintf(error_buf, "soundFont item #%u 'patchMappings' #%u is missing 'destination'", i + 1, k + 1); + goto error; + } + if (destination_found > 1) { + sprintf(error_buf, "soundFont item #%u 'patchMappings' #%u contains more than one 'destination'", i + 1, k + 1); + goto error; + } + if (source_found > 1) { + sprintf(error_buf, "soundFont item #%u 'patchMappings' #%u contains more than one 'source'", i + 1, k + 1); + } + } + patches_needed *= l; + } + presets_to_allocate += patches_needed; + } + + rval->count = presets_to_allocate; + rval->presets = calloc(sizeof(BASS_MIDI_FONTEX), rval->count); + + if (!rval->presets) { + strcpy(error_buf, "Out of memory"); + goto error; + } + + preset_number = 0; + + for (i = arr->u.array.length, j = 0; i--; ++j) { + const json_value * obj = arr->u.array.values[i]; + const json_value * path = json_object_item(obj, "fileName"); + const json_value * gain = json_object_item(obj, "gain"); + const json_value * channels = json_object_item(obj, "channels"); + const json_value * patchMappings = json_object_item(obj, "patchMappings"); + const void * bass_path; + const char * path_ptr = path->u.string.ptr; + unsigned int bass_flags = 0; +#ifdef _WIN32 + if (!isletter(*path_ptr) || path_ptr[1] != ':') { + if (strlen(path_ptr) + (base_path_end - base_path + 2) > 32767) { + strcpy(error_buf, "Base path plus SoundFont relative path is longer than 32767 characters"); + goto error; + } + strcpy(path_temp, base_path); + if (*base_path_end != '\\' && *base_path_end != '/') + strcat(path_temp, "\\"); + strcat(path_temp, path_ptr); + path_ptr = path_temp; + } + MultiByteToWideChar(CP_UTF8, 0, path_ptr, -1, path16, 32767); + path16[32767] = '\0'; + bass_path = (void *) path16; + bass_flags = BASS_UNICODE; +#else + if (*path_ptr != '/') { + if (strlen(path_ptr) + (base_path_end - base_path + 2) > 32767) { + strcpy(error_buf, "Base path plus SoundFont relative path is longer than 32767 characters"); + goto error; + } + strcpy(path_temp, base_path); + if (*base_path_end != '/') + strcat(path_temp, "/"); + strcat(path_temp, path_ptr); + path_ptr = path_temp; + } + bass_path = (void *) path_ptr; +#endif + hfont = BASS_MIDI_FontInit(bass_path, bass_flags); + if (!hfont) { + int error_code = BASS_ErrorGetCode(); + if (error_code == BASS_ERROR_FILEOPEN) { + sprintf(error_buf, "Could not open SoundFont bank '%s'", path->u.string.ptr); + goto error; + } + else if (error_code == BASS_ERROR_FILEFORM) { + sprintf(error_buf, "SoundFont bank '%s' is not a supported format", path->u.string.ptr); + goto error; + } + else { + sprintf(error_buf, "SoundFont bank '%s' failed to load with error #%u", path->u.string.ptr, error_code); + goto error; + } + } + if (gain->type != json_none) { + double gain_value = 0.0; + if (gain->type == json_integer) { + gain_value = (double)gain->u.integer; + } + else if (gain->type == json_double) { + gain_value = gain->u.dbl; + } + gain_value = pow(10.0, gain_value / 20.0); + BASS_MIDI_FontSetVolume(hfont, gain_value); + } + fontex.font = hfont; + fontex.spreset = -1; + fontex.sbank = -1; + fontex.dpreset = -1; + fontex.dbank = 0; + fontex.dbanklsb = 0; + /* Simplest case, whole bank loading */ + if (channels->type == json_none && patchMappings->type == json_none) { + rval->presets[preset_number++] = fontex; + } + else if (patchMappings->type == json_none) { + for (k = 0, l = channels->u.array.length; k < l; ++k) { + fontex.dbanklsb = channels->u.array.values[k]->u.integer; + rval->presets[preset_number++] = fontex; + } + } + else if (channels->type == json_none) { + sflist_process_patchmappings(rval->presets + preset_number, &fontex, patchMappings, 0); + preset_number += patchMappings->u.array.length; + } + else { + for (k = 0, l = channels->u.array.length; k < l; ++k) { + sflist_process_patchmappings(rval->presets + preset_number, &fontex, patchMappings, channels->u.array.values[k]->u.integer); + preset_number += patchMappings->u.array.length; + } + } + } + + return rval; + +error: + if (hfont) { + BASS_MIDI_FontFree(hfont); + } + if (rval) { + sflist_free(rval); + } + return 0; +} + +static int strpbrkn_all(const char * str, size_t size, const char * chrs) +{ + const char * end = str + size; + + while (str < end && *chrs) { + while (str < end && *str != *chrs) ++str; + ++str, ++chrs; + } + + return str < end; +} + +sflist_presets * sflist_load(const char * sflist, size_t size, const char * base_path, char * error) +{ + sflist_presets * rval; + + json_value * list = 0; + + list = sflist_load_v2(sflist, size, error); + + if (!list) { + if (!strpbrkn_all(sflist, size, "{[]}")) + list = sflist_load_v1(sflist, size, error); + } + + if (!list) { + return 0; + } + + rval = sflist_process(list, base_path, error); + + json_builder_free(list); + + return rval; +} + +void sflist_free(sflist_presets *presetlist) +{ + if (presetlist) { + if (presetlist->presets) { + unsigned int i, j; + for (i = 0, j = presetlist->count; i < j; ++i) { + HSOUNDFONT hfont = presetlist->presets[i].font; + if (hfont) { + BASS_MIDI_FontFree(hfont); + } + } + free(presetlist->presets); + } + free(presetlist); + } +} + +const char * sflist_upgrade(const char * sflist, size_t size, char * error_buf) +{ + char * rval = 0; + + json_value * list = 0; + + size_t length = 0; + + const json_serialize_opts opts = + { + json_serialize_mode_multiline, + 0, + 3 /* indent_size */ + }; + + list = sflist_load_v2(sflist, size, error_buf); + + if (!list) { + if (!strpbrkn_all(sflist, size, "{[]}")) + list = sflist_load_v1(sflist, size, error_buf); + } + + if (!list) { + return 0; + } + + length = json_measure_ex( list, opts ); + + rval = (char *) malloc( length + 1 ); + + if (!rval) { + strcpy(error_buf, "Out of memory"); + goto error; + } + + json_serialize_ex( rval, list, opts ); + + json_builder_free( list ); + + rval[ length ] = '\0'; + + return (const char *) rval; + +error: + if ( list ) { + json_builder_free( list ); + } + return 0; +} + +void sflist_upgrade_free(const char *ptr) +{ + free( (void *) ptr ); +} + diff --git a/ThirdParty/BASS/sflist.h b/ThirdParty/BASS/sflist.h new file mode 100644 index 000000000..080f13be3 --- /dev/null +++ b/ThirdParty/BASS/sflist.h @@ -0,0 +1,62 @@ +/* vim: set et ts=3 sw=3 sts=3 ft=c: + * + * Copyright (C) 2016 Christopher Snowhill. All rights reserved. + * https://github.com/kode54/sflist + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 _SFLIST_H +#define _SFLIST_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct sflist_presets +{ + unsigned int count; + BASS_MIDI_FONTEX * presets; +} sflist_presets; + +#define sflist_max_error 1024 + +sflist_presets * sflist_load(const char * sflist, size_t size, const char * base_path, char * error); +void sflist_free(sflist_presets *); + +const char * sflist_upgrade(const char * sflist, size_t size, char * error); +void sflist_upgrade_free(const char *); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/ThirdParty/BASS/sflist_rewrite.c b/ThirdParty/BASS/sflist_rewrite.c new file mode 100644 index 000000000..809ba548f --- /dev/null +++ b/ThirdParty/BASS/sflist_rewrite.c @@ -0,0 +1,93 @@ +/* vim: set et ts=3 sw=3 sts=3 ft=c: + * + * Copyright (C) 2016 Christopher Snowhill. All rights reserved. + * https://github.com/kode54/sflist + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 +#include +#include +#include + +#include "sflist.h" + +int main(int argc, const char ** argv) +{ + char error[sflist_max_error]; + char path_temp[32768]; + int i; + FILE *f; + size_t length; + char * in; + const char * out; + + for (i = 1; i < argc; ++i) { + const char * end = argv[i] + strlen(argv[i]); + if (((end - argv[i]) >= 5) && !strcmp(&end[-5], ".json")) continue; + strcpy(path_temp, argv[i]); + strcat(path_temp, ".json"); + f = fopen(argv[i], "r"); + fseek(f, 0, SEEK_END); + length = ftell(f); + fseek(f, 0, SEEK_SET); + in = malloc(length + 1); + if (!in) { + fclose(f); + fputs("Out of memory.\n", stderr); + return 1; + } + if (fread(in, length, 1, f) != 1) { + free(in); + fclose(f); + fprintf(stderr, "Cannot read all of file '%s'.\n", argv[i]); + return 1; + } + fclose(f); + in[length] = '\0'; + out = sflist_upgrade(in, length, error); + free(in); + if (!out) { + fprintf(stderr, "Error processing '%s': %s\n", argv[i], error); + return 1; + } + f = fopen(path_temp, "w"); + if (!f) { + sflist_upgrade_free(out); + fprintf(stderr, "Unable to open output file '%s'.\n", path_temp); + return 1; + } + if (fwrite(out, strlen(out), 1, f) != 1) { + fclose(f); + sflist_upgrade_free(out); + fprintf(stderr, "Unable to write to output file '%s'.\n", path_temp); + return 1; + } + fclose(f); + sflist_upgrade_free(out); + } + + return 0; +} diff --git a/ThirdParty/json/json-builder.c b/ThirdParty/json/json-builder.c new file mode 100644 index 000000000..949977a41 --- /dev/null +++ b/ThirdParty/json/json-builder.c @@ -0,0 +1,992 @@ + +/* vim: set et ts=3 sw=3 sts=3 ft=c: + * + * Copyright (C) 2014 James McLaughlin. All rights reserved. + * https://github.com/udp/json-builder + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 "json-builder.h" + +#include +#include +#include +#include + +#ifdef _MSC_VER + #define snprintf _snprintf +#endif + +const static json_serialize_opts default_opts = +{ + json_serialize_mode_single_line, + 0, + 3 /* indent_size */ +}; + +typedef struct json_builder_value +{ + json_value value; + + int is_builder_value; + + size_t additional_length_allocated; + size_t length_iterated; + +} json_builder_value; + +static int builderize (json_value * value) +{ + if (((json_builder_value *) value)->is_builder_value) + return 1; + + if (value->type == json_object) + { + unsigned int i; + + /* Values straight out of the parser have the names of object entries + * allocated in the same allocation as the values array itself. This is + * not desirable when manipulating values because the names would be easy + * to clobber. + */ + for (i = 0; i < value->u.object.length; ++ i) + { + json_char * name_copy; + json_object_entry * entry = &value->u.object.values [i]; + + if (! (name_copy = (json_char *) malloc ((entry->name_length + 1) * sizeof (json_char)))) + return 0; + + memcpy (name_copy, entry->name, entry->name_length + 1); + entry->name = name_copy; + } + } + + ((json_builder_value *) value)->is_builder_value = 1; + + return 1; +} + +const size_t json_builder_extra = sizeof(json_builder_value) - sizeof(json_value); + +/* These flags are set up from the opts before serializing to make the + * serializer conditions simpler. + */ +const int f_spaces_around_brackets = (1 << 0); +const int f_spaces_after_commas = (1 << 1); +const int f_spaces_after_colons = (1 << 2); +const int f_tabs = (1 << 3); + +int get_serialize_flags (json_serialize_opts opts) +{ + int flags = 0; + + if (opts.mode == json_serialize_mode_packed) + return 0; + + if (opts.mode == json_serialize_mode_multiline) + { + if (opts.opts & json_serialize_opt_use_tabs) + flags |= f_tabs; + } + else + { + if (! (opts.opts & json_serialize_opt_pack_brackets)) + flags |= f_spaces_around_brackets; + + if (! (opts.opts & json_serialize_opt_no_space_after_comma)) + flags |= f_spaces_after_commas; + } + + if (! (opts.opts & json_serialize_opt_no_space_after_colon)) + flags |= f_spaces_after_colons; + + return flags; +} + +json_value * json_array_new (size_t length) +{ + json_value * value = (json_value *) calloc (1, sizeof (json_builder_value)); + + if (!value) + return NULL; + + ((json_builder_value *) value)->is_builder_value = 1; + + value->type = json_array; + + if (! (value->u.array.values = (json_value **) malloc (length * sizeof (json_value *)))) + { + free (value); + return NULL; + } + + ((json_builder_value *) value)->additional_length_allocated = length; + + return value; +} + +json_value * json_array_push (json_value * array, json_value * value) +{ + assert (array->type == json_array); + + if (!builderize (array) || !builderize (value)) + return NULL; + + if (((json_builder_value *) array)->additional_length_allocated > 0) + { + -- ((json_builder_value *) array)->additional_length_allocated; + } + else + { + json_value ** values_new = (json_value **) realloc + (array->u.array.values, sizeof (json_value *) * (array->u.array.length + 1)); + + if (!values_new) + return NULL; + + array->u.array.values = values_new; + } + + array->u.array.values [array->u.array.length] = value; + ++ array->u.array.length; + + value->parent = array; + + return value; +} + +json_value * json_object_new (size_t length) +{ + json_value * value = (json_value *) calloc (1, sizeof (json_builder_value)); + + if (!value) + return NULL; + + ((json_builder_value *) value)->is_builder_value = 1; + + value->type = json_object; + + if (! (value->u.object.values = (json_object_entry *) calloc + (length, sizeof (*value->u.object.values)))) + { + free (value); + return NULL; + } + + ((json_builder_value *) value)->additional_length_allocated = length; + + return value; +} + +json_value * json_object_push (json_value * object, + const json_char * name, + json_value * value) +{ + return json_object_push_length (object, strlen (name), name, value); +} + +json_value * json_object_push_length (json_value * object, + unsigned int name_length, const json_char * name, + json_value * value) +{ + json_char * name_copy; + + assert (object->type == json_object); + + if (! (name_copy = (json_char *) malloc ((name_length + 1) * sizeof (json_char)))) + return NULL; + + memcpy (name_copy, name, name_length * sizeof (json_char)); + name_copy [name_length] = 0; + + if (!json_object_push_nocopy (object, name_length, name_copy, value)) + { + free (name_copy); + return NULL; + } + + return value; +} + +json_value * json_object_push_nocopy (json_value * object, + unsigned int name_length, json_char * name, + json_value * value) +{ + json_object_entry * entry; + + assert (object->type == json_object); + + if (!builderize (object) || !builderize (value)) + return NULL; + + if (((json_builder_value *) object)->additional_length_allocated > 0) + { + -- ((json_builder_value *) object)->additional_length_allocated; + } + else + { + json_object_entry * values_new = (json_object_entry *) + realloc (object->u.object.values, sizeof (*object->u.object.values) + * (object->u.object.length + 1)); + + if (!values_new) + return NULL; + + object->u.object.values = values_new; + } + + entry = object->u.object.values + object->u.object.length; + + entry->name_length = name_length; + entry->name = name; + entry->value = value; + + ++ object->u.object.length; + + value->parent = object; + + return value; +} + +json_value * json_string_new (const json_char * buf) +{ + return json_string_new_length (strlen (buf), buf); +} + +json_value * json_string_new_length (unsigned int length, const json_char * buf) +{ + json_value * value; + json_char * copy = (json_char *) malloc ((length + 1) * sizeof (json_char)); + + if (!copy) + return NULL; + + memcpy (copy, buf, length * sizeof (json_char)); + copy [length] = 0; + + if (! (value = json_string_new_nocopy (length, copy))) + { + free (copy); + return NULL; + } + + return value; +} + +json_value * json_string_new_nocopy (unsigned int length, json_char * buf) +{ + json_value * value = (json_value *) calloc (1, sizeof (json_builder_value)); + + if (!value) + return NULL; + + ((json_builder_value *) value)->is_builder_value = 1; + + value->type = json_string; + value->u.string.length = length; + value->u.string.ptr = buf; + + return value; +} + +json_value * json_integer_new (json_int_t integer) +{ + json_value * value = (json_value *) calloc (1, sizeof (json_builder_value)); + + if (!value) + return NULL; + + ((json_builder_value *) value)->is_builder_value = 1; + + value->type = json_integer; + value->u.integer = integer; + + return value; +} + +json_value * json_double_new (double dbl) +{ + json_value * value = (json_value *) calloc (1, sizeof (json_builder_value)); + + if (!value) + return NULL; + + ((json_builder_value *) value)->is_builder_value = 1; + + value->type = json_double; + value->u.dbl = dbl; + + return value; +} + +json_value * json_boolean_new (int b) +{ + json_value * value = (json_value *) calloc (1, sizeof (json_builder_value)); + + if (!value) + return NULL; + + ((json_builder_value *) value)->is_builder_value = 1; + + value->type = json_boolean; + value->u.boolean = b; + + return value; +} + +json_value * json_null_new () +{ + json_value * value = (json_value *) calloc (1, sizeof (json_builder_value)); + + if (!value) + return NULL; + + ((json_builder_value *) value)->is_builder_value = 1; + + value->type = json_null; + + return value; +} + +void json_object_sort (json_value * object, json_value * proto) +{ + unsigned int i, out_index = 0; + + if (!builderize (object)) + return; /* TODO error */ + + assert (object->type == json_object); + assert (proto->type == json_object); + + for (i = 0; i < proto->u.object.length; ++ i) + { + unsigned int j; + json_object_entry proto_entry = proto->u.object.values [i]; + + for (j = 0; j < object->u.object.length; ++ j) + { + json_object_entry entry = object->u.object.values [j]; + + if (entry.name_length != proto_entry.name_length) + continue; + + if (memcmp (entry.name, proto_entry.name, entry.name_length) != 0) + continue; + + object->u.object.values [j] = object->u.object.values [out_index]; + object->u.object.values [out_index] = entry; + + ++ out_index; + } + } +} + +json_value * json_object_merge (json_value * objectA, json_value * objectB) +{ + unsigned int i; + + assert (objectA->type == json_object); + assert (objectB->type == json_object); + assert (objectA != objectB); + + if (!builderize (objectA) || !builderize (objectB)) + return NULL; + + if (objectB->u.object.length <= + ((json_builder_value *) objectA)->additional_length_allocated) + { + ((json_builder_value *) objectA)->additional_length_allocated + -= objectB->u.object.length; + } + else + { + json_object_entry * values_new; + + unsigned int alloc = + objectA->u.object.length + + ((json_builder_value *) objectA)->additional_length_allocated + + objectB->u.object.length; + + if (! (values_new = (json_object_entry *) + realloc (objectA->u.object.values, sizeof (json_object_entry) * alloc))) + { + return NULL; + } + + objectA->u.object.values = values_new; + } + + for (i = 0; i < objectB->u.object.length; ++ i) + { + json_object_entry * entry = &objectA->u.object.values[objectA->u.object.length + i]; + + *entry = objectB->u.object.values[i]; + entry->value->parent = objectA; + } + + objectA->u.object.length += objectB->u.object.length; + + free (objectB->u.object.values); + free (objectB); + + return objectA; +} + +static size_t measure_string (unsigned int length, + const json_char * str) +{ + unsigned int i; + size_t measured_length = 0; + + for(i = 0; i < length; ++ i) + { + json_char c = str [i]; + + switch (c) + { + case '"': + case '\\': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + + measured_length += 2; + break; + + default: + + ++ measured_length; + break; + }; + }; + + return measured_length; +} + +#define PRINT_ESCAPED(c) do { \ + *buf ++ = '\\'; \ + *buf ++ = (c); \ +} while(0); \ + +static size_t serialize_string (json_char * buf, + unsigned int length, + const json_char * str) +{ + json_char * orig_buf = buf; + unsigned int i; + + for(i = 0; i < length; ++ i) + { + json_char c = str [i]; + + switch (c) + { + case '"': PRINT_ESCAPED ('\"'); continue; + case '\\': PRINT_ESCAPED ('\\'); continue; + case '\b': PRINT_ESCAPED ('b'); continue; + case '\f': PRINT_ESCAPED ('f'); continue; + case '\n': PRINT_ESCAPED ('n'); continue; + case '\r': PRINT_ESCAPED ('r'); continue; + case '\t': PRINT_ESCAPED ('t'); continue; + + default: + + *buf ++ = c; + break; + }; + }; + + return buf - orig_buf; +} + +size_t json_measure (json_value * value) +{ + return json_measure_ex (value, default_opts); +} + +#define MEASURE_NEWLINE() do { \ + ++ newlines; \ + indents += depth; \ +} while(0); \ + +size_t json_measure_ex (json_value * value, json_serialize_opts opts) +{ + size_t total = 1; /* null terminator */ + size_t newlines = 0; + size_t depth = 0; + size_t indents = 0; + int flags; + int bracket_size, comma_size, colon_size; + + flags = get_serialize_flags (opts); + + /* to reduce branching + */ + bracket_size = flags & f_spaces_around_brackets ? 2 : 1; + comma_size = flags & f_spaces_after_commas ? 2 : 1; + colon_size = flags & f_spaces_after_colons ? 2 : 1; + + while (value) + { + json_int_t integer; + json_object_entry * entry; + + switch (value->type) + { + case json_array: + + if (((json_builder_value *) value)->length_iterated == 0) + { + if (value->u.array.length == 0) + { + total += 2; /* `[]` */ + break; + } + + total += bracket_size; /* `[` */ + + ++ depth; + MEASURE_NEWLINE(); /* \n after [ */ + } + + if (((json_builder_value *) value)->length_iterated == value->u.array.length) + { + -- depth; + MEASURE_NEWLINE(); + total += bracket_size; /* `]` */ + + ((json_builder_value *) value)->length_iterated = 0; + break; + } + + if (((json_builder_value *) value)->length_iterated > 0) + { + total += comma_size; /* `, ` */ + + MEASURE_NEWLINE(); + } + + ((json_builder_value *) value)->length_iterated++; + value = value->u.array.values [((json_builder_value *) value)->length_iterated - 1]; + continue; + + case json_object: + + if (((json_builder_value *) value)->length_iterated == 0) + { + if (value->u.object.length == 0) + { + total += 2; /* `{}` */ + break; + } + + total += bracket_size; /* `{` */ + + ++ depth; + MEASURE_NEWLINE(); /* \n after { */ + } + + if (((json_builder_value *) value)->length_iterated == value->u.object.length) + { + -- depth; + MEASURE_NEWLINE(); + total += bracket_size; /* `}` */ + + ((json_builder_value *) value)->length_iterated = 0; + break; + } + + if (((json_builder_value *) value)->length_iterated > 0) + { + total += comma_size; /* `, ` */ + MEASURE_NEWLINE(); + } + + entry = value->u.object.values + (((json_builder_value *) value)->length_iterated ++); + + total += 2 + colon_size; /* `"": ` */ + total += measure_string (entry->name_length, entry->name); + + value = entry->value; + continue; + + case json_string: + + total += 2; /* `""` */ + total += measure_string (value->u.string.length, value->u.string.ptr); + break; + + case json_integer: + + integer = value->u.integer; + + if (integer < 0) + { + total += 1; /* `-` */ + integer = - integer; + } + + ++ total; /* first digit */ + + while (integer >= 10) + { + ++ total; /* another digit */ + integer /= 10; + } + + break; + + case json_double: + + total += snprintf (NULL, 0, "%g", value->u.dbl); + + if (value->u.dbl - floor (value->u.dbl) < 0.001) + total += 2; + + break; + + case json_boolean: + + total += value->u.boolean ? + 4: /* `true` */ + 5; /* `false` */ + + break; + + case json_null: + + total += 4; /* `null` */ + break; + + default: + break; + }; + + value = value->parent; + } + + if (opts.mode == json_serialize_mode_multiline) + { + total += newlines * (((opts.opts & json_serialize_opt_CRLF) ? 2 : 1) + opts.indent_size); + total += indents * opts.indent_size; + } + + return total; +} + +void json_serialize (json_char * buf, json_value * value) +{ + json_serialize_ex (buf, value, default_opts); +} + +#define PRINT_NEWLINE() do { \ + if (opts.mode == json_serialize_mode_multiline) { \ + if (opts.opts & json_serialize_opt_CRLF) \ + *buf ++ = '\r'; \ + *buf ++ = '\n'; \ + for(i = 0; i < indent; ++ i) \ + *buf ++ = indent_char; \ + } \ +} while(0); \ + +#define PRINT_OPENING_BRACKET(c) do { \ + *buf ++ = (c); \ + if (flags & f_spaces_around_brackets) \ + *buf ++ = ' '; \ +} while(0); \ + +#define PRINT_CLOSING_BRACKET(c) do { \ + if (flags & f_spaces_around_brackets) \ + *buf ++ = ' '; \ + *buf ++ = (c); \ +} while(0); \ + +void json_serialize_ex (json_char * buf, json_value * value, json_serialize_opts opts) +{ + json_int_t integer, orig_integer; + json_object_entry * entry; + json_char * ptr, * dot; + int indent = 0; + char indent_char; + int i; + int flags; + + flags = get_serialize_flags (opts); + + indent_char = flags & f_tabs ? '\t' : ' '; + + while (value) + { + switch (value->type) + { + case json_array: + + if (((json_builder_value *) value)->length_iterated == 0) + { + if (value->u.array.length == 0) + { + *buf ++ = '['; + *buf ++ = ']'; + + break; + } + + PRINT_OPENING_BRACKET ('['); + + indent += opts.indent_size; + PRINT_NEWLINE(); + } + + if (((json_builder_value *) value)->length_iterated == value->u.array.length) + { + indent -= opts.indent_size; + PRINT_NEWLINE(); + PRINT_CLOSING_BRACKET (']'); + + ((json_builder_value *) value)->length_iterated = 0; + break; + } + + if (((json_builder_value *) value)->length_iterated > 0) + { + *buf ++ = ','; + + if (flags & f_spaces_after_commas) + *buf ++ = ' '; + + PRINT_NEWLINE(); + } + + ((json_builder_value *) value)->length_iterated++; + value = value->u.array.values [((json_builder_value *) value)->length_iterated - 1]; + continue; + + case json_object: + + if (((json_builder_value *) value)->length_iterated == 0) + { + if (value->u.object.length == 0) + { + *buf ++ = '{'; + *buf ++ = '}'; + + break; + } + + PRINT_OPENING_BRACKET ('{'); + + indent += opts.indent_size; + PRINT_NEWLINE(); + } + + if (((json_builder_value *) value)->length_iterated == value->u.object.length) + { + indent -= opts.indent_size; + PRINT_NEWLINE(); + PRINT_CLOSING_BRACKET ('}'); + + ((json_builder_value *) value)->length_iterated = 0; + break; + } + + if (((json_builder_value *) value)->length_iterated > 0) + { + *buf ++ = ','; + + if (flags & f_spaces_after_commas) + *buf ++ = ' '; + + PRINT_NEWLINE(); + } + + entry = value->u.object.values + (((json_builder_value *) value)->length_iterated ++); + + *buf ++ = '\"'; + buf += serialize_string (buf, entry->name_length, entry->name); + *buf ++ = '\"'; + *buf ++ = ':'; + + if (flags & f_spaces_after_colons) + *buf ++ = ' '; + + value = entry->value; + continue; + + case json_string: + + *buf ++ = '\"'; + buf += serialize_string (buf, value->u.string.length, value->u.string.ptr); + *buf ++ = '\"'; + break; + + case json_integer: + + integer = value->u.integer; + + if (integer < 0) + { + *buf ++ = '-'; + integer = - integer; + } + + orig_integer = integer; + + ++ buf; + + while (integer >= 10) + { + ++ buf; + integer /= 10; + } + + integer = orig_integer; + ptr = buf; + + do + { + *-- ptr = "0123456789"[integer % 10]; + + } while ((integer /= 10) > 0); + + break; + + case json_double: + + ptr = buf; + + buf += sprintf (buf, "%g", value->u.dbl); + + if ((dot = strchr (ptr, ','))) + { + *dot = '.'; + } + else if (!strchr (ptr, '.')) + { + *buf ++ = '.'; + *buf ++ = '0'; + } + + break; + + case json_boolean: + + if (value->u.boolean) + { + memcpy (buf, "true", 4); + buf += 4; + } + else + { + memcpy (buf, "false", 5); + buf += 5; + } + + break; + + case json_null: + + memcpy (buf, "null", 4); + buf += 4; + break; + + default: + break; + }; + + value = value->parent; + } + + *buf = 0; +} + +void json_builder_free (json_value * value) +{ + json_value * cur_value; + + if (!value) + return; + + value->parent = 0; + + while (value) + { + switch (value->type) + { + case json_array: + + if (!value->u.array.length) + { + free (value->u.array.values); + break; + } + + value = value->u.array.values [-- value->u.array.length]; + continue; + + case json_object: + + if (!value->u.object.length) + { + free (value->u.object.values); + break; + } + + -- value->u.object.length; + + if (((json_builder_value *) value)->is_builder_value) + { + /* Names are allocated separately for builder values. In parser + * values, they are part of the same allocation as the values array + * itself. + */ + free (value->u.object.values [value->u.object.length].name); + } + + value = value->u.object.values [value->u.object.length].value; + continue; + + case json_string: + + free (value->u.string.ptr); + break; + + default: + break; + }; + + cur_value = value; + value = value->parent; + free (cur_value); + } +} + + + + + + diff --git a/ThirdParty/json/json-builder.h b/ThirdParty/json/json-builder.h new file mode 100644 index 000000000..50a56685d --- /dev/null +++ b/ThirdParty/json/json-builder.h @@ -0,0 +1,159 @@ + +/* vim: set et ts=3 sw=3 sts=3 ft=c: + * + * Copyright (C) 2014 James McLaughlin. All rights reserved. + * https://github.com/udp/json-builder + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 _JSON_BUILDER_H +#define _JSON_BUILDER_H + +/* Requires json.h from json-parser + * https://github.com/udp/json-parser + */ +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* IMPORTANT NOTE: If you want to use json-builder functions with values + * allocated by json-parser as part of the parsing process, you must pass + * json_builder_extra as the value_extra setting in json_settings when + * parsing. Otherwise there will not be room for the extra state and + * json-builder WILL invoke undefined behaviour. + * + * Also note that unlike json-parser, json-builder does not currently support + * custom allocators (for no particular reason other than that it doesn't have + * any settings or global state.) + */ +extern const size_t json_builder_extra; + + +/*** Arrays + *** + * Note that all of these length arguments are just a hint to allow for + * pre-allocation - passing 0 is fine. + */ +json_value * json_array_new (size_t length); +json_value * json_array_push (json_value * array, json_value *); + + +/*** Objects + ***/ +json_value * json_object_new (size_t length); + +json_value * json_object_push (json_value * object, + const json_char * name, + json_value *); + +/* Same as json_object_push, but doesn't call strlen() for you. + */ +json_value * json_object_push_length (json_value * object, + unsigned int name_length, const json_char * name, + json_value *); + +/* Same as json_object_push_length, but doesn't copy the name buffer before + * storing it in the value. Use this micro-optimisation at your own risk. + */ +json_value * json_object_push_nocopy (json_value * object, + unsigned int name_length, json_char * name, + json_value *); + +/* Merges all entries from objectB into objectA and destroys objectB. + */ +json_value * json_object_merge (json_value * objectA, json_value * objectB); + +/* Sort the entries of an object based on the order in a prototype object. + * Helpful when reading JSON and writing it again to preserve user order. + */ +void json_object_sort (json_value * object, json_value * proto); + + + +/*** Strings + ***/ +json_value * json_string_new (const json_char *); +json_value * json_string_new_length (unsigned int length, const json_char *); +json_value * json_string_new_nocopy (unsigned int length, json_char *); + + +/*** Everything else + ***/ +json_value * json_integer_new (json_int_t); +json_value * json_double_new (double); +json_value * json_boolean_new (int); +json_value * json_null_new (); + + +/*** Serializing + ***/ +#define json_serialize_mode_multiline 0 +#define json_serialize_mode_single_line 1 +#define json_serialize_mode_packed 2 + +#define json_serialize_opt_CRLF (1 << 1) +#define json_serialize_opt_pack_brackets (1 << 2) +#define json_serialize_opt_no_space_after_comma (1 << 3) +#define json_serialize_opt_no_space_after_colon (1 << 4) +#define json_serialize_opt_use_tabs (1 << 5) + +typedef struct json_serialize_opts +{ + int mode; + int opts; + int indent_size; + +} json_serialize_opts; + + +/* Returns a length in characters that is at least large enough to hold the + * value in its serialized form, including a null terminator. + */ +size_t json_measure (json_value *); +size_t json_measure_ex (json_value *, json_serialize_opts); + + +/* Serializes a JSON value into the buffer given (which must already be + * allocated with a length of at least json_measure(value, opts)) + */ +void json_serialize (json_char * buf, json_value *); +void json_serialize_ex (json_char * buf, json_value *, json_serialize_opts); + + +/*** Cleaning up + ***/ +void json_builder_free (json_value *); + +#ifdef __cplusplus +} +#endif + +#endif + + + diff --git a/ThirdParty/json/json.c b/ThirdParty/json/json.c new file mode 100644 index 000000000..6012bad7d --- /dev/null +++ b/ThirdParty/json/json.c @@ -0,0 +1,1011 @@ +/* vim: set et ts=3 sw=3 sts=3 ft=c: + * + * Copyright (C) 2012, 2013, 2014 James McLaughlin et al. All rights reserved. + * https://github.com/udp/json-parser + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 "json.h" + +#ifdef _MSC_VER + #ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS + #endif +#endif + +const struct _json_value json_value_none; + +#include +#include +#include +#include + +typedef unsigned int json_uchar; + +static unsigned char hex_value (json_char c) +{ + if (isdigit(c)) + return c - '0'; + + switch (c) { + case 'a': case 'A': return 0x0A; + case 'b': case 'B': return 0x0B; + case 'c': case 'C': return 0x0C; + case 'd': case 'D': return 0x0D; + case 'e': case 'E': return 0x0E; + case 'f': case 'F': return 0x0F; + default: return 0xFF; + } +} + +typedef struct +{ + unsigned long used_memory; + + unsigned int uint_max; + unsigned long ulong_max; + + json_settings settings; + int first_pass; + + const json_char * ptr; + unsigned int cur_line, cur_col; + +} json_state; + +static void * default_alloc (size_t size, int zero, void * user_data) +{ + return zero ? calloc (1, size) : malloc (size); +} + +static void default_free (void * ptr, void * user_data) +{ + free (ptr); +} + +static void * json_alloc (json_state * state, unsigned long size, int zero) +{ + if ((state->ulong_max - state->used_memory) < size) + return 0; + + if (state->settings.max_memory + && (state->used_memory += size) > state->settings.max_memory) + { + return 0; + } + + return state->settings.mem_alloc (size, zero, state->settings.user_data); +} + +static int new_value (json_state * state, + json_value ** top, json_value ** root, json_value ** alloc, + json_type type) +{ + json_value * value; + int values_size; + + if (!state->first_pass) + { + value = *top = *alloc; + *alloc = (*alloc)->_reserved.next_alloc; + + if (!*root) + *root = value; + + switch (value->type) + { + case json_array: + + if (value->u.array.length == 0) + break; + + if (! (value->u.array.values = (json_value **) json_alloc + (state, value->u.array.length * sizeof (json_value *), 0)) ) + { + return 0; + } + + value->u.array.length = 0; + break; + + case json_object: + + if (value->u.object.length == 0) + break; + + values_size = sizeof (*value->u.object.values) * value->u.object.length; + + if (! (value->u.object.values = (json_object_entry *) json_alloc + (state, values_size + ((unsigned long) value->u.object.values), 0)) ) + { + return 0; + } + + value->_reserved.object_mem = (*(char **) &value->u.object.values) + values_size; + + value->u.object.length = 0; + break; + + case json_string: + + if (! (value->u.string.ptr = (json_char *) json_alloc + (state, (value->u.string.length + 1) * sizeof (json_char), 0)) ) + { + return 0; + } + + value->u.string.length = 0; + break; + + default: + break; + }; + + return 1; + } + + if (! (value = (json_value *) json_alloc + (state, sizeof (json_value) + state->settings.value_extra, 1))) + { + return 0; + } + + if (!*root) + *root = value; + + value->type = type; + value->parent = *top; + + #ifdef JSON_TRACK_SOURCE + value->line = state->cur_line; + value->col = state->cur_col; + #endif + + if (*alloc) + (*alloc)->_reserved.next_alloc = value; + + *alloc = *top = value; + + return 1; +} + +#define whitespace \ + case '\n': ++ state.cur_line; state.cur_col = 0; \ + case ' ': case '\t': case '\r' + +#define string_add(b) \ + do { if (!state.first_pass) string [string_length] = b; ++ string_length; } while (0); + +#define line_and_col \ + state.cur_line, state.cur_col + +static const long + flag_next = 1 << 0, + flag_reproc = 1 << 1, + flag_need_comma = 1 << 2, + flag_seek_value = 1 << 3, + flag_escaped = 1 << 4, + flag_string = 1 << 5, + flag_need_colon = 1 << 6, + flag_done = 1 << 7, + flag_num_negative = 1 << 8, + flag_num_zero = 1 << 9, + flag_num_e = 1 << 10, + flag_num_e_got_sign = 1 << 11, + flag_num_e_negative = 1 << 12, + flag_line_comment = 1 << 13, + flag_block_comment = 1 << 14; + +json_value * json_parse_ex (json_settings * settings, + const json_char * json, + size_t length, + char * error_buf) +{ + json_char error [json_error_max]; + const json_char * end; + json_value * top, * root, * alloc = 0; + json_state state = { 0 }; + long flags; + long num_digits = 0, num_e = 0; + json_int_t num_fraction = 0; + + /* Skip UTF-8 BOM + */ + if (length >= 3 && ((unsigned char) json [0]) == 0xEF + && ((unsigned char) json [1]) == 0xBB + && ((unsigned char) json [2]) == 0xBF) + { + json += 3; + length -= 3; + } + + error[0] = '\0'; + end = (json + length); + + memcpy (&state.settings, settings, sizeof (json_settings)); + + if (!state.settings.mem_alloc) + state.settings.mem_alloc = default_alloc; + + if (!state.settings.mem_free) + state.settings.mem_free = default_free; + + memset (&state.uint_max, 0xFF, sizeof (state.uint_max)); + memset (&state.ulong_max, 0xFF, sizeof (state.ulong_max)); + + state.uint_max -= 8; /* limit of how much can be added before next check */ + state.ulong_max -= 8; + + for (state.first_pass = 1; state.first_pass >= 0; -- state.first_pass) + { + json_uchar uchar; + unsigned char uc_b1, uc_b2, uc_b3, uc_b4; + json_char * string = 0; + unsigned int string_length = 0; + + top = root = 0; + flags = flag_seek_value; + + state.cur_line = 1; + + for (state.ptr = json ;; ++ state.ptr) + { + json_char b = (state.ptr == end ? 0 : *state.ptr); + + if (flags & flag_string) + { + if (!b) + { sprintf (error, "Unexpected EOF in string (at %d:%d)", line_and_col); + goto e_failed; + } + + if (string_length > state.uint_max) + goto e_overflow; + + if (flags & flag_escaped) + { + flags &= ~ flag_escaped; + + switch (b) + { + case 'b': string_add ('\b'); break; + case 'f': string_add ('\f'); break; + case 'n': string_add ('\n'); break; + case 'r': string_add ('\r'); break; + case 't': string_add ('\t'); break; + case 'u': + + if (end - state.ptr < 4 || + (uc_b1 = hex_value (*++ state.ptr)) == 0xFF || + (uc_b2 = hex_value (*++ state.ptr)) == 0xFF || + (uc_b3 = hex_value (*++ state.ptr)) == 0xFF || + (uc_b4 = hex_value (*++ state.ptr)) == 0xFF) + { + sprintf (error, "Invalid character value `%c` (at %d:%d)", b, line_and_col); + goto e_failed; + } + + uc_b1 = (uc_b1 << 4) | uc_b2; + uc_b2 = (uc_b3 << 4) | uc_b4; + uchar = (uc_b1 << 8) | uc_b2; + + if ((uchar & 0xF800) == 0xD800) { + json_uchar uchar2; + + if (end - state.ptr < 6 || (*++ state.ptr) != '\\' || (*++ state.ptr) != 'u' || + (uc_b1 = hex_value (*++ state.ptr)) == 0xFF || + (uc_b2 = hex_value (*++ state.ptr)) == 0xFF || + (uc_b3 = hex_value (*++ state.ptr)) == 0xFF || + (uc_b4 = hex_value (*++ state.ptr)) == 0xFF) + { + sprintf (error, "Invalid character value `%c` (at %d:%d)", b, line_and_col); + goto e_failed; + } + + uc_b1 = (uc_b1 << 4) | uc_b2; + uc_b2 = (uc_b3 << 4) | uc_b4; + uchar2 = (uc_b1 << 8) | uc_b2; + + uchar = 0x010000 | ((uchar & 0x3FF) << 10) | (uchar2 & 0x3FF); + } + + if (sizeof (json_char) >= sizeof (json_uchar) || (uchar <= 0x7F)) + { + string_add ((json_char) uchar); + break; + } + + if (uchar <= 0x7FF) + { + if (state.first_pass) + string_length += 2; + else + { string [string_length ++] = 0xC0 | (uchar >> 6); + string [string_length ++] = 0x80 | (uchar & 0x3F); + } + + break; + } + + if (uchar <= 0xFFFF) { + if (state.first_pass) + string_length += 3; + else + { string [string_length ++] = 0xE0 | (uchar >> 12); + string [string_length ++] = 0x80 | ((uchar >> 6) & 0x3F); + string [string_length ++] = 0x80 | (uchar & 0x3F); + } + + break; + } + + if (state.first_pass) + string_length += 4; + else + { string [string_length ++] = 0xF0 | (uchar >> 18); + string [string_length ++] = 0x80 | ((uchar >> 12) & 0x3F); + string [string_length ++] = 0x80 | ((uchar >> 6) & 0x3F); + string [string_length ++] = 0x80 | (uchar & 0x3F); + } + + break; + + default: + string_add (b); + }; + + continue; + } + + if (b == '\\') + { + flags |= flag_escaped; + continue; + } + + if (b == '"') + { + if (!state.first_pass) + string [string_length] = 0; + + flags &= ~ flag_string; + string = 0; + + switch (top->type) + { + case json_string: + + top->u.string.length = string_length; + flags |= flag_next; + + break; + + case json_object: + + if (state.first_pass) + (*(json_char **) &top->u.object.values) += string_length + 1; + else + { + top->u.object.values [top->u.object.length].name + = (json_char *) top->_reserved.object_mem; + + top->u.object.values [top->u.object.length].name_length + = string_length; + + (*(json_char **) &top->_reserved.object_mem) += string_length + 1; + } + + flags |= flag_seek_value | flag_need_colon; + continue; + + default: + break; + }; + } + else + { + string_add (b); + continue; + } + } + + if (state.settings.settings & json_enable_comments) + { + if (flags & (flag_line_comment | flag_block_comment)) + { + if (flags & flag_line_comment) + { + if (b == '\r' || b == '\n' || !b) + { + flags &= ~ flag_line_comment; + -- state.ptr; /* so null can be reproc'd */ + } + + continue; + } + + if (flags & flag_block_comment) + { + if (!b) + { sprintf (error, "%d:%d: Unexpected EOF in block comment", line_and_col); + goto e_failed; + } + + if (b == '*' && state.ptr < (end - 1) && state.ptr [1] == '/') + { + flags &= ~ flag_block_comment; + ++ state.ptr; /* skip closing sequence */ + } + + continue; + } + } + else if (b == '/') + { + if (! (flags & (flag_seek_value | flag_done)) && top->type != json_object) + { sprintf (error, "%d:%d: Comment not allowed here", line_and_col); + goto e_failed; + } + + if (++ state.ptr == end) + { sprintf (error, "%d:%d: EOF unexpected", line_and_col); + goto e_failed; + } + + switch (b = *state.ptr) + { + case '/': + flags |= flag_line_comment; + continue; + + case '*': + flags |= flag_block_comment; + continue; + + default: + sprintf (error, "%d:%d: Unexpected `%c` in comment opening sequence", line_and_col, b); + goto e_failed; + }; + } + } + + if (flags & flag_done) + { + if (!b) + break; + + switch (b) + { + whitespace: + continue; + + default: + + sprintf (error, "%d:%d: Trailing garbage: `%c`", + state.cur_line, state.cur_col, b); + + goto e_failed; + }; + } + + if (flags & flag_seek_value) + { + switch (b) + { + whitespace: + continue; + + case ']': + + if (top && top->type == json_array) + flags = (flags & ~ (flag_need_comma | flag_seek_value)) | flag_next; + else + { sprintf (error, "%d:%d: Unexpected ]", line_and_col); + goto e_failed; + } + + break; + + default: + + if (flags & flag_need_comma) + { + if (b == ',') + { flags &= ~ flag_need_comma; + continue; + } + else + { + sprintf (error, "%d:%d: Expected , before %c", + state.cur_line, state.cur_col, b); + + goto e_failed; + } + } + + if (flags & flag_need_colon) + { + if (b == ':') + { flags &= ~ flag_need_colon; + continue; + } + else + { + sprintf (error, "%d:%d: Expected : before %c", + state.cur_line, state.cur_col, b); + + goto e_failed; + } + } + + flags &= ~ flag_seek_value; + + switch (b) + { + case '{': + + if (!new_value (&state, &top, &root, &alloc, json_object)) + goto e_alloc_failure; + + continue; + + case '[': + + if (!new_value (&state, &top, &root, &alloc, json_array)) + goto e_alloc_failure; + + flags |= flag_seek_value; + continue; + + case '"': + + if (!new_value (&state, &top, &root, &alloc, json_string)) + goto e_alloc_failure; + + flags |= flag_string; + + string = top->u.string.ptr; + string_length = 0; + + continue; + + case 't': + + if ((end - state.ptr) < 3 || *(++ state.ptr) != 'r' || + *(++ state.ptr) != 'u' || *(++ state.ptr) != 'e') + { + goto e_unknown_value; + } + + if (!new_value (&state, &top, &root, &alloc, json_boolean)) + goto e_alloc_failure; + + top->u.boolean = 1; + + flags |= flag_next; + break; + + case 'f': + + if ((end - state.ptr) < 4 || *(++ state.ptr) != 'a' || + *(++ state.ptr) != 'l' || *(++ state.ptr) != 's' || + *(++ state.ptr) != 'e') + { + goto e_unknown_value; + } + + if (!new_value (&state, &top, &root, &alloc, json_boolean)) + goto e_alloc_failure; + + flags |= flag_next; + break; + + case 'n': + + if ((end - state.ptr) < 3 || *(++ state.ptr) != 'u' || + *(++ state.ptr) != 'l' || *(++ state.ptr) != 'l') + { + goto e_unknown_value; + } + + if (!new_value (&state, &top, &root, &alloc, json_null)) + goto e_alloc_failure; + + flags |= flag_next; + break; + + default: + + if (isdigit (b) || b == '-') + { + if (!new_value (&state, &top, &root, &alloc, json_integer)) + goto e_alloc_failure; + + if (!state.first_pass) + { + while (isdigit (b) || b == '+' || b == '-' + || b == 'e' || b == 'E' || b == '.') + { + if ( (++ state.ptr) == end) + { + b = 0; + break; + } + + b = *state.ptr; + } + + flags |= flag_next | flag_reproc; + break; + } + + flags &= ~ (flag_num_negative | flag_num_e | + flag_num_e_got_sign | flag_num_e_negative | + flag_num_zero); + + num_digits = 0; + num_fraction = 0; + num_e = 0; + + if (b != '-') + { + flags |= flag_reproc; + break; + } + + flags |= flag_num_negative; + continue; + } + else + { sprintf (error, "%d:%d: Unexpected %c when seeking value", line_and_col, b); + goto e_failed; + } + }; + }; + } + else + { + switch (top->type) + { + case json_object: + + switch (b) + { + whitespace: + continue; + + case '"': + + if (flags & flag_need_comma) + { sprintf (error, "%d:%d: Expected , before \"", line_and_col); + goto e_failed; + } + + flags |= flag_string; + + string = (json_char *) top->_reserved.object_mem; + string_length = 0; + + break; + + case '}': + + flags = (flags & ~ flag_need_comma) | flag_next; + break; + + case ',': + + if (flags & flag_need_comma) + { + flags &= ~ flag_need_comma; + break; + } + + default: + sprintf (error, "%d:%d: Unexpected `%c` in object", line_and_col, b); + goto e_failed; + }; + + break; + + case json_integer: + case json_double: + + if (isdigit (b)) + { + ++ num_digits; + + if (top->type == json_integer || flags & flag_num_e) + { + if (! (flags & flag_num_e)) + { + if (flags & flag_num_zero) + { sprintf (error, "%d:%d: Unexpected `0` before `%c`", line_and_col, b); + goto e_failed; + } + + if (num_digits == 1 && b == '0') + flags |= flag_num_zero; + } + else + { + flags |= flag_num_e_got_sign; + num_e = (num_e * 10) + (b - '0'); + continue; + } + + top->u.integer = (top->u.integer * 10) + (b - '0'); + continue; + } + + num_fraction = (num_fraction * 10) + (b - '0'); + continue; + } + + if (b == '+' || b == '-') + { + if ( (flags & flag_num_e) && !(flags & flag_num_e_got_sign)) + { + flags |= flag_num_e_got_sign; + + if (b == '-') + flags |= flag_num_e_negative; + + continue; + } + } + else if (b == '.' && top->type == json_integer) + { + if (!num_digits) + { sprintf (error, "%d:%d: Expected digit before `.`", line_and_col); + goto e_failed; + } + + top->type = json_double; + top->u.dbl = (double) top->u.integer; + + num_digits = 0; + continue; + } + + if (! (flags & flag_num_e)) + { + if (top->type == json_double) + { + if (!num_digits) + { sprintf (error, "%d:%d: Expected digit after `.`", line_and_col); + goto e_failed; + } + + top->u.dbl += ((double) num_fraction) / (pow (10.0, (double) num_digits)); + } + + if (b == 'e' || b == 'E') + { + flags |= flag_num_e; + + if (top->type == json_integer) + { + top->type = json_double; + top->u.dbl = (double) top->u.integer; + } + + num_digits = 0; + flags &= ~ flag_num_zero; + + continue; + } + } + else + { + if (!num_digits) + { sprintf (error, "%d:%d: Expected digit after `e`", line_and_col); + goto e_failed; + } + + top->u.dbl *= pow (10.0, (double) + (flags & flag_num_e_negative ? - num_e : num_e)); + } + + if (flags & flag_num_negative) + { + if (top->type == json_integer) + top->u.integer = - top->u.integer; + else + top->u.dbl = - top->u.dbl; + } + + flags |= flag_next | flag_reproc; + break; + + default: + break; + }; + } + + if (flags & flag_reproc) + { + flags &= ~ flag_reproc; + -- state.ptr; + } + + if (flags & flag_next) + { + flags = (flags & ~ flag_next) | flag_need_comma; + + if (!top->parent) + { + /* root value done */ + + flags |= flag_done; + continue; + } + + if (top->parent->type == json_array) + flags |= flag_seek_value; + + if (!state.first_pass) + { + json_value * parent = top->parent; + + switch (parent->type) + { + case json_object: + + parent->u.object.values + [parent->u.object.length].value = top; + + break; + + case json_array: + + parent->u.array.values + [parent->u.array.length] = top; + + break; + + default: + break; + }; + } + + if ( (++ top->parent->u.array.length) > state.uint_max) + goto e_overflow; + + top = top->parent; + + continue; + } + } + + alloc = root; + } + + return root; + +e_unknown_value: + + sprintf (error, "%d:%d: Unknown value", line_and_col); + goto e_failed; + +e_alloc_failure: + + strcpy (error, "Memory allocation failure"); + goto e_failed; + +e_overflow: + + sprintf (error, "%d:%d: Too long (caught overflow)", line_and_col); + goto e_failed; + +e_failed: + + if (error_buf) + { + if (*error) + strcpy (error_buf, error); + else + strcpy (error_buf, "Unknown error"); + } + + if (state.first_pass) + alloc = root; + + while (alloc) + { + top = alloc->_reserved.next_alloc; + state.settings.mem_free (alloc, state.settings.user_data); + alloc = top; + } + + if (!state.first_pass) + json_value_free_ex (&state.settings, root); + + return 0; +} + +json_value * json_parse (const json_char * json, size_t length) +{ + json_settings settings = { 0 }; + return json_parse_ex (&settings, json, length, 0); +} + +void json_value_free_ex (json_settings * settings, json_value * value) +{ + json_value * cur_value; + + if (!value) + return; + + value->parent = 0; + + while (value) + { + switch (value->type) + { + case json_array: + + if (!value->u.array.length) + { + settings->mem_free (value->u.array.values, settings->user_data); + break; + } + + value = value->u.array.values [-- value->u.array.length]; + continue; + + case json_object: + + if (!value->u.object.length) + { + settings->mem_free (value->u.object.values, settings->user_data); + break; + } + + value = value->u.object.values [-- value->u.object.length].value; + continue; + + case json_string: + + settings->mem_free (value->u.string.ptr, settings->user_data); + break; + + default: + break; + }; + + cur_value = value; + value = value->parent; + settings->mem_free (cur_value, settings->user_data); + } +} + +void json_value_free (json_value * value) +{ + json_settings settings = { 0 }; + settings.mem_free = default_free; + json_value_free_ex (&settings, value); +} + diff --git a/ThirdParty/json/json.h b/ThirdParty/json/json.h new file mode 100644 index 000000000..f6549ec4e --- /dev/null +++ b/ThirdParty/json/json.h @@ -0,0 +1,283 @@ + +/* vim: set et ts=3 sw=3 sts=3 ft=c: + * + * Copyright (C) 2012, 2013, 2014 James McLaughlin et al. All rights reserved. + * https://github.com/udp/json-parser + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 _JSON_H +#define _JSON_H + +#ifndef json_char + #define json_char char +#endif + +#ifndef json_int_t + #ifndef _MSC_VER + #include + #define json_int_t int64_t + #else + #define json_int_t __int64 + #endif +#endif + +#include + +#ifdef __cplusplus + + #include + + extern "C" + { + +#endif + +typedef struct +{ + unsigned long max_memory; + int settings; + + /* Custom allocator support (leave null to use malloc/free) + */ + + void * (* mem_alloc) (size_t, int zero, void * user_data); + void (* mem_free) (void *, void * user_data); + + void * user_data; /* will be passed to mem_alloc and mem_free */ + + size_t value_extra; /* how much extra space to allocate for values? */ + +} json_settings; + +#define json_enable_comments 0x01 + +typedef enum +{ + json_none, + json_object, + json_array, + json_integer, + json_double, + json_string, + json_boolean, + json_null + +} json_type; + +extern const struct _json_value json_value_none; + +typedef struct _json_object_entry +{ + json_char * name; + unsigned int name_length; + + struct _json_value * value; + +} json_object_entry; + +typedef struct _json_value +{ + struct _json_value * parent; + + json_type type; + + union + { + int boolean; + json_int_t integer; + double dbl; + + struct + { + unsigned int length; + json_char * ptr; /* null terminated */ + + } string; + + struct + { + unsigned int length; + + json_object_entry * values; + + #if defined(__cplusplus) && __cplusplus >= 201103L + decltype(values) begin () const + { return values; + } + decltype(values) end () const + { return values + length; + } + #endif + + } object; + + struct + { + unsigned int length; + struct _json_value ** values; + + #if defined(__cplusplus) && __cplusplus >= 201103L + decltype(values) begin () const + { return values; + } + decltype(values) end () const + { return values + length; + } + #endif + + } array; + + } u; + + union + { + struct _json_value * next_alloc; + void * object_mem; + + } _reserved; + + #ifdef JSON_TRACK_SOURCE + + /* Location of the value in the source JSON + */ + unsigned int line, col; + + #endif + + + /* Some C++ operator sugar */ + + #ifdef __cplusplus + + public: + + inline _json_value () + { memset (this, 0, sizeof (_json_value)); + } + + inline const struct _json_value &operator [] (int index) const + { + if (type != json_array || index < 0 + || ((unsigned int) index) >= u.array.length) + { + return json_value_none; + } + + return *u.array.values [index]; + } + + inline const struct _json_value &operator [] (const char * index) const + { + if (type != json_object) + return json_value_none; + + for (unsigned int i = 0; i < u.object.length; ++ i) + if (!strcmp (u.object.values [i].name, index)) + return *u.object.values [i].value; + + return json_value_none; + } + + inline operator const char * () const + { + switch (type) + { + case json_string: + return u.string.ptr; + + default: + return ""; + }; + } + + inline operator json_int_t () const + { + switch (type) + { + case json_integer: + return u.integer; + + case json_double: + return (json_int_t) u.dbl; + + default: + return 0; + }; + } + + inline operator bool () const + { + if (type != json_boolean) + return false; + + return u.boolean != 0; + } + + inline operator double () const + { + switch (type) + { + case json_integer: + return (double) u.integer; + + case json_double: + return u.dbl; + + default: + return 0; + }; + } + + #endif + +} json_value; + +json_value * json_parse (const json_char * json, + size_t length); + +#define json_error_max 128 +json_value * json_parse_ex (json_settings * settings, + const json_char * json, + size_t length, + char * error); + +void json_value_free (json_value *); + + +/* Not usually necessary, unless you used a custom mem_alloc and now want to + * use a custom mem_free. + */ +void json_value_free_ex (json_settings * settings, + json_value *); + + +#ifdef __cplusplus + } /* extern "C" */ +#endif + +#endif + +