silverbullet/plug-api/lib/parse-query.ts

219 lines
6.4 KiB
TypeScript
Raw Normal View History

2024-02-29 22:23:05 +08:00
import { type AST, parseTreeToAST } from "./tree.ts";
import type { Query, QueryExpression } from "../types.ts";
import { language } from "$sb/syscalls.ts";
export function astToKvQuery(
node: AST,
): Query {
const query: Query = {
querySource: "",
};
const [queryType, querySource, ...clauses] = node;
if (queryType !== "Query") {
throw new Error(`Expected query type, got ${queryType}`);
}
query.querySource = querySource[1] as string;
for (const clause of clauses) {
const [clauseType] = clause;
switch (clauseType) {
case "WhereClause": {
if (query.filter) {
query.filter = [
"and",
query.filter,
expressionToKvQueryExpression(clause[2]),
];
} else {
query.filter = expressionToKvQueryExpression(clause[2]);
}
break;
}
case "OrderClause": {
if (!query.orderBy) {
query.orderBy = [];
}
for (const orderBy of clause.slice(2)) {
if (orderBy[0] === "OrderBy") {
// console.log("orderBy", orderBy);
const expr = orderBy[1][1];
if (orderBy[2]) {
query.orderBy.push({
expr: expressionToKvQueryExpression(expr),
desc: orderBy[2][1][1] === "desc",
});
} else {
query.orderBy.push({
expr: expressionToKvQueryExpression(expr),
desc: false,
});
}
}
}
break;
}
case "LimitClause": {
query.limit = expressionToKvQueryExpression(clause[2][1]);
break;
}
case "SelectClause": {
for (const select of clause.slice(2)) {
if (select[0] === "Select") {
if (!query.select) {
query.select = [];
}
if (select.length === 2) {
query.select.push({
name: cleanIdentifier(select[1][1] as string),
});
} else {
query.select.push({
name: cleanIdentifier(select[3][1] as string),
expr: expressionToKvQueryExpression(select[1]),
});
}
}
}
break;
}
case "RenderClause": {
2023-10-30 21:15:12 +08:00
// console.log("Render clause", clause);
const pageRef = (clause as any[]).find((c) => c[0] === "PageRef");
query.render = pageRef[1].slice(2, -2);
query.renderAll = !!(clause as any[]).find((c) => c[0] === "all");
break;
}
default:
throw new Error(`Unknown clause type: ${clauseType}`);
}
}
return query;
}
function cleanIdentifier(s: string): string {
if (s.startsWith("`") && s.endsWith("`")) {
return s.slice(1, -1);
}
return s;
}
export function expressionToKvQueryExpression(node: AST): QueryExpression {
if (["LVal", "Expression", "Value"].includes(node[0])) {
return expressionToKvQueryExpression(node[1]);
}
// console.log("Got expression", node);
switch (node[0]) {
case "Attribute": {
return [
"attr",
expressionToKvQueryExpression(node[1]),
cleanIdentifier(node[3][1] as string),
];
}
case "Identifier":
return ["attr", cleanIdentifier(node[1] as string)];
case "String":
return ["string", (node[1] as string).slice(1, -1)];
case "Number":
return ["number", +(node[1])];
case "Bool":
return ["boolean", node[1][1] === "true"];
case "null":
return ["null"];
case "Regex":
return ["regexp", (node[1] as string).slice(1, -1), "i"];
case "List": {
const exprs: AST[] = [];
for (const expr of node.slice(2)) {
if (expr[0] === "Expression") {
exprs.push(expr);
}
}
return ["array", exprs.map(expressionToKvQueryExpression)];
}
case "Object": {
const objAttrs: [string, QueryExpression][] = [];
for (const kv of node.slice(2)) {
if (typeof kv === "string") {
continue;
}
const [_, key, _colon, expr] = kv;
objAttrs.push([
key[1].slice(1, -1) as string,
expressionToKvQueryExpression(
expr,
),
]);
}
return ["object", objAttrs];
}
case "BinExpression": {
const lval = expressionToKvQueryExpression(node[1]);
const binOp = node[2][0] === "InKW" ? "in" : (node[2] as string).trim();
const val = expressionToKvQueryExpression(node[3]);
return [binOp as any, lval, val];
}
case "LogicalExpression": {
const op1 = expressionToKvQueryExpression(node[1]);
const op = node[2];
const op2 = expressionToKvQueryExpression(node[3]);
return [op[1] as any, op1, op2];
}
case "ParenthesizedExpression": {
return expressionToKvQueryExpression(node[2]);
}
case "Call": {
// console.log("Call", node);
const fn = cleanIdentifier(node[1][1] as string);
const args: AST[] = [];
for (const expr of node.slice(2)) {
if (expr[0] === "Expression") {
args.push(expr);
}
}
return ["call", fn, args.map(expressionToKvQueryExpression)];
}
case "UnaryExpression": {
// console.log("UnaryExpression", node);
if (node[1][0] === "NotKW" || node[1][0] === "!") {
return ["not", expressionToKvQueryExpression(node[2])];
} else if (node[1][0] === "-") {
return ["-", expressionToKvQueryExpression(node[2])];
}
throw new Error(`Unknown unary expression: ${node[1][0]}`);
}
case "TopLevelVal": {
return ["attr"];
}
case "GlobalIdentifier": {
return ["global", (node[1] as string).substring(1)];
}
case "TernaryExpression": {
const [_, condition, _space, ifTrue, _space2, ifFalse] = node;
return [
"?",
expressionToKvQueryExpression(condition),
expressionToKvQueryExpression(ifTrue),
expressionToKvQueryExpression(ifFalse),
];
}
case "QueryExpression": {
return ["query", astToKvQuery(node[2])];
}
case "PageRef": {
return ["pageref", (node[1] as string).slice(2, -2)];
}
default:
throw new Error(`Not supported: ${node[0]}`);
}
}
export async function parseQuery(query: string): Promise<Query> {
const queryAST = parseTreeToAST(
await language.parseLanguage(
"query",
query,
),
);
return astToKvQuery(queryAST[1]);
}