643 lines
17 KiB
TypeScript
643 lines
17 KiB
TypeScript
import { lezerToParseTree } from "$common/markdown_parser/parse_tree.ts";
|
|
import {
|
|
type AST as CrudeAST,
|
|
parseTreeToAST,
|
|
} from "@silverbulletmd/silverbullet/lib/tree";
|
|
import { parser } from "./parse-lua.js";
|
|
import { styleTags } from "@lezer/highlight";
|
|
|
|
const luaStyleTags = styleTags({
|
|
// Identifier: t.variableName,
|
|
// TagIdentifier: t.variableName,
|
|
// GlobalIdentifier: t.variableName,
|
|
// String: t.string,
|
|
// Number: t.number,
|
|
// PageRef: ct.WikiLinkTag,
|
|
// BinExpression: t.operator,
|
|
// TernaryExpression: t.operator,
|
|
// Regex: t.regexp,
|
|
// "where limit select render Order OrderKW and or null as InKW NotKW BooleanKW each all":
|
|
// t.keyword,
|
|
});
|
|
|
|
export const highlightingQueryParser = parser.configure({
|
|
props: [
|
|
luaStyleTags,
|
|
],
|
|
});
|
|
|
|
export type LuaBlock = {
|
|
type: "Block";
|
|
statements: LuaStatement[];
|
|
};
|
|
|
|
// STATEMENTS
|
|
export type LuaReturnStatement = {
|
|
type: "Return";
|
|
expressions: LuaExpression[];
|
|
};
|
|
|
|
export type LuaStatement =
|
|
| LuaSemicolonStatement
|
|
| LuaLabelStatement
|
|
| LuaBreakStatement
|
|
| LuaGotoStatement
|
|
| LuaReturnStatement
|
|
| LuaBlock
|
|
| LuaWhileStatement
|
|
| LuaRepeatStatement
|
|
| LuaIfStatement
|
|
| LuaForStatement
|
|
| LuaForInStatement
|
|
| LuaFunctionStatement
|
|
| LuaLocalFunctionStatement
|
|
| LuaAssignmentStatement
|
|
| LuaLocalStatement
|
|
| LuaFunctionCallStatement;
|
|
|
|
export type LuaSemicolonStatement = {
|
|
type: "Semicolon";
|
|
};
|
|
|
|
export type LuaLabelStatement = {
|
|
type: "Label";
|
|
name: string;
|
|
};
|
|
|
|
export type LuaBreakStatement = {
|
|
type: "Break";
|
|
};
|
|
|
|
export type LuaGotoStatement = {
|
|
type: "Goto";
|
|
name: string;
|
|
};
|
|
|
|
export type LuaWhileStatement = {
|
|
type: "While";
|
|
condition: LuaExpression;
|
|
block: LuaBlock;
|
|
};
|
|
|
|
export type LuaRepeatStatement = {
|
|
type: "Repeat";
|
|
block: LuaBlock;
|
|
condition: LuaExpression;
|
|
};
|
|
|
|
export type LuaIfStatement = {
|
|
type: "If";
|
|
conditions: { condition: LuaExpression; block: LuaBlock }[];
|
|
elseBlock?: LuaBlock;
|
|
};
|
|
|
|
export type LuaForStatement = {
|
|
type: "For";
|
|
name: string;
|
|
start: LuaExpression;
|
|
end: LuaExpression;
|
|
step?: LuaExpression;
|
|
block: LuaBlock;
|
|
};
|
|
|
|
export type LuaForInStatement = {
|
|
type: "ForIn";
|
|
names: string[];
|
|
expressions: LuaExpression[];
|
|
block: LuaBlock;
|
|
};
|
|
|
|
export type LuaFunctionStatement = {
|
|
type: "Function";
|
|
name: LuaFunctionName;
|
|
body: LuaFunctionBody;
|
|
};
|
|
|
|
export type LuaLocalFunctionStatement = {
|
|
type: "LocalFunction";
|
|
name: string;
|
|
body: LuaFunctionBody;
|
|
};
|
|
|
|
export type LuaFunctionName = {
|
|
type: "FunctionName";
|
|
propNames?: string[];
|
|
colonName?: string;
|
|
};
|
|
|
|
export type LuaFunctionBody = {
|
|
type: "FunctionBody";
|
|
parameters: string[];
|
|
block: LuaBlock;
|
|
};
|
|
|
|
export type LuaAssignmentStatement = {
|
|
type: "Assignment";
|
|
variables: LuaLValue[];
|
|
expressions: LuaExpression[];
|
|
};
|
|
|
|
export type LuaLValue =
|
|
| LuaVariable
|
|
| LuaPropertyAccessExpression
|
|
| LuaTableAccessExpression;
|
|
|
|
export type LuaLocalStatement = {
|
|
type: "Local";
|
|
names: LuaAttName[];
|
|
expressions?: LuaExpression[];
|
|
};
|
|
|
|
export type LuaAttName = {
|
|
type: "AttName";
|
|
name: string;
|
|
attribute?: string;
|
|
};
|
|
|
|
export type LuaFunctionCallStatement = {
|
|
type: "FunctionCallStatement";
|
|
call: LuaFunctionCallExpression;
|
|
};
|
|
|
|
// EXPRESSIONS
|
|
export type LuaExpression =
|
|
| LuaNilLiteral
|
|
| LuaBooleanLiteral
|
|
| LuaNumberLiteral
|
|
| LuaStringLiteral
|
|
| LuaPrefixExpression
|
|
| LuaBinaryExpression
|
|
| LuaUnaryExpression
|
|
| LuaTableConstructor
|
|
| LuaFunctionDefinition;
|
|
|
|
export type LuaNilLiteral = {
|
|
type: "Nil";
|
|
};
|
|
|
|
export type LuaBooleanLiteral = {
|
|
type: "Boolean";
|
|
value: boolean;
|
|
};
|
|
|
|
export type LuaNumberLiteral = {
|
|
type: "Number";
|
|
value: number;
|
|
};
|
|
|
|
export type LuaStringLiteral = {
|
|
type: "String";
|
|
value: string;
|
|
};
|
|
|
|
export type LuaPrefixExpression =
|
|
| LuaVariableExpression
|
|
| LuaParenthesizedExpression
|
|
| LuaFunctionCallExpression;
|
|
|
|
export type LuaParenthesizedExpression = {
|
|
type: "Parenthesized";
|
|
expression: LuaExpression;
|
|
};
|
|
|
|
export type LuaVariableExpression =
|
|
| LuaVariable
|
|
| LuaPropertyAccessExpression
|
|
| LuaTableAccessExpression;
|
|
|
|
export type LuaVariable = {
|
|
type: "Variable";
|
|
name: string;
|
|
};
|
|
|
|
export type LuaPropertyAccessExpression = {
|
|
type: "PropertyAccess";
|
|
object: LuaPrefixExpression;
|
|
property: string;
|
|
};
|
|
|
|
export type LuaTableAccessExpression = {
|
|
type: "TableAccess";
|
|
object: LuaPrefixExpression;
|
|
key: LuaExpression;
|
|
};
|
|
|
|
export type LuaFunctionCallExpression = {
|
|
type: "FunctionCall";
|
|
prefix: LuaPrefixExpression;
|
|
name?: string;
|
|
args: LuaExpression[];
|
|
};
|
|
|
|
export type LuaBinaryExpression = {
|
|
type: "Binary";
|
|
operator: string;
|
|
left: LuaExpression;
|
|
right: LuaExpression;
|
|
};
|
|
|
|
export type LuaUnaryExpression = {
|
|
type: "Unary";
|
|
operator: string;
|
|
argument: LuaExpression;
|
|
};
|
|
|
|
export type LuaTableConstructor = {
|
|
type: "TableConstructor";
|
|
fields: LuaTableField[];
|
|
};
|
|
|
|
export type LuaTableField =
|
|
| LuaDynamicField
|
|
| LuaPropField
|
|
| LuaExpressionField;
|
|
|
|
export type LuaDynamicField = {
|
|
type: "DynamicField";
|
|
key: LuaExpression;
|
|
value: LuaExpression;
|
|
};
|
|
|
|
export type LuaPropField = {
|
|
type: "PropField";
|
|
key: string;
|
|
value: LuaExpression;
|
|
};
|
|
|
|
export type LuaExpressionField = {
|
|
type: "ExpressionField";
|
|
value: LuaExpression;
|
|
};
|
|
|
|
export type LuaFunctionDefinition = {
|
|
type: "FunctionDefinition";
|
|
body: LuaFunctionBody;
|
|
};
|
|
|
|
function parseChunk(n: CrudeAST): LuaBlock {
|
|
const t = n as [string, ...CrudeAST[]];
|
|
if (t[0] !== "Chunk") {
|
|
throw new Error(`Expected Chunk, got ${t[0]}`);
|
|
}
|
|
return parseBlock(t[1]);
|
|
}
|
|
|
|
function parseBlock(n: CrudeAST): LuaBlock {
|
|
const t = n as [string, ...CrudeAST[]];
|
|
if (t[0] !== "Block") {
|
|
throw new Error(`Expected Block, got ${t[0]}`);
|
|
}
|
|
const statements = t.slice(1).map(parseStatement);
|
|
return { type: "Block", statements };
|
|
}
|
|
|
|
function parseStatement(n: CrudeAST): LuaStatement {
|
|
const t = n as [string, ...CrudeAST[]];
|
|
switch (t[0]) {
|
|
case "Block":
|
|
return parseChunk(t[1]);
|
|
case "Semicolon":
|
|
return { type: "Semicolon" };
|
|
case "Label":
|
|
return { type: "Label", name: t[2][1] as string };
|
|
case "Break":
|
|
return { type: "Break" };
|
|
case "Goto":
|
|
return { type: "Goto", name: t[2][1] as string };
|
|
case "Scope":
|
|
return parseBlock(t[2]);
|
|
case ";":
|
|
return { type: "Semicolon" };
|
|
case "WhileStatement":
|
|
return {
|
|
type: "While",
|
|
condition: parseExpression(t[2]),
|
|
block: parseBlock(t[4]),
|
|
};
|
|
case "RepeatStatement":
|
|
return {
|
|
type: "Repeat",
|
|
block: parseBlock(t[2]),
|
|
condition: parseExpression(t[4]),
|
|
};
|
|
case "IfStatement": {
|
|
const conditions: { condition: LuaExpression; block: LuaBlock }[] =
|
|
[];
|
|
let elseBlock: LuaBlock | undefined = undefined;
|
|
for (let i = 1; i < t.length; i += 4) {
|
|
console.log("Looking at", t[i]);
|
|
if (t[i][0] === "if" || t[i][0] === "elseif") {
|
|
conditions.push({
|
|
condition: parseExpression(t[i + 1]),
|
|
block: parseBlock(t[i + 3]),
|
|
});
|
|
} else if (t[i][0] === "else") {
|
|
elseBlock = parseBlock(t[i + 1]);
|
|
} else if (t[i][0] === "end") {
|
|
break;
|
|
} else {
|
|
throw new Error(`Unknown if clause type: ${t[i][0]}`);
|
|
}
|
|
}
|
|
return {
|
|
type: "If",
|
|
conditions,
|
|
elseBlock,
|
|
};
|
|
}
|
|
case "ForStatement":
|
|
if (t[2][0] === "ForNumeric") {
|
|
const forNumeric = t[2] as [string, ...CrudeAST[]];
|
|
return {
|
|
type: "For",
|
|
name: forNumeric[1][1] as string,
|
|
start: parseExpression(forNumeric[3]),
|
|
end: parseExpression(forNumeric[5]),
|
|
step: forNumeric[6]
|
|
? parseExpression(forNumeric[7])
|
|
: undefined,
|
|
block: parseBlock(t[4]),
|
|
};
|
|
} else {
|
|
const forGeneric = t[2] as [string, ...CrudeAST[]];
|
|
return {
|
|
type: "ForIn",
|
|
names: parseNameList(forGeneric[1]),
|
|
expressions: parseExpList(forGeneric[3]),
|
|
block: parseBlock(t[4]),
|
|
};
|
|
}
|
|
case "Function":
|
|
return {
|
|
type: "Function",
|
|
name: parseFunctionName(t[2]),
|
|
body: parseFunctionBody(t[3]),
|
|
};
|
|
case "LocalFunction":
|
|
return {
|
|
type: "LocalFunction",
|
|
name: t[3][1] as string,
|
|
body: parseFunctionBody(t[4]),
|
|
};
|
|
case "FunctionCall":
|
|
return {
|
|
type: "FunctionCallStatement",
|
|
call: parseExpression([
|
|
"FunctionCall",
|
|
...t.slice(1),
|
|
]) as LuaFunctionCallExpression,
|
|
};
|
|
case "Assign":
|
|
return {
|
|
type: "Assignment",
|
|
variables: (t[1].slice(1) as CrudeAST[]).filter((t) =>
|
|
t[0] != ","
|
|
).map(parseLValue),
|
|
expressions: parseExpList(t[3]),
|
|
};
|
|
case "Local":
|
|
return {
|
|
type: "Local",
|
|
names: parseAttNames(t[2]),
|
|
expressions: t[4] ? parseExpList(t[4]) : [],
|
|
};
|
|
default:
|
|
console.error(t);
|
|
throw new Error(`Unknown statement type: ${t[0]}`);
|
|
}
|
|
}
|
|
|
|
function parseAttNames(n: CrudeAST): LuaAttName[] {
|
|
const t = n as [string, ...CrudeAST[]];
|
|
if (t[0] !== "AttNameList") {
|
|
throw new Error(`Expected AttNameList, got ${t[0]}`);
|
|
}
|
|
return t.slice(1).filter((t) => t[0] !== ",").map(parseAttName);
|
|
}
|
|
|
|
function parseAttName(n: CrudeAST): LuaAttName {
|
|
const t = n as [string, ...CrudeAST[]];
|
|
if (t[0] !== "AttName") {
|
|
throw new Error(`Expected AttName, got ${t[0]}`);
|
|
}
|
|
return {
|
|
type: "AttName",
|
|
name: t[1][1] as string,
|
|
attribute: t[2][2] ? t[2][2][1] as string : undefined,
|
|
};
|
|
}
|
|
|
|
function parseLValue(n: CrudeAST): LuaLValue {
|
|
const t = n as [string, ...CrudeAST[]];
|
|
switch (t[0]) {
|
|
case "Name":
|
|
return { type: "Variable", name: t[1] as string };
|
|
case "Property":
|
|
return {
|
|
type: "PropertyAccess",
|
|
object: parsePrefixExpression(t[1]),
|
|
property: t[3][1] as string,
|
|
};
|
|
case "MemberExpression":
|
|
return {
|
|
type: "TableAccess",
|
|
object: parsePrefixExpression(t[1]),
|
|
key: parseExpression(t[3]),
|
|
};
|
|
default:
|
|
console.error(t);
|
|
throw new Error(`Unknown lvalue type: ${t[0]}`);
|
|
}
|
|
}
|
|
|
|
function parseFunctionName(n: CrudeAST): LuaFunctionName {
|
|
const t = n as [string, ...CrudeAST[]];
|
|
if (t[0] !== "FuncName") {
|
|
throw new Error(`Expected FunctionName, got ${t[0]}`);
|
|
}
|
|
const propNames: string[] = [];
|
|
let colonName: string | undefined = undefined;
|
|
for (let i = 1; i < t.length; i += 2) {
|
|
propNames.push(t[i][1] as string);
|
|
if (t[i + 1] && t[i + 1][0] === ":") {
|
|
colonName = t[i + 2][1] as string;
|
|
break;
|
|
}
|
|
}
|
|
return { type: "FunctionName", propNames, colonName };
|
|
}
|
|
|
|
function parseNameList(n: CrudeAST): string[] {
|
|
const t = n as [string, ...CrudeAST[]];
|
|
if (t[0] !== "NameList") {
|
|
throw new Error(`Expected NameList, got ${t[0]}`);
|
|
}
|
|
return t.slice(1).filter((t) => t[0] === "Name").map((t) => t[1] as string);
|
|
}
|
|
|
|
function parseExpList(n: CrudeAST): LuaExpression[] {
|
|
const t = n as [string, ...CrudeAST[]];
|
|
if (t[0] !== "ExpList") {
|
|
throw new Error(`Expected ExpList, got ${t[0]}`);
|
|
}
|
|
return t.slice(1).filter((t) => t[0] !== ",").map(parseExpression);
|
|
}
|
|
|
|
function parseExpression(n: CrudeAST): LuaExpression {
|
|
const t = n as [string, ...CrudeAST[]];
|
|
switch (t[0]) {
|
|
case "LiteralString": {
|
|
let cleanString = t[1] as string;
|
|
// Remove quotes etc
|
|
cleanString = cleanString.slice(1, -1);
|
|
return { type: "String", value: cleanString };
|
|
}
|
|
case "Number":
|
|
return { type: "Number", value: parseFloat(t[1] as string) };
|
|
case "BinaryExpression":
|
|
return {
|
|
type: "Binary",
|
|
operator: t[2][1] as string,
|
|
left: parseExpression(t[1]),
|
|
right: parseExpression(t[3]),
|
|
};
|
|
case "UnaryExpression":
|
|
return {
|
|
type: "Unary",
|
|
operator: t[1][1] as string,
|
|
argument: parseExpression(t[2]),
|
|
};
|
|
case "Property":
|
|
return {
|
|
type: "PropertyAccess",
|
|
object: parsePrefixExpression(t[1]),
|
|
property: t[3][1] as string,
|
|
};
|
|
|
|
case "Parens":
|
|
return parseExpression(t[2]);
|
|
case "FunctionCall": {
|
|
if (t[2][0] === ":") {
|
|
return {
|
|
type: "FunctionCall",
|
|
prefix: parsePrefixExpression(t[1]),
|
|
name: t[3][1] as string,
|
|
args: parseFunctionArgs(t.slice(4)),
|
|
};
|
|
}
|
|
return {
|
|
type: "FunctionCall",
|
|
prefix: parsePrefixExpression(t[1]),
|
|
args: parseFunctionArgs(t.slice(2)),
|
|
};
|
|
}
|
|
case "FunctionDef": {
|
|
const body = parseFunctionBody(t[2]);
|
|
return {
|
|
type: "FunctionDefinition",
|
|
body,
|
|
};
|
|
}
|
|
case "Name":
|
|
return { type: "Variable", name: t[1] as string };
|
|
case "Ellipsis":
|
|
return { type: "Variable", name: "..." };
|
|
case "true":
|
|
return { type: "Boolean", value: true };
|
|
case "false":
|
|
return { type: "Boolean", value: false };
|
|
case "TableConstructor":
|
|
return {
|
|
type: "TableConstructor",
|
|
fields: t.slice(2, -1).filter((t) =>
|
|
!(typeof t === "string" ||
|
|
["{", "}"].includes(t[1] as string))
|
|
).map(parseTableField),
|
|
};
|
|
case "nil":
|
|
return { type: "Nil" };
|
|
default:
|
|
console.error(t);
|
|
throw new Error(`Unknown expression type: ${t[0]}`);
|
|
}
|
|
}
|
|
|
|
function parseFunctionArgs(n: CrudeAST[]): LuaExpression[] {
|
|
console.log("Parsing function args", n);
|
|
return n.filter((t) => ![",", "(", ")"].includes(t[0])).map(
|
|
parseExpression,
|
|
);
|
|
}
|
|
|
|
function parseFunctionBody(n: CrudeAST): LuaFunctionBody {
|
|
const t = n as [string, ...CrudeAST[]];
|
|
if (t[0] !== "FuncBody") {
|
|
throw new Error(`Expected FunctionBody, got ${t[0]}`);
|
|
}
|
|
return {
|
|
type: "FunctionBody",
|
|
parameters: (t[2] as CrudeAST[]).slice(1).filter((t) =>
|
|
["Name", "Ellipsis"].includes(t[0])
|
|
)
|
|
.map((t) => t[1] as string),
|
|
block: parseBlock(t[4]),
|
|
};
|
|
}
|
|
|
|
function parsePrefixExpression(n: CrudeAST): LuaPrefixExpression {
|
|
const t = n as [string, ...CrudeAST[]];
|
|
switch (t[0]) {
|
|
case "Name":
|
|
return { type: "Variable", name: t[1] as string };
|
|
case "Property":
|
|
return {
|
|
type: "PropertyAccess",
|
|
object: parsePrefixExpression(t[1]),
|
|
property: t[3][1] as string,
|
|
};
|
|
case "Parens":
|
|
return { type: "Parenthesized", expression: parseExpression(t[2]) };
|
|
default:
|
|
console.error(t);
|
|
throw new Error(`Unknown prefix expression type: ${t[0]}`);
|
|
}
|
|
}
|
|
|
|
function parseTableField(n: CrudeAST): LuaTableField {
|
|
const t = n as [string, ...CrudeAST[]];
|
|
switch (t[0]) {
|
|
case "FieldExp":
|
|
return {
|
|
type: "ExpressionField",
|
|
value: parseExpression(t[1]),
|
|
};
|
|
case "FieldProp":
|
|
return {
|
|
type: "PropField",
|
|
key: t[1][1] as string,
|
|
value: parseExpression(t[3]),
|
|
};
|
|
case "FieldDynamic":
|
|
return {
|
|
type: "DynamicField",
|
|
key: parseExpression(t[2]),
|
|
value: parseExpression(t[5]),
|
|
};
|
|
default:
|
|
console.error(t);
|
|
throw new Error(`Unknown table field type: ${t[0]}`);
|
|
}
|
|
}
|
|
|
|
export function parse(t: string): LuaBlock {
|
|
const crudeAst = parseToCrudeAST(t);
|
|
console.log("Crude AST", JSON.stringify(crudeAst, null, 2));
|
|
const result = parseChunk(crudeAst);
|
|
console.log("Parsed AST", JSON.stringify(result, null, 2));
|
|
return result;
|
|
}
|
|
|
|
export function parseToCrudeAST(t: string): CrudeAST {
|
|
return parseTreeToAST(lezerToParseTree(t, parser.parse(t).topNode), true);
|
|
}
|