@@ -344,21 +344,32 @@ export class ContextFreeGrammar {
344344}
345345
346346public del ( ) :ContextFreeGrammar ;
347- public del ( step :( cfg : ContextFreeGrammar ) => void ) :ContextFreeGrammar ;
348- public del ( step ?:( cfg : ContextFreeGrammar ) => void ) :ContextFreeGrammar {
349- const productions = new Map < string , Production > ( ) ;
347+ public del ( step :StepCallback < ContextFreeGrammar > ) :ContextFreeGrammar ;
348+ public del ( step ?:StepCallback < ContextFreeGrammar > ) :ContextFreeGrammar {
349+ const productions = new Map < string , Array < Array < Token > > > ( [ ... this . productions ] . map ( ( [ nt , p ] ) => [ nt , p . map ( s => [ ... s ] ) ] ) ) ;
350350
351351const nullableNonTerminals = new Set < string > ( this . nullableNonTerminals ( ) ) ;
352352const nullNonTerminals = new Set < string > ( this . nullNonTerminals ( nullableNonTerminals ) ) ;
353353
354- for ( const [ nt , p ] of [ ...this . productions ] . sort ( ( [ a , _a ] , [ b , _b ] ) => sortBySymbolButFirst ( a , b , this . start ) ) ) {
354+ for ( const [ nt , p ] of [ ...productions ] . sort ( ( [ a , _a ] , [ b , _b ] ) => sortBySymbolButFirst ( a , b , this . start ) ) ) {
355+ let changeHappened = false ;
356+ let changeDescription :string = null ;
355357if ( ! nullNonTerminals . has ( nt ) ) {
356- const production = new Array < ReadonlyArray < Token > > ( ) ;
358+ const factoredOut = new Array < string > ( ) ;
359+ const removed = new Array < string > ( ) ;
360+ let removedEmpty = false ;
361+ let production = new Array < Array < Token > > ( ) ;
357362for ( const s of p ) {
358363const sequences = new Array < Array < Token > > ( new Array < Token > ( ) ) ;
359364for ( const token of s ) {
360- if ( token . kind === TokenKind . Empty ||
361- ( token . kind === TokenKind . NonTerminal && nullNonTerminals . has ( token . identifier ) ) ) {
365+ if ( token . kind === TokenKind . Empty ) {
366+ removedEmpty = true ;
367+ changeHappened = true ;
368+ continue ;
369+ }
370+ else if ( token . kind === TokenKind . NonTerminal && nullNonTerminals . has ( token . identifier ) ) {
371+ removed . push ( token . identifier ) ;
372+ changeHappened = true ;
362373continue ;
363374}
364375else if ( token . kind === TokenKind . NonTerminal && nullableNonTerminals . has ( token . identifier ) ) {
@@ -367,6 +378,8 @@ export class ContextFreeGrammar {
367378sequences . push ( [ ...sequences [ i ] ] ) ;
368379sequences [ i ] . push ( token ) ;
369380}
381+ factoredOut . push ( token . identifier ) ;
382+ changeHappened = true ;
370383}
371384else {
372385for ( const sequence of sequences ) {
@@ -376,21 +389,44 @@ export class ContextFreeGrammar {
376389}
377390production . push ( ...sequences . filter ( s => s . length > 0 ) ) ;
378391}
379- productions . set ( nt , production ) ;
380- step ?.( new ContextFreeGrammar (
381- new Set < string > ( [
382- ...productions . keys ( ) ,
383- ...[ ...productions . values ( ) ] . flatMap ( p =>
384- p . flatMap ( s =>
385- s . filter ( t => t . kind === TokenKind . NonTerminal )
386- . map ( t => t . identifier )
387- )
388- )
389- ] ) ,
390- this . terminals ,
391- productions ,
392- this . start ,
393- ) ) ;
392+ production = production . filter ( a => a . length > 0 ) ;
393+ if ( production . length > 0 ) {
394+ productions . set ( nt , production ) ;
395+ if ( changeHappened ) {
396+ const changes = new Array < string > ( ) ;
397+ if ( removed . length > 0 ) {
398+ changes . push ( `null producing non-terminal${ removed . length > 1 ?"s" :"" } ${ descriptionNaturalList ( removed ) } were removed` ) ;
399+ }
400+ if ( factoredOut . length > 0 ) {
401+ changes . push ( `nullable non-terminal${ factoredOut . length > 1 ?"s" :"" } ${ descriptionNaturalList ( factoredOut ) } were factored out` ) ;
402+ }
403+ if ( removedEmpty ) {
404+ changes . push ( "empty string tokens were removed" ) ;
405+ }
406+ changeDescription = `From the production of${ nt } ,${ descriptionNaturalList ( changes ) } .` ;
407+ }
408+ }
409+ else {
410+ productions . delete ( nt ) ;
411+ changeHappened = true ;
412+ changeDescription = `The non-terminal${ nt } only produced the empty string, and where thus removed.` ;
413+ }
414+ }
415+ else {
416+ productions . delete ( nt ) ;
417+ changeHappened = true ;
418+ changeDescription = `The non-terminal${ nt } only produced the empty string, and where thus removed.` ;
419+ }
420+ if ( changeHappened ) {
421+ step ?.(
422+ new ContextFreeGrammar (
423+ productions . keys ( ) ,
424+ this . terminals ,
425+ productions ,
426+ this . start ,
427+ ) ,
428+ changeDescription
429+ ) ;
394430}
395431}
396432