├── dist.info ├── CMakeLists.txt ├── dkjson-2.5-2.rockspec ├── .travis.yml ├── speedtest.lua ├── versions.txt ├── cmake ├── FindLua.cmake ├── dist.cmake └── lua.cmake ├── readme.txt ├── jsontest.lua └── dkjson.lua /dist.info: -------------------------------------------------------------------------------- 1 | --- This file is part of LuaDist project 2 | 3 | name = "dkjson" 4 | version = "2.5" 5 | 6 | desc = "dkjson is a module for encoding and decoding JSON data. It supports UTF-8." 7 | author = "David Kolf" 8 | license = "MIT/X11" 9 | url = "http://dkolf.de/src/dkjson-lua.fsl/" 10 | maintainer = "Peter Drahoš" 11 | 12 | depends = { 13 | "lua >= 5.1" 14 | } 15 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2007-2013 LuaDist. 2 | # Created by Peter Drahoš 3 | # Redistribution and use of this file is allowed according to the terms of the MIT license. 4 | # For details see the COPYRIGHT file distributed with LuaDist. 5 | # Please note that the package source code is licensed under its own license. 6 | 7 | project ( dkjson NONE ) 8 | cmake_minimum_required ( VERSION 2.8 ) 9 | include ( cmake/dist.cmake ) 10 | include ( lua ) 11 | 12 | install_lua_module ( dkjson dkjson.lua ) 13 | install_example ( jsontest.lua speedtest.lua ) 14 | install_data ( versions.txt ) -------------------------------------------------------------------------------- /dkjson-2.5-2.rockspec: -------------------------------------------------------------------------------- 1 | package = "dkjson" 2 | version = "2.5-2" 3 | source = { 4 | url = "http://dkolf.de/src/dkjson-lua.fsl/tarball/dkjson-2.5.tar.gz?uuid=release_2_5", 5 | file = "dkjson-2.5.tar.gz" 6 | } 7 | description = { 8 | summary = "David Kolf's JSON module for Lua", 9 | detailed = [[ 10 | dkjson is a module for encoding and decoding JSON data. It supports UTF-8. 11 | 12 | JSON (JavaScript Object Notation) is a format for serializing data based 13 | on the syntax for JavaScript data structures. 14 | 15 | dkjson is written in Lua without any dependencies, but 16 | when LPeg is available dkjson uses it to speed up decoding. 17 | ]], 18 | homepage = "http://dkolf.de/src/dkjson-lua.fsl/", 19 | license = "MIT/X11" 20 | } 21 | dependencies = { 22 | "lua >= 5.1, < 5.4" 23 | } 24 | build = { 25 | type = "builtin", 26 | modules = { 27 | dkjson = "dkjson.lua" 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # 2 | # LuaDist Travis-CI Hook 3 | # 4 | 5 | # We assume C build environments 6 | language: C 7 | 8 | # Try using multiple Lua Implementations 9 | env: 10 | - TOOL="gcc" # Use native compiler (GCC usually) 11 | - TOOL="clang" # Use clang 12 | - TOOL="i686-w64-mingw32" # 32bit MinGW 13 | - TOOL="x86_64-w64-mingw32" # 64bit MinGW 14 | - TOOL="arm-linux-gnueabihf" # ARM hard-float (hf), linux 15 | 16 | # Crosscompile builds may fail 17 | matrix: 18 | allow_failures: 19 | - env: TOOL="i686-w64-mingw32" 20 | - env: TOOL="x86_64-w64-mingw32" 21 | - env: TOOL="arm-linux-gnueabihf" 22 | 23 | # Install dependencies 24 | install: 25 | - git clone git://github.com/LuaDist/Tools.git ~/_tools 26 | - ~/_tools/travis/travis install 27 | 28 | # Bootstap 29 | before_script: 30 | - ~/_tools/travis/travis bootstrap 31 | 32 | # Build the module 33 | script: 34 | - ~/_tools/travis/travis build 35 | 36 | # Execute additional tests or commands 37 | after_script: 38 | - ~/_tools/travis/travis test 39 | 40 | # Only watch the master branch 41 | branches: 42 | only: 43 | - master 44 | 45 | # Notify the LuaDist Dev group if needed 46 | notifications: 47 | recipients: 48 | - luadist-dev@googlegroups.com 49 | email: 50 | on_success: change 51 | on_failure: always 52 | -------------------------------------------------------------------------------- /speedtest.lua: -------------------------------------------------------------------------------- 1 | local encode, decode 2 | 3 | local test_module = ... -- command line argument 4 | --local test_module = 'cmj-json' 5 | --local test_module = 'dkjson' 6 | --local test_module = 'dkjson-nopeg' 7 | --local test_module = 'fleece' 8 | --local test_module = 'jf-json' 9 | --locel test_module = 'lua-yajl' 10 | --local test_module = 'mp-cjson' 11 | --local test_module = 'nm-json' 12 | --local test_module = 'sb-json' 13 | --local test_module = 'th-json' 14 | 15 | 16 | if test_module == 'cmj-json' then 17 | -- http://json.luaforge.net/ 18 | local json = require "cmjjson" -- renamed, the original file was just 'json' 19 | encode = json.encode 20 | decode = json.decode 21 | elseif test_module == 'dkjson' then 22 | -- http://chiselapp.com/user/dhkolf/repository/dkjson/ 23 | local dkjson = require "dkjson" 24 | encode = dkjson.encode 25 | decode = dkjson.decode 26 | elseif test_module == 'dkjson-nopeg' then 27 | package.preload["lpeg"] = function () error "lpeg disabled" end 28 | package.loaded["lpeg"] = nil 29 | lpeg = nil 30 | local dkjson = require "dkjson" 31 | encode = dkjson.encode 32 | decode = dkjson.decode 33 | elseif test_module == 'fleece' then 34 | -- http://www.eonblast.com/fleece/ 35 | local fleece = require "fleece" 36 | encode = function(x) return fleece.json(x, "E4") end 37 | elseif test_module == 'jf-json' then 38 | -- http://regex.info/blog/lua/json 39 | local json = require "jfjson" -- renamed, the original file was just 'JSON' 40 | encode = function(x) return json:encode(x) end 41 | decode = function(x) return json:decode(x) end 42 | elseif test_module == 'lua-yajl' then 43 | -- http://github.com/brimworks/lua-yajl 44 | local yajl = require ("yajl") 45 | encode = yajl.to_string 46 | decode = yajl.to_value 47 | elseif test_module == 'mp-cjson' then 48 | -- http://www.kyne.com.au/~mark/software/lua-cjson.php 49 | local json = require "cjson" 50 | encode = json.encode 51 | decode = json.decode 52 | elseif test_module == 'nm-json' then 53 | -- http://luaforge.net/projects/luajsonlib/ 54 | local json = require "LuaJSON" 55 | encode = json.encode or json.stringify 56 | decode = json.decode or json.parse 57 | elseif test_module == 'sb-json' then 58 | -- http://www.chipmunkav.com/downloads/Json.lua 59 | local json = require "sbjson" -- renamed, the original file was just 'Json' 60 | encode = json.Encode 61 | decode = json.Decode 62 | elseif test_module == 'th-json' then 63 | -- http://luaforge.net/projects/luajson/ 64 | local json = require "json" 65 | encode = json.encode 66 | decode = json.decode 67 | else 68 | print "No module specified" 69 | return 70 | end 71 | 72 | -- example data taken from 73 | -- http://de.wikipedia.org/wiki/JavaScript_Object_Notation 74 | 75 | local str = [[ 76 | { 77 | "Herausgeber": "Xema", 78 | "Nummer": "1234-5678-9012-3456", 79 | "Deckung": 26, 80 | "Währung": "EUR", 81 | "Inhaber": { 82 | "Name": "Mustermann", 83 | "Vorname": "Max", 84 | "männlich": true, 85 | "Depot": {}, 86 | "Hobbys": [ "Reiten", "Golfen", "Lesen" ], 87 | "Alter": 42, 88 | "Kinder": [0], 89 | "Partner": null 90 | } 91 | } 92 | ]] 93 | 94 | local tbl = { 95 | Herausgeber= "Xema", 96 | Nummer= "1234-5678-9012-3456", 97 | Deckung= 2e+6, 98 | ["Währung"]= "EUR", 99 | Inhaber= { 100 | Name= "Mustermann", 101 | Vorname= "Max", 102 | ["männlich"]= true, 103 | Depot= {}, 104 | Hobbys= { "Reiten", "Golfen", "Lesen" }, 105 | Alter= 42, 106 | Kinder= {}, 107 | Partner= nil 108 | --Partner= json.null 109 | } 110 | } 111 | 112 | local t1, t2 113 | 114 | if decode then 115 | t1 = os.clock () 116 | for i = 1,100000 do 117 | decode (str) 118 | end 119 | t2 = os.clock () 120 | print ("Decoding:", t2 - t1) 121 | end 122 | 123 | if encode then 124 | t1 = os.clock () 125 | for i = 1,100000 do 126 | encode (tbl) 127 | end 128 | t2 = os.clock () 129 | print ("Encoding:", t2 - t1) 130 | end 131 | 132 | -------------------------------------------------------------------------------- /versions.txt: -------------------------------------------------------------------------------- 1 | Version 2.5 (2014-04-28) 2 | =========== 3 | 4 | Changes since version 2.4: 5 | 6 | * Added customizable exception handling. 7 | * Decode input that contains JavaScript comments. 8 | 9 | Version 2.4 (2013-09-28) 10 | =========== 11 | 12 | Changes since version 2.3: 13 | 14 | * Fixed encoding and decoding of numbers in different numeric locales. 15 | * Prevent using version 0.11 of LPeg (causes segmentation faults on 16 | some systems). 17 | 18 | Version 1.3 (2013-09-28) 19 | =========== 20 | 21 | Changes since version 1.2: 22 | 23 | * Fixed encoding and decoding of numbers in different numeric locales. 24 | 25 | Version 2.3 (2013-04-14) 26 | =========== 27 | 28 | Changes since version 2.2: 29 | 30 | * Corrected the range of escaped characters. Among other characters 31 | U+2029 was missing, which would cause trouble when parsed by a 32 | JavaScript interpreter. 33 | * Added options to register the module table in a global variable. 34 | This is useful in environments where functions similar to require are 35 | not available. 36 | 37 | Version 1.2 (2013-04-14) 38 | =========== 39 | 40 | Changes since version 1.1: 41 | 42 | * Corrected the range of escaped characters. Among other characters 43 | U+2029 was missing, which would cause trouble when parsed by a 44 | JavaScript interpreter. 45 | * Locations for error messages were off by one in the first line. 46 | 47 | Version 2.2 (2012-04-28) 48 | =========== 49 | 50 | Changes since version 2.1: 51 | 52 | * __jsontype is only used for empty tables. 53 | * It is possible to decode tables without assigning metatables. 54 | * Locations for error messages were off by one in the first line. 55 | * There is no LPeg version of json.quotestring anymore. 56 | 57 | Version 2.1 (2011-07-08) 58 | =========== 59 | 60 | Changes since version 2.0: 61 | 62 | * Changed the documentation to Markdown format. 63 | * LPeg is now parsing only a single value at a time to avoid running 64 | out of Lua stack for big arrays and objects. 65 | * Read __tojson, __jsontype and __jsonorder even from blocked metatables 66 | through the debug module. 67 | * Fixed decoding single numbers (only affected the non-LPeg mode). 68 | * Corrected the range of escaped Unicode control characters. 69 | 70 | Version 1.1 (2011-07-08) 71 | =========== 72 | 73 | Changes since version 1.0: 74 | 75 | * The values NaN/+Inf/-Inf are recognised and encoded as "null" like in 76 | the original JavaScript implementation. 77 | * Read __tojson even from blocked metatables through the debug module. 78 | * Fixed decoding single numbers. 79 | * Corrected the range of escaped Unicode control characters. 80 | 81 | Version 2.0 (2011-05-31) 82 | =========== 83 | 84 | Changes since version 1.0: 85 | 86 | * Optional LPeg support. 87 | * Invalid input data for encoding raises errors instead of returning nil 88 | and the error message. (Invalid data for encoding is usually a 89 | programming error. Raising an error removes the work of explicitly 90 | checking the result). 91 | * The metatable field __jsontype can control whether a Lua table is 92 | encoded as a JSON array or object. (Mainly useful for empty tables). 93 | * When decoding, two metatables are created. One is used to mark the arrays 94 | while the other one is used for the objects. (The metatables are 95 | created once for each decoding operation to make sandboxing possible. 96 | However, you can specify your own metatables as arguments). 97 | * There are no spaces added any longer when encoding. 98 | * It is possible to explicitly sort keys for encoding by providing an array with key 99 | names to the option "keyorder" or the metatable field __jsonorder. 100 | * The values NaN/+Inf/-Inf are recognised and encoded as "null" like in 101 | the original JavaScript implementation. 102 | 103 | Version 1.0 104 | =========== 105 | 106 | Initial version, released 2010-08-28. 107 | 108 | -------------------------------------------------------------------------------- /cmake/FindLua.cmake: -------------------------------------------------------------------------------- 1 | # Locate Lua library 2 | # This module defines 3 | # LUA_EXECUTABLE, if found 4 | # LUA_FOUND, if false, do not try to link to Lua 5 | # LUA_LIBRARIES 6 | # LUA_INCLUDE_DIR, where to find lua.h 7 | # LUA_VERSION_STRING, the version of Lua found (since CMake 2.8.8) 8 | # 9 | # Note that the expected include convention is 10 | # #include "lua.h" 11 | # and not 12 | # #include 13 | # This is because, the lua location is not standardized and may exist 14 | # in locations other than lua/ 15 | 16 | #============================================================================= 17 | # Copyright 2007-2009 Kitware, Inc. 18 | # Modified to support Lua 5.2 by LuaDist 2012 19 | # 20 | # Distributed under the OSI-approved BSD License (the "License"); 21 | # see accompanying file Copyright.txt for details. 22 | # 23 | # This software is distributed WITHOUT ANY WARRANTY; without even the 24 | # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 25 | # See the License for more information. 26 | #============================================================================= 27 | # (To distribute this file outside of CMake, substitute the full 28 | # License text for the above reference.) 29 | # 30 | # The required version of Lua can be specified using the 31 | # standard syntax, e.g. FIND_PACKAGE(Lua 5.1) 32 | # Otherwise the module will search for any available Lua implementation 33 | 34 | # Always search for non-versioned lua first (recommended) 35 | SET(_POSSIBLE_LUA_INCLUDE include include/lua) 36 | SET(_POSSIBLE_LUA_EXECUTABLE lua) 37 | SET(_POSSIBLE_LUA_LIBRARY lua) 38 | 39 | # Determine possible naming suffixes (there is no standard for this) 40 | IF(Lua_FIND_VERSION_MAJOR AND Lua_FIND_VERSION_MINOR) 41 | SET(_POSSIBLE_SUFFIXES "${Lua_FIND_VERSION_MAJOR}${Lua_FIND_VERSION_MINOR}" "${Lua_FIND_VERSION_MAJOR}.${Lua_FIND_VERSION_MINOR}" "-${Lua_FIND_VERSION_MAJOR}.${Lua_FIND_VERSION_MINOR}") 42 | ELSE(Lua_FIND_VERSION_MAJOR AND Lua_FIND_VERSION_MINOR) 43 | SET(_POSSIBLE_SUFFIXES "52" "5.2" "-5.2" "51" "5.1" "-5.1") 44 | ENDIF(Lua_FIND_VERSION_MAJOR AND Lua_FIND_VERSION_MINOR) 45 | 46 | # Set up possible search names and locations 47 | FOREACH(_SUFFIX ${_POSSIBLE_SUFFIXES}) 48 | LIST(APPEND _POSSIBLE_LUA_INCLUDE "include/lua${_SUFFIX}") 49 | LIST(APPEND _POSSIBLE_LUA_EXECUTABLE "lua${_SUFFIX}") 50 | LIST(APPEND _POSSIBLE_LUA_LIBRARY "lua${_SUFFIX}") 51 | ENDFOREACH(_SUFFIX) 52 | 53 | # Find the lua executable 54 | FIND_PROGRAM(LUA_EXECUTABLE 55 | NAMES ${_POSSIBLE_LUA_EXECUTABLE} 56 | ) 57 | 58 | # Find the lua header 59 | FIND_PATH(LUA_INCLUDE_DIR lua.h 60 | HINTS 61 | $ENV{LUA_DIR} 62 | PATH_SUFFIXES ${_POSSIBLE_LUA_INCLUDE} 63 | PATHS 64 | ~/Library/Frameworks 65 | /Library/Frameworks 66 | /usr/local 67 | /usr 68 | /sw # Fink 69 | /opt/local # DarwinPorts 70 | /opt/csw # Blastwave 71 | /opt 72 | ) 73 | 74 | # Find the lua library 75 | FIND_LIBRARY(LUA_LIBRARY 76 | NAMES ${_POSSIBLE_LUA_LIBRARY} 77 | HINTS 78 | $ENV{LUA_DIR} 79 | PATH_SUFFIXES lib64 lib 80 | PATHS 81 | ~/Library/Frameworks 82 | /Library/Frameworks 83 | /usr/local 84 | /usr 85 | /sw 86 | /opt/local 87 | /opt/csw 88 | /opt 89 | ) 90 | 91 | IF(LUA_LIBRARY) 92 | # include the math library for Unix 93 | IF(UNIX AND NOT APPLE) 94 | FIND_LIBRARY(LUA_MATH_LIBRARY m) 95 | SET( LUA_LIBRARIES "${LUA_LIBRARY};${LUA_MATH_LIBRARY}" CACHE STRING "Lua Libraries") 96 | # For Windows and Mac, don't need to explicitly include the math library 97 | ELSE(UNIX AND NOT APPLE) 98 | SET( LUA_LIBRARIES "${LUA_LIBRARY}" CACHE STRING "Lua Libraries") 99 | ENDIF(UNIX AND NOT APPLE) 100 | ENDIF(LUA_LIBRARY) 101 | 102 | # Determine Lua version 103 | IF(LUA_INCLUDE_DIR AND EXISTS "${LUA_INCLUDE_DIR}/lua.h") 104 | FILE(STRINGS "${LUA_INCLUDE_DIR}/lua.h" lua_version_str REGEX "^#define[ \t]+LUA_RELEASE[ \t]+\"Lua .+\"") 105 | 106 | STRING(REGEX REPLACE "^#define[ \t]+LUA_RELEASE[ \t]+\"Lua ([^\"]+)\".*" "\\1" LUA_VERSION_STRING "${lua_version_str}") 107 | UNSET(lua_version_str) 108 | ENDIF() 109 | 110 | INCLUDE(FindPackageHandleStandardArgs) 111 | # handle the QUIETLY and REQUIRED arguments and set LUA_FOUND to TRUE if 112 | # all listed variables are TRUE 113 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(Lua 114 | REQUIRED_VARS LUA_LIBRARIES LUA_INCLUDE_DIR 115 | VERSION_VAR LUA_VERSION_STRING) 116 | 117 | MARK_AS_ADVANCED(LUA_INCLUDE_DIR LUA_LIBRARIES LUA_LIBRARY LUA_MATH_LIBRARY LUA_EXECUTABLE) 118 | 119 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | David Kolf's JSON module for Lua 5.1/5.2 2 | ======================================== 3 | 4 | *Version 2.5* 5 | 6 | In the default configuration this module writes no global values, not even 7 | the module table. Import it using 8 | 9 | json = require ("dkjson") 10 | 11 | In environments where `require` or a similiar function are not available 12 | and you cannot receive the return value of the module, you can set the 13 | option `register_global_module_table` to `true`. The module table will 14 | then be saved in the global variable with the name given by the option 15 | `global_module_name`. 16 | 17 | Exported functions and values: 18 | 19 | `json.encode (object [, state])` 20 | -------------------------------- 21 | 22 | Create a string representing the object. `Object` can be a table, 23 | a string, a number, a boolean, `nil`, `json.null` or any object with 24 | a function `__tojson` in its metatable. A table can only use strings 25 | and numbers as keys and its values have to be valid objects as 26 | well. It raises an error for any invalid data types or reference 27 | cycles. 28 | 29 | `state` is an optional table with the following fields: 30 | 31 | - `indent` 32 | When `indent` (a boolean) is set, the created string will contain 33 | newlines and indentations. Otherwise it will be one long line. 34 | - `keyorder` 35 | `keyorder` is an array to specify the ordering of keys in the 36 | encoded output. If an object has keys which are not in this array 37 | they are written after the sorted keys. 38 | - `level` 39 | This is the initial level of indentation used when `indent` is 40 | set. For each level two spaces are added. When absent it is set 41 | to 0. 42 | - `buffer` 43 | `buffer` is an array to store the strings for the result so they 44 | can be concatenated at once. When it isn't given, the encode 45 | function will create it temporary and will return the 46 | concatenated result. 47 | - `bufferlen` 48 | When `bufferlen` is set, it has to be the index of the last 49 | element of `buffer`. 50 | - `tables` 51 | `tables` is a set to detect reference cycles. It is created 52 | temporary when absent. Every table that is currently processed 53 | is used as key, the value is `true`. 54 | - `exception` 55 | When `exception` is given, it will be called whenever the encoder 56 | cannot encode a given value. 57 | The parameters are `reason`, `value`, `state` and `defaultmessage`. 58 | `reason` is either `"reference cycle"`, `"custom encoder failed"` or 59 | `"unsupported type"`. `value` is the original value that caused the 60 | exception, `state` is this state table, `defaultmessage` is the message 61 | of the error that would usually be raised. 62 | You can either return `true` and add directly to the buffer or you can 63 | return the string directly. To keep raising an error return `nil` and 64 | the desired error message. 65 | An example implementation for an exception function is given in 66 | `json.encodeexception`. 67 | 68 | When `state.buffer` was set, the return value will be `true` on 69 | success. Without `state.buffer` the return value will be a string. 70 | 71 | `json.decode (string [, position [, null]])` 72 | -------------------------------------------- 73 | 74 | Decode `string` starting at `position` or at 1 if `position` was 75 | omitted. 76 | 77 | `null` is an optional value to be returned for null values. The 78 | default is `nil`, but you could set it to `json.null` or any other 79 | value. 80 | 81 | The return values are the object or `nil`, the position of the next 82 | character that doesn't belong to the object, and in case of errors 83 | an error message. 84 | 85 | Two metatables are created. Every array or object that is decoded gets 86 | a metatable with the `__jsontype` field set to either `array` or 87 | `object`. If you want to provide your own metatables use the syntax 88 | 89 | json.decode (string, position, null, objectmeta, arraymeta) 90 | 91 | To prevent the assigning of metatables pass `nil`: 92 | 93 | json.decode (string, position, null, nil) 94 | 95 | `.__jsonorder` 96 | ------------------------- 97 | 98 | `__jsonorder` can overwrite the `keyorder` for a specific table. 99 | 100 | `.__jsontype` 101 | ------------------------ 102 | 103 | `__jsontype` can be either `"array"` or `"object"`. This value is only 104 | checked for empty tables. (The default for empty tables is `"array"`). 105 | 106 | `.__tojson (self, state)` 107 | ------------------------------------ 108 | 109 | You can provide your own `__tojson` function in a metatable. In this 110 | function you can either add directly to the buffer and return true, 111 | or you can return a string. On errors nil and a message should be 112 | returned. 113 | 114 | `json.null` 115 | ----------- 116 | 117 | You can use this value for setting explicit `null` values. 118 | 119 | `json.version` 120 | -------------- 121 | 122 | Set to `"dkjson 2.5"`. 123 | 124 | `json.quotestring (string)` 125 | --------------------------- 126 | 127 | Quote a UTF-8 string and escape critical characters using JSON 128 | escape sequences. This function is only necessary when you build 129 | your own `__tojson` functions. 130 | 131 | `json.addnewline (state)` 132 | ------------------------- 133 | 134 | When `state.indent` is set, add a newline to `state.buffer` and spaces 135 | according to `state.level`. 136 | 137 | `json.encodeexception (reason, value, state, defaultmessage)` 138 | ------------------------------------------------------------- 139 | 140 | This function can be used as value to the `exception` option. Instead of 141 | raising an error this function encodes the error message as a string. This 142 | can help to debug malformed input data. 143 | 144 | x = json.encode(value, { exception = json.encodeexception }) 145 | 146 | LPeg support 147 | ------------ 148 | 149 | When the local configuration variable `always_try_using_lpeg` is set, 150 | this module tries to load LPeg to replace the `decode` function. The 151 | speed increase is significant. You can get the LPeg module at 152 | . 153 | When LPeg couldn't be loaded, the pure Lua functions stay active. 154 | 155 | In case you don't want this module to require LPeg on its own, 156 | disable the option `always_try_using_lpeg` in the options section at 157 | the top of the module. 158 | 159 | In this case you can later load LPeg support using 160 | 161 | ### `json.use_lpeg ()` 162 | 163 | Require the LPeg module and replace the functions `quotestring` and 164 | and `decode` with functions that use LPeg patterns. 165 | This function returns the module table, so you can load the module 166 | using: 167 | 168 | json = require "dkjson".use_lpeg() 169 | 170 | Alternatively you can use `pcall` so the JSON module still works when 171 | LPeg isn't found. 172 | 173 | json = require "dkjson" 174 | pcall (json.use_lpeg) 175 | 176 | ### `json.using_lpeg` 177 | 178 | This variable is set to `true` when LPeg was loaded successfully. 179 | 180 | --------------------------------------------------------------------- 181 | 182 | Contact 183 | ------- 184 | 185 | You can contact the author by sending an e-mail to 'david' at the 186 | domain 'dkolf.de'. 187 | 188 | --------------------------------------------------------------------- 189 | 190 | *Copyright (C) 2010-2014 David Heiko Kolf* 191 | 192 | Permission is hereby granted, free of charge, to any person obtaining 193 | a copy of this software and associated documentation files (the 194 | "Software"), to deal in the Software without restriction, including 195 | without limitation the rights to use, copy, modify, merge, publish, 196 | distribute, sublicense, and/or sell copies of the Software, and to 197 | permit persons to whom the Software is furnished to do so, subject to 198 | the following conditions: 199 | 200 | The above copyright notice and this permission notice shall be 201 | included in all copies or substantial portions of the Software. 202 | 203 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 204 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 205 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 206 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 207 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 208 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 209 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 210 | SOFTWARE. 211 | 212 | -------------------------------------------------------------------------------- /cmake/dist.cmake: -------------------------------------------------------------------------------- 1 | # LuaDist CMake utility library. 2 | # Provides sane project defaults and macros common to LuaDist CMake builds. 3 | # 4 | # Copyright (C) 2007-2012 LuaDist. 5 | # by David Manura, Peter Drahoš 6 | # Redistribution and use of this file is allowed according to the terms of the MIT license. 7 | # For details see the COPYRIGHT file distributed with LuaDist. 8 | # Please note that the package source code is licensed under its own license. 9 | 10 | ## Extract information from dist.info 11 | if ( NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/dist.info ) 12 | message ( FATAL_ERROR 13 | "Missing dist.info file (${CMAKE_CURRENT_SOURCE_DIR}/dist.info)." ) 14 | endif () 15 | file ( READ ${CMAKE_CURRENT_SOURCE_DIR}/dist.info DIST_INFO ) 16 | if ( "${DIST_INFO}" STREQUAL "" ) 17 | message ( FATAL_ERROR "Failed to load dist.info." ) 18 | endif () 19 | # Reads field `name` from dist.info string `DIST_INFO` into variable `var`. 20 | macro ( _parse_dist_field name var ) 21 | string ( REGEX REPLACE ".*${name}[ \t]?=[ \t]?[\"']([^\"']+)[\"'].*" "\\1" 22 | ${var} "${DIST_INFO}" ) 23 | if ( ${var} STREQUAL DIST_INFO ) 24 | message ( FATAL_ERROR "Failed to extract \"${var}\" from dist.info" ) 25 | endif () 26 | endmacro () 27 | # 28 | _parse_dist_field ( name DIST_NAME ) 29 | _parse_dist_field ( version DIST_VERSION ) 30 | _parse_dist_field ( license DIST_LICENSE ) 31 | _parse_dist_field ( author DIST_AUTHOR ) 32 | _parse_dist_field ( maintainer DIST_MAINTAINER ) 33 | _parse_dist_field ( url DIST_URL ) 34 | _parse_dist_field ( desc DIST_DESC ) 35 | message ( "DIST_NAME: ${DIST_NAME}") 36 | message ( "DIST_VERSION: ${DIST_VERSION}") 37 | message ( "DIST_LICENSE: ${DIST_LICENSE}") 38 | message ( "DIST_AUTHOR: ${DIST_AUTHOR}") 39 | message ( "DIST_MAINTAINER: ${DIST_MAINTAINER}") 40 | message ( "DIST_URL: ${DIST_URL}") 41 | message ( "DIST_DESC: ${DIST_DESC}") 42 | string ( REGEX REPLACE ".*depends[ \t]?=[ \t]?[\"']([^\"']+)[\"'].*" "\\1" 43 | DIST_DEPENDS ${DIST_INFO} ) 44 | if ( DIST_DEPENDS STREQUAL DIST_INFO ) 45 | set ( DIST_DEPENDS "" ) 46 | endif () 47 | message ( "DIST_DEPENDS: ${DIST_DEPENDS}") 48 | ## 2DO: Parse DIST_DEPENDS and try to install Dependencies with automatically using externalproject_add 49 | 50 | 51 | ## INSTALL DEFAULTS (Relative to CMAKE_INSTALL_PREFIX) 52 | # Primary paths 53 | set ( INSTALL_BIN bin CACHE PATH "Where to install binaries to." ) 54 | set ( INSTALL_LIB lib CACHE PATH "Where to install libraries to." ) 55 | set ( INSTALL_INC include CACHE PATH "Where to install headers to." ) 56 | set ( INSTALL_ETC etc CACHE PATH "Where to store configuration files" ) 57 | set ( INSTALL_SHARE share CACHE PATH "Directory for shared data." ) 58 | 59 | # Secondary paths 60 | option ( INSTALL_VERSION 61 | "Install runtime libraries and executables with version information." OFF) 62 | set ( INSTALL_DATA ${INSTALL_SHARE}/${DIST_NAME} CACHE PATH 63 | "Directory the package can store documentation, tests or other data in.") 64 | set ( INSTALL_DOC ${INSTALL_DATA}/doc CACHE PATH 65 | "Recommended directory to install documentation into.") 66 | set ( INSTALL_EXAMPLE ${INSTALL_DATA}/example CACHE PATH 67 | "Recommended directory to install examples into.") 68 | set ( INSTALL_TEST ${INSTALL_DATA}/test CACHE PATH 69 | "Recommended directory to install tests into.") 70 | set ( INSTALL_FOO ${INSTALL_DATA}/etc CACHE PATH 71 | "Where to install additional files") 72 | 73 | # Tweaks and other defaults 74 | # Setting CMAKE to use loose block and search for find modules in source directory 75 | set ( CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS true ) 76 | set ( CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH} ) 77 | option ( BUILD_SHARED_LIBS "Build shared libraries" ON ) 78 | 79 | # In MSVC, prevent warnings that can occur when using standard libraries. 80 | if ( MSVC ) 81 | add_definitions ( -D_CRT_SECURE_NO_WARNINGS ) 82 | endif () 83 | 84 | # RPath and relative linking 85 | option ( USE_RPATH "Use relative linking." ON) 86 | if ( USE_RPATH ) 87 | string ( REGEX REPLACE "[^!/]+" ".." UP_DIR ${INSTALL_BIN} ) 88 | set ( CMAKE_SKIP_BUILD_RPATH FALSE CACHE STRING "" FORCE ) 89 | set ( CMAKE_BUILD_WITH_INSTALL_RPATH FALSE CACHE STRING "" FORCE ) 90 | set ( CMAKE_INSTALL_RPATH $ORIGIN/${UP_DIR}/${INSTALL_LIB} 91 | CACHE STRING "" FORCE ) 92 | set ( CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE CACHE STRING "" FORCE ) 93 | set ( CMAKE_INSTALL_NAME_DIR @executable_path/${UP_DIR}/${INSTALL_LIB} 94 | CACHE STRING "" FORCE ) 95 | endif () 96 | 97 | ## MACROS 98 | # Parser macro 99 | macro ( parse_arguments prefix arg_names option_names) 100 | set ( DEFAULT_ARGS ) 101 | foreach ( arg_name ${arg_names} ) 102 | set ( ${prefix}_${arg_name} ) 103 | endforeach () 104 | foreach ( option ${option_names} ) 105 | set ( ${prefix}_${option} FALSE ) 106 | endforeach () 107 | 108 | set ( current_arg_name DEFAULT_ARGS ) 109 | set ( current_arg_list ) 110 | foreach ( arg ${ARGN} ) 111 | set ( larg_names ${arg_names} ) 112 | list ( FIND larg_names "${arg}" is_arg_name ) 113 | if ( is_arg_name GREATER -1 ) 114 | set ( ${prefix}_${current_arg_name} ${current_arg_list} ) 115 | set ( current_arg_name ${arg} ) 116 | set ( current_arg_list ) 117 | else () 118 | set ( loption_names ${option_names} ) 119 | list ( FIND loption_names "${arg}" is_option ) 120 | if ( is_option GREATER -1 ) 121 | set ( ${prefix}_${arg} TRUE ) 122 | else () 123 | set ( current_arg_list ${current_arg_list} ${arg} ) 124 | endif () 125 | endif () 126 | endforeach () 127 | set ( ${prefix}_${current_arg_name} ${current_arg_list} ) 128 | endmacro () 129 | 130 | 131 | # install_executable ( executable_targets ) 132 | # Installs any executables generated using "add_executable". 133 | # USE: install_executable ( lua ) 134 | # NOTE: subdirectories are NOT supported 135 | set ( CPACK_COMPONENT_RUNTIME_DISPLAY_NAME "${DIST_NAME} Runtime" ) 136 | set ( CPACK_COMPONENT_RUNTIME_DESCRIPTION 137 | "Executables and runtime libraries. Installed into ${INSTALL_BIN}." ) 138 | macro ( install_executable ) 139 | foreach ( _file ${ARGN} ) 140 | if ( INSTALL_VERSION ) 141 | set_target_properties ( ${_file} PROPERTIES VERSION ${DIST_VERSION} 142 | SOVERSION ${DIST_VERSION} ) 143 | endif () 144 | install ( TARGETS ${_file} RUNTIME DESTINATION ${INSTALL_BIN} 145 | COMPONENT Runtime ) 146 | endforeach() 147 | endmacro () 148 | 149 | # install_library ( library_targets ) 150 | # Installs any libraries generated using "add_library" into apropriate places. 151 | # USE: install_library ( libexpat ) 152 | # NOTE: subdirectories are NOT supported 153 | set ( CPACK_COMPONENT_LIBRARY_DISPLAY_NAME "${DIST_NAME} Development Libraries" ) 154 | set ( CPACK_COMPONENT_LIBRARY_DESCRIPTION 155 | "Static and import libraries needed for development. Installed into ${INSTALL_LIB} or ${INSTALL_BIN}." ) 156 | macro ( install_library ) 157 | foreach ( _file ${ARGN} ) 158 | if ( INSTALL_VERSION ) 159 | set_target_properties ( ${_file} PROPERTIES VERSION ${DIST_VERSION} 160 | SOVERSION ${DIST_VERSION} ) 161 | endif () 162 | install ( TARGETS ${_file} 163 | RUNTIME DESTINATION ${INSTALL_BIN} COMPONENT Runtime 164 | LIBRARY DESTINATION ${INSTALL_LIB} COMPONENT Runtime 165 | ARCHIVE DESTINATION ${INSTALL_LIB} COMPONENT Library ) 166 | endforeach() 167 | endmacro () 168 | 169 | # helper function for various install_* functions, for PATTERN/REGEX args. 170 | macro ( _complete_install_args ) 171 | if ( NOT("${_ARG_PATTERN}" STREQUAL "") ) 172 | set ( _ARG_PATTERN PATTERN ${_ARG_PATTERN} ) 173 | endif () 174 | if ( NOT("${_ARG_REGEX}" STREQUAL "") ) 175 | set ( _ARG_REGEX REGEX ${_ARG_REGEX} ) 176 | endif () 177 | endmacro () 178 | 179 | # install_header ( files/directories [INTO destination] ) 180 | # Install a directories or files into header destination. 181 | # USE: install_header ( lua.h luaconf.h ) or install_header ( GL ) 182 | # USE: install_header ( mylib.h INTO mylib ) 183 | # For directories, supports optional PATTERN/REGEX arguments like install(). 184 | set ( CPACK_COMPONENT_HEADER_DISPLAY_NAME "${DIST_NAME} Development Headers" ) 185 | set ( CPACK_COMPONENT_HEADER_DESCRIPTION 186 | "Headers needed for development. Installed into ${INSTALL_INC}." ) 187 | macro ( install_header ) 188 | parse_arguments ( _ARG "INTO;PATTERN;REGEX" "" ${ARGN} ) 189 | _complete_install_args() 190 | foreach ( _file ${_ARG_DEFAULT_ARGS} ) 191 | if ( IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${_file}" ) 192 | install ( DIRECTORY ${_file} DESTINATION ${INSTALL_INC}/${_ARG_INTO} 193 | COMPONENT Header ${_ARG_PATTERN} ${_ARG_REGEX} ) 194 | else () 195 | install ( FILES ${_file} DESTINATION ${INSTALL_INC}/${_ARG_INTO} 196 | COMPONENT Header ) 197 | endif () 198 | endforeach() 199 | endmacro () 200 | 201 | # install_data ( files/directories [INTO destination] ) 202 | # This installs additional data files or directories. 203 | # USE: install_data ( extra data.dat ) 204 | # USE: install_data ( image1.png image2.png INTO images ) 205 | # For directories, supports optional PATTERN/REGEX arguments like install(). 206 | set ( CPACK_COMPONENT_DATA_DISPLAY_NAME "${DIST_NAME} Data" ) 207 | set ( CPACK_COMPONENT_DATA_DESCRIPTION 208 | "Application data. Installed into ${INSTALL_DATA}." ) 209 | macro ( install_data ) 210 | parse_arguments ( _ARG "INTO;PATTERN;REGEX" "" ${ARGN} ) 211 | _complete_install_args() 212 | foreach ( _file ${_ARG_DEFAULT_ARGS} ) 213 | if ( IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${_file}" ) 214 | install ( DIRECTORY ${_file} 215 | DESTINATION ${INSTALL_DATA}/${_ARG_INTO} 216 | COMPONENT Data ${_ARG_PATTERN} ${_ARG_REGEX} ) 217 | else () 218 | install ( FILES ${_file} DESTINATION ${INSTALL_DATA}/${_ARG_INTO} 219 | COMPONENT Data ) 220 | endif () 221 | endforeach() 222 | endmacro () 223 | 224 | # INSTALL_DOC ( files/directories [INTO destination] ) 225 | # This installs documentation content 226 | # USE: install_doc ( doc/ doc.pdf ) 227 | # USE: install_doc ( index.html INTO html ) 228 | # For directories, supports optional PATTERN/REGEX arguments like install(). 229 | set ( CPACK_COMPONENT_DOCUMENTATION_DISPLAY_NAME "${DIST_NAME} Documentation" ) 230 | set ( CPACK_COMPONENT_DOCUMENTATION_DESCRIPTION 231 | "Application documentation. Installed into ${INSTALL_DOC}." ) 232 | macro ( install_doc ) 233 | parse_arguments ( _ARG "INTO;PATTERN;REGEX" "" ${ARGN} ) 234 | _complete_install_args() 235 | foreach ( _file ${_ARG_DEFAULT_ARGS} ) 236 | if ( IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${_file}" ) 237 | install ( DIRECTORY ${_file} DESTINATION ${INSTALL_DOC}/${_ARG_INTO} 238 | COMPONENT Documentation ${_ARG_PATTERN} ${_ARG_REGEX} ) 239 | else () 240 | install ( FILES ${_file} DESTINATION ${INSTALL_DOC}/${_ARG_INTO} 241 | COMPONENT Documentation ) 242 | endif () 243 | endforeach() 244 | endmacro () 245 | 246 | # install_example ( files/directories [INTO destination] ) 247 | # This installs additional examples 248 | # USE: install_example ( examples/ exampleA ) 249 | # USE: install_example ( super_example super_data INTO super) 250 | # For directories, supports optional PATTERN/REGEX argument like install(). 251 | set ( CPACK_COMPONENT_EXAMPLE_DISPLAY_NAME "${DIST_NAME} Examples" ) 252 | set ( CPACK_COMPONENT_EXAMPLE_DESCRIPTION 253 | "Examples and their associated data. Installed into ${INSTALL_EXAMPLE}." ) 254 | macro ( install_example ) 255 | parse_arguments ( _ARG "INTO;PATTERN;REGEX" "" ${ARGN} ) 256 | _complete_install_args() 257 | foreach ( _file ${_ARG_DEFAULT_ARGS} ) 258 | if ( IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${_file}" ) 259 | install ( DIRECTORY ${_file} DESTINATION ${INSTALL_EXAMPLE}/${_ARG_INTO} 260 | COMPONENT Example ${_ARG_PATTERN} ${_ARG_REGEX} ) 261 | else () 262 | install ( FILES ${_file} DESTINATION ${INSTALL_EXAMPLE}/${_ARG_INTO} 263 | COMPONENT Example ) 264 | endif () 265 | endforeach() 266 | endmacro () 267 | 268 | # install_test ( files/directories [INTO destination] ) 269 | # This installs tests and test files, DOES NOT EXECUTE TESTS 270 | # USE: install_test ( my_test data.sql ) 271 | # USE: install_test ( feature_x_test INTO x ) 272 | # For directories, supports optional PATTERN/REGEX argument like install(). 273 | set ( CPACK_COMPONENT_TEST_DISPLAY_NAME "${DIST_NAME} Tests" ) 274 | set ( CPACK_COMPONENT_TEST_DESCRIPTION 275 | "Tests and associated data. Installed into ${INSTALL_TEST}." ) 276 | macro ( install_test ) 277 | parse_arguments ( _ARG "INTO;PATTERN;REGEX" "" ${ARGN} ) 278 | _complete_install_args() 279 | foreach ( _file ${_ARG_DEFAULT_ARGS} ) 280 | if ( IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${_file}" ) 281 | install ( DIRECTORY ${_file} DESTINATION ${INSTALL_TEST}/${_ARG_INTO} 282 | COMPONENT Test ${_ARG_PATTERN} ${_ARG_REGEX} ) 283 | else () 284 | install ( FILES ${_file} DESTINATION ${INSTALL_TEST}/${_ARG_INTO} 285 | COMPONENT Test ) 286 | endif () 287 | endforeach() 288 | endmacro () 289 | 290 | # install_foo ( files/directories [INTO destination] ) 291 | # This installs optional or otherwise unneeded content 292 | # USE: install_foo ( etc/ example.doc ) 293 | # USE: install_foo ( icon.png logo.png INTO icons) 294 | # For directories, supports optional PATTERN/REGEX argument like install(). 295 | set ( CPACK_COMPONENT_OTHER_DISPLAY_NAME "${DIST_NAME} Unspecified Content" ) 296 | set ( CPACK_COMPONENT_OTHER_DESCRIPTION 297 | "Other unspecified content. Installed into ${INSTALL_FOO}." ) 298 | macro ( install_foo ) 299 | parse_arguments ( _ARG "INTO;PATTERN;REGEX" "" ${ARGN} ) 300 | _complete_install_args() 301 | foreach ( _file ${_ARG_DEFAULT_ARGS} ) 302 | if ( IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${_file}" ) 303 | install ( DIRECTORY ${_file} DESTINATION ${INSTALL_FOO}/${_ARG_INTO} 304 | COMPONENT Other ${_ARG_PATTERN} ${_ARG_REGEX} ) 305 | else () 306 | install ( FILES ${_file} DESTINATION ${INSTALL_FOO}/${_ARG_INTO} 307 | COMPONENT Other ) 308 | endif () 309 | endforeach() 310 | endmacro () 311 | 312 | ## CTest defaults 313 | 314 | ## CPack defaults 315 | set ( CPACK_GENERATOR "ZIP" ) 316 | set ( CPACK_STRIP_FILES TRUE ) 317 | set ( CPACK_PACKAGE_NAME "${DIST_NAME}" ) 318 | set ( CPACK_PACKAGE_VERSION "${DIST_VERSION}") 319 | set ( CPACK_PACKAGE_VENDOR "LuaDist" ) 320 | set ( CPACK_COMPONENTS_ALL Runtime Library Header Data Documentation Example Other ) 321 | include ( CPack ) 322 | -------------------------------------------------------------------------------- /cmake/lua.cmake: -------------------------------------------------------------------------------- 1 | # LuaDist CMake utility library for Lua. 2 | # 3 | # Copyright (C) 2007-2012 LuaDist. 4 | # by David Manura, Peter Drahos 5 | # Redistribution and use of this file is allowed according to the terms of the MIT license. 6 | # For details see the COPYRIGHT file distributed with LuaDist. 7 | # Please note that the package source code is licensed under its own license. 8 | 9 | set ( INSTALL_LMOD ${INSTALL_LIB}/lua 10 | CACHE PATH "Directory to install Lua modules." ) 11 | set ( INSTALL_CMOD ${INSTALL_LIB}/lua 12 | CACHE PATH "Directory to install Lua binary modules." ) 13 | 14 | option ( SKIP_LUA_WRAPPER 15 | "Do not build and install Lua executable wrappers." OFF) 16 | 17 | # List of (Lua module name, file path) pairs. 18 | # Used internally by add_lua_test. Built by add_lua_module. 19 | set ( _lua_modules ) 20 | 21 | # utility function: appends path `path` to path `basepath`, properly 22 | # handling cases when `path` may be relative or absolute. 23 | macro ( _append_path basepath path result ) 24 | if ( IS_ABSOLUTE "${path}" ) 25 | set ( ${result} "${path}" ) 26 | else () 27 | set ( ${result} "${basepath}/${path}" ) 28 | endif () 29 | endmacro () 30 | 31 | # install_lua_executable ( target source ) 32 | # Automatically generate a binary wrapper for lua application and install it 33 | # The wrapper and the source of the application will be placed into /bin 34 | # If the application source did not have .lua suffix then it will be added 35 | # USE: lua_executable ( sputnik src/sputnik.lua ) 36 | macro ( install_lua_executable _name _source ) 37 | get_filename_component ( _source_name ${_source} NAME_WE ) 38 | if ( NOT SKIP_LUA_WRAPPER ) 39 | enable_language ( C ) 40 | 41 | find_package ( Lua REQUIRED ) 42 | include_directories ( ${LUA_INCLUDE_DIR} ) 43 | 44 | set ( _wrapper ${CMAKE_CURRENT_BINARY_DIR}/${_name}.c ) 45 | set ( _code 46 | "// Not so simple executable wrapper for Lua apps 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | 53 | lua_State *L\; 54 | 55 | static int getargs (lua_State *L, char **argv, int n) { 56 | int narg\; 57 | int i\; 58 | int argc = 0\; 59 | while (argv[argc]) argc++\; 60 | narg = argc - (n + 1)\; 61 | luaL_checkstack(L, narg + 3, \"too many arguments to script\")\; 62 | for (i=n+1\; i < argc\; i++) 63 | lua_pushstring(L, argv[i])\; 64 | lua_createtable(L, narg, n + 1)\; 65 | for (i=0\; i < argc\; i++) { 66 | lua_pushstring(L, argv[i])\; 67 | lua_rawseti(L, -2, i - n)\; 68 | } 69 | return narg\; 70 | } 71 | 72 | static void lstop (lua_State *L, lua_Debug *ar) { 73 | (void)ar\; 74 | lua_sethook(L, NULL, 0, 0)\; 75 | luaL_error(L, \"interrupted!\")\; 76 | } 77 | 78 | static void laction (int i) { 79 | signal(i, SIG_DFL)\; 80 | lua_sethook(L, lstop, LUA_MASKCALL | LUA_MASKRET | LUA_MASKCOUNT, 1)\; 81 | } 82 | 83 | static void l_message (const char *pname, const char *msg) { 84 | if (pname) fprintf(stderr, \"%s: \", pname)\; 85 | fprintf(stderr, \"%s\\n\", msg)\; 86 | fflush(stderr)\; 87 | } 88 | 89 | static int report (lua_State *L, int status) { 90 | if (status && !lua_isnil(L, -1)) { 91 | const char *msg = lua_tostring(L, -1)\; 92 | if (msg == NULL) msg = \"(error object is not a string)\"\; 93 | l_message(\"${_source_name}\", msg)\; 94 | lua_pop(L, 1)\; 95 | } 96 | return status\; 97 | } 98 | 99 | static int traceback (lua_State *L) { 100 | if (!lua_isstring(L, 1)) 101 | return 1\; 102 | lua_getfield(L, LUA_GLOBALSINDEX, \"debug\")\; 103 | if (!lua_istable(L, -1)) { 104 | lua_pop(L, 1)\; 105 | return 1\; 106 | } 107 | lua_getfield(L, -1, \"traceback\")\; 108 | if (!lua_isfunction(L, -1)) { 109 | lua_pop(L, 2)\; 110 | return 1\; 111 | } 112 | lua_pushvalue(L, 1)\; 113 | lua_pushinteger(L, 2)\; 114 | lua_call(L, 2, 1)\; 115 | return 1\; 116 | } 117 | 118 | static int docall (lua_State *L, int narg, int clear) { 119 | int status\; 120 | int base = lua_gettop(L) - narg\; 121 | lua_pushcfunction(L, traceback)\; 122 | lua_insert(L, base)\; 123 | signal(SIGINT, laction)\; 124 | status = lua_pcall(L, narg, (clear ? 0 : LUA_MULTRET), base)\; 125 | signal(SIGINT, SIG_DFL)\; 126 | lua_remove(L, base)\; 127 | if (status != 0) lua_gc(L, LUA_GCCOLLECT, 0)\; 128 | return status\; 129 | } 130 | 131 | int main (int argc, char **argv) { 132 | L=lua_open()\; 133 | lua_gc(L, LUA_GCSTOP, 0)\; 134 | luaL_openlibs(L)\; 135 | lua_gc(L, LUA_GCRESTART, 0)\; 136 | int narg = getargs(L, argv, 0)\; 137 | lua_setglobal(L, \"arg\")\; 138 | 139 | // Script 140 | char script[500] = \"./${_source_name}.lua\"\; 141 | lua_getglobal(L, \"_PROGDIR\")\; 142 | if (lua_isstring(L, -1)) { 143 | sprintf( script, \"%s/${_source_name}.lua\", lua_tostring(L, -1))\; 144 | } 145 | lua_pop(L, 1)\; 146 | 147 | // Run 148 | int status = luaL_loadfile(L, script)\; 149 | lua_insert(L, -(narg+1))\; 150 | if (status == 0) 151 | status = docall(L, narg, 0)\; 152 | else 153 | lua_pop(L, narg)\; 154 | 155 | report(L, status)\; 156 | lua_close(L)\; 157 | return status\; 158 | }; 159 | ") 160 | file ( WRITE ${_wrapper} ${_code} ) 161 | add_executable ( ${_name} ${_wrapper} ) 162 | target_link_libraries ( ${_name} ${LUA_LIBRARY} ) 163 | install ( TARGETS ${_name} DESTINATION ${INSTALL_BIN} ) 164 | endif() 165 | install ( PROGRAMS ${_source} DESTINATION ${INSTALL_BIN} 166 | RENAME ${_source_name}.lua ) 167 | endmacro () 168 | 169 | macro ( _lua_module_helper is_install _name ) 170 | parse_arguments ( _MODULE "LINK;ALL_IN_ONE" "" ${ARGN} ) 171 | # _target is CMake-compatible target name for module (e.g. socket_core). 172 | # _module is relative path of target (e.g. socket/core), 173 | # without extension (e.g. .lua/.so/.dll). 174 | # _MODULE_SRC is list of module source files (e.g. .lua and .c files). 175 | # _MODULE_NAMES is list of module names (e.g. socket.core). 176 | if ( _MODULE_ALL_IN_ONE ) 177 | string ( REGEX REPLACE "\\..*" "" _target "${_name}" ) 178 | string ( REGEX REPLACE "\\..*" "" _module "${_name}" ) 179 | set ( _target "${_target}_all_in_one") 180 | set ( _MODULE_SRC ${_MODULE_ALL_IN_ONE} ) 181 | set ( _MODULE_NAMES ${_name} ${_MODULE_DEFAULT_ARGS} ) 182 | else () 183 | string ( REPLACE "." "_" _target "${_name}" ) 184 | string ( REPLACE "." "/" _module "${_name}" ) 185 | set ( _MODULE_SRC ${_MODULE_DEFAULT_ARGS} ) 186 | set ( _MODULE_NAMES ${_name} ) 187 | endif () 188 | if ( NOT _MODULE_SRC ) 189 | message ( FATAL_ERROR "no module sources specified" ) 190 | endif () 191 | list ( GET _MODULE_SRC 0 _first_source ) 192 | 193 | get_filename_component ( _ext ${_first_source} EXT ) 194 | if ( _ext STREQUAL ".lua" ) # Lua source module 195 | list ( LENGTH _MODULE_SRC _len ) 196 | if ( _len GREATER 1 ) 197 | message ( FATAL_ERROR "more than one source file specified" ) 198 | endif () 199 | 200 | set ( _module "${_module}.lua" ) 201 | 202 | get_filename_component ( _module_dir ${_module} PATH ) 203 | get_filename_component ( _module_filename ${_module} NAME ) 204 | _append_path ( "${CMAKE_CURRENT_SOURCE_DIR}" "${_first_source}" _module_path ) 205 | list ( APPEND _lua_modules "${_name}" "${_module_path}" ) 206 | 207 | if ( ${is_install} ) 208 | install ( FILES ${_first_source} DESTINATION ${INSTALL_LMOD}/${_module_dir} 209 | RENAME ${_module_filename} ) 210 | endif () 211 | else () # Lua C binary module 212 | enable_language ( C ) 213 | find_package ( Lua REQUIRED ) 214 | include_directories ( ${LUA_INCLUDE_DIR} ) 215 | 216 | set ( _module "${_module}${CMAKE_SHARED_MODULE_SUFFIX}" ) 217 | 218 | get_filename_component ( _module_dir ${_module} PATH ) 219 | get_filename_component ( _module_filenamebase ${_module} NAME_WE ) 220 | foreach ( _thisname ${_MODULE_NAMES} ) 221 | list ( APPEND _lua_modules "${_thisname}" 222 | "${CMAKE_CURRENT_BINARY_DIR}/\${CMAKE_CFG_INTDIR}/${_module}" ) 223 | endforeach () 224 | 225 | add_library( ${_target} MODULE ${_MODULE_SRC}) 226 | target_link_libraries ( ${_target} ${LUA_LIBRARY} ${_MODULE_LINK} ) 227 | set_target_properties ( ${_target} PROPERTIES LIBRARY_OUTPUT_DIRECTORY 228 | "${_module_dir}" PREFIX "" OUTPUT_NAME "${_module_filenamebase}" ) 229 | if ( ${is_install} ) 230 | install ( TARGETS ${_target} DESTINATION ${INSTALL_CMOD}/${_module_dir}) 231 | endif () 232 | endif () 233 | endmacro () 234 | 235 | # add_lua_module 236 | # Builds a Lua source module into a destination locatable by Lua 237 | # require syntax. 238 | # Binary modules are also supported where this function takes sources and 239 | # libraries to compile separated by LINK keyword. 240 | # USE: add_lua_module ( socket.http src/http.lua ) 241 | # USE2: add_lua_module ( mime.core src/mime.c ) 242 | # USE3: add_lua_module ( socket.core ${SRC_SOCKET} LINK ${LIB_SOCKET} ) 243 | # USE4: add_lua_module ( ssl.context ssl.core ALL_IN_ONE src/context.c src/ssl.c ) 244 | # This form builds an "all-in-one" module (e.g. ssl.so or ssl.dll containing 245 | # both modules ssl.context and ssl.core). The CMake target name will be 246 | # ssl_all_in_one. 247 | # Also sets variable _module_path (relative path where module typically 248 | # would be installed). 249 | macro ( add_lua_module ) 250 | _lua_module_helper ( 0 ${ARGN} ) 251 | endmacro () 252 | 253 | 254 | # install_lua_module 255 | # This is the same as `add_lua_module` but also installs the module. 256 | # USE: install_lua_module ( socket.http src/http.lua ) 257 | # USE2: install_lua_module ( mime.core src/mime.c ) 258 | # USE3: install_lua_module ( socket.core ${SRC_SOCKET} LINK ${LIB_SOCKET} ) 259 | macro ( install_lua_module ) 260 | _lua_module_helper ( 1 ${ARGN} ) 261 | endmacro () 262 | 263 | # Builds string representing Lua table mapping Lua modules names to file 264 | # paths. Used internally. 265 | macro ( _make_module_table _outvar ) 266 | set ( ${_outvar} ) 267 | list ( LENGTH _lua_modules _n ) 268 | if ( ${_n} GREATER 0 ) # avoids cmake complaint 269 | foreach ( _i RANGE 1 ${_n} 2 ) 270 | list ( GET _lua_modules ${_i} _path ) 271 | math ( EXPR _ii ${_i}-1 ) 272 | list ( GET _lua_modules ${_ii} _name ) 273 | set ( ${_outvar} "${_table} ['${_name}'] = '${_path}'\;\n") 274 | endforeach () 275 | endif () 276 | set ( ${_outvar} 277 | "local modules = { 278 | ${_table}}" ) 279 | endmacro () 280 | 281 | # add_lua_test ( _testfile [ WORKING_DIRECTORY _working_dir ] ) 282 | # Runs Lua script `_testfile` under CTest tester. 283 | # Optional named argument `WORKING_DIRECTORY` is current working directory to 284 | # run test under (defaults to ${CMAKE_CURRENT_BINARY_DIR}). 285 | # Both paths, if relative, are relative to ${CMAKE_CURRENT_SOURCE_DIR}. 286 | # Any modules previously defined with install_lua_module are automatically 287 | # preloaded (via package.preload) prior to running the test script. 288 | # Under LuaDist, set test=true in config.lua to enable testing. 289 | # USE: add_lua_test ( test/test1.lua [args...] [WORKING_DIRECTORY dir]) 290 | macro ( add_lua_test _testfile ) 291 | if ( NOT SKIP_TESTING ) 292 | parse_arguments ( _ARG "WORKING_DIRECTORY" "" ${ARGN} ) 293 | include ( CTest ) 294 | find_program ( LUA NAMES lua lua.bat ) 295 | get_filename_component ( TESTFILEABS ${_testfile} ABSOLUTE ) 296 | get_filename_component ( TESTFILENAME ${_testfile} NAME ) 297 | get_filename_component ( TESTFILEBASE ${_testfile} NAME_WE ) 298 | 299 | # Write wrapper script. 300 | # Note: One simple way to allow the script to find modules is 301 | # to just put them in package.preload. 302 | set ( TESTWRAPPER ${CMAKE_CURRENT_BINARY_DIR}/${TESTFILENAME} ) 303 | _make_module_table ( _table ) 304 | set ( TESTWRAPPERSOURCE 305 | "local CMAKE_CFG_INTDIR = ... or '.' 306 | ${_table} 307 | local function preload_modules(modules) 308 | for name, path in pairs(modules) do 309 | if path:match'%.lua' then 310 | package.preload[name] = assert(loadfile(path)) 311 | else 312 | local name = name:gsub('.*%-', '') -- remove any hyphen prefix 313 | local symbol = 'luaopen_' .. name:gsub('%.', '_') 314 | --improve: generalize to support all-in-one loader? 315 | local path = path:gsub('%$%{CMAKE_CFG_INTDIR%}', CMAKE_CFG_INTDIR) 316 | package.preload[name] = assert(package.loadlib(path, symbol)) 317 | end 318 | end 319 | end 320 | preload_modules(modules) 321 | arg[0] = '${TESTFILEABS}' 322 | table.remove(arg, 1) 323 | return assert(loadfile '${TESTFILEABS}')(unpack(arg)) 324 | " ) 325 | if ( _ARG_WORKING_DIRECTORY ) 326 | get_filename_component ( 327 | TESTCURRENTDIRABS ${_ARG_WORKING_DIRECTORY} ABSOLUTE ) 328 | # note: CMake 2.6 (unlike 2.8) lacks WORKING_DIRECTORY parameter. 329 | set ( _pre ${CMAKE_COMMAND} -E chdir "${TESTCURRENTDIRABS}" ) 330 | endif () 331 | file ( WRITE ${TESTWRAPPER} ${TESTWRAPPERSOURCE}) 332 | add_test ( NAME ${TESTFILEBASE} COMMAND ${_pre} ${LUA} 333 | ${TESTWRAPPER} "${CMAKE_CFG_INTDIR}" 334 | ${_ARG_DEFAULT_ARGS} ) 335 | endif () 336 | # see also http://gdcm.svn.sourceforge.net/viewvc/gdcm/Sandbox/CMakeModules/UsePythonTest.cmake 337 | # Note: ${CMAKE_CFG_INTDIR} is a command-line argument to allow proper 338 | # expansion by the native build tool. 339 | endmacro () 340 | 341 | 342 | # Converts Lua source file `_source` to binary string embedded in C source 343 | # file `_target`. Optionally compiles Lua source to byte code (not available 344 | # under LuaJIT2, which doesn't have a bytecode loader). Additionally, Lua 345 | # versions of bin2c [1] and luac [2] may be passed respectively as additional 346 | # arguments. 347 | # 348 | # [1] http://lua-users.org/wiki/BinToCee 349 | # [2] http://lua-users.org/wiki/LuaCompilerInLua 350 | function ( add_lua_bin2c _target _source ) 351 | find_program ( LUA NAMES lua lua.bat ) 352 | execute_process ( COMMAND ${LUA} -e "string.dump(function()end)" 353 | RESULT_VARIABLE _LUA_DUMP_RESULT ERROR_QUIET ) 354 | if ( NOT ${_LUA_DUMP_RESULT} ) 355 | SET ( HAVE_LUA_DUMP true ) 356 | endif () 357 | message ( "-- string.dump=${HAVE_LUA_DUMP}" ) 358 | 359 | if ( ARGV2 ) 360 | get_filename_component ( BIN2C ${ARGV2} ABSOLUTE ) 361 | set ( BIN2C ${LUA} ${BIN2C} ) 362 | else () 363 | find_program ( BIN2C NAMES bin2c bin2c.bat ) 364 | endif () 365 | if ( HAVE_LUA_DUMP ) 366 | if ( ARGV3 ) 367 | get_filename_component ( LUAC ${ARGV3} ABSOLUTE ) 368 | set ( LUAC ${LUA} ${LUAC} ) 369 | else () 370 | find_program ( LUAC NAMES luac luac.bat ) 371 | endif () 372 | endif ( HAVE_LUA_DUMP ) 373 | message ( "-- bin2c=${BIN2C}" ) 374 | message ( "-- luac=${LUAC}" ) 375 | 376 | get_filename_component ( SOURCEABS ${_source} ABSOLUTE ) 377 | if ( HAVE_LUA_DUMP ) 378 | get_filename_component ( SOURCEBASE ${_source} NAME_WE ) 379 | add_custom_command ( 380 | OUTPUT ${_target} DEPENDS ${_source} 381 | COMMAND ${LUAC} -o ${CMAKE_CURRENT_BINARY_DIR}/${SOURCEBASE}.lo 382 | ${SOURCEABS} 383 | COMMAND ${BIN2C} ${CMAKE_CURRENT_BINARY_DIR}/${SOURCEBASE}.lo 384 | ">${_target}" ) 385 | else () 386 | add_custom_command ( 387 | OUTPUT ${_target} DEPENDS ${SOURCEABS} 388 | COMMAND ${BIN2C} ${_source} ">${_target}" ) 389 | endif () 390 | endfunction() 391 | -------------------------------------------------------------------------------- /jsontest.lua: -------------------------------------------------------------------------------- 1 | local encode, decode, dkencode, dkdecode 2 | 3 | 4 | local test_module, opt = ... -- command line argument 5 | --local test_module = 'cmj-json' 6 | --local test_module = 'dkjson' 7 | --local test_module = 'dkjson-nopeg' 8 | --local test_module = 'fleece' 9 | --local test_module = 'jf-json' 10 | --locel test_module = 'lua-yajl' 11 | --local test_module = 'mp-cjson' 12 | --local test_module = 'nm-json' 13 | --local test_module = 'sb-json' 14 | --local test_module = 'th-json' 15 | test_module = test_module or 'dkjson' 16 | 17 | --local opt = "esc" -- Test which characters in the BMP get escaped and whether this is correct 18 | --local opt = "esc_full" -- Full range from 0 to 0x10ffff 19 | --local opt = "esc_asc" -- Just 0 to 127 20 | 21 | --local opt = "refcycle" -- What happens when a reference cycle gets encoded? 22 | 23 | local testlocale = "de_DE.UTF8" 24 | 25 | local function inlocale(fn) 26 | local oldloc = os.setlocale(nil, 'numeric') 27 | if not os.setlocale(testlocale, 'numeric') then 28 | print("test could not switch to locale "..testlocale) 29 | else 30 | fn() 31 | end 32 | os.setlocale(oldloc, 'numeric') 33 | end 34 | 35 | if test_module == 'dkjson-nopeg' then 36 | test_module = 'dkjson' 37 | package.preload["lpeg"] = function () error "lpeg disabled" end 38 | package.loaded["lpeg"] = nil 39 | lpeg = nil 40 | end 41 | 42 | if test_module == 'dkjson-lulpeg' then 43 | test_module = 'dkjson' 44 | package.loaded["lpeg"] = require "lulpeg" 45 | end 46 | 47 | do 48 | -- http://chiselapp.com/user/dhkolf/repository/dkjson/ 49 | local dkjson = require "dkjson" 50 | dkencode = dkjson.encode 51 | dkdecode = dkjson.decode 52 | end 53 | 54 | if test_module == 'cmj-json' then 55 | -- https://github.com/craigmj/json4lua/ 56 | -- http://json.luaforge.net/ 57 | local json = require "cmjjson" -- renamed, the original file was just 'json' 58 | encode = json.encode 59 | decode = json.decode 60 | elseif test_module == 'dkjson' then 61 | -- http://chiselapp.com/user/dhkolf/repository/dkjson/ 62 | encode = dkencode 63 | decode = dkdecode 64 | elseif test_module == 'fleece' then 65 | -- http://www.eonblast.com/fleece/ 66 | local fleece = require "fleece" 67 | encode = function(x) return fleece.json(x, "E4") end 68 | elseif test_module == 'jf-json' then 69 | -- http://regex.info/blog/lua/json 70 | local json = require "jfjson" -- renamed, the original file was just 'JSON' 71 | encode = function(x) return json:encode(x) end 72 | decode = function(x) return json:decode(x) end 73 | elseif test_module == 'lua-yajl' then 74 | -- http://github.com/brimworks/lua-yajl 75 | local yajl = require ("yajl") 76 | encode = yajl.to_string 77 | decode = yajl.to_value 78 | elseif test_module == 'mp-cjson' then 79 | -- http://www.kyne.com.au/~mark/software/lua-cjson.php 80 | local json = require "cjson" 81 | encode = json.encode 82 | decode = json.decode 83 | elseif test_module == 'nm-json' then 84 | -- http://luaforge.net/projects/luajsonlib/ 85 | local json = require "LuaJSON" 86 | encode = json.encode or json.stringify 87 | decode = json.decode or json.parse 88 | elseif test_module == 'sb-json' then 89 | -- http://www.chipmunkav.com/downloads/Json.lua 90 | local json = require "sbjson" -- renamed, the original file was just 'Json' 91 | encode = json.Encode 92 | decode = json.Decode 93 | elseif test_module == 'th-json' then 94 | -- https://github.com/harningt/luajson 95 | -- http://luaforge.net/projects/luajson/ 96 | local json = require "json" 97 | encode = json.encode 98 | decode = json.decode 99 | else 100 | print "No module specified" 101 | return 102 | end 103 | 104 | if not encode then 105 | print ("No encode method") 106 | else 107 | local x, r 108 | 109 | local escapecodes = { 110 | ["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f", 111 | ["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t", ["/"] = "\\/" 112 | } 113 | local function test (x, n, expect) 114 | local enc = encode{ x }:match("^%s*%[%s*%\"(.-)%\"%s*%]%s*$") 115 | if not enc or (escapecodes[x] ~= enc 116 | and ("\\u%04x"):format(n) ~= enc:gsub("[A-F]", string.lower) 117 | and not (expect and enc:match("^"..expect.."$"))) then 118 | print(("U+%04X isn't encoded correctly: %q"):format(n, enc)) 119 | end 120 | end 121 | 122 | -- necessary escapes for JSON: 123 | for i = 0,31 do 124 | test(string.char(i), i) 125 | end 126 | test("\"", ("\""):byte()) 127 | test("\\", ("\\"):byte()) 128 | -- necessary escapes for JavaScript: 129 | test("\226\128\168", 0x2028) 130 | test("\226\128\169", 0x2029) 131 | -- invalid escapes that were seen in the wild: 132 | test("'", ("'"):byte(), "%'") 133 | 134 | r,x = pcall (encode, { [1000] = "x" }) 135 | if not r then 136 | print ("encoding a sparse array (#=0) raises an error:", x) 137 | else 138 | if #x > 30 then 139 | print ("sparse array (#=0) encoded as:", x:sub(1,15).." <...> "..x:sub(-15,-1), "#"..#x) 140 | else 141 | print ("sparse array (#=0) encoded as:", x) 142 | end 143 | end 144 | 145 | r,x = pcall (encode, { [1] = "a", [1000] = "x" }) 146 | if not r then 147 | print ("encoding a sparse array (#=1) raises an error:", x) 148 | else 149 | if #x > 30 then 150 | print ("sparse array (#=1) encoded as:", x:sub(1,15).." <...> "..x:sub(-15,-1), "#str="..#x) 151 | else 152 | print ("sparse array (#=1) encoded as:", x) 153 | end 154 | end 155 | 156 | r,x = pcall (encode, { [1] = "a", [5] = "c", ["x"] = "x" }) 157 | if not r then 158 | print ("encoding a mixed table raises an error:", x) 159 | else 160 | print ("mixed table encoded as:", x) 161 | end 162 | 163 | r, x = pcall(encode, { math.huge*0 }) -- NaN 164 | if not r then 165 | print ("encoding NaN raises an error:", x) 166 | else 167 | r = dkdecode(x) 168 | if not r then 169 | print ("NaN isn't converted into valid JSON:", x) 170 | elseif type(r[1]) == "number" and r[1] == r[1] then -- a number, but not NaN 171 | print ("NaN is converted into a valid number:", x) 172 | else 173 | print ("NaN is converted to:", x) 174 | end 175 | end 176 | 177 | if test_module == 'fleece' then 178 | print ("Fleece (0.3.1) is known to freeze on +/-Inf") 179 | else 180 | r, x = pcall(encode, { math.huge }) -- +Inf 181 | if not r then 182 | print ("encoding +Inf raises an error:", x) 183 | else 184 | r = dkdecode(x) 185 | if not r then 186 | print ("+Inf isn't converted into valid JSON:", x) 187 | else 188 | print ("+Inf is converted to:", x) 189 | end 190 | end 191 | 192 | r, x = pcall(encode, { -math.huge }) -- -Inf 193 | if not r then 194 | print ("encoding -Inf raises an error:", x) 195 | else 196 | r = dkdecode(x) 197 | if not r then 198 | print ("-Inf isn't converted into valid JSON:", x) 199 | else 200 | print ("-Inf is converted to:", x) 201 | end 202 | end 203 | end 204 | 205 | inlocale(function () 206 | local r, x = pcall(encode, { 0.5 }) 207 | if not r then 208 | print("encoding 0.5 in locale raises an error:", x) 209 | elseif not x:find(".", 1, true) then 210 | print("In locale 0.5 isn't converted into valid JSON:", x) 211 | end 212 | end) 213 | 214 | -- special tests for dkjson: 215 | if test_module == 'dkjson' then 216 | do -- encode a function 217 | local why, value, exstate 218 | local state = { 219 | exception = function (w, v, s) 220 | why, value, exstate = w, v, s 221 | return "\"demo\"" 222 | end 223 | } 224 | local encfunction = function () end 225 | r, x = pcall(dkencode, { encfunction }, state ) 226 | if not r then 227 | print("encoding a function with exception handler raises an error:", x) 228 | else 229 | if x ~= "[\"demo\"]" then 230 | print("expected to see output of exception handler for type exception, but got", x) 231 | end 232 | if why ~= "unsupported type" then 233 | print("expected exception reason to be 'unsupported type' for type exception") 234 | end 235 | if value ~= encfunction then 236 | print("expected to recieve value for type exception") 237 | end 238 | if exstate ~= state then 239 | print("expected to recieve state for type exception") 240 | end 241 | end 242 | 243 | r, x = pcall(dkencode, { function () end }, { 244 | exception = function (w, v, s) 245 | return nil, "demo" 246 | end 247 | }) 248 | if r or x ~= "demo" then 249 | print("expected custom error for type exception, but got:", r, x) 250 | end 251 | 252 | r, x = pcall(dkencode, { function () end }, { 253 | exception = function (w, v, s) 254 | return nil 255 | end 256 | }) 257 | if r or x ~= "type 'function' is not supported by JSON." then 258 | print("expected default error for type exception, but got:", r, x) 259 | end 260 | end 261 | 262 | do -- encode a reference cycle 263 | local why, value, exstate 264 | local state = { 265 | exception = function (w, v, s) 266 | why, value, exstate = w, v, s 267 | return "\"demo\"" 268 | end 269 | } 270 | local a = {} 271 | a[1] = a 272 | r, x = pcall(dkencode, a, state ) 273 | if not r then 274 | print("encoding a reference cycle with exception handler raises an error:", x) 275 | else 276 | if x ~= "[\"demo\"]" then 277 | print("expected to see output of exception handler for reference cycle exception, but got", x) 278 | end 279 | if why ~= "reference cycle" then 280 | print("expected exception reason to be 'reference cycle' for reference cycle exception") 281 | end 282 | if value ~= a then 283 | print("expected to recieve value for reference cycle exception") 284 | end 285 | if exstate ~= state then 286 | print("expected to recieve state for reference cycle exception") 287 | end 288 | end 289 | end 290 | 291 | do -- example exception handler 292 | r = dkencode(function () end, { exception = require "dkjson".encodeexception }) 293 | if r ~= [[""]] then 294 | print("expected the exception encoder to encode default error message, but got", r) 295 | end 296 | end 297 | 298 | do -- test state buffer for custom __tojson function 299 | local origstate = {} 300 | local usedstate, usedbuffer, usedbufferlen 301 | dkencode({ setmetatable({}, { 302 | __tojson = function(self, state) 303 | usedstate = state 304 | usedbuffer = state.buffer 305 | usedbufferlen = state.bufferlen 306 | return true 307 | end 308 | }) }, origstate) 309 | if usedstate ~= origstate then print("expected tojson-function to recieve the original state") end 310 | if type(usedbuffer) ~= 'table' or #usedbuffer < 1 then print("expected buffer in tojson-function to be an array") end 311 | if usedbufferlen ~= 1 then print("expected bufferlen in tojson-function to be 1, but got "..tostring(usedbufferlen)) end 312 | end 313 | 314 | do -- do not keep buffer and bufferlen when they were not present initially 315 | local origstate = {} 316 | dkencode(setmetatable({}, {__tojson = function() return true end}), origstate) 317 | if origstate.buffer ~= nil then print("expected buffer to be reset to nil") end 318 | if origstate.bufferlen ~= nil then print("expected bufferlen to be reset to nil") end 319 | end 320 | 321 | do -- keep buffer and update bufferlen when they were present initially 322 | local origbuffer = {} 323 | local origstate = { buffer = origbuffer } 324 | dkencode(true, origstate) 325 | if origstate.buffer ~= origbuffer then print("expected original buffer to remain") end 326 | if origstate.bufferlen ~= 1 then print("expected bufferlen to be updated") end 327 | end 328 | end 329 | end 330 | 331 | if not decode then 332 | print ("No decode method") 333 | else 334 | local x, r 335 | 336 | x = decode[=[ ["\u0000"] ]=] 337 | if x[1] ~= "\000" then 338 | print ("\\u0000 isn't decoded correctly") 339 | end 340 | 341 | x = decode[=[ ["\u20AC"] ]=] 342 | if x[1] ~= "\226\130\172" then 343 | print ("\\u20AC isn't decoded correctly") 344 | end 345 | 346 | x = decode[=[ ["\uD834\uDD1E"] ]=] 347 | if x[1] ~= "\240\157\132\158" then 348 | print ("\\uD834\\uDD1E isn't decoded correctly") 349 | end 350 | 351 | r, x = pcall(decode, [=[ 352 | {"x":{"x":{"x":{"x":{"x": {"x":{"x":{"x":{"x":{"x": {"x":{"x":{"x":{"x":{"x": 353 | {"x":{"x":{"x":{"x":{"x": {"x":{"x":{"x":{"x":{"x": {"x":{"x":{"x":{"x":{"x": 354 | {"x":{"x":{"x":{"x":{"x": {"x":{"x":{"x":{"x":{"x": {"x":{"x":{"x":{"x":{"x": 355 | {"x":{"x":{"x":{"x":{"x": {"x":{"x":{"x":{"x":{"x": {"x":{"x":{"x":{"x":{"x": 356 | "deep down" 357 | } } } } } } } } } } } } } } } 358 | } } } } } } } } } } } } } } } 359 | } } } } } } } } } } } } } } } 360 | } } } } } } } } } } } } } } } 361 | ]=]) 362 | 363 | if not r then 364 | print ("decoding a deep nested table raises an error:", x) 365 | else 366 | local i = 0 367 | while type(x) == 'table' do 368 | i = i + 1 369 | x = x.x 370 | end 371 | if i ~= 60 or x ~= "deep down" then 372 | print ("deep nested table isn't decoded correctly") 373 | end 374 | end 375 | 376 | if false and test_module == 'cmj-json' then 377 | -- unfortunatly the version can't be read 378 | print ("decoding a big array takes ages (or forever?) on cmj-json prior to version 0.9.5") 379 | else 380 | r, x = pcall(decode, "["..("0,"):rep(100000).."0]") 381 | if not r then 382 | print ("decoding a big array raises an error:", x) 383 | else 384 | if type(x) ~= 'table' or #x ~= 100001 then 385 | print ("big array isn't decoded correctly") 386 | end 387 | end 388 | end 389 | 390 | r, x = pcall(decode, "{}") 391 | if not r then 392 | print ("decoding an empty object raises an error:", x) 393 | end 394 | 395 | r, x = pcall(decode, "[]") 396 | if not r then 397 | print ("decoding an empty array raises an error:", x) 398 | end 399 | 400 | r, x = pcall(decode, "[1e+2]") 401 | if not r then 402 | print ("decoding a number with exponential notation raises an error:", x) 403 | elseif x[1] ~= 1e+2 then 404 | print ("1e+2 decoded incorrectly:", r[1]) 405 | end 406 | 407 | inlocale(function () 408 | local r, x = pcall(decode, "[0.5]") 409 | if not r then 410 | print("decoding 0.5 in locale raises an error:", x) 411 | elseif not x then 412 | print("cannot decode 0.5 in locale") 413 | elseif x[1] ~= 0.5 then 414 | print("decoded 0.5 incorrectly in locale:", x[1]) 415 | end 416 | end) 417 | 418 | -- special tests for dkjson: 419 | if test_module == 'dkjson' then 420 | x = dkdecode[=[ [{"x":0}] ]=] 421 | local m = getmetatable(x) 422 | if not m or m.__jsontype ~= 'array' then 423 | print (".__jsontype ~= array") 424 | end 425 | local m = getmetatable(x[1]) 426 | if not m or m.__jsontype ~= 'object' then 427 | print (".__jsontype ~= object") 428 | end 429 | 430 | local x,p,m = dkdecode" invalid " 431 | if p ~= 2 or type(m) ~= 'string' or not m:find("at line 1, column 2$") then 432 | print (("Invalid location: position=%d, message=%q"):format(p,m)) 433 | end 434 | local x,p,m = dkdecode" \n invalid " 435 | if p ~= 4 or type(m) ~= 'string' or not m:find("at line 2, column 2$") then 436 | print (("Invalid location: position=%d, message=%q"):format(p,m)) 437 | end 438 | 439 | do -- single line comments 440 | local x, p, m = dkdecode [[ 441 | {"test://" // comment // --? 442 | : [ // continues 443 | 0] // 444 | } 445 | ]] 446 | if type(x) ~= 'table' or type(x["test://"]) ~= 'table' or x["test://"][1] ~= 0 then 447 | print("could not decode a string with single line comments: "..tostring(m)) 448 | end 449 | end 450 | 451 | do -- multi line comments 452 | local x, p, m = dkdecode [[ 453 | {"test:/*"/**//* 454 | hi! this is a comment 455 | */ : [/** / **/ 0] 456 | } 457 | ]] 458 | if type(x) ~= 'table' or type(x["test:/*"]) ~= 'table' or x["test:/*"][1] ~= 0 then 459 | print("could not decode a string with multi line comments: "..tostring(m)) 460 | end 461 | end 462 | end 463 | end 464 | 465 | if encode and opt == "refcycle" then 466 | local a = {} 467 | a.a = a 468 | print ("Trying a reference cycle...") 469 | encode(a) 470 | end 471 | 472 | if encode and (opt or ""):sub(1,3) == "esc" then 473 | 474 | local strchar, strbyte, strformat = string.char, string.byte, string.format 475 | local floor = math.floor 476 | 477 | local function unichar (value) 478 | if value < 0 then 479 | return nil 480 | elseif value <= 0x007f then 481 | return strchar (value) 482 | elseif value <= 0x07ff then 483 | return strchar (0xc0 + floor(value/0x40), 484 | 0x80 + (floor(value) % 0x40)) 485 | elseif value <= 0xffff then 486 | return strchar (0xe0 + floor(value/0x1000), 487 | 0x80 + (floor(value/0x40) % 0x40), 488 | 0x80 + (floor(value) % 0x40)) 489 | elseif value <= 0x10ffff then 490 | return strchar (0xf0 + floor(value/0x40000), 491 | 0x80 + (floor(value/0x1000) % 0x40), 492 | 0x80 + (floor(value/0x40) % 0x40), 493 | 0x80 + (floor(value) % 0x40)) 494 | else 495 | return nil 496 | end 497 | end 498 | 499 | local escapecodes = { 500 | ["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f", 501 | ["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t", ["/"] = "\\/" 502 | } 503 | 504 | local function escapeutf8 (uchar) 505 | local a, b, c, d = strbyte (uchar, 1, 4) 506 | a, b, c, d = a or 0, b or 0, c or 0, d or 0 507 | if a <= 0x7f then 508 | value = a 509 | elseif 0xc0 <= a and a <= 0xdf and b >= 0x80 then 510 | value = (a - 0xc0) * 0x40 + b - 0x80 511 | elseif 0xe0 <= a and a <= 0xef and b >= 0x80 and c >= 0x80 then 512 | value = ((a - 0xe0) * 0x40 + b - 0x80) * 0x40 + c - 0x80 513 | elseif 0xf0 <= a and a <= 0xf7 and b >= 0x80 and c >= 0x80 and d >= 0x80 then 514 | value = (((a - 0xf0) * 0x40 + b - 0x80) * 0x40 + c - 0x80) * 0x40 + d - 0x80 515 | else 516 | return "" 517 | end 518 | if value <= 0xffff then 519 | return strformat ("\\u%.4x", value) 520 | elseif value <= 0x10ffff then 521 | -- encode as UTF-16 surrogate pair 522 | value = value - 0x10000 523 | local highsur, lowsur = 0xD800 + floor (value/0x400), 0xDC00 + (value % 0x400) 524 | return strformat ("\\u%.4x\\u%.4x", highsur, lowsur) 525 | else 526 | return "" 527 | end 528 | end 529 | 530 | local isspecial = {} 531 | local unifile = io.open("UnicodeData.txt") 532 | if unifile then 533 | -- 534 | -- each line consists of 15 parts for each defined codepoints 535 | local pat = {} 536 | for i = 1,14 do 537 | pat[i] = "[^;]*;" 538 | end 539 | pat[1] = "([^;]*);" -- Codepoint 540 | pat[3] = "([^;]*);" -- Category 541 | pat[15] = "[^;]*" 542 | pat = table.concat(pat) 543 | 544 | for line in unifile:lines() do 545 | local cp, cat = line:match(pat) 546 | if cat:match("^C[^so]") or cat:match("^Z[lp]") then 547 | isspecial[tonumber(cp, 16)] = cat 548 | end 549 | end 550 | unifile:close() 551 | end 552 | 553 | local x,xe 554 | 555 | local t = {} 556 | local esc = {} 557 | local escerr = {} 558 | local range 559 | if opt == "esc_full" then range = 0x10ffff 560 | elseif opt == "esc_asc" then range = 0x7f 561 | else range = 0xffff end 562 | 563 | for i = 0,range do 564 | t[1] = unichar(i) 565 | xe = encode(t) 566 | x = string.match(xe, "^%s*%[%s*%\"(.*)%\"%s*%]%s*$") 567 | if type(x) ~= 'string' then 568 | escerr[i] = xe 569 | elseif string.lower(x) == escapeutf8(t[1]) then 570 | esc[i] = 'u' 571 | elseif x == escapecodes[t[1]] then 572 | esc[i] = 'c' 573 | elseif x:sub(1,1) == "\\" then 574 | escerr[i] = xe 575 | end 576 | end 577 | do 578 | local i = 0 579 | while i <= range do 580 | local first 581 | while i <= range and not (esc[i] or isspecial[i]) do i = i + 1 end 582 | if i > range then break end 583 | first = i 584 | local special = isspecial[i] 585 | if esc[i] and special then 586 | while esc[i] and isspecial[i] == special do i = i + 1 end 587 | if i-1 > first then 588 | print (("Escaped %s characters from U+%04X to U+%04X"):format(special,first,i-1)) 589 | else 590 | print (("Escaped %s character U+%04X"):format(special,first)) 591 | end 592 | elseif esc[i] then 593 | while esc[i] and not isspecial[i] do i = i + 1 end 594 | if i-1 > first then 595 | print (("Escaped from U+%04X to U+%04X"):format(first,i-1)) 596 | else 597 | if first >= 32 and first <= 127 then 598 | print (("Escaped U+%04X (%c)"):format(first,first)) 599 | else 600 | print (("Escaped U+%04X"):format(first)) 601 | end 602 | end 603 | elseif special then 604 | while not esc[i] and isspecial[i] == special do i = i + 1 end 605 | if i-1 > first then 606 | print (("Unescaped %s characters from U+%04X to U+%04X"):format(special,first,i-1)) 607 | else 608 | print (("Unescaped %s character U+%04X"):format(special,first)) 609 | end 610 | end 611 | end 612 | end 613 | do 614 | local i = 0 615 | while i <= range do 616 | local first 617 | while i <= range and not escerr[i] do i = i + 1 end 618 | if not escerr[i] then break end 619 | first = i 620 | while escerr[i] do i = i + 1 end 621 | if i-1 > first then 622 | print (("Errors while escaping from U+%04X to U+%04X"):format(first, i-1)) 623 | else 624 | print (("Errors while escaping U+%04X"):format(first)) 625 | end 626 | end 627 | end 628 | 629 | end 630 | 631 | -- Copyright (C) 2011 David Heiko Kolf 632 | -- 633 | -- Permission is hereby granted, free of charge, to any person obtaining 634 | -- a copy of this software and associated documentation files (the 635 | -- "Software"), to deal in the Software without restriction, including 636 | -- without limitation the rights to use, copy, modify, merge, publish, 637 | -- distribute, sublicense, and/or sell copies of the Software, and to 638 | -- permit persons to whom the Software is furnished to do so, subject to 639 | -- the following conditions: 640 | -- 641 | -- The above copyright notice and this permission notice shall be 642 | -- included in all copies or substantial portions of the Software. 643 | -- 644 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 645 | -- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 646 | -- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 647 | -- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 648 | -- BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 649 | -- ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 650 | -- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 651 | -- SOFTWARE. 652 | 653 | 654 | -------------------------------------------------------------------------------- /dkjson.lua: -------------------------------------------------------------------------------- 1 | -- Module options: 2 | local always_try_using_lpeg = true 3 | local register_global_module_table = false 4 | local global_module_name = 'json' 5 | 6 | --[==[ 7 | 8 | David Kolf's JSON module for Lua 5.1/5.2 9 | 10 | Version 2.5 11 | 12 | 13 | For the documentation see the corresponding readme.txt or visit 14 | . 15 | 16 | You can contact the author by sending an e-mail to 'david' at the 17 | domain 'dkolf.de'. 18 | 19 | 20 | Copyright (C) 2010-2014 David Heiko Kolf 21 | 22 | Permission is hereby granted, free of charge, to any person obtaining 23 | a copy of this software and associated documentation files (the 24 | "Software"), to deal in the Software without restriction, including 25 | without limitation the rights to use, copy, modify, merge, publish, 26 | distribute, sublicense, and/or sell copies of the Software, and to 27 | permit persons to whom the Software is furnished to do so, subject to 28 | the following conditions: 29 | 30 | The above copyright notice and this permission notice shall be 31 | included in all copies or substantial portions of the Software. 32 | 33 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 34 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 35 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 36 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 37 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 38 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 39 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 40 | SOFTWARE. 41 | 42 | --]==] 43 | 44 | -- global dependencies: 45 | local pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset = 46 | pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset 47 | local error, require, pcall, select = error, require, pcall, select 48 | local floor, huge = math.floor, math.huge 49 | local strrep, gsub, strsub, strbyte, strchar, strfind, strlen, strformat = 50 | string.rep, string.gsub, string.sub, string.byte, string.char, 51 | string.find, string.len, string.format 52 | local strmatch = string.match 53 | local concat = table.concat 54 | 55 | local json = { version = "dkjson 2.5" } 56 | 57 | if register_global_module_table then 58 | _G[global_module_name] = json 59 | end 60 | 61 | local _ENV = nil -- blocking globals in Lua 5.2 62 | 63 | pcall (function() 64 | -- Enable access to blocked metatables. 65 | -- Don't worry, this module doesn't change anything in them. 66 | local debmeta = require "debug".getmetatable 67 | if debmeta then getmetatable = debmeta end 68 | end) 69 | 70 | json.null = setmetatable ({}, { 71 | __tojson = function () return "null" end 72 | }) 73 | 74 | local function isarray (tbl) 75 | local max, n, arraylen = 0, 0, 0 76 | for k,v in pairs (tbl) do 77 | if k == 'n' and type(v) == 'number' then 78 | arraylen = v 79 | if v > max then 80 | max = v 81 | end 82 | else 83 | if type(k) ~= 'number' or k < 1 or floor(k) ~= k then 84 | return false 85 | end 86 | if k > max then 87 | max = k 88 | end 89 | n = n + 1 90 | end 91 | end 92 | if max > 10 and max > arraylen and max > n * 2 then 93 | return false -- don't create an array with too many holes 94 | end 95 | return true, max 96 | end 97 | 98 | local escapecodes = { 99 | ["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f", 100 | ["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t" 101 | } 102 | 103 | local function escapeutf8 (uchar) 104 | local value = escapecodes[uchar] 105 | if value then 106 | return value 107 | end 108 | local a, b, c, d = strbyte (uchar, 1, 4) 109 | a, b, c, d = a or 0, b or 0, c or 0, d or 0 110 | if a <= 0x7f then 111 | value = a 112 | elseif 0xc0 <= a and a <= 0xdf and b >= 0x80 then 113 | value = (a - 0xc0) * 0x40 + b - 0x80 114 | elseif 0xe0 <= a and a <= 0xef and b >= 0x80 and c >= 0x80 then 115 | value = ((a - 0xe0) * 0x40 + b - 0x80) * 0x40 + c - 0x80 116 | elseif 0xf0 <= a and a <= 0xf7 and b >= 0x80 and c >= 0x80 and d >= 0x80 then 117 | value = (((a - 0xf0) * 0x40 + b - 0x80) * 0x40 + c - 0x80) * 0x40 + d - 0x80 118 | else 119 | return "" 120 | end 121 | if value <= 0xffff then 122 | return strformat ("\\u%.4x", value) 123 | elseif value <= 0x10ffff then 124 | -- encode as UTF-16 surrogate pair 125 | value = value - 0x10000 126 | local highsur, lowsur = 0xD800 + floor (value/0x400), 0xDC00 + (value % 0x400) 127 | return strformat ("\\u%.4x\\u%.4x", highsur, lowsur) 128 | else 129 | return "" 130 | end 131 | end 132 | 133 | local function fsub (str, pattern, repl) 134 | -- gsub always builds a new string in a buffer, even when no match 135 | -- exists. First using find should be more efficient when most strings 136 | -- don't contain the pattern. 137 | if strfind (str, pattern) then 138 | return gsub (str, pattern, repl) 139 | else 140 | return str 141 | end 142 | end 143 | 144 | local function quotestring (value) 145 | -- based on the regexp "escapable" in https://github.com/douglascrockford/JSON-js 146 | value = fsub (value, "[%z\1-\31\"\\\127]", escapeutf8) 147 | if strfind (value, "[\194\216\220\225\226\239]") then 148 | value = fsub (value, "\194[\128-\159\173]", escapeutf8) 149 | value = fsub (value, "\216[\128-\132]", escapeutf8) 150 | value = fsub (value, "\220\143", escapeutf8) 151 | value = fsub (value, "\225\158[\180\181]", escapeutf8) 152 | value = fsub (value, "\226\128[\140-\143\168-\175]", escapeutf8) 153 | value = fsub (value, "\226\129[\160-\175]", escapeutf8) 154 | value = fsub (value, "\239\187\191", escapeutf8) 155 | value = fsub (value, "\239\191[\176-\191]", escapeutf8) 156 | end 157 | return "\"" .. value .. "\"" 158 | end 159 | json.quotestring = quotestring 160 | 161 | local function replace(str, o, n) 162 | local i, j = strfind (str, o, 1, true) 163 | if i then 164 | return strsub(str, 1, i-1) .. n .. strsub(str, j+1, -1) 165 | else 166 | return str 167 | end 168 | end 169 | 170 | -- locale independent num2str and str2num functions 171 | local decpoint, numfilter 172 | 173 | local function updatedecpoint () 174 | decpoint = strmatch(tostring(0.5), "([^05+])") 175 | -- build a filter that can be used to remove group separators 176 | numfilter = "[^0-9%-%+eE" .. gsub(decpoint, "[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0") .. "]+" 177 | end 178 | 179 | updatedecpoint() 180 | 181 | local function num2str (num) 182 | return replace(fsub(tostring(num), numfilter, ""), decpoint, ".") 183 | end 184 | 185 | local function str2num (str) 186 | local num = tonumber(replace(str, ".", decpoint)) 187 | if not num then 188 | updatedecpoint() 189 | num = tonumber(replace(str, ".", decpoint)) 190 | end 191 | return num 192 | end 193 | 194 | local function addnewline2 (level, buffer, buflen) 195 | buffer[buflen+1] = "\n" 196 | buffer[buflen+2] = strrep (" ", level) 197 | buflen = buflen + 2 198 | return buflen 199 | end 200 | 201 | function json.addnewline (state) 202 | if state.indent then 203 | state.bufferlen = addnewline2 (state.level or 0, 204 | state.buffer, state.bufferlen or #(state.buffer)) 205 | end 206 | end 207 | 208 | local encode2 -- forward declaration 209 | 210 | local function addpair (key, value, prev, indent, level, buffer, buflen, tables, globalorder, state) 211 | local kt = type (key) 212 | if kt ~= 'string' and kt ~= 'number' then 213 | return nil, "type '" .. kt .. "' is not supported as a key by JSON." 214 | end 215 | if prev then 216 | buflen = buflen + 1 217 | buffer[buflen] = "," 218 | end 219 | if indent then 220 | buflen = addnewline2 (level, buffer, buflen) 221 | end 222 | buffer[buflen+1] = quotestring (key) 223 | buffer[buflen+2] = ":" 224 | return encode2 (value, indent, level, buffer, buflen + 2, tables, globalorder, state) 225 | end 226 | 227 | local function appendcustom(res, buffer, state) 228 | local buflen = state.bufferlen 229 | if type (res) == 'string' then 230 | buflen = buflen + 1 231 | buffer[buflen] = res 232 | end 233 | return buflen 234 | end 235 | 236 | local function exception(reason, value, state, buffer, buflen, defaultmessage) 237 | defaultmessage = defaultmessage or reason 238 | local handler = state.exception 239 | if not handler then 240 | return nil, defaultmessage 241 | else 242 | state.bufferlen = buflen 243 | local ret, msg = handler (reason, value, state, defaultmessage) 244 | if not ret then return nil, msg or defaultmessage end 245 | return appendcustom(ret, buffer, state) 246 | end 247 | end 248 | 249 | function json.encodeexception(reason, value, state, defaultmessage) 250 | return quotestring("<" .. defaultmessage .. ">") 251 | end 252 | 253 | encode2 = function (value, indent, level, buffer, buflen, tables, globalorder, state) 254 | local valtype = type (value) 255 | local valmeta = getmetatable (value) 256 | valmeta = type (valmeta) == 'table' and valmeta -- only tables 257 | local valtojson = valmeta and valmeta.__tojson 258 | if valtojson then 259 | if tables[value] then 260 | return exception('reference cycle', value, state, buffer, buflen) 261 | end 262 | tables[value] = true 263 | state.bufferlen = buflen 264 | local ret, msg = valtojson (value, state) 265 | if not ret then return exception('custom encoder failed', value, state, buffer, buflen, msg) end 266 | tables[value] = nil 267 | buflen = appendcustom(ret, buffer, state) 268 | elseif value == nil then 269 | buflen = buflen + 1 270 | buffer[buflen] = "null" 271 | elseif valtype == 'number' then 272 | local s 273 | if value ~= value or value >= huge or -value >= huge then 274 | -- This is the behaviour of the original JSON implementation. 275 | s = "null" 276 | else 277 | s = num2str (value) 278 | end 279 | buflen = buflen + 1 280 | buffer[buflen] = s 281 | elseif valtype == 'boolean' then 282 | buflen = buflen + 1 283 | buffer[buflen] = value and "true" or "false" 284 | elseif valtype == 'string' then 285 | buflen = buflen + 1 286 | buffer[buflen] = quotestring (value) 287 | elseif valtype == 'table' then 288 | if tables[value] then 289 | return exception('reference cycle', value, state, buffer, buflen) 290 | end 291 | tables[value] = true 292 | level = level + 1 293 | local isa, n = isarray (value) 294 | if n == 0 and valmeta and valmeta.__jsontype == 'object' then 295 | isa = false 296 | end 297 | local msg 298 | if isa then -- JSON array 299 | buflen = buflen + 1 300 | buffer[buflen] = "[" 301 | for i = 1, n do 302 | buflen, msg = encode2 (value[i], indent, level, buffer, buflen, tables, globalorder, state) 303 | if not buflen then return nil, msg end 304 | if i < n then 305 | buflen = buflen + 1 306 | buffer[buflen] = "," 307 | end 308 | end 309 | buflen = buflen + 1 310 | buffer[buflen] = "]" 311 | else -- JSON object 312 | local prev = false 313 | buflen = buflen + 1 314 | buffer[buflen] = "{" 315 | local order = valmeta and valmeta.__jsonorder or globalorder 316 | if order then 317 | local used = {} 318 | n = #order 319 | for i = 1, n do 320 | local k = order[i] 321 | local v = value[k] 322 | if v then 323 | used[k] = true 324 | buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state) 325 | prev = true -- add a seperator before the next element 326 | end 327 | end 328 | for k,v in pairs (value) do 329 | if not used[k] then 330 | buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state) 331 | if not buflen then return nil, msg end 332 | prev = true -- add a seperator before the next element 333 | end 334 | end 335 | else -- unordered 336 | for k,v in pairs (value) do 337 | buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state) 338 | if not buflen then return nil, msg end 339 | prev = true -- add a seperator before the next element 340 | end 341 | end 342 | if indent then 343 | buflen = addnewline2 (level - 1, buffer, buflen) 344 | end 345 | buflen = buflen + 1 346 | buffer[buflen] = "}" 347 | end 348 | tables[value] = nil 349 | else 350 | return exception ('unsupported type', value, state, buffer, buflen, 351 | "type '" .. valtype .. "' is not supported by JSON.") 352 | end 353 | return buflen 354 | end 355 | 356 | function json.encode (value, state) 357 | state = state or {} 358 | local oldbuffer = state.buffer 359 | local buffer = oldbuffer or {} 360 | state.buffer = buffer 361 | updatedecpoint() 362 | local ret, msg = encode2 (value, state.indent, state.level or 0, 363 | buffer, state.bufferlen or 0, state.tables or {}, state.keyorder, state) 364 | if not ret then 365 | error (msg, 2) 366 | elseif oldbuffer == buffer then 367 | state.bufferlen = ret 368 | return true 369 | else 370 | state.bufferlen = nil 371 | state.buffer = nil 372 | return concat (buffer) 373 | end 374 | end 375 | 376 | local function loc (str, where) 377 | local line, pos, linepos = 1, 1, 0 378 | while true do 379 | pos = strfind (str, "\n", pos, true) 380 | if pos and pos < where then 381 | line = line + 1 382 | linepos = pos 383 | pos = pos + 1 384 | else 385 | break 386 | end 387 | end 388 | return "line " .. line .. ", column " .. (where - linepos) 389 | end 390 | 391 | local function unterminated (str, what, where) 392 | return nil, strlen (str) + 1, "unterminated " .. what .. " at " .. loc (str, where) 393 | end 394 | 395 | local function scanwhite (str, pos) 396 | while true do 397 | pos = strfind (str, "%S", pos) 398 | if not pos then return nil end 399 | local sub2 = strsub (str, pos, pos + 1) 400 | if sub2 == "\239\187" and strsub (str, pos + 2, pos + 2) == "\191" then 401 | -- UTF-8 Byte Order Mark 402 | pos = pos + 3 403 | elseif sub2 == "//" then 404 | pos = strfind (str, "[\n\r]", pos + 2) 405 | if not pos then return nil end 406 | elseif sub2 == "/*" then 407 | pos = strfind (str, "*/", pos + 2) 408 | if not pos then return nil end 409 | pos = pos + 2 410 | else 411 | return pos 412 | end 413 | end 414 | end 415 | 416 | local escapechars = { 417 | ["\""] = "\"", ["\\"] = "\\", ["/"] = "/", ["b"] = "\b", ["f"] = "\f", 418 | ["n"] = "\n", ["r"] = "\r", ["t"] = "\t" 419 | } 420 | 421 | local function unichar (value) 422 | if value < 0 then 423 | return nil 424 | elseif value <= 0x007f then 425 | return strchar (value) 426 | elseif value <= 0x07ff then 427 | return strchar (0xc0 + floor(value/0x40), 428 | 0x80 + (floor(value) % 0x40)) 429 | elseif value <= 0xffff then 430 | return strchar (0xe0 + floor(value/0x1000), 431 | 0x80 + (floor(value/0x40) % 0x40), 432 | 0x80 + (floor(value) % 0x40)) 433 | elseif value <= 0x10ffff then 434 | return strchar (0xf0 + floor(value/0x40000), 435 | 0x80 + (floor(value/0x1000) % 0x40), 436 | 0x80 + (floor(value/0x40) % 0x40), 437 | 0x80 + (floor(value) % 0x40)) 438 | else 439 | return nil 440 | end 441 | end 442 | 443 | local function scanstring (str, pos) 444 | local lastpos = pos + 1 445 | local buffer, n = {}, 0 446 | while true do 447 | local nextpos = strfind (str, "[\"\\]", lastpos) 448 | if not nextpos then 449 | return unterminated (str, "string", pos) 450 | end 451 | if nextpos > lastpos then 452 | n = n + 1 453 | buffer[n] = strsub (str, lastpos, nextpos - 1) 454 | end 455 | if strsub (str, nextpos, nextpos) == "\"" then 456 | lastpos = nextpos + 1 457 | break 458 | else 459 | local escchar = strsub (str, nextpos + 1, nextpos + 1) 460 | local value 461 | if escchar == "u" then 462 | value = tonumber (strsub (str, nextpos + 2, nextpos + 5), 16) 463 | if value then 464 | local value2 465 | if 0xD800 <= value and value <= 0xDBff then 466 | -- we have the high surrogate of UTF-16. Check if there is a 467 | -- low surrogate escaped nearby to combine them. 468 | if strsub (str, nextpos + 6, nextpos + 7) == "\\u" then 469 | value2 = tonumber (strsub (str, nextpos + 8, nextpos + 11), 16) 470 | if value2 and 0xDC00 <= value2 and value2 <= 0xDFFF then 471 | value = (value - 0xD800) * 0x400 + (value2 - 0xDC00) + 0x10000 472 | else 473 | value2 = nil -- in case it was out of range for a low surrogate 474 | end 475 | end 476 | end 477 | value = value and unichar (value) 478 | if value then 479 | if value2 then 480 | lastpos = nextpos + 12 481 | else 482 | lastpos = nextpos + 6 483 | end 484 | end 485 | end 486 | end 487 | if not value then 488 | value = escapechars[escchar] or escchar 489 | lastpos = nextpos + 2 490 | end 491 | n = n + 1 492 | buffer[n] = value 493 | end 494 | end 495 | if n == 1 then 496 | return buffer[1], lastpos 497 | elseif n > 1 then 498 | return concat (buffer), lastpos 499 | else 500 | return "", lastpos 501 | end 502 | end 503 | 504 | local scanvalue -- forward declaration 505 | 506 | local function scantable (what, closechar, str, startpos, nullval, objectmeta, arraymeta) 507 | local len = strlen (str) 508 | local tbl, n = {}, 0 509 | local pos = startpos + 1 510 | if what == 'object' then 511 | setmetatable (tbl, objectmeta) 512 | else 513 | setmetatable (tbl, arraymeta) 514 | end 515 | while true do 516 | pos = scanwhite (str, pos) 517 | if not pos then return unterminated (str, what, startpos) end 518 | local char = strsub (str, pos, pos) 519 | if char == closechar then 520 | return tbl, pos + 1 521 | end 522 | local val1, err 523 | val1, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta) 524 | if err then return nil, pos, err end 525 | pos = scanwhite (str, pos) 526 | if not pos then return unterminated (str, what, startpos) end 527 | char = strsub (str, pos, pos) 528 | if char == ":" then 529 | if val1 == nil then 530 | return nil, pos, "cannot use nil as table index (at " .. loc (str, pos) .. ")" 531 | end 532 | pos = scanwhite (str, pos + 1) 533 | if not pos then return unterminated (str, what, startpos) end 534 | local val2 535 | val2, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta) 536 | if err then return nil, pos, err end 537 | tbl[val1] = val2 538 | pos = scanwhite (str, pos) 539 | if not pos then return unterminated (str, what, startpos) end 540 | char = strsub (str, pos, pos) 541 | else 542 | n = n + 1 543 | tbl[n] = val1 544 | end 545 | if char == "," then 546 | pos = pos + 1 547 | end 548 | end 549 | end 550 | 551 | scanvalue = function (str, pos, nullval, objectmeta, arraymeta) 552 | pos = pos or 1 553 | pos = scanwhite (str, pos) 554 | if not pos then 555 | return nil, strlen (str) + 1, "no valid JSON value (reached the end)" 556 | end 557 | local char = strsub (str, pos, pos) 558 | if char == "{" then 559 | return scantable ('object', "}", str, pos, nullval, objectmeta, arraymeta) 560 | elseif char == "[" then 561 | return scantable ('array', "]", str, pos, nullval, objectmeta, arraymeta) 562 | elseif char == "\"" then 563 | return scanstring (str, pos) 564 | else 565 | local pstart, pend = strfind (str, "^%-?[%d%.]+[eE]?[%+%-]?%d*", pos) 566 | if pstart then 567 | local number = str2num (strsub (str, pstart, pend)) 568 | if number then 569 | return number, pend + 1 570 | end 571 | end 572 | pstart, pend = strfind (str, "^%a%w*", pos) 573 | if pstart then 574 | local name = strsub (str, pstart, pend) 575 | if name == "true" then 576 | return true, pend + 1 577 | elseif name == "false" then 578 | return false, pend + 1 579 | elseif name == "null" then 580 | return nullval, pend + 1 581 | end 582 | end 583 | return nil, pos, "no valid JSON value at " .. loc (str, pos) 584 | end 585 | end 586 | 587 | local function optionalmetatables(...) 588 | if select("#", ...) > 0 then 589 | return ... 590 | else 591 | return {__jsontype = 'object'}, {__jsontype = 'array'} 592 | end 593 | end 594 | 595 | function json.decode (str, pos, nullval, ...) 596 | local objectmeta, arraymeta = optionalmetatables(...) 597 | return scanvalue (str, pos, nullval, objectmeta, arraymeta) 598 | end 599 | 600 | function json.use_lpeg () 601 | local g = require ("lpeg") 602 | 603 | if g.version() == "0.11" then 604 | error "due to a bug in LPeg 0.11, it cannot be used for JSON matching" 605 | end 606 | 607 | local pegmatch = g.match 608 | local P, S, R = g.P, g.S, g.R 609 | 610 | local function ErrorCall (str, pos, msg, state) 611 | if not state.msg then 612 | state.msg = msg .. " at " .. loc (str, pos) 613 | state.pos = pos 614 | end 615 | return false 616 | end 617 | 618 | local function Err (msg) 619 | return g.Cmt (g.Cc (msg) * g.Carg (2), ErrorCall) 620 | end 621 | 622 | local SingleLineComment = P"//" * (1 - S"\n\r")^0 623 | local MultiLineComment = P"/*" * (1 - P"*/")^0 * P"*/" 624 | local Space = (S" \n\r\t" + P"\239\187\191" + SingleLineComment + MultiLineComment)^0 625 | 626 | local PlainChar = 1 - S"\"\\\n\r" 627 | local EscapeSequence = (P"\\" * g.C (S"\"\\/bfnrt" + Err "unsupported escape sequence")) / escapechars 628 | local HexDigit = R("09", "af", "AF") 629 | local function UTF16Surrogate (match, pos, high, low) 630 | high, low = tonumber (high, 16), tonumber (low, 16) 631 | if 0xD800 <= high and high <= 0xDBff and 0xDC00 <= low and low <= 0xDFFF then 632 | return true, unichar ((high - 0xD800) * 0x400 + (low - 0xDC00) + 0x10000) 633 | else 634 | return false 635 | end 636 | end 637 | local function UTF16BMP (hex) 638 | return unichar (tonumber (hex, 16)) 639 | end 640 | local U16Sequence = (P"\\u" * g.C (HexDigit * HexDigit * HexDigit * HexDigit)) 641 | local UnicodeEscape = g.Cmt (U16Sequence * U16Sequence, UTF16Surrogate) + U16Sequence/UTF16BMP 642 | local Char = UnicodeEscape + EscapeSequence + PlainChar 643 | local String = P"\"" * g.Cs (Char ^ 0) * (P"\"" + Err "unterminated string") 644 | local Integer = P"-"^(-1) * (P"0" + (R"19" * R"09"^0)) 645 | local Fractal = P"." * R"09"^0 646 | local Exponent = (S"eE") * (S"+-")^(-1) * R"09"^1 647 | local Number = (Integer * Fractal^(-1) * Exponent^(-1))/str2num 648 | local Constant = P"true" * g.Cc (true) + P"false" * g.Cc (false) + P"null" * g.Carg (1) 649 | local SimpleValue = Number + String + Constant 650 | local ArrayContent, ObjectContent 651 | 652 | -- The functions parsearray and parseobject parse only a single value/pair 653 | -- at a time and store them directly to avoid hitting the LPeg limits. 654 | local function parsearray (str, pos, nullval, state) 655 | local obj, cont 656 | local npos 657 | local t, nt = {}, 0 658 | repeat 659 | obj, cont, npos = pegmatch (ArrayContent, str, pos, nullval, state) 660 | if not npos then break end 661 | pos = npos 662 | nt = nt + 1 663 | t[nt] = obj 664 | until cont == 'last' 665 | return pos, setmetatable (t, state.arraymeta) 666 | end 667 | 668 | local function parseobject (str, pos, nullval, state) 669 | local obj, key, cont 670 | local npos 671 | local t = {} 672 | repeat 673 | key, obj, cont, npos = pegmatch (ObjectContent, str, pos, nullval, state) 674 | if not npos then break end 675 | pos = npos 676 | t[key] = obj 677 | until cont == 'last' 678 | return pos, setmetatable (t, state.objectmeta) 679 | end 680 | 681 | local Array = P"[" * g.Cmt (g.Carg(1) * g.Carg(2), parsearray) * Space * (P"]" + Err "']' expected") 682 | local Object = P"{" * g.Cmt (g.Carg(1) * g.Carg(2), parseobject) * Space * (P"}" + Err "'}' expected") 683 | local Value = Space * (Array + Object + SimpleValue) 684 | local ExpectedValue = Value + Space * Err "value expected" 685 | ArrayContent = Value * Space * (P"," * g.Cc'cont' + g.Cc'last') * g.Cp() 686 | local Pair = g.Cg (Space * String * Space * (P":" + Err "colon expected") * ExpectedValue) 687 | ObjectContent = Pair * Space * (P"," * g.Cc'cont' + g.Cc'last') * g.Cp() 688 | local DecodeValue = ExpectedValue * g.Cp () 689 | 690 | function json.decode (str, pos, nullval, ...) 691 | local state = {} 692 | state.objectmeta, state.arraymeta = optionalmetatables(...) 693 | local obj, retpos = pegmatch (DecodeValue, str, pos, nullval, state) 694 | if state.msg then 695 | return nil, state.pos, state.msg 696 | else 697 | return obj, retpos 698 | end 699 | end 700 | 701 | -- use this function only once: 702 | json.use_lpeg = function () return json end 703 | 704 | json.using_lpeg = true 705 | 706 | return json -- so you can get the module using json = require "dkjson".use_lpeg() 707 | end 708 | 709 | if always_try_using_lpeg then 710 | pcall (json.use_lpeg) 711 | end 712 | 713 | return json 714 | 715 | --------------------------------------------------------------------------------