proc_to_ast-0.2.0/0000755000004100000410000000000014632135270014060 5ustar www-datawww-dataproc_to_ast-0.2.0/.gitignore0000644000004100000410000000030714632135270016050 0ustar www-datawww-data*.gem *.rbc .bundle .config .yardoc Gemfile.lock InstalledFiles _yardoc coverage doc/ lib/bundler/man pkg rdoc spec/reports test/tmp test/version_tmp tmp *.bundle *.so *.o *.a mkmf.log rspec.output proc_to_ast-0.2.0/.github/0000755000004100000410000000000014632135270015420 5ustar www-datawww-dataproc_to_ast-0.2.0/.github/workflows/0000755000004100000410000000000014632135270017455 5ustar www-datawww-dataproc_to_ast-0.2.0/.github/workflows/rspec.yml0000644000004100000410000000123414632135270021314 0ustar www-datawww-dataname: RSpec on: push: branches: [ master ] pull_request: jobs: test: runs-on: ubuntu-latest strategy: matrix: ruby-version: ['3.1', '3.2', '3.3'] steps: - uses: actions/checkout@v2 - name: Set up Ruby # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby, # change this to (see https://github.com/ruby/setup-ruby#versioning): # uses: ruby/setup-ruby@v1 uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby-version }} bundler-cache: true # runs 'bundle install' and caches installed gems automatically - name: Run tests run: bundle exec rake proc_to_ast-0.2.0/lib/0000755000004100000410000000000014632135270014626 5ustar www-datawww-dataproc_to_ast-0.2.0/lib/proc_to_ast/0000755000004100000410000000000014632135270017142 5ustar www-datawww-dataproc_to_ast-0.2.0/lib/proc_to_ast/version.rb0000644000004100000410000000005114632135270021150 0ustar www-datawww-datamodule ProcToAst VERSION = "0.2.0" end proc_to_ast-0.2.0/lib/proc_to_ast/parser_gem.rb0000644000004100000410000000543514632135270021622 0ustar www-datawww-datarequire "proc_to_ast/version" require "parser/current" require "unparser" require "rouge" module ProcToAst class MultiMatchError < StandardError; end class Parser attr_reader :parser @formatter = Rouge::Formatters::Terminal256.new @lexer = Rouge::Lexers::Ruby.new class << self def highlight(source) @formatter.format(@lexer.lex(source)) end end def initialize @parser = ::Parser::CurrentRuby.default_parser @parser.diagnostics.consumer = ->(diagnostic) {} # suppress error message end # Read file and try parsing # if success parse, find proc AST # # @param filename [String] reading file path # @param linenum [Integer] start line number # @return [Parser::AST::Node] Proc AST def parse(filename, linenum) @filename, @linenum = filename, linenum buf = [] File.open(filename, "rb").each_with_index do |line, index| next if index < linenum - 1 buf << line begin return do_parse(buf.join) rescue ::Parser::SyntaxError node = trim_and_retry(buf) return node if node end end fail(::Parser::SyntaxError, "Unknown error") end private def do_parse(source) parser.reset source_buffer = ::Parser::Source::Buffer.new(@filename, @linenum) source_buffer.source = source node = parser.parse(source_buffer) block_nodes = traverse_node(node) if block_nodes.length == 1 block_nodes.first else raise ProcToAst::MultiMatchError end end # Remove tail comma and wrap dummy method, and retry parsing # For proc inner Array or Hash def trim_and_retry(buf) *lines, last = buf # For inner Array or Hash or Arguments list. lines << last.gsub(/,\s*$/, "") do_parse("a(#{lines.join})") # wrap dummy method rescue ::Parser::SyntaxError end def traverse_node(node) if node.type != :block node.children.flat_map { |child| if child.is_a?(AST::Node) traverse_node(child) end }.compact else [node] end end end end class Proc # @return [Parser::AST::Node] Proc AST def to_ast filename, linenum = source_location parser = ProcToAst::Parser.new parser.parse(filename, linenum) end # @param highlight [Boolean] enable output highlight # @return [String] proc source code def to_source(highlight: false) source = Unparser.unparse(to_ast) if highlight ProcToAst::Parser.highlight(source) else source end end def to_raw_source(highlight: false) source = to_ast.loc.expression.source.force_encoding("UTF-8") if highlight ProcToAst::Parser.highlight(source) else source end end end proc_to_ast-0.2.0/lib/proc_to_ast.rb0000644000004100000410000000004114632135270017462 0ustar www-datawww-datarequire "proc_to_ast/parser_gem" proc_to_ast-0.2.0/LICENSE.txt0000644000004100000410000000205214632135270015702 0ustar www-datawww-dataCopyright (c) 2014 joker1007 MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. proc_to_ast-0.2.0/spec/0000755000004100000410000000000014632135270015012 5ustar www-datawww-dataproc_to_ast-0.2.0/spec/spec_helper.rb0000644000004100000410000000013414632135270017626 0ustar www-datawww-data$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) require 'proc_to_ast/parser_gem' proc_to_ast-0.2.0/spec/proc_to_ast_spec.rb0000644000004100000410000001177114632135270020674 0ustar www-datawww-datarequire 'spec_helper' describe Proc do describe "#to_ast" do it "return Parser::AST::Node" do ast = -> { 1 + 1 }.to_ast expect(ast).to be_a(Parser::AST::Node) expect(ast.type).to eq(:block) end context "proc variation" do it "converts Kernel#proc" do pr = proc { p 1 } expect(pr.to_ast).to be_a(AST::Node) end it "converts Proc.new" do pr = Proc.new do |b| puts b end expect(pr.to_ast).to be_a(AST::Node) end it "long proc" do pr = Proc.new do |b| p 1 p 1 p 1 p 1 p 1 p 1 p 1 p 1 p 1 p 1 p 1 p 1 p 1 p 1 p 1 p 1 p 1 p 1 p 1 end expect(pr.to_ast).to be_a(AST::Node) end it "converts block passing method" do def receive_block(&block) block.to_ast end block_pass = receive_block do |n| puts n [1, 2, 3].map do |i| i * 2 end end block_pass2 = receive_block { %w(a b c).map(&:upcase) } expect(block_pass).to be_a(AST::Node) expect(block_pass2).to be_a(AST::Node) end it "raise ProcToAst::MultiMatchError, when other block exists on same line " do _ = [1].map {|i| i * 2}; fuga = ->(a) { p a } expect{ fuga.to_ast }.to raise_error(ProcToAst::MultiMatchError) end it "inner array" do array = [ 1, -> { 2 }, [ -> { 3 }, ], proc do |a| [ a, ] end ] expect(array[1].to_ast).to be_a(AST::Node) expect(array[1].to_source).to eq("lambda {\n 2\n}") expect(array[2][0].to_source).to eq("lambda {\n 3\n}") expect(array[3].to_source).to eq("proc { |a,|\n [a]\n}") end it "inner two dimension array" do array = [ [1, 2, -> { 3 }], [4, 5, -> { 6 }] ] expect(array[0][2].to_ast).to be_a(AST::Node) expect(array[1][2].to_ast).to be_a(AST::Node) end it "inner hash" do hash_oneline = {a: -> { 1 }} expect(hash_oneline[:a].to_ast).to be_a(AST::Node) expect(hash_oneline[:a].to_source).to eq("lambda {\n 1\n}") end it "inner multiline hash" do hash = { a: -> { 1 }, :b => -> { 2 }, c: -> { 3 }, d: -> { 4 } } expect(hash[:a].to_ast).to be_a(AST::Node) expect(hash[:a].to_source).to eq("lambda {\n 1\n}") expect(hash[:b].to_ast).to be_a(AST::Node) expect(hash[:b].to_source).to eq("lambda {\n 2\n}") expect(hash[:c].to_ast).to be_a(AST::Node) expect(hash[:c].to_source).to eq("lambda {\n 3\n}") expect(hash[:d].to_ast).to be_a(AST::Node) expect(hash[:d].to_source).to eq("lambda {\n 4\n}") end it "inner another hash" do hash = { a: 1, :b => -> { 2 }, } expect(hash[:b].to_ast).to be_a(AST::Node) expect(hash[:b].to_source).to eq("lambda {\n 2\n}") end end end describe "#to_source" do it "return source code string" do fuga = ->(a) { p a } expect(fuga.to_source).to eq("lambda { |a,|\n p(a)\n}") expect(fuga.to_source(highlight: true)).to eq("\e[01mlambda\e[00m\e[38;5;230m \e[39m\e[38;5;87m{\e[39m\e[38;5;230m \e[39m\e[38;5;87;01m|\e[39;00m\e[38;5;230ma\e[39m\e[38;5;87m,\e[39m\e[38;5;87;01m|\e[39;00m\e[38;5;230m\e[39m\n\e[38;5;230m \e[39m\e[01mp\e[00m\e[38;5;87m(\e[39m\e[38;5;230ma\e[39m\e[38;5;87m)\e[39m\e[38;5;230m\e[39m\n\e[38;5;230m\e[39m\e[38;5;87m}\e[39m") end it "charset is UTF-8" do fuga = ->(a) { p a } expect(fuga.to_source.encoding).to eq(Encoding::UTF_8) end it "can handle multibyte characters" do pr = ->(a) { a + "ほげほげ" } expect(pr.to_source).to eq("lambda { |a,|\n a + \"\\xE3\\x81\\xBB\\xE3\\x81\\x92\\xE3\\x81\\xBB\\xE3\\x81\\x92\"\n}") end end describe "#to_raw_source" do it "return original source code" do pr = ->(a) { a + 1 } expect(pr.to_raw_source).to eq("->(a) { a + 1 }") end it "charset is UTF-8" do pr = ->(a) { a + 1 } expect(pr.to_raw_source.encoding).to eq(Encoding::UTF_8) end it "can handle multibyte characters" do pr = ->(a) { a + "ほげほげ" } expect(pr.to_raw_source).to eq("->(a) { a + \"ほげほげ\" }") expect(pr.to_raw_source(highlight: true)).to eq("\e[38;5;87;01m->\e[39;00m\e[38;5;87m(\e[39m\e[38;5;230ma\e[39m\e[38;5;87m)\e[39m\e[38;5;230m \e[39m\e[38;5;87m{\e[39m\e[38;5;230m \e[39m\e[38;5;230ma\e[39m\e[38;5;230m \e[39m\e[38;5;87;01m+\e[39;00m\e[38;5;230m \e[39m\e[38;5;229;01m\"ほげほげ\"\e[39;00m\e[38;5;230m \e[39m\e[38;5;87m}\e[39m") end end end proc_to_ast-0.2.0/.rspec0000644000004100000410000000003714632135270015175 0ustar www-datawww-data--format documentation --color proc_to_ast-0.2.0/Rakefile0000644000004100000410000000016614632135270015530 0ustar www-datawww-datarequire "bundler/gem_tasks" require "rspec/core/rake_task" RSpec::Core::RakeTask.new(:spec) task :default => :spec proc_to_ast-0.2.0/Gemfile0000644000004100000410000000014014632135270015346 0ustar www-datawww-datasource 'https://rubygems.org' # Specify your gem's dependencies in proc_to_ast.gemspec gemspec proc_to_ast-0.2.0/proc_to_ast.gemspec0000644000004100000410000000216014632135270017740 0ustar www-datawww-data# coding: utf-8 lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'proc_to_ast/version' Gem::Specification.new do |spec| spec.name = "proc_to_ast" spec.version = ProcToAst::VERSION spec.authors = ["joker1007"] spec.email = ["kakyoin.hierophant@gmail.com"] spec.summary = %q{Convert Proc object to AST::Node} spec.description = %q{Add #to_ast method to Proc. #to_ast converts Proc object to AST::Node.} spec.homepage = "https://github.com/joker1007/proc_to_ast" spec.license = "MIT" spec.files = `git ls-files -z`.split("\x0") spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) spec.require_paths = ["lib"] spec.add_runtime_dependency "parser" spec.add_runtime_dependency "unparser" spec.add_runtime_dependency "rouge" spec.add_development_dependency "bundler", ">= 1.5" spec.add_development_dependency "rake" spec.add_development_dependency "rspec" spec.required_ruby_version = '>= 2.0.0' end proc_to_ast-0.2.0/README.md0000644000004100000410000000235614632135270015345 0ustar www-datawww-data# proc_to_ast [![Gem Version](https://badge.fury.io/rb/proc_to_ast.svg)](http://badge.fury.io/rb/proc_to_ast) [![RSpec](https://github.com/joker1007/proc_to_ast/actions/workflows/rspec.yml/badge.svg)](https://github.com/joker1007/proc_to_ast/actions/workflows/rspec.yml) Add `#to_ast` method to Proc. `#to_ast` convert Proc to `Parser::AST::Node`, using [parser](https://github.com/whitequark/parser "whitequark/parser") gem. ## Installation Add this line to your application's Gemfile: gem 'proc_to_ast' And then execute: $ bundle Or install it yourself as: $ gem install proc_to_ast ## Usage ```ruby require 'proc_to_ast' foo = proc { p(1 + 1) } foo.to_ast # => # (block # (send nil :proc) # (args) # (send nil :p # (send # (int 1) :+ # (int 1)))) foo.to_source # => "proc do\n p(1 + 1)\nend" foo.to_source(highlight: true) # => "proc \e[32mdo\e[0m\n p(\e[1;34m1\e[0m + \e[1;34m1\e[0m)\n\e[32mend\e[0m" ``` ## Contributing 1. Fork it ( https://github.com/[my-github-username]/proc_to_ast/fork ) 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Add some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create a new Pull Request