Attached below is my full peggyjs grammar. It is technically feature complete, but struggles with a last problem. The test term that is not being correctly parsed is 2d6 + 1d8
. The intended result is to receive the term back as string, unchanged, however the parser will not process it, as it is unable to loop around to the start from a PoolTerm
to parse another addition.
I attempted to solve the issue by adding PoolTerm as allowed alternative to the Factor term, like so:
Factor
= _ "(" _ expr:PoolTerm _ ")" flavor:Flavor? _ {
return "(" + expr + ")" + (flavor || "")
}
/ PoolTerm
/ RollData
This however triggers an infinite loop error due to left recursion:
Line 36, column 1: Possible infinite loop when parsing (left recursion: PoolTerm -> RollTerm -> Factor -> PoolTerm)
I’ve experimented around, but just ended up with breaking the grammar even more. How could the grammar be modified to solve this issue?
Full grammar:
{{
const funcMap = {
/* Math */
"**": "exp",
"%": "mod",
/* Logic */
"||": "or",
"&&": "and",
"!": "neg",
/* Comparison */
"=": "eq",
"==": "eq",
"===": "eq",
"!=": "neq",
"!==": "neq",
"<": "lt",
">": "gt",
"<=": "lte",
">=": "gte",
/* Bitwise Operations */
"~": "bitFlip",
"|": "bitOr",
"^": "bitXOr",
"&": "bitAnd",
/* Other */
"??": "ifNull",
"ternaryOp": "ifThenElse",
"?:": "ifElse"
}
}}
PoolTerm
= _ "{" head:RollTerm tail:(_ "," _ RollTerm)* "}" mod:RollModifier? flavor:Flavor? {
return "{" + tail.reduce(function(result, element) {
return result + ", " + element[3]
}, head).trim() + "}" + (mod || "") + (flavor || "")
}
/ RollTerm
RollTerm "Dice Roll"
= _ left:Factor [dD] right:(Factor / [a-z]i) mod:RollModifier? flavor:Flavor? _ {
return left + "d" + right + (mod || "") + (flavor || "")
}
/ RollTermWithFunc
RollModifier = $([^ (){}[]$+-*/,]+)
RollTermWithFunc
= _ head:Function "d" tail:Factor _ {
return "(" + head + ")d" + tail;
} / ShortHandTernary
ShortHandTernary
= head:Ternary tail:(_ "?:" _ Ternary)* {
return tail.reduce(function(result, element) {
return funcMap["?:"] + "(" + result + ", " + element[3] + ") ";
}, head).trim()
}
Ternary
= head:Logic tail:(_ "?" _ Logic _ ":" _ Logic)* {
return tail.reduce(function(result, element) {
return funcMap["ternaryOp"] + "(" + result + ", " + element[3] + ", " + element[7] + ") ";
}, head).trim()
}
Logic
= head:LogicAnd tail:(_ ("||" / "??") _ LogicAnd)* {
return tail.reduce(function(result, element) {
return funcMap[element[1]] + "(" + result + ", " + element.slice(3).join("") + ") "
}, head || "").trim()
}
LogicAnd
= head:BitWiseOr tail:(_ "&&" _ BitWiseOr)* {
return tail.reduce(function(result, element) {
return funcMap[element[1]] + "(" + result.trim() + ", " + element.slice(3).join("").trim() + ") "
}, head || "").trim()
}
BitWiseOr
= head:BitWiseXOr tail:(_ "|" _ BitWiseXOr)* {
return tail.reduce(function(result, element) {
return funcMap[element[1]] + "(" + result.trim() + ", " + element.slice(3).join("").trim() + ") "
}, head || "").trim()
}
BitWiseXOr
= head:BitWiseAnd tail:(_ "^" _ BitWiseAnd)* {
return tail.reduce(function(result, element) {
return funcMap[element[1]] + "(" + result.trim() + ", " + element.slice(3).join("").trim() + ") "
}, head || "").trim()
}
BitWiseAnd
= head:Equality tail:(_ "&" _ Equality)* {
return tail.reduce(function(result, element) {
return funcMap[element[1]] + "(" + result.trim() + ", " + element.slice(3).join("").trim() + ") "
}, head || "").trim()
}
Equality
= head:Relation tail:(_ EqualityOperator _ Relation)* {
return tail.reduce(function(result, element) {
return funcMap[element[1]] + "(" + result.trim() + ", " + element.slice(3).join("").trim() + ") "
}, head || "").trim()
}
EqualityOperator = "===" / "==" / "=" / "!===" / "!=="
Relation
= head:Expression tail:(_ RelationOperator _ Expression)* {
return tail.reduce(function(result, element) {
return funcMap[element[1]] + "(" + result.trim() + ", " + element.slice(3).join("").trim() + ") "
}, head || "").trim()
}
RelationOperator = "<=" / ">=" / "<" / ">"
Expression
= head:Term tail:(_ ("+" / "-") _ Term)* {
return tail.reduce(function(result, element) {
return result + " " + element.join("")
}, head || " ").trim();
}
Term
= head:Exponential tail:(_ ("*" / "/" / "%") _ Exponential)* {
return tail.reduce(function(result, element) {
if(element[1] === "%") {
return funcMap[element[1]] + "(" + result + ", " + element.slice(3).join("") + ") "
}
return result + " " + element.join("")
}, head || " ").trim();
}
Exponential
= head:Prefix tail:(_ "**" _ Prefix)* {
return tail.reduce(function(result, element) {
return funcMap[element[1]] + "(" + result + ", " + element.slice(3).join("") + ") "
}, head || " ").trim()
}
Prefix
= _ op:PrefixOperator _ factor:(Factor / Function) _ {
const func = funcMap[op] || null;
if(func) {
return func + "(" + factor.trim() + ") "
}
else {
return op + factor
}
}
/ Factor / Function
PrefixOperator = "!" / "~"
Function
= funcName:FunctionName "(" tail:($(_ ShortHandTernary _ "," _)* $(_ ShortHandTernary _)) ")" flavor:Flavor? _ {
return funcName + "(" + tail.reduce(function(result, element) {
return (result + element).replace(/s*,s*/g, ", ")
}, "").trim() + ")" + (flavor || "");
}
FunctionName = $([a-z$_]i [a-z$_0-9]i*)
Factor
= _ "(" _ expr:PoolTerm _ ")" flavor:Flavor? _ {
return "(" + expr + ")" + (flavor || "")
}
/ RollData
RollData "Variable"
= _ "@" [a-z_]i [a-z0-9._]i* _ {
return text()
}
/ Numeric
Numeric "Number"
= _ [0-9]+ ("." [0-9]+)? Flavor? _ {
return text()
}
Flavor "Roll Flavor"
= $("[" [^[]]+ "]")
_ "Whitespace"
= [ ]*
This is the link to the peggyjs online parser: https://peggyjs.org/online.html
Appreciate the help.