jruby-parser 0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/generate_parser +46 -0
- data/bin/optimize_parser.rb +106 -0
- data/bin/patch_parser.rb +105 -0
- data/lib/jruby-parser/core_ext/array.rb +10 -0
- data/lib/jruby-parser/core_ext/attr_assign_node.rb +6 -0
- data/lib/jruby-parser/core_ext/boolean.rb +11 -0
- data/lib/jruby-parser/core_ext/call_node.rb +6 -0
- data/lib/jruby-parser/core_ext/fcall_node.rb +6 -0
- data/lib/jruby-parser/core_ext/list_node.rb +10 -0
- data/lib/jruby-parser/core_ext/nil.rb +7 -0
- data/lib/jruby-parser/core_ext/node.rb +66 -0
- data/lib/jruby-parser/core_ext/numeric.rb +13 -0
- data/lib/jruby-parser/core_ext/op_element_asgn_node.rb +6 -0
- data/lib/jruby-parser/core_ext/string.rb +7 -0
- data/lib/jruby-parser/core_ext/super_node.rb +6 -0
- data/lib/jruby-parser/util/coercer.rb +44 -0
- data/lib/jruby-parser/version.rb +3 -0
- data/lib/jruby-parser.jar +0 -0
- data/lib/jruby-parser.rb +36 -0
- data/lib/yydebug.jar +0 -0
- data/sample/simple.rb +20 -0
- data/spec/ast/node_path.rb +20 -0
- data/spec/helpers/node_helpers.rb +120 -0
- data/spec/helpers/parser_helpers.rb +23 -0
- data/spec/jruby-parser/find_spec.rb +30 -0
- data/spec/jruby-parser/rewriting_spec.rb +57 -0
- data/spec/parser/alias_spec.rb +18 -0
- data/spec/parser/broken_spec.rb +25 -0
- data/spec/positions/arg_spec.rb +129 -0
- data/spec/positions/call_spec.rb +132 -0
- data/spec/positions/conditionals_spec.rb +15 -0
- data/spec/positions/hash_spec.rb +25 -0
- data/spec/positions/heredoc_spec.rb +109 -0
- data/spec/positions/str_spec.rb +13 -0
- metadata +105 -0
data/bin/generate_parser
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
|
3
|
+
# Run from your JRuby home directory....More smarts needed here.
|
4
|
+
|
5
|
+
###### Change these to tastes ######
|
6
|
+
JAY=jay
|
7
|
+
RUBY=ruby
|
8
|
+
PARSER_BASE=DefaultRubyParser
|
9
|
+
YYTABLE_PREFIX=
|
10
|
+
DEBUG=true
|
11
|
+
###### Do not change below ######
|
12
|
+
|
13
|
+
if [ "$1" != "" ]; then
|
14
|
+
PARSER_BASE=$1
|
15
|
+
fi
|
16
|
+
shift
|
17
|
+
|
18
|
+
if [ "$1" != "" ]; then
|
19
|
+
YYTABLE_PREFIX=$1
|
20
|
+
fi
|
21
|
+
|
22
|
+
if [ "$DEBUG" != "" ]; then
|
23
|
+
DEBUG_FLAG=-t
|
24
|
+
# Nonesense...my script-fu is weak
|
25
|
+
DEBUG_STRIP="xdyhbk"
|
26
|
+
else
|
27
|
+
DEBUG_FLAG=
|
28
|
+
DEBUG_STRIP="^//t"
|
29
|
+
fi
|
30
|
+
|
31
|
+
echo "Generating Parser '$PARSER_BASE' w/ YYTable prefix of '$YYTABLE_PREFIX'"
|
32
|
+
|
33
|
+
PARSER_DIR=src/org/jrubyparser/parser
|
34
|
+
|
35
|
+
pushd $PARSER_DIR
|
36
|
+
|
37
|
+
# Generate grammar as intermediate file
|
38
|
+
$JAY $DEBUG_FLAG $PARSER_BASE.y < skeleton.parser | grep -v $DEBUG_STRIP >$PARSER_BASE.out
|
39
|
+
|
40
|
+
# Patch file to get around Java static initialization issues plus extract
|
41
|
+
# a bunch of stuff to seperate file (yytables).
|
42
|
+
$RUBY ../../../../bin/patch_parser.rb $PARSER_BASE.out $YYTABLE_PREFIX > $PARSER_BASE.out2
|
43
|
+
$RUBY ../../../../bin/optimize_parser.rb $PARSER_BASE.out2 $YYTABLE_PREFIX > $PARSER_BASE.java
|
44
|
+
rm -f $PARSER_BASE.out $PARSER_BASE.out2
|
45
|
+
|
46
|
+
popd
|
@@ -0,0 +1,106 @@
|
|
1
|
+
class PostProcessor
|
2
|
+
def initialize(source, out=STDOUT)
|
3
|
+
@out = out
|
4
|
+
@lines = File.readlines(source)
|
5
|
+
@index = -1
|
6
|
+
@case_bodies = {}
|
7
|
+
@max_case_number = -1
|
8
|
+
end
|
9
|
+
|
10
|
+
# Read/Unread with ability to push back one line for a single lookahead
|
11
|
+
def read
|
12
|
+
@index += 1
|
13
|
+
line = @last ? @last : @lines[@index]
|
14
|
+
@last = nil
|
15
|
+
line
|
16
|
+
end
|
17
|
+
|
18
|
+
def unread(line)
|
19
|
+
@index -= 1
|
20
|
+
@last = line
|
21
|
+
end
|
22
|
+
|
23
|
+
def end_of_actions?(line)
|
24
|
+
return line =~ %r{^//\s*ACTIONS_END}
|
25
|
+
end
|
26
|
+
|
27
|
+
def translate
|
28
|
+
while (line = read)
|
29
|
+
if line =~ %r{^//\s*ACTIONS_BEGIN}
|
30
|
+
translate_actions
|
31
|
+
elsif line =~ %r{^//\s*ACTION_BODIES}
|
32
|
+
generate_action_body_methods
|
33
|
+
else
|
34
|
+
@out.puts line
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def generate_action_body_methods
|
40
|
+
@out.puts "static ParserState[] states = new ParserState[#{@max_case_number+1}];"
|
41
|
+
@out.puts "static {";
|
42
|
+
@case_bodies.each do |state, code_body|
|
43
|
+
generate_action_body_method(state, code_body)
|
44
|
+
end
|
45
|
+
@out.puts "}";
|
46
|
+
end
|
47
|
+
|
48
|
+
def generate_action_body_method(state, code_body)
|
49
|
+
@out.puts "states[#{state}] = new ParserState() {"
|
50
|
+
@out.puts " public Object execute(ParserSupport support, Lexer lexer, Object yyVal, Object[] yyVals, int yyTop) {"
|
51
|
+
code_body.each { |line| @out.puts line }
|
52
|
+
@out.puts " return yyVal;"
|
53
|
+
@out.puts " }"
|
54
|
+
@out.puts "};"
|
55
|
+
end
|
56
|
+
|
57
|
+
def translate_actions
|
58
|
+
count = 1
|
59
|
+
while (translate_action)
|
60
|
+
count += 1
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Assumptions:
|
65
|
+
# 1. no break; in our code. A bit weak, but this is highly specialized code.
|
66
|
+
# 2. All productions will have a line containing only { (with optional comment)
|
67
|
+
# 3. All productions will end with a line containly only } followed by break in ass 1.
|
68
|
+
def translate_action
|
69
|
+
line = read
|
70
|
+
return false if end_of_actions?(line) || line !~ /case\s+(\d+):/
|
71
|
+
case_number = $1.to_i
|
72
|
+
|
73
|
+
line = read
|
74
|
+
return false if line !~ /line\s+(\d+)/
|
75
|
+
line_number = $1
|
76
|
+
|
77
|
+
# Extra boiler plate '{' that we do not need
|
78
|
+
line = read
|
79
|
+
return false if line !~ /^\s*\{\s*(\/\*.*\*\/)?$/
|
80
|
+
|
81
|
+
@max_case_number = case_number if case_number > @max_case_number
|
82
|
+
|
83
|
+
label = "case#{case_number}_line#{line_number}"
|
84
|
+
|
85
|
+
body = []
|
86
|
+
last_line = nil
|
87
|
+
while (line = read)
|
88
|
+
if line =~ /^\s*\}\s*$/ # Extra trailing boiler plate
|
89
|
+
next_line = read
|
90
|
+
if next_line =~ /break;/
|
91
|
+
break
|
92
|
+
else
|
93
|
+
body << line
|
94
|
+
unread next_line
|
95
|
+
end
|
96
|
+
else
|
97
|
+
body << line
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
@case_bodies[case_number] = body
|
102
|
+
true
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
PostProcessor.new(ARGV.shift).translate
|
data/bin/patch_parser.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
def get_numbers_until_end_block(table)
|
2
|
+
while gets
|
3
|
+
break if /\};/ =~ $_
|
4
|
+
next if /^\/\// =~ $_
|
5
|
+
split(/,/).each do |number|
|
6
|
+
n = number.strip
|
7
|
+
table.push(n.to_i) unless n == ""
|
8
|
+
end
|
9
|
+
end
|
10
|
+
table
|
11
|
+
end
|
12
|
+
|
13
|
+
while gets
|
14
|
+
break if /protected static final short\[\] yyTable = \{/ =~ $_
|
15
|
+
print $_
|
16
|
+
end
|
17
|
+
|
18
|
+
# A little hacky...gets before ARGV to shift off and open file
|
19
|
+
yytable_prefix = ARGV.shift || ''
|
20
|
+
|
21
|
+
table4 = get_numbers_until_end_block([])
|
22
|
+
|
23
|
+
puts " protected static final short[] yyTable = #{yytable_prefix}YyTables.yyTable();"
|
24
|
+
|
25
|
+
while gets
|
26
|
+
break if /protected static final short\[\] yyCheck = \{/ =~ $_
|
27
|
+
print $_
|
28
|
+
end
|
29
|
+
|
30
|
+
check4 = get_numbers_until_end_block([])
|
31
|
+
|
32
|
+
puts " protected static final short[] yyCheck = #{yytable_prefix}YyTables.yyCheck();"
|
33
|
+
|
34
|
+
while gets
|
35
|
+
print $_
|
36
|
+
end
|
37
|
+
|
38
|
+
table2 = table4.slice!(0, table4.size / 2)
|
39
|
+
table3 = table4.slice!(0, table4.size / 2)
|
40
|
+
table1 = table2.slice!(0, table2.size / 2)
|
41
|
+
check2 = check4.slice!(0, check4.size / 2)
|
42
|
+
check3 = check4.slice!(0, check4.size / 2)
|
43
|
+
check1 = check2.slice!(0, check2.size / 2)
|
44
|
+
|
45
|
+
def printShortArray(table, f)
|
46
|
+
table.each_with_index { |e, i|
|
47
|
+
f.print "\n " if (i % 10 == 0)
|
48
|
+
begin
|
49
|
+
f.printf "%4d, ", e
|
50
|
+
rescue ArgumentError => a
|
51
|
+
$stderr.puts "Trouble printing '#{e}' on index #{i}"
|
52
|
+
end
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
def printShortMethod(f, table, name)
|
57
|
+
f.puts " private static final short[] yy#{name}() {"
|
58
|
+
f.puts " return new short[] {"
|
59
|
+
printShortArray table, f
|
60
|
+
f.puts
|
61
|
+
f.puts " };"
|
62
|
+
f.puts " }"
|
63
|
+
f.puts
|
64
|
+
end
|
65
|
+
|
66
|
+
open("#{yytable_prefix}YyTables.java", "w") { |f|
|
67
|
+
f.print <<END
|
68
|
+
package org.jrubyparser.parser;
|
69
|
+
|
70
|
+
public class #{yytable_prefix}YyTables {
|
71
|
+
private static short[] combine(short[] t1, short[] t2,
|
72
|
+
short[] t3, short[] t4) {
|
73
|
+
short[] t = new short[t1.length + t2.length + t3.length + t4.length];
|
74
|
+
int index = 0;
|
75
|
+
System.arraycopy(t1, 0, t, index, t1.length);
|
76
|
+
index += t1.length;
|
77
|
+
System.arraycopy(t2, 0, t, index, t2.length);
|
78
|
+
index += t2.length;
|
79
|
+
System.arraycopy(t3, 0, t, index, t3.length);
|
80
|
+
index += t3.length;
|
81
|
+
System.arraycopy(t4, 0, t, index, t4.length);
|
82
|
+
return t;
|
83
|
+
}
|
84
|
+
|
85
|
+
public static final short[] yyTable() {
|
86
|
+
return combine(yyTable1(), yyTable2(), yyTable3(), yyTable4());
|
87
|
+
}
|
88
|
+
|
89
|
+
public static final short[] yyCheck() {
|
90
|
+
return combine(yyCheck1(), yyCheck2(), yyCheck3(), yyCheck4());
|
91
|
+
}
|
92
|
+
END
|
93
|
+
|
94
|
+
printShortMethod(f, table1, "Table1")
|
95
|
+
printShortMethod(f, table2, "Table2")
|
96
|
+
printShortMethod(f, table3, "Table3")
|
97
|
+
printShortMethod(f, table4, "Table4")
|
98
|
+
|
99
|
+
printShortMethod(f, check1, "Check1")
|
100
|
+
printShortMethod(f, check2, "Check2")
|
101
|
+
printShortMethod(f, check3, "Check3")
|
102
|
+
printShortMethod(f, check4, "Check4")
|
103
|
+
|
104
|
+
f.puts "}"
|
105
|
+
}
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'java'
|
2
|
+
|
3
|
+
class org::jrubyparser::ast::Node
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
##
|
7
|
+
# Find nth child element of this node
|
8
|
+
#
|
9
|
+
def [](value)
|
10
|
+
child_nodes[value]
|
11
|
+
end
|
12
|
+
|
13
|
+
##
|
14
|
+
# Replace the nth child element of this node with the specified value.
|
15
|
+
# if the value is an actual Ruby value it will attempt to call to_ast_node
|
16
|
+
# on it to do automatic coercion to an AST node. If the node does not
|
17
|
+
# contain positioning information then it will just use the
|
18
|
+
# old nodes information
|
19
|
+
def []=(index, value)
|
20
|
+
value = value.to_ast_node if value.respond_to? :to_ast_node
|
21
|
+
|
22
|
+
old_value = child_nodes[index]
|
23
|
+
value.position = old_value.position unless value.position
|
24
|
+
child_nodes[index] = value
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# Find first node by name (which is short_name of actual node)
|
29
|
+
# === parameters
|
30
|
+
# * _name_ is the name of the class you want to find
|
31
|
+
# === examples
|
32
|
+
#
|
33
|
+
# root.find(:fcall) # Find first child node of type fcall (depth-first)
|
34
|
+
#
|
35
|
+
def find_type(name, &block)
|
36
|
+
name = name.to_s
|
37
|
+
return self if name == short_name && (!block_given? || yield(self))
|
38
|
+
|
39
|
+
child_nodes.each do |child|
|
40
|
+
value = child.find_type(name, &block)
|
41
|
+
|
42
|
+
return value if value
|
43
|
+
end
|
44
|
+
nil
|
45
|
+
end
|
46
|
+
alias find_node find_type
|
47
|
+
|
48
|
+
def each(&block)
|
49
|
+
yield self
|
50
|
+
child_nodes.each { |child| child.each(&block) }
|
51
|
+
end
|
52
|
+
|
53
|
+
##
|
54
|
+
# Convert this node back to human-readable source code.
|
55
|
+
#
|
56
|
+
def to_source(opts = {})
|
57
|
+
filename = opts[:filename] ? opts[:filename] : '(string)'
|
58
|
+
java.io.StringWriter.new.tap do |writer|
|
59
|
+
accept org.jrubyparser.rewriter.ReWriteVisitor.new(writer, filename)
|
60
|
+
end.to_s
|
61
|
+
end
|
62
|
+
|
63
|
+
def short_name
|
64
|
+
java_class.name.gsub(/(^.*\.|Node$)/, '').downcase
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module JRubyParser
|
2
|
+
module Receiver
|
3
|
+
def self.included(cls)
|
4
|
+
cls.class_eval do
|
5
|
+
def receiver=(value)
|
6
|
+
value = value.to_ast_node if value.respond_to? :to_ast_node
|
7
|
+
old_value = getReceiver
|
8
|
+
value.position = old_value.position unless value.position
|
9
|
+
setReceiver(value)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module Value
|
16
|
+
def self.included(cls)
|
17
|
+
cls.class_eval do
|
18
|
+
def value=(value)
|
19
|
+
value = value.to_ast_node if value.respond_to? :to_ast_node
|
20
|
+
old_value = getValue
|
21
|
+
value.position = old_value.position unless value.position
|
22
|
+
setValue(value)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
module Args
|
29
|
+
def self.included(cls)
|
30
|
+
cls.class_eval do
|
31
|
+
def args=(value)
|
32
|
+
value = value.to_ast_node if value.respond_to? :to_ast_node
|
33
|
+
old_value = getArgs
|
34
|
+
unless value.position
|
35
|
+
value.position = old_value.position
|
36
|
+
value.each { |e| e.position = old_value.position } #if value.respond_to? :each
|
37
|
+
end
|
38
|
+
|
39
|
+
setArgs(value)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
Binary file
|
data/lib/jruby-parser.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'java'
|
2
|
+
require 'jruby-parser.jar'
|
3
|
+
require 'jruby-parser/core_ext/array'
|
4
|
+
require 'jruby-parser/core_ext/boolean'
|
5
|
+
require 'jruby-parser/core_ext/nil'
|
6
|
+
require 'jruby-parser/core_ext/call_node'
|
7
|
+
require 'jruby-parser/core_ext/fcall_node'
|
8
|
+
require 'jruby-parser/core_ext/list_node'
|
9
|
+
require 'jruby-parser/core_ext/node'
|
10
|
+
require 'jruby-parser/core_ext/numeric' # float,fixnum
|
11
|
+
require 'jruby-parser/core_ext/op_element_asgn_node'
|
12
|
+
require 'jruby-parser/core_ext/string'
|
13
|
+
|
14
|
+
module JRubyParser
|
15
|
+
Compat = org.jrubyparser.CompatVersion
|
16
|
+
|
17
|
+
##
|
18
|
+
# Parse source string and return a Abstract Syntax Tree (AST) of the source.
|
19
|
+
# You may also pass in additional options to affect the reported filename
|
20
|
+
# and which version of Ruby you want to use:
|
21
|
+
#
|
22
|
+
# === Parameters
|
23
|
+
# * _source_string_ source you want to parse
|
24
|
+
# * _opts_ customize how your source is parsed (:filename, and :version [defaults to 1.9])
|
25
|
+
# === Example
|
26
|
+
# JRubyParser.parse(%q{puts "hello world"}, :version => JRubyParser::Compat::RUBY1_8)
|
27
|
+
#
|
28
|
+
def parse(source_string, opts={})
|
29
|
+
filename = opts[:filename] ? opts[:filename] : '(string)'
|
30
|
+
version = opts[:version] ? opts[:version] : Compat::RUBY1_9
|
31
|
+
config = org.jrubyparser.parser.ParserConfiguration.new(0, version)
|
32
|
+
reader = java.io.StringReader.new(source_string)
|
33
|
+
org.jrubyparser.Parser.new.parse(filename, reader, config)
|
34
|
+
end
|
35
|
+
module_function :parse
|
36
|
+
end
|
data/lib/yydebug.jar
ADDED
Binary file
|
data/sample/simple.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'jruby-parser'
|
2
|
+
|
3
|
+
# Extra options hash can be passed in to override parser configuration
|
4
|
+
# opts = {:version => JRubyParser::Compat::RUBY1_8, :filename => 'name.rb'}
|
5
|
+
# root = JRubyParser.parse("b = foo(1)", opts)
|
6
|
+
root = JRubyParser.parse("b = foo(1)")
|
7
|
+
|
8
|
+
# Enumerable is mixed into AST tree
|
9
|
+
# fcall = foot.find { |e| e.short_name == "fcall" }
|
10
|
+
# ...but find_node is pretty common for spec writing:
|
11
|
+
fcall = root.find_node(:fcall)
|
12
|
+
|
13
|
+
# Change the AST.
|
14
|
+
fcall.name = 'bar'
|
15
|
+
|
16
|
+
# Notice this should be a TrueNode, but true knows to coerce into TrueNode
|
17
|
+
fcall.args[0] = true
|
18
|
+
|
19
|
+
# Write out the new source "b = bar(1)"
|
20
|
+
p root.to_source
|
@@ -0,0 +1,20 @@
|
|
1
|
+
$LOAD_PATH.unshift File.dirname(__FILE__) + "/../helpers"
|
2
|
+
$LOAD_PATH.unshift File.dirname(__FILE__) + "/../../lib"
|
3
|
+
require 'jruby-parser'
|
4
|
+
require 'parser_helpers'
|
5
|
+
require 'node_helpers'
|
6
|
+
|
7
|
+
describe Parser do
|
8
|
+
it "should parse alias with quotationmarks" do
|
9
|
+
root = parse <<-EOF
|
10
|
+
def foo(arg)
|
11
|
+
puts arg
|
12
|
+
end
|
13
|
+
EOF
|
14
|
+
list = root.pathTo(root.find_node(:defn))
|
15
|
+
list.size.should == 3
|
16
|
+
list.root.node_name.should == "RootNode"
|
17
|
+
list.leaf.node_name.should == "DefnNode"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'java'
|
2
|
+
|
3
|
+
import org.jrubyparser.SourcePosition
|
4
|
+
import org.jrubyparser.ast.Node
|
5
|
+
|
6
|
+
class SourcePosition
|
7
|
+
def to_a
|
8
|
+
[start_line, end_line, start_offset, end_offset]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
###########
|
13
|
+
|
14
|
+
class AstPositionMatcher
|
15
|
+
def initialize(*args)
|
16
|
+
@position = args
|
17
|
+
end
|
18
|
+
|
19
|
+
def matches?(actual)
|
20
|
+
@actual = actual
|
21
|
+
actual.position.to_a == @position
|
22
|
+
end
|
23
|
+
|
24
|
+
def failure_message
|
25
|
+
return %[expected #{@actual.position.to_a.inspect} to have position #{@position.inspect}]
|
26
|
+
end
|
27
|
+
|
28
|
+
def negative_failure_message
|
29
|
+
return %[expected #{@actual.position.to_a.inspect} to not have position #{@position.inspect}]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
module HavePosition
|
34
|
+
def have_position(*args)
|
35
|
+
AstPositionMatcher.new(*args)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class AstNameMatcher
|
40
|
+
def initialize(name)
|
41
|
+
@name = name
|
42
|
+
end
|
43
|
+
|
44
|
+
def matches?(actual)
|
45
|
+
@actual = actual
|
46
|
+
actual.name == @name
|
47
|
+
end
|
48
|
+
|
49
|
+
def failure_message
|
50
|
+
return %[expected #{@actual.inspect} to have name #{@name.inspect}]
|
51
|
+
end
|
52
|
+
|
53
|
+
def negative_failure_message
|
54
|
+
return %[expected #{@actual.inspect} to not have name #{@name.inspect}]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
module HaveName
|
59
|
+
def have_name(name)
|
60
|
+
AstNameMatcher.new(name)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class AstNameAndPositionMatcher
|
65
|
+
def initialize(name, *args)
|
66
|
+
@name, @position = name, args
|
67
|
+
end
|
68
|
+
|
69
|
+
def matches?(actual)
|
70
|
+
@actual = actual
|
71
|
+
actual.name == @name && actual.position.to_a == @position
|
72
|
+
end
|
73
|
+
|
74
|
+
def failure_message
|
75
|
+
return %[expected #{@actual.name.inspect}, #{@actual.position.to_a.inspect} to have name and position #{@name.inspect}, #{@position.inspect}]
|
76
|
+
end
|
77
|
+
|
78
|
+
def negative_failure_message
|
79
|
+
return %[expected #{@actual.name.inspect}, #{@actual.position.to_a.inspect} to not have and position #{@name.inspect}, #{@position.inspect}]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
module HaveNameAndPosition
|
84
|
+
def have_name_and_position(*args)
|
85
|
+
AstNameAndPositionMatcher.new(*args)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
class ArgCountsMatcher
|
90
|
+
def initialize(pre, optional, rest, block)
|
91
|
+
@args = [pre, optional, rest, block]
|
92
|
+
end
|
93
|
+
|
94
|
+
def matches?(args)
|
95
|
+
@actual = [args.pre_count, args.optional_count, args.rest != nil, args.block != nil]
|
96
|
+
@args == @actual
|
97
|
+
end
|
98
|
+
|
99
|
+
def failure_message
|
100
|
+
return %[expected #{@actual.inspect} to have name #{@args.inspect}]
|
101
|
+
end
|
102
|
+
|
103
|
+
def negative_failure_message
|
104
|
+
return %[expected #{@actual.inspect} to not have name #{@args.inspect}]
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
module HaveArgCounts
|
109
|
+
def have_arg_counts(*args)
|
110
|
+
ArgCountsMatcher.new(*args)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
#module Spec::Example::ExampleMethods
|
115
|
+
class Object
|
116
|
+
include HavePosition
|
117
|
+
include HaveName
|
118
|
+
include HaveNameAndPosition
|
119
|
+
include HaveArgCounts
|
120
|
+
end
|