maccro 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,85 @@
1
+ require_relative "node"
2
+ require_relative "literal"
3
+
4
+ module Maccro
5
+ module DSL
6
+ class LocalVariable < Node
7
+ # LVAR: local variable in method local scope (method or local arguments)
8
+ # DVAR: dynamically defined local variable (defined&initialized in local scope)
9
+ SUB_NODETYPE_LIST = [:LVAR, :DVAR].freeze
10
+
11
+ def self.match?(node)
12
+ SUB_NODETYPE_LIST.include?(node.type)
13
+ end
14
+ end
15
+
16
+ class GlobalVariable < Node
17
+ def type; :GVAR; end
18
+ def self.match?(node); node.type == :GVAR; end
19
+ end
20
+
21
+ class InstanceVariable < Node
22
+ def type; :IVAR; end
23
+ def self.match?(node); node.type == :IVAR; end
24
+ end
25
+
26
+ class ClassVariable < Node
27
+ def type; :CVAR; end
28
+ def self.match?(node); node.type == :CVAR; end
29
+ end
30
+
31
+ class Variable < Node
32
+ SUB_TYPES = [LocalVariable, GlobalVariable, InstanceVariable, ClassVariable].freeze
33
+
34
+ def type; :MACCRO_VARIABLE; end
35
+
36
+ def self.match?(node)
37
+ SUB_TYPES.any?{|s| s.match?(node) }
38
+ end
39
+ end
40
+
41
+ class NthRefVariable < Node
42
+ # $1, $2 (regexp group capture reference)
43
+ def type; :NTH_REF; end
44
+ def self.match?(node); node.type == :NTH_REF; end
45
+ end
46
+
47
+ class BackRefVariable < Node
48
+ # $&, $`, ...
49
+ def type; :BACK_REF; end
50
+ def self.match?(node); node.type == :BACK_REF; end
51
+ end
52
+
53
+ class SpecialVariable < Node
54
+ SUB_TYPES = [NthRefVariable, BackRefVariable].freeze
55
+
56
+ def type; :MACCRO_SPECIAL_VARIABLE; end
57
+
58
+ def self.match?(node)
59
+ SUB_TYPES.any?{|s| s.match?(node) }
60
+ end
61
+ end
62
+
63
+ class Constant < Node
64
+ def type; :CONST; end
65
+ def self.match?(node); node.type == :CONST; end
66
+ end
67
+
68
+ class Self < Node
69
+ def type; :SELF; end
70
+ def self.match?(node); node.type == :SELF; end
71
+ end
72
+
73
+ class Value < Node
74
+ SUB_TYPES = [
75
+ Variable, SpecialVariable, Constant,
76
+ Literal, String, RegexpCompiledOnce, DRegexp, DSymbol,
77
+ Self, NilValue, TrueValue, FalseValue, Lambda, Array, Hash,
78
+ ].freeze
79
+
80
+ def self.match?(node)
81
+ SUB_TYPES.any?{|s| s.match?(node) }
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,15 @@
1
+ require_relative "./code_util"
2
+
3
+ module Kernel
4
+ def dirty_require(feature)
5
+ Maccro::CodeUtil.suppress_warning do
6
+ require feature
7
+ end
8
+ end
9
+
10
+ def dirty_load(file, priv = false)
11
+ Maccro::CodeUtil.suppress_warning do
12
+ load(file, priv)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,34 @@
1
+ require_relative 'code_util'
2
+
3
+ module Maccro
4
+ Match = Struct.new(:rule, :placeholders, :range, keyword_init: true)
5
+ # placeholders: name => code_range (in method)
6
+
7
+ class Matched
8
+ attr_reader :matches
9
+
10
+ def initialize(matches)
11
+ @matches = matches.sort{|a, b| a.range <=> b.range }
12
+ end
13
+
14
+ def self.get_replace_pairs(entire_source, placeholders) # name => code_ranges
15
+ replace_pairs = {}
16
+ placeholders.each_pair do |name, code_range|
17
+ replace_pairs[name] = CodeUtil.code_range_to_code(entire_source, code_range)
18
+ end
19
+ replace_pairs # name => snippets
20
+ end
21
+
22
+ def rewrite(source)
23
+ # TODO: implement @safe_reference
24
+ source = source.dup
25
+ # move tail to head, not to break code positions of unprocessed matches
26
+ @matches.reverse.each do |match|
27
+ replace_pairs = self.class.get_replace_pairs(source, match.placeholders)
28
+ range = CodeUtil.code_range_to_range(source, match.range)
29
+ source[range] = match.rule.after_code(replace_pairs)
30
+ end
31
+ source
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,4 @@
1
+ require "maccro/builtin"
2
+
3
+ Maccro::Builtin.register_all
4
+ Maccro.enable
@@ -0,0 +1,106 @@
1
+ require_relative 'dsl'
2
+ require_relative 'matched'
3
+ require_relative 'code_util'
4
+
5
+ module Maccro
6
+ class Rule
7
+ attr_reader :name, :before, :after, :matcher, :under, :safe_reference
8
+
9
+ def initialize(name, before, after, under: nil, safe_reference: false)
10
+ @name = name
11
+ @before = before
12
+ @after = after
13
+ @under = under
14
+ @safe_reference = safe_reference
15
+
16
+ # TODO: check all placeholder in @after exist in @before
17
+ # (placeholders in @before are not required to exist in @after, because it may be removed)
18
+ # TODO: check $TARGET exists in under just once
19
+
20
+ # TODO: implement a matcher in @before matches in multi times
21
+
22
+ @matcher = DSL.matcher(before)
23
+ @pruner = under && DSL.matcher(under) || nil
24
+ end
25
+
26
+ def match(ast)
27
+ matches = []
28
+
29
+ if @pruner
30
+ dig_prune(matches, ast)
31
+ else
32
+ dig_match(matches, ast)
33
+ end
34
+
35
+ return nil if matches.empty?
36
+
37
+ Matched.new(matches)
38
+ end
39
+
40
+ def dig_prune(matches, ast)
41
+ if @pruner.match?(ast)
42
+ placeholders = {}
43
+ @pruner.capture(ast, placeholders)
44
+ dig_match(matches, placeholders[:__target__])
45
+ elsif ast.respond_to?(:children)
46
+ ast.children.each do |c|
47
+ dig_prune(matches, c)
48
+ end
49
+ elsif ast.respond_to?(:each)
50
+ ast.each do |i|
51
+ dig_prune(matches, i)
52
+ end
53
+ end
54
+ end
55
+
56
+ def dig_match(matches, ast)
57
+ if @matcher.match?(ast)
58
+ placeholders = {}
59
+ @matcher.capture(ast, placeholders)
60
+ matches << Match.new(rule: self, placeholders: placeholders, range: ast.to_code_range)
61
+ elsif ast.respond_to?(:children)
62
+ ast.children.each do |c|
63
+ dig_match(matches, c)
64
+ end
65
+ elsif ast.respond_to?(:each)
66
+ ast.each do |i|
67
+ dig_match(matches, i)
68
+ end
69
+ end
70
+ end
71
+
72
+ def self.find_placeholder_code_ranges(ast, name)
73
+ return [] unless ast.is_a? RubyVM::AbstractSyntaxTree::Node
74
+
75
+ if ast.type == :VCALL && ast.children.first.to_s == name
76
+ return [CodeRange.from_node(ast)]
77
+ end
78
+
79
+ ranges = []
80
+ ast.children.each do |c|
81
+ rs = find_placeholder_code_ranges(c, name)
82
+ ranges.concat(rs) unless rs.empty?
83
+ end
84
+ ranges.sort
85
+ end
86
+
87
+ def after_code(replace_pairs) # name => snippet
88
+ code = @after.dup
89
+ ast = CodeUtil.parse_to_ast(@after)
90
+ code_range_to_name = {}
91
+ replace_pairs.each_key do |name|
92
+ self.class.find_placeholder_code_ranges(ast, name).each do |r|
93
+ code_range_to_name[r] = name
94
+ end
95
+ end
96
+ # reverse is not to break code position for unprocessed code ranges
97
+ code_range_to_name.keys.sort.reverse.each do |code_range|
98
+ name = code_range_to_name[code_range]
99
+ snippet = replace_pairs[name]
100
+ range = CodeUtil.code_range_to_range(@after.dup, code_range)
101
+ code[range] = snippet
102
+ end
103
+ code
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,3 @@
1
+ module Maccro
2
+ VERSION = "0.1.0"
3
+ end
data/maccro.gemspec ADDED
@@ -0,0 +1,27 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "maccro/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "maccro"
8
+ spec.version = Maccro::VERSION
9
+ spec.authors = ["TAGOMORI Satoshi"]
10
+ spec.email = ["tagomoris@gmail.com"]
11
+
12
+ spec.summary = %q{Pure black magic scroll}
13
+ spec.description = %q{Macro processor for Ruby 2.6 or later}
14
+ spec.homepage = "https://github.com/tagomoris/maccro"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
18
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
19
+ end
20
+ spec.bindir = "exe"
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.add_development_dependency "bundler"
25
+ spec.add_development_dependency "rake"
26
+ spec.add_development_dependency "test-unit"
27
+ end
metadata ADDED
@@ -0,0 +1,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: maccro
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - TAGOMORI Satoshi
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-04-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: test-unit
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Macro processor for Ruby 2.6 or later
56
+ email:
57
+ - tagomoris@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - Gemfile
64
+ - LICENSE.txt
65
+ - README.md
66
+ - Rakefile
67
+ - bin/console
68
+ - bin/setup
69
+ - lib/maccro.rb
70
+ - lib/maccro/builtin.rb
71
+ - lib/maccro/code_range.rb
72
+ - lib/maccro/code_util.rb
73
+ - lib/maccro/dsl.rb
74
+ - lib/maccro/dsl/assign.rb
75
+ - lib/maccro/dsl/expression.rb
76
+ - lib/maccro/dsl/literal.rb
77
+ - lib/maccro/dsl/node.rb
78
+ - lib/maccro/dsl/value.rb
79
+ - lib/maccro/kernel_ext.rb
80
+ - lib/maccro/matched.rb
81
+ - lib/maccro/rewrite_the_world.rb
82
+ - lib/maccro/rule.rb
83
+ - lib/maccro/version.rb
84
+ - maccro.gemspec
85
+ homepage: https://github.com/tagomoris/maccro
86
+ licenses:
87
+ - MIT
88
+ metadata: {}
89
+ post_install_message:
90
+ rdoc_options: []
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ requirements: []
104
+ rubygems_version: 3.0.3
105
+ signing_key:
106
+ specification_version: 4
107
+ summary: Pure black magic scroll
108
+ test_files: []