macros 0.1
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/Gemfile +3 -0
- data/Gemfile.lock +55 -0
- data/README.md +114 -0
- data/example/main.rb +6 -0
- data/example/my_macros.rb +11 -0
- data/example/the_code.rb +4 -0
- data/lib/macros.rb +70 -0
- data/lib/macros/compiler.rb +47 -0
- data/lib/macros/expander.rb +39 -0
- data/lib/macros/loader.rb +31 -0
- data/lib/macros/sexp.rb +59 -0
- data/macros.gemspec +20 -0
- data/scratch.rb +38 -0
- data/spec/macros/compiler_spec.rb +66 -0
- data/spec/macros/expander_spec.rb +37 -0
- data/spec/macros/sexp_spec.rb +46 -0
- metadata +107 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0c9898a7427d06f00baffd3b44089cb932afb9b9
|
4
|
+
data.tar.gz: de39271f19a73ee5fd2877ebaf231b3904abd477
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d74a8017921471d947eb243c8e3319f827ed70fbbbc8f8a0dd29dcd9a185b2042d089c0920644268bd9d5a88b5f38743c94e09e0f304af8e0e14176b2e189f49
|
7
|
+
data.tar.gz: d309a711b05cbc4c9859983a4d61aac73ed94dc5c4b5f3a48a951f123f74df4253254469a8c5d88894da88d7340058542ae033c925db29386a6fca7522bacba1
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
macros (0.1)
|
5
|
+
parser (> 0)
|
6
|
+
unparser (> 0)
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
abstract_type (0.0.7)
|
12
|
+
adamantium (0.2.0)
|
13
|
+
ice_nine (~> 0.11.0)
|
14
|
+
memoizable (~> 0.4.0)
|
15
|
+
ast (2.1.0)
|
16
|
+
concord (0.1.5)
|
17
|
+
adamantium (~> 0.2.0)
|
18
|
+
equalizer (~> 0.0.9)
|
19
|
+
diff-lcs (1.2.5)
|
20
|
+
equalizer (0.0.11)
|
21
|
+
ice_nine (0.11.1)
|
22
|
+
memoizable (0.4.2)
|
23
|
+
thread_safe (~> 0.3, >= 0.3.1)
|
24
|
+
parser (2.2.3.0)
|
25
|
+
ast (>= 1.1, < 3.0)
|
26
|
+
procto (0.0.2)
|
27
|
+
rspec (3.3.0)
|
28
|
+
rspec-core (~> 3.3.0)
|
29
|
+
rspec-expectations (~> 3.3.0)
|
30
|
+
rspec-mocks (~> 3.3.0)
|
31
|
+
rspec-core (3.3.2)
|
32
|
+
rspec-support (~> 3.3.0)
|
33
|
+
rspec-expectations (3.3.1)
|
34
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
35
|
+
rspec-support (~> 3.3.0)
|
36
|
+
rspec-mocks (3.3.2)
|
37
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
38
|
+
rspec-support (~> 3.3.0)
|
39
|
+
rspec-support (3.3.0)
|
40
|
+
thread_safe (0.3.5)
|
41
|
+
unparser (0.2.4)
|
42
|
+
abstract_type (~> 0.0.7)
|
43
|
+
adamantium (~> 0.2.0)
|
44
|
+
concord (~> 0.1.5)
|
45
|
+
diff-lcs (~> 1.2.5)
|
46
|
+
equalizer (~> 0.0.9)
|
47
|
+
parser (~> 2.2.2)
|
48
|
+
procto (~> 0.0.2)
|
49
|
+
|
50
|
+
PLATFORMS
|
51
|
+
ruby
|
52
|
+
|
53
|
+
DEPENDENCIES
|
54
|
+
macros!
|
55
|
+
rspec (~> 3.0)
|
data/README.md
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
# Macros
|
2
|
+
|
3
|
+
Macros for Ruby
|
4
|
+
|
5
|
+
## Install
|
6
|
+
|
7
|
+
```
|
8
|
+
gem install macros
|
9
|
+
```
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
Any source file that contains macro definitions, or that requires macro
|
14
|
+
expansion, should be loaded with `Macros.require`.
|
15
|
+
|
16
|
+
``` ruby
|
17
|
+
require 'macros'
|
18
|
+
|
19
|
+
Macros.require 'my_macros'
|
20
|
+
Macros.require 'my_code_using_macros'
|
21
|
+
```
|
22
|
+
|
23
|
+
Macros look like normal method definitions, but they are defined inside a
|
24
|
+
`Macros do ; end` block.
|
25
|
+
|
26
|
+
``` ruby
|
27
|
+
# my_macros.rb
|
28
|
+
|
29
|
+
Macros do
|
30
|
+
# Replace the +output+ method with the +puts+ method
|
31
|
+
def with_output(ast)
|
32
|
+
treemap(ast) do |node|
|
33
|
+
if smatch? node, s(:send, nil, :output)
|
34
|
+
s(:send, nil, :puts, *node.children.drop(2))
|
35
|
+
else
|
36
|
+
node
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
```
|
42
|
+
|
43
|
+
Now this code
|
44
|
+
|
45
|
+
``` ruby
|
46
|
+
# my_code_using_macros.rb
|
47
|
+
|
48
|
+
with_output do
|
49
|
+
output "foo"
|
50
|
+
output "bar"
|
51
|
+
end
|
52
|
+
```
|
53
|
+
|
54
|
+
Will be transformed into
|
55
|
+
|
56
|
+
``` ruby
|
57
|
+
puts "foo"
|
58
|
+
puts "bar"
|
59
|
+
```
|
60
|
+
|
61
|
+
## Helpers
|
62
|
+
|
63
|
+
The macro will receive an instance of `Parser::AST::Node`, and must return an
|
64
|
+
instance of `Parser::AST::Node`. Inside the macro definition the following
|
65
|
+
convenience functions are available:
|
66
|
+
|
67
|
+
### `s(type, *children)`
|
68
|
+
|
69
|
+
Construct an AST node of given type, with specific children.
|
70
|
+
|
71
|
+
### `node?(n)`
|
72
|
+
|
73
|
+
Is the given object an AST node?
|
74
|
+
|
75
|
+
### `treemap(node, &tranform)`
|
76
|
+
|
77
|
+
Similar to `Enumerable#map`, but performs a full tree walk, passing any
|
78
|
+
`AST::Node` to the block.
|
79
|
+
|
80
|
+
### `treefilter(node, &pred)`
|
81
|
+
|
82
|
+
Returns an array of any node in the tree that satisfies the predicate
|
83
|
+
|
84
|
+
### `sfind(node, spec)`
|
85
|
+
|
86
|
+
`spec` is an Array of symbols, integers, and arrays. It is used a bit like XPath
|
87
|
+
or CSS locators.
|
88
|
+
|
89
|
+
``` ruby
|
90
|
+
node = s(:def, :a_name, s(:args, s(:arg, :x), s(:arg, :y)))
|
91
|
+
sfind(node, [:def, 1, :args, [:arg, 0]])
|
92
|
+
# => [:x, y]
|
93
|
+
```
|
94
|
+
|
95
|
+
### `smatch?(node, pattern)`
|
96
|
+
|
97
|
+
Checks if the node matches the "pattern"
|
98
|
+
|
99
|
+
``` ruby
|
100
|
+
node = s(:def, :a_name, s(:args, s(:arg, :x), s(:arg, :y)))
|
101
|
+
smatch?(node, s(:def, :a_name))
|
102
|
+
# => true
|
103
|
+
```
|
104
|
+
|
105
|
+
## Is this a joke?
|
106
|
+
|
107
|
+
Well, it works, but it's a toy. Working with Ruby syntax trees is pretty
|
108
|
+
awkward, and macros can easily lead to a mess. You have been warned!
|
109
|
+
|
110
|
+
## License
|
111
|
+
|
112
|
+
© Arne Brasseur 2015
|
113
|
+
|
114
|
+
Eclipse Public License
|
data/example/main.rb
ADDED
data/example/the_code.rb
ADDED
data/lib/macros.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
require 'parser/current'
|
4
|
+
require 'unparser'
|
5
|
+
|
6
|
+
MAIN = self
|
7
|
+
|
8
|
+
module Macros
|
9
|
+
|
10
|
+
NotFound = Module.new.freeze
|
11
|
+
|
12
|
+
def self.parse(string)
|
13
|
+
Parser::CurrentRuby.parse(string)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.require(path)
|
17
|
+
@loader ||= Loader.new
|
18
|
+
@loader.require(path)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Parser::AST::Node
|
23
|
+
def inspect(indent=0)
|
24
|
+
indented = " " * indent
|
25
|
+
sexp = "#{indented}s(:#{@type}"
|
26
|
+
|
27
|
+
first_node_child = children.index do |child|
|
28
|
+
child.is_a?(AST::Node) || child.is_a?(Array)
|
29
|
+
end || children.count
|
30
|
+
|
31
|
+
children.each_with_index do |child, idx|
|
32
|
+
if child.is_a?(AST::Node) && idx >= first_node_child
|
33
|
+
sexp << ",\n#{child.inspect(indent + 1)}"
|
34
|
+
else
|
35
|
+
sexp << ", #{child.inspect}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
sexp << ")"
|
40
|
+
|
41
|
+
sexp
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# module Kernel
|
46
|
+
# alias system_require require
|
47
|
+
# def require(name)
|
48
|
+
# $LOAD_PATH.each do |p|
|
49
|
+
# so_file = (Pathname(p) + "#{name}.so")
|
50
|
+
# rb_file = (Pathname(p) + "#{name}.rb")
|
51
|
+
|
52
|
+
# if so_file.file?
|
53
|
+
# system_require(name)
|
54
|
+
# break
|
55
|
+
# end
|
56
|
+
|
57
|
+
# if rb_file.file?
|
58
|
+
# unless $".include? rb_file.to_s
|
59
|
+
# Macros::Loader.new.load(rb_file)
|
60
|
+
# $" << rb_file.to_s
|
61
|
+
# end
|
62
|
+
# end
|
63
|
+
# end
|
64
|
+
# end
|
65
|
+
# end
|
66
|
+
|
67
|
+
require_relative 'macros/sexp'
|
68
|
+
require_relative 'macros/compiler'
|
69
|
+
require_relative 'macros/expander'
|
70
|
+
require_relative 'macros/loader'
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Macros
|
2
|
+
|
3
|
+
class Compiler
|
4
|
+
include Sexp
|
5
|
+
|
6
|
+
def collect_defmacros(sexp)
|
7
|
+
Hash[
|
8
|
+
treefilter(sexp, &method(:macros_node?)).flat_map(&method(:compile_macros))
|
9
|
+
]
|
10
|
+
end
|
11
|
+
|
12
|
+
def reject_defmacros(node)
|
13
|
+
if macros_node?(node)
|
14
|
+
s(:begin)
|
15
|
+
else
|
16
|
+
s(node.type, *node.children.reject(&method(:macros_node?)))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def macros_node?(node)
|
21
|
+
smatch?(node, s(:block, s(:send, nil, :Macros)))
|
22
|
+
end
|
23
|
+
|
24
|
+
def compile_macros(node)
|
25
|
+
treefilter(node) { |n| smatch?(n, s(:def)) }.map(&method(:compile_macro))
|
26
|
+
end
|
27
|
+
|
28
|
+
def compile_macro(node)
|
29
|
+
name, args, body = extract_macro(node)
|
30
|
+
[name,
|
31
|
+
s(:block,
|
32
|
+
s(:send, nil, :lambda),
|
33
|
+
s(:args, *args.map { |a| s(:arg, a) }),
|
34
|
+
body)]
|
35
|
+
end
|
36
|
+
|
37
|
+
def extract_macro(node)
|
38
|
+
name = sfind(node, [0])
|
39
|
+
|
40
|
+
args = sfind(node, [:def, 1, :args, [0]])
|
41
|
+
body = sfind(node, [:def, 2])
|
42
|
+
|
43
|
+
[name, args, body]
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Macros
|
2
|
+
|
3
|
+
class Expander
|
4
|
+
include Sexp
|
5
|
+
|
6
|
+
def initialize(macros)
|
7
|
+
@macros = macros
|
8
|
+
end
|
9
|
+
|
10
|
+
def macroexpand(sexp)
|
11
|
+
treemap(sexp, &method(:macroexpand_1))
|
12
|
+
end
|
13
|
+
|
14
|
+
def macroexpand_1(node)
|
15
|
+
return node unless macro_block?(node)
|
16
|
+
macro = seval @macros[sfind(node, [:block, 0, :send, 1])]
|
17
|
+
body = sfind(node, [:block, 2])
|
18
|
+
macro.call(body)
|
19
|
+
end
|
20
|
+
|
21
|
+
# foo(1,2) { some_more_code }
|
22
|
+
#
|
23
|
+
# >> (block
|
24
|
+
# >> (send nil :foo
|
25
|
+
# >> (int 1)
|
26
|
+
# >> (int 2))
|
27
|
+
# >> (args)
|
28
|
+
# >> (send nil :some_more_code)))
|
29
|
+
def macro_block?(node)
|
30
|
+
# send with block
|
31
|
+
node.type == :block &&
|
32
|
+
# target is implicit self
|
33
|
+
sfind(node, [:block, 0, :send, 0]).nil? &&
|
34
|
+
# known macro
|
35
|
+
@macros.key?(sfind(node, [:block, 0, :send, 1]))
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Macros
|
2
|
+
class Loader
|
3
|
+
include Macros::Sexp
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@compiler = Macros::Compiler.new
|
7
|
+
@macros = {}
|
8
|
+
@expander = Macros::Expander.new(@macros)
|
9
|
+
end
|
10
|
+
|
11
|
+
def require(name)
|
12
|
+
$LOAD_PATH.each do |p|
|
13
|
+
rb_file = (Pathname(p) + "#{name}.rb")
|
14
|
+
|
15
|
+
if rb_file.file?
|
16
|
+
unless $".include? rb_file.to_s
|
17
|
+
load(rb_file)
|
18
|
+
$" << rb_file.to_s
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def load(pathname)
|
25
|
+
ast = Macros.parse pathname.read
|
26
|
+
@macros.merge! @compiler.collect_defmacros(ast)
|
27
|
+
rest_ast = @compiler.reject_defmacros(ast)
|
28
|
+
MAIN.instance_eval Unparser.unparse @expander.macroexpand(rest_ast)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/macros/sexp.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
module Macros
|
2
|
+
|
3
|
+
module Sexp
|
4
|
+
extend self
|
5
|
+
|
6
|
+
def s(type, *children)
|
7
|
+
Parser::AST::Node.new(type, children)
|
8
|
+
end
|
9
|
+
|
10
|
+
# Traverse into sexp by type and child position
|
11
|
+
def sfind(sexp, specs)
|
12
|
+
specs.inject(sexp) do |node, spec|
|
13
|
+
return NotFound if node.nil?
|
14
|
+
|
15
|
+
if spec.is_a?(Symbol) && node.type == spec
|
16
|
+
node
|
17
|
+
elsif spec.is_a?(Integer) && node.children.length > spec
|
18
|
+
node.children[spec]
|
19
|
+
elsif spec.is_a?(Array)
|
20
|
+
node.children.grep(AST::Node)
|
21
|
+
.flat_map { |child| sfind(child, spec) }
|
22
|
+
.reject { |child| child == NotFound }
|
23
|
+
else
|
24
|
+
return NotFound
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def treemap(node, &block)
|
30
|
+
block.call(s(node.type, *node.children.map do |child|
|
31
|
+
next child unless child.is_a? AST::Node
|
32
|
+
block.call(treemap(child, &block))
|
33
|
+
end))
|
34
|
+
end
|
35
|
+
|
36
|
+
def treefilter(node, &block)
|
37
|
+
acc = []
|
38
|
+
acc << node if block.call(node)
|
39
|
+
treemap(node) { |n| acc << n if block.call(n) ; n}
|
40
|
+
acc.freeze
|
41
|
+
end
|
42
|
+
|
43
|
+
def smatch?(node, pattern)
|
44
|
+
return false unless node.type == pattern.type
|
45
|
+
pattern.children.zip(node.children).all? do |a, b|
|
46
|
+
a == b || node?(a) && smatch?(b, a)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def node?(node)
|
51
|
+
node.is_a?(AST::Node)
|
52
|
+
end
|
53
|
+
|
54
|
+
def seval(ast)
|
55
|
+
eval(Unparser.unparse(ast))
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
data/macros.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Gem::Specification.new do |gem|
|
2
|
+
gem.name = 'macros'
|
3
|
+
gem.version = '0.1'
|
4
|
+
gem.authors = [ 'Arne Brasseur' ]
|
5
|
+
gem.email = [ 'arne@arnebrasseur.net' ]
|
6
|
+
gem.description = 'Macros for Ruby'
|
7
|
+
gem.summary = gem.description
|
8
|
+
gem.homepage = 'https://github.com/plexus/macros'
|
9
|
+
gem.license = 'MPL'
|
10
|
+
|
11
|
+
gem.require_paths = %w[lib]
|
12
|
+
gem.files = `git ls-files`.split $/
|
13
|
+
gem.test_files = gem.files.grep(/^spec/)
|
14
|
+
gem.extra_rdoc_files = %w[README.md]
|
15
|
+
|
16
|
+
gem.add_runtime_dependency 'parser', '> 0'
|
17
|
+
gem.add_runtime_dependency 'unparser', '> 0'
|
18
|
+
|
19
|
+
gem.add_development_dependency 'rspec', '~> 3.0'
|
20
|
+
end
|
data/scratch.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require '/home/arne/projects/macros/lib/macros'
|
2
|
+
|
3
|
+
code = %q{
|
4
|
+
Macros do
|
5
|
+
def foo(param1_ast, param2_ast, &block_ast)
|
6
|
+
inside_macro_1(param1_ast, param2_ast, block_ast)
|
7
|
+
inside_macro_2(param1_ast, param2_ast, block_ast)
|
8
|
+
end
|
9
|
+
|
10
|
+
def bar(a,b)
|
11
|
+
inside_macro_1(param1_ast, param2_ast, block_ast)
|
12
|
+
inside_macro_2(param1_ast, param2_ast, block_ast)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
foo(1, 2) do |x|
|
17
|
+
some_more_code
|
18
|
+
end
|
19
|
+
|
20
|
+
}
|
21
|
+
|
22
|
+
include Macros::Sexp
|
23
|
+
|
24
|
+
tree = Parser::CurrentRuby.parse(code)
|
25
|
+
|
26
|
+
dm = tree.children.first # !> assigned but unused variable - dm
|
27
|
+
#p sfind(dm, [:block, 0, :send, 2, 1])
|
28
|
+
|
29
|
+
#puts Macros::Compiler.new.collect_defmacros(Parser::CurrentRuby.parse(code))
|
30
|
+
|
31
|
+
p Parser::CurrentRuby.parse(code).children.last
|
32
|
+
# >> (block
|
33
|
+
# >> (send nil :foo
|
34
|
+
# >> (int 1)
|
35
|
+
# >> (int 2))
|
36
|
+
# >> (args
|
37
|
+
# >> (arg :x))
|
38
|
+
# >> (send nil :some_more_code))
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require_relative '../../lib/macros'
|
2
|
+
|
3
|
+
RSpec.describe Macros::Compiler do
|
4
|
+
include Macros::Sexp
|
5
|
+
|
6
|
+
let(:code) {
|
7
|
+
%q{
|
8
|
+
Macros do
|
9
|
+
def foo(param1_ast, param2_ast, &block_ast)
|
10
|
+
inside_macro_1(param1_ast, param2_ast, block_ast)
|
11
|
+
inside_macro_2(param1_ast, param2_ast, block_ast)
|
12
|
+
end
|
13
|
+
|
14
|
+
def bar(a,b)
|
15
|
+
inside_macro_1(param1_ast, param2_ast, block_ast)
|
16
|
+
inside_macro_2(param1_ast, param2_ast, block_ast)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
}
|
20
|
+
}
|
21
|
+
|
22
|
+
let(:ast) { Macros.parse(code) }
|
23
|
+
|
24
|
+
describe '#collect_defmacros' do
|
25
|
+
specify do
|
26
|
+
expect(subject.collect_defmacros(ast)).to eql(
|
27
|
+
foo: s(:block,
|
28
|
+
s(:send, nil, :lambda),
|
29
|
+
s(:args,
|
30
|
+
s(:arg, :param1_ast),
|
31
|
+
s(:arg, :param2_ast),
|
32
|
+
s(:arg, :block_ast)),
|
33
|
+
s(:begin,
|
34
|
+
s(:send, nil, :inside_macro_1,
|
35
|
+
s(:lvar, :param1_ast),
|
36
|
+
s(:lvar, :param2_ast),
|
37
|
+
s(:lvar, :block_ast)),
|
38
|
+
s(:send, nil, :inside_macro_2,
|
39
|
+
s(:lvar, :param1_ast),
|
40
|
+
s(:lvar, :param2_ast),
|
41
|
+
s(:lvar, :block_ast)))),
|
42
|
+
bar: s(:block,
|
43
|
+
s(:send, nil, :lambda),
|
44
|
+
s(:args,
|
45
|
+
s(:arg, :a),
|
46
|
+
s(:arg, :b)),
|
47
|
+
s(:begin,
|
48
|
+
s(:send, nil, :inside_macro_1,
|
49
|
+
s(:send, nil, :param1_ast),
|
50
|
+
s(:send, nil, :param2_ast),
|
51
|
+
s(:send, nil, :block_ast)),
|
52
|
+
s(:send, nil, :inside_macro_2,
|
53
|
+
s(:send, nil, :param1_ast),
|
54
|
+
s(:send, nil, :param2_ast),
|
55
|
+
s(:send, nil, :block_ast)))))
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe '#macros_node?' do
|
60
|
+
it 'will match "Macros" blocks' do
|
61
|
+
expect(subject.macros_node? Macros.parse('Macros { foo }')).to be true
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe ''
|
66
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require_relative '../../lib/macros'
|
2
|
+
|
3
|
+
RSpec.describe Macros::Expander do
|
4
|
+
include Macros::Sexp
|
5
|
+
|
6
|
+
let(:macro) { Macros.parse("
|
7
|
+
-> ast {
|
8
|
+
S = Macros::Sexp
|
9
|
+
S.treemap(ast) do |node|
|
10
|
+
if S.smatch? node, s(:send, nil, :output)
|
11
|
+
s(:send, nil, :puts, *node.children.drop(2))
|
12
|
+
else
|
13
|
+
node
|
14
|
+
end
|
15
|
+
end
|
16
|
+
}")}
|
17
|
+
|
18
|
+
|
19
|
+
subject { described_class.new(foo: macro) }
|
20
|
+
|
21
|
+
describe '#macro_block?' do
|
22
|
+
specify do
|
23
|
+
expect(subject.macro_block?(Macros.parse("foo { bar }"))).to be true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '#macroexpand_1' do
|
28
|
+
specify do
|
29
|
+
expect(subject.macroexpand_1(Macros.parse("foo { output 'x' ; output 'y' }")))
|
30
|
+
.to eql s(:begin,
|
31
|
+
s(:send, nil, :puts,
|
32
|
+
s(:str, "x")),
|
33
|
+
s(:send, nil, :puts,
|
34
|
+
s(:str, "y")))
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require_relative '../../lib/macros'
|
2
|
+
|
3
|
+
RSpec.describe Macros::Sexp do
|
4
|
+
include described_class
|
5
|
+
|
6
|
+
let(:block_node) {
|
7
|
+
s(:block,
|
8
|
+
s(:send, nil, :foo,
|
9
|
+
s(:int, 1),
|
10
|
+
s(:int, 2)),
|
11
|
+
s(:args,
|
12
|
+
s(:arg, :x)),
|
13
|
+
s(:send, nil, :some_more_code))
|
14
|
+
}
|
15
|
+
|
16
|
+
describe '#smatch?' do
|
17
|
+
specify do
|
18
|
+
expect(smatch?(s(:send, nil, :foo), s(:send))).to be true
|
19
|
+
end
|
20
|
+
|
21
|
+
specify do
|
22
|
+
expect(
|
23
|
+
smatch?(
|
24
|
+
s(:send, nil, :foo),
|
25
|
+
s(:send, nil, :bar))).to be false
|
26
|
+
end
|
27
|
+
|
28
|
+
specify do
|
29
|
+
expect(
|
30
|
+
smatch?(
|
31
|
+
block_node,
|
32
|
+
s(:block, s(:send, nil, :foo))
|
33
|
+
)
|
34
|
+
).to be true
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe '#treefilter' do
|
39
|
+
specify do
|
40
|
+
expect(treefilter(block_node) { |n| smatch?(n, s(:send))})
|
41
|
+
.to eql [
|
42
|
+
s(:send, nil, :foo, s(:int, 1), s(:int, 2)),
|
43
|
+
s(:send, nil, :some_more_code)]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
metadata
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: macros
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.1'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Arne Brasseur
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-10-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: parser
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
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: unparser
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
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: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
description: Macros for Ruby
|
56
|
+
email:
|
57
|
+
- arne@arnebrasseur.net
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files:
|
61
|
+
- README.md
|
62
|
+
files:
|
63
|
+
- Gemfile
|
64
|
+
- Gemfile.lock
|
65
|
+
- README.md
|
66
|
+
- example/main.rb
|
67
|
+
- example/my_macros.rb
|
68
|
+
- example/the_code.rb
|
69
|
+
- lib/macros.rb
|
70
|
+
- lib/macros/compiler.rb
|
71
|
+
- lib/macros/expander.rb
|
72
|
+
- lib/macros/loader.rb
|
73
|
+
- lib/macros/sexp.rb
|
74
|
+
- macros.gemspec
|
75
|
+
- scratch.rb
|
76
|
+
- spec/macros/compiler_spec.rb
|
77
|
+
- spec/macros/expander_spec.rb
|
78
|
+
- spec/macros/sexp_spec.rb
|
79
|
+
homepage: https://github.com/plexus/macros
|
80
|
+
licenses:
|
81
|
+
- MPL
|
82
|
+
metadata: {}
|
83
|
+
post_install_message:
|
84
|
+
rdoc_options: []
|
85
|
+
require_paths:
|
86
|
+
- lib
|
87
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
requirements: []
|
98
|
+
rubyforge_project:
|
99
|
+
rubygems_version: 2.2.3
|
100
|
+
signing_key:
|
101
|
+
specification_version: 4
|
102
|
+
summary: Macros for Ruby
|
103
|
+
test_files:
|
104
|
+
- spec/macros/compiler_spec.rb
|
105
|
+
- spec/macros/expander_spec.rb
|
106
|
+
- spec/macros/sexp_spec.rb
|
107
|
+
has_rdoc:
|