machete 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/.yardopts +1 -0
- data/LICENSE +22 -0
- data/README.md +88 -0
- data/Rakefile +19 -0
- data/VERSION +1 -0
- data/lib/machete.rb +55 -0
- data/lib/machete/matchers.rb +58 -0
- data/lib/machete/parser.y +184 -0
- data/lib/machete/version.rb +3 -0
- data/machete.gemspec +28 -0
- data/spec/machete/matchers_spec.rb +186 -0
- data/spec/machete/parser_spec.rb +262 -0
- data/spec/machete_spec.rb +39 -0
- data/spec/spec_helper.rb +5 -0
- metadata +151 -0
data/.gitignore
ADDED
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--no-private
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2011 SUSE
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
Machete
|
2
|
+
=======
|
3
|
+
|
4
|
+
Machete is a simple tool for matching Rubinius AST nodes against patterns. You can use it if you are writing any kind of tool that processes Ruby code and needs to do some work on specific types of nodes, needs to find patterns in the code, etc.
|
5
|
+
|
6
|
+
Installation
|
7
|
+
------------
|
8
|
+
|
9
|
+
You need to install [Rubinius](http://rubini.us/) first. You can then install Machete:
|
10
|
+
|
11
|
+
$ gem install machete
|
12
|
+
|
13
|
+
Usage
|
14
|
+
-----
|
15
|
+
|
16
|
+
First, require the library:
|
17
|
+
|
18
|
+
require "machete"
|
19
|
+
|
20
|
+
You can now use one of two methods Machete offers: `Machete.matches?` and `Machete.find`.
|
21
|
+
|
22
|
+
The `Machete.matches?` method matches a Rubinus AST node against a pattern:
|
23
|
+
|
24
|
+
Machete.matches?('foo.bar'.to_ast, 'Send<receiver = Send<receiver = Self, name = :foo>, name = :bar>')
|
25
|
+
# => true
|
26
|
+
|
27
|
+
Machete.matches?('42'.to_ast, 'Send<receiver = Send<receiver = Self, name = :foo>, name = :bar>')
|
28
|
+
# => false
|
29
|
+
|
30
|
+
(See below for pattern syntax description.)
|
31
|
+
|
32
|
+
The `Machete.find` method finds all nodes in a Rubinius AST tree matching a pattern:
|
33
|
+
|
34
|
+
Machete.find('42 + 43 + 44'.to_ast, 'FixnumLiteral')
|
35
|
+
# => [
|
36
|
+
# #<Rubinius::AST::FixnumLiteral:0x10b0 @value=44 @line=1>,
|
37
|
+
# #<Rubinius::AST::FixnumLiteral:0x10b8 @value=43 @line=1>,
|
38
|
+
# #<Rubinius::AST::FixnumLiteral:0x10c0 @value=42 @line=1>
|
39
|
+
# ]
|
40
|
+
|
41
|
+
Pattern Syntax
|
42
|
+
--------------
|
43
|
+
|
44
|
+
Rubinius AST consists of instances of classes that represent various types of nodes:
|
45
|
+
|
46
|
+
'42'.to_ast # => #<Rubinius::AST::FixnumLiteral:0xf28 @value=42 @line=1>
|
47
|
+
'"abcd"'.to_ast # => #<Rubinius::AST::StringLiteral:0xf60 @line=1 @string="abcd">
|
48
|
+
|
49
|
+
To match a specific node type, just use its class name in the pattern:
|
50
|
+
|
51
|
+
Machete.matches?('42'.to_ast, 'FixnumLiteral') # => true
|
52
|
+
Machete.matches?('"abcd"'.to_ast, 'FixnumLiteral') # => false
|
53
|
+
|
54
|
+
If you want to match specific attribute of the node, specify its value inside `<...>` right after the node name:
|
55
|
+
|
56
|
+
Machete.matches?('42'.to_ast, 'FixnumLiteral<value = 42>') # => true
|
57
|
+
Machete.matches?('45'.to_ast, 'FixnumLiteral<value = 42>') # => false
|
58
|
+
|
59
|
+
The attribute value can be an integer, string, symbol or other pattern. This means you can easily match nested nodes recursively. You can also specify multiple attributes:
|
60
|
+
|
61
|
+
Machete.matches?('foo.bar'.to_ast, 'Send<receiver = Send<receiver = Self, name = :foo>, name = :bar>')
|
62
|
+
# => true
|
63
|
+
|
64
|
+
Machete.matches?('42'.to_ast, 'Send<receiver = Send<receiver = Self, name = :foo>, name = :bar>')
|
65
|
+
# => false
|
66
|
+
|
67
|
+
To specify multiple alternatives, use the choice operator:
|
68
|
+
|
69
|
+
Machete.matches?('42'.to_ast, 'FixnumLiteral | StringLiteral') # => true
|
70
|
+
Machete.matches?('"abcd"'.to_ast, 'FixnumLiteral | StringLiteral') # => true
|
71
|
+
|
72
|
+
FAQ
|
73
|
+
---
|
74
|
+
|
75
|
+
**Why did you chose Rubinius AST as a base? Aren't there other tools for Ruby parsing which are not VM-specific?**
|
76
|
+
|
77
|
+
There are three other tools which were considered but each has its issues:
|
78
|
+
|
79
|
+
* [parse_tree](http://parsetree.rubyforge.org/) — unmaintained and unsupported for 1.9
|
80
|
+
* [ruby_parser](http://parsetree.rubyforge.org/) — sometimes reports wrong line numbers for the nodes (this is a killer for some use cases)
|
81
|
+
* [Ripper](http://rubyforge.org/projects/ripper/) — usable but the generated AST is too low level (the patterns would be too complex and low-level)
|
82
|
+
|
83
|
+
Rubinius AST is also by far the easiest to work with.
|
84
|
+
|
85
|
+
Acknowledgement
|
86
|
+
---------------
|
87
|
+
|
88
|
+
The general idea and inspiration for the pattern syntax was taken form Python's [2to3](http://docs.python.org/library/2to3.html) tool.
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require "rspec/core/rake_task"
|
2
|
+
require "yard"
|
3
|
+
|
4
|
+
desc "Generate the expression parser"
|
5
|
+
task :parser do
|
6
|
+
source = "lib/machete/parser.y"
|
7
|
+
target = "lib/machete/parser.rb"
|
8
|
+
unless uptodate?(target, source)
|
9
|
+
system "racc -o #{target} #{source}" or exit 1
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
RSpec::Core::RakeTask.new
|
14
|
+
task :spec => :parser
|
15
|
+
|
16
|
+
YARD::Rake::YardocTask.new
|
17
|
+
task :yard => :parser
|
18
|
+
|
19
|
+
task :default => :spec
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/lib/machete.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/machete/matchers")
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + "/machete/parser")
|
3
|
+
require File.expand_path(File.dirname(__FILE__) + "/machete/version")
|
4
|
+
|
5
|
+
module Machete
|
6
|
+
# Matches a Rubinius AST node against a pattern.
|
7
|
+
#
|
8
|
+
# @param [Rubinius::AST::Node] node node to match
|
9
|
+
# @param [String] pattern pattern to match the node against (see {file:README.md} for syntax description)
|
10
|
+
#
|
11
|
+
# @example Succesfull match
|
12
|
+
# Machete.matches?('foo.bar'.to_ast, 'Send<receiver = Send<receiver = Self, name = :foo>, name = :bar>')
|
13
|
+
# # => true
|
14
|
+
#
|
15
|
+
# @example Failed match
|
16
|
+
# Machete.matches?('42'.to_ast, 'Send<receiver = Send<receiver = Self, name = :foo>, name = :bar>')
|
17
|
+
# # => false
|
18
|
+
#
|
19
|
+
# @return [Boolean] +true+ if the node matches the pattern, +false+ otherwise
|
20
|
+
#
|
21
|
+
# @raise [Matchete::Parser::SyntaxError] if the pattern is invalid
|
22
|
+
def self.matches?(node, pattern)
|
23
|
+
Parser.new.parse(pattern).matches?(node)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Finds all nodes in a Rubinius AST matching a pattern.
|
27
|
+
#
|
28
|
+
# @param [Rubinius::AST::Node] ast tree to search
|
29
|
+
# @param [String] pattern pattern to match the nodes against (see {file:README.md} for syntax description)
|
30
|
+
#
|
31
|
+
# @example
|
32
|
+
# Machete.find('42 + 43 + 44'.to_ast, 'FixnumLiteral')
|
33
|
+
# # => [
|
34
|
+
# # #<Rubinius::AST::FixnumLiteral:0x10b0 @value=44 @line=1>,
|
35
|
+
# # #<Rubinius::AST::FixnumLiteral:0x10b8 @value=43 @line=1>,
|
36
|
+
# # #<Rubinius::AST::FixnumLiteral:0x10c0 @value=42 @line=1>
|
37
|
+
# # ]
|
38
|
+
#
|
39
|
+
# @return [Array] list of matching nodes (in unspecified order)
|
40
|
+
#
|
41
|
+
# @raise [Matchete::Parser::SyntaxError] if the pattern is invalid
|
42
|
+
def self.find(ast, pattern)
|
43
|
+
matcher = Parser.new.parse(pattern)
|
44
|
+
|
45
|
+
result = []
|
46
|
+
result << ast if matcher.matches?(ast)
|
47
|
+
|
48
|
+
ast.walk(true) do |dummy, node|
|
49
|
+
result << node if matcher.matches?(node)
|
50
|
+
true
|
51
|
+
end
|
52
|
+
|
53
|
+
result
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Machete
|
2
|
+
# @private
|
3
|
+
module Matchers
|
4
|
+
# @private
|
5
|
+
class ChoiceMatcher
|
6
|
+
attr_reader :alternatives
|
7
|
+
|
8
|
+
def initialize(alternatives)
|
9
|
+
@alternatives = alternatives
|
10
|
+
end
|
11
|
+
|
12
|
+
def ==(other)
|
13
|
+
other.instance_of?(self.class) && @alternatives == other.alternatives
|
14
|
+
end
|
15
|
+
|
16
|
+
def matches?(node)
|
17
|
+
alternatives.any? { |a| a.matches?(node) }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# @private
|
22
|
+
class NodeMatcher
|
23
|
+
attr_reader :class_name, :attrs
|
24
|
+
|
25
|
+
def initialize(class_name, attrs = {})
|
26
|
+
@class_name, @attrs = class_name, attrs
|
27
|
+
end
|
28
|
+
|
29
|
+
def ==(other)
|
30
|
+
other.instance_of?(self.class) &&
|
31
|
+
@class_name == other.class_name &&
|
32
|
+
@attrs == other.attrs
|
33
|
+
end
|
34
|
+
|
35
|
+
def matches?(node)
|
36
|
+
node.class == Rubinius::AST.const_get(@class_name) &&
|
37
|
+
attrs.all? { |name, matcher| matcher.matches?(node.send(name)) }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# @private
|
42
|
+
class LiteralMatcher
|
43
|
+
attr_reader :literal
|
44
|
+
|
45
|
+
def initialize(literal)
|
46
|
+
@literal = literal
|
47
|
+
end
|
48
|
+
|
49
|
+
def ==(other)
|
50
|
+
other.instance_of?(self.class) && @literal == other.literal
|
51
|
+
end
|
52
|
+
|
53
|
+
def matches?(node)
|
54
|
+
@literal == node
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
class Machete::Parser
|
2
|
+
|
3
|
+
token VAR_NAME
|
4
|
+
token CLASS_NAME
|
5
|
+
token SYMBOL
|
6
|
+
token INTEGER
|
7
|
+
token STRING
|
8
|
+
|
9
|
+
start expression
|
10
|
+
|
11
|
+
rule
|
12
|
+
|
13
|
+
expression : primary
|
14
|
+
| expression "|" primary {
|
15
|
+
result = if val[0].is_a?(ChoiceMatcher)
|
16
|
+
ChoiceMatcher.new(val[0].alternatives << val[2])
|
17
|
+
else
|
18
|
+
ChoiceMatcher.new([val[0], val[2]])
|
19
|
+
end
|
20
|
+
}
|
21
|
+
|
22
|
+
primary : node
|
23
|
+
| literal
|
24
|
+
|
25
|
+
node : CLASS_NAME {
|
26
|
+
result = NodeMatcher.new(val[0].to_sym)
|
27
|
+
}
|
28
|
+
| CLASS_NAME "<" attrs ">" {
|
29
|
+
result = NodeMatcher.new(val[0].to_sym, val[2])
|
30
|
+
}
|
31
|
+
|
32
|
+
attrs : attr
|
33
|
+
| attrs "," attr { result = val[0].merge(val[2]) }
|
34
|
+
|
35
|
+
attr : VAR_NAME "=" expression { result = { val[0].to_sym => val[2] } }
|
36
|
+
|
37
|
+
literal : SYMBOL { result = LiteralMatcher.new(val[0][1..-1].to_sym) }
|
38
|
+
| INTEGER {
|
39
|
+
value = if val[0] =~ /^0[bB]/
|
40
|
+
val[0][2..-1].to_i(2)
|
41
|
+
elsif val[0] =~ /^0[oO]/
|
42
|
+
val[0][2..-1].to_i(8)
|
43
|
+
elsif val[0] =~ /^0[dD]/
|
44
|
+
val[0][2..-1].to_i(10)
|
45
|
+
elsif val[0] =~ /^0[xX]/
|
46
|
+
val[0][2..-1].to_i(16)
|
47
|
+
elsif val[0] =~ /^0/
|
48
|
+
val[0][1..-1].to_i(8)
|
49
|
+
else
|
50
|
+
val[0].to_i
|
51
|
+
end
|
52
|
+
result = LiteralMatcher.new(value)
|
53
|
+
}
|
54
|
+
| STRING {
|
55
|
+
quote = val[0][0..0]
|
56
|
+
value = if quote == "'"
|
57
|
+
val[0][1..-2].gsub("\\\\", "\\").gsub("\\'", "'")
|
58
|
+
elsif quote == '"'
|
59
|
+
val[0][1..-2].
|
60
|
+
gsub("\\\\", "\\").
|
61
|
+
gsub('\\"', '"').
|
62
|
+
gsub("\\n", "\n").
|
63
|
+
gsub("\\t", "\t").
|
64
|
+
gsub("\\r", "\r").
|
65
|
+
gsub("\\f", "\f").
|
66
|
+
gsub("\\v", "\v").
|
67
|
+
gsub("\\a", "\a").
|
68
|
+
gsub("\\e", "\e").
|
69
|
+
gsub("\\b", "\b").
|
70
|
+
gsub("\\s", "\s").
|
71
|
+
gsub(/\\([0-7]{1,3})/) { $1.to_i(8).chr }.
|
72
|
+
gsub(/\\x([0-9a-fA-F]{1,2})/) { $1.to_i(16).chr }
|
73
|
+
else
|
74
|
+
raise "Unknown quote: #{quote.inspect}."
|
75
|
+
end
|
76
|
+
result = LiteralMatcher.new(value)
|
77
|
+
}
|
78
|
+
|
79
|
+
---- inner
|
80
|
+
|
81
|
+
include Matchers
|
82
|
+
|
83
|
+
class SyntaxError < StandardError; end
|
84
|
+
|
85
|
+
def parse(input)
|
86
|
+
@input = input
|
87
|
+
@pos = 0
|
88
|
+
|
89
|
+
do_parse
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
SIMPLE_TOKENS = ["|", "<", ">", ",", "="]
|
95
|
+
|
96
|
+
COMPLEX_TOKENS = [
|
97
|
+
[:VAR_NAME, /^[a-z_][a-zA-Z0-9_]*/],
|
98
|
+
[:CLASS_NAME, /^[A-Z][a-zA-Z0-9_]*/],
|
99
|
+
[:SYMBOL, /^:[a-zA-Z_][a-zA-Z0-9_]*/],
|
100
|
+
[
|
101
|
+
:INTEGER,
|
102
|
+
/^
|
103
|
+
[+-]? # sign
|
104
|
+
(
|
105
|
+
0[bB][01]+(_[01]+)* # binary (prefixed)
|
106
|
+
|
|
107
|
+
0[oO][0-7]+(_[0-7]+)* # octal (prefixed)
|
108
|
+
|
|
109
|
+
0[dD]\d+(_\d+)* # decimal (prefixed)
|
110
|
+
|
|
111
|
+
0[xX][0-9a-fA-F]+(_[0-9a-fA-F]+)* # hexadecimal (prefixed)
|
112
|
+
|
|
113
|
+
0[0-7]*(_[0-7]+)* # octal (unprefixed)
|
114
|
+
|
|
115
|
+
[1-9]\d*(_\d+)* # decimal (unprefixed)
|
116
|
+
)
|
117
|
+
/x
|
118
|
+
],
|
119
|
+
[
|
120
|
+
:STRING,
|
121
|
+
/^
|
122
|
+
(
|
123
|
+
' # sinqle-quoted string
|
124
|
+
(
|
125
|
+
\\[\\'] # escape
|
126
|
+
|
|
127
|
+
[^'] # regular character
|
128
|
+
)*
|
129
|
+
'
|
130
|
+
|
|
131
|
+
" # double-quoted string
|
132
|
+
(
|
133
|
+
\\ # escape
|
134
|
+
(
|
135
|
+
[\\"ntrfvaebs] # one-character escape
|
136
|
+
|
|
137
|
+
[0-7]{1,3} # octal number escape
|
138
|
+
|
|
139
|
+
x[0-9a-fA-F]{1,2} # hexadecimal number escape
|
140
|
+
)
|
141
|
+
|
|
142
|
+
[^"] # regular character
|
143
|
+
)*
|
144
|
+
"
|
145
|
+
)
|
146
|
+
/x
|
147
|
+
]
|
148
|
+
]
|
149
|
+
|
150
|
+
def next_token
|
151
|
+
skip_whitespace
|
152
|
+
|
153
|
+
return false if remaining_input.empty?
|
154
|
+
|
155
|
+
SIMPLE_TOKENS.each do |token|
|
156
|
+
if remaining_input[0...token.length] == token
|
157
|
+
@pos += token.length
|
158
|
+
return [token, token]
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
COMPLEX_TOKENS.each do |type, regexp|
|
163
|
+
if remaining_input =~ regexp
|
164
|
+
@pos += $&.length
|
165
|
+
return [type, $&]
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
raise SyntaxError, "Unexpected character: #{remaining_input[0..0].inspect}."
|
170
|
+
end
|
171
|
+
|
172
|
+
def skip_whitespace
|
173
|
+
if remaining_input =~ /^[ \t\r\n]+/
|
174
|
+
@pos += $&.length
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def remaining_input
|
179
|
+
@input[@pos..-1]
|
180
|
+
end
|
181
|
+
|
182
|
+
def on_error(error_token_id, error_value, value_stack)
|
183
|
+
raise SyntaxError, "Unexpected token: #{error_value.inspect}."
|
184
|
+
end
|
data/machete.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require File.expand_path(File.dirname(__FILE__) + "/lib/machete/version")
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "machete"
|
7
|
+
s.version = Machete::VERSION
|
8
|
+
s.summary = "Simple tool for matching Rubinius AST nodes against patterns"
|
9
|
+
s.description = <<-EOT.split("\n").map(&:strip).join(" ")
|
10
|
+
Machete is a simple tool for matching Rubinius AST nodes against patterns.
|
11
|
+
You can use it if you are writing any kind of tool that processes Ruby code
|
12
|
+
and needs to do some work on specific types of nodes, needs to find patterns
|
13
|
+
in the code, etc.
|
14
|
+
EOT
|
15
|
+
|
16
|
+
s.author = "David Majda"
|
17
|
+
s.email = "dmajda@suse.de"
|
18
|
+
s.homepage = "https://github.com/openSUSE/machete"
|
19
|
+
s.license = "MIT"
|
20
|
+
|
21
|
+
s.files = `git ls-files`.split("\n") + ["lib/machete/parser.rb"]
|
22
|
+
|
23
|
+
s.add_development_dependency "racc"
|
24
|
+
s.add_development_dependency "rspec"
|
25
|
+
s.add_development_dependency "rdoc"
|
26
|
+
s.add_development_dependency "rdiscount"
|
27
|
+
s.add_development_dependency "yard"
|
28
|
+
end
|
@@ -0,0 +1,186 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module Machete::Matchers
|
4
|
+
describe ChoiceMatcher do
|
5
|
+
before :each do
|
6
|
+
@alternatives = [
|
7
|
+
LiteralMatcher.new(42),
|
8
|
+
LiteralMatcher.new(43),
|
9
|
+
LiteralMatcher.new(44)
|
10
|
+
]
|
11
|
+
@matcher = ChoiceMatcher.new(@alternatives)
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "initialize" do
|
15
|
+
it "sets attributes correctly" do
|
16
|
+
@matcher.alternatives.should == @alternatives
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "==" do
|
21
|
+
it "returns true when passed the same object" do
|
22
|
+
@matcher.should == @matcher
|
23
|
+
end
|
24
|
+
|
25
|
+
it "returns true when passed a ChoiceMatcher initialized with the same parameters" do
|
26
|
+
@matcher.should == ChoiceMatcher.new(@alternatives)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "returns false when passed some random object" do
|
30
|
+
@matcher.should_not == Object.new
|
31
|
+
end
|
32
|
+
|
33
|
+
it "returns false when passed a subclass of ChoiceMatcher initialized with the same parameters" do
|
34
|
+
class SubclassedChoiceMatcher < ChoiceMatcher
|
35
|
+
end
|
36
|
+
|
37
|
+
@matcher.should_not == SubclassedChoiceMatcher.new(@alternatives)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "returns false when passed a ChoiceMatcher initialized with different parameters" do
|
41
|
+
@matcher.should_not == ChoiceMatcher.new([
|
42
|
+
LiteralMatcher.new(45),
|
43
|
+
LiteralMatcher.new(46),
|
44
|
+
LiteralMatcher.new(47)
|
45
|
+
])
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "matches?" do
|
50
|
+
it "matches any alternative" do
|
51
|
+
@matcher.matches?(42).should be_true
|
52
|
+
@matcher.matches?(43).should be_true
|
53
|
+
@matcher.matches?(44).should be_true
|
54
|
+
end
|
55
|
+
|
56
|
+
it "does not match a non-alternative" do
|
57
|
+
@matcher.matches?(45).should be_false
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe NodeMatcher do
|
63
|
+
before :each do
|
64
|
+
@attrs = {
|
65
|
+
:source => LiteralMatcher.new("abcd"),
|
66
|
+
:options => LiteralMatcher.new(128)
|
67
|
+
}
|
68
|
+
@matcher = NodeMatcher.new(:RegexLiteral, @attrs)
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "initializa" do
|
72
|
+
describe "when passed one parameter" do
|
73
|
+
it "sets attributes correctly" do
|
74
|
+
matcher = NodeMatcher.new(:RegexLiteral)
|
75
|
+
|
76
|
+
matcher.class_name.should == :RegexLiteral
|
77
|
+
matcher.attrs.should == {}
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe "when passed two parameters" do
|
82
|
+
it "sets attributes correctly" do
|
83
|
+
matcher = NodeMatcher.new(:RegexLiteral, @attrs)
|
84
|
+
|
85
|
+
matcher.class_name.should == :RegexLiteral
|
86
|
+
matcher.attrs.should == @attrs
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe "==" do
|
92
|
+
it "returns true when passed the same object" do
|
93
|
+
@matcher.should == @matcher
|
94
|
+
end
|
95
|
+
|
96
|
+
it "returns true when passed a NodeMatcher initialized with the same parameters" do
|
97
|
+
@matcher.should == NodeMatcher.new(:RegexLiteral, @attrs)
|
98
|
+
end
|
99
|
+
|
100
|
+
it "returns false when passed some random object" do
|
101
|
+
@matcher.should_not == Object.new
|
102
|
+
end
|
103
|
+
|
104
|
+
it "returns false when passed a subclass of NodeMatcher initialized with the same parameters" do
|
105
|
+
class SubclassedNodeMatcher < NodeMatcher
|
106
|
+
end
|
107
|
+
|
108
|
+
@matcher.should_not ==
|
109
|
+
SubclassedNodeMatcher.new(:RegexLiteral, @attrs)
|
110
|
+
end
|
111
|
+
|
112
|
+
it "returns false when passed a NodeMatcher initialized with different parameters" do
|
113
|
+
@matcher.should_not == NodeMatcher.new(:StringLiteral, {
|
114
|
+
:source => LiteralMatcher.new("abcd"),
|
115
|
+
:options => LiteralMatcher.new(128)
|
116
|
+
})
|
117
|
+
@matcher.should_not == NodeMatcher.new(:RegexLiteral, {
|
118
|
+
:source => LiteralMatcher.new("efgh"),
|
119
|
+
:options => LiteralMatcher.new(256)
|
120
|
+
})
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe "matches?" do
|
125
|
+
it "matches a node with correct class and matching attributes" do
|
126
|
+
@matcher.matches?(Rubinius::AST::RegexLiteral.new(0, "abcd", 128)).should be_true
|
127
|
+
end
|
128
|
+
|
129
|
+
it "does not match a node with incorrect class" do
|
130
|
+
@matcher.matches?(Rubinius::AST::StringLiteral.new(0, "abcd")).should be_false
|
131
|
+
end
|
132
|
+
|
133
|
+
it "does not match a node with non-matching attributes" do
|
134
|
+
@matcher.matches?(Rubinius::AST::RegexLiteral.new(0, "efgh", 128)).should be_false
|
135
|
+
@matcher.matches?(Rubinius::AST::RegexLiteral.new(0, "efgh", 256)).should be_false
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
describe LiteralMatcher do
|
141
|
+
before :each do
|
142
|
+
@matcher = LiteralMatcher.new(42)
|
143
|
+
end
|
144
|
+
|
145
|
+
describe "initialize" do
|
146
|
+
it "sets attributes correctly" do
|
147
|
+
@matcher.literal.should == 42
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
describe "==" do
|
152
|
+
it "returns true when passed the same object" do
|
153
|
+
@matcher.should == @matcher
|
154
|
+
end
|
155
|
+
|
156
|
+
it "returns true when passed a LiteralMatcher initialized with the same parameters" do
|
157
|
+
@matcher.should == LiteralMatcher.new(42)
|
158
|
+
end
|
159
|
+
|
160
|
+
it "returns false when passed some random object" do
|
161
|
+
@matcher.should_not == Object.new
|
162
|
+
end
|
163
|
+
|
164
|
+
it "returns false when passed a subclass of LiteralMatcher initialized with the same parameters" do
|
165
|
+
class SubclassedLiteralMatcher < LiteralMatcher
|
166
|
+
end
|
167
|
+
|
168
|
+
@matcher.should_not == SubclassedLiteralMatcher.new(42)
|
169
|
+
end
|
170
|
+
|
171
|
+
it "returns false when passed a LiteralMatcher initialized with different parameters" do
|
172
|
+
@matcher.should_not == LiteralMatcher.new(43)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
describe "matches?" do
|
177
|
+
it "matches an object equivalent to the literal" do
|
178
|
+
@matcher.matches?(42).should be_true
|
179
|
+
end
|
180
|
+
|
181
|
+
it "does not match an object not equivalent to the literal" do
|
182
|
+
@matcher.matches?(43).should be_false
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
@@ -0,0 +1,262 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module Machete
|
4
|
+
include Matchers
|
5
|
+
|
6
|
+
describe Parser do
|
7
|
+
RSpec::Matchers.define :be_parsed_as do |ast|
|
8
|
+
match do |input|
|
9
|
+
Parser.new.parse(input).should == ast
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
RSpec::Matchers.define :not_be_parsed do |message|
|
14
|
+
match do |input|
|
15
|
+
lambda {
|
16
|
+
Parser.new.parse(input)
|
17
|
+
}.should raise_exception(Parser::SyntaxError, message)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
before :each do
|
22
|
+
@i42 = LiteralMatcher.new(42)
|
23
|
+
@i43 = LiteralMatcher.new(43)
|
24
|
+
@i44 = LiteralMatcher.new(44)
|
25
|
+
|
26
|
+
@foo = NodeMatcher.new(:Foo)
|
27
|
+
@foo_a = NodeMatcher.new(:Foo, :a => @i42)
|
28
|
+
@foo_ab = NodeMatcher.new(:Foo, :a => @i42, :b => @i43)
|
29
|
+
|
30
|
+
@ch4243 = ChoiceMatcher.new([@i42, @i43])
|
31
|
+
@ch424344 = ChoiceMatcher.new([@i42, @i43, @i44])
|
32
|
+
end
|
33
|
+
|
34
|
+
def node_matcher_with_attr(attr)
|
35
|
+
NodeMatcher.new(:Foo, { attr => @i42 })
|
36
|
+
end
|
37
|
+
|
38
|
+
# Canonical expression is "42 | 43".
|
39
|
+
it "parses expression" do
|
40
|
+
'42'.should be_parsed_as(@i42)
|
41
|
+
'42 | 43'.should be_parsed_as(@ch4243)
|
42
|
+
'42 | 43 | 44'.should be_parsed_as(@ch424344)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Canonical primary is "42".
|
46
|
+
it "parses primary" do
|
47
|
+
'Foo<a = 42>'.should be_parsed_as(@foo_a)
|
48
|
+
'42'.should be_parsed_as(@i42)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Canonical node is "Foo".
|
52
|
+
it "parses node" do
|
53
|
+
'Foo'.should be_parsed_as(@foo)
|
54
|
+
'Foo<a = 42, b = 43>'.should be_parsed_as(@foo_ab)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Canonical attrs is "a = 42, b = 43".
|
58
|
+
it "parses attrs" do
|
59
|
+
'Foo<a = 42>'.should be_parsed_as(@foo_a)
|
60
|
+
'Foo<a = 42, b = 43>'.should be_parsed_as(@foo_ab)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Canonical attr is "a = 42".
|
64
|
+
it "parses attr" do
|
65
|
+
'Foo<a = 42 | 43>'.should be_parsed_as(NodeMatcher.new(:Foo, :a => @ch4243))
|
66
|
+
end
|
67
|
+
|
68
|
+
# Canonical literal is "42".
|
69
|
+
it "parses literal" do
|
70
|
+
':a'.should be_parsed_as(LiteralMatcher.new(:a))
|
71
|
+
'42'.should be_parsed_as(@i42)
|
72
|
+
'"abcd"'.should be_parsed_as(LiteralMatcher.new("abcd"))
|
73
|
+
end
|
74
|
+
|
75
|
+
# Canonical VAR_NAME is "a".
|
76
|
+
it "parses VAR_NAME" do
|
77
|
+
'Foo<a = 42>'.should be_parsed_as(node_matcher_with_attr(:a))
|
78
|
+
'Foo<z = 42>'.should be_parsed_as(node_matcher_with_attr(:z))
|
79
|
+
'Foo<_ = 42>'.should be_parsed_as(node_matcher_with_attr(:_))
|
80
|
+
'Foo<aa = 42>'.should be_parsed_as(node_matcher_with_attr(:aa))
|
81
|
+
'Foo<az = 42>'.should be_parsed_as(node_matcher_with_attr(:az))
|
82
|
+
'Foo<aA = 42>'.should be_parsed_as(node_matcher_with_attr(:aA))
|
83
|
+
'Foo<aZ = 42>'.should be_parsed_as(node_matcher_with_attr(:aZ))
|
84
|
+
'Foo<a0 = 42>'.should be_parsed_as(node_matcher_with_attr(:a0))
|
85
|
+
'Foo<a9 = 42>'.should be_parsed_as(node_matcher_with_attr(:a9))
|
86
|
+
'Foo<a_ = 42>'.should be_parsed_as(node_matcher_with_attr(:a_))
|
87
|
+
'Foo<abcd = 42>'.should be_parsed_as(node_matcher_with_attr(:abcd))
|
88
|
+
end
|
89
|
+
|
90
|
+
# Canonical CLASS_NAME is "A".
|
91
|
+
it "parses CLASS_NAME" do
|
92
|
+
'A'.should be_parsed_as(NodeMatcher.new(:A))
|
93
|
+
'Z'.should be_parsed_as(NodeMatcher.new(:Z))
|
94
|
+
'Aa'.should be_parsed_as(NodeMatcher.new(:Aa))
|
95
|
+
'Az'.should be_parsed_as(NodeMatcher.new(:Az))
|
96
|
+
'AA'.should be_parsed_as(NodeMatcher.new(:AA))
|
97
|
+
'AZ'.should be_parsed_as(NodeMatcher.new(:AZ))
|
98
|
+
'A0'.should be_parsed_as(NodeMatcher.new(:A0))
|
99
|
+
'A9'.should be_parsed_as(NodeMatcher.new(:A9))
|
100
|
+
'A_'.should be_parsed_as(NodeMatcher.new(:A_))
|
101
|
+
'Abcd'.should be_parsed_as(NodeMatcher.new(:Abcd))
|
102
|
+
end
|
103
|
+
|
104
|
+
# Canonical SYMBOL is ":a".
|
105
|
+
it "parses SYMBOL" do
|
106
|
+
':a'.should be_parsed_as(LiteralMatcher.new(:a))
|
107
|
+
':z'.should be_parsed_as(LiteralMatcher.new(:z))
|
108
|
+
':A'.should be_parsed_as(LiteralMatcher.new(:A))
|
109
|
+
':Z'.should be_parsed_as(LiteralMatcher.new(:Z))
|
110
|
+
':_'.should be_parsed_as(LiteralMatcher.new(:_))
|
111
|
+
':aa'.should be_parsed_as(LiteralMatcher.new(:aa))
|
112
|
+
':az'.should be_parsed_as(LiteralMatcher.new(:az))
|
113
|
+
':aA'.should be_parsed_as(LiteralMatcher.new(:aA))
|
114
|
+
':aZ'.should be_parsed_as(LiteralMatcher.new(:aZ))
|
115
|
+
':a0'.should be_parsed_as(LiteralMatcher.new(:a0))
|
116
|
+
':a9'.should be_parsed_as(LiteralMatcher.new(:a9))
|
117
|
+
':a_'.should be_parsed_as(LiteralMatcher.new(:a_))
|
118
|
+
':abcd'.should be_parsed_as(LiteralMatcher.new(:abcd))
|
119
|
+
end
|
120
|
+
|
121
|
+
# Canonical INTEGER is "42".
|
122
|
+
it "parses INTEGER" do
|
123
|
+
# Sign
|
124
|
+
'+1'.should be_parsed_as(LiteralMatcher.new(1))
|
125
|
+
'-1'.should be_parsed_as(LiteralMatcher.new(-1))
|
126
|
+
'1'.should be_parsed_as(LiteralMatcher.new(1))
|
127
|
+
|
128
|
+
# Binary (prefixed)
|
129
|
+
'0b1'.should be_parsed_as(LiteralMatcher.new(0b1))
|
130
|
+
'0B1'.should be_parsed_as(LiteralMatcher.new(0b1))
|
131
|
+
'0b0'.should be_parsed_as(LiteralMatcher.new(0b0))
|
132
|
+
'0b1'.should be_parsed_as(LiteralMatcher.new(0b1))
|
133
|
+
'0b101'.should be_parsed_as(LiteralMatcher.new(0b101))
|
134
|
+
'0b1_0'.should be_parsed_as(LiteralMatcher.new(0b10))
|
135
|
+
'0b1_1'.should be_parsed_as(LiteralMatcher.new(0b11))
|
136
|
+
'0b1_101'.should be_parsed_as(LiteralMatcher.new(0b1101))
|
137
|
+
'0b1_0_1_0'.should be_parsed_as(LiteralMatcher.new(0b1010))
|
138
|
+
|
139
|
+
# Octall (prefixed)
|
140
|
+
'0o1'.should be_parsed_as(LiteralMatcher.new(0o1))
|
141
|
+
'0O1'.should be_parsed_as(LiteralMatcher.new(0o1))
|
142
|
+
'0o0'.should be_parsed_as(LiteralMatcher.new(0o0))
|
143
|
+
'0o7'.should be_parsed_as(LiteralMatcher.new(0o7))
|
144
|
+
'0o123'.should be_parsed_as(LiteralMatcher.new(0o123))
|
145
|
+
'0o1_0'.should be_parsed_as(LiteralMatcher.new(0o10))
|
146
|
+
'0o1_7'.should be_parsed_as(LiteralMatcher.new(0o17))
|
147
|
+
'0o1_123'.should be_parsed_as(LiteralMatcher.new(0o1123))
|
148
|
+
'0o1_2_3_4'.should be_parsed_as(LiteralMatcher.new(0o1234))
|
149
|
+
|
150
|
+
# Decimal (prefixed)
|
151
|
+
'0d1'.should be_parsed_as(LiteralMatcher.new(0d1))
|
152
|
+
'0D1'.should be_parsed_as(LiteralMatcher.new(0d1))
|
153
|
+
'0d0'.should be_parsed_as(LiteralMatcher.new(0d0))
|
154
|
+
'0d9'.should be_parsed_as(LiteralMatcher.new(0d9))
|
155
|
+
'0d123'.should be_parsed_as(LiteralMatcher.new(0d123))
|
156
|
+
'0d1_0'.should be_parsed_as(LiteralMatcher.new(0d10))
|
157
|
+
'0d1_9'.should be_parsed_as(LiteralMatcher.new(0d19))
|
158
|
+
'0d1_123'.should be_parsed_as(LiteralMatcher.new(0d1123))
|
159
|
+
'0d1_2_3_4'.should be_parsed_as(LiteralMatcher.new(0d1234))
|
160
|
+
|
161
|
+
# Hexadecimal (prefixed)
|
162
|
+
'0x1'.should be_parsed_as(LiteralMatcher.new(0x1))
|
163
|
+
'0X1'.should be_parsed_as(LiteralMatcher.new(0x1))
|
164
|
+
'0x0'.should be_parsed_as(LiteralMatcher.new(0x0))
|
165
|
+
'0x9'.should be_parsed_as(LiteralMatcher.new(0x9))
|
166
|
+
'0xa'.should be_parsed_as(LiteralMatcher.new(0xA))
|
167
|
+
'0xf'.should be_parsed_as(LiteralMatcher.new(0xF))
|
168
|
+
'0xA'.should be_parsed_as(LiteralMatcher.new(0xA))
|
169
|
+
'0xF'.should be_parsed_as(LiteralMatcher.new(0xF))
|
170
|
+
'0x123'.should be_parsed_as(LiteralMatcher.new(0x123))
|
171
|
+
'0x1_0'.should be_parsed_as(LiteralMatcher.new(0x10))
|
172
|
+
'0x1_9'.should be_parsed_as(LiteralMatcher.new(0x19))
|
173
|
+
'0x1_a'.should be_parsed_as(LiteralMatcher.new(0x1A))
|
174
|
+
'0x1_f'.should be_parsed_as(LiteralMatcher.new(0x1F))
|
175
|
+
'0x1_A'.should be_parsed_as(LiteralMatcher.new(0x1A))
|
176
|
+
'0x1_F'.should be_parsed_as(LiteralMatcher.new(0x1F))
|
177
|
+
'0x1_123'.should be_parsed_as(LiteralMatcher.new(0x1123))
|
178
|
+
'0x1_2_3_4'.should be_parsed_as(LiteralMatcher.new(0x1234))
|
179
|
+
|
180
|
+
# Octal (unprefixed)
|
181
|
+
'0'.should be_parsed_as(LiteralMatcher.new(0))
|
182
|
+
'00'.should be_parsed_as(LiteralMatcher.new(0))
|
183
|
+
'07'.should be_parsed_as(LiteralMatcher.new(07))
|
184
|
+
'0123'.should be_parsed_as(LiteralMatcher.new(0123))
|
185
|
+
'0_0'.should be_parsed_as(LiteralMatcher.new(0))
|
186
|
+
'0_7'.should be_parsed_as(LiteralMatcher.new(07))
|
187
|
+
'0_123'.should be_parsed_as(LiteralMatcher.new(0123))
|
188
|
+
'0_1_2_3'.should be_parsed_as(LiteralMatcher.new(0123))
|
189
|
+
|
190
|
+
# Decimal (unprefixed)
|
191
|
+
'1'.should be_parsed_as(LiteralMatcher.new(1))
|
192
|
+
'9'.should be_parsed_as(LiteralMatcher.new(9))
|
193
|
+
'10'.should be_parsed_as(LiteralMatcher.new(10))
|
194
|
+
'19'.should be_parsed_as(LiteralMatcher.new(19))
|
195
|
+
'1234'.should be_parsed_as(LiteralMatcher.new(1234))
|
196
|
+
'1_0'.should be_parsed_as(LiteralMatcher.new(10))
|
197
|
+
'1_9'.should be_parsed_as(LiteralMatcher.new(19))
|
198
|
+
'1_123'.should be_parsed_as(LiteralMatcher.new(1123))
|
199
|
+
'1_2_3_4'.should be_parsed_as(LiteralMatcher.new(1234))
|
200
|
+
end
|
201
|
+
|
202
|
+
# Canonical STRING is "\"abcd\"".
|
203
|
+
it "parses STRING" do
|
204
|
+
"''".should be_parsed_as(LiteralMatcher.new(""))
|
205
|
+
"'a'".should be_parsed_as(LiteralMatcher.new("a"))
|
206
|
+
"'\\\\'".should be_parsed_as(LiteralMatcher.new("\\"))
|
207
|
+
"'\\''".should be_parsed_as(LiteralMatcher.new("'"))
|
208
|
+
"'abc'".should be_parsed_as(LiteralMatcher.new("abc"))
|
209
|
+
|
210
|
+
'""'.should be_parsed_as(LiteralMatcher.new(""))
|
211
|
+
'"a"'.should be_parsed_as(LiteralMatcher.new("a"))
|
212
|
+
'"\\\\"'.should be_parsed_as(LiteralMatcher.new("\\"))
|
213
|
+
'"\\""'.should be_parsed_as(LiteralMatcher.new('"'))
|
214
|
+
'"\\n"'.should be_parsed_as(LiteralMatcher.new("\n"))
|
215
|
+
'"\\t"'.should be_parsed_as(LiteralMatcher.new("\t"))
|
216
|
+
'"\\r"'.should be_parsed_as(LiteralMatcher.new("\r"))
|
217
|
+
'"\\f"'.should be_parsed_as(LiteralMatcher.new("\f"))
|
218
|
+
'"\\v"'.should be_parsed_as(LiteralMatcher.new("\v"))
|
219
|
+
'"\\a"'.should be_parsed_as(LiteralMatcher.new("\a"))
|
220
|
+
'"\\e"'.should be_parsed_as(LiteralMatcher.new("\e"))
|
221
|
+
'"\\b"'.should be_parsed_as(LiteralMatcher.new("\b"))
|
222
|
+
'"\\s"'.should be_parsed_as(LiteralMatcher.new("\s"))
|
223
|
+
'"\\0"'.should be_parsed_as(LiteralMatcher.new("\0"))
|
224
|
+
'"\\7"'.should be_parsed_as(LiteralMatcher.new("\7"))
|
225
|
+
'"\\123"'.should be_parsed_as(LiteralMatcher.new("\123"))
|
226
|
+
'"\\x0"'.should be_parsed_as(LiteralMatcher.new("\x0"))
|
227
|
+
'"\\x9"'.should be_parsed_as(LiteralMatcher.new("\x9"))
|
228
|
+
'"\\xa"'.should be_parsed_as(LiteralMatcher.new("\xa"))
|
229
|
+
'"\\xf"'.should be_parsed_as(LiteralMatcher.new("\xf"))
|
230
|
+
'"\\xA"'.should be_parsed_as(LiteralMatcher.new("\xA"))
|
231
|
+
'"\\xF"'.should be_parsed_as(LiteralMatcher.new("\xF"))
|
232
|
+
'"\\x12"'.should be_parsed_as(LiteralMatcher.new("\x12"))
|
233
|
+
'"abc"'.should be_parsed_as(LiteralMatcher.new("abc"))
|
234
|
+
end
|
235
|
+
|
236
|
+
it "skips whitespace before tokens" do
|
237
|
+
'42'.should be_parsed_as(@i42)
|
238
|
+
' 42'.should be_parsed_as(@i42)
|
239
|
+
"\t42".should be_parsed_as(@i42)
|
240
|
+
"\r42".should be_parsed_as(@i42)
|
241
|
+
"\n42".should be_parsed_as(@i42)
|
242
|
+
' 42'.should be_parsed_as(@i42)
|
243
|
+
end
|
244
|
+
|
245
|
+
it "skips whitespace after tokens" do
|
246
|
+
'42'.should be_parsed_as(@i42)
|
247
|
+
'42 '.should be_parsed_as(@i42)
|
248
|
+
"42\t".should be_parsed_as(@i42)
|
249
|
+
"42\r".should be_parsed_as(@i42)
|
250
|
+
"42\n".should be_parsed_as(@i42)
|
251
|
+
'42 '.should be_parsed_as(@i42)
|
252
|
+
end
|
253
|
+
|
254
|
+
it "handles lexical errors" do
|
255
|
+
'@#%'.should not_be_parsed("Unexpected character: \"@\".")
|
256
|
+
end
|
257
|
+
|
258
|
+
it "handles syntax errors" do
|
259
|
+
'42 43'.should not_be_parsed("Unexpected token: \"43\".")
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Machete do
|
4
|
+
describe "matches?" do
|
5
|
+
it "returns true when passed matching node and pattern" do
|
6
|
+
Machete.matches?('42'.to_ast, 'FixnumLiteral<value = 42>').should be_true
|
7
|
+
end
|
8
|
+
|
9
|
+
it "returns false when passed non-matching node and pattern" do
|
10
|
+
Machete.matches?('43'.to_ast, 'FixnumLiteral<value = 42>').should be_false
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "find" do
|
15
|
+
it "returns [] when no node matches the pattern" do
|
16
|
+
Machete.find('"abcd"'.to_ast, 'FixnumLiteral').should == []
|
17
|
+
end
|
18
|
+
|
19
|
+
it "returns root node if it matches the pattern" do
|
20
|
+
nodes = Machete.find('42'.to_ast, 'FixnumLiteral')
|
21
|
+
|
22
|
+
nodes.size.should == 1
|
23
|
+
nodes[0].should be_instance_of(Rubinius::AST::FixnumLiteral)
|
24
|
+
nodes[0].value.should == 42
|
25
|
+
end
|
26
|
+
|
27
|
+
it "returns child nodes if they match the pattern" do
|
28
|
+
nodes = Machete.find('42 + 43 + 44'.to_ast, 'FixnumLiteral').sort_by(&:value)
|
29
|
+
|
30
|
+
nodes.size.should == 3
|
31
|
+
nodes[0].should be_instance_of(Rubinius::AST::FixnumLiteral)
|
32
|
+
nodes[0].value.should == 42
|
33
|
+
nodes[1].should be_instance_of(Rubinius::AST::FixnumLiteral)
|
34
|
+
nodes[1].value.should == 43
|
35
|
+
nodes[2].should be_instance_of(Rubinius::AST::FixnumLiteral)
|
36
|
+
nodes[2].value.should == 44
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: machete
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- David Majda
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-07-27 00:00:00 +02:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: racc
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :development
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: rspec
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 3
|
44
|
+
segments:
|
45
|
+
- 0
|
46
|
+
version: "0"
|
47
|
+
type: :development
|
48
|
+
version_requirements: *id002
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: rdoc
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
hash: 3
|
58
|
+
segments:
|
59
|
+
- 0
|
60
|
+
version: "0"
|
61
|
+
type: :development
|
62
|
+
version_requirements: *id003
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
name: rdiscount
|
65
|
+
prerelease: false
|
66
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
hash: 3
|
72
|
+
segments:
|
73
|
+
- 0
|
74
|
+
version: "0"
|
75
|
+
type: :development
|
76
|
+
version_requirements: *id004
|
77
|
+
- !ruby/object:Gem::Dependency
|
78
|
+
name: yard
|
79
|
+
prerelease: false
|
80
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
hash: 3
|
86
|
+
segments:
|
87
|
+
- 0
|
88
|
+
version: "0"
|
89
|
+
type: :development
|
90
|
+
version_requirements: *id005
|
91
|
+
description: Machete is a simple tool for matching Rubinius AST nodes against patterns. You can use it if you are writing any kind of tool that processes Ruby code and needs to do some work on specific types of nodes, needs to find patterns in the code, etc.
|
92
|
+
email: dmajda@suse.de
|
93
|
+
executables: []
|
94
|
+
|
95
|
+
extensions: []
|
96
|
+
|
97
|
+
extra_rdoc_files: []
|
98
|
+
|
99
|
+
files:
|
100
|
+
- .gitignore
|
101
|
+
- .yardopts
|
102
|
+
- LICENSE
|
103
|
+
- README.md
|
104
|
+
- Rakefile
|
105
|
+
- VERSION
|
106
|
+
- lib/machete.rb
|
107
|
+
- lib/machete/matchers.rb
|
108
|
+
- lib/machete/parser.y
|
109
|
+
- lib/machete/version.rb
|
110
|
+
- machete.gemspec
|
111
|
+
- spec/machete/matchers_spec.rb
|
112
|
+
- spec/machete/parser_spec.rb
|
113
|
+
- spec/machete_spec.rb
|
114
|
+
- spec/spec_helper.rb
|
115
|
+
- lib/machete/parser.rb
|
116
|
+
has_rdoc: true
|
117
|
+
homepage: https://github.com/openSUSE/machete
|
118
|
+
licenses:
|
119
|
+
- MIT
|
120
|
+
post_install_message:
|
121
|
+
rdoc_options: []
|
122
|
+
|
123
|
+
require_paths:
|
124
|
+
- lib
|
125
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
126
|
+
none: false
|
127
|
+
requirements:
|
128
|
+
- - ">="
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
hash: 3
|
131
|
+
segments:
|
132
|
+
- 0
|
133
|
+
version: "0"
|
134
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
135
|
+
none: false
|
136
|
+
requirements:
|
137
|
+
- - ">="
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
hash: 3
|
140
|
+
segments:
|
141
|
+
- 0
|
142
|
+
version: "0"
|
143
|
+
requirements: []
|
144
|
+
|
145
|
+
rubyforge_project:
|
146
|
+
rubygems_version: 1.5.2
|
147
|
+
signing_key:
|
148
|
+
specification_version: 3
|
149
|
+
summary: Simple tool for matching Rubinius AST nodes against patterns
|
150
|
+
test_files: []
|
151
|
+
|