maccro 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +359 -0
- data/Rakefile +12 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/maccro.rb +147 -0
- data/lib/maccro/builtin.rb +105 -0
- data/lib/maccro/code_range.rb +63 -0
- data/lib/maccro/code_util.rb +106 -0
- data/lib/maccro/dsl.rb +182 -0
- data/lib/maccro/dsl/assign.rb +100 -0
- data/lib/maccro/dsl/expression.rb +166 -0
- data/lib/maccro/dsl/literal.rb +121 -0
- data/lib/maccro/dsl/node.rb +89 -0
- data/lib/maccro/dsl/value.rb +85 -0
- data/lib/maccro/kernel_ext.rb +15 -0
- data/lib/maccro/matched.rb +34 -0
- data/lib/maccro/rewrite_the_world.rb +4 -0
- data/lib/maccro/rule.rb +106 -0
- data/lib/maccro/version.rb +3 -0
- data/maccro.gemspec +27 -0
- metadata +108 -0
@@ -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
|
data/lib/maccro/rule.rb
ADDED
@@ -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
|
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: []
|