Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement syntax for importing rules #292

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions lib/compiler/asts.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,20 @@ const visitor = require("./visitor");

// AST utilities.
const asts = {
findRule(ast, name) {
for (let i = 0; i < ast.rules.length; i++) {
if (ast.rules[i].name === name) {
return ast.rules[i];
findRule(ast, name, includeImports = false) {
for (const rule of ast.rules) {
if (rule.name === name) {
return rule;
}
}

if (includeImports && ast.imports) {
for (const _import of ast.imports) {
for (const rule of _import.rules) {
if (rule.alias === name) {
return rule;
}
}
}
}

Expand Down
1 change: 1 addition & 0 deletions lib/compiler/opcodes.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const opcodes = {
// Rules

RULE: 27, // RULE r
IMPORTED_RULE: 43, // IMPORTED_RULE i, r

// Failure Reporting

Expand Down
21 changes: 19 additions & 2 deletions lib/compiler/passes/generate-bytecode.js
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,10 @@ const { ALWAYS_MATCH, SOMETIMES_MATCH, NEVER_MATCH } = require("./inference-matc
//
// stack.push(parseRule(r));
//
// [43] IMPORTED_RULE i, r
//
// stack.push(parseImportedRule(i, r));
//
// Failure Reporting
// -----------------
//
Expand Down Expand Up @@ -270,7 +274,7 @@ const { ALWAYS_MATCH, SOMETIMES_MATCH, NEVER_MATCH } = require("./inference-matc
// that is equivalent of an unknown match result and signals the generator that
// runtime check for the |FAILED| is required. Trick is explained on the
// Wikipedia page (https://en.wikipedia.org/wiki/Asm.js#Code_generation)
function generateBytecode(ast, options) {
function generateBytecode(ast, options, session) {
const literals = [];
const classes = [];
const expectations = [];
Expand Down Expand Up @@ -954,7 +958,20 @@ function generateBytecode(ast, options) {
},

rule_ref(node) {
return [op.RULE, asts.indexOfRule(ast, node.name)];
const rule = asts.indexOfRule(ast, node.name);
if (rule < 0) {
for (let i = 0; i < ast.imports.length; ++i) {
const rule = ast.imports[i].rules.findIndex(
rule => rule.alias === node.name
);
if (rule >= 0) {
return [op.IMPORTED_RULE, i, rule];
}
}
session.error(`Unknown imported rule ${node.name}`, node.location);
}

return [op.RULE, rule];
},

literal(node) {
Expand Down
7 changes: 7 additions & 0 deletions lib/compiler/passes/generate-js.js
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,13 @@ function generateJS(ast, options) {
ip += 2;
break;

case op.IMPORTED_RULE: { // IMPORTED_RULE i, r
const _import = ast.imports[bc[ip + 1]];
const rule = _import.rules[bc[ip + 2]];
parts.push(stack.push(rule.name + "()"));
ip += 3;
break;
}
case op.SILENT_FAILS_ON: // SILENT_FAILS_ON
parts.push("peg$silentFails++;");
ip++;
Expand Down
3 changes: 2 additions & 1 deletion lib/compiler/passes/inference-match-result.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ function inferenceMatchResult(ast) {
}

const inference = visitor.build({
imported_rule: sometimesMatch,
rule(node) {
let oldResult;
let count = 0;
Expand Down Expand Up @@ -151,7 +152,7 @@ function inferenceMatchResult(ast) {
semantic_and: sometimesMatch,
semantic_not: sometimesMatch,
rule_ref(node) {
const rule = asts.findRule(ast, node.name);
const rule = asts.findRule(ast, node.name, true);

return (node.match = inference(rule));
},
Expand Down
2 changes: 1 addition & 1 deletion lib/compiler/passes/remove-proxy-rules.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ function removeProxyRules(ast, options, session) {
node.location,
[{
message: "This rule will be used",
location: asts.findRule(ast, to).nameLocation,
location: asts.findRule(ast, to, true).nameLocation,
}]
);
}
Expand Down
57 changes: 50 additions & 7 deletions lib/compiler/passes/report-duplicate-rules.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,71 @@
"use strict";

const asts = require("../asts");
const visitor = require("../visitor");

// Checks that each rule is defined only once.
function reportDuplicateRules(ast, options, session) {
const rules = {};
const defined = {};
const imported = {};

function checkRepository(repository, node, errorMessage, detailMessage) {
if (Object.prototype.hasOwnProperty.call(repository, node.name)) {
session.error(
errorMessage,
node.nameLocation,
[{
message: detailMessage,
location: repository[node.name],
}]
);

return true;
}

return false;
}

const check = visitor.build({
rule(node) {
if (Object.prototype.hasOwnProperty.call(rules, node.name)) {
imported_rule(node) {
if (checkRepository(
imported,
node,
`Rule "${node.name}" is already imported`,
"Original import location"
)) {
// Do not rewrite original rule location
return;
}

imported[node.name] = node.location;

const rule = asts.findRule(ast, node.name);
if (rule) {
session.error(
`Rule "${node.name}" is already defined`,
node.aliasLocation
? `Rule with the same name "${node.name}" is already defined in the grammar`
: `Rule with the same name "${node.name}" is already defined in the grammar, try to add \`as <alias_name>\` to the imported one`,
node.nameLocation,
[{
message: "Original rule location",
location: rules[node.name],
message: "Rule defined here",
location: rule.nameLocation,
}]
);
}
},

rule(node) {
if (checkRepository(
defined,
node,
`Rule "${node.name}" is already defined`,
"Original rule location"
)) {
// Do not rewrite original rule location
return;
}

rules[node.name] = node.nameLocation;
defined[node.name] = node.nameLocation;
},
});

Expand Down
4 changes: 2 additions & 2 deletions lib/compiler/passes/report-undefined-rules.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ const visitor = require("../visitor");
function reportUndefinedRules(ast, options, session) {
const check = visitor.build({
rule_ref(node) {
if (!asts.findRule(ast, node.name)) {
if (!asts.findRule(ast, node.name, true)) {
session.error(
`Rule "${node.name}" is not defined`,
`Rule "${node.name}" is not defined or imported`,
node.location
);
}
Expand Down
6 changes: 6 additions & 0 deletions lib/compiler/visitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ const visitor = {

const DEFAULT_FUNCTIONS = {
grammar(node, ...args) {
if (node.imports) {
node.imports.forEach(_import => visit(_import, ...args));
}

if (node.topLevelInitializer) {
visit(node.topLevelInitializer, ...args);
}
Expand All @@ -39,6 +43,8 @@ const visitor = {
node.rules.forEach(rule => visit(rule, ...args));
},

import: visitChildren("rules"),
imported_rule: visitNop,
top_level_initializer: visitNop,
initializer: visitNop,
rule: visitExpression,
Expand Down
Loading