Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added LuaJIT compatibility #8

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@ notes
packages
tags
tests/utf8.dat
*~
*.swp
go
18 changes: 17 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,30 @@ cmake_minimum_required(VERSION 2.6)

option(USE_INTERNAL_FPCONV "Use internal strtod() / g_fmt() code for performance")
option(MULTIPLE_THREADS "Support multi-threaded apps with internal fpconv - recommended" ON)
option(USE_LUA "Use Lua (also called 'C' Lua) version 5.1 includes (default)" ON)
option(USE_LUAJIT "Use LuaJIT includes instead of 'C' Lua ones (recommended, if you're using LuaJIT, but disabled by default)")

set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/")

if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release CACHE STRING
"Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel."
FORCE)
endif()

find_package(Lua51 REQUIRED)
if(USE_LUAJIT)
# Find luajit
find_package(LuaJIT REQUIRED)
set(USE_LUA OFF)
# / Find lua
endif()

if(USE_LUA)
# Find lua
find_package(Lua51 REQUIRED)
# / Find lua
endif()

include_directories(${LUA_INCLUDE_DIR})

if(NOT USE_INTERNAL_FPCONV)
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ $(TARGET): $(OBJS)

install: $(TARGET)
mkdir -p $(DESTDIR)/$(LUA_CMODULE_DIR)
rm -f $(DESTDIR)/$(LUA_CMODULE_DIR)/$(TARGET)
cp $(TARGET) $(DESTDIR)/$(LUA_CMODULE_DIR)
chmod $(EXECPERM) $(DESTDIR)/$(LUA_CMODULE_DIR)/$(TARGET)

Expand Down
62 changes: 62 additions & 0 deletions cmake/Modules/FindLuaJIT.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Locate LuaJIT library
# This module defines
# LUAJIT_FOUND, if false, do not try to link to Lua
# LUA_LIBRARIES
# LUA_INCLUDE_DIR, where to find lua.h
# LUAJIT_VERSION_STRING, the version of Lua found (since CMake 2.8.8)

## Copied from default CMake FindLua51.cmake

find_path(LUA_INCLUDE_DIR luajit.h
HINTS
ENV LUA_DIR
PATH_SUFFIXES include/luajit-2.1 include/luajit-2.0 include
PATHS
~/Library/Frameworks
/Library/Frameworks
/sw # Fink
/opt/local # DarwinPorts
/opt/csw # Blastwave
/opt
)

find_library(LUA_LIBRARY
NAMES luajit-5.1
HINTS
ENV LUA_DIR
PATH_SUFFIXES lib
PATHS
~/Library/Frameworks
/Library/Frameworks
/sw
/opt/local
/opt/csw
/opt
)

if(LUA_LIBRARY)
# include the math library for Unix
if(UNIX AND NOT APPLE)
find_library(LUA_MATH_LIBRARY m)
set( LUA_LIBRARIES "${LUA_LIBRARY};${LUA_MATH_LIBRARY}" CACHE STRING "Lua Libraries")
# For Windows and Mac, don't need to explicitly include the math library
else()
set( LUA_LIBRARIES "${LUA_LIBRARY}" CACHE STRING "Lua Libraries")
endif()
endif()

if(LUA_INCLUDE_DIR AND EXISTS "${LUA_INCLUDE_DIR}/luajit.h")
file(STRINGS "${LUA_INCLUDE_DIR}/luajit.h" luajit_version_str REGEX "^#define[ \t]+LUAJIT_VERSION[ \t]+\"LuaJIT .+\"")

string(REGEX REPLACE "^#define[ \t]+LUAJIT_VERSION[ \t]+\"LuaJIT ([^\"]+)\".*" "\\1" LUAJIT_VERSION_STRING "${luajit_version_str}")
unset(luajit_version_str)
endif()

include(FindPackageHandleStandardArgs)
# handle the QUIETLY and REQUIRED arguments and set LUA_FOUND to TRUE if
# all listed variables are TRUE
FIND_PACKAGE_HANDLE_STANDARD_ARGS(LuaJIT
REQUIRED_VARS LUA_LIBRARIES LUA_INCLUDE_DIR
VERSION_VAR LUAJIT_VERSION_STRING)

mark_as_advanced(LUA_INCLUDE_DIR LUA_LIBRARIES LUA_LIBRARY LUA_MATH_LIBRARY)
48 changes: 40 additions & 8 deletions lua_cjson.c
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
#define DEFAULT_DECODE_INVALID_NUMBERS 1
#define DEFAULT_ENCODE_KEEP_BUFFER 1
#define DEFAULT_ENCODE_NUMBER_PRECISION 14
#define DEFAULT_ENCODE_EMPTY_TABLE_AS_OBJECT 1

#ifdef DISABLE_INVALID_NUMBERS
#undef DEFAULT_DECODE_INVALID_NUMBERS
Expand All @@ -81,6 +82,7 @@ typedef enum {
T_ARR_END,
T_STRING,
T_NUMBER,
T_INTEGER,
T_BOOLEAN,
T_NULL,
T_COLON,
Expand All @@ -98,6 +100,7 @@ static const char *json_token_type_name[] = {
"T_ARR_END",
"T_STRING",
"T_NUMBER",
"T_INTEGER",
"T_BOOLEAN",
"T_NULL",
"T_COLON",
Expand All @@ -124,6 +127,7 @@ typedef struct {
int encode_invalid_numbers; /* 2 => Encode as "null" */
int encode_number_precision;
int encode_keep_buffer;
int encode_empty_table_as_object;

int decode_invalid_numbers;
int decode_max_depth;
Expand All @@ -143,6 +147,7 @@ typedef struct {
union {
const char *string;
double number;
lua_Integer integer;
int boolean;
} value;
int string_len;
Expand Down Expand Up @@ -300,6 +305,14 @@ static int json_cfg_encode_number_precision(lua_State *l)
return json_integer_option(l, 1, &cfg->encode_number_precision, 1, 14);
}

/* Configures how to treat empty table when encode lua table */
static int json_cfg_encode_empty_table_as_object(lua_State *l)
{
json_config_t *cfg = json_arg_init(l, 1);

return json_enum_option(l, 1, &cfg->encode_empty_table_as_object, NULL, 1);
}

/* Configures JSON encoding buffer persistence */
static int json_cfg_encode_keep_buffer(lua_State *l)
{
Expand Down Expand Up @@ -390,6 +403,7 @@ static void json_create_config(lua_State *l)
cfg->decode_invalid_numbers = DEFAULT_DECODE_INVALID_NUMBERS;
cfg->encode_keep_buffer = DEFAULT_ENCODE_KEEP_BUFFER;
cfg->encode_number_precision = DEFAULT_ENCODE_NUMBER_PRECISION;
cfg->encode_empty_table_as_object = DEFAULT_ENCODE_EMPTY_TABLE_AS_OBJECT;

#if DEFAULT_ENCODE_KEEP_BUFFER > 0
strbuf_init(&cfg->encode_buf, 0);
Expand Down Expand Up @@ -586,8 +600,17 @@ static void json_append_array(lua_State *l, json_config_t *cfg, int current_dept
static void json_append_number(lua_State *l, json_config_t *cfg,
strbuf_t *json, int lindex)
{
double num = lua_tonumber(l, lindex);
int len;
#if LUA_VERSION_NUM >= 503
if (lua_isinteger(l, lindex)) {
lua_Integer num = lua_tointeger(l, lindex);
strbuf_ensure_empty_length(json, FPCONV_G_FMT_BUFSIZE); /* max length of int64 is 19 */
len = lua_integer2str(strbuf_empty_ptr(json), num);
strbuf_extend_length(json, len);
return;
}
#endif
double num = lua_tonumber(l, lindex);

if (cfg->encode_invalid_numbers == 0) {
/* Prevent encoding invalid numbers */
Expand Down Expand Up @@ -685,7 +708,7 @@ static void json_append_data(lua_State *l, json_config_t *cfg,
current_depth++;
json_check_encode_depth(l, cfg, current_depth, json);
len = lua_array_length(l, cfg, json);
if (len > 0)
if (len > 0 || (len == 0 && !cfg->encode_empty_table_as_object))
json_append_array(l, cfg, current_depth, json, len);
else
json_append_object(l, cfg, current_depth, json);
Expand Down Expand Up @@ -1002,13 +1025,18 @@ static int json_is_invalid_number(json_parse_t *json)
static void json_next_number_token(json_parse_t *json, json_token_t *token)
{
char *endptr;

token->type = T_NUMBER;
token->value.number = fpconv_strtod(json->ptr, &endptr);
if (json->ptr == endptr)
token->value.integer = strtoll(json->ptr, &endptr, 0);
if (json->ptr == endptr) {
json_set_token_error(token, json, "invalid number");
else
json->ptr = endptr; /* Skip the processed number */
return;
}
if (*endptr == '.' || *endptr == 'e' || *endptr == 'E') {
token->type = T_NUMBER;
token->value.number = fpconv_strtod(json->ptr, &endptr);
} else {
token->type = T_INTEGER;
}
json->ptr = endptr; /* Skip the processed number */

return;
}
Expand Down Expand Up @@ -1237,6 +1265,9 @@ static void json_process_value(lua_State *l, json_parse_t *json,
case T_NUMBER:
lua_pushnumber(l, token->value.number);
break;;
case T_INTEGER:
lua_pushinteger(l, token->value.integer);
break;;
case T_BOOLEAN:
lua_pushboolean(l, token->value.boolean);
break;;
Expand Down Expand Up @@ -1352,6 +1383,7 @@ static int lua_cjson_new(lua_State *l)
luaL_Reg reg[] = {
{ "encode", json_encode },
{ "decode", json_decode },
{ "encode_empty_table_as_object", json_cfg_encode_empty_table_as_object },
{ "encode_sparse_array", json_cfg_encode_sparse_array },
{ "encode_max_depth", json_cfg_encode_max_depth },
{ "decode_max_depth", json_cfg_decode_max_depth },
Expand Down
70 changes: 70 additions & 0 deletions tests/TestLua.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package TestLua;

use Test::Base -Base;
use IPC::Run3;
use Cwd;

use Test::LongString;

our @EXPORT = qw( run_tests );

$ENV{LUA_CPATH} = "../?.so;;";
$ENV{LUA_PATH} = "../lua/?.lua;;";
#$ENV{LUA_PATH} = ($ENV{LUA_PATH} || "" ) . ';' . getcwd . "/runtime/?.lua" . ';;';

sub run_test ($) {
my $block = shift;
#print $json_xs->pretty->encode(\@new_rows);
#my $res = #print $json_xs->pretty->encode($res);
my $name = $block->name;

my $lua = $block->lua or
die "No --- lua specified for test $name\n";

my $luafile = "test_case.lua";

open my $fh, ">$luafile" or
die "Cannot open $luafile for writing: $!\n";

print $fh $lua;
close $fh;

my ($res, $err);

my @cmd;

if ($ENV{TEST_LUA_USE_VALGRIND}) {
@cmd = ('valgrind', '-q', '--leak-check=full', 'lua', 'test_case.lua');
} else {
@cmd = ('lua', 'test_case.lua');
}

run3 \@cmd, undef, \$res, \$err;
my $rc = $?;

#warn "res:$res\nerr:$err\n";

if (defined $block->err) {
$err =~ /.*:.*:.*: (.*\s)?/;
$err = $1;
is $err, $block->err, "$name - err expected";

} elsif ($rc) {
die "Failed to execute --- lua for test $name: $err\n";

} else {
#is $res, $block->out, "$name - output ok";
is $res, $block->out, "$name - output ok";
}

is $rc, ($block->exit || 0), "$name - exit code ok";
#unlink 'test_case.lua' or warn "could not delete \'test_case.lua\':$!";
}

sub run_tests () {
for my $block (blocks()) {
run_test($block);
}
}

1;
52 changes: 52 additions & 0 deletions tests/agentzh.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use TestLua;

plan tests => 2 * blocks();

run_tests();

__DATA__

=== TEST 1: empty tables as objects
--- lua
local cjson = require "cjson"
print(cjson.encode({}))
print(cjson.encode({dogs = {}}))
--- out
{}
{"dogs":{}}



=== TEST 2: empty tables as arrays
--- lua
local cjson = require "cjson"
cjson.encode_empty_table_as_object(false)
print(cjson.encode({}))
print(cjson.encode({dogs = {}}))
--- out
[]
{"dogs":[]}



=== TEST 3: empty tables as objects (explicit)
--- lua
local cjson = require "cjson"
cjson.encode_empty_table_as_object(true)
print(cjson.encode({}))
print(cjson.encode({dogs = {}}))
--- out
{}
{"dogs":{}}



=== TEST 4: & in JSON
--- lua
local cjson = require "cjson"
local a="[\"a=1&b=2\"]"
local b=cjson.decode(a)
print(cjson.encode(b))
--- out
["a=1&b=2"]