Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parameterizing semantic operations #38

Closed
seidtgeist opened this issue Sep 10, 2015 · 8 comments
Closed

Parameterizing semantic operations #38

seidtgeist opened this issue Sep 10, 2015 · 8 comments

Comments

@seidtgeist
Copy link
Contributor

I'll start with an example grammar:

DateGrammar {
  Date = "now"   -- now
       | "later" -- later
}

And a semantic operation:

semantics.addOperation('extractDate', {
  'Date_now': function() {
    let now = new Date()
    return now
  },

  'Date_later': function() {
    let now = new Date()
    let later = new Date(now + 3 * 3600)
    return later
  }
})

Now, when writing tests for this semantic operation I'd like to set a fixed date instead of relying on the current time. I realize this probably wasn't possible short of wrapping everything in another scope or (gulp) using dynamic scope.

So I imagined something like this:

What if semantic operations could be parameterized?

let match = grammar.match('now')
let now = new Date(1440000000000)
let date = semantics(match).extract(now)
assert(+date === +now)

And the parameters could be accessed like this?

semantics.addOperation('extractDate', {
  'Date_now': function() {
    let now = this.parameters[0]
    return now
  },

  'Date_later': function() {
    let now = this.parameters[0]
    let later = new Date(now + 3 * 3600)
    return later
  }
})

This could be accessed via this.parameters, or this.context, or this.contextParameters, and would be the same value in all nodes.

@alexwarth
Copy link
Contributor

Thanks, Stephan.

I'd love to enable Ohm's operations to take arguments, and have been thinking about how to do this for a while. This is a good time to make it a higher-priority item, so I'm glad you've opened this issue.

Accessing the operation's arguments via this in a semantic action doesn't seem right, though, because this corresponds to a CST (concrete syntax tree) node, not the operation. And the CST node doesn't have arguments, at least in your formulation.

I'll think about this a little more over the next week, and let you know if I come up with anything.

Cheers,
Alex

@seidtgeist
Copy link
Contributor Author

Alex, thank you for looking into this :)

@pdubroy
Copy link
Contributor

pdubroy commented Sep 14, 2015

I would think that the arguments should behave like regular function arguments -- so it should be possible to write a semantic action like this:

name: function(lastName, comma, firstName) {
  return lastName.stringValue('upper') + firstName.stringValue('lower');
}

...where stringValue is an operation that takes one parameter. For the case where you want all nodes to be parameterized by the same value, I think that closing over a variable is already a decent way to do it:

function makeActionDict(now) {
  return {
    Date_later: function() {
      return new Date(now + 3 * 3600);
    },
    ...
  };
}
semantics.addOperation('extractDate', makeActionDict(1440000000000));

@seidtgeist
Copy link
Contributor Author

For the case where you want all nodes to be parameterized by the same value, I think that closing over a variable is already a decent way to do it

Yes, that's true. My concern was for parsing incrementally (as input is gathered) where this might be creating lots of function objects. I'm not sure about the overhead involved though, parsing might actually be more expensive :)

Oh, and actually in my case it's probably fine to create an action dict at the start of the input, since the context won't have to change or exist for that long. Thanks :)

@pdubroy
Copy link
Contributor

pdubroy commented Sep 14, 2015

Yes, that's true. My concern was for parsing incrementally (as input is gathered) where this might be creating lots of function objects. I'm not sure about the overhead involved though, parsing might actually be more expensive :)

You can reuse the same semantics instance with different MatchResult instances, so the overhead shouldn't be a concern.

@alexwarth
Copy link
Contributor

Fixed by 8e3ad7f.

It used to be that you could only add parameter-less operations to a semantics, e.g.,

myGrammar.semantics().addOperation('myOp', { ... });

This is still allowed, but it's now shorthand for:

myGrammar.semantics().addOperation('myOp()', { ... });

The first argument to Semantics.prototype.addOperation is now a string that specifies both the name of an operation, and (optionally) the names of its arguments. (This string is parsed using the NameAndFormals rule in the OperationsAndAttributes grammar, which you can see at src/operations-and-attributes.ohm.) So if we wanted myOp to take two parameters, x and y, we would write:

myGrammar.semantics().addOperation('myOp(x, y)', { ... });

These arguments must be supplied every time the operation is invoked, and are accessed by name via the node's args property, e.g.,

myGrammar.semantics().addOperation('myOp(x, y)', {
  Foo: function(bar) {
    return bar.myOp(this.args.x + 1, this.args.y * 2);
  },
  ...
});

(Stephan: as you can see, I changed my mind on the this thing. Thanks again for the suggestion! :))

Please let us know if you have any questions, comments, etc.

Cheers,
Alex

@seidtgeist
Copy link
Contributor Author

Alex, this looks great!

I'll change my current usage of my semantics and report back if I lean anything new.

Thank you for adding a major feature so quickly!

@alexwarth
Copy link
Contributor

Glad you like it, Stephan! Looking forward to hearing whatever thoughts you have about it after you get a chance to use it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants