chevrotain
Version:
Chevrotain is a high performance fault tolerant javascript parsing DSL for building recursive decent parsers
212 lines (202 loc) • 8.2 kB
JavaScript
;(function(root, factory) {
if (typeof define === "function" && define.amd) {
// AMD. Register as an anonymous module.
// TODO: remove dependency to Chevrotain
define(["../vendor/railroad-diagrams"], factory)
} else if (typeof module === "object" && module.exports) {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
// TODO: remove dependency to Chevrotain
module.exports = factory(require("../vendor/railroad-diagrams"))
} else {
// Browser globals (root is window)
root.diagrams_builder = factory(root.railroad)
}
})(this, function(railroad) {
var Diagram = railroad.Diagram
var Sequence = railroad.Sequence
var Choice = railroad.Choice
var Optional = railroad.Optional
var OneOrMore = railroad.OneOrMore
var ZeroOrMore = railroad.ZeroOrMore
var Terminal = railroad.Terminal
var NonTerminal = railroad.NonTerminal
/**
* @param {chevrotain.gast.ISerializedGast} topRules
*
* @returns {string} - The htmlText that will render the diagrams
*/
function buildSyntaxDiagramsText(topRules) {
var diagramsHtml = ""
topRules.forEach(function(production) {
var currDiagramHtml = convertProductionToDiagram(
production,
production.name
)
diagramsHtml +=
'<h2 class="diagramHeader">' +
production.name +
"</h2>" +
currDiagramHtml
})
return diagramsHtml
}
function definitionsToSubDiagrams(definitions, topRuleName) {
var subDiagrams = definitions.map(function(subProd) {
return convertProductionToDiagram(subProd, topRuleName)
})
return subDiagrams
}
/**
* @param {chevrotain.gast.ISerializedTerminal} prod
* @param {string} topRuleName
* @param {string} dslRuleName
*
* @return {RailRoadDiagram.Terminal}
*/
function createTerminalFromSerializedGast(prod, topRuleName, dslRuleName) {
// PATTERN static property will not exist when using custom lexers (hand built or other lexer generators)
var toolTipTitle = undefined
// avoid trying to use a custom token pattern as the title.
if (
typeof prod.pattern === "string" ||
Object.prototype.toString.call(prod.pattern) === "[object RegExp]"
) {
toolTipTitle = prod.pattern
}
return railroad.Terminal(
prod.label,
undefined,
toolTipTitle,
prod.occurrenceInParent,
topRuleName,
dslRuleName,
prod.name
)
}
/**
* @param prod
* @param topRuleName
*
* Converts a single Chevrotain Grammar production to a RailRoad Diagram.
* This is also exported to allow custom logic in the creation of the diagrams.
* @returns {*}
*/
function convertProductionToDiagram(prod, topRuleName) {
if (prod.type === "NonTerminal") {
// must handle NonTerminal separately from the other AbstractProductions as we do not want to expand the subDefinition
// of a reference and cause infinite loops
return NonTerminal(
getNonTerminalName(prod),
undefined,
prod.occurrenceInParent,
topRuleName
)
} else if (prod.type !== "Terminal") {
var subDiagrams = definitionsToSubDiagrams(
prod.definition,
topRuleName
)
if (prod.type === "Rule") {
return Diagram.apply(this, subDiagrams)
} else if (prod.type === "Flat") {
return Sequence.apply(this, subDiagrams)
} else if (prod.type === "Option") {
if (subDiagrams.length > 1) {
return Optional(Sequence.apply(this, subDiagrams))
} else if (subDiagrams.length === 1) {
return Optional(subDiagrams[0])
} else {
throw Error("Empty Optional production, OOPS!")
}
} else if (prod.type === "Repetition") {
if (subDiagrams.length > 1) {
return ZeroOrMore(Sequence.apply(this, subDiagrams))
} else if (subDiagrams.length === 1) {
return ZeroOrMore(subDiagrams[0])
} else {
throw Error("Empty Optional production, OOPS!")
}
} else if (prod.type === "Alternation") {
// todo: what does the first argument of choice (the index 0 means?)
return Choice.apply(this, [0].concat(subDiagrams))
} else if (prod.type === "RepetitionMandatory") {
if (subDiagrams.length > 1) {
return OneOrMore(Sequence.apply(this, subDiagrams))
} else if (subDiagrams.length === 1) {
return OneOrMore(subDiagrams[0])
} else {
throw Error("Empty Optional production, OOPS!")
}
} else if (prod.type === "RepetitionWithSeparator") {
if (subDiagrams.length > 0) {
// MANY_SEP(separator, definition) === (definition (separator definition)*)?
return Optional(
Sequence.apply(
this,
subDiagrams.concat([
ZeroOrMore(
Sequence.apply(
this,
[
createTerminalFromSerializedGast(
prod.separator,
topRuleName,
"many_sep"
)
].concat(subDiagrams)
)
)
])
)
)
} else {
throw Error("Empty Optional production, OOPS!")
}
} else if (prod.type === "RepetitionMandatoryWithSeparator") {
if (subDiagrams.length > 0) {
// AT_LEAST_ONE_SEP(separator, definition) === definition (separator definition)*
return Sequence.apply(
this,
subDiagrams.concat([
ZeroOrMore(
Sequence.apply(
this,
[
createTerminalFromSerializedGast(
prod.separator,
topRuleName,
"at_least_one_sep"
)
].concat(subDiagrams)
)
)
])
)
} else {
throw Error("Empty Optional production, OOPS!")
}
}
} else if (prod.type === "Terminal") {
return createTerminalFromSerializedGast(
prod,
topRuleName,
"consume"
)
} else {
throw Error("non exhaustive match")
}
}
function getNonTerminalName(prod) {
if (prod.nonTerminalName !== undefined) {
return prod.nonTerminalName
} else {
return prod.name
}
}
return {
buildSyntaxDiagramsText: buildSyntaxDiagramsText,
convertProductionToDiagram: convertProductionToDiagram
}
})