/*
*  A lexer implementation for the LibreSignage markup transpiler.
*  The token definitions are in lexer_defs.js.
*/

var lexeme = require('./lexeme.js');
var ldefs = require('./lexer_defs.js');
var lpost = require('./lexer_post.js');
var err = require('./error.js');
var util = require('util');


function tok_match(tok, str, index) {
	let ret = null;
	for (let re of tok.re) {
		ret = str.substring(index).match(re);
		if (ret) { return ret; }
	}
	return null;
}

function tok_context(tok, lexemes, index) {
	/*
	*  Token context checking function.
	*
	*  This function checks the current lexeme array against
	*  the context defined in tok.context using index as the
	*  reference point. tok is a token defined in
	*  ldefs.tokens, which contains a context array of the
	*  following form.
	*
	*  context = [
	*    { '-2': 'TOKEN_NAME_1', '-1': 'TOKEN_NAME_2'}, // Case 1
	*    {'-1': 'TOKEN_NAME_2'}, // Case 2
	*  ];
	*
	*  The context array is basically an array of objects
	*  with indices relative to the current lexeme position
	*  in the already processed lexemes array. (0 is the
	*  lexeme corresponding to tok, -1 is the previous one
	*  etc.). The values associated with the keys are the
	*  token types that are expected to be found at the
	*  corresponding position for the token context to match.
	*  A token type can be prefixed with an exclamation mark
	*  (!) to negate the meaning of that specific token, eg.
	*  match if the token at this index is NOT this token.
	*  Each individual object in the context array is a separate
	*  case that is used to check the context. The cases are
	*  matched in the order they appear in the array and the
	*  matching ends as soon as one of the cases has matched.
	*
	*  If this function finds a context match, the corresponding
	*  case object is returned. Otherwise null is returned.
	*/
	let ret = null;
	let mtype = '';
	let inv = false;

	if (!tok.context) { return -1; }
	for (let mcase in tok.context) {
		for (let i in tok.context[mcase]) {
			if (tok.context[mcase][i].charAt(0) != '!') {
				// Normal match.
				mtype = tok.context[mcase][i];
				inv = false;
			} else {
				// Inverted match.
				mtype = tok.context[mcase][i].substring(1);
				inv = true;
			}

			let tmp = index + parseInt(i, 10);
			if (tmp < 0 && inv) {
				// Match index OOB but matching inverted -> continue.
				ret = mcase;
				continue;
			} else if (tmp < 0) {
				// Match index OOB -> break.
				ret = null;
				break;
			}

			if ((lexemes[tmp].type == mtype) == inv) {
				ret = null;
				break;
			}
			ret = mcase;
		}
		if (ret) { return ret; }
	}
	return null;
}

module.exports.process = function(str) {
	/*
	*  Main lexer processing function.
	*
	*  This function loops over the input string, matching it
	*  against the lexer tokens in ldefs.tokens. If a token
	*  matches at the current position ln_pos, the respective
	*  lexeme is extracted from the string and it's context is
	*  checked using tok_context(). (See tok_context() for more
	*  info on the token context concept.) If the context matches
	*  as well, the lexeme is pushed onto the ret array and the
	*  ln_pos variable is advanced by the length of the extracted
	*  lexeme. After all lines in str have been processed, the
	*  ret array is passed to the post-processing function
	*  lpost.lexer_post() and the resulting array is returned.
	*/
	let ret = [];
	let ln_pos = 0;
	let ln_num = 0;
	let l = null;
	let l_index = 0;
	let tok = null;
	let match = null;
	let mc = null;

	lines = str.split("\n");
	for (let ln of lines) {
		while (ln_pos < ln.length) {
			for (let k in ldefs.tokens) {
				tok = ldefs.tokens[k];
				if (tok.nomatch) { continue; }
				match = tok_match(tok, ln, ln_pos);
				if (match) {
					l = new lexeme.Lexeme(k, match[0], ln_num, ln_pos);
					mc = tok_context(tok, ret.concat(l), l_index);
					if (mc != null) {
						ret.push(l);
						ln_pos += match[0].length;
						l_index++;
						break;
					} else {
						l = null;
					}
				}
			}
			if (!l) { break; }
			l = null;
		}
		ln_pos = 0;
		ln_num++;
	}
	return lpost.lexer_post(ret);
}
