1
+ assert = require ' assert'
2
+ { createHash } = require ' crypto'
1
3
oldConsole = require ' console'
2
4
fs = require ' fs'
3
5
os = require ' os'
4
6
path = require ' path'
5
- { performance } = require ' perf_hooks '
7
+ stream = require ' stream '
6
8
_ = require ' underscore'
7
9
{ spawn , exec , execSync } = require ' child_process'
8
10
CoffeeScript = require ' ./lib/coffeescript'
9
11
helpers = require ' ./lib/coffeescript/helpers'
12
+ util = require ' util'
13
+ process = require ' process'
14
+ readline = require ' readline'
15
+
16
+ sha256 = -> createHash ' sha256'
17
+
18
+ checksumFile = (inPath ) ->
19
+ {dir , base } = path .parse inPath
20
+ outPath = path .join dir, " .#{ base} .sha256"
21
+ console .debug " checksum: '#{ inPath} ' => '#{ outPath} '"
22
+ outStream = fs .createReadStream inPath
23
+ .pipe sha256 ()
24
+ .setEncoding ' hex'
25
+ .pipe fs .createWriteStream outPath
26
+ await stream .promises .finished outStream
27
+ await fs .promises .readFile outPath, encoding : ' utf8'
10
28
11
29
# ANSI Terminal Colors.
12
30
bold = red = green = yellow = reset = ' '
@@ -108,28 +126,111 @@ run = (args, callback) ->
108
126
# Build the CoffeeScript language from source.
109
127
buildParser = ->
110
128
helpers .extend global , require ' util'
111
- require ' jison'
112
129
113
- startParserBuild = performance .now ()
130
+ # (1) cache parser build
131
+ # (1.1) cache on grammar.coffee
132
+ # (1.2) cache on jison dep
133
+ # (2) cache file compilation
134
+ # (3) make source maps work for errors in the coffeescript compiler!
114
135
115
- # Gather summary statistics about the grammar.
116
- parser = require (' ./lib/coffeescript/grammar' ).parser
117
- {symbols_ , terminals_ , productions_ } = parser
118
- countKeys = (obj ) -> (Object .keys obj).length
119
- numSyms = countKeys symbols_
120
- numTerms = countKeys terminals_
121
- numProds = countKeys productions_
122
- console .info " parser created (#{ numSyms} symbols, #{ numTerms} terminals, #{ numProds} productions)"
123
-
124
- loadGrammar = performance .now ()
125
- console .info " loading grammar: #{ loadGrammar - startParserBuild} ms"
126
-
127
- # We don't need `moduleMain`, since the parser is unlikely to be run standalone.
128
- fs .writeFileSync ' lib/coffeescript/parser.js' , parser .generate (moduleMain : -> )
129
-
130
- parserBuildComplete = performance .now ()
131
- console .info " parser generation: #{ parserBuildComplete - loadGrammar} ms"
132
- console .info " full parser build time: #{ parserBuildComplete - startParserBuild} ms"
136
+ grammarChecksum = await checksumFile ' lib/coffeescript/grammar.js'
137
+ console .debug " grammar checksum: #{ grammarChecksum} "
138
+ parserChecksum = await checksumFile ' lib/coffeescript/parser.js'
139
+ console .debug " parser checksum: #{ parserChecksum} "
140
+ try
141
+ console .debug ' reading attestation file for parser compile caching...'
142
+ attestation = await fs .promises .readFile ' lib/coffeescript/.jison-attestation.txt' , encoding : ' utf8'
143
+ console .debug " attestation: #{ attestation} "
144
+ assert attestation .length is 129
145
+ [grammar , sep , parser ] = [attestation[... 64 ], attestation[64 ], attestation[65 .. ]]
146
+ assert sep is ' :' and grammar .length is 64 and parser .length is 64
147
+ if grammar == grammarChecksum and parser == parserChecksum
148
+ console .debug ' success! using cached parser...'
149
+ return attestation
150
+ else
151
+ console .warn ' attestation was out of date, compiling jison grammar...'
152
+ catch e
153
+ assert e .code is ' ENOENT'
154
+ console .debug ' attestation file not found, compiling jison grammar...'
155
+
156
+ jisonScript = ->
157
+ assert = require ' assert'
158
+ fs = require ' fs'
159
+ # Gather summary statistics about the grammar.
160
+ { performance } = require ' perf_hooks'
161
+ require ' jison'
162
+
163
+ sendMsg = (obj ) ->
164
+ msg = JSON .stringify obj
165
+ process .stdout .write " #{ msg} \n "
166
+
167
+ startParserBuild = performance .now ()
168
+ sendMsg {startParserBuild}
169
+
170
+ parser = require (' ./lib/coffeescript/grammar' ).parser
171
+ {symbols_ , terminals_ , productions_ } = parser
172
+ countKeys = (obj ) -> (Object .keys obj).length
173
+ sendMsg
174
+ numSyms : countKeys symbols_
175
+ numTerms : countKeys terminals_
176
+ numProds : countKeys productions_
177
+
178
+ loadGrammar = performance .now ()
179
+ sendMsg {loadGrammar}
180
+
181
+ # We don't need `moduleMain`, since the parser is unlikely to be run standalone.
182
+ parserText = parser .generate (moduleMain : -> )
183
+ await fs .promises .writeFile ' lib/coffeescript/parser.js' , parserText, encoding : ' utf8'
184
+
185
+ parserBuildComplete = performance .now ()
186
+ sendMsg {parserBuildComplete}
187
+
188
+ process .exit 0
189
+
190
+ scriptText = " (#{ jisonScript .toString ()} ());"
191
+ await fs .promises .writeFile ' .jison-script.js' , scriptText, encoding : ' utf8'
192
+ child = spawn process .execPath , [' --experimental-default-type=commonjs' , " .jison-script.js" ]
193
+
194
+ child .stderr .pipe process .stderr
195
+
196
+ rl = readline .createInterface
197
+ input : child .stdout
198
+ terminal : no
199
+ crlfDelay : Infinity
200
+
201
+ msgEnv = {}
202
+ rl .on ' line' , (line ) ->
203
+ msg = JSON .parse line
204
+ Object .assign msgEnv, msg
205
+ if msg .startParserBuild ?
206
+ {startParserBuild } = msgEnv
207
+ console .debug " parser compile began at timestamp #{ startParserBuild} "
208
+ else if msg .numSyms ?
209
+ {numSyms , numTerms , numProds } = msgEnv
210
+ console .debug " parser created (#{ numSyms} symbols, #{ numTerms} terminals, #{ numProds} productions)"
211
+ else if msg .loadGrammar ?
212
+ {loadGrammar , startParserBuild } = msgEnv
213
+ console .debug " loading grammar: #{ loadGrammar - startParserBuild} ms"
214
+ else if msg .parserBuildComplete ?
215
+ {parserBuildComplete , loadGrammar , startParserBuild } = msgEnv
216
+ console .debug " parser generation: #{ parserBuildComplete - loadGrammar} ms"
217
+ console .debug " full parser build time: #{ parserBuildComplete - startParserBuild} ms"
218
+ else throw new Error " unrecognized fork msg: #{ JSON .stringify msg} "
219
+ child .on ' error' , (err ) ->
220
+ console .error " subprocess issue: #{ err} "
221
+ process .exit 1
222
+ code = await new Promise (res , rej ) ->
223
+ child .on ' exit' , (code ) -> res (code)
224
+ assert code is 0
225
+ rl .close ()
226
+
227
+ parserChecksum = await checksumFile ' lib/coffeescript/parser.js'
228
+ console .debug " new parser checksum: #{ parserChecksum} "
229
+ attestation = " #{ grammarChecksum} :#{ parserChecksum} "
230
+ assert attestation .length is 129
231
+ console .debug " writing new attestation '#{ attestation} ' now..."
232
+ await fs .promises .writeFile ' lib/coffeescript/.jison-attestation.txt' , attestation, encoding : ' utf8'
233
+ attestation
133
234
134
235
buildExceptParser = (callback ) ->
135
236
files = fs .readdirSync ' src'
@@ -138,8 +239,10 @@ buildExceptParser = (callback) ->
138
239
run [' -c' , ' -o' , ' lib/coffeescript' ].concat (files), callback
139
240
140
241
build = (callback ) ->
141
- buildParser ()
142
- buildExceptParser callback
242
+ util .callbackify (buildParser) (err , attestation ) ->
243
+ throw err if err?
244
+ console .debug {attestation}
245
+ buildExceptParser callback
143
246
144
247
transpile = (code , options = {}) ->
145
248
options .minify = process .env .MINIFY isnt ' false'
@@ -198,7 +301,8 @@ watchAndBuildAndTest = (harmony = no) ->
198
301
199
302
consoleTask ' build' , ' build the CoffeeScript compiler from source' , build
200
303
201
- task ' build:parser' , ' build the Jison parser only' , buildParser
304
+ consoleTask ' build:parser' , ' build the Jison parser only' , ->
305
+ await buildParser ()
202
306
203
307
task ' build:except-parser' , ' build the CoffeeScript compiler, except for the Jison parser' , buildExceptParser
204
308
0 commit comments