Implemented issue: #47
This commit is contained in:
BIN
core/src/main/java/org/luaj/vm2/libs/CjsonLib$Encoder.class
Normal file
BIN
core/src/main/java/org/luaj/vm2/libs/CjsonLib$Encoder.class
Normal file
Binary file not shown.
BIN
core/src/main/java/org/luaj/vm2/libs/CjsonLib$Parser.class
Normal file
BIN
core/src/main/java/org/luaj/vm2/libs/CjsonLib$Parser.class
Normal file
Binary file not shown.
BIN
core/src/main/java/org/luaj/vm2/libs/CjsonLib$decode.class
Normal file
BIN
core/src/main/java/org/luaj/vm2/libs/CjsonLib$decode.class
Normal file
Binary file not shown.
BIN
core/src/main/java/org/luaj/vm2/libs/CjsonLib$encode.class
Normal file
BIN
core/src/main/java/org/luaj/vm2/libs/CjsonLib$encode.class
Normal file
Binary file not shown.
BIN
core/src/main/java/org/luaj/vm2/libs/CjsonLib$loader.class
Normal file
BIN
core/src/main/java/org/luaj/vm2/libs/CjsonLib$loader.class
Normal file
Binary file not shown.
BIN
core/src/main/java/org/luaj/vm2/libs/CjsonLib.class
Normal file
BIN
core/src/main/java/org/luaj/vm2/libs/CjsonLib.class
Normal file
Binary file not shown.
414
core/src/main/java/org/luaj/vm2/libs/CjsonLib.java
Normal file
414
core/src/main/java/org/luaj/vm2/libs/CjsonLib.java
Normal file
@@ -0,0 +1,414 @@
|
||||
package org.luaj.vm2.libs;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.luaj.vm2.LuaDouble;
|
||||
import org.luaj.vm2.LuaInteger;
|
||||
import org.luaj.vm2.LuaString;
|
||||
import org.luaj.vm2.LuaTable;
|
||||
import org.luaj.vm2.LuaUserdata;
|
||||
import org.luaj.vm2.LuaValue;
|
||||
import org.luaj.vm2.Varargs;
|
||||
|
||||
/**
|
||||
* Minimal built-in cjson-compatible module implemented in Java.
|
||||
* It supports require("cjson"), cjson.encode(...), cjson.decode(...), and cjson.null.
|
||||
*/
|
||||
public class CjsonLib extends TwoArgFunction {
|
||||
|
||||
private static final Object NULL_SENTINEL = new Object();
|
||||
|
||||
public LuaValue call(LuaValue modname, LuaValue env) {
|
||||
LuaValue pkg = env.get("package");
|
||||
if (!pkg.istable()) {
|
||||
return env;
|
||||
}
|
||||
LuaValue preload = pkg.get("preload");
|
||||
if (!preload.istable()) {
|
||||
return env;
|
||||
}
|
||||
preload.set("cjson", new loader());
|
||||
return env;
|
||||
}
|
||||
|
||||
static LuaValue nullValue() {
|
||||
return new LuaUserdata(NULL_SENTINEL);
|
||||
}
|
||||
|
||||
static boolean isNullSentinel(LuaValue value) {
|
||||
return value.isuserdata() && value.touserdata() == NULL_SENTINEL;
|
||||
}
|
||||
|
||||
static final class loader extends TwoArgFunction {
|
||||
public LuaValue call(LuaValue modname, LuaValue env) {
|
||||
LuaTable module = new LuaTable(0, 3);
|
||||
module.set("encode", new encode());
|
||||
module.set("decode", new decode());
|
||||
module.set("null", nullValue());
|
||||
return module;
|
||||
}
|
||||
}
|
||||
|
||||
static final class encode extends OneArgFunction {
|
||||
public LuaValue call(LuaValue value) {
|
||||
StringBuilder out = new StringBuilder();
|
||||
new Encoder().append(value, out, new HashSet());
|
||||
return LuaValue.valueOf(out.toString());
|
||||
}
|
||||
}
|
||||
|
||||
static final class decode extends OneArgFunction {
|
||||
public LuaValue call(LuaValue value) {
|
||||
Parser parser = new Parser(value.checkjstring());
|
||||
LuaValue result = parser.parseValue();
|
||||
parser.skipWhitespace();
|
||||
if (!parser.eof()) {
|
||||
error("trailing garbage");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Encoder {
|
||||
void append(LuaValue value, StringBuilder out, Set seen) {
|
||||
if (value.isnil() || isNullSentinel(value)) {
|
||||
out.append("null");
|
||||
} else if (value.isboolean()) {
|
||||
out.append(value.toboolean() ? "true" : "false");
|
||||
} else if (value.isnumber()) {
|
||||
double d = value.todouble();
|
||||
if (Double.isNaN(d) || Double.isInfinite(d)) {
|
||||
error("cannot encode NaN or infinite numbers");
|
||||
}
|
||||
out.append(value.tojstring());
|
||||
} else if (value.isstring()) {
|
||||
appendString(value.checkjstring(), out);
|
||||
} else if (value.istable()) {
|
||||
appendTable(value.checktable(), out, seen);
|
||||
} else {
|
||||
error("unsupported type for json encoding: " + value.typename());
|
||||
}
|
||||
}
|
||||
|
||||
private void appendTable(LuaTable table, StringBuilder out, Set seen) {
|
||||
if (!seen.add(table)) {
|
||||
error("circular reference");
|
||||
}
|
||||
try {
|
||||
if (isArray(table)) {
|
||||
out.append('[');
|
||||
int n = table.length();
|
||||
for (int i = 1; i <= n; i++) {
|
||||
if (i > 1) {
|
||||
out.append(',');
|
||||
}
|
||||
append(table.get(i), out, seen);
|
||||
}
|
||||
out.append(']');
|
||||
} else {
|
||||
out.append('{');
|
||||
LuaValue key = LuaValue.NIL;
|
||||
boolean first = true;
|
||||
while (true) {
|
||||
Varargs next = table.next(key);
|
||||
key = next.arg1();
|
||||
if (key.isnil()) {
|
||||
break;
|
||||
}
|
||||
if (!first) {
|
||||
out.append(',');
|
||||
}
|
||||
first = false;
|
||||
appendString(key.checkjstring(), out);
|
||||
out.append(':');
|
||||
append(next.arg(2), out, seen);
|
||||
}
|
||||
out.append('}');
|
||||
}
|
||||
} finally {
|
||||
seen.remove(table);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isArray(LuaTable table) {
|
||||
int n = table.length();
|
||||
int count = 0;
|
||||
LuaValue key = LuaValue.NIL;
|
||||
while (true) {
|
||||
Varargs next = table.next(key);
|
||||
key = next.arg1();
|
||||
if (key.isnil()) {
|
||||
break;
|
||||
}
|
||||
if (!key.isinttype()) {
|
||||
return false;
|
||||
}
|
||||
int index = key.toint();
|
||||
if (index < 1 || index > n) {
|
||||
return false;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
return count == n;
|
||||
}
|
||||
|
||||
private void appendString(String value, StringBuilder out) {
|
||||
out.append('"');
|
||||
for (int i = 0; i < value.length(); i++) {
|
||||
char c = value.charAt(i);
|
||||
switch (c) {
|
||||
case '"':
|
||||
case '\\':
|
||||
out.append('\\').append(c);
|
||||
break;
|
||||
case '\b':
|
||||
out.append("\\b");
|
||||
break;
|
||||
case '\f':
|
||||
out.append("\\f");
|
||||
break;
|
||||
case '\n':
|
||||
out.append("\\n");
|
||||
break;
|
||||
case '\r':
|
||||
out.append("\\r");
|
||||
break;
|
||||
case '\t':
|
||||
out.append("\\t");
|
||||
break;
|
||||
default:
|
||||
if (c < 0x20) {
|
||||
String hex = Integer.toHexString(c);
|
||||
out.append("\\u");
|
||||
for (int j = hex.length(); j < 4; j++) {
|
||||
out.append('0');
|
||||
}
|
||||
out.append(hex);
|
||||
} else {
|
||||
out.append(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
out.append('"');
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Parser {
|
||||
private final String input;
|
||||
private int pos;
|
||||
|
||||
Parser(String input) {
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
LuaValue parseValue() {
|
||||
skipWhitespace();
|
||||
if (eof()) {
|
||||
error("unexpected end of json");
|
||||
}
|
||||
char c = input.charAt(pos);
|
||||
switch (c) {
|
||||
case '{':
|
||||
return parseObject();
|
||||
case '[':
|
||||
return parseArray();
|
||||
case '"':
|
||||
return LuaValue.valueOf(parseString());
|
||||
case 't':
|
||||
expect("true");
|
||||
return LuaValue.TRUE;
|
||||
case 'f':
|
||||
expect("false");
|
||||
return LuaValue.FALSE;
|
||||
case 'n':
|
||||
expect("null");
|
||||
return nullValue();
|
||||
default:
|
||||
if (c == '-' || (c >= '0' && c <= '9')) {
|
||||
return parseNumber();
|
||||
}
|
||||
error("invalid json value");
|
||||
return LuaValue.NIL;
|
||||
}
|
||||
}
|
||||
|
||||
private LuaValue parseObject() {
|
||||
LuaTable table = new LuaTable();
|
||||
pos++;
|
||||
skipWhitespace();
|
||||
if (consume('}')) {
|
||||
return table;
|
||||
}
|
||||
while (true) {
|
||||
skipWhitespace();
|
||||
if (eof() || input.charAt(pos) != '"') {
|
||||
error("expected string key");
|
||||
}
|
||||
String key = parseString();
|
||||
skipWhitespace();
|
||||
if (!consume(':')) {
|
||||
error("expected ':'");
|
||||
}
|
||||
table.set(key, parseValue());
|
||||
skipWhitespace();
|
||||
if (consume('}')) {
|
||||
return table;
|
||||
}
|
||||
if (!consume(',')) {
|
||||
error("expected ',' or '}'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private LuaValue parseArray() {
|
||||
LuaTable table = new LuaTable();
|
||||
int index = 1;
|
||||
pos++;
|
||||
skipWhitespace();
|
||||
if (consume(']')) {
|
||||
return table;
|
||||
}
|
||||
while (true) {
|
||||
table.set(index++, parseValue());
|
||||
skipWhitespace();
|
||||
if (consume(']')) {
|
||||
return table;
|
||||
}
|
||||
if (!consume(',')) {
|
||||
error("expected ',' or ']'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private LuaValue parseNumber() {
|
||||
int start = pos;
|
||||
if (input.charAt(pos) == '-') {
|
||||
pos++;
|
||||
}
|
||||
readDigits();
|
||||
boolean isFloat = false;
|
||||
if (!eof() && input.charAt(pos) == '.') {
|
||||
isFloat = true;
|
||||
pos++;
|
||||
readDigits();
|
||||
}
|
||||
if (!eof()) {
|
||||
char c = input.charAt(pos);
|
||||
if (c == 'e' || c == 'E') {
|
||||
isFloat = true;
|
||||
pos++;
|
||||
if (!eof()) {
|
||||
char sign = input.charAt(pos);
|
||||
if (sign == '+' || sign == '-') {
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
readDigits();
|
||||
}
|
||||
}
|
||||
String number = input.substring(start, pos);
|
||||
try {
|
||||
return isFloat ? LuaValue.valueOf(Double.parseDouble(number)) : LuaValue.valueOf(Long.parseLong(number));
|
||||
} catch (NumberFormatException e) {
|
||||
error("invalid number");
|
||||
return LuaValue.NIL;
|
||||
}
|
||||
}
|
||||
|
||||
private void readDigits() {
|
||||
int start = pos;
|
||||
while (!eof()) {
|
||||
char c = input.charAt(pos);
|
||||
if (c < '0' || c > '9') {
|
||||
break;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
if (start == pos) {
|
||||
error("invalid number");
|
||||
}
|
||||
}
|
||||
|
||||
private String parseString() {
|
||||
StringBuffer out = new StringBuffer();
|
||||
pos++;
|
||||
while (!eof()) {
|
||||
char c = input.charAt(pos++);
|
||||
if (c == '"') {
|
||||
return out.toString();
|
||||
}
|
||||
if (c != '\\') {
|
||||
out.append(c);
|
||||
continue;
|
||||
}
|
||||
if (eof()) {
|
||||
error("unterminated escape");
|
||||
}
|
||||
char esc = input.charAt(pos++);
|
||||
switch (esc) {
|
||||
case '"':
|
||||
case '\\':
|
||||
case '/':
|
||||
out.append(esc);
|
||||
break;
|
||||
case 'b':
|
||||
out.append('\b');
|
||||
break;
|
||||
case 'f':
|
||||
out.append('\f');
|
||||
break;
|
||||
case 'n':
|
||||
out.append('\n');
|
||||
break;
|
||||
case 'r':
|
||||
out.append('\r');
|
||||
break;
|
||||
case 't':
|
||||
out.append('\t');
|
||||
break;
|
||||
case 'u':
|
||||
if (pos + 4 > input.length()) {
|
||||
error("invalid unicode escape");
|
||||
}
|
||||
out.append((char) Integer.parseInt(input.substring(pos, pos + 4), 16));
|
||||
pos += 4;
|
||||
break;
|
||||
default:
|
||||
error("invalid escape");
|
||||
}
|
||||
}
|
||||
error("unterminated string");
|
||||
return null;
|
||||
}
|
||||
|
||||
void skipWhitespace() {
|
||||
while (!eof()) {
|
||||
char c = input.charAt(pos);
|
||||
if (c != ' ' && c != '\n' && c != '\r' && c != '\t') {
|
||||
break;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
|
||||
boolean eof() {
|
||||
return pos >= input.length();
|
||||
}
|
||||
|
||||
private void expect(String token) {
|
||||
if (!input.regionMatches(pos, token, 0, token.length())) {
|
||||
error("expected '" + token + "'");
|
||||
}
|
||||
pos += token.length();
|
||||
}
|
||||
|
||||
private boolean consume(char c) {
|
||||
if (!eof() && input.charAt(pos) == c) {
|
||||
pos++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,6 +95,7 @@ public class JmePlatform {
|
||||
Globals globals = new Globals();
|
||||
globals.load(new BaseLib());
|
||||
globals.load(new PackageLib());
|
||||
globals.load(new CjsonLib());
|
||||
globals.load(new Bit32Lib());
|
||||
globals.load(new OsLib());
|
||||
globals.load(new MathLib());
|
||||
|
||||
Binary file not shown.
@@ -88,6 +88,7 @@ public class JsePlatform {
|
||||
Globals globals = new Globals();
|
||||
globals.load(new JseBaseLib());
|
||||
globals.load(new PackageLib());
|
||||
globals.load(new CjsonLib());
|
||||
globals.load(new Bit32Lib());
|
||||
globals.load(new TableLib());
|
||||
globals.load(new JseStringLib());
|
||||
|
||||
@@ -390,6 +390,25 @@ public class FragmentsTest extends TestSuite {
|
||||
}
|
||||
}
|
||||
|
||||
public void testCjsonRequireEncodeDecode() {
|
||||
try {
|
||||
Globals globals = JsePlatform.standardGlobals();
|
||||
Varargs result = globals.load(
|
||||
"local cjson = require('cjson')\n" +
|
||||
"local json = cjson.encode({name='lua', values={1, true, cjson.null}})\n" +
|
||||
"local decoded = cjson.decode(json)\n" +
|
||||
"return decoded.name, decoded.values[1], decoded.values[2], decoded.values[3] == cjson.null\n",
|
||||
"cjson.lua").invoke();
|
||||
assertEquals(LuaValue.valueOf("lua"), result.arg1());
|
||||
assertEquals(LuaValue.valueOf(1), result.arg(2));
|
||||
assertEquals(LuaValue.TRUE, result.arg(3));
|
||||
assertEquals(LuaValue.TRUE, result.arg(4));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
fail(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public void testTableMove() {
|
||||
runFragment(
|
||||
LuaValue.varargsOf(new LuaValue[] {
|
||||
|
||||
Reference in New Issue
Block a user