text: import configs from './configs.js';
  
  /**
   * Takes an array of tag function names and returns a settings object that can be used to find tag
   * functions in strings with template literals. The returned object is meant to be used by the
   * `splitFragments` function.
   *
   * @function createTagSettings
   * @param {(string[]|Object)} tags A array of tag function names of the tags object (where keys
   *     are the tag function names and the values are the tag functions).
   * @returns {Object} A "tag settings" object or null if the provided `tags` is empty. The "tag
   *     settings" object contains the following attributes:
   *         * `longestTagLength`: The length of the longest tag.
   *         * `shortestTagLength`: The length of the shortest tag.
   *         * `tagsExp`: A regular expression to test if a string ends with one of the given tags
   *             prefixed with an optional "await".
   */
  export function createTagSettings(tags) {
  	if (!Array.isArray(tags) && typeof tags !== 'object') {
  		throw new TypeError('The option `tags` must be an array of strings or an object.');
  	}
  
  	const sorted = [];
  	const tagNames = tags && !Array.isArray(tags) ? Object.keys(tags) : tags;
  
  	if (tagNames.length === 0) {
  		return null;
  	}
  
  	// Iterate over `tagNames` to sort them, in descending order, by their length.
  	for (const tag of tagNames) {
  		// Find the index of a tag that is shorter than `tag`.
  		const i = sorted.findIndex(it => (it.length < tag.length));
  
  		// If found, insert `tag` before the shorter tag. Otherwise, add it to the end of `sorted`.
  		if (i !== -1) {
  			sorted.splice(i, 0, tag);
  		} else {
  			sorted.push(tag);
  		}
  	}
  
  	return {
  		longestTagLength: sorted[0].length,
  		shortestTagLength: sorted[sorted.length - 1].length,
  		tagsExp: new RegExp(`(await )?(${ sorted.join('|') })$`),
  	};
  }
  
  /**
   * Splits a string into an array of fragments. A fragment represents a portion of the `input`
   * string that is an expression, a template literal, or a string. Returns `null` if `input`
   * doesn't contain any expressions or template literals. For instance:
   *
   * ╔═════════════════════════════════════════╦═════════════════════════════════════════════════╗
   * ║ Input string                            ║ Result                                          ║
   * ╠═════════════════════════════════════════╬═════════════════════════════════════════════════╣
   * ║ 'String with no fragments'              ║ null                                            ║
   * ╠═════════════════════════════════════════╬═════════════════════════════════════════════════╣
   * ║ '${ host.price }'                       ║ [{ hasAwait: false, expression: 'host.price' }] ║
   * ╠═════════════════════════════════════════╬═════════════════════════════════════════════════╣
   * ║                                         ║ [                                               ║
   * ║                                         ║     'btn btn-',                                 ║
   * ║                                         ║     {                                           ║
   * ║ 'btn btn-${ host.btnType }'             ║         hasAwait: false,                        ║
   * ║                                         ║         expression: 'host.btnType'              ║
   * ║                                         ║     }                                           ║
   * ║                                         ║ ]                                               ║
   * ╠═════════════════════════════════════════╬═════════════════════════════════════════════════╣
   * ║                                         ║ [{                                              ║
   * ║                                         ║     tag: undefined,                             ║
   * ║ '`btn btn-${ host.btnType }`'           ║     hasAwait: false,                            ║
   * ║                                         ║     template: '`btn btn-${ host.btnType }`'     ║
   * ║                                         ║ }]                                              ║
   * ╠═════════════════════════════════════════╬═════════════════════════════════════════════════╣
   * ║                                         ║ [                                               ║
   * ║                                         ║     {                                           ║
   * ║                                         ║         tag: 'i18n',                            ║
   * ║                                         ║         hasAwait: false,                        ║
   * ║                                         ║         template: '`price`'                     ║
   * ║ 'i18n`price`: $${ this.dataset.price }' ║     },                                          ║
   * ║                                         ║     '": $"',                                    ║
   * ║                                         ║     {                                           ║
   * ║                                         ║         hasAwait: false,                        ║
   * ║                                         ║         expression: 'this.dataset.price'        ║
   * ║                                         ║     }                                           ║
   * ║                                         ║ ]                                               ║
   * ╚═════════════════════════════════════════╩═════════════════════════════════════════════════╝
   *
   * The last example contains a tagged template literal. In order to identify tags, the "tags"
   * configuration object must be set first. For instance:
   *
   *     import { setConfigs } from 'path/to/configs.js';
   *     import splitFragments, { createTagSettings } from 'path/to/splitFragments.js';
   *     // Pass all tag names that could be found in the string.
   *     setConfigs({ tags: ['i18n', 'currency', 'date'] });
   *     // Template literals can be tagged using any tag name in the "tags" configuration.
   *     const input = 'i18n`price`: $${ this.dataset.price }';
   *     const result = splitFragments(input);
   *     console.log(result); // Logs the result array shown in the last example above.
   *
   * @function splitFragments
   * @param {string} input A string that may or may not contain expressions and/or template literals.
   * @returns {Array} An array of fragments or `null` if the input string doesn't contain any
   *     expressions or template literals.
   */
  export default function splitFragments(input) {
  	// Container for all fragments.
  	const fragments = [];
  	const tags = configs.get('tags');
  	const settings = createTagSettings(tags);
  
  	/**
  	 * These variables are used in the for loop below to iterate over each character in `input`.
  	 */
  	// `a` is the previous character, the one before position `i`.
  	let a = '';
  	// `b` is the current character, the one at position `i`.
  	let b = input.charAt(0);
  	// `c` is the next character, the one after position `i`.
  	let c = input.charAt(1);
  	/**
  	 * Index position, within `input`, where a template literal starts. The value -1 indicates
  	 * that the start of template literal has not been found during the iteration.
  	 */
  	let templateStart = -1;
  	/**
  	 * Index position, within `input`, where an expression starts. The value -1 indicates that
  	 * the start of an expression has not been found during the iteration.
  	 */
  	let expressionStart = -1;
  	/**
  	 * Number of opened curly braces. This is used during the iteration to make sure
  	 * expressions are well balanced.
  	 */
  	let openedBraces = 0;
  
  	/**
  	 * Iterate over each character in `input`.
  	 * The variable `h` is the index position after the last parsed "fragment" and `i` is the index
  	 * position of the current character. After each iteration, `i` is incremented and the values
  	 * for `a`, `b`, and `c` are updated accordingly.
  	 */
  	for (let h = 0, i = 0; i < input.length; a = b, b = c, c = input.charAt(++i + 1)) {
  		/**
  		 * If the current character is a backtick and there's not an expression currently
  		 * started ("${") prior to the current index position `i`. Backticks within expressions
  		 * are ignored.
  		 */
  		if (b === '`' && expressionStart === -1) {
  			/**
  			 * If there's backtick prior to the current index position `i` that started a template
  			 * literal, then the current character (a backtick) closes the template literal, which
  			 * must be extracted from `input` and added to `fragments`.
  			 */
  			if (templateStart !== -1) {
  				let match, hasAwait, tag;
  
  				/**
  				 * Do not proceed (continue to the next iteration) if the backtick is escaped. A
  				 * backtick within a template literal is "escaped" if it's preceded by a backslash.
  				 */
  				if (a === '\\') {
  					continue;
  				}
  
  				/**
  				 * If the template literal starts after `h`, we need to extract the string prior
  				 * to the start of the template (from position `h` to `templateStart`, but not
  				 * including `templateStart`) and add it as a "string fragment" to `fragments`.
  				 */
  				if (h < templateStart) {
  					let j = templateStart;
  
  					/**
  					 * If the string "fragment" is as long or longer than the shortest registered
  					 * tag, the template literal could be tagged. If the template literal is
  					 * tagged, the name of the tag function, preceded by an optional "await ",
  					 * would be at the end of the string fragment.
  					 */
  					if (settings && (j - h) >= settings.shortestTagLength) {
  						/**
  						 * Subtract the length of the longest registered tag and the length of
  						 * "await " from `j`. If the template literal is tagged, the name of the
  						 * tag function and the optional "await " will be contained within `j` and
  						 * the `templateStart`.
  						 */
  						j -= settings.longestTagLength + 6;
  
  						/**
  						 * Subtract the piece of string from `j` (or from `h`, if `j` is less
  						 * than `h`) to the `templateStart` to search for a tag function and an
  						 * optional "await ".
  						 */
  						const pre = input.substring(j < h ? h : j, templateStart);
  						// Perform the search.
  						const result = pre.match(settings.tagsExp) || [];
  
  						/**
  						 * If a `result` was found, the first position will be the full string
  						 * that matches the regular expression, second position will indicate if
  						 * an "await " was found, and third position will be the name of the tag
  						 * function.
  						 */
  						[match, hasAwait, tag] = result;
  
  						/**
  						 * Assign `templateStart` to `j` minus the length of the `match` if a
  						 * a match was found.
  						 */
  						j = templateStart - (match ? match.length : 0);
  					}
  
  					// If `h < j`, subtract the "string fragment" and add it to `fragments`.
  					if (h < j) {
  						fragments.push(input.substring(h, j));
  					}
  				}
  
  				// Extract the template literal and add a "template fragment" object to `fragments`.
  				fragments.push({
  					tag,
  					hasAwait: !!hasAwait,
  					template: input.substring(templateStart, i + 1),
  				});
  
  				/**
  				 * Now that the "template fragment" was extracted, `h` needs to be updated to the
  				 * next index position in the iteration and `templateStart` is changed back to -1
  				 * indicating that there's no template currently started/opened.
  				 */
  				h = i + 1;
  				templateStart = -1;
  			} else if (c !== '`') {
  				/**
  				 * If the next character is not a backtick, then the current backtick is the start
  				 * of a template literal.
  				 */
  				templateStart = i;
  			}
  		} else if (b === '{') {
  			if (expressionStart !== -1) {
  				// Keep track of the number of opened braces within an expression.
  				openedBraces++;
  			} else if (a === '$') {
  				/**
  				 * The prior index position is the start of an expression if the prior character
  				 * is "$" and the current character is "{".
  				 */
  				expressionStart = i - 1;
  			}
  		} else if (b === '}' && expressionStart !== -1) {
  			// If the current character is "}" and there's an expression currently started/opened.
  			if (openedBraces) {
  				// If there are `openedBraces`, `b` closes the last open bracket.
  				openedBraces--;
  			} else if (templateStart !== -1) {
  				/**
  				 * If the expression is within a template literal simply change `expressionStart`
  				 * back to -1 indicating that there's no expression currently started/opened.
  				 */
  				expressionStart = -1;
  			} else {
  				/**
  				 * If there's no template started/opened, the "expression fragment" must be added
  				 * to `fragments`.
  				 */
  
  				// If there's a "string fragment" before the expression, add it to `fragments`.
  				if (h < expressionStart) {
  					fragments.push(input.substring(h, expressionStart));
  				}
  
  				// Extract the expression and check if it has an await.
  				const expression = input.substring(expressionStart + 2, i).trim();
  				const hasAwait = expression.indexOf('await ') !== -1;
  
  				// Add the expression to `fragments`.
  				fragments.push({ hasAwait, expression });
  
  				/**
  				 * Now that the "expression fragment" was extracted, `h` needs to be updated to the
  				 * next index position in the iteration and `expressionStart` is changed back to
  				 * -1 indicating that there's no expression currently started/opened.
  				 */
  				h = i + 1;
  				expressionStart = -1;
  			}
  		}
  
  		if ((i + 1) === input.length && h <= i) {
  			/**
  			 * If we're at the last iteration, the last "string fragment" (everything after `h`)
  			 * needs to be added to `fragments`.
  			 */
  			fragments.push(input.substr(h));
  		}
  	}
  
  	if (fragments.length === 0 || (fragments.length === 1 && fragments[0] === input)) {
  		return null;
  	}
  
  	return fragments;
  }
  
  /**
   * Generates an expression by joining all the `fragments` together separated by a "+" sign. When
   * evaluated, the returned expression concatenates together all the resulting values of each
   * fragment.
   *
   * @function joinFragments
   * @param {string[]} fragments The parsed fragments.
   * @param {boolean} [isEventCallback=false] If `true`, the returned expression will be an arrow
   *     function that takes an `event` argument.
   * @returns {string}
   */
  export function joinFragments(fragments, isEventCallback = false) {
  	let isAsync = false;
  	const tagsName = configs.get('tagsName');
  	const eventName = configs.get('eventName');
  
  	let src = fragments.map((fragment) => {
  		if (typeof fragment === 'string') {
  			return JSON.stringify(fragment);
  		}
  
  		if (fragment.hasAwait) {
  			isAsync = true;
  		}
  
  		if (fragment.expression) {
  			return `(${ fragment.expression })`;
  		}
  
  		// The fragment is a template literal.
  		let { template } = fragment;
  
  		// If the fragment had a tag function, add it before the template literal.
  		if (fragment.tag) {
  			template = `${ tagsName }.${ fragment.tag }${ template }`;
  		}
  
  		return fragment.hasAwait ? `(await ${ template })` : template;
  	});
  
  	/**
  	 * If there are multiple fragments and the first one is an expression (which, when evaluated,
  	 * might not be string), use implicit coercion (add an empty string at the beginning) to make
  	 * sure that the result of evaluating all the fragments together is a string.
  	 */
  	if (fragments.length > 1 && fragments[0].expression) {
  		src.splice(0, 0, '""');
  	}
  
  	src = src.join(' + ');
  
  	if (isEventCallback) {
  		src = `${ isAsync ? 'async ' : '' }(${ eventName }) => ${ src }`;
  	}
  
  	return src;
  }