deno fmt
parent
a5c4bcc43b
commit
c0a248daba
|
@ -1,252 +1,252 @@
|
||||||
type ASTPosition = {
|
type ASTPosition = {
|
||||||
from?: number;
|
from?: number;
|
||||||
to?: number;
|
to?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LuaBlock = {
|
export type LuaBlock = {
|
||||||
type: "Block";
|
type: "Block";
|
||||||
statements: LuaStatement[];
|
statements: LuaStatement[];
|
||||||
} & ASTPosition;
|
} & ASTPosition;
|
||||||
|
|
||||||
// STATEMENTS
|
// STATEMENTS
|
||||||
export type LuaReturnStatement = {
|
export type LuaReturnStatement = {
|
||||||
type: "Return";
|
type: "Return";
|
||||||
expressions: LuaExpression[];
|
expressions: LuaExpression[];
|
||||||
} & ASTPosition;
|
} & ASTPosition;
|
||||||
|
|
||||||
export type LuaStatement =
|
export type LuaStatement =
|
||||||
| LuaSemicolonStatement
|
| LuaSemicolonStatement
|
||||||
| LuaLabelStatement
|
| LuaLabelStatement
|
||||||
| LuaBreakStatement
|
| LuaBreakStatement
|
||||||
| LuaGotoStatement
|
| LuaGotoStatement
|
||||||
| LuaReturnStatement
|
| LuaReturnStatement
|
||||||
| LuaBlock
|
| LuaBlock
|
||||||
| LuaWhileStatement
|
| LuaWhileStatement
|
||||||
| LuaRepeatStatement
|
| LuaRepeatStatement
|
||||||
| LuaIfStatement
|
| LuaIfStatement
|
||||||
| LuaForStatement
|
| LuaForStatement
|
||||||
| LuaForInStatement
|
| LuaForInStatement
|
||||||
| LuaFunctionStatement
|
| LuaFunctionStatement
|
||||||
| LuaLocalFunctionStatement
|
| LuaLocalFunctionStatement
|
||||||
| LuaAssignmentStatement
|
| LuaAssignmentStatement
|
||||||
| LuaLocalStatement
|
| LuaLocalStatement
|
||||||
| LuaFunctionCallStatement;
|
| LuaFunctionCallStatement;
|
||||||
|
|
||||||
export type LuaSemicolonStatement = {
|
export type LuaSemicolonStatement = {
|
||||||
type: "Semicolon";
|
type: "Semicolon";
|
||||||
} & ASTPosition;
|
} & ASTPosition;
|
||||||
|
|
||||||
export type LuaLabelStatement = {
|
export type LuaLabelStatement = {
|
||||||
type: "Label";
|
type: "Label";
|
||||||
name: string;
|
name: string;
|
||||||
} & ASTPosition;
|
} & ASTPosition;
|
||||||
|
|
||||||
export type LuaBreakStatement = {
|
export type LuaBreakStatement = {
|
||||||
type: "Break";
|
type: "Break";
|
||||||
} & ASTPosition;
|
} & ASTPosition;
|
||||||
|
|
||||||
export type LuaGotoStatement = {
|
export type LuaGotoStatement = {
|
||||||
type: "Goto";
|
type: "Goto";
|
||||||
name: string;
|
name: string;
|
||||||
} & ASTPosition;
|
} & ASTPosition;
|
||||||
|
|
||||||
export type LuaWhileStatement = {
|
export type LuaWhileStatement = {
|
||||||
type: "While";
|
type: "While";
|
||||||
condition: LuaExpression;
|
condition: LuaExpression;
|
||||||
block: LuaBlock;
|
block: LuaBlock;
|
||||||
} & ASTPosition;
|
} & ASTPosition;
|
||||||
|
|
||||||
export type LuaRepeatStatement = {
|
export type LuaRepeatStatement = {
|
||||||
type: "Repeat";
|
type: "Repeat";
|
||||||
block: LuaBlock;
|
block: LuaBlock;
|
||||||
condition: LuaExpression;
|
condition: LuaExpression;
|
||||||
} & ASTPosition;
|
} & ASTPosition;
|
||||||
|
|
||||||
export type LuaIfStatement = {
|
export type LuaIfStatement = {
|
||||||
type: "If";
|
type: "If";
|
||||||
conditions: { condition: LuaExpression; block: LuaBlock }[];
|
conditions: { condition: LuaExpression; block: LuaBlock }[];
|
||||||
elseBlock?: LuaBlock;
|
elseBlock?: LuaBlock;
|
||||||
} & ASTPosition;
|
} & ASTPosition;
|
||||||
|
|
||||||
export type LuaForStatement = {
|
export type LuaForStatement = {
|
||||||
type: "For";
|
type: "For";
|
||||||
name: string;
|
name: string;
|
||||||
start: LuaExpression;
|
start: LuaExpression;
|
||||||
end: LuaExpression;
|
end: LuaExpression;
|
||||||
step?: LuaExpression;
|
step?: LuaExpression;
|
||||||
block: LuaBlock;
|
block: LuaBlock;
|
||||||
} & ASTPosition;
|
} & ASTPosition;
|
||||||
|
|
||||||
export type LuaForInStatement = {
|
export type LuaForInStatement = {
|
||||||
type: "ForIn";
|
type: "ForIn";
|
||||||
names: string[];
|
names: string[];
|
||||||
expressions: LuaExpression[];
|
expressions: LuaExpression[];
|
||||||
block: LuaBlock;
|
block: LuaBlock;
|
||||||
} & ASTPosition;
|
} & ASTPosition;
|
||||||
|
|
||||||
export type LuaFunctionStatement = {
|
export type LuaFunctionStatement = {
|
||||||
type: "Function";
|
type: "Function";
|
||||||
name: LuaFunctionName;
|
name: LuaFunctionName;
|
||||||
body: LuaFunctionBody;
|
body: LuaFunctionBody;
|
||||||
} & ASTPosition;
|
} & ASTPosition;
|
||||||
|
|
||||||
export type LuaLocalFunctionStatement = {
|
export type LuaLocalFunctionStatement = {
|
||||||
type: "LocalFunction";
|
type: "LocalFunction";
|
||||||
name: string;
|
name: string;
|
||||||
body: LuaFunctionBody;
|
body: LuaFunctionBody;
|
||||||
} & ASTPosition;
|
} & ASTPosition;
|
||||||
|
|
||||||
export type LuaFunctionName = {
|
export type LuaFunctionName = {
|
||||||
type: "FunctionName";
|
type: "FunctionName";
|
||||||
propNames: string[];
|
propNames: string[];
|
||||||
colonName?: string;
|
colonName?: string;
|
||||||
} & ASTPosition;
|
} & ASTPosition;
|
||||||
|
|
||||||
export type LuaFunctionBody = {
|
export type LuaFunctionBody = {
|
||||||
type: "FunctionBody";
|
type: "FunctionBody";
|
||||||
parameters: string[];
|
parameters: string[];
|
||||||
block: LuaBlock;
|
block: LuaBlock;
|
||||||
} & ASTPosition;
|
} & ASTPosition;
|
||||||
|
|
||||||
export type LuaAssignmentStatement = {
|
export type LuaAssignmentStatement = {
|
||||||
type: "Assignment";
|
type: "Assignment";
|
||||||
variables: LuaLValue[];
|
variables: LuaLValue[];
|
||||||
expressions: LuaExpression[];
|
expressions: LuaExpression[];
|
||||||
} & ASTPosition;
|
} & ASTPosition;
|
||||||
|
|
||||||
export type LuaLValue =
|
export type LuaLValue =
|
||||||
| LuaVariable
|
| LuaVariable
|
||||||
| LuaPropertyAccessExpression
|
| LuaPropertyAccessExpression
|
||||||
| LuaTableAccessExpression;
|
| LuaTableAccessExpression;
|
||||||
|
|
||||||
export type LuaLocalStatement = {
|
export type LuaLocalStatement = {
|
||||||
type: "Local";
|
type: "Local";
|
||||||
names: LuaAttName[];
|
names: LuaAttName[];
|
||||||
expressions?: LuaExpression[];
|
expressions?: LuaExpression[];
|
||||||
} & ASTPosition;
|
} & ASTPosition;
|
||||||
|
|
||||||
export type LuaAttName = {
|
export type LuaAttName = {
|
||||||
type: "AttName";
|
type: "AttName";
|
||||||
name: string;
|
name: string;
|
||||||
attribute?: string;
|
attribute?: string;
|
||||||
} & ASTPosition;
|
} & ASTPosition;
|
||||||
|
|
||||||
export type LuaFunctionCallStatement = {
|
export type LuaFunctionCallStatement = {
|
||||||
type: "FunctionCallStatement";
|
type: "FunctionCallStatement";
|
||||||
call: LuaFunctionCallExpression;
|
call: LuaFunctionCallExpression;
|
||||||
} & ASTPosition;
|
} & ASTPosition;
|
||||||
|
|
||||||
// EXPRESSIONS
|
// EXPRESSIONS
|
||||||
export type LuaExpression =
|
export type LuaExpression =
|
||||||
| LuaNilLiteral
|
| LuaNilLiteral
|
||||||
| LuaBooleanLiteral
|
| LuaBooleanLiteral
|
||||||
| LuaNumberLiteral
|
| LuaNumberLiteral
|
||||||
| LuaStringLiteral
|
| LuaStringLiteral
|
||||||
| LuaPrefixExpression
|
| LuaPrefixExpression
|
||||||
| LuaBinaryExpression
|
| LuaBinaryExpression
|
||||||
| LuaUnaryExpression
|
| LuaUnaryExpression
|
||||||
| LuaTableConstructor
|
| LuaTableConstructor
|
||||||
| LuaFunctionDefinition;
|
| LuaFunctionDefinition;
|
||||||
|
|
||||||
export type LuaNilLiteral = {
|
export type LuaNilLiteral = {
|
||||||
type: "Nil";
|
type: "Nil";
|
||||||
} & ASTPosition;
|
} & ASTPosition;
|
||||||
|
|
||||||
export type LuaBooleanLiteral = {
|
export type LuaBooleanLiteral = {
|
||||||
type: "Boolean";
|
type: "Boolean";
|
||||||
value: boolean;
|
value: boolean;
|
||||||
} & ASTPosition;
|
} & ASTPosition;
|
||||||
|
|
||||||
export type LuaNumberLiteral = {
|
export type LuaNumberLiteral = {
|
||||||
type: "Number";
|
type: "Number";
|
||||||
value: number;
|
value: number;
|
||||||
} & ASTPosition;
|
} & ASTPosition;
|
||||||
|
|
||||||
export type LuaStringLiteral = {
|
export type LuaStringLiteral = {
|
||||||
type: "String";
|
type: "String";
|
||||||
value: string;
|
value: string;
|
||||||
} & ASTPosition;
|
} & ASTPosition;
|
||||||
|
|
||||||
export type LuaPrefixExpression =
|
export type LuaPrefixExpression =
|
||||||
| LuaVariableExpression
|
| LuaVariableExpression
|
||||||
| LuaParenthesizedExpression
|
| LuaParenthesizedExpression
|
||||||
| LuaFunctionCallExpression;
|
| LuaFunctionCallExpression;
|
||||||
|
|
||||||
export type LuaParenthesizedExpression = {
|
export type LuaParenthesizedExpression = {
|
||||||
type: "Parenthesized";
|
type: "Parenthesized";
|
||||||
expression: LuaExpression;
|
expression: LuaExpression;
|
||||||
} & ASTPosition;
|
} & ASTPosition;
|
||||||
|
|
||||||
export type LuaVariableExpression =
|
export type LuaVariableExpression =
|
||||||
| LuaVariable
|
| LuaVariable
|
||||||
| LuaPropertyAccessExpression
|
| LuaPropertyAccessExpression
|
||||||
| LuaTableAccessExpression;
|
| LuaTableAccessExpression;
|
||||||
|
|
||||||
export type LuaVariable = {
|
export type LuaVariable = {
|
||||||
type: "Variable";
|
type: "Variable";
|
||||||
name: string;
|
name: string;
|
||||||
} & ASTPosition;
|
} & ASTPosition;
|
||||||
|
|
||||||
export type LuaPropertyAccessExpression = {
|
export type LuaPropertyAccessExpression = {
|
||||||
type: "PropertyAccess";
|
type: "PropertyAccess";
|
||||||
object: LuaPrefixExpression;
|
object: LuaPrefixExpression;
|
||||||
property: string;
|
property: string;
|
||||||
} & ASTPosition;
|
} & ASTPosition;
|
||||||
|
|
||||||
export type LuaTableAccessExpression = {
|
export type LuaTableAccessExpression = {
|
||||||
type: "TableAccess";
|
type: "TableAccess";
|
||||||
object: LuaPrefixExpression;
|
object: LuaPrefixExpression;
|
||||||
key: LuaExpression;
|
key: LuaExpression;
|
||||||
} & ASTPosition;
|
} & ASTPosition;
|
||||||
|
|
||||||
export type LuaFunctionCallExpression = {
|
export type LuaFunctionCallExpression = {
|
||||||
type: "FunctionCall";
|
type: "FunctionCall";
|
||||||
prefix: LuaPrefixExpression;
|
prefix: LuaPrefixExpression;
|
||||||
name?: string;
|
name?: string;
|
||||||
args: LuaExpression[];
|
args: LuaExpression[];
|
||||||
} & ASTPosition;
|
} & ASTPosition;
|
||||||
|
|
||||||
export type LuaBinaryExpression = {
|
export type LuaBinaryExpression = {
|
||||||
type: "Binary";
|
type: "Binary";
|
||||||
operator: string;
|
operator: string;
|
||||||
left: LuaExpression;
|
left: LuaExpression;
|
||||||
right: LuaExpression;
|
right: LuaExpression;
|
||||||
} & ASTPosition;
|
} & ASTPosition;
|
||||||
|
|
||||||
export type LuaUnaryExpression = {
|
export type LuaUnaryExpression = {
|
||||||
type: "Unary";
|
type: "Unary";
|
||||||
operator: string;
|
operator: string;
|
||||||
argument: LuaExpression;
|
argument: LuaExpression;
|
||||||
} & ASTPosition;
|
} & ASTPosition;
|
||||||
|
|
||||||
export type LuaTableConstructor = {
|
export type LuaTableConstructor = {
|
||||||
type: "TableConstructor";
|
type: "TableConstructor";
|
||||||
fields: LuaTableField[];
|
fields: LuaTableField[];
|
||||||
} & ASTPosition;
|
} & ASTPosition;
|
||||||
|
|
||||||
export type LuaTableField =
|
export type LuaTableField =
|
||||||
| LuaDynamicField
|
| LuaDynamicField
|
||||||
| LuaPropField
|
| LuaPropField
|
||||||
| LuaExpressionField;
|
| LuaExpressionField;
|
||||||
|
|
||||||
export type LuaDynamicField = {
|
export type LuaDynamicField = {
|
||||||
type: "DynamicField";
|
type: "DynamicField";
|
||||||
key: LuaExpression;
|
key: LuaExpression;
|
||||||
value: LuaExpression;
|
value: LuaExpression;
|
||||||
} & ASTPosition;
|
} & ASTPosition;
|
||||||
|
|
||||||
export type LuaPropField = {
|
export type LuaPropField = {
|
||||||
type: "PropField";
|
type: "PropField";
|
||||||
key: string;
|
key: string;
|
||||||
value: LuaExpression;
|
value: LuaExpression;
|
||||||
} & ASTPosition;
|
} & ASTPosition;
|
||||||
|
|
||||||
export type LuaExpressionField = {
|
export type LuaExpressionField = {
|
||||||
type: "ExpressionField";
|
type: "ExpressionField";
|
||||||
value: LuaExpression;
|
value: LuaExpression;
|
||||||
} & ASTPosition;
|
} & ASTPosition;
|
||||||
|
|
||||||
export type LuaFunctionDefinition = {
|
export type LuaFunctionDefinition = {
|
||||||
type: "FunctionDefinition";
|
type: "FunctionDefinition";
|
||||||
body: LuaFunctionBody;
|
body: LuaFunctionBody;
|
||||||
} & ASTPosition;
|
} & ASTPosition;
|
||||||
|
|
|
@ -5,133 +5,133 @@ import type { LuaBlock, LuaFunctionCallStatement } from "./ast.ts";
|
||||||
import { evalExpression, evalStatement } from "./eval.ts";
|
import { evalExpression, evalStatement } from "./eval.ts";
|
||||||
|
|
||||||
function evalExpr(s: string, e = new LuaEnv()): any {
|
function evalExpr(s: string, e = new LuaEnv()): any {
|
||||||
return evalExpression(
|
return evalExpression(
|
||||||
(parse(`e(${s})`).statements[0] as LuaFunctionCallStatement).call
|
(parse(`e(${s})`).statements[0] as LuaFunctionCallStatement).call
|
||||||
.args[0],
|
.args[0],
|
||||||
e,
|
e,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function evalBlock(s: string, e = new LuaEnv()): Promise<void> {
|
function evalBlock(s: string, e = new LuaEnv()): Promise<void> {
|
||||||
return evalStatement(parse(s) as LuaBlock, e);
|
return evalStatement(parse(s) as LuaBlock, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
Deno.test("Evaluator test", async () => {
|
Deno.test("Evaluator test", async () => {
|
||||||
const env = new LuaEnv();
|
const env = new LuaEnv();
|
||||||
env.set("test", new LuaNativeJSFunction((n) => n));
|
env.set("test", new LuaNativeJSFunction((n) => n));
|
||||||
env.set("asyncTest", new LuaNativeJSFunction((n) => Promise.resolve(n)));
|
env.set("asyncTest", new LuaNativeJSFunction((n) => Promise.resolve(n)));
|
||||||
|
|
||||||
// Basic arithmetic
|
// Basic arithmetic
|
||||||
assertEquals(evalExpr(`1 + 2 + 3 - 3`), 3);
|
assertEquals(evalExpr(`1 + 2 + 3 - 3`), 3);
|
||||||
assertEquals(evalExpr(`4 // 3`), 1);
|
assertEquals(evalExpr(`4 // 3`), 1);
|
||||||
assertEquals(evalExpr(`4 % 3`), 1);
|
assertEquals(evalExpr(`4 % 3`), 1);
|
||||||
|
|
||||||
// Strings
|
// Strings
|
||||||
assertEquals(evalExpr(`"a" .. "b"`), "ab");
|
assertEquals(evalExpr(`"a" .. "b"`), "ab");
|
||||||
|
|
||||||
// Logic
|
// Logic
|
||||||
assertEquals(evalExpr(`true and false`), false);
|
assertEquals(evalExpr(`true and false`), false);
|
||||||
assertEquals(evalExpr(`true or false`), true);
|
assertEquals(evalExpr(`true or false`), true);
|
||||||
assertEquals(evalExpr(`not true`), false);
|
assertEquals(evalExpr(`not true`), false);
|
||||||
|
|
||||||
// Tables
|
// Tables
|
||||||
const tbl = evalExpr(`{3, 1, 2}`);
|
const tbl = evalExpr(`{3, 1, 2}`);
|
||||||
assertEquals(tbl.get(1), 3);
|
assertEquals(tbl.get(1), 3);
|
||||||
assertEquals(tbl.get(2), 1);
|
assertEquals(tbl.get(2), 1);
|
||||||
assertEquals(tbl.get(3), 2);
|
assertEquals(tbl.get(3), 2);
|
||||||
assertEquals(tbl.toArray(), [3, 1, 2]);
|
assertEquals(tbl.toArray(), [3, 1, 2]);
|
||||||
|
|
||||||
assertEquals(evalExpr(`{name=test("Zef"), age=100}`, env).toObject(), {
|
assertEquals(evalExpr(`{name=test("Zef"), age=100}`, env).toObject(), {
|
||||||
name: "Zef",
|
name: "Zef",
|
||||||
age: 100,
|
age: 100,
|
||||||
});
|
});
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
(await evalExpr(`{name="Zef", age=asyncTest(100)}`, env)).toObject(),
|
(await evalExpr(`{name="Zef", age=asyncTest(100)}`, env)).toObject(),
|
||||||
{
|
{
|
||||||
name: "Zef",
|
name: "Zef",
|
||||||
age: 100,
|
age: 100,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
assertEquals(evalExpr(`{[3+2]=1, ["a".."b"]=2}`).toObject(), {
|
assertEquals(evalExpr(`{[3+2]=1, ["a".."b"]=2}`).toObject(), {
|
||||||
5: 1,
|
5: 1,
|
||||||
ab: 2,
|
ab: 2,
|
||||||
});
|
});
|
||||||
|
|
||||||
assertEquals(evalExpr(`#{}`), 0);
|
assertEquals(evalExpr(`#{}`), 0);
|
||||||
assertEquals(evalExpr(`#{1, 2, 3}`), 3);
|
assertEquals(evalExpr(`#{1, 2, 3}`), 3);
|
||||||
|
|
||||||
// Unary operators
|
// Unary operators
|
||||||
assertEquals(await evalExpr(`-asyncTest(3)`, env), -3);
|
assertEquals(await evalExpr(`-asyncTest(3)`, env), -3);
|
||||||
|
|
||||||
// Function calls
|
// Function calls
|
||||||
assertEquals(singleResult(evalExpr(`test(3)`, env)), 3);
|
assertEquals(singleResult(evalExpr(`test(3)`, env)), 3);
|
||||||
assertEquals(singleResult(await evalExpr(`asyncTest(3) + 1`, env)), 4);
|
assertEquals(singleResult(await evalExpr(`asyncTest(3) + 1`, env)), 4);
|
||||||
});
|
});
|
||||||
|
|
||||||
Deno.test("Statement evaluation", async () => {
|
Deno.test("Statement evaluation", async () => {
|
||||||
const env = new LuaEnv();
|
const env = new LuaEnv();
|
||||||
env.set("test", new LuaNativeJSFunction((n) => n));
|
env.set("test", new LuaNativeJSFunction((n) => n));
|
||||||
env.set("asyncTest", new LuaNativeJSFunction((n) => Promise.resolve(n)));
|
env.set("asyncTest", new LuaNativeJSFunction((n) => Promise.resolve(n)));
|
||||||
|
|
||||||
assertEquals(undefined, await evalBlock(`a = 3`, env));
|
assertEquals(undefined, await evalBlock(`a = 3`, env));
|
||||||
assertEquals(env.get("a"), 3);
|
assertEquals(env.get("a"), 3);
|
||||||
assertEquals(undefined, await evalBlock(`b = test(3)`, env));
|
assertEquals(undefined, await evalBlock(`b = test(3)`, env));
|
||||||
assertEquals(env.get("b"), 3);
|
assertEquals(env.get("b"), 3);
|
||||||
|
|
||||||
await evalBlock(`c = asyncTest(3)`, env);
|
await evalBlock(`c = asyncTest(3)`, env);
|
||||||
assertEquals(env.get("c"), 3);
|
assertEquals(env.get("c"), 3);
|
||||||
|
|
||||||
// Multiple assignments
|
// Multiple assignments
|
||||||
const env2 = new LuaEnv();
|
const env2 = new LuaEnv();
|
||||||
assertEquals(undefined, await evalBlock(`a, b = 1, 2`, env2));
|
assertEquals(undefined, await evalBlock(`a, b = 1, 2`, env2));
|
||||||
assertEquals(env2.get("a"), 1);
|
assertEquals(env2.get("a"), 1);
|
||||||
assertEquals(env2.get("b"), 2);
|
assertEquals(env2.get("b"), 2);
|
||||||
|
|
||||||
// Other lvalues
|
// Other lvalues
|
||||||
const env3 = new LuaEnv();
|
const env3 = new LuaEnv();
|
||||||
await evalBlock(`tbl = {1, 2, 3}`, env3);
|
await evalBlock(`tbl = {1, 2, 3}`, env3);
|
||||||
await evalBlock(`tbl[1] = 3`, env3);
|
await evalBlock(`tbl[1] = 3`, env3);
|
||||||
assertEquals(env3.get("tbl").toArray(), [3, 2, 3]);
|
assertEquals(env3.get("tbl").toArray(), [3, 2, 3]);
|
||||||
await evalBlock("tbl.name = 'Zef'", env3);
|
await evalBlock("tbl.name = 'Zef'", env3);
|
||||||
assertEquals(env3.get("tbl").get("name"), "Zef");
|
assertEquals(env3.get("tbl").get("name"), "Zef");
|
||||||
await evalBlock(`tbl[2] = {age=10}`, env3);
|
await evalBlock(`tbl[2] = {age=10}`, env3);
|
||||||
await evalBlock(`tbl[2].age = 20`, env3);
|
await evalBlock(`tbl[2].age = 20`, env3);
|
||||||
assertEquals(env3.get("tbl").get(2).get("age"), 20);
|
assertEquals(env3.get("tbl").get(2).get("age"), 20);
|
||||||
|
|
||||||
// Blocks and scopes
|
// Blocks and scopes
|
||||||
const env4 = new LuaEnv();
|
const env4 = new LuaEnv();
|
||||||
env4.set("print", new LuaNativeJSFunction(console.log));
|
env4.set("print", new LuaNativeJSFunction(console.log));
|
||||||
await evalBlock(
|
await evalBlock(
|
||||||
`
|
`
|
||||||
a = 1
|
a = 1
|
||||||
do
|
do
|
||||||
-- sets global a to 3
|
-- sets global a to 3
|
||||||
a = 3
|
a = 3
|
||||||
print("The number is: " .. a)
|
print("The number is: " .. a)
|
||||||
end`,
|
end`,
|
||||||
env4,
|
env4,
|
||||||
);
|
);
|
||||||
assertEquals(env4.get("a"), 3);
|
assertEquals(env4.get("a"), 3);
|
||||||
|
|
||||||
const env5 = new LuaEnv();
|
const env5 = new LuaEnv();
|
||||||
env5.set("print", new LuaNativeJSFunction(console.log));
|
env5.set("print", new LuaNativeJSFunction(console.log));
|
||||||
|
|
||||||
await evalBlock(
|
await evalBlock(
|
||||||
`
|
`
|
||||||
a = 1
|
a = 1
|
||||||
if a > 0 then
|
if a > 0 then
|
||||||
a = 3
|
a = 3
|
||||||
else
|
else
|
||||||
a = 0
|
a = 0
|
||||||
end`,
|
end`,
|
||||||
env5,
|
env5,
|
||||||
);
|
);
|
||||||
assertEquals(env5.get("a"), 3);
|
assertEquals(env5.get("a"), 3);
|
||||||
|
|
||||||
await evalBlock(
|
await evalBlock(
|
||||||
`
|
`
|
||||||
if a < 0 then
|
if a < 0 then
|
||||||
a = -1
|
a = -1
|
||||||
elseif a > 0 then
|
elseif a > 0 then
|
||||||
|
@ -139,25 +139,25 @@ Deno.test("Statement evaluation", async () => {
|
||||||
else
|
else
|
||||||
a = 0
|
a = 0
|
||||||
end`,
|
end`,
|
||||||
env5,
|
env5,
|
||||||
);
|
);
|
||||||
assertEquals(env5.get("a"), 1);
|
assertEquals(env5.get("a"), 1);
|
||||||
|
|
||||||
await evalBlock(
|
await evalBlock(
|
||||||
`
|
`
|
||||||
var = 1
|
var = 1
|
||||||
do
|
do
|
||||||
local var
|
local var
|
||||||
var = 2
|
var = 2
|
||||||
end`,
|
end`,
|
||||||
env5,
|
env5,
|
||||||
);
|
);
|
||||||
assertEquals(env5.get("var"), 1);
|
assertEquals(env5.get("var"), 1);
|
||||||
|
|
||||||
// While loop
|
// While loop
|
||||||
const env6 = new LuaEnv();
|
const env6 = new LuaEnv();
|
||||||
await evalBlock(
|
await evalBlock(
|
||||||
`
|
`
|
||||||
c = 0
|
c = 0
|
||||||
while true do
|
while true do
|
||||||
c = c + 1
|
c = c + 1
|
||||||
|
@ -166,14 +166,14 @@ Deno.test("Statement evaluation", async () => {
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
`,
|
`,
|
||||||
env6,
|
env6,
|
||||||
);
|
);
|
||||||
assertEquals(env6.get("c"), 3);
|
assertEquals(env6.get("c"), 3);
|
||||||
|
|
||||||
// Repeat loop
|
// Repeat loop
|
||||||
const env7 = new LuaEnv();
|
const env7 = new LuaEnv();
|
||||||
await evalBlock(
|
await evalBlock(
|
||||||
`
|
`
|
||||||
c = 0
|
c = 0
|
||||||
repeat
|
repeat
|
||||||
c = c + 1
|
c = c + 1
|
||||||
|
@ -182,20 +182,20 @@ Deno.test("Statement evaluation", async () => {
|
||||||
end
|
end
|
||||||
until false
|
until false
|
||||||
`,
|
`,
|
||||||
env7,
|
env7,
|
||||||
);
|
);
|
||||||
assertEquals(env7.get("c"), 3);
|
assertEquals(env7.get("c"), 3);
|
||||||
|
|
||||||
// Function definition and calling
|
// Function definition and calling
|
||||||
const env8 = new LuaEnv();
|
const env8 = new LuaEnv();
|
||||||
env8.set("print", new LuaNativeJSFunction(console.log));
|
env8.set("print", new LuaNativeJSFunction(console.log));
|
||||||
await evalBlock(
|
await evalBlock(
|
||||||
`
|
`
|
||||||
function test(a)
|
function test(a)
|
||||||
return a + 1
|
return a + 1
|
||||||
end
|
end
|
||||||
print("3 + 1 = " .. test(3))
|
print("3 + 1 = " .. test(3))
|
||||||
`,
|
`,
|
||||||
env8,
|
env8,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,86 +1,85 @@
|
||||||
import { parse } from "$common/space_lua/parse.ts";
|
import { parse } from "$common/space_lua/parse.ts";
|
||||||
|
|
||||||
|
|
||||||
Deno.test("Test Lua parser", () => {
|
Deno.test("Test Lua parser", () => {
|
||||||
// Basic block test
|
// Basic block test
|
||||||
parse(`
|
parse(`
|
||||||
print("Hello, World!")
|
print("Hello, World!")
|
||||||
print(10)
|
print(10)
|
||||||
`);
|
`);
|
||||||
parse("");
|
parse("");
|
||||||
// Expression tests
|
// Expression tests
|
||||||
parse(
|
parse(
|
||||||
`e(1, 1.2, -3.8, +4, #lst, true, false, nil, "string", "Hello there \x00", ...)`,
|
`e(1, 1.2, -3.8, +4, #lst, true, false, nil, "string", "Hello there \x00", ...)`,
|
||||||
);
|
);
|
||||||
|
|
||||||
parse(`e(10 << 10, 10 >> 10, 10 & 10, 10 | 10, 10 ~ 10)`);
|
parse(`e(10 << 10, 10 >> 10, 10 & 10, 10 | 10, 10 ~ 10)`);
|
||||||
|
|
||||||
parse(`e(true and false or true)`);
|
parse(`e(true and false or true)`);
|
||||||
parse(`e(a < 3 and b > 4 or b == 5 or c <= 6 and d >= 7 or a /= 8)`);
|
parse(`e(a < 3 and b > 4 or b == 5 or c <= 6 and d >= 7 or a /= 8)`);
|
||||||
parse(`e(a.b.c)`);
|
parse(`e(a.b.c)`);
|
||||||
parse(`e((1+2))`);
|
parse(`e((1+2))`);
|
||||||
|
|
||||||
// Table expressions
|
// Table expressions
|
||||||
parse(`e({})`);
|
parse(`e({})`);
|
||||||
parse(`e({1, 2, 3, })`);
|
parse(`e({1, 2, 3, })`);
|
||||||
parse(`e({1 ; 2 ; 3})`);
|
parse(`e({1 ; 2 ; 3})`);
|
||||||
parse(`e({a = 1, b = 2, c = 3})`);
|
parse(`e({a = 1, b = 2, c = 3})`);
|
||||||
parse(`e({[3] = 1, [10 * 10] = "sup"})`);
|
parse(`e({[3] = 1, [10 * 10] = "sup"})`);
|
||||||
|
|
||||||
// Function calls
|
// Function calls
|
||||||
parse(`e(func(), func(1, 2, 3), a.b(), a.b.c:hello(), (a.b)(7))`);
|
parse(`e(func(), func(1, 2, 3), a.b(), a.b.c:hello(), (a.b)(7))`);
|
||||||
|
|
||||||
// Function expression
|
// Function expression
|
||||||
parse(`e(function(a, b) test() end)`);
|
parse(`e(function(a, b) test() end)`);
|
||||||
parse(`e(function(a, b, ...) end)`);
|
parse(`e(function(a, b, ...) end)`);
|
||||||
|
|
||||||
// Statements
|
// Statements
|
||||||
parse(`do end`);
|
parse(`do end`);
|
||||||
parse(`do print() end`);
|
parse(`do print() end`);
|
||||||
parse(`::hello::
|
parse(`::hello::
|
||||||
goto hello`);
|
goto hello`);
|
||||||
parse(`while true do print() end`);
|
parse(`while true do print() end`);
|
||||||
parse(`repeat print() until false`);
|
parse(`repeat print() until false`);
|
||||||
parse(
|
parse(
|
||||||
`if 1 == 2 then print() elseif 1 < 2 then print2() else print3() end`,
|
`if 1 == 2 then print() elseif 1 < 2 then print2() else print3() end`,
|
||||||
);
|
);
|
||||||
parse(`if true then print() end`);
|
parse(`if true then print() end`);
|
||||||
parse(`if true then print() else print2() end`);
|
parse(`if true then print() else print2() end`);
|
||||||
parse(`if true then print() elseif false then print2() end`);
|
parse(`if true then print() elseif false then print2() end`);
|
||||||
|
|
||||||
// For loops
|
// For loops
|
||||||
parse(`for i = 1, 10, 1 do print(i) end`);
|
parse(`for i = 1, 10, 1 do print(i) end`);
|
||||||
parse(`for i = 1, 10 do print(i) end`);
|
parse(`for i = 1, 10 do print(i) end`);
|
||||||
parse(`for el in each({1, 2, 3}) do print(i) end`);
|
parse(`for el in each({1, 2, 3}) do print(i) end`);
|
||||||
parse(`for i, l in 1, pairs() do print(i) end`);
|
parse(`for i, l in 1, pairs() do print(i) end`);
|
||||||
|
|
||||||
// Function statements
|
// Function statements
|
||||||
parse(`function a() end`);
|
parse(`function a() end`);
|
||||||
parse(`function a:b() end`);
|
parse(`function a:b() end`);
|
||||||
parse(`function a.b.c:d() end`);
|
parse(`function a.b.c:d() end`);
|
||||||
parse(`function a.b.c() end`);
|
parse(`function a.b.c() end`);
|
||||||
parse(`function hello(a, b) end`);
|
parse(`function hello(a, b) end`);
|
||||||
parse(`function hello(a, b, ...) end`);
|
parse(`function hello(a, b, ...) end`);
|
||||||
parse(`local function hello() end`);
|
parse(`local function hello() end`);
|
||||||
|
|
||||||
// Assignments, local variables etc.
|
// Assignments, local variables etc.
|
||||||
parse(`a = 1`);
|
parse(`a = 1`);
|
||||||
parse(`a, b = 1, 2`);
|
parse(`a, b = 1, 2`);
|
||||||
parse(`a.b.c = 1`);
|
parse(`a.b.c = 1`);
|
||||||
parse(`a["name"] = 1`);
|
parse(`a["name"] = 1`);
|
||||||
parse(`local a, b<const>`);
|
parse(`local a, b<const>`);
|
||||||
parse(`local a = 1`);
|
parse(`local a = 1`);
|
||||||
parse(`local a<const> = 4`);
|
parse(`local a<const> = 4`);
|
||||||
parse(`local a, b = 1, 2`);
|
parse(`local a, b = 1, 2`);
|
||||||
|
|
||||||
// Function calls
|
// Function calls
|
||||||
parse(`a(1, 2, 3)`);
|
parse(`a(1, 2, 3)`);
|
||||||
parse(`print "Sup"`);
|
parse(`print "Sup"`);
|
||||||
parse(`e(1 + print "8")`);
|
parse(`e(1 + print "8")`);
|
||||||
|
|
||||||
// Return statements
|
// Return statements
|
||||||
parse(`return`);
|
parse(`return`);
|
||||||
parse(`return 1`);
|
parse(`return 1`);
|
||||||
parse(`return 1, 2, 3`);
|
parse(`return 1, 2, 3`);
|
||||||
// return;
|
// return;
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,519 +1,515 @@
|
||||||
import { lezerToParseTree } from "$common/markdown_parser/parse_tree.ts";
|
import { lezerToParseTree } from "$common/markdown_parser/parse_tree.ts";
|
||||||
import {
|
import {
|
||||||
cleanTree,
|
cleanTree,
|
||||||
type ParseTree,
|
type ParseTree,
|
||||||
} from "@silverbulletmd/silverbullet/lib/tree";
|
} from "@silverbulletmd/silverbullet/lib/tree";
|
||||||
import { parser } from "./parse-lua.js";
|
import { parser } from "./parse-lua.js";
|
||||||
import { styleTags } from "@lezer/highlight";
|
import { styleTags } from "@lezer/highlight";
|
||||||
import type {
|
import type {
|
||||||
LuaAttName,
|
LuaAttName,
|
||||||
LuaBlock,
|
LuaBlock,
|
||||||
LuaExpression,
|
LuaExpression,
|
||||||
LuaFunctionBody,
|
LuaFunctionBody,
|
||||||
LuaFunctionCallExpression,
|
LuaFunctionCallExpression,
|
||||||
LuaFunctionName,
|
LuaFunctionName,
|
||||||
LuaLValue,
|
LuaLValue,
|
||||||
LuaPrefixExpression,
|
LuaPrefixExpression,
|
||||||
LuaStatement,
|
LuaStatement,
|
||||||
LuaTableField,
|
LuaTableField,
|
||||||
} from "./ast.ts";
|
} from "./ast.ts";
|
||||||
|
|
||||||
const luaStyleTags = styleTags({
|
const luaStyleTags = styleTags({
|
||||||
// Identifier: t.variableName,
|
// Identifier: t.variableName,
|
||||||
// TagIdentifier: t.variableName,
|
// TagIdentifier: t.variableName,
|
||||||
// GlobalIdentifier: t.variableName,
|
// GlobalIdentifier: t.variableName,
|
||||||
// String: t.string,
|
// String: t.string,
|
||||||
// Number: t.number,
|
// Number: t.number,
|
||||||
// PageRef: ct.WikiLinkTag,
|
// PageRef: ct.WikiLinkTag,
|
||||||
// BinExpression: t.operator,
|
// BinExpression: t.operator,
|
||||||
// TernaryExpression: t.operator,
|
// TernaryExpression: t.operator,
|
||||||
// Regex: t.regexp,
|
// Regex: t.regexp,
|
||||||
// "where limit select render Order OrderKW and or null as InKW NotKW BooleanKW each all":
|
// "where limit select render Order OrderKW and or null as InKW NotKW BooleanKW each all":
|
||||||
// t.keyword,
|
// t.keyword,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const highlightingQueryParser = parser.configure({
|
export const highlightingQueryParser = parser.configure({
|
||||||
props: [
|
props: [
|
||||||
luaStyleTags,
|
luaStyleTags,
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
function parseChunk(t: ParseTree): LuaBlock {
|
function parseChunk(t: ParseTree): LuaBlock {
|
||||||
if (t.type !== "Chunk") {
|
if (t.type !== "Chunk") {
|
||||||
throw new Error(`Expected Chunk, got ${t.type}`);
|
throw new Error(`Expected Chunk, got ${t.type}`);
|
||||||
}
|
}
|
||||||
return parseBlock(t.children![0]);
|
return parseBlock(t.children![0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseBlock(t: ParseTree): LuaBlock {
|
function parseBlock(t: ParseTree): LuaBlock {
|
||||||
if (t.type !== "Block") {
|
if (t.type !== "Block") {
|
||||||
throw new Error(`Expected Block, got ${t.type}`);
|
throw new Error(`Expected Block, got ${t.type}`);
|
||||||
}
|
}
|
||||||
const statements = t.children!.map(parseStatement);
|
const statements = t.children!.map(parseStatement);
|
||||||
return { type: "Block", statements, from: t.from, to: t.to };
|
return { type: "Block", statements, from: t.from, to: t.to };
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStatement(t: ParseTree): LuaStatement {
|
function parseStatement(t: ParseTree): LuaStatement {
|
||||||
switch (t.type) {
|
switch (t.type) {
|
||||||
case "Block":
|
case "Block":
|
||||||
return parseChunk(t.children![0]);
|
return parseChunk(t.children![0]);
|
||||||
case "Semicolon":
|
case "Semicolon":
|
||||||
return { type: "Semicolon", from: t.from, to: t.to };
|
return { type: "Semicolon", from: t.from, to: t.to };
|
||||||
case "Label":
|
case "Label":
|
||||||
return {
|
return {
|
||||||
type: "Label",
|
type: "Label",
|
||||||
name: t.children![1].children![0].text!,
|
name: t.children![1].children![0].text!,
|
||||||
from: t.from,
|
from: t.from,
|
||||||
to: t.to,
|
to: t.to,
|
||||||
};
|
};
|
||||||
case "Break":
|
case "Break":
|
||||||
return { type: "Break", from: t.from, to: t.to };
|
return { type: "Break", from: t.from, to: t.to };
|
||||||
case "Goto":
|
case "Goto":
|
||||||
return {
|
return {
|
||||||
type: "Goto",
|
type: "Goto",
|
||||||
name: t.children![1].children![0].text!,
|
name: t.children![1].children![0].text!,
|
||||||
from: t.from,
|
from: t.from,
|
||||||
to: t.to,
|
to: t.to,
|
||||||
};
|
};
|
||||||
case "Scope":
|
case "Scope":
|
||||||
return parseBlock(t.children![1]);
|
return parseBlock(t.children![1]);
|
||||||
case ";":
|
case ";":
|
||||||
return { type: "Semicolon", from: t.from, to: t.to };
|
return { type: "Semicolon", from: t.from, to: t.to };
|
||||||
case "WhileStatement":
|
case "WhileStatement":
|
||||||
return {
|
return {
|
||||||
type: "While",
|
type: "While",
|
||||||
condition: parseExpression(t.children![1]),
|
condition: parseExpression(t.children![1]),
|
||||||
block: parseBlock(t.children![3]),
|
block: parseBlock(t.children![3]),
|
||||||
};
|
};
|
||||||
case "RepeatStatement":
|
case "RepeatStatement":
|
||||||
return {
|
return {
|
||||||
type: "Repeat",
|
type: "Repeat",
|
||||||
block: parseBlock(t.children![1]),
|
block: parseBlock(t.children![1]),
|
||||||
condition: parseExpression(t.children![3]),
|
condition: parseExpression(t.children![3]),
|
||||||
};
|
};
|
||||||
case "IfStatement": {
|
case "IfStatement": {
|
||||||
const conditions: {
|
const conditions: {
|
||||||
condition: LuaExpression;
|
condition: LuaExpression;
|
||||||
block: LuaBlock;
|
block: LuaBlock;
|
||||||
from?: number;
|
from?: number;
|
||||||
to?: number;
|
to?: number;
|
||||||
}[] = [];
|
}[] = [];
|
||||||
let elseBlock: LuaBlock | undefined = undefined;
|
let elseBlock: LuaBlock | undefined = undefined;
|
||||||
for (let i = 0; i < t.children!.length; i += 4) {
|
for (let i = 0; i < t.children!.length; i += 4) {
|
||||||
console.log("Looking at", t.children![i]);
|
console.log("Looking at", t.children![i]);
|
||||||
const child = t.children![i];
|
const child = t.children![i];
|
||||||
if (
|
if (
|
||||||
child.children![0].text === "if" ||
|
child.children![0].text === "if" ||
|
||||||
child.children![0].text === "elseif"
|
child.children![0].text === "elseif"
|
||||||
) {
|
) {
|
||||||
conditions.push({
|
conditions.push({
|
||||||
condition: parseExpression(t.children![i + 1]),
|
condition: parseExpression(t.children![i + 1]),
|
||||||
block: parseBlock(t.children![i + 3]),
|
block: parseBlock(t.children![i + 3]),
|
||||||
from: child.from,
|
from: child.from,
|
||||||
to: child.to,
|
to: child.to,
|
||||||
});
|
});
|
||||||
} else if (child.children![0].text === "else") {
|
} else if (child.children![0].text === "else") {
|
||||||
elseBlock = parseBlock(t.children![i + 1]);
|
elseBlock = parseBlock(t.children![i + 1]);
|
||||||
} else if (child.children![0].text === "end") {
|
} else if (child.children![0].text === "end") {
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unknown if clause type: ${child.children![0].text}`,
|
`Unknown if clause type: ${child.children![0].text}`,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
type: "If",
|
|
||||||
conditions,
|
|
||||||
elseBlock,
|
|
||||||
from: t.from,
|
|
||||||
to: t.to,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
case "ForStatement":
|
}
|
||||||
if (t.children![1].type === "ForNumeric") {
|
return {
|
||||||
const forNumeric = t.children![1];
|
type: "If",
|
||||||
return {
|
conditions,
|
||||||
type: "For",
|
elseBlock,
|
||||||
name: forNumeric.children![0].children![0].text!,
|
from: t.from,
|
||||||
start: parseExpression(forNumeric.children![2]),
|
to: t.to,
|
||||||
end: parseExpression(forNumeric.children![4]),
|
};
|
||||||
step: forNumeric.children![5]
|
|
||||||
? parseExpression(forNumeric.children![6])
|
|
||||||
: undefined,
|
|
||||||
block: parseBlock(t.children![3]),
|
|
||||||
from: t.from,
|
|
||||||
to: t.to,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
const forGeneric = t.children![1];
|
|
||||||
return {
|
|
||||||
type: "ForIn",
|
|
||||||
names: parseNameList(forGeneric.children![0]),
|
|
||||||
expressions: parseExpList(forGeneric.children![2]),
|
|
||||||
block: parseBlock(t.children![3]),
|
|
||||||
from: t.from,
|
|
||||||
to: t.to,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case "Function":
|
|
||||||
return {
|
|
||||||
type: "Function",
|
|
||||||
name: parseFunctionName(t.children![1]),
|
|
||||||
body: parseFunctionBody(t.children![2]),
|
|
||||||
from: t.from,
|
|
||||||
to: t.to,
|
|
||||||
};
|
|
||||||
case "LocalFunction":
|
|
||||||
return {
|
|
||||||
type: "LocalFunction",
|
|
||||||
name: t.children![2].children![0].text!,
|
|
||||||
body: parseFunctionBody(t.children![3]),
|
|
||||||
};
|
|
||||||
case "FunctionCall":
|
|
||||||
return {
|
|
||||||
type: "FunctionCallStatement",
|
|
||||||
call: parseExpression(
|
|
||||||
{
|
|
||||||
type: "FunctionCall",
|
|
||||||
children: t.children!,
|
|
||||||
from: t.from,
|
|
||||||
to: t.to,
|
|
||||||
},
|
|
||||||
) as LuaFunctionCallExpression,
|
|
||||||
};
|
|
||||||
case "Assign":
|
|
||||||
return {
|
|
||||||
type: "Assignment",
|
|
||||||
variables: t.children![0].children!.filter((t) =>
|
|
||||||
t.type !== ","
|
|
||||||
).map(
|
|
||||||
parseLValue,
|
|
||||||
),
|
|
||||||
expressions: parseExpList(t.children![2]),
|
|
||||||
from: t.from,
|
|
||||||
to: t.to,
|
|
||||||
};
|
|
||||||
case "Local":
|
|
||||||
return {
|
|
||||||
type: "Local",
|
|
||||||
names: parseAttNames(t.children![1]),
|
|
||||||
expressions: t.children![3] ? parseExpList(t.children![3]) : [],
|
|
||||||
from: t.from,
|
|
||||||
to: t.to,
|
|
||||||
};
|
|
||||||
case "ReturnStatement": {
|
|
||||||
const expressions = t.children![1]
|
|
||||||
? parseExpList(t.children![1])
|
|
||||||
: [];
|
|
||||||
return { type: "Return", expressions, from: t.from, to: t.to };
|
|
||||||
}
|
|
||||||
case "break":
|
|
||||||
return { type: "Break", from: t.from, to: t.to };
|
|
||||||
default:
|
|
||||||
console.error(t);
|
|
||||||
throw new Error(`Unknown statement type: ${t.children![0].text}`);
|
|
||||||
}
|
}
|
||||||
|
case "ForStatement":
|
||||||
|
if (t.children![1].type === "ForNumeric") {
|
||||||
|
const forNumeric = t.children![1];
|
||||||
|
return {
|
||||||
|
type: "For",
|
||||||
|
name: forNumeric.children![0].children![0].text!,
|
||||||
|
start: parseExpression(forNumeric.children![2]),
|
||||||
|
end: parseExpression(forNumeric.children![4]),
|
||||||
|
step: forNumeric.children![5]
|
||||||
|
? parseExpression(forNumeric.children![6])
|
||||||
|
: undefined,
|
||||||
|
block: parseBlock(t.children![3]),
|
||||||
|
from: t.from,
|
||||||
|
to: t.to,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const forGeneric = t.children![1];
|
||||||
|
return {
|
||||||
|
type: "ForIn",
|
||||||
|
names: parseNameList(forGeneric.children![0]),
|
||||||
|
expressions: parseExpList(forGeneric.children![2]),
|
||||||
|
block: parseBlock(t.children![3]),
|
||||||
|
from: t.from,
|
||||||
|
to: t.to,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "Function":
|
||||||
|
return {
|
||||||
|
type: "Function",
|
||||||
|
name: parseFunctionName(t.children![1]),
|
||||||
|
body: parseFunctionBody(t.children![2]),
|
||||||
|
from: t.from,
|
||||||
|
to: t.to,
|
||||||
|
};
|
||||||
|
case "LocalFunction":
|
||||||
|
return {
|
||||||
|
type: "LocalFunction",
|
||||||
|
name: t.children![2].children![0].text!,
|
||||||
|
body: parseFunctionBody(t.children![3]),
|
||||||
|
};
|
||||||
|
case "FunctionCall":
|
||||||
|
return {
|
||||||
|
type: "FunctionCallStatement",
|
||||||
|
call: parseExpression(
|
||||||
|
{
|
||||||
|
type: "FunctionCall",
|
||||||
|
children: t.children!,
|
||||||
|
from: t.from,
|
||||||
|
to: t.to,
|
||||||
|
},
|
||||||
|
) as LuaFunctionCallExpression,
|
||||||
|
};
|
||||||
|
case "Assign":
|
||||||
|
return {
|
||||||
|
type: "Assignment",
|
||||||
|
variables: t.children![0].children!.filter((t) => t.type !== ",").map(
|
||||||
|
parseLValue,
|
||||||
|
),
|
||||||
|
expressions: parseExpList(t.children![2]),
|
||||||
|
from: t.from,
|
||||||
|
to: t.to,
|
||||||
|
};
|
||||||
|
case "Local":
|
||||||
|
return {
|
||||||
|
type: "Local",
|
||||||
|
names: parseAttNames(t.children![1]),
|
||||||
|
expressions: t.children![3] ? parseExpList(t.children![3]) : [],
|
||||||
|
from: t.from,
|
||||||
|
to: t.to,
|
||||||
|
};
|
||||||
|
case "ReturnStatement": {
|
||||||
|
const expressions = t.children![1] ? parseExpList(t.children![1]) : [];
|
||||||
|
return { type: "Return", expressions, from: t.from, to: t.to };
|
||||||
|
}
|
||||||
|
case "break":
|
||||||
|
return { type: "Break", from: t.from, to: t.to };
|
||||||
|
default:
|
||||||
|
console.error(t);
|
||||||
|
throw new Error(`Unknown statement type: ${t.children![0].text}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseAttNames(t: ParseTree): LuaAttName[] {
|
function parseAttNames(t: ParseTree): LuaAttName[] {
|
||||||
if (t.type !== "AttNameList") {
|
if (t.type !== "AttNameList") {
|
||||||
throw new Error(`Expected AttNameList, got ${t.type}`);
|
throw new Error(`Expected AttNameList, got ${t.type}`);
|
||||||
}
|
}
|
||||||
return t.children!.filter((t) => t.type !== ",").map(parseAttName);
|
return t.children!.filter((t) => t.type !== ",").map(parseAttName);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseAttName(t: ParseTree): LuaAttName {
|
function parseAttName(t: ParseTree): LuaAttName {
|
||||||
if (t.type !== "AttName") {
|
if (t.type !== "AttName") {
|
||||||
throw new Error(`Expected AttName, got ${t.type}`);
|
throw new Error(`Expected AttName, got ${t.type}`);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
type: "AttName",
|
type: "AttName",
|
||||||
name: t.children![0].children![0].text!,
|
name: t.children![0].children![0].text!,
|
||||||
attribute: t.children![1].children![1]
|
attribute: t.children![1].children![1]
|
||||||
? t.children![1].children![1].children![0].text!
|
? t.children![1].children![1].children![0].text!
|
||||||
: undefined,
|
: undefined,
|
||||||
from: t.from,
|
from: t.from,
|
||||||
to: t.to,
|
to: t.to,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseLValue(t: ParseTree): LuaLValue {
|
function parseLValue(t: ParseTree): LuaLValue {
|
||||||
switch (t.type) {
|
switch (t.type) {
|
||||||
case "Name":
|
case "Name":
|
||||||
return {
|
return {
|
||||||
type: "Variable",
|
type: "Variable",
|
||||||
name: t.children![0].text!,
|
name: t.children![0].text!,
|
||||||
from: t.from,
|
from: t.from,
|
||||||
to: t.to,
|
to: t.to,
|
||||||
};
|
};
|
||||||
case "Property":
|
case "Property":
|
||||||
return {
|
return {
|
||||||
type: "PropertyAccess",
|
type: "PropertyAccess",
|
||||||
object: parsePrefixExpression(t.children![0]),
|
object: parsePrefixExpression(t.children![0]),
|
||||||
property: t.children![2].children![0].text!,
|
property: t.children![2].children![0].text!,
|
||||||
from: t.from,
|
from: t.from,
|
||||||
to: t.to,
|
to: t.to,
|
||||||
};
|
};
|
||||||
case "MemberExpression":
|
case "MemberExpression":
|
||||||
return {
|
return {
|
||||||
type: "TableAccess",
|
type: "TableAccess",
|
||||||
object: parsePrefixExpression(t.children![0]),
|
object: parsePrefixExpression(t.children![0]),
|
||||||
key: parseExpression(t.children![2]),
|
key: parseExpression(t.children![2]),
|
||||||
from: t.from,
|
from: t.from,
|
||||||
to: t.to,
|
to: t.to,
|
||||||
};
|
};
|
||||||
default:
|
default:
|
||||||
console.error(t);
|
console.error(t);
|
||||||
throw new Error(`Unknown lvalue type: ${t.type}`);
|
throw new Error(`Unknown lvalue type: ${t.type}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseFunctionName(t: ParseTree): LuaFunctionName {
|
function parseFunctionName(t: ParseTree): LuaFunctionName {
|
||||||
if (t.type !== "FuncName") {
|
if (t.type !== "FuncName") {
|
||||||
throw new Error(`Expected FunctionName, got ${t.type}`);
|
throw new Error(`Expected FunctionName, got ${t.type}`);
|
||||||
|
}
|
||||||
|
const propNames: string[] = [];
|
||||||
|
let colonName: string | undefined = undefined;
|
||||||
|
for (let i = 0; i < t.children!.length; i += 2) {
|
||||||
|
const prop = t.children![i];
|
||||||
|
propNames.push(prop.children![0].text!);
|
||||||
|
if (t.children![i + 1] && t.children![i + 1].type === ":") {
|
||||||
|
colonName = t.children![i + 2].children![0].text!;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
const propNames: string[] = [];
|
}
|
||||||
let colonName: string | undefined = undefined;
|
return {
|
||||||
for (let i = 0; i < t.children!.length; i += 2) {
|
type: "FunctionName",
|
||||||
const prop = t.children![i];
|
propNames,
|
||||||
propNames.push(prop.children![0].text!);
|
colonName,
|
||||||
if (t.children![i + 1] && t.children![i + 1].type === ":") {
|
from: t.from,
|
||||||
colonName = t.children![i + 2].children![0].text!;
|
to: t.to,
|
||||||
break;
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
type: "FunctionName",
|
|
||||||
propNames,
|
|
||||||
colonName,
|
|
||||||
from: t.from,
|
|
||||||
to: t.to,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseNameList(t: ParseTree): string[] {
|
function parseNameList(t: ParseTree): string[] {
|
||||||
if (t.type !== "NameList") {
|
if (t.type !== "NameList") {
|
||||||
throw new Error(`Expected NameList, got ${t.type}`);
|
throw new Error(`Expected NameList, got ${t.type}`);
|
||||||
}
|
}
|
||||||
return t.children!.filter((t) => t.type === "Name").map((t) =>
|
return t.children!.filter((t) => t.type === "Name").map((t) =>
|
||||||
t.children![0].text!
|
t.children![0].text!
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseExpList(t: ParseTree): LuaExpression[] {
|
function parseExpList(t: ParseTree): LuaExpression[] {
|
||||||
if (t.type !== "ExpList") {
|
if (t.type !== "ExpList") {
|
||||||
throw new Error(`Expected ExpList, got ${t.type}`);
|
throw new Error(`Expected ExpList, got ${t.type}`);
|
||||||
}
|
}
|
||||||
return t.children!.filter((t) => t.type !== ",").map(parseExpression);
|
return t.children!.filter((t) => t.type !== ",").map(parseExpression);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseExpression(t: ParseTree): LuaExpression {
|
function parseExpression(t: ParseTree): LuaExpression {
|
||||||
switch (t.type) {
|
switch (t.type) {
|
||||||
case "LiteralString": {
|
case "LiteralString": {
|
||||||
let cleanString = t.children![0].text!;
|
let cleanString = t.children![0].text!;
|
||||||
// Remove quotes etc
|
// Remove quotes etc
|
||||||
cleanString = cleanString.slice(1, -1);
|
cleanString = cleanString.slice(1, -1);
|
||||||
return {
|
return {
|
||||||
type: "String",
|
type: "String",
|
||||||
value: cleanString,
|
value: cleanString,
|
||||||
from: t.from,
|
from: t.from,
|
||||||
to: t.to,
|
to: t.to,
|
||||||
};
|
};
|
||||||
}
|
|
||||||
case "Number":
|
|
||||||
return {
|
|
||||||
type: "Number",
|
|
||||||
value: parseFloat(t.children![0].text!),
|
|
||||||
from: t.from,
|
|
||||||
to: t.to,
|
|
||||||
};
|
|
||||||
case "BinaryExpression":
|
|
||||||
return {
|
|
||||||
type: "Binary",
|
|
||||||
operator: t.children![1].children![0].text!,
|
|
||||||
left: parseExpression(t.children![0]),
|
|
||||||
right: parseExpression(t.children![2]),
|
|
||||||
from: t.from,
|
|
||||||
to: t.to,
|
|
||||||
};
|
|
||||||
case "UnaryExpression":
|
|
||||||
return {
|
|
||||||
type: "Unary",
|
|
||||||
operator: t.children![0].children![0].text!,
|
|
||||||
argument: parseExpression(t.children![1]),
|
|
||||||
from: t.from,
|
|
||||||
to: t.to,
|
|
||||||
};
|
|
||||||
case "Property":
|
|
||||||
return {
|
|
||||||
type: "PropertyAccess",
|
|
||||||
object: parsePrefixExpression(t.children![0]),
|
|
||||||
property: t.children![2].children![0].text!,
|
|
||||||
from: t.from,
|
|
||||||
to: t.to,
|
|
||||||
};
|
|
||||||
|
|
||||||
case "Parens":
|
|
||||||
return parseExpression(t.children![1]);
|
|
||||||
case "FunctionCall": {
|
|
||||||
if (t.children![1].type === ":") {
|
|
||||||
return {
|
|
||||||
type: "FunctionCall",
|
|
||||||
prefix: parsePrefixExpression(t.children![0]),
|
|
||||||
name: t.children![2].children![0].text!,
|
|
||||||
args: parseFunctionArgs(t.children!.slice(3)),
|
|
||||||
from: t.from,
|
|
||||||
to: t.to,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
type: "FunctionCall",
|
|
||||||
prefix: parsePrefixExpression(t.children![0]),
|
|
||||||
args: parseFunctionArgs(t.children!.slice(1)),
|
|
||||||
from: t.from,
|
|
||||||
to: t.to,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case "FunctionDef": {
|
|
||||||
const body = parseFunctionBody(t.children![1]);
|
|
||||||
return {
|
|
||||||
type: "FunctionDefinition",
|
|
||||||
body,
|
|
||||||
from: t.from,
|
|
||||||
to: t.to,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case "Name":
|
|
||||||
return {
|
|
||||||
type: "Variable",
|
|
||||||
name: t.children![0].text!,
|
|
||||||
from: t.from,
|
|
||||||
to: t.to,
|
|
||||||
};
|
|
||||||
case "Ellipsis":
|
|
||||||
return { type: "Variable", name: "...", from: t.from, to: t.to };
|
|
||||||
case "true":
|
|
||||||
return { type: "Boolean", value: true, from: t.from, to: t.to };
|
|
||||||
case "false":
|
|
||||||
return { type: "Boolean", value: false, from: t.from, to: t.to };
|
|
||||||
case "TableConstructor":
|
|
||||||
return {
|
|
||||||
type: "TableConstructor",
|
|
||||||
fields: t.children!.slice(1, -1).filter((t) =>
|
|
||||||
["FieldExp", "FieldProp", "FieldDynamic"].includes(t.type!)
|
|
||||||
).map(parseTableField),
|
|
||||||
from: t.from,
|
|
||||||
to: t.to,
|
|
||||||
};
|
|
||||||
case "nil":
|
|
||||||
return { type: "Nil", from: t.from, to: t.to };
|
|
||||||
default:
|
|
||||||
console.error(t);
|
|
||||||
throw new Error(`Unknown expression type: ${t.type}`);
|
|
||||||
}
|
}
|
||||||
|
case "Number":
|
||||||
|
return {
|
||||||
|
type: "Number",
|
||||||
|
value: parseFloat(t.children![0].text!),
|
||||||
|
from: t.from,
|
||||||
|
to: t.to,
|
||||||
|
};
|
||||||
|
case "BinaryExpression":
|
||||||
|
return {
|
||||||
|
type: "Binary",
|
||||||
|
operator: t.children![1].children![0].text!,
|
||||||
|
left: parseExpression(t.children![0]),
|
||||||
|
right: parseExpression(t.children![2]),
|
||||||
|
from: t.from,
|
||||||
|
to: t.to,
|
||||||
|
};
|
||||||
|
case "UnaryExpression":
|
||||||
|
return {
|
||||||
|
type: "Unary",
|
||||||
|
operator: t.children![0].children![0].text!,
|
||||||
|
argument: parseExpression(t.children![1]),
|
||||||
|
from: t.from,
|
||||||
|
to: t.to,
|
||||||
|
};
|
||||||
|
case "Property":
|
||||||
|
return {
|
||||||
|
type: "PropertyAccess",
|
||||||
|
object: parsePrefixExpression(t.children![0]),
|
||||||
|
property: t.children![2].children![0].text!,
|
||||||
|
from: t.from,
|
||||||
|
to: t.to,
|
||||||
|
};
|
||||||
|
|
||||||
|
case "Parens":
|
||||||
|
return parseExpression(t.children![1]);
|
||||||
|
case "FunctionCall": {
|
||||||
|
if (t.children![1].type === ":") {
|
||||||
|
return {
|
||||||
|
type: "FunctionCall",
|
||||||
|
prefix: parsePrefixExpression(t.children![0]),
|
||||||
|
name: t.children![2].children![0].text!,
|
||||||
|
args: parseFunctionArgs(t.children!.slice(3)),
|
||||||
|
from: t.from,
|
||||||
|
to: t.to,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: "FunctionCall",
|
||||||
|
prefix: parsePrefixExpression(t.children![0]),
|
||||||
|
args: parseFunctionArgs(t.children!.slice(1)),
|
||||||
|
from: t.from,
|
||||||
|
to: t.to,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "FunctionDef": {
|
||||||
|
const body = parseFunctionBody(t.children![1]);
|
||||||
|
return {
|
||||||
|
type: "FunctionDefinition",
|
||||||
|
body,
|
||||||
|
from: t.from,
|
||||||
|
to: t.to,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "Name":
|
||||||
|
return {
|
||||||
|
type: "Variable",
|
||||||
|
name: t.children![0].text!,
|
||||||
|
from: t.from,
|
||||||
|
to: t.to,
|
||||||
|
};
|
||||||
|
case "Ellipsis":
|
||||||
|
return { type: "Variable", name: "...", from: t.from, to: t.to };
|
||||||
|
case "true":
|
||||||
|
return { type: "Boolean", value: true, from: t.from, to: t.to };
|
||||||
|
case "false":
|
||||||
|
return { type: "Boolean", value: false, from: t.from, to: t.to };
|
||||||
|
case "TableConstructor":
|
||||||
|
return {
|
||||||
|
type: "TableConstructor",
|
||||||
|
fields: t.children!.slice(1, -1).filter((t) =>
|
||||||
|
["FieldExp", "FieldProp", "FieldDynamic"].includes(t.type!)
|
||||||
|
).map(parseTableField),
|
||||||
|
from: t.from,
|
||||||
|
to: t.to,
|
||||||
|
};
|
||||||
|
case "nil":
|
||||||
|
return { type: "Nil", from: t.from, to: t.to };
|
||||||
|
default:
|
||||||
|
console.error(t);
|
||||||
|
throw new Error(`Unknown expression type: ${t.type}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseFunctionArgs(ts: ParseTree[]): LuaExpression[] {
|
function parseFunctionArgs(ts: ParseTree[]): LuaExpression[] {
|
||||||
console.log("Parsing function args", JSON.stringify(ts, null, 2));
|
console.log("Parsing function args", JSON.stringify(ts, null, 2));
|
||||||
return ts.filter((t) => ![",", "(", ")"].includes(t.type!)).map(
|
return ts.filter((t) => ![",", "(", ")"].includes(t.type!)).map(
|
||||||
parseExpression,
|
parseExpression,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseFunctionBody(t: ParseTree): LuaFunctionBody {
|
function parseFunctionBody(t: ParseTree): LuaFunctionBody {
|
||||||
if (t.type !== "FuncBody") {
|
if (t.type !== "FuncBody") {
|
||||||
throw new Error(`Expected FunctionBody, got ${t.type}`);
|
throw new Error(`Expected FunctionBody, got ${t.type}`);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
type: "FunctionBody",
|
type: "FunctionBody",
|
||||||
parameters: t.children![1].children!.filter((t) =>
|
parameters: t.children![1].children!.filter((t) =>
|
||||||
["Name", "Ellipsis"].includes(t.type!)
|
["Name", "Ellipsis"].includes(t.type!)
|
||||||
)
|
)
|
||||||
.map((t) => t.children![0].text!),
|
.map((t) => t.children![0].text!),
|
||||||
block: parseBlock(t.children![3]),
|
block: parseBlock(t.children![3]),
|
||||||
from: t.from,
|
from: t.from,
|
||||||
to: t.to,
|
to: t.to,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function parsePrefixExpression(t: ParseTree): LuaPrefixExpression {
|
function parsePrefixExpression(t: ParseTree): LuaPrefixExpression {
|
||||||
switch (t.type) {
|
switch (t.type) {
|
||||||
case "Name":
|
case "Name":
|
||||||
return {
|
return {
|
||||||
type: "Variable",
|
type: "Variable",
|
||||||
name: t.children![0].text!,
|
name: t.children![0].text!,
|
||||||
from: t.from,
|
from: t.from,
|
||||||
to: t.to,
|
to: t.to,
|
||||||
};
|
};
|
||||||
case "Property":
|
case "Property":
|
||||||
return {
|
return {
|
||||||
type: "PropertyAccess",
|
type: "PropertyAccess",
|
||||||
object: parsePrefixExpression(t.children![0]),
|
object: parsePrefixExpression(t.children![0]),
|
||||||
property: t.children![2].children![0].text!,
|
property: t.children![2].children![0].text!,
|
||||||
from: t.from,
|
from: t.from,
|
||||||
to: t.to,
|
to: t.to,
|
||||||
};
|
};
|
||||||
case "MemberExpression":
|
case "MemberExpression":
|
||||||
return {
|
return {
|
||||||
type: "TableAccess",
|
type: "TableAccess",
|
||||||
object: parsePrefixExpression(t.children![0]),
|
object: parsePrefixExpression(t.children![0]),
|
||||||
key: parseExpression(t.children![2]),
|
key: parseExpression(t.children![2]),
|
||||||
from: t.from,
|
from: t.from,
|
||||||
to: t.to,
|
to: t.to,
|
||||||
};
|
};
|
||||||
case "Parens":
|
case "Parens":
|
||||||
return {
|
return {
|
||||||
type: "Parenthesized",
|
type: "Parenthesized",
|
||||||
expression: parseExpression(t.children![1]),
|
expression: parseExpression(t.children![1]),
|
||||||
from: t.from,
|
from: t.from,
|
||||||
to: t.to,
|
to: t.to,
|
||||||
};
|
};
|
||||||
default:
|
default:
|
||||||
console.error(t);
|
console.error(t);
|
||||||
throw new Error(`Unknown prefix expression type: ${t.type}`);
|
throw new Error(`Unknown prefix expression type: ${t.type}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseTableField(t: ParseTree): LuaTableField {
|
function parseTableField(t: ParseTree): LuaTableField {
|
||||||
switch (t.type) {
|
switch (t.type) {
|
||||||
case "FieldExp":
|
case "FieldExp":
|
||||||
return {
|
return {
|
||||||
type: "ExpressionField",
|
type: "ExpressionField",
|
||||||
value: parseExpression(t.children![0]),
|
value: parseExpression(t.children![0]),
|
||||||
from: t.from,
|
from: t.from,
|
||||||
to: t.to,
|
to: t.to,
|
||||||
};
|
};
|
||||||
case "FieldProp":
|
case "FieldProp":
|
||||||
return {
|
return {
|
||||||
type: "PropField",
|
type: "PropField",
|
||||||
key: t.children![0].children![0].text!,
|
key: t.children![0].children![0].text!,
|
||||||
value: parseExpression(t.children![2]),
|
value: parseExpression(t.children![2]),
|
||||||
from: t.from,
|
from: t.from,
|
||||||
to: t.to,
|
to: t.to,
|
||||||
};
|
};
|
||||||
case "FieldDynamic":
|
case "FieldDynamic":
|
||||||
return {
|
return {
|
||||||
type: "DynamicField",
|
type: "DynamicField",
|
||||||
key: parseExpression(t.children![1]),
|
key: parseExpression(t.children![1]),
|
||||||
value: parseExpression(t.children![4]),
|
value: parseExpression(t.children![4]),
|
||||||
from: t.from,
|
from: t.from,
|
||||||
to: t.to,
|
to: t.to,
|
||||||
};
|
};
|
||||||
default:
|
default:
|
||||||
console.error(t);
|
console.error(t);
|
||||||
throw new Error(`Unknown table field type: ${t.type}`);
|
throw new Error(`Unknown table field type: ${t.type}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parse(s: string): LuaBlock {
|
export function parse(s: string): LuaBlock {
|
||||||
const t = parseToCrudeAST(s);
|
const t = parseToCrudeAST(s);
|
||||||
console.log("Clean tree", JSON.stringify(t, null, 2));
|
console.log("Clean tree", JSON.stringify(t, null, 2));
|
||||||
const result = parseChunk(t);
|
const result = parseChunk(t);
|
||||||
console.log("Parsed AST", JSON.stringify(result, null, 2));
|
console.log("Parsed AST", JSON.stringify(result, null, 2));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseToCrudeAST(t: string): ParseTree {
|
export function parseToCrudeAST(t: string): ParseTree {
|
||||||
return cleanTree(lezerToParseTree(t, parser.parse(t).topNode), true);
|
return cleanTree(lezerToParseTree(t, parser.parse(t).topNode), true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,52 +2,52 @@ import type { LuaFunctionBody } from "./ast.ts";
|
||||||
import { evalStatement } from "$common/space_lua/eval.ts";
|
import { evalStatement } from "$common/space_lua/eval.ts";
|
||||||
|
|
||||||
export class LuaEnv implements ILuaSettable, ILuaGettable {
|
export class LuaEnv implements ILuaSettable, ILuaGettable {
|
||||||
variables = new Map<string, LuaValue>();
|
variables = new Map<string, LuaValue>();
|
||||||
|
|
||||||
constructor(readonly parent?: LuaEnv) {
|
constructor(readonly parent?: LuaEnv) {
|
||||||
}
|
}
|
||||||
|
|
||||||
setLocal(name: string, value: LuaValue) {
|
setLocal(name: string, value: LuaValue) {
|
||||||
this.variables.set(name, value);
|
this.variables.set(name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
set(key: string, value: LuaValue): void {
|
set(key: string, value: LuaValue): void {
|
||||||
if (this.variables.has(key) || !this.parent) {
|
if (this.variables.has(key) || !this.parent) {
|
||||||
this.variables.set(key, value);
|
this.variables.set(key, value);
|
||||||
} else {
|
} else {
|
||||||
this.parent.set(key, value);
|
this.parent.set(key, value);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
get(name: string): LuaValue | undefined {
|
get(name: string): LuaValue | undefined {
|
||||||
if (this.variables.has(name)) {
|
if (this.variables.has(name)) {
|
||||||
return this.variables.get(name);
|
return this.variables.get(name);
|
||||||
}
|
|
||||||
if (this.parent) {
|
|
||||||
return this.parent.get(name);
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
if (this.parent) {
|
||||||
|
return this.parent.get(name);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class LuaMultiRes {
|
export class LuaMultiRes {
|
||||||
constructor(readonly values: any[]) {
|
constructor(readonly values: any[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
unwrap(): any {
|
unwrap(): any {
|
||||||
if (this.values.length !== 1) {
|
if (this.values.length !== 1) {
|
||||||
throw new Error("Cannot unwrap multiple values");
|
throw new Error("Cannot unwrap multiple values");
|
||||||
}
|
|
||||||
return this.values[0];
|
|
||||||
}
|
}
|
||||||
|
return this.values[0];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function singleResult(value: any): any {
|
export function singleResult(value: any): any {
|
||||||
if (value instanceof LuaMultiRes) {
|
if (value instanceof LuaMultiRes) {
|
||||||
return value.unwrap();
|
return value.unwrap();
|
||||||
} else {
|
} else {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// These types are for documentation only
|
// These types are for documentation only
|
||||||
|
@ -55,207 +55,207 @@ export type LuaValue = any;
|
||||||
export type JSValue = any;
|
export type JSValue = any;
|
||||||
|
|
||||||
export interface ILuaFunction {
|
export interface ILuaFunction {
|
||||||
call(...args: LuaValue[]): Promise<LuaValue> | LuaValue;
|
call(...args: LuaValue[]): Promise<LuaValue> | LuaValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ILuaSettable {
|
export interface ILuaSettable {
|
||||||
set(key: LuaValue, value: LuaValue): void;
|
set(key: LuaValue, value: LuaValue): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ILuaGettable {
|
export interface ILuaGettable {
|
||||||
get(key: LuaValue): LuaValue | undefined;
|
get(key: LuaValue): LuaValue | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class LuaFunction implements ILuaFunction {
|
export class LuaFunction implements ILuaFunction {
|
||||||
constructor(private body: LuaFunctionBody, private closure: LuaEnv) {
|
constructor(private body: LuaFunctionBody, private closure: LuaEnv) {
|
||||||
}
|
}
|
||||||
|
|
||||||
call(...args: LuaValue[]): Promise<LuaValue> | LuaValue {
|
call(...args: LuaValue[]): Promise<LuaValue> | LuaValue {
|
||||||
// Create a new environment for this function call
|
// Create a new environment for this function call
|
||||||
const env = new LuaEnv(this.closure);
|
const env = new LuaEnv(this.closure);
|
||||||
// Assign the passed arguments to the parameters
|
// Assign the passed arguments to the parameters
|
||||||
for (let i = 0; i < this.body.parameters.length; i++) {
|
for (let i = 0; i < this.body.parameters.length; i++) {
|
||||||
let arg = args[i];
|
let arg = args[i];
|
||||||
if (arg === undefined) {
|
if (arg === undefined) {
|
||||||
arg = null;
|
arg = null;
|
||||||
}
|
}
|
||||||
env.set(this.body.parameters[i], arg);
|
env.set(this.body.parameters[i], arg);
|
||||||
}
|
|
||||||
return evalStatement(this.body.block, env).catch((e: any) => {
|
|
||||||
if (e instanceof LuaReturn) {
|
|
||||||
if (e.values.length === 0) {
|
|
||||||
return;
|
|
||||||
} else if (e.values.length === 1) {
|
|
||||||
return e.values[0];
|
|
||||||
} else {
|
|
||||||
return new LuaMultiRes(e.values);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
return evalStatement(this.body.block, env).catch((e: any) => {
|
||||||
|
if (e instanceof LuaReturn) {
|
||||||
|
if (e.values.length === 0) {
|
||||||
|
return;
|
||||||
|
} else if (e.values.length === 1) {
|
||||||
|
return e.values[0];
|
||||||
|
} else {
|
||||||
|
return new LuaMultiRes(e.values);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class LuaNativeJSFunction implements ILuaFunction {
|
export class LuaNativeJSFunction implements ILuaFunction {
|
||||||
constructor(readonly fn: (...args: JSValue[]) => JSValue) {
|
constructor(readonly fn: (...args: JSValue[]) => JSValue) {
|
||||||
}
|
}
|
||||||
|
|
||||||
call(...args: LuaValue[]): Promise<LuaValue> | LuaValue {
|
call(...args: LuaValue[]): Promise<LuaValue> | LuaValue {
|
||||||
const result = this.fn(...args.map(luaValueToJS));
|
const result = this.fn(...args.map(luaValueToJS));
|
||||||
if (result instanceof Promise) {
|
if (result instanceof Promise) {
|
||||||
return result.then(jsToLuaValue);
|
return result.then(jsToLuaValue);
|
||||||
} else {
|
} else {
|
||||||
return jsToLuaValue(result);
|
return jsToLuaValue(result);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class LuaTable implements ILuaSettable, ILuaGettable {
|
export class LuaTable implements ILuaSettable, ILuaGettable {
|
||||||
// To optimize the table implementation we use a combination of different data structures
|
// 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
|
// 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>;
|
private stringKeys: Record<string, any>;
|
||||||
// Other keys we can support using a Map as a fallback
|
// Other keys we can support using a Map as a fallback
|
||||||
private otherKeys: Map<any, any> | null;
|
private otherKeys: Map<any, any> | null;
|
||||||
// When tables are used as arrays, we use a native JavaScript array for that
|
// When tables are used as arrays, we use a native JavaScript array for that
|
||||||
private arrayPart: any[];
|
private arrayPart: any[];
|
||||||
|
|
||||||
// TODO: Actually implement metatables
|
// TODO: Actually implement metatables
|
||||||
private metatable: LuaTable | null;
|
private metatable: LuaTable | null;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
// For efficiency and performance reasons we pre-allocate these (modern JS engines are very good at optimizing this)
|
// For efficiency and performance reasons we pre-allocate these (modern JS engines are very good at optimizing this)
|
||||||
this.stringKeys = {};
|
this.stringKeys = {};
|
||||||
this.arrayPart = [];
|
this.arrayPart = [];
|
||||||
this.otherKeys = null; // Only create this when needed
|
this.otherKeys = null; // Only create this when needed
|
||||||
this.metatable = null;
|
this.metatable = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get length(): number {
|
||||||
|
return this.arrayPart.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
get length(): number {
|
get(key: LuaValue): LuaValue | undefined {
|
||||||
return this.arrayPart.length;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
set(key: LuaValue, value: LuaValue) {
|
toArray(): JSValue[] {
|
||||||
if (typeof key === "string") {
|
return this.arrayPart;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get(key: LuaValue): LuaValue | undefined {
|
toObject(): Record<string, JSValue> {
|
||||||
if (typeof key === "string") {
|
const result = { ...this.stringKeys };
|
||||||
return this.stringKeys[key];
|
for (const i in this.arrayPart) {
|
||||||
} else if (Number.isInteger(key) && key >= 1) {
|
result[parseInt(i) + 1] = this.arrayPart[i];
|
||||||
return this.arrayPart[key - 1];
|
|
||||||
} else if (this.otherKeys) {
|
|
||||||
return this.otherKeys.get(key);
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
toArray(): JSValue[] {
|
static fromArray(arr: JSValue[]): LuaTable {
|
||||||
return this.arrayPart;
|
const table = new LuaTable();
|
||||||
|
for (let i = 0; i < arr.length; i++) {
|
||||||
|
table.set(i + 1, arr[i]);
|
||||||
}
|
}
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
|
||||||
toObject(): Record<string, JSValue> {
|
static fromObject(obj: Record<string, JSValue>): LuaTable {
|
||||||
const result = { ...this.stringKeys };
|
const table = new LuaTable();
|
||||||
for (const i in this.arrayPart) {
|
for (const key in obj) {
|
||||||
result[parseInt(i) + 1] = this.arrayPart[i];
|
table.set(key, obj[key]);
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static fromArray(arr: JSValue[]): LuaTable {
|
|
||||||
const table = new LuaTable();
|
|
||||||
for (let i = 0; i < arr.length; i++) {
|
|
||||||
table.set(i + 1, arr[i]);
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
return table;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LuaLValueContainer = { env: ILuaSettable; key: LuaValue };
|
export type LuaLValueContainer = { env: ILuaSettable; key: LuaValue };
|
||||||
|
|
||||||
export function luaSet(obj: any, key: any, value: any) {
|
export function luaSet(obj: any, key: any, value: any) {
|
||||||
if (obj instanceof LuaTable) {
|
if (obj instanceof LuaTable) {
|
||||||
obj.set(key, value);
|
obj.set(key, value);
|
||||||
} else {
|
} else {
|
||||||
obj[key] = value;
|
obj[key] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function luaGet(obj: any, key: any): any {
|
export function luaGet(obj: any, key: any): any {
|
||||||
if (obj instanceof LuaTable) {
|
if (obj instanceof LuaTable) {
|
||||||
return obj.get(key);
|
return obj.get(key);
|
||||||
} else {
|
} else {
|
||||||
return obj[key];
|
return obj[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function luaLen(obj: any): number {
|
export function luaLen(obj: any): number {
|
||||||
if (obj instanceof LuaTable) {
|
if (obj instanceof LuaTable) {
|
||||||
return obj.toArray().length;
|
return obj.toArray().length;
|
||||||
} else if (Array.isArray(obj)) {
|
} else if (Array.isArray(obj)) {
|
||||||
return obj.length;
|
return obj.length;
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class LuaBreak extends Error {
|
export class LuaBreak extends Error {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class LuaReturn extends Error {
|
export class LuaReturn extends Error {
|
||||||
constructor(readonly values: LuaValue[]) {
|
constructor(readonly values: LuaValue[]) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function luaTruthy(value: any): boolean {
|
export function luaTruthy(value: any): boolean {
|
||||||
if (value === undefined || value === null || value === false) {
|
if (value === undefined || value === null || value === false) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (value instanceof LuaTable) {
|
if (value instanceof LuaTable) {
|
||||||
return value.length > 0;
|
return value.length > 0;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function jsToLuaValue(value: any): any {
|
export function jsToLuaValue(value: any): any {
|
||||||
if (value instanceof LuaTable) {
|
if (value instanceof LuaTable) {
|
||||||
return value;
|
return value;
|
||||||
} else if (Array.isArray(value)) {
|
} else if (Array.isArray(value)) {
|
||||||
return LuaTable.fromArray(value.map(jsToLuaValue));
|
return LuaTable.fromArray(value.map(jsToLuaValue));
|
||||||
} else if (typeof value === "object") {
|
} else if (typeof value === "object") {
|
||||||
return LuaTable.fromObject(value);
|
return LuaTable.fromObject(value);
|
||||||
} else {
|
} else {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function luaValueToJS(value: any): any {
|
export function luaValueToJS(value: any): any {
|
||||||
if (value instanceof LuaTable) {
|
if (value instanceof LuaTable) {
|
||||||
// This is a heuristic: if this table is used as an array, we return an array
|
// This is a heuristic: if this table is used as an array, we return an array
|
||||||
if (value.length > 0) {
|
if (value.length > 0) {
|
||||||
return value.toArray();
|
return value.toArray();
|
||||||
} else {
|
|
||||||
return value.toObject();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
return value;
|
return value.toObject();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,19 +3,19 @@ import { assertEquals } from "@std/assert/equals";
|
||||||
import { assert } from "@std/assert";
|
import { assert } from "@std/assert";
|
||||||
|
|
||||||
Deno.test("Test promise helpers", async () => {
|
Deno.test("Test promise helpers", async () => {
|
||||||
const r = evalPromiseValues([1, 2, 3]);
|
const r = evalPromiseValues([1, 2, 3]);
|
||||||
// should return the same array not as a promise
|
// should return the same array not as a promise
|
||||||
assertEquals(r, [1, 2, 3]);
|
assertEquals(r, [1, 2, 3]);
|
||||||
const asyncR = evalPromiseValues([
|
const asyncR = evalPromiseValues([
|
||||||
new Promise((resolve) => {
|
new Promise((resolve) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
resolve(1);
|
resolve(1);
|
||||||
}, 5);
|
}, 5);
|
||||||
}),
|
}),
|
||||||
Promise.resolve(2),
|
Promise.resolve(2),
|
||||||
3,
|
3,
|
||||||
]);
|
]);
|
||||||
// should return a promise
|
// should return a promise
|
||||||
assert(asyncR instanceof Promise);
|
assert(asyncR instanceof Promise);
|
||||||
assertEquals(await asyncR, [1, 2, 3]);
|
assertEquals(await asyncR, [1, 2, 3]);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
export function evalPromiseValues(vals: any[]): Promise<any[]> | any[] {
|
export function evalPromiseValues(vals: any[]): Promise<any[]> | any[] {
|
||||||
const promises = [];
|
const promises = [];
|
||||||
const promiseResults = new Array(vals.length);
|
const promiseResults = new Array(vals.length);
|
||||||
for (let i = 0; i < vals.length; i++) {
|
for (let i = 0; i < vals.length; i++) {
|
||||||
if (vals[i] instanceof Promise) {
|
if (vals[i] instanceof Promise) {
|
||||||
promises.push(vals[i].then((v: any) => promiseResults[i] = v));
|
promises.push(vals[i].then((v: any) => promiseResults[i] = v));
|
||||||
} else {
|
|
||||||
promiseResults[i] = vals[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (promises.length === 0) {
|
|
||||||
return promiseResults;
|
|
||||||
} else {
|
} else {
|
||||||
return Promise.all(promises).then(() => promiseResults);
|
promiseResults[i] = vals[i];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (promises.length === 0) {
|
||||||
|
return promiseResults;
|
||||||
|
} else {
|
||||||
|
return Promise.all(promises).then(() => promiseResults);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { localDateString } from "$lib/dates.ts";
|
import { localDateString } from "$lib/dates.ts";
|
||||||
|
|
||||||
Deno.test("Dates", () => {
|
Deno.test("Dates", () => {
|
||||||
console.log("Local date string", localDateString(new Date()));
|
console.log("Local date string", localDateString(new Date()));
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,63 +2,63 @@ import { assertEquals } from "@std/assert/equals";
|
||||||
import { determineType, jsonTypeToString } from "./attributes.ts";
|
import { determineType, jsonTypeToString } from "./attributes.ts";
|
||||||
|
|
||||||
Deno.test("JSON Determine type", () => {
|
Deno.test("JSON Determine type", () => {
|
||||||
// Determine type tests
|
// Determine type tests
|
||||||
assertEquals(determineType(null), { type: "null" });
|
assertEquals(determineType(null), { type: "null" });
|
||||||
assertEquals(determineType(undefined), { type: "null" });
|
assertEquals(determineType(undefined), { type: "null" });
|
||||||
assertEquals(determineType("hello"), { type: "string" });
|
assertEquals(determineType("hello"), { type: "string" });
|
||||||
assertEquals(determineType(10), { type: "number" });
|
assertEquals(determineType(10), { type: "number" });
|
||||||
assertEquals(determineType(true), { type: "boolean" });
|
assertEquals(determineType(true), { type: "boolean" });
|
||||||
assertEquals(determineType({}), { type: "object", properties: {} });
|
assertEquals(determineType({}), { type: "object", properties: {} });
|
||||||
assertEquals(determineType([]), { type: "array" });
|
assertEquals(determineType([]), { type: "array" });
|
||||||
assertEquals(determineType([1]), {
|
assertEquals(determineType([1]), {
|
||||||
type: "array",
|
type: "array",
|
||||||
items: { type: "number" },
|
items: { type: "number" },
|
||||||
});
|
});
|
||||||
assertEquals(
|
assertEquals(
|
||||||
determineType({ name: "Pete", age: 10, siblings: ["Sarah"] }),
|
determineType({ name: "Pete", age: 10, siblings: ["Sarah"] }),
|
||||||
{
|
{
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
name: { type: "string" },
|
name: { type: "string" },
|
||||||
age: { type: "number" },
|
age: { type: "number" },
|
||||||
siblings: { type: "array", items: { type: "string" } },
|
siblings: { type: "array", items: { type: "string" } },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
Deno.test("Serialize JSON Type to string", () => {
|
Deno.test("Serialize JSON Type to string", () => {
|
||||||
assertEquals(jsonTypeToString({ type: "string" }), "string");
|
assertEquals(jsonTypeToString({ type: "string" }), "string");
|
||||||
assertEquals(jsonTypeToString({ type: "null" }), "null");
|
assertEquals(jsonTypeToString({ type: "null" }), "null");
|
||||||
assertEquals(jsonTypeToString({ type: "number" }), "number");
|
assertEquals(jsonTypeToString({ type: "number" }), "number");
|
||||||
assertEquals(jsonTypeToString({ type: "boolean" }), "boolean");
|
assertEquals(jsonTypeToString({ type: "boolean" }), "boolean");
|
||||||
assertEquals(jsonTypeToString({ type: "array" }), "any[]");
|
assertEquals(jsonTypeToString({ type: "array" }), "any[]");
|
||||||
assertEquals(
|
assertEquals(
|
||||||
jsonTypeToString({ type: "array", items: { type: "number" } }),
|
jsonTypeToString({ type: "array", items: { type: "number" } }),
|
||||||
"number[]",
|
"number[]",
|
||||||
);
|
);
|
||||||
assertEquals(
|
assertEquals(
|
||||||
jsonTypeToString({ type: "object", properties: {} }),
|
jsonTypeToString({ type: "object", properties: {} }),
|
||||||
"{}",
|
"{}",
|
||||||
);
|
);
|
||||||
assertEquals(
|
assertEquals(
|
||||||
jsonTypeToString({
|
jsonTypeToString({
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
name: { type: "string" },
|
name: { type: "string" },
|
||||||
age: { type: "number" },
|
age: { type: "number" },
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
"{name: string; age: number;}",
|
"{name: string; age: number;}",
|
||||||
);
|
);
|
||||||
assertEquals(
|
assertEquals(
|
||||||
jsonTypeToString({
|
jsonTypeToString({
|
||||||
anyOf: [
|
anyOf: [
|
||||||
{ type: "string" },
|
{ type: "string" },
|
||||||
{ type: "number" },
|
{ type: "number" },
|
||||||
{ type: "boolean" },
|
{ type: "boolean" },
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
"string | number | boolean",
|
"string | number | boolean",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,24 +10,24 @@ const itemsMd = `
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Deno.test("Test item extraction", async () => {
|
Deno.test("Test item extraction", async () => {
|
||||||
const t = parseMarkdown(itemsMd);
|
const t = parseMarkdown(itemsMd);
|
||||||
const items = await extractItems("test", t);
|
const items = await extractItems("test", t);
|
||||||
|
|
||||||
assertEquals(items[0].name, "Item 1");
|
assertEquals(items[0].name, "Item 1");
|
||||||
assertEquals(items[0].age, 100);
|
assertEquals(items[0].age, 100);
|
||||||
assertEquals(items[0].page, "test");
|
assertEquals(items[0].page, "test");
|
||||||
assertEquals(items[0].parent, undefined);
|
assertEquals(items[0].parent, undefined);
|
||||||
assertEquals(items[0].text, "Item 1 #tag1 #tag2 [age: 100]");
|
assertEquals(items[0].text, "Item 1 #tag1 #tag2 [age: 100]");
|
||||||
assertEquals(new Set(items[0].tags), new Set(["tag1", "tag2"]));
|
assertEquals(new Set(items[0].tags), new Set(["tag1", "tag2"]));
|
||||||
assertEquals(new Set(items[0].itags), new Set(["item", "tag1", "tag2"]));
|
assertEquals(new Set(items[0].itags), new Set(["item", "tag1", "tag2"]));
|
||||||
|
|
||||||
assertEquals(items[1].name, "Item 1.1");
|
assertEquals(items[1].name, "Item 1.1");
|
||||||
assertEquals(new Set(items[1].tags), new Set(["tag3", "tag1"]));
|
assertEquals(new Set(items[1].tags), new Set(["tag3", "tag1"]));
|
||||||
assertEquals(
|
assertEquals(
|
||||||
new Set(items[1].itags),
|
new Set(items[1].itags),
|
||||||
new Set(["tag3", "tag2", "tag1", "item"]),
|
new Set(["tag3", "tag2", "tag1", "item"]),
|
||||||
);
|
);
|
||||||
assertEquals(items[1].parent, items[0].ref);
|
assertEquals(items[1].parent, items[0].ref);
|
||||||
|
|
||||||
assertEquals(items[2].parent, items[1].ref);
|
assertEquals(items[2].parent, items[1].ref);
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,34 +11,34 @@ const itemsMd = `
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Deno.test("Test task extraction", async () => {
|
Deno.test("Test task extraction", async () => {
|
||||||
const t = parseMarkdown(itemsMd);
|
const t = parseMarkdown(itemsMd);
|
||||||
const tasks = await extractTasks("test", t);
|
const tasks = await extractTasks("test", t);
|
||||||
|
|
||||||
assertEquals(tasks.length, 3);
|
assertEquals(tasks.length, 3);
|
||||||
assertEquals(tasks[0].name, "Task 1");
|
assertEquals(tasks[0].name, "Task 1");
|
||||||
assertEquals(tasks[0].age, 200);
|
assertEquals(tasks[0].age, 200);
|
||||||
assertEquals(tasks[0].page, "test");
|
assertEquals(tasks[0].page, "test");
|
||||||
assertEquals(tasks[0].text, "Task 1 [age: 200]");
|
assertEquals(tasks[0].text, "Task 1 [age: 200]");
|
||||||
assertEquals(new Set(tasks[0].itags), new Set(["tag1", "tag2", "task"]));
|
assertEquals(new Set(tasks[0].itags), new Set(["tag1", "tag2", "task"]));
|
||||||
assertEquals(tasks[0].parent, "test@1");
|
assertEquals(tasks[0].parent, "test@1");
|
||||||
assertEquals(tasks[1].name, "Task 2");
|
assertEquals(tasks[1].name, "Task 2");
|
||||||
// Don't inherit attributes
|
// Don't inherit attributes
|
||||||
assertEquals(tasks[1].age, undefined);
|
assertEquals(tasks[1].age, undefined);
|
||||||
// But inherit tags through itags, not tags
|
// But inherit tags through itags, not tags
|
||||||
assertEquals(
|
assertEquals(
|
||||||
new Set(tasks[1].tags),
|
new Set(tasks[1].tags),
|
||||||
new Set(["tag1", "tag3"]),
|
new Set(["tag1", "tag3"]),
|
||||||
);
|
);
|
||||||
assertEquals(
|
assertEquals(
|
||||||
new Set(tasks[1].itags),
|
new Set(tasks[1].itags),
|
||||||
new Set(["tag1", "tag3", "task", "tag2"]),
|
new Set(["tag1", "tag3", "task", "tag2"]),
|
||||||
);
|
);
|
||||||
assertEquals(tasks[1].parent, "test@1");
|
assertEquals(tasks[1].parent, "test@1");
|
||||||
// Deeply
|
// Deeply
|
||||||
assertEquals(tasks[2].name, "Task 2.1");
|
assertEquals(tasks[2].name, "Task 2.1");
|
||||||
assertEquals(tasks[2].tags, []);
|
assertEquals(tasks[2].tags, []);
|
||||||
assertEquals(
|
assertEquals(
|
||||||
new Set(tasks[2].itags),
|
new Set(tasks[2].itags),
|
||||||
new Set(["tag1", "tag3", "task", "tag2"]),
|
new Set(["tag1", "tag3", "task", "tag2"]),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue