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 ( / i m p o r t \s * \{ \s * ( .* C o n t e x t ) \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 ( ) ;
0 commit comments