Skip to content

Visitor API

Abe Pralle edited this page Apr 18, 2022 · 11 revisions

Froley generates and updates two different "Visitor" base classes that can be used to rebuild and/or analyze an AST: Visitor and InspectionVisitor.

Both Visitor classes use a sophisticated multi-tiered double dispatch system that allows calls to be initiated on base class Cmd references while ensuring that the calls are dispatched to the appropriate extended-class overloads.

Visitor

Base class Visitor is a "rebuilding" Visitor. By default it will visit each node in an AST, rebuilding the AST by reassigning each child to be the node resulting from visiting the child. The rebuilt AST will be the same unless you overload various node handlers to return different nodes - for example, you might define the on() for a general Access node to return a resolved GetVar node instead.

Here is the standard per-node call sequence for a standard Visitor. By default each handler invokes calls to the indented handlers underneath - for example, the default implementation of on() invokes on_visit() and then on_validate().

on(cmd:CmdType)->Cmd
  on_visit(cmd:CmdType)
    on_visit_children(cmd:CmdType)
  on_validate(cmd:CmdType)->Cmd

Due to the double dispatch system, the visitor methods you call are not the same as the methods you overload to change behavior. The table below shows the mapping.

Method Call Overloadable Handler
visit(Cmd)->Cmd on(CmdType)->Cmd
visit_content(Cmd)->Cmd on_visit(CmdType)
visit_children(Cmd)->Cmd on_visit_children(CmdType)
validate(Cmd)->Cmd on_validate(CmdType)

Here is a table describing the overloadable handlers in more detail.

Visit Callback Description
on(CmdType)->Cmd Overload this to return a different Cmd node to replace the given subtree and/or to have total control over how the given node is visited.
on_visit(CmdType) Overload this to handle visiting the node without needing a return value. By default it simply invokes on_visit_children(), so if you overload this method then you must call on_visit_children() if you want to.
on_visit_children(CmdType) Overload/overide this to define how child nodes are visited for a given node type.
on_validate(CmdType)->Cmd Overload this to convert a node to another type after all the standard calls have been made.

In extended Visitor classes you only need to overload the node types you want to add custom logic to.

InspectionVisitor

Base class InspectionVisitor<<$ReturnType>> is designed to analyze an AST without necessarily modifying the structure. It allows custom data to be passed back from visit() calls and AST traversal terminates early with a non-zero (non-null, non-false) return value.

The API is similar but different to that of Visitor.

on(cmd:CmdType)->$ReturnType
  on_visit(cmd:CmdType)
  on_visit_children(cmd:CmdType)->$ReturnType

Due to the double dispatch system, the visitor methods you call are not the same as the methods you override or overload to change behavior. The table below shows the mapping.

Method Call Overloadable Handler
visit(Cmd)->Cmd on(CmdType)->Cmd
visit_content(Cmd)->Cmd on_visit(CmdType)
visit_children(Cmd)->Cmd on_visit_children(CmdType)

Here are two common InspectionVisitor patterns:

  1. Search for something specific, then return the result immediately. In this case extend InspectionVisitor<<$ResultType>>, then override on() methods and return true/non-null/non-zero when you find what you're looking for. For example, to check and see if a node subtree contains a Return node:

     ...
     if (ContainsReturn.visit(cmd)) ...
     ...
     class ContainsReturn : InspectionVisitor<<Logical>> [singleton]
       METHODS
         method on( cmd:Return )->Logical
           return true
     endClass
    
  2. Visit every node without stopping early. In this case extend InspectionVisitor and overload on_visit() methods that you're interested in performing analysis on. For example, to build a table of all unique literal strings in an AST:

     local collector = StringCollector()
     @trace collector.unique_strings
     ...
     class StringCollector : InspectionVisitor
       PROPERTIES
         unique_strings = Set<<String>>()
    
       METHODS
         method on_visit( cmd:LiteralString )
           unique_strings.add( cmd )
     endClass
    

Clone this wiki locally