Skip to content

Provide a BasicVisitor #88

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

Merged
merged 1 commit into from
May 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ It is built with only standard library dependencies. It additionally ships with
- [construct_keys](#construct_keys)
- [Visitor](#visitor)
- [visit_method](#visit_method)
- [BasicVisitor](#basicvisitor)
- [Language server](#language-server)
- [textDocument/formatting](#textdocumentformatting)
- [textDocument/inlayHints](#textdocumentinlayhints)
Expand Down Expand Up @@ -373,6 +374,20 @@ Did you mean? visit_binary
from bin/console:8:in `<main>'
```

### BasicVisitor

When you're defining your own visitor, by default it will walk down the tree even if you don't define `visit_*` methods. This is to ensure you can define a subset of the necessary methods in order to only interact with the nodes you're interested in. If you'd like to change this default to instead raise an error if you visit a node you haven't explicitly handled, you can instead inherit from `BasicVisitor`.

```ruby
class MyVisitor < SyntaxTree::BasicVisitor
def visit_int(node)
# ...
end
end
```

The visitor defined above will error out unless it's only visiting a `SyntaxTree::Int` node. This is useful in a couple of ways, e.g., if you're trying to define a visitor to handle the whole tree but it's currently a work-in-progress.

## Language server

Syntax Tree additionally ships with a language server conforming to the [language server protocol](https://microsoft.github.io/language-server-protocol/). It can be invoked through the CLI by running:
Expand Down
2 changes: 2 additions & 0 deletions lib/syntax_tree.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
require_relative "syntax_tree/node"
require_relative "syntax_tree/parser"
require_relative "syntax_tree/version"

require_relative "syntax_tree/basic_visitor"
require_relative "syntax_tree/visitor"
require_relative "syntax_tree/visitor/field_visitor"
require_relative "syntax_tree/visitor/json_visitor"
Expand Down
74 changes: 74 additions & 0 deletions lib/syntax_tree/basic_visitor.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# frozen_string_literal: true

module SyntaxTree
# BasicVisitor is the parent class of the Visitor class that provides the
# ability to walk down the tree. It does not define any handlers, so you
# should extend this class if you want your visitor to raise an error if you
# attempt to visit a node that you don't handle.
class BasicVisitor
# This is raised when you use the Visitor.visit_method method and it fails.
# It is correctable to through DidYouMean.
class VisitMethodError < StandardError
attr_reader :visit_method

def initialize(visit_method)
@visit_method = visit_method
super("Invalid visit method: #{visit_method}")
end
end

# This class is used by DidYouMean to offer corrections to invalid visit
# method names.
class VisitMethodChecker
attr_reader :visit_method

def initialize(error)
@visit_method = error.visit_method
end

def corrections
@corrections ||=
DidYouMean::SpellChecker.new(
dictionary: Visitor.visit_methods
).correct(visit_method)
end

DidYouMean.correct_error(VisitMethodError, self)
end

class << self
# This method is here to help folks write visitors.
#
# It's not always easy to ensure you're writing the correct method name in
# the visitor since it's perfectly valid to define methods that don't
# override these parent methods.
#
# If you use this method, you can ensure you're writing the correct method
# name. It will raise an error if the visit method you're defining isn't
# actually a method on the parent visitor.
def visit_method(method_name)
return if visit_methods.include?(method_name)

raise VisitMethodError, method_name
end

# This is the list of all of the valid visit methods.
def visit_methods
@visit_methods ||=
Visitor.instance_methods.grep(/^visit_(?!child_nodes)/)
end
end

def visit(node)
node&.accept(self)
end

def visit_all(nodes)
nodes.map { |node| visit(node) }
end

def visit_child_nodes(node)
visit_all(node.child_nodes)
end
end
end
67 changes: 1 addition & 66 deletions lib/syntax_tree/visitor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,72 +4,7 @@ module SyntaxTree
# Visitor is a parent class that provides the ability to walk down the tree
# and handle a subset of nodes. By defining your own subclass, you can
# explicitly handle a node type by defining a visit_* method.
class Visitor
# This is raised when you use the Visitor.visit_method method and it fails.
# It is correctable to through DidYouMean.
class VisitMethodError < StandardError
attr_reader :visit_method

def initialize(visit_method)
@visit_method = visit_method
super("Invalid visit method: #{visit_method}")
end
end

# This class is used by DidYouMean to offer corrections to invalid visit
# method names.
class VisitMethodChecker
attr_reader :visit_method

def initialize(error)
@visit_method = error.visit_method
end

def corrections
@corrections ||=
DidYouMean::SpellChecker.new(
dictionary: Visitor.visit_methods
).correct(visit_method)
end

DidYouMean.correct_error(VisitMethodError, self)
end

class << self
# This method is here to help folks write visitors.
#
# It's not always easy to ensure you're writing the correct method name in
# the visitor since it's perfectly valid to define methods that don't
# override these parent methods.
#
# If you use this method, you can ensure you're writing the correct method
# name. It will raise an error if the visit method you're defining isn't
# actually a method on the parent visitor.
def visit_method(method_name)
return if visit_methods.include?(method_name)

raise VisitMethodError, method_name
end

# This is the list of all of the valid visit methods.
def visit_methods
@visit_methods ||=
Visitor.instance_methods.grep(/^visit_(?!child_nodes)/)
end
end

def visit(node)
node&.accept(self)
end

def visit_all(nodes)
nodes.map { |node| visit(node) }
end

def visit_child_nodes(node)
visit_all(node.child_nodes)
end

class Visitor < BasicVisitor
# Visit an ARef node.
alias visit_aref visit_child_nodes

Expand Down
4 changes: 1 addition & 3 deletions lib/syntax_tree/visitor/field_visitor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,7 @@ class Visitor
# of circumstances, like when visiting the list of optional parameters
# defined on a method.
#
class FieldVisitor < Visitor
attr_reader :q

class FieldVisitor < BasicVisitor
def visit_aref(node)
node(node, "aref") do
field("collection", node.collection)
Expand Down