Skip to content

Commit

Permalink
Implemented option for parsing text with an alternative entry rule
Browse files Browse the repository at this point in the history
  • Loading branch information
spoenemann committed Mar 7, 2024
1 parent f6889f2 commit 258ec99
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 12 deletions.
20 changes: 16 additions & 4 deletions packages/langium/src/parser/langium-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ interface AssignmentElement {

export interface BaseParser {
rule(rule: ParserRule, impl: RuleImpl): RuleResult;
getRule(name: string): RuleResult | undefined;
alternatives(idx: number, choices: Array<IOrAlt<any>>): void;
optional(idx: number, callback: DSLMethodOpts<unknown>): void;
many(idx: number, callback: DSLMethodOpts<unknown>): void;
Expand All @@ -75,6 +76,9 @@ export abstract class AbstractLangiumParser implements BaseParser {
protected readonly wrapper: ChevrotainWrapper;
protected _unorderedGroups: Map<string, boolean[]> = new Map<string, boolean[]>();

protected allRules = new Map<string, RuleResult>();
protected mainRule!: RuleResult;

constructor(services: LangiumCoreServices) {
this.lexer = services.parser.Lexer;
const tokens = this.lexer.definition;
Expand Down Expand Up @@ -106,6 +110,10 @@ export abstract class AbstractLangiumParser implements BaseParser {
abstract action($type: string, action: Action): void;
abstract construct(): unknown;

getRule(name: string): RuleResult | undefined {
return this.allRules.get(name);
}

isRecording(): boolean {
return this.wrapper.IS_RECORDING;
}
Expand All @@ -129,7 +137,6 @@ export class LangiumParser extends AbstractLangiumParser {
private readonly astReflection: AstReflection;
private readonly nodeBuilder = new CstNodeBuilder();
private stack: any[] = [];
private mainRule!: RuleResult;
private assignmentMap = new Map<AbstractElement, AssignmentElement | undefined>();

private get current(): any {
Expand All @@ -146,17 +153,22 @@ export class LangiumParser extends AbstractLangiumParser {
rule(rule: ParserRule, impl: RuleImpl): RuleResult {
const type = rule.fragment ? undefined : isDataTypeRule(rule) ? DatatypeSymbol : getTypeName(rule);
const ruleMethod = this.wrapper.DEFINE_RULE(withRuleSuffix(rule.name), this.startImplementation(type, impl).bind(this));
this.allRules.set(rule.name, ruleMethod);
if (rule.entry) {
this.mainRule = ruleMethod;
}
return ruleMethod;
}

parse<T extends AstNode = AstNode>(input: string): ParseResult<T> {
parse<T extends AstNode = AstNode>(input: string, options: { rule?: string } = {}): ParseResult<T> {
this.nodeBuilder.buildRootNode(input);
const lexerResult = this.lexer.tokenize(input);
this.wrapper.input = lexerResult.tokens;
const result = this.mainRule.call(this.wrapper, {});
const ruleMethod = options.rule ? this.allRules.get(options.rule) : this.mainRule;
if (!ruleMethod) {
throw new Error(options.rule ? `No rule found with name '${options.rule}'` : 'No main rule available.');
}
const result = ruleMethod.call(this.wrapper, {});
this.nodeBuilder.addHiddenTokens(lexerResult.hidden);
this.unorderedGroups.clear();
return {
Expand Down Expand Up @@ -424,7 +436,6 @@ export interface CompletionParserResult {
}

export class LangiumCompletionParser extends AbstractLangiumParser {
private mainRule!: RuleResult;
private tokens: IToken[] = [];

private elementStack: AbstractElement[] = [];
Expand Down Expand Up @@ -457,6 +468,7 @@ export class LangiumCompletionParser extends AbstractLangiumParser {

rule(rule: ParserRule, impl: RuleImpl): RuleResult {
const ruleMethod = this.wrapper.DEFINE_RULE(withRuleSuffix(rule.name), this.startImplementation(impl).bind(this));
this.allRules.set(rule.name, ruleMethod);
if (rule.entry) {
this.mainRule = ruleMethod;
}
Expand Down
10 changes: 2 additions & 8 deletions packages/langium/src/parser/parser-builder-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ type RuleContext = {
type ParserContext = {
parser: BaseParser
tokens: TokenTypeDictionary
rules: Map<string, Rule>
ruleNames: Map<AstNode, string>
}

Expand All @@ -39,11 +38,9 @@ type Predicate = (args: Args) => boolean;
type Method = (args: Args) => void;

export function createParser<T extends BaseParser>(grammar: Grammar, parser: T, tokens: TokenTypeDictionary): T {
const rules = new Map<string, Rule>();
const parserContext: ParserContext = {
parser,
tokens,
rules,
ruleNames: new Map()
};
buildRules(parserContext, grammar);
Expand All @@ -62,10 +59,7 @@ function buildRules(parserContext: ParserContext, grammar: Grammar): void {
many: 1,
or: 1
};
ctx.rules.set(
rule.name,
parserContext.parser.rule(rule, buildElement(ctx, rule.definition))
);
parserContext.parser.rule(rule, buildElement(ctx, rule.definition));
}
}

Expand Down Expand Up @@ -369,7 +363,7 @@ function wrap(ctx: RuleContext, guard: Condition | undefined, method: Method, ca

function getRule(ctx: ParserContext, element: ParserRule | AbstractElement): Rule {
const name = getRuleName(ctx, element);
const rule = ctx.rules.get(name);
const rule = ctx.parser.getRule(name);
if (!rule) throw new Error(`Rule "${name}" not found."`);
return rule;
}
Expand Down
60 changes: 60 additions & 0 deletions packages/langium/test/parser/langium-parser.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/******************************************************************************
* Copyright 2024 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 type { LangiumParser } from 'langium';
import { describe, expect, test, beforeEach } from 'vitest';
import { createServicesForGrammar } from 'langium/grammar';

describe('Partial parsing', () => {
const content = `
grammar Test
entry Model: (a+=A | b+=B)*;
A: 'a' name=ID;
B: 'b' name=ID;
terminal ID: /[_a-zA-Z][\\w_]*/;
hidden terminal WS: /\\s+/;
`;

let parser: LangiumParser;

beforeEach(async () => {
parser = await parserFromGrammar(content);
});

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function expectCorrectParse(text: string, rule?: string): any {
const result = parser.parse(text, { rule });
expect(result.parserErrors.length).toBe(0);
return result.value;
}

function expectErrorneousParse(text: string, rule?: string): void {
const result = parser.parse(text, { rule });
expect(result.parserErrors.length).toBeGreaterThan(0);
}

test('Should parse correctly with normal entry rule', () => {
const result = expectCorrectParse('a Foo b Bar');
expect(result.a[0].name).toEqual('Foo');
expect(result.b[0].name).toEqual('Bar');
});

test('Should parse correctly with alternative entry rule A', () => {
const result = expectCorrectParse('a Foo', 'A');
expect(result.name).toEqual('Foo');
expectErrorneousParse('b Bar', 'A');
});

test('Should parse correctly with alternative entry rule B', () => {
const result = expectCorrectParse('b Foo', 'B');
expect(result.name).toEqual('Foo');
expectErrorneousParse('a Foo', 'B');
});
});

async function parserFromGrammar(grammar: string): Promise<LangiumParser> {
return (await createServicesForGrammar({ grammar })).parser.LangiumParser;
}

0 comments on commit 258ec99

Please sign in to comment.