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