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