Skip to content

Commit 8effd2f

Browse files
cache the parser build by checksum!
1 parent a642b6c commit 8effd2f

File tree

3 files changed

+131
-24
lines changed

3 files changed

+131
-24
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ parser.output
1010
npm-debug.log*
1111
yarn.lock
1212
.DS_Store
13+
/.jison-script.js

Cakefile

Lines changed: 128 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,30 @@
1+
assert = require 'assert'
2+
{ createHash } = require 'crypto'
13
oldConsole = require 'console'
24
fs = require 'fs'
35
os = require 'os'
46
path = require 'path'
5-
{ performance } = require 'perf_hooks'
7+
stream = require 'stream'
68
_ = require 'underscore'
79
{ spawn, exec, execSync } = require 'child_process'
810
CoffeeScript = require './lib/coffeescript'
911
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'
1028

1129
# ANSI Terminal Colors.
1230
bold = red = green = yellow = reset = ''
@@ -108,28 +126,111 @@ run = (args, callback) ->
108126
# Build the CoffeeScript language from source.
109127
buildParser = ->
110128
helpers.extend global, require 'util'
111-
require 'jison'
112129

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!
114135

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
133234

134235
buildExceptParser = (callback) ->
135236
files = fs.readdirSync 'src'
@@ -138,8 +239,10 @@ buildExceptParser = (callback) ->
138239
run ['-c', '-o', 'lib/coffeescript'].concat(files), callback
139240

140241
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
143246

144247
transpile = (code, options = {}) ->
145248
options.minify = process.env.MINIFY isnt 'false'
@@ -198,7 +301,8 @@ watchAndBuildAndTest = (harmony = no) ->
198301

199302
consoleTask 'build', 'build the CoffeeScript compiler from source', build
200303

201-
task 'build:parser', 'build the Jison parser only', buildParser
304+
consoleTask 'build:parser', 'build the Jison parser only', ->
305+
await buildParser()
202306

203307
task 'build:except-parser', 'build the CoffeeScript compiler, except for the Jison parser', buildExceptParser
204308

lib/coffeescript/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.*.sha256
2+
.jison-attestation.txt

0 commit comments

Comments
 (0)