Skip to content

Various updates #309

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 11 commits into from
Feb 10, 2023
6 changes: 6 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ Security/Eval:
Style/AccessorGrouping:
Enabled: false

Style/Alias:
Enabled: false

Style/CaseEquality:
Enabled: false

Expand Down Expand Up @@ -117,6 +120,9 @@ Style/FormatStringToken:
Style/GuardClause:
Enabled: false

Style/HashLikeCase:
Enabled: false

Style/IdenticalConditionalBranches:
Enabled: false

Expand Down
16 changes: 7 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ It is built with only standard library dependencies. It additionally ships with
- [visit_methods](#visit_methods)
- [BasicVisitor](#basicvisitor)
- [MutationVisitor](#mutationvisitor)
- [WithEnvironment](#withenvironment)
- [WithScope](#withscope)
- [Language server](#language-server)
- [textDocument/formatting](#textdocumentformatting)
- [textDocument/inlayHint](#textdocumentinlayhint)
Expand Down Expand Up @@ -341,7 +341,7 @@ This function takes an input string containing Ruby code, parses it into its und

### SyntaxTree.mutation(&block)

This function yields a new mutation visitor to the block, and then returns the initialized visitor. It's effectively a shortcut for creating a `SyntaxTree::Visitor::MutationVisitor` without having to remember the class name. For more information on that visitor, see the definition below.
This function yields a new mutation visitor to the block, and then returns the initialized visitor. It's effectively a shortcut for creating a `SyntaxTree::MutationVisitor` without having to remember the class name. For more information on that visitor, see the definition below.

### SyntaxTree.search(source, query, &block)

Expand Down Expand Up @@ -558,7 +558,7 @@ The `MutationVisitor` is a visitor that can be used to mutate the tree. It works

```ruby
# Create a new visitor
visitor = SyntaxTree::Visitor::MutationVisitor.new
visitor = SyntaxTree::MutationVisitor.new

# Specify that it should mutate If nodes with assignments in their predicates
visitor.mutate("IfNode[predicate: Assign | OpAssign]") do |node|
Expand Down Expand Up @@ -588,20 +588,18 @@ SyntaxTree::Formatter.format(source, program.accept(visitor))
# => "if (a = 1)\nend\n"
```

### WithEnvironment
### WithScope

The `WithEnvironment` module can be included in visitors to automatically keep track of local variables and arguments
defined inside each environment. A `current_environment` accessor is made available to the request, allowing it to find
all usages and definitions of a local.
The `WithScope` module can be included in visitors to automatically keep track of local variables and arguments defined inside each scope. A `current_scope` accessor is made available to the request, allowing it to find all usages and definitions of a local.

```ruby
class MyVisitor < Visitor
include WithEnvironment
prepend WithScope

def visit_ident(node)
# find_local will return a Local for any local variables or arguments
# present in the current environment or nil if the identifier is not a local
local = current_environment.find_local(node)
local = current_scope.find_local(node)

puts local.type # the type of the local (:variable or :argument)
puts local.definitions # the array of locations where this local is defined
Expand Down
152 changes: 88 additions & 64 deletions lib/syntax_tree.rb
Original file line number Diff line number Diff line change
@@ -1,58 +1,41 @@
# frozen_string_literal: true

require "cgi"
require "etc"
require "json"
require "pp"
require "prettier_print"
require "ripper"
require "stringio"

require_relative "syntax_tree/formatter"
require_relative "syntax_tree/node"
require_relative "syntax_tree/dsl"
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"
require_relative "syntax_tree/visitor/match_visitor"
require_relative "syntax_tree/visitor/mermaid_visitor"
require_relative "syntax_tree/visitor/mutation_visitor"
require_relative "syntax_tree/visitor/pretty_print_visitor"
require_relative "syntax_tree/visitor/environment"
require_relative "syntax_tree/visitor/with_environment"

require_relative "syntax_tree/formatter"
require_relative "syntax_tree/parser"
require_relative "syntax_tree/pattern"
require_relative "syntax_tree/search"
require_relative "syntax_tree/index"

require_relative "syntax_tree/yarv"
require_relative "syntax_tree/yarv/basic_block"
require_relative "syntax_tree/yarv/bf"
require_relative "syntax_tree/yarv/calldata"
require_relative "syntax_tree/yarv/compiler"
require_relative "syntax_tree/yarv/control_flow_graph"
require_relative "syntax_tree/yarv/data_flow_graph"
require_relative "syntax_tree/yarv/decompiler"
require_relative "syntax_tree/yarv/disassembler"
require_relative "syntax_tree/yarv/instruction_sequence"
require_relative "syntax_tree/yarv/instructions"
require_relative "syntax_tree/yarv/legacy"
require_relative "syntax_tree/yarv/local_table"
require_relative "syntax_tree/yarv/sea_of_nodes"
require_relative "syntax_tree/yarv/assembler"
require_relative "syntax_tree/yarv/vm"

require_relative "syntax_tree/translation"
require_relative "syntax_tree/version"

# Syntax Tree is a suite of tools built on top of the internal CRuby parser. It
# provides the ability to generate a syntax tree from source, as well as the
# tools necessary to inspect and manipulate that syntax tree. It can be used to
# build formatters, linters, language servers, and more.
module SyntaxTree
# Syntax Tree the library has many features that aren't always used by the
# CLI. Requiring those features takes time, so we autoload as many constants
# as possible in order to keep the CLI as fast as possible.

autoload :DSL, "syntax_tree/dsl"
autoload :FieldVisitor, "syntax_tree/field_visitor"
autoload :Index, "syntax_tree/index"
autoload :JSONVisitor, "syntax_tree/json_visitor"
autoload :LanguageServer, "syntax_tree/language_server"
autoload :MatchVisitor, "syntax_tree/match_visitor"
autoload :Mermaid, "syntax_tree/mermaid"
autoload :MermaidVisitor, "syntax_tree/mermaid_visitor"
autoload :MutationVisitor, "syntax_tree/mutation_visitor"
autoload :Pattern, "syntax_tree/pattern"
autoload :PrettyPrintVisitor, "syntax_tree/pretty_print_visitor"
autoload :Search, "syntax_tree/search"
autoload :Translation, "syntax_tree/translation"
autoload :WithScope, "syntax_tree/with_scope"
autoload :YARV, "syntax_tree/yarv"

# This holds references to objects that respond to both #parse and #format
# so that we can use them in the CLI.
HANDLERS = {}
Expand All @@ -71,40 +54,80 @@ module SyntaxTree
# that Syntax Tree can format arbitrary parts of a document.
DEFAULT_INDENTATION = 0

# This is a hook provided so that plugins can register themselves as the
# handler for a particular file type.
def self.register_handler(extension, handler)
HANDLERS[extension] = handler
# Parses the given source and returns the formatted source.
def self.format(
source,
maxwidth = DEFAULT_PRINT_WIDTH,
base_indentation = DEFAULT_INDENTATION,
options: Formatter::Options.new
)
format_node(
source,
parse(source),
maxwidth,
base_indentation,
options: options
)
end

# Parses the given source and returns the syntax tree.
def self.parse(source)
parser = Parser.new(source)
response = parser.parse
response unless parser.error?
# Parses the given file and returns the formatted source.
def self.format_file(
filepath,
maxwidth = DEFAULT_PRINT_WIDTH,
base_indentation = DEFAULT_INDENTATION,
options: Formatter::Options.new
)
format(read(filepath), maxwidth, base_indentation, options: options)
end

# Parses the given source and returns the formatted source.
def self.format(
# Accepts a node in the tree and returns the formatted source.
def self.format_node(
source,
node,
maxwidth = DEFAULT_PRINT_WIDTH,
base_indentation = DEFAULT_INDENTATION,
options: Formatter::Options.new
)
formatter = Formatter.new(source, [], maxwidth, options: options)
parse(source).format(formatter)
node.format(formatter)

formatter.flush(base_indentation)
formatter.output.join
end

# Indexes the given source code to return a list of all class, module, and
# method definitions. Used to quickly provide indexing capability for IDEs or
# documentation generation.
def self.index(source)
Index.index(source)
end

# Indexes the given file to return a list of all class, module, and method
# definitions. Used to quickly provide indexing capability for IDEs or
# documentation generation.
def self.index_file(filepath)
Index.index_file(filepath)
end

# A convenience method for creating a new mutation visitor.
def self.mutation
visitor = Visitor::MutationVisitor.new
visitor = MutationVisitor.new
yield visitor
visitor
end

# Parses the given source and returns the syntax tree.
def self.parse(source)
parser = Parser.new(source)
response = parser.parse
response unless parser.error?
end

# Parses the given file and returns the syntax tree.
def self.parse_file(filepath)
parse(read(filepath))
end

# Returns the source from the given filepath taking into account any potential
# magic encoding comments.
def self.read(filepath)
Expand All @@ -120,23 +143,24 @@ def self.read(filepath)
File.read(filepath, encoding: encoding)
end

# This is a hook provided so that plugins can register themselves as the
# handler for a particular file type.
def self.register_handler(extension, handler)
HANDLERS[extension] = handler
end

# Searches through the given source using the given pattern and yields each
# node in the tree that matches the pattern to the given block.
def self.search(source, query, &block)
Search.new(Pattern.new(query).compile).scan(parse(source), &block)
end
pattern = Pattern.new(query).compile
program = parse(source)

# Indexes the given source code to return a list of all class, module, and
# method definitions. Used to quickly provide indexing capability for IDEs or
# documentation generation.
def self.index(source)
Index.index(source)
Search.new(pattern).scan(program, &block)
end

# Indexes the given file to return a list of all class, module, and method
# definitions. Used to quickly provide indexing capability for IDEs or
# documentation generation.
def self.index_file(filepath)
Index.index_file(filepath)
# Searches through the given file using the given pattern and yields each
# node in the tree that matches the pattern to the given block.
def self.search_file(filepath, query, &block)
search(read(filepath), query, &block)
end
end
4 changes: 2 additions & 2 deletions lib/syntax_tree/cli.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# frozen_string_literal: true

require "etc"
require "optparse"

module SyntaxTree
Expand Down Expand Up @@ -238,7 +239,7 @@ def run(item)
# representation.
class Json < Action
def run(item)
object = Visitor::JSONVisitor.new.visit(item.handler.parse(item.source))
object = item.handler.parse(item.source).accept(JSONVisitor.new)
puts JSON.pretty_generate(object)
end
end
Expand Down Expand Up @@ -501,7 +502,6 @@ def run(argv)
when "j", "json"
Json.new(options)
when "lsp"
require "syntax_tree/language_server"
LanguageServer.new(print_width: options.print_width).run
return 0
when "m", "match"
Expand Down
Loading