lua
Zef Hemel 2024-09-11 21:17:56 +02:00
parent f7fe7cadbc
commit 8725655b9d
4 changed files with 659 additions and 1 deletions

View File

@ -0,0 +1,171 @@
/* Based on: https://github.com/R167/lezer-lua */
@precedence {
call,
power @right,
prefix,
times @left,
plus @left,
concat @right,
shift @left,
bitand @left,
xor @left,
bitor @left,
compare @left,
and @left,
or @left
}
@top Chunk { Block }
Block { statement* ReturnStatement? }
ReturnStatement { kw<"return"> exp? ";"?}
@skip { newline | space | Comment }
statement[@isGroup=Statement] {
";" |
Label |
kw<"break"> |
Goto{ kw<"goto"> Name } |
Scope { kw<"do"> Block kw<"end"> } |
WhileStatement { kw<"while"> exp kw<"do"> Block kw<"end"> } |
RepeatStatement { kw<"repeat"> Block kw<"until"> exp } |
IfStatement |
ForStatement |
Function { kw<"function"> FuncName FuncBody } |
LocalFunction { kw<"local"> kw<"function"> Name FuncBody } |
Assign { VarList "=" ExpList } |
Local { kw<"local"> AttNameList ("=" ExpList)? } |
FunctionCall ~fcall
}
IfStatement {
kw<"if"> exp kw<"then"> Block
(kw<"elseif"> exp kw<"then"> Block)*
(kw<"else"> Block)
kw<"end">
}
ForNumeric { Name "=" exp "," exp ("," exp)? }
ForGeneric { NameList kw<"in"> ExpList }
ForStatement {
kw<"for"> (ForNumeric | ForGeneric) kw<"do"> Block kw<"end">
}
FuncName { Name ("." Name)* (":" Name)? }
FuncBody { "(" ArgList ")" Block kw<"end"> }
list<term> { term ("," term)* }
NameList { list<Name> }
ExpList { list<exp> }
VarList { list<var> }
ArgList { list<var | "..."> }
AttNameList { list<Name Attrib> }
Attrib { ( "<" Name ">" )? }
exp {
kw<"nil"> | kw<"true"> | kw<"false"> | "..." |
Number |
LiteralString |
prefixexp |
BinaryExpression |
UnaryExpression |
TableConstructor |
FunctionDef { kw<"function"> FuncBody }
}
field[@isGroup=Field] {
FieldDynamic { "[" exp "]" "=" exp } |
FieldProp { Name "=" exp } |
FieldExp { exp }
}
prefixexp {
var |
Parens { "(" exp ")" ~parens } |
FunctionCall ~fcall
}
FunctionCall { prefixexp (":" Name)? !call args }
args {
LiteralString |
TableConstructor |
funcParams[@dynamicPrecedence=1] { "(" list<exp>? ")" ~parens }
}
var {
Name | Property { (prefixexp "." Name) } | MemberExpression { (prefixexp "[" exp "]") }
}
kw<term> { @specialize[@name={term}]<identifier, term> }
Name { identifier }
Label { "::" Name "::" }
LiteralString { simpleString }
BinaryExpression {
exp !or kw<"or"> exp |
exp !and kw<"and"> exp |
exp !compare CompareOp exp |
exp !bitor BitOp{"|"} exp |
exp !bitand BitOp{"&"} exp |
exp !xor BitOp{"~"} exp |
exp !shift BitOp{"<<" | ">>"} exp |
exp !concat ".." exp |
exp !plus ArithOp{"+" | minus} exp |
exp !times ArithOp{"*" | "/" | "%" | "//"} exp |
exp !power ArithOp{"^"} exp
}
UnaryExpression {
!prefix kw<"not"> exp |
!prefix (ArithOp{"+" | minus} | BitOp{"~"}) exp
}
TableConstructor { "{" (field (fieldsep field)* fieldsep?)? "}" }
@tokens {
CompareOp { "<" | ">" | $[<>=~/] "=" }
word { std.asciiLetter (std.digit | std.asciiLetter)* }
identifier { word }
stringEscape {
"\\" ($[abfnz"'\\] | digit digit? digit?) |
"\\x" hex hex |
// NOTE: this should really be /[0-7]hex{5}/ at max, but that's annoying to write
"\\u{" hex+ "}"
}
simpleString { "'" (stringEscape | ![\r\n\\'])+ "'" | '"' (stringEscape | ![\r\n\\"])+ '"'}
hex { $[0-9a-fA-F] }
digit { std.digit }
Number {
digit+ ("." digit+)? ($[eE] $[+\-] digit+)? |
"0" $[xX] hex+ ("." hex+)? ($[pP] $[+/-] digit+)?
}
Comment { "--" ![\n\r]* }
space { ($[ \t\f] | "\\" $[\n\r])+ }
newline { $[\n\r] | "\n\r" | "\r\n" }
"..."[@name=Ellipsis]
".."[@name=Concat]
@precedence { Comment, minus }
minus {"-"}
fieldsep { $[,;] }
"(" ")" "[" "]" "{" "}"
"." "," ";" ":" "::"
}

View File

@ -0,0 +1,42 @@
import { parse } from "$common/space_lua/parse.ts";
import { assertEquals } from "@std/assert/equals";
Deno.test("Test Lua parser", () => {
// Basic block test
parse(`
print("Hello, World!")
print(10)
`);
parse("");
// Expression tests
parse(
`e(1, 1.2, -3.8, +4, true, false, nil, "string", "Hello there \x00", ...)`,
);
parse(`e(10 << 10, 10 >> 10, 10 & 10, 10 | 10, 10 ~ 10)`);
assertEquals(
parse(`e(1 + 2 - 3 * 4 / 4)`),
parse(`e(1 + 2 - ((3 * 4) / 4))`),
);
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.b.c)`);
parse(`e((1+2))`);
// Table expressions
parse(`e({})`);
parse(`e({1, 2, 3, })`);
parse(`e({1 ; 2 ; 3})`);
parse(`e({a = 1, b = 2, c = 3})`);
parse(`e({[3] = 1, [10 * 10] = "sup"})`);
// Function calls
parse(`e(func(), func(1, 2, 3), a.b(), a.b.c:hello(), (a.b)(7))`);
// Function expression
parse(`function sayHi()
print("Hi")
end`);
parse(`e(function(a, b) end)`);
parse(`e(function(a, b, ...) end)`);
});

443
common/space_lua/parse.ts Normal file
View File

@ -0,0 +1,443 @@
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);
}

View File

@ -3,6 +3,7 @@
QUERY_GRAMMAR=common/markdown_parser/query.grammar QUERY_GRAMMAR=common/markdown_parser/query.grammar
EXPRESSION_GRAMMAR=common/markdown_parser/expression.grammar.generated EXPRESSION_GRAMMAR=common/markdown_parser/expression.grammar.generated
TEMPLATE_GRAMMAR=common/template/template.grammar TEMPLATE_GRAMMAR=common/template/template.grammar
LUA_GRAMMAR=common/space_lua/lua.grammar
LEZER_GENERATOR_VERSION=1.5.1 LEZER_GENERATOR_VERSION=1.5.1
# Generate a patched grammer for just expressions # Generate a patched grammer for just expressions
@ -12,3 +13,4 @@ tail -n +2 $QUERY_GRAMMAR >> $EXPRESSION_GRAMMAR
deno run -A npm:@lezer/generator@$LEZER_GENERATOR_VERSION $QUERY_GRAMMAR -o common/markdown_parser/parse-query.js deno run -A npm:@lezer/generator@$LEZER_GENERATOR_VERSION $QUERY_GRAMMAR -o common/markdown_parser/parse-query.js
deno run -A npm:@lezer/generator@$LEZER_GENERATOR_VERSION $EXPRESSION_GRAMMAR -o common/markdown_parser/parse-expression.js deno run -A npm:@lezer/generator@$LEZER_GENERATOR_VERSION $EXPRESSION_GRAMMAR -o common/markdown_parser/parse-expression.js
deno run -A npm:@lezer/generator@$LEZER_GENERATOR_VERSION $TEMPLATE_GRAMMAR -o common/template/parse-template.js deno run -A npm:@lezer/generator@$LEZER_GENERATOR_VERSION $TEMPLATE_GRAMMAR -o common/template/parse-template.js
deno run -A npm:@lezer/generator@$LEZER_GENERATOR_VERSION $LUA_GRAMMAR -o common/space_lua/parse-lua.js