From 66f6155ac48e1e21d02e97bf005a583b8bda3932 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 3 May 2022 16:11:57 -0400 Subject: [PATCH 1/3] Standardize visitors Add a parent visitor to visit each of the fields on visitors. Then add a MatchVisitor that transforms into a Ruby pattern matching expression. --- lib/syntax_tree.rb | 2 + lib/syntax_tree/cli.rb | 32 +- lib/syntax_tree/visitor/field_visitor.rb | 1171 +++++++++++++++ lib/syntax_tree/visitor/json_visitor.rb | 1328 +---------------- lib/syntax_tree/visitor/match_visitor.rb | 115 ++ .../visitor/pretty_print_visitor.rb | 1182 +-------------- 6 files changed, 1367 insertions(+), 2463 deletions(-) create mode 100644 lib/syntax_tree/visitor/field_visitor.rb create mode 100644 lib/syntax_tree/visitor/match_visitor.rb diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index f1df71c5..632b062e 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -11,7 +11,9 @@ require_relative "syntax_tree/parser" require_relative "syntax_tree/version" 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/pretty_print_visitor" # If PrettyPrint::Align isn't defined, then we haven't gotten the updated diff --git a/lib/syntax_tree/cli.rb b/lib/syntax_tree/cli.rb index 1bf09cf7..f799fcb4 100644 --- a/lib/syntax_tree/cli.rb +++ b/lib/syntax_tree/cli.rb @@ -103,7 +103,7 @@ def failure # An action of the CLI that prints out the doc tree IR for the given source. class Doc < Action def run(handler, filepath, source) - formatter = Formatter.new([]) + formatter = Formatter.new(source, []) handler.parse(source).format(formatter) pp formatter.groups.first end @@ -116,6 +116,26 @@ def run(handler, filepath, source) end end + # An action of the CLI that converts the source into its equivalent JSON + # representation. + class Json < Action + def run(handler, filepath, source) + object = Visitor::JSONVisitor.new.visit(handler.parse(source)) + puts JSON.pretty_generate(object) + end + end + + # An action of the CLI that outputs a pattern-matching Ruby expression that + # would match the input given. + class Match < Action + def run(handler, filepath, source) + formatter = Formatter.new(source, []) + Visitor::MatchVisitor.new(formatter).visit(handler.parse(source)) + formatter.flush + puts formatter.output.join + end + end + # An action of the CLI that formats the input source and writes the # formatted output back to the file. class Write < Action @@ -154,6 +174,12 @@ def run(handler, filepath, source) #{Color.bold("stree format [OPTIONS] [FILE]")} Print out the formatted version of the given files + #{Color.bold("stree json [OPTIONS] [FILE]")} + Print out the JSON representation of the given files + + #{Color.bold("stree match [OPTIONS] [FILE]")} + Print out a pattern-matching Ruby expression that would match the given files + #{Color.bold("stree help")} Display this help message @@ -201,6 +227,10 @@ def run(argv) Debug.new when "doc" Doc.new + when "j", "json" + Json.new + when "m", "match" + Match.new when "f", "format" Format.new when "w", "write" diff --git a/lib/syntax_tree/visitor/field_visitor.rb b/lib/syntax_tree/visitor/field_visitor.rb new file mode 100644 index 00000000..1a24f40d --- /dev/null +++ b/lib/syntax_tree/visitor/field_visitor.rb @@ -0,0 +1,1171 @@ +# frozen_string_literal: true + +module SyntaxTree + class Visitor + # This is the parent class of a lot of built-in visitors for Syntax Tree. It + # reflects visiting each of the fields on every node in turn. It itself does + # not do anything with these fields, it leaves that behavior up to the + # subclass to implement. + # + # In order to properly use this class, you will need to subclass it and + # implement #comments, #field, #list, #node, #pairs, and #text. Those are + # documented at the bottom of this file. + class FieldVisitor < Visitor + attr_reader :q + + def visit_aref(node) + node(node, "aref") do + field("collection", node.collection) + field("index", node.index) + comments(node) + end + end + + def visit_aref_field(node) + node(node, "aref_field") do + field("collection", node.collection) + field("index", node.index) + comments(node) + end + end + + def visit_alias(node) + node(node, "alias") do + field("left", node.left) + field("right", node.right) + comments(node) + end + end + + def visit_arg_block(node) + node(node, "arg_block") do + field("value", node.value) if node.value + comments(node) + end + end + + def visit_arg_paren(node) + node(node, "arg_paren") do + field("arguments", node.arguments) + comments(node) + end + end + + def visit_arg_star(node) + node(node, "arg_star") do + field("value", node.value) + comments(node) + end + end + + def visit_args(node) + node(node, "args") do + list("parts", node.parts) + comments(node) + end + end + + def visit_args_forward(node) + visit_token(node, "args_forward") + end + + def visit_array(node) + node(node, "array") do + field("contents", node.contents) + comments(node) + end + end + + def visit_aryptn(node) + node(node, "aryptn") do + field("constant", node.constant) if node.constant + list("requireds", node.requireds) if node.requireds.any? + field("rest", node.rest) if node.rest + list("posts", node.posts) if node.posts.any? + comments(node) + end + end + + def visit_assign(node) + node(node, "assign") do + field("target", node.target) + field("value", node.value) + comments(node) + end + end + + def visit_assoc(node) + node(node, "assoc") do + field("key", node.key) + field("value", node.value) if node.value + comments(node) + end + end + + def visit_assoc_splat(node) + node(node, "assoc_splat") do + field("value", node.value) + comments(node) + end + end + + def visit_backref(node) + visit_token(node, "backref") + end + + def visit_backtick(node) + visit_token(node, "backtick") + end + + def visit_bare_assoc_hash(node) + node(node, "bare_assoc_hash") do + list("assocs", node.assocs) + comments(node) + end + end + + def visit_BEGIN(node) + node(node, "BEGIN") do + field("statements", node.statements) + comments(node) + end + end + + def visit_begin(node) + node(node, "begin") do + field("bodystmt", node.bodystmt) + comments(node) + end + end + + def visit_binary(node) + node(node, "binary") do + field("left", node.left) + text("operator", node.operator) + field("right", node.right) + comments(node) + end + end + + def visit_blockarg(node) + node(node, "blockarg") do + field("name", node.name) if node.name + comments(node) + end + end + + def visit_block_var(node) + node(node, "block_var") do + field("params", node.params) + list("locals", node.locals) if node.locals.any? + comments(node) + end + end + + def visit_bodystmt(node) + node(node, "bodystmt") do + field("statements", node.statements) + field("rescue_clause", node.rescue_clause) if node.rescue_clause + field("else_clause", node.else_clause) if node.else_clause + field("ensure_clause", node.ensure_clause) if node.ensure_clause + comments(node) + end + end + + def visit_brace_block(node) + node(node, "brace_block") do + field("block_var", node.block_var) if node.block_var + field("statements", node.statements) + comments(node) + end + end + + def visit_break(node) + node(node, "break") do + field("arguments", node.arguments) + comments(node) + end + end + + def visit_call(node) + node(node, "call") do + field("receiver", node.receiver) + field("operator", node.operator) + field("message", node.message) + field("arguments", node.arguments) if node.arguments + comments(node) + end + end + + def visit_case(node) + node(node, "case") do + field("keyword", node.keyword) + field("value", node.value) if node.value + field("consequent", node.consequent) + comments(node) + end + end + + def visit_CHAR(node) + visit_token(node, "CHAR") + end + + def visit_class(node) + node(node, "class") do + field("constant", node.constant) + field("superclass", node.superclass) if node.superclass + field("bodystmt", node.bodystmt) + comments(node) + end + end + + def visit_comma(node) + node(node, "comma") do + field("value", node) + end + end + + def visit_command(node) + node(node, "command") do + field("message", node.message) + field("arguments", node.arguments) + comments(node) + end + end + + def visit_command_call(node) + node(node, "command_call") do + field("receiver", node.receiver) + field("operator", node.operator) + field("message", node.message) + field("arguments", node.arguments) if node.arguments + comments(node) + end + end + + def visit_comment(node) + node(node, "comment") do + field("value", node.value) + end + end + + def visit_const(node) + visit_token(node, "const") + end + + def visit_const_path_field(node) + node(node, "const_path_field") do + field("parent", node.parent) + field("constant", node.constant) + comments(node) + end + end + + def visit_const_path_ref(node) + node(node, "const_path_ref") do + field("parent", node.parent) + field("constant", node.constant) + comments(node) + end + end + + def visit_const_ref(node) + node(node, "const_ref") do + field("constant", node.constant) + comments(node) + end + end + + def visit_cvar(node) + visit_token(node, "cvar") + end + + def visit_def(node) + node(node, "def") do + field("name", node.name) + field("params", node.params) + field("bodystmt", node.bodystmt) + comments(node) + end + end + + def visit_def_endless(node) + node(node, "def_endless") do + if node.target + field("target", node.target) + field("operator", node.operator) + end + + field("name", node.name) + field("paren", node.paren) if node.paren + field("statement", node.statement) + comments(node) + end + end + + def visit_defined(node) + node(node, "defined") do + field("value", node.value) + comments(node) + end + end + + def visit_defs(node) + node(node, "defs") do + field("target", node.target) + field("operator", node.operator) + field("name", node.name) + field("params", node.params) + field("bodystmt", node.bodystmt) + comments(node) + end + end + + def visit_do_block(node) + node(node, "do_block") do + field("block_var", node.block_var) if node.block_var + field("bodystmt", node.bodystmt) + comments(node) + end + end + + def visit_dot2(node) + node(node, "dot2") do + field("left", node.left) if node.left + field("right", node.right) if node.right + comments(node) + end + end + + def visit_dot3(node) + node(node, "dot3") do + field("left", node.left) if node.left + field("right", node.right) if node.right + comments(node) + end + end + + def visit_dyna_symbol(node) + node(node, "dyna_symbol") do + list("parts", node.parts) + comments(node) + end + end + + def visit_END(node) + node(node, "END") do + field("statements", node.statements) + comments(node) + end + end + + def visit_else(node) + node(node, "else") do + field("statements", node.statements) + comments(node) + end + end + + def visit_elsif(node) + node(node, "elsif") do + field("predicate", node.predicate) + field("statements", node.statements) + field("consequent", node.consequent) if node.consequent + comments(node) + end + end + + def visit_embdoc(node) + node(node, "embdoc") do + field("value", node.value) + end + end + + def visit_embexpr_beg(node) + node(node, "embexpr_beg") do + field("value", node.value) + end + end + + def visit_embexpr_end(node) + node(node, "embexpr_end") do + field("value", node.value) + end + end + + def visit_embvar(node) + node(node, "embvar") do + field("value", node.value) + end + end + + def visit_ensure(node) + node(node, "ensure") do + field("statements", node.statements) + comments(node) + end + end + + def visit_excessed_comma(node) + visit_token(node, "excessed_comma") + end + + def visit_fcall(node) + node(node, "fcall") do + field("value", node.value) + field("arguments", node.arguments) if node.arguments + comments(node) + end + end + + def visit_field(node) + node(node, "field") do + field("parent", node.parent) + field("operator", node.operator) + field("name", node.name) + comments(node) + end + end + + def visit_float(node) + visit_token(node, "float") + end + + def visit_fndptn(node) + node(node, "fndptn") do + field("constant", node.constant) if node.constant + field("left", node.left) + list("values", node.values) + field("right", node.right) + comments(node) + end + end + + def visit_for(node) + node(node, "for") do + field("index", node.index) + field("collection", node.collection) + field("statements", node.statements) + comments(node) + end + end + + def visit_gvar(node) + visit_token(node, "gvar") + end + + def visit_hash(node) + node(node, "hash") do + list("assocs", node.assocs) if node.assocs.any? + comments(node) + end + end + + def visit_heredoc(node) + node(node, "heredoc") do + list("parts", node.parts) + comments(node) + end + end + + def visit_heredoc_beg(node) + visit_token(node, "heredoc_beg") + end + + def visit_hshptn(node) + node(node, "hshptn") do + field("constant", node.constant) if node.constant + pairs("keywords", node.keywords) if node.keywords.any? + field("keyword_rest", node.keyword_rest) if node.keyword_rest + comments(node) + end + end + + def visit_ident(node) + visit_token(node, "ident") + end + + def visit_if(node) + node(node, "if") do + field("predicate", node.predicate) + field("statements", node.statements) + field("consequent", node.consequent) if node.consequent + comments(node) + end + end + + def visit_if_mod(node) + node(node, "if_mod") do + field("statement", node.statement) + field("predicate", node.predicate) + comments(node) + end + end + + def visit_if_op(node) + node(node, "ifop") do + field("predicate", node.predicate) + field("truthy", node.truthy) + field("falsy", node.falsy) + comments(node) + end + end + + def visit_imaginary(node) + visit_token(node, "imaginary") + end + + def visit_in(node) + node(node, "in") do + field("pattern", node.pattern) + field("statements", node.statements) + field("consequent", node.consequent) if node.consequent + comments(node) + end + end + + def visit_int(node) + visit_token(node, "int") + end + + def visit_ivar(node) + visit_token(node, "ivar") + end + + def visit_kw(node) + visit_token(node, "kw") + end + + def visit_kwrest_param(node) + node(node, "kwrest_param") do + field("name", node.name) + comments(node) + end + end + + def visit_label(node) + visit_token(node, "label") + end + + def visit_label_end(node) + node(node, "label_end") do + field("value", node.value) + end + end + + def visit_lambda(node) + node(node, "lambda") do + field("params", node.params) + field("statements", node.statements) + comments(node) + end + end + + def visit_lbrace(node) + visit_token(node, "lbrace") + end + + def visit_lbracket(node) + visit_token(node, "lbracket") + end + + def visit_lparen(node) + visit_token(node, "lparen") + end + + def visit_massign(node) + node(node, "massign") do + field("target", node.target) + field("value", node.value) + comments(node) + end + end + + def visit_method_add_block(node) + node(node, "method_add_block") do + field("call", node.call) + field("block", node.block) + comments(node) + end + end + + def visit_mlhs(node) + node(node, "mlhs") do + list("parts", node.parts) + comments(node) + end + end + + def visit_mlhs_paren(node) + node(node, "mlhs_paren") do + field("contents", node.contents) + comments(node) + end + end + + def visit_module(node) + node(node, "module") do + field("constant", node.constant) + field("bodystmt", node.bodystmt) + comments(node) + end + end + + def visit_mrhs(node) + node(node, "mrhs") do + list("parts", node.parts) + comments(node) + end + end + + def visit_next(node) + node(node, "next") do + field("arguments", node.arguments) + comments(node) + end + end + + def visit_not(node) + node(node, "not") do + field("statement", node.statement) + comments(node) + end + end + + def visit_op(node) + visit_token(node, "op") + end + + def visit_opassign(node) + node(node, "opassign") do + field("target", node.target) + field("operator", node.operator) + field("value", node.value) + comments(node) + end + end + + def visit_params(node) + node(node, "params") do + list("requireds", node.requireds) if node.requireds.any? + pairs("optionals", node.optionals) if node.optionals.any? + field("rest", node.rest) if node.rest + list("posts", node.posts) if node.posts.any? + pairs("keywords", node.keywords) if node.keywords.any? + field("keyword_rest", node.keyword_rest) if node.keyword_rest + field("block", node.block) if node.block + comments(node) + end + end + + def visit_paren(node) + node(node, "paren") do + field("contents", node.contents) + comments(node) + end + end + + def visit_period(node) + visit_token(node, "period") + end + + def visit_pinned_begin(node) + node(node, "pinned_begin") do + field("statement", node.statement) + comments(node) + end + end + + def visit_pinned_var_ref(node) + node(node, "pinned_var_ref") do + field("value", node.value) + comments(node) + end + end + + def visit_program(node) + node(node, "program") do + field("statements", node.statements) + comments(node) + end + end + + def visit_qsymbols(node) + node(node, "qsymbols") do + list("elements", node.elements) + comments(node) + end + end + + def visit_qsymbols_beg(node) + node(node, "qsymbols_beg") do + field("value", node.value) + end + end + + def visit_qwords(node) + node(node, "qwords") do + list("elements", node.elements) + comments(node) + end + end + + def visit_qwords_beg(node) + node(node, "qwords_beg") do + field("value", node.value) + end + end + + def visit_rassign(node) + node(node, "rassign") do + field("value", node.value) + field("operator", node.operator) + field("pattern", node.pattern) + comments(node) + end + end + + def visit_rational(node) + visit_token(node, "rational") + end + + def visit_rbrace(node) + node(node, "rbrace") do + field("value", node.value) + end + end + + def visit_rbracket(node) + node(node, "rbracket") do + field("value", node.value) + end + end + + def visit_redo(node) + visit_token(node, "redo") + end + + def visit_regexp_beg(node) + node(node, "regexp_beg") do + field("value", node.value) + end + end + + def visit_regexp_content(node) + node(node, "regexp_content") do + list("parts", node.parts) + end + end + + def visit_regexp_end(node) + node(node, "regexp_end") do + field("value", node.value) + end + end + + def visit_regexp_literal(node) + node(node, "regexp_literal") do + list("parts", node.parts) + comments(node) + end + end + + def visit_rescue(node) + node(node, "rescue") do + field("exception", node.exception) if node.exception + field("statements", node.statements) + field("consequent", node.consequent) if node.consequent + comments(node) + end + end + + def visit_rescue_ex(node) + node(node, "rescue_ex") do + field("exceptions", node.exceptions) + field("variable", node.variable) + comments(node) + end + end + + def visit_rescue_mod(node) + node(node, "rescue_mod") do + field("statement", node.statement) + field("value", node.value) + comments(node) + end + end + + def visit_rest_param(node) + node(node, "rest_param") do + field("name", node.name) + comments(node) + end + end + + def visit_retry(node) + visit_token(node, "retry") + end + + def visit_return(node) + node(node, "return") do + field("arguments", node.arguments) + comments(node) + end + end + + def visit_return0(node) + visit_token(node, "return0") + end + + def visit_rparen(node) + node(node, "rparen") do + field("value", node.value) + end + end + + def visit_sclass(node) + node(node, "sclass") do + field("target", node.target) + field("bodystmt", node.bodystmt) + comments(node) + end + end + + def visit_statements(node) + node(node, "statements") do + list("body", node.body) + comments(node) + end + end + + def visit_string_concat(node) + node(node, "string_concat") do + field("left", node.left) + field("right", node.right) + comments(node) + end + end + + def visit_string_content(node) + node(node, "string_content") do + list("parts", node.parts) + end + end + + def visit_string_dvar(node) + node(node, "string_dvar") do + field("variable", node.variable) + comments(node) + end + end + + def visit_string_embexpr(node) + node(node, "string_embexpr") do + field("statements", node.statements) + comments(node) + end + end + + def visit_string_literal(node) + node(node, "string_literal") do + list("parts", node.parts) + comments(node) + end + end + + def visit_super(node) + node(node, "super") do + field("arguments", node.arguments) + comments(node) + end + end + + def visit_symbeg(node) + node(node, "symbeg") do + field("value", node.value) + end + end + + def visit_symbol_content(node) + node(node, "symbol_content") do + field("value", node.value) + end + end + + def visit_symbol_literal(node) + node(node, "symbol_literal") do + field("value", node.value) + comments(node) + end + end + + def visit_symbols(node) + node(node, "symbols") do + list("elements", node.elements) + comments(node) + end + end + + def visit_symbols_beg(node) + node(node, "symbols_beg") do + field("value", node.value) + end + end + + def visit_tlambda(node) + node(node, "tlambda") do + field("value", node.value) + end + end + + def visit_tlambeg(node) + node(node, "tlambeg") do + field("value", node.value) + end + end + + def visit_top_const_field(node) + node(node, "top_const_field") do + field("constant", node.constant) + comments(node) + end + end + + def visit_top_const_ref(node) + node(node, "top_const_ref") do + field("constant", node.constant) + comments(node) + end + end + + def visit_tstring_beg(node) + node(node, "tstring_beg") do + field("value", node.value) + end + end + + def visit_tstring_content(node) + visit_token(node, "tstring_content") + end + + def visit_tstring_end(node) + node(node, "tstring_end") do + field("value", node.value) + end + end + + def visit_unary(node) + node(node, "unary") do + field("operator", node.operator) + field("statement", node.statement) + comments(node) + end + end + + def visit_undef(node) + node(node, "undef") do + list("symbols", node.symbols) + comments(node) + end + end + + def visit_unless(node) + node(node, "unless") do + field("predicate", node.predicate) + field("statements", node.statements) + field("consequent", node.consequent) if node.consequent + comments(node) + end + end + + def visit_unless_mod(node) + node(node, "unless_mod") do + field("statement", node.statement) + field("predicate", node.predicate) + comments(node) + end + end + + def visit_until(node) + node(node, "until") do + field("predicate", node.predicate) + field("statements", node.statements) + comments(node) + end + end + + def visit_until_mod(node) + node(node, "until_mod") do + field("statement", node.statement) + field("predicate", node.predicate) + comments(node) + end + end + + def visit_var_alias(node) + node(node, "var_alias") do + field("left", node.left) + field("right", node.right) + comments(node) + end + end + + def visit_var_field(node) + node(node, "var_field") do + field("value", node.value) + comments(node) + end + end + + def visit_var_ref(node) + node(node, "var_ref") do + field("value", node.value) + comments(node) + end + end + + def visit_vcall(node) + node(node, "vcall") do + field("value", node.value) + comments(node) + end + end + + def visit_void_stmt(node) + node(node, "void_stmt") do + comments(node) + end + end + + def visit_when(node) + node(node, "when") do + field("arguments", node.arguments) + field("statements", node.statements) + field("consequent", node.consequent) if node.consequent + comments(node) + end + end + + def visit_while(node) + node(node, "while") do + field("predicate", node.predicate) + field("statements", node.statements) + comments(node) + end + end + + def visit_while_mod(node) + node(node, "while_mod") do + field("statement", node.statement) + field("predicate", node.predicate) + comments(node) + end + end + + def visit_word(node) + node(node, "word") do + list("parts", node.parts) + comments(node) + end + end + + def visit_words(node) + node(node, "words") do + list("elements", node.elements) + comments(node) + end + end + + def visit_words_beg(node) + node(node, "words_beg") do + field("value", node.value) + end + end + + def visit_xstring(node) + node(node, "xstring") do + list("parts", node.parts) + end + end + + def visit_xstring_literal(node) + node(node, "xstring_literal") do + list("parts", node.parts) + comments(node) + end + end + + def visit_yield(node) + node(node, "yield") do + field("arguments", node.arguments) + comments(node) + end + end + + def visit_yield0(node) + visit_token(node, "yield0") + end + + def visit_zsuper(node) + visit_token(node, "zsuper") + end + + def visit___end__(node) + visit_token(node, "__end__") + end + + private + + # This accepts the node that is being visited and does something depending + # on the comments attached to the node. + def comments(node) + raise NotImplementedError + end + + # This accepts the name of the field being visited as a string (like + # "value") and the actual value of that field. The value can be a subclass + # of Node or any other type that can be held within the tree. + def field(name, value) + raise NotImplementedError + end + + # This accepts the name of the field being visited as well as a list of + # values. This is used, for example, when visiting something like the body + # of a Statements node. + def list(name, values) + raise NotImplementedError + end + + # This is the parent serialization method for each node. It is called with + # the node itself, as well as the type of the node as a string. The type + # is an internally used value that usually resembles the name of the + # ripper event that generated the node. The method should yield to the + # given block which then calls through to visit each of the fields on the + # node. + def node(node, type) + raise NotImplementedError + end + + # This accepts the name of the field being visited as well as a string + # value representing the value of the field. + def text(name, value) + raise NotImplementedError + end + + # This accepts the name of the field being visited as well as a list of + # pairs that represent the value of the field. It is used only in a couple + # of circumstances, like when visiting the list of optional parameters + # defined on a method. + def pairs(name, values) + raise NotImplementedError + end + + def visit_token(node, type) + node(node, type) do + field("value", node.value) + comments(node) + end + end + end + end +end diff --git a/lib/syntax_tree/visitor/json_visitor.rb b/lib/syntax_tree/visitor/json_visitor.rb index 9f0c8f94..fb98a999 100644 --- a/lib/syntax_tree/visitor/json_visitor.rb +++ b/lib/syntax_tree/visitor/json_visitor.rb @@ -2,1315 +2,44 @@ module SyntaxTree class Visitor - class JSONVisitor < Visitor - def visit_aref(node) - { - type: :aref, - collection: visit(node.collection), - index: visit(node.index), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_aref_field(node) - { - type: :aref_field, - collection: visit(node.collection), - index: visit(node.index), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_alias(node) - { - type: :alias, - left: visit(node.left), - right: visit(node.right), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_arg_block(node) - { - type: :arg_block, - value: visit(node.value), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_arg_paren(node) - { - type: :arg_paren, - args: visit(node.arguments), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_arg_star(node) - { - type: :arg_star, - value: visit(node.value), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_args(node) - { - type: :args, - parts: visit_all(node.parts), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_args_forward(node) - visit_token(:args_forward, node) - end - - def visit_array(node) - { - type: :array, - cnts: visit(node.contents), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_aryptn(node) - { - type: :aryptn, - constant: visit(node.constant), - reqs: visit_all(node.requireds), - rest: visit(node.rest), - posts: visit_all(node.posts), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_assign(node) - { - type: :assign, - target: visit(node.target), - value: visit(node.value), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_assoc(node) - { - type: :assoc, - key: visit(node.key), - value: visit(node.value), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_assoc_splat(node) - { - type: :assoc_splat, - value: visit(node.value), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_backref(node) - visit_token(:backref, node) - end - - def visit_backtick(node) - visit_token(:backtick, node) - end - - def visit_bare_assoc_hash(node) - { - type: :bare_assoc_hash, - assocs: visit_all(node.assocs), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_BEGIN(node) - { - type: :BEGIN, - lbrace: visit(node.lbrace), - stmts: visit(node.statements), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_begin(node) - { - type: :begin, - bodystmt: visit(node.bodystmt), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_binary(node) - { - type: :binary, - left: visit(node.left), - op: node.operator, - right: visit(node.right), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_blockarg(node) - { - type: :blockarg, - name: visit(node.name), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_block_var(node) - { - type: :block_var, - params: visit(node.params), - locals: visit_all(node.locals), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_bodystmt(node) - { - type: :bodystmt, - stmts: visit(node.statements), - rsc: visit(node.rescue_clause), - els: visit(node.else_clause), - ens: visit(node.ensure_clause), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_brace_block(node) - { - type: :brace_block, - lbrace: visit(node.lbrace), - block_var: visit(node.block_var), - stmts: visit(node.statements), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_break(node) - { - type: :break, - args: visit(node.arguments), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_call(node) - { - type: :call, - receiver: visit(node.receiver), - op: visit_call_operator(node.operator), - message: node.message == :call ? :call : visit(node.message), - args: visit(node.arguments), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_case(node) - { - type: :case, - value: visit(node.value), - cons: visit(node.consequent), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_CHAR(node) - visit_token(:CHAR, node) - end - - def visit_class(node) - { - type: :class, - constant: visit(node.constant), - superclass: visit(node.superclass), - bodystmt: visit(node.bodystmt), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_comma(node) - visit_token(:comma, node) - end - - def visit_command(node) - { - type: :command, - message: visit(node.message), - args: visit(node.arguments), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_command_call(node) - { - type: :command_call, - receiver: visit(node.receiver), - op: visit_call_operator(node.operator), - message: visit(node.message), - args: visit(node.arguments), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_comment(node) - { - type: :comment, - value: node.value, - inline: node.inline, - loc: visit_location(node.location) - } - end - - def visit_const(node) - visit_token(:const, node) - end - - def visit_const_path_field(node) - { - type: :const_path_field, - parent: visit(node.parent), - constant: visit(node.constant), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_const_path_ref(node) - { - type: :const_path_ref, - parent: visit(node.parent), - constant: visit(node.constant), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_const_ref(node) - { - type: :const_ref, - constant: visit(node.constant), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_cvar(node) - visit_token(:cvar, node) - end - - def visit_def(node) - { - type: :def, - name: visit(node.name), - params: visit(node.params), - bodystmt: visit(node.bodystmt), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_def_endless(node) - { - type: :def_endless, - name: visit(node.name), - paren: visit(node.paren), - stmt: visit(node.statement), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_defined(node) - visit_token(:defined, node) - end - - def visit_defs(node) - { - type: :defs, - target: visit(node.target), - op: visit(node.operator), - name: visit(node.name), - params: visit(node.params), - bodystmt: visit(node.bodystmt), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_do_block(node) - { - type: :do_block, - keyword: visit(node.keyword), - block_var: visit(node.block_var), - bodystmt: visit(node.bodystmt), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_dot2(node) - { - type: :dot2, - left: visit(node.left), - right: visit(node.right), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_dot3(node) - { - type: :dot3, - left: visit(node.left), - right: visit(node.right), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_dyna_symbol(node) - { - type: :dyna_symbol, - parts: visit_all(node.parts), - quote: node.quote, - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_END(node) - { - type: :END, - lbrace: visit(node.lbrace), - stmts: visit(node.statements), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_else(node) - { - type: :else, - stmts: visit(node.statements), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_elsif(node) - { - type: :elsif, - pred: visit(node.predicate), - stmts: visit(node.statements), - cons: visit(node.consequent), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_embdoc(node) - { - type: :embdoc, - value: node.value, - loc: visit_location(node.location) - } - end - - def visit_embexpr_beg(node) - { - type: :embexpr_beg, - value: node.value, - loc: visit_location(node.location) - } - end - - def visit_embexpr_end(node) - { - type: :embexpr_end, - value: node.value, - loc: visit_location(node.location) - } - end - - def visit_embvar(node) - { - type: :embvar, - value: node.value, - loc: visit_location(node.location) - } - end - - def visit_ensure(node) - { - type: :ensure, - keyword: visit(node.keyword), - stmts: visit(node.statements), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_excessed_comma(node) - visit_token(:excessed_comma, node) - end - - def visit_fcall(node) - { - type: :fcall, - value: visit(node.value), - args: visit(node.arguments), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_field(node) - { - type: :field, - parent: visit(node.parent), - op: visit_call_operator(node.operator), - name: visit(node.name), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_float(node) - visit_token(:float, node) - end - - def visit_fndptn(node) - { - type: :fndptn, - constant: visit(node.constant), - left: visit(node.left), - values: visit_all(node.values), - right: visit(node.right), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_for(node) - { - type: :for, - index: visit(node.index), - collection: visit(node.collection), - stmts: visit(node.statements), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end + # This visitor transforms the AST into a hash that contains only primitives + # that can be easily serialized into JSON. + class JSONVisitor < FieldVisitor + attr_reader :target - def visit_gvar(node) - visit_token(:gvar, node) + def initialize + @target = nil end - def visit_hash(node) - { - type: :hash, - assocs: visit_all(node.assocs), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_heredoc(node) - { - type: :heredoc, - beging: visit(node.beginning), - ending: node.ending, - parts: visit_all(node.parts), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_heredoc_beg(node) - visit_token(:heredoc_beg, node) - end - - def visit_hshptn(node) - { - type: :hshptn, - constant: visit(node.constant), - keywords: node.keywords.map { |(name, value)| [visit(name), visit(value)] }, - kwrest: visit(node.keyword_rest), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_ident(node) - visit_token(:ident, node) - end - - def visit_if(node) - { - type: :if, - pred: visit(node.predicate), - stmts: visit(node.statements), - cons: visit(node.consequent), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_if_mod(node) - { - type: :if_mod, - stmt: visit(node.statement), - pred: visit(node.predicate), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_if_op(node) - { - type: :ifop, - pred: visit(node.predicate), - tthy: visit(node.truthy), - flsy: visit(node.falsy), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_imaginary(node) - visit_token(:imaginary, node) - end - - def visit_in(node) - { - type: :in, - pattern: visit(node.pattern), - stmts: visit(node.statements), - cons: visit(node.consequent), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_int(node) - visit_token(:int, node) - end - - def visit_ivar(node) - visit_token(:ivar, node) - end - - def visit_kw(node) - visit_token(:kw, node) - end - - def visit_kwrest_param(node) - { - type: :kwrest_param, - name: visit(node.name), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_label(node) - visit_token(:label, node) - end - - def visit_label_end(node) - visit_token(:label_end, node) - end - - def visit_lambda(node) - { - type: :lambda, - params: visit(node.params), - stmts: visit(node.statements), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_lbrace(node) - visit_token(:lbrace, node) - end - - def visit_lbracket(node) - visit_token(:lbracket, node) - end - - def visit_lparen(node) - visit_token(:lparen, node) - end - - def visit_massign(node) - { - type: :massign, - target: visit(node.target), - value: visit(node.value), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_method_add_block(node) - { - type: :method_add_block, - call: visit(node.call), - block: visit(node.block), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_mlhs(node) - { - type: :mlhs, - parts: visit_all(node.parts), - comma: node.comma, - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_mlhs_paren(node) - { - type: :mlhs_paren, - cnts: visit(node.contents), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_module(node) - { - type: :module, - constant: visit(node.constant), - bodystmt: visit(node.bodystmt), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_mrhs(node) - { - type: :mrhs, - parts: visit_all(node.parts), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_next(node) - { - type: :next, - args: visit(node.arguments), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_not(node) - { - type: :not, - value: visit(node.statement), - paren: node.parentheses, - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_op(node) - visit_token(:op, node) - end - - def visit_opassign(node) - { - type: :opassign, - target: visit(node.target), - op: visit(node.operator), - value: visit(node.value), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_params(node) - { - type: :params, - reqs: visit_all(node.requireds), - opts: node.optionals.map { |(name, value)| [visit(name), visit(value)] }, - rest: visit(node.rest), - posts: visit_all(node.posts), - keywords: node.keywords.map { |(name, value)| [visit(name), visit(value || nil)] }, - kwrest: node.keyword_rest == :nil ? "nil" : visit(node.keyword_rest), - block: visit(node.block), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_paren(node) - { - type: :paren, - lparen: visit(node.lparen), - cnts: visit(node.contents), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_period(node) - visit_token(:period, node) - end - - def visit_pinned_begin(node) - { - type: :pinned_begin, - stmt: visit(node.statement), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_pinned_var_ref(node) - { - type: :pinned_var_ref, - value: visit(node.value), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_program(node) - { - type: :program, - stmts: visit(node.statements), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_qsymbols(node) - { - type: :qsymbols, - elems: visit_all(node.elements), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_qsymbols_beg(node) - visit_token(:qsymbols_beg, node) - end - - def visit_qwords(node) - { - type: :qwords, - elems: visit_all(node.elements), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_qwords_beg(node) - visit_token(:qwords_beg, node) - end - - def visit_rassign(node) - { - type: :rassign, - value: visit(node.value), - op: visit(node.operator), - pattern: visit(node.pattern), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_rational(node) - visit_token(:rational, node) - end - - def visit_rbrace(node) - visit_token(:rbrace, node) - end - - def visit_rbracket(node) - visit_token(:rbracket, node) - end - - def visit_redo(node) - visit_token(:redo, node) - end - - def visit_regexp_beg(node) - visit_token(:regexp_beg, node) - end - - def visit_regexp_content(node) - { - type: :regexp_content, - beging: node.beginning, - parts: visit_all(node.parts), - loc: visit_location(node.location) - } - end - - def visit_regexp_end(node) - visit_token(:regexp_end, node) - end - - def visit_regexp_literal(node) - { - type: :regexp_literal, - beging: node.beginning, - ending: node.ending, - parts: visit_all(node.parts), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_rescue(node) - { - type: :rescue, - extn: visit(node.exception), - stmts: visit(node.statements), - cons: visit(node.consequent), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_rescue_ex(node) - { - type: :rescue_ex, - extns: visit(node.exceptions), - var: visit(node.variable), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_rescue_mod(node) - { - type: :rescue_mod, - stmt: visit(node.statement), - value: visit(node.value), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_rest_param(node) - { - type: :rest_param, - name: visit(node.name), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_retry(node) - visit_token(:retry, node) - end - - def visit_return(node) - { - type: :return, - args: visit(node.arguments), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_return0(node) - visit_token(:return0, node) - end - - def visit_rparen(node) - visit_token(:rparen, node) - end - - def visit_sclass(node) - { - type: :sclass, - target: visit(node.target), - bodystmt: visit(node.bodystmt), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_statements(node) - { - type: :statements, - body: visit_all(node.body), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_string_concat(node) - { - type: :string_concat, - left: visit(node.left), - right: visit(node.right), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_string_content(node) - { - type: :string_content, - parts: visit_all(node.parts), - loc: visit_location(node.location) - } - end - - def visit_string_dvar(node) - { - type: :string_dvar, - var: visit(node.variable), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_string_embexpr(node) - { - type: :string_embexpr, - stmts: visit(node.statements), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_string_literal(node) - { - type: :string_literal, - parts: visit_all(node.parts), - quote: node.quote, - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_super(node) - { - type: :super, - args: visit(node.arguments), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_symbeg(node) - visit_token(:symbeg, node) - end - - def visit_symbol_content(node) - { - type: :symbol_content, - value: visit(node.value), - loc: visit_location(node.location) - } - end - - def visit_symbol_literal(node) - { - type: :symbol_literal, - value: visit(node.value), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_symbols(node) - { - type: :symbols, - elems: visit_all(node.elements), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_symbols_beg(node) - visit_token(:symbols_beg, node) - end - - def visit_tlambda(node) - visit_token(:tlambda, node) - end - - def visit_tlambeg(node) - visit_token(:tlambeg, node) - end - - def visit_top_const_field(node) - { - type: :top_const_field, - constant: visit(node.constant), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_top_const_ref(node) - { - type: :top_const_ref, - constant: visit(node.constant), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_tstring_beg(node) - visit_token(:tstring_beg, node) - end - - def visit_tstring_content(node) - visit_token(:tstring_content, node) - end - - def visit_tstring_end(node) - visit_token(:tstring_end, node) - end - - def visit_unary(node) - { - type: :unary, - op: node.operator, - value: visit(node.statement), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_undef(node) - { - type: :undef, - syms: visit_all(node.symbols), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_unless(node) - { - type: :unless, - pred: visit(node.predicate), - stmts: visit(node.statements), - cons: visit(node.consequent), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_unless_mod(node) - { - type: :unless_mod, - stmt: visit(node.statement), - pred: visit(node.predicate), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_until(node) - { - type: :until, - pred: visit(node.predicate), - stmts: visit(node.statements), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_until_mod(node) - { - type: :until_mod, - stmt: visit(node.statement), - pred: visit(node.predicate), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_var_alias(node) - { - type: :var_alias, - left: visit(node.left), - right: visit(node.right), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_var_field(node) - { - type: :var_field, - value: visit(node.value), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_var_ref(node) - { - type: :var_ref, - value: visit(node.value), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_vcall(node) - { - type: :vcall, - value: visit(node.value), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_void_stmt(node) - { - type: :void_stmt, - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_when(node) - { - type: :when, - args: visit(node.arguments), - stmts: visit(node.statements), - cons: visit(node.consequent), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_while(node) - { - type: :while, - pred: visit(node.predicate), - stmts: visit(node.statements), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_while_mod(node) - { - type: :while_mod, - stmt: visit(node.statement), - pred: visit(node.predicate), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_word(node) - { - type: :word, - parts: visit_all(node.parts), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_words(node) - { - type: :words, - elems: visit_all(node.elements), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_words_beg(node) - visit_token(:words_beg, node) - end + private - def visit_xstring(node) - { - type: :xstring, - parts: visit_all(node.parts), - loc: visit_location(node.location) - } + def comments(node) + target[:comments] = visit_all(node.comments) end - def visit_xstring_literal(node) - { - type: :xstring_literal, - parts: visit_all(node.parts), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } + def field(name, value) + target[name] = value.is_a?(Node) ? visit(value) : value end - def visit_yield(node) - { - type: :yield, - args: visit(node.arguments), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } + def list(name, values) + target[name] = visit_all(values) end - def visit_yield0(node) - visit_token(:yield0, node) + def node(node, type) + previous = @target + @target = { type: type, location: visit_location(node.location) } + yield + @target + ensure + @target = previous end - def visit_zsuper(node) - visit_token(:zsuper, node) + def pairs(name, values) + target[name] = values.map { |(key, value)| [visit(key), visit(value)] } end - def visit___end__(node) - visit_token(:__end__, node) - end - - private - - def visit_call_operator(operator) - operator == :"::" ? :"::" : visit(operator) + def text(name, value) + target[name] = value end def visit_location(location) @@ -1321,15 +50,6 @@ def visit_location(location) location.end_char ] end - - def visit_token(type, node) - { - type: type, - value: node.value, - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end end end end diff --git a/lib/syntax_tree/visitor/match_visitor.rb b/lib/syntax_tree/visitor/match_visitor.rb new file mode 100644 index 00000000..b3da85d1 --- /dev/null +++ b/lib/syntax_tree/visitor/match_visitor.rb @@ -0,0 +1,115 @@ +# frozen_string_literal: true + +module SyntaxTree + class Visitor + # This visitor transforms the AST into a Ruby pattern matching expression + # that would match correctly against the AST. + class MatchVisitor < FieldVisitor + attr_reader :q + + def initialize(q) + @q = q + end + + def visit(node) + if node.is_a?(Node) + super + else + node.pretty_print(q) + end + end + + private + + def comments(node) + return if node.comments.empty? + + q.nest(0) do + q.text("comments: [") + q.indent do + q.breakable("") + q.seplist(node.comments) { |comment| comment.pretty_print(q) } + end + q.breakable("") + q.text("]") + end + end + + def field(name, value) + q.nest(0) do + q.text(name) + q.text(": ") + visit(value) + end + end + + def list(name, values) + q.group do + q.text(name) + q.text(": [") + q.indent do + q.breakable("") + q.seplist(values) { |value| visit(value) } + end + q.breakable("") + q.text("]") + end + end + + def node(node, type) + items = [] + q.with_target(items) { yield } + + if items.empty? + q.text(node.class.name) + return + end + + q.group do + q.text(node.class.name) + q.text("[") + q.indent do + q.breakable("") + q.seplist(items) { |item| q.target << item } + end + q.breakable("") + q.text("]") + end + end + + def pairs(name, values) + q.group do + q.text(name) + q.text(": [") + q.indent do + q.breakable("") + q.seplist(values) do |(key, value)| + q.group do + q.text("[") + q.indent do + q.breakable("") + visit(key) + q.text(",") + q.breakable + visit(value || nil) + end + q.breakable("") + q.text("]") + end + end + end + q.breakable("") + q.text("]") + end + end + + def text(name, value) + q.nest(0) do + q.text(name) + q.text(": ") + q.text(value) + end + end + end + end +end diff --git a/lib/syntax_tree/visitor/pretty_print_visitor.rb b/lib/syntax_tree/visitor/pretty_print_visitor.rb index 40420a15..c74a4cc9 100644 --- a/lib/syntax_tree/visitor/pretty_print_visitor.rb +++ b/lib/syntax_tree/visitor/pretty_print_visitor.rb @@ -2,1166 +2,23 @@ module SyntaxTree class Visitor - class PrettyPrintVisitor < Visitor + # This visitor pretty-prints the AST into an equivalent s-expression. + class PrettyPrintVisitor < FieldVisitor attr_reader :q def initialize(q) @q = q end - def visit_aref(node) - node("aref") do - field("collection", node.collection) - field("index", node.index) - comments(node) - end - end - - def visit_aref_field(node) - node("aref_field") do - field("collection", node.collection) - field("index", node.index) - comments(node) - end - end - - def visit_alias(node) - node("alias") do - field("left", node.left) - field("right", node.right) - comments(node) - end - end - - def visit_arg_block(node) - node("arg_block") do - field("value", node.value) if node.value - comments(node) - end - end - - def visit_arg_paren(node) - node("arg_paren") do - field("arguments", node.arguments) - comments(node) - end - end - - def visit_arg_star(node) - node("arg_star") do - field("value", node.value) - comments(node) - end - end - - def visit_args(node) - node("args") do - list("parts", node.parts) - comments(node) - end - end - - def visit_args_forward(node) - visit_token("args_forward", node) - end - - def visit_array(node) - node("array") do - field("contents", node.contents) - comments(node) - end - end - - def visit_aryptn(node) - node("aryptn") do - field("constant", node.constant) if node.constant - list("requireds", node.requireds) if node.requireds.any? - field("rest", node.rest) if node.rest - list("posts", node.posts) if node.posts.any? - comments(node) - end - end - - def visit_assign(node) - node("assign") do - field("target", node.target) - field("value", node.value) - comments(node) - end - end - - def visit_assoc(node) - node("assoc") do - field("key", node.key) - field("value", node.value) if node.value - comments(node) - end - end - - def visit_assoc_splat(node) - node("assoc_splat") do - field("value", node.value) - comments(node) - end - end - - def visit_backref(node) - visit_token("backref", node) - end - - def visit_backtick(node) - visit_token("backtick", node) - end - - def visit_bare_assoc_hash(node) - node("bare_assoc_hash") do - list("assocs", node.assocs) - comments(node) - end - end - - def visit_BEGIN(node) - node("BEGIN") do - field("statements", node.statements) - comments(node) - end - end - - def visit_begin(node) - node("begin") do - field("bodystmt", node.bodystmt) - comments(node) - end - end - - def visit_binary(node) - node("binary") do - field("left", node.left) - text("operator", node.operator) - field("right", node.right) - comments(node) - end - end - - def visit_blockarg(node) - node("blockarg") do - field("name", node.name) if node.name - comments(node) - end - end - - def visit_block_var(node) - node("block_var") do - field("params", node.params) - list("locals", node.locals) if node.locals.any? - comments(node) - end - end - - def visit_bodystmt(node) - node("bodystmt") do - field("statements", node.statements) - field("rescue_clause", node.rescue_clause) if node.rescue_clause - field("else_clause", node.else_clause) if node.else_clause - field("ensure_clause", node.ensure_clause) if node.ensure_clause - comments(node) - end - end - - def visit_brace_block(node) - node("brace_block") do - field("block_var", node.block_var) if node.block_var - field("statements", node.statements) - comments(node) - end - end - - def visit_break(node) - node("break") do - field("arguments", node.arguments) - comments(node) - end - end - - def visit_call(node) - node("call") do - field("receiver", node.receiver) - field("operator", node.operator) - field("message", node.message) - field("arguments", node.arguments) if node.arguments - comments(node) - end - end - - def visit_case(node) - node("case") do - field("keyword", node.keyword) - field("value", node.value) if node.value - field("consequent", node.consequent) - comments(node) - end - end - - def visit_CHAR(node) - visit_token("CHAR", node) - end - - def visit_class(node) - node("class") do - field("constant", node.constant) - field("superclass", node.superclass) if node.superclass - field("bodystmt", node.bodystmt) - comments(node) - end - end - - def visit_comma(node) - node("comma") do - field("value", node) - end - end - - def visit_command(node) - node("command") do - field("message", node.message) - field("arguments", node.arguments) - comments(node) - end - end - - def visit_command_call(node) - node("command_call") do - field("receiver", node.receiver) - field("operator", node.operator) - field("message", node.message) - field("arguments", node.arguments) if node.arguments - comments(node) - end - end - - def visit_comment(node) - node("comment") do - field("value", node.value) - end - end - - def visit_const(node) - visit_token("const", node) - end - - def visit_const_path_field(node) - node("const_path_field") do - field("parent", node.parent) - field("constant", node.constant) - comments(node) - end - end - - def visit_const_path_ref(node) - node("const_path_ref") do - field("parent", node.parent) - field("constant", node.constant) - comments(node) - end - end - - def visit_const_ref(node) - node("const_ref") do - field("constant", node.constant) - comments(node) - end - end - - def visit_cvar(node) - visit_token("cvar", node) - end - - def visit_def(node) - node("def") do - field("name", node.name) - field("params", node.params) - field("bodystmt", node.bodystmt) - comments(node) - end - end - - def visit_def_endless(node) - node("def_endless") do - if node.target - field("target", node.target) - field("operator", node.operator) - end - - field("name", node.name) - field("paren", node.paren) if node.paren - field("statement", node.statement) - comments(node) - end - end - - def visit_defined(node) - node("defined") do - field("value", node.value) - comments(node) - end - end - - def visit_defs(node) - node("defs") do - field("target", node.target) - field("operator", node.operator) - field("name", node.name) - field("params", node.params) - field("bodystmt", node.bodystmt) - comments(node) - end - end - - def visit_do_block(node) - node("do_block") do - field("block_var", node.block_var) if node.block_var - field("bodystmt", node.bodystmt) - comments(node) - end - end - - def visit_dot2(node) - node("dot2") do - field("left", node.left) if node.left - field("right", node.right) if node.right - comments(node) - end - end - - def visit_dot3(node) - node("dot3") do - field("left", node.left) if node.left - field("right", node.right) if node.right - comments(node) - end - end - - def visit_dyna_symbol(node) - node("dyna_symbol") do - list("parts", node.parts) - comments(node) - end - end - - def visit_END(node) - node("END") do - field("statements", node.statements) - comments(node) - end - end - - def visit_else(node) - node("else") do - field("statements", node.statements) - comments(node) - end - end - - def visit_elsif(node) - node("elsif") do - field("predicate", node.predicate) - field("statements", node.statements) - field("consequent", node.consequent) if node.consequent - comments(node) - end - end - - def visit_embdoc(node) - node("embdoc") do - field("value", node.value) - end - end - - def visit_embexpr_beg(node) - node("embexpr_beg") do - field("value", node.value) - end - end - - def visit_embexpr_end(node) - node("embexpr_end") do - field("value", node.value) - end - end - - def visit_embvar(node) - node("embvar") do - field("value", node.value) - end - end - - def visit_ensure(node) - node("ensure") do - field("statements", node.statements) - comments(node) - end - end - - def visit_excessed_comma(node) - visit_token("excessed_comma", node) - end - - def visit_fcall(node) - node("fcall") do - field("value", node.value) - field("arguments", node.arguments) if node.arguments - comments(node) - end - end - - def visit_field(node) - node("field") do - field("parent", node.parent) - field("operator", node.operator) - field("name", node.name) - comments(node) - end - end - - def visit_float(node) - visit_token("float", node) - end - - def visit_fndptn(node) - node("fndptn") do - field("constant", node.constant) if node.constant - field("left", node.left) - list("values", node.values) - field("right", node.right) - comments(node) - end - end - - def visit_for(node) - node("for") do - field("index", node.index) - field("collection", node.collection) - field("statements", node.statements) - comments(node) - end - end - - def visit_gvar(node) - visit_token("gvar", node) - end - - def visit_hash(node) - node("hash") do - list("assocs", node.assocs) if node.assocs.any? - comments(node) - end - end - - def visit_heredoc(node) - node("heredoc") do - list("parts", node.parts) - comments(node) - end - end - - def visit_heredoc_beg(node) - visit_token("heredoc_beg", node) - end - - def visit_hshptn(node) - node("hshptn") do - field("constant", node.constant) if node.constant - - if node.keywords.any? - q.breakable - q.group(2, "(", ")") do - q.seplist(node.keywords) do |(key, value)| - q.group(2, "(", ")") do - key.pretty_print(q) - - if value - q.breakable - value.pretty_print(q) - end - end - end - end - end - - field("keyword_rest", node.keyword_rest) if node.keyword_rest - comments(node) - end - end - - def visit_ident(node) - visit_token("ident", node) - end - - def visit_if(node) - node("if") do - field("predicate", node.predicate) - field("statements", node.statements) - field("consequent", node.consequent) if node.consequent - comments(node) - end - end - - def visit_if_mod(node) - node("if_mod") do - field("statement", node.statement) - field("predicate", node.predicate) - comments(node) - end - end - - def visit_if_op(node) - node("ifop") do - field("predicate", node.predicate) - field("truthy", node.truthy) - field("falsy", node.falsy) - comments(node) - end - end - - def visit_imaginary(node) - visit_token("imaginary", node) - end - - def visit_in(node) - node("in") do - field("pattern", node.pattern) - field("statements", node.statements) - field("consequent", node.consequent) if node.consequent - comments(node) - end - end - - def visit_int(node) - visit_token("int", node) - end - - def visit_ivar(node) - visit_token("ivar", node) - end - - def visit_kw(node) - visit_token("kw", node) - end - - def visit_kwrest_param(node) - node("kwrest_param") do - field("name", node.name) - comments(node) - end - end - - def visit_label(node) - node("label") do - q.breakable - q.text(":") - q.text(node.value[0...-1]) - comments(node) - end - end - - def visit_label_end(node) - node("label_end") do - field("value", node.value) - end - end - - def visit_lambda(node) - node("lambda") do - field("params", node.params) - field("statements", node.statements) - comments(node) - end - end - - def visit_lbrace(node) - visit_token("lbrace", node) - end - - def visit_lbracket(node) - visit_token("lbracket", node) - end - - def visit_lparen(node) - visit_token("lparen", node) - end - - def visit_massign(node) - node("massign") do - field("target", node.target) - field("value", node.value) - comments(node) - end - end - - def visit_method_add_block(node) - node("method_add_block") do - field("call", node.call) - field("block", node.block) - comments(node) - end - end - - def visit_mlhs(node) - node("mlhs") do - list("parts", node.parts) - comments(node) - end - end - - def visit_mlhs_paren(node) - node("mlhs_paren") do - field("contents", node.contents) - comments(node) - end - end - - def visit_module(node) - node("module") do - field("constant", node.constant) - field("bodystmt", node.bodystmt) - comments(node) - end - end - - def visit_mrhs(node) - node("mrhs") do - list("parts", node.parts) - comments(node) - end - end - - def visit_next(node) - node("next") do - field("arguments", node.arguments) - comments(node) - end - end - - def visit_not(node) - node("not") do - field("statement", node.statement) - comments(node) - end - end - - def visit_op(node) - visit_token("op", node) - end - - def visit_opassign(node) - node("opassign") do - field("target", node.target) - field("operator", node.operator) - field("value", node.value) - comments(node) - end - end - - def visit_params(node) - node("params") do - list("requireds", node.requireds) if node.requireds.any? - - if node.optionals.any? - q.breakable - q.group(2, "(", ")") do - q.seplist(node.optionals) do |(name, default)| - name.pretty_print(q) - q.text("=") - q.group(2) do - q.breakable("") - default.pretty_print(q) - end - end - end - end - - field("rest", node.rest) if node.rest - list("posts", node.posts) if node.posts.any? - - if node.keywords.any? - q.breakable - q.group(2, "(", ")") do - q.seplist(node.keywords) do |(name, default)| - name.pretty_print(q) - - if default - q.text("=") - q.group(2) do - q.breakable("") - default.pretty_print(q) - end - end - end - end - end - - field("keyword_rest", node.keyword_rest) if node.keyword_rest - field("block", node.block) if node.block - comments(node) - end - end - - def visit_paren(node) - node("paren") do - field("contents", node.contents) - comments(node) - end - end - - def visit_period(node) - visit_token("period", node) - end - - def visit_pinned_begin(node) - node("pinned_begin") do - field("statement", node.statement) - comments(node) - end - end - - def visit_pinned_var_ref(node) - node("pinned_var_ref") do - field("value", node.value) - comments(node) - end - end - - def visit_program(node) - node("program") do - field("statements", node.statements) - comments(node) - end - end - - def visit_qsymbols(node) - node("qsymbols") do - list("elements", node.elements) - comments(node) - end - end - - def visit_qsymbols_beg(node) - node("qsymbols_beg") do - field("value", node.value) - end - end - - def visit_qwords(node) - node("qwords") do - list("elements", node.elements) - comments(node) - end - end - - def visit_qwords_beg(node) - node("qwords_beg") do - field("value", node.value) - end - end - - def visit_rassign(node) - node("rassign") do - field("value", node.value) - field("operator", node.operator) - field("pattern", node.pattern) - comments(node) - end - end - - def visit_rational(node) - visit_token("rational", node) - end - - def visit_rbrace(node) - node("rbrace") do - field("value", node.value) - end - end - - def visit_rbracket(node) - node("rbracket") do - field("value", node.value) - end - end - - def visit_redo(node) - visit_token("redo", node) - end - - def visit_regexp_beg(node) - node("regexp_beg") do - field("value", node.value) - end - end - - def visit_regexp_content(node) - node("regexp_content") do - list("parts", node.parts) - end - end - - def visit_regexp_end(node) - node("regexp_end") do - field("value", node.value) - end - end - - def visit_regexp_literal(node) - node("regexp_literal") do - list("parts", node.parts) - comments(node) - end - end - - def visit_rescue(node) - node("rescue") do - field("exception", node.exception) if node.exception - field("statements", node.statements) - field("consequent", node.consequent) if node.consequent - comments(node) - end - end - - def visit_rescue_ex(node) - node("rescue_ex") do - field("exceptions", node.exceptions) - field("variable", node.variable) - comments(node) - end - end - - def visit_rescue_mod(node) - node("rescue_mod") do - field("statement", node.statement) - field("value", node.value) - comments(node) - end - end - - def visit_rest_param(node) - node("rest_param") do - field("name", node.name) - comments(node) - end - end - - def visit_retry(node) - visit_token("retry", node) - end - - def visit_return(node) - node("return") do - field("arguments", node.arguments) - comments(node) - end - end - - def visit_return0(node) - visit_token("return0", node) - end - - def visit_rparen(node) - node("rparen") do - field("value", node.value) - end - end - - def visit_sclass(node) - node("sclass") do - field("target", node.target) - field("bodystmt", node.bodystmt) - comments(node) - end - end - - def visit_statements(node) - node("statements") do - list("body", node.body) - comments(node) - end - end - - def visit_string_concat(node) - node("string_concat") do - field("left", node.left) - field("right", node.right) - comments(node) - end - end - - def visit_string_content(node) - node("string_content") do - list("parts", node.parts) - end - end - - def visit_string_dvar(node) - node("string_dvar") do - field("variable", node.variable) - comments(node) - end - end - - def visit_string_embexpr(node) - node("string_embexpr") do - field("statements", node.statements) - comments(node) - end - end - - def visit_string_literal(node) - node("string_literal") do - list("parts", node.parts) - comments(node) - end - end - - def visit_super(node) - node("super") do - field("arguments", node.arguments) - comments(node) - end - end - - def visit_symbeg(node) - node("symbeg") do - field("value", node.value) - end - end - - def visit_symbol_content(node) - node("symbol_content") do - field("value", node.value) - end - end - - def visit_symbol_literal(node) - node("symbol_literal") do - field("value", node.value) - comments(node) - end - end - - def visit_symbols(node) - node("symbols") do - list("elements", node.elements) - comments(node) - end - end - - def visit_symbols_beg(node) - node("symbols_beg") do - field("value", node.value) - end - end - - def visit_tlambda(node) - node("tlambda") do - field("value", node.value) - end - end - - def visit_tlambeg(node) - node("tlambeg") do - field("value", node.value) - end - end - - def visit_top_const_field(node) - node("top_const_field") do - field("constant", node.constant) - comments(node) - end - end - - def visit_top_const_ref(node) - node("top_const_ref") do - field("constant", node.constant) - comments(node) - end - end - - def visit_tstring_beg(node) - node("tstring_beg") do - field("value", node.value) - end - end - - def visit_tstring_content(node) - visit_token("tstring_content", node) - end - - def visit_tstring_end(node) - node("tstring_end") do - field("value", node.value) - end - end - - def visit_unary(node) - node("unary") do - field("operator", node.operator) - field("statement", node.statement) - comments(node) - end - end - - def visit_undef(node) - node("undef") do - list("symbols", node.symbols) - comments(node) - end - end - - def visit_unless(node) - node("unless") do - field("predicate", node.predicate) - field("statements", node.statements) - field("consequent", node.consequent) if node.consequent - comments(node) - end - end - - def visit_unless_mod(node) - node("unless_mod") do - field("statement", node.statement) - field("predicate", node.predicate) - comments(node) - end - end - - def visit_until(node) - node("until") do - field("predicate", node.predicate) - field("statements", node.statements) - comments(node) - end - end - - def visit_until_mod(node) - node("until_mod") do - field("statement", node.statement) - field("predicate", node.predicate) - comments(node) - end - end - - def visit_var_alias(node) - node("var_alias") do - field("left", node.left) - field("right", node.right) - comments(node) - end - end - - def visit_var_field(node) - node("var_field") do - field("value", node.value) - comments(node) - end - end - - def visit_var_ref(node) - node("var_ref") do - field("value", node.value) - comments(node) - end - end - - def visit_vcall(node) - node("vcall") do - field("value", node.value) - comments(node) - end - end - - def visit_void_stmt(node) - node("void_stmt") do - comments(node) - end - end - - def visit_when(node) - node("when") do - field("arguments", node.arguments) - field("statements", node.statements) - field("consequent", node.consequent) if node.consequent - comments(node) - end - end - - def visit_while(node) - node("while") do - field("predicate", node.predicate) - field("statements", node.statements) - comments(node) - end - end - - def visit_while_mod(node) - node("while_mod") do - field("statement", node.statement) - field("predicate", node.predicate) - comments(node) - end - end - - def visit_word(node) - node("word") do - list("parts", node.parts) - comments(node) - end - end - - def visit_words(node) - node("words") do - list("elements", node.elements) - comments(node) - end - end - - def visit_words_beg(node) - node("words_beg") do - field("value", node.value) - end - end - - def visit_xstring(node) - node("xstring") do - list("parts", node.parts) - end - end - - def visit_xstring_literal(node) - node("xstring_literal") do - list("parts", node.parts) - comments(node) - end - end - - def visit_yield(node) - node("yield") do - field("arguments", node.arguments) + def visit_label(node) + node(node, "label") do + q.breakable + q.text(":") + q.text(node.value[0...-1]) comments(node) end end - def visit_yield0(node) - visit_token("yield0", node) - end - - def visit_zsuper(node) - visit_token("zsuper", node) - end - - def visit___end__(node) - visit_token("__end__", node) - end - private def comments(node) @@ -1190,24 +47,33 @@ def list(_name, values) end end - def node(type) + def node(node, type) q.group(2, "(", ")") do q.text(type) yield end end + def pairs(_name, values) + q.group(2, "(", ")") do + q.seplist(values) do |(key, value)| + key.pretty_print(q) + + if value + q.text("=") + q.group(2) do + q.breakable("") + value.pretty_print(q) + end + end + end + end + end + def text(_name, value) q.breakable q.text(value) end - - def visit_token(type, node) - node(type) do - field("value", node.value) - comments(node) - end - end end end end From 69b0db5dbe348ef1f6e885d3fcc02791b18de4ab Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 3 May 2022 16:56:32 -0400 Subject: [PATCH 2/3] Rework InlayHints to use a visitor --- .../language_server/inlay_hints.rb | 118 ++++++++++++------ 1 file changed, 80 insertions(+), 38 deletions(-) diff --git a/lib/syntax_tree/language_server/inlay_hints.rb b/lib/syntax_tree/language_server/inlay_hints.rb index 0bed2a80..02cbc6f5 100644 --- a/lib/syntax_tree/language_server/inlay_hints.rb +++ b/lib/syntax_tree/language_server/inlay_hints.rb @@ -2,14 +2,72 @@ module SyntaxTree class LanguageServer - class InlayHints - attr_reader :before, :after + class InlayHints < Visitor + attr_reader :stack, :before, :after def initialize + @stack = [] @before = Hash.new { |hash, key| hash[key] = +"" } @after = Hash.new { |hash, key| hash[key] = +"" } end + def visit(node) + stack << node + result = super + stack.pop + result + end + + # Adds parentheses around assignments contained within the default values + # of parameters. For example, + # + # def foo(a = b = c) + # end + # + # becomes + # + # def foo(a = ₍b = c₎) + # end + # + def visit_assign(node) + parentheses(node.location) if stack[-2].is_a?(Params) + end + + # Adds parentheses around binary expressions to make it clear which + # subexpression will be evaluated first. For example, + # + # a + b * c + # + # becomes + # + # a + ₍b * c₎ + # + def visit_binary(node) + case stack[-2] + in Assign | OpAssign + parentheses(node.location) + in Binary[operator: operator] if operator != node.operator + parentheses(node.location) + else + end + end + + # Adds parentheses around ternary operators contained within certain + # expressions where it could be confusing which subexpression will get + # evaluated first. For example, + # + # a ? b : c ? d : e + # + # becomes + # + # a ? b : ₍c ? d : e₎ + # + def visit_if_op(node) + if stack[-2] in Assign | Binary | IfOp | OpAssign + parentheses(node.location) + end + end + # Adds the implicitly rescued StandardError into a bare rescue clause. For # example, # @@ -23,54 +81,38 @@ def initialize # rescue StandardError # end # - def bare_rescue(location) - after[location.start_char + "rescue".length] << " StandardError" + def visit_rescue(node) + if node.exception.nil? + after[node.location.start_char + "rescue".length] << " StandardError" + end end - # Adds implicit parentheses around certain expressions to make it clear - # which subexpression will be evaluated first. For example, + # Adds parentheses around unary statements using the - operator that are + # contained within Binary nodes. For example, # - # a + b * c + # -a + b # # becomes # - # a + ₍b * c₎ + # ₍-a₎ + b # - def precedence_parentheses(location) - before[location.start_char] << "₍" - after[location.end_char] << "₎" + def visit_unary(node) + if stack[-2].is_a?(Binary) && (node.operator == "-") + parentheses(node.location) + end end def self.find(program) - inlay_hints = new - queue = [[nil, program]] - - until queue.empty? - parent_node, child_node = queue.shift - - child_node.child_nodes.each do |grand_child_node| - queue << [child_node, grand_child_node] if grand_child_node - end + visitor = new + visitor.visit(program) + visitor + end - case [parent_node, child_node] - in _, Rescue[exception: nil, location:] - inlay_hints.bare_rescue(location) - in Assign | Binary | IfOp | OpAssign, IfOp[location:] - inlay_hints.precedence_parentheses(location) - in Assign | OpAssign, Binary[location:] - inlay_hints.precedence_parentheses(location) - in Binary[operator: parent_oper], Binary[operator: child_oper, location:] if parent_oper != child_oper - inlay_hints.precedence_parentheses(location) - in Binary, Unary[operator: "-", location:] - inlay_hints.precedence_parentheses(location) - in Params, Assign[location:] - inlay_hints.precedence_parentheses(location) - else - # do nothing - end - end + private - inlay_hints + def parentheses(location) + before[location.start_char] << "₍" + after[location.end_char] << "₎" end end end From d26544c188d204c7114e0a68734cf18102789b40 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 3 May 2022 17:02:42 -0400 Subject: [PATCH 3/3] Fix symbol printing --- lib/syntax_tree/visitor/match_visitor.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/syntax_tree/visitor/match_visitor.rb b/lib/syntax_tree/visitor/match_visitor.rb index b3da85d1..ab88ae58 100644 --- a/lib/syntax_tree/visitor/match_visitor.rb +++ b/lib/syntax_tree/visitor/match_visitor.rb @@ -107,7 +107,7 @@ def text(name, value) q.nest(0) do q.text(name) q.text(": ") - q.text(value) + value.pretty_print(q) end end end