ohmjs / ohm

A library and language for building parsers, interpreters, compilers, etc.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

BREAKING CHANGE: Eliminate default action for iteration nodes

pdubroy opened this issue · comments

Current behaviour

Take the following grammar and a simple toUpperCase operation:

const g = ohm.grammar(`
  G {
    exclamation = letter+ "!"
  }
`);
const s = g.createSemantics().addOperation('asString()', {
  exclamation(letterIter, _) {
    return letterIter.asString();
  },
  letter: l => l.sourceString
})

Since there is no _iter action specified, the code letterIter.asString() will invoke the default semantic action, which for iteration nodes returns an array containing the results of calling asString() on each child of the node's children. In other words, the following action would be equivalent to the default behaviour:

_iter(children) {
  return children.map(c => c.asString());
}

Problems

The default behaviour described above can cause confusion, because in some cases, a call to asString() returns string, and in other cases, it returns string[]. The programmer who is writing the actions might not realize they are dealing with an iteration node, and it's also not obvious to people reading the code. In addition, JavaScript's type coercion means that this kind of error can manifest in strange ways, such as unexplained commas in the string output (since 'x' + ['a', 'b'] produces 'xa,b').

This behaviour also means that in TypeScript, the default action is not well typed. In TypeScript terms, if an operation has a return type T for non-terminal nodes, then the default action returns T[] for iteration nodes. This also means that the default action can also return T[] for a non-terminal node, if its only child is an iteration node.

Proposed Changes

To address these problems, I propose that we eliminate the default behaviour for iteration nodes. Under the proposed behaviour, in the example above the call to letterIter.asString() would result in a "missing semantic action" error. To resolve this, you could do one of two things:

  1. Write an explicit _iter action, which specifies the behaviour for iteration nodes.
  2. Explicitly map over the node's children in the parent semantic action. E.g., the parent action could be rewritten as follows:
    exclamation(letterIter, _) {
      return letterIter.children.map(c => c.asString());
    }
    

Since this would be a breaking change, I'm curious to hear how existing Ohm users would feel about it.