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
Proposal: interface type productions #229
Comments
I've thought the same and I agree it would be nice, but I haven't come up with an API that I like. Maybe something like this could work... package main
import "fmt"
type sum interface{ sum() }
type One struct{}
func (o One) sum() {}
type Two struct{}
func (t Two) sum() {}
func OneOf[T any](a ...T) {
for _, b := range a {
fmt.Printf("%T\n", b)
}
}
func main() {
OneOf[sum](One{}, Two{})
fmt.Println("Hello, 世界")
} |
You can achieve this with ParseFromLexer |
Ah, I will give this a try - the docs made me think this only accepted the top-level production:
|
Intriguing, but I think I'm not making the connection with how the example function would be used with the parser. Would |
That's correct, but the idea would be to have a separate sub-parser, then implement Parseable and call ParseFromLexer with the lexer, on the sub-grammar.
Yes that was my thought, and internally it would effectively translate to a disjunction of each element, a bit like: type intermediate struct {
One *One ` @@`
Two *Two `| @@
} |
Ah that's unfortunate. In my case, the "grammar" and the "sub-grammar" have overlap in what productions they can produce. I don't think this solution would work for me in this case.
Ah ok, I see - and what if the scenario is that my interface |
I threw together an ugly proof of concept, but it works and demonstrates a bit of what I'd like to be able to do: |
Why is that a problem? You can have a sub-parser include overlapping productions, that's not an issue.
The same way you would do it normally - implement Parseable. |
BTW re. precedence climbing, the included example also does this by overriding Parseable. |
RE:
I don't think this works - If the production type is itself an interface value (for example type Expr struct {
VariantA *VariantA
VariantB *VariantC
VariantC *VariantC
// ...
VariantZ *VariantZ
} Additionally, if I want to encode operator precedence in the parsed AST, it results in a hierarchy that is deeply nested: type Expr struct {
PrecLevel BinaryLevelTop
}
type BinaryLevelTop struct {
Lhs BinaryLevelTopSub1
Op *string
Rhs *BinaryLevelTop
}
type BinaryLevelTopSub1 struct {
Lhs BinaryLevelTopSubN
Op *string
Rhs *BinaryLevelTopSub1
}
// ...
type BinaryLevelTopSubN struct {
Unary Unary
}
type Unary struct { Op *string, Atom Atom }
type Atom struct { /* ... */ } If I have an expression that consists of only an What I would like to be able to do instead is Which leads me to, RE:
Let's say I have anonymous lambda functions in my expression grammar. My For a concrete example of the full behavior I'd like to use: type File struct {
Statements []Stmt `@@*`
}
type Stmt struct {
ExprStmt Expr `@@`
/** other stmt types */
}
// The Expr interface type
type Expr interface{ isExpr() }
// The parser definition
var theParser = participle.MustBuild(&File{}, participle.UseInterface(exprParser))
func exprParser(lex *lexer.PeekingLexer) (Expr, error) { return exprParserWithPrec(lex, 0) }
func exprParserWithPrec(lex *lexer.PeekingLexer, minPrec int) (Expr, error) {
/* ... example of parsing specific productions ... */
if lex.Peek().Value == "func" {
/*... parse funcHeader, etc ...*/
var funcBody []Stmt
for lex.Peek().Value != "}" {
var s Stmt
if err := theParser.ParseProduction(lex, &s); err != nil { return err }
funcBody = append(funcBody, s)
}
return FuncExpr{ funcHeader, funcBody }
}
/* ... */
} |
Yes... I meant that you would implement Parseable on the concrete elements of the interface. This wouldn't work if your top-level parsing logic is more than But eg. for a terminal this would be fine. type Value interface { val() }
type Num struct {
Value int64 `@Int`
}
func (Num) val() {}
type String struct {
Value string `@String`
}
func (String) val() {}
type Lambda struct {
Params []*Param `"func" "(" @@* ")"`
Body *Body `"{" @@ "}"`
}
func (Lambda) val() {} One downside of this approach is non-struct types have to be wrapped in a struct.
|
Ah, ok - I misunderstood what you meant initially, I see what you are saying now, and that makes sense.
I agree with the fact that the average case is In my case, the expression grammar can all be parsed with a single-token lookahead and if I can place this all inside a single function that is just munching tokens and producing Would you consider it ergonomic enough if there were a separate |
Yeah, I think that would be totally fine. I think ParseProduction() would be handy too, if you're interested, though my vague memory indicates that might require quite a bit of plumbing. |
Based on what I've seen I think you're right about the plumbing. There's a If the ergonomics work out there, I can clean up and put together a PR |
I have a working prototype, but there are some interesting cases to consider: If a user calls There may be a "broken chain" where there are some productions that only get parsed via I think this API makes the most sense if, rather than a specific I'm going to keep playing with this a bit this morning and will push up the proof-of-concept branch a bit later, but wanted to solicit some thoughts on the above |
Valid concern, and obviously already the case with Somewhat related, I also had the thought that this solution shouldn't really be explicitly tied to interfaces - it's basically an alternative mechanism for specifying a
This seems less obvious to me, but also I have a plan to make the parser generic (see #208) which this would break. |
BTW breaking the API is not a big concern (assuming it's not a gratuitous change) as v2 is still in alpha. |
I'm in agreement with all of your conclusions, but I think it will be easier to discuss specific ergonomic issues etc. once there's some concrete code to talk about. I'm making these adjustments to the proof-of-concept, and once that's done I'll open a draft PR for some feedback and with I think a few additional questions around handling of unions. |
@alecthomas I've put up a draft PR at #233. I tried to add some complex test cases to test & demonstrate the use cases for these two features. I've also left some comments there discussing a few details around unions and custom productions. Notably, right now this PR does not include the |
Now that #233 has been merged, are you still open to the addition of |
Yeah I think so, as long as it's not crazy intrusive it seems like a reasonable thing to have. |
In the past, when I've written recursive descent parsers by hand, I have used interfaces to represent "classes" of productions, such as
Stmt
,Expr
, etc - this simplifies the consuming code considerably.I would like to be able to do something similar using
participle
- for example, I would like to define anExpr
interface, and then parse that manually while allowing the rest of the grammar to be managed byparticiple
.Perhaps it could look something like this:
As an additional nice-to-have, it would be cool if there was a way to give a
*lexer.PeekingLexer
and a production to the parser, and have it fill in the data for me. Then I could composeparticiple
-managed productions within the manual parsing code forExpr
. This would allow me to break out ofparticiple
in a limited way, just for the things that I need (such as managing precedence climbing)The text was updated successfully, but these errors were encountered: