From 125fc32917d774ff68891c0543bde7726c4a8204 Mon Sep 17 00:00:00 2001 From: lolleko Date: Wed, 7 Feb 2018 15:43:31 +0100 Subject: [PATCH 1/4] Moved Test functionality to util module & Added tests for loops --- package-lock.json | 6 ++ package.json | 1 + test/integration/lua/loops.spec.ts | 68 ++++++++++++ test/integration/lua/lualib.spec.ts | 160 +++++++++------------------- test/runner.ts | 2 +- test/src/util.ts | 68 ++++++++++++ 6 files changed, 194 insertions(+), 111 deletions(-) create mode 100644 test/integration/lua/loops.spec.ts create mode 100644 test/src/util.ts diff --git a/package-lock.json b/package-lock.json index 1b2c9b607..fa9c8b183 100644 --- a/package-lock.json +++ b/package-lock.json @@ -73,6 +73,12 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", + "dev": true + }, "duplexer": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", diff --git a/package.json b/package.json index fc36866f9..4638cea4b 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "coverage": "nyc --reporter=lcov npm test && nyc report" }, "devDependencies": { + "deep-equal": "^1.0.1", "nyc": "^11.4.1" } } diff --git a/test/integration/lua/loops.spec.ts b/test/integration/lua/loops.spec.ts new file mode 100644 index 000000000..f68d1c4f2 --- /dev/null +++ b/test/integration/lua/loops.spec.ts @@ -0,0 +1,68 @@ +import { Expect, Test, TestCase } from "alsatian"; +import * as util from "../../src/util" + +const deepEqual = require('deep-equal') + +export class LuaLoopTests { + + @TestCase([0, 1, 2, 3], [1, 2, 3, 4]) + @Test("for") + public for(inp: T[], expected: T[]) { + // Transpile + let lua = util.transpileString( + `let arrTest = [${inp.toString()}]; + for (let i = 0; i < arrTest.length; ++i) { + arrTest[i] = arrTest[i] + 1; + } + return ArrayToString(arrTest);` + , util.dummyTypes.Array + ); + + // Execute + let result = util.executeLua(lua); + + // Assert + Expect(result).toBe(expected.toString()); + } + + @TestCase({ ['test1']: 0, ['test2']: 1, ['test3']: 2 }, { ['test1']: 1, ['test2']: 2, ['test3']: 3 }) + @Test("forin") + public forin(inp: any, expected: any) { + // Transpile + let lua = util.transpileString( + `let objTest = ${JSON.stringify(inp)}; + for (let key in objTest) { + objTest[key] = objTest[key] + 1; + } + return JSONStringify(objTest);` + , util.dummyTypes.Object + ); + + // Execute + let result = util.executeLua(lua); + + // Assert + Expect(deepEqual(JSON.parse(result), expected)).toBe(true); + } + + @TestCase([0,1,2], [1,2,3]) + @Test("forof") + public forof(inp: any, expected: any) { + // Transpile + let lua = util.transpileString( + `let objTest = [${inp.toString()}]; + let arrResultTest = {}; + for (let value of objTest) { + arrResultTest.push(value + 1) + } + return ArrayToString(arrResultTest);` + , util.dummyTypes.Array + ); + + // Execute + let result = util.executeLua(lua); + + // Assert + Expect(result).toBe(expected.toString()); + } +} diff --git a/test/integration/lua/lualib.spec.ts b/test/integration/lua/lualib.spec.ts index 56331e012..ead18717a 100644 --- a/test/integration/lua/lualib.spec.ts +++ b/test/integration/lua/lualib.spec.ts @@ -1,170 +1,115 @@ import { Expect, Test, TestCase } from "alsatian"; +import * as util from "../../src/util" -import * as ts from "typescript"; -import {LuaTranspiler, TranspileError} from "../../../dist/Transpiler"; - -const LuaVM = require("lua.vm.js"); -const fs = require("fs"); - -const dummyArrayType = { flags: ts.TypeFlags.Object, symbol: {escapedName: "Array"}}; -let dummyType = {}; -const dummyChecker = {getTypeAtLocation: function() {return dummyType;}} -function transpileString(str: string): string { - const file = ts.createSourceFile("", str, ts.ScriptTarget.Latest); - const result = LuaTranspiler.transpileSourceFile(file, dummyChecker, false); - return result.trim(); -} -function executeLua(lua: string): string { - const luavm = new LuaVM.Lua.State(); - return luavm.execute(lua)[0]; -} - -const lualib = fs.readFileSync("dist/lualib/typescript.lua") + "\n"; - -const toStringDef = "function ToString(list)\n"+ - "local result = \"\"\n" + - "for i=1,#list do result = result .. list[i]\n" + - "if i < #list then result = result .. ',' end end\n"+ - "return result end\n"; - -export class LuaTests { +export class LuaLibArrayTests { @TestCase([], "x => x") - @TestCase([0,1,2,3], "x => x") - @TestCase([0,1,2,3], "x => x*2") - @TestCase([1,2,3,4], "x => -x") - @TestCase([0,1,2,3], "x => x+2") - @TestCase([0,1,2,3], "x => x%2 == 0 ? x + 1 : x - 1") + @TestCase([0, 1, 2, 3], "x => x") + @TestCase([0, 1, 2, 3], "x => x*2") + @TestCase([1, 2, 3, 4], "x => -x") + @TestCase([0, 1, 2, 3], "x => x+2") + @TestCase([0, 1, 2, 3], "x => x%2 == 0 ? x + 1 : x - 1") @Test("array.map") public map(inp: T[], func: string) { - // Make typechecker return array type - dummyType = dummyArrayType; // Transpile - let lua = transpileString(`return ToString([${inp.toString()}].map(${func}))`); - - // Add library - lua = toStringDef + lualib + lua; + let lua = util.transpileString(`return ArrayToString([${inp.toString()}].map(${func}))`, util.dummyTypes.Array); // Execute - let result = executeLua(lua); + let result = util.executeLua(lua); // Assert Expect(result).toBe(inp.map(eval(func)).toString()); } @TestCase([], "x => x > 1") - @TestCase([0,1,2,3], "x => x > 1") - @TestCase([0,1,2,3], "x => x < 3") - @TestCase([0,1,2,3], "x => x < 0") - @TestCase([0,-1,-2,-3], "x => x < 0") - @TestCase([0,1,2,3], "() => true") - @TestCase([0,1,2,3], "() => false") + @TestCase([0, 1, 2, 3], "x => x > 1") + @TestCase([0, 1, 2, 3], "x => x < 3") + @TestCase([0, 1, 2, 3], "x => x < 0") + @TestCase([0, -1, -2, -3], "x => x < 0") + @TestCase([0, 1, 2, 3], "() => true") + @TestCase([0, 1, 2, 3], "() => false") @Test("array.filter") public filter(inp: T[], func: string) { - // Make typechecker return array type - dummyType = dummyArrayType; // Transpile - let lua = transpileString(`return ToString([${inp.toString()}].filter(${func}))`); - - // Add library - lua = toStringDef + lualib + lua; + let lua = util.transpileString(`return ArrayToString([${inp.toString()}].filter(${func}))`, util.dummyTypes.Array); // Execute - let result = executeLua(lua); + let result = util.executeLua(lua); // Assert Expect(result).toBe(inp.filter(eval(func)).toString()); } @TestCase([], "x => x > 1") - @TestCase([0,1,2,3], "x => x > 1") + @TestCase([0, 1, 2, 3], "x => x > 1") @TestCase([false, true, false], "x => x") @TestCase([true, true, true], "x => x") @Test("array.every") public every(inp: T[], func: string) { - // Make typechecker return array type - dummyType = dummyArrayType; // Transpile - let lua = transpileString(`return [${inp.toString()}].every(${func}))`); - - // Add library - lua = toStringDef + lualib + lua; + let lua = util.transpileString(`return [${inp.toString()}].every(${func}))`, util.dummyTypes.Array); // Execute - let result = executeLua(lua); + let result = util.executeLua(lua); // Assert Expect(result.toString()).toBe(inp.every(eval(func)).toString()); } @TestCase([], "x => x > 1") - @TestCase([0,1,2,3], "x => x > 1") + @TestCase([0, 1, 2, 3], "x => x > 1") @TestCase([false, true, false], "x => x") @TestCase([true, true, true], "x => x") @Test("array.some") public some(inp: T[], func: string) { - // Make typechecker return array type - dummyType = dummyArrayType; // Transpile - let lua = transpileString(`return [${inp.toString()}].some(${func}))`); - - // Add library - lua = toStringDef + lualib + lua; + let lua = util.transpileString(`return [${inp.toString()}].some(${func}))`, util.dummyTypes.Array); // Execute - let result = executeLua(lua); + let result = util.executeLua(lua); // Assert Expect(result.toString()).toBe(inp.some(eval(func)).toString()); } @TestCase([], 1, 2) - @TestCase([0,1,2,3], 1, 2) - @TestCase([0,1,2,3], 1, 1) - @TestCase([0,1,2,3], 1, -1) - @TestCase([0,1,2,3], -3, -1) - @TestCase([0,1,2,3,4,5], 1, 3) - @TestCase([0,1,2,3,4,5], 3) + @TestCase([0, 1, 2, 3], 1, 2) + @TestCase([0, 1, 2, 3], 1, 1) + @TestCase([0, 1, 2, 3], 1, -1) + @TestCase([0, 1, 2, 3], -3, -1) + @TestCase([0, 1, 2, 3, 4, 5], 1, 3) + @TestCase([0, 1, 2, 3, 4, 5], 3) @Test("array.slice") public slice(inp: T[], start: number, end?: number) { - // Make typechecker return array type - dummyType = dummyArrayType; // Transpile - let lua = transpileString(`return ToString([${inp.toString()}].slice(${start}, ${end}))`); - - // Add library - lua = toStringDef + lualib + lua; + let lua = util.transpileString(`return ArrayToString([${inp.toString()}].slice(${start}, ${end}))`, util.dummyTypes.Array); // Execute - let result = executeLua(lua); + let result = util.executeLua(lua); // Assert Expect(result).toBe(inp.slice(start, end).toString()); } @TestCase([], 0, 0, 9, 10, 11) - @TestCase([0,1,2,3], 1, 0, 9, 10, 11) - @TestCase([0,1,2,3], 2, 2, 9, 10, 11) - @TestCase([0,1,2,3], 4, 1, 8, 9) - @TestCase([0,1,2,3], 4, 0, 8, 9) - @TestCase([0,1,2,3,4,5], 5, 9, 10, 11) - @TestCase([0,1,2,3,4,5], 3, 2, 3, 4, 5) + @TestCase([0, 1, 2, 3], 1, 0, 9, 10, 11) + @TestCase([0, 1, 2, 3], 2, 2, 9, 10, 11) + @TestCase([0, 1, 2, 3], 4, 1, 8, 9) + @TestCase([0, 1, 2, 3], 4, 0, 8, 9) + @TestCase([0, 1, 2, 3, 4, 5], 5, 9, 10, 11) + @TestCase([0, 1, 2, 3, 4, 5], 3, 2, 3, 4, 5) @Test("array.splice[Insert]") public spliceInsert(inp: T[], start: number, deleteCount: number, ...newElements: any[]) { - // Make typechecker return array type - dummyType = dummyArrayType; // Transpile - let lua = transpileString( - `let spliceTestTable = [${inp.toString()}] + let lua = util.transpileString( + `let spliceTestTable = [${inp.toString()}]; spliceTestTable.splice(${start}, ${deleteCount}, ${newElements}); - return ToString(spliceTestTable);` + return ArrayToString(spliceTestTable);`, + util.dummyTypes.Array ); - // Add library - lua = toStringDef + lualib + lua; - // Execute - let result = executeLua(lua); + let result = util.executeLua(lua); // Assert inp.splice(start, deleteCount, ...newElements) @@ -172,24 +117,19 @@ export class LuaTests { } @TestCase([], 1, 1) - @TestCase([0,1,2,3], 1, 1) - @TestCase([0,1,2,3], 10, 1) - @TestCase([0,1,2,3], 4) - @TestCase([0,1,2,3,4,5], 3) - @TestCase([0,1,2,3,4,5], 2, 2) - @TestCase([0,1,2,3,4,5,6,7,8], 5, 9, 10, 11) + @TestCase([0, 1, 2, 3], 1, 1) + @TestCase([0, 1, 2, 3], 10, 1) + @TestCase([0, 1, 2, 3], 4) + @TestCase([0, 1, 2, 3, 4, 5], 3) + @TestCase([0, 1, 2, 3, 4, 5], 2, 2) + @TestCase([0, 1, 2, 3, 4, 5, 6, 7, 8], 5, 9, 10, 11) @Test("array.splice[Remove]") public spliceRemove(inp: T[], start: number, deleteCount?: number, ...newElements: any[]) { - // Make typechecker return array type - dummyType = dummyArrayType; // Transpile - let lua = transpileString(`return ToString([${inp.toString()}].splice(${start}, ${deleteCount}, ${newElements}))`); - - // Add library - lua = toStringDef + lualib + lua; + let lua = util.transpileString(`return ArrayToString([${inp.toString()}].splice(${start}, ${deleteCount}, ${newElements}))`, util.dummyTypes.Array); // Execute - let result = executeLua(lua); + let result = util.executeLua(lua); // Assert if (deleteCount) { diff --git a/test/runner.ts b/test/runner.ts index 07d2db0c9..b5f89b0f3 100644 --- a/test/runner.ts +++ b/test/runner.ts @@ -23,4 +23,4 @@ testRunner.run(testSet); // this will be called after all tests have been run //.then((results) => done()) // this will be called if there was a problem - //.catch((error) => doSomethingWith(error)); \ No newline at end of file + //.catch((error) => doSomethingWith(error)); diff --git a/test/src/util.ts b/test/src/util.ts new file mode 100644 index 000000000..f3b813bb7 --- /dev/null +++ b/test/src/util.ts @@ -0,0 +1,68 @@ +import * as ts from "typescript"; +import { LuaTranspiler, TranspileError } from "../../dist/Transpiler"; + +const LuaVM = require("lua.vm.js"); +const fs = require("fs"); + +export namespace dummyTypes { + export const Array = { flags: ts.TypeFlags.Object, symbol: { escapedName: "Array" } }; + export const Object = { flags: ts.TypeFlags.Object, symbol: { escapedName: "Object" } } +} + +export function transpileString(str: string, dummyType: any): string { + const dummyChecker = { getTypeAtLocation: function() { return dummyType; } } + const file = ts.createSourceFile("", str, ts.ScriptTarget.Latest); + const result = LuaTranspiler.transpileSourceFile(file, dummyChecker, false); + return result.trim(); +} + +export function executeLua(lua: string, withLib = true): string { + if (withLib) { + lua = minimalTestLib + lua + } + const luavm = new LuaVM.Lua.State(); + return luavm.execute(lua)[0]; +} + +const lualib = fs.readFileSync("dist/lualib/typescript.lua") + "\n"; + +const arrayToStringDef = ` +function ArrayToString(list) + local result = "" + for i=1,#list do result = result .. list[i] + if i < #list then + result = result .. ',' + end + end + return result +end +` + +const jsonStringifyDef = ` +function JSONStringify(t, isTable) + local result = '{' + + local empty = true + + for k, v in pairs(t) do + empty = false + if type(v) == 'table' then + if (v ~= t) then + result = result .. '"' .. tostring(k) .. '":' + result = result .. JSONStringify(v) + result = result .. ',' + end + elseif type(v) ~= 'function' then + result = result .. '"' .. tostring(k) .. '":' .. tostring(v) .. ',' + end + end + + if empty then + return '{}' + else + return result:sub(1, -2) .. '}' + end +end +` + +export const minimalTestLib = lualib + arrayToStringDef + jsonStringifyDef From 3c03b5954d5753cacdadcb3fd4dd10425731ac10 Mon Sep 17 00:00:00 2001 From: lolleko Date: Wed, 7 Feb 2018 16:08:04 +0100 Subject: [PATCH 2/4] Added test "forin[Array]" & Fixed fail from previous PR Previous PR changed for in ipairs/pairs to \"pairs\" instead of pairs --- dist/Transpiler.js | 10 ++++++---- src/Transpiler.ts | 2 +- test/integration/lua/loops.spec.ts | 19 +++++++++++++++++-- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/dist/Transpiler.js b/dist/Transpiler.js index 5b0821b53..38da095a0 100644 --- a/dist/Transpiler.js +++ b/dist/Transpiler.js @@ -246,11 +246,11 @@ var LuaTranspiler = /** @class */ (function () { var identifier = variable.name; // Transpile expression var expression = this.transpileExpression(node.expression); - // Use ipairs for array types, pairs otherwise - var isArray = TSHelper_1.TSHelper.isArrayType(this.checker.getTypeAtLocation(node.expression)); - var pairs = isArray ? "ipairs" : "pairs"; + if (TSHelper_1.TSHelper.isArrayType(this.checker.getTypeAtLocation(node.expression))) { + throw new TranspileError("Iterating over arrays with 'for in' is not allowed.", node); + } // Make header - var result = this.indent + ("for " + identifier.escapedText + ", _ in " + pairs + "(" + expression + ") do\n"); + var result = this.indent + ("for " + identifier.escapedText + ", _ in pairs(" + expression + ") do\n"); // For body this.pushIndent(); result += this.transpileStatement(node.statement); @@ -561,6 +561,8 @@ var LuaTranspiler = /** @class */ (function () { return "TS_slice(" + caller + ", " + params + ")"; case "splice": return "TS_splice(" + caller + ", " + params + ")"; + case "join": + return "table.concat(" + caller + ", " + params + ")"; default: throw new TranspileError("Unsupported array function: " + expression.name.escapedText, node); } diff --git a/src/Transpiler.ts b/src/Transpiler.ts index 8397c64a4..e834ff27d 100644 --- a/src/Transpiler.ts +++ b/src/Transpiler.ts @@ -274,7 +274,7 @@ export class LuaTranspiler { } // Make header - let result = this.indent + `for ${identifier.escapedText}, _ in "pairs"(${expression}) do\n`; + let result = this.indent + `for ${identifier.escapedText}, _ in pairs(${expression}) do\n`; // For body this.pushIndent(); diff --git a/test/integration/lua/loops.spec.ts b/test/integration/lua/loops.spec.ts index f68d1c4f2..2248d7c3a 100644 --- a/test/integration/lua/loops.spec.ts +++ b/test/integration/lua/loops.spec.ts @@ -26,8 +26,8 @@ export class LuaLoopTests { } @TestCase({ ['test1']: 0, ['test2']: 1, ['test3']: 2 }, { ['test1']: 1, ['test2']: 2, ['test3']: 3 }) - @Test("forin") - public forin(inp: any, expected: any) { + @Test("forin[Object]") + public forinObject(inp: any, expected: any) { // Transpile let lua = util.transpileString( `let objTest = ${JSON.stringify(inp)}; @@ -45,6 +45,21 @@ export class LuaLoopTests { Expect(deepEqual(JSON.parse(result), expected)).toBe(true); } + @TestCase([1,2,3]) + @Test("forin[Array]") + public forinArray(inp: T[]) { + // Transpile & Assert + Expect(() => { + let lua = util.transpileString( + `let arrTest = [${inp.toString()}]; + for (let key in arrTest) { + arrTest[key]++; + }` + , util.dummyTypes.Array + ); + }).toThrowError(Error, "Iterating over arrays with 'for in' is not allowed."); + } + @TestCase([0,1,2], [1,2,3]) @Test("forof") public forof(inp: any, expected: any) { From fe1036120744afd1446ea7f5c353aaa33cd5d5f7 Mon Sep 17 00:00:00 2001 From: lolleko Date: Wed, 7 Feb 2018 19:34:22 +0100 Subject: [PATCH 3/4] Added Json lib for lua <-> js communication --- .gitignore | 1 + test/integration/lua/loops.spec.ts | 14 +- test/integration/lua/lualib.spec.ts | 26 +- test/src/json.lua | 400 ++++++++++++++++++++++++++++ test/src/util.ts | 41 +-- 5 files changed, 423 insertions(+), 59 deletions(-) create mode 100644 test/src/json.lua diff --git a/.gitignore b/.gitignore index c650dfcea..c0af5a22a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *.js node_modules/ *.lua +!json.lua !dist/*.js !dist/lualib/*.lua diff --git a/test/integration/lua/loops.spec.ts b/test/integration/lua/loops.spec.ts index 2248d7c3a..4da4ab365 100644 --- a/test/integration/lua/loops.spec.ts +++ b/test/integration/lua/loops.spec.ts @@ -10,11 +10,11 @@ export class LuaLoopTests { public for(inp: T[], expected: T[]) { // Transpile let lua = util.transpileString( - `let arrTest = [${inp.toString()}]; + `let arrTest = ${JSON.stringify(inp)}; for (let i = 0; i < arrTest.length; ++i) { arrTest[i] = arrTest[i] + 1; } - return ArrayToString(arrTest);` + return JSONStringify(arrTest);` , util.dummyTypes.Array ); @@ -22,7 +22,7 @@ export class LuaLoopTests { let result = util.executeLua(lua); // Assert - Expect(result).toBe(expected.toString()); + Expect(result).toBe(JSON.stringify(expected)); } @TestCase({ ['test1']: 0, ['test2']: 1, ['test3']: 2 }, { ['test1']: 1, ['test2']: 2, ['test3']: 3 }) @@ -51,7 +51,7 @@ export class LuaLoopTests { // Transpile & Assert Expect(() => { let lua = util.transpileString( - `let arrTest = [${inp.toString()}]; + `let arrTest = ${JSON.stringify(inp)}; for (let key in arrTest) { arrTest[key]++; }` @@ -65,12 +65,12 @@ export class LuaLoopTests { public forof(inp: any, expected: any) { // Transpile let lua = util.transpileString( - `let objTest = [${inp.toString()}]; + `let objTest = ${JSON.stringify(inp)}; let arrResultTest = {}; for (let value of objTest) { arrResultTest.push(value + 1) } - return ArrayToString(arrResultTest);` + return JSONStringify(arrResultTest);` , util.dummyTypes.Array ); @@ -78,6 +78,6 @@ export class LuaLoopTests { let result = util.executeLua(lua); // Assert - Expect(result).toBe(expected.toString()); + Expect(result).toBe(JSON.stringify(expected)); } } diff --git a/test/integration/lua/lualib.spec.ts b/test/integration/lua/lualib.spec.ts index ead18717a..57063709d 100644 --- a/test/integration/lua/lualib.spec.ts +++ b/test/integration/lua/lualib.spec.ts @@ -12,13 +12,13 @@ export class LuaLibArrayTests { @Test("array.map") public map(inp: T[], func: string) { // Transpile - let lua = util.transpileString(`return ArrayToString([${inp.toString()}].map(${func}))`, util.dummyTypes.Array); + let lua = util.transpileString(`return JSONStringify([${inp.toString()}].map(${func}))`, util.dummyTypes.Array); // Execute let result = util.executeLua(lua); // Assert - Expect(result).toBe(inp.map(eval(func)).toString()); + Expect(result).toBe(JSON.stringify(inp.map(eval(func)))); } @TestCase([], "x => x > 1") @@ -31,13 +31,13 @@ export class LuaLibArrayTests { @Test("array.filter") public filter(inp: T[], func: string) { // Transpile - let lua = util.transpileString(`return ArrayToString([${inp.toString()}].filter(${func}))`, util.dummyTypes.Array); + let lua = util.transpileString(`return JSONStringify([${inp.toString()}].filter(${func}))`, util.dummyTypes.Array); // Execute let result = util.executeLua(lua); // Assert - Expect(result).toBe(inp.filter(eval(func)).toString()); + Expect(result).toBe(JSON.stringify(inp.filter(eval(func)))); } @TestCase([], "x => x > 1") @@ -53,7 +53,7 @@ export class LuaLibArrayTests { let result = util.executeLua(lua); // Assert - Expect(result.toString()).toBe(inp.every(eval(func)).toString()); + Expect(JSON.stringify(result)).toBe(JSON.stringify(inp.every(eval(func)))); } @TestCase([], "x => x > 1") @@ -69,7 +69,7 @@ export class LuaLibArrayTests { let result = util.executeLua(lua); // Assert - Expect(result.toString()).toBe(inp.some(eval(func)).toString()); + Expect(JSON.stringify(result)).toBe(JSON.stringify(inp.some(eval(func)))); } @TestCase([], 1, 2) @@ -82,13 +82,13 @@ export class LuaLibArrayTests { @Test("array.slice") public slice(inp: T[], start: number, end?: number) { // Transpile - let lua = util.transpileString(`return ArrayToString([${inp.toString()}].slice(${start}, ${end}))`, util.dummyTypes.Array); + let lua = util.transpileString(`return JSONStringify([${inp.toString()}].slice(${start}, ${end}))`, util.dummyTypes.Array); // Execute let result = util.executeLua(lua); // Assert - Expect(result).toBe(inp.slice(start, end).toString()); + Expect(result).toBe(JSON.stringify(inp.slice(start, end))); } @TestCase([], 0, 0, 9, 10, 11) @@ -104,7 +104,7 @@ export class LuaLibArrayTests { let lua = util.transpileString( `let spliceTestTable = [${inp.toString()}]; spliceTestTable.splice(${start}, ${deleteCount}, ${newElements}); - return ArrayToString(spliceTestTable);`, + return JSONStringify(spliceTestTable);`, util.dummyTypes.Array ); @@ -113,7 +113,7 @@ export class LuaLibArrayTests { // Assert inp.splice(start, deleteCount, ...newElements) - Expect(result).toBe(inp.toString()); + Expect(result).toBe(JSON.stringify(inp)); } @TestCase([], 1, 1) @@ -126,16 +126,16 @@ export class LuaLibArrayTests { @Test("array.splice[Remove]") public spliceRemove(inp: T[], start: number, deleteCount?: number, ...newElements: any[]) { // Transpile - let lua = util.transpileString(`return ArrayToString([${inp.toString()}].splice(${start}, ${deleteCount}, ${newElements}))`, util.dummyTypes.Array); + let lua = util.transpileString(`return JSONStringify([${inp.toString()}].splice(${start}, ${deleteCount}, ${newElements}))`, util.dummyTypes.Array); // Execute let result = util.executeLua(lua); // Assert if (deleteCount) { - Expect(result).toBe(inp.splice(start, deleteCount, ...newElements).toString()); + Expect(result).toBe(JSON.stringify(inp.splice(start, deleteCount, ...newElements))); } else { - Expect(result).toBe(inp.splice(start).toString()); + Expect(result).toBe(JSON.stringify(inp.splice(start))); } } } diff --git a/test/src/json.lua b/test/src/json.lua new file mode 100644 index 000000000..dbe7f829e --- /dev/null +++ b/test/src/json.lua @@ -0,0 +1,400 @@ +-- +-- json.lua +-- +-- Copyright (c) 2015 rxi +-- +-- This library is free software; you can redistribute it and/or modify it +-- under the terms of the MIT license. See LICENSE for details. +-- +-- +-- LICENSE CONTENTS: +-- +-- Copyright (c) 2015 rxi +-- +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy of +-- this software and associated documentation files (the "Software"), to deal in +-- the Software without restriction, including without limitation the rights to +-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +-- of the Software, and to permit persons to whom the Software is furnished to do +-- so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in all +-- copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. + +local json = { _version = "0.1.0" } + +------------------------------------------------------------------------------- +-- Encode +------------------------------------------------------------------------------- + +local encode + +local escape_char_map = { + [ "\\" ] = "\\\\", + [ "\"" ] = "\\\"", + [ "\b" ] = "\\b", + [ "\f" ] = "\\f", + [ "\n" ] = "\\n", + [ "\r" ] = "\\r", + [ "\t" ] = "\\t", +} + +local escape_char_map_inv = { [ "\\/" ] = "/" } +for k, v in pairs(escape_char_map) do + escape_char_map_inv[v] = k +end + + +local function escape_char(c) + return escape_char_map[c] or string.format("\\u%04x", c:byte()) +end + + +local function encode_nil(val) + return "null" +end + + +local function encode_table(val, stack) + local res = {} + stack = stack or {} + + -- Circular reference? + if stack[val] then error("circular reference") end + + stack[val] = true + + if val[1] ~= nil or next(val) == nil then + -- Treat as array -- check keys are valid and it is not sparse + local n = 0 + for k in pairs(val) do + if type(k) ~= "number" then + error("invalid table: mixed or invalid key types") + end + n = n + 1 + end + if n ~= #val then + error("invalid table: sparse array") + end + -- Encode + for i, v in ipairs(val) do + table.insert(res, encode(v, stack)) + end + stack[val] = nil + return "[" .. table.concat(res, ",") .. "]" + + else + -- Treat as an object + for k, v in pairs(val) do + if type(k) ~= "string" then + error("invalid table: mixed or invalid key types") + end + table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) + end + stack[val] = nil + return "{" .. table.concat(res, ",") .. "}" + end +end + + +local function encode_string(val) + return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' +end + + +local function encode_number(val) + -- Check for NaN, -inf and inf + if val ~= val or val <= -math.huge or val >= math.huge then + error("unexpected number value '" .. tostring(val) .. "'") + end + return string.format("%.14g", val) +end + + +local type_func_map = { + [ "nil" ] = encode_nil, + [ "table" ] = encode_table, + [ "string" ] = encode_string, + [ "number" ] = encode_number, + [ "boolean" ] = tostring, +} + + +encode = function(val, stack) + local t = type(val) + local f = type_func_map[t] + if f then + return f(val, stack) + end + error("unexpected type '" .. t .. "'") +end + + +function JSONStringify(val) + return ( encode(val) ) +end + + +------------------------------------------------------------------------------- +-- Decode +------------------------------------------------------------------------------- + +local parse + +local function create_set(...) + local res = {} + for i = 1, select("#", ...) do + res[ select(i, ...) ] = true + end + return res +end + +local space_chars = create_set(" ", "\t", "\r", "\n") +local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") +local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") +local literals = create_set("true", "false", "null") + +local literal_map = { + [ "true" ] = true, + [ "false" ] = false, + [ "null" ] = nil, +} + + +local function next_char(str, idx, set, negate) + for i = idx, #str do + if set[str:sub(i, i)] ~= negate then + return i + end + end + return #str + 1 +end + + +local function decode_error(str, idx, msg) + local line_count = 1 + local col_count = 1 + for i = 1, idx - 1 do + col_count = col_count + 1 + if str:sub(i, i) == "\n" then + line_count = line_count + 1 + col_count = 1 + end + end + error( string.format("%s at line %d col %d", msg, line_count, col_count) ) +end + + +local function codepoint_to_utf8(n) + -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa + local f = math.floor + if n <= 0x7f then + return string.char(n) + elseif n <= 0x7ff then + return string.char(f(n / 64) + 192, n % 64 + 128) + elseif n <= 0xffff then + return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) + elseif n <= 0x10ffff then + return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, + f(n % 4096 / 64) + 128, n % 64 + 128) + end + error( string.format("invalid unicode codepoint '%x'", n) ) +end + + +local function parse_unicode_escape(s) + local n1 = tonumber( s:sub(3, 6), 16 ) + local n2 = tonumber( s:sub(9, 12), 16 ) + -- Surrogate pair? + if n2 then + return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) + else + return codepoint_to_utf8(n1) + end +end + + +local function parse_string(str, i) + local has_unicode_escape = false + local has_surrogate_escape = false + local has_escape = false + local last + for j = i + 1, #str do + local x = str:byte(j) + + if x < 32 then + decode_error(str, j, "control character in string") + end + + if last == 92 then -- "\\" (escape char) + if x == 117 then -- "u" (unicode escape sequence) + local hex = str:sub(j + 1, j + 5) + if not hex:find("%x%x%x%x") then + decode_error(str, j, "invalid unicode escape in string") + end + if hex:find("^[dD][89aAbB]") then + has_surrogate_escape = true + else + has_unicode_escape = true + end + else + local c = string.char(x) + if not escape_chars[c] then + decode_error(str, j, "invalid escape char '" .. c .. "' in string") + end + has_escape = true + end + last = nil + + elseif x == 34 then -- '"' (end of string) + local s = str:sub(i + 1, j - 1) + if has_surrogate_escape then + s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape) + end + if has_unicode_escape then + s = s:gsub("\\u....", parse_unicode_escape) + end + if has_escape then + s = s:gsub("\\.", escape_char_map_inv) + end + return s, j + 1 + + else + last = x + end + end + decode_error(str, i, "expected closing quote for string") +end + + +local function parse_number(str, i) + local x = next_char(str, i, delim_chars) + local s = str:sub(i, x - 1) + local n = tonumber(s) + if not n then + decode_error(str, i, "invalid number '" .. s .. "'") + end + return n, x +end + + +local function parse_literal(str, i) + local x = next_char(str, i, delim_chars) + local word = str:sub(i, x - 1) + if not literals[word] then + decode_error(str, i, "invalid literal '" .. word .. "'") + end + return literal_map[word], x +end + + +local function parse_array(str, i) + local res = {} + local n = 1 + i = i + 1 + while 1 do + local x + i = next_char(str, i, space_chars, true) + -- Empty / end of array? + if str:sub(i, i) == "]" then + i = i + 1 + break + end + -- Read token + x, i = parse(str, i) + res[n] = x + n = n + 1 + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "]" then break end + if chr ~= "," then decode_error(str, i, "expected ']' or ','") end + end + return res, i +end + + +local function parse_object(str, i) + local res = {} + i = i + 1 + while 1 do + local key, val + i = next_char(str, i, space_chars, true) + -- Empty / end of object? + if str:sub(i, i) == "}" then + i = i + 1 + break + end + -- Read key + if str:sub(i, i) ~= '"' then + decode_error(str, i, "expected string for key") + end + key, i = parse(str, i) + -- Read ':' delimiter + i = next_char(str, i, space_chars, true) + if str:sub(i, i) ~= ":" then + decode_error(str, i, "expected ':' after key") + end + i = next_char(str, i + 1, space_chars, true) + -- Read value + val, i = parse(str, i) + -- Set + res[key] = val + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "}" then break end + if chr ~= "," then decode_error(str, i, "expected '}' or ','") end + end + return res, i +end + + +local char_func_map = { + [ '"' ] = parse_string, + [ "0" ] = parse_number, + [ "1" ] = parse_number, + [ "2" ] = parse_number, + [ "3" ] = parse_number, + [ "4" ] = parse_number, + [ "5" ] = parse_number, + [ "6" ] = parse_number, + [ "7" ] = parse_number, + [ "8" ] = parse_number, + [ "9" ] = parse_number, + [ "-" ] = parse_number, + [ "t" ] = parse_literal, + [ "f" ] = parse_literal, + [ "n" ] = parse_literal, + [ "[" ] = parse_array, + [ "{" ] = parse_object, +} + + +parse = function(str, idx) + local chr = str:sub(idx, idx) + local f = char_func_map[chr] + if f then + return f(str, idx) + end + decode_error(str, idx, "unexpected character '" .. chr .. "'") +end + + +function JSONParse(str) + if type(str) ~= "string" then + error("expected argument of type string, got " .. type(str)) + end + return ( parse(str, next_char(str, 1, space_chars, true)) ) +end diff --git a/test/src/util.ts b/test/src/util.ts index f3b813bb7..009ebe1c4 100644 --- a/test/src/util.ts +++ b/test/src/util.ts @@ -26,43 +26,6 @@ export function executeLua(lua: string, withLib = true): string { const lualib = fs.readFileSync("dist/lualib/typescript.lua") + "\n"; -const arrayToStringDef = ` -function ArrayToString(list) - local result = "" - for i=1,#list do result = result .. list[i] - if i < #list then - result = result .. ',' - end - end - return result -end -` +const jsonlib = fs.readFileSync("test/src/json.lua") + "\n"; -const jsonStringifyDef = ` -function JSONStringify(t, isTable) - local result = '{' - - local empty = true - - for k, v in pairs(t) do - empty = false - if type(v) == 'table' then - if (v ~= t) then - result = result .. '"' .. tostring(k) .. '":' - result = result .. JSONStringify(v) - result = result .. ',' - end - elseif type(v) ~= 'function' then - result = result .. '"' .. tostring(k) .. '":' .. tostring(v) .. ',' - end - end - - if empty then - return '{}' - else - return result:sub(1, -2) .. '}' - end -end -` - -export const minimalTestLib = lualib + arrayToStringDef + jsonStringifyDef +export const minimalTestLib = lualib + jsonlib From 1e0abc2ca77ba9765c8c9eca1894f04f56e8dc28 Mon Sep 17 00:00:00 2001 From: lolleko Date: Wed, 7 Feb 2018 20:16:23 +0100 Subject: [PATCH 4/4] Removed json decode from json lib --- test/src/json.lua | 256 ---------------------------------------------- 1 file changed, 256 deletions(-) diff --git a/test/src/json.lua b/test/src/json.lua index dbe7f829e..6dafe9fe4 100644 --- a/test/src/json.lua +++ b/test/src/json.lua @@ -142,259 +142,3 @@ end function JSONStringify(val) return ( encode(val) ) end - - -------------------------------------------------------------------------------- --- Decode -------------------------------------------------------------------------------- - -local parse - -local function create_set(...) - local res = {} - for i = 1, select("#", ...) do - res[ select(i, ...) ] = true - end - return res -end - -local space_chars = create_set(" ", "\t", "\r", "\n") -local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") -local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") -local literals = create_set("true", "false", "null") - -local literal_map = { - [ "true" ] = true, - [ "false" ] = false, - [ "null" ] = nil, -} - - -local function next_char(str, idx, set, negate) - for i = idx, #str do - if set[str:sub(i, i)] ~= negate then - return i - end - end - return #str + 1 -end - - -local function decode_error(str, idx, msg) - local line_count = 1 - local col_count = 1 - for i = 1, idx - 1 do - col_count = col_count + 1 - if str:sub(i, i) == "\n" then - line_count = line_count + 1 - col_count = 1 - end - end - error( string.format("%s at line %d col %d", msg, line_count, col_count) ) -end - - -local function codepoint_to_utf8(n) - -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa - local f = math.floor - if n <= 0x7f then - return string.char(n) - elseif n <= 0x7ff then - return string.char(f(n / 64) + 192, n % 64 + 128) - elseif n <= 0xffff then - return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) - elseif n <= 0x10ffff then - return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, - f(n % 4096 / 64) + 128, n % 64 + 128) - end - error( string.format("invalid unicode codepoint '%x'", n) ) -end - - -local function parse_unicode_escape(s) - local n1 = tonumber( s:sub(3, 6), 16 ) - local n2 = tonumber( s:sub(9, 12), 16 ) - -- Surrogate pair? - if n2 then - return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) - else - return codepoint_to_utf8(n1) - end -end - - -local function parse_string(str, i) - local has_unicode_escape = false - local has_surrogate_escape = false - local has_escape = false - local last - for j = i + 1, #str do - local x = str:byte(j) - - if x < 32 then - decode_error(str, j, "control character in string") - end - - if last == 92 then -- "\\" (escape char) - if x == 117 then -- "u" (unicode escape sequence) - local hex = str:sub(j + 1, j + 5) - if not hex:find("%x%x%x%x") then - decode_error(str, j, "invalid unicode escape in string") - end - if hex:find("^[dD][89aAbB]") then - has_surrogate_escape = true - else - has_unicode_escape = true - end - else - local c = string.char(x) - if not escape_chars[c] then - decode_error(str, j, "invalid escape char '" .. c .. "' in string") - end - has_escape = true - end - last = nil - - elseif x == 34 then -- '"' (end of string) - local s = str:sub(i + 1, j - 1) - if has_surrogate_escape then - s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape) - end - if has_unicode_escape then - s = s:gsub("\\u....", parse_unicode_escape) - end - if has_escape then - s = s:gsub("\\.", escape_char_map_inv) - end - return s, j + 1 - - else - last = x - end - end - decode_error(str, i, "expected closing quote for string") -end - - -local function parse_number(str, i) - local x = next_char(str, i, delim_chars) - local s = str:sub(i, x - 1) - local n = tonumber(s) - if not n then - decode_error(str, i, "invalid number '" .. s .. "'") - end - return n, x -end - - -local function parse_literal(str, i) - local x = next_char(str, i, delim_chars) - local word = str:sub(i, x - 1) - if not literals[word] then - decode_error(str, i, "invalid literal '" .. word .. "'") - end - return literal_map[word], x -end - - -local function parse_array(str, i) - local res = {} - local n = 1 - i = i + 1 - while 1 do - local x - i = next_char(str, i, space_chars, true) - -- Empty / end of array? - if str:sub(i, i) == "]" then - i = i + 1 - break - end - -- Read token - x, i = parse(str, i) - res[n] = x - n = n + 1 - -- Next token - i = next_char(str, i, space_chars, true) - local chr = str:sub(i, i) - i = i + 1 - if chr == "]" then break end - if chr ~= "," then decode_error(str, i, "expected ']' or ','") end - end - return res, i -end - - -local function parse_object(str, i) - local res = {} - i = i + 1 - while 1 do - local key, val - i = next_char(str, i, space_chars, true) - -- Empty / end of object? - if str:sub(i, i) == "}" then - i = i + 1 - break - end - -- Read key - if str:sub(i, i) ~= '"' then - decode_error(str, i, "expected string for key") - end - key, i = parse(str, i) - -- Read ':' delimiter - i = next_char(str, i, space_chars, true) - if str:sub(i, i) ~= ":" then - decode_error(str, i, "expected ':' after key") - end - i = next_char(str, i + 1, space_chars, true) - -- Read value - val, i = parse(str, i) - -- Set - res[key] = val - -- Next token - i = next_char(str, i, space_chars, true) - local chr = str:sub(i, i) - i = i + 1 - if chr == "}" then break end - if chr ~= "," then decode_error(str, i, "expected '}' or ','") end - end - return res, i -end - - -local char_func_map = { - [ '"' ] = parse_string, - [ "0" ] = parse_number, - [ "1" ] = parse_number, - [ "2" ] = parse_number, - [ "3" ] = parse_number, - [ "4" ] = parse_number, - [ "5" ] = parse_number, - [ "6" ] = parse_number, - [ "7" ] = parse_number, - [ "8" ] = parse_number, - [ "9" ] = parse_number, - [ "-" ] = parse_number, - [ "t" ] = parse_literal, - [ "f" ] = parse_literal, - [ "n" ] = parse_literal, - [ "[" ] = parse_array, - [ "{" ] = parse_object, -} - - -parse = function(str, idx) - local chr = str:sub(idx, idx) - local f = char_func_map[chr] - if f then - return f(str, idx) - end - decode_error(str, idx, "unexpected character '" .. chr .. "'") -end - - -function JSONParse(str) - if type(str) ~= "string" then - error("expected argument of type string, got " .. type(str)) - end - return ( parse(str, next_char(str, 1, space_chars, true)) ) -end