ptal / oak

A typed parser generator embedded in Rust code for Parsing Expression Grammars
Apache License 2.0
142 stars 14 forks source link

Allow using external functions as semantic actions #95

Closed trolley813 closed 3 years ago

trolley813 commented 7 years ago

When you write the context actions (such as e > f) in the grammar, they actually must be defined (not only declared) inside the grammar! macro. But it becomes uncomfortable when the grammar is large (for example, one for a whole programming language may require several tens of actions). So, it's currently impossible to write like this:

// in actions/part1.rs
pub fn action1() {
    // ...
}
// in actions/part2.rs
pub fn action2() {
    // ...
}

// in grammar
grammar! test {
    use super::actions::part1::*;
    use super::actions::part2::*;
    // ...
    some_test = e1 > action1 / e2 > action2
    // ...
}

So, for large grammar, allowing use functions from other modules would be useful.

ptal commented 7 years ago

Actually, this is not possible because the Oak compiler needs to access the return type of semantic action to generate the code. What you can do now is to write a kind of "declaration" such that:

// in actions/part2.rs
pub fn action2() -> TypeAction {
    // ...
}

// in grammar
grammar! test {
  use super::actions::part2::*;
  fn forward_action2() -> TypeAction { action2() }
  ...

I know it's not totally satisfactory, I think the only way to get around is to add type ascription on rules such that you can specify the return type of a rule (actually I tried hard to avoid it since I wanted the grammar to be the clearer as possible.) I do not close it for the moment and will give it more thoughts later.

zyvitski commented 5 years ago

Could the return type bot just also be declared outside of the grammar macro? And then imported into the module defined by the grammar macro?

ptal commented 5 years ago

When generating the code we need to know the return type of the semantic actions, but the only knowledge we have is what's inside the current macro, so we need a way to communicate this type. The way chosen in Oak is to declare the function inside the macro. Another possible way (not implemented in Oak) could be to allow type annotations on the rule:

r -> usize = digit+ > string_to_usize

and then we don't need the function string_to_usize to be declared inside the macro. The problem with this approach is that we can have type or arity mismatch that will only be detected when rustc compiles the generated code. With the current approach, such errors can be detected by Oak and the error messages are on the macro (and not the generated code).