444 lines
11 KiB
TypeScript
444 lines
11 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
|
|
| LuaLocalAssignmentStatement
|
|
| 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";
|
|
name: string;
|
|
propNames?: string[];
|
|
colonName?: string;
|
|
};
|
|
|
|
export type LuaFunctionBody = {
|
|
type: "FunctionBody";
|
|
parameters: string[];
|
|
block: LuaBlock;
|
|
};
|
|
|
|
export type LuaAssignmentStatement = {
|
|
type: "Assignment";
|
|
variables: LuaExpression[];
|
|
expressions: LuaExpression[];
|
|
};
|
|
|
|
export type LuaLocalAssignmentStatement = {
|
|
type: "LocalAssignment";
|
|
names: string[];
|
|
expressions: LuaExpression[];
|
|
};
|
|
|
|
export type LuaFunctionCallStatement = {
|
|
type: "FunctionCallStatement";
|
|
name: string;
|
|
args: LuaExpression[];
|
|
};
|
|
|
|
// 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[1] as string };
|
|
case "Break":
|
|
return { type: "Break" };
|
|
case "Goto":
|
|
return { type: "Goto", name: t[1] as string };
|
|
case "FunctionCall":
|
|
return {
|
|
type: "FunctionCallStatement",
|
|
name: t[1][1] as string,
|
|
args: t.slice(2, -1).filter((t) =>
|
|
![",", "(", ")"].includes(t[1] as string)
|
|
).map(parseExpression),
|
|
};
|
|
default:
|
|
console.error(t);
|
|
throw new Error(`Unknown statement type: ${t[0]}`);
|
|
}
|
|
}
|
|
|
|
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: t.slice(4, -1).filter((t) =>
|
|
![",", "(", ")"].includes(t[1] as string)
|
|
).map(parseExpression),
|
|
};
|
|
}
|
|
return {
|
|
type: "FunctionCall",
|
|
prefix: parsePrefixExpression(t[1]),
|
|
args: t.slice(3, -1).filter((t) =>
|
|
![",", "(", ")"].includes(t[1] as string)
|
|
).map(parseExpression),
|
|
};
|
|
}
|
|
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 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);
|
|
}
|