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

Is antlr4's golang visitor pattern in a usable state? #2504

Open
ianamason opened this issue Mar 6, 2019 · 46 comments
Open

Is antlr4's golang visitor pattern in a usable state? #2504

ianamason opened this issue Mar 6, 2019 · 46 comments

Comments

@ianamason
Copy link

I asked this question over on stack overflow, without much enlightenment. I was told to ask it
here.

Is antlr4's golang visitor pattern in a usable state?

I see no working examples, and I see a few pull requests that remain open. I would like to avoid going down the garden path.

#1807

#1843

There are also pull requests that make me think the golang target might be dead in the water.

#2152

The fact that the antlr4 documentation page for golang is over two years stale is also not
encouraging.

So all I am really asking is should I steer clear of golang, or is the documentation just somewhere that google doesn't see :-)

@nikunjy
Copy link

nikunjy commented Apr 14, 2019

Looking to use antlr with golang. Can you address some of these @parrt ?

@parrt
Copy link
Member

parrt commented Apr 15, 2019

Sorry. I don't know Go.

@parrt parrt closed this as completed Apr 15, 2019
@ianamason
Copy link
Author

Might want to be honest then, and remove go from the supported language targets. At least warn the gullible user that they may be disappointed.

@parrt
Copy link
Member

parrt commented Apr 15, 2019

Are you claiming it doesn't support Go in any way?

@ianamason
Copy link
Author

ianamason commented Apr 15, 2019

I am claiming that I could not get the --visitor pattern to work. Nor could I find any working examples
of other people getting it to work. What I did find were issues in this repository that seem to indicate that it never worked, and that people have tried and failed to fix it. I ended up having to fall back on the listener pattern for the task I had at hand, but given what I know now, would probably
have not chosen go for the project I am/was working on.

@parrt
Copy link
Member

parrt commented Apr 15, 2019

I see. ok, well, my apologies. There are lots of different language targets for this project and it's very hard to keep track of the current state of every piece of every API. It's also no longer my focus.

@ianamason
Copy link
Author

Might be an idea then to keep the issue open, so that someone with time and inclination
may succeed where others have failed.

@ocurr
Copy link

ocurr commented Apr 15, 2019

@ianamason According to @pboyer 's comment on #1841 the Go visitor implementation is in a usable state; however, the user is currently required to put in more work then, for instance, the java implementation.

@parrt parrt reopened this Apr 15, 2019
@ianamason
Copy link
Author

@ocurr indeed I did see that issue thread, though may have incorrectly got the impression that the
matter was still unresolved. The certainly was no link to any working visitor example that I could see.
I may add a link to the issue in the original post so that others can benefit.

Is there a working example somewhere that people could follow?

@davesisson
Copy link
Contributor

davesisson commented Apr 16, 2019 via email

@ianamason
Copy link
Author

@davesisson, a simple working example would go a long way to "resolving" this issue. Having to flesh out all the methods still sounds a simpler task than trying to get the listener pattern to accomplish the same thing.

@monodop
Copy link

monodop commented Apr 16, 2019

I would also like to see a working example from somewhere. I am getting hung up on some typing issues that prevent my project from even compiling, so I can't even get to the point where I can try your suggestion @davesisson

Disclaimer, I am very new to Golang so it could be something stupid that I am doing, but having a working, compiling starting point would really help me to get started.

@nikunjy
Copy link

nikunjy commented Apr 17, 2019

Okay I wrote an implementation. I don't think it is the correct way of writing it but it is the best I could do with my limited understanding.

https://github.com/nikunjy/antlr-calc-go

I spent couple of hours trying to understand the antlr golang code base (which I think is not well written and can be improved by collaboration). If there are authors/reviewers who will review the code then I would be down to improve it but looking at the past threads it seems like golang requests have died with time.

@davesisson can you comment on this code base and let me know if this is the right way of doing the implementation. After getting frustrated I ended up writing a Visit(ParseTree) for my visitor which I think is the wrong way of writing, and I'd like you to comment on that if you have the time

func (c *CalculatorVisitorImpl) Visit(tree antlr.ParseTree) interface{} {
	switch val := tree.(type) {
	case *CalculateContext:
		return val.Accept(c)
	case *ToSetVarContext:
		return val.Accept(c)
	}
	return nil
}

I am having to write the Visit with this type switch statement, i would also have to probably write VisitChildren.

@nikunjy
Copy link

nikunjy commented Apr 18, 2019

okay I think i am getting the hang of it (?)
Wrote a generic rules engine too if anyone is looking for that kind of thing
https://github.com/nikunjy/rules

@nikunjy
Copy link

nikunjy commented May 9, 2019

Any comments here @davesisson ?

@apzimmer
Copy link

apzimmer commented May 27, 2019

@nikunjy what do you think is wrong with your example? @parrt Huge fan of Antlr4, and agreed it is worth placing a warning here until the visitor pattern is known to work in Go.

@cvgw
Copy link

cvgw commented Dec 17, 2019

I've been able to get the visitor working but the default code generated with the -vistor option for golang seems like it could be improved.

The most glaring issue (imo) is the composition of the Base{xxxxx}Visitor type. The way the composition and the methods currently are it looks like you could do some inheritance/method overriding (similar to how listener works), but it does not work the way I think most users would reasonably expect.

From my trials the most effective solution was to copy every method from the Base{xxxxx}Visitor generated by antlr and define each method on a new visitor type.

The main issue seems to be with VisitChildren which is defined on the absolute base in the antlr package. I think the idea was that users should be able to override the VisitChildren in their unique implementation of visitor, but I don't believe golang composition works that way. Perhaps the implementation was copied from Java without enough refactoring for golang?

I apologize that I have not included a suggested fix, but I have yet to come up with one.

Example:

type FooVisitor struct {
  BaseFooGrammarVisitor
}

func (v *FooVisitor) VisitChildren(ctx antlr.RuleNode) interface{} {
// do some stuff
}

func (v *FooVisitor) VisitSomeRule(ctx SomeRuleContext) interface{} {
  return v.VisitChildren(ctx)
}

v := &FooVisitor{}

start.Accept(v)

// call *FooVisitor.VisitSomeRule() => *FooVisitor.VisitChildren() => *BaseFooGrammarVisitor.VisitRuleNotOverriden() =>  *BaseParseTreeVisitor.VisitChildren
// *BasePareTreeVisitor.VisitChildren always returns nil

Here is a small go playground demo which might to help highlight my thinking https://play.golang.com/p/oVqsS6eAZPf
Apologies if I have made any omissions or mistakes.

@DavidGamba
Copy link

This is my current implementation of the calculator from chapter 4 using the visitor pattern.
https://github.com/DavidGamba/go-antlr-calc
Hopefully someone finds it useful. Couldn't get the underline error to work.
As mentioned before, had to implement every method.

@mttbx
Copy link

mttbx commented Sep 7, 2020

I really need a elegent antlr visitor for go. Is anybody fix it? Can you send a PR?

@mcolburn
Copy link

I also would like to see an official example on how to use the visitor for golang antlr.

@mitar
Copy link
Contributor

mitar commented May 4, 2021

There are few PRs which are improving state here:

@prnvbn
Copy link

prnvbn commented May 4, 2021

I have personally used #3086 to write a compiler for a language. Before this the Visitor pattern was non-exist. Any other suggested features for this PR are also welcome.

@parrt
Copy link
Member

parrt commented May 4, 2021

Hi Guys, sounds like the Go target needs some love and previous guy can't work on it anymore. The tests seem to fail even. Shall we organize a posse to solve this among the folks here? I don't know Go.

@Joakker
Copy link
Contributor

Joakker commented May 4, 2021

I'd be willing to help. Personally I opened #3066 to improve the documentation, and have created a parser implementation here in go, so I'd consider myself moderately knowledgeable in the subject

@prnvbn
Copy link

prnvbn commented May 4, 2021

I would also like to help! I used Go and ANTLR to write a compiler for a language(WACC) and also opened up #3086. Would definitely want to see better Go support in ANTLR.

@parrt
Copy link
Member

parrt commented May 4, 2021

okay, so it sounds like we have @Joakker and @prnvbn as a Go team!

Could we start by figuring out what's up with the feeling test suite for Go? Then we can add the cool new things you got. I will start a new Go issue

@Joakker
Copy link
Contributor

Joakker commented May 23, 2021

Now that the matter of the test suite is settled, I believe the next course of action would be to clean up the go runtime and divide it into smaller subpackages because the codebase is pretty cluttered and difficult to navigate as is.

This, of course, would break the tests again, but I plan on addressing the matter through many smaller PRs so things will break small.

@parrt
Copy link
Member

parrt commented Dec 28, 2021

Gang, it looks like we have multiple related Go visitor PRs... once the world starts up again next week might be good to take a look at things; adding @jcking here as well

@Sauci
Copy link

Sauci commented Mar 4, 2022

Hello all, any news on this topic?

@elexx
Copy link

elexx commented Jun 1, 2022

Hello. Is there any news on this issue?

@parrt
Copy link
Member

parrt commented Jun 3, 2022

Hi. Go target is currently being worked on but I'm unclear how long it will be before we have an update. The main developer is busy at work. I still haven't learned enough Go to make progress myself.

@perezd
Copy link

perezd commented Oct 7, 2022

Am I correct to understand that -visitor is now implemented as expected?
https://github.com/antlr/antlr4/releases/tag/4.11.0

@parrt
Copy link
Member

parrt commented Oct 8, 2022

Heh @jimidle can you remind me where we are on Go visitors?

@perezd
Copy link

perezd commented Oct 8, 2022

FWIW, when I run with -visitor, I see files! That's gotta be a good sign right? :)

@parrt
Copy link
Member

parrt commented Oct 8, 2022

a great sign and @jimidle has done a HUGE overhaul of target.

@jimidle
Copy link
Collaborator

jimidle commented Oct 11, 2022

The visitor and listener all seem to work. I am using a listener but not a visitor.

Create your own struct with the generate base struct embedded in it, then implement the funcs you want to override on your created struct. If you do not override a func in the base, then the base will be called and it will do nothing. If you have a func in your local struct, then it will be called with that receiver. I have not looked in to the visitor too much, but the listener works as advertised. I have no reason to believe that the visitor does not work, but I will do a little research on it and obviously fix anything that needs fixing.

@perezd
Copy link

perezd commented Oct 14, 2022

@jimidle So I tried what you suggested but it seems to be misbehaving, here's a repo w/ a devcontainer all set up w/ deps and what not to repro it: https://github.com/perezd/visitor-example

Am I just doing this wrong somehow? "HERE" never gets printed...I'm trying the standard JSON grammar.

@ghost
Copy link

ghost commented Oct 30, 2022

Hi @perezd,

I Had a similar experience.

I noticed some nice comments in "tree.go" on line 72 to 91

they may be related to the problem, am not sure though.

// TODO
//func (this ParseTreeVisitor) Visit(ctx) {
//	if (Utils.isArray(ctx)) {
//		self := this
//		return ctx.map(function(child) { return VisitAtom(self, child)})
//	} else {
//		return VisitAtom(this, ctx)
//	}
//}
//
//func VisitAtom(Visitor, ctx) {
//	if (ctx.parser == nil) { //is terminal
//		return
//	}
//
//	name := ctx.parser.ruleNames[ctx.ruleIndex]
//	funcName := "Visit" + Utils.titleCase(name)
//
//	return Visitor[funcName](ctx)
//}

using the suggestion at #1841 (comment)

I got Visit and VisitChildren - when inheriting from antlr.ParseTreeVisitor - to say Hello,

The rule-based functions seems to be overridden when inheriting from Base....ParserVisitor , they seems to be on mute for now

type ...ParserVisitorImpl struct {
	antlr.ParseTreeVisitor
	//ParseTreeVisitor *Base...ParserVisitor
}

getting to understand all the involved antlr code to get something to work again may be quite an occupation

no quick win possible using the visitor pattern in go I suppose

@ghost
Copy link

ghost commented Oct 30, 2022

I recommend to use a tree walker instead.

An example that can be transposed to go is available at https://tomassetti.me/antlr-mega-tutorial/#chapter38

@ghost
Copy link

ghost commented Nov 1, 2022

I got something to work using the "visitor" pattern.

I share this to @perezd, @parrt, @pboyer and @jimidle, @danaugrs with enthusiasm

based on https://www.antlr.org/download/antlr-4.11.1-complete.jar

gaining inspiration from

antlr4 -Dlanguage=Go -visitor -no-listener -package parser -o ./ parser/Blah.g4
type BlahVisitorImpl struct {
	BaseBlahVisitor
}

implement Visit and VisitChildren similar to stated in #1841 (comment)

override all visit methods towards and including all levels (deep) in the grammer you want to visit, based on the generated BaseBlahVisitor, style. copy pased from BaseBlahVisitor and change to BlahVisitorImpl basically and do your other stuff where needed

func (v *BlahVisitorImpl) VisitVisitBlahLevel1(ctx *Snowflake_fileContext) interface{} {
	fmt.Println(fmt.Sprintf("DEBUG VisitBlahLevel1"))
	return v.VisitChildren(ctx)
}

func (v *BlahVisitorImpl) VisitVisitBlahLevel2(ctx *Snowflake_fileContext) interface{} {
	fmt.Println(fmt.Sprintf("DEBUG VisitBlahLevel2"))
	return v.VisitChildren(ctx)
}

// ...

at each level, the return v.VisitChildren(ctx) call at the end seems to steer the "visit"

the main body would be something similar to

input := antlr.NewInputStream(strSqlText)
lexer := parser.NewBlahLexer(input)
stream := antlr.NewCommonTokenStream(lexer, antlr.TokenDefaultChannel)
myparser := parser.NewBlahParser(stream)
myparser.BuildParseTrees = true
myparser.AddErrorListener(antlr.NewDiagnosticErrorListener(true))
fileContext := myparser.Level1()
visitor := parser.BlahVisitorImpl{}
visitor.Visit(fileContext)

I admit that a working reference example - maintained per release - would be nice

@fikin
Copy link

fikin commented Aug 12, 2023

i think we should have different visitor template generated by antlr for go.

currently generated class and its base are influenced way too literally by java. in go inheritance and polymorphism are not implemented by structure composition (as facilitated by generated visitor and corresponding antlr base class), but by code duplication and execution branching.
plus the other problems with current template, like use of interface{} which leads to extra type casting and error handling for the calling code, and lack of error in returned arguments makes this generated+base class code, imho completely useless.

for example, the most straightforward visitor to print entire tree are 2 general purpose functions and nothing more:

func printNodeVisitor(node antlr.Tree) (string,error) {
  switch n:= node.(type) {
    case antlr.ErrorNode:
      return "", fmt.Errorf(">>%s<<",n.(antlr.ParseTree).GetText())
    case antlr.TerminalNode:
      return n.(antlr.ParseTree).GetText(), nil
    default:
      return printNodeChildrenVisitor(n)
  }
}

func printNodeChildrenVisitor(node antlr.Tree) (string, error) {
  arr := []string{}
  for _, c := range node.GetChildren() {
    str, err := printNodeVisitor(c)
    if err != nil {
      return "", err
    }
    arr = append(arr, str)
  }
  return strings.Join(arr, ""), nil
}

of course, in practice one could use a bit more stricter form of switch construct:

  switch n:= node.(type) {
    case IXYZContext, IDEFContext: /* these are generated interfaces, for each parser rule */
      return printNodeChildrenVisitor(n) /* node specific logic */
    case antlr.ErrorNode:
      return "", fmt.Errorf(">>%s<<",n.(antlr.ParseTree).GetText())
    case antlr.TerminalNode:
      return n.(antlr.ParseTree).GetText(), nil
    default:
      return "", fmt.Errorf("unrecognized node type : %v", n)
  }

and use purpose designed typed arguments, suitable for the visiting task.

i'm of the opinion that for golang target, antlr should generate template functions with switch statements and ask the coder to customize them as per need.
current code is generally not beneficial for any use case and basically it is only complicating the end result.

@hugheaves
Copy link

Just an an "FYI" in case anyone else runs into this issue:

My current "workaround" for this is to execute a one-line patch on the generated visitor code during the build process. The patch makes the "VisitChildren" function a function pointer in the BaseVisitor struct, so that it can be overridden in my Visitor implementation.

Here's the patch:
https://github.com/hugheaves/scadformat/blob/main/internal/parser/openscad_base_visitor.go.patch

Here's where VisitChildren is overridden:
https://github.com/hugheaves/scadformat/blob/018681900884365676409ae2fddef814d76bf60e/internal/formatter/formattingvisitor.go#L50

It's obviously a bit of a hack, but it avoids having to implement every VisitXXX function in my visitor.

@codershangfeng
Copy link

@hugheaves Yep, your approach works well on my side, but I keep the *antlr.BaseParseTreeVisitor in the generated base visitor to avoid impl. it in the customized visitor 😄

@jimidle
Copy link
Collaborator

jimidle commented Mar 7, 2024 via email

@hugheaves
Copy link

hugheaves commented Mar 7, 2024

I really need to crate a sample project. I have used both the visitor and the walker in anger. The thing to remember is that Go isn't a class based language. I am putting in some time on ANTLR shortly and will provide an example. I have tried to remove embeddings via pointers as that isn't the right way but it was initially implemented like that. I will try to provide an example.

That would be helpful. Because Go is not a "class based language", the approach you describe above (#2504 (comment)) does not work.

@jimidle
Copy link
Collaborator

jimidle commented Mar 7, 2024 via email

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