import { AndRule, type DataRef, OrRule, type Rule, type RuleTestResponse, } from '@os-team/lexical-rules'; import { type Parser } from './Parser.js'; import ChoiceParser, { type Choice } from './ChoiceParser.js'; import SeqParser, { type Seq } from './SeqParser.js'; import NameRule from '../rules/NameRule.js'; import createLazyParser from './createLazyParser.js'; import QuantifierRule, { type Quantifier } from '../rules/QuantifierRule.js'; type CpContent = string | Choice | Seq; export type Cp = CpContent | [CpContent, '?' | '*' | '+']; /** * See https://www.w3.org/TR/xml/#NT-cp */ class CpParser implements Parser { private readonly choiceParser: ChoiceParser; private readonly seqParser: SeqParser; private readonly rule: Rule<[string | Choice | Seq, Quantifier]>; public constructor() { const nameRule = new NameRule(); // Name this.choiceParser = createLazyParser(ChoiceParser); // choice this.seqParser = createLazyParser(SeqParser); // seq const quantifierRule = new QuantifierRule(); // ('?' | '*' | '+')? const nameOrChoiceOrSeqRule = new OrRule([ nameRule, this.choiceParser, this.seqParser, ]); this.rule = new AndRule([nameOrChoiceOrSeqRule, quantifierRule]); // (Name | choice | seq) ('?' | '*' | '+')? } public test(ref: DataRef, pos: number): RuleTestResponse { const [isValid, nextPos, res] = this.rule.test(ref, pos); if (!isValid || res === undefined) return [false, nextPos]; const [content, quantifier] = res; const cp: Cp = quantifier ? [content, quantifier] : content; return [true, nextPos, cp]; } public build(data: Cp) { const [content, quantifier = ''] = Array.isArray(data) ? data : [data]; let strContent: string; if (typeof content === 'string') { strContent = content; } else if (content.type === 'CHOICE') { strContent = this.choiceParser.build(content); } else { strContent = this.seqParser.build(content); } return `${strContent}${quantifier}`; } } export default CpParser;