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