javascriptparsinggrammarpegjs

Conditional grammar rule in PEGjs


I'm trying to implement a simple DSL that parses basic arithmetic expressions. This needs to be done in the browser, so I'm using PEGjs to generate the parser.

Terms in the expression can be numbers (integers or real), variables (variables are properties on a context object passed to the parser), conditionals or properties accessed via dot notation.

I want the conditionals to look like this condition?value, where if condition is true, the term equates to value. The variables on either side of the ? could also be dot notation accessed properties of an object like this object.property1?object.property2.

So if the parser is passed an object like this:

context = {
  depth: 100,
  material: {
    thickness: 20
    include: true
  }
  edge: {
    face: 4.5
  }
}

The expression:

500 + depth + material.include?edge.face + material.thickness should equate to 624.5.

I've been using the PEGjs online editor. I've tried lots of different approaches, but I can't seem to nail the conditional. Everything else works. Here are the relevant rules:

Variable "variable"
  = variable:identifier accessor:("." identifier)* {
      var result = context[variable], i

      for (i = 0; i < accessor.length; i++) {
        result = result[accessor[i][1]]
      }

      return result
    }

identifier
  = identifier:$([0-9a-zA-Z_\$]+)

Conditional
  = condition:Variable "?" value:Variable {
    return condition ? value : 0
  }

I've looked at the example grammar for javascript in the PEGjs github repo, and the conditional rule looks a lot like what I've got here, but I still can't get it to work.

What would be the correct way to implement a conditional statement like the one I've described in a PEGjs rule?


Solution

  • I know that this is a bit late, but the issue is that your variable is a string evaluating to "material.include".

    Look at this code:

    var result = context[variable], i
    

    You are trying to access a property named "material.include" from your context object, which would look like this:

    {
        "material.include": true
    }
    

    Rather than trying to access the object referenced by the "material" property, and then the "include" property off the resulting object, which would look like this:

    {
        "material": {
            "include": true
        }
    }
    

    The solution would be to split the variable string by "." characters and then recursively find your property:

    Variable "variable"
      = variable:identifier accessor:("." identifier)* {
          var path = variable.split(".");
          var result = path.reduce( (nextObject, propName) => nextObject[propName], context );
    
          for (var i = 0; i < accessor.length; i++) {
            result = result[accessor[i][1]]
          }
    
          return result
        }
    

    Note that this solution is not complete, as it will cause an error if you try to access material.include where material is never defined in your context. You may want to add additional error handling, but it does work for the given example.