Spaces:
Runtime error
Runtime error
| ; | |
| const parse = require('../definition-syntax/parse.cjs'); | |
| const MATCH = { type: 'Match' }; | |
| const MISMATCH = { type: 'Mismatch' }; | |
| const DISALLOW_EMPTY = { type: 'DisallowEmpty' }; | |
| const LEFTPARENTHESIS = 40; // ( | |
| const RIGHTPARENTHESIS = 41; // ) | |
| function createCondition(match, thenBranch, elseBranch) { | |
| // reduce node count | |
| if (thenBranch === MATCH && elseBranch === MISMATCH) { | |
| return match; | |
| } | |
| if (match === MATCH && thenBranch === MATCH && elseBranch === MATCH) { | |
| return match; | |
| } | |
| if (match.type === 'If' && match.else === MISMATCH && thenBranch === MATCH) { | |
| thenBranch = match.then; | |
| match = match.match; | |
| } | |
| return { | |
| type: 'If', | |
| match, | |
| then: thenBranch, | |
| else: elseBranch | |
| }; | |
| } | |
| function isFunctionType(name) { | |
| return ( | |
| name.length > 2 && | |
| name.charCodeAt(name.length - 2) === LEFTPARENTHESIS && | |
| name.charCodeAt(name.length - 1) === RIGHTPARENTHESIS | |
| ); | |
| } | |
| function isEnumCapatible(term) { | |
| return ( | |
| term.type === 'Keyword' || | |
| term.type === 'AtKeyword' || | |
| term.type === 'Function' || | |
| term.type === 'Type' && isFunctionType(term.name) | |
| ); | |
| } | |
| function buildGroupMatchGraph(combinator, terms, atLeastOneTermMatched) { | |
| switch (combinator) { | |
| case ' ': { | |
| // Juxtaposing components means that all of them must occur, in the given order. | |
| // | |
| // a b c | |
| // = | |
| // match a | |
| // then match b | |
| // then match c | |
| // then MATCH | |
| // else MISMATCH | |
| // else MISMATCH | |
| // else MISMATCH | |
| let result = MATCH; | |
| for (let i = terms.length - 1; i >= 0; i--) { | |
| const term = terms[i]; | |
| result = createCondition( | |
| term, | |
| result, | |
| MISMATCH | |
| ); | |
| } | |
| return result; | |
| } | |
| case '|': { | |
| // A bar (|) separates two or more alternatives: exactly one of them must occur. | |
| // | |
| // a | b | c | |
| // = | |
| // match a | |
| // then MATCH | |
| // else match b | |
| // then MATCH | |
| // else match c | |
| // then MATCH | |
| // else MISMATCH | |
| let result = MISMATCH; | |
| let map = null; | |
| for (let i = terms.length - 1; i >= 0; i--) { | |
| let term = terms[i]; | |
| // reduce sequence of keywords into a Enum | |
| if (isEnumCapatible(term)) { | |
| if (map === null && i > 0 && isEnumCapatible(terms[i - 1])) { | |
| map = Object.create(null); | |
| result = createCondition( | |
| { | |
| type: 'Enum', | |
| map | |
| }, | |
| MATCH, | |
| result | |
| ); | |
| } | |
| if (map !== null) { | |
| const key = (isFunctionType(term.name) ? term.name.slice(0, -1) : term.name).toLowerCase(); | |
| if (key in map === false) { | |
| map[key] = term; | |
| continue; | |
| } | |
| } | |
| } | |
| map = null; | |
| // create a new conditonal node | |
| result = createCondition( | |
| term, | |
| MATCH, | |
| result | |
| ); | |
| } | |
| return result; | |
| } | |
| case '&&': { | |
| // A double ampersand (&&) separates two or more components, | |
| // all of which must occur, in any order. | |
| // Use MatchOnce for groups with a large number of terms, | |
| // since &&-groups produces at least N!-node trees | |
| if (terms.length > 5) { | |
| return { | |
| type: 'MatchOnce', | |
| terms, | |
| all: true | |
| }; | |
| } | |
| // Use a combination tree for groups with small number of terms | |
| // | |
| // a && b && c | |
| // = | |
| // match a | |
| // then [b && c] | |
| // else match b | |
| // then [a && c] | |
| // else match c | |
| // then [a && b] | |
| // else MISMATCH | |
| // | |
| // a && b | |
| // = | |
| // match a | |
| // then match b | |
| // then MATCH | |
| // else MISMATCH | |
| // else match b | |
| // then match a | |
| // then MATCH | |
| // else MISMATCH | |
| // else MISMATCH | |
| let result = MISMATCH; | |
| for (let i = terms.length - 1; i >= 0; i--) { | |
| const term = terms[i]; | |
| let thenClause; | |
| if (terms.length > 1) { | |
| thenClause = buildGroupMatchGraph( | |
| combinator, | |
| terms.filter(function(newGroupTerm) { | |
| return newGroupTerm !== term; | |
| }), | |
| false | |
| ); | |
| } else { | |
| thenClause = MATCH; | |
| } | |
| result = createCondition( | |
| term, | |
| thenClause, | |
| result | |
| ); | |
| } | |
| return result; | |
| } | |
| case '||': { | |
| // A double bar (||) separates two or more options: | |
| // one or more of them must occur, in any order. | |
| // Use MatchOnce for groups with a large number of terms, | |
| // since ||-groups produces at least N!-node trees | |
| if (terms.length > 5) { | |
| return { | |
| type: 'MatchOnce', | |
| terms, | |
| all: false | |
| }; | |
| } | |
| // Use a combination tree for groups with small number of terms | |
| // | |
| // a || b || c | |
| // = | |
| // match a | |
| // then [b || c] | |
| // else match b | |
| // then [a || c] | |
| // else match c | |
| // then [a || b] | |
| // else MISMATCH | |
| // | |
| // a || b | |
| // = | |
| // match a | |
| // then match b | |
| // then MATCH | |
| // else MATCH | |
| // else match b | |
| // then match a | |
| // then MATCH | |
| // else MATCH | |
| // else MISMATCH | |
| let result = atLeastOneTermMatched ? MATCH : MISMATCH; | |
| for (let i = terms.length - 1; i >= 0; i--) { | |
| const term = terms[i]; | |
| let thenClause; | |
| if (terms.length > 1) { | |
| thenClause = buildGroupMatchGraph( | |
| combinator, | |
| terms.filter(function(newGroupTerm) { | |
| return newGroupTerm !== term; | |
| }), | |
| true | |
| ); | |
| } else { | |
| thenClause = MATCH; | |
| } | |
| result = createCondition( | |
| term, | |
| thenClause, | |
| result | |
| ); | |
| } | |
| return result; | |
| } | |
| } | |
| } | |
| function buildMultiplierMatchGraph(node) { | |
| let result = MATCH; | |
| let matchTerm = buildMatchGraphInternal(node.term); | |
| if (node.max === 0) { | |
| // disable repeating of empty match to prevent infinite loop | |
| matchTerm = createCondition( | |
| matchTerm, | |
| DISALLOW_EMPTY, | |
| MISMATCH | |
| ); | |
| // an occurrence count is not limited, make a cycle; | |
| // to collect more terms on each following matching mismatch | |
| result = createCondition( | |
| matchTerm, | |
| null, // will be a loop | |
| MISMATCH | |
| ); | |
| result.then = createCondition( | |
| MATCH, | |
| MATCH, | |
| result // make a loop | |
| ); | |
| if (node.comma) { | |
| result.then.else = createCondition( | |
| { type: 'Comma', syntax: node }, | |
| result, | |
| MISMATCH | |
| ); | |
| } | |
| } else { | |
| // create a match node chain for [min .. max] interval with optional matches | |
| for (let i = node.min || 1; i <= node.max; i++) { | |
| if (node.comma && result !== MATCH) { | |
| result = createCondition( | |
| { type: 'Comma', syntax: node }, | |
| result, | |
| MISMATCH | |
| ); | |
| } | |
| result = createCondition( | |
| matchTerm, | |
| createCondition( | |
| MATCH, | |
| MATCH, | |
| result | |
| ), | |
| MISMATCH | |
| ); | |
| } | |
| } | |
| if (node.min === 0) { | |
| // allow zero match | |
| result = createCondition( | |
| MATCH, | |
| MATCH, | |
| result | |
| ); | |
| } else { | |
| // create a match node chain to collect [0 ... min - 1] required matches | |
| for (let i = 0; i < node.min - 1; i++) { | |
| if (node.comma && result !== MATCH) { | |
| result = createCondition( | |
| { type: 'Comma', syntax: node }, | |
| result, | |
| MISMATCH | |
| ); | |
| } | |
| result = createCondition( | |
| matchTerm, | |
| result, | |
| MISMATCH | |
| ); | |
| } | |
| } | |
| return result; | |
| } | |
| function buildMatchGraphInternal(node) { | |
| if (typeof node === 'function') { | |
| return { | |
| type: 'Generic', | |
| fn: node | |
| }; | |
| } | |
| switch (node.type) { | |
| case 'Group': { | |
| let result = buildGroupMatchGraph( | |
| node.combinator, | |
| node.terms.map(buildMatchGraphInternal), | |
| false | |
| ); | |
| if (node.disallowEmpty) { | |
| result = createCondition( | |
| result, | |
| DISALLOW_EMPTY, | |
| MISMATCH | |
| ); | |
| } | |
| return result; | |
| } | |
| case 'Multiplier': | |
| return buildMultiplierMatchGraph(node); | |
| case 'Type': | |
| case 'Property': | |
| return { | |
| type: node.type, | |
| name: node.name, | |
| syntax: node | |
| }; | |
| case 'Keyword': | |
| return { | |
| type: node.type, | |
| name: node.name.toLowerCase(), | |
| syntax: node | |
| }; | |
| case 'AtKeyword': | |
| return { | |
| type: node.type, | |
| name: '@' + node.name.toLowerCase(), | |
| syntax: node | |
| }; | |
| case 'Function': | |
| return { | |
| type: node.type, | |
| name: node.name.toLowerCase() + '(', | |
| syntax: node | |
| }; | |
| case 'String': | |
| // convert a one char length String to a Token | |
| if (node.value.length === 3) { | |
| return { | |
| type: 'Token', | |
| value: node.value.charAt(1), | |
| syntax: node | |
| }; | |
| } | |
| // otherwise use it as is | |
| return { | |
| type: node.type, | |
| value: node.value.substr(1, node.value.length - 2).replace(/\\'/g, '\''), | |
| syntax: node | |
| }; | |
| case 'Token': | |
| return { | |
| type: node.type, | |
| value: node.value, | |
| syntax: node | |
| }; | |
| case 'Comma': | |
| return { | |
| type: node.type, | |
| syntax: node | |
| }; | |
| default: | |
| throw new Error('Unknown node type:', node.type); | |
| } | |
| } | |
| function buildMatchGraph(syntaxTree, ref) { | |
| if (typeof syntaxTree === 'string') { | |
| syntaxTree = parse.parse(syntaxTree); | |
| } | |
| return { | |
| type: 'MatchGraph', | |
| match: buildMatchGraphInternal(syntaxTree), | |
| syntax: ref || null, | |
| source: syntaxTree | |
| }; | |
| } | |
| exports.DISALLOW_EMPTY = DISALLOW_EMPTY; | |
| exports.MATCH = MATCH; | |
| exports.MISMATCH = MISMATCH; | |
| exports.buildMatchGraph = buildMatchGraph; | |