<reponame>swiing/parser
/**
 *  (c)2021 - swiing
 *  parser@1.0.0
 *  A minimalist, generic-purpose parser for simple user-defined languages.
 */

/**
 * A language spec, that can be extended
 */

// @minify: for production, I could get rid of this, and provide the full
// language at once, without splitting into pieces (extensions).

// If I want to build a minified language, I should set the environment 
// variable to true.
// For a minified language, words a single characters (except for those words
// which are used as keys outside of the language itself). That means I can get 
// rid of spaces (in alternates and phrases), and of "|" (in alternates).
const minifiedlang = process.env.minifiedlang || false;

class Language {

  /** 
   *  @param {Object} spec in the form
   *  { terminals, phrases, alternates, semantics }
   */
  constructor( spec ) {
    this.extend( spec );
  }

  /** 
   *  @param {Object} spec in the form
   *  { terminals, phrases, alternates, semantics }
   *  @return this
   */
  extend( spec ) {
    let
      me = this,
      key,
      separator = " | ";
	  
    // avoid duplicates 
    // see https://medium.com/dailyjs/how-to-remove-array-duplicates-in-es6-5daa8789641c
    function unique( string ) {
      return [...new Set( string.split( separator ) )].join ( separator )
    }

    // alternates come on top of the existing alternates
    for( key in spec.alternates )
      if( me.alternates && me.alternates[ key ] )
		// alternates are separated by "|", but in case of minified language
	    // there is no separator at all between altenates (which are single
		// characters).
        spec.alternates[ key ] = unique( me.alternates[ key ]
		                             + separator + spec.alternates[ key ] );

    if( minifiedlang ) {
      // remove any space in phrases
      for( key in spec.phrases )
        spec.phrases[ key ] = spec.phrases[ key ].replace( /\s+/g, "" );
      // remove any space and | in alternates
      for( key in spec.alternates )
        spec.alternates[ key ] = spec.alternates[ key ].replace( /\s+|\|/g, "" );
    }
	
    // merge the extension into this
    for( key in spec )
      me[key] = Object.assign( me[key] || {}, spec[key] );
    
    // allow chaining extensions
    return me
  }

}
// eof util/language

export default Language;
