More work on Lua
parent
f08c66d305
commit
aa712ed8f4
|
@ -1,8 +1,8 @@
|
|||
import { assertEquals } from "@std/assert/equals";
|
||||
import { LuaEnv, LuaNativeJSFunction, singleResult } from "./runtime.ts";
|
||||
import { parse } from "./parse.ts";
|
||||
import type { LuaFunctionCallStatement } from "./ast.ts";
|
||||
import { evalExpression } from "./eval.ts";
|
||||
import type { LuaBlock, LuaFunctionCallStatement } from "./ast.ts";
|
||||
import { evalExpression, evalStatement } from "./eval.ts";
|
||||
|
||||
function evalExpr(s: string, e = new LuaEnv()): any {
|
||||
return evalExpression(
|
||||
|
@ -12,6 +12,10 @@ function evalExpr(s: string, e = new LuaEnv()): any {
|
|||
);
|
||||
}
|
||||
|
||||
function evalBlock(s: string, e = new LuaEnv()): Promise<void> {
|
||||
return evalStatement(parse(s) as LuaBlock, e);
|
||||
}
|
||||
|
||||
Deno.test("Evaluator test", async () => {
|
||||
const env = new LuaEnv();
|
||||
env.set("test", new LuaNativeJSFunction((n) => n));
|
||||
|
@ -22,11 +26,19 @@ Deno.test("Evaluator test", async () => {
|
|||
assertEquals(evalExpr(`4 // 3`), 1);
|
||||
assertEquals(evalExpr(`4 % 3`), 1);
|
||||
|
||||
// Strings
|
||||
assertEquals(evalExpr(`"a" .. "b"`), "ab");
|
||||
|
||||
// Logic
|
||||
assertEquals(evalExpr(`true and false`), false);
|
||||
assertEquals(evalExpr(`true or false`), true);
|
||||
assertEquals(evalExpr(`not true`), false);
|
||||
|
||||
// Tables
|
||||
const tbl = evalExpr(`{3, 1, 2}`);
|
||||
assertEquals(tbl.entries.get(1), 3);
|
||||
assertEquals(tbl.entries.get(2), 1);
|
||||
assertEquals(tbl.entries.get(3), 2);
|
||||
assertEquals(tbl.get(1), 3);
|
||||
assertEquals(tbl.get(2), 1);
|
||||
assertEquals(tbl.get(3), 2);
|
||||
assertEquals(tbl.toArray(), [3, 1, 2]);
|
||||
|
||||
assertEquals(evalExpr(`{name=test("Zef"), age=100}`, env).toObject(), {
|
||||
|
@ -51,9 +63,126 @@ Deno.test("Evaluator test", async () => {
|
|||
assertEquals(evalExpr(`#{1, 2, 3}`), 3);
|
||||
|
||||
// Unary operators
|
||||
|
||||
assertEquals(await evalExpr(`-asyncTest(3)`, env), -3);
|
||||
|
||||
// Function calls
|
||||
assertEquals(singleResult(evalExpr(`test(3)`, env)), 3);
|
||||
assertEquals(singleResult(await evalExpr(`asyncTest(3) + 1`, env)), 4);
|
||||
});
|
||||
|
||||
Deno.test("Statement evaluation", async () => {
|
||||
const env = new LuaEnv();
|
||||
env.set("test", new LuaNativeJSFunction((n) => n));
|
||||
env.set("asyncTest", new LuaNativeJSFunction((n) => Promise.resolve(n)));
|
||||
|
||||
assertEquals(undefined, await evalBlock(`a = 3`, env));
|
||||
assertEquals(env.get("a"), 3);
|
||||
assertEquals(undefined, await evalBlock(`b = test(3)`, env));
|
||||
assertEquals(env.get("b"), 3);
|
||||
|
||||
await evalBlock(`c = asyncTest(3)`, env);
|
||||
assertEquals(env.get("c"), 3);
|
||||
|
||||
// Multiple assignments
|
||||
const env2 = new LuaEnv();
|
||||
assertEquals(undefined, await evalBlock(`a, b = 1, 2`, env2));
|
||||
assertEquals(env2.get("a"), 1);
|
||||
assertEquals(env2.get("b"), 2);
|
||||
|
||||
// Other lvalues
|
||||
const env3 = new LuaEnv();
|
||||
await evalBlock(`tbl = {1, 2, 3}`, env3);
|
||||
await evalBlock(`tbl[1] = 3`, env3);
|
||||
assertEquals(env3.get("tbl").toArray(), [3, 2, 3]);
|
||||
await evalBlock("tbl.name = 'Zef'", env3);
|
||||
assertEquals(env3.get("tbl").get("name"), "Zef");
|
||||
await evalBlock(`tbl[2] = {age=10}`, env3);
|
||||
await evalBlock(`tbl[2].age = 20`, env3);
|
||||
assertEquals(env3.get("tbl").get(2).get("age"), 20);
|
||||
|
||||
// Blocks and scopes
|
||||
const env4 = new LuaEnv();
|
||||
env4.set("print", new LuaNativeJSFunction(console.log));
|
||||
await evalBlock(
|
||||
`
|
||||
a = 1
|
||||
do
|
||||
-- sets global a to 3
|
||||
a = 3
|
||||
print("The number is: " .. a)
|
||||
end`,
|
||||
env4,
|
||||
);
|
||||
assertEquals(env4.get("a"), 3);
|
||||
|
||||
const env5 = new LuaEnv();
|
||||
env5.set("print", new LuaNativeJSFunction(console.log));
|
||||
|
||||
await evalBlock(
|
||||
`
|
||||
a = 1
|
||||
if a > 0 then
|
||||
a = 3
|
||||
else
|
||||
a = 0
|
||||
end`,
|
||||
env5,
|
||||
);
|
||||
assertEquals(env5.get("a"), 3);
|
||||
|
||||
await evalBlock(
|
||||
`
|
||||
if a < 0 then
|
||||
a = -1
|
||||
elseif a > 0 then
|
||||
a = 1
|
||||
else
|
||||
a = 0
|
||||
end`,
|
||||
env5,
|
||||
);
|
||||
assertEquals(env5.get("a"), 1);
|
||||
|
||||
await evalBlock(
|
||||
`
|
||||
var = 1
|
||||
do
|
||||
local var
|
||||
var = 2
|
||||
end`,
|
||||
env5,
|
||||
);
|
||||
assertEquals(env5.get("var"), 1);
|
||||
|
||||
// While loop
|
||||
const env6 = new LuaEnv();
|
||||
await evalBlock(
|
||||
`
|
||||
c = 0
|
||||
while true do
|
||||
c = c + 1
|
||||
if c == 3 then
|
||||
break
|
||||
end
|
||||
end
|
||||
`,
|
||||
env6,
|
||||
);
|
||||
assertEquals(env6.get("c"), 3);
|
||||
|
||||
// Repeat loop
|
||||
const env7 = new LuaEnv();
|
||||
await evalBlock(
|
||||
`
|
||||
c = 0
|
||||
repeat
|
||||
c = c + 1
|
||||
if c == 3 then
|
||||
break
|
||||
end
|
||||
until false
|
||||
`,
|
||||
env7,
|
||||
);
|
||||
assertEquals(env7.get("c"), 3);
|
||||
});
|
||||
|
|
|
@ -1,18 +1,26 @@
|
|||
import type { LuaExpression } from "$common/space_lua/ast.ts";
|
||||
import type {
|
||||
LuaExpression,
|
||||
LuaLValue,
|
||||
LuaStatement,
|
||||
} from "$common/space_lua/ast.ts";
|
||||
import { evalPromiseValues } from "$common/space_lua/util.ts";
|
||||
import {
|
||||
type ILuaFunction,
|
||||
type LuaEnv,
|
||||
LuaBreak,
|
||||
LuaEnv,
|
||||
luaGet,
|
||||
luaLen,
|
||||
type LuaLValueContainer,
|
||||
LuaTable,
|
||||
luaTruthy,
|
||||
type LuaValue,
|
||||
singleResult,
|
||||
} from "./runtime.ts";
|
||||
|
||||
export function evalExpression(
|
||||
e: LuaExpression,
|
||||
env: LuaEnv,
|
||||
): Promise<any> | any {
|
||||
): Promise<LuaValue> | LuaValue {
|
||||
switch (e.type) {
|
||||
case "String":
|
||||
// TODO: Deal with escape sequences
|
||||
|
@ -101,13 +109,13 @@ export function evalExpression(
|
|||
const value = evalExpression(field.value, env);
|
||||
if (value instanceof Promise) {
|
||||
promises.push(value.then((value) => {
|
||||
table.entries.set(
|
||||
table.set(
|
||||
field.key,
|
||||
singleResult(value),
|
||||
);
|
||||
}));
|
||||
} else {
|
||||
table.entries.set(field.key, singleResult(value));
|
||||
table.set(field.key, singleResult(value));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -126,14 +134,14 @@ export function evalExpression(
|
|||
? value
|
||||
: Promise.resolve(value),
|
||||
]).then(([key, value]) => {
|
||||
table.entries.set(
|
||||
table.set(
|
||||
singleResult(key),
|
||||
singleResult(value),
|
||||
);
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
table.entries.set(
|
||||
table.set(
|
||||
singleResult(key),
|
||||
singleResult(value),
|
||||
);
|
||||
|
@ -145,15 +153,15 @@ export function evalExpression(
|
|||
if (value instanceof Promise) {
|
||||
promises.push(value.then((value) => {
|
||||
// +1 because Lua tables are 1-indexed
|
||||
table.entries.set(
|
||||
table.entries.size + 1,
|
||||
table.set(
|
||||
table.length + 1,
|
||||
singleResult(value),
|
||||
);
|
||||
}));
|
||||
} else {
|
||||
// +1 because Lua tables are 1-indexed
|
||||
table.entries.set(
|
||||
table.entries.size + 1,
|
||||
table.set(
|
||||
table.length + 1,
|
||||
singleResult(value),
|
||||
);
|
||||
}
|
||||
|
@ -175,7 +183,7 @@ export function evalExpression(
|
|||
function evalPrefixExpression(
|
||||
e: LuaExpression,
|
||||
env: LuaEnv,
|
||||
): Promise<any> | any {
|
||||
): Promise<LuaValue> | LuaValue {
|
||||
switch (e.type) {
|
||||
case "Variable": {
|
||||
const value = env.get(e.name);
|
||||
|
@ -262,3 +270,160 @@ function luaOp(op: string, left: any, right: any): any {
|
|||
throw new Error(`Unknown operator ${op}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function evalStatement(
|
||||
s: LuaStatement,
|
||||
env: LuaEnv,
|
||||
): Promise<void> {
|
||||
switch (s.type) {
|
||||
case "Assignment": {
|
||||
const values = await evalPromiseValues(
|
||||
s.expressions.map((value) => evalExpression(value, env)),
|
||||
);
|
||||
const lvalues = await evalPromiseValues(s.variables
|
||||
.map((lval) => evalLValue(lval, env)));
|
||||
|
||||
for (let i = 0; i < lvalues.length; i++) {
|
||||
lvalues[i].env.set(lvalues[i].key, values[i]);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case "Local": {
|
||||
for (let i = 0; i < s.names.length; i++) {
|
||||
if (!s.expressions || s.expressions[i] === undefined) {
|
||||
env.setLocal(s.names[i].name, null);
|
||||
} else {
|
||||
const value = await evalExpression(s.expressions[i], env);
|
||||
env.setLocal(s.names[i].name, value);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "Semicolon":
|
||||
break;
|
||||
case "Label":
|
||||
case "Goto":
|
||||
throw new Error("Labels and gotos are not supported yet");
|
||||
case "Block": {
|
||||
const newEnv = new LuaEnv(env);
|
||||
for (const statement of s.statements) {
|
||||
await evalStatement(statement, newEnv);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "If": {
|
||||
for (const cond of s.conditions) {
|
||||
if (luaTruthy(await evalExpression(cond.condition, env))) {
|
||||
return evalStatement(cond.block, env);
|
||||
}
|
||||
}
|
||||
if (s.elseBlock) {
|
||||
return evalStatement(s.elseBlock, env);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "While": {
|
||||
while (luaTruthy(await evalExpression(s.condition, env))) {
|
||||
try {
|
||||
await evalStatement(s.block, env);
|
||||
} catch (e: any) {
|
||||
if (e instanceof LuaBreak) {
|
||||
break;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "Repeat": {
|
||||
do {
|
||||
try {
|
||||
await evalStatement(s.block, env);
|
||||
} catch (e: any) {
|
||||
if (e instanceof LuaBreak) {
|
||||
break;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
} while (!luaTruthy(await evalExpression(s.condition, env)));
|
||||
break;
|
||||
}
|
||||
case "Break":
|
||||
throw new LuaBreak();
|
||||
case "FunctionCallStatement": {
|
||||
return evalExpression(s.call, env);
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unknown statement type ${s.type}`);
|
||||
}
|
||||
}
|
||||
|
||||
function evalLValue(
|
||||
lval: LuaLValue,
|
||||
env: LuaEnv,
|
||||
): LuaLValueContainer | Promise<LuaLValueContainer> {
|
||||
switch (lval.type) {
|
||||
case "Variable":
|
||||
return { env, key: lval.name };
|
||||
case "TableAccess": {
|
||||
const objValue = evalExpression(
|
||||
lval.object,
|
||||
env,
|
||||
);
|
||||
const keyValue = evalExpression(lval.key, env);
|
||||
if (
|
||||
objValue instanceof Promise ||
|
||||
keyValue instanceof Promise
|
||||
) {
|
||||
return Promise.all([
|
||||
objValue instanceof Promise
|
||||
? objValue
|
||||
: Promise.resolve(objValue),
|
||||
keyValue instanceof Promise
|
||||
? keyValue
|
||||
: Promise.resolve(keyValue),
|
||||
]).then(([objValue, keyValue]) => ({
|
||||
env: singleResult(objValue),
|
||||
key: singleResult(keyValue),
|
||||
}));
|
||||
} else {
|
||||
return {
|
||||
env: singleResult(objValue),
|
||||
key: singleResult(keyValue),
|
||||
};
|
||||
}
|
||||
}
|
||||
case "PropertyAccess": {
|
||||
const objValue = evalExpression(
|
||||
lval.object,
|
||||
env,
|
||||
);
|
||||
if (objValue instanceof Promise) {
|
||||
return objValue.then((objValue) => {
|
||||
if (!objValue.set) {
|
||||
throw new Error(
|
||||
`Not a settable object: ${objValue}`,
|
||||
);
|
||||
}
|
||||
return {
|
||||
env: objValue,
|
||||
key: lval.property,
|
||||
};
|
||||
});
|
||||
} else {
|
||||
if (!objValue.set) {
|
||||
throw new Error(
|
||||
`Not a settable object: ${objValue}`,
|
||||
);
|
||||
}
|
||||
return {
|
||||
env: objValue,
|
||||
key: lval.property,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -165,6 +165,8 @@ function parseStatement(n: CrudeAST): LuaStatement {
|
|||
names: parseAttNames(t[2]),
|
||||
expressions: t[4] ? parseExpList(t[4]) : [],
|
||||
};
|
||||
case "break":
|
||||
return { type: "Break" };
|
||||
default:
|
||||
console.error(t);
|
||||
throw new Error(`Unknown statement type: ${t[0]}`);
|
||||
|
@ -359,6 +361,12 @@ function parsePrefixExpression(n: CrudeAST): LuaPrefixExpression {
|
|||
object: parsePrefixExpression(t[1]),
|
||||
property: t[3][1] as string,
|
||||
};
|
||||
case "MemberExpression":
|
||||
return {
|
||||
type: "TableAccess",
|
||||
object: parsePrefixExpression(t[1]),
|
||||
key: parseExpression(t[3]),
|
||||
};
|
||||
case "Parens":
|
||||
return { type: "Parenthesized", expression: parseExpression(t[2]) };
|
||||
default:
|
||||
|
|
|
@ -1,15 +1,24 @@
|
|||
import type { LuaFunctionBody } from "./ast.ts";
|
||||
|
||||
export class LuaEnv {
|
||||
variables = new Map<string, any>();
|
||||
export class LuaEnv implements ILuaSettable {
|
||||
variables = new Map<string, LuaValue>();
|
||||
|
||||
constructor(readonly parent?: LuaEnv) {
|
||||
}
|
||||
|
||||
set(name: string, value: any) {
|
||||
setLocal(name: string, value: LuaValue) {
|
||||
this.variables.set(name, value);
|
||||
}
|
||||
|
||||
get(name: string): any {
|
||||
set(key: string, value: LuaValue): void {
|
||||
if (this.variables.has(key) || !this.parent) {
|
||||
this.variables.set(key, value);
|
||||
} else {
|
||||
this.parent.set(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
get(name: string): LuaValue {
|
||||
if (this.variables.has(name)) {
|
||||
return this.variables.get(name);
|
||||
}
|
||||
|
@ -40,71 +49,130 @@ export function singleResult(value: any): any {
|
|||
}
|
||||
}
|
||||
|
||||
// These types are for documentation only
|
||||
export type LuaValue = any;
|
||||
export type JSValue = any;
|
||||
|
||||
export interface ILuaFunction {
|
||||
call(...args: any[]): Promise<LuaMultiRes> | LuaMultiRes;
|
||||
call(...args: LuaValue[]): Promise<LuaValue> | LuaValue;
|
||||
}
|
||||
|
||||
export interface ILuaSettable {
|
||||
set(key: LuaValue, value: LuaValue): void;
|
||||
}
|
||||
|
||||
export class LuaFunction implements ILuaFunction {
|
||||
constructor(private body: LuaFunctionBody, private closure: LuaEnv) {
|
||||
}
|
||||
|
||||
call(..._args: any[]): Promise<LuaMultiRes> | LuaMultiRes {
|
||||
call(...args: LuaValue[]): Promise<LuaValue> | LuaValue {
|
||||
// Create a new environment for this function call
|
||||
const env = new LuaEnv(this.closure);
|
||||
// Assign the passed arguments to the parameters
|
||||
for (let i = 0; i < this.body.parameters.length; i++) {
|
||||
let arg = args[i];
|
||||
if (arg === undefined) {
|
||||
arg = null;
|
||||
}
|
||||
env.set(this.body.parameters[i], arg);
|
||||
}
|
||||
throw new Error("Not yet implemented funciton call");
|
||||
}
|
||||
}
|
||||
|
||||
export class LuaNativeJSFunction implements ILuaFunction {
|
||||
constructor(readonly fn: (...args: any[]) => any) {
|
||||
constructor(readonly fn: (...args: JSValue[]) => JSValue) {
|
||||
}
|
||||
|
||||
call(...args: any[]): Promise<LuaMultiRes> | LuaMultiRes {
|
||||
const result = this.fn(...args);
|
||||
call(...args: LuaValue[]): Promise<LuaValue> | LuaValue {
|
||||
const result = this.fn(...args.map(luaValueToJS));
|
||||
if (result instanceof Promise) {
|
||||
return result.then((result) => new LuaMultiRes([result]));
|
||||
return result.then(jsToLuaValue);
|
||||
} else {
|
||||
return new LuaMultiRes([result]);
|
||||
return jsToLuaValue(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class LuaTable {
|
||||
constructor(readonly entries: Map<any, any> = new Map()) {
|
||||
export class LuaTable implements ILuaSettable {
|
||||
// To optimize the table implementation we use a combination of different data structures
|
||||
// When tables are used as maps, the common case is that they are string keys, so we use a simple object for that
|
||||
private stringKeys: Record<string, any>;
|
||||
// Other keys we can support using a Map as a fallback
|
||||
private otherKeys: Map<any, any> | null;
|
||||
// When tables are used as arrays, we use a native JavaScript array for that
|
||||
private arrayPart: any[];
|
||||
|
||||
// TODO: Actually implement metatables
|
||||
private metatable: LuaTable | null;
|
||||
|
||||
constructor() {
|
||||
// For efficiency and performance reasons we pre-allocate these (modern JS engines are very good at optimizing this)
|
||||
this.stringKeys = {};
|
||||
this.arrayPart = [];
|
||||
this.otherKeys = null; // Only create this when needed
|
||||
this.metatable = null;
|
||||
}
|
||||
|
||||
get(key: any): any {
|
||||
return this.entries.get(key);
|
||||
get length(): number {
|
||||
return this.arrayPart.length;
|
||||
}
|
||||
|
||||
set(key: any, value: any) {
|
||||
this.entries.set(key, value);
|
||||
set(key: LuaValue, value: LuaValue) {
|
||||
if (typeof key === "string") {
|
||||
this.stringKeys[key] = value;
|
||||
} else if (Number.isInteger(key) && key >= 1) {
|
||||
this.arrayPart[key - 1] = value;
|
||||
} else {
|
||||
if (!this.otherKeys) {
|
||||
this.otherKeys = new Map();
|
||||
}
|
||||
this.otherKeys.set(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the table to a a JavaScript array, assuming it uses integer keys
|
||||
* @returns
|
||||
*/
|
||||
toArray(): any[] {
|
||||
const result = [];
|
||||
const keys = Array.from(this.entries.keys()).sort();
|
||||
for (const key of keys) {
|
||||
result.push(this.entries.get(key));
|
||||
get(key: LuaValue): LuaValue {
|
||||
if (typeof key === "string") {
|
||||
return this.stringKeys[key];
|
||||
} else if (Number.isInteger(key) && key >= 1) {
|
||||
return this.arrayPart[key - 1];
|
||||
} else if (this.otherKeys) {
|
||||
return this.otherKeys.get(key);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
toArray(): JSValue[] {
|
||||
return this.arrayPart;
|
||||
}
|
||||
|
||||
toObject(): Record<string, JSValue> {
|
||||
const result = { ...this.stringKeys };
|
||||
for (const i in this.arrayPart) {
|
||||
result[parseInt(i) + 1] = this.arrayPart[i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the table to a JavaScript object, assuming it uses string keys
|
||||
* @returns
|
||||
*/
|
||||
toObject(): Record<string, any> {
|
||||
const result: Record<string, any> = {};
|
||||
for (const [key, value] of this.entries.entries()) {
|
||||
result[key] = value;
|
||||
static fromArray(arr: JSValue[]): LuaTable {
|
||||
const table = new LuaTable();
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
table.set(i + 1, arr[i]);
|
||||
}
|
||||
return result;
|
||||
return table;
|
||||
}
|
||||
|
||||
static fromObject(obj: Record<string, JSValue>): LuaTable {
|
||||
const table = new LuaTable();
|
||||
for (const key in obj) {
|
||||
table.set(key, obj[key]);
|
||||
}
|
||||
return table;
|
||||
}
|
||||
}
|
||||
|
||||
export type LuaLValueContainer = { env: ILuaSettable; key: LuaValue };
|
||||
|
||||
export function luaSet(obj: any, key: any, value: any) {
|
||||
if (obj instanceof LuaTable) {
|
||||
obj.set(key, value);
|
||||
|
@ -130,3 +198,41 @@ export function luaLen(obj: any): number {
|
|||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
export class LuaBreak extends Error {
|
||||
}
|
||||
|
||||
export function luaTruthy(value: any): boolean {
|
||||
if (value === undefined || value === null || value === false) {
|
||||
return false;
|
||||
}
|
||||
if (value instanceof LuaTable) {
|
||||
return value.length > 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function jsToLuaValue(value: any): any {
|
||||
if (value instanceof LuaTable) {
|
||||
return value;
|
||||
} else if (Array.isArray(value)) {
|
||||
return LuaTable.fromArray(value.map(jsToLuaValue));
|
||||
} else if (typeof value === "object") {
|
||||
return LuaTable.fromObject(value);
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
export function luaValueToJS(value: any): any {
|
||||
if (value instanceof LuaTable) {
|
||||
// This is a heuristic: if this table is used as an array, we return an array
|
||||
if (value.length > 0) {
|
||||
return value.toArray();
|
||||
} else {
|
||||
return value.toObject();
|
||||
}
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -226,7 +226,7 @@ export function parseTreeToAST(tree: ParseTree, omitTrimmable = true): AST {
|
|||
}
|
||||
const ast: AST = [tree.type!];
|
||||
for (const node of tree.children!) {
|
||||
if (node.type && !node.type.endsWith("Mark")) {
|
||||
if (node.type && !node.type.endsWith("Mark") && node.type !== "Comment") {
|
||||
ast.push(parseTreeToAST(node, omitTrimmable));
|
||||
}
|
||||
if (node.text && (omitTrimmable && node.text.trim() || !omitTrimmable)) {
|
||||
|
|
Loading…
Reference in New Issue