Skip to content

Commit

Permalink
Make entry rule explicit (#305)
Browse files Browse the repository at this point in the history
  • Loading branch information
pluralia committed Nov 18, 2021
1 parent 62898bd commit f3b04b4
Show file tree
Hide file tree
Showing 16 changed files with 72 additions and 46 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
grammar Arithmetics

Module:
entry Module:
'module' name=ID
(statements+=Statement)*;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const grammar = (): Grammar => loaded || (loaded = loadGrammar(`{
"parameters": [],
"name": "Module",
"hiddenTokens": [],
"entry": true,
"alternatives": {
"$type": "Group",
"elements": [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
grammar DomainModel

Domainmodel:
entry Domainmodel:
(elements+=AbstractElement)*;

AbstractElement:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const grammar = (): Grammar => loaded || (loaded = loadGrammar(`{
"parameters": [],
"name": "Domainmodel",
"hiddenTokens": [],
"entry": true,
"alternatives": {
"$type": "Assignment",
"feature": "elements",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const grammar = (): Grammar => loaded || (loaded = loadGrammar(`{
"parameters": [],
"name": "Statemachine",
"hiddenTokens": [],
"entry": true,
"alternatives": {
"$type": "Group",
"elements": [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
grammar Statemachine

Statemachine:
entry Statemachine:
'statemachine' name=ID
('events' events+=Event+)?
('commands' commands+=Command+)?
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/langium-vscode/data/langium.tmLanguage.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
},
{
"name": "keyword.control.langium",
"match": "\\b(grammar|terminal|enum|fragment|hidden|generate|as|with|import|returns)\\b"
"match": "\\b(grammar|terminal|enum|entry|fragment|hidden|generate|as|with|import|returns)\\b"
},
{
"name": "constant.language.langium",
Expand Down
3 changes: 0 additions & 3 deletions packages/langium/src/documents/document-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

import { CancellationToken, Connection, Diagnostic } from 'vscode-languageserver';
import { DocumentValidator } from '../validation/document-validator';
import { LangiumParser } from '../parser/langium-parser';
import { ScopeComputation } from '../references/scope';
import { LangiumServices } from '../services';
import { DocumentState, LangiumDocument, LangiumDocuments } from './document';
Expand Down Expand Up @@ -47,7 +46,6 @@ export interface BuildResult<T extends AstNode = AstNode> {

export class DefaultDocumentBuilder implements DocumentBuilder {
protected readonly connection?: Connection;
protected readonly parser: LangiumParser;
protected readonly scopeComputation: ScopeComputation;
protected readonly documentValidator: DocumentValidator;
protected readonly langiumDocuments: LangiumDocuments;
Expand All @@ -56,7 +54,6 @@ export class DefaultDocumentBuilder implements DocumentBuilder {

constructor(services: LangiumServices) {
this.connection = services.lsp.Connection;
this.parser = services.parser.LangiumParser;
this.linker = services.references.Linker;
this.scopeComputation = services.references.ScopeComputation;
this.documentValidator = services.validation.DocumentValidator;
Expand Down
1 change: 1 addition & 0 deletions packages/langium/src/grammar/generated/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ export function isReferencedMetamodel(item: unknown): item is ReferencedMetamode
export interface ParserRule extends AbstractRule {
alternatives: AbstractElement
definesHiddenTokens: boolean
entry: boolean
hiddenTokens: Array<Reference<AbstractRule>>
parameters: Array<Parameter>
wildcard: boolean
Expand Down
32 changes: 24 additions & 8 deletions packages/langium/src/grammar/generated/grammar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export const grammar = (): Grammar => loaded || (loaded = loadGrammar(`{
"parameters": [],
"name": "Grammar",
"hiddenTokens": [],
"entry": true,
"alternatives": {
"$type": "Group",
"elements": [
Expand Down Expand Up @@ -474,14 +475,29 @@ export const grammar = (): Grammar => loaded || (loaded = loadGrammar(`{
"$type": "Group",
"elements": [
{
"$type": "Assignment",
"feature": "fragment",
"operator": "?=",
"terminal": {
"$type": "Keyword",
"value": "fragment"
},
"elements": []
"$type": "Alternatives",
"elements": [
{
"$type": "Assignment",
"feature": "entry",
"operator": "?=",
"terminal": {
"$type": "Keyword",
"value": "entry"
},
"elements": []
},
{
"$type": "Assignment",
"feature": "fragment",
"operator": "?=",
"terminal": {
"$type": "Keyword",
"value": "fragment"
},
"elements": []
}
]
},
{
"$type": "RuleCall",
Expand Down
2 changes: 1 addition & 1 deletion packages/langium/src/grammar/grammar-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ export function getRuleType(rule: ast.AbstractRule | undefined): string {
}

export function getEntryRule(grammar: ast.Grammar): ast.ParserRule | undefined {
return grammar.rules.find(e => ast.isParserRule(e)) as ast.ParserRule;
return grammar.rules.find(e => ast.isParserRule(e) && e.entry) as ast.ParserRule;
}

export function loadGrammar(json: string): ast.Grammar {
Expand Down
19 changes: 19 additions & 0 deletions packages/langium/src/grammar/langium-grammar-code-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ export class LangiumGrammarCodeActionProvider implements CodeActionProvider {
return this.fixHiddenTerminals(diagnostic, document);
case IssueCodes.UseRegexTokens:
return this.fixRegexTokens(diagnostic, document);
case IssueCodes.MakeRuleEntry:
return this.addEntryKeyword(diagnostic, document);
default:
return undefined;
}
Expand Down Expand Up @@ -67,6 +69,23 @@ export class LangiumGrammarCodeActionProvider implements CodeActionProvider {
};
}

private addEntryKeyword(diagnostic: Diagnostic, document: LangiumDocument): CodeAction | undefined {
return {
title: 'Add entry keyword',
kind: CodeActionKind.QuickFix,
diagnostics: [diagnostic],
isPreferred: true,
edit: {
changes: {
[document.textDocument.uri]: [{
range: {start: diagnostic.range.start, end: diagnostic.range.start},
newText: 'entry '
}]
}
}
};
}

private fixRegexTokens(diagnostic: Diagnostic, document: LangiumDocument): CodeAction | undefined {
const offset = document.textDocument.offsetAt(diagnostic.range.start);
const rootCst = document.parseResult.value.$cstNode;
Expand Down
24 changes: 14 additions & 10 deletions packages/langium/src/grammar/langium-grammar-validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export class LangiumGrammarValidationRegistry extends ValidationRegistry {
UnorderedGroup: validator.checkUnorderedGroup,
Grammar: [
validator.checkGrammarName,
validator.checkFirstGrammarRule,
validator.checkEntryGrammarRule,
validator.checkUniqueRuleName,
validator.checkGrammarHiddenTokens,
validator.checkGrammarForUnusedRules
Expand All @@ -52,6 +52,7 @@ export namespace IssueCodes {
export const RuleNameUppercase = 'rule-name-uppercase';
export const HiddenGrammarTokens = 'hidden-grammar-tokens';
export const UseRegexTokens = 'use-regex-tokens';
export const MakeRuleEntry = 'entry-rule-token-syntax';
}

export class LangiumGrammarValidator {
Expand All @@ -71,16 +72,19 @@ export class LangiumGrammarValidator {
}
}

checkFirstGrammarRule(grammar: ast.Grammar, accept: ValidationAcceptor): void {
const firstRule = getEntryRule(grammar);
if (firstRule) {
if (isDataTypeRule(firstRule)) {
accept('error', 'The entry rule cannot be a data type rule.', { node: firstRule, property: 'name' });
} else if (firstRule.fragment) {
accept('error', 'The entry rule cannot be a fragment.', { node: firstRule, property: 'name' });
checkEntryGrammarRule(grammar: ast.Grammar, accept: ValidationAcceptor): void {
const entryRules = grammar.rules.filter(e => ast.isParserRule(e) && e.entry) as ast.ParserRule[];
if (entryRules.length === 0) {
const possibleEntryRule = grammar.rules.find(e => ast.isParserRule(e) && !isDataTypeRule(e));
if (possibleEntryRule) {
accept('error', 'The grammar is missing an entry parser rule. This rule can be an entry one.', { node: possibleEntryRule, property: 'name', code: IssueCodes.MakeRuleEntry });
} else {
accept('error', 'This grammar is missing an entry parser rule.', { node: grammar, property: 'name' });
}
} else {
accept('error', 'This grammar is missing an entry parser rule.', { node: grammar, property: 'name' });
} else if (entryRules.length > 1) {
entryRules.forEach(rule => accept('error', 'The entry rule has to be unique.', { node: rule, property: 'name' }));
} else if (isDataTypeRule(entryRules[0])) {
accept('error', 'The entry rule cannot be a data type rule.', { node: entryRules[0], property: 'name' });
}
}

Expand Down
4 changes: 2 additions & 2 deletions packages/langium/src/grammar/langium-grammar.langium
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ terminal string: /"[^"]*"|'[^']*'/;
hidden terminal ML_COMMENT: /\/\*[\s\S]*?\*\//;
hidden terminal SL_COMMENT: /\/\/[^\n\r]*/;

Grammar:
entry Grammar:
'grammar' name=ID ('with' usedGrammars+=[Grammar|ID] (',' usedGrammars+=[Grammar|ID])*)?
(definesHiddenTokens?='hidden' '(' (hiddenTokens+=[AbstractRule|ID] (',' hiddenTokens+=[AbstractRule|ID])*)? ')')?
metamodelDeclarations+=AbstractMetamodelDeclaration*
Expand All @@ -31,7 +31,7 @@ Annotation:

ParserRule :
(
^fragment?='fragment' RuleNameAndParams (wildcard?='*' | ('returns' type=ID)?)
(^entry?='entry' | ^fragment?='fragment') RuleNameAndParams (wildcard?='*' | ('returns' type=ID)?)
| RuleNameAndParams ('returns' type=ID)?
)
(definesHiddenTokens?='hidden' '(' (hiddenTokens+=[AbstractRule|ID] (',' hiddenTokens+=[AbstractRule|ID])*)? ')')? ':'
Expand Down
20 changes: 3 additions & 17 deletions packages/langium/src/parser/langium-parser-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ function getToken(ctx: ParserContext, name: string): TokenType {
}

function buildParserRules(parserContext: ParserContext, grammar: Grammar): void {
let first = true;
for (const rule of stream(grammar.rules).filter(isParserRule)) {
const ctx: RuleContext = {
...parserContext,
Expand All @@ -70,25 +69,12 @@ function buildParserRules(parserContext: ParserContext, grammar: Grammar): void
many: 1,
or: 1
};
buildRule(ctx, rule, first);
first = false;
const method = (rule.entry ? ctx.parser.MAIN_RULE : ctx.parser.DEFINE_RULE).bind(ctx.parser);
const type = rule.fragment ? undefined : isDataTypeRule(rule) ? DatatypeSymbol : getTypeName(rule);
ctx.rules.set(rule.name, method(rule.name, type, buildRuleContent(ctx, rule)));
}
}

function buildRule(ctx: RuleContext, rule: ParserRule, first: boolean): void {
const method = (first ? ctx.parser.MAIN_RULE : ctx.parser.DEFINE_RULE).bind(ctx.parser);
let type: string | symbol | undefined;
if (!rule.fragment) {
if (isDataTypeRule(rule)) {
type = DatatypeSymbol;
} else {
type = getTypeName(rule);
}
}

ctx.rules.set(rule.name, method(rule.name, type, buildRuleContent(ctx, rule)));
}

function buildRuleContent(ctx: RuleContext, rule: ParserRule): () => unknown {
const method = buildElement(ctx, rule.alternatives);
const arrays: string[] = [];
Expand Down

0 comments on commit f3b04b4

Please sign in to comment.