UNPKG

emk

Version:

ECMAScript Make - a powerful build tool for the JavaScript elite

385 lines (310 loc) 8.79 kB
const braces = require('braces'); const F_TEXT_TO_REGEX = s => s.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'); const F_GLOB_TO_REGEX = (s, s_wildcard_quantifier='+') => s .replace(/[-/\\^$+?.()|[\]{}]/g, '\\$&') .replace(/[*]/g, `([^/]${s_wildcard_quantifier})`); class pattern_fragment { static from_struct(k_emk, g_fragment) { return h_eval[g_fragment.type](k_emk, g_fragment); } constructor(g_this) { Object.assign(this, g_this); } bind(s_binding) { return new this.constructor({ ...this, binding: s_binding, }); } // textual() { // let g_eval = this.eval; // return !!(g_eval.text || g_eval.list); // } // test(s_target) { // let g_eval = this.eval; // // text // if(g_eval.text) return s_target === g_eval.text; // // list // if(g_eval.list) return g_eval.list.some(s => s === g_eval.text); // // pattern // return g_eval.pattern.test(s_target); // } // match(r_pattern) { // let g_eval = this.eval; // // text // if(g_eval.text) return r_pattern.test(g_eval.text)? [g_eval.text]: []; // // list // if(g_eval.list) return g_eval.list.filter(s => r_pattern.test(s)); // // pattern // throw new Error(`encountered pattern on pattern match`); // } } class pattern_fragment_text extends pattern_fragment { static from_text(k_emk, s_text) { return new pattern_fragment_text({ emk: k_emk, text: s_text, // regex: s_text.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'), group_count: 0, }); } to_regex() { return F_TEXT_TO_REGEX(this.text); } test_text(s_test) { return s_test === this.text; } test_pattern(r_pattern) { return r_pattern.test(this.text); } } class pattern_fragment_enum extends pattern_fragment { static from_list(k_emk, a_enum, s_binding) { // // convert to regex // let s_pattern = a_enum.map(F_TEXT_TO_REGEX).join('|'); // { // type: 'enumeration', // value: z_value, // eval: { // list: z_value, // regex: `(${s_pattern})`, // length: (new RegExp(s_pattern+'|')).exec('').length, // binding: s_key, // }, // }; return new pattern_fragment_enum({ emk: k_emk, enum: a_enum, combos: a_enum.map(s => ({ bindings: { [s_binding]: s, }, value: s, })), }); } static from_combos(k_emk, a_combos) { return new pattern_fragment_enum({ emk: k_emk, enum: a_combos.map(({ value: s, }) => s), combos: a_combos, }); } to_regex() { return `(${this.enum.map(s => F_TEXT_TO_REGEX(s)).join('|')})`; } test_text(s_test) { return this.enum.some(s => s_test === s); } match_pattern(r_pattern) { for(let s_item of this.enum) { if(r_pattern.test(s_item)) { return s_item; } } return null; } bind_pattern(s_key) { } } class match extends Array { constructor(z_base) { super(); this.push(z_base); } matches() { return [...this]; } toString() { return this[0]; } } // given string prototype methods for(let s_method in String.prototype) { if(!Array.prototype[s_method]) { match.prototype[s_method] = function(...a_args) { return String.prototype[s_method].apply(this.toString(), a_args); }; } // already in array else { match.prototype[s_method] = function(...a_args) { throw new Error(`attempting to call '.${s_method}(${a_args.join(', ')})' on match object, which could be for either String or Array. to call on String, first cast the object to a string (e.g., +''). to call on Array of matches, cast to array by calling member '.matches()'`); }; } } class pattern_fragment_regex extends pattern_fragment { static from_regex_str(k_emk, s_regex, s_name='_', n_groups=null) { return new pattern_fragment_regex({ emk: k_emk, regex: new RegExp(`^(${s_regex})$`), group_count: null === n_groups? (new RegExp(s_regex+'|')).exec('').length: n_groups, binding: s_name || '_', }); } static from_glob_str(k_emk, s_glob, s_name, n_groups=null) { // // create def struct // h_defs[s_key] = { // type: 'glob', // value: z_value, // eval: fragment_types.glob({value:z_value}, this), // }; let s_regex = F_GLOB_TO_REGEX(s_glob, '*'); return new pattern_fragment_regex({ emk: k_emk, regex: new RegExp(`^(${s_regex})$`), group_count: null === n_groups? (new RegExp(s_regex+'|')).exec('').length: n_groups, binding: s_name || '_', }); } to_regex() { return this.regex; } test_text(s_test) { return this.regex.test(s_test); } match_text(s_text) { // exec regex let m_match = this.regex.exec(s_text); // no match if(!m_match) return null; // prep matches let h_matches = {}; // create groups let a_groups = [this.binding]; for(let i_add=0, s_var=this.binding; i_add<this.group_count; i_add++) { a_groups.push(s_var); } // align groups for(let i_group=0, nl_match=m_match.length; i_group<nl_match-2; i_group++) { let s_var = a_groups[i_group]; if(!(s_var in h_matches)) h_matches[s_var] = new match(m_match[i_group+2]); else h_matches[s_var].push(m_match[i_group+2]); } // hash of matches return h_matches; } } const permutate = (a_frags, s_current='', a_combos=[], h_bindings={}) => { if(!a_frags.length) { a_combos.push({ bindings: h_bindings, value: s_current, }); } let k_frag = a_frags[0]; let a_subfrags = a_frags.slice(1); if(k_frag instanceof pattern_fragment_text) { permutate(a_subfrags, s_current+k_frag.text, a_combos, h_bindings); } else if(k_frag instanceof pattern_fragment_enum) { for(let s_text of k_frag.enum) { permutate(a_subfrags, s_current+s_text, a_combos, { ...h_bindings, [k_frag.binding]: s_text, }); } } return a_combos; }; let h_eval = { text: (k_emk, g) => pattern_fragment_text.from_text(k_emk, g.value), glob: (k_emk, g) => { let s_regex = braces(`/${g.value}/`); // globbing if(s_regex.includes('*')) { return pattern_fragment_regex.from_regex_str(k_emk, s_regex.slice(1, -1), g.name); } // no globbing! use expansion else { return pattern_fragment_enum.from_list(k_emk, braces.expand(`/${g.value}/`)); } // convert glob string to regular expression // g.value.replace(/\{([^,}]*)(,?)\}/g, '($1'); // g.value.replace(/\{/g, '('); }, pattern: (k_emk, g_pattern) => { // merge adjacent text let g_prev = null; let a_values = []; for(let g_sub of g_pattern.value) { if(g_prev && 'text' === g_prev.type && 'text' === g_sub.type) { g_prev.value += g_sub.value; } else { g_prev = g_sub; a_values.push(g_sub); } } // map to fragments let a_frags = a_values.map(g => pattern_fragment.from_struct(k_emk, g)); // single item if(1 === a_frags.length) return a_frags[0]; // no patterns; make all permutations if(a_frags.every(k => !(k instanceof pattern_fragment_regex))) { return pattern_fragment_enum.from_combos(k_emk, permutate(a_frags)); } // make pattern else { return pattern_fragment_regex.from_regex_str(k_emk, a_frags.map(k => k.to_regex()).join('')); } }, regex: (k_emk, g) => pattern_fragment_regex.from_regex_str(k_emk, g.value), reference: (k_emk, g) => k_emk.defs[g.value], label: (k_emk, g) => { // reference if(g.value in k_emk.defs) { return k_emk.defs[g.value].bind(g.value); } // word capture else { return pattern_fragment_regex.from_regex_str(k_emk, `([^/]*?)`, g.value, 1); } }, // if(g.assignment) { // let g_assignment = g_label.assignment; // let a_assign = h_eval[g_assignment.type](g_assignment, k_mk); // a_assign[2] = g_label.value; // return a_assign; // } capture_glob: (k_emk, g) => { let g_value = g.value; let s_value = h_eval[g_value.type](k_emk, g_value); return pattern_fragment_regex.from_regex_str(k_emk, s_value, g.name); }, capture_regex: (k_emk, g) => { let g_value = g.value; let a_value = h_eval[g_value.type](k_emk, g_value); return pattern_fragment_regex.from_regex_str(k_emk, a_value[0], g.name, a_value[1]); }, // capture: (g_capture, k_mk) => { // let g_contents = g_capture.value; // // simple pattern // if('pattern' === g_contents.type) { // let g_pattern = g_contents.value; // return h_eval[g_pattern.type](g_pattern, k_mk); // } // // labelled pattern use // else if('label' === g_contents.type) { // let g_assignment = g_contents.assignment; // return [...h_eval[g_assignment.type](g_assignment, k_mk).slice(0, -1), g_contents.label]; // } // // unrecognized type // else { // throw new Error(`unexpected token type: ${g_contents.type}`); // } // }, }; module.exports = { evals: h_eval, // execute on target string compile: g_fragment => ({ bind: k_emk => pattern_fragment.from_struct(k_emk, g_fragment), }), pattern_fragment, pattern_fragment_text, pattern_fragment_enum, pattern_fragment_regex, };