Skip to content

Commit 9139693

Browse files
committed
feat: fetch grammar from upstream
uses head.json to keep track of what commit hash from upstream is published
1 parent 542cfbb commit 9139693

File tree

12 files changed

+541
-1890
lines changed

12 files changed

+541
-1890
lines changed

.gitignore

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
11
node_modules
22
dist
3-
src/parser/JavaParser.ts
4-
src/parser/JavaParserListener.ts
5-
src/parser/JavaParserVisitor.ts
6-
src/parser/JavaLexer.ts
7-
src/parser/JavaContexts.ts
8-
src/parser/*.interp
3+
src/parser/*
4+
!src/parser/.gitkeep

build.ts

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import { promises as fs } from 'fs';
2+
import rimraf from 'rimraf';
3+
import fetch from 'node-fetch';
4+
import path from 'path';
5+
import { exec } from 'child_process';
6+
import { once } from 'events';
7+
import { EOL } from 'os';
8+
import { promisify } from 'util';
9+
10+
const files = [
11+
'JavaParser.g4',
12+
'JavaLexer.g4'
13+
];
14+
15+
const main = () =>
16+
withLog(
17+
'Checking if head is stale... ',
18+
getIsStale(),
19+
isStale => isStale ? 'Stale' : 'Up-to date'
20+
)
21+
.then(isStale => isStale || process.argv.includes('--force'))
22+
.then(shouldBuild =>
23+
!shouldBuild
24+
? (console.log('Exiting, use --force to build anyway'), Promise.reject(terminationSignal))
25+
: Promise.resolve()
26+
)
27+
.then(() => withLog('Fetching files from upstream... ', getFiles()))
28+
.then(files => withLog('Writing files... ', writeFiles(files)))
29+
.then(() => withLog('Updating head.json... ', updateHead()))
30+
.then(() => withLog('Generating parser...\n', writeParser()))
31+
.then(() => withLog('Generating contexts... ', writeParserContexts()))
32+
.then(() => withLog('Compiling typescript files... ', writeJavascript()))
33+
.then(() => console.log('Build successful!'))
34+
.catch(payload =>
35+
payload === terminationSignal
36+
? Promise.resolve()
37+
: Promise.reject(payload)
38+
)
39+
40+
const getIsStale = () =>
41+
Promise.all([getHead(), getUpstreamHead()])
42+
.then(([head, upstreamHead]) =>
43+
files.some(file => head[file] !== upstreamHead[file])
44+
)
45+
46+
const getHead = () =>
47+
fs.readFile(path.join(__dirname, 'src/head.json'), 'utf-8')
48+
.then(JSON.parse) as Promise<{ [file: string]: string }>
49+
50+
let upstreamHeadCache: { [file: string]: string } | undefined;
51+
const getUpstreamHead = () =>
52+
upstreamHeadCache ? Promise.resolve(upstreamHeadCache) :
53+
Promise.all(
54+
files.map(file =>
55+
fetch(`https://api.github.com/repos/antlr/grammars-v4/commits?path=java/java/${file}`)
56+
.then(res => res.json())
57+
.then(commits => ({ [file]: commits[0].sha as string }))
58+
)
59+
)
60+
.then(mergeAll)
61+
.then(upstreamHead => {
62+
upstreamHeadCache = upstreamHead;
63+
return Promise.resolve(upstreamHead);
64+
});
65+
66+
const getFiles = () =>
67+
Promise.all(
68+
files.map(
69+
file =>
70+
fetch(`https://raw.githubusercontent.com/antlr/grammars-v4/master/java/java/${file}`)
71+
.then(res => res.text())
72+
.then(data => ({ [file]: data }))
73+
)
74+
)
75+
.then(mergeAll)
76+
77+
const writeFiles = (files: { [file: string]: string }) =>
78+
Promise.all(
79+
Object.entries(files)
80+
.map(([file, data]) =>
81+
fs.writeFile(path.join(__dirname, 'src/parser/', file), data)
82+
)
83+
)
84+
85+
const updateHead = () =>
86+
getUpstreamHead()
87+
.then(head =>
88+
fs.writeFile(
89+
path.join(__dirname, 'src/head.json'),
90+
JSON.stringify(head, null, ' ')
91+
)
92+
)
93+
94+
const writeParser = () =>
95+
execCommand(`${prependBinDir('antlr4ts')} -visitor -o src/parser -Xexact-output-dir src/parser/JavaLexer.g4 src/parser/JavaParser.g4`)
96+
97+
const writeParserContexts = () =>
98+
fs.readFile(path.join(__dirname, '/src/parser/JavaParserListener.ts'), 'utf-8')
99+
.then(listenerSource =>
100+
listenerSource
101+
.split(EOL)
102+
.map((l) => {
103+
let matches = l.match(/import\s*\{\s*(.*Context)\s*\}.*/);
104+
if (matches === null) return null;
105+
return matches[1];
106+
})
107+
.filter((c) => c !== null)
108+
)
109+
.then(contexts => contexts.reduce((list, context) => list + ` ${context},${EOL}`, ''))
110+
.then(exportList => `export {${EOL}${exportList}} from './JavaParser';`)
111+
.then(contextsSource => fs.writeFile(path.join(__dirname, '/src/parser/JavaContexts.ts'), contextsSource));
112+
113+
const writeJavascript = () =>
114+
promisify(rimraf)(path.join(__dirname, "/dist"))
115+
.then(() => execCommand(prependBinDir('tsc')));
116+
117+
const withLog = <T>(
118+
label: string,
119+
promise: Promise<T>,
120+
fulfilMessage: ((value: T) => string) = () => 'Done'
121+
) => {
122+
process.stdout.write(label);
123+
return promise
124+
.then(value => {
125+
process.stdout.write(fulfilMessage(value) + '\n')
126+
return Promise.resolve(value);
127+
})
128+
.catch(error => {
129+
process.stdout.write('Something went wrong\n');
130+
return Promise.reject(error);
131+
})
132+
}
133+
134+
const terminationSignal = Symbol('terminationSignal');
135+
136+
const execCommand = (command: string) => {
137+
let childProcess = exec(command, { cwd: __dirname })
138+
childProcess.stdout.pipe(process.stdout);
139+
childProcess.stderr.pipe(process.stderr);
140+
141+
return (
142+
once(childProcess, 'exit')
143+
.then(([code]: [number]) => code === 0 ? Promise.resolve() : Promise.reject())
144+
)
145+
}
146+
147+
const prependBinDir = (p: string) =>
148+
path.join(__dirname, "/node_modules/.bin/", p);
149+
150+
type MergeAll = <T extends object[]>(xs: T) => UnionToIntersection<T[number]>;
151+
const mergeAll: MergeAll = xs => xs.reduce((m, x) => ({ ...m, ...x }), {}) as any;
152+
153+
type UnionToIntersection<U> =
154+
(U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never
155+
156+
main();

generate-contexts.js

Lines changed: 0 additions & 35 deletions
This file was deleted.

package.json

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,26 +21,28 @@
2121
"dist"
2222
],
2323
"scripts": {
24-
"build": "rimraf dist && npm run generate:parser && npm run generate:contexts && tsc",
25-
"format": "prettier --write src/**.ts **/*.json",
24+
"build": "ts-node build.ts",
25+
"format": "prettier --write build.ts src/**.ts **/*.json",
2626
"prepublish": "yarn build",
27-
"generate:parser": "antlr4ts -visitor -o src/parser -Xexact-output-dir src/parser/JavaLexer.g4 src/parser/JavaParser.g4",
28-
"generate:contexts": "node generate-contexts.js",
2927
"precommit": "lint-staged",
3028
"postcommit": "git update-index --again",
3129
"test": "jest"
3230
},
3331
"devDependencies": {
3432
"@types/jest": "^26.0.4",
3533
"@types/node": "^14.0.22",
34+
"@types/node-fetch": "^2.5.7",
35+
"@types/rimraf": "^3.0.0",
3636
"antlr4ts-cli": "^0.5.0-alpha.3",
37-
"husky": "^0.14.3",
37+
"husky": "^4.2.5",
3838
"jest": "^26.1.0",
39-
"lint-staged": "^7.2.0",
40-
"prettier": "^1.13.7",
41-
"rimraf": "^2.6.2",
39+
"lint-staged": "^10.2.11",
40+
"node-fetch": "^2.6.0",
41+
"prettier": "^2.0.5",
42+
"rimraf": "^3.0.2",
4243
"ts-jest": "^26.1.1",
43-
"tslint": "^5.10.0",
44+
"ts-node": "^8.10.2",
45+
"tslint": "^6.1.2",
4446
"typescript": "^3.9.6"
4547
},
4648
"dependencies": {

src/head.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"JavaParser.g4": null,
3+
"JavaLexer.g4": null
4+
}

src/parser/.gitkeep

Whitespace-only changes.

0 commit comments

Comments
 (0)