/****************************************************************************** * Copyright 2022 TypeFox GmbH * This program and the accompanying materials are made available under the * terms of the MIT License, which is available in the project root. ******************************************************************************/ import map from "lodash-es/map.js"; import filter from "lodash-es/filter.js"; import { Alternation, NonTerminal, Option, RepetitionMandatory, Repetition, Terminal, RepetitionWithSeparator, RepetitionMandatoryWithSeparator } from "chevrotain"; export function buildATNKey(rule, type, occurrence) { return `${rule.name}_${type}_${occurrence}`; } export const ATN_INVALID_TYPE = 0; export const ATN_BASIC = 1; export const ATN_RULE_START = 2; export const ATN_PLUS_BLOCK_START = 4; export const ATN_STAR_BLOCK_START = 5; // Currently unused as the ATN is not used for lexing export const ATN_TOKEN_START = 6; export const ATN_RULE_STOP = 7; export const ATN_BLOCK_END = 8; export const ATN_STAR_LOOP_BACK = 9; export const ATN_STAR_LOOP_ENTRY = 10; export const ATN_PLUS_LOOP_BACK = 11; export const ATN_LOOP_END = 12; export class AbstractTransition { constructor(target) { this.target = target; } isEpsilon() { return false; } } export class AtomTransition extends AbstractTransition { constructor(target, tokenType) { super(target); this.tokenType = tokenType; } } export class EpsilonTransition extends AbstractTransition { constructor(target) { super(target); } isEpsilon() { return true; } } export class RuleTransition extends AbstractTransition { constructor(ruleStart, rule, followState) { super(ruleStart); this.rule = rule; this.followState = followState; } isEpsilon() { return true; } } export function createATN(rules) { const atn = { decisionMap: {}, decisionStates: [], ruleToStartState: new Map(), ruleToStopState: new Map(), states: [] }; createRuleStartAndStopATNStates(atn, rules); const ruleLength = rules.length; for (let i = 0; i < ruleLength; i++) { const rule = rules[i]; const ruleBlock = block(atn, rule, rule); if (ruleBlock === undefined) { continue; } buildRuleHandle(atn, rule, ruleBlock); } return atn; } function createRuleStartAndStopATNStates(atn, rules) { const ruleLength = rules.length; for (let i = 0; i < ruleLength; i++) { const rule = rules[i]; const start = newState(atn, rule, undefined, { type: ATN_RULE_START }); const stop = newState(atn, rule, undefined, { type: ATN_RULE_STOP }); start.stop = stop; atn.ruleToStartState.set(rule, start); atn.ruleToStopState.set(rule, stop); } } function atom(atn, rule, production) { if (production instanceof Terminal) { return tokenRef(atn, rule, production.terminalType, production); } else if (production instanceof NonTerminal) { return ruleRef(atn, rule, production); } else if (production instanceof Alternation) { return alternation(atn, rule, production); } else if (production instanceof Option) { return option(atn, rule, production); } else if (production instanceof Repetition) { return repetition(atn, rule, production); } else if (production instanceof RepetitionWithSeparator) { return repetitionSep(atn, rule, production); } else if (production instanceof RepetitionMandatory) { return repetitionMandatory(atn, rule, production); } else if (production instanceof RepetitionMandatoryWithSeparator) { return repetitionMandatorySep(atn, rule, production); } else { return block(atn, rule, production); } } function repetition(atn, rule, repetition) { const starState = newState(atn, rule, repetition, { type: ATN_STAR_BLOCK_START }); defineDecisionState(atn, starState); const handle = makeAlts(atn, rule, starState, repetition, block(atn, rule, repetition)); return star(atn, rule, repetition, handle); } function repetitionSep(atn, rule, repetition) { const starState = newState(atn, rule, repetition, { type: ATN_STAR_BLOCK_START }); defineDecisionState(atn, starState); const handle = makeAlts(atn, rule, starState, repetition, block(atn, rule, repetition)); const sep = tokenRef(atn, rule, repetition.separator, repetition); return star(atn, rule, repetition, handle, sep); } function repetitionMandatory(atn, rule, repetition) { const plusState = newState(atn, rule, repetition, { type: ATN_PLUS_BLOCK_START }); defineDecisionState(atn, plusState); const handle = makeAlts(atn, rule, plusState, repetition, block(atn, rule, repetition)); return plus(atn, rule, repetition, handle); } function repetitionMandatorySep(atn, rule, repetition) { const plusState = newState(atn, rule, repetition, { type: ATN_PLUS_BLOCK_START }); defineDecisionState(atn, plusState); const handle = makeAlts(atn, rule, plusState, repetition, block(atn, rule, repetition)); const sep = tokenRef(atn, rule, repetition.separator, repetition); return plus(atn, rule, repetition, handle, sep); } function alternation(atn, rule, alternation) { const start = newState(atn, rule, alternation, { type: ATN_BASIC }); defineDecisionState(atn, start); const alts = map(alternation.definition, (e) => atom(atn, rule, e)); const handle = makeAlts(atn, rule, start, alternation, ...alts); return handle; } function option(atn, rule, option) { const start = newState(atn, rule, option, { type: ATN_BASIC }); defineDecisionState(atn, start); const handle = makeAlts(atn, rule, start, option, block(atn, rule, option)); return optional(atn, rule, option, handle); } function block(atn, rule, block) { const handles = filter(map(block.definition, (e) => atom(atn, rule, e)), (e) => e !== undefined); if (handles.length === 1) { return handles[0]; } else if (handles.length === 0) { return undefined; } else { return makeBlock(atn, handles); } } function plus(atn, rule, plus, handle, sep) { const blkStart = handle.left; const blkEnd = handle.right; const loop = newState(atn, rule, plus, { type: ATN_PLUS_LOOP_BACK }); defineDecisionState(atn, loop); const end = newState(atn, rule, plus, { type: ATN_LOOP_END }); blkStart.loopback = loop; end.loopback = loop; atn.decisionMap[buildATNKey(rule, sep ? 'RepetitionMandatoryWithSeparator' : 'RepetitionMandatory', plus.idx)] = loop; epsilon(blkEnd, loop); // block can see loop back // Depending on whether we have a separator we put the exit transition at index 1 or 0 // This influences the chosen option in the lookahead DFA if (sep === undefined) { epsilon(loop, blkStart); // loop back to start epsilon(loop, end); // exit } else { epsilon(loop, end); // exit // loop back to start with separator epsilon(loop, sep.left); epsilon(sep.right, blkStart); } return { left: blkStart, right: end }; } function star(atn, rule, star, handle, sep) { const start = handle.left; const end = handle.right; const entry = newState(atn, rule, star, { type: ATN_STAR_LOOP_ENTRY }); defineDecisionState(atn, entry); const loopEnd = newState(atn, rule, star, { type: ATN_LOOP_END }); const loop = newState(atn, rule, star, { type: ATN_STAR_LOOP_BACK }); entry.loopback = loop; loopEnd.loopback = loop; epsilon(entry, start); // loop enter edge (alt 2) epsilon(entry, loopEnd); // bypass loop edge (alt 1) epsilon(end, loop); // block end hits loop back if (sep !== undefined) { epsilon(loop, loopEnd); // end loop // loop back to start of handle using separator epsilon(loop, sep.left); epsilon(sep.right, start); } else { epsilon(loop, entry); // loop back to entry/exit decision } atn.decisionMap[buildATNKey(rule, sep ? 'RepetitionWithSeparator' : 'Repetition', star.idx)] = entry; return { left: entry, right: loopEnd }; } function optional(atn, rule, optional, handle) { const start = handle.left; const end = handle.right; epsilon(start, end); atn.decisionMap[buildATNKey(rule, 'Option', optional.idx)] = start; return handle; } function defineDecisionState(atn, state) { atn.decisionStates.push(state); state.decision = atn.decisionStates.length - 1; return state.decision; } function makeAlts(atn, rule, start, production, ...alts) { const end = newState(atn, rule, production, { type: ATN_BLOCK_END, start }); start.end = end; for (const alt of alts) { if (alt !== undefined) { // hook alts up to decision block epsilon(start, alt.left); epsilon(alt.right, end); } else { epsilon(start, end); } } const handle = { left: start, right: end }; atn.decisionMap[buildATNKey(rule, getProdType(production), production.idx)] = start; return handle; } function getProdType(production) { if (production instanceof Alternation) { return 'Alternation'; } else if (production instanceof Option) { return 'Option'; } else if (production instanceof Repetition) { return 'Repetition'; } else if (production instanceof RepetitionWithSeparator) { return 'RepetitionWithSeparator'; } else if (production instanceof RepetitionMandatory) { return 'RepetitionMandatory'; } else if (production instanceof RepetitionMandatoryWithSeparator) { return 'RepetitionMandatoryWithSeparator'; } else { throw new Error('Invalid production type encountered'); } } function makeBlock(atn, alts) { const altsLength = alts.length; for (let i = 0; i < altsLength - 1; i++) { const handle = alts[i]; let transition; if (handle.left.transitions.length === 1) { transition = handle.left.transitions[0]; } const isRuleTransition = transition instanceof RuleTransition; const ruleTransition = transition; const next = alts[i + 1].left; if (handle.left.type === ATN_BASIC && handle.right.type === ATN_BASIC && transition !== undefined && ((isRuleTransition && ruleTransition.followState === handle.right) || transition.target === handle.right)) { // we can avoid epsilon edge to next element if (isRuleTransition) { ruleTransition.followState = next; } else { transition.target = next; } removeState(atn, handle.right); // we skipped over this state } else { // need epsilon if previous block's right end node is complex epsilon(handle.right, next); } } const first = alts[0]; const last = alts[altsLength - 1]; return { left: first.left, right: last.right }; } function tokenRef(atn, rule, tokenType, production) { const left = newState(atn, rule, production, { type: ATN_BASIC }); const right = newState(atn, rule, production, { type: ATN_BASIC }); addTransition(left, new AtomTransition(right, tokenType)); return { left, right }; } function ruleRef(atn, currentRule, nonTerminal) { const rule = nonTerminal.referencedRule; const start = atn.ruleToStartState.get(rule); const left = newState(atn, currentRule, nonTerminal, { type: ATN_BASIC }); const right = newState(atn, currentRule, nonTerminal, { type: ATN_BASIC }); const call = new RuleTransition(start, rule, right); addTransition(left, call); return { left, right }; } function buildRuleHandle(atn, rule, block) { const start = atn.ruleToStartState.get(rule); epsilon(start, block.left); const stop = atn.ruleToStopState.get(rule); epsilon(block.right, stop); const handle = { left: start, right: stop }; return handle; } function epsilon(a, b) { const transition = new EpsilonTransition(b); addTransition(a, transition); } function newState(atn, rule, production, partial) { const t = Object.assign({ atn, production, epsilonOnlyTransitions: false, rule, transitions: [], nextTokenWithinRule: [], stateNumber: atn.states.length }, partial); atn.states.push(t); return t; } function addTransition(state, transition) { // A single ATN state can only contain epsilon transitions or non-epsilon transitions // Because they are never mixed, only setting the property for the first transition is fine if (state.transitions.length === 0) { state.epsilonOnlyTransitions = transition.isEpsilon(); } state.transitions.push(transition); } function removeState(atn, state) { atn.states.splice(atn.states.indexOf(state), 1); } //# sourceMappingURL=atn.js.map