ebnf2railroad
Advanced tools
Comparing version 1.1.0 to 1.2.0
@@ -7,2 +7,20 @@ # Changelog | ||
## [1.2.0] - 2018-11-01 | ||
### Added | ||
- Show validation warnings for duplicate declarations | ||
- Show validation warnings for missing references | ||
- Option `--validate` to exit with status code 2 if document has | ||
warnings | ||
- Option `--quite` to suppress output to console | ||
- Optimize EBNF syntax as `( a ), { a }` in diagram as `a+` (one or more) | ||
- Optimize EBNF syntax as `a | { b }` in diagram as choice with "skip", | ||
"a", or one or more "b" | ||
- Optimize EBNF syntax as `a | [ b ]` in diagram as choice with "skip", | ||
"a", or "b" | ||
### Changed | ||
- Long choice lists are now spread over multiple columns, if the | ||
length exceeds 10. | ||
- Updated styling of document | ||
## [1.1.0] - 2018-10-30 | ||
@@ -9,0 +27,0 @@ ### Added |
{ | ||
"name": "ebnf2railroad", | ||
"version": "1.1.0", | ||
"version": "1.2.0", | ||
"description": "EBNF to Railroad diagram", | ||
@@ -29,3 +29,3 @@ "keywords": [ | ||
"commander": "^2.19.0", | ||
"railroad-diagrams": "https://github.com/tabatkins/railroad-diagrams.git", | ||
"railroad-diagrams": "https://github.com/matthijsgroen/railroad-diagrams.git#add-horizontal-choice", | ||
"showdown": "^1.8.7" | ||
@@ -32,0 +32,0 @@ }, |
@@ -17,3 +17,12 @@ # EBNF 2 RailRoad | ||
``` | ||
ebnf2railroad my-definition.ebnf | ||
Usage: ebnf2railroad [options] <file> | ||
Converts an ISO/IEC 14977 EBNF file to a HTML file with SVG railroad diagrams | ||
Options: | ||
-V, --version output the version number | ||
-o, --target [target] output the file to target destination. | ||
-q, --quiet suppress output to STDOUT | ||
--validate exit with status code 2 if ebnf document has warnings | ||
-h, --help output usage information | ||
``` | ||
@@ -20,0 +29,0 @@ |
@@ -6,3 +6,3 @@ const program = require("commander"); | ||
const { parser } = require("./ebnf-parser"); | ||
const { createDocumentation, optimizeAst } = require("./report-builder"); | ||
const { createDocumentation, valididateEbnf } = require("./report-builder"); | ||
@@ -14,2 +14,4 @@ program.version("1.0.0"); | ||
.option("-o, --target [target]", "output the file to target destination.") | ||
.option("-q, --quiet", "suppress output to STDOUT") | ||
.option("--validate", "exit with status code 2 if ebnf document has warnings") | ||
.description( | ||
@@ -25,2 +27,3 @@ "Converts an ISO/IEC 14977 EBNF file to a HTML file with SVG railroad diagrams" | ||
} | ||
const allowOutput = !program.quiet; | ||
@@ -39,9 +42,17 @@ try { | ||
const ast = parser.parse(ebnf); | ||
const optimizedAst = optimizeAst(ast); | ||
const report = createDocumentation(optimizedAst, { | ||
const warnings = valididateEbnf(ast); | ||
warnings.length > 0 && | ||
allowOutput && | ||
warnings.forEach(warning => console.warn(warning)); | ||
const report = createDocumentation(ast, { | ||
title: basename | ||
}); | ||
await writeFile(targetFilename, report, "utf8"); | ||
allowOutput && console.log(`📜 Document created at ${targetFilename}`); | ||
warnings.length > 0 && program.validate && process.exit(2); | ||
} catch (e) { | ||
console.error(e.message); | ||
if (allowOutput) console.error(e.message); | ||
process.exit(1); | ||
@@ -48,0 +59,0 @@ } |
@@ -96,3 +96,3 @@ /* parser generated by jison 0.4.18 */ | ||
case 4: | ||
this.$ = { identifier: $$[$0-3].trim(), definition: $$[$0-1] }; | ||
this.$ = { identifier: $$[$0-3].trim(), definition: $$[$0-1], location: _$[$0-3].first_line }; | ||
break; | ||
@@ -99,0 +99,0 @@ case 6: |
@@ -5,2 +5,3 @@ const { | ||
Diagram, | ||
HorizontalChoice, | ||
NonTerminal, | ||
@@ -73,2 +74,4 @@ OneOrMore, | ||
const SHRINK_CHOICE = 10; | ||
const productionToDiagram = production => { | ||
@@ -84,2 +87,5 @@ if (production.identifier) { | ||
} | ||
if (production.skip) { | ||
return Skip(); | ||
} | ||
if (production.specialSequence) { | ||
@@ -91,4 +97,13 @@ const sequence = Terminal(" " + production.specialSequence + " "); | ||
if (production.choice) { | ||
const makeChoice = items => new Choice(0, items); | ||
const options = production.choice.map(productionToDiagram); | ||
return Choice(0, ...options); | ||
const choiceLists = []; | ||
while (options.length > SHRINK_CHOICE) { | ||
const subList = options.splice(0, SHRINK_CHOICE); | ||
choiceLists.push(makeChoice(subList)); | ||
} | ||
choiceLists.push(makeChoice(options)); | ||
return choiceLists.length > 1 | ||
? HorizontalChoice(...choiceLists) | ||
: choiceLists[0]; | ||
} | ||
@@ -195,3 +210,3 @@ if (production.sequence) { | ||
} | ||
const diagram = productionToDiagram(production); | ||
const diagram = productionToDiagram(optimizeProduction(production)); | ||
return ebnfTemplate({ | ||
@@ -216,2 +231,8 @@ identifier: production.identifier, | ||
const skipFirst = list => | ||
[ | ||
list.some(e => e === "skip") && { skip: true }, | ||
...list.filter(e => e !== "skip") | ||
].filter(Boolean); | ||
const optimizeProduction = production => { | ||
@@ -227,3 +248,23 @@ if (production.definition) { | ||
...production, | ||
choice: production.choice.map(optimizeProduction) | ||
choice: skipFirst( | ||
production.choice | ||
.map((item, idx, list) => { | ||
const optimizedItem = optimizeProduction(item); | ||
if (optimizedItem.repetition && optimizedItem.skippable) { | ||
return [ | ||
"skip", | ||
{ | ||
...optimizedItem, | ||
skippable: false | ||
} | ||
]; | ||
} else if (optimizedItem.optional) { | ||
return ["skip", optimizedItem.optional]; | ||
} else { | ||
return [optimizedItem]; | ||
} | ||
}) | ||
.reduce((acc, item) => acc.concat(item), []) | ||
.filter((item, index, list) => list.indexOf(item) === index) | ||
) | ||
}; | ||
@@ -239,8 +280,18 @@ } | ||
const isSame = | ||
JSON.stringify(ahead.repetition) === JSON.stringify(item); | ||
JSON.stringify(ahead.repetition) === JSON.stringify(item) || | ||
(item.group && | ||
JSON.stringify(ahead.repetition) === | ||
JSON.stringify(item.group)); | ||
if (isSame) { | ||
ahead.skippable = false; | ||
ahead.changeSkippable = true; | ||
return false; | ||
} | ||
} | ||
if (item.changeSkippable) { | ||
delete item["changeSkippable"]; | ||
return { | ||
...optimizeProduction(item), | ||
skippable: false | ||
}; | ||
} | ||
return optimizeProduction(item); | ||
@@ -273,7 +324,36 @@ }) | ||
const optimizeAst = ast => ast.map(line => optimizeProduction(line)); | ||
const valididateEbnf = ast => { | ||
const identifiers = ast.map(production => production.identifier); | ||
const doubleDeclarations = ast | ||
.map((declaration, index) => { | ||
// skip comments, but keep index in array intact (filter would break index) | ||
if (!declaration.identifier) return false; | ||
const firstDeclaration = identifiers.indexOf(declaration.identifier); | ||
if (firstDeclaration === index) return false; | ||
return `${declaration.location}: Duplicate declaration: "${ | ||
declaration.identifier | ||
}" already declared on line ${ast[firstDeclaration].location}.`; | ||
}) | ||
.filter(Boolean); | ||
const missingReferences = ast | ||
.filter(declaration => declaration.identifier) | ||
.map(declaration => | ||
getReferences(declaration) | ||
.filter((item, index, list) => list.indexOf(item) === index) | ||
.filter(reference => !identifiers.includes(reference)) | ||
.map( | ||
missingReference => | ||
`${declaration.location}: Missing reference: "${missingReference}".` | ||
) | ||
) | ||
.filter(m => m.length > 0) | ||
.reduce((acc, elem) => acc.concat(elem), []); | ||
return doubleDeclarations.concat(missingReferences).sort(); | ||
}; | ||
module.exports = { | ||
createDocumentation, | ||
optimizeAst | ||
valididateEbnf | ||
}; |
@@ -26,4 +26,6 @@ const { Converter } = require("showdown"); | ||
color: #0F0C00; | ||
background: #FFFCF0; | ||
background: #FFFCFC; | ||
} | ||
h1 { font-size: 2em; } | ||
h2 { font-size: 1.5em; } | ||
code { | ||
@@ -30,0 +32,0 @@ padding: 1em 1em 1em 3em; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
HTTP dependency
Supply chain riskContains a dependency which resolves to a remote HTTP URL which could be used to inject untrusted code and reduce overall package reliability.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
HTTP dependency
Supply chain riskContains a dependency which resolves to a remote HTTP URL which could be used to inject untrusted code and reduce overall package reliability.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
420134
16
1175
45