@@ -291,6 +291,136 @@ extension BNFConvert {
291
291
}
292
292
}
293
293
294
+ extension BNFConvert {
295
+ // TODO: I just want a use-def chain
296
+ func calculateUseGraph( ) -> [ NonTerminalSymbol : [ NonTerminalSymbol ] ] {
297
+ fatalError ( )
298
+ }
299
+
300
+
301
+
302
+ /// Optimize the BNF
303
+ mutating func optimize( ) {
304
+ // Iterate until we reach a fixed point
305
+ var changed = true
306
+ while changed {
307
+ changed = false
308
+
309
+ //
310
+ // Value propagation: propagate small single-choice single-symbol
311
+ // productions
312
+ //
313
+ // A ::= B C D E
314
+ // B ::= "b"
315
+ // C ::= C2
316
+ // C2 ::= "c"
317
+ // D ::= "d" "d" "d"
318
+ // E ::= "e" "e" "e" "e" ...
319
+ //
320
+ // -->
321
+ //
322
+ // A ::= "b" "c" "d" "d" "d" E
323
+ // E ::= "e" "e" "e" "e" ...
324
+ //
325
+
326
+ // Build up a list of single-choice single-symbol productions
327
+ // for upwards propagation
328
+ let terminalSequenceThreshold = 3
329
+ var singles = [ NonTerminalSymbol: Symbol] ( )
330
+ for (key, val) in productions {
331
+ if val. count == 1 {
332
+ let valChoice = val. first!
333
+ if valChoice. sequence. count == 1 {
334
+ let valSym = valChoice. sequence. first!
335
+ if case . terminalSequence( let array) = valSym {
336
+ if array. count > terminalSequenceThreshold {
337
+ continue
338
+ }
339
+ }
340
+ singles [ key] = valSym
341
+ }
342
+ }
343
+ }
344
+
345
+ for (key, val) in productions {
346
+ var valCopy = val
347
+ var valCopyDidChange = false
348
+
349
+ for choiceIdx in val. indices {
350
+
351
+ let choice = val [ choiceIdx]
352
+ var choiceCopy = choice
353
+ var choiceCopyDidChange = false
354
+
355
+ for idx in choice. sequence. indices {
356
+ if case . nonTerminal( let nt) = choice. sequence [ idx] {
357
+ if let sym = singles [ nt] {
358
+ choiceCopy. sequence [ idx] = sym
359
+ choiceCopyDidChange = true
360
+ }
361
+ }
362
+ }
363
+
364
+ if choiceCopyDidChange {
365
+ valCopy [ choiceIdx] = choiceCopy
366
+ valCopyDidChange = true
367
+ }
368
+ }
369
+
370
+ if valCopyDidChange {
371
+ productions [ key] = valCopy
372
+ changed = true
373
+ }
374
+ }
375
+
376
+ // TODO: I think the below is unnecessary, since that would have
377
+ // upwards propagated for everyone except root.
378
+ //
379
+ // // Check for a simple layer of redirection:
380
+ // //
381
+ // // A ::= B
382
+ // // B ::= ...
383
+ // //
384
+ // // -->
385
+ // //
386
+ // // A ::= ...
387
+ // for (key, val) in productions {
388
+ // if val.count == 1 {
389
+ // let valChoice = val.first!
390
+ // if valChoice.sequence.count == 1 {
391
+ // let valSym = valChoice.sequence.first!
392
+ // if case .nonTerminal(let rhs) = valSym {
393
+ // guard let rhsProd = productions[rhs] else {
394
+ // fatalError("Invariant violated: Unknown production")
395
+ // }
396
+ // productions[key] = rhsProd
397
+ // changed = true
398
+ // }
399
+ // }
400
+ // }
401
+ // }
402
+
403
+ // Check ROOT, since it has no uses it couldn't upward propagate
404
+ // a single non-terminal child
405
+ guard let rootSymbol = root else {
406
+ fatalError ( " Invariant violated: no root set " )
407
+ }
408
+ guard let val = productions [ rootSymbol] else {
409
+ // TODO: or is this an empty grammar?
410
+ // TODO: test empty regex
411
+ fatalError ( " Invariant violated: root has no production " )
412
+ }
413
+
414
+ // TODO: This isn't a win when RHS already has uses
415
+ if val. count == 1 {
416
+ if case . nonTerminal( let rhs) = val. first!. sequence. first! {
417
+ productions [ rootSymbol] = productions [ rhs]
418
+ changed = true
419
+ }
420
+ }
421
+ }
422
+ }
423
+ }
294
424
295
425
296
426
extension BNFConvert {
0 commit comments