I am stuck trying to make a ast/visitor with generic return type

Thanks to previous help from @quinedot I got one big issue sorted but I am still stuck

Outline, I have a parser that emits a concrete AST, the AST nodes struct are generated programmatically.

Then I have a visitor which visits that tree. I want to be able to run different visitors against the same tree.

I am stuck trying to work out how to specify a generic return type for the visitor methods.

Code essentials

Base for the nodes


pub trait IBaseNode: AsAny {
    fn get_name(&self) -> String;
    fn add_child(&mut self, child: Box<dyn IBaseNode>);
    fn accept(&self, visitor: &mut dyn LoxVisitor);
}

pub struct BaseNode {
    children: Vec<Box<dyn IBaseNode>>,
}

base for the visitors

pub trait AsVisitor {
    fn as_visitor(&mut self) -> &mut dyn LoxVisitor;
}

impl<T: LoxVisitor /* + Sized */> AsVisitor for T {
    fn as_visitor(&mut self) -> &mut dyn LoxVisitor {
        self
    }
}

visitor defintion, the trait has default implementations that do nothing but propagate the visit down the tree

pub trait LoxVisitor: AsVisitor {
    fn visit_program(&mut self, node: &ProgramNode) {
        println!("Visit Program");
        for child in &node.base.children {
            child.accept(self.as_visitor())
        }
    }
    fn visit_variable_declaration(&mut self, node: &Variable_declarationNode) {
        println!("Visit Variable_declaration");
        for child in &node.base.children {
            child.accept(self.as_visitor())
        }
    }
......
}

a node

pub struct ProgramNode {
    base: BaseNode,
    text: String,
}
impl IBaseNode for ProgramNode {
    fn get_name(&self) -> String {
        "ProgramNode".to_string()
    }

    fn add_child(&mut self, child: Box<dyn IBaseNode>) {
        self.base.children.push(child);
    }
    fn accept(&self, visitor: &mut dyn LoxVisitor) {
        visitor.visit_program(self);
    }
}
impl ProgramNode {
    pub fn new(text: &str) -> Self {
        Self {
            base: BaseNode { children: vec![] },
            text: text.to_string(),
        }
    }
    pub fn new_dyn(text: &str) -> Box<dyn IBaseNode> {
        Box::new(Self {
            base: BaseNode { children: vec![] },
            text: text.to_string(),
        })
    }
    pub fn get_text(&self) -> &str {
        &self.text
    }
}
... // many more follow

my problem is that I need the visit_xxx methods to return a value. The type of the value is specific to the visitor. So I clearly need something like this in the visitor trait

pub trait LoxVisitor: AsVisitor {
    type TR:Default
    fn visit_program(&mut self, node: &ProgramNode) -> Self::TR {
        println!("Visit Program");
        for child in &node.base.children {
            child.accept(self.as_visitor())
        }
    }

but when I follow through on that I end up with the node type being specialized to the visitor. So I cannot run two visitors through the same AST because the AST type (in particular BaseNode and IBaseNode) is specialized to the visitor type.

I know this is doable because I had a visitor implementation generated by antlr4rust, but the code that antrl4rust generated is incredibly convoluted and I cannot work out how it works (its what driving me to use pest and my own ast/visitor infrastructure). I cant tell which bits are needed for the multiple visitors work and which bits are used to implement other things that I dont need

Are we talking about a known set of types or not? (Spoilers: maybe you can use an enum.)

Can the usefulness of the types be encapsulated in a trait? (Spoilers: maybe you can type erase the return type into a Box<dyn Trait>.)

Playground for future use. (I just collected your snippets without relevant changes, best I could.)

the frustrating thing is that I already have a working version in antlr but dont understand how it works, the code is incredibly convoluted. Antlr generates code files from the grammar for a given language, the rust version is not merged into the official code base, and the project seems abandoned (which is why I decided to try the same thing with pest and my own generator). The c# and java files it generates are simple and easily understood, not the rust.

I know I am pushing your generosity here - would you be willing to look at the working project and see if you can work out how it works?

Note: I am trying to make a generalized toolkit, I do not want any constraints on the return type

I can make a simplified example of the antlr one, just supports , say, 'print int|string'

I can't promise I'll look at it (or figure it out if I do look at it), but feel free to share a link.

reduced to bare bones its here pm100/tinylox (github.com)

the directory src/antlr contains the files generated by the antlr4rust generator

there are two visitors, ErrDetectVisit that checks for errors in the parse tree, and InterpVisit that runs the interpreter

I've done something like this before, though fully statically typed.

Mine looked roughly like this:

trait Visitor {
    type NodeXOutput;
    fn visit_node_x(&mut self) -> Self::NodeXOutput;
    // repeat for each concrete node
}

trait Node {
    type Output<V: Visitor>; // must be filled in as V::VisitSelfOutput

    fn accept<V>(&self, v: &mut V) -> Self::Output<V>;
}

Each node is written to return "whatever V emits when it visits my node type."

Right thats what I want; but I am trying to build a toolkit for making visitors with pest. I read the .pest file and generate supporting traits and classes that the user should then use, and so I need to make the return type generic. The challenge is that I want to allow the caller to instantiate different visitors on the same tree but with different return types.

I know this is doable because I managed to do it with antlr4rust but that code is incredible convoluted and impossible to follow. I cant see how to extract from it the bits I need (why not use the antlr4rust code anyway - because that project seems abandoned and it has many other problems). But that why I posted a miniature version of the antlr4rust project, in case anybody wanted to look at it and explain how they managed to do it.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.