Skip to content

Commit 5b34b15

Browse files
committed
kwlist
1 parent bf4612f commit 5b34b15

File tree

3 files changed

+653
-1
lines changed

3 files changed

+653
-1
lines changed

packages/deparser/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@
4040
"organize-transformers": "ts-node scripts/organize-transformers-by-version.ts",
4141
"generate-version-deparsers": "ts-node scripts/generate-version-deparsers.ts",
4242
"generate-packages": "ts-node scripts/generate-version-packages.ts",
43-
"prepare-versions": "npm run strip-transformer-types && npm run strip-direct-transformer-types && npm run strip-deparser-types && npm run organize-transformers && npm run generate-version-deparsers && npm run generate-packages"
43+
"prepare-versions": "npm run strip-transformer-types && npm run strip-direct-transformer-types && npm run strip-deparser-types && npm run organize-transformers && npm run generate-version-deparsers && npm run generate-packages",
44+
"keywords": "ts-node scripts/keywords.ts"
4445
},
4546
"keywords": [
4647
"sql",
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import fs from "node:fs";
2+
import path from "node:path";
3+
import readline from "node:readline";
4+
5+
function ask(question: string): Promise<string> {
6+
const rl = readline.createInterface({
7+
input: process.stdin,
8+
output: process.stdout,
9+
});
10+
11+
return new Promise((resolve) => {
12+
rl.question(`${question}: `, (answer) => {
13+
rl.close();
14+
resolve(answer.trim());
15+
});
16+
});
17+
}
18+
19+
function requireNonEmpty(value: string | undefined, label: string): string {
20+
if (!value) {
21+
console.error(`❌ Missing ${label}.`);
22+
process.exit(1);
23+
}
24+
return value;
25+
}
26+
27+
function expandTilde(p: string): string {
28+
if (p.startsWith("~/")) {
29+
return path.join(process.env.HOME || "", p.slice(2));
30+
}
31+
return p;
32+
}
33+
34+
async function main() {
35+
const [, , kwlistArg, outArg] = process.argv;
36+
37+
// kwlist.h path is required (CLI arg or prompt), output defaults to src/kwlist.ts
38+
let kwlistPathInput = kwlistArg;
39+
if (!kwlistPathInput) {
40+
console.log("e.g. ~/code/postgres/postgres/src/include/parser/kwlist.h");
41+
kwlistPathInput = requireNonEmpty(await ask("Path to PostgreSQL kwlist.h"), "kwlist.h path");
42+
}
43+
44+
const outPathInput = outArg ?? path.resolve(__dirname, "../src/kwlist.ts");
45+
46+
const kwlistPath = path.resolve(expandTilde(kwlistPathInput));
47+
const outPath = path.resolve(outPathInput);
48+
49+
if (!fs.existsSync(kwlistPath)) {
50+
console.error(`❌ kwlist.h not found: ${kwlistPath}`);
51+
process.exit(1);
52+
}
53+
54+
const src = fs.readFileSync(kwlistPath, "utf8");
55+
56+
// PG_KEYWORD("word", TOKEN, KIND_KEYWORD, ...)
57+
const re = /^PG_KEYWORD\("([^"]+)",\s*[^,]+,\s*([A-Z_]+)_KEYWORD\b/gm;
58+
59+
const kinds = new Map<string, Set<string>>();
60+
let m: RegExpExecArray | null;
61+
while ((m = re.exec(src))) {
62+
const word = m[1].toLowerCase();
63+
const kind = `${m[2]}_KEYWORD`;
64+
65+
if (!kinds.has(kind)) kinds.set(kind, new Set());
66+
kinds.get(kind)!.add(word);
67+
}
68+
69+
// Stable, sorted output
70+
const keywordsByKind: Record<string, string[]> = {};
71+
for (const [kind, set] of kinds.entries()) {
72+
keywordsByKind[kind] = [...set].sort();
73+
}
74+
75+
const ts = `/* eslint-disable */
76+
/**
77+
* Generated from PostgreSQL kwlist.h
78+
* DO NOT EDIT BY HAND.
79+
*/
80+
81+
export type KeywordKind =
82+
| "NO_KEYWORD"
83+
| "UNRESERVED_KEYWORD"
84+
| "COL_NAME_KEYWORD"
85+
| "TYPE_FUNC_NAME_KEYWORD"
86+
| "RESERVED_KEYWORD";
87+
88+
export const kwlist = ${JSON.stringify(keywordsByKind, null, 2)
89+
.replace(/"([A-Z_]+)"/g, "$1")} as const;
90+
91+
export const RESERVED_KEYWORDS = new Set(kwlist.RESERVED_KEYWORD ?? []);
92+
export const UNRESERVED_KEYWORDS = new Set(kwlist.UNRESERVED_KEYWORD ?? []);
93+
export const COL_NAME_KEYWORDS = new Set(kwlist.COL_NAME_KEYWORD ?? []);
94+
export const TYPE_FUNC_NAME_KEYWORDS = new Set(kwlist.TYPE_FUNC_NAME_KEYWORD ?? []);
95+
96+
export function keywordKindOf(word: string): KeywordKind {
97+
const w = word.toLowerCase();
98+
if (RESERVED_KEYWORDS.has(w)) return "RESERVED_KEYWORD";
99+
if (TYPE_FUNC_NAME_KEYWORDS.has(w)) return "TYPE_FUNC_NAME_KEYWORD";
100+
if (COL_NAME_KEYWORDS.has(w)) return "COL_NAME_KEYWORD";
101+
if (UNRESERVED_KEYWORDS.has(w)) return "UNRESERVED_KEYWORD";
102+
return "NO_KEYWORD";
103+
}
104+
`;
105+
106+
fs.writeFileSync(outPath, ts, "utf8");
107+
console.log(`✅ Wrote ${outPath}`);
108+
console.log(` Source: ${kwlistPath}`);
109+
}
110+
111+
main().catch((err) => {
112+
console.error(err);
113+
process.exit(1);
114+
});

0 commit comments

Comments
 (0)