@@ -438,6 +438,62 @@ class MergingOpAssembler {
438438 }
439439}
440440
441+ /**
442+ * Canonicalizes a sequence of operations. Specifically:
443+ * - Skips no-op changes.
444+ * - Reorders consecutive '-' and '+' operations.
445+ * - Combines consecutive operations when possible.
446+ *
447+ * @param {Iterable.<Op> } ops - Iterable of operations to combine.
448+ * @param {boolean } finalize - If truthy, omits the final op if it is an attributeless keep op.
449+ * @yields {Op} The canonicalized operations.
450+ * @returns {number } How much the sequence of operations changes the length (in characters).
451+ */
452+ const canonicalizeOps = function * ( ops , finalize ) {
453+ let minusOps = [ ] ;
454+ let plusOps = [ ] ;
455+ let keepOps = [ ] ;
456+ let prevOpcode = '' ;
457+ let lengthChange = 0 ;
458+
459+ const flushPlusMinus = function * ( ) {
460+ yield * squashOps ( minusOps , false ) ;
461+ minusOps = [ ] ;
462+ yield * squashOps ( plusOps , false ) ;
463+ plusOps = [ ] ;
464+ } ;
465+
466+ const flushKeeps = function * ( finalize ) {
467+ yield * squashOps ( keepOps , finalize ) ;
468+ keepOps = [ ] ;
469+ } ;
470+
471+ for ( const op of ops ) {
472+ if ( ! op . opcode || ! op . chars ) continue ;
473+ switch ( op . opcode ) {
474+ case '-' :
475+ if ( prevOpcode === '=' ) yield * flushKeeps ( false ) ;
476+ minusOps . push ( op ) ;
477+ lengthChange -= op . chars ;
478+ break ;
479+ case '+' :
480+ if ( prevOpcode === '=' ) yield * flushKeeps ( false ) ;
481+ plusOps . push ( op ) ;
482+ lengthChange += op . chars ;
483+ break ;
484+ case '=' :
485+ if ( prevOpcode !== '=' ) yield * flushPlusMinus ( ) ;
486+ keepOps . push ( op ) ;
487+ break ;
488+ }
489+ prevOpcode = op . opcode ;
490+ }
491+
492+ yield * flushPlusMinus ( ) ;
493+ yield * flushKeeps ( finalize ) ;
494+ return lengthChange ;
495+ } ;
496+
441497/**
442498 * Creates an object that allows you to append operations (type Op) and also compresses them if
443499 * possible. Like MergingOpAssembler, but able to produce conforming exportss from slightly looser
@@ -449,49 +505,19 @@ class MergingOpAssembler {
449505 */
450506class SmartOpAssembler {
451507 constructor ( ) {
452- this . _minusAssem = new MergingOpAssembler ( ) ;
453- this . _plusAssem = new MergingOpAssembler ( ) ;
454- this . _keepAssem = new MergingOpAssembler ( ) ;
455- this . _assem = exports . stringAssembler ( ) ;
456- this . _lastOpcode = '' ;
457- this . _lengthChange = 0 ;
458- }
459-
460- _flushKeeps ( ) {
461- this . _assem . append ( this . _keepAssem . toString ( ) ) ;
462- this . _keepAssem . clear ( ) ;
508+ this . clear ( ) ;
463509 }
464510
465- _flushPlusMinus ( ) {
466- this . _assem . append ( this . _minusAssem . toString ( ) ) ;
467- this . _minusAssem . clear ( ) ;
468- this . _assem . append ( this . _plusAssem . toString ( ) ) ;
469- this . _plusAssem . clear ( ) ;
511+ _serialize ( finalize ) {
512+ this . _serialized = serializeOps ( ( function * ( ) {
513+ this . _lengthChange = yield * canonicalizeOps ( this . _ops , finalize ) ;
514+ } ) . call ( this ) ) ;
470515 }
471516
472517 append ( op ) {
473- if ( ! op . opcode ) return ;
474- if ( ! op . chars ) return ;
475-
476- if ( op . opcode === '-' ) {
477- if ( this . _lastOpcode === '=' ) {
478- this . _flushKeeps ( ) ;
479- }
480- this . _minusAssem . append ( op ) ;
481- this . _lengthChange -= op . chars ;
482- } else if ( op . opcode === '+' ) {
483- if ( this . _lastOpcode === '=' ) {
484- this . _flushKeeps ( ) ;
485- }
486- this . _plusAssem . append ( op ) ;
487- this . _lengthChange += op . chars ;
488- } else if ( op . opcode === '=' ) {
489- if ( this . _lastOpcode !== '=' ) {
490- this . _flushPlusMinus ( ) ;
491- }
492- this . _keepAssem . append ( op ) ;
493- }
494- this . _lastOpcode = op . opcode ;
518+ this . _serialized = null ;
519+ this . _lengthChange = null ;
520+ this . _ops . push ( copyOp ( op ) ) ;
495521 }
496522
497523 appendOpWithText ( opcode , text , attribs , pool ) {
@@ -513,24 +539,22 @@ class SmartOpAssembler {
513539 }
514540
515541 toString ( ) {
516- this . _flushPlusMinus ( ) ;
517- this . _flushKeeps ( ) ;
518- return this . _assem . toString ( ) ;
542+ if ( this . _serialized == null ) this . _serialize ( false ) ;
543+ return this . _serialized ;
519544 }
520545
521546 clear ( ) {
522- this . _minusAssem . clear ( ) ;
523- this . _plusAssem . clear ( ) ;
524- this . _keepAssem . clear ( ) ;
525- this . _assem . clear ( ) ;
526- this . _lengthChange = 0 ;
547+ this . _ops = [ ] ;
548+ this . _serialized = null ;
549+ this . _lengthChange = null ;
527550 }
528551
529552 endDocument ( ) {
530- this . _keepAssem . endDocument ( ) ;
553+ this . _serialize ( true ) ;
531554 }
532555
533556 getLengthChange ( ) {
557+ if ( this . _lengthChange == null ) this . _serialize ( false ) ;
534558 return this . _lengthChange ;
535559 }
536560}
0 commit comments