first commit
This commit is contained in:
+101
@@ -0,0 +1,101 @@
|
||||
MRuby::GemBox.new do |conf|
|
||||
# Meta-programming features
|
||||
conf.gem :core => "mruby-metaprog"
|
||||
|
||||
# Use standard IO/File class
|
||||
conf.gem :core => "mruby-io"
|
||||
|
||||
# Use standard Array#pack, String#unpack methods
|
||||
conf.gem :core => "mruby-pack"
|
||||
|
||||
# Use standard Kernel#sprintf method
|
||||
conf.gem :core => "mruby-sprintf"
|
||||
|
||||
# Use standard print/puts/p
|
||||
conf.gem :core => "mruby-print"
|
||||
|
||||
# Use standard Math module
|
||||
conf.gem :core => "mruby-math"
|
||||
|
||||
# Use standard Time class
|
||||
conf.gem :core => "mruby-time"
|
||||
|
||||
# Use standard Struct class
|
||||
conf.gem :core => "mruby-struct"
|
||||
|
||||
# Use Comparable module extension
|
||||
conf.gem :core => "mruby-compar-ext"
|
||||
|
||||
# Use Enumerable module extension
|
||||
conf.gem :core => "mruby-enum-ext"
|
||||
|
||||
# Use String class extension
|
||||
conf.gem :core => "mruby-string-ext"
|
||||
|
||||
# Use Numeric class extension
|
||||
conf.gem :core => "mruby-numeric-ext"
|
||||
|
||||
# Use Array class extension
|
||||
conf.gem :core => "mruby-array-ext"
|
||||
|
||||
# Use Hash class extension
|
||||
conf.gem :core => "mruby-hash-ext"
|
||||
|
||||
# Use Range class extension
|
||||
conf.gem :core => "mruby-range-ext"
|
||||
|
||||
# Use Proc class extension
|
||||
conf.gem :core => "mruby-proc-ext"
|
||||
|
||||
# Use Symbol class extension
|
||||
conf.gem :core => "mruby-symbol-ext"
|
||||
|
||||
# Use Random class
|
||||
conf.gem :core => "mruby-random"
|
||||
|
||||
# Use Object class extension
|
||||
conf.gem :core => "mruby-object-ext"
|
||||
|
||||
# Use ObjectSpace class
|
||||
conf.gem :core => "mruby-objectspace"
|
||||
|
||||
# Use Fiber class
|
||||
conf.gem :core => "mruby-fiber"
|
||||
|
||||
# Use Enumerator class (require mruby-fiber)
|
||||
conf.gem :core => "mruby-enumerator"
|
||||
|
||||
# Use Enumerator::Lazy class (require mruby-enumerator)
|
||||
conf.gem :core => "mruby-enum-lazy"
|
||||
|
||||
# Use toplevel object (main) methods extension
|
||||
conf.gem :core => "mruby-toplevel-ext"
|
||||
|
||||
# Use Rational/Complex numbers
|
||||
conf.gem :core => "mruby-rational"
|
||||
conf.gem :core => "mruby-complex"
|
||||
|
||||
# Generate mirb command
|
||||
conf.gem :core => "mruby-bin-mirb"
|
||||
|
||||
# Generate mruby command
|
||||
conf.gem :core => "mruby-bin-mruby"
|
||||
|
||||
# Generate mruby-strip command
|
||||
conf.gem :core => "mruby-bin-strip"
|
||||
|
||||
# Use Kernel module extension
|
||||
conf.gem :core => "mruby-kernel-ext"
|
||||
|
||||
# Use class/module extension
|
||||
conf.gem :core => "mruby-class-ext"
|
||||
|
||||
# Use Method/UnboundMethod class
|
||||
conf.gem :core => "mruby-method"
|
||||
|
||||
# Use eval()
|
||||
conf.gem :core => "mruby-eval"
|
||||
|
||||
# Use mruby-compiler to build other mrbgems
|
||||
conf.gem :core => "mruby-compiler"
|
||||
end
|
||||
@@ -0,0 +1,9 @@
|
||||
MRuby::GemBox.new do |conf|
|
||||
conf.gem :core => "mruby-sprintf"
|
||||
conf.gem :core => "mruby-print"
|
||||
|
||||
Dir.glob("#{root}/mrbgems/mruby-*/mrbgem.rake") do |x|
|
||||
g = File.basename File.dirname x
|
||||
conf.gem :core => g unless g =~ /^mruby-(print|sprintf|bin-debugger|test)$/
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
MRuby::Gem::Specification.new('mruby-array-ext') do |spec|
|
||||
spec.license = 'MIT'
|
||||
spec.author = 'mruby developers'
|
||||
spec.summary = 'Array class extension'
|
||||
end
|
||||
@@ -0,0 +1,946 @@
|
||||
class Array
|
||||
##
|
||||
# call-seq:
|
||||
# ary.uniq! -> ary or nil
|
||||
# ary.uniq! { |item| ... } -> ary or nil
|
||||
#
|
||||
# Removes duplicate elements from +self+.
|
||||
# Returns <code>nil</code> if no changes are made (that is, no
|
||||
# duplicates are found).
|
||||
#
|
||||
# a = [ "a", "a", "b", "b", "c" ]
|
||||
# a.uniq! #=> ["a", "b", "c"]
|
||||
# b = [ "a", "b", "c" ]
|
||||
# b.uniq! #=> nil
|
||||
# c = [["student","sam"], ["student","george"], ["teacher","matz"]]
|
||||
# c.uniq! { |s| s.first } # => [["student", "sam"], ["teacher", "matz"]]
|
||||
#
|
||||
def uniq!(&block)
|
||||
hash = {}
|
||||
if block
|
||||
self.each do |val|
|
||||
key = block.call(val)
|
||||
hash[key] = val unless hash.key?(key)
|
||||
end
|
||||
result = hash.values
|
||||
else
|
||||
hash = {}
|
||||
self.each do |val|
|
||||
hash[val] = val
|
||||
end
|
||||
result = hash.keys
|
||||
end
|
||||
if result.size == self.size
|
||||
nil
|
||||
else
|
||||
self.replace(result)
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# ary.uniq -> new_ary
|
||||
# ary.uniq { |item| ... } -> new_ary
|
||||
#
|
||||
# Returns a new array by removing duplicate values in +self+.
|
||||
#
|
||||
# a = [ "a", "a", "b", "b", "c" ]
|
||||
# a.uniq #=> ["a", "b", "c"]
|
||||
#
|
||||
# b = [["student","sam"], ["student","george"], ["teacher","matz"]]
|
||||
# b.uniq { |s| s.first } # => [["student", "sam"], ["teacher", "matz"]]
|
||||
#
|
||||
def uniq(&block)
|
||||
ary = self.dup
|
||||
ary.uniq!(&block)
|
||||
ary
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# ary - other_ary -> new_ary
|
||||
#
|
||||
# Array Difference---Returns a new array that is a copy of
|
||||
# the original array, removing any items that also appear in
|
||||
# <i>other_ary</i>. (If you need set-like behavior, see the
|
||||
# library class Set.)
|
||||
#
|
||||
# [ 1, 1, 2, 2, 3, 3, 4, 5 ] - [ 1, 2, 4 ] #=> [ 3, 3, 5 ]
|
||||
#
|
||||
def -(elem)
|
||||
raise TypeError, "can't convert #{elem.class} into Array" unless elem.class == Array
|
||||
|
||||
hash = {}
|
||||
array = []
|
||||
idx = 0
|
||||
len = elem.size
|
||||
while idx < len
|
||||
hash[elem[idx]] = true
|
||||
idx += 1
|
||||
end
|
||||
idx = 0
|
||||
len = size
|
||||
while idx < len
|
||||
v = self[idx]
|
||||
array << v unless hash[v]
|
||||
idx += 1
|
||||
end
|
||||
array
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# ary.difference(other_ary1, other_ary2, ...) -> new_ary
|
||||
#
|
||||
# Returns a new array that is a copy of the original array, removing all
|
||||
# occurrences of any item that also appear in +other_ary+. The order is
|
||||
# preserved from the original array.
|
||||
#
|
||||
def difference(*args)
|
||||
ary = self
|
||||
args.each do |x|
|
||||
ary = ary - x
|
||||
end
|
||||
ary
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# ary | other_ary -> new_ary
|
||||
#
|
||||
# Set Union---Returns a new array by joining this array with
|
||||
# <i>other_ary</i>, removing duplicates.
|
||||
#
|
||||
# [ "a", "b", "c" ] | [ "c", "d", "a" ]
|
||||
# #=> [ "a", "b", "c", "d" ]
|
||||
#
|
||||
def |(elem)
|
||||
raise TypeError, "can't convert #{elem.class} into Array" unless elem.class == Array
|
||||
|
||||
ary = self + elem
|
||||
ary.uniq! or ary
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# ary.union(other_ary,...) -> new_ary
|
||||
#
|
||||
# Set Union---Returns a new array by joining this array with
|
||||
# <i>other_ary</i>, removing duplicates.
|
||||
#
|
||||
# ["a", "b", "c"].union(["c", "d", "a"], ["a", "c", "e"])
|
||||
# #=> ["a", "b", "c", "d", "e"]
|
||||
#
|
||||
def union(*args)
|
||||
ary = self.dup
|
||||
args.each do |x|
|
||||
ary.concat(x)
|
||||
ary.uniq!
|
||||
end
|
||||
ary
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# ary & other_ary -> new_ary
|
||||
#
|
||||
# Set Intersection---Returns a new array
|
||||
# containing elements common to the two arrays, with no duplicates.
|
||||
#
|
||||
# [ 1, 1, 3, 5 ] & [ 1, 2, 3 ] #=> [ 1, 3 ]
|
||||
#
|
||||
def &(elem)
|
||||
raise TypeError, "can't convert #{elem.class} into Array" unless elem.class == Array
|
||||
|
||||
hash = {}
|
||||
array = []
|
||||
idx = 0
|
||||
len = elem.size
|
||||
while idx < len
|
||||
hash[elem[idx]] = true
|
||||
idx += 1
|
||||
end
|
||||
idx = 0
|
||||
len = size
|
||||
while idx < len
|
||||
v = self[idx]
|
||||
if hash[v]
|
||||
array << v
|
||||
hash.delete v
|
||||
end
|
||||
idx += 1
|
||||
end
|
||||
array
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# ary.intersection(other_ary,...) -> new_ary
|
||||
#
|
||||
# Set Intersection---Returns a new array containing elements common to
|
||||
# this array and <i>other_ary</i>s, removing duplicates. The order is
|
||||
# preserved from the original array.
|
||||
#
|
||||
# [1, 2, 3].intersection([3, 4, 1], [1, 3, 5]) #=> [1, 3]
|
||||
#
|
||||
def intersection(*args)
|
||||
ary = self
|
||||
args.each do |x|
|
||||
ary = ary & x
|
||||
end
|
||||
ary
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# ary.flatten -> new_ary
|
||||
# ary.flatten(level) -> new_ary
|
||||
#
|
||||
# Returns a new array that is a one-dimensional flattening of this
|
||||
# array (recursively). That is, for every element that is an array,
|
||||
# extract its elements into the new array. If the optional
|
||||
# <i>level</i> argument determines the level of recursion to flatten.
|
||||
#
|
||||
# s = [ 1, 2, 3 ] #=> [1, 2, 3]
|
||||
# t = [ 4, 5, 6, [7, 8] ] #=> [4, 5, 6, [7, 8]]
|
||||
# a = [ s, t, 9, 10 ] #=> [[1, 2, 3], [4, 5, 6, [7, 8]], 9, 10]
|
||||
# a.flatten #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
# a = [ 1, 2, [3, [4, 5] ] ]
|
||||
# a.flatten(1) #=> [1, 2, 3, [4, 5]]
|
||||
#
|
||||
def flatten(depth=nil)
|
||||
res = dup
|
||||
res.flatten! depth
|
||||
res
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# ary.flatten! -> ary or nil
|
||||
# ary.flatten!(level) -> array or nil
|
||||
#
|
||||
# Flattens +self+ in place.
|
||||
# Returns <code>nil</code> if no modifications were made (i.e.,
|
||||
# <i>ary</i> contains no subarrays.) If the optional <i>level</i>
|
||||
# argument determines the level of recursion to flatten.
|
||||
#
|
||||
# a = [ 1, 2, [3, [4, 5] ] ]
|
||||
# a.flatten! #=> [1, 2, 3, 4, 5]
|
||||
# a.flatten! #=> nil
|
||||
# a #=> [1, 2, 3, 4, 5]
|
||||
# a = [ 1, 2, [3, [4, 5] ] ]
|
||||
# a.flatten!(1) #=> [1, 2, 3, [4, 5]]
|
||||
#
|
||||
def flatten!(depth=nil)
|
||||
modified = false
|
||||
ar = []
|
||||
idx = 0
|
||||
len = size
|
||||
while idx < len
|
||||
e = self[idx]
|
||||
if e.is_a?(Array) && (depth.nil? || depth > 0)
|
||||
ar += e.flatten(depth.nil? ? nil : depth - 1)
|
||||
modified = true
|
||||
else
|
||||
ar << e
|
||||
end
|
||||
idx += 1
|
||||
end
|
||||
if modified
|
||||
self.replace(ar)
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# ary.compact -> new_ary
|
||||
#
|
||||
# Returns a copy of +self+ with all +nil+ elements removed.
|
||||
#
|
||||
# [ "a", nil, "b", nil, "c", nil ].compact
|
||||
# #=> [ "a", "b", "c" ]
|
||||
#
|
||||
def compact
|
||||
result = self.dup
|
||||
result.compact!
|
||||
result
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# ary.compact! -> ary or nil
|
||||
#
|
||||
# Removes +nil+ elements from the array.
|
||||
# Returns +nil+ if no changes were made, otherwise returns
|
||||
# <i>ary</i>.
|
||||
#
|
||||
# [ "a", nil, "b", nil, "c" ].compact! #=> [ "a", "b", "c" ]
|
||||
# [ "a", "b", "c" ].compact! #=> nil
|
||||
#
|
||||
def compact!
|
||||
result = self.select { |e| !e.nil? }
|
||||
if result.size == self.size
|
||||
nil
|
||||
else
|
||||
self.replace(result)
|
||||
end
|
||||
end
|
||||
|
||||
# for efficiency
|
||||
def reverse_each(&block)
|
||||
return to_enum :reverse_each unless block
|
||||
|
||||
i = self.size - 1
|
||||
while i>=0
|
||||
block.call(self[i])
|
||||
i -= 1
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# ary.fetch(index) -> obj
|
||||
# ary.fetch(index, default) -> obj
|
||||
# ary.fetch(index) { |index| block } -> obj
|
||||
#
|
||||
# Tries to return the element at position +index+, but throws an IndexError
|
||||
# exception if the referenced +index+ lies outside of the array bounds. This
|
||||
# error can be prevented by supplying a second argument, which will act as a
|
||||
# +default+ value.
|
||||
#
|
||||
# Alternatively, if a block is given it will only be executed when an
|
||||
# invalid +index+ is referenced.
|
||||
#
|
||||
# Negative values of +index+ count from the end of the array.
|
||||
#
|
||||
# a = [ 11, 22, 33, 44 ]
|
||||
# a.fetch(1) #=> 22
|
||||
# a.fetch(-1) #=> 44
|
||||
# a.fetch(4, 'cat') #=> "cat"
|
||||
# a.fetch(100) { |i| puts "#{i} is out of bounds" }
|
||||
# #=> "100 is out of bounds"
|
||||
#
|
||||
|
||||
def fetch(n, ifnone=NONE, &block)
|
||||
warn "block supersedes default value argument" if !n.nil? && ifnone != NONE && block
|
||||
|
||||
idx = n
|
||||
if idx < 0
|
||||
idx += size
|
||||
end
|
||||
if idx < 0 || size <= idx
|
||||
return block.call(n) if block
|
||||
if ifnone == NONE
|
||||
raise IndexError, "index #{n} outside of array bounds: #{-size}...#{size}"
|
||||
end
|
||||
return ifnone
|
||||
end
|
||||
self[idx]
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# ary.fill(obj) -> ary
|
||||
# ary.fill(obj, start [, length]) -> ary
|
||||
# ary.fill(obj, range ) -> ary
|
||||
# ary.fill { |index| block } -> ary
|
||||
# ary.fill(start [, length] ) { |index| block } -> ary
|
||||
# ary.fill(range) { |index| block } -> ary
|
||||
#
|
||||
# The first three forms set the selected elements of +self+ (which
|
||||
# may be the entire array) to +obj+.
|
||||
#
|
||||
# A +start+ of +nil+ is equivalent to zero.
|
||||
#
|
||||
# A +length+ of +nil+ is equivalent to the length of the array.
|
||||
#
|
||||
# The last three forms fill the array with the value of the given block,
|
||||
# which is passed the absolute index of each element to be filled.
|
||||
#
|
||||
# Negative values of +start+ count from the end of the array, where +-1+ is
|
||||
# the last element.
|
||||
#
|
||||
# a = [ "a", "b", "c", "d" ]
|
||||
# a.fill("x") #=> ["x", "x", "x", "x"]
|
||||
# a.fill("w", -1) #=> ["x", "x", "x", "w"]
|
||||
# a.fill("z", 2, 2) #=> ["x", "x", "z", "z"]
|
||||
# a.fill("y", 0..1) #=> ["y", "y", "z", "z"]
|
||||
# a.fill { |i| i*i } #=> [0, 1, 4, 9]
|
||||
# a.fill(-2) { |i| i*i*i } #=> [0, 1, 8, 27]
|
||||
# a.fill(1, 2) { |i| i+1 } #=> [0, 2, 3, 27]
|
||||
# a.fill(0..1) { |i| i+1 } #=> [1, 2, 3, 27]
|
||||
#
|
||||
|
||||
def fill(arg0=nil, arg1=nil, arg2=nil, &block)
|
||||
if arg0.nil? && arg1.nil? && arg2.nil? && !block
|
||||
raise ArgumentError, "wrong number of arguments (0 for 1..3)"
|
||||
end
|
||||
|
||||
beg = len = 0
|
||||
ary = []
|
||||
if block
|
||||
if arg0.nil? && arg1.nil? && arg2.nil?
|
||||
# ary.fill { |index| block } -> ary
|
||||
beg = 0
|
||||
len = self.size
|
||||
elsif !arg0.nil? && arg0.kind_of?(Range)
|
||||
# ary.fill(range) { |index| block } -> ary
|
||||
beg = arg0.begin
|
||||
beg += self.size if beg < 0
|
||||
len = arg0.end
|
||||
len += self.size if len < 0
|
||||
len += 1 unless arg0.exclude_end?
|
||||
elsif !arg0.nil?
|
||||
# ary.fill(start [, length] ) { |index| block } -> ary
|
||||
beg = arg0
|
||||
beg += self.size if beg < 0
|
||||
if arg1.nil?
|
||||
len = self.size
|
||||
else
|
||||
len = arg0 + arg1
|
||||
end
|
||||
end
|
||||
else
|
||||
if !arg0.nil? && arg1.nil? && arg2.nil?
|
||||
# ary.fill(obj) -> ary
|
||||
beg = 0
|
||||
len = self.size
|
||||
elsif !arg0.nil? && !arg1.nil? && arg1.kind_of?(Range)
|
||||
# ary.fill(obj, range ) -> ary
|
||||
beg = arg1.begin
|
||||
beg += self.size if beg < 0
|
||||
len = arg1.end
|
||||
len += self.size if len < 0
|
||||
len += 1 unless arg1.exclude_end?
|
||||
elsif !arg0.nil? && !arg1.nil?
|
||||
# ary.fill(obj, start [, length]) -> ary
|
||||
beg = arg1
|
||||
beg += self.size if beg < 0
|
||||
if arg2.nil?
|
||||
len = self.size
|
||||
else
|
||||
len = beg + arg2
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
i = beg
|
||||
if block
|
||||
while i < len
|
||||
self[i] = block.call(i)
|
||||
i += 1
|
||||
end
|
||||
else
|
||||
while i < len
|
||||
self[i] = arg0
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# ary.rotate(count=1) -> new_ary
|
||||
#
|
||||
# Returns a new array by rotating +self+ so that the element at +count+ is
|
||||
# the first element of the new array.
|
||||
#
|
||||
# If +count+ is negative then it rotates in the opposite direction, starting
|
||||
# from the end of +self+ where +-1+ is the last element.
|
||||
#
|
||||
# a = [ "a", "b", "c", "d" ]
|
||||
# a.rotate #=> ["b", "c", "d", "a"]
|
||||
# a #=> ["a", "b", "c", "d"]
|
||||
# a.rotate(2) #=> ["c", "d", "a", "b"]
|
||||
# a.rotate(-3) #=> ["b", "c", "d", "a"]
|
||||
|
||||
def rotate(count=1)
|
||||
ary = []
|
||||
len = self.length
|
||||
|
||||
if len > 0
|
||||
idx = (count < 0) ? (len - (~count % len) - 1) : (count % len) # rotate count
|
||||
len.times do
|
||||
ary << self[idx]
|
||||
idx += 1
|
||||
idx = 0 if idx > len-1
|
||||
end
|
||||
end
|
||||
ary
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# ary.rotate!(count=1) -> ary
|
||||
#
|
||||
# Rotates +self+ in place so that the element at +count+ comes first, and
|
||||
# returns +self+.
|
||||
#
|
||||
# If +count+ is negative then it rotates in the opposite direction, starting
|
||||
# from the end of the array where +-1+ is the last element.
|
||||
#
|
||||
# a = [ "a", "b", "c", "d" ]
|
||||
# a.rotate! #=> ["b", "c", "d", "a"]
|
||||
# a #=> ["b", "c", "d", "a"]
|
||||
# a.rotate!(2) #=> ["d", "a", "b", "c"]
|
||||
# a.rotate!(-3) #=> ["a", "b", "c", "d"]
|
||||
|
||||
def rotate!(count=1)
|
||||
self.replace(self.rotate(count))
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# ary.delete_if { |item| block } -> ary
|
||||
# ary.delete_if -> Enumerator
|
||||
#
|
||||
# Deletes every element of +self+ for which block evaluates to +true+.
|
||||
#
|
||||
# The array is changed instantly every time the block is called, not after
|
||||
# the iteration is over.
|
||||
#
|
||||
# See also Array#reject!
|
||||
#
|
||||
# If no block is given, an Enumerator is returned instead.
|
||||
#
|
||||
# scores = [ 97, 42, 75 ]
|
||||
# scores.delete_if {|score| score < 80 } #=> [97]
|
||||
|
||||
def delete_if(&block)
|
||||
return to_enum :delete_if unless block
|
||||
|
||||
idx = 0
|
||||
while idx < self.size do
|
||||
if block.call(self[idx])
|
||||
self.delete_at(idx)
|
||||
else
|
||||
idx += 1
|
||||
end
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# ary.reject! { |item| block } -> ary or nil
|
||||
# ary.reject! -> Enumerator
|
||||
#
|
||||
# Equivalent to Array#delete_if, deleting elements from +self+ for which the
|
||||
# block evaluates to +true+, but returns +nil+ if no changes were made.
|
||||
#
|
||||
# The array is changed instantly every time the block is called, not after
|
||||
# the iteration is over.
|
||||
#
|
||||
# See also Enumerable#reject and Array#delete_if.
|
||||
#
|
||||
# If no block is given, an Enumerator is returned instead.
|
||||
|
||||
def reject!(&block)
|
||||
return to_enum :reject! unless block
|
||||
|
||||
len = self.size
|
||||
idx = 0
|
||||
while idx < self.size do
|
||||
if block.call(self[idx])
|
||||
self.delete_at(idx)
|
||||
else
|
||||
idx += 1
|
||||
end
|
||||
end
|
||||
if self.size == len
|
||||
nil
|
||||
else
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# ary.insert(index, obj...) -> ary
|
||||
#
|
||||
# Inserts the given values before the element with the given +index+.
|
||||
#
|
||||
# Negative indices count backwards from the end of the array, where +-1+ is
|
||||
# the last element.
|
||||
#
|
||||
# a = %w{ a b c d }
|
||||
# a.insert(2, 99) #=> ["a", "b", 99, "c", "d"]
|
||||
# a.insert(-2, 1, 2, 3) #=> ["a", "b", 99, "c", 1, 2, 3, "d"]
|
||||
|
||||
def insert(idx, *args)
|
||||
idx += self.size + 1 if idx < 0
|
||||
self[idx, 0] = args
|
||||
self
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# ary.bsearch {|x| block } -> elem
|
||||
#
|
||||
# By using binary search, finds a value from this array which meets
|
||||
# the given condition in O(log n) where n is the size of the array.
|
||||
#
|
||||
# You can use this method in two use cases: a find-minimum mode and
|
||||
# a find-any mode. In either case, the elements of the array must be
|
||||
# monotone (or sorted) with respect to the block.
|
||||
#
|
||||
# In find-minimum mode (this is a good choice for typical use case),
|
||||
# the block must return true or false, and there must be an index i
|
||||
# (0 <= i <= ary.size) so that:
|
||||
#
|
||||
# - the block returns false for any element whose index is less than
|
||||
# i, and
|
||||
# - the block returns true for any element whose index is greater
|
||||
# than or equal to i.
|
||||
#
|
||||
# This method returns the i-th element. If i is equal to ary.size,
|
||||
# it returns nil.
|
||||
#
|
||||
# ary = [0, 4, 7, 10, 12]
|
||||
# ary.bsearch {|x| x >= 4 } #=> 4
|
||||
# ary.bsearch {|x| x >= 6 } #=> 7
|
||||
# ary.bsearch {|x| x >= -1 } #=> 0
|
||||
# ary.bsearch {|x| x >= 100 } #=> nil
|
||||
#
|
||||
# In find-any mode (this behaves like libc's bsearch(3)), the block
|
||||
# must return a number, and there must be two indices i and j
|
||||
# (0 <= i <= j <= ary.size) so that:
|
||||
#
|
||||
# - the block returns a positive number for ary[k] if 0 <= k < i,
|
||||
# - the block returns zero for ary[k] if i <= k < j, and
|
||||
# - the block returns a negative number for ary[k] if
|
||||
# j <= k < ary.size.
|
||||
#
|
||||
# Under this condition, this method returns any element whose index
|
||||
# is within i...j. If i is equal to j (i.e., there is no element
|
||||
# that satisfies the block), this method returns nil.
|
||||
#
|
||||
# ary = [0, 4, 7, 10, 12]
|
||||
# # try to find v such that 4 <= v < 8
|
||||
# ary.bsearch {|x| 1 - (x / 4).truncate } #=> 4 or 7
|
||||
# # try to find v such that 8 <= v < 10
|
||||
# ary.bsearch {|x| 4 - (x / 2).truncate } #=> nil
|
||||
#
|
||||
# You must not mix the two modes at a time; the block must always
|
||||
# return either true/false, or always return a number. It is
|
||||
# undefined which value is actually picked up at each iteration.
|
||||
|
||||
def bsearch(&block)
|
||||
return to_enum :bsearch unless block
|
||||
|
||||
if idx = bsearch_index(&block)
|
||||
self[idx]
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# ary.bsearch_index {|x| block } -> int or nil
|
||||
#
|
||||
# By using binary search, finds an index of a value from this array which
|
||||
# meets the given condition in O(log n) where n is the size of the array.
|
||||
#
|
||||
# It supports two modes, depending on the nature of the block and they are
|
||||
# exactly the same as in the case of #bsearch method with the only difference
|
||||
# being that this method returns the index of the element instead of the
|
||||
# element itself. For more details consult the documentation for #bsearch.
|
||||
|
||||
def bsearch_index(&block)
|
||||
return to_enum :bsearch_index unless block
|
||||
|
||||
low = 0
|
||||
high = size
|
||||
satisfied = false
|
||||
|
||||
while low < high
|
||||
mid = ((low+high)/2).truncate
|
||||
res = block.call self[mid]
|
||||
|
||||
case res
|
||||
when 0 # find-any mode: Found!
|
||||
return mid
|
||||
when Numeric # find-any mode: Continue...
|
||||
in_lower_half = res < 0
|
||||
when true # find-min mode
|
||||
in_lower_half = true
|
||||
satisfied = true
|
||||
when false, nil # find-min mode
|
||||
in_lower_half = false
|
||||
else
|
||||
raise TypeError, 'invalid block result (must be numeric, true, false or nil)'
|
||||
end
|
||||
|
||||
if in_lower_half
|
||||
high = mid
|
||||
else
|
||||
low = mid + 1
|
||||
end
|
||||
end
|
||||
|
||||
satisfied ? low : nil
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# ary.keep_if { |item| block } -> ary
|
||||
# ary.keep_if -> Enumerator
|
||||
#
|
||||
# Deletes every element of +self+ for which the given block evaluates to
|
||||
# +false+.
|
||||
#
|
||||
# See also Array#select!
|
||||
#
|
||||
# If no block is given, an Enumerator is returned instead.
|
||||
#
|
||||
# a = [1, 2, 3, 4, 5]
|
||||
# a.keep_if { |val| val > 3 } #=> [4, 5]
|
||||
|
||||
def keep_if(&block)
|
||||
return to_enum :keep_if unless block
|
||||
|
||||
idx = 0
|
||||
len = self.size
|
||||
while idx < self.size do
|
||||
if block.call(self[idx])
|
||||
idx += 1
|
||||
else
|
||||
self.delete_at(idx)
|
||||
end
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# ary.select! {|item| block } -> ary or nil
|
||||
# ary.select! -> Enumerator
|
||||
#
|
||||
# Invokes the given block passing in successive elements from +self+,
|
||||
# deleting elements for which the block returns a +false+ value.
|
||||
#
|
||||
# If changes were made, it will return +self+, otherwise it returns +nil+.
|
||||
#
|
||||
# See also Array#keep_if
|
||||
#
|
||||
# If no block is given, an Enumerator is returned instead.
|
||||
|
||||
def select!(&block)
|
||||
return to_enum :select! unless block
|
||||
|
||||
result = []
|
||||
idx = 0
|
||||
len = size
|
||||
while idx < len
|
||||
elem = self[idx]
|
||||
result << elem if block.call(elem)
|
||||
idx += 1
|
||||
end
|
||||
return nil if len == result.size
|
||||
self.replace(result)
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# ary.index(val) -> int or nil
|
||||
# ary.index {|item| block } -> int or nil
|
||||
#
|
||||
# Returns the _index_ of the first object in +ary+ such that the object is
|
||||
# <code>==</code> to +obj+.
|
||||
#
|
||||
# If a block is given instead of an argument, returns the _index_ of the
|
||||
# first object for which the block returns +true+. Returns +nil+ if no
|
||||
# match is found.
|
||||
#
|
||||
# ISO 15.2.12.5.14
|
||||
def index(val=NONE, &block)
|
||||
return to_enum(:find_index, val) if !block && val == NONE
|
||||
|
||||
if block
|
||||
idx = 0
|
||||
len = size
|
||||
while idx < len
|
||||
return idx if block.call self[idx]
|
||||
idx += 1
|
||||
end
|
||||
else
|
||||
return self.__ary_index(val)
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# ary.dig(idx, ...) -> object
|
||||
#
|
||||
# Extracts the nested value specified by the sequence of <i>idx</i>
|
||||
# objects by calling +dig+ at each step, returning +nil+ if any
|
||||
# intermediate step is +nil+.
|
||||
#
|
||||
def dig(idx,*args)
|
||||
n = self[idx]
|
||||
if args.size > 0
|
||||
n&.dig(*args)
|
||||
else
|
||||
n
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# ary.permutation { |p| block } -> ary
|
||||
# ary.permutation -> Enumerator
|
||||
# ary.permutation(n) { |p| block } -> ary
|
||||
# ary.permutation(n) -> Enumerator
|
||||
#
|
||||
# When invoked with a block, yield all permutations of length +n+ of the
|
||||
# elements of the array, then return the array itself.
|
||||
#
|
||||
# If +n+ is not specified, yield all permutations of all elements.
|
||||
#
|
||||
# The implementation makes no guarantees about the order in which the
|
||||
# permutations are yielded.
|
||||
#
|
||||
# If no block is given, an Enumerator is returned instead.
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# a = [1, 2, 3]
|
||||
# a.permutation.to_a #=> [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
|
||||
# a.permutation(1).to_a #=> [[1],[2],[3]]
|
||||
# a.permutation(2).to_a #=> [[1,2],[1,3],[2,1],[2,3],[3,1],[3,2]]
|
||||
# a.permutation(3).to_a #=> [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
|
||||
# a.permutation(0).to_a #=> [[]] # one permutation of length 0
|
||||
# a.permutation(4).to_a #=> [] # no permutations of length 4
|
||||
def permutation(n=self.size, &block)
|
||||
return to_enum(:permutation, n) unless block
|
||||
size = self.size
|
||||
if n == 0
|
||||
yield []
|
||||
elsif 0 < n && n <= size
|
||||
i = 0
|
||||
while i<size
|
||||
result = [self[i]]
|
||||
if n-1 > 0
|
||||
ary = self[0...i] + self[i+1..-1]
|
||||
ary.permutation(n-1) do |c|
|
||||
yield result + c
|
||||
end
|
||||
else
|
||||
yield result
|
||||
end
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# ary.combination(n) { |c| block } -> ary
|
||||
# ary.combination(n) -> Enumerator
|
||||
#
|
||||
# When invoked with a block, yields all combinations of length +n+ of elements
|
||||
# from the array and then returns the array itself.
|
||||
#
|
||||
# The implementation makes no guarantees about the order in which the
|
||||
# combinations are yielded.
|
||||
#
|
||||
# If no block is given, an Enumerator is returned instead.
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# a = [1, 2, 3, 4]
|
||||
# a.combination(1).to_a #=> [[1],[2],[3],[4]]
|
||||
# a.combination(2).to_a #=> [[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]]
|
||||
# a.combination(3).to_a #=> [[1,2,3],[1,2,4],[1,3,4],[2,3,4]]
|
||||
# a.combination(4).to_a #=> [[1,2,3,4]]
|
||||
# a.combination(0).to_a #=> [[]] # one combination of length 0
|
||||
# a.combination(5).to_a #=> [] # no combinations of length 5
|
||||
|
||||
def combination(n, &block)
|
||||
return to_enum(:combination, n) unless block
|
||||
size = self.size
|
||||
if n == 0
|
||||
yield []
|
||||
elsif n == 1
|
||||
i = 0
|
||||
while i<size
|
||||
yield [self[i]]
|
||||
i += 1
|
||||
end
|
||||
elsif n <= size
|
||||
i = 0
|
||||
while i<size
|
||||
result = [self[i]]
|
||||
self[i+1..-1].combination(n-1) do |c|
|
||||
yield result + c
|
||||
end
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# ary.transpose -> new_ary
|
||||
#
|
||||
# Assumes that self is an array of arrays and transposes the rows and columns.
|
||||
#
|
||||
# If the length of the subarrays don't match, an IndexError is raised.
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# a = [[1,2], [3,4], [5,6]]
|
||||
# a.transpose #=> [[1, 3, 5], [2, 4, 6]]
|
||||
|
||||
def transpose
|
||||
return [] if empty?
|
||||
|
||||
column_count = nil
|
||||
self.each do |row|
|
||||
raise TypeError unless row.is_a?(Array)
|
||||
column_count ||= row.size
|
||||
raise IndexError, 'element size differs' unless column_count == row.size
|
||||
end
|
||||
|
||||
Array.new(column_count) do |column_index|
|
||||
self.map { |row| row[column_index] }
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# ary.to_h -> Hash
|
||||
# ary.to_h{|item| ... } -> Hash
|
||||
#
|
||||
# Returns the result of interpreting <i>aray</i> as an array of
|
||||
# <tt>[key, value]</tt> pairs. If a block is given, it should
|
||||
# return <tt>[key, value]</tt> pairs to construct a hash.
|
||||
#
|
||||
# [[:foo, :bar], [1, 2]].to_h
|
||||
# # => {:foo => :bar, 1 => 2}
|
||||
# [1, 2].to_h{|x| [x, x*2]}
|
||||
# # => {1 => 2, 2 => 4}
|
||||
#
|
||||
def to_h(&blk)
|
||||
h = {}
|
||||
self.each do |v|
|
||||
v = blk.call(v) if blk
|
||||
raise TypeError, "wrong element type #{v.class}" unless Array === v
|
||||
raise ArgumentError, "wrong array length (expected 2, was #{v.length})" unless v.length == 2
|
||||
h[v[0]] = v[1]
|
||||
end
|
||||
h
|
||||
end
|
||||
|
||||
alias append push
|
||||
alias prepend unshift
|
||||
alias filter! select!
|
||||
end
|
||||
@@ -0,0 +1,200 @@
|
||||
#include <mruby.h>
|
||||
#include <mruby/value.h>
|
||||
#include <mruby/array.h>
|
||||
#include <mruby/range.h>
|
||||
#include <mruby/hash.h>
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* ary.assoc(obj) -> new_ary or nil
|
||||
*
|
||||
* Searches through an array whose elements are also arrays
|
||||
* comparing _obj_ with the first element of each contained array
|
||||
* using obj.==.
|
||||
* Returns the first contained array that matches (that
|
||||
* is, the first associated array),
|
||||
* or +nil+ if no match is found.
|
||||
* See also <code>Array#rassoc</code>.
|
||||
*
|
||||
* s1 = [ "colors", "red", "blue", "green" ]
|
||||
* s2 = [ "letters", "a", "b", "c" ]
|
||||
* s3 = "foo"
|
||||
* a = [ s1, s2, s3 ]
|
||||
* a.assoc("letters") #=> [ "letters", "a", "b", "c" ]
|
||||
* a.assoc("foo") #=> nil
|
||||
*/
|
||||
|
||||
static mrb_value
|
||||
mrb_ary_assoc(mrb_state *mrb, mrb_value ary)
|
||||
{
|
||||
mrb_int i;
|
||||
mrb_value v;
|
||||
mrb_value k = mrb_get_arg1(mrb);
|
||||
|
||||
for (i = 0; i < RARRAY_LEN(ary); ++i) {
|
||||
v = mrb_check_array_type(mrb, RARRAY_PTR(ary)[i]);
|
||||
if (!mrb_nil_p(v) && RARRAY_LEN(v) > 0 &&
|
||||
mrb_equal(mrb, RARRAY_PTR(v)[0], k))
|
||||
return v;
|
||||
}
|
||||
return mrb_nil_value();
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* ary.rassoc(obj) -> new_ary or nil
|
||||
*
|
||||
* Searches through the array whose elements are also arrays. Compares
|
||||
* _obj_ with the second element of each contained array using
|
||||
* <code>==</code>. Returns the first contained array that matches. See
|
||||
* also <code>Array#assoc</code>.
|
||||
*
|
||||
* a = [ [ 1, "one"], [2, "two"], [3, "three"], ["ii", "two"] ]
|
||||
* a.rassoc("two") #=> [2, "two"]
|
||||
* a.rassoc("four") #=> nil
|
||||
*/
|
||||
|
||||
static mrb_value
|
||||
mrb_ary_rassoc(mrb_state *mrb, mrb_value ary)
|
||||
{
|
||||
mrb_int i;
|
||||
mrb_value v;
|
||||
mrb_value value = mrb_get_arg1(mrb);
|
||||
|
||||
for (i = 0; i < RARRAY_LEN(ary); ++i) {
|
||||
v = RARRAY_PTR(ary)[i];
|
||||
if (mrb_array_p(v) &&
|
||||
RARRAY_LEN(v) > 1 &&
|
||||
mrb_equal(mrb, RARRAY_PTR(v)[1], value))
|
||||
return v;
|
||||
}
|
||||
return mrb_nil_value();
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* ary.at(index) -> obj or nil
|
||||
*
|
||||
* Returns the element at _index_. A
|
||||
* negative index counts from the end of +self+. Returns +nil+
|
||||
* if the index is out of range. See also <code>Array#[]</code>.
|
||||
*
|
||||
* a = [ "a", "b", "c", "d", "e" ]
|
||||
* a.at(0) #=> "a"
|
||||
* a.at(-1) #=> "e"
|
||||
*/
|
||||
|
||||
static mrb_value
|
||||
mrb_ary_at(mrb_state *mrb, mrb_value ary)
|
||||
{
|
||||
mrb_int pos;
|
||||
mrb_get_args(mrb, "i", &pos);
|
||||
|
||||
return mrb_ary_entry(ary, pos);
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
mrb_ary_values_at(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
mrb_int argc;
|
||||
mrb_value *argv;
|
||||
|
||||
mrb_get_args(mrb, "*", &argv, &argc);
|
||||
|
||||
return mrb_get_values_at(mrb, self, RARRAY_LEN(self), argc, argv, mrb_ary_ref);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* ary.slice!(index) -> obj or nil
|
||||
* ary.slice!(start, length) -> new_ary or nil
|
||||
* ary.slice!(range) -> new_ary or nil
|
||||
*
|
||||
* Deletes the element(s) given by an +index+ (optionally up to +length+
|
||||
* elements) or by a +range+.
|
||||
*
|
||||
* Returns the deleted object (or objects), or +nil+ if the +index+ is out of
|
||||
* range.
|
||||
*
|
||||
* a = [ "a", "b", "c" ]
|
||||
* a.slice!(1) #=> "b"
|
||||
* a #=> ["a", "c"]
|
||||
* a.slice!(-1) #=> "c"
|
||||
* a #=> ["a"]
|
||||
* a.slice!(100) #=> nil
|
||||
* a #=> ["a"]
|
||||
*/
|
||||
|
||||
static mrb_value
|
||||
mrb_ary_slice_bang(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
struct RArray *a = mrb_ary_ptr(self);
|
||||
mrb_int i, j, k, len, alen;
|
||||
mrb_value val;
|
||||
mrb_value *ptr;
|
||||
mrb_value ary;
|
||||
|
||||
mrb_ary_modify(mrb, a);
|
||||
|
||||
if (mrb_get_argc(mrb) == 1) {
|
||||
mrb_value index = mrb_get_arg1(mrb);
|
||||
|
||||
switch (mrb_type(index)) {
|
||||
case MRB_TT_RANGE:
|
||||
if (mrb_range_beg_len(mrb, index, &i, &len, ARY_LEN(a), TRUE) == MRB_RANGE_OK) {
|
||||
goto delete_pos_len;
|
||||
}
|
||||
else {
|
||||
return mrb_nil_value();
|
||||
}
|
||||
case MRB_TT_FIXNUM:
|
||||
val = mrb_funcall(mrb, self, "delete_at", 1, index);
|
||||
return val;
|
||||
default:
|
||||
val = mrb_funcall(mrb, self, "delete_at", 1, index);
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
mrb_get_args(mrb, "ii", &i, &len);
|
||||
delete_pos_len:
|
||||
alen = ARY_LEN(a);
|
||||
if (i < 0) i += alen;
|
||||
if (i < 0 || alen < i) return mrb_nil_value();
|
||||
if (len < 0) return mrb_nil_value();
|
||||
if (alen == i) return mrb_ary_new(mrb);
|
||||
if (len > alen - i) len = alen - i;
|
||||
|
||||
ary = mrb_ary_new_capa(mrb, len);
|
||||
ptr = ARY_PTR(a);
|
||||
for (j = i, k = 0; k < len; ++j, ++k) {
|
||||
mrb_ary_push(mrb, ary, ptr[j]);
|
||||
}
|
||||
|
||||
ptr += i;
|
||||
for (j = i; j < alen - len; ++j) {
|
||||
*ptr = *(ptr+len);
|
||||
++ptr;
|
||||
}
|
||||
|
||||
mrb_ary_resize(mrb, self, alen - len);
|
||||
return ary;
|
||||
}
|
||||
|
||||
void
|
||||
mrb_mruby_array_ext_gem_init(mrb_state* mrb)
|
||||
{
|
||||
struct RClass * a = mrb->array_class;
|
||||
|
||||
mrb_define_method(mrb, a, "assoc", mrb_ary_assoc, MRB_ARGS_REQ(1));
|
||||
mrb_define_method(mrb, a, "at", mrb_ary_at, MRB_ARGS_REQ(1));
|
||||
mrb_define_method(mrb, a, "rassoc", mrb_ary_rassoc, MRB_ARGS_REQ(1));
|
||||
mrb_define_method(mrb, a, "values_at", mrb_ary_values_at, MRB_ARGS_ANY());
|
||||
mrb_define_method(mrb, a, "slice!", mrb_ary_slice_bang, MRB_ARGS_ARG(1,1));
|
||||
}
|
||||
|
||||
void
|
||||
mrb_mruby_array_ext_gem_final(mrb_state* mrb)
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,415 @@
|
||||
##
|
||||
# Array(Ext) Test
|
||||
|
||||
def assert_permutation_combination(exp, receiver, meth, *args)
|
||||
act = []
|
||||
ret = receiver.__send__(meth, *args) { |v| act << v }
|
||||
assert "assert_#{meth}" do
|
||||
assert_equal(exp, act.sort)
|
||||
assert_same(receiver, ret)
|
||||
end
|
||||
end
|
||||
|
||||
def assert_permutation(exp, receiver, *args)
|
||||
assert_permutation_combination(exp, receiver, :permutation, *args)
|
||||
end
|
||||
|
||||
def assert_combination(exp, receiver, *args)
|
||||
assert_permutation_combination(exp, receiver, :combination, *args)
|
||||
end
|
||||
|
||||
assert("Array#assoc") do
|
||||
s1 = [ "colors", "red", "blue", "green" ]
|
||||
s2 = [ "letters", "a", "b", "c" ]
|
||||
s3 = "foo"
|
||||
a = [ s1, s2, s3 ]
|
||||
|
||||
assert_equal [ "letters", "a", "b", "c" ], a.assoc("letters")
|
||||
assert_nil a.assoc("foo")
|
||||
end
|
||||
|
||||
assert("Array#at") do
|
||||
a = [ "a", "b", "c", "d", "e" ]
|
||||
assert_equal "a", a.at(0)
|
||||
assert_equal "e", a.at(-1)
|
||||
end
|
||||
|
||||
assert("Array#rassoc") do
|
||||
a = [ [ 1, "one"], [2, "two"], [3, "three"], ["ii", "two"] ]
|
||||
|
||||
assert_equal [2, "two"], a.rassoc("two")
|
||||
assert_nil a.rassoc("four")
|
||||
end
|
||||
|
||||
assert("Array#uniq!") do
|
||||
a = [1, 2, 3, 1]
|
||||
a.uniq!
|
||||
assert_equal [1, 2, 3], a
|
||||
|
||||
b = [ "a", "b", "c" ]
|
||||
assert_nil b.uniq!
|
||||
|
||||
c = [["student","sam"], ["student","george"], ["teacher","matz"]]
|
||||
assert_equal [["student", "sam"], ["teacher", "matz"]], c.uniq! { |s| s.first }
|
||||
|
||||
d = [["student","sam"], ["teacher","matz"]]
|
||||
assert_nil d.uniq! { |s| s.first }
|
||||
end
|
||||
|
||||
assert("Array#uniq") do
|
||||
a = [1, 2, 3, 1]
|
||||
assert_equal [1, 2, 3], a.uniq
|
||||
assert_equal [1, 2, 3, 1], a
|
||||
|
||||
b = [["student","sam"], ["student","george"], ["teacher","matz"]]
|
||||
assert_equal [["student", "sam"], ["teacher", "matz"]], b.uniq { |s| s.first }
|
||||
end
|
||||
|
||||
assert("Array#-") do
|
||||
a = [1, 2, 3, 1]
|
||||
b = [1]
|
||||
c = 1
|
||||
|
||||
assert_raise(TypeError) { a - c }
|
||||
assert_equal [2, 3], (a - b)
|
||||
assert_equal [1, 2, 3, 1], a
|
||||
end
|
||||
|
||||
assert("Array#|") do
|
||||
a = [1, 2, 3, 1]
|
||||
b = [1, 4]
|
||||
c = 1
|
||||
|
||||
assert_raise(TypeError) { a | c }
|
||||
assert_equal [1, 2, 3, 4], (a | b)
|
||||
assert_equal [1, 2, 3, 1], a
|
||||
end
|
||||
|
||||
assert("Array#union") do
|
||||
a = [1, 2, 3, 1]
|
||||
b = [1, 4]
|
||||
c = [1, 5]
|
||||
|
||||
assert_equal [1, 2, 3, 4, 5], a.union(b,c)
|
||||
end
|
||||
|
||||
assert("Array#difference") do
|
||||
a = [1, 2, 3, 1, 6, 7]
|
||||
b = [1, 4, 6]
|
||||
c = [1, 5, 7]
|
||||
|
||||
assert_equal [2, 3], a.difference(b,c)
|
||||
end
|
||||
|
||||
assert("Array#&") do
|
||||
a = [1, 2, 3, 1]
|
||||
b = [1, 4]
|
||||
c = 1
|
||||
|
||||
assert_raise(TypeError) { a & c }
|
||||
assert_equal [1], (a & b)
|
||||
assert_equal [1, 2, 3, 1], a
|
||||
end
|
||||
|
||||
assert("Array#intersection") do
|
||||
a = [1, 2, 3, 1, 8, 6, 7, 8]
|
||||
b = [1, 4, 6, 8]
|
||||
c = [1, 5, 7, 8]
|
||||
|
||||
assert_equal [1, 8], a.intersection(b,c)
|
||||
end
|
||||
|
||||
assert("Array#flatten") do
|
||||
assert_equal [1, 2, "3", {4=>5}, :'6'], [1, 2, "3", {4=>5}, :'6'].flatten
|
||||
assert_equal [1, 2, 3, 4, 5, 6], [1, 2, [3, 4, 5], 6].flatten
|
||||
assert_equal [1, 2, 3, 4, 5, 6], [1, 2, [3, [4, 5], 6]].flatten
|
||||
assert_equal [1, [2, [3, [4, [5, [6]]]]]], [1, [2, [3, [4, [5, [6]]]]]].flatten(0)
|
||||
assert_equal [1, 2, [3, [4, [5, [6]]]]], [1, [2, [3, [4, [5, [6]]]]]].flatten(1)
|
||||
assert_equal [1, 2, 3, [4, [5, [6]]]], [1, [2, [3, [4, [5, [6]]]]]].flatten(2)
|
||||
assert_equal [1, 2, 3, 4, [5, [6]]], [1, [2, [3, [4, [5, [6]]]]]].flatten(3)
|
||||
assert_equal [1, 2, 3, 4, 5, [6]], [1, [2, [3, [4, [5, [6]]]]]].flatten(4)
|
||||
assert_equal [1, 2, 3, 4, 5, 6], [1, [2, [3, [4, [5, [6]]]]]].flatten(5)
|
||||
end
|
||||
|
||||
assert("Array#flatten!") do
|
||||
assert_equal [1, 2, 3, 4, 5, 6], [1, 2, [3, [4, 5], 6]].flatten!
|
||||
end
|
||||
|
||||
assert("Array#compact") do
|
||||
a = [1, nil, "2", nil, :t, false, nil]
|
||||
assert_equal [1, "2", :t, false], a.compact
|
||||
assert_equal [1, nil, "2", nil, :t, false, nil], a
|
||||
end
|
||||
|
||||
assert("Array#compact!") do
|
||||
a = [1, nil, "2", nil, :t, false, nil]
|
||||
a.compact!
|
||||
assert_equal [1, "2", :t, false], a
|
||||
end
|
||||
|
||||
assert("Array#fetch") do
|
||||
a = [ 11, 22, 33, 44 ]
|
||||
assert_equal 22, a.fetch(1)
|
||||
assert_equal 44, a.fetch(-1)
|
||||
assert_equal 'cat', a.fetch(4, 'cat')
|
||||
ret = 0
|
||||
a.fetch(100) { |i| ret = i }
|
||||
assert_equal 100, ret
|
||||
assert_raise(IndexError) { a.fetch(100) }
|
||||
end
|
||||
|
||||
assert("Array#fill") do
|
||||
a = [ "a", "b", "c", "d" ]
|
||||
assert_equal ["x", "x", "x", "x"], a.fill("x")
|
||||
assert_equal ["x", "x", "x", "w"], a.fill("w", -1)
|
||||
assert_equal ["x", "x", "z", "z"], a.fill("z", 2, 2)
|
||||
assert_equal ["y", "y", "z", "z"], a.fill("y", 0..1)
|
||||
assert_equal [0, 1, 4, 9], a.fill { |i| i*i }
|
||||
assert_equal [0, 1, 8, 27], a.fill(-2) { |i| i*i*i }
|
||||
assert_equal [0, 2, 3, 27], a.fill(1, 2) { |i| i+1 }
|
||||
assert_equal [1, 2, 3, 27], a.fill(0..1) { |i| i+1 }
|
||||
assert_raise(ArgumentError) { a.fill }
|
||||
|
||||
assert_equal([0, 1, 2, 3, -1, 5], [0, 1, 2, 3, 4, 5].fill(-1, -2, 1))
|
||||
assert_equal([0, 1, 2, 3, -1, -1, -1], [0, 1, 2, 3, 4, 5].fill(-1, -2, 3))
|
||||
assert_equal([0, 1, 2, -1, -1, 5], [0, 1, 2, 3, 4, 5].fill(-1, 3..4))
|
||||
assert_equal([0, 1, 2, -1, 4, 5], [0, 1, 2, 3, 4, 5].fill(-1, 3...4))
|
||||
assert_equal([0, 1, -1, -1, -1, 5], [0, 1, 2, 3, 4, 5].fill(-1, 2..-2))
|
||||
assert_equal([0, 1, -1, -1, 4, 5], [0, 1, 2, 3, 4, 5].fill(-1, 2...-2))
|
||||
assert_equal([0, 1, 2, 13, 14, 5], [0, 1, 2, 3, 4, 5].fill(3..4){|i| i+10})
|
||||
assert_equal([0, 1, 2, 13, 4, 5], [0, 1, 2, 3, 4, 5].fill(3...4){|i| i+10})
|
||||
assert_equal([0, 1, 12, 13, 14, 5], [0, 1, 2, 3, 4, 5].fill(2..-2){|i| i+10})
|
||||
assert_equal([0, 1, 12, 13, 4, 5], [0, 1, 2, 3, 4, 5].fill(2...-2){|i| i+10})
|
||||
|
||||
assert_equal [1, 2, 3, 4, 'x', 'x'], [1, 2, 3, 4, 5, 6].fill('x', -2..-1)
|
||||
assert_equal [1, 2, 3, 4, 'x', 6], [1, 2, 3, 4, 5, 6].fill('x', -2...-1)
|
||||
assert_equal [1, 2, 3, 4, 5, 6], [1, 2, 3, 4, 5, 6].fill('x', -2...-2)
|
||||
assert_equal [1, 2, 3, 4, 'x', 6], [1, 2, 3, 4, 5, 6].fill('x', -2..-2)
|
||||
assert_equal [1, 2, 3, 4, 5, 6], [1, 2, 3, 4, 5, 6].fill('x', -2..0)
|
||||
end
|
||||
|
||||
assert("Array#reverse_each") do
|
||||
a = [ "a", "b", "c", "d" ]
|
||||
b = []
|
||||
a.reverse_each do |i|
|
||||
b << i
|
||||
end
|
||||
assert_equal [ "d", "c", "b", "a" ], b
|
||||
end
|
||||
|
||||
assert("Array#rotate") do
|
||||
a = ["a", "b", "c", "d"]
|
||||
assert_equal ["b", "c", "d", "a"], a.rotate
|
||||
assert_equal ["a", "b", "c", "d"], a
|
||||
assert_equal ["c", "d", "a", "b"], a.rotate(2)
|
||||
assert_equal ["b", "c", "d", "a"], a.rotate(-3)
|
||||
assert_equal ["c", "d", "a", "b"], a.rotate(10)
|
||||
assert_equal [], [].rotate
|
||||
end
|
||||
|
||||
assert("Array#rotate!") do
|
||||
a = ["a", "b", "c", "d"]
|
||||
assert_equal ["b", "c", "d", "a"], a.rotate!
|
||||
assert_equal ["b", "c", "d", "a"], a
|
||||
assert_equal ["d", "a", "b", "c"], a.rotate!(2)
|
||||
assert_equal ["a", "b", "c", "d"], a.rotate!(-3)
|
||||
assert_equal ["c", "d", "a", "b"], a.rotate(10)
|
||||
assert_equal [], [].rotate!
|
||||
end
|
||||
|
||||
assert("Array#delete_if") do
|
||||
a = [1, 2, 3, 4, 5]
|
||||
assert_equal [1, 2, 3, 4, 5], a.delete_if { false }
|
||||
assert_equal [1, 2, 3, 4, 5], a
|
||||
|
||||
a = [1, 2, 3, 4, 5]
|
||||
assert_equal [], a.delete_if { true }
|
||||
assert_equal [], a
|
||||
|
||||
a = [1, 2, 3, 4, 5]
|
||||
assert_equal [1, 2, 3], a.delete_if { |i| i > 3 }
|
||||
assert_equal [1, 2, 3], a
|
||||
end
|
||||
|
||||
assert("Array#reject!") do
|
||||
a = [1, 2, 3, 4, 5]
|
||||
assert_nil a.reject! { false }
|
||||
assert_equal [1, 2, 3, 4, 5], a
|
||||
|
||||
a = [1, 2, 3, 4, 5]
|
||||
assert_equal [], a.reject! { true }
|
||||
assert_equal [], a
|
||||
|
||||
a = [1, 2, 3, 4, 5]
|
||||
assert_equal [1, 2, 3], a.reject! { |val| val > 3 }
|
||||
assert_equal [1, 2, 3], a
|
||||
end
|
||||
|
||||
assert("Array#insert") do
|
||||
a = ["a", "b", "c", "d"]
|
||||
assert_equal ["a", "b", 99, "c", "d"], a.insert(2, 99)
|
||||
assert_equal ["a", "b", 99, "c", 1, 2, 3, "d"], a.insert(-2, 1, 2, 3)
|
||||
|
||||
b = ["a", "b", "c", "d"]
|
||||
assert_equal ["a", "b", "c", "d", nil, nil, 99], b.insert(6, 99)
|
||||
end
|
||||
|
||||
assert("Array#bsearch") do
|
||||
# Find minimum mode
|
||||
a = [0, 2, 4]
|
||||
assert_equal 0, a.bsearch{ |x| x >= -1 }
|
||||
assert_equal 0, a.bsearch{ |x| x >= 0 }
|
||||
assert_equal 2, a.bsearch{ |x| x >= 1 }
|
||||
assert_equal 2, a.bsearch{ |x| x >= 2 }
|
||||
assert_equal 4, a.bsearch{ |x| x >= 3 }
|
||||
assert_equal 4, a.bsearch{ |x| x >= 4 }
|
||||
assert_nil a.bsearch{ |x| x >= 5 }
|
||||
|
||||
# Find any mode
|
||||
a = [0, 4, 8]
|
||||
def between(lo, x, hi)
|
||||
if x < lo
|
||||
1
|
||||
elsif x > hi
|
||||
-1
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
assert_nil a.bsearch{ |x| between(-3, x, -1) }
|
||||
assert_equal 0, a.bsearch{ |x| between(-1, x, 1) }
|
||||
assert_nil a.bsearch{ |x| between( 1, x, 3) }
|
||||
assert_equal 4, a.bsearch{ |x| between( 3, x, 5) }
|
||||
assert_nil a.bsearch{ |x| between( 5, x, 7) }
|
||||
assert_equal 8, a.bsearch{ |x| between( 7, x, 9) }
|
||||
assert_nil a.bsearch{ |x| between( 9, x, 11) }
|
||||
|
||||
assert_equal 0, a.bsearch{ |x| between( 0, x, 3) }
|
||||
assert_equal 4, a.bsearch{ |x| between( 0, x, 4) }
|
||||
assert_equal 4, a.bsearch{ |x| between( 4, x, 8) }
|
||||
assert_equal 8, a.bsearch{ |x| between( 5, x, 8) }
|
||||
|
||||
# Invalid block result
|
||||
assert_raise TypeError, 'invalid block result (must be numeric, true, false or nil)' do
|
||||
a.bsearch{ 'I like to watch the world burn' }
|
||||
end
|
||||
end
|
||||
|
||||
# tested through Array#bsearch
|
||||
#assert("Array#bsearch_index") do
|
||||
#end
|
||||
|
||||
assert("Array#keep_if") do
|
||||
a = [1, 2, 3, 4, 5]
|
||||
assert_equal [1, 2, 3, 4, 5], a.keep_if { true }
|
||||
assert_equal [1, 2, 3, 4, 5], a
|
||||
|
||||
a = [1, 2, 3, 4, 5]
|
||||
assert_equal [], a.keep_if { false }
|
||||
assert_equal [], a
|
||||
|
||||
a = [1, 2, 3, 4, 5]
|
||||
assert_equal [4, 5], a.keep_if { |val| val > 3 }
|
||||
assert_equal [4, 5], a
|
||||
end
|
||||
|
||||
assert("Array#select!") do
|
||||
a = [1, 2, 3, 4, 5]
|
||||
assert_nil a.select! { true }
|
||||
assert_equal [1, 2, 3, 4, 5], a
|
||||
|
||||
a = [1, 2, 3, 4, 5]
|
||||
assert_equal [], a.select! { false }
|
||||
assert_equal [], a
|
||||
|
||||
a = [1, 2, 3, 4, 5]
|
||||
assert_equal [4, 5], a.select! { |val| val > 3 }
|
||||
assert_equal [4, 5], a
|
||||
end
|
||||
|
||||
assert('Array#values_at') do
|
||||
a = %w{red green purple white none}
|
||||
|
||||
assert_equal %w{red purple none}, a.values_at(0, 2, 4)
|
||||
assert_equal ['green', 'white', nil, nil], a.values_at(1, 3, 5, 7)
|
||||
assert_equal ['none', 'white', 'white', nil], a.values_at(-1, -2, -2, -7)
|
||||
assert_equal ['none', nil, nil, 'red', 'green', 'purple'], a.values_at(4..6, 0...3)
|
||||
assert_raise(TypeError) { a.values_at 'tt' }
|
||||
end
|
||||
|
||||
assert('Array#to_h') do
|
||||
assert_equal({}, [].to_h)
|
||||
assert_equal({a: 1, b:2}, [[:a, 1], [:b, 2]].to_h)
|
||||
|
||||
assert_raise(TypeError) { [1].to_h }
|
||||
assert_raise(ArgumentError) { [[1]].to_h }
|
||||
end
|
||||
|
||||
assert("Array#index (block)") do
|
||||
assert_nil (1..10).to_a.index { |i| i % 5 == 0 and i % 7 == 0 }
|
||||
assert_equal 34, (1..100).to_a.index { |i| i % 5 == 0 and i % 7 == 0 }
|
||||
end
|
||||
|
||||
assert("Array#dig") do
|
||||
h = [[[1]], 0]
|
||||
assert_equal(1, h.dig(0, 0, 0))
|
||||
assert_nil(h.dig(2, 0))
|
||||
assert_raise(TypeError) {h.dig(:a)}
|
||||
end
|
||||
|
||||
assert("Array#slice!") do
|
||||
a = [1, 2, 3]
|
||||
b = a.slice!(0)
|
||||
c = [1, 2, 3, 4, 5]
|
||||
d = c.slice!(0, 2)
|
||||
e = [1, 2, 3, 4, 5]
|
||||
f = e.slice!(1..3)
|
||||
g = [1, 2, 3]
|
||||
h = g.slice!(-1)
|
||||
i = [1, 2, 3]
|
||||
j = i.slice!(0, -1)
|
||||
|
||||
assert_equal(a, [2, 3])
|
||||
assert_equal(b, 1)
|
||||
assert_equal(c, [3, 4, 5])
|
||||
assert_equal(d, [1, 2])
|
||||
assert_equal(e, [1, 5])
|
||||
assert_equal(f, [2, 3, 4])
|
||||
assert_equal(g, [1, 2])
|
||||
assert_equal(h, 3)
|
||||
assert_equal(i, [1, 2, 3])
|
||||
assert_equal(j, nil)
|
||||
end
|
||||
|
||||
assert("Array#permutation") do
|
||||
a = [1, 2, 3]
|
||||
assert_permutation([[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]], a)
|
||||
assert_permutation([[1],[2],[3]], a, 1)
|
||||
assert_permutation([[1,2],[1,3],[2,1],[2,3],[3,1],[3,2]], a, 2)
|
||||
assert_permutation([[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]], a, 3)
|
||||
assert_permutation([[]], a, 0)
|
||||
assert_permutation([], a, 4)
|
||||
assert_permutation([], a, -1)
|
||||
end
|
||||
|
||||
assert("Array#combination") do
|
||||
a = [1, 2, 3, 4]
|
||||
assert_combination([[1],[2],[3],[4]], a, 1)
|
||||
assert_combination([[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]], a, 2)
|
||||
assert_combination([[1,2,3],[1,2,4],[1,3,4],[2,3,4]], a, 3)
|
||||
assert_combination([[1,2,3,4]], a, 4)
|
||||
assert_combination([[]], a, 0)
|
||||
assert_combination([], a, 5)
|
||||
assert_combination([], a, -1)
|
||||
end
|
||||
|
||||
assert('Array#transpose') do
|
||||
assert_equal([].transpose, [])
|
||||
assert_equal([[]].transpose, [])
|
||||
assert_equal([[1]].transpose, [[1]])
|
||||
assert_equal([[1,2,3]].transpose, [[1], [2], [3]])
|
||||
assert_equal([[1], [2], [3]].transpose, [[1,2,3]])
|
||||
assert_equal([[1,2], [3,4], [5,6]].transpose, [[1,3,5], [2,4,6]])
|
||||
assert_raise(TypeError) { [1].transpose }
|
||||
assert_raise(IndexError) { [[1], [2,3,4]].transpose }
|
||||
end
|
||||
@@ -0,0 +1,26 @@
|
||||
unless MRuby::Build.current.kind_of?(MRuby::CrossBuild)
|
||||
MRuby::Gem::Specification.new('mruby-bin-config') do |spec|
|
||||
name = 'mruby-config'
|
||||
spec.license = 'MIT'
|
||||
spec.author = 'mruby developers'
|
||||
spec.summary = "#{name} command"
|
||||
|
||||
mruby_config_dir = "#{build.build_dir}/bin"
|
||||
mruby_config = name + (ENV['OS'] == 'Windows_NT' ? '.bat' : '')
|
||||
mruby_config_path = "#{mruby_config_dir}/#{mruby_config}"
|
||||
make_cfg = "#{build.build_dir}/lib/libmruby.flags.mak"
|
||||
tmplt_path = "#{__dir__}/#{mruby_config}"
|
||||
build.bins << mruby_config
|
||||
|
||||
directory mruby_config_dir
|
||||
|
||||
file mruby_config_path => [mruby_config_dir, make_cfg, tmplt_path] do |t|
|
||||
config = Hash[File.readlines(make_cfg).map!(&:chomp).map! {|l|
|
||||
l.gsub('\\"', '"').split(' = ', 2).map! {|s| s.sub(/^(?=.)/, 'echo ')}
|
||||
}]
|
||||
tmplt = File.read(tmplt_path)
|
||||
File.write(t.name, tmplt.gsub(/(#{Regexp.union(*config.keys)})\b/, config))
|
||||
chmod(0755, t.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,20 @@
|
||||
#!/bin/sh
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case $1 in
|
||||
--cflags) echo MRUBY_CFLAGS;;
|
||||
--ldflags) echo MRUBY_LDFLAGS;;
|
||||
--ldflags-before-libs) echo MRUBY_LDFLAGS_BEFORE_LIBS;;
|
||||
--libs) echo MRUBY_LIBS;;
|
||||
--libmruby-path) echo MRUBY_LIBMRUBY_PATH;;
|
||||
--help) echo "Usage: mruby-config [switches]"
|
||||
echo " switches:"
|
||||
echo " --cflags print flags passed to compiler"
|
||||
echo " --ldflags print flags passed to linker"
|
||||
echo " --ldflags-before-libs print flags passed to linker before linked libraries"
|
||||
echo " --libs print linked libraries"
|
||||
echo " --libmruby-path print libmruby path"
|
||||
exit 0;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
@@ -0,0 +1,42 @@
|
||||
@echo off
|
||||
|
||||
:top
|
||||
shift
|
||||
if "%0" equ "" goto :eof
|
||||
if "%0" equ "--cflags" goto cflags
|
||||
if "%0" equ "--ldflags" goto ldflags
|
||||
if "%0" equ "--ldflags-before-libs" goto ldflagsbeforelibs
|
||||
if "%0" equ "--libs" goto libs
|
||||
if "%0" equ "--libmruby-path" goto libmrubypath
|
||||
if "%0" equ "--help" goto showhelp
|
||||
echo Invalid Option
|
||||
goto :eof
|
||||
|
||||
:cflags
|
||||
echo MRUBY_CFLAGS
|
||||
goto top
|
||||
|
||||
:libs
|
||||
echo MRUBY_LIBS
|
||||
goto top
|
||||
|
||||
:ldflags
|
||||
echo MRUBY_LDFLAGS
|
||||
goto top
|
||||
|
||||
:ldflagsbeforelibs
|
||||
echo MRUBY_LDFLAGS_BEFORE_LIBS
|
||||
goto top
|
||||
|
||||
:libmrubypath
|
||||
echo MRUBY_LIBMRUBY_PATH
|
||||
goto top
|
||||
|
||||
:showhelp
|
||||
echo Usage: mruby-config [switches]
|
||||
echo switches:
|
||||
echo --cflags print flags passed to compiler
|
||||
echo --ldflags print flags passed to linker
|
||||
echo --ldflags-before-libs print flags passed to linker before linked libraries
|
||||
echo --libs print linked libraries
|
||||
echo --libmruby-path print libmruby path
|
||||
@@ -0,0 +1,286 @@
|
||||
require 'open3'
|
||||
require 'tempfile'
|
||||
|
||||
class BinTest_MrubyBinDebugger
|
||||
@debug1=false
|
||||
@debug2=true
|
||||
@debug3=true
|
||||
def self.test(rubysource, testcase)
|
||||
script, bin = Tempfile.new(['test', '.rb']), Tempfile.new(['test', '.mrb'])
|
||||
|
||||
# .rb
|
||||
script.write rubysource
|
||||
script.flush
|
||||
|
||||
# compile
|
||||
`./bin/mrbc -g -o "#{bin.path}" "#{script.path}"`
|
||||
|
||||
# add mrdb quit
|
||||
testcase << {:cmd=>"quit"}
|
||||
|
||||
stdin_data = testcase.map{|t| t[:cmd]}.join("\n") << "\n"
|
||||
|
||||
["bin/mrdb #{script.path}","bin/mrdb -b #{bin.path}"].each do |cmd|
|
||||
o, s = Open3.capture2(cmd, :stdin_data => stdin_data)
|
||||
|
||||
exp_vals = testcase.map{|t| t.fetch(:exp, nil)}
|
||||
unexp_vals = testcase.map{|t| t.fetch(:unexp, nil)}
|
||||
|
||||
if @debug1
|
||||
o.split("\n").each_with_index do |i,actual|
|
||||
p [i,actual]
|
||||
end
|
||||
end
|
||||
# compare actual / expected
|
||||
o.split("\n").each do |actual|
|
||||
next if actual.empty?
|
||||
exp = exp_vals.shift
|
||||
if @debug2
|
||||
a = true
|
||||
a = actual.include?(exp) unless exp.nil?
|
||||
p [actual, exp] unless a
|
||||
end
|
||||
assert_true actual.include?(exp) unless exp.nil?
|
||||
end
|
||||
# compare actual / unexpected
|
||||
o.split("\n").each do |actual|
|
||||
next if actual.empty?
|
||||
unexp = unexp_vals.shift
|
||||
if @debug3
|
||||
a = false
|
||||
a = actual.include?(unexp) unless unexp.nil?
|
||||
p [actual, unexp] if a
|
||||
end
|
||||
assert_false actual.include?(unexp) unless unexp.nil?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
INVCMD = "invalid command"
|
||||
|
||||
assert('mruby-bin-debugger(mrdb) command line') do
|
||||
# ruby source
|
||||
src = "foo = 'foo'\n"
|
||||
|
||||
str = ""
|
||||
103.times {
|
||||
str += "1234567890"
|
||||
}
|
||||
cmd = "p a=#{str}"
|
||||
|
||||
# test case
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>cmd[0...1023], :unexp=>'command line too long.'}])
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>cmd[0...1024], :unexp=>'command line too long.'}])
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>cmd[0...1025], :exp=>'command line too long.'}])
|
||||
end
|
||||
|
||||
assert('mruby-bin-debugger(mrdb) command: "break"') do
|
||||
# ruby source
|
||||
src = "foo = 'foo'\n"
|
||||
|
||||
# test case
|
||||
tc = []
|
||||
tc << {:cmd=>"b", :unexp=>INVCMD}
|
||||
tc << {:cmd=>"br", :unexp=>INVCMD}
|
||||
tc << {:cmd=>"brea", :unexp=>INVCMD}
|
||||
tc << {:cmd=>"break", :unexp=>INVCMD}
|
||||
BinTest_MrubyBinDebugger.test(src, tc)
|
||||
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"bl", :exp=>INVCMD}])
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"breaka", :exp=>INVCMD}])
|
||||
end
|
||||
|
||||
assert('mruby-bin-debugger(mrdb) command: "continue"') do
|
||||
# ruby source
|
||||
src = "foo = 'foo'\n"
|
||||
|
||||
# test case
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"c", :unexp=>INVCMD}])
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"co", :unexp=>INVCMD}])
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"continu", :unexp=>INVCMD}])
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"continue", :unexp=>INVCMD}])
|
||||
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"cn", :exp=>INVCMD}])
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"continuee", :exp=>INVCMD}])
|
||||
end
|
||||
|
||||
assert('mruby-bin-debugger(mrdb) command: "delete"') do
|
||||
# ruby source
|
||||
src = "foo = 'foo'\n"
|
||||
|
||||
# test case
|
||||
tc = []
|
||||
tc << {:cmd=>"d 1", :unexp=>INVCMD}
|
||||
tc << {:cmd=>"de 1", :unexp=>INVCMD}
|
||||
tc << {:cmd=>"delet 1", :unexp=>INVCMD}
|
||||
tc << {:cmd=>"delete 1", :unexp=>INVCMD}
|
||||
BinTest_MrubyBinDebugger.test(src, tc)
|
||||
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"dd 1", :exp=>INVCMD}])
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"deletee 1", :exp=>INVCMD}])
|
||||
end
|
||||
|
||||
assert('mruby-bin-debugger(mrdb) command: "disable"') do
|
||||
# ruby source
|
||||
src = "foo = 'foo'\n"
|
||||
|
||||
# test case
|
||||
tc = []
|
||||
tc << {:cmd=>"dis", :unexp=>INVCMD}
|
||||
tc << {:cmd=>"disa", :unexp=>INVCMD}
|
||||
tc << {:cmd=>"disabl", :unexp=>INVCMD}
|
||||
tc << {:cmd=>"disable", :unexp=>INVCMD}
|
||||
BinTest_MrubyBinDebugger.test(src, tc)
|
||||
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"di", :exp=>INVCMD}])
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"disb", :exp=>INVCMD}])
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"disablee", :exp=>INVCMD}])
|
||||
end
|
||||
|
||||
assert('mruby-bin-debugger(mrdb) command: "enable"') do
|
||||
# ruby source
|
||||
src = "foo = 'foo'\n"
|
||||
|
||||
# test case
|
||||
tc = []
|
||||
tc << {:cmd=>"en", :unexp=>INVCMD}
|
||||
tc << {:cmd=>"ena", :unexp=>INVCMD}
|
||||
tc << {:cmd=>"enabl", :unexp=>INVCMD}
|
||||
tc << {:cmd=>"enable", :unexp=>INVCMD}
|
||||
BinTest_MrubyBinDebugger.test(src, tc)
|
||||
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"e", :exp=>INVCMD}])
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"enb", :exp=>INVCMD}])
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"enablee", :exp=>INVCMD}])
|
||||
end
|
||||
|
||||
assert('mruby-bin-debugger(mrdb) command: "eval"') do
|
||||
# ruby source
|
||||
src = "foo = 'foo'\n"
|
||||
|
||||
# test case
|
||||
tc = []
|
||||
tc << {:cmd=>"ev", :unexp=>INVCMD}
|
||||
tc << {:cmd=>"eva", :unexp=>INVCMD}
|
||||
tc << {:cmd=>"eval", :unexp=>INVCMD}
|
||||
BinTest_MrubyBinDebugger.test(src, tc)
|
||||
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"e", :exp=>INVCMD}])
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"evl", :exp=>INVCMD}])
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"evall", :exp=>INVCMD}])
|
||||
end
|
||||
|
||||
assert('mruby-bin-debugger(mrdb) command: "help"') do
|
||||
# ruby source
|
||||
src = "foo = 'foo'\n"
|
||||
|
||||
# test case
|
||||
tc = []
|
||||
tc << {:cmd=>"h", :unexp=>INVCMD}
|
||||
tc << {:cmd=>"he", :unexp=>INVCMD}
|
||||
tc << {:cmd=>"hel", :unexp=>INVCMD}
|
||||
tc << {:cmd=>"help", :unexp=>INVCMD}
|
||||
BinTest_MrubyBinDebugger.test(src, tc)
|
||||
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"hl", :exp=>INVCMD}])
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"helpp", :exp=>INVCMD}])
|
||||
end
|
||||
|
||||
assert('mruby-bin-debugger(mrdb) command: "info breakpoints"') do
|
||||
# ruby source
|
||||
src = "foo = 'foo'\n"
|
||||
|
||||
# test case
|
||||
tc = []
|
||||
tc << {:cmd=>"i b", :unexp=>INVCMD}
|
||||
tc << {:cmd=>"in b", :unexp=>INVCMD}
|
||||
tc << {:cmd=>"i br", :unexp=>INVCMD}
|
||||
tc << {:cmd=>"inf breakpoint", :unexp=>INVCMD}
|
||||
tc << {:cmd=>"info breakpoints", :unexp=>INVCMD}
|
||||
BinTest_MrubyBinDebugger.test(src, tc)
|
||||
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"ii b", :exp=>INVCMD}])
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"i bb", :exp=>INVCMD}])
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"infoo breakpoints", :exp=>INVCMD}])
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"info breakpointss", :exp=>INVCMD}])
|
||||
end
|
||||
|
||||
assert('mruby-bin-debugger(mrdb) command: "list"') do
|
||||
# ruby source
|
||||
src = "foo = 'foo'\n"
|
||||
|
||||
# test case
|
||||
tc = []
|
||||
tc << {:cmd=>"l", :unexp=>INVCMD}
|
||||
tc << {:cmd=>"li", :unexp=>INVCMD}
|
||||
tc << {:cmd=>"lis", :unexp=>INVCMD}
|
||||
tc << {:cmd=>"list", :unexp=>INVCMD}
|
||||
BinTest_MrubyBinDebugger.test(src, tc)
|
||||
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"ll", :exp=>INVCMD}])
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"listt", :exp=>INVCMD}])
|
||||
end
|
||||
|
||||
assert('mruby-bin-debugger(mrdb) command: "print"') do
|
||||
# ruby source
|
||||
src = "foo = 'foo'\n"
|
||||
|
||||
# test case
|
||||
tc = []
|
||||
tc << {:cmd=>"p", :unexp=>INVCMD}
|
||||
tc << {:cmd=>"pr", :unexp=>INVCMD}
|
||||
tc << {:cmd=>"prin", :unexp=>INVCMD}
|
||||
tc << {:cmd=>"print", :unexp=>INVCMD}
|
||||
BinTest_MrubyBinDebugger.test(src, tc)
|
||||
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"pp", :exp=>INVCMD}])
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"printt", :exp=>INVCMD}])
|
||||
end
|
||||
|
||||
assert('mruby-bin-debugger(mrdb) command: "quit"') do
|
||||
# ruby source
|
||||
src = "foo = 'foo'\n"
|
||||
|
||||
# test case
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"q", :unexp=>INVCMD}])
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"qu", :unexp=>INVCMD}])
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"qui", :unexp=>INVCMD}])
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"quit", :unexp=>INVCMD}])
|
||||
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"qq", :exp=>INVCMD}])
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"quitt", :exp=>INVCMD}])
|
||||
end
|
||||
|
||||
assert('mruby-bin-debugger(mrdb) command: "run"') do
|
||||
# ruby source
|
||||
src = "foo = 'foo'\n"
|
||||
|
||||
# test case
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"r", :unexp=>INVCMD}])
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"ru", :unexp=>INVCMD}])
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"run", :unexp=>INVCMD}])
|
||||
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"rr", :exp=>INVCMD}])
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"runn", :exp=>INVCMD}])
|
||||
end
|
||||
|
||||
assert('mruby-bin-debugger(mrdb) command: "step"') do
|
||||
# ruby source
|
||||
src = <<"SRC"
|
||||
while true
|
||||
foo = 'foo'
|
||||
end
|
||||
SRC
|
||||
|
||||
# test case
|
||||
tc = []
|
||||
tc << {:cmd=>"s", :unexp=>INVCMD}
|
||||
tc << {:cmd=>"st", :unexp=>INVCMD}
|
||||
tc << {:cmd=>"ste", :unexp=>INVCMD}
|
||||
tc << {:cmd=>"step", :unexp=>INVCMD}
|
||||
BinTest_MrubyBinDebugger.test(src, tc)
|
||||
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"ss", :exp=>INVCMD}])
|
||||
BinTest_MrubyBinDebugger.test(src, [{:cmd=>"stepp", :exp=>INVCMD}])
|
||||
end
|
||||
@@ -0,0 +1,703 @@
|
||||
require 'open3'
|
||||
require 'tempfile'
|
||||
require 'strscan'
|
||||
|
||||
class BinTest_MrubyBinDebugger
|
||||
# @debug1=false
|
||||
# @debug2=true
|
||||
def self.test(rubysource, testcase)
|
||||
script, bin = Tempfile.new(['test', '.rb']), Tempfile.new(['test', '.mrb'])
|
||||
|
||||
# .rb
|
||||
script.write rubysource
|
||||
script.flush
|
||||
|
||||
# compile
|
||||
`./bin/mrbc -g -o "#{bin.path}" "#{script.path}"`
|
||||
|
||||
# add mrdb quit
|
||||
testcase << {:cmd=>"quit"}
|
||||
|
||||
stdin_data = testcase.map{|t| t[:cmd]}.join("\n") << "\n"
|
||||
|
||||
prompt = /^\(#{Regexp.escape(script.path)}:\d+\) /
|
||||
["bin/mrdb #{script.path}","bin/mrdb -b #{bin.path}"].each do |cmd|
|
||||
o, s = Open3.capture2(cmd, :stdin_data => stdin_data)
|
||||
scanner = StringScanner.new(o)
|
||||
scanner.skip_until(prompt)
|
||||
testcase.each do |tc|
|
||||
exp = tc[:exp]
|
||||
if exp
|
||||
act = scanner.scan_until(/\n/)
|
||||
break unless assert_operator act, :start_with?, exp
|
||||
end
|
||||
scanner.skip_until(prompt)
|
||||
end
|
||||
|
||||
=begin
|
||||
if @debug1
|
||||
o.split("\n").each_with_index do |i,actual|
|
||||
p [i,actual]
|
||||
end
|
||||
end
|
||||
# compare actual / expected
|
||||
o.split("\n").each do |actual|
|
||||
next if actual.empty?
|
||||
exp = exp_vals.shift
|
||||
if @debug2
|
||||
a = true
|
||||
a = actual.include?(exp) unless exp.nil?
|
||||
p [actual, exp] unless a
|
||||
end
|
||||
assert_true actual.include?(exp) unless exp.nil?
|
||||
end
|
||||
=end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
assert('mruby-bin-debugger(print) invalid arguments') do
|
||||
# ruby source
|
||||
src = "foo = 'foo'\n"
|
||||
|
||||
# test case
|
||||
tc = []
|
||||
tc << {:cmd=>"p", :exp=>"Parameter not specified."}
|
||||
|
||||
BinTest_MrubyBinDebugger.test(src, tc)
|
||||
end
|
||||
|
||||
assert('mruby-bin-debugger(print) nomal') do
|
||||
# ruby source
|
||||
src = <<"SRC"
|
||||
foo = 'foo'
|
||||
bar = foo
|
||||
baz = bar
|
||||
SRC
|
||||
|
||||
# test case
|
||||
tc = []
|
||||
tc << {:cmd=>"s"}
|
||||
tc << {:cmd=>"p (1+2)", :exp=>'$1 = 3'}
|
||||
tc << {:cmd=>"p foo", :exp=>'$2 = "foo"'}
|
||||
tc << {:cmd=>"p foo*=2", :exp=>'$3 = "foofoo"'}
|
||||
tc << {:cmd=>"s"}
|
||||
tc << {:cmd=>"p bar", :exp=>'$4 = "foofoo"'}
|
||||
|
||||
BinTest_MrubyBinDebugger.test(src, tc)
|
||||
end
|
||||
|
||||
assert('mruby-bin-debugger(print) error') do
|
||||
# ruby source
|
||||
src = "foo = 'foo'\n"
|
||||
|
||||
# test case
|
||||
tc = []
|
||||
tc << {:cmd=>"p (1+2", :exp=>'$1 = line 1: syntax error'}
|
||||
tc << {:cmd=>"p bar", :exp=>'$2 = undefined method'}
|
||||
|
||||
BinTest_MrubyBinDebugger.test(src, tc)
|
||||
end
|
||||
|
||||
# Kernel#instance_eval(string) does't work multiple statements.
|
||||
=begin
|
||||
assert('mruby-bin-debugger(print) multiple statements') do
|
||||
# ruby source
|
||||
src = <<"SRC"
|
||||
x = 0
|
||||
y = 0
|
||||
z = 0
|
||||
SRC
|
||||
|
||||
# test case
|
||||
tc = []
|
||||
tc << {:cmd=>"s",}
|
||||
tc << {:cmd=>"p x=1;x+=2", :exp=>"3"}
|
||||
tc << {:cmd=>"s",}
|
||||
tc << {:cmd=>"p x", :exp=>"3"}
|
||||
|
||||
BinTest_MrubyBinDebugger.test(src, tc)
|
||||
end
|
||||
=end
|
||||
|
||||
assert('mruby-bin-debugger(print) scope:top') do
|
||||
# ruby source (bp is break point)
|
||||
src = "bp=nil\n"
|
||||
|
||||
# test case
|
||||
tc = []
|
||||
tc << {:cmd=>"p self", :exp=>'$1 = main'}
|
||||
|
||||
BinTest_MrubyBinDebugger.test(src, tc)
|
||||
end
|
||||
|
||||
assert('mruby-bin-debugger(print) scope:class') do
|
||||
# ruby source (bp is break point)
|
||||
src = <<"SRC"
|
||||
class TestClassScope
|
||||
bp = nil
|
||||
end
|
||||
SRC
|
||||
|
||||
# test case
|
||||
tc = []
|
||||
tc << {:cmd=>"s"}
|
||||
tc << {:cmd=>"p self", :exp=>'$1 = TestClassScope'}
|
||||
|
||||
BinTest_MrubyBinDebugger.test(src, tc)
|
||||
end
|
||||
|
||||
assert('mruby-bin-debugger(print) scope:module') do
|
||||
# ruby source (bp is break point)
|
||||
src = <<"SRC"
|
||||
class TestModuleScope
|
||||
bp = nil
|
||||
end
|
||||
SRC
|
||||
|
||||
# test case
|
||||
tc = []
|
||||
tc << {:cmd=>"s"}
|
||||
tc << {:cmd=>"p self", :exp=>'$1 = TestModuleScope'}
|
||||
|
||||
BinTest_MrubyBinDebugger.test(src, tc)
|
||||
end
|
||||
|
||||
assert('mruby-bin-debugger(print) scope:instance method') do
|
||||
# ruby source (bp is break point)
|
||||
src = <<"SRC"
|
||||
class TestMethodScope
|
||||
def m
|
||||
bp = nil
|
||||
end
|
||||
end
|
||||
TestMethodScope.new.m
|
||||
SRC
|
||||
|
||||
tc = []
|
||||
tc << {:cmd=>"b 3"}
|
||||
tc << {:cmd=>"r"}
|
||||
tc << {:cmd=>"p self", :exp=>'$1 = #<TestMethodScope:'}
|
||||
|
||||
BinTest_MrubyBinDebugger.test(src, tc)
|
||||
end
|
||||
|
||||
assert('mruby-bin-debugger(print) scope:class method') do
|
||||
# ruby source (bp is break point)
|
||||
src = <<"SRC"
|
||||
class TestClassMethodScope
|
||||
def self.cm
|
||||
bp = nil
|
||||
end
|
||||
end
|
||||
TestClassMethodScope.cm
|
||||
SRC
|
||||
|
||||
tc = []
|
||||
tc << {:cmd=>"b 3"}
|
||||
tc << {:cmd=>"r"}
|
||||
tc << {:cmd=>"p self", :exp=>'$1 = TestClassMethodScope'}
|
||||
|
||||
BinTest_MrubyBinDebugger.test(src, tc)
|
||||
end
|
||||
|
||||
assert('mruby-bin-debugger(print) scope:block') do
|
||||
# ruby source (bp is break point)
|
||||
src = <<"SRC"
|
||||
1.times do
|
||||
bp = nil
|
||||
end
|
||||
class TestBlockScope
|
||||
1.times do
|
||||
bp = nil
|
||||
end
|
||||
def m
|
||||
1.times do
|
||||
bp = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
TestBlockScope.new.m
|
||||
SRC
|
||||
|
||||
tc = []
|
||||
tc << {:cmd=>"b 2"}
|
||||
tc << {:cmd=>"b 6"}
|
||||
tc << {:cmd=>"b 10"}
|
||||
tc << {:cmd=>"c"}
|
||||
tc << {:cmd=>"p self", :exp=>'$1 = main'}
|
||||
tc << {:cmd=>"c"}
|
||||
tc << {:cmd=>"p self", :exp=>'$2 = TestBlockScope'}
|
||||
tc << {:cmd=>"c"}
|
||||
tc << {:cmd=>"p self", :exp=>'$3 = #<TestBlockScope:'}
|
||||
|
||||
BinTest_MrubyBinDebugger.test(src, tc)
|
||||
end
|
||||
|
||||
assert('mruby-bin-debugger(print) same name:local variabe') do
|
||||
# ruby source (bp is break point)
|
||||
src = <<"SRC"
|
||||
lv = 'top'
|
||||
class TestLocalVariableName
|
||||
lv = 'class'
|
||||
def m
|
||||
lv = 'instance method'
|
||||
bp = nil
|
||||
end
|
||||
bp = nil
|
||||
end
|
||||
TestLocalVariableName.new.m
|
||||
bp = nil
|
||||
SRC
|
||||
|
||||
tc = []
|
||||
tc << {:cmd=>"b 6"}
|
||||
tc << {:cmd=>"b 8"}
|
||||
tc << {:cmd=>"b 11"}
|
||||
tc << {:cmd=>"r"}
|
||||
tc << {:cmd=>"p lv", :exp=>'$1 = "class"'}
|
||||
tc << {:cmd=>"c"}
|
||||
tc << {:cmd=>"p lv", :exp=>'$2 = "instance method"'}
|
||||
tc << {:cmd=>"c"}
|
||||
tc << {:cmd=>"p lv", :exp=>'$3 = "top"'}
|
||||
|
||||
BinTest_MrubyBinDebugger.test(src, tc)
|
||||
end
|
||||
|
||||
assert('mruby-bin-debugger(print) same name:instance variabe') do
|
||||
# ruby source (bp is break point)
|
||||
src = <<"SRC"
|
||||
@iv = 'top'
|
||||
class TestInstanceVariableName
|
||||
def initialize(v)
|
||||
@iv = v
|
||||
end
|
||||
def m
|
||||
bp = nil
|
||||
end
|
||||
end
|
||||
i1 = TestInstanceVariableName.new('instance1')
|
||||
i2 = TestInstanceVariableName.new('instance2')
|
||||
i1.m
|
||||
i2.m
|
||||
bp = nil
|
||||
SRC
|
||||
|
||||
tc = []
|
||||
tc << {:cmd=>"b 7"}
|
||||
tc << {:cmd=>"b 14"}
|
||||
tc << {:cmd=>"r"}
|
||||
tc << {:cmd=>"p @iv", :exp=>'$1 = "instance1"'}
|
||||
tc << {:cmd=>"c"}
|
||||
tc << {:cmd=>"p @iv", :exp=>'$2 = "instance2"'}
|
||||
tc << {:cmd=>"c"}
|
||||
tc << {:cmd=>"p @iv", :exp=>'$3 = "top"'}
|
||||
|
||||
BinTest_MrubyBinDebugger.test(src, tc)
|
||||
end
|
||||
|
||||
# Kernel#instance_eval(string) does't work const.
|
||||
=begin
|
||||
assert('mruby-bin-debugger(print) same name:const') do
|
||||
# ruby source (bp is break point)
|
||||
src = <<"SRC"
|
||||
CONST='top'
|
||||
class TestConstNameSuperClass
|
||||
CONST='super class'
|
||||
def m
|
||||
bp = nil
|
||||
end
|
||||
end
|
||||
class TestConstNameSubClass < TestConstNameSuperClass
|
||||
CONST='sub class'
|
||||
def m
|
||||
bp = nil
|
||||
end
|
||||
end
|
||||
|
||||
TestConstNameSuperClass.new.m()
|
||||
TestConstNameSubClass.new.m()
|
||||
bp = nil
|
||||
SRC
|
||||
|
||||
# todo: wait for 'break' to be implemented
|
||||
tc = []
|
||||
9.times { tc << {:cmd=>"s"} }
|
||||
tc << {:cmd=>"p CONST", :exp=>"super class"}
|
||||
3.times { tc << {:cmd=>"s"} }
|
||||
tc << {:cmd=>"p CONST", :exp=>"sub class"}
|
||||
1.times { tc << {:cmd=>"s"} }
|
||||
tc << {:cmd=>"p CONST", :exp=>"top"}
|
||||
|
||||
BinTest_MrubyBinDebugger.test(src, tc)
|
||||
end
|
||||
=end
|
||||
|
||||
assert('mruby-bin-debugger(print) Literal:Numeric') do
|
||||
# ruby source
|
||||
src = "foo = 'foo'\n"
|
||||
|
||||
# test case
|
||||
tc = []
|
||||
tc << {:cmd=>"p 100", :exp=>'$1 = 100'}
|
||||
tc << {:cmd=>"p -0b100", :exp=>'$2 = -4'}
|
||||
tc << {:cmd=>"p +0100", :exp=>'$3 = 64'}
|
||||
tc << {:cmd=>"p 0x100", :exp=>'$4 = 256'}
|
||||
tc << {:cmd=>"p 1_234", :exp=>'$5 = 1234'}
|
||||
tc << {:cmd=>"p 0b1000_0000", :exp=>"$6 = #{0b1000_0000}"}
|
||||
tc << {:cmd=>"p 0x1000_0000", :exp=>"$7 = #{0x1000_0000}"}
|
||||
|
||||
tc << {:cmd=>"p 3.14", :exp=>'$8 = 3.14'}
|
||||
tc << {:cmd=>"p -12.3", :exp=>'$9 = -12.3'}
|
||||
tc << {:cmd=>"p +12.000", :exp=>'$10 = 12'}
|
||||
tc << {:cmd=>"p 1e4", :exp=>'$11 = 10000'}
|
||||
tc << {:cmd=>"p -0.1e-2", :exp=>'$12 = -0.001'}
|
||||
|
||||
BinTest_MrubyBinDebugger.test(src, tc)
|
||||
end
|
||||
|
||||
assert('mruby-bin-debugger(print) Literal:String') do
|
||||
# ruby source
|
||||
src = <<"SRC"
|
||||
foo = 'foo'
|
||||
bar = "bar"
|
||||
baz = "baz"
|
||||
SRC
|
||||
|
||||
# test case
|
||||
tc = []
|
||||
tc << {:cmd=>"s"}
|
||||
tc << {:cmd=>"s"}
|
||||
|
||||
tc << {:cmd=>'p "str"', :exp=>'$1 = "str"'}
|
||||
tc << {:cmd=>'p "s\tt\rr\n"', :exp=>'$2 = "s\\tt\\rr\\n"'}
|
||||
tc << {:cmd=>'p "\C-a\C-z"', :exp=>'$3 = "\\x01\\x1a"'}
|
||||
tc << {:cmd=>'p "#{foo+bar}"', :exp=>'$4 = "foobar"'}
|
||||
|
||||
tc << {:cmd=>'p \'str\'', :exp=>'$5 = "str"'}
|
||||
tc << {:cmd=>'p \'s\\tt\\rr\\n\'', :exp=>'$6 = "s\\\\tt\\\\rr\\\\n"'}
|
||||
tc << {:cmd=>'p \'\\C-a\\C-z\'', :exp=>'$7 = "\\\\C-a\\\\C-z"'}
|
||||
tc << {:cmd=>'p \'#{foo+bar}\'', :exp=>'$8 = "\\#{foo+bar}"'}
|
||||
|
||||
tc << {:cmd=>'p %!str!', :exp=>'$9 = "str"'}
|
||||
tc << {:cmd=>'p %!s\tt\rr\n!', :exp=>'$10 = "s\\tt\\rr\\n"'}
|
||||
tc << {:cmd=>'p %!\C-a\C-z!', :exp=>'$11 = "\\x01\\x1a"'}
|
||||
tc << {:cmd=>'p %!#{foo+bar}!', :exp=>'$12 = "foobar"'}
|
||||
|
||||
tc << {:cmd=>'p %Q!str!', :exp=>'$13 = "str"'}
|
||||
tc << {:cmd=>'p %Q!s\tt\rr\n!', :exp=>'$14 = "s\\tt\\rr\\n"'}
|
||||
tc << {:cmd=>'p %Q!\C-a\C-z!', :exp=>'$15 = "\\x01\\x1a"'}
|
||||
tc << {:cmd=>'p %Q!#{foo+bar}!', :exp=>'$16 = "foobar"'}
|
||||
|
||||
tc << {:cmd=>'p %q!str!', :exp=>'$17 = "str"'}
|
||||
tc << {:cmd=>'p %q!s\\tt\\rr\\n!', :exp=>'$18 = "s\\\\tt\\\\rr\\\\n"'}
|
||||
tc << {:cmd=>'p %q!\\C-a\\C-z!', :exp=>'$19 = "\\\\C-a\\\\C-z"'}
|
||||
tc << {:cmd=>'p %q!#{foo+bar}!', :exp=>'$20 = "\\#{foo+bar}"'}
|
||||
|
||||
BinTest_MrubyBinDebugger.test(src, tc)
|
||||
end
|
||||
|
||||
assert('mruby-bin-debugger(print) Literal:Array') do
|
||||
# ruby source
|
||||
src = <<"SRC"
|
||||
foo = 'foo'
|
||||
bar = "bar"
|
||||
baz = "baz"
|
||||
SRC
|
||||
|
||||
# test case
|
||||
tc = []
|
||||
tc << {:cmd=>"s"}
|
||||
tc << {:cmd=>"s"}
|
||||
|
||||
tc << {:cmd=>'p []', :exp=>'$1 = []'}
|
||||
tc << {:cmd=>'p [ 5, 12, 8, 10, ]', :exp=>'$2 = [5, 12, 8, 10]'}
|
||||
tc << {:cmd=>'p [1,2.5,"#{foo+bar}"]', :exp=>'$3 = [1, 2.5, "foobar"]'}
|
||||
tc << {:cmd=>'p %w[3.14 A\ &\ B #{foo}]', :exp=>'$4 = ["3.14", "A & B", "\#{foo}"]'}
|
||||
tc << {:cmd=>'p %W[3.14 A\ &\ B #{foo}]', :exp=>'$5 = ["3.14", "A & B", "foo"]'}
|
||||
|
||||
BinTest_MrubyBinDebugger.test(src, tc)
|
||||
end
|
||||
|
||||
assert('mruby-bin-debugger(print) Literal:Hash') do
|
||||
# ruby source
|
||||
src = <<"SRC"
|
||||
foo = 'foo'
|
||||
bar = "bar"
|
||||
baz = "baz"
|
||||
SRC
|
||||
|
||||
# test case
|
||||
tc = []
|
||||
tc << {:cmd=>"s"}
|
||||
tc << {:cmd=>"s"}
|
||||
|
||||
tc << {:cmd=>'p {}', :exp=>'$1 = {}'}
|
||||
tc << {:cmd=>'p {"one"=>1,"two"=>2}', :exp=>'$2 = {"one"=>1, "two"=>2}'}
|
||||
tc << {:cmd=>'p {:eins=>"1", :zwei=>"2", }', :exp=>'$3 = {:eins=>"1", :zwei=>"2"}'}
|
||||
tc << {:cmd=>'p {uno:"one", dos: 2}', :exp=>'$4 = {:uno=>"one", :dos=>2}'}
|
||||
tc << {:cmd=>'p {"one"=>1, :zwei=>2, tres:3}', :exp=>'$5 = {"one"=>1, :zwei=>2, :tres=>3}'}
|
||||
tc << {:cmd=>'p {:foo=>"#{foo}",:bar=>"#{bar}"}', :exp=>'$6 = {:foo=>"foo", :bar=>"bar"}'}
|
||||
|
||||
BinTest_MrubyBinDebugger.test(src, tc)
|
||||
end
|
||||
|
||||
assert('mruby-bin-debugger(print) Literal:Range') do
|
||||
# ruby source
|
||||
src = "foo = 'foo'\n"
|
||||
|
||||
# test case
|
||||
tc = []
|
||||
tc << {:cmd=>'p 1..10', :exp=>'$1 = 1..10'}
|
||||
tc << {:cmd=>'p 1...10', :exp=>'$2 = 1...10'}
|
||||
tc << {:cmd=>'p 100..10', :exp=>'$3 = 100..10'}
|
||||
tc << {:cmd=>'p 1 ... 10', :exp=>'$4 = 1...10'}
|
||||
|
||||
tc << {:cmd=>'p "1" .. "9"', :exp=>'$5 = "1".."9"'}
|
||||
tc << {:cmd=>'p "A" ... "Z"', :exp=>'$6 = "A"..."Z"'}
|
||||
|
||||
BinTest_MrubyBinDebugger.test(src, tc)
|
||||
end
|
||||
|
||||
assert('mruby-bin-debugger(print) Literal:Symbol') do
|
||||
# ruby source
|
||||
src = <<"SRC"
|
||||
foo = 'foo'
|
||||
bar = "bar"
|
||||
baz = "baz"
|
||||
SRC
|
||||
|
||||
# test case
|
||||
tc = []
|
||||
tc << {:cmd=>"s"}
|
||||
tc << {:cmd=>"s"}
|
||||
|
||||
tc << {:cmd=>'p :sym', :exp=>'$1 = :sym'}
|
||||
tc << {:cmd=>'p :"sd"', :exp=>'$2 = :sd'}
|
||||
tc << {:cmd=>"p :'ss'", :exp=>'$3 = :ss'}
|
||||
tc << {:cmd=>'p :"123"', :exp=>'$4 = :"123"'}
|
||||
tc << {:cmd=>'p :"#{foo} baz"', :exp=>'$5 = :"foo baz"'}
|
||||
tc << {:cmd=>'p %s!symsym!', :exp=>'$6 = :symsym'}
|
||||
|
||||
BinTest_MrubyBinDebugger.test(src, tc)
|
||||
end
|
||||
|
||||
assert('mruby-bin-debugger(print) Unary operation') do
|
||||
# ruby source
|
||||
src = "foo = 'foo'\n"
|
||||
|
||||
# test case
|
||||
tc = []
|
||||
tc << {:cmd=>'p +10', :exp=>'$1 = 10'}
|
||||
tc << {:cmd=>'p -100', :exp=>'$2 = -100'}
|
||||
tc << {:cmd=>'p !true', :exp=>'$3 = false'}
|
||||
tc << {:cmd=>'p !false', :exp=>'$4 = true'}
|
||||
tc << {:cmd=>'p !nil', :exp=>'$5 = true'}
|
||||
tc << {:cmd=>'p !1', :exp=>'$6 = false'}
|
||||
|
||||
BinTest_MrubyBinDebugger.test(src, tc)
|
||||
end
|
||||
|
||||
assert('mruby-bin-debugger(print) Binary operation') do
|
||||
# ruby source
|
||||
src = <<"SRC"
|
||||
CONST = 100
|
||||
a,b,c = 1, 5, 8
|
||||
foo,bar,baz = 'foo','bar','baz'
|
||||
ary = []
|
||||
SRC
|
||||
|
||||
# test case
|
||||
tc = []
|
||||
tc << {:cmd=>'s'}
|
||||
tc << {:cmd=>'s'}
|
||||
tc << {:cmd=>'s'}
|
||||
|
||||
tc << {:cmd=>'p a+1', :exp=>'$1 = 2'}
|
||||
tc << {:cmd=>'p 2-b', :exp=>'$2 = -3'}
|
||||
tc << {:cmd=>'p c * 3', :exp=>'$3 = 24'}
|
||||
tc << {:cmd=>'p a/b', :exp=>'$4 = 0.2'}
|
||||
tc << {:cmd=>'p c%b', :exp=>'$5 = 3'}
|
||||
tc << {:cmd=>'p 2**10', :exp=>'$6 = 1024'}
|
||||
tc << {:cmd=>'p ~3', :exp=>'$7 = -4'}
|
||||
|
||||
tc << {:cmd=>'p 1<<2', :exp=>'$8 = 4'}
|
||||
tc << {:cmd=>'p 64>>5', :exp=>'$9 = 2'}
|
||||
|
||||
tc << {:cmd=>'p a|c', :exp=>'$10 = 9'}
|
||||
tc << {:cmd=>'p a&b', :exp=>'$11 = 1'}
|
||||
tc << {:cmd=>'p a^b', :exp=>'$12 = 4'}
|
||||
|
||||
tc << {:cmd=>'p a>b', :exp=>'$13 = false'}
|
||||
tc << {:cmd=>'p a<b', :exp=>'$14 = true'}
|
||||
tc << {:cmd=>'p b>=5', :exp=>'$15 = true'}
|
||||
tc << {:cmd=>'p b<=5', :exp=>'$16 = true'}
|
||||
|
||||
tc << {:cmd=>'p "A"<=>"B"', :exp=>'$17 = -1'}
|
||||
tc << {:cmd=>'p "A"=="B"', :exp=>'$18 = false'}
|
||||
tc << {:cmd=>'p "A"==="B"', :exp=>'$19 = false'}
|
||||
tc << {:cmd=>'p "A"!="B"', :exp=>'$20 = true'}
|
||||
|
||||
tc << {:cmd=>'p false || true', :exp=>'$21 = true'}
|
||||
tc << {:cmd=>'p false && true', :exp=>'$22 = false'}
|
||||
|
||||
tc << {:cmd=>'p not nil', :exp=>'$23 = true'}
|
||||
tc << {:cmd=>'p false or true', :exp=>'$24 = true'}
|
||||
tc << {:cmd=>'p false and true', :exp=>'$25 = false'}
|
||||
|
||||
BinTest_MrubyBinDebugger.test(src, tc)
|
||||
end
|
||||
|
||||
assert('mruby-bin-debugger(print) Ternary operation') do
|
||||
# ruby source
|
||||
src = <<"SRC"
|
||||
CONST = 100
|
||||
a,b,c = 1, 5, -10
|
||||
foo,bar,baz = 'foo','bar','baz'
|
||||
ary = []
|
||||
SRC
|
||||
|
||||
# test case
|
||||
tc = []
|
||||
tc << {:cmd=>'s'}
|
||||
tc << {:cmd=>'s'}
|
||||
tc << {:cmd=>'s'}
|
||||
|
||||
tc << {:cmd=>'p (a < b) ? a : b', :exp=>'$1 = 1'}
|
||||
tc << {:cmd=>'p (a > b) ? a : b', :exp=>'$2 = 5'}
|
||||
tc << {:cmd=>'p true ? "true" : "false"', :exp=>'$3 = "true"'}
|
||||
tc << {:cmd=>'p false ? "true" : "false"', :exp=>'$4 = "false"'}
|
||||
tc << {:cmd=>'p nil ? "true" : "false"', :exp=>'$5 = "false"'}
|
||||
|
||||
BinTest_MrubyBinDebugger.test(src, tc)
|
||||
end
|
||||
|
||||
assert('mruby-bin-debugger(print) Substitution:simple') do
|
||||
# ruby source
|
||||
src = <<"SRC"
|
||||
CONST = 100
|
||||
a,b,c = 1, 5, -10
|
||||
foo,bar,baz = 'foo','bar','baz'
|
||||
ary = []
|
||||
SRC
|
||||
|
||||
# test case
|
||||
tc = []
|
||||
tc << {:cmd=>'s'}
|
||||
tc << {:cmd=>'s'}
|
||||
tc << {:cmd=>'s'}
|
||||
|
||||
tc << {:cmd=>'p a=2', :exp=>'$1 = 2'}
|
||||
tc << {:cmd=>'p foo=[foo,bar,baz]', :exp=>'$2 = ["foo", "bar", "baz"]'}
|
||||
|
||||
tc << {:cmd=>'p undefined=-1', :exp=>'$3 = -1'}
|
||||
tc << {:cmd=>'p "#{undefined}"', :exp=>'$4 = undefined method'}
|
||||
|
||||
BinTest_MrubyBinDebugger.test(src, tc)
|
||||
end
|
||||
|
||||
assert('mruby-bin-debugger(print) Substitution:self') do
|
||||
# ruby source
|
||||
src = <<"SRC"
|
||||
CONST = 100
|
||||
a,b,c = 1, 5, -10
|
||||
foo,bar,baz = 'foo','bar','baz'
|
||||
ary = []
|
||||
SRC
|
||||
|
||||
# test case
|
||||
tc = []
|
||||
tc << {:cmd=>'s'}
|
||||
tc << {:cmd=>'s'}
|
||||
tc << {:cmd=>'s'}
|
||||
|
||||
tc << {:cmd=>'p a+=9', :exp=>'$1 = 10'}
|
||||
tc << {:cmd=>'p b-=c', :exp=>'$2 = 15'}
|
||||
tc << {:cmd=>'p bar*=2', :exp=>'$3 = "barbar"'}
|
||||
tc << {:cmd=>'p a/=4', :exp=>'$4 = 2.5'}
|
||||
tc << {:cmd=>'p c%=4', :exp=>'$5 = 2'}
|
||||
|
||||
tc << {:cmd=>'p b&=0b0101', :exp=>'$6 = 5'}
|
||||
tc << {:cmd=>'p c|=0x10', :exp=>'$7 = 18'}
|
||||
|
||||
tc << {:cmd=>'p "#{a} #{b} #{c}"', :exp=>'$8 = "2.5 5 18"'}
|
||||
tc << {:cmd=>'p "#{foo}#{bar}#{baz}"', :exp=>'$9 = "foobarbarbaz"'}
|
||||
|
||||
tc << {:cmd=>'p a,b,c=[10,20,30]',:exp=>'$10 = [10, 20, 30]'}
|
||||
tc << {:cmd=>'p [a,b,c]', :exp=>'$11 = [10, 20, 30]'}
|
||||
tc << {:cmd=>'p a,b=b,a', :exp=>'$12 = [20, 10]'}
|
||||
tc << {:cmd=>'p [a,b]', :exp=>'$13 = [20, 10]'}
|
||||
|
||||
tc << {:cmd=>'p undefined=-1', :exp=>'$14 = -1'}
|
||||
tc << {:cmd=>'p "#{undefined}"', :exp=>'$15 = undefined method'}
|
||||
|
||||
BinTest_MrubyBinDebugger.test(src, tc)
|
||||
end
|
||||
|
||||
assert('mruby-bin-debugger(print) Substitution:multiple') do
|
||||
# ruby source
|
||||
src = <<"SRC"
|
||||
CONST = 100
|
||||
a,b,c = 1, 5, -10
|
||||
foo,bar,baz = 'foo','bar','baz'
|
||||
ary = []
|
||||
SRC
|
||||
|
||||
# test case
|
||||
tc = []
|
||||
tc << {:cmd=>'s'}
|
||||
tc << {:cmd=>'s'}
|
||||
tc << {:cmd=>'s'}
|
||||
|
||||
tc << {:cmd=>'p a,b=[10,20]', :exp=>'$1 = [10, 20]'}
|
||||
tc << {:cmd=>'p [a,b,c]', :exp=>'$2 = [10, 20, -10]'}
|
||||
|
||||
tc << {:cmd=>'p foo,bar=["FOO","BAR","BAZ"]', :exp=>'$3 = ["FOO", "BAR", "BAZ"]'}
|
||||
tc << {:cmd=>'p [foo,bar,baz]', :exp=>'$4 = ["FOO", "BAR", "baz"]'}
|
||||
|
||||
tc << {:cmd=>'p a,foo=foo,a', :exp=>'$5 = ["FOO", 10]'}
|
||||
tc << {:cmd=>'p [a,foo]', :exp=>'$6 = ["FOO", 10]'}
|
||||
|
||||
# tc << {:cmd=>'p a,*b=[123, 456, 789]'}
|
||||
# tc << {:cmd=>'p [a,b]', :exp=>'[123, [456, 789]]'}
|
||||
|
||||
BinTest_MrubyBinDebugger.test(src, tc)
|
||||
end
|
||||
|
||||
assert('mruby-bin-debugger(print) Substitution:self') do
|
||||
# ruby source
|
||||
src = <<"SRC"
|
||||
CONST = 100
|
||||
a,b,c = 1, 5, -10
|
||||
foo,bar,baz = 'foo','bar','baz'
|
||||
ary = []
|
||||
SRC
|
||||
|
||||
# test case
|
||||
tc = []
|
||||
tc << {:cmd=>'s'}
|
||||
tc << {:cmd=>'s'}
|
||||
tc << {:cmd=>'s'}
|
||||
|
||||
tc << {:cmd=>'p a+=9', :exp=>'$1 = 10'}
|
||||
tc << {:cmd=>'p b-=c', :exp=>'$2 = 15'}
|
||||
tc << {:cmd=>'p bar*=2', :exp=>'$3 = "barbar"'}
|
||||
tc << {:cmd=>'p a/=4', :exp=>'$4 = 2.5'}
|
||||
tc << {:cmd=>'p c%=4', :exp=>'$5 = 2'}
|
||||
|
||||
tc << {:cmd=>'p b&=0b0101', :exp=>'$6 = 5'}
|
||||
tc << {:cmd=>'p c|=0x10', :exp=>'$7 = 18'}
|
||||
|
||||
tc << {:cmd=>'p "#{a} #{b} #{c}"', :exp=>'$8 = "2.5 5 18"'}
|
||||
tc << {:cmd=>'p "#{foo}#{bar}#{baz}"', :exp=>'$9 = "foobarbarbaz"'}
|
||||
|
||||
tc << {:cmd=>'p a,b,c=[10,20,30]',:exp=>'$10 = [10, 20, 30]'}
|
||||
tc << {:cmd=>'p [a,b,c]', :exp=>'$11 = [10, 20, 30]'}
|
||||
tc << {:cmd=>'p a,b=b,a', :exp=>'$12 = [20, 10]'}
|
||||
tc << {:cmd=>'p [a,b]', :exp=>'$13 = [20, 10]'}
|
||||
|
||||
tc << {:cmd=>'p undefined=-1', :exp=>'$14 = -1'}
|
||||
tc << {:cmd=>'p "#{undefined}"', :exp=>'$15 = undefined method'}
|
||||
|
||||
BinTest_MrubyBinDebugger.test(src, tc)
|
||||
end
|
||||
@@ -0,0 +1,9 @@
|
||||
MRuby::Gem::Specification.new('mruby-bin-debugger') do |spec|
|
||||
spec.license = 'MIT'
|
||||
spec.author = 'mruby developers'
|
||||
spec.summary = 'mruby debugger command'
|
||||
|
||||
spec.add_dependency('mruby-eval', :core => 'mruby-eval')
|
||||
|
||||
spec.bins = %w(mrdb)
|
||||
end
|
||||
@@ -0,0 +1,507 @@
|
||||
/*
|
||||
** apibreak.c
|
||||
**
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <mruby.h>
|
||||
#include <mruby/irep.h>
|
||||
#include "mrdb.h"
|
||||
#include <mruby/debug.h>
|
||||
#include <mruby/opcode.h>
|
||||
#include <mruby/class.h>
|
||||
#include <mruby/proc.h>
|
||||
#include <mruby/variable.h>
|
||||
#include "mrdberror.h"
|
||||
#include "apibreak.h"
|
||||
|
||||
#define MAX_BREAKPOINTNO (MAX_BREAKPOINT * 1024)
|
||||
#define MRB_DEBUG_BP_FILE_OK (0x0001)
|
||||
#define MRB_DEBUG_BP_LINENO_OK (0x0002)
|
||||
|
||||
static uint16_t
|
||||
check_lineno(mrb_irep_debug_info_file *info_file, uint16_t lineno)
|
||||
{
|
||||
uint32_t count = info_file->line_entry_count;
|
||||
uint16_t l_idx;
|
||||
|
||||
if (info_file->line_type == mrb_debug_line_ary) {
|
||||
for (l_idx = 0; l_idx < count; ++l_idx) {
|
||||
if (lineno == info_file->lines.ary[l_idx]) {
|
||||
return lineno;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (l_idx = 0; l_idx < count; ++l_idx) {
|
||||
if (lineno == info_file->lines.flat_map[l_idx].line) {
|
||||
return lineno;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int32_t
|
||||
get_break_index(mrb_debug_context *dbg, uint32_t bpno)
|
||||
{
|
||||
uint32_t i;
|
||||
int32_t index;
|
||||
char hit = FALSE;
|
||||
|
||||
for(i = 0 ; i < dbg->bpnum; i++) {
|
||||
if (dbg->bp[i].bpno == bpno) {
|
||||
hit = TRUE;
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hit == FALSE) {
|
||||
return MRB_DEBUG_BREAK_INVALID_NO;
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
static void
|
||||
free_breakpoint(mrb_state *mrb, mrb_debug_breakpoint *bp)
|
||||
{
|
||||
switch(bp->type) {
|
||||
case MRB_DEBUG_BPTYPE_LINE:
|
||||
mrb_free(mrb, (void*)bp->point.linepoint.file);
|
||||
break;
|
||||
case MRB_DEBUG_BPTYPE_METHOD:
|
||||
mrb_free(mrb, (void*)bp->point.methodpoint.method_name);
|
||||
if (bp->point.methodpoint.class_name != NULL) {
|
||||
mrb_free(mrb, (void*)bp->point.methodpoint.class_name);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static uint16_t
|
||||
check_file_lineno(mrb_state *mrb, struct mrb_irep *irep, const char *file, uint16_t lineno)
|
||||
{
|
||||
mrb_irep_debug_info_file *info_file;
|
||||
uint16_t result = 0;
|
||||
uint16_t f_idx;
|
||||
uint16_t fix_lineno;
|
||||
uint16_t i;
|
||||
|
||||
for (f_idx = 0; f_idx < irep->debug_info->flen; ++f_idx) {
|
||||
const char *filename;
|
||||
info_file = irep->debug_info->files[f_idx];
|
||||
filename = mrb_sym_name_len(mrb, info_file->filename_sym, NULL);
|
||||
if (!strcmp(filename, file)) {
|
||||
result = MRB_DEBUG_BP_FILE_OK;
|
||||
|
||||
fix_lineno = check_lineno(info_file, lineno);
|
||||
if (fix_lineno != 0) {
|
||||
return result | MRB_DEBUG_BP_LINENO_OK;
|
||||
}
|
||||
}
|
||||
for (i=0; i < irep->rlen; ++i) {
|
||||
result |= check_file_lineno(mrb, irep->reps[i], file, lineno);
|
||||
if (result == (MRB_DEBUG_BP_FILE_OK | MRB_DEBUG_BP_LINENO_OK)) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static int32_t
|
||||
compare_break_method(mrb_state *mrb, mrb_debug_breakpoint *bp, struct RClass *class_obj, mrb_sym method_sym, mrb_bool* isCfunc)
|
||||
{
|
||||
const char* class_name;
|
||||
const char* method_name;
|
||||
mrb_method_t m;
|
||||
struct RClass* sc;
|
||||
const char* sn;
|
||||
mrb_sym ssym;
|
||||
mrb_debug_methodpoint *method_p;
|
||||
mrb_bool is_defined;
|
||||
|
||||
method_name = mrb_sym_name(mrb, method_sym);
|
||||
|
||||
method_p = &bp->point.methodpoint;
|
||||
if (strcmp(method_p->method_name, method_name) == 0) {
|
||||
class_name = mrb_class_name(mrb, class_obj);
|
||||
if (class_name == NULL) {
|
||||
if (method_p->class_name == NULL) {
|
||||
return bp->bpno;
|
||||
}
|
||||
}
|
||||
else if (method_p->class_name != NULL) {
|
||||
m = mrb_method_search_vm(mrb, &class_obj, method_sym);
|
||||
if (MRB_METHOD_UNDEF_P(m)) {
|
||||
return MRB_DEBUG_OK;
|
||||
}
|
||||
if (MRB_METHOD_CFUNC_P(m)) {
|
||||
*isCfunc = TRUE;
|
||||
}
|
||||
|
||||
is_defined = mrb_class_defined(mrb, method_p->class_name);
|
||||
if (is_defined == FALSE) {
|
||||
return MRB_DEBUG_OK;
|
||||
}
|
||||
|
||||
sc = mrb_class_get(mrb, method_p->class_name);
|
||||
ssym = mrb_symbol(mrb_check_intern_cstr(mrb, method_p->method_name));
|
||||
m = mrb_method_search_vm(mrb, &sc, ssym);
|
||||
if (MRB_METHOD_UNDEF_P(m)) {
|
||||
return MRB_DEBUG_OK;
|
||||
}
|
||||
|
||||
class_name = mrb_class_name(mrb, class_obj);
|
||||
sn = mrb_class_name(mrb, sc);
|
||||
if (strcmp(sn, class_name) == 0) {
|
||||
return bp->bpno;
|
||||
}
|
||||
}
|
||||
}
|
||||
return MRB_DEBUG_OK;
|
||||
}
|
||||
|
||||
int32_t
|
||||
mrb_debug_set_break_line(mrb_state *mrb, mrb_debug_context *dbg, const char *file, uint16_t lineno)
|
||||
{
|
||||
int32_t index;
|
||||
char* set_file;
|
||||
uint16_t result;
|
||||
|
||||
if ((mrb == NULL)||(dbg == NULL)||(file == NULL)) {
|
||||
return MRB_DEBUG_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (dbg->bpnum >= MAX_BREAKPOINT) {
|
||||
return MRB_DEBUG_BREAK_NUM_OVER;
|
||||
}
|
||||
|
||||
if (dbg->next_bpno > MAX_BREAKPOINTNO) {
|
||||
return MRB_DEBUG_BREAK_NO_OVER;
|
||||
}
|
||||
|
||||
/* file and lineno check (line type mrb_debug_line_ary only.) */
|
||||
result = check_file_lineno(mrb, dbg->root_irep, file, lineno);
|
||||
if (result == 0) {
|
||||
return MRB_DEBUG_BREAK_INVALID_FILE;
|
||||
}
|
||||
else if (result == MRB_DEBUG_BP_FILE_OK) {
|
||||
return MRB_DEBUG_BREAK_INVALID_LINENO;
|
||||
}
|
||||
|
||||
set_file = (char*)mrb_malloc(mrb, strlen(file) + 1);
|
||||
|
||||
index = dbg->bpnum;
|
||||
dbg->bp[index].bpno = dbg->next_bpno;
|
||||
dbg->next_bpno++;
|
||||
dbg->bp[index].enable = TRUE;
|
||||
dbg->bp[index].type = MRB_DEBUG_BPTYPE_LINE;
|
||||
dbg->bp[index].point.linepoint.lineno = lineno;
|
||||
dbg->bpnum++;
|
||||
|
||||
strncpy(set_file, file, strlen(file) + 1);
|
||||
|
||||
dbg->bp[index].point.linepoint.file = set_file;
|
||||
|
||||
return dbg->bp[index].bpno;
|
||||
}
|
||||
|
||||
int32_t
|
||||
mrb_debug_set_break_method(mrb_state *mrb, mrb_debug_context *dbg, const char *class_name, const char *method_name)
|
||||
{
|
||||
int32_t index;
|
||||
char* set_class;
|
||||
char* set_method;
|
||||
|
||||
if ((mrb == NULL) || (dbg == NULL) || (method_name == NULL)) {
|
||||
return MRB_DEBUG_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (dbg->bpnum >= MAX_BREAKPOINT) {
|
||||
return MRB_DEBUG_BREAK_NUM_OVER;
|
||||
}
|
||||
|
||||
if (dbg->next_bpno > MAX_BREAKPOINTNO) {
|
||||
return MRB_DEBUG_BREAK_NO_OVER;
|
||||
}
|
||||
|
||||
if (class_name != NULL) {
|
||||
set_class = (char*)mrb_malloc(mrb, strlen(class_name) + 1);
|
||||
strncpy(set_class, class_name, strlen(class_name) + 1);
|
||||
}
|
||||
else {
|
||||
set_class = NULL;
|
||||
}
|
||||
|
||||
set_method = (char*)mrb_malloc(mrb, strlen(method_name) + 1);
|
||||
|
||||
strncpy(set_method, method_name, strlen(method_name) + 1);
|
||||
|
||||
index = dbg->bpnum;
|
||||
dbg->bp[index].bpno = dbg->next_bpno;
|
||||
dbg->next_bpno++;
|
||||
dbg->bp[index].enable = TRUE;
|
||||
dbg->bp[index].type = MRB_DEBUG_BPTYPE_METHOD;
|
||||
dbg->bp[index].point.methodpoint.method_name = set_method;
|
||||
dbg->bp[index].point.methodpoint.class_name = set_class;
|
||||
dbg->bpnum++;
|
||||
|
||||
return dbg->bp[index].bpno;
|
||||
}
|
||||
|
||||
int32_t
|
||||
mrb_debug_get_breaknum(mrb_state *mrb, mrb_debug_context *dbg)
|
||||
{
|
||||
if ((mrb == NULL) || (dbg == NULL)) {
|
||||
return MRB_DEBUG_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
return dbg->bpnum;
|
||||
}
|
||||
|
||||
int32_t
|
||||
mrb_debug_get_break_all(mrb_state *mrb, mrb_debug_context *dbg, uint32_t size, mrb_debug_breakpoint *bp)
|
||||
{
|
||||
uint32_t get_size = 0;
|
||||
|
||||
if ((mrb == NULL) || (dbg == NULL) || (bp == NULL)) {
|
||||
return MRB_DEBUG_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (dbg->bpnum >= size) {
|
||||
get_size = size;
|
||||
}
|
||||
else {
|
||||
get_size = dbg->bpnum;
|
||||
}
|
||||
|
||||
memcpy(bp, dbg->bp, sizeof(mrb_debug_breakpoint) * get_size);
|
||||
|
||||
return get_size;
|
||||
}
|
||||
|
||||
int32_t
|
||||
mrb_debug_get_break(mrb_state *mrb, mrb_debug_context *dbg, uint32_t bpno, mrb_debug_breakpoint *bp)
|
||||
{
|
||||
int32_t index;
|
||||
|
||||
if ((mrb == NULL) || (dbg == NULL) || (bp == NULL)) {
|
||||
return MRB_DEBUG_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
index = get_break_index(dbg, bpno);
|
||||
if (index == MRB_DEBUG_BREAK_INVALID_NO) {
|
||||
return MRB_DEBUG_BREAK_INVALID_NO;
|
||||
}
|
||||
|
||||
bp->bpno = dbg->bp[index].bpno;
|
||||
bp->enable = dbg->bp[index].enable;
|
||||
bp->point = dbg->bp[index].point;
|
||||
bp->type = dbg->bp[index].type;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t
|
||||
mrb_debug_delete_break(mrb_state *mrb, mrb_debug_context *dbg, uint32_t bpno)
|
||||
{
|
||||
uint32_t i;
|
||||
int32_t index;
|
||||
|
||||
if ((mrb == NULL) ||(dbg == NULL)) {
|
||||
return MRB_DEBUG_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
index = get_break_index(dbg, bpno);
|
||||
if (index == MRB_DEBUG_BREAK_INVALID_NO) {
|
||||
return MRB_DEBUG_BREAK_INVALID_NO;
|
||||
}
|
||||
|
||||
free_breakpoint(mrb, &dbg->bp[index]);
|
||||
|
||||
for(i = index ; i < dbg->bpnum; i++) {
|
||||
if ((i + 1) == dbg->bpnum) {
|
||||
memset(&dbg->bp[i], 0, sizeof(mrb_debug_breakpoint));
|
||||
}
|
||||
else {
|
||||
memcpy(&dbg->bp[i], &dbg->bp[i + 1], sizeof(mrb_debug_breakpoint));
|
||||
}
|
||||
}
|
||||
|
||||
dbg->bpnum--;
|
||||
|
||||
return MRB_DEBUG_OK;
|
||||
}
|
||||
|
||||
int32_t
|
||||
mrb_debug_delete_break_all(mrb_state *mrb, mrb_debug_context *dbg)
|
||||
{
|
||||
uint32_t i;
|
||||
|
||||
if ((mrb == NULL) || (dbg == NULL)) {
|
||||
return MRB_DEBUG_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
for(i = 0 ; i < dbg->bpnum ; i++) {
|
||||
free_breakpoint(mrb, &dbg->bp[i]);
|
||||
}
|
||||
|
||||
dbg->bpnum = 0;
|
||||
|
||||
return MRB_DEBUG_OK;
|
||||
}
|
||||
|
||||
int32_t
|
||||
mrb_debug_enable_break(mrb_state *mrb, mrb_debug_context *dbg, uint32_t bpno)
|
||||
{
|
||||
int32_t index = 0;
|
||||
|
||||
if ((mrb == NULL) || (dbg == NULL)) {
|
||||
return MRB_DEBUG_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
index = get_break_index(dbg, bpno);
|
||||
if (index == MRB_DEBUG_BREAK_INVALID_NO) {
|
||||
return MRB_DEBUG_BREAK_INVALID_NO;
|
||||
}
|
||||
|
||||
dbg->bp[index].enable = TRUE;
|
||||
|
||||
return MRB_DEBUG_OK;
|
||||
}
|
||||
|
||||
int32_t
|
||||
mrb_debug_enable_break_all(mrb_state *mrb, mrb_debug_context *dbg)
|
||||
{
|
||||
uint32_t i;
|
||||
|
||||
if ((mrb == NULL) || (dbg == NULL)) {
|
||||
return MRB_DEBUG_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
for(i = 0 ; i < dbg->bpnum; i++) {
|
||||
dbg->bp[i].enable = TRUE;
|
||||
}
|
||||
|
||||
return MRB_DEBUG_OK;
|
||||
}
|
||||
|
||||
int32_t
|
||||
mrb_debug_disable_break(mrb_state *mrb, mrb_debug_context *dbg, uint32_t bpno)
|
||||
{
|
||||
int32_t index = 0;
|
||||
|
||||
if ((mrb == NULL) || (dbg == NULL)) {
|
||||
return MRB_DEBUG_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
index = get_break_index(dbg, bpno);
|
||||
if (index == MRB_DEBUG_BREAK_INVALID_NO) {
|
||||
return MRB_DEBUG_BREAK_INVALID_NO;
|
||||
}
|
||||
|
||||
dbg->bp[index].enable = FALSE;
|
||||
|
||||
return MRB_DEBUG_OK;
|
||||
}
|
||||
|
||||
int32_t
|
||||
mrb_debug_disable_break_all(mrb_state *mrb, mrb_debug_context *dbg)
|
||||
{
|
||||
uint32_t i;
|
||||
|
||||
if ((mrb == NULL) || (dbg == NULL)) {
|
||||
return MRB_DEBUG_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
for(i = 0 ; i < dbg->bpnum; i++) {
|
||||
dbg->bp[i].enable = FALSE;
|
||||
}
|
||||
|
||||
return MRB_DEBUG_OK;
|
||||
}
|
||||
|
||||
static mrb_bool
|
||||
check_start_pc_for_line(mrb_state *mrb, mrb_irep *irep, const mrb_code *pc, uint16_t line)
|
||||
{
|
||||
if (pc > irep->iseq) {
|
||||
if (line == mrb_debug_get_line(mrb, irep, pc - irep->iseq - 1)) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
int32_t
|
||||
mrb_debug_check_breakpoint_line(mrb_state *mrb, mrb_debug_context *dbg, const char *file, uint16_t line)
|
||||
{
|
||||
mrb_debug_breakpoint *bp;
|
||||
mrb_debug_linepoint *line_p;
|
||||
uint32_t i;
|
||||
|
||||
if ((mrb == NULL) || (dbg == NULL) || (file == NULL) || (line <= 0)) {
|
||||
return MRB_DEBUG_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (!check_start_pc_for_line(mrb, dbg->irep, dbg->pc, line)) {
|
||||
return MRB_DEBUG_OK;
|
||||
}
|
||||
|
||||
bp = dbg->bp;
|
||||
for(i=0; i<dbg->bpnum; i++) {
|
||||
switch (bp->type) {
|
||||
case MRB_DEBUG_BPTYPE_LINE:
|
||||
if (bp->enable == TRUE) {
|
||||
line_p = &bp->point.linepoint;
|
||||
if ((strcmp(line_p->file, file) == 0) && (line_p->lineno == line)) {
|
||||
return bp->bpno;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MRB_DEBUG_BPTYPE_METHOD:
|
||||
break;
|
||||
case MRB_DEBUG_BPTYPE_NONE:
|
||||
default:
|
||||
return MRB_DEBUG_OK;
|
||||
}
|
||||
bp++;
|
||||
}
|
||||
return MRB_DEBUG_OK;
|
||||
}
|
||||
|
||||
|
||||
int32_t
|
||||
mrb_debug_check_breakpoint_method(mrb_state *mrb, mrb_debug_context *dbg, struct RClass *class_obj, mrb_sym method_sym, mrb_bool* isCfunc)
|
||||
{
|
||||
mrb_debug_breakpoint *bp;
|
||||
int32_t bpno;
|
||||
uint32_t i;
|
||||
|
||||
if ((mrb == NULL) || (dbg == NULL) || (class_obj == NULL)) {
|
||||
return MRB_DEBUG_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
bp = dbg->bp;
|
||||
for(i=0; i<dbg->bpnum; i++) {
|
||||
if (bp->type == MRB_DEBUG_BPTYPE_METHOD) {
|
||||
if (bp->enable == TRUE) {
|
||||
bpno = compare_break_method(mrb, bp, class_obj, method_sym, isCfunc);
|
||||
if (bpno > 0) {
|
||||
return bpno;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (bp->type == MRB_DEBUG_BPTYPE_NONE) {
|
||||
break;
|
||||
}
|
||||
bp++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
** apibreak.h
|
||||
**
|
||||
*/
|
||||
|
||||
#ifndef APIBREAK_H_
|
||||
#define APIBREAK_H_
|
||||
|
||||
#include <mruby.h>
|
||||
#include "mrdb.h"
|
||||
|
||||
int32_t mrb_debug_set_break_line(mrb_state *, mrb_debug_context *, const char *, uint16_t);
|
||||
int32_t mrb_debug_set_break_method(mrb_state *, mrb_debug_context *, const char *, const char *);
|
||||
int32_t mrb_debug_get_breaknum(mrb_state *, mrb_debug_context *);
|
||||
int32_t mrb_debug_get_break_all(mrb_state *, mrb_debug_context *, uint32_t, mrb_debug_breakpoint bp[]);
|
||||
int32_t mrb_debug_get_break(mrb_state *, mrb_debug_context *, uint32_t, mrb_debug_breakpoint *);
|
||||
int32_t mrb_debug_delete_break(mrb_state *, mrb_debug_context *, uint32_t);
|
||||
int32_t mrb_debug_delete_break_all(mrb_state *, mrb_debug_context *);
|
||||
int32_t mrb_debug_enable_break(mrb_state *, mrb_debug_context *, uint32_t);
|
||||
int32_t mrb_debug_enable_break_all(mrb_state *, mrb_debug_context *);
|
||||
int32_t mrb_debug_disable_break(mrb_state *, mrb_debug_context *, uint32_t);
|
||||
int32_t mrb_debug_disable_break_all(mrb_state *, mrb_debug_context *);
|
||||
int32_t mrb_debug_check_breakpoint_line(mrb_state *, mrb_debug_context *, const char *, uint16_t);
|
||||
int32_t mrb_debug_check_breakpoint_method(mrb_state *, mrb_debug_context *, struct RClass *, mrb_sym, mrb_bool*);
|
||||
|
||||
#endif /* APIBREAK_H_ */
|
||||
@@ -0,0 +1,240 @@
|
||||
/*
|
||||
* apilist.c
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "mrdb.h"
|
||||
#include "mrdberror.h"
|
||||
#include "apilist.h"
|
||||
#include <mruby/compile.h>
|
||||
#include <mruby/irep.h>
|
||||
#include <mruby/debug.h>
|
||||
|
||||
#define LINE_BUF_SIZE MAX_COMMAND_LINE
|
||||
|
||||
typedef struct source_file {
|
||||
char *path;
|
||||
uint16_t lineno;
|
||||
FILE *fp;
|
||||
} source_file;
|
||||
|
||||
static void
|
||||
source_file_free(mrb_state *mrb, source_file *file)
|
||||
{
|
||||
if (file != NULL) {
|
||||
if (file->path != NULL) {
|
||||
mrb_free(mrb, file->path);
|
||||
}
|
||||
if (file->fp != NULL) {
|
||||
fclose(file->fp);
|
||||
file->fp = NULL;
|
||||
}
|
||||
mrb_free(mrb, file);
|
||||
}
|
||||
}
|
||||
|
||||
static char*
|
||||
build_path(mrb_state *mrb, const char *dir, const char *base)
|
||||
{
|
||||
int len;
|
||||
char *path = NULL;
|
||||
|
||||
len = strlen(base) + 1;
|
||||
|
||||
if (strcmp(dir, ".")) {
|
||||
len += strlen(dir) + sizeof("/") - 1;
|
||||
}
|
||||
|
||||
path = (char*)mrb_malloc(mrb, len);
|
||||
memset(path, 0, len);
|
||||
|
||||
if (strcmp(dir, ".")) {
|
||||
strcat(path, dir);
|
||||
strcat(path, "/");
|
||||
}
|
||||
strcat(path, base);
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
static char*
|
||||
dirname(mrb_state *mrb, const char *path)
|
||||
{
|
||||
size_t len;
|
||||
const char *p;
|
||||
char *dir;
|
||||
|
||||
if (path == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
p = strrchr(path, '/');
|
||||
len = p != NULL ? (size_t)(p - path) : strlen(path);
|
||||
|
||||
dir = (char*)mrb_malloc(mrb, len + 1);
|
||||
strncpy(dir, path, len);
|
||||
dir[len] = '\0';
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
static source_file*
|
||||
source_file_new(mrb_state *mrb, mrb_debug_context *dbg, char *filename)
|
||||
{
|
||||
source_file *file;
|
||||
|
||||
file = (source_file*)mrb_malloc(mrb, sizeof(source_file));
|
||||
|
||||
memset(file, '\0', sizeof(source_file));
|
||||
file->fp = fopen(filename, "rb");
|
||||
|
||||
if (file->fp == NULL) {
|
||||
source_file_free(mrb, file);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
file->lineno = 1;
|
||||
file->path = (char*)mrb_malloc(mrb, strlen(filename) + 1);
|
||||
strcpy(file->path, filename);
|
||||
return file;
|
||||
}
|
||||
|
||||
static mrb_bool
|
||||
remove_newlines(char *s, FILE *fp)
|
||||
{
|
||||
int c;
|
||||
char *p;
|
||||
size_t len;
|
||||
|
||||
if ((len = strlen(s)) == 0) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
p = s + len - 1;
|
||||
|
||||
if (*p != '\r' && *p != '\n') {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (*p == '\r') {
|
||||
/* peek the next character and skip '\n' */
|
||||
if ((c = fgetc(fp)) != '\n') {
|
||||
ungetc(c, fp);
|
||||
}
|
||||
}
|
||||
|
||||
/* remove trailing newline characters */
|
||||
while (s <= p && (*p == '\r' || *p == '\n')) {
|
||||
*p-- = '\0';
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
show_lines(source_file *file, uint16_t line_min, uint16_t line_max)
|
||||
{
|
||||
char buf[LINE_BUF_SIZE];
|
||||
int show_lineno = 1, found_newline = 0, is_printed = 0;
|
||||
|
||||
if (file->fp == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (fgets(buf, sizeof(buf), file->fp) != NULL) {
|
||||
found_newline = remove_newlines(buf, file->fp);
|
||||
|
||||
if (line_min <= file->lineno) {
|
||||
if (show_lineno) {
|
||||
printf("%-8d", file->lineno);
|
||||
}
|
||||
show_lineno = found_newline;
|
||||
printf(found_newline ? "%s\n" : "%s", buf);
|
||||
is_printed = 1;
|
||||
}
|
||||
|
||||
if (found_newline) {
|
||||
if (line_max < ++file->lineno) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (is_printed && !found_newline) {
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
char*
|
||||
mrb_debug_get_source(mrb_state *mrb, mrdb_state *mrdb, const char *srcpath, const char *filename)
|
||||
{
|
||||
int i;
|
||||
FILE *fp;
|
||||
const char *search_path[3];
|
||||
char *path = NULL;
|
||||
const char *srcname = strrchr(filename, '/');
|
||||
|
||||
if (srcname) srcname++;
|
||||
else srcname = filename;
|
||||
|
||||
search_path[0] = srcpath;
|
||||
search_path[1] = dirname(mrb, mrb_debug_get_filename(mrb, mrdb->dbg->irep, 0));
|
||||
search_path[2] = ".";
|
||||
|
||||
for (i = 0; i < 3; i++) {
|
||||
if (search_path[i] == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((path = build_path(mrb, search_path[i], srcname)) == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((fp = fopen(path, "rb")) == NULL) {
|
||||
mrb_free(mrb, path);
|
||||
path = NULL;
|
||||
continue;
|
||||
}
|
||||
fclose(fp);
|
||||
break;
|
||||
}
|
||||
|
||||
mrb_free(mrb, (void *)search_path[1]);
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
int32_t
|
||||
mrb_debug_list(mrb_state *mrb, mrb_debug_context *dbg, char *filename, uint16_t line_min, uint16_t line_max)
|
||||
{
|
||||
char *ext;
|
||||
source_file *file;
|
||||
|
||||
if (mrb == NULL || dbg == NULL || filename == NULL) {
|
||||
return MRB_DEBUG_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
ext = strrchr(filename, '.');
|
||||
|
||||
if (ext == NULL || strcmp(ext, ".rb")) {
|
||||
printf("List command only supports .rb file.\n");
|
||||
return MRB_DEBUG_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (line_min > line_max) {
|
||||
return MRB_DEBUG_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if ((file = source_file_new(mrb, dbg, filename)) != NULL) {
|
||||
show_lines(file, line_min, line_max);
|
||||
source_file_free(mrb, file);
|
||||
return MRB_DEBUG_OK;
|
||||
}
|
||||
else {
|
||||
printf("Invalid source file named %s.\n", filename);
|
||||
return MRB_DEBUG_INVALID_ARGUMENT;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* apilist.h
|
||||
*/
|
||||
|
||||
#ifndef APILIST_H_
|
||||
#define APILIST_H_
|
||||
|
||||
#include <mruby.h>
|
||||
#include "mrdb.h"
|
||||
|
||||
int32_t mrb_debug_list(mrb_state *, mrb_debug_context *, char *, uint16_t, uint16_t);
|
||||
char* mrb_debug_get_source(mrb_state *, mrdb_state *, const char *, const char *);
|
||||
|
||||
#endif /* APILIST_H_ */
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
** apiprint.c
|
||||
**
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "mrdb.h"
|
||||
#include <mruby/value.h>
|
||||
#include <mruby/class.h>
|
||||
#include <mruby/compile.h>
|
||||
#include <mruby/error.h>
|
||||
#include <mruby/numeric.h>
|
||||
#include <mruby/string.h>
|
||||
#include "apiprint.h"
|
||||
|
||||
static void
|
||||
mrdb_check_syntax(mrb_state *mrb, mrb_debug_context *dbg, const char *expr, size_t len)
|
||||
{
|
||||
mrbc_context *c;
|
||||
|
||||
c = mrbc_context_new(mrb);
|
||||
c->no_exec = TRUE;
|
||||
c->capture_errors = TRUE;
|
||||
mrbc_filename(mrb, c, (const char*)dbg->prvfile);
|
||||
c->lineno = dbg->prvline;
|
||||
|
||||
/* Load program */
|
||||
mrb_load_nstring_cxt(mrb, expr, len, c);
|
||||
|
||||
mrbc_context_free(mrb, c);
|
||||
}
|
||||
|
||||
mrb_value
|
||||
mrb_debug_eval(mrb_state *mrb, mrb_debug_context *dbg, const char *expr, size_t len, mrb_bool *exc, int direct_eval)
|
||||
{
|
||||
void (*tmp)(struct mrb_state *, struct mrb_irep *, const mrb_code *, mrb_value *);
|
||||
mrb_value ruby_code;
|
||||
mrb_value s;
|
||||
mrb_value v;
|
||||
mrb_value recv;
|
||||
|
||||
/* disable code_fetch_hook */
|
||||
tmp = mrb->code_fetch_hook;
|
||||
mrb->code_fetch_hook = NULL;
|
||||
|
||||
mrdb_check_syntax(mrb, dbg, expr, len);
|
||||
if (mrb->exc) {
|
||||
v = mrb_obj_value(mrb->exc);
|
||||
mrb->exc = 0;
|
||||
}
|
||||
else if (direct_eval) {
|
||||
recv = dbg->regs[0];
|
||||
|
||||
v = mrb_funcall(mrb, recv, expr, 0);
|
||||
}
|
||||
else {
|
||||
/*
|
||||
* begin
|
||||
* expr
|
||||
* rescue => e
|
||||
* e
|
||||
* end
|
||||
*/
|
||||
ruby_code = mrb_str_new_lit(mrb, "begin\n");
|
||||
ruby_code = mrb_str_cat(mrb, ruby_code, expr, len);
|
||||
ruby_code = mrb_str_cat_lit(mrb, ruby_code, "\nrescue => e\ne\nend");
|
||||
|
||||
recv = dbg->regs[0];
|
||||
|
||||
v = mrb_funcall(mrb, recv, "instance_eval", 1, ruby_code);
|
||||
}
|
||||
|
||||
if (exc) {
|
||||
*exc = mrb_obj_is_kind_of(mrb, v, mrb->eException_class);
|
||||
}
|
||||
|
||||
s = mrb_inspect(mrb, v);
|
||||
|
||||
/* enable code_fetch_hook */
|
||||
mrb->code_fetch_hook = tmp;
|
||||
|
||||
return s;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* apiprint.h
|
||||
*/
|
||||
|
||||
#ifndef APIPRINT_H_
|
||||
#define APIPRINT_H_
|
||||
|
||||
#include <mruby.h>
|
||||
#include "mrdb.h"
|
||||
|
||||
mrb_value mrb_debug_eval(mrb_state*, mrb_debug_context*, const char*, size_t, mrb_bool*, int);
|
||||
|
||||
#endif /* APIPRINT_H_ */
|
||||
@@ -0,0 +1,436 @@
|
||||
/*
|
||||
** cmdbreak.c
|
||||
**
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include <mruby.h>
|
||||
#include <mruby/dump.h>
|
||||
#include <mruby/debug.h>
|
||||
#include <mruby/string.h>
|
||||
#include "mrdb.h"
|
||||
#include "mrdberror.h"
|
||||
#include "apibreak.h"
|
||||
|
||||
#define BREAK_SET_MSG_LINE "Breakpoint %d: file %s, line %d.\n"
|
||||
#define BREAK_SET_MSG_METHOD "Breakpoint %d: method %s.\n"
|
||||
#define BREAK_SET_MSG_CLASS_METHOD "Breakpoint %d: class %s, method %s.\n"
|
||||
#define BREAK_INFO_MSG_HEADER "Num Type Enb What"
|
||||
#define BREAK_INFO_MSG_LINEBREAK "%-8ubreakpoint %s at %s:%u\n"
|
||||
#define BREAK_INFO_MSG_METHODBREAK "%-8ubreakpoint %s in %s:%s\n"
|
||||
#define BREAK_INFO_MSG_METHODBREAK_NOCLASS "%-8ubreakpoint %s in %s\n"
|
||||
#define BREAK_INFO_MSG_ENABLE "y"
|
||||
#define BREAK_INFO_MSG_DISABLE "n"
|
||||
|
||||
#define BREAK_ERR_MSG_INVALIDARG "Internal error."
|
||||
#define BREAK_ERR_MSG_BLANK "Try \'help break\' for more information."
|
||||
#define BREAK_ERR_MSG_RANGEOVER "The line number range is from 1 to 65535."
|
||||
#define BREAK_ERR_MSG_NUMOVER "Exceeded the setable number of breakpoint."
|
||||
#define BREAK_ERR_MSG_NOOVER "Breakno is over the available number.Please 'quit' and restart mrdb."
|
||||
#define BREAK_ERR_MSG_INVALIDSTR "String \'%s\' is invalid.\n"
|
||||
#define BREAK_ERR_MSG_INVALIDLINENO "Line %d in file \"%s\" is unavailable.\n"
|
||||
#define BREAK_ERR_MSG_INVALIDCLASS "Class name \'%s\' is invalid.\n"
|
||||
#define BREAK_ERR_MSG_INVALIDMETHOD "Method name \'%s\' is invalid.\n"
|
||||
#define BREAK_ERR_MSG_INVALIDFILE "Source file named \"%s\" is unavailable.\n"
|
||||
#define BREAK_ERR_MSG_INVALIDBPNO "warning: bad breakpoint number at or near '%s'\n"
|
||||
#define BREAK_ERR_MSG_INVALIDBPNO_INFO "Args must be numbers variables."
|
||||
#define BREAK_ERR_MSG_NOBPNO "No breakpoint number %d.\n"
|
||||
#define BREAK_ERR_MSG_NOBPNO_INFO "No breakpoint matching '%d'\n"
|
||||
#define BREAK_ERR_MSG_NOBPNO_INFOALL "No breakpoints."
|
||||
|
||||
#define LINENO_MAX_DIGIT 6
|
||||
#define BPNO_LETTER_NUM 9
|
||||
|
||||
typedef int32_t (*all_command_func)(mrb_state *, mrb_debug_context *);
|
||||
typedef int32_t (*select_command_func)(mrb_state *, mrb_debug_context *, uint32_t);
|
||||
|
||||
static void
|
||||
print_api_common_error(int32_t error)
|
||||
{
|
||||
switch(error) {
|
||||
case MRB_DEBUG_INVALID_ARGUMENT:
|
||||
puts(BREAK_ERR_MSG_INVALIDARG);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#undef STRTOUL
|
||||
#define STRTOUL(ul,s) { \
|
||||
int i; \
|
||||
ul = 0; \
|
||||
for(i=0; ISDIGIT(s[i]); i++) ul = 10*ul + (s[i] -'0'); \
|
||||
}
|
||||
|
||||
static int32_t
|
||||
parse_breakpoint_no(char* args)
|
||||
{
|
||||
char* ps = args;
|
||||
uint32_t l;
|
||||
|
||||
if ((*ps == '0')||(strlen(ps) >= BPNO_LETTER_NUM)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
while (!(ISBLANK(*ps)||ISCNTRL(*ps))) {
|
||||
if (!ISDIGIT(*ps)) {
|
||||
return 0;
|
||||
}
|
||||
ps++;
|
||||
}
|
||||
|
||||
STRTOUL(l, args);
|
||||
return l;
|
||||
}
|
||||
|
||||
static mrb_bool
|
||||
exe_set_command_all(mrb_state *mrb, mrdb_state *mrdb, all_command_func func)
|
||||
{
|
||||
int32_t ret = MRB_DEBUG_OK;
|
||||
|
||||
if (mrdb->wcnt == 1) {
|
||||
ret = func(mrb, mrdb->dbg);
|
||||
print_api_common_error(ret);
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
exe_set_command_select(mrb_state *mrb, mrdb_state *mrdb, select_command_func func)
|
||||
{
|
||||
char* ps;
|
||||
int32_t ret = MRB_DEBUG_OK;
|
||||
int32_t bpno = 0;
|
||||
int32_t i;
|
||||
|
||||
for(i=1; i<mrdb->wcnt; i++) {
|
||||
ps = mrdb->words[i];
|
||||
bpno = parse_breakpoint_no(ps);
|
||||
if (bpno == 0) {
|
||||
printf(BREAK_ERR_MSG_INVALIDBPNO, ps);
|
||||
break;
|
||||
}
|
||||
ret = func(mrb, mrdb->dbg, (uint32_t)bpno);
|
||||
if (ret == MRB_DEBUG_BREAK_INVALID_NO) {
|
||||
printf(BREAK_ERR_MSG_NOBPNO, bpno);
|
||||
}
|
||||
else if (ret != MRB_DEBUG_OK) {
|
||||
print_api_common_error(ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mrb_debug_bptype
|
||||
check_bptype(char* args)
|
||||
{
|
||||
char* ps = args;
|
||||
|
||||
if (ISBLANK(*ps)||ISCNTRL(*ps)) {
|
||||
puts(BREAK_ERR_MSG_BLANK);
|
||||
return MRB_DEBUG_BPTYPE_NONE;
|
||||
}
|
||||
|
||||
if (!ISDIGIT(*ps)) {
|
||||
return MRB_DEBUG_BPTYPE_METHOD;
|
||||
}
|
||||
|
||||
while (!(ISBLANK(*ps)||ISCNTRL(*ps))) {
|
||||
if (!ISDIGIT(*ps)) {
|
||||
printf(BREAK_ERR_MSG_INVALIDSTR, args);
|
||||
return MRB_DEBUG_BPTYPE_NONE;
|
||||
}
|
||||
ps++;
|
||||
}
|
||||
|
||||
if ((*args == '0')||(strlen(args) >= LINENO_MAX_DIGIT)) {
|
||||
puts(BREAK_ERR_MSG_RANGEOVER);
|
||||
return MRB_DEBUG_BPTYPE_NONE;
|
||||
}
|
||||
|
||||
return MRB_DEBUG_BPTYPE_LINE;
|
||||
}
|
||||
|
||||
static void
|
||||
print_breakpoint(mrb_debug_breakpoint *bp)
|
||||
{
|
||||
const char* enable_letter[] = {BREAK_INFO_MSG_DISABLE, BREAK_INFO_MSG_ENABLE};
|
||||
|
||||
if (bp->type == MRB_DEBUG_BPTYPE_LINE) {
|
||||
printf(BREAK_INFO_MSG_LINEBREAK,
|
||||
bp->bpno, enable_letter[bp->enable], bp->point.linepoint.file, bp->point.linepoint.lineno);
|
||||
}
|
||||
else {
|
||||
if (bp->point.methodpoint.class_name == NULL) {
|
||||
printf(BREAK_INFO_MSG_METHODBREAK_NOCLASS,
|
||||
bp->bpno, enable_letter[bp->enable], bp->point.methodpoint.method_name);
|
||||
}
|
||||
else {
|
||||
printf(BREAK_INFO_MSG_METHODBREAK,
|
||||
bp->bpno, enable_letter[bp->enable], bp->point.methodpoint.class_name, bp->point.methodpoint.method_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
info_break_all(mrb_state *mrb, mrdb_state *mrdb)
|
||||
{
|
||||
int32_t bpnum = 0;
|
||||
int32_t i = 0;
|
||||
int32_t ret = MRB_DEBUG_OK;
|
||||
mrb_debug_breakpoint *bp_list;
|
||||
|
||||
bpnum = mrb_debug_get_breaknum(mrb, mrdb->dbg);
|
||||
if (bpnum < 0) {
|
||||
print_api_common_error(bpnum);
|
||||
return;
|
||||
}
|
||||
else if (bpnum == 0) {
|
||||
puts(BREAK_ERR_MSG_NOBPNO_INFOALL);
|
||||
return;
|
||||
}
|
||||
bp_list = (mrb_debug_breakpoint*)mrb_malloc(mrb, bpnum * sizeof(mrb_debug_breakpoint));
|
||||
|
||||
ret = mrb_debug_get_break_all(mrb, mrdb->dbg, (uint32_t)bpnum, bp_list);
|
||||
if (ret < 0) {
|
||||
print_api_common_error(ret);
|
||||
return;
|
||||
}
|
||||
puts(BREAK_INFO_MSG_HEADER);
|
||||
for(i = 0 ; i < bpnum ; i++) {
|
||||
print_breakpoint(&bp_list[i]);
|
||||
}
|
||||
|
||||
mrb_free(mrb, bp_list);
|
||||
}
|
||||
|
||||
static void
|
||||
info_break_select(mrb_state *mrb, mrdb_state *mrdb)
|
||||
{
|
||||
int32_t ret = MRB_DEBUG_OK;
|
||||
int32_t bpno = 0;
|
||||
char* ps = mrdb->command;
|
||||
mrb_debug_breakpoint bp;
|
||||
mrb_bool isFirst = TRUE;
|
||||
int32_t i;
|
||||
|
||||
for(i=2; i<mrdb->wcnt; i++) {
|
||||
ps = mrdb->words[i];
|
||||
bpno = parse_breakpoint_no(ps);
|
||||
if (bpno == 0) {
|
||||
puts(BREAK_ERR_MSG_INVALIDBPNO_INFO);
|
||||
break;
|
||||
}
|
||||
|
||||
ret = mrb_debug_get_break(mrb, mrdb->dbg, bpno, &bp);
|
||||
if (ret == MRB_DEBUG_BREAK_INVALID_NO) {
|
||||
printf(BREAK_ERR_MSG_NOBPNO_INFO, bpno);
|
||||
break;
|
||||
}
|
||||
else if (ret != MRB_DEBUG_OK) {
|
||||
print_api_common_error(ret);
|
||||
break;
|
||||
}
|
||||
else if (isFirst == TRUE) {
|
||||
isFirst = FALSE;
|
||||
puts(BREAK_INFO_MSG_HEADER);
|
||||
}
|
||||
print_breakpoint(&bp);
|
||||
}
|
||||
}
|
||||
|
||||
mrb_debug_bptype
|
||||
parse_breakcommand(mrb_state *mrb, mrdb_state *mrdb, const char **file, uint32_t *line, char **cname, char **method)
|
||||
{
|
||||
mrb_debug_context *dbg = mrdb->dbg;
|
||||
char *args;
|
||||
char *body;
|
||||
mrb_debug_bptype type;
|
||||
uint32_t l;
|
||||
|
||||
if (mrdb->wcnt <= 1) {
|
||||
puts(BREAK_ERR_MSG_BLANK);
|
||||
return MRB_DEBUG_BPTYPE_NONE;
|
||||
}
|
||||
|
||||
args = mrdb->words[1];
|
||||
if ((body = strrchr(args, ':')) == NULL) {
|
||||
body = args;
|
||||
type = check_bptype(body);
|
||||
}
|
||||
else {
|
||||
if (body == args) {
|
||||
printf(BREAK_ERR_MSG_INVALIDSTR, args);
|
||||
return MRB_DEBUG_BPTYPE_NONE;
|
||||
}
|
||||
*body = '\0';
|
||||
type = check_bptype(++body);
|
||||
}
|
||||
|
||||
switch(type) {
|
||||
case MRB_DEBUG_BPTYPE_LINE:
|
||||
STRTOUL(l, body);
|
||||
if (l <= 65535) {
|
||||
*line = l;
|
||||
*file = (body == args)? mrb_debug_get_filename(mrb, dbg->irep, dbg->pc - dbg->irep->iseq): args;
|
||||
}
|
||||
else {
|
||||
puts(BREAK_ERR_MSG_RANGEOVER);
|
||||
type = MRB_DEBUG_BPTYPE_NONE;
|
||||
}
|
||||
break;
|
||||
case MRB_DEBUG_BPTYPE_METHOD:
|
||||
if (body == args) {
|
||||
/* method only */
|
||||
if (ISUPPER(*body)||ISLOWER(*body)||(*body == '_')) {
|
||||
*method = body;
|
||||
*cname = NULL;
|
||||
}
|
||||
else {
|
||||
printf(BREAK_ERR_MSG_INVALIDMETHOD, args);
|
||||
type = MRB_DEBUG_BPTYPE_NONE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (ISUPPER(*args)) {
|
||||
switch(*body) {
|
||||
case '@': case '$': case '?': case '.': case ',': case ':':
|
||||
case ';': case '#': case '\\': case '\'': case '\"':
|
||||
printf(BREAK_ERR_MSG_INVALIDMETHOD, body);
|
||||
type = MRB_DEBUG_BPTYPE_NONE;
|
||||
break;
|
||||
default:
|
||||
*method = body;
|
||||
*cname = args;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
printf(BREAK_ERR_MSG_INVALIDCLASS, args);
|
||||
type = MRB_DEBUG_BPTYPE_NONE;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MRB_DEBUG_BPTYPE_NONE:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
dbgcmd_state
|
||||
dbgcmd_break(mrb_state *mrb, mrdb_state *mrdb)
|
||||
{
|
||||
mrb_debug_bptype type;
|
||||
mrb_debug_context *dbg = mrdb->dbg;
|
||||
const char *file = NULL;
|
||||
uint32_t line = 0;
|
||||
char *cname = NULL;
|
||||
char *method = NULL;
|
||||
int32_t ret;
|
||||
|
||||
type = parse_breakcommand(mrb, mrdb, &file, &line, &cname, &method);
|
||||
switch (type) {
|
||||
case MRB_DEBUG_BPTYPE_LINE:
|
||||
ret = mrb_debug_set_break_line(mrb, dbg, file, line);
|
||||
break;
|
||||
case MRB_DEBUG_BPTYPE_METHOD:
|
||||
ret = mrb_debug_set_break_method(mrb, dbg, cname, method);
|
||||
break;
|
||||
case MRB_DEBUG_BPTYPE_NONE:
|
||||
default:
|
||||
return DBGST_PROMPT;
|
||||
}
|
||||
|
||||
if (ret >= 0) {
|
||||
if (type == MRB_DEBUG_BPTYPE_LINE) {
|
||||
printf(BREAK_SET_MSG_LINE, ret, file, line);
|
||||
}
|
||||
else if ((type == MRB_DEBUG_BPTYPE_METHOD)&&(cname == NULL)) {
|
||||
printf(BREAK_SET_MSG_METHOD, ret, method);
|
||||
}
|
||||
else {
|
||||
printf(BREAK_SET_MSG_CLASS_METHOD, ret, cname, method);
|
||||
}
|
||||
}
|
||||
else {
|
||||
switch (ret) {
|
||||
case MRB_DEBUG_BREAK_INVALID_LINENO:
|
||||
printf(BREAK_ERR_MSG_INVALIDLINENO, line, file);
|
||||
break;
|
||||
case MRB_DEBUG_BREAK_INVALID_FILE:
|
||||
printf(BREAK_ERR_MSG_INVALIDFILE, file);
|
||||
break;
|
||||
case MRB_DEBUG_BREAK_NUM_OVER:
|
||||
puts(BREAK_ERR_MSG_NUMOVER);
|
||||
break;
|
||||
case MRB_DEBUG_BREAK_NO_OVER:
|
||||
puts(BREAK_ERR_MSG_NOOVER);
|
||||
break;
|
||||
case MRB_DEBUG_INVALID_ARGUMENT:
|
||||
puts(BREAK_ERR_MSG_INVALIDARG);
|
||||
break;
|
||||
case MRB_DEBUG_NOBUF:
|
||||
puts("T.B.D.");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return DBGST_PROMPT;
|
||||
}
|
||||
|
||||
dbgcmd_state
|
||||
dbgcmd_info_break(mrb_state *mrb, mrdb_state *mrdb)
|
||||
{
|
||||
if (mrdb->wcnt == 2) {
|
||||
info_break_all(mrb, mrdb);
|
||||
}
|
||||
else {
|
||||
info_break_select(mrb, mrdb);
|
||||
}
|
||||
|
||||
return DBGST_PROMPT;
|
||||
}
|
||||
|
||||
dbgcmd_state
|
||||
dbgcmd_delete(mrb_state *mrb, mrdb_state *mrdb)
|
||||
{
|
||||
mrb_bool ret = FALSE;
|
||||
|
||||
ret = exe_set_command_all(mrb, mrdb, mrb_debug_delete_break_all);
|
||||
if (ret != TRUE) {
|
||||
exe_set_command_select(mrb, mrdb, mrb_debug_delete_break);
|
||||
}
|
||||
|
||||
return DBGST_PROMPT;
|
||||
}
|
||||
|
||||
dbgcmd_state
|
||||
dbgcmd_enable(mrb_state *mrb, mrdb_state *mrdb)
|
||||
{
|
||||
mrb_bool ret = FALSE;
|
||||
|
||||
ret = exe_set_command_all(mrb, mrdb, mrb_debug_enable_break_all);
|
||||
if (ret != TRUE) {
|
||||
exe_set_command_select(mrb, mrdb, mrb_debug_enable_break);
|
||||
}
|
||||
|
||||
return DBGST_PROMPT;
|
||||
}
|
||||
|
||||
dbgcmd_state
|
||||
dbgcmd_disable(mrb_state *mrb, mrdb_state *mrdb)
|
||||
{
|
||||
mrb_bool ret = FALSE;
|
||||
|
||||
ret = exe_set_command_all(mrb, mrdb, mrb_debug_disable_break_all);
|
||||
if (ret != TRUE) {
|
||||
exe_set_command_select(mrb, mrdb, mrb_debug_disable_break);
|
||||
}
|
||||
return DBGST_PROMPT;
|
||||
}
|
||||
@@ -0,0 +1,508 @@
|
||||
/*
|
||||
** cmdmisc.c - mruby debugger miscellaneous command functions
|
||||
**
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "apilist.h"
|
||||
#include <mruby/compile.h>
|
||||
|
||||
typedef struct help_msg {
|
||||
const char *cmd1;
|
||||
const char *cmd2;
|
||||
const char *short_msg;
|
||||
const char *long_msg;
|
||||
} help_msg;
|
||||
|
||||
static help_msg help_msg_list[] = {
|
||||
{
|
||||
"b[reak]", NULL, "Set breakpoint",
|
||||
"Usage: break [file:]line\n"
|
||||
" break [class:]method\n"
|
||||
"\n"
|
||||
"Set breakpoint at specified line or method.\n"
|
||||
"If \'[file:]line\' is specified, break at start of code for that line (in a file).\n"
|
||||
"If \'[class:]method\' is specified, break at start of code for that method (of the class).\n"
|
||||
},
|
||||
{
|
||||
"c[ontinue]", NULL, "Continue program being debugged",
|
||||
"Usage: continue [N]\n"
|
||||
"\n"
|
||||
"Continue program stopped by a breakpoint.\n"
|
||||
"If N, which is non negative value, is passed,\n"
|
||||
"proceed program until the N-th breakpoint is coming.\n"
|
||||
"If N is not passed, N is assumed 1.\n"
|
||||
},
|
||||
{
|
||||
"d[elete]", NULL, "Delete some breakpoints",
|
||||
"Usage: delete [bpno1 [bpno2 [... [bpnoN]]]]\n"
|
||||
"\n"
|
||||
"Delete some breakpoints.\n"
|
||||
"Arguments are breakpoint numbers with spaces in between.\n"
|
||||
"To delete all breakpoints, give no argument.\n"
|
||||
},
|
||||
{
|
||||
"dis[able]", NULL, "Disable some breakpoints",
|
||||
"Usage: disable [bpno1 [bpno2 [... [bpnoN]]]]\n"
|
||||
"\n"
|
||||
"Disable some breakpoints.\n"
|
||||
"Arguments are breakpoint numbers with spaces in between.\n"
|
||||
"To disable all breakpoints, give no argument.\n"
|
||||
},
|
||||
{
|
||||
"en[able]", NULL, "Enable some breakpoints",
|
||||
"Usage: enable [bpno1 [bpno2 [... [bpnoN]]]]\n"
|
||||
"\n"
|
||||
"Enable some breakpoints.\n"
|
||||
"Arguments are breakpoint numbers with spaces in between.\n"
|
||||
"To enable all breakpoints, give no argument.\n"
|
||||
},
|
||||
{
|
||||
"ev[al]", NULL, "Evaluate expression",
|
||||
"Usage: eval expr\n"
|
||||
"\n"
|
||||
"It evaluates and prints the value of the mruby expression.\n"
|
||||
"This is equivalent to the \'print\' command.\n"
|
||||
},
|
||||
{
|
||||
"h[elp]", NULL, "Print this help",
|
||||
"Usage: help [command]\n"
|
||||
"\n"
|
||||
"With no arguments, help displays a short list of commands.\n"
|
||||
"With a command name as help argument, help displays how to use that command.\n"
|
||||
},
|
||||
{
|
||||
"i[nfo]", "b[reakpoints]", "Status of breakpoints",
|
||||
"Usage: info breakpoints [bpno1 [bpno2 [... [bpnoN]]]]\n"
|
||||
"\n"
|
||||
"Status of specified breakpoints (all user-settable breakpoints if no argument).\n"
|
||||
"Arguments are breakpoint numbers with spaces in between.\n"
|
||||
},
|
||||
{
|
||||
"i[nfo]", "l[ocals]", "Print name of local variables",
|
||||
"Usage: info locals\n"
|
||||
"\n"
|
||||
"Print name of local variables.\n"
|
||||
},
|
||||
{
|
||||
"l[ist]", NULL, "List specified line",
|
||||
"Usage: list\n"
|
||||
" list first[,last]\n"
|
||||
" list filename:first[,last]\n"
|
||||
"\n"
|
||||
"Print lines from a source file.\n"
|
||||
"\n"
|
||||
"With first and last, list prints lines from first to last.\n"
|
||||
"When last is empty, it stands for ten lines away from first.\n"
|
||||
"With filename, list prints lines in the specified source file.\n"
|
||||
},
|
||||
{
|
||||
"p[rint]", NULL, "Print value of expression",
|
||||
"Usage: print expr\n"
|
||||
"\n"
|
||||
"It evaluates and prints the value of the mruby expression.\n"
|
||||
"This is equivalent to the \'eval\' command.\n"
|
||||
},
|
||||
{
|
||||
"q[uit]", NULL, "Exit mrdb",
|
||||
"Usage: quit\n"
|
||||
"\n"
|
||||
"Exit mrdb.\n"
|
||||
},
|
||||
{
|
||||
"r[un]", NULL, "Start debugged program",
|
||||
"Usage: run\n"
|
||||
"\n"
|
||||
"Start debugged program.\n"
|
||||
},
|
||||
{
|
||||
"s[tep]", NULL, "Step program until it reaches a different source line",
|
||||
"Usage: step\n"
|
||||
"\n"
|
||||
"Step program until it reaches a different source line.\n"
|
||||
},
|
||||
{ NULL, NULL, NULL, NULL }
|
||||
};
|
||||
|
||||
typedef struct listcmd_parser_state {
|
||||
mrb_bool parse_error;
|
||||
mrb_bool has_line_min;
|
||||
mrb_bool has_line_max;
|
||||
char *filename;
|
||||
uint16_t line_min;
|
||||
uint16_t line_max;
|
||||
} listcmd_parser_state;
|
||||
|
||||
static listcmd_parser_state*
|
||||
listcmd_parser_state_new(mrb_state *mrb)
|
||||
{
|
||||
listcmd_parser_state *st = (listcmd_parser_state*)mrb_malloc(mrb, sizeof(listcmd_parser_state));
|
||||
memset(st, 0, sizeof(listcmd_parser_state));
|
||||
return st;
|
||||
}
|
||||
|
||||
static void
|
||||
listcmd_parser_state_free(mrb_state *mrb, listcmd_parser_state *st)
|
||||
{
|
||||
if (st != NULL) {
|
||||
if (st->filename != NULL) {
|
||||
mrb_free(mrb, st->filename);
|
||||
}
|
||||
mrb_free(mrb, st);
|
||||
}
|
||||
}
|
||||
|
||||
static mrb_bool
|
||||
parse_uint(char **sp, uint16_t *n)
|
||||
{
|
||||
char *p;
|
||||
int i;
|
||||
|
||||
if (*sp == NULL || **sp == '\0') {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
for (p = *sp; *p != '\0' && ISDIGIT(*p); p++) ;
|
||||
|
||||
if (p != *sp && (i = atoi(*sp)) >= 0) {
|
||||
*n = (uint16_t)i;
|
||||
*sp = p;
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static mrb_bool
|
||||
skip_char(char **sp, char c)
|
||||
{
|
||||
if (*sp != NULL && **sp == c) {
|
||||
++*sp;
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static mrb_bool
|
||||
parse_lineno(mrb_state *mrb, char **sp, listcmd_parser_state *st)
|
||||
{
|
||||
if (*sp == NULL || **sp == '\0') {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
st->has_line_min = FALSE;
|
||||
st->has_line_max = FALSE;
|
||||
|
||||
if (parse_uint(sp, &st->line_min)) {
|
||||
st->has_line_min = TRUE;
|
||||
}
|
||||
else {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (skip_char(sp, ',')) {
|
||||
if (parse_uint(sp, &st->line_max)) {
|
||||
st->has_line_max = TRUE;
|
||||
}
|
||||
else {
|
||||
st->parse_error = TRUE;
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static mrb_bool
|
||||
parse_filename(mrb_state *mrb, char **sp, listcmd_parser_state *st)
|
||||
{
|
||||
char *p;
|
||||
int len;
|
||||
|
||||
if (st->filename != NULL) {
|
||||
mrb_free(mrb, st->filename);
|
||||
st->filename = NULL;
|
||||
}
|
||||
|
||||
if ((p = strchr(*sp, ':')) != NULL) {
|
||||
len = p - *sp;
|
||||
}
|
||||
else {
|
||||
len = strlen(*sp);
|
||||
}
|
||||
|
||||
if (len > 0) {
|
||||
st->filename = (char*)mrb_malloc(mrb, len + 1);
|
||||
strncpy(st->filename, *sp, len);
|
||||
st->filename[len] = '\0';
|
||||
*sp += len;
|
||||
return TRUE;
|
||||
}
|
||||
else {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
char*
|
||||
replace_ext(mrb_state *mrb, const char *filename, const char *ext)
|
||||
{
|
||||
size_t len;
|
||||
const char *p;
|
||||
char *s;
|
||||
|
||||
if (filename == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ((p = strrchr(filename, '.')) != NULL && strchr(p, '/') == NULL) {
|
||||
len = p - filename;
|
||||
}
|
||||
else {
|
||||
len = strlen(filename);
|
||||
}
|
||||
|
||||
s = (char*)mrb_malloc(mrb, len + strlen(ext) + 1);
|
||||
memset(s, '\0', len + strlen(ext) + 1);
|
||||
strncpy(s, filename, len);
|
||||
strcat(s, ext);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
static mrb_bool
|
||||
parse_listcmd_args(mrb_state *mrb, mrdb_state *mrdb, listcmd_parser_state *st)
|
||||
{
|
||||
char *p;
|
||||
|
||||
switch (mrdb->wcnt) {
|
||||
case 2:
|
||||
p = mrdb->words[1];
|
||||
|
||||
/* mrdb->words[1] ::= <lineno> | <filename> ':' <lineno> | <filename> */
|
||||
if (!parse_lineno(mrb, &p, st)) {
|
||||
if (parse_filename(mrb, &p, st)) {
|
||||
if (skip_char(&p, ':')) {
|
||||
if (!parse_lineno(mrb, &p, st)) {
|
||||
st->parse_error = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
st->parse_error = TRUE;
|
||||
}
|
||||
}
|
||||
if (*p != '\0') {
|
||||
st->parse_error = TRUE;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
case 0:
|
||||
/* do nothing */
|
||||
break;
|
||||
default:
|
||||
st->parse_error = TRUE;
|
||||
printf("too many arguments\n");
|
||||
break;
|
||||
}
|
||||
|
||||
if (!st->parse_error) {
|
||||
if (!st->has_line_min) {
|
||||
st->line_min = (!st->filename && mrdb->dbg->prvline > 0) ? mrdb->dbg->prvline : 1;
|
||||
}
|
||||
|
||||
if (!st->has_line_max) {
|
||||
st->line_max = st->line_min + 9;
|
||||
}
|
||||
|
||||
if (st->filename == NULL) {
|
||||
if (mrdb->dbg->prvfile && strcmp(mrdb->dbg->prvfile, "-")) {
|
||||
st->filename = replace_ext(mrb, mrdb->dbg->prvfile, ".rb");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (st->parse_error || st->filename == NULL) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static mrb_bool
|
||||
check_cmd_pattern(const char *pattern, const char *cmd)
|
||||
{
|
||||
const char *lbracket, *rbracket, *p, *q;
|
||||
|
||||
if (pattern == NULL && cmd == NULL) {
|
||||
return TRUE;
|
||||
}
|
||||
if (pattern == NULL || cmd == NULL) {
|
||||
return FALSE;
|
||||
}
|
||||
if ((lbracket = strchr(pattern, '[')) == NULL) {
|
||||
return !strcmp(pattern, cmd);
|
||||
}
|
||||
if ((rbracket = strchr(pattern, ']')) == NULL) {
|
||||
return FALSE;
|
||||
}
|
||||
if (strncmp(pattern, cmd, lbracket - pattern)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
p = lbracket + 1;
|
||||
q = (char *)cmd + (lbracket - pattern);
|
||||
|
||||
for ( ; p < rbracket && *q != '\0'; p++, q++) {
|
||||
if (*p != *q) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return *q == '\0';
|
||||
}
|
||||
|
||||
static help_msg*
|
||||
get_help_msg(char *cmd1, char *cmd2)
|
||||
{
|
||||
help_msg *p;
|
||||
|
||||
if (cmd1 == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
for (p = help_msg_list; p->cmd1 != NULL; p++) {
|
||||
if (check_cmd_pattern(p->cmd1, cmd1) && check_cmd_pattern(p->cmd2, cmd2)) {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static mrb_bool
|
||||
show_short_help(void)
|
||||
{
|
||||
help_msg *p;
|
||||
|
||||
printf("Commands\n");
|
||||
|
||||
for (p = help_msg_list; p->cmd1 != NULL; p++) {
|
||||
if (p->cmd2 == NULL) {
|
||||
printf(" %s -- %s\n", p->cmd1, p->short_msg);
|
||||
}
|
||||
else {
|
||||
printf(" %s %s -- %s\n", p->cmd1, p->cmd2, p->short_msg);
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static mrb_bool
|
||||
show_long_help(char *cmd1, char *cmd2)
|
||||
{
|
||||
help_msg *help;
|
||||
|
||||
if ((help = get_help_msg(cmd1, cmd2)) == NULL) {
|
||||
return FALSE;
|
||||
}
|
||||
printf("%s", help->long_msg);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
dbgcmd_state
|
||||
dbgcmd_list(mrb_state *mrb, mrdb_state *mrdb)
|
||||
{
|
||||
char *filename;
|
||||
listcmd_parser_state *st = listcmd_parser_state_new(mrb);
|
||||
|
||||
if (parse_listcmd_args(mrb, mrdb, st)) {
|
||||
if ((filename = mrb_debug_get_source(mrb, mrdb, mrdb->srcpath, st->filename)) == NULL) {
|
||||
filename = st->filename;
|
||||
}
|
||||
mrb_debug_list(mrb, mrdb->dbg, filename, st->line_min, st->line_max);
|
||||
|
||||
if (filename != NULL && filename != st->filename) {
|
||||
mrb_free(mrb, filename);
|
||||
}
|
||||
listcmd_parser_state_free(mrb, st);
|
||||
}
|
||||
|
||||
return DBGST_PROMPT;
|
||||
}
|
||||
|
||||
dbgcmd_state
|
||||
dbgcmd_help(mrb_state *mrb, mrdb_state *mrdb)
|
||||
{
|
||||
mrb_bool is_valid;
|
||||
int i;
|
||||
|
||||
switch (mrdb->wcnt) {
|
||||
case 0:
|
||||
case 1:
|
||||
is_valid = show_short_help();
|
||||
break;
|
||||
case 2:
|
||||
is_valid = show_long_help(mrdb->words[1], NULL);
|
||||
break;
|
||||
case 3:
|
||||
is_valid = show_long_help(mrdb->words[1], mrdb->words[2]);
|
||||
break;
|
||||
default:
|
||||
is_valid = FALSE;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!is_valid) {
|
||||
printf("Invalid command \"");
|
||||
for (i = 1; i < mrdb->wcnt; i++) {
|
||||
printf("%s%s", i == 1 ? "" : " ", mrdb->words[i]);
|
||||
}
|
||||
printf("\". Try \"help\".\n");
|
||||
}
|
||||
|
||||
return DBGST_PROMPT;
|
||||
}
|
||||
|
||||
dbgcmd_state
|
||||
dbgcmd_quit(mrb_state *mrb, mrdb_state *mrdb)
|
||||
{
|
||||
switch (mrdb->dbg->xm) {
|
||||
case DBG_RUN:
|
||||
case DBG_STEP:
|
||||
case DBG_NEXT:
|
||||
while (1) {
|
||||
char c;
|
||||
int buf;
|
||||
|
||||
printf("The program is running. Exit anyway? (y or n) ");
|
||||
fflush(stdout);
|
||||
|
||||
if ((buf = getchar()) == EOF) {
|
||||
mrdb->dbg->xm = DBG_QUIT;
|
||||
break;
|
||||
}
|
||||
c = buf;
|
||||
while (buf != '\n' && (buf = getchar()) != EOF) ;
|
||||
|
||||
if (c == 'y' || c == 'Y') {
|
||||
mrdb->dbg->xm = DBG_QUIT;
|
||||
break;
|
||||
}
|
||||
else if (c == 'n' || c == 'N') {
|
||||
break;
|
||||
}
|
||||
else {
|
||||
printf("Please answer y or n.\n");
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
mrdb->dbg->xm = DBG_QUIT;
|
||||
break;
|
||||
}
|
||||
|
||||
if (mrdb->dbg->xm == DBG_QUIT) {
|
||||
struct RClass *exc;
|
||||
exc = mrb_define_class(mrb, "DebuggerExit", mrb->eException_class);
|
||||
mrb_raise(mrb, exc, "Exit mrdb.");
|
||||
}
|
||||
return DBGST_PROMPT;
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
** cmdprint.c - mruby debugger print command functions
|
||||
**
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "mrdb.h"
|
||||
#include <mruby/value.h>
|
||||
#include <mruby/class.h>
|
||||
#include <mruby/compile.h>
|
||||
#include <mruby/error.h>
|
||||
#include <mruby/numeric.h>
|
||||
#include <mruby/string.h>
|
||||
#include "apiprint.h"
|
||||
|
||||
dbgcmd_state
|
||||
dbgcmd_print(mrb_state *mrb, mrdb_state *mrdb)
|
||||
{
|
||||
mrb_value expr;
|
||||
mrb_value result;
|
||||
uint8_t wcnt;
|
||||
int ai;
|
||||
|
||||
if (mrdb->wcnt <= 1) {
|
||||
puts("Parameter not specified.");
|
||||
return DBGST_PROMPT;
|
||||
}
|
||||
|
||||
ai = mrb_gc_arena_save(mrb);
|
||||
|
||||
/* eval expr */
|
||||
expr = mrb_str_new_cstr(mrb, NULL);
|
||||
for (wcnt=1; wcnt<mrdb->wcnt; wcnt++) {
|
||||
expr = mrb_str_cat_lit(mrb, expr, " ");
|
||||
expr = mrb_str_cat_cstr(mrb, expr, mrdb->words[wcnt]);
|
||||
}
|
||||
|
||||
result = mrb_debug_eval(mrb, mrdb->dbg, RSTRING_PTR(expr), RSTRING_LEN(expr), NULL, 0);
|
||||
|
||||
/* $print_no = result */
|
||||
printf("$%lu = ", (unsigned long)mrdb->print_no++);
|
||||
fwrite(RSTRING_PTR(result), RSTRING_LEN(result), 1, stdout);
|
||||
putc('\n', stdout);
|
||||
|
||||
if (mrdb->print_no == 0) {
|
||||
mrdb->print_no = 1;
|
||||
}
|
||||
|
||||
mrb_gc_arena_restore(mrb, ai);
|
||||
|
||||
return DBGST_PROMPT;
|
||||
}
|
||||
|
||||
dbgcmd_state
|
||||
dbgcmd_eval(mrb_state *mrb, mrdb_state *mrdb)
|
||||
{
|
||||
return dbgcmd_print(mrb, mrdb);
|
||||
}
|
||||
|
||||
dbgcmd_state
|
||||
dbgcmd_info_local(mrb_state *mrb, mrdb_state *mrdb)
|
||||
{
|
||||
mrb_value result;
|
||||
mrb_value s;
|
||||
int ai;
|
||||
|
||||
ai = mrb_gc_arena_save(mrb);
|
||||
|
||||
result = mrb_debug_eval(mrb, mrdb->dbg, "local_variables", 0, NULL, 1);
|
||||
|
||||
s = mrb_str_cat_lit(mrb, result, "\0");
|
||||
printf("$%lu = %s\n", (unsigned long)mrdb->print_no++, RSTRING_PTR(s));
|
||||
|
||||
if (mrdb->print_no == 0) {
|
||||
mrdb->print_no = 1;
|
||||
}
|
||||
|
||||
mrb_gc_arena_restore(mrb, ai);
|
||||
|
||||
return DBGST_PROMPT;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
** cmdrun.c - mruby debugger run command functions
|
||||
**
|
||||
*/
|
||||
|
||||
#include <mruby/opcode.h>
|
||||
#include "mrdb.h"
|
||||
|
||||
dbgcmd_state
|
||||
dbgcmd_run(mrb_state *mrb, mrdb_state *mrdb)
|
||||
{
|
||||
mrb_debug_context *dbg = mrdb->dbg;
|
||||
|
||||
if (dbg->xm == DBG_INIT){
|
||||
dbg->xm = DBG_RUN;
|
||||
}
|
||||
else {
|
||||
dbg->xm = DBG_QUIT;
|
||||
if (dbg->xphase == DBG_PHASE_RUNNING){
|
||||
struct RClass *exc;
|
||||
puts("Start it from the beginning.");
|
||||
exc = mrb_define_class(mrb, "DebuggerRestart", mrb->eException_class);
|
||||
mrb_raise(mrb, exc, "Restart mrdb.");
|
||||
}
|
||||
}
|
||||
|
||||
return DBGST_RESTART;
|
||||
}
|
||||
|
||||
dbgcmd_state
|
||||
dbgcmd_continue(mrb_state *mrb, mrdb_state *mrdb)
|
||||
{
|
||||
mrb_debug_context *dbg = mrdb->dbg;
|
||||
int ccnt = 1;
|
||||
|
||||
if (mrdb->wcnt > 1){
|
||||
sscanf(mrdb->words[1], "%d", &ccnt);
|
||||
}
|
||||
dbg->ccnt = (uint16_t)(ccnt > 0 ? ccnt : 1); /* count of continue */
|
||||
|
||||
if (dbg->xphase == DBG_PHASE_AFTER_RUN){
|
||||
puts("The program is not running.");
|
||||
dbg->xm = DBG_QUIT;
|
||||
}
|
||||
else {
|
||||
dbg->xm = DBG_RUN;
|
||||
}
|
||||
return DBGST_CONTINUE;
|
||||
}
|
||||
|
||||
dbgcmd_state
|
||||
dbgcmd_step(mrb_state *mrb, mrdb_state *mrdb)
|
||||
{
|
||||
mrdb->dbg->xm = DBG_STEP;
|
||||
return DBGST_CONTINUE;
|
||||
}
|
||||
|
||||
dbgcmd_state
|
||||
dbgcmd_next(mrb_state *mrb, mrdb_state *mrdb)
|
||||
{
|
||||
mrdb->dbg->xm = DBG_NEXT;
|
||||
mrdb->dbg->prvci = mrb->c->ci;
|
||||
return DBGST_CONTINUE;
|
||||
}
|
||||
@@ -0,0 +1,761 @@
|
||||
/*
|
||||
** mrdb.c - mruby debugger
|
||||
**
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include <mruby.h>
|
||||
#include <mruby/dump.h>
|
||||
#include <mruby/debug.h>
|
||||
#include <mruby/class.h>
|
||||
#include <mruby/opcode.h>
|
||||
#include <mruby/variable.h>
|
||||
|
||||
#include "mrdb.h"
|
||||
#include "apibreak.h"
|
||||
#include "apilist.h"
|
||||
|
||||
void mrdb_state_free(mrb_state *);
|
||||
|
||||
static mrb_debug_context *_debug_context = NULL;
|
||||
static mrdb_state *_mrdb_state = NULL;
|
||||
|
||||
struct _args {
|
||||
FILE *rfp;
|
||||
char* fname;
|
||||
char* srcpath;
|
||||
int argc;
|
||||
char** argv;
|
||||
mrb_bool mrbfile : 1;
|
||||
};
|
||||
|
||||
typedef struct debug_command {
|
||||
const char *cmd1;
|
||||
const char *cmd2;
|
||||
uint8_t len1;
|
||||
uint8_t len2;
|
||||
uint8_t div;
|
||||
debug_command_id id;
|
||||
debug_command_func func;
|
||||
} debug_command;
|
||||
|
||||
static const debug_command debug_command_list[] = {
|
||||
{"break", NULL, 1, 0, 0, DBGCMD_BREAK, dbgcmd_break}, /* b[reak] */
|
||||
{"continue", NULL, 1, 0, 0, DBGCMD_CONTINUE, dbgcmd_continue}, /* c[ontinue] */
|
||||
{"delete", NULL, 1, 0, 1, DBGCMD_DELETE, dbgcmd_delete}, /* d[elete] */
|
||||
{"disable", NULL, 3, 0, 1, DBGCMD_DISABLE, dbgcmd_disable}, /* dis[able] */
|
||||
{"enable", NULL, 2, 0, 1, DBGCMD_ENABLE, dbgcmd_enable}, /* en[able] */
|
||||
{"eval", NULL, 2, 0, 0, DBGCMD_EVAL, dbgcmd_eval}, /* ev[al] */
|
||||
{"help", NULL, 1, 0, 1, DBGCMD_HELP, dbgcmd_help}, /* h[elp] */
|
||||
{"info", "breakpoints", 1, 1, 1, DBGCMD_INFO_BREAK, dbgcmd_info_break}, /* i[nfo] b[reakpoints] */
|
||||
{"info", "locals", 1, 1, 0, DBGCMD_INFO_LOCAL, dbgcmd_info_local}, /* i[nfo] l[ocals] */
|
||||
{"list", NULL, 1, 0, 1, DBGCMD_LIST, dbgcmd_list}, /* l[ist] */
|
||||
{"print", NULL, 1, 0, 0, DBGCMD_PRINT, dbgcmd_print}, /* p[rint] */
|
||||
{"quit", NULL, 1, 0, 0, DBGCMD_QUIT, dbgcmd_quit}, /* q[uit] */
|
||||
{"run", NULL, 1, 0, 0, DBGCMD_RUN, dbgcmd_run}, /* r[un] */
|
||||
{"step", NULL, 1, 0, 1, DBGCMD_STEP, dbgcmd_step}, /* s[tep] */
|
||||
{"next", NULL, 1, 0, 1, DBGCMD_NEXT, dbgcmd_next}, /* n[ext] */
|
||||
{NULL}
|
||||
};
|
||||
|
||||
|
||||
static void
|
||||
usage(const char *name)
|
||||
{
|
||||
static const char *const usage_msg[] = {
|
||||
"switches:",
|
||||
"-b load and execute RiteBinary (mrb) file",
|
||||
"-d specify source directory",
|
||||
"--version print the version",
|
||||
"--copyright print the copyright",
|
||||
NULL
|
||||
};
|
||||
const char *const *p = usage_msg;
|
||||
|
||||
printf("Usage: %s [switches] programfile\n", name);
|
||||
while (*p) {
|
||||
printf(" %s\n", *p++);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
parse_args(mrb_state *mrb, int argc, char **argv, struct _args *args)
|
||||
{
|
||||
char **origargv = argv;
|
||||
static const struct _args args_zero = { 0 };
|
||||
|
||||
*args = args_zero;
|
||||
|
||||
for (argc--,argv++; argc > 0; argc--,argv++) {
|
||||
char *item;
|
||||
if (argv[0][0] != '-') break;
|
||||
|
||||
item = argv[0] + 1;
|
||||
switch (*item++) {
|
||||
case 'b':
|
||||
args->mrbfile = TRUE;
|
||||
break;
|
||||
case 'd':
|
||||
if (item[0]) {
|
||||
goto append_srcpath;
|
||||
}
|
||||
else if (argc > 1) {
|
||||
argc--; argv++;
|
||||
item = argv[0];
|
||||
append_srcpath:
|
||||
if (!args->srcpath) {
|
||||
size_t buflen;
|
||||
char *buf;
|
||||
|
||||
buflen = strlen(item) + 1;
|
||||
buf = (char *)mrb_malloc(mrb, buflen);
|
||||
memcpy(buf, item, buflen);
|
||||
args->srcpath = buf;
|
||||
}
|
||||
else {
|
||||
size_t srcpathlen;
|
||||
size_t itemlen;
|
||||
|
||||
srcpathlen = strlen(args->srcpath);
|
||||
itemlen = strlen(item);
|
||||
args->srcpath =
|
||||
(char *)mrb_realloc(mrb, args->srcpath, srcpathlen + itemlen + 2);
|
||||
args->srcpath[srcpathlen] = '\n';
|
||||
memcpy(args->srcpath + srcpathlen + 1, item, itemlen + 1);
|
||||
}
|
||||
}
|
||||
else {
|
||||
printf("%s: No path specified for -d\n", *origargv);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
break;
|
||||
case '-':
|
||||
if (strcmp((*argv) + 2, "version") == 0) {
|
||||
mrb_show_version(mrb);
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
else if (strcmp((*argv) + 2, "copyright") == 0) {
|
||||
mrb_show_copyright(mrb);
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
default:
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
if (args->rfp == NULL) {
|
||||
if (*argv == NULL) {
|
||||
printf("%s: Program file not specified.\n", *origargv);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
else {
|
||||
args->rfp = fopen(argv[0], args->mrbfile ? "rb" : "r");
|
||||
if (args->rfp == NULL) {
|
||||
printf("%s: Cannot open program file. (%s)\n", *origargv, *argv);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
args->fname = argv[0];
|
||||
argc--; argv++;
|
||||
}
|
||||
}
|
||||
args->argv = (char **)mrb_realloc(mrb, args->argv, sizeof(char*) * (argc + 1));
|
||||
memcpy(args->argv, argv, (argc+1) * sizeof(char*));
|
||||
args->argc = argc;
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
static void
|
||||
cleanup(mrb_state *mrb, struct _args *args)
|
||||
{
|
||||
if (args->rfp)
|
||||
fclose(args->rfp);
|
||||
if (args->srcpath)
|
||||
mrb_free(mrb, args->srcpath);
|
||||
if (args->argv)
|
||||
mrb_free(mrb, args->argv);
|
||||
mrdb_state_free(mrb);
|
||||
mrb_close(mrb);
|
||||
}
|
||||
|
||||
static mrb_debug_context*
|
||||
mrb_debug_context_new(mrb_state *mrb)
|
||||
{
|
||||
mrb_debug_context *dbg = (mrb_debug_context*)mrb_malloc(mrb, sizeof(mrb_debug_context));
|
||||
|
||||
memset(dbg, 0, sizeof(mrb_debug_context));
|
||||
|
||||
dbg->xm = DBG_INIT;
|
||||
dbg->xphase = DBG_PHASE_BEFORE_RUN;
|
||||
dbg->next_bpno = 1;
|
||||
|
||||
return dbg;
|
||||
}
|
||||
|
||||
mrb_debug_context*
|
||||
mrb_debug_context_get(mrb_state *mrb)
|
||||
{
|
||||
if (!_debug_context) {
|
||||
_debug_context = mrb_debug_context_new(mrb);
|
||||
}
|
||||
return _debug_context;
|
||||
}
|
||||
|
||||
void
|
||||
mrb_debug_context_set(mrb_debug_context *dbg)
|
||||
{
|
||||
_debug_context = dbg;
|
||||
}
|
||||
|
||||
void
|
||||
mrb_debug_context_free(mrb_state *mrb)
|
||||
{
|
||||
if (_debug_context) {
|
||||
mrb_debug_delete_break_all(mrb, _debug_context);
|
||||
mrb_free(mrb, _debug_context);
|
||||
_debug_context = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static mrdb_state*
|
||||
mrdb_state_new(mrb_state *mrb)
|
||||
{
|
||||
mrdb_state *mrdb = (mrdb_state*)mrb_malloc(mrb, sizeof(mrdb_state));
|
||||
|
||||
memset(mrdb, 0, sizeof(mrdb_state));
|
||||
|
||||
mrdb->dbg = mrb_debug_context_get(mrb);
|
||||
mrdb->command = (char*)mrb_malloc(mrb, MAX_COMMAND_LINE+1);
|
||||
mrdb->print_no = 1;
|
||||
|
||||
return mrdb;
|
||||
}
|
||||
|
||||
mrdb_state*
|
||||
mrdb_state_get(mrb_state *mrb)
|
||||
{
|
||||
if (!_mrdb_state) {
|
||||
_mrdb_state = mrdb_state_new(mrb);
|
||||
}
|
||||
return _mrdb_state;
|
||||
}
|
||||
|
||||
void
|
||||
mrdb_state_set(mrdb_state *mrdb)
|
||||
{
|
||||
_mrdb_state = mrdb;
|
||||
}
|
||||
|
||||
void
|
||||
mrdb_state_free(mrb_state *mrb)
|
||||
{
|
||||
mrb_debug_context_free(mrb);
|
||||
if (_mrdb_state) {
|
||||
mrb_free(mrb, _mrdb_state->command);
|
||||
mrb_free(mrb, _mrdb_state);
|
||||
_mrdb_state = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static char*
|
||||
get_command(mrb_state *mrb, mrdb_state *mrdb)
|
||||
{
|
||||
int i;
|
||||
int c;
|
||||
|
||||
for (i=0; i<MAX_COMMAND_LINE; i++) {
|
||||
if ((c=getchar()) == EOF || c == '\n') break;
|
||||
mrdb->command[i] = c;
|
||||
}
|
||||
|
||||
if (i == 0 && feof(stdin)) {
|
||||
clearerr(stdin);
|
||||
strcpy(mrdb->command, "quit");
|
||||
i += sizeof("quit") - 1;
|
||||
}
|
||||
|
||||
if (i == MAX_COMMAND_LINE) {
|
||||
for ( ; (c=getchar()) != EOF && c !='\n'; i++) ;
|
||||
}
|
||||
|
||||
if (i > MAX_COMMAND_LINE) {
|
||||
printf("command line too long.\n");
|
||||
i = 0; /* discard command data */
|
||||
}
|
||||
mrdb->command[i] = '\0';
|
||||
|
||||
return mrdb->command;
|
||||
}
|
||||
|
||||
static char*
|
||||
pick_out_word(mrb_state *mrb, char **pp)
|
||||
{
|
||||
char *ps;
|
||||
|
||||
for (ps=*pp; ISBLANK(*ps); ps++) ;
|
||||
if (*ps == '\0') {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (*ps == '\"' || *ps == '\'') {
|
||||
*pp = strchr(ps+1, *ps);
|
||||
if (*pp) (*pp)++;
|
||||
}
|
||||
else {
|
||||
*pp = strpbrk(ps, " \t");
|
||||
}
|
||||
|
||||
if (!*pp) {
|
||||
*pp = ps + strlen(ps);
|
||||
}
|
||||
|
||||
if (**pp != '\0') {
|
||||
**pp = '\0';
|
||||
(*pp)++;
|
||||
}
|
||||
|
||||
return ps;
|
||||
}
|
||||
|
||||
static debug_command*
|
||||
parse_command(mrb_state *mrb, mrdb_state *mrdb, char *buf)
|
||||
{
|
||||
debug_command *cmd = NULL;
|
||||
char *p = buf;
|
||||
size_t wlen;
|
||||
|
||||
/* get word #1 */
|
||||
mrdb->words[0] = pick_out_word(mrb, &p);
|
||||
if (!mrdb->words[0]) {
|
||||
return NULL;
|
||||
}
|
||||
mrdb->wcnt = 1;
|
||||
/* set remain parameter */
|
||||
for ( ; *p && ISBLANK(*p); p++) ;
|
||||
if (*p) {
|
||||
mrdb->words[mrdb->wcnt++] = p;
|
||||
}
|
||||
|
||||
/* check word #1 */
|
||||
for (cmd=(debug_command*)debug_command_list; cmd->cmd1; cmd++) {
|
||||
wlen = strlen(mrdb->words[0]);
|
||||
if (wlen >= cmd->len1 &&
|
||||
strncmp(mrdb->words[0], cmd->cmd1, wlen) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (cmd->cmd2) {
|
||||
if (mrdb->wcnt > 1) {
|
||||
/* get word #2 */
|
||||
mrdb->words[1] = pick_out_word(mrb, &p);
|
||||
if (mrdb->words[1]) {
|
||||
/* update remain parameter */
|
||||
for ( ; *p && ISBLANK(*p); p++) ;
|
||||
if (*p) {
|
||||
mrdb->words[mrdb->wcnt++] = p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* check word #1,#2 */
|
||||
for ( ; cmd->cmd1; cmd++) {
|
||||
wlen = strlen(mrdb->words[0]);
|
||||
if (wlen < cmd->len1 ||
|
||||
strncmp(mrdb->words[0], cmd->cmd1, wlen)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!cmd->cmd2) break; /* word #1 only */
|
||||
|
||||
if (mrdb->wcnt == 1) continue; /* word #2 not specified */
|
||||
|
||||
wlen = strlen(mrdb->words[1]);
|
||||
if (wlen >= cmd->len2 &&
|
||||
strncmp(mrdb->words[1], cmd->cmd2, wlen) == 0) {
|
||||
break; /* word #1 and #2 */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* divide remain parameters */
|
||||
if (cmd->cmd1 && cmd->div) {
|
||||
p = mrdb->words[--mrdb->wcnt];
|
||||
for ( ; mrdb->wcnt<MAX_COMMAND_WORD; mrdb->wcnt++) {
|
||||
mrdb->words[mrdb->wcnt] = pick_out_word(mrb, &p);
|
||||
if (!mrdb->words[mrdb->wcnt]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cmd->cmd1 ? cmd : NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
print_info_stopped_break(mrb_state *mrb, mrdb_state *mrdb)
|
||||
{
|
||||
mrb_debug_breakpoint bp;
|
||||
int32_t ret;
|
||||
uint16_t lineno;
|
||||
const char *file;
|
||||
const char *method_name;
|
||||
const char *class_name;
|
||||
|
||||
ret = mrb_debug_get_break(mrb, mrdb->dbg, mrdb->dbg->stopped_bpno, &bp);
|
||||
if (ret == 0) {
|
||||
switch(bp.type) {
|
||||
case MRB_DEBUG_BPTYPE_LINE:
|
||||
file = bp.point.linepoint.file;
|
||||
lineno = bp.point.linepoint.lineno;
|
||||
printf("Breakpoint %d, at %s:%d\n", bp.bpno, file, lineno);
|
||||
break;
|
||||
case MRB_DEBUG_BPTYPE_METHOD:
|
||||
method_name = bp.point.methodpoint.method_name;
|
||||
class_name = bp.point.methodpoint.class_name;
|
||||
if (class_name == NULL) {
|
||||
printf("Breakpoint %d, %s\n", bp.bpno, method_name);
|
||||
}
|
||||
else {
|
||||
printf("Breakpoint %d, %s:%s\n", bp.bpno, class_name, method_name);
|
||||
}
|
||||
if (mrdb->dbg->isCfunc) {
|
||||
printf("Stopped before calling the C function.\n");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
print_info_stopped_step_next(mrb_state *mrb, mrdb_state *mrdb)
|
||||
{
|
||||
const char* file = mrdb->dbg->prvfile;
|
||||
uint16_t lineno = mrdb->dbg->prvline;
|
||||
printf("%s:%d\n", file, lineno);
|
||||
}
|
||||
|
||||
static void
|
||||
print_info_stopped_code(mrb_state *mrb, mrdb_state *mrdb)
|
||||
{
|
||||
char* file = mrb_debug_get_source(mrb, mrdb, mrdb->srcpath, mrdb->dbg->prvfile);
|
||||
uint16_t lineno = mrdb->dbg->prvline;
|
||||
if (file != NULL) {
|
||||
mrb_debug_list(mrb, mrdb->dbg, file, lineno, lineno);
|
||||
mrb_free(mrb, file);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
print_info_stopped(mrb_state *mrb, mrdb_state *mrdb)
|
||||
{
|
||||
switch(mrdb->dbg->bm) {
|
||||
case BRK_BREAK:
|
||||
print_info_stopped_break(mrb, mrdb);
|
||||
print_info_stopped_code(mrb, mrdb);
|
||||
break;
|
||||
case BRK_STEP:
|
||||
case BRK_NEXT:
|
||||
print_info_stopped_step_next(mrb, mrdb);
|
||||
print_info_stopped_code(mrb, mrdb);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static debug_command*
|
||||
get_and_parse_command(mrb_state *mrb, mrdb_state *mrdb)
|
||||
{
|
||||
debug_command *cmd = NULL;
|
||||
char *p;
|
||||
int i;
|
||||
|
||||
while (!cmd) {
|
||||
for (p=NULL; !p || *p=='\0'; ) {
|
||||
printf("(%s:%d) ", mrdb->dbg->prvfile, mrdb->dbg->prvline);
|
||||
fflush(stdout);
|
||||
p = get_command(mrb, mrdb);
|
||||
}
|
||||
|
||||
cmd = parse_command(mrb, mrdb, p);
|
||||
#ifdef _DBG_MRDB_PARSER_
|
||||
for (i=0; i<mrdb->wcnt; i++) {
|
||||
printf("%d: %s\n", i, mrdb->words[i]);
|
||||
}
|
||||
#endif
|
||||
if (!cmd) {
|
||||
printf("invalid command (");
|
||||
for (i=0; i<mrdb->wcnt; i++) {
|
||||
if (i>0) {
|
||||
printf(" ");
|
||||
}
|
||||
printf("%s", mrdb->words[i]);
|
||||
}
|
||||
puts(")");
|
||||
}
|
||||
}
|
||||
return cmd;
|
||||
}
|
||||
|
||||
static int32_t
|
||||
check_method_breakpoint(mrb_state *mrb, mrb_irep *irep, const mrb_code *pc, mrb_value *regs)
|
||||
{
|
||||
struct RClass* c;
|
||||
mrb_sym sym;
|
||||
int32_t bpno;
|
||||
mrb_bool isCfunc;
|
||||
struct mrb_insn_data insn;
|
||||
|
||||
mrb_debug_context *dbg = mrb_debug_context_get(mrb);
|
||||
|
||||
isCfunc = FALSE;
|
||||
bpno = dbg->method_bpno;
|
||||
dbg->method_bpno = 0;
|
||||
|
||||
insn = mrb_decode_insn(pc);
|
||||
switch(insn.insn) {
|
||||
case OP_SEND:
|
||||
case OP_SENDB:
|
||||
c = mrb_class(mrb, regs[insn.a]);
|
||||
sym = irep->syms[insn.b];
|
||||
break;
|
||||
case OP_SUPER:
|
||||
c = mrb->c->ci->target_class->super;
|
||||
sym = mrb->c->ci->mid;
|
||||
break;
|
||||
default:
|
||||
sym = 0;
|
||||
break;
|
||||
}
|
||||
if (sym != 0) {
|
||||
dbg->method_bpno = mrb_debug_check_breakpoint_method(mrb, dbg, c, sym, &isCfunc);
|
||||
if (isCfunc) {
|
||||
bpno = dbg->method_bpno;
|
||||
dbg->method_bpno = 0;
|
||||
}
|
||||
}
|
||||
dbg->isCfunc = isCfunc;
|
||||
return bpno;
|
||||
}
|
||||
|
||||
static void
|
||||
mrb_code_fetch_hook(mrb_state *mrb, mrb_irep *irep, const mrb_code *pc, mrb_value *regs)
|
||||
{
|
||||
const char *file;
|
||||
int32_t line;
|
||||
int32_t bpno;
|
||||
|
||||
mrb_debug_context *dbg = mrb_debug_context_get(mrb);
|
||||
|
||||
mrb_assert(dbg);
|
||||
|
||||
dbg->irep = irep;
|
||||
dbg->pc = pc;
|
||||
dbg->regs = regs;
|
||||
|
||||
if (dbg->xphase == DBG_PHASE_RESTART) {
|
||||
dbg->root_irep = irep;
|
||||
dbg->prvfile = NULL;
|
||||
dbg->prvline = 0;
|
||||
dbg->prvci = NULL;
|
||||
dbg->xm = DBG_RUN;
|
||||
dbg->xphase = DBG_PHASE_RUNNING;
|
||||
}
|
||||
|
||||
file = mrb_debug_get_filename(mrb, irep, pc - irep->iseq);
|
||||
line = mrb_debug_get_line(mrb, irep, pc - irep->iseq);
|
||||
|
||||
switch (dbg->xm) {
|
||||
case DBG_STEP:
|
||||
if (!file || (dbg->prvfile == file && dbg->prvline == line)) {
|
||||
return;
|
||||
}
|
||||
dbg->method_bpno = 0;
|
||||
dbg->bm = BRK_STEP;
|
||||
break;
|
||||
|
||||
case DBG_NEXT:
|
||||
if (!file || (dbg->prvfile == file && dbg->prvline == line)) {
|
||||
return;
|
||||
}
|
||||
if ((intptr_t)(dbg->prvci) < (intptr_t)(mrb->c->ci)) {
|
||||
return;
|
||||
}
|
||||
dbg->prvci = NULL;
|
||||
dbg->method_bpno = 0;
|
||||
dbg->bm = BRK_NEXT;
|
||||
break;
|
||||
|
||||
case DBG_RUN:
|
||||
bpno = check_method_breakpoint(mrb, irep, pc, regs);
|
||||
if (bpno > 0) {
|
||||
dbg->stopped_bpno = bpno;
|
||||
dbg->bm = BRK_BREAK;
|
||||
break;
|
||||
}
|
||||
if (dbg->prvfile != file || dbg->prvline != line) {
|
||||
bpno = mrb_debug_check_breakpoint_line(mrb, dbg, file, line);
|
||||
if (bpno > 0) {
|
||||
dbg->stopped_bpno = bpno;
|
||||
dbg->bm = BRK_BREAK;
|
||||
break;
|
||||
}
|
||||
}
|
||||
dbg->prvfile = file;
|
||||
dbg->prvline = line;
|
||||
return;
|
||||
case DBG_INIT:
|
||||
dbg->root_irep = irep;
|
||||
dbg->bm = BRK_INIT;
|
||||
if (!file || line < 0) {
|
||||
puts("Cannot get debugging information.");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
dbg->prvfile = file;
|
||||
dbg->prvline = line;
|
||||
|
||||
if (dbg->bm == BRK_BREAK && --dbg->ccnt > 0) {
|
||||
return;
|
||||
}
|
||||
dbg->break_hook(mrb, dbg);
|
||||
|
||||
dbg->xphase = DBG_PHASE_RUNNING;
|
||||
}
|
||||
|
||||
static mrdb_exemode
|
||||
mrb_debug_break_hook(mrb_state *mrb, mrb_debug_context *dbg)
|
||||
{
|
||||
debug_command *cmd;
|
||||
dbgcmd_state st = DBGST_CONTINUE;
|
||||
mrdb_state *mrdb = mrdb_state_get(mrb);
|
||||
|
||||
print_info_stopped(mrb, mrdb);
|
||||
|
||||
while (1) {
|
||||
cmd = get_and_parse_command(mrb, mrdb);
|
||||
mrb_assert(cmd);
|
||||
|
||||
st = cmd->func(mrb, mrdb);
|
||||
|
||||
if ((st == DBGST_CONTINUE) || (st == DBGST_RESTART)) break;
|
||||
}
|
||||
return dbg->xm;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
mrb_state *mrb = mrb_open();
|
||||
int n = -1;
|
||||
struct _args args;
|
||||
mrb_value v;
|
||||
mrdb_state *mrdb;
|
||||
mrdb_state *mrdb_backup;
|
||||
mrb_debug_context* dbg_backup;
|
||||
debug_command *cmd;
|
||||
|
||||
l_restart:
|
||||
|
||||
if (mrb == NULL) {
|
||||
fputs("Invalid mrb_state, exiting mruby\n", stderr);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/* parse command parameters */
|
||||
n = parse_args(mrb, argc, argv, &args);
|
||||
if (n == EXIT_FAILURE || args.rfp == NULL) {
|
||||
cleanup(mrb, &args);
|
||||
usage(argv[0]);
|
||||
return n;
|
||||
}
|
||||
|
||||
/* initialize debugger information */
|
||||
mrdb = mrdb_state_get(mrb);
|
||||
mrb_assert(mrdb && mrdb->dbg);
|
||||
mrdb->srcpath = args.srcpath;
|
||||
|
||||
if (mrdb->dbg->xm == DBG_QUIT) {
|
||||
mrdb->dbg->xphase = DBG_PHASE_RESTART;
|
||||
}
|
||||
else {
|
||||
mrdb->dbg->xphase = DBG_PHASE_BEFORE_RUN;
|
||||
}
|
||||
mrdb->dbg->xm = DBG_INIT;
|
||||
mrdb->dbg->ccnt = 1;
|
||||
|
||||
/* setup hook functions */
|
||||
mrb->code_fetch_hook = mrb_code_fetch_hook;
|
||||
mrdb->dbg->break_hook = mrb_debug_break_hook;
|
||||
|
||||
if (args.mrbfile) { /* .mrb */
|
||||
v = mrb_load_irep_file(mrb, args.rfp);
|
||||
}
|
||||
else { /* .rb */
|
||||
mrbc_context *cc = mrbc_context_new(mrb);
|
||||
mrbc_filename(mrb, cc, args.fname);
|
||||
v = mrb_load_file_cxt(mrb, args.rfp, cc);
|
||||
mrbc_context_free(mrb, cc);
|
||||
}
|
||||
if (mrdb->dbg->xm == DBG_QUIT && !mrb_undef_p(v) && mrb->exc) {
|
||||
const char *classname = mrb_obj_classname(mrb, mrb_obj_value(mrb->exc));
|
||||
if (!strcmp(classname, "DebuggerExit")) {
|
||||
cleanup(mrb, &args);
|
||||
return 0;
|
||||
}
|
||||
if (!strcmp(classname, "DebuggerRestart")) {
|
||||
mrdb_backup = mrdb_state_get(mrb);
|
||||
dbg_backup = mrb_debug_context_get(mrb);
|
||||
|
||||
mrdb_state_set(NULL);
|
||||
mrb_debug_context_set(NULL);
|
||||
|
||||
cleanup(mrb, &args);
|
||||
mrb = mrb_open();
|
||||
|
||||
mrdb_state_set(mrdb_backup);
|
||||
mrb_debug_context_set(dbg_backup);
|
||||
|
||||
goto l_restart;
|
||||
}
|
||||
}
|
||||
puts("mruby application exited.");
|
||||
mrdb->dbg->xphase = DBG_PHASE_AFTER_RUN;
|
||||
if (!mrb_undef_p(v)) {
|
||||
if (mrb->exc) {
|
||||
mrb_print_error(mrb);
|
||||
}
|
||||
else {
|
||||
printf(" => ");
|
||||
mrb_p(mrb, v);
|
||||
}
|
||||
}
|
||||
|
||||
mrdb->dbg->prvfile = "-";
|
||||
mrdb->dbg->prvline = 0;
|
||||
|
||||
while (1) {
|
||||
cmd = get_and_parse_command(mrb, mrdb);
|
||||
mrb_assert(cmd);
|
||||
|
||||
if (cmd->id == DBGCMD_QUIT) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ( cmd->func(mrb, mrdb) == DBGST_RESTART ) goto l_restart;
|
||||
}
|
||||
|
||||
cleanup(mrb, &args);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
** mrdb.h - mruby debugger
|
||||
**
|
||||
*/
|
||||
|
||||
#ifndef MRDB_H
|
||||
#define MRDB_H
|
||||
|
||||
#include <mruby.h>
|
||||
|
||||
#include "mrdbconf.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
# define __func__ __FUNCTION__
|
||||
#endif
|
||||
|
||||
#define MAX_COMMAND_WORD (16)
|
||||
|
||||
typedef enum debug_command_id {
|
||||
DBGCMD_RUN,
|
||||
DBGCMD_CONTINUE,
|
||||
DBGCMD_NEXT,
|
||||
DBGCMD_STEP,
|
||||
DBGCMD_BREAK,
|
||||
DBGCMD_INFO_BREAK,
|
||||
DBGCMD_INFO_LOCAL,
|
||||
DBGCMD_WATCH,
|
||||
DBGCMD_INFO_WATCH,
|
||||
DBGCMD_ENABLE,
|
||||
DBGCMD_DISABLE,
|
||||
DBGCMD_DELETE,
|
||||
DBGCMD_PRINT,
|
||||
DBGCMD_DISPLAY,
|
||||
DBGCMD_INFO_DISPLAY,
|
||||
DBGCMD_DELETE_DISPLAY,
|
||||
DBGCMD_EVAL,
|
||||
DBGCMD_BACKTRACE,
|
||||
DBGCMD_LIST,
|
||||
DBGCMD_HELP,
|
||||
DBGCMD_QUIT,
|
||||
DBGCMD_UNKNOWN
|
||||
} debug_command_id;
|
||||
|
||||
typedef enum dbgcmd_state {
|
||||
DBGST_CONTINUE,
|
||||
DBGST_PROMPT,
|
||||
DBGST_COMMAND_ERROR,
|
||||
DBGST_MAX,
|
||||
DBGST_RESTART
|
||||
} dbgcmd_state;
|
||||
|
||||
typedef enum mrdb_exemode {
|
||||
DBG_INIT,
|
||||
DBG_RUN,
|
||||
DBG_STEP,
|
||||
DBG_NEXT,
|
||||
DBG_QUIT,
|
||||
} mrdb_exemode;
|
||||
|
||||
typedef enum mrdb_exephase {
|
||||
DBG_PHASE_BEFORE_RUN,
|
||||
DBG_PHASE_RUNNING,
|
||||
DBG_PHASE_AFTER_RUN,
|
||||
DBG_PHASE_RESTART,
|
||||
} mrdb_exephase;
|
||||
|
||||
typedef enum mrdb_brkmode {
|
||||
BRK_INIT,
|
||||
BRK_BREAK,
|
||||
BRK_STEP,
|
||||
BRK_NEXT,
|
||||
BRK_QUIT,
|
||||
} mrdb_brkmode;
|
||||
|
||||
typedef enum {
|
||||
MRB_DEBUG_BPTYPE_NONE,
|
||||
MRB_DEBUG_BPTYPE_LINE,
|
||||
MRB_DEBUG_BPTYPE_METHOD,
|
||||
} mrb_debug_bptype;
|
||||
|
||||
struct mrb_irep;
|
||||
struct mrbc_context;
|
||||
struct mrb_debug_context;
|
||||
|
||||
typedef struct mrb_debug_linepoint {
|
||||
const char *file;
|
||||
uint16_t lineno;
|
||||
} mrb_debug_linepoint;
|
||||
|
||||
typedef struct mrb_debug_methodpoint {
|
||||
const char *class_name;
|
||||
const char *method_name;
|
||||
} mrb_debug_methodpoint;
|
||||
|
||||
typedef struct mrb_debug_breakpoint {
|
||||
uint32_t bpno;
|
||||
uint8_t enable;
|
||||
mrb_debug_bptype type;
|
||||
union point {
|
||||
mrb_debug_linepoint linepoint;
|
||||
mrb_debug_methodpoint methodpoint;
|
||||
} point;
|
||||
} mrb_debug_breakpoint;
|
||||
|
||||
typedef struct mrb_debug_context {
|
||||
struct mrb_irep *root_irep;
|
||||
struct mrb_irep *irep;
|
||||
const mrb_code *pc;
|
||||
mrb_value *regs;
|
||||
|
||||
const char *prvfile;
|
||||
int32_t prvline;
|
||||
mrb_callinfo *prvci;
|
||||
|
||||
mrdb_exemode xm;
|
||||
mrdb_exephase xphase;
|
||||
mrdb_brkmode bm;
|
||||
int16_t bmi;
|
||||
|
||||
uint16_t ccnt;
|
||||
uint16_t scnt;
|
||||
|
||||
mrb_debug_breakpoint bp[MAX_BREAKPOINT];
|
||||
uint32_t bpnum;
|
||||
int32_t next_bpno;
|
||||
int32_t method_bpno;
|
||||
int32_t stopped_bpno;
|
||||
mrb_bool isCfunc;
|
||||
|
||||
mrdb_exemode (*break_hook)(mrb_state *mrb, struct mrb_debug_context *dbg);
|
||||
|
||||
} mrb_debug_context;
|
||||
|
||||
typedef struct mrdb_state {
|
||||
char *command;
|
||||
uint8_t wcnt;
|
||||
uint8_t pi;
|
||||
char *words[MAX_COMMAND_WORD];
|
||||
const char *srcpath;
|
||||
uint32_t print_no;
|
||||
|
||||
mrb_debug_context *dbg;
|
||||
} mrdb_state;
|
||||
|
||||
typedef dbgcmd_state (*debug_command_func)(mrb_state*, mrdb_state*);
|
||||
|
||||
/* cmdrun.c */
|
||||
dbgcmd_state dbgcmd_run(mrb_state*, mrdb_state*);
|
||||
dbgcmd_state dbgcmd_continue(mrb_state*, mrdb_state*);
|
||||
dbgcmd_state dbgcmd_step(mrb_state*, mrdb_state*);
|
||||
dbgcmd_state dbgcmd_next(mrb_state*, mrdb_state*);
|
||||
/* cmdbreak.c */
|
||||
dbgcmd_state dbgcmd_break(mrb_state*, mrdb_state*);
|
||||
dbgcmd_state dbgcmd_info_break(mrb_state*, mrdb_state*);
|
||||
dbgcmd_state dbgcmd_info_local(mrb_state*, mrdb_state*);
|
||||
dbgcmd_state dbgcmd_delete(mrb_state*, mrdb_state*);
|
||||
dbgcmd_state dbgcmd_enable(mrb_state*, mrdb_state*);
|
||||
dbgcmd_state dbgcmd_disable(mrb_state*, mrdb_state*);
|
||||
/* cmdprint.c */
|
||||
dbgcmd_state dbgcmd_print(mrb_state*, mrdb_state*);
|
||||
dbgcmd_state dbgcmd_eval(mrb_state*, mrdb_state*);
|
||||
/* cmdmisc.c */
|
||||
dbgcmd_state dbgcmd_list(mrb_state*, mrdb_state*);
|
||||
dbgcmd_state dbgcmd_help(mrb_state*, mrdb_state*);
|
||||
dbgcmd_state dbgcmd_quit(mrb_state*, mrdb_state*);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
** mrdbconf.h - mruby debugger configuration
|
||||
**
|
||||
*/
|
||||
|
||||
#ifndef MRDBCONF_H
|
||||
#define MRDBCONF_H
|
||||
|
||||
#ifndef MRB_ENABLE_DEBUG_HOOK
|
||||
# error mruby-bin-debugger need 'MRB_ENABLE_DEBUG_HOOK' configuration in your 'build_config.rb'
|
||||
#endif
|
||||
|
||||
#ifdef MRB_DISABLE_STDIO
|
||||
# error mruby-bin-debugger conflicts 'MRB_DISABLE_STDIO' configuration in your 'build_config.rb'
|
||||
#endif
|
||||
|
||||
/* configuration options: */
|
||||
/* maximum size for command buffer */
|
||||
#define MAX_COMMAND_LINE 1024
|
||||
|
||||
/* maximum number of setable breakpoint */
|
||||
#define MAX_BREAKPOINT 5
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
** mrdberror.h - mruby debugger error code
|
||||
**
|
||||
*/
|
||||
|
||||
#ifndef MRDBERROR_H
|
||||
#define MRDBERROR_H
|
||||
|
||||
#define MRB_DEBUG_OK (0)
|
||||
#define MRB_DEBUG_NOBUF (-1)
|
||||
#define MRB_DEBUG_INVALID_ARGUMENT (-2)
|
||||
|
||||
#define MRB_DEBUG_BREAK_INVALID_LINENO (-11)
|
||||
#define MRB_DEBUG_BREAK_INVALID_FILE (-12)
|
||||
#define MRB_DEBUG_BREAK_INVALID_NO (-13)
|
||||
#define MRB_DEBUG_BREAK_NUM_OVER (-14)
|
||||
#define MRB_DEBUG_BREAK_NO_OVER (-15)
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
require 'open3'
|
||||
|
||||
assert('mirb normal operations') do
|
||||
o, s = Open3.capture2('bin/mirb', :stdin_data => "a=1\nb=2\na+b\n")
|
||||
assert_true o.include?('=> 3')
|
||||
assert_true o.include?('=> 2')
|
||||
end
|
||||
|
||||
assert('regression for #1563') do
|
||||
o, s = Open3.capture2('bin/mirb', :stdin_data => "a=1;b=2;c=3\nb\nc")
|
||||
assert_true o.include?('=> 3')
|
||||
end
|
||||
|
||||
assert('mirb -d option') do
|
||||
o, _ = Open3.capture2('bin/mirb', :stdin_data => "$DEBUG\n")
|
||||
assert_true o.include?('=> false')
|
||||
o, _ = Open3.capture2('bin/mirb -d', :stdin_data => "$DEBUG\n")
|
||||
assert_true o.include?('=> true')
|
||||
end
|
||||
|
||||
assert('mirb -r option') do
|
||||
lib = Tempfile.new('lib.rb')
|
||||
lib.write <<EOS
|
||||
class Hoge
|
||||
def hoge
|
||||
:hoge
|
||||
end
|
||||
end
|
||||
EOS
|
||||
lib.flush
|
||||
|
||||
o, _ = Open3.capture2("bin/mirb -r #{lib.path}", :stdin_data => "Hoge.new.hoge\n")
|
||||
assert_true o.include?('=> :hoge')
|
||||
end
|
||||
@@ -0,0 +1,33 @@
|
||||
MRuby::Gem::Specification.new('mruby-bin-mirb') do |spec|
|
||||
spec.license = 'MIT'
|
||||
spec.author = 'mruby developers'
|
||||
spec.summary = 'mirb command'
|
||||
|
||||
if spec.build.cc.search_header_path 'readline/readline.h'
|
||||
spec.cc.defines << "ENABLE_READLINE"
|
||||
if spec.build.cc.search_header_path 'termcap.h'
|
||||
if MRUBY_BUILD_HOST_IS_CYGWIN || MRUBY_BUILD_HOST_IS_OPENBSD
|
||||
if spec.build.cc.search_header_path 'termcap.h'
|
||||
if MRUBY_BUILD_HOST_IS_CYGWIN then
|
||||
spec.linker.libraries << 'ncurses'
|
||||
else
|
||||
spec.linker.libraries << 'termcap'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if RUBY_PLATFORM.include?('netbsd')
|
||||
spec.linker.libraries << 'edit'
|
||||
else
|
||||
spec.linker.libraries << 'readline'
|
||||
if spec.build.cc.search_header_path 'curses.h'
|
||||
spec.linker.libraries << 'ncurses'
|
||||
end
|
||||
end
|
||||
elsif spec.build.cc.search_header_path 'linenoise.h'
|
||||
spec.cc.defines << "ENABLE_LINENOISE"
|
||||
end
|
||||
|
||||
spec.bins = %w(mirb)
|
||||
spec.add_dependency('mruby-compiler', :core => 'mruby-compiler')
|
||||
end
|
||||
@@ -0,0 +1,709 @@
|
||||
/*
|
||||
** mirb - Embeddable Interactive Ruby Shell
|
||||
**
|
||||
** This program takes code from the user in
|
||||
** an interactive way and executes it
|
||||
** immediately. It's a REPL...
|
||||
*/
|
||||
|
||||
#include <mruby.h>
|
||||
|
||||
#ifdef MRB_DISABLE_STDIO
|
||||
# error mruby-bin-mirb conflicts 'MRB_DISABLE_STDIO' configuration in your 'build_config.rb'
|
||||
#endif
|
||||
|
||||
#include <mruby/array.h>
|
||||
#include <mruby/proc.h>
|
||||
#include <mruby/compile.h>
|
||||
#include <mruby/dump.h>
|
||||
#include <mruby/string.h>
|
||||
#include <mruby/variable.h>
|
||||
#include <mruby/throw.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include <signal.h>
|
||||
#include <setjmp.h>
|
||||
|
||||
#ifdef ENABLE_READLINE
|
||||
#include <readline/readline.h>
|
||||
#include <readline/history.h>
|
||||
#define MIRB_ADD_HISTORY(line) add_history(line)
|
||||
#define MIRB_READLINE(ch) readline(ch)
|
||||
#if !defined(RL_READLINE_VERSION) || RL_READLINE_VERSION < 0x600
|
||||
/* libedit & older readline do not have rl_free() */
|
||||
#define MIRB_LINE_FREE(line) free(line)
|
||||
#else
|
||||
#define MIRB_LINE_FREE(line) rl_free(line)
|
||||
#endif
|
||||
#define MIRB_WRITE_HISTORY(path) write_history(path)
|
||||
#define MIRB_READ_HISTORY(path) read_history(path)
|
||||
#define MIRB_USING_HISTORY() using_history()
|
||||
#elif defined(ENABLE_LINENOISE)
|
||||
#define ENABLE_READLINE
|
||||
#include <linenoise.h>
|
||||
#define MIRB_ADD_HISTORY(line) linenoiseHistoryAdd(line)
|
||||
#define MIRB_READLINE(ch) linenoise(ch)
|
||||
#define MIRB_LINE_FREE(line) linenoiseFree(line)
|
||||
#define MIRB_WRITE_HISTORY(path) linenoiseHistorySave(path)
|
||||
#define MIRB_READ_HISTORY(path) linenoiseHistoryLoad(history_path)
|
||||
#define MIRB_USING_HISTORY()
|
||||
#endif
|
||||
|
||||
#ifndef _WIN32
|
||||
#define MIRB_SIGSETJMP(env) sigsetjmp(env, 1)
|
||||
#define MIRB_SIGLONGJMP(env, val) siglongjmp(env, val)
|
||||
#define SIGJMP_BUF sigjmp_buf
|
||||
#else
|
||||
#define MIRB_SIGSETJMP(env) setjmp(env)
|
||||
#define MIRB_SIGLONGJMP(env, val) longjmp(env, val)
|
||||
#define SIGJMP_BUF jmp_buf
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_READLINE
|
||||
|
||||
static const char history_file_name[] = ".mirb_history";
|
||||
|
||||
static char *
|
||||
get_history_path(mrb_state *mrb)
|
||||
{
|
||||
char *path = NULL;
|
||||
const char *home = getenv("HOME");
|
||||
|
||||
#ifdef _WIN32
|
||||
if (home != NULL) {
|
||||
home = getenv("USERPROFILE");
|
||||
}
|
||||
#endif
|
||||
|
||||
if (home != NULL) {
|
||||
int len = snprintf(NULL, 0, "%s/%s", home, history_file_name);
|
||||
if (len >= 0) {
|
||||
size_t size = len + 1;
|
||||
path = (char *)mrb_malloc_simple(mrb, size);
|
||||
if (path != NULL) {
|
||||
int n = snprintf(path, size, "%s/%s", home, history_file_name);
|
||||
if (n != len) {
|
||||
mrb_free(mrb, path);
|
||||
path = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static void
|
||||
p(mrb_state *mrb, mrb_value obj, int prompt)
|
||||
{
|
||||
mrb_value val;
|
||||
char* msg;
|
||||
|
||||
val = mrb_funcall(mrb, obj, "inspect", 0);
|
||||
if (prompt) {
|
||||
if (!mrb->exc) {
|
||||
fputs(" => ", stdout);
|
||||
}
|
||||
else {
|
||||
val = mrb_funcall(mrb, mrb_obj_value(mrb->exc), "inspect", 0);
|
||||
}
|
||||
}
|
||||
if (!mrb_string_p(val)) {
|
||||
val = mrb_obj_as_string(mrb, obj);
|
||||
}
|
||||
msg = mrb_locale_from_utf8(RSTRING_PTR(val), (int)RSTRING_LEN(val));
|
||||
fwrite(msg, strlen(msg), 1, stdout);
|
||||
mrb_locale_free(msg);
|
||||
putc('\n', stdout);
|
||||
}
|
||||
|
||||
/* Guess if the user might want to enter more
|
||||
* or if he wants an evaluation of his code now */
|
||||
static mrb_bool
|
||||
is_code_block_open(struct mrb_parser_state *parser)
|
||||
{
|
||||
mrb_bool code_block_open = FALSE;
|
||||
|
||||
/* check for heredoc */
|
||||
if (parser->parsing_heredoc != NULL) return TRUE;
|
||||
|
||||
/* check for unterminated string */
|
||||
if (parser->lex_strterm) return TRUE;
|
||||
|
||||
/* check if parser error are available */
|
||||
if (0 < parser->nerr) {
|
||||
const char unexpected_end[] = "syntax error, unexpected $end";
|
||||
const char *message = parser->error_buffer[0].message;
|
||||
|
||||
/* a parser error occur, we have to check if */
|
||||
/* we need to read one more line or if there is */
|
||||
/* a different issue which we have to show to */
|
||||
/* the user */
|
||||
|
||||
if (strncmp(message, unexpected_end, sizeof(unexpected_end) - 1) == 0) {
|
||||
code_block_open = TRUE;
|
||||
}
|
||||
else if (strcmp(message, "syntax error, unexpected keyword_end") == 0) {
|
||||
code_block_open = FALSE;
|
||||
}
|
||||
else if (strcmp(message, "syntax error, unexpected tREGEXP_BEG") == 0) {
|
||||
code_block_open = FALSE;
|
||||
}
|
||||
return code_block_open;
|
||||
}
|
||||
|
||||
switch (parser->lstate) {
|
||||
|
||||
/* all states which need more code */
|
||||
|
||||
case EXPR_BEG:
|
||||
/* beginning of a statement, */
|
||||
/* that means previous line ended */
|
||||
code_block_open = FALSE;
|
||||
break;
|
||||
case EXPR_DOT:
|
||||
/* a message dot was the last token, */
|
||||
/* there has to come more */
|
||||
code_block_open = TRUE;
|
||||
break;
|
||||
case EXPR_CLASS:
|
||||
/* a class keyword is not enough! */
|
||||
/* we need also a name of the class */
|
||||
code_block_open = TRUE;
|
||||
break;
|
||||
case EXPR_FNAME:
|
||||
/* a method name is necessary */
|
||||
code_block_open = TRUE;
|
||||
break;
|
||||
case EXPR_VALUE:
|
||||
/* if, elsif, etc. without condition */
|
||||
code_block_open = TRUE;
|
||||
break;
|
||||
|
||||
/* now all the states which are closed */
|
||||
|
||||
case EXPR_ARG:
|
||||
/* an argument is the last token */
|
||||
code_block_open = FALSE;
|
||||
break;
|
||||
|
||||
/* all states which are unsure */
|
||||
|
||||
case EXPR_CMDARG:
|
||||
break;
|
||||
case EXPR_END:
|
||||
/* an expression was ended */
|
||||
break;
|
||||
case EXPR_ENDARG:
|
||||
/* closing parenthese */
|
||||
break;
|
||||
case EXPR_ENDFN:
|
||||
/* definition end */
|
||||
break;
|
||||
case EXPR_MID:
|
||||
/* jump keyword like break, return, ... */
|
||||
break;
|
||||
case EXPR_MAX_STATE:
|
||||
/* don't know what to do with this token */
|
||||
break;
|
||||
default:
|
||||
/* this state is unexpected! */
|
||||
break;
|
||||
}
|
||||
|
||||
return code_block_open;
|
||||
}
|
||||
|
||||
struct _args {
|
||||
FILE *rfp;
|
||||
mrb_bool verbose : 1;
|
||||
mrb_bool debug : 1;
|
||||
int argc;
|
||||
char** argv;
|
||||
int libc;
|
||||
char **libv;
|
||||
};
|
||||
|
||||
static void
|
||||
usage(const char *name)
|
||||
{
|
||||
static const char *const usage_msg[] = {
|
||||
"switches:",
|
||||
"-d set $DEBUG to true (same as `mruby -d`)",
|
||||
"-r library same as `mruby -r`",
|
||||
"-v print version number, then run in verbose mode",
|
||||
"--verbose run in verbose mode",
|
||||
"--version print the version",
|
||||
"--copyright print the copyright",
|
||||
NULL
|
||||
};
|
||||
const char *const *p = usage_msg;
|
||||
|
||||
printf("Usage: %s [switches] [programfile] [arguments]\n", name);
|
||||
while (*p)
|
||||
printf(" %s\n", *p++);
|
||||
}
|
||||
|
||||
static char *
|
||||
dup_arg_item(mrb_state *mrb, const char *item)
|
||||
{
|
||||
size_t buflen = strlen(item) + 1;
|
||||
char *buf = (char*)mrb_malloc(mrb, buflen);
|
||||
memcpy(buf, item, buflen);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static int
|
||||
parse_args(mrb_state *mrb, int argc, char **argv, struct _args *args)
|
||||
{
|
||||
char **origargv = argv;
|
||||
static const struct _args args_zero = { 0 };
|
||||
|
||||
*args = args_zero;
|
||||
|
||||
for (argc--,argv++; argc > 0; argc--,argv++) {
|
||||
char *item;
|
||||
if (argv[0][0] != '-') break;
|
||||
|
||||
item = argv[0] + 1;
|
||||
switch (*item++) {
|
||||
case 'd':
|
||||
args->debug = TRUE;
|
||||
break;
|
||||
case 'r':
|
||||
if (!item[0]) {
|
||||
if (argc <= 1) {
|
||||
printf("%s: No library specified for -r\n", *origargv);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
argc--; argv++;
|
||||
item = argv[0];
|
||||
}
|
||||
if (args->libc == 0) {
|
||||
args->libv = (char**)mrb_malloc(mrb, sizeof(char*));
|
||||
}
|
||||
else {
|
||||
args->libv = (char**)mrb_realloc(mrb, args->libv, sizeof(char*) * (args->libc + 1));
|
||||
}
|
||||
args->libv[args->libc++] = dup_arg_item(mrb, item);
|
||||
break;
|
||||
case 'v':
|
||||
if (!args->verbose) mrb_show_version(mrb);
|
||||
args->verbose = TRUE;
|
||||
break;
|
||||
case '-':
|
||||
if (strcmp((*argv) + 2, "version") == 0) {
|
||||
mrb_show_version(mrb);
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
else if (strcmp((*argv) + 2, "verbose") == 0) {
|
||||
args->verbose = TRUE;
|
||||
break;
|
||||
}
|
||||
else if (strcmp((*argv) + 2, "copyright") == 0) {
|
||||
mrb_show_copyright(mrb);
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
default:
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
if (args->rfp == NULL) {
|
||||
if (*argv != NULL) {
|
||||
args->rfp = fopen(argv[0], "r");
|
||||
if (args->rfp == NULL) {
|
||||
printf("Cannot open program file. (%s)\n", *argv);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
argc--; argv++;
|
||||
}
|
||||
}
|
||||
args->argv = (char **)mrb_realloc(mrb, args->argv, sizeof(char*) * (argc + 1));
|
||||
memcpy(args->argv, argv, (argc+1) * sizeof(char*));
|
||||
args->argc = argc;
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
static void
|
||||
cleanup(mrb_state *mrb, struct _args *args)
|
||||
{
|
||||
if (args->rfp)
|
||||
fclose(args->rfp);
|
||||
mrb_free(mrb, args->argv);
|
||||
if (args->libc) {
|
||||
while (args->libc--) {
|
||||
mrb_free(mrb, args->libv[args->libc]);
|
||||
}
|
||||
mrb_free(mrb, args->libv);
|
||||
}
|
||||
mrb_close(mrb);
|
||||
}
|
||||
|
||||
/* Print a short remark for the user */
|
||||
static void
|
||||
print_hint(void)
|
||||
{
|
||||
printf("mirb - Embeddable Interactive Ruby Shell\n\n");
|
||||
}
|
||||
|
||||
#ifndef ENABLE_READLINE
|
||||
/* Print the command line prompt of the REPL */
|
||||
static void
|
||||
print_cmdline(int code_block_open)
|
||||
{
|
||||
if (code_block_open) {
|
||||
printf("* ");
|
||||
}
|
||||
else {
|
||||
printf("> ");
|
||||
}
|
||||
fflush(stdout);
|
||||
}
|
||||
#endif
|
||||
|
||||
void mrb_codedump_all(mrb_state*, struct RProc*);
|
||||
|
||||
static int
|
||||
check_keyword(const char *buf, const char *word)
|
||||
{
|
||||
const char *p = buf;
|
||||
size_t len = strlen(word);
|
||||
|
||||
/* skip preceding spaces */
|
||||
while (*p && ISSPACE(*p)) {
|
||||
p++;
|
||||
}
|
||||
/* check keyword */
|
||||
if (strncmp(p, word, len) != 0) {
|
||||
return 0;
|
||||
}
|
||||
p += len;
|
||||
/* skip trailing spaces */
|
||||
while (*p) {
|
||||
if (!ISSPACE(*p)) return 0;
|
||||
p++;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
#ifndef ENABLE_READLINE
|
||||
volatile sig_atomic_t input_canceled = 0;
|
||||
void
|
||||
ctrl_c_handler(int signo)
|
||||
{
|
||||
input_canceled = 1;
|
||||
}
|
||||
#else
|
||||
SIGJMP_BUF ctrl_c_buf;
|
||||
void
|
||||
ctrl_c_handler(int signo)
|
||||
{
|
||||
MIRB_SIGLONGJMP(ctrl_c_buf, 1);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef DISABLE_MIRB_UNDERSCORE
|
||||
void decl_lv_underscore(mrb_state *mrb, mrbc_context *cxt)
|
||||
{
|
||||
struct RProc *proc;
|
||||
struct mrb_parser_state *parser;
|
||||
|
||||
parser = mrb_parse_string(mrb, "_=nil", cxt);
|
||||
if (parser == NULL) {
|
||||
fputs("create parser state error\n", stderr);
|
||||
mrb_close(mrb);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
proc = mrb_generate_code(mrb, parser);
|
||||
mrb_vm_run(mrb, proc, mrb_top_self(mrb), 0);
|
||||
|
||||
mrb_parser_free(parser);
|
||||
}
|
||||
#endif
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
char ruby_code[4096] = { 0 };
|
||||
char last_code_line[1024] = { 0 };
|
||||
#ifndef ENABLE_READLINE
|
||||
int last_char;
|
||||
size_t char_index;
|
||||
#else
|
||||
char *history_path;
|
||||
char* line;
|
||||
#endif
|
||||
mrbc_context *cxt;
|
||||
struct mrb_parser_state *parser;
|
||||
mrb_state *mrb;
|
||||
mrb_value result;
|
||||
struct _args args;
|
||||
mrb_value ARGV;
|
||||
int n;
|
||||
int i;
|
||||
mrb_bool code_block_open = FALSE;
|
||||
int ai;
|
||||
unsigned int stack_keep = 0;
|
||||
|
||||
/* new interpreter instance */
|
||||
mrb = mrb_open();
|
||||
if (mrb == NULL) {
|
||||
fputs("Invalid mrb interpreter, exiting mirb\n", stderr);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
n = parse_args(mrb, argc, argv, &args);
|
||||
if (n == EXIT_FAILURE) {
|
||||
cleanup(mrb, &args);
|
||||
usage(argv[0]);
|
||||
return n;
|
||||
}
|
||||
|
||||
ARGV = mrb_ary_new_capa(mrb, args.argc);
|
||||
for (i = 0; i < args.argc; i++) {
|
||||
char* utf8 = mrb_utf8_from_locale(args.argv[i], -1);
|
||||
if (utf8) {
|
||||
mrb_ary_push(mrb, ARGV, mrb_str_new_cstr(mrb, utf8));
|
||||
mrb_utf8_free(utf8);
|
||||
}
|
||||
}
|
||||
mrb_define_global_const(mrb, "ARGV", ARGV);
|
||||
mrb_gv_set(mrb, mrb_intern_lit(mrb, "$DEBUG"), mrb_bool_value(args.debug));
|
||||
|
||||
#ifdef ENABLE_READLINE
|
||||
history_path = get_history_path(mrb);
|
||||
if (history_path == NULL) {
|
||||
fputs("failed to get history path\n", stderr);
|
||||
mrb_close(mrb);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
MIRB_USING_HISTORY();
|
||||
MIRB_READ_HISTORY(history_path);
|
||||
#endif
|
||||
|
||||
print_hint();
|
||||
|
||||
cxt = mrbc_context_new(mrb);
|
||||
|
||||
#ifndef DISABLE_MIRB_UNDERSCORE
|
||||
decl_lv_underscore(mrb, cxt);
|
||||
#endif
|
||||
|
||||
/* Load libraries */
|
||||
for (i = 0; i < args.libc; i++) {
|
||||
FILE *lfp = fopen(args.libv[i], "r");
|
||||
if (lfp == NULL) {
|
||||
printf("Cannot open library file. (%s)\n", args.libv[i]);
|
||||
cleanup(mrb, &args);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
mrb_load_file_cxt(mrb, lfp, cxt);
|
||||
fclose(lfp);
|
||||
}
|
||||
|
||||
cxt->capture_errors = TRUE;
|
||||
cxt->lineno = 1;
|
||||
mrbc_filename(mrb, cxt, "(mirb)");
|
||||
if (args.verbose) cxt->dump_result = TRUE;
|
||||
|
||||
ai = mrb_gc_arena_save(mrb);
|
||||
|
||||
while (TRUE) {
|
||||
char *utf8;
|
||||
struct mrb_jmpbuf c_jmp;
|
||||
|
||||
MRB_TRY(&c_jmp);
|
||||
mrb->jmp = &c_jmp;
|
||||
if (args.rfp) {
|
||||
if (fgets(last_code_line, sizeof(last_code_line)-1, args.rfp) != NULL)
|
||||
goto done;
|
||||
break;
|
||||
}
|
||||
|
||||
#ifndef ENABLE_READLINE
|
||||
print_cmdline(code_block_open);
|
||||
|
||||
signal(SIGINT, ctrl_c_handler);
|
||||
char_index = 0;
|
||||
while ((last_char = getchar()) != '\n') {
|
||||
if (last_char == EOF) break;
|
||||
if (char_index >= sizeof(last_code_line)-2) {
|
||||
fputs("input string too long\n", stderr);
|
||||
continue;
|
||||
}
|
||||
last_code_line[char_index++] = last_char;
|
||||
}
|
||||
signal(SIGINT, SIG_DFL);
|
||||
if (input_canceled) {
|
||||
ruby_code[0] = '\0';
|
||||
last_code_line[0] = '\0';
|
||||
code_block_open = FALSE;
|
||||
puts("^C");
|
||||
input_canceled = 0;
|
||||
continue;
|
||||
}
|
||||
if (last_char == EOF) {
|
||||
fputs("\n", stdout);
|
||||
break;
|
||||
}
|
||||
|
||||
last_code_line[char_index++] = '\n';
|
||||
last_code_line[char_index] = '\0';
|
||||
#else
|
||||
if (MIRB_SIGSETJMP(ctrl_c_buf) == 0) {
|
||||
;
|
||||
}
|
||||
else {
|
||||
ruby_code[0] = '\0';
|
||||
last_code_line[0] = '\0';
|
||||
code_block_open = FALSE;
|
||||
puts("^C");
|
||||
}
|
||||
signal(SIGINT, ctrl_c_handler);
|
||||
line = MIRB_READLINE(code_block_open ? "* " : "> ");
|
||||
signal(SIGINT, SIG_DFL);
|
||||
|
||||
if (line == NULL) {
|
||||
printf("\n");
|
||||
break;
|
||||
}
|
||||
if (strlen(line) > sizeof(last_code_line)-2) {
|
||||
fputs("input string too long\n", stderr);
|
||||
continue;
|
||||
}
|
||||
strcpy(last_code_line, line);
|
||||
strcat(last_code_line, "\n");
|
||||
MIRB_ADD_HISTORY(line);
|
||||
MIRB_LINE_FREE(line);
|
||||
#endif
|
||||
|
||||
done:
|
||||
if (code_block_open) {
|
||||
if (strlen(ruby_code)+strlen(last_code_line) > sizeof(ruby_code)-1) {
|
||||
fputs("concatenated input string too long\n", stderr);
|
||||
continue;
|
||||
}
|
||||
strcat(ruby_code, last_code_line);
|
||||
}
|
||||
else {
|
||||
if (check_keyword(last_code_line, "quit") || check_keyword(last_code_line, "exit")) {
|
||||
break;
|
||||
}
|
||||
strcpy(ruby_code, last_code_line);
|
||||
}
|
||||
|
||||
utf8 = mrb_utf8_from_locale(ruby_code, -1);
|
||||
if (!utf8) abort();
|
||||
|
||||
/* parse code */
|
||||
parser = mrb_parser_new(mrb);
|
||||
if (parser == NULL) {
|
||||
fputs("create parser state error\n", stderr);
|
||||
break;
|
||||
}
|
||||
parser->s = utf8;
|
||||
parser->send = utf8 + strlen(utf8);
|
||||
parser->lineno = cxt->lineno;
|
||||
mrb_parser_parse(parser, cxt);
|
||||
code_block_open = is_code_block_open(parser);
|
||||
mrb_utf8_free(utf8);
|
||||
|
||||
if (code_block_open) {
|
||||
/* no evaluation of code */
|
||||
}
|
||||
else {
|
||||
if (0 < parser->nwarn) {
|
||||
/* warning */
|
||||
char* msg = mrb_locale_from_utf8(parser->warn_buffer[0].message, -1);
|
||||
printf("line %d: %s\n", parser->warn_buffer[0].lineno, msg);
|
||||
mrb_locale_free(msg);
|
||||
}
|
||||
if (0 < parser->nerr) {
|
||||
/* syntax error */
|
||||
char* msg = mrb_locale_from_utf8(parser->error_buffer[0].message, -1);
|
||||
printf("line %d: %s\n", parser->error_buffer[0].lineno, msg);
|
||||
mrb_locale_free(msg);
|
||||
}
|
||||
else {
|
||||
/* generate bytecode */
|
||||
struct RProc *proc = mrb_generate_code(mrb, parser);
|
||||
if (proc == NULL) {
|
||||
fputs("codegen error\n", stderr);
|
||||
mrb_parser_free(parser);
|
||||
break;
|
||||
}
|
||||
|
||||
if (args.verbose) {
|
||||
mrb_codedump_all(mrb, proc);
|
||||
}
|
||||
/* adjust stack length of toplevel environment */
|
||||
if (mrb->c->cibase->env) {
|
||||
struct REnv *e = mrb->c->cibase->env;
|
||||
if (e && MRB_ENV_LEN(e) < proc->body.irep->nlocals) {
|
||||
MRB_ENV_SET_LEN(e, proc->body.irep->nlocals);
|
||||
}
|
||||
}
|
||||
/* pass a proc for evaluation */
|
||||
/* evaluate the bytecode */
|
||||
result = mrb_vm_run(mrb,
|
||||
proc,
|
||||
mrb_top_self(mrb),
|
||||
stack_keep);
|
||||
stack_keep = proc->body.irep->nlocals;
|
||||
/* did an exception occur? */
|
||||
if (mrb->exc) {
|
||||
p(mrb, mrb_obj_value(mrb->exc), 0);
|
||||
mrb->exc = 0;
|
||||
}
|
||||
else {
|
||||
/* no */
|
||||
if (!mrb_respond_to(mrb, result, mrb_intern_lit(mrb, "inspect"))){
|
||||
result = mrb_any_to_s(mrb, result);
|
||||
}
|
||||
p(mrb, result, 1);
|
||||
#ifndef DISABLE_MIRB_UNDERSCORE
|
||||
*(mrb->c->stack + 1) = result;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
ruby_code[0] = '\0';
|
||||
last_code_line[0] = '\0';
|
||||
mrb_gc_arena_restore(mrb, ai);
|
||||
}
|
||||
mrb_parser_free(parser);
|
||||
cxt->lineno++;
|
||||
MRB_CATCH(&c_jmp) {
|
||||
p(mrb, mrb_obj_value(mrb->exc), 0);
|
||||
mrb->exc = 0;
|
||||
}
|
||||
MRB_END_EXC(&c_jmp);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_READLINE
|
||||
MIRB_WRITE_HISTORY(history_path);
|
||||
mrb_free(mrb, history_path);
|
||||
#endif
|
||||
|
||||
if (args.rfp) fclose(args.rfp);
|
||||
mrb_free(mrb, args.argv);
|
||||
if (args.libv) {
|
||||
for (i = 0; i < args.libc; ++i) {
|
||||
mrb_free(mrb, args.libv[i]);
|
||||
}
|
||||
mrb_free(mrb, args.libv);
|
||||
}
|
||||
mrbc_context_free(mrb, cxt);
|
||||
mrb_close(mrb);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
MRuby::Gem::Specification.new 'mruby-bin-mrbc' do |spec|
|
||||
spec.license = 'MIT'
|
||||
spec.author = 'mruby developers'
|
||||
spec.summary = 'mruby compiler executable'
|
||||
|
||||
spec.add_dependency 'mruby-compiler', :core => 'mruby-compiler'
|
||||
|
||||
exec = exefile("#{build.build_dir}/bin/mrbc")
|
||||
mrbc_objs = Dir.glob("#{spec.dir}/tools/mrbc/*.c").map { |f| objfile(f.pathmap("#{spec.build_dir}/tools/mrbc/%n")) }.flatten
|
||||
|
||||
file exec => mrbc_objs + [build.libmruby_core_static] do |t|
|
||||
build.linker.run t.name, t.prerequisites
|
||||
end
|
||||
|
||||
build.bins << 'mrbc' unless build.bins.find { |v| v == 'mrbc' }
|
||||
end
|
||||
@@ -0,0 +1,347 @@
|
||||
#include <mruby.h>
|
||||
|
||||
#ifdef MRB_DISABLE_STDIO
|
||||
# error mruby-bin-mrbc conflicts 'MRB_DISABLE_STDIO' configuration in your 'build_config.rb'
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <mruby/compile.h>
|
||||
#include <mruby/dump.h>
|
||||
#include <mruby/proc.h>
|
||||
|
||||
#define RITEBIN_EXT ".mrb"
|
||||
#define C_EXT ".c"
|
||||
|
||||
struct mrbc_args {
|
||||
int argc;
|
||||
char **argv;
|
||||
int idx;
|
||||
const char *prog;
|
||||
const char *outfile;
|
||||
const char *initname;
|
||||
mrb_bool check_syntax : 1;
|
||||
mrb_bool verbose : 1;
|
||||
mrb_bool remove_lv : 1;
|
||||
unsigned int flags : 4;
|
||||
};
|
||||
|
||||
static void
|
||||
usage(const char *name)
|
||||
{
|
||||
static const char *const usage_msg[] = {
|
||||
"switches:",
|
||||
"-c check syntax only",
|
||||
"-o<outfile> place the output into <outfile>",
|
||||
"-v print version number, then turn on verbose mode",
|
||||
"-g produce debugging information",
|
||||
"-B<symbol> binary <symbol> output in C language format",
|
||||
"--remove-lv remove local variables",
|
||||
"--verbose run at verbose mode",
|
||||
"--version print the version",
|
||||
"--copyright print the copyright",
|
||||
NULL
|
||||
};
|
||||
const char *const *p = usage_msg;
|
||||
|
||||
printf("Usage: %s [switches] programfile\n", name);
|
||||
while (*p)
|
||||
printf(" %s\n", *p++);
|
||||
}
|
||||
|
||||
static char *
|
||||
get_outfilename(mrb_state *mrb, char *infile, const char *ext)
|
||||
{
|
||||
size_t ilen, flen, elen;
|
||||
char *outfile;
|
||||
char *p = NULL;
|
||||
|
||||
ilen = strlen(infile);
|
||||
flen = ilen;
|
||||
if (*ext) {
|
||||
elen = strlen(ext);
|
||||
if ((p = strrchr(infile, '.'))) {
|
||||
ilen = p - infile;
|
||||
}
|
||||
flen += elen;
|
||||
}
|
||||
else {
|
||||
flen = ilen;
|
||||
}
|
||||
outfile = (char*)mrb_malloc(mrb, flen+1);
|
||||
strncpy(outfile, infile, ilen+1);
|
||||
if (p) {
|
||||
strncpy(outfile+ilen, ext, elen+1);
|
||||
}
|
||||
|
||||
return outfile;
|
||||
}
|
||||
|
||||
static int
|
||||
parse_args(mrb_state *mrb, int argc, char **argv, struct mrbc_args *args)
|
||||
{
|
||||
static const struct mrbc_args args_zero = { 0 };
|
||||
int i;
|
||||
|
||||
*args = args_zero;
|
||||
args->argc = argc;
|
||||
args->argv = argv;
|
||||
args->prog = argv[0];
|
||||
|
||||
for (i=1; i<argc; i++) {
|
||||
if (argv[i][0] == '-') {
|
||||
switch ((argv[i])[1]) {
|
||||
case 'o':
|
||||
if (args->outfile) {
|
||||
fprintf(stderr, "%s: an output file is already specified. (%s)\n",
|
||||
args->prog, args->outfile);
|
||||
return -1;
|
||||
}
|
||||
if (argv[i][2] == '\0' && argv[i+1]) {
|
||||
i++;
|
||||
args->outfile = get_outfilename(mrb, argv[i], "");
|
||||
}
|
||||
else {
|
||||
args->outfile = get_outfilename(mrb, argv[i] + 2, "");
|
||||
}
|
||||
break;
|
||||
case 'B':
|
||||
if (argv[i][2] == '\0' && argv[i+1]) {
|
||||
i++;
|
||||
args->initname = argv[i];
|
||||
}
|
||||
else {
|
||||
args->initname = argv[i]+2;
|
||||
}
|
||||
if (*args->initname == '\0') {
|
||||
fprintf(stderr, "%s: function name is not specified.\n", args->prog);
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
case 'c':
|
||||
args->check_syntax = TRUE;
|
||||
break;
|
||||
case 'v':
|
||||
if (!args->verbose) mrb_show_version(mrb);
|
||||
args->verbose = TRUE;
|
||||
break;
|
||||
case 'g':
|
||||
args->flags |= DUMP_DEBUG_INFO;
|
||||
break;
|
||||
case 'E':
|
||||
case 'e':
|
||||
fprintf(stderr, "%s: -e/-E option no longer needed.\n", args->prog);
|
||||
break;
|
||||
case 'h':
|
||||
return -1;
|
||||
case '-':
|
||||
if (argv[i][1] == '\n') {
|
||||
return i;
|
||||
}
|
||||
if (strcmp(argv[i] + 2, "version") == 0) {
|
||||
mrb_show_version(mrb);
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
else if (strcmp(argv[i] + 2, "verbose") == 0) {
|
||||
args->verbose = TRUE;
|
||||
break;
|
||||
}
|
||||
else if (strcmp(argv[i] + 2, "copyright") == 0) {
|
||||
mrb_show_copyright(mrb);
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
else if (strcmp(argv[i] + 2, "remove-lv") == 0) {
|
||||
args->remove_lv = TRUE;
|
||||
break;
|
||||
}
|
||||
return -1;
|
||||
default:
|
||||
return i;
|
||||
}
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
static void
|
||||
cleanup(mrb_state *mrb, struct mrbc_args *args)
|
||||
{
|
||||
mrb_free(mrb, (void*)args->outfile);
|
||||
mrb_close(mrb);
|
||||
}
|
||||
|
||||
static int
|
||||
partial_hook(struct mrb_parser_state *p)
|
||||
{
|
||||
mrbc_context *c = p->cxt;
|
||||
struct mrbc_args *args = (struct mrbc_args *)c->partial_data;
|
||||
const char *fn;
|
||||
|
||||
if (p->f) fclose(p->f);
|
||||
if (args->idx >= args->argc) {
|
||||
p->f = NULL;
|
||||
return -1;
|
||||
}
|
||||
fn = args->argv[args->idx++];
|
||||
p->f = fopen(fn, "rb");
|
||||
if (p->f == NULL) {
|
||||
fprintf(stderr, "%s: cannot open program file. (%s)\n", args->prog, fn);
|
||||
return -1;
|
||||
}
|
||||
mrb_parser_set_filename(p, fn);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
load_file(mrb_state *mrb, struct mrbc_args *args)
|
||||
{
|
||||
mrbc_context *c;
|
||||
mrb_value result;
|
||||
char *input = args->argv[args->idx];
|
||||
FILE *infile;
|
||||
mrb_bool need_close = FALSE;
|
||||
|
||||
c = mrbc_context_new(mrb);
|
||||
if (args->verbose)
|
||||
c->dump_result = TRUE;
|
||||
c->no_exec = TRUE;
|
||||
if (input[0] == '-' && input[1] == '\0') {
|
||||
infile = stdin;
|
||||
}
|
||||
else {
|
||||
need_close = TRUE;
|
||||
if ((infile = fopen(input, "rb")) == NULL) {
|
||||
fprintf(stderr, "%s: cannot open program file. (%s)\n", args->prog, input);
|
||||
return mrb_nil_value();
|
||||
}
|
||||
}
|
||||
mrbc_filename(mrb, c, input);
|
||||
args->idx++;
|
||||
if (args->idx < args->argc) {
|
||||
need_close = FALSE;
|
||||
mrbc_partial_hook(mrb, c, partial_hook, (void*)args);
|
||||
}
|
||||
|
||||
result = mrb_load_file_cxt(mrb, infile, c);
|
||||
if (need_close) fclose(infile);
|
||||
mrbc_context_free(mrb, c);
|
||||
if (mrb_undef_p(result)) {
|
||||
return mrb_nil_value();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static int
|
||||
dump_file(mrb_state *mrb, FILE *wfp, const char *outfile, struct RProc *proc, struct mrbc_args *args)
|
||||
{
|
||||
int n = MRB_DUMP_OK;
|
||||
mrb_irep *irep = proc->body.irep;
|
||||
|
||||
if (args->remove_lv) {
|
||||
mrb_irep_remove_lv(mrb, irep);
|
||||
}
|
||||
if (args->initname) {
|
||||
n = mrb_dump_irep_cfunc(mrb, irep, args->flags, wfp, args->initname);
|
||||
if (n == MRB_DUMP_INVALID_ARGUMENT) {
|
||||
fprintf(stderr, "%s: invalid C language symbol name\n", args->initname);
|
||||
}
|
||||
}
|
||||
else {
|
||||
n = mrb_dump_irep_binary(mrb, irep, args->flags, wfp);
|
||||
}
|
||||
if (n != MRB_DUMP_OK) {
|
||||
fprintf(stderr, "%s: error in mrb dump (%s) %d\n", args->prog, outfile, n);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
mrb_state *mrb = mrb_open();
|
||||
int n, result;
|
||||
struct mrbc_args args;
|
||||
FILE *wfp;
|
||||
mrb_value load;
|
||||
|
||||
if (mrb == NULL) {
|
||||
fputs("Invalid mrb_state, exiting mrbc\n", stderr);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
n = parse_args(mrb, argc, argv, &args);
|
||||
if (n < 0) {
|
||||
cleanup(mrb, &args);
|
||||
usage(argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (n == argc) {
|
||||
fprintf(stderr, "%s: no program file given\n", args.prog);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (args.outfile == NULL && !args.check_syntax) {
|
||||
if (n + 1 == argc) {
|
||||
args.outfile = get_outfilename(mrb, argv[n], args.initname ? C_EXT : RITEBIN_EXT);
|
||||
}
|
||||
else {
|
||||
fprintf(stderr, "%s: output file should be specified to compile multiple files\n", args.prog);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
args.idx = n;
|
||||
load = load_file(mrb, &args);
|
||||
if (mrb_nil_p(load)) {
|
||||
cleanup(mrb, &args);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (args.check_syntax) {
|
||||
printf("%s:%s:Syntax OK\n", args.prog, argv[n]);
|
||||
}
|
||||
|
||||
if (args.check_syntax) {
|
||||
cleanup(mrb, &args);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
if (args.outfile) {
|
||||
if (strcmp("-", args.outfile) == 0) {
|
||||
wfp = stdout;
|
||||
}
|
||||
else if ((wfp = fopen(args.outfile, "wb")) == NULL) {
|
||||
fprintf(stderr, "%s: cannot open output file:(%s)\n", args.prog, args.outfile);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
fprintf(stderr, "Output file is required\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
result = dump_file(mrb, wfp, args.outfile, mrb_proc_ptr(load), &args);
|
||||
fclose(wfp);
|
||||
cleanup(mrb, &args);
|
||||
if (result != MRB_DUMP_OK) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
void
|
||||
mrb_init_mrblib(mrb_state *mrb)
|
||||
{
|
||||
}
|
||||
|
||||
#ifndef DISABLE_GEMS
|
||||
void
|
||||
mrb_init_mrbgems(mrb_state *mrb)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
mrb_final_mrbgems(mrb_state *mrb)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,164 @@
|
||||
require 'tempfile'
|
||||
require 'open3'
|
||||
|
||||
def assert_mruby(exp_out, exp_err, exp_success, args)
|
||||
out, err, stat = Open3.capture3(cmd("mruby"), *args)
|
||||
assert "assert_mruby" do
|
||||
assert_operator(exp_out, :===, out, "standard output")
|
||||
assert_operator(exp_err, :===, err, "standard error")
|
||||
assert_equal(exp_success, stat.success?, "exit success?")
|
||||
end
|
||||
end
|
||||
|
||||
assert('regression for #1564') do
|
||||
assert_mruby("", /\A-e:1:2: syntax error, .*\n\z/, false, %w[-e <<])
|
||||
assert_mruby("", /\A-e:1:3: syntax error, .*\n\z/, false, %w[-e <<-])
|
||||
end
|
||||
|
||||
assert('regression for #1572') do
|
||||
script, bin = Tempfile.new('test.rb'), Tempfile.new('test.mrb')
|
||||
File.write script.path, 'p "ok"'
|
||||
system "#{cmd('mrbc')} -g -o #{bin.path} #{script.path}"
|
||||
o = `#{cmd('mruby')} -b #{bin.path}`.strip
|
||||
assert_equal '"ok"', o
|
||||
end
|
||||
|
||||
assert '$0 value' do
|
||||
script, bin = Tempfile.new('test.rb'), Tempfile.new('test.mrb')
|
||||
|
||||
# .rb script
|
||||
script.write "p $0\n"
|
||||
script.flush
|
||||
assert_equal "\"#{script.path}\"", `#{cmd('mruby')} "#{script.path}"`.chomp
|
||||
|
||||
# .mrb file
|
||||
`#{cmd('mrbc')} -o "#{bin.path}" "#{script.path}"`
|
||||
assert_equal "\"#{bin.path}\"", `#{cmd('mruby')} -b "#{bin.path}"`.chomp
|
||||
|
||||
# one liner
|
||||
assert_equal '"-e"', `#{cmd('mruby')} -e #{shellquote('p $0')}`.chomp
|
||||
end
|
||||
|
||||
assert 'ARGV value' do
|
||||
assert_mruby(%{["ab", "cde"]\n}, "", true, %w[-e p(ARGV) ab cde])
|
||||
assert_mruby("[]\n", "", true, %w[-e p(ARGV)])
|
||||
end
|
||||
|
||||
assert('float literal') do
|
||||
script, bin = Tempfile.new('test.rb'), Tempfile.new('test.mrb')
|
||||
File.write script.path, 'p [3.21, 2e308.infinite?, -2e308.infinite?]'
|
||||
system "#{cmd('mrbc')} -g -o #{bin.path} #{script.path}"
|
||||
assert_equal "[3.21, 1, -1]", `#{cmd('mruby')} -b #{bin.path}`.chomp!
|
||||
end
|
||||
|
||||
assert '__END__', '8.6' do
|
||||
script = Tempfile.new('test.rb')
|
||||
|
||||
script.write <<EOS
|
||||
p 'test'
|
||||
__END__ = 'fin'
|
||||
p __END__
|
||||
__END__
|
||||
p 'legend'
|
||||
EOS
|
||||
script.flush
|
||||
assert_equal "\"test\"\n\"fin\"\n", `#{cmd('mruby')} #{script.path}`
|
||||
end
|
||||
|
||||
assert('garbage collecting built-in classes') do
|
||||
script = Tempfile.new('test.rb')
|
||||
|
||||
script.write <<RUBY
|
||||
NilClass = nil
|
||||
GC.start
|
||||
Array.dup
|
||||
print nil.class.to_s
|
||||
RUBY
|
||||
script.flush
|
||||
assert_equal "NilClass", `#{cmd('mruby')} #{script.path}`
|
||||
assert_equal 0, $?.exitstatus
|
||||
end
|
||||
|
||||
assert('mruby -c option') do
|
||||
assert_mruby("Syntax OK\n", "", true, ["-c", "-e", "p 1"])
|
||||
assert_mruby("", /\A-e:1:7: syntax error, .*\n\z/, false, ["-c", "-e", "p 1; 1."])
|
||||
end
|
||||
|
||||
assert('mruby -d option') do
|
||||
assert_mruby("false\n", "", true, ["-e", "p $DEBUG"])
|
||||
assert_mruby("true\n", "", true, ["-dep $DEBUG"])
|
||||
end
|
||||
|
||||
assert('mruby -e option (no code specified)') do
|
||||
assert_mruby("", /\A.*: No code specified for -e\n\z/, false, %w[-e])
|
||||
end
|
||||
|
||||
assert('mruby -h option') do
|
||||
assert_mruby(/\AUsage: #{Regexp.escape cmd("mruby")} .*/m, "", true, %w[-h])
|
||||
end
|
||||
|
||||
assert('mruby -r option') do
|
||||
lib = Tempfile.new('lib.rb')
|
||||
lib.write <<EOS
|
||||
class Hoge
|
||||
def hoge
|
||||
:hoge
|
||||
end
|
||||
end
|
||||
EOS
|
||||
lib.flush
|
||||
|
||||
script = Tempfile.new('test.rb')
|
||||
script.write <<EOS
|
||||
print Hoge.new.hoge
|
||||
EOS
|
||||
script.flush
|
||||
assert_equal 'hoge', `#{cmd('mruby')} -r #{lib.path} #{script.path}`
|
||||
assert_equal 0, $?.exitstatus
|
||||
|
||||
assert_equal 'hogeClass', `#{cmd('mruby')} -r #{lib.path} -r #{script.path} -e #{shellquote('print Hoge.class')}`
|
||||
assert_equal 0, $?.exitstatus
|
||||
end
|
||||
|
||||
assert('mruby -r option (no library specified)') do
|
||||
assert_mruby("", /\A.*: No library specified for -r\n\z/, false, %w[-r])
|
||||
end
|
||||
|
||||
assert('mruby -r option (file not found)') do
|
||||
assert_mruby("", /\A.*: Cannot open library file: .*\n\z/, false, %w[-r _no_exists_])
|
||||
end
|
||||
|
||||
assert('mruby -v option') do
|
||||
ver_re = '\Amruby \d+\.\d+\.\d+ \(\d+-\d+-\d+\)\n'
|
||||
assert_mruby(/#{ver_re}\z/, "", true, %w[-v])
|
||||
assert_mruby(/#{ver_re}^[^\n]*NODE.*\n:end\n\z/m, "", true, %w[-v -e p(:end)])
|
||||
end
|
||||
|
||||
assert('mruby --verbose option') do
|
||||
assert_mruby(/\A[^\n]*NODE.*\n:end\n\z/m, "", true, %w[--verbose -e p(:end)])
|
||||
end
|
||||
|
||||
assert('mruby --') do
|
||||
assert_mruby(%{["-x", "1"]\n}, "", true, %w[-e p(ARGV) -- -x 1])
|
||||
end
|
||||
|
||||
assert('mruby invalid short option') do
|
||||
assert_mruby("", /\A.*: invalid option -1 .*\n\z/, false, %w[-1])
|
||||
end
|
||||
|
||||
assert('mruby invalid long option') do
|
||||
assert_mruby("", /\A.*: invalid option --longopt .*\n\z/, false, %w[--longopt])
|
||||
end
|
||||
|
||||
assert('unhandled exception') do
|
||||
assert_mruby("", /\bEXCEPTION\b.*\n\z/, false, %w[-e raise("EXCEPTION")])
|
||||
end
|
||||
|
||||
assert('program file not found') do
|
||||
assert_mruby("", /\A.*: Cannot open program file: .*\n\z/, false, %w[_no_exists_])
|
||||
end
|
||||
|
||||
assert('codegen error') do
|
||||
code = "def f(#{(1..100).map{|n| "a#{n}"} * ","}); end"
|
||||
assert_mruby("", /\Acodegen error:.*\n\z/, false, ["-e", code])
|
||||
end
|
||||
@@ -0,0 +1,12 @@
|
||||
MRuby::Gem::Specification.new('mruby-bin-mruby') do |spec|
|
||||
spec.license = 'MIT'
|
||||
spec.author = 'mruby developers'
|
||||
spec.summary = 'mruby command'
|
||||
spec.bins = %w(mruby)
|
||||
spec.add_dependency('mruby-compiler', :core => 'mruby-compiler')
|
||||
spec.add_test_dependency('mruby-print', :core => 'mruby-print')
|
||||
|
||||
if build.cxx_exception_enabled?
|
||||
build.compile_as_cxx("#{spec.dir}/tools/mruby/mruby.c", "#{spec.build_dir}/tools/mruby/mruby.cxx")
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,355 @@
|
||||
#include <mruby.h>
|
||||
|
||||
#ifdef MRB_DISABLE_STDIO
|
||||
# error mruby-bin-mruby conflicts 'MRB_DISABLE_STDIO' configuration in your 'build_config.rb'
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <mruby/array.h>
|
||||
#include <mruby/compile.h>
|
||||
#include <mruby/dump.h>
|
||||
#include <mruby/variable.h>
|
||||
|
||||
struct _args {
|
||||
FILE *rfp;
|
||||
char *cmdline;
|
||||
mrb_bool fname : 1;
|
||||
mrb_bool mrbfile : 1;
|
||||
mrb_bool check_syntax : 1;
|
||||
mrb_bool verbose : 1;
|
||||
mrb_bool version : 1;
|
||||
mrb_bool debug : 1;
|
||||
int argc;
|
||||
char **argv;
|
||||
int libc;
|
||||
char **libv;
|
||||
};
|
||||
|
||||
struct options {
|
||||
int argc;
|
||||
char **argv;
|
||||
char *program;
|
||||
char *opt;
|
||||
char short_opt[2];
|
||||
};
|
||||
|
||||
static void
|
||||
usage(const char *name)
|
||||
{
|
||||
static const char *const usage_msg[] = {
|
||||
"switches:",
|
||||
"-b load and execute RiteBinary (mrb) file",
|
||||
"-c check syntax only",
|
||||
"-d set debugging flags (set $DEBUG to true)",
|
||||
"-e 'command' one line of script",
|
||||
"-r library load the library before executing your script",
|
||||
"-v print version number, then run in verbose mode",
|
||||
"--verbose run in verbose mode",
|
||||
"--version print the version",
|
||||
"--copyright print the copyright",
|
||||
NULL
|
||||
};
|
||||
const char *const *p = usage_msg;
|
||||
|
||||
printf("Usage: %s [switches] [programfile] [arguments]\n", name);
|
||||
while (*p)
|
||||
printf(" %s\n", *p++);
|
||||
}
|
||||
|
||||
static void
|
||||
options_init(struct options *opts, int argc, char **argv)
|
||||
{
|
||||
opts->argc = argc;
|
||||
opts->argv = argv;
|
||||
opts->program = *argv;
|
||||
*opts->short_opt = 0;
|
||||
}
|
||||
|
||||
static const char *
|
||||
options_opt(struct options *opts)
|
||||
{
|
||||
/* concatenated short options (e.g. `-cv`) */
|
||||
if (*opts->short_opt && *++opts->opt) {
|
||||
short_opt:
|
||||
opts->short_opt[0] = *opts->opt;
|
||||
opts->short_opt[1] = 0;
|
||||
return opts->short_opt;
|
||||
}
|
||||
|
||||
while (++opts->argv, --opts->argc) {
|
||||
opts->opt = *opts->argv;
|
||||
|
||||
/* empty || not start with `-` || `-` */
|
||||
if (!opts->opt[0] || opts->opt[0] != '-' || !opts->opt[1]) return NULL;
|
||||
|
||||
if (opts->opt[1] == '-') {
|
||||
/* `--` */
|
||||
if (!opts->opt[2]) {
|
||||
++opts->argv, --opts->argc;
|
||||
return NULL;
|
||||
}
|
||||
/* long option */
|
||||
opts->opt += 2;
|
||||
*opts->short_opt = 0;
|
||||
return opts->opt;
|
||||
}
|
||||
else {
|
||||
/* short option */
|
||||
++opts->opt;
|
||||
goto short_opt;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *
|
||||
options_arg(struct options *opts)
|
||||
{
|
||||
if (*opts->short_opt && opts->opt[1]) {
|
||||
/* concatenated short option and option argument (e.g. `-rLIBRARY`) */
|
||||
*opts->short_opt = 0;
|
||||
return opts->opt + 1;
|
||||
}
|
||||
--opts->argc, ++opts->argv;
|
||||
return opts->argc ? *opts->argv : NULL;
|
||||
}
|
||||
|
||||
static char *
|
||||
dup_arg_item(mrb_state *mrb, const char *item)
|
||||
{
|
||||
size_t buflen = strlen(item) + 1;
|
||||
char *buf = (char*)mrb_malloc(mrb, buflen);
|
||||
memcpy(buf, item, buflen);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static int
|
||||
parse_args(mrb_state *mrb, int argc, char **argv, struct _args *args)
|
||||
{
|
||||
static const struct _args args_zero = { 0 };
|
||||
struct options opts[1];
|
||||
const char *opt, *item;
|
||||
|
||||
*args = args_zero;
|
||||
options_init(opts, argc, argv);
|
||||
while ((opt = options_opt(opts))) {
|
||||
if (strcmp(opt, "b") == 0) {
|
||||
args->mrbfile = TRUE;
|
||||
}
|
||||
else if (strcmp(opt, "c") == 0) {
|
||||
args->check_syntax = TRUE;
|
||||
}
|
||||
else if (strcmp(opt, "d") == 0) {
|
||||
args->debug = TRUE;
|
||||
}
|
||||
else if (strcmp(opt, "e") == 0) {
|
||||
if ((item = options_arg(opts))) {
|
||||
if (!args->cmdline) {
|
||||
args->cmdline = dup_arg_item(mrb, item);
|
||||
}
|
||||
else {
|
||||
size_t cmdlinelen;
|
||||
size_t itemlen;
|
||||
|
||||
cmdlinelen = strlen(args->cmdline);
|
||||
itemlen = strlen(item);
|
||||
args->cmdline =
|
||||
(char *)mrb_realloc(mrb, args->cmdline, cmdlinelen + itemlen + 2);
|
||||
args->cmdline[cmdlinelen] = '\n';
|
||||
memcpy(args->cmdline + cmdlinelen + 1, item, itemlen + 1);
|
||||
}
|
||||
}
|
||||
else {
|
||||
fprintf(stderr, "%s: No code specified for -e\n", opts->program);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
else if (strcmp(opt, "h") == 0) {
|
||||
usage(opts->program);
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
else if (strcmp(opt, "r") == 0) {
|
||||
if ((item = options_arg(opts))) {
|
||||
if (args->libc == 0) {
|
||||
args->libv = (char**)mrb_malloc(mrb, sizeof(char*));
|
||||
}
|
||||
else {
|
||||
args->libv = (char**)mrb_realloc(mrb, args->libv, sizeof(char*) * (args->libc + 1));
|
||||
}
|
||||
args->libv[args->libc++] = dup_arg_item(mrb, item);
|
||||
}
|
||||
else {
|
||||
fprintf(stderr, "%s: No library specified for -r\n", opts->program);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
else if (strcmp(opt, "v") == 0) {
|
||||
if (!args->verbose) {
|
||||
mrb_show_version(mrb);
|
||||
args->version = TRUE;
|
||||
}
|
||||
args->verbose = TRUE;
|
||||
}
|
||||
else if (strcmp(opt, "version") == 0) {
|
||||
mrb_show_version(mrb);
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
else if (strcmp(opt, "verbose") == 0) {
|
||||
args->verbose = TRUE;
|
||||
}
|
||||
else if (strcmp(opt, "copyright") == 0) {
|
||||
mrb_show_copyright(mrb);
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
else {
|
||||
fprintf(stderr, "%s: invalid option %s%s (-h will show valid options)\n",
|
||||
opts->program, opt[1] ? "--" : "-", opt);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
argc = opts->argc; argv = opts->argv;
|
||||
if (args->cmdline == NULL) {
|
||||
if (*argv == NULL) {
|
||||
if (args->version) exit(EXIT_SUCCESS);
|
||||
args->rfp = stdin;
|
||||
}
|
||||
else {
|
||||
args->rfp = strcmp(argv[0], "-") == 0 ?
|
||||
stdin : fopen(argv[0], args->mrbfile ? "rb" : "r");
|
||||
if (args->rfp == NULL) {
|
||||
fprintf(stderr, "%s: Cannot open program file: %s\n", opts->program, argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
args->fname = TRUE;
|
||||
args->cmdline = argv[0];
|
||||
argc--; argv++;
|
||||
}
|
||||
}
|
||||
args->argv = (char **)mrb_realloc(mrb, args->argv, sizeof(char*) * (argc + 1));
|
||||
memcpy(args->argv, argv, (argc+1) * sizeof(char*));
|
||||
args->argc = argc;
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
static void
|
||||
cleanup(mrb_state *mrb, struct _args *args)
|
||||
{
|
||||
if (args->rfp && args->rfp != stdin)
|
||||
fclose(args->rfp);
|
||||
if (!args->fname)
|
||||
mrb_free(mrb, args->cmdline);
|
||||
mrb_free(mrb, args->argv);
|
||||
if (args->libc) {
|
||||
while (args->libc--) {
|
||||
mrb_free(mrb, args->libv[args->libc]);
|
||||
}
|
||||
mrb_free(mrb, args->libv);
|
||||
}
|
||||
mrb_close(mrb);
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
mrb_state *mrb = mrb_open();
|
||||
int n = -1;
|
||||
int i;
|
||||
struct _args args;
|
||||
mrb_value ARGV;
|
||||
mrbc_context *c;
|
||||
mrb_value v;
|
||||
mrb_sym zero_sym;
|
||||
|
||||
if (mrb == NULL) {
|
||||
fprintf(stderr, "%s: Invalid mrb_state, exiting mruby\n", *argv);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
n = parse_args(mrb, argc, argv, &args);
|
||||
if (n == EXIT_FAILURE || (args.cmdline == NULL && args.rfp == NULL)) {
|
||||
cleanup(mrb, &args);
|
||||
return n;
|
||||
}
|
||||
else {
|
||||
int ai = mrb_gc_arena_save(mrb);
|
||||
ARGV = mrb_ary_new_capa(mrb, args.argc);
|
||||
for (i = 0; i < args.argc; i++) {
|
||||
char* utf8 = mrb_utf8_from_locale(args.argv[i], -1);
|
||||
if (utf8) {
|
||||
mrb_ary_push(mrb, ARGV, mrb_str_new_cstr(mrb, utf8));
|
||||
mrb_utf8_free(utf8);
|
||||
}
|
||||
}
|
||||
mrb_define_global_const(mrb, "ARGV", ARGV);
|
||||
mrb_gv_set(mrb, mrb_intern_lit(mrb, "$DEBUG"), mrb_bool_value(args.debug));
|
||||
|
||||
c = mrbc_context_new(mrb);
|
||||
if (args.verbose)
|
||||
c->dump_result = TRUE;
|
||||
if (args.check_syntax)
|
||||
c->no_exec = TRUE;
|
||||
|
||||
/* Set $0 */
|
||||
zero_sym = mrb_intern_lit(mrb, "$0");
|
||||
if (args.rfp) {
|
||||
const char *cmdline;
|
||||
cmdline = args.cmdline ? args.cmdline : "-";
|
||||
mrbc_filename(mrb, c, cmdline);
|
||||
mrb_gv_set(mrb, zero_sym, mrb_str_new_cstr(mrb, cmdline));
|
||||
}
|
||||
else {
|
||||
mrbc_filename(mrb, c, "-e");
|
||||
mrb_gv_set(mrb, zero_sym, mrb_str_new_lit(mrb, "-e"));
|
||||
}
|
||||
|
||||
/* Load libraries */
|
||||
for (i = 0; i < args.libc; i++) {
|
||||
FILE *lfp = fopen(args.libv[i], args.mrbfile ? "rb" : "r");
|
||||
if (lfp == NULL) {
|
||||
fprintf(stderr, "%s: Cannot open library file: %s\n", *argv, args.libv[i]);
|
||||
mrbc_context_free(mrb, c);
|
||||
cleanup(mrb, &args);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (args.mrbfile) {
|
||||
v = mrb_load_irep_file_cxt(mrb, lfp, c);
|
||||
}
|
||||
else {
|
||||
v = mrb_load_file_cxt(mrb, lfp, c);
|
||||
}
|
||||
fclose(lfp);
|
||||
}
|
||||
|
||||
/* Load program */
|
||||
if (args.mrbfile) {
|
||||
v = mrb_load_irep_file_cxt(mrb, args.rfp, c);
|
||||
}
|
||||
else if (args.rfp) {
|
||||
v = mrb_load_file_cxt(mrb, args.rfp, c);
|
||||
}
|
||||
else {
|
||||
char* utf8 = mrb_utf8_from_locale(args.cmdline, -1);
|
||||
if (!utf8) abort();
|
||||
v = mrb_load_string_cxt(mrb, utf8, c);
|
||||
mrb_utf8_free(utf8);
|
||||
}
|
||||
|
||||
mrb_gc_arena_restore(mrb, ai);
|
||||
mrbc_context_free(mrb, c);
|
||||
if (mrb->exc) {
|
||||
if (!mrb_undef_p(v)) {
|
||||
mrb_print_error(mrb);
|
||||
}
|
||||
n = EXIT_FAILURE;
|
||||
}
|
||||
else if (args.check_syntax) {
|
||||
puts("Syntax OK");
|
||||
}
|
||||
}
|
||||
cleanup(mrb, &args);
|
||||
|
||||
return n;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
require 'tempfile'
|
||||
|
||||
assert('no files') do
|
||||
o = `#{cmd('mruby-strip')} 2>&1`
|
||||
assert_equal 1, $?.exitstatus
|
||||
assert_equal "no files to strip", o.split("\n")[0]
|
||||
end
|
||||
|
||||
assert('file not found') do
|
||||
o = `#{cmd('mruby-strip')} not_found.mrb 2>&1`
|
||||
assert_equal 1, $?.exitstatus
|
||||
assert_equal "can't open file for reading not_found.mrb\n", o
|
||||
end
|
||||
|
||||
assert('not irep file') do
|
||||
t = Tempfile.new('script.rb')
|
||||
t.write 'p test\n'
|
||||
t.flush
|
||||
o = `#{cmd('mruby-strip')} #{t.path} 2>&1`
|
||||
assert_equal 1, $?.exitstatus
|
||||
assert_equal "can't read irep file #{t.path}\n", o
|
||||
end
|
||||
|
||||
assert('success') do
|
||||
script_file, compiled1, compiled2 =
|
||||
Tempfile.new('script.rb'), Tempfile.new('c1.mrb'), Tempfile.new('c2.mrb')
|
||||
script_file.write "p 'test'\n"
|
||||
script_file.flush
|
||||
`#{cmd('mrbc')} -g -o #{compiled1.path} #{script_file.path}`
|
||||
`#{cmd('mrbc')} -g -o #{compiled2.path} #{script_file.path}`
|
||||
|
||||
o = `#{cmd('mruby-strip')} #{compiled1.path}`
|
||||
assert_equal 0, $?.exitstatus
|
||||
assert_equal "", o
|
||||
assert_equal `#{cmd('mruby')} #{script_file.path}`, `#{cmd('mruby')} -b #{compiled1.path}`
|
||||
|
||||
o = `#{cmd('mruby-strip')} #{compiled1.path} #{compiled2.path}`
|
||||
assert_equal 0, $?.exitstatus
|
||||
assert_equal "", o
|
||||
end
|
||||
|
||||
assert('check debug section') do
|
||||
script_file, with_debug, without_debug =
|
||||
Tempfile.new('script.rb'), Tempfile.new('c1.mrb'), Tempfile.new('c2.mrb')
|
||||
script_file.write "p 'test'\n"
|
||||
script_file.flush
|
||||
`#{cmd('mrbc')} -o #{without_debug.path} #{script_file.path}`
|
||||
`#{cmd('mrbc')} -g -o #{with_debug.path} #{script_file.path}`
|
||||
|
||||
assert_true with_debug.size >= without_debug.size
|
||||
|
||||
`#{cmd('mruby-strip')} #{with_debug.path}`
|
||||
assert_equal without_debug.size, with_debug.size
|
||||
end
|
||||
|
||||
assert('check lv section') do
|
||||
script_file, with_lv, without_lv =
|
||||
Tempfile.new('script.rb'), Tempfile.new('c1.mrb'), Tempfile.new('c2.mrb')
|
||||
script_file.write <<EOS
|
||||
a, b = 0, 1
|
||||
a += b
|
||||
p Kernel.local_variables
|
||||
EOS
|
||||
script_file.flush
|
||||
`#{cmd('mrbc')} -o #{with_lv.path} #{script_file.path}`
|
||||
`#{cmd('mrbc')} -o #{without_lv.path} #{script_file.path}`
|
||||
|
||||
`#{cmd('mruby-strip')} -l #{without_lv.path}`
|
||||
assert_true without_lv.size < with_lv.size
|
||||
#
|
||||
# assert_equal '[:a, :b]', `#{cmd('mruby')} -b #{with_lv.path}`.chomp
|
||||
# assert_equal '[]', `#{cmd('mruby')} -b #{without_lv.path}`.chomp
|
||||
end
|
||||
@@ -0,0 +1,6 @@
|
||||
MRuby::Gem::Specification.new('mruby-bin-strip') do |spec|
|
||||
spec.license = 'MIT'
|
||||
spec.author = 'mruby developers'
|
||||
spec.summary = 'irep dump debug section remover command'
|
||||
spec.bins = %w(mruby-strip)
|
||||
end
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
#include <mruby.h>
|
||||
|
||||
#ifdef MRB_DISABLE_STDIO
|
||||
# error mruby-bin-strip conflicts 'MRB_DISABLE_STDIO' configuration in your 'build_config.rb'
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <mruby/irep.h>
|
||||
#include <mruby/dump.h>
|
||||
|
||||
struct strip_args {
|
||||
int argc_start;
|
||||
int argc;
|
||||
char **argv;
|
||||
mrb_bool lvar;
|
||||
};
|
||||
|
||||
static void
|
||||
print_usage(const char *f)
|
||||
{
|
||||
printf("Usage: %s [switches] irepfiles\n", f);
|
||||
printf("switches:\n");
|
||||
printf(" -l, --lvar remove LVAR section too.\n");
|
||||
}
|
||||
|
||||
static int
|
||||
parse_args(int argc, char **argv, struct strip_args *args)
|
||||
{
|
||||
int i;
|
||||
|
||||
args->argc_start = 0;
|
||||
args->argc = argc;
|
||||
args->argv = argv;
|
||||
args->lvar = FALSE;
|
||||
|
||||
for (i = 1; i < argc; ++i) {
|
||||
const size_t len = strlen(argv[i]);
|
||||
if (len >= 2 && argv[i][0] == '-') {
|
||||
switch (argv[i][1]) {
|
||||
case 'l':
|
||||
args->lvar = TRUE;
|
||||
break;
|
||||
case '-':
|
||||
if (strncmp((*argv) + 2, "lvar", len) == 0) {
|
||||
args->lvar = TRUE;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
args->argc_start = i;
|
||||
return i;
|
||||
}
|
||||
|
||||
static int
|
||||
strip(mrb_state *mrb, struct strip_args *args)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = args->argc_start; i < args->argc; ++i) {
|
||||
char *filename;
|
||||
FILE *rfile;
|
||||
mrb_irep *irep;
|
||||
FILE *wfile;
|
||||
int dump_result;
|
||||
|
||||
filename = args->argv[i];
|
||||
rfile = fopen(filename, "rb");
|
||||
if (rfile == NULL) {
|
||||
fprintf(stderr, "can't open file for reading %s\n", filename);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
irep = mrb_read_irep_file(mrb, rfile);
|
||||
fclose(rfile);
|
||||
if (irep == NULL) {
|
||||
fprintf(stderr, "can't read irep file %s\n", filename);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/* clear lv if --lvar is enabled */
|
||||
if (args->lvar) {
|
||||
mrb_irep_remove_lv(mrb, irep);
|
||||
}
|
||||
|
||||
wfile = fopen(filename, "wb");
|
||||
if (wfile == NULL) {
|
||||
fprintf(stderr, "can't open file for writing %s\n", filename);
|
||||
mrb_irep_decref(mrb, irep);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/* debug flag must always be false */
|
||||
dump_result = mrb_dump_irep_binary(mrb, irep, FALSE, wfile);
|
||||
|
||||
fclose(wfile);
|
||||
mrb_irep_decref(mrb, irep);
|
||||
|
||||
if (dump_result != MRB_DUMP_OK) {
|
||||
fprintf(stderr, "error occurred during dumping %s\n", filename);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
struct strip_args args;
|
||||
int args_result;
|
||||
mrb_state *mrb;
|
||||
int ret;
|
||||
|
||||
if (argc <= 1) {
|
||||
printf("no files to strip\n");
|
||||
print_usage(argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
args_result = parse_args(argc, argv, &args);
|
||||
if (args_result < 0) {
|
||||
print_usage(argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
mrb = mrb_open_core(mrb_default_allocf, NULL);
|
||||
if (mrb == NULL) {
|
||||
fputs("Invalid mrb_state, exiting mruby-strip\n", stderr);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
ret = strip(mrb, &args);
|
||||
|
||||
mrb_close(mrb);
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
MRuby::Gem::Specification.new('mruby-class-ext') do |spec|
|
||||
spec.license = 'MIT'
|
||||
spec.author = 'mruby developers'
|
||||
spec.summary = 'class/module extension'
|
||||
end
|
||||
@@ -0,0 +1,89 @@
|
||||
class Module
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# mod < other -> true, false, or nil
|
||||
#
|
||||
# Returns true if `mod` is a subclass of `other`. Returns
|
||||
# <code>nil</code> if there's no relationship between the two.
|
||||
# (Think of the relationship in terms of the class definition:
|
||||
# "class A < B" implies "A < B".)
|
||||
#
|
||||
def <(other)
|
||||
if self.equal?(other)
|
||||
false
|
||||
else
|
||||
self <= other
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# mod <= other -> true, false, or nil
|
||||
#
|
||||
# Returns true if `mod` is a subclass of `other` or
|
||||
# is the same as `other`. Returns
|
||||
# <code>nil</code> if there's no relationship between the two.
|
||||
# (Think of the relationship in terms of the class definition:
|
||||
# "class A < B" implies "A < B".)
|
||||
def <=(other)
|
||||
raise TypeError, 'compared with non class/module' unless other.is_a?(Module)
|
||||
if self.ancestors.include?(other)
|
||||
return true
|
||||
elsif other.ancestors.include?(self)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# mod > other -> true, false, or nil
|
||||
#
|
||||
# Returns true if `mod` is an ancestor of `other`. Returns
|
||||
# <code>nil</code> if there's no relationship between the two.
|
||||
# (Think of the relationship in terms of the class definition:
|
||||
# "class A < B" implies "B > A".)
|
||||
#
|
||||
def >(other)
|
||||
if self.equal?(other)
|
||||
false
|
||||
else
|
||||
self >= other
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# mod >= other -> true, false, or nil
|
||||
#
|
||||
# Returns true if `mod` is an ancestor of `other`, or the
|
||||
# two modules are the same. Returns
|
||||
# <code>nil</code> if there's no relationship between the two.
|
||||
# (Think of the relationship in terms of the class definition:
|
||||
# "class A < B" implies "B > A".)
|
||||
#
|
||||
def >=(other)
|
||||
raise TypeError, 'compared with non class/module' unless other.is_a?(Module)
|
||||
return other < self
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# module <=> other_module -> -1, 0, +1, or nil
|
||||
#
|
||||
# Comparison---Returns -1, 0, +1 or nil depending on whether `module`
|
||||
# includes `other_module`, they are the same, or if `module` is included by
|
||||
# `other_module`.
|
||||
#
|
||||
# Returns `nil` if `module` has no relationship with `other_module`, if
|
||||
# `other_module` is not a module, or if the two values are incomparable.
|
||||
#
|
||||
def <=>(other)
|
||||
return 0 if self.equal?(other)
|
||||
return nil unless other.is_a?(Module)
|
||||
cmp = self < other
|
||||
return -1 if cmp
|
||||
return 1 unless cmp.nil?
|
||||
return nil
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,72 @@
|
||||
#include "mruby.h"
|
||||
#include "mruby/class.h"
|
||||
#include "mruby/string.h"
|
||||
|
||||
static mrb_value
|
||||
mrb_mod_name(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
mrb_value name = mrb_class_path(mrb, mrb_class_ptr(self));
|
||||
if (mrb_string_p(name)) {
|
||||
MRB_SET_FROZEN_FLAG(mrb_basic_ptr(name));
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
mrb_mod_singleton_class_p(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
return mrb_bool_value(mrb_sclass_p(self));
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* module_exec(arg...) {|var...| block } -> obj
|
||||
* class_exec(arg...) {|var...| block } -> obj
|
||||
*
|
||||
* Evaluates the given block in the context of the
|
||||
* class/module. The method defined in the block will belong
|
||||
* to the receiver. Any arguments passed to the method will be
|
||||
* passed to the block. This can be used if the block needs to
|
||||
* access instance variables.
|
||||
*
|
||||
* class Thing
|
||||
* end
|
||||
* Thing.class_exec{
|
||||
* def hello() "Hello there!" end
|
||||
* }
|
||||
* puts Thing.new.hello()
|
||||
*/
|
||||
|
||||
static mrb_value
|
||||
mrb_mod_module_exec(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
const mrb_value *argv;
|
||||
mrb_int argc;
|
||||
mrb_value blk;
|
||||
struct RClass *c;
|
||||
|
||||
mrb_get_args(mrb, "*&!", &argv, &argc, &blk);
|
||||
|
||||
c = mrb_class_ptr(self);
|
||||
if (mrb->c->ci->acc < 0) {
|
||||
return mrb_yield_with_class(mrb, blk, argc, argv, self, c);
|
||||
}
|
||||
mrb->c->ci->target_class = c;
|
||||
return mrb_yield_cont(mrb, blk, self, argc, argv);
|
||||
}
|
||||
|
||||
void
|
||||
mrb_mruby_class_ext_gem_init(mrb_state *mrb)
|
||||
{
|
||||
struct RClass *mod = mrb->module_class;
|
||||
|
||||
mrb_define_method(mrb, mod, "name", mrb_mod_name, MRB_ARGS_NONE());
|
||||
mrb_define_method(mrb, mod, "singleton_class?", mrb_mod_singleton_class_p, MRB_ARGS_NONE());
|
||||
mrb_define_method(mrb, mod, "module_exec", mrb_mod_module_exec, MRB_ARGS_ANY()|MRB_ARGS_BLOCK());
|
||||
mrb_define_method(mrb, mod, "class_exec", mrb_mod_module_exec, MRB_ARGS_ANY()|MRB_ARGS_BLOCK());
|
||||
}
|
||||
|
||||
void
|
||||
mrb_mruby_class_ext_gem_final(mrb_state *mrb)
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
assert 'Module#<' do
|
||||
a = Class.new
|
||||
b = Class.new(a)
|
||||
c = Class.new(a)
|
||||
d = Module.new
|
||||
e = Class.new { include d }
|
||||
f = Module.new { include d }
|
||||
|
||||
# compare class to class
|
||||
assert_true b < a
|
||||
assert_false b < b
|
||||
assert_false a < b
|
||||
assert_nil c < b
|
||||
|
||||
# compare class to module
|
||||
assert_true e < d
|
||||
assert_false d < e
|
||||
assert_nil a < d
|
||||
|
||||
# compare module to module
|
||||
assert_true f < d
|
||||
assert_false f < f
|
||||
assert_false d < f
|
||||
|
||||
assert_raise(TypeError) { a < Object.new }
|
||||
end
|
||||
|
||||
assert 'Module#<=' do
|
||||
a = Class.new
|
||||
b = Class.new(a)
|
||||
c = Class.new(a)
|
||||
d = Module.new
|
||||
e = Class.new { include d }
|
||||
f = Module.new { include d }
|
||||
|
||||
# compare class to class
|
||||
assert_true b <= a
|
||||
assert_true b <= b
|
||||
assert_false a <= b
|
||||
assert_nil c <= b
|
||||
|
||||
# compare class to module
|
||||
assert_true e <= d
|
||||
assert_false d <= e
|
||||
assert_nil a <= d
|
||||
|
||||
# compare module to module
|
||||
assert_true f <= d
|
||||
assert_true f <= f
|
||||
assert_false d <= f
|
||||
|
||||
assert_raise(TypeError) { a <= Object.new }
|
||||
end
|
||||
|
||||
assert 'Module#name' do
|
||||
module Outer
|
||||
class Inner; end
|
||||
const_set :SetInner, Class.new
|
||||
end
|
||||
|
||||
assert_equal 'Outer', Outer.name
|
||||
assert_equal 'Outer::Inner', Outer::Inner.name
|
||||
assert_equal 'Outer::SetInner', Outer::SetInner.name
|
||||
|
||||
outer = Module.new do
|
||||
const_set :SetInner, Class.new
|
||||
end
|
||||
Object.const_set :SetOuter, outer
|
||||
|
||||
assert_equal 'SetOuter', SetOuter.name
|
||||
assert_equal 'SetOuter::SetInner', SetOuter::SetInner.name
|
||||
|
||||
mod = Module.new
|
||||
cls = Class.new
|
||||
|
||||
assert_nil mod.name
|
||||
assert_nil cls.name
|
||||
end
|
||||
|
||||
assert 'Module#singleton_class?' do
|
||||
mod = Module.new
|
||||
cls = Class.new
|
||||
scl = (class <<cls; self; end)
|
||||
|
||||
assert_false mod.singleton_class?
|
||||
assert_false cls.singleton_class?
|
||||
assert_true scl.singleton_class?
|
||||
end
|
||||
|
||||
assert 'Module#module_eval' do
|
||||
mod = Module.new
|
||||
mod.class_exec(1,2,3) do |a,b,c|
|
||||
assert_equal([1,2,3], [a,b,c])
|
||||
def hi
|
||||
"hi"
|
||||
end
|
||||
end
|
||||
cls = Class.new
|
||||
cls.class_exec(42) do |x|
|
||||
assert_equal(42, x)
|
||||
include mod
|
||||
def hello
|
||||
"hello"
|
||||
end
|
||||
end
|
||||
obj = cls.new
|
||||
assert_equal("hi", obj.hi)
|
||||
assert_equal("hello", obj.hello)
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
MRuby::Gem::Specification.new('mruby-compar-ext') do |spec|
|
||||
spec.license = 'MIT'
|
||||
spec.author = 'mruby developers'
|
||||
spec.summary = 'Enumerable module extension'
|
||||
end
|
||||
@@ -0,0 +1,31 @@
|
||||
module Comparable
|
||||
##
|
||||
# Returns <i>min</i> if <i>obj</i> <code><=></code> <i>min</i> is less
|
||||
# than zero, <i>max</i> if <i>obj</i> <code><=></code> <i>max</i> is
|
||||
# greater than zero and <i>obj</i> otherwise.
|
||||
#
|
||||
# 12.clamp(0, 100) #=> 12
|
||||
# 523.clamp(0, 100) #=> 100
|
||||
# -3.123.clamp(0, 100) #=> 0
|
||||
#
|
||||
# 'd'.clamp('a', 'f') #=> 'd'
|
||||
# 'z'.clamp('a', 'f') #=> 'f'
|
||||
#
|
||||
def clamp(min, max)
|
||||
if (min <=> max) > 0
|
||||
raise ArgumentError, "min argument must be smaller than max argument"
|
||||
end
|
||||
c = self <=> min
|
||||
if c == 0
|
||||
return self
|
||||
elsif c < 0
|
||||
return min
|
||||
end
|
||||
c = self <=> max
|
||||
if c > 0
|
||||
return max
|
||||
else
|
||||
return self
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,30 @@
|
||||
require 'tempfile'
|
||||
|
||||
assert('Compiling multiple files without new line in last line. #2361') do
|
||||
a, b, out = Tempfile.new('a.rb'), Tempfile.new('b.rb'), Tempfile.new('out.mrb')
|
||||
a.write('module A; end')
|
||||
a.flush
|
||||
b.write('module B; end')
|
||||
b.flush
|
||||
result = `#{cmd('mrbc')} -c -o #{out.path} #{a.path} #{b.path} 2>&1`
|
||||
assert_equal "#{cmd('mrbc')}:#{a.path}:Syntax OK", result.chomp
|
||||
assert_equal 0, $?.exitstatus
|
||||
end
|
||||
|
||||
assert('parsing function with void argument') do
|
||||
a, out = Tempfile.new('a.rb'), Tempfile.new('out.mrb')
|
||||
a.write('f ()')
|
||||
a.flush
|
||||
result = `#{cmd('mrbc')} -c -o #{out.path} #{a.path} 2>&1`
|
||||
assert_equal "#{cmd('mrbc')}:#{a.path}:Syntax OK", result.chomp
|
||||
assert_equal 0, $?.exitstatus
|
||||
end
|
||||
|
||||
assert('embedded document with invalid terminator') do
|
||||
a, out = Tempfile.new('a.rb'), Tempfile.new('out.mrb')
|
||||
a.write("=begin\n=endx\n")
|
||||
a.flush
|
||||
result = `#{cmd('mrbc')} -c -o #{out.path} #{a.path} 2>&1`
|
||||
assert_equal "#{a.path}:3:0: embedded document meets end of file", result.chomp
|
||||
assert_equal 1, $?.exitstatus
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,47 @@
|
||||
%{
|
||||
struct kwtable {const char *name; int id[2]; enum mrb_lex_state_enum state;};
|
||||
%}
|
||||
|
||||
struct kwtable;
|
||||
%%
|
||||
__ENCODING__, {keyword__ENCODING__, keyword__ENCODING__}, EXPR_END
|
||||
__FILE__, {keyword__FILE__, keyword__FILE__}, EXPR_END
|
||||
__LINE__, {keyword__LINE__, keyword__LINE__}, EXPR_END
|
||||
BEGIN, {keyword_BEGIN, keyword_BEGIN}, EXPR_END
|
||||
END, {keyword_END, keyword_END}, EXPR_END
|
||||
alias, {keyword_alias, keyword_alias}, EXPR_FNAME
|
||||
and, {keyword_and, keyword_and}, EXPR_VALUE
|
||||
begin, {keyword_begin, keyword_begin}, EXPR_BEG
|
||||
break, {keyword_break, keyword_break}, EXPR_MID
|
||||
case, {keyword_case, keyword_case}, EXPR_VALUE
|
||||
class, {keyword_class, keyword_class}, EXPR_CLASS
|
||||
def, {keyword_def, keyword_def}, EXPR_FNAME
|
||||
do, {keyword_do, keyword_do}, EXPR_BEG
|
||||
else, {keyword_else, keyword_else}, EXPR_BEG
|
||||
elsif, {keyword_elsif, keyword_elsif}, EXPR_VALUE
|
||||
end, {keyword_end, keyword_end}, EXPR_END
|
||||
ensure, {keyword_ensure, keyword_ensure}, EXPR_BEG
|
||||
false, {keyword_false, keyword_false}, EXPR_END
|
||||
for, {keyword_for, keyword_for}, EXPR_VALUE
|
||||
if, {keyword_if, modifier_if}, EXPR_VALUE
|
||||
in, {keyword_in, keyword_in}, EXPR_VALUE
|
||||
module, {keyword_module, keyword_module}, EXPR_VALUE
|
||||
next, {keyword_next, keyword_next}, EXPR_MID
|
||||
nil, {keyword_nil, keyword_nil}, EXPR_END
|
||||
not, {keyword_not, keyword_not}, EXPR_ARG
|
||||
or, {keyword_or, keyword_or}, EXPR_VALUE
|
||||
redo, {keyword_redo, keyword_redo}, EXPR_END
|
||||
rescue, {keyword_rescue, modifier_rescue}, EXPR_MID
|
||||
retry, {keyword_retry, keyword_retry}, EXPR_END
|
||||
return, {keyword_return, keyword_return}, EXPR_MID
|
||||
self, {keyword_self, keyword_self}, EXPR_END
|
||||
super, {keyword_super, keyword_super}, EXPR_ARG
|
||||
then, {keyword_then, keyword_then}, EXPR_BEG
|
||||
true, {keyword_true, keyword_true}, EXPR_END
|
||||
undef, {keyword_undef, keyword_undef}, EXPR_FNAME
|
||||
unless, {keyword_unless, modifier_unless}, EXPR_VALUE
|
||||
until, {keyword_until, modifier_until}, EXPR_VALUE
|
||||
when, {keyword_when, keyword_when}, EXPR_VALUE
|
||||
while, {keyword_while, modifier_while}, EXPR_VALUE
|
||||
yield, {keyword_yield, keyword_yield}, EXPR_ARG
|
||||
%%
|
||||
@@ -0,0 +1,203 @@
|
||||
/* ANSI-C code produced by gperf version 3.1 */
|
||||
/* Command-line: gperf -L ANSI-C -C -p -j1 -i 1 -g -o -t -N mrb_reserved_word -k'1,3,$' /home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords */
|
||||
|
||||
#if !((' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \
|
||||
&& ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \
|
||||
&& (')' == 41) && ('*' == 42) && ('+' == 43) && (',' == 44) \
|
||||
&& ('-' == 45) && ('.' == 46) && ('/' == 47) && ('0' == 48) \
|
||||
&& ('1' == 49) && ('2' == 50) && ('3' == 51) && ('4' == 52) \
|
||||
&& ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) \
|
||||
&& ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) \
|
||||
&& ('=' == 61) && ('>' == 62) && ('?' == 63) && ('A' == 65) \
|
||||
&& ('B' == 66) && ('C' == 67) && ('D' == 68) && ('E' == 69) \
|
||||
&& ('F' == 70) && ('G' == 71) && ('H' == 72) && ('I' == 73) \
|
||||
&& ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) \
|
||||
&& ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) \
|
||||
&& ('R' == 82) && ('S' == 83) && ('T' == 84) && ('U' == 85) \
|
||||
&& ('V' == 86) && ('W' == 87) && ('X' == 88) && ('Y' == 89) \
|
||||
&& ('Z' == 90) && ('[' == 91) && ('\\' == 92) && (']' == 93) \
|
||||
&& ('^' == 94) && ('_' == 95) && ('a' == 97) && ('b' == 98) \
|
||||
&& ('c' == 99) && ('d' == 100) && ('e' == 101) && ('f' == 102) \
|
||||
&& ('g' == 103) && ('h' == 104) && ('i' == 105) && ('j' == 106) \
|
||||
&& ('k' == 107) && ('l' == 108) && ('m' == 109) && ('n' == 110) \
|
||||
&& ('o' == 111) && ('p' == 112) && ('q' == 113) && ('r' == 114) \
|
||||
&& ('s' == 115) && ('t' == 116) && ('u' == 117) && ('v' == 118) \
|
||||
&& ('w' == 119) && ('x' == 120) && ('y' == 121) && ('z' == 122) \
|
||||
&& ('{' == 123) && ('|' == 124) && ('}' == 125) && ('~' == 126))
|
||||
/* The character set is not based on ISO-646. */
|
||||
#error "gperf generated tables don't work with this execution character set. Please report a bug to <[email protected]>."
|
||||
#endif
|
||||
|
||||
#line 1 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
|
||||
struct kwtable {const char *name; int id[2]; enum mrb_lex_state_enum state;};
|
||||
#line 5 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
struct kwtable;
|
||||
|
||||
#define TOTAL_KEYWORDS 40
|
||||
#define MIN_WORD_LENGTH 2
|
||||
#define MAX_WORD_LENGTH 12
|
||||
#define MIN_HASH_VALUE 8
|
||||
#define MAX_HASH_VALUE 50
|
||||
/* maximum key range = 43, duplicates = 0 */
|
||||
|
||||
#ifdef __GNUC__
|
||||
__inline
|
||||
#else
|
||||
#ifdef __cplusplus
|
||||
inline
|
||||
#endif
|
||||
#endif
|
||||
static unsigned int
|
||||
hash (register const char *str, register size_t len)
|
||||
{
|
||||
static const unsigned char asso_values[] =
|
||||
{
|
||||
51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
|
||||
51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
|
||||
51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
|
||||
51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
|
||||
51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
|
||||
51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
|
||||
51, 51, 51, 51, 51, 51, 14, 51, 16, 8,
|
||||
11, 13, 51, 51, 51, 51, 10, 51, 13, 51,
|
||||
51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
|
||||
51, 51, 51, 51, 51, 11, 51, 13, 1, 26,
|
||||
4, 1, 8, 28, 51, 23, 51, 1, 1, 27,
|
||||
5, 19, 21, 51, 8, 3, 3, 11, 51, 21,
|
||||
24, 16, 51, 51, 51, 51, 51, 51, 51, 51,
|
||||
51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
|
||||
51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
|
||||
51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
|
||||
51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
|
||||
51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
|
||||
51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
|
||||
51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
|
||||
51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
|
||||
51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
|
||||
51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
|
||||
51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
|
||||
51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
|
||||
51, 51, 51, 51, 51, 51
|
||||
};
|
||||
register unsigned int hval = len;
|
||||
|
||||
switch (hval)
|
||||
{
|
||||
default:
|
||||
hval += asso_values[(unsigned char)str[2]];
|
||||
/*FALLTHROUGH*/
|
||||
case 2:
|
||||
case 1:
|
||||
hval += asso_values[(unsigned char)str[0]];
|
||||
break;
|
||||
}
|
||||
return hval + asso_values[(unsigned char)str[len - 1]];
|
||||
}
|
||||
|
||||
const struct kwtable *
|
||||
mrb_reserved_word (register const char *str, register size_t len)
|
||||
{
|
||||
static const struct kwtable wordlist[] =
|
||||
{
|
||||
{""}, {""}, {""}, {""}, {""}, {""}, {""}, {""},
|
||||
#line 15 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
{"break", {keyword_break, keyword_break}, EXPR_MID},
|
||||
#line 20 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
{"else", {keyword_else, keyword_else}, EXPR_BEG},
|
||||
#line 30 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
{"nil", {keyword_nil, keyword_nil}, EXPR_END},
|
||||
#line 23 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
{"ensure", {keyword_ensure, keyword_ensure}, EXPR_BEG},
|
||||
#line 22 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
{"end", {keyword_end, keyword_end}, EXPR_END},
|
||||
#line 39 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
{"then", {keyword_then, keyword_then}, EXPR_BEG},
|
||||
#line 31 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
{"not", {keyword_not, keyword_not}, EXPR_ARG},
|
||||
#line 24 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
{"false", {keyword_false, keyword_false}, EXPR_END},
|
||||
#line 37 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
{"self", {keyword_self, keyword_self}, EXPR_END},
|
||||
#line 21 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
{"elsif", {keyword_elsif, keyword_elsif}, EXPR_VALUE},
|
||||
#line 34 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
{"rescue", {keyword_rescue, modifier_rescue}, EXPR_MID},
|
||||
#line 40 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
{"true", {keyword_true, keyword_true}, EXPR_END},
|
||||
#line 43 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
{"until", {keyword_until, modifier_until}, EXPR_VALUE},
|
||||
#line 42 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
{"unless", {keyword_unless, modifier_unless}, EXPR_VALUE},
|
||||
#line 36 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
{"return", {keyword_return, keyword_return}, EXPR_MID},
|
||||
#line 18 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
{"def", {keyword_def, keyword_def}, EXPR_FNAME},
|
||||
#line 13 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
{"and", {keyword_and, keyword_and}, EXPR_VALUE},
|
||||
#line 19 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
{"do", {keyword_do, keyword_do}, EXPR_BEG},
|
||||
#line 46 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
{"yield", {keyword_yield, keyword_yield}, EXPR_ARG},
|
||||
#line 25 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
{"for", {keyword_for, keyword_for}, EXPR_VALUE},
|
||||
#line 41 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
{"undef", {keyword_undef, keyword_undef}, EXPR_FNAME},
|
||||
#line 32 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
{"or", {keyword_or, keyword_or}, EXPR_VALUE},
|
||||
#line 27 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
{"in", {keyword_in, keyword_in}, EXPR_VALUE},
|
||||
#line 44 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
{"when", {keyword_when, keyword_when}, EXPR_VALUE},
|
||||
#line 35 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
{"retry", {keyword_retry, keyword_retry}, EXPR_END},
|
||||
#line 26 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
{"if", {keyword_if, modifier_if}, EXPR_VALUE},
|
||||
#line 16 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
{"case", {keyword_case, keyword_case}, EXPR_VALUE},
|
||||
#line 33 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
{"redo", {keyword_redo, keyword_redo}, EXPR_END},
|
||||
#line 29 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
{"next", {keyword_next, keyword_next}, EXPR_MID},
|
||||
#line 38 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
{"super", {keyword_super, keyword_super}, EXPR_ARG},
|
||||
#line 28 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
{"module", {keyword_module, keyword_module}, EXPR_VALUE},
|
||||
#line 14 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
{"begin", {keyword_begin, keyword_begin}, EXPR_BEG},
|
||||
#line 9 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
{"__LINE__", {keyword__LINE__, keyword__LINE__}, EXPR_END},
|
||||
#line 8 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
{"__FILE__", {keyword__FILE__, keyword__FILE__}, EXPR_END},
|
||||
#line 7 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
{"__ENCODING__", {keyword__ENCODING__, keyword__ENCODING__}, EXPR_END},
|
||||
#line 11 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
{"END", {keyword_END, keyword_END}, EXPR_END},
|
||||
#line 12 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
{"alias", {keyword_alias, keyword_alias}, EXPR_FNAME},
|
||||
#line 10 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
{"BEGIN", {keyword_BEGIN, keyword_BEGIN}, EXPR_END},
|
||||
{""},
|
||||
#line 17 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
{"class", {keyword_class, keyword_class}, EXPR_CLASS},
|
||||
{""}, {""},
|
||||
#line 45 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
{"while", {keyword_while, modifier_while}, EXPR_VALUE}
|
||||
};
|
||||
|
||||
if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH)
|
||||
{
|
||||
register unsigned int key = hash (str, len);
|
||||
|
||||
if (key <= MAX_HASH_VALUE)
|
||||
{
|
||||
register const char *s = wordlist[key].name;
|
||||
|
||||
if (*str == *s && !strcmp (str + 1, s + 1))
|
||||
return &wordlist[key];
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#line 47 "/home/matz/work/mruby/mrbgems/mruby-compiler/core/keywords"
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
** node.h - nodes of abstract syntax tree
|
||||
**
|
||||
** See Copyright Notice in mruby.h
|
||||
*/
|
||||
|
||||
#ifndef MRUBY_COMPILER_NODE_H
|
||||
#define MRUBY_COMPILER_NODE_H
|
||||
|
||||
enum node_type {
|
||||
NODE_METHOD,
|
||||
NODE_SCOPE,
|
||||
NODE_BLOCK,
|
||||
NODE_IF,
|
||||
NODE_CASE,
|
||||
NODE_WHEN,
|
||||
NODE_WHILE,
|
||||
NODE_UNTIL,
|
||||
NODE_ITER,
|
||||
NODE_FOR,
|
||||
NODE_BREAK,
|
||||
NODE_NEXT,
|
||||
NODE_REDO,
|
||||
NODE_RETRY,
|
||||
NODE_BEGIN,
|
||||
NODE_RESCUE,
|
||||
NODE_ENSURE,
|
||||
NODE_AND,
|
||||
NODE_OR,
|
||||
NODE_NOT,
|
||||
NODE_MASGN,
|
||||
NODE_ASGN,
|
||||
NODE_CDECL,
|
||||
NODE_CVASGN,
|
||||
NODE_CVDECL,
|
||||
NODE_OP_ASGN,
|
||||
NODE_CALL,
|
||||
NODE_SCALL,
|
||||
NODE_FCALL,
|
||||
NODE_SUPER,
|
||||
NODE_ZSUPER,
|
||||
NODE_ARRAY,
|
||||
NODE_ZARRAY,
|
||||
NODE_HASH,
|
||||
NODE_KW_HASH,
|
||||
NODE_RETURN,
|
||||
NODE_YIELD,
|
||||
NODE_LVAR,
|
||||
NODE_DVAR,
|
||||
NODE_GVAR,
|
||||
NODE_IVAR,
|
||||
NODE_CONST,
|
||||
NODE_CVAR,
|
||||
NODE_NVAR,
|
||||
NODE_NTH_REF,
|
||||
NODE_BACK_REF,
|
||||
NODE_MATCH,
|
||||
NODE_INT,
|
||||
NODE_FLOAT,
|
||||
NODE_NEGATE,
|
||||
NODE_LAMBDA,
|
||||
NODE_SYM,
|
||||
NODE_STR,
|
||||
NODE_DSTR,
|
||||
NODE_XSTR,
|
||||
NODE_DXSTR,
|
||||
NODE_REGX,
|
||||
NODE_DREGX,
|
||||
NODE_DREGX_ONCE,
|
||||
NODE_ARG,
|
||||
NODE_ARGS_TAIL,
|
||||
NODE_KW_ARG,
|
||||
NODE_KW_REST_ARGS,
|
||||
NODE_SPLAT,
|
||||
NODE_TO_ARY,
|
||||
NODE_SVALUE,
|
||||
NODE_BLOCK_ARG,
|
||||
NODE_DEF,
|
||||
NODE_SDEF,
|
||||
NODE_ALIAS,
|
||||
NODE_UNDEF,
|
||||
NODE_CLASS,
|
||||
NODE_MODULE,
|
||||
NODE_SCLASS,
|
||||
NODE_COLON2,
|
||||
NODE_COLON3,
|
||||
NODE_DOT2,
|
||||
NODE_DOT3,
|
||||
NODE_SELF,
|
||||
NODE_NIL,
|
||||
NODE_TRUE,
|
||||
NODE_FALSE,
|
||||
NODE_DEFINED,
|
||||
NODE_POSTEXE,
|
||||
NODE_DSYM,
|
||||
NODE_HEREDOC,
|
||||
NODE_LITERAL_DELIM,
|
||||
NODE_WORDS,
|
||||
NODE_SYMBOLS,
|
||||
NODE_LAST
|
||||
};
|
||||
|
||||
#endif /* MRUBY_COMPILER_NODE_H */
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,38 @@
|
||||
MRuby::Gem::Specification.new 'mruby-compiler' do |spec|
|
||||
spec.license = 'MIT'
|
||||
spec.author = 'mruby developers'
|
||||
spec.summary = 'mruby compiler library'
|
||||
|
||||
lex_def = "#{dir}/core/lex.def"
|
||||
core_objs = Dir.glob("#{dir}/core/*.c").map { |f|
|
||||
next nil if build.cxx_exception_enabled? and f =~ /(codegen).c$/
|
||||
objfile(f.pathmap("#{build_dir}/core/%n"))
|
||||
}.compact
|
||||
|
||||
if build.cxx_exception_enabled?
|
||||
core_objs <<
|
||||
build.compile_as_cxx("#{dir}/core/y.tab.c", "#{build_dir}/core/y.tab.cxx",
|
||||
objfile("#{build_dir}/y.tab"), ["#{dir}/core"]) <<
|
||||
build.compile_as_cxx("#{dir}/core/codegen.c", "#{build_dir}/core/codegen.cxx")
|
||||
else
|
||||
core_objs << objfile("#{build_dir}/core/y.tab")
|
||||
file objfile("#{build_dir}/core/y.tab") => "#{dir}/core/y.tab.c" do |t|
|
||||
cc.run t.name, t.prerequisites.first, [], ["#{dir}/core"]
|
||||
end
|
||||
end
|
||||
|
||||
# Parser
|
||||
file "#{dir}/core/y.tab.c" => ["#{dir}/core/parse.y", lex_def] do |t|
|
||||
yacc.run t.name, t.prerequisites.first
|
||||
content = File.read(t.name).gsub(/^#line +\d+ +"\K.*$/){$&.relative_path}
|
||||
File.write(t.name, content)
|
||||
end
|
||||
|
||||
# Lexical analyzer
|
||||
file lex_def => "#{dir}/core/keywords" do |t|
|
||||
gperf.run t.name, t.prerequisites.first
|
||||
end
|
||||
|
||||
file build.libmruby_core_static => core_objs
|
||||
build.libmruby << core_objs
|
||||
end
|
||||
@@ -0,0 +1,7 @@
|
||||
MRuby::Gem::Specification.new('mruby-complex') do |spec|
|
||||
spec.license = 'MIT'
|
||||
spec.author = 'mruby developers'
|
||||
spec.summary = 'Complex class'
|
||||
|
||||
spec.add_dependency 'mruby-math', core: 'mruby-math'
|
||||
end
|
||||
@@ -0,0 +1,122 @@
|
||||
class Complex < Numeric
|
||||
def self.polar(abs, arg = 0)
|
||||
Complex(abs * Math.cos(arg), abs * Math.sin(arg))
|
||||
end
|
||||
|
||||
def inspect
|
||||
"(#{to_s})"
|
||||
end
|
||||
|
||||
def to_s
|
||||
"#{real}#{'+' unless imaginary < 0}#{imaginary}i"
|
||||
end
|
||||
|
||||
def +@
|
||||
Complex(real, imaginary)
|
||||
end
|
||||
|
||||
def -@
|
||||
Complex(-real, -imaginary)
|
||||
end
|
||||
|
||||
def +(rhs)
|
||||
if rhs.is_a? Complex
|
||||
Complex(real + rhs.real, imaginary + rhs.imaginary)
|
||||
elsif rhs.is_a? Numeric
|
||||
Complex(real + rhs, imaginary)
|
||||
end
|
||||
end
|
||||
|
||||
def -(rhs)
|
||||
if rhs.is_a? Complex
|
||||
Complex(real - rhs.real, imaginary - rhs.imaginary)
|
||||
elsif rhs.is_a? Numeric
|
||||
Complex(real - rhs, imaginary)
|
||||
end
|
||||
end
|
||||
|
||||
def *(rhs)
|
||||
if rhs.is_a? Complex
|
||||
Complex(real * rhs.real - imaginary * rhs.imaginary, real * rhs.imaginary + rhs.real * imaginary)
|
||||
elsif rhs.is_a? Numeric
|
||||
Complex(real * rhs, imaginary * rhs)
|
||||
end
|
||||
end
|
||||
|
||||
def /(rhs)
|
||||
if rhs.is_a? Complex
|
||||
__div__(rhs)
|
||||
elsif rhs.is_a? Numeric
|
||||
Complex(real / rhs, imaginary / rhs)
|
||||
end
|
||||
end
|
||||
alias_method :quo, :/
|
||||
|
||||
def ==(rhs)
|
||||
if rhs.is_a? Complex
|
||||
real == rhs.real && imaginary == rhs.imaginary
|
||||
elsif rhs.is_a? Numeric
|
||||
imaginary == 0 && real == rhs
|
||||
end
|
||||
end
|
||||
|
||||
def abs
|
||||
Math.hypot imaginary, real
|
||||
end
|
||||
alias_method :magnitude, :abs
|
||||
|
||||
def abs2
|
||||
real * real + imaginary * imaginary
|
||||
end
|
||||
|
||||
def arg
|
||||
Math.atan2 imaginary, real
|
||||
end
|
||||
alias_method :angle, :arg
|
||||
alias_method :phase, :arg
|
||||
|
||||
def conjugate
|
||||
Complex(real, -imaginary)
|
||||
end
|
||||
alias_method :conj, :conjugate
|
||||
|
||||
def fdiv(numeric)
|
||||
Complex(real.to_f / numeric, imaginary.to_f / numeric)
|
||||
end
|
||||
|
||||
def polar
|
||||
[abs, arg]
|
||||
end
|
||||
|
||||
def real?
|
||||
false
|
||||
end
|
||||
|
||||
def rectangular
|
||||
[real, imaginary]
|
||||
end
|
||||
alias_method :rect, :rectangular
|
||||
|
||||
def to_r
|
||||
raise RangeError.new "can't convert #{to_s} into Rational" unless imaginary.zero?
|
||||
Rational(real, 1)
|
||||
end
|
||||
|
||||
alias_method :imag, :imaginary
|
||||
|
||||
[Fixnum, Float].each do |cls|
|
||||
[:+, :-, :*, :/, :==].each do |op|
|
||||
cls.instance_eval do
|
||||
original_operator_name = :"__original_operator_#{op}_complex"
|
||||
alias_method original_operator_name, op
|
||||
define_method op do |rhs|
|
||||
if rhs.is_a? Complex
|
||||
Complex(self).__send__(op, rhs)
|
||||
else
|
||||
__send__(original_operator_name, rhs)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,248 @@
|
||||
#include <mruby.h>
|
||||
#include <mruby/class.h>
|
||||
#include <mruby/numeric.h>
|
||||
#include <math.h>
|
||||
|
||||
#ifdef MRB_WITHOUT_FLOAT
|
||||
# error Complex conflicts 'MRB_WITHOUT_FLOAT' configuration in your 'build_config.rb'
|
||||
#endif
|
||||
|
||||
struct mrb_complex {
|
||||
mrb_float real;
|
||||
mrb_float imaginary;
|
||||
};
|
||||
|
||||
#ifdef MRB_USE_FLOAT
|
||||
#define F(x) x##f
|
||||
#else
|
||||
#define F(x) x
|
||||
#endif
|
||||
|
||||
#if defined(MRB_64BIT) || defined(MRB_USE_FLOAT)
|
||||
|
||||
#define COMPLEX_USE_ISTRUCT
|
||||
/* use TT_ISTRUCT */
|
||||
#include <mruby/istruct.h>
|
||||
|
||||
#define complex_ptr(mrb, v) (struct mrb_complex*)mrb_istruct_ptr(v)
|
||||
|
||||
static struct RBasic*
|
||||
complex_alloc(mrb_state *mrb, struct RClass *c, struct mrb_complex **p)
|
||||
{
|
||||
struct RIStruct *s;
|
||||
|
||||
s = (struct RIStruct*)mrb_obj_alloc(mrb, MRB_TT_ISTRUCT, c);
|
||||
*p = (struct mrb_complex*)s->inline_data;
|
||||
|
||||
return (struct RBasic*)s;
|
||||
}
|
||||
|
||||
#else
|
||||
/* use TT_DATA */
|
||||
#include <mruby/data.h>
|
||||
|
||||
static const struct mrb_data_type mrb_complex_type = {"Complex", mrb_free};
|
||||
|
||||
static struct RBasic*
|
||||
complex_alloc(mrb_state *mrb, struct RClass *c, struct mrb_complex **p)
|
||||
{
|
||||
struct RData *d;
|
||||
|
||||
Data_Make_Struct(mrb, c, struct mrb_complex, &mrb_complex_type, *p, d);
|
||||
|
||||
return (struct RBasic*)d;
|
||||
}
|
||||
|
||||
static struct mrb_complex*
|
||||
complex_ptr(mrb_state *mrb, mrb_value v)
|
||||
{
|
||||
struct mrb_complex *p;
|
||||
|
||||
p = DATA_GET_PTR(mrb, v, &mrb_complex_type, struct mrb_complex);
|
||||
if (!p) {
|
||||
mrb_raise(mrb, E_ARGUMENT_ERROR, "uninitialized complex");
|
||||
}
|
||||
return p;
|
||||
}
|
||||
#endif
|
||||
|
||||
static mrb_value
|
||||
complex_new(mrb_state *mrb, mrb_float real, mrb_float imaginary)
|
||||
{
|
||||
struct RClass *c = mrb_class_get(mrb, "Complex");
|
||||
struct mrb_complex *p;
|
||||
struct RBasic *comp = complex_alloc(mrb, c, &p);
|
||||
p->real = real;
|
||||
p->imaginary = imaginary;
|
||||
MRB_SET_FROZEN_FLAG(comp);
|
||||
|
||||
return mrb_obj_value(comp);
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
complex_real(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
struct mrb_complex *p = complex_ptr(mrb, self);
|
||||
return mrb_float_value(mrb, p->real);
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
complex_imaginary(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
struct mrb_complex *p = complex_ptr(mrb, self);
|
||||
return mrb_float_value(mrb, p->imaginary);
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
complex_s_rect(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
mrb_float real, imaginary = 0.0;
|
||||
|
||||
mrb_get_args(mrb, "f|f", &real, &imaginary);
|
||||
return complex_new(mrb, real, imaginary);
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
complex_to_f(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
struct mrb_complex *p = complex_ptr(mrb, self);
|
||||
|
||||
if (p->imaginary != 0) {
|
||||
mrb_raisef(mrb, E_RANGE_ERROR, "can't convert %v into Float", self);
|
||||
}
|
||||
|
||||
return mrb_float_value(mrb, p->real);
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
complex_to_i(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
struct mrb_complex *p = complex_ptr(mrb, self);
|
||||
|
||||
if (p->imaginary != 0) {
|
||||
mrb_raisef(mrb, E_RANGE_ERROR, "can't convert %v into Float", self);
|
||||
}
|
||||
return mrb_int_value(mrb, p->real);
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
complex_to_c(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
return self;
|
||||
}
|
||||
|
||||
/* Arithmetic on (significand, exponent) pairs avoids premature overflow in
|
||||
complex division */
|
||||
struct float_pair {
|
||||
mrb_float s;
|
||||
int x;
|
||||
};
|
||||
|
||||
static void
|
||||
add_pair(struct float_pair *s, struct float_pair const *a,
|
||||
struct float_pair const *b)
|
||||
{
|
||||
if (b->s == 0.0F) {
|
||||
*s = *a;
|
||||
} else if (a->s == 0.0F) {
|
||||
*s = *b;
|
||||
} else if (a->x >= b->x) {
|
||||
s->s = a->s + F(ldexp)(b->s, b->x - a->x);
|
||||
s->x = a->x;
|
||||
} else {
|
||||
s->s = F(ldexp)(a->s, a->x - b->x) + b->s;
|
||||
s->x = b->x;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
mul_pair(struct float_pair *p, struct float_pair const *a,
|
||||
struct float_pair const *b)
|
||||
{
|
||||
p->s = a->s * b->s;
|
||||
p->x = a->x + b->x;
|
||||
}
|
||||
|
||||
static void
|
||||
div_pair(struct float_pair *q, struct float_pair const *a,
|
||||
struct float_pair const *b)
|
||||
{
|
||||
q->s = a->s / b->s;
|
||||
q->x = a->x - b->x;
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
complex_div(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
mrb_value rhs = mrb_get_arg1(mrb);
|
||||
struct mrb_complex *a, *b;
|
||||
struct float_pair ar, ai, br, bi;
|
||||
struct float_pair br2, bi2;
|
||||
struct float_pair div;
|
||||
struct float_pair ar_br, ai_bi;
|
||||
struct float_pair ai_br, ar_bi;
|
||||
struct float_pair zr, zi;
|
||||
|
||||
a = complex_ptr(mrb, self);
|
||||
b = complex_ptr(mrb, rhs);
|
||||
|
||||
/* Split floating point components into significand and exponent */
|
||||
ar.s = F(frexp)(a->real, &ar.x);
|
||||
ai.s = F(frexp)(a->imaginary, &ai.x);
|
||||
br.s = F(frexp)(b->real, &br.x);
|
||||
bi.s = F(frexp)(b->imaginary, &bi.x);
|
||||
|
||||
/* Perform arithmetic on (significand, exponent) pairs to produce
|
||||
the result: */
|
||||
|
||||
/* the divisor */
|
||||
mul_pair(&br2, &br, &br);
|
||||
mul_pair(&bi2, &bi, &bi);
|
||||
add_pair(&div, &br2, &bi2);
|
||||
|
||||
/* real component */
|
||||
mul_pair(&ar_br, &ar, &br);
|
||||
mul_pair(&ai_bi, &ai, &bi);
|
||||
add_pair(&zr, &ar_br, &ai_bi);
|
||||
div_pair(&zr, &zr, &div);
|
||||
|
||||
/* imaginary component */
|
||||
mul_pair(&ai_br, &ai, &br);
|
||||
mul_pair(&ar_bi, &ar, &bi);
|
||||
ar_bi.s = -ar_bi.s;
|
||||
add_pair(&zi, &ai_br, &ar_bi);
|
||||
div_pair(&zi, &zi, &div);
|
||||
|
||||
/* assemble the result */
|
||||
return complex_new(mrb, F(ldexp)(zr.s, zr.x), F(ldexp)(zi.s, zi.x));
|
||||
}
|
||||
|
||||
void mrb_mruby_complex_gem_init(mrb_state *mrb)
|
||||
{
|
||||
struct RClass *comp;
|
||||
|
||||
#ifdef COMPLEX_USE_ISTRUCT
|
||||
mrb_assert(sizeof(struct mrb_complex) < ISTRUCT_DATA_SIZE);
|
||||
#endif
|
||||
comp = mrb_define_class(mrb, "Complex", mrb_class_get(mrb, "Numeric"));
|
||||
#ifdef COMPLEX_USE_ISTRUCT
|
||||
MRB_SET_INSTANCE_TT(comp, MRB_TT_ISTRUCT);
|
||||
#else
|
||||
MRB_SET_INSTANCE_TT(comp, MRB_TT_DATA);
|
||||
#endif
|
||||
mrb_undef_class_method(mrb, comp, "new");
|
||||
mrb_define_class_method(mrb, comp, "rectangular", complex_s_rect, MRB_ARGS_REQ(1)|MRB_ARGS_OPT(1));
|
||||
mrb_define_class_method(mrb, comp, "rect", complex_s_rect, MRB_ARGS_REQ(1)|MRB_ARGS_OPT(1));
|
||||
mrb_define_method(mrb, mrb->kernel_module, "Complex", complex_s_rect, MRB_ARGS_REQ(1)|MRB_ARGS_OPT(1));
|
||||
mrb_define_method(mrb, comp, "real", complex_real, MRB_ARGS_NONE());
|
||||
mrb_define_method(mrb, comp, "imaginary", complex_imaginary, MRB_ARGS_NONE());
|
||||
mrb_define_method(mrb, comp, "to_f", complex_to_f, MRB_ARGS_NONE());
|
||||
mrb_define_method(mrb, comp, "to_i", complex_to_i, MRB_ARGS_NONE());
|
||||
mrb_define_method(mrb, comp, "to_c", complex_to_c, MRB_ARGS_NONE());
|
||||
mrb_define_method(mrb, comp, "__div__", complex_div, MRB_ARGS_REQ(1));
|
||||
}
|
||||
|
||||
void
|
||||
mrb_mruby_complex_gem_final(mrb_state* mrb)
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
def assert_complex(real, exp)
|
||||
assert "assert_complex" do
|
||||
assert_float real.real, exp.real
|
||||
assert_float real.imaginary, exp.imaginary
|
||||
end
|
||||
end
|
||||
|
||||
assert 'Complex' do
|
||||
c = 123i
|
||||
assert_equal Complex, c.class
|
||||
assert_equal [c.real, c.imaginary], [0, 123]
|
||||
c = 123 + -1.23i
|
||||
assert_equal Complex, c.class
|
||||
assert_equal [c.real, c.imaginary], [123, -1.23]
|
||||
end
|
||||
|
||||
assert 'Complex::polar' do
|
||||
assert_complex Complex.polar(3, 0), (3 + 0i)
|
||||
assert_complex Complex.polar(3, Math::PI/2), (0 + 3i)
|
||||
assert_complex Complex.polar(3, Math::PI), (-3 + 0i)
|
||||
assert_complex Complex.polar(3, -Math::PI/2), (0 + -3i)
|
||||
end
|
||||
|
||||
assert 'Complex::rectangular' do
|
||||
assert_complex Complex.rectangular(1, 2), (1 + 2i)
|
||||
end
|
||||
|
||||
assert 'Complex#*' do
|
||||
assert_complex Complex(2, 3) * Complex(2, 3), (-5 + 12i)
|
||||
assert_complex Complex(900) * Complex(1), (900 + 0i)
|
||||
assert_complex Complex(-2, 9) * Complex(-9, 2), (0 - 85i)
|
||||
assert_complex Complex(9, 8) * 4, (36 + 32i)
|
||||
assert_complex Complex(20, 9) * 9.8, (196.0 + 88.2i)
|
||||
end
|
||||
|
||||
assert 'Complex#+' do
|
||||
assert_complex Complex(2, 3) + Complex(2, 3) , (4 + 6i)
|
||||
assert_complex Complex(900) + Complex(1) , (901 + 0i)
|
||||
assert_complex Complex(-2, 9) + Complex(-9, 2), (-11 + 11i)
|
||||
assert_complex Complex(9, 8) + 4 , (13 + 8i)
|
||||
assert_complex Complex(20, 9) + 9.8 , (29.8 + 9i)
|
||||
end
|
||||
|
||||
assert 'Complex#-' do
|
||||
assert_complex Complex(2, 3) - Complex(2, 3) , (0 + 0i)
|
||||
assert_complex Complex(900) - Complex(1) , (899 + 0i)
|
||||
assert_complex Complex(-2, 9) - Complex(-9, 2), (7 + 7i)
|
||||
assert_complex Complex(9, 8) - 4 , (5 + 8i)
|
||||
assert_complex Complex(20, 9) - 9.8 , (10.2 + 9i)
|
||||
end
|
||||
|
||||
assert 'Complex#-@' do
|
||||
assert_complex(-Complex(1, 2), (-1 - 2i))
|
||||
end
|
||||
|
||||
assert 'Complex#/' do
|
||||
assert_complex Complex(2, 3) / Complex(2, 3) , (1 + 0i)
|
||||
assert_complex Complex(900) / Complex(1) , (900 + 0i)
|
||||
assert_complex Complex(-2, 9) / Complex(-9, 2), ((36 / 85) - (77i / 85))
|
||||
assert_complex Complex(9, 8) / 4 , ((9 / 4) + 2i)
|
||||
assert_complex Complex(20, 9) / 9.8 , (2.0408163265306123 + 0.9183673469387754i)
|
||||
if 1e39.infinite? then
|
||||
# MRB_USE_FLOAT in effect
|
||||
ten = 1e21
|
||||
one = 1e20
|
||||
else
|
||||
ten = 1e201
|
||||
one = 1e200
|
||||
end
|
||||
assert_complex Complex(ten, ten) / Complex(one, one), Complex(10.0, 0.0)
|
||||
end
|
||||
|
||||
assert 'Complex#==' do
|
||||
assert_true Complex(2, 3) == Complex(2, 3)
|
||||
assert_true Complex(5) == 5
|
||||
assert_true Complex(0) == 0.0
|
||||
end
|
||||
|
||||
assert 'Complex#abs' do
|
||||
assert_float Complex(-1).abs, 1
|
||||
assert_float Complex(3.0, -4.0).abs, 5.0
|
||||
if 1e39.infinite? then
|
||||
# MRB_USE_FLOAT in effect
|
||||
exp = 125
|
||||
else
|
||||
exp = 1021
|
||||
end
|
||||
assert_true Complex(3.0*2.0**exp, 4.0*2.0**exp).abs.finite?
|
||||
assert_float Complex(3.0*2.0**exp, 4.0*2.0**exp).abs, 5.0*2.0**exp
|
||||
end
|
||||
|
||||
assert 'Complex#abs2' do
|
||||
assert_float Complex(-1).abs2, 1
|
||||
assert_float Complex(3.0, -4.0).abs2, 25.0
|
||||
end
|
||||
|
||||
assert 'Complex#arg' do
|
||||
assert_float Complex.polar(3, Math::PI/2).arg, 1.5707963267948966
|
||||
end
|
||||
|
||||
assert 'Complex#conjugate' do
|
||||
assert_complex Complex(1, 2).conjugate, (1 - 2i)
|
||||
end
|
||||
|
||||
assert 'Complex#fdiv' do
|
||||
assert_complex Complex(11, 22).fdiv(3), (3.6666666666666665 + 7.333333333333333i)
|
||||
end
|
||||
|
||||
assert 'Complex#imaginary' do
|
||||
assert_float Complex(7).imaginary , 0
|
||||
assert_float Complex(9, -4).imaginary, -4
|
||||
end
|
||||
|
||||
assert 'Complex#polar' do
|
||||
assert_equal Complex(1, 2).polar, [2.23606797749979, 1.1071487177940904]
|
||||
end
|
||||
|
||||
assert 'Complex#real' do
|
||||
assert_float Complex(7).real, 7
|
||||
assert_float Complex(9, -4).real, 9
|
||||
end
|
||||
|
||||
assert 'Complex#real?' do
|
||||
assert_false Complex(1).real?
|
||||
end
|
||||
|
||||
assert 'Complex::rectangular' do
|
||||
assert_equal Complex(1, 2).rectangular, [1, 2]
|
||||
end
|
||||
|
||||
assert 'Complex::to_c' do
|
||||
assert_equal Complex(1, 2).to_c, Complex(1, 2)
|
||||
end
|
||||
|
||||
assert 'Complex::to_f' do
|
||||
assert_float Complex(1, 0).to_f, 1.0
|
||||
assert_raise(RangeError) do
|
||||
Complex(1, 2).to_f
|
||||
end
|
||||
end
|
||||
|
||||
assert 'Complex::to_i' do
|
||||
assert_equal Complex(1, 0).to_i, 1
|
||||
assert_raise(RangeError) do
|
||||
Complex(1, 2).to_i
|
||||
end
|
||||
end
|
||||
|
||||
assert 'Complex#frozen?' do
|
||||
assert_predicate(1i, :frozen?)
|
||||
assert_predicate(Complex(2,3), :frozen?)
|
||||
assert_predicate(4+5i, :frozen?)
|
||||
end
|
||||
@@ -0,0 +1,6 @@
|
||||
MRuby::Gem::Specification.new('mruby-enum-chain') do |spec|
|
||||
spec.license = 'MIT'
|
||||
spec.author = 'mruby developers'
|
||||
spec.summary = 'Enumerator::Chain class'
|
||||
spec.add_dependency('mruby-enumerator', :core => 'mruby-enumerator')
|
||||
end
|
||||
@@ -0,0 +1,62 @@
|
||||
##
|
||||
# chain.rb Enumerator::Chain class
|
||||
# See Copyright Notice in mruby.h
|
||||
|
||||
module Enumerable
|
||||
def chain(*args)
|
||||
Enumerator::Chain.new(self, *args)
|
||||
end
|
||||
end
|
||||
|
||||
class Enumerator
|
||||
def +(other)
|
||||
Chain.new(self, other)
|
||||
end
|
||||
|
||||
class Chain
|
||||
include Enumerable
|
||||
|
||||
def initialize(*args)
|
||||
@enums = args.freeze
|
||||
@pos = -1
|
||||
end
|
||||
|
||||
def each(&block)
|
||||
return to_enum unless block
|
||||
|
||||
i = 0
|
||||
while i < @enums.size
|
||||
@pos = i
|
||||
@enums[i].each(&block)
|
||||
i += 1
|
||||
end
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
def size
|
||||
@enums.reduce(0) do |a, e|
|
||||
return nil unless e.respond_to?(:size)
|
||||
a + e.size
|
||||
end
|
||||
end
|
||||
|
||||
def rewind
|
||||
while 0 <= @pos && @pos < @enums.size
|
||||
e = @enums[@pos]
|
||||
e.rewind if e.respond_to?(:rewind)
|
||||
@pos -= 1
|
||||
end
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
def +(other)
|
||||
self.class.new(self, other)
|
||||
end
|
||||
|
||||
def inspect
|
||||
"#<#{self.class}: #{@enums.inspect}>"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,108 @@
|
||||
##
|
||||
# Enumerator::Chain test
|
||||
|
||||
assert("Enumerable#chain") do
|
||||
a = []
|
||||
b = {}
|
||||
c = Object.new # not has #each method
|
||||
|
||||
assert_kind_of Enumerator::Chain, a.chain
|
||||
assert_kind_of Enumerator::Chain, a.chain(b)
|
||||
assert_kind_of Enumerator::Chain, a.chain(b, c)
|
||||
assert_raise(NoMethodError) { c.chain }
|
||||
end
|
||||
|
||||
assert("Enumerator#+") do
|
||||
a = [].each
|
||||
b = {}.each
|
||||
c = Object.new # not has #each method
|
||||
|
||||
assert_kind_of Enumerator::Chain, a + b
|
||||
assert_kind_of Enumerator::Chain, a + c
|
||||
assert_kind_of Enumerator::Chain, b + a
|
||||
assert_kind_of Enumerator::Chain, b + c
|
||||
assert_raise(NoMethodError) { c + a }
|
||||
end
|
||||
|
||||
assert("Enumerator::Chain.new") do
|
||||
a = []
|
||||
b = {}
|
||||
c = Object.new # not has #each method
|
||||
|
||||
assert_kind_of Enumerator::Chain, Enumerator::Chain.new
|
||||
assert_kind_of Enumerator::Chain, Enumerator::Chain.new(a, a)
|
||||
assert_kind_of Enumerator::Chain, Enumerator::Chain.new(a, b)
|
||||
assert_kind_of Enumerator::Chain, Enumerator::Chain.new(a, c)
|
||||
assert_kind_of Enumerator::Chain, Enumerator::Chain.new(b, a)
|
||||
assert_kind_of Enumerator::Chain, Enumerator::Chain.new(b, b)
|
||||
assert_kind_of Enumerator::Chain, Enumerator::Chain.new(b, c)
|
||||
assert_kind_of Enumerator::Chain, Enumerator::Chain.new(c, a)
|
||||
end
|
||||
|
||||
assert("Enumerator::Chain#each") do
|
||||
a = [1, 2, 3]
|
||||
|
||||
aa = a.chain(a)
|
||||
assert_kind_of Enumerator, aa.each
|
||||
assert_equal [1, 2, 3, 1, 2, 3], aa.each.to_a
|
||||
|
||||
aa = a.chain(6..9)
|
||||
assert_kind_of Enumerator, aa.each
|
||||
assert_equal [1, 2, 3, 6, 7, 8, 9], aa.each.to_a
|
||||
|
||||
aa = a.chain((-3..-2).each_with_index, a)
|
||||
assert_kind_of Enumerator, aa.each
|
||||
assert_equal [1, 2, 3, [-3, 0], [-2, 1], 1, 2, 3], aa.each.to_a
|
||||
|
||||
aa = a.chain(Object.new)
|
||||
assert_kind_of Enumerator, aa.each
|
||||
assert_raise(NoMethodError) { aa.each.to_a }
|
||||
end
|
||||
|
||||
assert("Enumerator::Chain#size") do
|
||||
a = [1, 2, 3]
|
||||
|
||||
aa = a.chain(a)
|
||||
assert_equal 6, aa.size
|
||||
|
||||
aa = a.chain(3..4)
|
||||
assert_nil aa.size
|
||||
|
||||
aa = a.chain(3..4, a)
|
||||
assert_nil aa.size
|
||||
|
||||
aa = a.chain(Object.new)
|
||||
assert_nil aa.size
|
||||
end
|
||||
|
||||
assert("Enumerator::Chain#rewind") do
|
||||
rewound = nil
|
||||
e1 = [1, 2]
|
||||
e2 = (4..6)
|
||||
(class << e1; self end).define_method(:rewind) { rewound << self }
|
||||
(class << e2; self end).define_method(:rewind) { rewound << self }
|
||||
c = e1.chain(e2)
|
||||
|
||||
rewound = []
|
||||
c.rewind
|
||||
assert_equal [], rewound
|
||||
|
||||
rewound = []
|
||||
c.each{break c}.rewind
|
||||
assert_equal [e1], rewound
|
||||
|
||||
rewound = []
|
||||
c.each{}.rewind
|
||||
assert_equal [e2, e1], rewound
|
||||
end
|
||||
|
||||
assert("Enumerator::Chain#+") do
|
||||
a = [].chain
|
||||
b = {}.chain
|
||||
c = Object.new # not has #each method
|
||||
|
||||
assert_kind_of Enumerator::Chain, a + b
|
||||
assert_kind_of Enumerator::Chain, a + c
|
||||
assert_kind_of Enumerator::Chain, b + a
|
||||
assert_kind_of Enumerator::Chain, b + c
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
MRuby::Gem::Specification.new('mruby-enum-ext') do |spec|
|
||||
spec.license = 'MIT'
|
||||
spec.author = 'mruby developers'
|
||||
spec.summary = 'Enumerable module extension'
|
||||
end
|
||||
@@ -0,0 +1,859 @@
|
||||
##
|
||||
# Enumerable
|
||||
#
|
||||
module Enumerable
|
||||
##
|
||||
# call-seq:
|
||||
# enum.drop(n) -> array
|
||||
#
|
||||
# Drops first n elements from <i>enum</i>, and returns rest elements
|
||||
# in an array.
|
||||
#
|
||||
# a = [1, 2, 3, 4, 5, 0]
|
||||
# a.drop(3) #=> [4, 5, 0]
|
||||
|
||||
def drop(n)
|
||||
n = n.__to_int
|
||||
raise ArgumentError, "attempt to drop negative size" if n < 0
|
||||
|
||||
ary = []
|
||||
self.each {|*val| n == 0 ? ary << val.__svalue : n -= 1 }
|
||||
ary
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# enum.drop_while {|arr| block } -> array
|
||||
# enum.drop_while -> an_enumerator
|
||||
#
|
||||
# Drops elements up to, but not including, the first element for
|
||||
# which the block returns +nil+ or +false+ and returns an array
|
||||
# containing the remaining elements.
|
||||
#
|
||||
# If no block is given, an enumerator is returned instead.
|
||||
#
|
||||
# a = [1, 2, 3, 4, 5, 0]
|
||||
# a.drop_while {|i| i < 3 } #=> [3, 4, 5, 0]
|
||||
|
||||
def drop_while(&block)
|
||||
return to_enum :drop_while unless block
|
||||
|
||||
ary, state = [], false
|
||||
self.each do |*val|
|
||||
state = true if !state and !block.call(*val)
|
||||
ary << val.__svalue if state
|
||||
end
|
||||
ary
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# enum.take(n) -> array
|
||||
#
|
||||
# Returns first n elements from <i>enum</i>.
|
||||
#
|
||||
# a = [1, 2, 3, 4, 5, 0]
|
||||
# a.take(3) #=> [1, 2, 3]
|
||||
|
||||
def take(n)
|
||||
n = n.__to_int
|
||||
i = n.to_i
|
||||
raise ArgumentError, "attempt to take negative size" if i < 0
|
||||
ary = []
|
||||
return ary if i == 0
|
||||
self.each do |*val|
|
||||
ary << val.__svalue
|
||||
i -= 1
|
||||
break if i == 0
|
||||
end
|
||||
ary
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# enum.take_while {|arr| block } -> array
|
||||
# enum.take_while -> an_enumerator
|
||||
#
|
||||
# Passes elements to the block until the block returns +nil+ or +false+,
|
||||
# then stops iterating and returns an array of all prior elements.
|
||||
#
|
||||
# If no block is given, an enumerator is returned instead.
|
||||
#
|
||||
# a = [1, 2, 3, 4, 5, 0]
|
||||
# a.take_while {|i| i < 3 } #=> [1, 2]
|
||||
#
|
||||
def take_while(&block)
|
||||
return to_enum :take_while unless block
|
||||
|
||||
ary = []
|
||||
self.each do |*val|
|
||||
return ary unless block.call(*val)
|
||||
ary << val.__svalue
|
||||
end
|
||||
ary
|
||||
end
|
||||
|
||||
##
|
||||
# Iterates the given block for each array of consecutive <n>
|
||||
# elements.
|
||||
#
|
||||
# @return [nil]
|
||||
#
|
||||
# @example
|
||||
# (1..10).each_cons(3) {|a| p a}
|
||||
# # outputs below
|
||||
# [1, 2, 3]
|
||||
# [2, 3, 4]
|
||||
# [3, 4, 5]
|
||||
# [4, 5, 6]
|
||||
# [5, 6, 7]
|
||||
# [6, 7, 8]
|
||||
# [7, 8, 9]
|
||||
# [8, 9, 10]
|
||||
|
||||
def each_cons(n, &block)
|
||||
n = n.__to_int
|
||||
raise ArgumentError, "invalid size" if n <= 0
|
||||
|
||||
return to_enum(:each_cons,n) unless block
|
||||
ary = []
|
||||
n = n.to_i
|
||||
self.each do |*val|
|
||||
ary.shift if ary.size == n
|
||||
ary << val.__svalue
|
||||
block.call(ary.dup) if ary.size == n
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
##
|
||||
# Iterates the given block for each slice of <n> elements.
|
||||
#
|
||||
# @return [nil]
|
||||
#
|
||||
# @example
|
||||
# (1..10).each_slice(3) {|a| p a}
|
||||
# # outputs below
|
||||
# [1, 2, 3]
|
||||
# [4, 5, 6]
|
||||
# [7, 8, 9]
|
||||
# [10]
|
||||
|
||||
def each_slice(n, &block)
|
||||
n = n.__to_int
|
||||
raise ArgumentError, "invalid slice size" if n <= 0
|
||||
|
||||
return to_enum(:each_slice,n) unless block
|
||||
ary = []
|
||||
n = n.to_i
|
||||
self.each do |*val|
|
||||
ary << val.__svalue
|
||||
if ary.size == n
|
||||
block.call(ary)
|
||||
ary = []
|
||||
end
|
||||
end
|
||||
block.call(ary) unless ary.empty?
|
||||
nil
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# enum.group_by {| obj | block } -> a_hash
|
||||
# enum.group_by -> an_enumerator
|
||||
#
|
||||
# Returns a hash, which keys are evaluated result from the
|
||||
# block, and values are arrays of elements in <i>enum</i>
|
||||
# corresponding to the key.
|
||||
#
|
||||
# (1..6).group_by {|i| i%3} #=> {0=>[3, 6], 1=>[1, 4], 2=>[2, 5]}
|
||||
#
|
||||
def group_by(&block)
|
||||
return to_enum :group_by unless block
|
||||
|
||||
h = {}
|
||||
self.each do |*val|
|
||||
key = block.call(*val)
|
||||
sv = val.__svalue
|
||||
h.key?(key) ? (h[key] << sv) : (h[key] = [sv])
|
||||
end
|
||||
h
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# enum.sort_by { |obj| block } -> array
|
||||
# enum.sort_by -> an_enumerator
|
||||
#
|
||||
# Sorts <i>enum</i> using a set of keys generated by mapping the
|
||||
# values in <i>enum</i> through the given block.
|
||||
#
|
||||
# If no block is given, an enumerator is returned instead.
|
||||
|
||||
def sort_by(&block)
|
||||
return to_enum :sort_by unless block
|
||||
|
||||
ary = []
|
||||
orig = []
|
||||
self.each_with_index{|e, i|
|
||||
orig.push(e)
|
||||
ary.push([block.call(e), i])
|
||||
}
|
||||
if ary.size > 1
|
||||
ary.sort!
|
||||
end
|
||||
ary.collect{|e,i| orig[i]}
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# enum.first -> obj or nil
|
||||
# enum.first(n) -> an_array
|
||||
#
|
||||
# Returns the first element, or the first +n+ elements, of the enumerable.
|
||||
# If the enumerable is empty, the first form returns <code>nil</code>, and the
|
||||
# second form returns an empty array.
|
||||
def first(*args)
|
||||
case args.length
|
||||
when 0
|
||||
self.each do |*val|
|
||||
return val.__svalue
|
||||
end
|
||||
return nil
|
||||
when 1
|
||||
i = args[0].__to_int
|
||||
raise ArgumentError, "attempt to take negative size" if i < 0
|
||||
ary = []
|
||||
return ary if i == 0
|
||||
self.each do |*val|
|
||||
ary << val.__svalue
|
||||
i -= 1
|
||||
break if i == 0
|
||||
end
|
||||
ary
|
||||
else
|
||||
raise ArgumentError, "wrong number of arguments (given #{args.length}, expected 0..1)"
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# enum.count -> int
|
||||
# enum.count(item) -> int
|
||||
# enum.count { |obj| block } -> int
|
||||
#
|
||||
# Returns the number of items in +enum+ through enumeration.
|
||||
# If an argument is given, the number of items in +enum+ that
|
||||
# are equal to +item+ are counted. If a block is given, it
|
||||
# counts the number of elements yielding a true value.
|
||||
def count(v=NONE, &block)
|
||||
count = 0
|
||||
if block
|
||||
self.each do |*val|
|
||||
count += 1 if block.call(*val)
|
||||
end
|
||||
else
|
||||
if v == NONE
|
||||
self.each { count += 1 }
|
||||
else
|
||||
self.each do |*val|
|
||||
count += 1 if val.__svalue == v
|
||||
end
|
||||
end
|
||||
end
|
||||
count
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# enum.flat_map { |obj| block } -> array
|
||||
# enum.collect_concat { |obj| block } -> array
|
||||
# enum.flat_map -> an_enumerator
|
||||
# enum.collect_concat -> an_enumerator
|
||||
#
|
||||
# Returns a new array with the concatenated results of running
|
||||
# <em>block</em> once for every element in <i>enum</i>.
|
||||
#
|
||||
# If no block is given, an enumerator is returned instead.
|
||||
#
|
||||
# [1, 2, 3, 4].flat_map { |e| [e, -e] } #=> [1, -1, 2, -2, 3, -3, 4, -4]
|
||||
# [[1, 2], [3, 4]].flat_map { |e| e + [100] } #=> [1, 2, 100, 3, 4, 100]
|
||||
def flat_map(&block)
|
||||
return to_enum :flat_map unless block
|
||||
|
||||
ary = []
|
||||
self.each do |*e|
|
||||
e2 = block.call(*e)
|
||||
if e2.respond_to? :each
|
||||
e2.each {|e3| ary.push(e3) }
|
||||
else
|
||||
ary.push(e2)
|
||||
end
|
||||
end
|
||||
ary
|
||||
end
|
||||
alias collect_concat flat_map
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# enum.max_by {|obj| block } -> obj
|
||||
# enum.max_by -> an_enumerator
|
||||
#
|
||||
# Returns the object in <i>enum</i> that gives the maximum
|
||||
# value from the given block.
|
||||
#
|
||||
# If no block is given, an enumerator is returned instead.
|
||||
#
|
||||
# %w[albatross dog horse].max_by {|x| x.length } #=> "albatross"
|
||||
|
||||
def max_by(&block)
|
||||
return to_enum :max_by unless block
|
||||
|
||||
first = true
|
||||
max = nil
|
||||
max_cmp = nil
|
||||
|
||||
self.each do |*val|
|
||||
if first
|
||||
max = val.__svalue
|
||||
max_cmp = block.call(*val)
|
||||
first = false
|
||||
else
|
||||
if (cmp = block.call(*val)) > max_cmp
|
||||
max = val.__svalue
|
||||
max_cmp = cmp
|
||||
end
|
||||
end
|
||||
end
|
||||
max
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# enum.min_by {|obj| block } -> obj
|
||||
# enum.min_by -> an_enumerator
|
||||
#
|
||||
# Returns the object in <i>enum</i> that gives the minimum
|
||||
# value from the given block.
|
||||
#
|
||||
# If no block is given, an enumerator is returned instead.
|
||||
#
|
||||
# %w[albatross dog horse].min_by {|x| x.length } #=> "dog"
|
||||
|
||||
def min_by(&block)
|
||||
return to_enum :min_by unless block
|
||||
|
||||
first = true
|
||||
min = nil
|
||||
min_cmp = nil
|
||||
|
||||
self.each do |*val|
|
||||
if first
|
||||
min = val.__svalue
|
||||
min_cmp = block.call(*val)
|
||||
first = false
|
||||
else
|
||||
if (cmp = block.call(*val)) < min_cmp
|
||||
min = val.__svalue
|
||||
min_cmp = cmp
|
||||
end
|
||||
end
|
||||
end
|
||||
min
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# enum.minmax -> [min, max]
|
||||
# enum.minmax { |a, b| block } -> [min, max]
|
||||
#
|
||||
# Returns two elements array which contains the minimum and the
|
||||
# maximum value in the enumerable. The first form assumes all
|
||||
# objects implement <code>Comparable</code>; the second uses the
|
||||
# block to return <em>a <=> b</em>.
|
||||
#
|
||||
# a = %w(albatross dog horse)
|
||||
# a.minmax #=> ["albatross", "horse"]
|
||||
# a.minmax { |a, b| a.length <=> b.length } #=> ["dog", "albatross"]
|
||||
|
||||
def minmax(&block)
|
||||
max = nil
|
||||
min = nil
|
||||
first = true
|
||||
|
||||
self.each do |*val|
|
||||
if first
|
||||
val = val.__svalue
|
||||
max = val
|
||||
min = val
|
||||
first = false
|
||||
else
|
||||
val = val.__svalue
|
||||
if block
|
||||
max = val if block.call(val, max) > 0
|
||||
min = val if block.call(val, min) < 0
|
||||
else
|
||||
max = val if (val <=> max) > 0
|
||||
min = val if (val <=> min) < 0
|
||||
end
|
||||
end
|
||||
end
|
||||
[min, max]
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# enum.minmax_by { |obj| block } -> [min, max]
|
||||
# enum.minmax_by -> an_enumerator
|
||||
#
|
||||
# Returns a two element array containing the objects in
|
||||
# <i>enum</i> that correspond to the minimum and maximum values respectively
|
||||
# from the given block.
|
||||
#
|
||||
# If no block is given, an enumerator is returned instead.
|
||||
#
|
||||
# %w(albatross dog horse).minmax_by { |x| x.length } #=> ["dog", "albatross"]
|
||||
|
||||
def minmax_by(&block)
|
||||
return to_enum :minmax_by unless block
|
||||
|
||||
max = nil
|
||||
max_cmp = nil
|
||||
min = nil
|
||||
min_cmp = nil
|
||||
first = true
|
||||
|
||||
self.each do |*val|
|
||||
if first
|
||||
max = min = val.__svalue
|
||||
max_cmp = min_cmp = block.call(*val)
|
||||
first = false
|
||||
else
|
||||
if (cmp = block.call(*val)) > max_cmp
|
||||
max = val.__svalue
|
||||
max_cmp = cmp
|
||||
end
|
||||
if (cmp = block.call(*val)) < min_cmp
|
||||
min = val.__svalue
|
||||
min_cmp = cmp
|
||||
end
|
||||
end
|
||||
end
|
||||
[min, max]
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# enum.none? [{ |obj| block }] -> true or false
|
||||
# enum.none?(pattern) -> true or false
|
||||
#
|
||||
# Passes each element of the collection to the given block. The method
|
||||
# returns <code>true</code> if the block never returns <code>true</code>
|
||||
# for all elements. If the block is not given, <code>none?</code> will return
|
||||
# <code>true</code> only if none of the collection members is true.
|
||||
#
|
||||
# If a pattern is supplied instead, the method returns whether
|
||||
# <code>pattern === element</code> for none of the collection members.
|
||||
#
|
||||
# %w(ant bear cat).none? { |word| word.length == 5 } #=> true
|
||||
# %w(ant bear cat).none? { |word| word.length >= 4 } #=> false
|
||||
# %w{ant bear cat}.none?(/d/) #=> true
|
||||
# [1, 3.14, 42].none?(Float) #=> false
|
||||
# [].none? #=> true
|
||||
# [nil, false].none? #=> true
|
||||
# [nil, true].none? #=> false
|
||||
|
||||
def none?(pat=NONE, &block)
|
||||
if pat != NONE
|
||||
self.each do |*val|
|
||||
return false if pat === val.__svalue
|
||||
end
|
||||
elsif block
|
||||
self.each do |*val|
|
||||
return false if block.call(*val)
|
||||
end
|
||||
else
|
||||
self.each do |*val|
|
||||
return false if val.__svalue
|
||||
end
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# enum.one? [{ |obj| block }] -> true or false
|
||||
# enum.one?(pattern) -> true or false
|
||||
#
|
||||
# Passes each element of the collection to the given block. The method
|
||||
# returns <code>true</code> if the block returns <code>true</code>
|
||||
# exactly once. If the block is not given, <code>one?</code> will return
|
||||
# <code>true</code> only if exactly one of the collection members is
|
||||
# true.
|
||||
#
|
||||
# If a pattern is supplied instead, the method returns whether
|
||||
# <code>pattern === element</code> for exactly one collection member.
|
||||
#
|
||||
# %w(ant bear cat).one? { |word| word.length == 4 } #=> true
|
||||
# %w(ant bear cat).one? { |word| word.length > 4 } #=> false
|
||||
# %w(ant bear cat).one? { |word| word.length < 4 } #=> false
|
||||
# %w{ant bear cat}.one?(/t/) #=> false
|
||||
# [nil, true, 99].one? #=> false
|
||||
# [nil, true, false].one? #=> true
|
||||
# [ nil, true, 99 ].one?(Integer) #=> true
|
||||
# [].one? #=> false
|
||||
|
||||
def one?(pat=NONE, &block)
|
||||
count = 0
|
||||
if pat!=NONE
|
||||
self.each do |*val|
|
||||
count += 1 if pat === val.__svalue
|
||||
return false if count > 1
|
||||
end
|
||||
elsif block
|
||||
self.each do |*val|
|
||||
count += 1 if block.call(*val)
|
||||
return false if count > 1
|
||||
end
|
||||
else
|
||||
self.each do |*val|
|
||||
count += 1 if val.__svalue
|
||||
return false if count > 1
|
||||
end
|
||||
end
|
||||
|
||||
count == 1 ? true : false
|
||||
end
|
||||
|
||||
# ISO 15.3.2.2.1
|
||||
# call-seq:
|
||||
# enum.all? [{ |obj| block } ] -> true or false
|
||||
# enum.all?(pattern) -> true or false
|
||||
#
|
||||
# Passes each element of the collection to the given block. The method
|
||||
# returns <code>true</code> if the block never returns
|
||||
# <code>false</code> or <code>nil</code>. If the block is not given,
|
||||
# Ruby adds an implicit block of <code>{ |obj| obj }</code> which will
|
||||
# cause #all? to return +true+ when none of the collection members are
|
||||
# +false+ or +nil+.
|
||||
#
|
||||
# If a pattern is supplied instead, the method returns whether
|
||||
# <code>pattern === element</code> for every collection member.
|
||||
#
|
||||
# %w[ant bear cat].all? { |word| word.length >= 3 } #=> true
|
||||
# %w[ant bear cat].all? { |word| word.length >= 4 } #=> false
|
||||
# %w[ant bear cat].all?(/t/) #=> false
|
||||
# [1, 2i, 3.14].all?(Numeric) #=> true
|
||||
# [nil, true, 99].all? #=> false
|
||||
#
|
||||
def all?(pat=NONE, &block)
|
||||
if pat != NONE
|
||||
self.each{|*val| return false unless pat === val.__svalue}
|
||||
elsif block
|
||||
self.each{|*val| return false unless block.call(*val)}
|
||||
else
|
||||
self.each{|*val| return false unless val.__svalue}
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
# ISO 15.3.2.2.2
|
||||
# call-seq:
|
||||
# enum.any? [{ |obj| block }] -> true or false
|
||||
# enum.any?(pattern) -> true or false
|
||||
#
|
||||
# Passes each element of the collection to the given block. The method
|
||||
# returns <code>true</code> if the block ever returns a value other
|
||||
# than <code>false</code> or <code>nil</code>. If the block is not
|
||||
# given, Ruby adds an implicit block of <code>{ |obj| obj }</code> that
|
||||
# will cause #any? to return +true+ if at least one of the collection
|
||||
# members is not +false+ or +nil+.
|
||||
#
|
||||
# If a pattern is supplied instead, the method returns whether
|
||||
# <code>pattern === element</code> for any collection member.
|
||||
#
|
||||
# %w[ant bear cat].any? { |word| word.length >= 3 } #=> true
|
||||
# %w[ant bear cat].any? { |word| word.length >= 4 } #=> true
|
||||
# %w[ant bear cat].any?(/d/) #=> false
|
||||
# [nil, true, 99].any?(Integer) #=> true
|
||||
# [nil, true, 99].any? #=> true
|
||||
# [].any? #=> false
|
||||
#
|
||||
def any?(pat=NONE, &block)
|
||||
if pat != NONE
|
||||
self.each{|*val| return true if pat === val.__svalue}
|
||||
elsif block
|
||||
self.each{|*val| return true if block.call(*val)}
|
||||
else
|
||||
self.each{|*val| return true if val.__svalue}
|
||||
end
|
||||
false
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# enum.each_with_object(obj) { |(*args), memo_obj| ... } -> obj
|
||||
# enum.each_with_object(obj) -> an_enumerator
|
||||
#
|
||||
# Iterates the given block for each element with an arbitrary
|
||||
# object given, and returns the initially given object.
|
||||
#
|
||||
# If no block is given, returns an enumerator.
|
||||
#
|
||||
# (1..10).each_with_object([]) { |i, a| a << i*2 }
|
||||
# #=> [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
|
||||
#
|
||||
|
||||
def each_with_object(obj, &block)
|
||||
return to_enum(:each_with_object, obj) unless block
|
||||
|
||||
self.each {|*val| block.call(val.__svalue, obj) }
|
||||
obj
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# enum.reverse_each { |item| block } -> enum
|
||||
# enum.reverse_each -> an_enumerator
|
||||
#
|
||||
# Builds a temporary array and traverses that array in reverse order.
|
||||
#
|
||||
# If no block is given, an enumerator is returned instead.
|
||||
#
|
||||
# (1..3).reverse_each { |v| p v }
|
||||
#
|
||||
# produces:
|
||||
#
|
||||
# 3
|
||||
# 2
|
||||
# 1
|
||||
#
|
||||
|
||||
def reverse_each(&block)
|
||||
return to_enum :reverse_each unless block
|
||||
|
||||
ary = self.to_a
|
||||
i = ary.size - 1
|
||||
while i>=0
|
||||
block.call(ary[i])
|
||||
i -= 1
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# enum.cycle(n=nil) { |obj| block } -> nil
|
||||
# enum.cycle(n=nil) -> an_enumerator
|
||||
#
|
||||
# Calls <i>block</i> for each element of <i>enum</i> repeatedly _n_
|
||||
# times or forever if none or +nil+ is given. If a non-positive
|
||||
# number is given or the collection is empty, does nothing. Returns
|
||||
# +nil+ if the loop has finished without getting interrupted.
|
||||
#
|
||||
# Enumerable#cycle saves elements in an internal array so changes
|
||||
# to <i>enum</i> after the first pass have no effect.
|
||||
#
|
||||
# If no block is given, an enumerator is returned instead.
|
||||
#
|
||||
# a = ["a", "b", "c"]
|
||||
# a.cycle { |x| puts x } # print, a, b, c, a, b, c,.. forever.
|
||||
# a.cycle(2) { |x| puts x } # print, a, b, c, a, b, c.
|
||||
#
|
||||
|
||||
def cycle(nv = nil, &block)
|
||||
return to_enum(:cycle, nv) unless block
|
||||
|
||||
n = nil
|
||||
|
||||
if nv.nil?
|
||||
n = -1
|
||||
else
|
||||
n = nv.__to_int
|
||||
return nil if n <= 0
|
||||
end
|
||||
|
||||
ary = []
|
||||
each do |*i|
|
||||
ary.push(i)
|
||||
yield(*i)
|
||||
end
|
||||
return nil if ary.empty?
|
||||
|
||||
while n < 0 || 0 < (n -= 1)
|
||||
ary.each do |i|
|
||||
yield(*i)
|
||||
end
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# enum.find_index(value) -> int or nil
|
||||
# enum.find_index { |obj| block } -> int or nil
|
||||
# enum.find_index -> an_enumerator
|
||||
#
|
||||
# Compares each entry in <i>enum</i> with <em>value</em> or passes
|
||||
# to <em>block</em>. Returns the index for the first for which the
|
||||
# evaluated value is non-false. If no object matches, returns
|
||||
# <code>nil</code>
|
||||
#
|
||||
# If neither block nor argument is given, an enumerator is returned instead.
|
||||
#
|
||||
# (1..10).find_index { |i| i % 5 == 0 and i % 7 == 0 } #=> nil
|
||||
# (1..100).find_index { |i| i % 5 == 0 and i % 7 == 0 } #=> 34
|
||||
# (1..100).find_index(50) #=> 49
|
||||
#
|
||||
|
||||
def find_index(val=NONE, &block)
|
||||
return to_enum(:find_index, val) if !block && val == NONE
|
||||
|
||||
idx = 0
|
||||
if block
|
||||
self.each do |*e|
|
||||
return idx if block.call(*e)
|
||||
idx += 1
|
||||
end
|
||||
else
|
||||
self.each do |*e|
|
||||
return idx if e.__svalue == val
|
||||
idx += 1
|
||||
end
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# enum.zip(arg, ...) -> an_array_of_array
|
||||
# enum.zip(arg, ...) { |arr| block } -> nil
|
||||
#
|
||||
# Takes one element from <i>enum</i> and merges corresponding
|
||||
# elements from each <i>args</i>. This generates a sequence of
|
||||
# <em>n</em>-element arrays, where <em>n</em> is one more than the
|
||||
# count of arguments. The length of the resulting sequence will be
|
||||
# <code>enum#size</code>. If the size of any argument is less than
|
||||
# <code>enum#size</code>, <code>nil</code> values are supplied. If
|
||||
# a block is given, it is invoked for each output array, otherwise
|
||||
# an array of arrays is returned.
|
||||
#
|
||||
# a = [ 4, 5, 6 ]
|
||||
# b = [ 7, 8, 9 ]
|
||||
#
|
||||
# a.zip(b) #=> [[4, 7], [5, 8], [6, 9]]
|
||||
# [1, 2, 3].zip(a, b) #=> [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
|
||||
# [1, 2].zip(a, b) #=> [[1, 4, 7], [2, 5, 8]]
|
||||
# a.zip([1, 2], [8]) #=> [[4, 1, 8], [5, 2, nil], [6, nil, nil]]
|
||||
#
|
||||
# c = []
|
||||
# a.zip(b) { |x, y| c << x + y } #=> nil
|
||||
# c #=> [11, 13, 15]
|
||||
#
|
||||
|
||||
def zip(*arg, &block)
|
||||
result = block ? nil : []
|
||||
arg = arg.map do |a|
|
||||
unless a.respond_to?(:to_a)
|
||||
raise TypeError, "wrong argument type #{a.class} (must respond to :to_a)"
|
||||
end
|
||||
a.to_a
|
||||
end
|
||||
|
||||
i = 0
|
||||
self.each do |*val|
|
||||
a = []
|
||||
a.push(val.__svalue)
|
||||
idx = 0
|
||||
while idx < arg.size
|
||||
a.push(arg[idx][i])
|
||||
idx += 1
|
||||
end
|
||||
i += 1
|
||||
if result.nil?
|
||||
block.call(a)
|
||||
else
|
||||
result.push(a)
|
||||
end
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# enum.to_h -> hash
|
||||
#
|
||||
# Returns the result of interpreting <i>enum</i> as a list of
|
||||
# <tt>[key, value]</tt> pairs.
|
||||
#
|
||||
# %i[hello world].each_with_index.to_h
|
||||
# # => {:hello => 0, :world => 1}
|
||||
#
|
||||
|
||||
def to_h(&blk)
|
||||
h = {}
|
||||
if blk
|
||||
self.each do |v|
|
||||
v = blk.call(v)
|
||||
raise TypeError, "wrong element type #{v.class} (expected Array)" unless v.is_a? Array
|
||||
raise ArgumentError, "element has wrong array length (expected 2, was #{v.size})" if v.size != 2
|
||||
h[v[0]] = v[1]
|
||||
end
|
||||
else
|
||||
self.each do |*v|
|
||||
v = v.__svalue
|
||||
raise TypeError, "wrong element type #{v.class} (expected Array)" unless v.is_a? Array
|
||||
raise ArgumentError, "element has wrong array length (expected 2, was #{v.size})" if v.size != 2
|
||||
h[v[0]] = v[1]
|
||||
end
|
||||
end
|
||||
h
|
||||
end
|
||||
|
||||
def uniq(&block)
|
||||
hash = {}
|
||||
if block
|
||||
self.each do|*v|
|
||||
v = v.__svalue
|
||||
hash[block.call(v)] ||= v
|
||||
end
|
||||
else
|
||||
self.each do|*v|
|
||||
v = v.__svalue
|
||||
hash[v] ||= v
|
||||
end
|
||||
end
|
||||
hash.values
|
||||
end
|
||||
|
||||
def filter_map(&blk)
|
||||
return to_enum(:filter_map) unless blk
|
||||
|
||||
ary = []
|
||||
self.each do |x|
|
||||
x = blk.call(x)
|
||||
ary.push x if x
|
||||
end
|
||||
ary
|
||||
end
|
||||
|
||||
alias filter select
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# enum.tally -> a_hash
|
||||
#
|
||||
# Tallys the collection. Returns a hash where the keys are the
|
||||
# elements and the values are numbers of elements in the collection
|
||||
# that correspond to the key.
|
||||
#
|
||||
# ["a", "b", "c", "b"].tally #=> {"a"=>1, "b"=>2, "c"=>1}
|
||||
def tally
|
||||
hash = {}
|
||||
self.each do |x|
|
||||
hash[x] = (hash[x]||0)+1
|
||||
end
|
||||
hash
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,198 @@
|
||||
##
|
||||
# Enumerable(Ext) Test
|
||||
|
||||
assert("Enumerable#drop") do
|
||||
a = [1, 2, 3, 4, 5, 0]
|
||||
|
||||
assert_equal [4, 5, 0], a.drop(3)
|
||||
assert_equal [], a.drop(6)
|
||||
end
|
||||
|
||||
assert("Enumerable#drop_while") do
|
||||
a = [1, 2, 3, 4, 5, 0]
|
||||
assert_equal [3, 4, 5, 0], a.drop_while {|i| i < 3 }
|
||||
end
|
||||
|
||||
assert("Enumerable#take") do
|
||||
a = [1, 2, 3, 4, 5, 0]
|
||||
assert_equal [1, 2, 3], a.take(3)
|
||||
end
|
||||
|
||||
assert("Enumerable#take_while") do
|
||||
a = [1, 2, 3, 4, 5, 0]
|
||||
assert_equal [1, 2], a.take_while {|i| i < 3}
|
||||
end
|
||||
|
||||
assert("Enumerable#each_cons") do
|
||||
a = []
|
||||
b = (1..5).each_cons(3){|e| a << e}
|
||||
assert_equal [[1, 2, 3], [2, 3, 4], [3, 4, 5]], a
|
||||
assert_equal nil, b
|
||||
end
|
||||
|
||||
assert("Enumerable#each_slice") do
|
||||
a = []
|
||||
b = (1..10).each_slice(3){|e| a << e}
|
||||
assert_equal [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]], a
|
||||
assert_equal nil, b
|
||||
end
|
||||
|
||||
assert("Enumerable#group_by") do
|
||||
r = (1..6).group_by {|i| i % 3 }
|
||||
assert_equal [3, 6], r[0]
|
||||
assert_equal [1, 4], r[1]
|
||||
assert_equal [2, 5], r[2]
|
||||
end
|
||||
|
||||
assert("Enumerable#sort_by") do
|
||||
assert_equal ["car", "train", "bicycle"], %w{car bicycle train}.sort_by {|e| e.length}
|
||||
end
|
||||
|
||||
assert("Enumerable#first") do
|
||||
a = Object.new
|
||||
a.extend Enumerable
|
||||
def a.each
|
||||
yield 1
|
||||
yield 2
|
||||
yield 3
|
||||
end
|
||||
assert_equal 1, a.first
|
||||
assert_equal [1, 2], a.first(2)
|
||||
assert_equal [1, 2, 3], a.first(10)
|
||||
a = Object.new
|
||||
a.extend Enumerable
|
||||
def a.each
|
||||
end
|
||||
assert_nil a.first
|
||||
end
|
||||
|
||||
assert("Enumerable#count") do
|
||||
a = [1, 2, 4, 2]
|
||||
assert_equal 4, a.count
|
||||
assert_equal 2, a.count(2)
|
||||
assert_equal 3, a.count{|x| x % 2 == 0}
|
||||
end
|
||||
|
||||
assert("Enumerable#flat_map") do
|
||||
assert_equal [1, 2, 3, 4], [1, 2, 3, 4].flat_map { |e| e }
|
||||
assert_equal [1, -1, 2, -2, 3, -3, 4, -4], [1, 2, 3, 4].flat_map { |e| [e, -e] }
|
||||
assert_equal [1, 2, 100, 3, 4, 100], [[1, 2], [3, 4]].flat_map { |e| e + [100] }
|
||||
end
|
||||
|
||||
assert("Enumerable#max_by") do
|
||||
assert_equal "albatross", %w[albatross dog horse].max_by { |x| x.length }
|
||||
end
|
||||
|
||||
assert("Enumerable#min_by") do
|
||||
assert_equal "dog", %w[albatross dog horse].min_by { |x| x.length }
|
||||
end
|
||||
|
||||
assert("Enumerable#minmax") do
|
||||
a = %w(albatross dog horse)
|
||||
assert_equal ["albatross", "horse"], a.minmax
|
||||
assert_equal ["dog", "albatross"], a.minmax { |a, b| a.length <=> b.length }
|
||||
end
|
||||
|
||||
assert("Enumerable#minmax_by") do
|
||||
assert_equal ["dog", "albatross"], %w(albatross dog horse).minmax_by { |x| x.length }
|
||||
end
|
||||
|
||||
assert("Enumerable#none?") do
|
||||
assert_true %w(ant bear cat).none? { |word| word.length == 5 }
|
||||
assert_false %w(ant bear cat).none? { |word| word.length >= 4 }
|
||||
assert_false [1, 3.14, 42].none?(Float)
|
||||
assert_true [].none?
|
||||
assert_true [nil, false].none?
|
||||
assert_false [nil, true].none?
|
||||
end
|
||||
|
||||
assert("Enumerable#one?") do
|
||||
assert_true %w(ant bear cat).one? { |word| word.length == 4 }
|
||||
assert_false %w(ant bear cat).one? { |word| word.length > 4 }
|
||||
assert_false %w(ant bear cat).one? { |word| word.length < 4 }
|
||||
assert_true [1, 3.14, 42].one?(Float)
|
||||
assert_false [nil, true, 99].one?
|
||||
assert_true [nil, true, false].one?
|
||||
assert_true [ nil, true, 99 ].one?(Integer)
|
||||
assert_false [].one?
|
||||
end
|
||||
|
||||
assert("Enumerable#all? (enhancement)") do
|
||||
assert_false [1, 2, 3.14].all?(Integer)
|
||||
assert_true [1, 2, 3.14].all?(Numeric)
|
||||
end
|
||||
|
||||
assert("Enumerable#any? (enhancement)") do
|
||||
assert_false [1, 2, 3].all?(Float)
|
||||
assert_true [nil, true, 99].any?(Integer)
|
||||
end
|
||||
|
||||
assert("Enumerable#each_with_object") do
|
||||
assert_equal [2, 4, 6, 8, 10, 12, 14, 16, 18, 20], (1..10).each_with_object([]) { |i, a| a << i*2 }
|
||||
assert_raise(ArgumentError) { (1..10).each_with_object() { |i, a| a << i*2 } }
|
||||
end
|
||||
|
||||
assert("Enumerable#reverse_each") do
|
||||
r = (1..3)
|
||||
a = []
|
||||
assert_same r, r.reverse_each { |v| a << v }
|
||||
assert_equal [3, 2, 1], a
|
||||
end
|
||||
|
||||
assert("Enumerable#cycle") do
|
||||
a = []
|
||||
["a", "b", "c"].cycle(2) { |v| a << v }
|
||||
assert_equal ["a", "b", "c", "a", "b", "c"], a
|
||||
assert_raise(TypeError) { ["a", "b", "c"].cycle("a") { |v| a << v } }
|
||||
|
||||
empty = Class.new do
|
||||
include Enumerable
|
||||
def each
|
||||
end
|
||||
end
|
||||
assert_nil empty.new.cycle { break :nope }
|
||||
end
|
||||
|
||||
assert("Enumerable#find_index") do
|
||||
assert_nil (1..10).find_index { |i| i % 5 == 0 and i % 7 == 0 }
|
||||
assert_equal 34, (1..100).find_index { |i| i % 5 == 0 and i % 7 == 0 }
|
||||
assert_equal 49 ,(1..100).find_index(50)
|
||||
end
|
||||
|
||||
assert("Enumerable#zip") do
|
||||
a = [ 4, 5, 6 ]
|
||||
b = [ 7, 8, 9 ]
|
||||
assert_equal [[4, 7], [5, 8], [6, 9]], a.zip(b)
|
||||
assert_equal [[1, 4, 7], [2, 5, 8], [3, 6, 9]], [1, 2, 3].zip(a, b)
|
||||
assert_equal [[1, 4, 7], [2, 5, 8]], [1, 2].zip(a, b)
|
||||
assert_equal [[4, 1, 8], [5, 2, nil], [6, nil, nil]], a.zip([1, 2], [8])
|
||||
|
||||
ret = []
|
||||
assert_equal nil, a.zip([1, 2], [8]) { |i| ret << i }
|
||||
assert_equal [[4, 1, 8], [5, 2, nil], [6, nil, nil]], ret
|
||||
|
||||
assert_raise(TypeError) { [1].zip(1) }
|
||||
end
|
||||
|
||||
assert("Enumerable#to_h") do
|
||||
c = Class.new {
|
||||
include Enumerable
|
||||
def each
|
||||
yield [1,2]
|
||||
yield [3,4]
|
||||
end
|
||||
}
|
||||
h0 = {1=>2, 3=>4}
|
||||
h = c.new.to_h
|
||||
assert_equal Hash, h.class
|
||||
assert_equal h0, h
|
||||
assert_equal({1=>4,3=>8}, c.new.to_h{|k,v|[k,v*2]})
|
||||
end
|
||||
|
||||
assert("Enumerable#filter_map") do
|
||||
assert_equal [4, 8, 12, 16, 20], (1..10).filter_map{|i| i * 2 if i%2==0}
|
||||
end
|
||||
|
||||
assert("Enumerable#tally") do
|
||||
assert_equal({"a"=>1, "b"=>2, "c"=>1}, ["a", "b", "c", "b"].tally)
|
||||
end
|
||||
@@ -0,0 +1,7 @@
|
||||
MRuby::Gem::Specification.new('mruby-enum-lazy') do |spec|
|
||||
spec.license = 'MIT'
|
||||
spec.author = 'mruby developers'
|
||||
spec.summary = 'Enumerator::Lazy class'
|
||||
spec.add_dependency('mruby-enumerator', :core => 'mruby-enumerator')
|
||||
spec.add_dependency('mruby-enum-ext', :core => 'mruby-enum-ext')
|
||||
end
|
||||
@@ -0,0 +1,178 @@
|
||||
module Enumerable
|
||||
|
||||
# = Enumerable#lazy implementation
|
||||
#
|
||||
# Enumerable#lazy returns an instance of Enumerator::Lazy.
|
||||
# You can use it just like as normal Enumerable object,
|
||||
# except these methods act as 'lazy':
|
||||
#
|
||||
# - map collect
|
||||
# - select find_all
|
||||
# - reject
|
||||
# - grep
|
||||
# - drop
|
||||
# - drop_while
|
||||
# - take_while
|
||||
# - flat_map collect_concat
|
||||
# - zip
|
||||
def lazy
|
||||
Enumerator::Lazy.new(self)
|
||||
end
|
||||
end
|
||||
|
||||
class Enumerator
|
||||
# == Acknowledgements
|
||||
#
|
||||
# Based on https://github.com/yhara/enumerable-lazy
|
||||
# Inspired by https://github.com/antimon2/enumerable_lz
|
||||
# http://jp.rubyist.net/magazine/?0034-Enumerable_lz (ja)
|
||||
class Lazy < Enumerator
|
||||
def initialize(obj, &block)
|
||||
super(){|yielder|
|
||||
begin
|
||||
obj.each{|x|
|
||||
if block
|
||||
block.call(yielder, x)
|
||||
else
|
||||
yielder << x
|
||||
end
|
||||
}
|
||||
rescue StopIteration
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def to_enum(meth=:each, *args, &block)
|
||||
unless self.respond_to?(meth)
|
||||
raise ArgumentError, "undefined method #{meth}"
|
||||
end
|
||||
lz = Lazy.new(self, &block)
|
||||
lz.obj = self
|
||||
lz.meth = meth
|
||||
lz.args = args
|
||||
lz
|
||||
end
|
||||
alias enum_for to_enum
|
||||
|
||||
def map(&block)
|
||||
Lazy.new(self){|yielder, val|
|
||||
yielder << block.call(val)
|
||||
}
|
||||
end
|
||||
alias collect map
|
||||
|
||||
def select(&block)
|
||||
Lazy.new(self){|yielder, val|
|
||||
if block.call(val)
|
||||
yielder << val
|
||||
end
|
||||
}
|
||||
end
|
||||
alias find_all select
|
||||
|
||||
def reject(&block)
|
||||
Lazy.new(self){|yielder, val|
|
||||
unless block.call(val)
|
||||
yielder << val
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def grep(pattern)
|
||||
Lazy.new(self){|yielder, val|
|
||||
if pattern === val
|
||||
yielder << val
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def drop(n)
|
||||
dropped = 0
|
||||
Lazy.new(self){|yielder, val|
|
||||
if dropped < n
|
||||
dropped += 1
|
||||
else
|
||||
yielder << val
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def drop_while(&block)
|
||||
dropping = true
|
||||
Lazy.new(self){|yielder, val|
|
||||
if dropping
|
||||
if not block.call(val)
|
||||
yielder << val
|
||||
dropping = false
|
||||
end
|
||||
else
|
||||
yielder << val
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def take(n)
|
||||
if n == 0
|
||||
return Lazy.new(self){raise StopIteration}
|
||||
end
|
||||
taken = 0
|
||||
Lazy.new(self){|yielder, val|
|
||||
yielder << val
|
||||
taken += 1
|
||||
if taken >= n
|
||||
raise StopIteration
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def take_while(&block)
|
||||
Lazy.new(self){|yielder, val|
|
||||
if block.call(val)
|
||||
yielder << val
|
||||
else
|
||||
raise StopIteration
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def flat_map(&block)
|
||||
Lazy.new(self){|yielder, val|
|
||||
ary = block.call(val)
|
||||
# TODO: check ary is an Array
|
||||
ary.each{|x|
|
||||
yielder << x
|
||||
}
|
||||
}
|
||||
end
|
||||
alias collect_concat flat_map
|
||||
|
||||
def zip(*args, &block)
|
||||
enums = [self] + args
|
||||
Lazy.new(self){|yielder, val|
|
||||
ary = enums.map{|e| e.next}
|
||||
if block
|
||||
yielder << block.call(ary)
|
||||
else
|
||||
yielder << ary
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def uniq(&block)
|
||||
hash = {}
|
||||
Lazy.new(self){|yielder, val|
|
||||
if block
|
||||
v = block.call(val)
|
||||
else
|
||||
v = val
|
||||
end
|
||||
unless hash.include?(v)
|
||||
yielder << val
|
||||
hash[v] = val
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
alias force to_a
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,53 @@
|
||||
assert("Enumerator::Lazy") do
|
||||
a = [1, 2]
|
||||
assert_equal Enumerator::Lazy, a.lazy.class
|
||||
end
|
||||
|
||||
assert("Enumerator::Lazy laziness") do
|
||||
a = Object.new
|
||||
def a.each
|
||||
return to_enum :each unless block_given?
|
||||
self.b << 10
|
||||
yield 1
|
||||
self.b << 20
|
||||
yield 2
|
||||
self.b << 30
|
||||
yield 3
|
||||
self.b << 40
|
||||
yield 4
|
||||
self.b << 50
|
||||
yield 5
|
||||
end
|
||||
def a.b(b=nil)
|
||||
@b = b if b
|
||||
@b
|
||||
end
|
||||
|
||||
a.b([])
|
||||
assert_equal [1,2], a.each.lazy.take(2).force
|
||||
assert_equal [10,20], a.b
|
||||
|
||||
a.b([])
|
||||
assert_equal [2,4], a.each.lazy.select{|x|x%2==0}.take(2).force
|
||||
assert_equal [10,20,30,40], a.b
|
||||
|
||||
a.b([])
|
||||
assert_equal [1], a.each.lazy.take_while{|x|x<2}.take(1).force
|
||||
assert_equal [10], a.b
|
||||
|
||||
a.b([])
|
||||
assert_equal [1], a.each.lazy.take_while{|x|x<2}.take(4).force
|
||||
assert_equal [10,20], a.b
|
||||
end
|
||||
|
||||
assert("Enumerator::Lazy#to_enum") do
|
||||
lazy_enum = (0..Float::INFINITY).lazy.to_enum(:each_slice, 2)
|
||||
assert_kind_of Enumerator::Lazy, lazy_enum
|
||||
assert_equal [0*1, 2*3, 4*5, 6*7], lazy_enum.map { |a| a.first * a.last }.first(4)
|
||||
end
|
||||
|
||||
assert("Enumerator::Lazy#zip with cycle") do
|
||||
e1 = [1, 2, 3].cycle
|
||||
e2 = [:a, :b].cycle
|
||||
assert_equal [[1,:a],[2,:b],[3,:a]], e1.lazy.zip(e2).first(3)
|
||||
end
|
||||
@@ -0,0 +1,6 @@
|
||||
MRuby::Gem::Specification.new('mruby-enumerator') do |spec|
|
||||
spec.license = 'MIT'
|
||||
spec.author = 'mruby developers'
|
||||
spec.add_dependency('mruby-fiber', :core => 'mruby-fiber')
|
||||
spec.summary = 'Enumerator class'
|
||||
end
|
||||
@@ -0,0 +1,689 @@
|
||||
##
|
||||
# enumerator.rb Enumerator class
|
||||
# See Copyright Notice in mruby.h
|
||||
|
||||
##
|
||||
# A class which allows both internal and external iteration.
|
||||
#
|
||||
# An Enumerator can be created by the following methods.
|
||||
# - {Kernel#to_enum}
|
||||
# - {Kernel#enum_for}
|
||||
# - {Enumerator#initialize Enumerator.new}
|
||||
#
|
||||
# Most methods have two forms: a block form where the contents
|
||||
# are evaluated for each item in the enumeration, and a non-block form
|
||||
# which returns a new Enumerator wrapping the iteration.
|
||||
#
|
||||
# enumerator = %w(one two three).each
|
||||
# puts enumerator.class # => Enumerator
|
||||
#
|
||||
# enumerator.each_with_object("foo") do |item, obj|
|
||||
# puts "#{obj}: #{item}"
|
||||
# end
|
||||
#
|
||||
# # foo: one
|
||||
# # foo: two
|
||||
# # foo: three
|
||||
#
|
||||
# enum_with_obj = enumerator.each_with_object("foo")
|
||||
# puts enum_with_obj.class # => Enumerator
|
||||
#
|
||||
# enum_with_obj.each do |item, obj|
|
||||
# puts "#{obj}: #{item}"
|
||||
# end
|
||||
#
|
||||
# # foo: one
|
||||
# # foo: two
|
||||
# # foo: three
|
||||
#
|
||||
# This allows you to chain Enumerators together. For example, you
|
||||
# can map a list's elements to strings containing the index
|
||||
# and the element as a string via:
|
||||
#
|
||||
# puts %w[foo bar baz].map.with_index { |w, i| "#{i}:#{w}" }
|
||||
# # => ["0:foo", "1:bar", "2:baz"]
|
||||
#
|
||||
# An Enumerator can also be used as an external iterator.
|
||||
# For example, Enumerator#next returns the next value of the iterator
|
||||
# or raises StopIteration if the Enumerator is at the end.
|
||||
#
|
||||
# e = [1,2,3].each # returns an enumerator object.
|
||||
# puts e.next # => 1
|
||||
# puts e.next # => 2
|
||||
# puts e.next # => 3
|
||||
# puts e.next # raises StopIteration
|
||||
#
|
||||
# You can use this to implement an internal iterator as follows:
|
||||
#
|
||||
# def ext_each(e)
|
||||
# while true
|
||||
# begin
|
||||
# vs = e.next_values
|
||||
# rescue StopIteration
|
||||
# return $!.result
|
||||
# end
|
||||
# y = yield(*vs)
|
||||
# e.feed y
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# o = Object.new
|
||||
#
|
||||
# def o.each
|
||||
# puts yield
|
||||
# puts yield(1)
|
||||
# puts yield(1, 2)
|
||||
# 3
|
||||
# end
|
||||
#
|
||||
# # use o.each as an internal iterator directly.
|
||||
# puts o.each {|*x| puts x; [:b, *x] }
|
||||
# # => [], [:b], [1], [:b, 1], [1, 2], [:b, 1, 2], 3
|
||||
#
|
||||
# # convert o.each to an external iterator for
|
||||
# # implementing an internal iterator.
|
||||
# puts ext_each(o.to_enum) {|*x| puts x; [:b, *x] }
|
||||
# # => [], [:b], [1], [:b, 1], [1, 2], [:b, 1, 2], 3
|
||||
#
|
||||
class Enumerator
|
||||
include Enumerable
|
||||
|
||||
##
|
||||
# @overload initialize(obj, method = :each, *args)
|
||||
#
|
||||
# Creates a new Enumerator object, which can be used as an
|
||||
# Enumerable.
|
||||
#
|
||||
# In the first form, iteration is defined by the given block, in
|
||||
# which a "yielder" object, given as block parameter, can be used to
|
||||
# yield a value by calling the +yield+ method (aliased as +<<+):
|
||||
#
|
||||
# fib = Enumerator.new do |y|
|
||||
# a = b = 1
|
||||
# loop do
|
||||
# y << a
|
||||
# a, b = b, a + b
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# p fib.take(10) # => [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
|
||||
#
|
||||
# In the second, deprecated, form, a generated Enumerator iterates over the
|
||||
# given object using the given method with the given arguments passed. This
|
||||
# form is left only for internal use.
|
||||
#
|
||||
# Use of this form is discouraged. Use Kernel#enum_for or Kernel#to_enum
|
||||
# instead.
|
||||
def initialize(obj=NONE, meth=:each, *args, &block)
|
||||
if block
|
||||
obj = Generator.new(&block)
|
||||
elsif obj == NONE
|
||||
raise ArgumentError, "wrong number of arguments (given 0, expected 1+)"
|
||||
end
|
||||
|
||||
@obj = obj
|
||||
@meth = meth
|
||||
@args = args
|
||||
@fib = nil
|
||||
@dst = nil
|
||||
@lookahead = nil
|
||||
@feedvalue = nil
|
||||
@stop_exc = false
|
||||
end
|
||||
attr_accessor :obj, :meth, :args
|
||||
attr_reader :fib
|
||||
|
||||
def initialize_copy(obj)
|
||||
raise TypeError, "can't copy type #{obj.class}" unless obj.kind_of? Enumerator
|
||||
raise TypeError, "can't copy execution context" if obj.fib
|
||||
@obj = obj.obj
|
||||
@meth = obj.meth
|
||||
@args = obj.args
|
||||
@fib = nil
|
||||
@lookahead = nil
|
||||
@feedvalue = nil
|
||||
self
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# e.with_index(offset = 0) {|(*args), idx| ... }
|
||||
# e.with_index(offset = 0)
|
||||
#
|
||||
# Iterates the given block for each element with an index, which
|
||||
# starts from +offset+. If no block is given, returns a new Enumerator
|
||||
# that includes the index, starting from +offset+
|
||||
#
|
||||
# +offset+:: the starting index to use
|
||||
#
|
||||
def with_index(offset=0, &block)
|
||||
return to_enum :with_index, offset unless block
|
||||
|
||||
if offset.nil?
|
||||
offset = 0
|
||||
else
|
||||
offset = offset.__to_int
|
||||
end
|
||||
|
||||
n = offset - 1
|
||||
enumerator_block_call do |*i|
|
||||
n += 1
|
||||
block.call i.__svalue, n
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# e.each_with_index {|(*args), idx| ... }
|
||||
# e.each_with_index
|
||||
#
|
||||
# Same as Enumerator#with_index(0), i.e. there is no starting offset.
|
||||
#
|
||||
# If no block is given, a new Enumerator is returned that includes the index.
|
||||
#
|
||||
def each_with_index(&block)
|
||||
with_index(0, &block)
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# e.each_with_object(obj) {|(*args), obj| ... }
|
||||
# e.each_with_object(obj)
|
||||
# e.with_object(obj) {|(*args), obj| ... }
|
||||
# e.with_object(obj)
|
||||
#
|
||||
# Iterates the given block for each element with an arbitrary object, +obj+,
|
||||
# and returns +obj+
|
||||
#
|
||||
# If no block is given, returns a new Enumerator.
|
||||
#
|
||||
# @example
|
||||
# to_three = Enumerator.new do |y|
|
||||
# 3.times do |x|
|
||||
# y << x
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# to_three_with_string = to_three.with_object("foo")
|
||||
# to_three_with_string.each do |x,string|
|
||||
# puts "#{string}: #{x}"
|
||||
# end
|
||||
#
|
||||
# # => foo:0
|
||||
# # => foo:1
|
||||
# # => foo:2
|
||||
#
|
||||
def with_object(object, &block)
|
||||
return to_enum(:with_object, object) unless block
|
||||
|
||||
enumerator_block_call do |i|
|
||||
block.call [i,object]
|
||||
end
|
||||
object
|
||||
end
|
||||
|
||||
def inspect
|
||||
if @args && @args.size > 0
|
||||
args = @args.join(", ")
|
||||
"#<#{self.class}: #{@obj.inspect}:#{@meth}(#{args})>"
|
||||
else
|
||||
"#<#{self.class}: #{@obj.inspect}:#{@meth}>"
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# enum.each { |elm| block } -> obj
|
||||
# enum.each -> enum
|
||||
# enum.each(*appending_args) { |elm| block } -> obj
|
||||
# enum.each(*appending_args) -> an_enumerator
|
||||
#
|
||||
# Iterates over the block according to how this Enumerator was constructed.
|
||||
# If no block and no arguments are given, returns self.
|
||||
#
|
||||
# === Examples
|
||||
#
|
||||
# Array.new(3) #=> [nil, nil, nil]
|
||||
# Array.new(3) { |i| i } #=> [0, 1, 2]
|
||||
# Array.to_enum(:new, 3).to_a #=> [0, 1, 2]
|
||||
# Array.to_enum(:new).each(3).to_a #=> [0, 1, 2]
|
||||
#
|
||||
# obj = Object.new
|
||||
#
|
||||
# def obj.each_arg(a, b=:b, *rest)
|
||||
# yield a
|
||||
# yield b
|
||||
# yield rest
|
||||
# :method_returned
|
||||
# end
|
||||
#
|
||||
# enum = obj.to_enum :each_arg, :a, :x
|
||||
#
|
||||
# enum.each.to_a #=> [:a, :x, []]
|
||||
# enum.each.equal?(enum) #=> true
|
||||
# enum.each { |elm| elm } #=> :method_returned
|
||||
#
|
||||
# enum.each(:y, :z).to_a #=> [:a, :x, [:y, :z]]
|
||||
# enum.each(:y, :z).equal?(enum) #=> false
|
||||
# enum.each(:y, :z) { |elm| elm } #=> :method_returned
|
||||
#
|
||||
def each(*argv, &block)
|
||||
obj = self
|
||||
if 0 < argv.length
|
||||
obj = self.dup
|
||||
args = obj.args
|
||||
if !args.empty?
|
||||
args = args.dup
|
||||
args.concat argv
|
||||
else
|
||||
args = argv.dup
|
||||
end
|
||||
obj.args = args
|
||||
end
|
||||
return obj unless block
|
||||
enumerator_block_call(&block)
|
||||
end
|
||||
|
||||
def enumerator_block_call(&block)
|
||||
@obj.__send__ @meth, *@args, &block
|
||||
end
|
||||
private :enumerator_block_call
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# e.next -> object
|
||||
#
|
||||
# Returns the next object in the enumerator, and move the internal position
|
||||
# forward. When the position reached at the end, StopIteration is raised.
|
||||
#
|
||||
# === Example
|
||||
#
|
||||
# a = [1,2,3]
|
||||
# e = a.to_enum
|
||||
# p e.next #=> 1
|
||||
# p e.next #=> 2
|
||||
# p e.next #=> 3
|
||||
# p e.next #raises StopIteration
|
||||
#
|
||||
# Note that enumeration sequence by +next+ does not affect other non-external
|
||||
# enumeration methods, unless the underlying iteration methods itself has
|
||||
# side-effect
|
||||
#
|
||||
def next
|
||||
next_values.__svalue
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# e.next_values -> array
|
||||
#
|
||||
# Returns the next object as an array in the enumerator, and move the
|
||||
# internal position forward. When the position reached at the end,
|
||||
# StopIteration is raised.
|
||||
#
|
||||
# This method can be used to distinguish <code>yield</code> and <code>yield
|
||||
# nil</code>.
|
||||
#
|
||||
# === Example
|
||||
#
|
||||
# o = Object.new
|
||||
# def o.each
|
||||
# yield
|
||||
# yield 1
|
||||
# yield 1, 2
|
||||
# yield nil
|
||||
# yield [1, 2]
|
||||
# end
|
||||
# e = o.to_enum
|
||||
# p e.next_values
|
||||
# p e.next_values
|
||||
# p e.next_values
|
||||
# p e.next_values
|
||||
# p e.next_values
|
||||
# e = o.to_enum
|
||||
# p e.next
|
||||
# p e.next
|
||||
# p e.next
|
||||
# p e.next
|
||||
# p e.next
|
||||
#
|
||||
# ## yield args next_values next
|
||||
# # yield [] nil
|
||||
# # yield 1 [1] 1
|
||||
# # yield 1, 2 [1, 2] [1, 2]
|
||||
# # yield nil [nil] nil
|
||||
# # yield [1, 2] [[1, 2]] [1, 2]
|
||||
#
|
||||
# Note that +next_values+ does not affect other non-external enumeration
|
||||
# methods unless underlying iteration method itself has side-effect
|
||||
#
|
||||
def next_values
|
||||
if @lookahead
|
||||
vs = @lookahead
|
||||
@lookahead = nil
|
||||
return vs
|
||||
end
|
||||
raise @stop_exc if @stop_exc
|
||||
|
||||
curr = Fiber.current
|
||||
|
||||
if !@fib || !@fib.alive?
|
||||
@dst = curr
|
||||
@fib = Fiber.new do
|
||||
result = each do |*args|
|
||||
feedvalue = nil
|
||||
Fiber.yield args
|
||||
if @feedvalue
|
||||
feedvalue = @feedvalue
|
||||
@feedvalue = nil
|
||||
end
|
||||
feedvalue
|
||||
end
|
||||
@stop_exc = StopIteration.new "iteration reached an end"
|
||||
@stop_exc.result = result
|
||||
Fiber.yield nil
|
||||
end
|
||||
@lookahead = nil
|
||||
end
|
||||
|
||||
vs = @fib.resume curr
|
||||
if @stop_exc
|
||||
@fib = nil
|
||||
@dst = nil
|
||||
@lookahead = nil
|
||||
@feedvalue = nil
|
||||
raise @stop_exc
|
||||
end
|
||||
vs
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# e.peek -> object
|
||||
#
|
||||
# Returns the next object in the enumerator, but doesn't move the internal
|
||||
# position forward. If the position is already at the end, StopIteration
|
||||
# is raised.
|
||||
#
|
||||
# === Example
|
||||
#
|
||||
# a = [1,2,3]
|
||||
# e = a.to_enum
|
||||
# p e.next #=> 1
|
||||
# p e.peek #=> 2
|
||||
# p e.peek #=> 2
|
||||
# p e.peek #=> 2
|
||||
# p e.next #=> 2
|
||||
# p e.next #=> 3
|
||||
# p e.next #raises StopIteration
|
||||
#
|
||||
def peek
|
||||
peek_values.__svalue
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# e.peek_values -> array
|
||||
#
|
||||
# Returns the next object as an array, similar to Enumerator#next_values, but
|
||||
# doesn't move the internal position forward. If the position is already at
|
||||
# the end, StopIteration is raised.
|
||||
#
|
||||
# === Example
|
||||
#
|
||||
# o = Object.new
|
||||
# def o.each
|
||||
# yield
|
||||
# yield 1
|
||||
# yield 1, 2
|
||||
# end
|
||||
# e = o.to_enum
|
||||
# p e.peek_values #=> []
|
||||
# e.next
|
||||
# p e.peek_values #=> [1]
|
||||
# p e.peek_values #=> [1]
|
||||
# e.next
|
||||
# p e.peek_values #=> [1, 2]
|
||||
# e.next
|
||||
# p e.peek_values # raises StopIteration
|
||||
#
|
||||
def peek_values
|
||||
if @lookahead.nil?
|
||||
@lookahead = next_values
|
||||
end
|
||||
@lookahead.dup
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# e.rewind -> e
|
||||
#
|
||||
# Rewinds the enumeration sequence to the beginning.
|
||||
#
|
||||
# If the enclosed object responds to a "rewind" method, it is called.
|
||||
#
|
||||
def rewind
|
||||
@obj.rewind if @obj.respond_to? :rewind
|
||||
@fib = nil
|
||||
@dst = nil
|
||||
@lookahead = nil
|
||||
@feedvalue = nil
|
||||
@stop_exc = false
|
||||
self
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# e.feed obj -> nil
|
||||
#
|
||||
# Sets the value to be returned by the next yield inside +e+.
|
||||
#
|
||||
# If the value is not set, the yield returns nil.
|
||||
#
|
||||
# This value is cleared after being yielded.
|
||||
#
|
||||
# # Array#map passes the array's elements to "yield" and collects the
|
||||
# # results of "yield" as an array.
|
||||
# # Following example shows that "next" returns the passed elements and
|
||||
# # values passed to "feed" are collected as an array which can be
|
||||
# # obtained by StopIteration#result.
|
||||
# e = [1,2,3].map
|
||||
# p e.next #=> 1
|
||||
# e.feed "a"
|
||||
# p e.next #=> 2
|
||||
# e.feed "b"
|
||||
# p e.next #=> 3
|
||||
# e.feed "c"
|
||||
# begin
|
||||
# e.next
|
||||
# rescue StopIteration
|
||||
# p $!.result #=> ["a", "b", "c"]
|
||||
# end
|
||||
#
|
||||
# o = Object.new
|
||||
# def o.each
|
||||
# x = yield # (2) blocks
|
||||
# p x # (5) => "foo"
|
||||
# x = yield # (6) blocks
|
||||
# p x # (8) => nil
|
||||
# x = yield # (9) blocks
|
||||
# p x # not reached w/o another e.next
|
||||
# end
|
||||
#
|
||||
# e = o.to_enum
|
||||
# e.next # (1)
|
||||
# e.feed "foo" # (3)
|
||||
# e.next # (4)
|
||||
# e.next # (7)
|
||||
# # (10)
|
||||
#
|
||||
def feed(value)
|
||||
raise TypeError, "feed value already set" if @feedvalue
|
||||
@feedvalue = value
|
||||
nil
|
||||
end
|
||||
|
||||
# just for internal
|
||||
class Generator
|
||||
include Enumerable
|
||||
def initialize(&block)
|
||||
raise TypeError, "wrong argument type #{self.class} (expected Proc)" unless block.kind_of? Proc
|
||||
|
||||
@proc = block
|
||||
end
|
||||
|
||||
def each(*args, &block)
|
||||
args.unshift Yielder.new(&block)
|
||||
@proc.call(*args)
|
||||
end
|
||||
end
|
||||
|
||||
# just for internal
|
||||
class Yielder
|
||||
def initialize(&block)
|
||||
raise LocalJumpError, "no block given" unless block
|
||||
|
||||
@proc = block
|
||||
end
|
||||
|
||||
def yield(*args)
|
||||
@proc.call(*args)
|
||||
end
|
||||
|
||||
def << *args
|
||||
self.yield(*args)
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# Enumerator.produce(initial = nil) { |val| } -> enumerator
|
||||
#
|
||||
# Creates an infinite enumerator from any block, just called over and
|
||||
# over. Result of the previous iteration is passed to the next one.
|
||||
# If +initial+ is provided, it is passed to the first iteration, and
|
||||
# becomes the first element of the enumerator; if it is not provided,
|
||||
# first iteration receives +nil+, and its result becomes first
|
||||
# element of the iterator.
|
||||
#
|
||||
# Raising StopIteration from the block stops an iteration.
|
||||
#
|
||||
# Examples of usage:
|
||||
#
|
||||
# Enumerator.produce(1, &:succ) # => enumerator of 1, 2, 3, 4, ....
|
||||
#
|
||||
# Enumerator.produce { rand(10) } # => infinite random number sequence
|
||||
#
|
||||
# ancestors = Enumerator.produce(node) { |prev| node = prev.parent or raise StopIteration }
|
||||
# enclosing_section = ancestors.find { |n| n.type == :section }
|
||||
def Enumerator.produce(init=NONE, &block)
|
||||
raise ArgumentError, "no block given" if block.nil?
|
||||
Enumerator.new do |y|
|
||||
if init == NONE
|
||||
val = nil
|
||||
else
|
||||
val = init
|
||||
y.yield(val)
|
||||
end
|
||||
begin
|
||||
while true
|
||||
y.yield(val = block.call(val))
|
||||
end
|
||||
rescue StopIteration
|
||||
# do nothing
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module Kernel
|
||||
##
|
||||
# call-seq:
|
||||
# obj.to_enum(method = :each, *args) -> enum
|
||||
# obj.enum_for(method = :each, *args) -> enum
|
||||
#
|
||||
# Creates a new Enumerator which will enumerate by calling +method+ on
|
||||
# +obj+, passing +args+ if any.
|
||||
#
|
||||
# === Examples
|
||||
#
|
||||
# str = "xyz"
|
||||
#
|
||||
# enum = str.enum_for(:each_byte)
|
||||
# enum.each { |b| puts b }
|
||||
# # => 120
|
||||
# # => 121
|
||||
# # => 122
|
||||
#
|
||||
# # protect an array from being modified by some_method
|
||||
# a = [1, 2, 3]
|
||||
# some_method(a.to_enum)
|
||||
#
|
||||
# It is typical to call to_enum when defining methods for
|
||||
# a generic Enumerable, in case no block is passed.
|
||||
#
|
||||
# Here is such an example with parameter passing:
|
||||
#
|
||||
# module Enumerable
|
||||
# # a generic method to repeat the values of any enumerable
|
||||
# def repeat(n)
|
||||
# raise ArgumentError, "#{n} is negative!" if n < 0
|
||||
# unless block_given?
|
||||
# return to_enum(__method__, n) # __method__ is :repeat here
|
||||
# end
|
||||
# each do |*val|
|
||||
# n.times { yield *val }
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# %i[hello world].repeat(2) { |w| puts w }
|
||||
# # => Prints 'hello', 'hello', 'world', 'world'
|
||||
# enum = (1..14).repeat(3)
|
||||
# # => returns an Enumerator when called without a block
|
||||
# enum.first(4) # => [1, 1, 1, 2]
|
||||
#
|
||||
def to_enum(meth=:each, *args)
|
||||
Enumerator.new self, meth, *args
|
||||
end
|
||||
alias enum_for to_enum
|
||||
end
|
||||
|
||||
module Enumerable
|
||||
# use Enumerator to use infinite sequence
|
||||
def zip(*args, &block)
|
||||
args = args.map do |a|
|
||||
if a.respond_to?(:each)
|
||||
a.to_enum(:each)
|
||||
else
|
||||
raise TypeError, "wrong argument type #{a.class} (must respond to :each)"
|
||||
end
|
||||
end
|
||||
|
||||
result = block ? nil : []
|
||||
|
||||
each do |*val|
|
||||
tmp = [val.__svalue]
|
||||
args.each do |arg|
|
||||
v = if arg.nil?
|
||||
nil
|
||||
else
|
||||
begin
|
||||
arg.next
|
||||
rescue StopIteration
|
||||
nil
|
||||
end
|
||||
end
|
||||
tmp.push(v)
|
||||
end
|
||||
if result.nil?
|
||||
block.call(tmp)
|
||||
else
|
||||
result.push(tmp)
|
||||
end
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,600 @@
|
||||
@obj = Object.new
|
||||
class << @obj
|
||||
include Enumerable
|
||||
def foo *a
|
||||
a.each { |x| yield x }
|
||||
end
|
||||
end
|
||||
|
||||
def assert_take(exp, enumerator)
|
||||
result = []
|
||||
n = exp.size
|
||||
enumerator.each do |v|
|
||||
result << v
|
||||
n -= 1
|
||||
break if n == 0
|
||||
end if n > 0
|
||||
assert_equal exp, result
|
||||
end
|
||||
|
||||
assert 'Enumerator.class' do
|
||||
assert_equal Class, Enumerator.class
|
||||
end
|
||||
|
||||
assert 'Enumerator.superclass' do
|
||||
assert_equal Object, Enumerator.superclass
|
||||
end
|
||||
|
||||
assert 'Enumerator.new' do
|
||||
assert_equal [0,1,2], 3.times.map{|i| i}.sort
|
||||
assert_equal [:x,:y,:z], [:x,:y,:z].each.map{|i| i}.sort
|
||||
assert_equal [[:x,1],[:y,2]], {x:1, y:2}.each.map{|i| i}.sort
|
||||
assert_equal [1,2,3], @obj.to_enum(:foo, 1,2,3).to_a
|
||||
assert_take [1,2,3], Enumerator.new { |y| i = 0; loop { y << (i += 1) } }
|
||||
assert_raise(ArgumentError) { Enumerator.new }
|
||||
|
||||
# examples
|
||||
fib = Enumerator.new do |y|
|
||||
a = b = 1
|
||||
loop do
|
||||
y << a
|
||||
a, b = b, a + b
|
||||
end
|
||||
end
|
||||
assert_take [1,1,2,3,5,8,13,21,34,55], fib
|
||||
end
|
||||
|
||||
assert 'Enumerator#initialize_copy' do
|
||||
assert_equal [1, 2, 3], @obj.to_enum(:foo, 1, 2, 3).dup.to_a
|
||||
e = @obj.to_enum :foo, 1, 2, 3
|
||||
assert_nothing_raised { assert_equal(1, e.next) }
|
||||
assert_raise(TypeError) { e.dup }
|
||||
|
||||
e = Enumerator.new { |y| i = 0; loop { y << (i += 1) } }.dup
|
||||
assert_nothing_raised { assert_equal(1, e.next) }
|
||||
assert_raise(TypeError) { e.dup }
|
||||
end
|
||||
|
||||
assert 'Enumerator#with_index' do
|
||||
assert_equal([[1,0],[2,1],[3,2]], @obj.to_enum(:foo, 1, 2, 3).with_index.to_a)
|
||||
assert_equal([[1,5],[2,6],[3,7]], @obj.to_enum(:foo, 1, 2, 3).with_index(5).to_a)
|
||||
a = []
|
||||
@obj.to_enum(:foo, 1, 2, 3).with_index(10).with_index(20) { |*i| a << i }
|
||||
assert_equal [[[1, 10], 20], [[2, 11], 21], [[3, 12], 22]], a
|
||||
end
|
||||
|
||||
assert 'Enumerator#with_index string offset' do
|
||||
assert_raise(TypeError){ @obj.to_enum(:foo, 1, 2, 3).with_index('1').to_a }
|
||||
end
|
||||
|
||||
assert 'Enumerator#each_with_index' do
|
||||
assert_equal([[1,0],[2,1],[3,2]], @obj.to_enum(:foo, 1, 2, 3).each_with_index.to_a)
|
||||
a = []
|
||||
@obj.to_enum(:foo, 1, 2, 3).each_with_index {|*i| a << i}
|
||||
assert_equal([[1, 0], [2, 1], [3, 2]], a)
|
||||
end
|
||||
|
||||
assert 'Enumerator#with_object' do
|
||||
obj = [0, 1]
|
||||
ret = (1..10).each.with_object(obj) {|i, memo|
|
||||
memo[0] += i
|
||||
memo[1] *= i
|
||||
}
|
||||
assert_true(obj.equal?(ret))
|
||||
assert_equal([55, 3628800], ret)
|
||||
end
|
||||
|
||||
assert 'Enumerator#with_object arguments' do
|
||||
to_three = Enumerator.new do |y|
|
||||
3.times do |x|
|
||||
y << x
|
||||
end
|
||||
end
|
||||
|
||||
a = []
|
||||
to_three_with_string = to_three.with_object("foo")
|
||||
to_three_with_string.each do |x,string|
|
||||
a << "#{string}:#{x}"
|
||||
end
|
||||
assert_equal ["foo:0","foo:1","foo:2"], a
|
||||
end
|
||||
|
||||
assert 'Enumerator#inspect' do
|
||||
e = (0..10).each
|
||||
assert_equal('#<Enumerator: 0..10:each>', e.inspect)
|
||||
e = 'FooObject'.enum_for(:foo, 1)
|
||||
assert_equal('#<Enumerator: "FooObject":foo(1)>', e.inspect)
|
||||
e = 'FooObject'.enum_for(:foo, 1, 2, 3)
|
||||
assert_equal('#<Enumerator: "FooObject":foo(1, 2, 3)>', e.inspect)
|
||||
e = nil.enum_for(:to_s)
|
||||
assert_equal('#<Enumerator: nil:to_s>', e.inspect)
|
||||
end
|
||||
|
||||
assert 'Enumerator#each' do
|
||||
o = Object.new
|
||||
def o.each(ary)
|
||||
ary << 1
|
||||
yield
|
||||
end
|
||||
ary = []
|
||||
e = o.to_enum.each(ary)
|
||||
e.next
|
||||
assert_equal([1], ary)
|
||||
end
|
||||
|
||||
assert 'Enumerator#each arguments' do
|
||||
obj = Object.new
|
||||
|
||||
def obj.each_arg(a, b=:b, *rest)
|
||||
yield a
|
||||
yield b
|
||||
yield rest
|
||||
:method_returned
|
||||
end
|
||||
|
||||
enum = obj.to_enum :each_arg, :a, :x
|
||||
|
||||
assert_equal [:a, :x, []], enum.each.to_a
|
||||
assert_true enum.each.equal?(enum)
|
||||
assert_equal :method_returned, enum.each { |elm| elm }
|
||||
|
||||
assert_equal [:a, :x, [:y, :z]], enum.each(:y, :z).to_a
|
||||
assert_false enum.each(:y, :z).equal?(enum)
|
||||
assert_equal :method_returned, enum.each(:y, :z) { |elm| elm }
|
||||
end
|
||||
|
||||
assert 'Enumerator#next' do
|
||||
e = 3.times
|
||||
3.times { |i|
|
||||
assert_equal i, e.next
|
||||
}
|
||||
assert_raise(StopIteration) { e.next }
|
||||
end
|
||||
|
||||
assert 'Enumerator#next_values' do
|
||||
o = Object.new
|
||||
def o.each
|
||||
yield
|
||||
yield 1
|
||||
yield 1, 2
|
||||
end
|
||||
e = o.to_enum
|
||||
assert_equal nil, e.next
|
||||
assert_equal 1, e.next
|
||||
assert_equal [1,2], e.next
|
||||
e = o.to_enum
|
||||
assert_equal [], e.next_values
|
||||
assert_equal [1], e.next_values
|
||||
assert_equal [1,2], e.next_values
|
||||
end
|
||||
|
||||
assert 'Enumerator#peek' do
|
||||
a = [1]
|
||||
e = a.each
|
||||
assert_equal 1, e.peek
|
||||
assert_equal 1, e.peek
|
||||
assert_equal 1, e.next
|
||||
assert_raise(StopIteration) { e.peek }
|
||||
assert_raise(StopIteration) { e.peek }
|
||||
end
|
||||
|
||||
assert 'Enumerator#peek modify' do
|
||||
o = Object.new
|
||||
def o.each
|
||||
yield 1,2
|
||||
end
|
||||
e = o.to_enum
|
||||
a = e.peek
|
||||
a << 3
|
||||
assert_equal([1,2], e.peek)
|
||||
end
|
||||
|
||||
assert 'Enumerator#peek_values' do
|
||||
o = Object.new
|
||||
def o.each
|
||||
yield
|
||||
yield 1
|
||||
yield 1, 2
|
||||
end
|
||||
e = o.to_enum
|
||||
assert_equal nil, e.peek
|
||||
assert_equal nil, e.next
|
||||
assert_equal 1, e.peek
|
||||
assert_equal 1, e.next
|
||||
assert_equal [1,2], e.peek
|
||||
assert_equal [1,2], e.next
|
||||
e = o.to_enum
|
||||
assert_equal [], e.peek_values
|
||||
assert_equal [], e.next_values
|
||||
assert_equal [1], e.peek_values
|
||||
assert_equal [1], e.next_values
|
||||
assert_equal [1,2], e.peek_values
|
||||
assert_equal [1,2], e.next_values
|
||||
e = o.to_enum
|
||||
assert_equal [], e.peek_values
|
||||
assert_equal nil, e.next
|
||||
assert_equal [1], e.peek_values
|
||||
assert_equal 1, e.next
|
||||
assert_equal [1,2], e.peek_values
|
||||
assert_equal [1,2], e.next
|
||||
e = o.to_enum
|
||||
assert_equal nil, e.peek
|
||||
assert_equal [], e.next_values
|
||||
assert_equal 1, e.peek
|
||||
assert_equal [1], e.next_values
|
||||
assert_equal [1,2], e.peek
|
||||
assert_equal [1,2], e.next_values
|
||||
end
|
||||
|
||||
assert 'Enumerator#peek_values modify' do
|
||||
o = Object.new
|
||||
def o.each
|
||||
yield 1,2
|
||||
end
|
||||
e = o.to_enum
|
||||
a = e.peek_values
|
||||
a << 3
|
||||
assert_equal [1,2], e.peek
|
||||
end
|
||||
|
||||
assert 'Enumerator#feed' do
|
||||
o = Object.new
|
||||
def o.each(ary)
|
||||
ary << yield
|
||||
ary << yield
|
||||
ary << yield
|
||||
end
|
||||
ary = []
|
||||
e = o.to_enum :each, ary
|
||||
e.next
|
||||
e.feed 1
|
||||
e.next
|
||||
e.feed 2
|
||||
e.next
|
||||
e.feed 3
|
||||
assert_raise(StopIteration) { e.next }
|
||||
assert_equal [1,2,3], ary
|
||||
end
|
||||
|
||||
assert 'Enumerator#feed mixed' do
|
||||
o = Object.new
|
||||
def o.each(ary)
|
||||
ary << yield
|
||||
ary << yield
|
||||
ary << yield
|
||||
end
|
||||
ary = []
|
||||
e = o.to_enum :each, ary
|
||||
e.next
|
||||
e.feed 1
|
||||
e.next
|
||||
e.next
|
||||
e.feed 3
|
||||
assert_raise(StopIteration) { e.next }
|
||||
assert_equal [1,nil,3], ary
|
||||
end
|
||||
|
||||
assert 'Enumerator#feed twice' do
|
||||
o = Object.new
|
||||
def o.each(ary)
|
||||
ary << yield
|
||||
ary << yield
|
||||
ary << yield
|
||||
end
|
||||
ary = []
|
||||
e = o.to_enum :each, ary
|
||||
e.feed 1
|
||||
assert_raise(TypeError) { e.feed 2 }
|
||||
end
|
||||
|
||||
assert 'Enumerator#feed before first next' do
|
||||
o = Object.new
|
||||
def o.each(ary)
|
||||
ary << yield
|
||||
ary << yield
|
||||
ary << yield
|
||||
end
|
||||
ary = []
|
||||
e = o.to_enum :each, ary
|
||||
e.feed 1
|
||||
e.next
|
||||
e.next
|
||||
assert_equal [1], ary
|
||||
end
|
||||
|
||||
assert 'Enumerator#feed yielder' do
|
||||
x = nil
|
||||
e = Enumerator.new {|y| x = y.yield; 10 }
|
||||
e.next
|
||||
e.feed 100
|
||||
assert_raise(StopIteration) { e.next }
|
||||
assert_equal 100, x
|
||||
end
|
||||
|
||||
assert 'Enumerator#rewind' do
|
||||
e = @obj.to_enum(:foo, 1, 2, 3)
|
||||
assert_equal 1, e.next
|
||||
assert_equal 2, e.next
|
||||
e.rewind
|
||||
assert_equal 1, e.next
|
||||
assert_equal 2, e.next
|
||||
assert_equal 3, e.next
|
||||
assert_raise(StopIteration) { e.next }
|
||||
end
|
||||
|
||||
assert 'Enumerator#rewind clear feed' do
|
||||
o = Object.new
|
||||
def o.each(ary)
|
||||
ary << yield
|
||||
ary << yield
|
||||
ary << yield
|
||||
end
|
||||
ary = []
|
||||
e = o.to_enum(:each, ary)
|
||||
e.next
|
||||
e.feed 1
|
||||
e.next
|
||||
e.feed 2
|
||||
e.rewind
|
||||
e.next
|
||||
e.next
|
||||
assert_equal([1,nil], ary)
|
||||
end
|
||||
|
||||
assert 'Enumerator#rewind clear' do
|
||||
o = Object.new
|
||||
def o.each(ary)
|
||||
ary << yield
|
||||
ary << yield
|
||||
ary << yield
|
||||
end
|
||||
ary = []
|
||||
e = o.to_enum :each, ary
|
||||
e.next
|
||||
e.feed 1
|
||||
e.next
|
||||
e.feed 2
|
||||
e.rewind
|
||||
e.next
|
||||
e.next
|
||||
assert_equal [1,nil], ary
|
||||
end
|
||||
|
||||
assert 'Enumerator::Generator' do
|
||||
# note: Enumerator::Generator is a class just for internal
|
||||
g = Enumerator::Generator.new {|y| y << 1 << 2 << 3; :foo }
|
||||
g2 = g.dup
|
||||
a = []
|
||||
assert_equal(:foo, g.each {|x| a << x })
|
||||
assert_equal([1, 2, 3], a)
|
||||
a = []
|
||||
assert_equal(:foo, g2.each {|x| a << x })
|
||||
assert_equal([1, 2, 3], a)
|
||||
end
|
||||
|
||||
assert 'Enumerator::Generator args' do
|
||||
g = Enumerator::Generator.new {|y, x| y << 1 << 2 << 3; x }
|
||||
a = []
|
||||
assert_equal(:bar, g.each(:bar) {|x| a << x })
|
||||
assert_equal([1, 2, 3], a)
|
||||
end
|
||||
|
||||
assert 'Enumerator::Yielder' do
|
||||
# note: Enumerator::Yielder is a class just for internal
|
||||
a = []
|
||||
y = Enumerator::Yielder.new {|x| a << x }
|
||||
assert_equal(y, y << 1 << 2 << 3)
|
||||
assert_equal([1, 2, 3], a)
|
||||
|
||||
a = []
|
||||
y = Enumerator::Yielder.new {|x| a << x }
|
||||
assert_equal([1], y.yield(1))
|
||||
assert_equal([1, 2], y.yield(2))
|
||||
assert_equal([1, 2, 3], y.yield(3))
|
||||
|
||||
assert_raise(LocalJumpError) { Enumerator::Yielder.new }
|
||||
end
|
||||
|
||||
assert 'next after StopIteration' do
|
||||
a = [1]
|
||||
e = a.each
|
||||
assert_equal(1, e.next)
|
||||
assert_raise(StopIteration) { e.next }
|
||||
assert_raise(StopIteration) { e.next }
|
||||
e.rewind
|
||||
assert_equal(1, e.next)
|
||||
assert_raise(StopIteration) { e.next }
|
||||
assert_raise(StopIteration) { e.next }
|
||||
end
|
||||
|
||||
assert 'gc' do
|
||||
assert_nothing_raised do
|
||||
1.times do
|
||||
foo = [1,2,3].to_enum
|
||||
GC.start
|
||||
end
|
||||
GC.start
|
||||
end
|
||||
end
|
||||
|
||||
assert 'nested iteration' do
|
||||
def (o = Object.new).each
|
||||
yield :ok1
|
||||
yield [:ok2, :x].each.next
|
||||
end
|
||||
e = o.to_enum
|
||||
assert_equal :ok1, e.next
|
||||
assert_equal :ok2, e.next
|
||||
assert_raise(StopIteration) { e.next }
|
||||
end
|
||||
|
||||
assert 'Kernel#to_enum' do
|
||||
e = nil
|
||||
assert_equal Enumerator, [].to_enum.class
|
||||
assert_nothing_raised { e = [].to_enum(:_not_implemented_) }
|
||||
assert_raise(NoMethodError) { e.first }
|
||||
end
|
||||
|
||||
assert 'modifying existing methods' do
|
||||
assert_equal Enumerator, loop.class
|
||||
e = 3.times
|
||||
i = 0
|
||||
loop_ret = loop {
|
||||
assert_equal i, e.next
|
||||
i += 1
|
||||
}
|
||||
end
|
||||
|
||||
assert 'Integral#times' do
|
||||
a = 3
|
||||
b = a.times
|
||||
c = []
|
||||
b.with_object(c) do |i, obj|
|
||||
obj << i
|
||||
end
|
||||
assert_equal 3, a
|
||||
assert_equal Enumerator, b.class
|
||||
assert_equal [0,1,2], c
|
||||
end
|
||||
|
||||
assert 'Enumerable#each_with_index' do
|
||||
assert_equal [['a',0],['b',1],['c',2]], ['a','b','c'].each_with_index.to_a
|
||||
end
|
||||
|
||||
assert 'Enumerable#map' do
|
||||
a = [1,2,3]
|
||||
b = a.map
|
||||
c = b.with_index do |i, index|
|
||||
[i*i, index*index]
|
||||
end
|
||||
assert_equal [1,2,3], a
|
||||
assert_equal [[1,0],[4,1],[9,4]], c
|
||||
end
|
||||
|
||||
assert 'Enumerable#find_all' do
|
||||
assert_equal [[3,4]], [[1,2],[3,4],[5,6]].find_all.each{ |i| i[1] == 4 }
|
||||
end
|
||||
|
||||
assert 'Array#each_index' do
|
||||
a = [1,2,3]
|
||||
b = a.each_index
|
||||
c = []
|
||||
b.with_index do |index1,index2|
|
||||
c << [index1+2,index2+5]
|
||||
end
|
||||
assert_equal [1,2,3], a
|
||||
assert_equal [[2,5],[3,6],[4,7]], c
|
||||
end
|
||||
|
||||
assert 'Array#map!' do
|
||||
a = [1,2,3]
|
||||
b = a.map!
|
||||
b.with_index do |i, index|
|
||||
[i*i, index*index]
|
||||
end
|
||||
assert_equal [[1,0],[4,1],[9,4]], a
|
||||
end
|
||||
|
||||
assert 'Hash#each' do
|
||||
a = {a:1,b:2}
|
||||
b = a.each
|
||||
c = []
|
||||
b.each do |k,v|
|
||||
c << [k,v]
|
||||
end
|
||||
assert_equal [[:a,1], [:b,2]], c.sort
|
||||
end
|
||||
|
||||
assert 'Hash#each_key' do
|
||||
assert_equal [:a,:b], {a:1,b:2}.each_key.to_a.sort
|
||||
end
|
||||
|
||||
assert 'Hash#each_value' do
|
||||
assert_equal [1,2], {a:1,b:2}.each_value.to_a.sort
|
||||
end
|
||||
|
||||
assert 'Hash#select' do
|
||||
h = {1=>2,3=>4,5=>6}
|
||||
hret = h.select.with_index {|a,_b| a[1] == 4}
|
||||
assert_equal({3=>4}, hret)
|
||||
assert_equal({1=>2,3=>4,5=>6}, h)
|
||||
end
|
||||
|
||||
assert 'Hash#select!' do
|
||||
h = {1=>2,3=>4,5=>6}
|
||||
hret = h.select!.with_index {|a,_b| a[1] == 4}
|
||||
assert_equal h, hret
|
||||
assert_equal({3=>4}, h)
|
||||
end
|
||||
|
||||
assert 'Hash#reject' do
|
||||
h = {1=>2,3=>4,5=>6}
|
||||
hret = h.reject.with_index {|a,_b| a[1] == 4}
|
||||
assert_equal({1=>2,5=>6}, hret)
|
||||
assert_equal({1=>2,3=>4,5=>6}, h)
|
||||
end
|
||||
|
||||
assert 'Hash#reject!' do
|
||||
h = {1=>2,3=>4,5=>6}
|
||||
hret = h.reject!.with_index {|a,_b| a[1] == 4}
|
||||
assert_equal h, hret
|
||||
assert_equal({1=>2,5=>6}, h)
|
||||
end
|
||||
|
||||
assert 'Range#each' do
|
||||
a = (1..5)
|
||||
b = a.each
|
||||
c = []
|
||||
b.each do |i|
|
||||
c << i
|
||||
end
|
||||
assert_equal [1,2,3,4,5], c
|
||||
end
|
||||
|
||||
assert 'Enumerable#zip' do
|
||||
assert_equal [[1, 10], [2, 11], [3, 12]], [1,2,3].zip(10..Float::INFINITY)
|
||||
|
||||
ret = []
|
||||
assert_equal nil, [1,2,3].zip(10..Float::INFINITY) { |i| ret << i }
|
||||
assert_equal [[1, 10], [2, 11], [3, 12]], ret
|
||||
|
||||
assert_raise(TypeError) { [1].zip(1) }
|
||||
end
|
||||
|
||||
assert 'Enumerator.produce' do
|
||||
assert_raise(ArgumentError) { Enumerator.produce }
|
||||
|
||||
# Without initial object
|
||||
passed_args = []
|
||||
enum = Enumerator.produce {|obj| passed_args << obj; (obj || 0).succ }
|
||||
assert_equal Enumerator, enum.class
|
||||
assert_take [1, 2, 3], enum
|
||||
assert_equal [nil, 1, 2], passed_args
|
||||
|
||||
# With initial object
|
||||
passed_args = []
|
||||
enum = Enumerator.produce(1) {|obj| passed_args << obj; obj.succ }
|
||||
assert_take [1, 2, 3], enum
|
||||
assert_equal [1, 2], passed_args
|
||||
|
||||
# Raising StopIteration
|
||||
words = %w[The quick brown fox jumps over the lazy dog]
|
||||
enum = Enumerator.produce { words.shift or raise StopIteration }
|
||||
assert_equal %w[The quick brown fox jumps over the lazy dog], enum.to_a
|
||||
|
||||
# Raising StopIteration
|
||||
object = [[[["abc", "def"], "ghi", "jkl"], "mno", "pqr"], "stuv", "wxyz"]
|
||||
enum = Enumerator.produce(object) {|obj|
|
||||
obj.respond_to?(:first) or raise StopIteration
|
||||
obj.first
|
||||
}
|
||||
assert_nothing_raised {
|
||||
assert_equal [
|
||||
[[[["abc", "def"], "ghi", "jkl"], "mno", "pqr"], "stuv", "wxyz"],
|
||||
[[["abc", "def"], "ghi", "jkl"], "mno", "pqr"],
|
||||
[["abc", "def"], "ghi", "jkl"],
|
||||
["abc", "def"],
|
||||
"abc",
|
||||
], enum.to_a
|
||||
}
|
||||
end
|
||||
@@ -0,0 +1,10 @@
|
||||
MRuby::Gem::Specification.new('mruby-error') do |spec|
|
||||
spec.license = 'MIT'
|
||||
spec.author = 'mruby developers'
|
||||
spec.summary = 'extensional error handling'
|
||||
|
||||
if build.cxx_exception_enabled?
|
||||
@objs << build.compile_as_cxx("#{spec.dir}/src/exception.c", "#{spec.build_dir}/src/exception.cxx")
|
||||
@objs.delete_if { |v| v == objfile("#{spec.build_dir}/src/exception") }
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,110 @@
|
||||
#include <mruby.h>
|
||||
#include <mruby/throw.h>
|
||||
#include <mruby/error.h>
|
||||
|
||||
MRB_API mrb_value
|
||||
mrb_protect(mrb_state *mrb, mrb_func_t body, mrb_value data, mrb_bool *state)
|
||||
{
|
||||
struct mrb_jmpbuf *prev_jmp = mrb->jmp;
|
||||
struct mrb_jmpbuf c_jmp;
|
||||
mrb_value result = mrb_nil_value();
|
||||
int ai = mrb_gc_arena_save(mrb);
|
||||
|
||||
if (state) { *state = FALSE; }
|
||||
|
||||
MRB_TRY(&c_jmp) {
|
||||
mrb->jmp = &c_jmp;
|
||||
result = body(mrb, data);
|
||||
mrb->jmp = prev_jmp;
|
||||
} MRB_CATCH(&c_jmp) {
|
||||
mrb->jmp = prev_jmp;
|
||||
result = mrb_obj_value(mrb->exc);
|
||||
mrb->exc = NULL;
|
||||
if (state) { *state = TRUE; }
|
||||
} MRB_END_EXC(&c_jmp);
|
||||
|
||||
mrb_gc_arena_restore(mrb, ai);
|
||||
mrb_gc_protect(mrb, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
MRB_API mrb_value
|
||||
mrb_ensure(mrb_state *mrb, mrb_func_t body, mrb_value b_data, mrb_func_t ensure, mrb_value e_data)
|
||||
{
|
||||
struct mrb_jmpbuf *prev_jmp = mrb->jmp;
|
||||
struct mrb_jmpbuf c_jmp;
|
||||
mrb_value result;
|
||||
int ai = mrb_gc_arena_save(mrb);
|
||||
|
||||
MRB_TRY(&c_jmp) {
|
||||
mrb->jmp = &c_jmp;
|
||||
result = body(mrb, b_data);
|
||||
mrb->jmp = prev_jmp;
|
||||
} MRB_CATCH(&c_jmp) {
|
||||
mrb->jmp = prev_jmp;
|
||||
mrb_gc_arena_restore(mrb, ai);
|
||||
ensure(mrb, e_data);
|
||||
MRB_THROW(mrb->jmp); /* rethrow catched exceptions */
|
||||
} MRB_END_EXC(&c_jmp);
|
||||
|
||||
mrb_gc_arena_restore(mrb, ai);
|
||||
mrb_gc_protect(mrb, result);
|
||||
ensure(mrb, e_data);
|
||||
mrb_gc_arena_restore(mrb, ai);
|
||||
mrb_gc_protect(mrb, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
MRB_API mrb_value
|
||||
mrb_rescue(mrb_state *mrb, mrb_func_t body, mrb_value b_data,
|
||||
mrb_func_t rescue, mrb_value r_data)
|
||||
{
|
||||
return mrb_rescue_exceptions(mrb, body, b_data, rescue, r_data, 1, &mrb->eStandardError_class);
|
||||
}
|
||||
|
||||
MRB_API mrb_value
|
||||
mrb_rescue_exceptions(mrb_state *mrb, mrb_func_t body, mrb_value b_data, mrb_func_t rescue, mrb_value r_data,
|
||||
mrb_int len, struct RClass **classes)
|
||||
{
|
||||
struct mrb_jmpbuf *prev_jmp = mrb->jmp;
|
||||
struct mrb_jmpbuf c_jmp;
|
||||
mrb_value result;
|
||||
mrb_bool error_matched = FALSE;
|
||||
mrb_int i;
|
||||
int ai = mrb_gc_arena_save(mrb);
|
||||
|
||||
MRB_TRY(&c_jmp) {
|
||||
mrb->jmp = &c_jmp;
|
||||
result = body(mrb, b_data);
|
||||
mrb->jmp = prev_jmp;
|
||||
} MRB_CATCH(&c_jmp) {
|
||||
mrb->jmp = prev_jmp;
|
||||
|
||||
for (i = 0; i < len; ++i) {
|
||||
if (mrb_obj_is_kind_of(mrb, mrb_obj_value(mrb->exc), classes[i])) {
|
||||
error_matched = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!error_matched) { MRB_THROW(mrb->jmp); }
|
||||
|
||||
mrb->exc = NULL;
|
||||
mrb_gc_arena_restore(mrb, ai);
|
||||
result = rescue(mrb, r_data);
|
||||
} MRB_END_EXC(&c_jmp);
|
||||
|
||||
mrb_gc_arena_restore(mrb, ai);
|
||||
mrb_gc_protect(mrb, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
void
|
||||
mrb_mruby_error_gem_init(mrb_state *mrb)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
mrb_mruby_error_gem_final(mrb_state *mrb)
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
#include <mruby.h>
|
||||
#include <mruby/error.h>
|
||||
#include <mruby/array.h>
|
||||
|
||||
static mrb_value
|
||||
protect_cb(mrb_state *mrb, mrb_value b)
|
||||
{
|
||||
return mrb_yield_argv(mrb, b, 0, NULL);
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
run_protect(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
mrb_value b;
|
||||
mrb_value ret[2];
|
||||
mrb_bool state;
|
||||
mrb_get_args(mrb, "&", &b);
|
||||
ret[0] = mrb_protect(mrb, protect_cb, b, &state);
|
||||
ret[1] = mrb_bool_value(state);
|
||||
return mrb_ary_new_from_values(mrb, 2, ret);
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
run_ensure(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
mrb_value b, e;
|
||||
mrb_get_args(mrb, "oo", &b, &e);
|
||||
return mrb_ensure(mrb, protect_cb, b, protect_cb, e);
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
run_rescue(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
mrb_value b, r;
|
||||
mrb_get_args(mrb, "oo", &b, &r);
|
||||
return mrb_rescue(mrb, protect_cb, b, protect_cb, r);
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
run_rescue_exceptions(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
mrb_value b, r;
|
||||
struct RClass *cls[1];
|
||||
mrb_get_args(mrb, "oo", &b, &r);
|
||||
cls[0] = E_TYPE_ERROR;
|
||||
return mrb_rescue_exceptions(mrb, protect_cb, b, protect_cb, r, 1, cls);
|
||||
}
|
||||
|
||||
void
|
||||
mrb_mruby_error_gem_test(mrb_state *mrb)
|
||||
{
|
||||
struct RClass *cls;
|
||||
|
||||
cls = mrb_define_class(mrb, "ExceptionTest", mrb->object_class);
|
||||
mrb_define_module_function(mrb, cls, "mrb_protect", run_protect, MRB_ARGS_NONE() | MRB_ARGS_BLOCK());
|
||||
mrb_define_module_function(mrb, cls, "mrb_ensure", run_ensure, MRB_ARGS_REQ(2));
|
||||
mrb_define_module_function(mrb, cls, "mrb_rescue", run_rescue, MRB_ARGS_REQ(2));
|
||||
mrb_define_module_function(mrb, cls, "mrb_rescue_exceptions", run_rescue_exceptions, MRB_ARGS_REQ(2));
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
assert 'mrb_protect' do
|
||||
# no failure in protect returns [result, false]
|
||||
assert_equal ['test', false] do
|
||||
ExceptionTest.mrb_protect { 'test' }
|
||||
end
|
||||
# failure in protect returns [exception, true]
|
||||
result = ExceptionTest.mrb_protect { raise 'test' }
|
||||
assert_kind_of RuntimeError, result[0]
|
||||
assert_true result[1]
|
||||
end
|
||||
|
||||
assert 'mrb_ensure' do
|
||||
a = false
|
||||
assert_equal 'test' do
|
||||
ExceptionTest.mrb_ensure Proc.new { 'test' }, Proc.new { a = true }
|
||||
end
|
||||
assert_true a
|
||||
|
||||
a = false
|
||||
assert_raise RuntimeError do
|
||||
ExceptionTest.mrb_ensure Proc.new { raise 'test' }, Proc.new { a = true }
|
||||
end
|
||||
assert_true a
|
||||
end
|
||||
|
||||
assert 'mrb_rescue' do
|
||||
assert_equal 'test' do
|
||||
ExceptionTest.mrb_rescue Proc.new { 'test' }, Proc.new {}
|
||||
end
|
||||
|
||||
class CustomExp < Exception
|
||||
end
|
||||
|
||||
assert_raise CustomExp do
|
||||
ExceptionTest.mrb_rescue Proc.new { raise CustomExp.new 'test' }, Proc.new { 'rescue' }
|
||||
end
|
||||
|
||||
assert_equal 'rescue' do
|
||||
ExceptionTest.mrb_rescue Proc.new { raise 'test' }, Proc.new { 'rescue' }
|
||||
end
|
||||
end
|
||||
|
||||
assert 'mrb_rescue_exceptions' do
|
||||
assert_equal 'test' do
|
||||
ExceptionTest.mrb_rescue_exceptions Proc.new { 'test' }, Proc.new {}
|
||||
end
|
||||
|
||||
assert_raise RangeError do
|
||||
ExceptionTest.mrb_rescue_exceptions Proc.new { raise RangeError.new 'test' }, Proc.new { 'rescue' }
|
||||
end
|
||||
|
||||
assert_equal 'rescue' do
|
||||
ExceptionTest.mrb_rescue_exceptions Proc.new { raise TypeError.new 'test' }, Proc.new { 'rescue' }
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,7 @@
|
||||
MRuby::Gem::Specification.new('mruby-eval') do |spec|
|
||||
spec.license = 'MIT'
|
||||
spec.author = 'mruby developers'
|
||||
spec.summary = 'standard Kernel#eval method'
|
||||
|
||||
add_dependency 'mruby-compiler', :core => 'mruby-compiler'
|
||||
end
|
||||
@@ -0,0 +1,188 @@
|
||||
#include <mruby.h>
|
||||
#include <mruby/class.h>
|
||||
#include <mruby/compile.h>
|
||||
#include <mruby/irep.h>
|
||||
#include <mruby/proc.h>
|
||||
#include <mruby/opcode.h>
|
||||
#include <mruby/error.h>
|
||||
|
||||
mrb_value mrb_exec_irep(mrb_state *mrb, mrb_value self, struct RProc *p);
|
||||
mrb_value mrb_obj_instance_eval(mrb_state *mrb, mrb_value self);
|
||||
|
||||
void mrb_codedump_all(mrb_state*, struct RProc*);
|
||||
|
||||
static struct RProc*
|
||||
create_proc_from_string(mrb_state *mrb, char *s, mrb_int len, mrb_value binding, const char *file, mrb_int line)
|
||||
{
|
||||
mrbc_context *cxt;
|
||||
struct mrb_parser_state *p;
|
||||
struct RProc *proc;
|
||||
struct REnv *e;
|
||||
mrb_callinfo *ci; /* callinfo of eval caller */
|
||||
struct RClass *target_class = NULL;
|
||||
int bidx;
|
||||
|
||||
if (!mrb_nil_p(binding)) {
|
||||
mrb_raise(mrb, E_ARGUMENT_ERROR, "Binding of eval must be nil.");
|
||||
}
|
||||
|
||||
cxt = mrbc_context_new(mrb);
|
||||
cxt->lineno = (uint16_t)line;
|
||||
|
||||
mrbc_filename(mrb, cxt, file ? file : "(eval)");
|
||||
cxt->capture_errors = TRUE;
|
||||
cxt->no_optimize = TRUE;
|
||||
ci = (mrb->c->ci > mrb->c->cibase) ? mrb->c->ci - 1 : mrb->c->cibase;
|
||||
cxt->upper = ci->proc && MRB_PROC_CFUNC_P(ci->proc) ? NULL : ci->proc;
|
||||
|
||||
p = mrb_parse_nstring(mrb, s, len, cxt);
|
||||
|
||||
/* only occur when memory ran out */
|
||||
if (!p) {
|
||||
mrb_raise(mrb, E_RUNTIME_ERROR, "Failed to create parser state.");
|
||||
}
|
||||
|
||||
if (0 < p->nerr) {
|
||||
/* parse error */
|
||||
mrb_value str;
|
||||
|
||||
if (file) {
|
||||
str = mrb_format(mrb, "file %s line %d: %s",
|
||||
file,
|
||||
p->error_buffer[0].lineno,
|
||||
p->error_buffer[0].message);
|
||||
}
|
||||
else {
|
||||
str = mrb_format(mrb, "line %d: %s",
|
||||
p->error_buffer[0].lineno,
|
||||
p->error_buffer[0].message);
|
||||
}
|
||||
mrb_parser_free(p);
|
||||
mrbc_context_free(mrb, cxt);
|
||||
mrb_exc_raise(mrb, mrb_exc_new_str(mrb, E_SYNTAX_ERROR, str));
|
||||
}
|
||||
|
||||
proc = mrb_generate_code(mrb, p);
|
||||
if (proc == NULL) {
|
||||
/* codegen error */
|
||||
mrb_parser_free(p);
|
||||
mrbc_context_free(mrb, cxt);
|
||||
mrb_raise(mrb, E_SCRIPT_ERROR, "codegen error");
|
||||
}
|
||||
if (mrb->c->ci > mrb->c->cibase) {
|
||||
ci = &mrb->c->ci[-1];
|
||||
}
|
||||
else {
|
||||
ci = mrb->c->cibase;
|
||||
}
|
||||
if (ci->proc) {
|
||||
target_class = MRB_PROC_TARGET_CLASS(ci->proc);
|
||||
}
|
||||
if (ci->proc && !MRB_PROC_CFUNC_P(ci->proc)) {
|
||||
if (ci->env) {
|
||||
e = ci->env;
|
||||
}
|
||||
else {
|
||||
e = (struct REnv*)mrb_obj_alloc(mrb, MRB_TT_ENV,
|
||||
(struct RClass*)target_class);
|
||||
e->mid = ci->mid;
|
||||
e->stack = ci[1].stackent;
|
||||
e->cxt = mrb->c;
|
||||
MRB_ENV_SET_LEN(e, ci->proc->body.irep->nlocals);
|
||||
bidx = ci->argc;
|
||||
if (ci->argc < 0) bidx = 2;
|
||||
else bidx += 1;
|
||||
MRB_ENV_SET_BIDX(e, bidx);
|
||||
ci->env = e;
|
||||
}
|
||||
proc->e.env = e;
|
||||
proc->flags |= MRB_PROC_ENVSET;
|
||||
mrb_field_write_barrier(mrb, (struct RBasic*)proc, (struct RBasic*)e);
|
||||
}
|
||||
proc->upper = ci->proc;
|
||||
mrb->c->ci->target_class = target_class;
|
||||
/* mrb_codedump_all(mrb, proc); */
|
||||
|
||||
mrb_parser_free(p);
|
||||
mrbc_context_free(mrb, cxt);
|
||||
|
||||
return proc;
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
exec_irep(mrb_state *mrb, mrb_value self, struct RProc *proc)
|
||||
{
|
||||
/* no argument passed from eval() */
|
||||
mrb->c->ci->argc = 0;
|
||||
if (mrb->c->ci->acc < 0) {
|
||||
ptrdiff_t cioff = mrb->c->ci - mrb->c->cibase;
|
||||
mrb_value ret = mrb_top_run(mrb, proc, self, 0);
|
||||
if (mrb->exc) {
|
||||
mrb_exc_raise(mrb, mrb_obj_value(mrb->exc));
|
||||
}
|
||||
mrb->c->ci = mrb->c->cibase + cioff;
|
||||
return ret;
|
||||
}
|
||||
/* clear block */
|
||||
mrb->c->stack[1] = mrb_nil_value();
|
||||
return mrb_exec_irep(mrb, self, proc);
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
f_eval(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
char *s;
|
||||
mrb_int len;
|
||||
mrb_value binding = mrb_nil_value();
|
||||
char *file = NULL;
|
||||
mrb_int line = 1;
|
||||
struct RProc *proc;
|
||||
|
||||
mrb_get_args(mrb, "s|ozi", &s, &len, &binding, &file, &line);
|
||||
|
||||
proc = create_proc_from_string(mrb, s, len, binding, file, line);
|
||||
mrb_assert(!MRB_PROC_CFUNC_P(proc));
|
||||
return exec_irep(mrb, self, proc);
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
f_instance_eval(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
mrb_value b;
|
||||
mrb_int argc; mrb_value *argv;
|
||||
|
||||
mrb_get_args(mrb, "*!&", &argv, &argc, &b);
|
||||
|
||||
if (mrb_nil_p(b)) {
|
||||
char *s;
|
||||
mrb_int len;
|
||||
char *file = NULL;
|
||||
mrb_int line = 1;
|
||||
mrb_value cv;
|
||||
struct RProc *proc;
|
||||
|
||||
mrb_get_args(mrb, "s|zi", &s, &len, &file, &line);
|
||||
cv = mrb_singleton_class(mrb, self);
|
||||
proc = create_proc_from_string(mrb, s, len, mrb_nil_value(), file, line);
|
||||
MRB_PROC_SET_TARGET_CLASS(proc, mrb_class_ptr(cv));
|
||||
mrb_assert(!MRB_PROC_CFUNC_P(proc));
|
||||
mrb->c->ci->target_class = mrb_class_ptr(cv);
|
||||
return exec_irep(mrb, self, proc);
|
||||
}
|
||||
else {
|
||||
mrb_get_args(mrb, "&", &b);
|
||||
return mrb_obj_instance_eval(mrb, self);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
mrb_mruby_eval_gem_init(mrb_state* mrb)
|
||||
{
|
||||
mrb_define_module_function(mrb, mrb->kernel_module, "eval", f_eval, MRB_ARGS_ARG(1, 3));
|
||||
mrb_define_method(mrb, mrb_class_get(mrb, "BasicObject"), "instance_eval", f_instance_eval, MRB_ARGS_OPT(3)|MRB_ARGS_BLOCK());
|
||||
}
|
||||
|
||||
void
|
||||
mrb_mruby_eval_gem_final(mrb_state* mrb)
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
assert('Kernel.eval', '15.3.1.2.3') do
|
||||
assert_equal(10) { Kernel.eval '1 * 10' }
|
||||
assert_equal('aaa') { Kernel.eval "'a' * 3" }
|
||||
assert_equal(10) {
|
||||
a = 10
|
||||
Kernel.eval "a"
|
||||
}
|
||||
assert_equal(20) {
|
||||
a = 10
|
||||
Kernel.eval "a = 20"
|
||||
a
|
||||
}
|
||||
assert_equal(15) {
|
||||
c = 5
|
||||
lambda {
|
||||
a = 10
|
||||
Kernel.eval "c = a + c"
|
||||
}.call
|
||||
c
|
||||
}
|
||||
assert_equal(5) {
|
||||
c = 5
|
||||
lambda {
|
||||
Kernel.eval 'lambda { c }.call'
|
||||
}.call
|
||||
}
|
||||
assert_equal(15) {
|
||||
c = 5
|
||||
lambda {
|
||||
a = 10
|
||||
Kernel.eval 'lambda { c = a + c }.call'
|
||||
}.call
|
||||
c
|
||||
}
|
||||
assert_equal(2) {
|
||||
a = 10
|
||||
Kernel.eval 'def f(a); b=a+1; end'
|
||||
f(1)
|
||||
}
|
||||
end
|
||||
|
||||
assert('Kernel#eval', '15.3.1.3.12') do
|
||||
assert_equal(10) { eval '1 * 10' }
|
||||
end
|
||||
|
||||
assert('rest arguments of eval') do
|
||||
assert_raise(ArgumentError) { Kernel.eval('0', 0, 'test', 0) }
|
||||
assert_equal ['test', 'test.rb', 10] do
|
||||
Kernel.eval('[\'test\', __FILE__, __LINE__]', nil, 'test.rb', 10)
|
||||
end
|
||||
end
|
||||
|
||||
assert 'eval syntax error' do
|
||||
assert_raise(SyntaxError) do
|
||||
eval 'p "test'
|
||||
end
|
||||
end
|
||||
|
||||
assert('String instance_eval') do
|
||||
obj = Object.new
|
||||
obj.instance_eval{ @test = 'test' }
|
||||
assert_raise(ArgumentError) { obj.instance_eval(0) { } }
|
||||
assert_raise(ArgumentError) { obj.instance_eval('0', 'test', 0, 'test') }
|
||||
assert_equal(['test.rb', 10]) { obj.instance_eval('[__FILE__, __LINE__]', 'test.rb', 10)}
|
||||
assert_equal('test') { obj.instance_eval('@test') }
|
||||
assert_equal('test') { obj.instance_eval { @test } }
|
||||
o = Object.new
|
||||
assert_equal ['', o, o], o.instance_eval("[''].each { |s| break [s, o, self] }")
|
||||
end
|
||||
|
||||
assert('Kernel.#eval(string) context') do
|
||||
class TestEvalConstScope
|
||||
EVAL_CONST_CLASS = 'class'
|
||||
def const_string
|
||||
eval 'EVAL_CONST_CLASS'
|
||||
end
|
||||
end
|
||||
obj = TestEvalConstScope.new
|
||||
assert_raise(NameError) { eval 'EVAL_CONST_CLASS' }
|
||||
assert_equal('class') { obj.const_string }
|
||||
end
|
||||
|
||||
assert('BasicObject#instance_eval with begin-rescue-ensure execution order') do
|
||||
class HellRaiser
|
||||
def raise_hell
|
||||
order = [:enter_raise_hell]
|
||||
begin
|
||||
order.push :begin
|
||||
self.instance_eval("raise 'error'")
|
||||
rescue
|
||||
order.push :rescue
|
||||
ensure
|
||||
order.push :ensure
|
||||
end
|
||||
order
|
||||
end
|
||||
end
|
||||
|
||||
hell_raiser = HellRaiser.new
|
||||
assert_equal([:enter_raise_hell, :begin, :rescue, :ensure], hell_raiser.raise_hell)
|
||||
end
|
||||
|
||||
assert('BasicObject#instance_eval to define singleton methods Issue #3141') do
|
||||
foo_class = Class.new do
|
||||
def bar(x)
|
||||
instance_eval "def baz; #{x}; end"
|
||||
end
|
||||
end
|
||||
|
||||
f1 = foo_class.new
|
||||
f2 = foo_class.new
|
||||
f1.bar 1
|
||||
f2.bar 2
|
||||
assert_equal(1){f1.baz}
|
||||
assert_equal(2){f2.baz}
|
||||
end
|
||||
|
||||
assert('Kernel.#eval(string) Issue #4021') do
|
||||
assert_equal('FOO') { (eval <<'EOS').call }
|
||||
foo = "FOO"
|
||||
Proc.new { foo }
|
||||
EOS
|
||||
assert_equal('FOO') {
|
||||
def do_eval(code)
|
||||
eval(code)
|
||||
end
|
||||
do_eval(<<'EOS').call
|
||||
foo = "FOO"
|
||||
Proc.new { foo }
|
||||
EOS
|
||||
}
|
||||
end
|
||||
|
||||
assert('Calling the same method as the variable name') do
|
||||
hoge = Object.new
|
||||
def hoge.fuga
|
||||
"Hit!"
|
||||
end
|
||||
assert_equal("Hit!") { fuga = "Miss!"; eval "hoge.fuga" }
|
||||
assert_equal("Hit!") { fuga = "Miss!"; -> { eval "hoge.fuga" }.call }
|
||||
assert_equal("Hit!") { -> { fuga = "Miss!"; eval "hoge.fuga" }.call }
|
||||
assert_equal("Hit!") { fuga = "Miss!"; eval("-> { hoge.fuga }").call }
|
||||
end
|
||||
|
||||
assert('Access numbered parameter from eval') do
|
||||
hoge = Object.new
|
||||
def hoge.fuga(a, &b)
|
||||
b.call(a)
|
||||
end
|
||||
assert_equal(6) {
|
||||
hoge.fuga(3) { _1 + eval("_1") }
|
||||
}
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
MRuby::Gem::Specification.new('mruby-exit') do |spec|
|
||||
spec.license = 'MIT'
|
||||
spec.author = 'mruby developers'
|
||||
spec.summary = 'Kernel#exit method'
|
||||
end
|
||||
@@ -0,0 +1,29 @@
|
||||
#include <stdlib.h>
|
||||
#include <mruby.h>
|
||||
|
||||
static mrb_value
|
||||
f_exit(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
mrb_value status = mrb_true_value();
|
||||
int istatus;
|
||||
|
||||
mrb_get_args(mrb, "|o", &status);
|
||||
istatus = mrb_true_p(status) ? EXIT_SUCCESS :
|
||||
mrb_false_p(status) ? EXIT_FAILURE :
|
||||
(int)mrb_int(mrb, status);
|
||||
exit(istatus);
|
||||
|
||||
/* not reached */
|
||||
return status;
|
||||
}
|
||||
|
||||
void
|
||||
mrb_mruby_exit_gem_init(mrb_state* mrb)
|
||||
{
|
||||
mrb_define_method(mrb, mrb->kernel_module, "exit", f_exit, MRB_ARGS_OPT(1));
|
||||
}
|
||||
|
||||
void
|
||||
mrb_mruby_exit_gem_final(mrb_state* mrb)
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
MRuby::Gem::Specification.new('mruby-fiber') do |spec|
|
||||
spec.license = 'MIT'
|
||||
spec.author = 'mruby developers'
|
||||
spec.summary = 'Fiber class'
|
||||
end
|
||||
@@ -0,0 +1,426 @@
|
||||
#include <mruby.h>
|
||||
#include <mruby/array.h>
|
||||
#include <mruby/class.h>
|
||||
#include <mruby/proc.h>
|
||||
|
||||
#define fiber_ptr(o) ((struct RFiber*)mrb_ptr(o))
|
||||
|
||||
#define FIBER_STACK_INIT_SIZE 64
|
||||
#define FIBER_CI_INIT_SIZE 8
|
||||
#define CI_ACC_RESUMED -3
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* Fiber.new{...} -> obj
|
||||
*
|
||||
* Creates a fiber, whose execution is suspend until it is explicitly
|
||||
* resumed using <code>Fiber#resume</code> method.
|
||||
* The code running inside the fiber can give up control by calling
|
||||
* <code>Fiber.yield</code> in which case it yields control back to caller
|
||||
* (the caller of the <code>Fiber#resume</code>).
|
||||
*
|
||||
* Upon yielding or termination the Fiber returns the value of the last
|
||||
* executed expression
|
||||
*
|
||||
* For instance:
|
||||
*
|
||||
* fiber = Fiber.new do
|
||||
* Fiber.yield 1
|
||||
* 2
|
||||
* end
|
||||
*
|
||||
* puts fiber.resume
|
||||
* puts fiber.resume
|
||||
* puts fiber.resume
|
||||
*
|
||||
* <em>produces</em>
|
||||
*
|
||||
* 1
|
||||
* 2
|
||||
* resuming dead fiber (FiberError)
|
||||
*
|
||||
* The <code>Fiber#resume</code> method accepts an arbitrary number of
|
||||
* parameters, if it is the first call to <code>resume</code> then they
|
||||
* will be passed as block arguments. Otherwise they will be the return
|
||||
* value of the call to <code>Fiber.yield</code>
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* fiber = Fiber.new do |first|
|
||||
* second = Fiber.yield first + 2
|
||||
* end
|
||||
*
|
||||
* puts fiber.resume 10
|
||||
* puts fiber.resume 14
|
||||
* puts fiber.resume 18
|
||||
*
|
||||
* <em>produces</em>
|
||||
*
|
||||
* 12
|
||||
* 14
|
||||
* resuming dead fiber (FiberError)
|
||||
*
|
||||
*/
|
||||
static mrb_value
|
||||
fiber_init(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
static const struct mrb_context mrb_context_zero = { 0 };
|
||||
struct RFiber *f = fiber_ptr(self);
|
||||
struct mrb_context *c;
|
||||
struct RProc *p;
|
||||
mrb_callinfo *ci;
|
||||
mrb_value blk;
|
||||
size_t slen;
|
||||
|
||||
mrb_get_args(mrb, "&!", &blk);
|
||||
|
||||
if (f->cxt) {
|
||||
mrb_raise(mrb, E_RUNTIME_ERROR, "cannot initialize twice");
|
||||
}
|
||||
p = mrb_proc_ptr(blk);
|
||||
if (MRB_PROC_CFUNC_P(p)) {
|
||||
mrb_raise(mrb, E_FIBER_ERROR, "tried to create Fiber from C defined method");
|
||||
}
|
||||
|
||||
c = (struct mrb_context*)mrb_malloc(mrb, sizeof(struct mrb_context));
|
||||
*c = mrb_context_zero;
|
||||
f->cxt = c;
|
||||
|
||||
/* initialize VM stack */
|
||||
slen = FIBER_STACK_INIT_SIZE;
|
||||
if (p->body.irep->nregs > slen) {
|
||||
slen += p->body.irep->nregs;
|
||||
}
|
||||
c->stbase = (mrb_value *)mrb_malloc(mrb, slen*sizeof(mrb_value));
|
||||
c->stend = c->stbase + slen;
|
||||
c->stack = c->stbase;
|
||||
|
||||
#ifdef MRB_NAN_BOXING
|
||||
{
|
||||
mrb_value *p = c->stbase;
|
||||
mrb_value *pend = c->stend;
|
||||
|
||||
while (p < pend) {
|
||||
SET_NIL_VALUE(*p);
|
||||
p++;
|
||||
}
|
||||
}
|
||||
#else
|
||||
memset(c->stbase, 0, slen * sizeof(mrb_value));
|
||||
#endif
|
||||
|
||||
/* copy receiver from a block */
|
||||
c->stack[0] = mrb->c->stack[0];
|
||||
|
||||
/* initialize callinfo stack */
|
||||
c->cibase = (mrb_callinfo *)mrb_calloc(mrb, FIBER_CI_INIT_SIZE, sizeof(mrb_callinfo));
|
||||
c->ciend = c->cibase + FIBER_CI_INIT_SIZE;
|
||||
c->ci = c->cibase;
|
||||
c->ci->stackent = c->stack;
|
||||
|
||||
/* adjust return callinfo */
|
||||
ci = c->ci;
|
||||
ci->target_class = MRB_PROC_TARGET_CLASS(p);
|
||||
ci->proc = p;
|
||||
mrb_field_write_barrier(mrb, (struct RBasic*)mrb_obj_ptr(self), (struct RBasic*)p);
|
||||
ci->pc = p->body.irep->iseq;
|
||||
ci[1] = ci[0];
|
||||
c->ci++; /* push dummy callinfo */
|
||||
|
||||
c->fib = f;
|
||||
c->status = MRB_FIBER_CREATED;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
static struct mrb_context*
|
||||
fiber_check(mrb_state *mrb, mrb_value fib)
|
||||
{
|
||||
struct RFiber *f = fiber_ptr(fib);
|
||||
|
||||
mrb_assert(f->tt == MRB_TT_FIBER);
|
||||
if (!f->cxt) {
|
||||
mrb_raise(mrb, E_FIBER_ERROR, "uninitialized Fiber");
|
||||
}
|
||||
return f->cxt;
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
fiber_result(mrb_state *mrb, const mrb_value *a, mrb_int len)
|
||||
{
|
||||
if (len == 0) return mrb_nil_value();
|
||||
if (len == 1) return a[0];
|
||||
return mrb_ary_new_from_values(mrb, len, a);
|
||||
}
|
||||
|
||||
/* mark return from context modifying method */
|
||||
#define MARK_CONTEXT_MODIFY(c) (c)->ci->target_class = NULL
|
||||
|
||||
static void
|
||||
fiber_check_cfunc(mrb_state *mrb, struct mrb_context *c)
|
||||
{
|
||||
mrb_callinfo *ci;
|
||||
|
||||
for (ci = c->ci; ci >= c->cibase; ci--) {
|
||||
if (ci->acc < 0) {
|
||||
mrb_raise(mrb, E_FIBER_ERROR, "can't cross C function boundary");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
fiber_switch_context(mrb_state *mrb, struct mrb_context *c)
|
||||
{
|
||||
if (mrb->c->fib) {
|
||||
mrb_write_barrier(mrb, (struct RBasic*)mrb->c->fib);
|
||||
}
|
||||
c->status = MRB_FIBER_RUNNING;
|
||||
mrb->c = c;
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
fiber_switch(mrb_state *mrb, mrb_value self, mrb_int len, const mrb_value *a, mrb_bool resume, mrb_bool vmexec)
|
||||
{
|
||||
struct mrb_context *c = fiber_check(mrb, self);
|
||||
struct mrb_context *old_c = mrb->c;
|
||||
enum mrb_fiber_state status;
|
||||
mrb_value value;
|
||||
|
||||
fiber_check_cfunc(mrb, c);
|
||||
status = c->status;
|
||||
switch (status) {
|
||||
case MRB_FIBER_TRANSFERRED:
|
||||
if (resume) {
|
||||
mrb_raise(mrb, E_FIBER_ERROR, "resuming transferred fiber");
|
||||
}
|
||||
break;
|
||||
case MRB_FIBER_RUNNING:
|
||||
case MRB_FIBER_RESUMED:
|
||||
mrb_raise(mrb, E_FIBER_ERROR, "double resume");
|
||||
break;
|
||||
case MRB_FIBER_TERMINATED:
|
||||
mrb_raise(mrb, E_FIBER_ERROR, "resuming dead fiber");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
old_c->status = resume ? MRB_FIBER_RESUMED : MRB_FIBER_TRANSFERRED;
|
||||
c->prev = resume ? mrb->c : (c->prev ? c->prev : mrb->root_c);
|
||||
fiber_switch_context(mrb, c);
|
||||
if (status == MRB_FIBER_CREATED) {
|
||||
mrb_value *b, *e;
|
||||
|
||||
if (!c->ci->proc) {
|
||||
mrb_raise(mrb, E_FIBER_ERROR, "double resume (current)");
|
||||
}
|
||||
mrb_stack_extend(mrb, len+2); /* for receiver and (optional) block */
|
||||
b = c->stack+1;
|
||||
e = b + len;
|
||||
while (b<e) {
|
||||
*b++ = *a++;
|
||||
}
|
||||
c->cibase->argc = (int)len;
|
||||
value = c->stack[0] = MRB_PROC_ENV(c->ci->proc)->stack[0];
|
||||
}
|
||||
else {
|
||||
value = fiber_result(mrb, a, len);
|
||||
}
|
||||
|
||||
if (vmexec) {
|
||||
c->vmexec = TRUE;
|
||||
value = mrb_vm_exec(mrb, c->ci[-1].proc, c->ci->pc);
|
||||
mrb->c = old_c;
|
||||
}
|
||||
else {
|
||||
MARK_CONTEXT_MODIFY(c);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* fiber.resume(args, ...) -> obj
|
||||
*
|
||||
* Resumes the fiber from the point at which the last <code>Fiber.yield</code>
|
||||
* was called, or starts running it if it is the first call to
|
||||
* <code>resume</code>. Arguments passed to resume will be the value of
|
||||
* the <code>Fiber.yield</code> expression or will be passed as block
|
||||
* parameters to the fiber's block if this is the first <code>resume</code>.
|
||||
*
|
||||
* Alternatively, when resume is called it evaluates to the arguments passed
|
||||
* to the next <code>Fiber.yield</code> statement inside the fiber's block
|
||||
* or to the block value if it runs to completion without any
|
||||
* <code>Fiber.yield</code>
|
||||
*/
|
||||
static mrb_value
|
||||
fiber_resume(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
mrb_value *a;
|
||||
mrb_int len;
|
||||
mrb_bool vmexec = FALSE;
|
||||
|
||||
mrb_get_args(mrb, "*!", &a, &len);
|
||||
if (mrb->c->ci->acc < 0) {
|
||||
vmexec = TRUE;
|
||||
}
|
||||
return fiber_switch(mrb, self, len, a, TRUE, vmexec);
|
||||
}
|
||||
|
||||
/* resume thread with given arguments */
|
||||
MRB_API mrb_value
|
||||
mrb_fiber_resume(mrb_state *mrb, mrb_value fib, mrb_int len, const mrb_value *a)
|
||||
{
|
||||
return fiber_switch(mrb, fib, len, a, TRUE, TRUE);
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* fiber.alive? -> true or false
|
||||
*
|
||||
* Returns true if the fiber can still be resumed. After finishing
|
||||
* execution of the fiber block this method will always return false.
|
||||
*/
|
||||
MRB_API mrb_value
|
||||
mrb_fiber_alive_p(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
struct mrb_context *c = fiber_check(mrb, self);
|
||||
return mrb_bool_value(c->status != MRB_FIBER_TERMINATED);
|
||||
}
|
||||
#define fiber_alive_p mrb_fiber_alive_p
|
||||
|
||||
static mrb_value
|
||||
fiber_eq(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
mrb_value other = mrb_get_arg1(mrb);
|
||||
|
||||
if (!mrb_fiber_p(other)) {
|
||||
return mrb_false_value();
|
||||
}
|
||||
return mrb_bool_value(fiber_ptr(self) == fiber_ptr(other));
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* fiber.transfer(args, ...) -> obj
|
||||
*
|
||||
* Transfers control to receiver fiber of the method call.
|
||||
* Unlike <code>resume</code> the receiver wouldn't be pushed to call
|
||||
* stack of fibers. Instead it will switch to the call stack of
|
||||
* transferring fiber.
|
||||
* When resuming a fiber that was transferred to another fiber it would
|
||||
* cause double resume error. Though when the fiber is re-transferred
|
||||
* and <code>Fiber.yield</code> is called, the fiber would be resumable.
|
||||
*/
|
||||
static mrb_value
|
||||
fiber_transfer(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
struct mrb_context *c = fiber_check(mrb, self);
|
||||
mrb_value* a;
|
||||
mrb_int len;
|
||||
|
||||
fiber_check_cfunc(mrb, mrb->c);
|
||||
mrb_get_args(mrb, "*!", &a, &len);
|
||||
|
||||
if (c == mrb->root_c) {
|
||||
mrb->c->status = MRB_FIBER_TRANSFERRED;
|
||||
fiber_switch_context(mrb, c);
|
||||
MARK_CONTEXT_MODIFY(c);
|
||||
return fiber_result(mrb, a, len);
|
||||
}
|
||||
|
||||
if (c == mrb->c) {
|
||||
return fiber_result(mrb, a, len);
|
||||
}
|
||||
|
||||
return fiber_switch(mrb, self, len, a, FALSE, FALSE);
|
||||
}
|
||||
|
||||
/* yield values to the caller fiber */
|
||||
/* mrb_fiber_yield() must be called as `return mrb_fiber_yield(...)` */
|
||||
MRB_API mrb_value
|
||||
mrb_fiber_yield(mrb_state *mrb, mrb_int len, const mrb_value *a)
|
||||
{
|
||||
struct mrb_context *c = mrb->c;
|
||||
|
||||
if (!c->prev) {
|
||||
mrb_raise(mrb, E_FIBER_ERROR, "can't yield from root fiber");
|
||||
}
|
||||
|
||||
fiber_check_cfunc(mrb, c);
|
||||
c->prev->status = MRB_FIBER_RUNNING;
|
||||
c->status = MRB_FIBER_SUSPENDED;
|
||||
fiber_switch_context(mrb, c->prev);
|
||||
c->prev = NULL;
|
||||
if (c->vmexec) {
|
||||
c->vmexec = FALSE;
|
||||
mrb->c->ci->acc = CI_ACC_RESUMED;
|
||||
}
|
||||
MARK_CONTEXT_MODIFY(mrb->c);
|
||||
return fiber_result(mrb, a, len);
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* Fiber.yield(args, ...) -> obj
|
||||
*
|
||||
* Yields control back to the context that resumed the fiber, passing
|
||||
* along any arguments that were passed to it. The fiber will resume
|
||||
* processing at this point when <code>resume</code> is called next.
|
||||
* Any arguments passed to the next <code>resume</code> will be the
|
||||
*
|
||||
* mruby limitation: Fiber resume/yield cannot cross C function boundary.
|
||||
* thus you cannot yield from #initialize which is called by mrb_funcall().
|
||||
*/
|
||||
static mrb_value
|
||||
fiber_yield(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
mrb_value *a;
|
||||
mrb_int len;
|
||||
|
||||
mrb_get_args(mrb, "*!", &a, &len);
|
||||
return mrb_fiber_yield(mrb, len, a);
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* Fiber.current() -> fiber
|
||||
*
|
||||
* Returns the current fiber. If you are not running in the context of
|
||||
* a fiber this method will return the root fiber.
|
||||
*/
|
||||
static mrb_value
|
||||
fiber_current(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
if (!mrb->c->fib) {
|
||||
struct RFiber *f = (struct RFiber*)mrb_obj_alloc(mrb, MRB_TT_FIBER, mrb_class_ptr(self));
|
||||
|
||||
f->cxt = mrb->c;
|
||||
mrb->c->fib = f;
|
||||
}
|
||||
return mrb_obj_value(mrb->c->fib);
|
||||
}
|
||||
|
||||
void
|
||||
mrb_mruby_fiber_gem_init(mrb_state* mrb)
|
||||
{
|
||||
struct RClass *c;
|
||||
|
||||
c = mrb_define_class(mrb, "Fiber", mrb->object_class);
|
||||
MRB_SET_INSTANCE_TT(c, MRB_TT_FIBER);
|
||||
|
||||
mrb_define_method(mrb, c, "initialize", fiber_init, MRB_ARGS_NONE()|MRB_ARGS_BLOCK());
|
||||
mrb_define_method(mrb, c, "resume", fiber_resume, MRB_ARGS_ANY());
|
||||
mrb_define_method(mrb, c, "transfer", fiber_transfer, MRB_ARGS_ANY());
|
||||
mrb_define_method(mrb, c, "alive?", fiber_alive_p, MRB_ARGS_NONE());
|
||||
mrb_define_method(mrb, c, "==", fiber_eq, MRB_ARGS_REQ(1));
|
||||
|
||||
mrb_define_class_method(mrb, c, "yield", fiber_yield, MRB_ARGS_ANY());
|
||||
mrb_define_class_method(mrb, c, "current", fiber_current, MRB_ARGS_NONE());
|
||||
|
||||
mrb_define_class(mrb, "FiberError", mrb->eStandardError_class);
|
||||
}
|
||||
|
||||
void
|
||||
mrb_mruby_fiber_gem_final(mrb_state* mrb)
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
assert('Fiber.new') do
|
||||
f = Fiber.new{}
|
||||
assert_kind_of Fiber, f
|
||||
end
|
||||
|
||||
assert('Fiber#resume') do
|
||||
f = Fiber.new{|x| x }
|
||||
assert_equal 2, f.resume(2)
|
||||
end
|
||||
|
||||
assert('Fiber#transfer') do
|
||||
f2 = nil
|
||||
f1 = Fiber.new do |v|
|
||||
Fiber.yield v
|
||||
f2.transfer
|
||||
end
|
||||
f2 = Fiber.new do
|
||||
f1.transfer(1)
|
||||
f1.transfer(1)
|
||||
Fiber.yield 2
|
||||
end
|
||||
assert_equal 1, f2.resume
|
||||
assert_raise(FiberError) { f2.resume }
|
||||
assert_equal 2, f2.transfer
|
||||
assert_raise(FiberError) { f1.resume }
|
||||
f1.transfer
|
||||
f2.resume
|
||||
assert_false f1.alive?
|
||||
assert_false f2.alive?
|
||||
end
|
||||
|
||||
assert('Fiber#alive?') do
|
||||
f = Fiber.new{ Fiber.yield }
|
||||
f.resume
|
||||
assert_true f.alive?
|
||||
f.resume
|
||||
assert_false f.alive?
|
||||
end
|
||||
|
||||
assert('Fiber#==') do
|
||||
root = Fiber.current
|
||||
assert_equal root, root
|
||||
assert_equal root, Fiber.current
|
||||
assert_false root != Fiber.current
|
||||
f = Fiber.new {
|
||||
assert_false root == Fiber.current
|
||||
}
|
||||
f.resume
|
||||
assert_false f == root
|
||||
assert_true f != root
|
||||
end
|
||||
|
||||
assert('Fiber.yield') do
|
||||
f = Fiber.new{|x| Fiber.yield x }
|
||||
assert_equal 3, f.resume(3)
|
||||
assert_true f.alive?
|
||||
end
|
||||
|
||||
assert('FiberError') do
|
||||
assert_equal StandardError, FiberError.superclass
|
||||
end
|
||||
|
||||
assert('Fiber iteration') do
|
||||
f1 = Fiber.new{
|
||||
[1,2,3].each{|x| Fiber.yield(x)}
|
||||
}
|
||||
f2 = Fiber.new{
|
||||
[9,8,7].each{|x| Fiber.yield(x)}
|
||||
}
|
||||
a = []
|
||||
3.times {
|
||||
a << f1.resume
|
||||
a << f2.resume
|
||||
}
|
||||
assert_equal [1,9,2,8,3,7], a
|
||||
end
|
||||
|
||||
assert('Fiber with splat in the block argument list') {
|
||||
assert_equal([1], Fiber.new{|*x|x}.resume(1))
|
||||
}
|
||||
|
||||
assert('Fiber raises on resume when dead') do
|
||||
assert_raise(FiberError) do
|
||||
f = Fiber.new{}
|
||||
f.resume
|
||||
assert_false f.alive?
|
||||
f.resume
|
||||
end
|
||||
end
|
||||
|
||||
assert('Yield raises when called on root fiber') do
|
||||
assert_raise(FiberError) { Fiber.yield }
|
||||
end
|
||||
|
||||
assert('Double resume of Fiber') do
|
||||
f1 = Fiber.new {}
|
||||
f2 = Fiber.new {
|
||||
f1.resume
|
||||
assert_raise(FiberError) { f2.resume }
|
||||
Fiber.yield 0
|
||||
}
|
||||
assert_equal 0, f2.resume
|
||||
f2.resume
|
||||
assert_false f1.alive?
|
||||
assert_false f2.alive?
|
||||
end
|
||||
|
||||
assert('Recursive resume of Fiber') do
|
||||
f1, f2 = nil, nil
|
||||
f1 = Fiber.new { assert_raise(FiberError) { f2.resume } }
|
||||
f2 = Fiber.new {
|
||||
f1.resume
|
||||
Fiber.yield 0
|
||||
}
|
||||
f3 = Fiber.new {
|
||||
f2.resume
|
||||
}
|
||||
assert_equal 0, f3.resume
|
||||
f2.resume
|
||||
assert_false f1.alive?
|
||||
assert_false f2.alive?
|
||||
assert_false f3.alive?
|
||||
end
|
||||
|
||||
assert('Root fiber resume') do
|
||||
root = Fiber.current
|
||||
assert_raise(FiberError) { root.resume }
|
||||
f = Fiber.new {
|
||||
assert_raise(FiberError) { root.resume }
|
||||
}
|
||||
f.resume
|
||||
assert_false f.alive?
|
||||
end
|
||||
|
||||
assert('Fiber without block') do
|
||||
assert_raise(ArgumentError) { Fiber.new }
|
||||
end
|
||||
|
||||
|
||||
assert('Transfer to self.') do
|
||||
result = []
|
||||
f = Fiber.new { result << :start; f.transfer; result << :end }
|
||||
f.transfer
|
||||
assert_equal [:start, :end], result
|
||||
|
||||
result = []
|
||||
f = Fiber.new { result << :start; f.transfer; result << :end }
|
||||
f.resume
|
||||
assert_equal [:start, :end], result
|
||||
end
|
||||
|
||||
assert('Resume transferred fiber') do
|
||||
f = Fiber.new {
|
||||
assert_raise(FiberError) { f.resume }
|
||||
}
|
||||
f.transfer
|
||||
end
|
||||
|
||||
assert('Root fiber transfer.') do
|
||||
result = nil
|
||||
root = Fiber.current
|
||||
f = Fiber.new {
|
||||
result = :ok
|
||||
root.transfer
|
||||
}
|
||||
f.resume
|
||||
assert_true f.alive?
|
||||
assert_equal :ok, result
|
||||
end
|
||||
|
||||
assert('Break nested fiber with root fiber transfer') do
|
||||
root = Fiber.current
|
||||
|
||||
result = nil
|
||||
f2 = nil
|
||||
f1 = Fiber.new {
|
||||
Fiber.yield f2.resume
|
||||
result = :f1
|
||||
}
|
||||
f2 = Fiber.new {
|
||||
result = :to_root
|
||||
root.transfer :from_f2
|
||||
result = :f2
|
||||
}
|
||||
assert_equal :from_f2, f1.resume
|
||||
assert_equal :to_root, result
|
||||
assert_equal :f2, f2.transfer
|
||||
assert_equal :f2, result
|
||||
assert_false f2.alive?
|
||||
assert_equal :f1, f1.resume
|
||||
assert_equal :f1, result
|
||||
assert_false f1.alive?
|
||||
end
|
||||
|
||||
assert('CRuby Fiber#transfer test.') do
|
||||
ary = []
|
||||
f2 = nil
|
||||
f1 = Fiber.new{
|
||||
ary << f2.transfer(:foo)
|
||||
:ok
|
||||
}
|
||||
f2 = Fiber.new{
|
||||
ary << f1.transfer(:baz)
|
||||
:ng
|
||||
}
|
||||
assert_equal :ok, f1.transfer
|
||||
assert_equal [:baz], ary
|
||||
end
|
||||
@@ -0,0 +1,6 @@
|
||||
MRuby::Gem::Specification.new('mruby-hash-ext') do |spec|
|
||||
spec.license = 'MIT'
|
||||
spec.author = 'mruby developers'
|
||||
spec.summary = 'Hash class extension'
|
||||
spec.add_dependency 'mruby-array-ext', core: 'mruby-array-ext'
|
||||
end
|
||||
@@ -0,0 +1,500 @@
|
||||
class Hash
|
||||
|
||||
# ISO does not define Hash#each_pair, so each_pair is defined in gem.
|
||||
alias each_pair each
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# Hash[ key, value, ... ] -> new_hash
|
||||
# Hash[ [ [key, value], ... ] ] -> new_hash
|
||||
# Hash[ object ] -> new_hash
|
||||
#
|
||||
# Creates a new hash populated with the given objects.
|
||||
#
|
||||
# Similar to the literal `{ _key_ => _value_, ... }`. In the first
|
||||
# form, keys and values occur in pairs, so there must be an even number of
|
||||
# arguments.
|
||||
#
|
||||
# The second and third form take a single argument which is either an array
|
||||
# of key-value pairs or an object convertible to a hash.
|
||||
#
|
||||
# Hash["a", 100, "b", 200] #=> {"a"=>100, "b"=>200}
|
||||
# Hash[ [ ["a", 100], ["b", 200] ] ] #=> {"a"=>100, "b"=>200}
|
||||
# Hash["a" => 100, "b" => 200] #=> {"a"=>100, "b"=>200}
|
||||
#
|
||||
|
||||
def self.[](*object)
|
||||
length = object.length
|
||||
if length == 1
|
||||
o = object[0]
|
||||
if Hash === o
|
||||
h = self.new
|
||||
o.each { |k, v| h[k] = v }
|
||||
return h
|
||||
elsif o.respond_to?(:to_a)
|
||||
h = self.new
|
||||
o.to_a.each do |i|
|
||||
raise ArgumentError, "wrong element type #{i.class} (expected array)" unless i.respond_to?(:to_a)
|
||||
k, v = nil
|
||||
case i.size
|
||||
when 2
|
||||
k = i[0]
|
||||
v = i[1]
|
||||
when 1
|
||||
k = i[0]
|
||||
else
|
||||
raise ArgumentError, "invalid number of elements (#{i.size} for 1..2)"
|
||||
end
|
||||
h[k] = v
|
||||
end
|
||||
return h
|
||||
end
|
||||
end
|
||||
unless length % 2 == 0
|
||||
raise ArgumentError, 'odd number of arguments for Hash'
|
||||
end
|
||||
h = self.new
|
||||
0.step(length - 2, 2) do |i|
|
||||
h[object[i]] = object[i + 1]
|
||||
end
|
||||
h
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# hsh.merge!(other_hash) -> hsh
|
||||
# hsh.merge!(other_hash){|key, oldval, newval| block} -> hsh
|
||||
#
|
||||
# Adds the contents of _other_hash_ to _hsh_. If no block is specified,
|
||||
# entries with duplicate keys are overwritten with the values from
|
||||
# _other_hash_, otherwise the value of each duplicate key is determined by
|
||||
# calling the block with the key, its value in _hsh_ and its value in
|
||||
# _other_hash_.
|
||||
#
|
||||
# h1 = { "a" => 100, "b" => 200 }
|
||||
# h2 = { "b" => 254, "c" => 300 }
|
||||
# h1.merge!(h2) #=> {"a"=>100, "b"=>254, "c"=>300}
|
||||
#
|
||||
# h1 = { "a" => 100, "b" => 200 }
|
||||
# h2 = { "b" => 254, "c" => 300 }
|
||||
# h1.merge!(h2) { |key, v1, v2| v1 }
|
||||
# #=> {"a"=>100, "b"=>200, "c"=>300}
|
||||
#
|
||||
|
||||
def merge!(other, &block)
|
||||
raise TypeError, "Hash required (#{other.class} given)" unless Hash === other
|
||||
if block
|
||||
other.each_key{|k|
|
||||
self[k] = (self.has_key?(k))? block.call(k, self[k], other[k]): other[k]
|
||||
}
|
||||
else
|
||||
other.each_key{|k| self[k] = other[k]}
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
alias update merge!
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# hsh.compact! -> hsh
|
||||
#
|
||||
# Removes all nil values from the hash. Returns the hash.
|
||||
# Returns nil if the hash does not contain nil values.
|
||||
#
|
||||
# h = { a: 1, b: false, c: nil }
|
||||
# h.compact! #=> { a: 1, b: false }
|
||||
#
|
||||
|
||||
def compact!
|
||||
keys = self.keys
|
||||
nk = keys.select{|k|
|
||||
self[k] != nil
|
||||
}
|
||||
return nil if (keys.size == nk.size)
|
||||
h = {}
|
||||
nk.each {|k|
|
||||
h[k] = self[k]
|
||||
}
|
||||
h
|
||||
self.replace(h)
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# hsh.compact -> new_hsh
|
||||
#
|
||||
# Returns a new hash with the nil values/key pairs removed
|
||||
#
|
||||
# h = { a: 1, b: false, c: nil }
|
||||
# h.compact #=> { a: 1, b: false }
|
||||
# h #=> { a: 1, b: false, c: nil }
|
||||
#
|
||||
def compact
|
||||
h = {}
|
||||
self.keys.select{|k|
|
||||
self[k] != nil
|
||||
}.each {|k|
|
||||
h[k] = self[k]
|
||||
}
|
||||
h
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# hsh.fetch(key [, default] ) -> obj
|
||||
# hsh.fetch(key) {| key | block } -> obj
|
||||
#
|
||||
# Returns a value from the hash for the given key. If the key can't be
|
||||
# found, there are several options: With no other arguments, it will
|
||||
# raise an <code>KeyError</code> exception; if <i>default</i> is
|
||||
# given, then that will be returned; if the optional code block is
|
||||
# specified, then that will be run and its result returned.
|
||||
#
|
||||
# h = { "a" => 100, "b" => 200 }
|
||||
# h.fetch("a") #=> 100
|
||||
# h.fetch("z", "go fish") #=> "go fish"
|
||||
# h.fetch("z") { |el| "go fish, #{el}"} #=> "go fish, z"
|
||||
#
|
||||
# The following example shows that an exception is raised if the key
|
||||
# is not found and a default value is not supplied.
|
||||
#
|
||||
# h = { "a" => 100, "b" => 200 }
|
||||
# h.fetch("z")
|
||||
#
|
||||
# <em>produces:</em>
|
||||
#
|
||||
# prog.rb:2:in 'fetch': key not found (KeyError)
|
||||
# from prog.rb:2
|
||||
#
|
||||
|
||||
def fetch(key, none=NONE, &block)
|
||||
unless self.key?(key)
|
||||
if block
|
||||
block.call(key)
|
||||
elsif none != NONE
|
||||
none
|
||||
else
|
||||
raise KeyError, "Key not found: #{key.inspect}"
|
||||
end
|
||||
else
|
||||
self[key]
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# hsh.delete_if {| key, value | block } -> hsh
|
||||
# hsh.delete_if -> an_enumerator
|
||||
#
|
||||
# Deletes every key-value pair from <i>hsh</i> for which <i>block</i>
|
||||
# evaluates to <code>true</code>.
|
||||
#
|
||||
# If no block is given, an enumerator is returned instead.
|
||||
#
|
||||
# h = { "a" => 100, "b" => 200, "c" => 300 }
|
||||
# h.delete_if {|key, value| key >= "b" } #=> {"a"=>100}
|
||||
#
|
||||
|
||||
def delete_if(&block)
|
||||
return to_enum :delete_if unless block
|
||||
|
||||
self.each do |k, v|
|
||||
self.delete(k) if block.call(k, v)
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# hash.flatten -> an_array
|
||||
# hash.flatten(level) -> an_array
|
||||
#
|
||||
# Returns a new array that is a one-dimensional flattening of this
|
||||
# hash. That is, for every key or value that is an array, extract
|
||||
# its elements into the new array. Unlike Array#flatten, this
|
||||
# method does not flatten recursively by default. The optional
|
||||
# <i>level</i> argument determines the level of recursion to flatten.
|
||||
#
|
||||
# a = {1=> "one", 2 => [2,"two"], 3 => "three"}
|
||||
# a.flatten # => [1, "one", 2, [2, "two"], 3, "three"]
|
||||
# a.flatten(2) # => [1, "one", 2, 2, "two", 3, "three"]
|
||||
#
|
||||
|
||||
def flatten(level=1)
|
||||
self.to_a.flatten(level)
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# hsh.invert -> new_hash
|
||||
#
|
||||
# Returns a new hash created by using <i>hsh</i>'s values as keys, and
|
||||
# the keys as values.
|
||||
#
|
||||
# h = { "n" => 100, "m" => 100, "y" => 300, "d" => 200, "a" => 0 }
|
||||
# h.invert #=> {0=>"a", 100=>"m", 200=>"d", 300=>"y"}
|
||||
#
|
||||
|
||||
def invert
|
||||
h = self.class.new
|
||||
self.each {|k, v| h[v] = k }
|
||||
h
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# hsh.keep_if {| key, value | block } -> hsh
|
||||
# hsh.keep_if -> an_enumerator
|
||||
#
|
||||
# Deletes every key-value pair from <i>hsh</i> for which <i>block</i>
|
||||
# evaluates to false.
|
||||
#
|
||||
# If no block is given, an enumerator is returned instead.
|
||||
#
|
||||
|
||||
def keep_if(&block)
|
||||
return to_enum :keep_if unless block
|
||||
|
||||
keys = []
|
||||
self.each do |k, v|
|
||||
unless block.call([k, v])
|
||||
self.delete(k)
|
||||
end
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# hsh.key(value) -> key
|
||||
#
|
||||
# Returns the key of an occurrence of a given value. If the value is
|
||||
# not found, returns <code>nil</code>.
|
||||
#
|
||||
# h = { "a" => 100, "b" => 200, "c" => 300, "d" => 300 }
|
||||
# h.key(200) #=> "b"
|
||||
# h.key(300) #=> "c"
|
||||
# h.key(999) #=> nil
|
||||
#
|
||||
|
||||
def key(val)
|
||||
self.each do |k, v|
|
||||
return k if v == val
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# hsh.to_h -> hsh or new_hash
|
||||
#
|
||||
# Returns +self+. If called on a subclass of Hash, converts
|
||||
# the receiver to a Hash object.
|
||||
#
|
||||
def to_h
|
||||
self
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# hash < other -> true or false
|
||||
#
|
||||
# Returns <code>true</code> if <i>hash</i> is subset of
|
||||
# <i>other</i>.
|
||||
#
|
||||
# h1 = {a:1, b:2}
|
||||
# h2 = {a:1, b:2, c:3}
|
||||
# h1 < h2 #=> true
|
||||
# h2 < h1 #=> false
|
||||
# h1 < h1 #=> false
|
||||
#
|
||||
def <(hash)
|
||||
raise TypeError, "can't convert #{hash.class} to Hash" unless Hash === hash
|
||||
size < hash.size and all? {|key, val|
|
||||
hash.key?(key) and hash[key] == val
|
||||
}
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# hash <= other -> true or false
|
||||
#
|
||||
# Returns <code>true</code> if <i>hash</i> is subset of
|
||||
# <i>other</i> or equals to <i>other</i>.
|
||||
#
|
||||
# h1 = {a:1, b:2}
|
||||
# h2 = {a:1, b:2, c:3}
|
||||
# h1 <= h2 #=> true
|
||||
# h2 <= h1 #=> false
|
||||
# h1 <= h1 #=> true
|
||||
#
|
||||
def <=(hash)
|
||||
raise TypeError, "can't convert #{hash.class} to Hash" unless Hash === hash
|
||||
size <= hash.size and all? {|key, val|
|
||||
hash.key?(key) and hash[key] == val
|
||||
}
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# hash > other -> true or false
|
||||
#
|
||||
# Returns <code>true</code> if <i>other</i> is subset of
|
||||
# <i>hash</i>.
|
||||
#
|
||||
# h1 = {a:1, b:2}
|
||||
# h2 = {a:1, b:2, c:3}
|
||||
# h1 > h2 #=> false
|
||||
# h2 > h1 #=> true
|
||||
# h1 > h1 #=> false
|
||||
#
|
||||
def >(hash)
|
||||
raise TypeError, "can't convert #{hash.class} to Hash" unless Hash === hash
|
||||
size > hash.size and hash.all? {|key, val|
|
||||
key?(key) and self[key] == val
|
||||
}
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# hash >= other -> true or false
|
||||
#
|
||||
# Returns <code>true</code> if <i>other</i> is subset of
|
||||
# <i>hash</i> or equals to <i>hash</i>.
|
||||
#
|
||||
# h1 = {a:1, b:2}
|
||||
# h2 = {a:1, b:2, c:3}
|
||||
# h1 >= h2 #=> false
|
||||
# h2 >= h1 #=> true
|
||||
# h1 >= h1 #=> true
|
||||
#
|
||||
def >=(hash)
|
||||
raise TypeError, "can't convert #{hash.class} to Hash" unless Hash === hash
|
||||
size >= hash.size and hash.all? {|key, val|
|
||||
key?(key) and self[key] == val
|
||||
}
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# hsh.dig(key,...) -> object
|
||||
#
|
||||
# Extracts the nested value specified by the sequence of <i>key</i>
|
||||
# objects by calling +dig+ at each step, returning +nil+ if any
|
||||
# intermediate step is +nil+.
|
||||
#
|
||||
def dig(idx,*args)
|
||||
n = self[idx]
|
||||
if args.size > 0
|
||||
n&.dig(*args)
|
||||
else
|
||||
n
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# hsh.transform_keys {|key| block } -> new_hash
|
||||
# hsh.transform_keys -> an_enumerator
|
||||
#
|
||||
# Returns a new hash, with the keys computed from running the block
|
||||
# once for each key in the hash, and the values unchanged.
|
||||
#
|
||||
# If no block is given, an enumerator is returned instead.
|
||||
#
|
||||
def transform_keys(&block)
|
||||
return to_enum :transform_keys unless block
|
||||
hash = {}
|
||||
self.keys.each do |k|
|
||||
new_key = block.call(k)
|
||||
hash[new_key] = self[k]
|
||||
end
|
||||
hash
|
||||
end
|
||||
##
|
||||
# call-seq:
|
||||
# hsh.transform_keys! {|key| block } -> hsh
|
||||
# hsh.transform_keys! -> an_enumerator
|
||||
#
|
||||
# Invokes the given block once for each key in <i>hsh</i>, replacing it
|
||||
# with the new key returned by the block, and then returns <i>hsh</i>.
|
||||
#
|
||||
# If no block is given, an enumerator is returned instead.
|
||||
#
|
||||
def transform_keys!(&block)
|
||||
return to_enum :transform_keys! unless block
|
||||
self.keys.each do |k|
|
||||
value = self[k]
|
||||
self.__delete(k)
|
||||
k = block.call(k) if block
|
||||
self[k] = value
|
||||
end
|
||||
self
|
||||
end
|
||||
##
|
||||
# call-seq:
|
||||
# hsh.transform_values {|value| block } -> new_hash
|
||||
# hsh.transform_values -> an_enumerator
|
||||
#
|
||||
# Returns a new hash with the results of running the block once for
|
||||
# every value.
|
||||
# This method does not change the keys.
|
||||
#
|
||||
# If no block is given, an enumerator is returned instead.
|
||||
#
|
||||
def transform_values(&b)
|
||||
return to_enum :transform_values unless block_given?
|
||||
hash = {}
|
||||
self.keys.each do |k|
|
||||
hash[k] = yield(self[k])
|
||||
end
|
||||
hash
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# hsh.transform_values! {|key| block } -> hsh
|
||||
# hsh.transform_values! -> an_enumerator
|
||||
#
|
||||
# Invokes the given block once for each value in the hash, replacing
|
||||
# with the new value returned by the block, and then returns <i>hsh</i>.
|
||||
#
|
||||
# If no block is given, an enumerator is returned instead.
|
||||
#
|
||||
def transform_values!(&b)
|
||||
return to_enum :transform_values! unless block_given?
|
||||
self.keys.each do |k|
|
||||
self[k] = yield(self[k])
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
def to_proc
|
||||
->x{self[x]}
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# hsh.fetch_values(key, ...) -> array
|
||||
# hsh.fetch_values(key, ...) { |key| block } -> array
|
||||
#
|
||||
# Returns an array containing the values associated with the given keys
|
||||
# but also raises <code>KeyError</code> when one of keys can't be found.
|
||||
# Also see <code>Hash#values_at</code> and <code>Hash#fetch</code>.
|
||||
#
|
||||
# h = { "cat" => "feline", "dog" => "canine", "cow" => "bovine" }
|
||||
#
|
||||
# h.fetch_values("cow", "cat") #=> ["bovine", "feline"]
|
||||
# h.fetch_values("cow", "bird") # raises KeyError
|
||||
# h.fetch_values("cow", "bird") { |k| k.upcase } #=> ["bovine", "BIRD"]
|
||||
#
|
||||
def fetch_values(*keys, &block)
|
||||
keys.map do |k|
|
||||
self.fetch(k, &block)
|
||||
end
|
||||
end
|
||||
|
||||
alias filter select
|
||||
alias filter! select!
|
||||
end
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
** hash.c - Hash class
|
||||
**
|
||||
** See Copyright Notice in mruby.h
|
||||
*/
|
||||
|
||||
#include <mruby.h>
|
||||
#include <mruby/array.h>
|
||||
#include <mruby/hash.h>
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* hsh.values_at(key, ...) -> array
|
||||
*
|
||||
* Return an array containing the values associated with the given keys.
|
||||
* Also see <code>Hash.select</code>.
|
||||
*
|
||||
* h = { "cat" => "feline", "dog" => "canine", "cow" => "bovine" }
|
||||
* h.values_at("cow", "cat") #=> ["bovine", "feline"]
|
||||
*/
|
||||
|
||||
static mrb_value
|
||||
hash_values_at(mrb_state *mrb, mrb_value hash)
|
||||
{
|
||||
mrb_value *argv, result;
|
||||
mrb_int argc, i;
|
||||
int ai;
|
||||
|
||||
mrb_get_args(mrb, "*", &argv, &argc);
|
||||
result = mrb_ary_new_capa(mrb, argc);
|
||||
ai = mrb_gc_arena_save(mrb);
|
||||
for (i = 0; i < argc; i++) {
|
||||
mrb_ary_push(mrb, result, mrb_hash_get(mrb, hash, argv[i]));
|
||||
mrb_gc_arena_restore(mrb, ai);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* hsh.slice(*keys) -> a_hash
|
||||
*
|
||||
* Returns a hash containing only the given keys and their values.
|
||||
*
|
||||
* h = { a: 100, b: 200, c: 300 }
|
||||
* h.slice(:a) #=> {:a=>100}
|
||||
* h.slice(:b, :c, :d) #=> {:b=>200, :c=>300}
|
||||
*/
|
||||
static mrb_value
|
||||
hash_slice(mrb_state *mrb, mrb_value hash)
|
||||
{
|
||||
mrb_value *argv, result;
|
||||
mrb_int argc, i;
|
||||
|
||||
mrb_get_args(mrb, "*", &argv, &argc);
|
||||
result = mrb_hash_new_capa(mrb, argc);
|
||||
if (argc == 0) return result; /* empty hash */
|
||||
for (i = 0; i < argc; i++) {
|
||||
mrb_value key = argv[i];
|
||||
mrb_value val;
|
||||
|
||||
val = mrb_hash_fetch(mrb, hash, key, mrb_undef_value());
|
||||
if (!mrb_undef_p(val)) {
|
||||
mrb_hash_set(mrb, result, key, val);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void
|
||||
mrb_mruby_hash_ext_gem_init(mrb_state *mrb)
|
||||
{
|
||||
struct RClass *h;
|
||||
|
||||
h = mrb->hash_class;
|
||||
mrb_define_method(mrb, h, "values_at", hash_values_at, MRB_ARGS_ANY());
|
||||
mrb_define_method(mrb, h, "slice", hash_slice, MRB_ARGS_ANY());
|
||||
}
|
||||
|
||||
void
|
||||
mrb_mruby_hash_ext_gem_final(mrb_state *mrb)
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
##
|
||||
# Hash(Ext) Test
|
||||
|
||||
assert('Hash.[] Hash') do
|
||||
a = Hash['a_key' => 'a_value']
|
||||
|
||||
assert_equal({'a_key' => 'a_value'}, a)
|
||||
end
|
||||
|
||||
assert('Hash.[] [ [ ["b_key", "b_value" ] ] ]') do
|
||||
a = Hash[ [ ['b_key', 'b_value'] ] ]
|
||||
|
||||
assert_equal({'b_key' => 'b_value'}, a)
|
||||
|
||||
a = Hash[ [ ] ]
|
||||
|
||||
assert_equal({}, a)
|
||||
|
||||
assert_raise(ArgumentError) do
|
||||
Hash[ [ ['b_key', 'b_value', 'b_over'] ] ]
|
||||
end
|
||||
|
||||
assert_raise(ArgumentError) do
|
||||
Hash[ [ [] ] ]
|
||||
end
|
||||
end
|
||||
|
||||
assert('Hash.[] "c_key", "c_value"') do
|
||||
a = Hash['c_key', 'c_value', 'd_key', 1]
|
||||
|
||||
assert_equal({'c_key' => 'c_value', 'd_key' => 1}, a)
|
||||
|
||||
a = Hash[]
|
||||
|
||||
assert_equal({}, a)
|
||||
|
||||
assert_raise(ArgumentError) do
|
||||
Hash['d_key']
|
||||
end
|
||||
end
|
||||
|
||||
assert('Hash.[] for sub class') do
|
||||
sub_hash_class = Class.new(Hash)
|
||||
sub_hash = sub_hash_class[]
|
||||
assert_equal(sub_hash_class, sub_hash.class)
|
||||
end
|
||||
|
||||
assert('Hash#merge!') do
|
||||
a = { 'abc_key' => 'abc_value', 'cba_key' => 'cba_value' }
|
||||
b = { 'cba_key' => 'XXX', 'xyz_key' => 'xyz_value' }
|
||||
|
||||
result_1 = a.merge! b
|
||||
|
||||
a = { 'abc_key' => 'abc_value', 'cba_key' => 'cba_value' }
|
||||
result_2 = a.merge!(b) do |key, original, new|
|
||||
original
|
||||
end
|
||||
|
||||
assert_equal({'abc_key' => 'abc_value', 'cba_key' => 'XXX',
|
||||
'xyz_key' => 'xyz_value' }, result_1)
|
||||
assert_equal({'abc_key' => 'abc_value', 'cba_key' => 'cba_value',
|
||||
'xyz_key' => 'xyz_value' }, result_2)
|
||||
|
||||
assert_raise(TypeError) do
|
||||
{ 'abc_key' => 'abc_value' }.merge! "a"
|
||||
end
|
||||
end
|
||||
|
||||
assert('Hash#values_at') do
|
||||
h = { "cat" => "feline", "dog" => "canine", "cow" => "bovine" }
|
||||
assert_equal ["bovine", "feline"], h.values_at("cow", "cat")
|
||||
|
||||
keys = []
|
||||
(0...1000).each { |v| keys.push "#{v}" }
|
||||
h = Hash.new { |hash,k| hash[k] = k }
|
||||
assert_equal keys, h.values_at(*keys)
|
||||
end
|
||||
|
||||
assert('Hash#compact') do
|
||||
h = { "cat" => "feline", "dog" => nil, "cow" => false }
|
||||
|
||||
assert_equal({ "cat" => "feline", "cow" => false }, h.compact)
|
||||
assert_equal({ "cat" => "feline", "dog" => nil, "cow" => false }, h)
|
||||
end
|
||||
|
||||
assert('Hash#compact!') do
|
||||
h = { "cat" => "feline", "dog" => nil, "cow" => false }
|
||||
|
||||
h.compact!
|
||||
assert_equal({ "cat" => "feline", "cow" => false }, h)
|
||||
end
|
||||
|
||||
assert('Hash#fetch') do
|
||||
h = { "cat" => "feline", "dog" => "canine", "cow" => "bovine" }
|
||||
assert_equal "feline", h.fetch("cat")
|
||||
assert_equal "mickey", h.fetch("mouse", "mickey")
|
||||
assert_equal "minny", h.fetch("mouse"){"minny"}
|
||||
assert_equal "mouse", h.fetch("mouse"){|k| k}
|
||||
assert_raise(KeyError) do
|
||||
h.fetch("gnu")
|
||||
end
|
||||
end
|
||||
|
||||
assert("Hash#delete_if") do
|
||||
base = { 1 => 'one', 2 => false, true => 'true', 'cat' => 99 }
|
||||
h1 = { 1 => 'one', 2 => false, true => 'true' }
|
||||
h2 = { 2 => false, 'cat' => 99 }
|
||||
h3 = { 2 => false }
|
||||
|
||||
h = base.dup
|
||||
assert_equal(h, h.delete_if { false })
|
||||
assert_equal({}, h.delete_if { true })
|
||||
|
||||
h = base.dup
|
||||
assert_equal(h1, h.delete_if {|k,v| k.instance_of?(String) })
|
||||
assert_equal(h1, h)
|
||||
|
||||
h = base.dup
|
||||
assert_equal(h2, h.delete_if {|k,v| v.instance_of?(String) })
|
||||
assert_equal(h2, h)
|
||||
|
||||
h = base.dup
|
||||
assert_equal(h3, h.delete_if {|k,v| v })
|
||||
assert_equal(h3, h)
|
||||
|
||||
h = base.dup
|
||||
n = 0
|
||||
h.delete_if {|*a|
|
||||
n += 1
|
||||
assert_equal(2, a.size)
|
||||
assert_equal(base[a[0]], a[1])
|
||||
h.shift
|
||||
true
|
||||
}
|
||||
assert_equal(base.size, n)
|
||||
end
|
||||
|
||||
assert("Hash#flatten") do
|
||||
a = {1=> "one", 2 => [2,"two"], 3 => [3, ["three"]]}
|
||||
assert_equal [1, "one", 2, [2, "two"], 3, [3, ["three"]]], a.flatten
|
||||
assert_equal [[1, "one"], [2, [2, "two"]], [3, [3, ["three"]]]], a.flatten(0)
|
||||
assert_equal [1, "one", 2, [2, "two"], 3, [3, ["three"]]], a.flatten(1)
|
||||
assert_equal [1, "one", 2, 2, "two", 3, 3, ["three"]], a.flatten(2)
|
||||
assert_equal [1, "one", 2, 2, "two", 3, 3, "three"], a.flatten(3)
|
||||
end
|
||||
|
||||
assert("Hash#invert") do
|
||||
h = { 1 => 'one', 2 => 'two', 3 => 'three',
|
||||
true => 'true', nil => 'nil' }.invert
|
||||
assert_equal 1, h['one']
|
||||
assert_equal true, h['true']
|
||||
assert_equal nil, h['nil']
|
||||
|
||||
h = { 'a' => 1, 'b' => 2, 'c' => 1 }.invert
|
||||
assert_equal(2, h.length)
|
||||
assert_include(%w[a c], h[1])
|
||||
assert_equal('b', h[2])
|
||||
end
|
||||
|
||||
assert("Hash#invert with sub class") do
|
||||
sub_hash_class = Class.new(Hash)
|
||||
sub_hash = sub_hash_class.new
|
||||
assert_equal(sub_hash_class, sub_hash.invert.class)
|
||||
end
|
||||
|
||||
assert("Hash#keep_if") do
|
||||
h = { 1 => 2, 3 => 4, 5 => 6 }
|
||||
assert_equal({3=>4,5=>6}, h.keep_if {|k, v| k + v >= 7 })
|
||||
h = { 1 => 2, 3 => 4, 5 => 6 }
|
||||
assert_equal({ 1 => 2, 3=> 4, 5 =>6} , h.keep_if { true })
|
||||
end
|
||||
|
||||
assert("Hash#key") do
|
||||
h = { "a" => 100, "b" => 200, "c" => 300, "d" => 300, nil => 'nil', 'nil' => nil }
|
||||
assert_equal "b", h.key(200)
|
||||
assert_equal "c", h.key(300)
|
||||
assert_nil h.key(999)
|
||||
assert_nil h.key('nil')
|
||||
assert_equal 'nil', h.key(nil)
|
||||
end
|
||||
|
||||
assert("Hash#to_h") do
|
||||
h = { "a" => 100, "b" => 200 }
|
||||
assert_equal Hash, h.to_h.class
|
||||
assert_equal h, h.to_h
|
||||
end
|
||||
|
||||
assert('Hash#<') do
|
||||
h1 = {a:1, b:2}
|
||||
h2 = {a:1, b:2, c:3}
|
||||
|
||||
assert_false(h1 < h1)
|
||||
assert_true(h1 < h2)
|
||||
assert_false(h2 < h1)
|
||||
assert_false(h2 < h2)
|
||||
|
||||
h1 = {a:1}
|
||||
h2 = {a:2}
|
||||
|
||||
assert_false(h1 < h1)
|
||||
assert_false(h1 < h2)
|
||||
assert_false(h2 < h1)
|
||||
assert_false(h2 < h2)
|
||||
end
|
||||
|
||||
assert('Hash#<=') do
|
||||
h1 = {a:1, b:2}
|
||||
h2 = {a:1, b:2, c:3}
|
||||
|
||||
assert_true(h1 <= h1)
|
||||
assert_true(h1 <= h2)
|
||||
assert_false(h2 <= h1)
|
||||
assert_true(h2 <= h2)
|
||||
|
||||
h1 = {a:1}
|
||||
h2 = {a:2}
|
||||
|
||||
assert_true(h1 <= h1)
|
||||
assert_false(h1 <= h2)
|
||||
assert_false(h2 <= h1)
|
||||
assert_true(h2 <= h2)
|
||||
end
|
||||
|
||||
assert('Hash#>=') do
|
||||
h1 = {a:1, b:2}
|
||||
h2 = {a:1, b:2, c:3}
|
||||
|
||||
assert_true(h1 >= h1)
|
||||
assert_false(h1 >= h2)
|
||||
assert_true(h2 >= h1)
|
||||
assert_true(h2 >= h2)
|
||||
|
||||
h1 = {a:1}
|
||||
h2 = {a:2}
|
||||
|
||||
assert_true(h1 >= h1)
|
||||
assert_false(h1 >= h2)
|
||||
assert_false(h2 >= h1)
|
||||
assert_true(h2 >= h2)
|
||||
end
|
||||
|
||||
assert('Hash#>') do
|
||||
h1 = {a:1, b:2}
|
||||
h2 = {a:1, b:2, c:3}
|
||||
|
||||
assert_false(h1 > h1)
|
||||
assert_false(h1 > h2)
|
||||
assert_true(h2 > h1)
|
||||
assert_false(h2 > h2)
|
||||
|
||||
h1 = {a:1}
|
||||
h2 = {a:2}
|
||||
|
||||
assert_false(h1 > h1)
|
||||
assert_false(h1 > h2)
|
||||
assert_false(h2 > h1)
|
||||
assert_false(h2 > h2)
|
||||
end
|
||||
|
||||
assert("Hash#dig") do
|
||||
h = {a:{b:{c:1}}}
|
||||
assert_equal(1, h.dig(:a, :b, :c))
|
||||
assert_nil(h.dig(:d))
|
||||
end
|
||||
|
||||
assert("Hash#transform_keys") do
|
||||
h = {"1" => 100, "2" => 200}
|
||||
assert_equal({"1!" => 100, "2!" => 200},
|
||||
h.transform_keys{|k| k+"!"})
|
||||
assert_equal({1 => 100, 2 => 200},
|
||||
h.transform_keys{|k|k.to_i})
|
||||
assert_same(h, h.transform_keys!{|k|k.to_i})
|
||||
assert_equal({1 => 100, 2 => 200}, h)
|
||||
end
|
||||
|
||||
assert("Hash#transform_values") do
|
||||
h = {a: 1, b: 2, c: 3}
|
||||
assert_equal({a: 2, b: 5, c: 10},
|
||||
h.transform_values{|v| v * v + 1})
|
||||
assert_equal({a: "1", b: "2", c: "3"},
|
||||
h.transform_values{|v|v.to_s})
|
||||
assert_same(h, h.transform_values!{|v|v.to_s})
|
||||
assert_equal({a: "1", b: "2", c: "3"}, h)
|
||||
end
|
||||
|
||||
assert("Hash#slice") do
|
||||
h = { a: 100, b: 200, c: 300 }
|
||||
assert_equal({:a=>100}, h.slice(:a))
|
||||
assert_equal({:b=>200, :c=>300}, h.slice(:b, :c, :d))
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
MRuby::Gem::Specification.new('mruby-inline-struct') do |spec|
|
||||
spec.license = 'MIT'
|
||||
spec.author = 'mruby developers'
|
||||
spec.summary = 'inline structure'
|
||||
end
|
||||
@@ -0,0 +1,84 @@
|
||||
#include <mruby.h>
|
||||
#include <mruby/class.h>
|
||||
#include <mruby/string.h>
|
||||
#include <mruby/istruct.h>
|
||||
|
||||
static mrb_value
|
||||
istruct_test_initialize(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
char *string = (char*)mrb_istruct_ptr(self);
|
||||
mrb_int size = mrb_istruct_size();
|
||||
mrb_value object = mrb_get_arg1(mrb);
|
||||
|
||||
if (mrb_fixnum_p(object)) {
|
||||
strncpy(string, "fixnum", size-1);
|
||||
}
|
||||
#ifndef MRB_WITHOUT_FLOAT
|
||||
else if (mrb_float_p(object)) {
|
||||
strncpy(string, "float", size-1);
|
||||
}
|
||||
#endif
|
||||
else if (mrb_string_p(object)) {
|
||||
strncpy(string, "string", size-1);
|
||||
}
|
||||
else {
|
||||
strncpy(string, "anything", size-1);
|
||||
}
|
||||
|
||||
string[size - 1] = 0; // force NULL at the end
|
||||
return self;
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
istruct_test_to_s(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
return mrb_str_new_cstr(mrb, (const char*)mrb_istruct_ptr(self));
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
istruct_test_length(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
return mrb_fixnum_value(mrb_istruct_size());
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
istruct_test_test_receive(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
mrb_value object = mrb_get_arg1(mrb);
|
||||
|
||||
if (mrb_obj_class(mrb, object) != mrb_class_get(mrb, "InlineStructTest"))
|
||||
{
|
||||
mrb_raise(mrb, E_TYPE_ERROR, "Expected InlineStructTest");
|
||||
}
|
||||
return mrb_bool_value(((char*)mrb_istruct_ptr(object))[0] == 's');
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
istruct_test_test_receive_direct(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
char *ptr;
|
||||
mrb_get_args(mrb, "I", &ptr);
|
||||
return mrb_bool_value(ptr[0] == 's');
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
istruct_test_mutate(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
char *ptr = (char*)mrb_istruct_ptr(self);
|
||||
memcpy(ptr, "mutate", 6);
|
||||
return mrb_nil_value();
|
||||
}
|
||||
|
||||
void mrb_mruby_inline_struct_gem_test(mrb_state *mrb)
|
||||
{
|
||||
struct RClass *cls;
|
||||
|
||||
cls = mrb_define_class(mrb, "InlineStructTest", mrb->object_class);
|
||||
MRB_SET_INSTANCE_TT(cls, MRB_TT_ISTRUCT);
|
||||
mrb_define_method(mrb, cls, "initialize", istruct_test_initialize, MRB_ARGS_REQ(1));
|
||||
mrb_define_method(mrb, cls, "to_s", istruct_test_to_s, MRB_ARGS_NONE());
|
||||
mrb_define_method(mrb, cls, "mutate", istruct_test_mutate, MRB_ARGS_NONE());
|
||||
mrb_define_class_method(mrb, cls, "length", istruct_test_length, MRB_ARGS_NONE());
|
||||
mrb_define_class_method(mrb, cls, "test_receive", istruct_test_test_receive, MRB_ARGS_REQ(1));
|
||||
mrb_define_class_method(mrb, cls, "test_receive_direct", istruct_test_test_receive_direct, MRB_ARGS_REQ(1));
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
##
|
||||
# InlineStruct Test
|
||||
|
||||
class InlineStructTest
|
||||
def extra_method
|
||||
:ok
|
||||
end
|
||||
|
||||
def test_ivar_set
|
||||
@var = :ivar
|
||||
end
|
||||
|
||||
def test_ivar_get
|
||||
@vat
|
||||
end
|
||||
end
|
||||
|
||||
assert('InlineStructTest#dup') do
|
||||
obj = InlineStructTest.new(1)
|
||||
assert_equal obj.to_s, 'fixnum'
|
||||
assert_equal obj.dup.to_s, 'fixnum'
|
||||
end
|
||||
|
||||
assert('InlineStructTest#clone') do
|
||||
obj = InlineStructTest.new(1)
|
||||
assert_equal obj.to_s, 'fixnum'
|
||||
assert_equal obj.clone.to_s, 'fixnum'
|
||||
end
|
||||
|
||||
assert('InlineStruct#object_id') do
|
||||
obj1 = InlineStructTest.new(1)
|
||||
obj2 = InlineStructTest.new(1)
|
||||
assert_not_equal obj1, obj2
|
||||
assert_not_equal obj1.object_id, obj2.object_id
|
||||
assert_not_equal obj1.object_id, obj1.dup.object_id
|
||||
assert_not_equal obj1.object_id, obj1.clone.object_id
|
||||
end
|
||||
|
||||
assert('InlineStructTest#mutate (dup)') do
|
||||
obj1 = InlineStructTest.new("foo")
|
||||
assert_equal obj1.to_s, "string"
|
||||
obj2 = obj1.dup
|
||||
assert_equal obj2.to_s, "string"
|
||||
obj1.mutate
|
||||
assert_equal obj1.to_s, "mutate"
|
||||
assert_equal obj2.to_s, "string"
|
||||
end
|
||||
|
||||
assert('InlineStructTest#mutate (clone)') do
|
||||
obj1 = InlineStructTest.new("foo")
|
||||
assert_equal obj1.to_s, "string"
|
||||
obj2 = obj1.clone
|
||||
assert_equal obj2.to_s, "string"
|
||||
obj1.mutate
|
||||
assert_equal obj1.to_s, "mutate"
|
||||
assert_equal obj2.to_s, "string"
|
||||
end
|
||||
|
||||
assert('InlineStructTest#test_receive(string)') do
|
||||
assert_equal InlineStructTest.test_receive(InlineStructTest.new('a')), true
|
||||
end
|
||||
|
||||
assert('InlineStructTest#test_receive(float)') do
|
||||
assert_equal InlineStructTest.test_receive(InlineStructTest.new(1.25)), false
|
||||
end
|
||||
|
||||
assert('InlineStructTest#test_receive(invalid object)') do
|
||||
assert_raise(TypeError) do
|
||||
InlineStructTest.test_receive([])
|
||||
end
|
||||
end
|
||||
|
||||
assert('InlineStructTest#test_receive(string)') do
|
||||
assert_equal InlineStructTest.test_receive_direct(InlineStructTest.new('a')), true
|
||||
end
|
||||
|
||||
assert('InlineStructTest#test_receive(float)') do
|
||||
assert_equal InlineStructTest.test_receive_direct(InlineStructTest.new(1.25)), false
|
||||
end
|
||||
|
||||
assert('InlineStructTest#test_receive(invalid object)') do
|
||||
assert_raise(TypeError) do
|
||||
InlineStructTest.test_receive_direct([])
|
||||
end
|
||||
end
|
||||
|
||||
assert('InlineStructTest#extra_method') do
|
||||
assert_equal InlineStructTest.new(1).extra_method, :ok
|
||||
end
|
||||
|
||||
assert('InlineStructTest instance variable') do
|
||||
obj = InlineStructTest.new(1)
|
||||
assert_raise(ArgumentError) do
|
||||
obj.test_ivar_set
|
||||
end
|
||||
assert_equal obj.test_ivar_get, nil
|
||||
end
|
||||
|
||||
# 64-bit mode
|
||||
if InlineStructTest.length == 24
|
||||
assert('InlineStructTest length [64 bit]') do
|
||||
assert_equal InlineStructTest.length, 3 * 8
|
||||
end
|
||||
end
|
||||
|
||||
# 32-bit mode
|
||||
if InlineStructTest.length == 12
|
||||
assert('InlineStructTest length [32 bit]') do
|
||||
assert_equal InlineStructTest.length, 3 * 4
|
||||
end
|
||||
end
|
||||
|
||||
# 16-bit mode
|
||||
if InlineStructTest.length == 6
|
||||
assert('InlineStructTest length [16 bit]') do
|
||||
assert_equal InlineStructTest.length, 3 * 2
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,192 @@
|
||||
mruby-io
|
||||
========
|
||||
|
||||
`IO` and `File` classes for mruby
|
||||
|
||||
## Installation
|
||||
Add the line below to your `build_config.rb`:
|
||||
|
||||
```
|
||||
conf.gem core: 'mruby-io'
|
||||
```
|
||||
|
||||
## Implemented methods
|
||||
|
||||
### IO
|
||||
- http://doc.ruby-lang.org/ja/1.9.3/class/IO.html
|
||||
|
||||
| method | mruby-io | memo |
|
||||
| ------------------------- | -------- | ---- |
|
||||
| IO.binread | | |
|
||||
| IO.binwrite | | |
|
||||
| IO.copy_stream | | |
|
||||
| IO.new, IO.for_fd, IO.open | o | |
|
||||
| IO.foreach | | |
|
||||
| IO.pipe | o | |
|
||||
| IO.popen | o | |
|
||||
| IO.read | o | |
|
||||
| IO.readlines | | |
|
||||
| IO.select | o | |
|
||||
| IO.sysopen | o | |
|
||||
| IO.try_convert | | |
|
||||
| IO.write | | |
|
||||
| IO#<< | | |
|
||||
| IO#advise | | |
|
||||
| IO#autoclose= | | |
|
||||
| IO#autoclose? | | |
|
||||
| IO#binmode | | |
|
||||
| IO#binmode? | | |
|
||||
| IO#bytes | | obsolete |
|
||||
| IO#chars | | obsolete |
|
||||
| IO#clone, IO#dup | o | |
|
||||
| IO#close | o | |
|
||||
| IO#close_on_exec= | o | |
|
||||
| IO#close_on_exec? | o | |
|
||||
| IO#close_read | | |
|
||||
| IO#close_write | | |
|
||||
| IO#closed? | o | |
|
||||
| IO#codepoints | | obsolete |
|
||||
| IO#each_byte | o | |
|
||||
| IO#each_char | o | |
|
||||
| IO#each_codepoint | | |
|
||||
| IO#each_line | o | |
|
||||
| IO#eof, IO#eof? | o | |
|
||||
| IO#external_encoding | | |
|
||||
| IO#fcntl | | |
|
||||
| IO#fdatasync | | |
|
||||
| IO#fileno, IO#to_i | o | |
|
||||
| IO#flush | o | |
|
||||
| IO#fsync | | |
|
||||
| IO#getbyte | | |
|
||||
| IO#getc | o | |
|
||||
| IO#gets | o | |
|
||||
| IO#internal_encoding | | |
|
||||
| IO#ioctl | | |
|
||||
| IO#isatty, IO#tty? | o | |
|
||||
| IO#lineno | | |
|
||||
| IO#lineno= | | |
|
||||
| IO#lines | | obsolete |
|
||||
| IO#pid | o | |
|
||||
| IO#pos, IO#tell | o | |
|
||||
| IO#pos= | o | |
|
||||
| IO#print | o | |
|
||||
| IO#printf | o | |
|
||||
| IO#putc | | |
|
||||
| IO#puts | o | |
|
||||
| IO#read | o | |
|
||||
| IO#read_nonblock | | |
|
||||
| IO#readbyte | | |
|
||||
| IO#readchar | o | |
|
||||
| IO#readline | o | |
|
||||
| IO#readlines | o | |
|
||||
| IO#readpartial | | |
|
||||
| IO#reopen | | |
|
||||
| IO#rewind | | |
|
||||
| IO#seek | o | |
|
||||
| IO#set_encoding | | |
|
||||
| IO#stat | | |
|
||||
| IO#sync | o | |
|
||||
| IO#sync= | o | |
|
||||
| IO#sysread | o | |
|
||||
| IO#sysseek | o | |
|
||||
| IO#syswrite | o | |
|
||||
| IO#to_io | | |
|
||||
| IO#ungetbyte | | |
|
||||
| IO#ungetc | o | |
|
||||
| IO#write | o | |
|
||||
| IO#write_nonblock | | |
|
||||
|
||||
### File
|
||||
- http://doc.ruby-lang.org/ja/1.9.3/class/File.html
|
||||
|
||||
| method | mruby-io | memo |
|
||||
| --------------------------- | -------- | ---- |
|
||||
| File.absolute_path | | |
|
||||
| File.atime | | |
|
||||
| File.basename | o | |
|
||||
| File.blockdev? | | FileTest |
|
||||
| File.chardev? | | FileTest |
|
||||
| File.chmod | o | |
|
||||
| File.chown | | |
|
||||
| File.ctime | | |
|
||||
| File.delete, File.unlink | o | |
|
||||
| File.directory? | o | FileTest |
|
||||
| File.dirname | o | |
|
||||
| File.executable? | | FileTest |
|
||||
| File.executable_real? | | FileTest |
|
||||
| File.exist?, exists? | o | FileTest |
|
||||
| File.expand_path | o | |
|
||||
| File.extname | o | |
|
||||
| File.file? | o | FileTest |
|
||||
| File.fnmatch, File.fnmatch? | | |
|
||||
| File.ftype | | |
|
||||
| File.grpowned? | | FileTest |
|
||||
| File.identical? | | FileTest |
|
||||
| File.join | o | |
|
||||
| File.lchmod | | |
|
||||
| File.lchown | | |
|
||||
| File.link | | |
|
||||
| File.lstat | | |
|
||||
| File.mtime | | |
|
||||
| File.new, File.open | o | |
|
||||
| File.owned? | | FileTest |
|
||||
| File.path | | |
|
||||
| File.pipe? | o | FileTest |
|
||||
| File.readable? | | FileTest |
|
||||
| File.readable_real? | | FileTest |
|
||||
| File.readlink | o | |
|
||||
| File.realdirpath | | |
|
||||
| File.realpath | o | |
|
||||
| File.rename | o | |
|
||||
| File.setgid? | | FileTest |
|
||||
| File.setuid? | | FileTest |
|
||||
| File.size | o | |
|
||||
| File.size? | o | FileTest |
|
||||
| File.socket? | o | FileTest |
|
||||
| File.split | | |
|
||||
| File.stat | | |
|
||||
| File.sticky? | | FileTest |
|
||||
| File.symlink | | |
|
||||
| File.symlink? | o | FileTest |
|
||||
| File.truncate | | |
|
||||
| File.umask | o | |
|
||||
| File.utime | | |
|
||||
| File.world_readable? | | |
|
||||
| File.world_writable? | | |
|
||||
| File.writable? | | FileTest |
|
||||
| File.writable_real? | | FileTest |
|
||||
| File.zero? | o | FileTest |
|
||||
| File#atime | | |
|
||||
| File#chmod | | |
|
||||
| File#chown | | |
|
||||
| File#ctime | | |
|
||||
| File#flock | o | |
|
||||
| File#lstat | | |
|
||||
| File#mtime | | |
|
||||
| File#path, File#to_path | o | |
|
||||
| File#size | | |
|
||||
| File#truncate | | |
|
||||
|
||||
|
||||
## License
|
||||
|
||||
Copyright (c) 2013 Internet Initiative Japan Inc.
|
||||
Copyright (c) 2017 mruby developers
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
** io.h - IO class
|
||||
*/
|
||||
|
||||
#ifndef MRUBY_IO_H
|
||||
#define MRUBY_IO_H
|
||||
|
||||
#include <mruby.h>
|
||||
|
||||
#ifdef MRB_DISABLE_STDIO
|
||||
# error IO and File conflicts 'MRB_DISABLE_STDIO' configuration in your 'build_config.rb'
|
||||
#endif
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if defined(MRB_WITHOUT_IO_PREAD_PWRITE)
|
||||
# undef MRB_WITH_IO_PREAD_PWRITE
|
||||
#elif !defined(MRB_WITH_IO_PREAD_PWRITE)
|
||||
# if defined(__unix__) || defined(__MACH__)
|
||||
# define MRB_WITH_IO_PREAD_PWRITE
|
||||
# endif
|
||||
#endif
|
||||
|
||||
struct mrb_io {
|
||||
int fd; /* file descriptor, or -1 */
|
||||
int fd2; /* file descriptor to write if it's different from fd, or -1 */
|
||||
int pid; /* child's pid (for pipes) */
|
||||
unsigned int readable:1,
|
||||
writable:1,
|
||||
sync:1,
|
||||
is_socket:1;
|
||||
};
|
||||
|
||||
#define MRB_O_RDONLY 0x0000
|
||||
#define MRB_O_WRONLY 0x0001
|
||||
#define MRB_O_RDWR 0x0002
|
||||
#define MRB_O_ACCMODE (MRB_O_RDONLY | MRB_O_WRONLY | MRB_O_RDWR)
|
||||
#define MRB_O_NONBLOCK 0x0004
|
||||
#define MRB_O_APPEND 0x0008
|
||||
#define MRB_O_SYNC 0x0010
|
||||
#define MRB_O_NOFOLLOW 0x0020
|
||||
#define MRB_O_CREAT 0x0040
|
||||
#define MRB_O_TRUNC 0x0080
|
||||
#define MRB_O_EXCL 0x0100
|
||||
#define MRB_O_NOCTTY 0x0200
|
||||
#define MRB_O_DIRECT 0x0400
|
||||
#define MRB_O_BINARY 0x0800
|
||||
#define MRB_O_SHARE_DELETE 0x1000
|
||||
#define MRB_O_TMPFILE 0x2000
|
||||
#define MRB_O_NOATIME 0x4000
|
||||
#define MRB_O_DSYNC 0x00008000
|
||||
#define MRB_O_RSYNC 0x00010000
|
||||
|
||||
#define MRB_O_RDONLY_P(f) ((mrb_bool)(((f) & MRB_O_ACCMODE) == MRB_O_RDONLY))
|
||||
#define MRB_O_WRONLY_P(f) ((mrb_bool)(((f) & MRB_O_ACCMODE) == MRB_O_WRONLY))
|
||||
#define MRB_O_RDWR_P(f) ((mrb_bool)(((f) & MRB_O_ACCMODE) == MRB_O_RDWR))
|
||||
#define MRB_O_READABLE_P(f) ((mrb_bool)((((f) & MRB_O_ACCMODE) | 2) == 2))
|
||||
#define MRB_O_WRITABLE_P(f) ((mrb_bool)(((((f) & MRB_O_ACCMODE) + 1) & 2) == 2))
|
||||
|
||||
#define E_IO_ERROR (mrb_class_get(mrb, "IOError"))
|
||||
#define E_EOF_ERROR (mrb_class_get(mrb, "EOFError"))
|
||||
|
||||
int mrb_io_fileno(mrb_state *mrb, mrb_value io);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
} /* extern "C" { */
|
||||
#endif
|
||||
#endif /* MRUBY_IO_H */
|
||||
@@ -0,0 +1,12 @@
|
||||
MRuby::Gem::Specification.new('mruby-io') do |spec|
|
||||
spec.license = 'MIT'
|
||||
spec.authors = ['Internet Initiative Japan Inc.', 'mruby developers']
|
||||
spec.summary = 'IO and File class'
|
||||
|
||||
spec.cc.include_paths << "#{build.root}/src"
|
||||
|
||||
if for_windows?
|
||||
spec.linker.libraries << "ws2_32"
|
||||
end
|
||||
spec.add_test_dependency 'mruby-time', core: 'mruby-time'
|
||||
end
|
||||
@@ -0,0 +1,203 @@
|
||||
class File < IO
|
||||
attr_accessor :path
|
||||
|
||||
def initialize(fd_or_path, mode = "r", perm = 0666)
|
||||
if fd_or_path.kind_of? Fixnum
|
||||
super(fd_or_path, mode)
|
||||
else
|
||||
@path = fd_or_path
|
||||
fd = IO.sysopen(@path, mode, perm)
|
||||
super(fd, mode)
|
||||
end
|
||||
end
|
||||
|
||||
def self.join(*names)
|
||||
return "" if names.empty?
|
||||
|
||||
names.map! do |name|
|
||||
case name
|
||||
when String
|
||||
name
|
||||
when Array
|
||||
if names == name
|
||||
raise ArgumentError, "recursive array"
|
||||
end
|
||||
join(*name)
|
||||
else
|
||||
raise TypeError, "no implicit conversion of #{name.class} into String"
|
||||
end
|
||||
end
|
||||
|
||||
return names[0] if names.size == 1
|
||||
|
||||
if names[0][-1] == File::SEPARATOR
|
||||
s = names[0][0..-2]
|
||||
else
|
||||
s = names[0].dup
|
||||
end
|
||||
|
||||
(1..names.size-2).each { |i|
|
||||
t = names[i]
|
||||
if t[0] == File::SEPARATOR and t[-1] == File::SEPARATOR
|
||||
t = t[1..-2]
|
||||
elsif t[0] == File::SEPARATOR
|
||||
t = t[1..-1]
|
||||
elsif t[-1] == File::SEPARATOR
|
||||
t = t[0..-2]
|
||||
end
|
||||
s += File::SEPARATOR + t if t != ""
|
||||
}
|
||||
if names[-1][0] == File::SEPARATOR
|
||||
s += File::SEPARATOR + names[-1][1..-1]
|
||||
else
|
||||
s += File::SEPARATOR + names[-1]
|
||||
end
|
||||
s
|
||||
end
|
||||
|
||||
def self._concat_path(path, base_path)
|
||||
if path[0] == "/" || path[1] == ':' # Windows root!
|
||||
expanded_path = path
|
||||
elsif path[0] == "~"
|
||||
if (path[1] == "/" || path[1] == nil)
|
||||
dir = path[1, path.size]
|
||||
home_dir = _gethome
|
||||
|
||||
unless home_dir
|
||||
raise ArgumentError, "couldn't find HOME environment -- expanding '~'"
|
||||
end
|
||||
|
||||
expanded_path = home_dir
|
||||
expanded_path += dir if dir
|
||||
expanded_path += "/"
|
||||
else
|
||||
splitted_path = path.split("/")
|
||||
user = splitted_path[0][1, splitted_path[0].size]
|
||||
dir = "/" + splitted_path[1, splitted_path.size].join("/")
|
||||
|
||||
home_dir = _gethome(user)
|
||||
|
||||
unless home_dir
|
||||
raise ArgumentError, "user #{user} doesn't exist"
|
||||
end
|
||||
|
||||
expanded_path = home_dir
|
||||
expanded_path += dir if dir
|
||||
expanded_path += "/"
|
||||
end
|
||||
else
|
||||
expanded_path = _concat_path(base_path, _getwd)
|
||||
expanded_path += "/" + path
|
||||
end
|
||||
|
||||
expanded_path
|
||||
end
|
||||
|
||||
def self.expand_path(path, default_dir = '.')
|
||||
expanded_path = _concat_path(path, default_dir)
|
||||
drive_prefix = ""
|
||||
if File::ALT_SEPARATOR && expanded_path.size > 2 &&
|
||||
("A".."Z").include?(expanded_path[0].upcase) && expanded_path[1] == ":"
|
||||
drive_prefix = expanded_path[0, 2]
|
||||
expanded_path = expanded_path[2, expanded_path.size]
|
||||
end
|
||||
expand_path_array = []
|
||||
if File::ALT_SEPARATOR && expanded_path.include?(File::ALT_SEPARATOR)
|
||||
expanded_path.gsub!(File::ALT_SEPARATOR, '/')
|
||||
end
|
||||
while expanded_path.include?('//')
|
||||
expanded_path = expanded_path.gsub('//', '/')
|
||||
end
|
||||
|
||||
if expanded_path != "/"
|
||||
expanded_path.split('/').each do |path_token|
|
||||
if path_token == '..'
|
||||
if expand_path_array.size > 1
|
||||
expand_path_array.pop
|
||||
end
|
||||
elsif path_token == '.'
|
||||
# nothing to do.
|
||||
else
|
||||
expand_path_array << path_token
|
||||
end
|
||||
end
|
||||
|
||||
expanded_path = expand_path_array.join("/")
|
||||
if expanded_path.empty?
|
||||
expanded_path = '/'
|
||||
end
|
||||
end
|
||||
if drive_prefix.empty?
|
||||
expanded_path
|
||||
else
|
||||
drive_prefix + expanded_path.gsub("/", File::ALT_SEPARATOR)
|
||||
end
|
||||
end
|
||||
|
||||
def self.foreach(file)
|
||||
if block_given?
|
||||
self.open(file) do |f|
|
||||
f.each {|l| yield l}
|
||||
end
|
||||
else
|
||||
return self.new(file)
|
||||
end
|
||||
end
|
||||
|
||||
def self.directory?(file)
|
||||
FileTest.directory?(file)
|
||||
end
|
||||
|
||||
def self.exist?(file)
|
||||
FileTest.exist?(file)
|
||||
end
|
||||
|
||||
def self.exists?(file)
|
||||
FileTest.exists?(file)
|
||||
end
|
||||
|
||||
def self.file?(file)
|
||||
FileTest.file?(file)
|
||||
end
|
||||
|
||||
def self.pipe?(file)
|
||||
FileTest.pipe?(file)
|
||||
end
|
||||
|
||||
def self.size(file)
|
||||
FileTest.size(file)
|
||||
end
|
||||
|
||||
def self.size?(file)
|
||||
FileTest.size?(file)
|
||||
end
|
||||
|
||||
def self.socket?(file)
|
||||
FileTest.socket?(file)
|
||||
end
|
||||
|
||||
def self.symlink?(file)
|
||||
FileTest.symlink?(file)
|
||||
end
|
||||
|
||||
def self.zero?(file)
|
||||
FileTest.zero?(file)
|
||||
end
|
||||
|
||||
def self.extname(filename)
|
||||
fname = self.basename(filename)
|
||||
return '' if fname[0] == '.' || fname.index('.').nil?
|
||||
ext = fname.split('.').last
|
||||
ext.empty? ? '' : ".#{ext}"
|
||||
end
|
||||
|
||||
def self.path(filename)
|
||||
if filename.kind_of?(String)
|
||||
filename
|
||||
elsif filename.respond_to?(:to_path)
|
||||
filename.to_path
|
||||
else
|
||||
raise TypeError, "no implicit conversion of #{filename.class} into String"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,13 @@
|
||||
class File
|
||||
module Constants
|
||||
FNM_SYSCASE = 0
|
||||
FNM_NOESCAPE = 1
|
||||
FNM_PATHNAME = 2
|
||||
FNM_DOTMATCH = 4
|
||||
FNM_CASEFOLD = 8
|
||||
end
|
||||
end
|
||||
|
||||
class File
|
||||
include File::Constants
|
||||
end
|
||||
@@ -0,0 +1,369 @@
|
||||
##
|
||||
# IO
|
||||
|
||||
class IOError < StandardError; end
|
||||
class EOFError < IOError; end
|
||||
|
||||
class IO
|
||||
SEEK_SET = 0
|
||||
SEEK_CUR = 1
|
||||
SEEK_END = 2
|
||||
|
||||
BUF_SIZE = 4096
|
||||
|
||||
def self.open(*args, &block)
|
||||
io = self.new(*args)
|
||||
|
||||
return io unless block
|
||||
|
||||
begin
|
||||
yield io
|
||||
ensure
|
||||
begin
|
||||
io.close unless io.closed?
|
||||
rescue StandardError
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.popen(command, mode = 'r', opts={}, &block)
|
||||
if !self.respond_to?(:_popen)
|
||||
raise NotImplementedError, "popen is not supported on this platform"
|
||||
end
|
||||
io = self._popen(command, mode, opts)
|
||||
return io unless block
|
||||
|
||||
begin
|
||||
yield io
|
||||
ensure
|
||||
begin
|
||||
io.close unless io.closed?
|
||||
rescue IOError
|
||||
# nothing
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.pipe(&block)
|
||||
if !self.respond_to?(:_pipe)
|
||||
raise NotImplementedError, "pipe is not supported on this platform"
|
||||
end
|
||||
if block
|
||||
begin
|
||||
r, w = IO._pipe
|
||||
yield r, w
|
||||
ensure
|
||||
r.close unless r.closed?
|
||||
w.close unless w.closed?
|
||||
end
|
||||
else
|
||||
IO._pipe
|
||||
end
|
||||
end
|
||||
|
||||
def self.read(path, length=nil, offset=nil, opt=nil)
|
||||
if not opt.nil? # 4 arguments
|
||||
offset ||= 0
|
||||
elsif not offset.nil? # 3 arguments
|
||||
if offset.is_a? Hash
|
||||
opt = offset
|
||||
offset = 0
|
||||
else
|
||||
opt = {}
|
||||
end
|
||||
elsif not length.nil? # 2 arguments
|
||||
if length.is_a? Hash
|
||||
opt = length
|
||||
offset = 0
|
||||
length = nil
|
||||
else
|
||||
offset = 0
|
||||
opt = {}
|
||||
end
|
||||
else # only 1 argument
|
||||
opt = {}
|
||||
offset = 0
|
||||
length = nil
|
||||
end
|
||||
|
||||
str = ""
|
||||
fd = -1
|
||||
io = nil
|
||||
begin
|
||||
if path[0] == "|"
|
||||
io = IO.popen(path[1..-1], (opt[:mode] || "r"))
|
||||
else
|
||||
mode = opt[:mode] || "r"
|
||||
fd = IO.sysopen(path, mode)
|
||||
io = IO.open(fd, mode)
|
||||
end
|
||||
io.seek(offset) if offset > 0
|
||||
str = io.read(length)
|
||||
ensure
|
||||
if io
|
||||
io.close
|
||||
elsif fd != -1
|
||||
IO._sysclose(fd)
|
||||
end
|
||||
end
|
||||
str
|
||||
end
|
||||
|
||||
def flush
|
||||
# mruby-io always writes immediately (no output buffer).
|
||||
raise IOError, "closed stream" if self.closed?
|
||||
self
|
||||
end
|
||||
|
||||
def hash
|
||||
# We must define IO#hash here because IO includes Enumerable and
|
||||
# Enumerable#hash will call IO#read...
|
||||
self.__id__
|
||||
end
|
||||
|
||||
def write(string)
|
||||
str = string.is_a?(String) ? string : string.to_s
|
||||
return 0 if str.empty?
|
||||
unless @buf.empty?
|
||||
# reset real pos ignore buf
|
||||
seek(pos, SEEK_SET)
|
||||
end
|
||||
len = syswrite(str)
|
||||
len
|
||||
end
|
||||
|
||||
def <<(str)
|
||||
write(str)
|
||||
self
|
||||
end
|
||||
|
||||
def eof?
|
||||
_check_readable
|
||||
begin
|
||||
_read_buf
|
||||
return @buf.empty?
|
||||
rescue EOFError
|
||||
return true
|
||||
end
|
||||
end
|
||||
alias_method :eof, :eof?
|
||||
|
||||
def pos
|
||||
raise IOError if closed?
|
||||
sysseek(0, SEEK_CUR) - @buf.bytesize
|
||||
end
|
||||
alias_method :tell, :pos
|
||||
|
||||
def pos=(i)
|
||||
seek(i, SEEK_SET)
|
||||
end
|
||||
|
||||
def rewind
|
||||
seek(0, SEEK_SET)
|
||||
end
|
||||
|
||||
def seek(i, whence = SEEK_SET)
|
||||
raise IOError if closed?
|
||||
sysseek(i, whence)
|
||||
@buf = ''
|
||||
0
|
||||
end
|
||||
|
||||
def _read_buf
|
||||
return @buf if @buf && @buf.bytesize > 0
|
||||
sysread(BUF_SIZE, @buf)
|
||||
end
|
||||
|
||||
def ungetc(substr)
|
||||
raise TypeError.new "expect String, got #{substr.class}" unless substr.is_a?(String)
|
||||
if @buf.empty?
|
||||
@buf.replace(substr)
|
||||
else
|
||||
@buf[0,0] = substr
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
def read(length = nil, outbuf = "")
|
||||
unless length.nil?
|
||||
unless length.is_a? Fixnum
|
||||
raise TypeError.new "can't convert #{length.class} into Integer"
|
||||
end
|
||||
if length < 0
|
||||
raise ArgumentError.new "negative length: #{length} given"
|
||||
end
|
||||
if length == 0
|
||||
return "" # easy case
|
||||
end
|
||||
end
|
||||
|
||||
array = []
|
||||
while 1
|
||||
begin
|
||||
_read_buf
|
||||
rescue EOFError
|
||||
array = nil if array.empty? and (not length.nil?) and length != 0
|
||||
break
|
||||
end
|
||||
|
||||
if length
|
||||
consume = (length <= @buf.bytesize) ? length : @buf.bytesize
|
||||
array.push IO._bufread(@buf, consume)
|
||||
length -= consume
|
||||
break if length == 0
|
||||
else
|
||||
array.push @buf
|
||||
@buf = ''
|
||||
end
|
||||
end
|
||||
|
||||
if array.nil?
|
||||
outbuf.replace("")
|
||||
nil
|
||||
else
|
||||
outbuf.replace(array.join)
|
||||
end
|
||||
end
|
||||
|
||||
def readline(arg = "\n", limit = nil)
|
||||
case arg
|
||||
when String
|
||||
rs = arg
|
||||
when Fixnum
|
||||
rs = "\n"
|
||||
limit = arg
|
||||
else
|
||||
raise ArgumentError
|
||||
end
|
||||
|
||||
if rs.nil?
|
||||
return read
|
||||
end
|
||||
|
||||
if rs == ""
|
||||
rs = "\n\n"
|
||||
end
|
||||
|
||||
array = []
|
||||
while 1
|
||||
begin
|
||||
_read_buf
|
||||
rescue EOFError
|
||||
array = nil if array.empty?
|
||||
break
|
||||
end
|
||||
|
||||
if limit && limit <= @buf.size
|
||||
array.push @buf[0, limit]
|
||||
@buf[0, limit] = ""
|
||||
break
|
||||
elsif idx = @buf.index(rs)
|
||||
len = idx + rs.size
|
||||
array.push @buf[0, len]
|
||||
@buf[0, len] = ""
|
||||
break
|
||||
else
|
||||
array.push @buf
|
||||
@buf = ''
|
||||
end
|
||||
end
|
||||
|
||||
raise EOFError.new "end of file reached" if array.nil?
|
||||
|
||||
array.join
|
||||
end
|
||||
|
||||
def gets(*args)
|
||||
begin
|
||||
readline(*args)
|
||||
rescue EOFError
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def readchar
|
||||
_read_buf
|
||||
_readchar(@buf)
|
||||
end
|
||||
|
||||
def getc
|
||||
begin
|
||||
readchar
|
||||
rescue EOFError
|
||||
c = @buf[0]
|
||||
@buf[0,1]="" if c
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
# 15.2.20.5.3
|
||||
def each(&block)
|
||||
return to_enum unless block
|
||||
|
||||
while line = self.gets
|
||||
block.call(line)
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
# 15.2.20.5.4
|
||||
def each_byte(&block)
|
||||
return to_enum(:each_byte) unless block
|
||||
|
||||
while char = self.getc
|
||||
block.call(char)
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
# 15.2.20.5.5
|
||||
alias each_line each
|
||||
|
||||
alias each_char each_byte
|
||||
|
||||
def readlines
|
||||
ary = []
|
||||
while (line = gets)
|
||||
ary << line
|
||||
end
|
||||
ary
|
||||
end
|
||||
|
||||
def puts(*args)
|
||||
i = 0
|
||||
len = args.size
|
||||
while i < len
|
||||
s = args[i].to_s
|
||||
write s
|
||||
write "\n" if (s[-1] != "\n")
|
||||
i += 1
|
||||
end
|
||||
write "\n" if len == 0
|
||||
nil
|
||||
end
|
||||
|
||||
def print(*args)
|
||||
i = 0
|
||||
len = args.size
|
||||
while i < len
|
||||
write args[i].to_s
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
def printf(*args)
|
||||
write sprintf(*args)
|
||||
nil
|
||||
end
|
||||
|
||||
alias_method :to_i, :fileno
|
||||
alias_method :tty?, :isatty
|
||||
end
|
||||
|
||||
STDIN = IO.open(0, "r")
|
||||
STDOUT = IO.open(1, "w")
|
||||
STDERR = IO.open(2, "w")
|
||||
|
||||
$stdin = STDIN
|
||||
$stdout = STDOUT
|
||||
$stderr = STDERR
|
||||
@@ -0,0 +1,31 @@
|
||||
module Kernel
|
||||
def `(cmd)
|
||||
IO.popen(cmd) { |io| io.read }
|
||||
end
|
||||
|
||||
def open(file, *rest, &block)
|
||||
raise ArgumentError unless file.is_a?(String)
|
||||
|
||||
if file[0] == "|"
|
||||
IO.popen(file[1..-1], *rest, &block)
|
||||
else
|
||||
File.open(file, *rest, &block)
|
||||
end
|
||||
end
|
||||
|
||||
def print(*args)
|
||||
$stdout.print(*args)
|
||||
end
|
||||
|
||||
def puts(*args)
|
||||
$stdout.puts(*args)
|
||||
end
|
||||
|
||||
def printf(*args)
|
||||
$stdout.printf(*args)
|
||||
end
|
||||
|
||||
def gets(*args)
|
||||
$stdin.gets(*args)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,647 @@
|
||||
/*
|
||||
** file.c - File class
|
||||
*/
|
||||
|
||||
#include "mruby.h"
|
||||
#include "mruby/class.h"
|
||||
#include "mruby/data.h"
|
||||
#include "mruby/string.h"
|
||||
#include "mruby/ext/io.h"
|
||||
#include "mruby/error.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#include <windows.h>
|
||||
#include <io.h>
|
||||
#define NULL_FILE "NUL"
|
||||
#define UNLINK _unlink
|
||||
#define GETCWD _getcwd
|
||||
#define CHMOD(a, b) 0
|
||||
#define MAXPATHLEN 1024
|
||||
#if !defined(PATH_MAX)
|
||||
#define PATH_MAX _MAX_PATH
|
||||
#endif
|
||||
#define realpath(N,R) _fullpath((R),(N),_MAX_PATH)
|
||||
#include <direct.h>
|
||||
#else
|
||||
#define NULL_FILE "/dev/null"
|
||||
#include <unistd.h>
|
||||
#define UNLINK unlink
|
||||
#define GETCWD getcwd
|
||||
#define CHMOD(a, b) chmod(a,b)
|
||||
#include <sys/file.h>
|
||||
#include <libgen.h>
|
||||
#include <sys/param.h>
|
||||
#include <pwd.h>
|
||||
#endif
|
||||
|
||||
#define FILE_SEPARATOR "/"
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#define PATH_SEPARATOR ";"
|
||||
#define FILE_ALT_SEPARATOR "\\"
|
||||
#define VOLUME_SEPARATOR ":"
|
||||
#else
|
||||
#define PATH_SEPARATOR ":"
|
||||
#endif
|
||||
|
||||
#ifndef LOCK_SH
|
||||
#define LOCK_SH 1
|
||||
#endif
|
||||
#ifndef LOCK_EX
|
||||
#define LOCK_EX 2
|
||||
#endif
|
||||
#ifndef LOCK_NB
|
||||
#define LOCK_NB 4
|
||||
#endif
|
||||
#ifndef LOCK_UN
|
||||
#define LOCK_UN 8
|
||||
#endif
|
||||
|
||||
#ifndef _WIN32
|
||||
typedef struct stat mrb_stat;
|
||||
# define mrb_stat(path, sb) stat(path, sb)
|
||||
# define mrb_fstat(fd, sb) fstat(fd, sb)
|
||||
#else
|
||||
typedef struct __stat64 mrb_stat;
|
||||
# define mrb_stat(path, sb) _stat64(path, sb)
|
||||
# define mrb_fstat(fd, sb) _fstat64(fd, sb)
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
static int
|
||||
flock(int fd, int operation) {
|
||||
OVERLAPPED ov;
|
||||
HANDLE h = (HANDLE)_get_osfhandle(fd);
|
||||
DWORD flags;
|
||||
flags = ((operation & LOCK_NB) ? LOCKFILE_FAIL_IMMEDIATELY : 0)
|
||||
| ((operation & LOCK_SH) ? LOCKFILE_EXCLUSIVE_LOCK : 0);
|
||||
memset(&ov, 0, sizeof(ov));
|
||||
return LockFileEx(h, flags, 0, 0xffffffff, 0xffffffff, &ov) ? 0 : -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
static mrb_value
|
||||
mrb_file_s_umask(mrb_state *mrb, mrb_value klass)
|
||||
{
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
/* nothing to do on windows */
|
||||
return mrb_fixnum_value(0);
|
||||
|
||||
#else
|
||||
mrb_int mask, omask;
|
||||
if (mrb_get_args(mrb, "|i", &mask) == 0) {
|
||||
omask = umask(0);
|
||||
umask(omask);
|
||||
} else {
|
||||
omask = umask(mask);
|
||||
}
|
||||
return mrb_fixnum_value(omask);
|
||||
#endif
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
mrb_file_s_unlink(mrb_state *mrb, mrb_value obj)
|
||||
{
|
||||
mrb_value *argv;
|
||||
mrb_value pathv;
|
||||
mrb_int argc, i;
|
||||
char *path;
|
||||
|
||||
mrb_get_args(mrb, "*", &argv, &argc);
|
||||
for (i = 0; i < argc; i++) {
|
||||
const char *utf8_path;
|
||||
pathv = mrb_ensure_string_type(mrb, argv[i]);
|
||||
utf8_path = RSTRING_CSTR(mrb, pathv);
|
||||
path = mrb_locale_from_utf8(utf8_path, -1);
|
||||
if (UNLINK(path) < 0) {
|
||||
mrb_locale_free(path);
|
||||
mrb_sys_fail(mrb, utf8_path);
|
||||
}
|
||||
mrb_locale_free(path);
|
||||
}
|
||||
return mrb_fixnum_value(argc);
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
mrb_file_s_rename(mrb_state *mrb, mrb_value obj)
|
||||
{
|
||||
mrb_value from, to;
|
||||
char *src, *dst;
|
||||
|
||||
mrb_get_args(mrb, "SS", &from, &to);
|
||||
src = mrb_locale_from_utf8(RSTRING_CSTR(mrb, from), -1);
|
||||
dst = mrb_locale_from_utf8(RSTRING_CSTR(mrb, to), -1);
|
||||
if (rename(src, dst) < 0) {
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
if (CHMOD(dst, 0666) == 0 && UNLINK(dst) == 0 && rename(src, dst) == 0) {
|
||||
mrb_locale_free(src);
|
||||
mrb_locale_free(dst);
|
||||
return mrb_fixnum_value(0);
|
||||
}
|
||||
#endif
|
||||
mrb_locale_free(src);
|
||||
mrb_locale_free(dst);
|
||||
mrb_sys_fail(mrb, RSTRING_CSTR(mrb, mrb_format(mrb, "(%v, %v)", from, to)));
|
||||
return mrb_fixnum_value(-1); /* not reached */
|
||||
}
|
||||
mrb_locale_free(src);
|
||||
mrb_locale_free(dst);
|
||||
return mrb_fixnum_value(0);
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
mrb_file_dirname(mrb_state *mrb, mrb_value klass)
|
||||
{
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
char dname[_MAX_DIR], vname[_MAX_DRIVE];
|
||||
char buffer[_MAX_DRIVE + _MAX_DIR];
|
||||
const char *utf8_path;
|
||||
char *path;
|
||||
size_t ridx;
|
||||
mrb_get_args(mrb, "z", &utf8_path);
|
||||
path = mrb_locale_from_utf8(utf8_path, -1);
|
||||
_splitpath(path, vname, dname, NULL, NULL);
|
||||
snprintf(buffer, _MAX_DRIVE + _MAX_DIR, "%s%s", vname, dname);
|
||||
mrb_locale_free(path);
|
||||
ridx = strlen(buffer);
|
||||
if (ridx == 0) {
|
||||
strncpy(buffer, ".", 2); /* null terminated */
|
||||
} else if (ridx > 1) {
|
||||
ridx--;
|
||||
while (ridx > 0 && (buffer[ridx] == '/' || buffer[ridx] == '\\')) {
|
||||
buffer[ridx] = '\0'; /* remove last char */
|
||||
ridx--;
|
||||
}
|
||||
}
|
||||
return mrb_str_new_cstr(mrb, buffer);
|
||||
#else
|
||||
char *dname, *path;
|
||||
mrb_value s;
|
||||
mrb_get_args(mrb, "S", &s);
|
||||
path = mrb_locale_from_utf8(mrb_str_to_cstr(mrb, s), -1);
|
||||
|
||||
if ((dname = dirname(path)) == NULL) {
|
||||
mrb_locale_free(path);
|
||||
mrb_sys_fail(mrb, "dirname");
|
||||
}
|
||||
mrb_locale_free(path);
|
||||
return mrb_str_new_cstr(mrb, dname);
|
||||
#endif
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
mrb_file_basename(mrb_state *mrb, mrb_value klass)
|
||||
{
|
||||
// NOTE: Do not use mrb_locale_from_utf8 here
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
char bname[_MAX_DIR];
|
||||
char extname[_MAX_EXT];
|
||||
char *path;
|
||||
size_t ridx;
|
||||
char buffer[_MAX_DIR + _MAX_EXT];
|
||||
mrb_value s;
|
||||
|
||||
mrb_get_args(mrb, "S", &s);
|
||||
path = mrb_str_to_cstr(mrb, s);
|
||||
ridx = strlen(path);
|
||||
if (ridx > 0) {
|
||||
ridx--;
|
||||
while (ridx > 0 && (path[ridx] == '/' || path[ridx] == '\\')) {
|
||||
path[ridx] = '\0';
|
||||
ridx--;
|
||||
}
|
||||
if (strncmp(path, "/", 2) == 0) {
|
||||
return mrb_str_new_cstr(mrb, path);
|
||||
}
|
||||
}
|
||||
_splitpath((const char*)path, NULL, NULL, bname, extname);
|
||||
snprintf(buffer, _MAX_DIR + _MAX_EXT, "%s%s", bname, extname);
|
||||
return mrb_str_new_cstr(mrb, buffer);
|
||||
#else
|
||||
char *bname, *path;
|
||||
mrb_value s;
|
||||
mrb_get_args(mrb, "S", &s);
|
||||
path = mrb_str_to_cstr(mrb, s);
|
||||
if ((bname = basename(path)) == NULL) {
|
||||
mrb_sys_fail(mrb, "basename");
|
||||
}
|
||||
if (strncmp(bname, "//", 3) == 0) bname[1] = '\0'; /* patch for Cygwin */
|
||||
return mrb_str_new_cstr(mrb, bname);
|
||||
#endif
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
mrb_file_realpath(mrb_state *mrb, mrb_value klass)
|
||||
{
|
||||
mrb_value pathname, dir_string, s, result;
|
||||
mrb_int argc;
|
||||
char *cpath;
|
||||
|
||||
argc = mrb_get_args(mrb, "S|S", &pathname, &dir_string);
|
||||
if (argc == 2) {
|
||||
s = mrb_str_dup(mrb, dir_string);
|
||||
s = mrb_str_append(mrb, s, mrb_str_new_cstr(mrb, FILE_SEPARATOR));
|
||||
s = mrb_str_append(mrb, s, pathname);
|
||||
pathname = s;
|
||||
}
|
||||
cpath = mrb_locale_from_utf8(RSTRING_CSTR(mrb, pathname), -1);
|
||||
result = mrb_str_buf_new(mrb, PATH_MAX);
|
||||
if (realpath(cpath, RSTRING_PTR(result)) == NULL) {
|
||||
mrb_locale_free(cpath);
|
||||
mrb_sys_fail(mrb, cpath);
|
||||
return result; /* not reached */
|
||||
}
|
||||
mrb_locale_free(cpath);
|
||||
mrb_str_resize(mrb, result, strlen(RSTRING_PTR(result)));
|
||||
return result;
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
mrb_file__getwd(mrb_state *mrb, mrb_value klass)
|
||||
{
|
||||
mrb_value path;
|
||||
char buf[MAXPATHLEN], *utf8;
|
||||
|
||||
if (GETCWD(buf, MAXPATHLEN) == NULL) {
|
||||
mrb_sys_fail(mrb, "getcwd(2)");
|
||||
}
|
||||
utf8 = mrb_utf8_from_locale(buf, -1);
|
||||
path = mrb_str_new_cstr(mrb, utf8);
|
||||
mrb_utf8_free(utf8);
|
||||
return path;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
#define IS_FILESEP(x) (x == (*(char*)(FILE_SEPARATOR)) || x == (*(char*)(FILE_ALT_SEPARATOR)))
|
||||
#define IS_VOLSEP(x) (x == (*(char*)(VOLUME_SEPARATOR)))
|
||||
#define IS_DEVICEID(x) (x == '.' || x == '?')
|
||||
#define CHECK_UNCDEV_PATH (IS_FILESEP(path[0]) && IS_FILESEP(path[1]))
|
||||
|
||||
static int
|
||||
is_absolute_traditional_path(const char *path, size_t len)
|
||||
{
|
||||
if (len < 3) return 0;
|
||||
return (ISALPHA(path[0]) && IS_VOLSEP(path[1]) && IS_FILESEP(path[2]));
|
||||
}
|
||||
|
||||
static int
|
||||
is_aboslute_unc_path(const char *path, size_t len) {
|
||||
if (len < 2) return 0;
|
||||
return (CHECK_UNCDEV_PATH && !IS_DEVICEID(path[2]));
|
||||
}
|
||||
|
||||
static int
|
||||
is_absolute_device_path(const char *path, size_t len) {
|
||||
if (len < 4) return 0;
|
||||
return (CHECK_UNCDEV_PATH && IS_DEVICEID(path[2]) && IS_FILESEP(path[3]));
|
||||
}
|
||||
|
||||
static int
|
||||
mrb_file_is_absolute_path(const char *path)
|
||||
{
|
||||
size_t len = strlen(path);
|
||||
if (IS_FILESEP(path[0])) return 1;
|
||||
if (len > 0)
|
||||
return (
|
||||
is_absolute_traditional_path(path, len) ||
|
||||
is_aboslute_unc_path(path, len) ||
|
||||
is_absolute_device_path(path, len)
|
||||
);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
#undef IS_FILESEP
|
||||
#undef IS_VOLSEP
|
||||
#undef IS_DEVICEID
|
||||
#undef CHECK_UNCDEV_PATH
|
||||
|
||||
#else
|
||||
static int
|
||||
mrb_file_is_absolute_path(const char *path)
|
||||
{
|
||||
return (path[0] == *(char*)(FILE_SEPARATOR));
|
||||
}
|
||||
#endif
|
||||
|
||||
static mrb_value
|
||||
mrb_file__gethome(mrb_state *mrb, mrb_value klass)
|
||||
{
|
||||
mrb_int argc;
|
||||
char *home;
|
||||
mrb_value path;
|
||||
|
||||
#ifndef _WIN32
|
||||
mrb_value username;
|
||||
|
||||
argc = mrb_get_args(mrb, "|S", &username);
|
||||
if (argc == 0) {
|
||||
home = getenv("HOME");
|
||||
if (home == NULL) {
|
||||
return mrb_nil_value();
|
||||
}
|
||||
if (!mrb_file_is_absolute_path(home)) {
|
||||
mrb_raise(mrb, E_ARGUMENT_ERROR, "non-absolute home");
|
||||
}
|
||||
} else {
|
||||
const char *cuser = RSTRING_CSTR(mrb, username);
|
||||
struct passwd *pwd = getpwnam(cuser);
|
||||
if (pwd == NULL) {
|
||||
return mrb_nil_value();
|
||||
}
|
||||
home = pwd->pw_dir;
|
||||
if (!mrb_file_is_absolute_path(home)) {
|
||||
mrb_raisef(mrb, E_ARGUMENT_ERROR, "non-absolute home of ~%v", username);
|
||||
}
|
||||
}
|
||||
home = mrb_locale_from_utf8(home, -1);
|
||||
path = mrb_str_new_cstr(mrb, home);
|
||||
mrb_locale_free(home);
|
||||
return path;
|
||||
#else /* _WIN32 */
|
||||
argc = mrb_get_argc(mrb);
|
||||
if (argc == 0) {
|
||||
home = getenv("USERPROFILE");
|
||||
if (home == NULL) {
|
||||
return mrb_nil_value();
|
||||
}
|
||||
if (!mrb_file_is_absolute_path(home)) {
|
||||
mrb_raise(mrb, E_ARGUMENT_ERROR, "non-absolute home");
|
||||
}
|
||||
} else {
|
||||
return mrb_nil_value();
|
||||
}
|
||||
home = mrb_locale_from_utf8(home, -1);
|
||||
path = mrb_str_new_cstr(mrb, home);
|
||||
mrb_locale_free(home);
|
||||
return path;
|
||||
#endif
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
mrb_file_mtime(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
mrb_value obj;
|
||||
struct stat st;
|
||||
int fd;
|
||||
|
||||
obj = mrb_obj_value(mrb_class_get(mrb, "Time"));
|
||||
fd = mrb_io_fileno(mrb, self);
|
||||
if (fstat(fd, &st) == -1)
|
||||
return mrb_false_value();
|
||||
return mrb_funcall(mrb, obj, "at", 1, mrb_fixnum_value(st.st_mtime));
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
mrb_file_flock(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
#if defined(sun)
|
||||
mrb_raise(mrb, E_NOTIMP_ERROR, "flock is not supported on Illumos/Solaris/Windows");
|
||||
#else
|
||||
mrb_int operation;
|
||||
int fd;
|
||||
|
||||
mrb_get_args(mrb, "i", &operation);
|
||||
fd = mrb_io_fileno(mrb, self);
|
||||
|
||||
while (flock(fd, (int)operation) == -1) {
|
||||
switch (errno) {
|
||||
case EINTR:
|
||||
/* retry */
|
||||
break;
|
||||
case EAGAIN: /* NetBSD */
|
||||
#if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN
|
||||
case EWOULDBLOCK: /* FreeBSD OpenBSD Linux */
|
||||
#endif
|
||||
if (operation & LOCK_NB) {
|
||||
return mrb_false_value();
|
||||
}
|
||||
/* FALLTHRU - should not happen */
|
||||
default:
|
||||
mrb_sys_fail(mrb, "flock failed");
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return mrb_fixnum_value(0);
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
mrb_file_size(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
mrb_stat st;
|
||||
int fd;
|
||||
|
||||
fd = mrb_io_fileno(mrb, self);
|
||||
if (mrb_fstat(fd, &st) == -1) {
|
||||
mrb_raise(mrb, E_RUNTIME_ERROR, "fstat failed");
|
||||
}
|
||||
|
||||
if (st.st_size > MRB_INT_MAX) {
|
||||
#ifdef MRB_WITHOUT_FLOAT
|
||||
mrb_raise(mrb, E_RUNTIME_ERROR, "File#size too large for MRB_WITHOUT_FLOAT");
|
||||
#else
|
||||
return mrb_float_value(mrb, (mrb_float)st.st_size);
|
||||
#endif
|
||||
}
|
||||
|
||||
return mrb_fixnum_value((mrb_int)st.st_size);
|
||||
}
|
||||
|
||||
static int
|
||||
mrb_ftruncate(int fd, mrb_int length)
|
||||
{
|
||||
#ifndef _WIN32
|
||||
return ftruncate(fd, (off_t)length);
|
||||
#else
|
||||
HANDLE file;
|
||||
__int64 cur;
|
||||
|
||||
file = (HANDLE)_get_osfhandle(fd);
|
||||
if (file == INVALID_HANDLE_VALUE) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
cur = _lseeki64(fd, 0, SEEK_CUR);
|
||||
if (cur == -1) return -1;
|
||||
|
||||
if (_lseeki64(fd, (__int64)length, SEEK_SET) == -1) return -1;
|
||||
|
||||
if (!SetEndOfFile(file)) {
|
||||
errno = EINVAL; /* TODO: GetLastError to errno */
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (_lseeki64(fd, cur, SEEK_SET) == -1) return -1;
|
||||
|
||||
return 0;
|
||||
#endif /* _WIN32 */
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
mrb_file_truncate(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
int fd;
|
||||
mrb_int length;
|
||||
mrb_value lenv = mrb_get_arg1(mrb);
|
||||
|
||||
fd = mrb_io_fileno(mrb, self);
|
||||
length = mrb_int(mrb, lenv);
|
||||
if (mrb_ftruncate(fd, length) != 0) {
|
||||
mrb_raise(mrb, E_IO_ERROR, "ftruncate failed");
|
||||
}
|
||||
|
||||
return mrb_fixnum_value(0);
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
mrb_file_s_symlink(mrb_state *mrb, mrb_value klass)
|
||||
{
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
mrb_raise(mrb, E_NOTIMP_ERROR, "symlink is not supported on this platform");
|
||||
#else
|
||||
mrb_value from, to;
|
||||
const char *src, *dst;
|
||||
int ai = mrb_gc_arena_save(mrb);
|
||||
|
||||
mrb_get_args(mrb, "SS", &from, &to);
|
||||
src = mrb_locale_from_utf8(RSTRING_CSTR(mrb, from), -1);
|
||||
dst = mrb_locale_from_utf8(RSTRING_CSTR(mrb, to), -1);
|
||||
if (symlink(src, dst) == -1) {
|
||||
mrb_locale_free(src);
|
||||
mrb_locale_free(dst);
|
||||
mrb_sys_fail(mrb, RSTRING_CSTR(mrb, mrb_format(mrb, "(%v, %v)", from, to)));
|
||||
}
|
||||
mrb_locale_free(src);
|
||||
mrb_locale_free(dst);
|
||||
mrb_gc_arena_restore(mrb, ai);
|
||||
#endif
|
||||
return mrb_fixnum_value(0);
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
mrb_file_s_chmod(mrb_state *mrb, mrb_value klass) {
|
||||
mrb_int mode;
|
||||
mrb_int argc, i;
|
||||
mrb_value *filenames;
|
||||
int ai = mrb_gc_arena_save(mrb);
|
||||
|
||||
mrb_get_args(mrb, "i*", &mode, &filenames, &argc);
|
||||
for (i = 0; i < argc; i++) {
|
||||
const char *utf8_path = RSTRING_CSTR(mrb, filenames[i]);
|
||||
char *path = mrb_locale_from_utf8(utf8_path, -1);
|
||||
if (CHMOD(path, mode) == -1) {
|
||||
mrb_locale_free(path);
|
||||
mrb_sys_fail(mrb, utf8_path);
|
||||
}
|
||||
mrb_locale_free(path);
|
||||
mrb_gc_arena_restore(mrb, ai);
|
||||
}
|
||||
|
||||
return mrb_fixnum_value(argc);
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
mrb_file_s_readlink(mrb_state *mrb, mrb_value klass) {
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
mrb_raise(mrb, E_NOTIMP_ERROR, "readlink is not supported on this platform");
|
||||
return mrb_nil_value(); // unreachable
|
||||
#else
|
||||
char *path, *buf, *tmp;
|
||||
size_t bufsize = 100;
|
||||
ssize_t rc;
|
||||
mrb_value ret;
|
||||
int ai = mrb_gc_arena_save(mrb);
|
||||
|
||||
mrb_get_args(mrb, "z", &path);
|
||||
tmp = mrb_locale_from_utf8(path, -1);
|
||||
|
||||
buf = (char *)mrb_malloc(mrb, bufsize);
|
||||
while ((rc = readlink(tmp, buf, bufsize)) == (ssize_t)bufsize && rc != -1) {
|
||||
bufsize *= 2;
|
||||
buf = (char *)mrb_realloc(mrb, buf, bufsize);
|
||||
}
|
||||
mrb_locale_free(tmp);
|
||||
if (rc == -1) {
|
||||
mrb_free(mrb, buf);
|
||||
mrb_sys_fail(mrb, path);
|
||||
}
|
||||
tmp = mrb_utf8_from_locale(buf, -1);
|
||||
ret = mrb_str_new(mrb, tmp, rc);
|
||||
mrb_locale_free(tmp);
|
||||
mrb_free(mrb, buf);
|
||||
|
||||
mrb_gc_arena_restore(mrb, ai);
|
||||
return ret;
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
mrb_init_file(mrb_state *mrb)
|
||||
{
|
||||
struct RClass *io, *file, *cnst;
|
||||
|
||||
io = mrb_class_get(mrb, "IO");
|
||||
file = mrb_define_class(mrb, "File", io);
|
||||
MRB_SET_INSTANCE_TT(file, MRB_TT_DATA);
|
||||
mrb_define_class_method(mrb, file, "umask", mrb_file_s_umask, MRB_ARGS_OPT(1));
|
||||
mrb_define_class_method(mrb, file, "delete", mrb_file_s_unlink, MRB_ARGS_ANY());
|
||||
mrb_define_class_method(mrb, file, "unlink", mrb_file_s_unlink, MRB_ARGS_ANY());
|
||||
mrb_define_class_method(mrb, file, "rename", mrb_file_s_rename, MRB_ARGS_REQ(2));
|
||||
mrb_define_class_method(mrb, file, "symlink", mrb_file_s_symlink, MRB_ARGS_REQ(2));
|
||||
mrb_define_class_method(mrb, file, "chmod", mrb_file_s_chmod, MRB_ARGS_REQ(1) | MRB_ARGS_REST());
|
||||
mrb_define_class_method(mrb, file, "readlink", mrb_file_s_readlink, MRB_ARGS_REQ(1));
|
||||
|
||||
mrb_define_class_method(mrb, file, "dirname", mrb_file_dirname, MRB_ARGS_REQ(1));
|
||||
mrb_define_class_method(mrb, file, "basename", mrb_file_basename, MRB_ARGS_REQ(1));
|
||||
mrb_define_class_method(mrb, file, "realpath", mrb_file_realpath, MRB_ARGS_REQ(1)|MRB_ARGS_OPT(1));
|
||||
mrb_define_class_method(mrb, file, "_getwd", mrb_file__getwd, MRB_ARGS_NONE());
|
||||
mrb_define_class_method(mrb, file, "_gethome", mrb_file__gethome, MRB_ARGS_OPT(1));
|
||||
|
||||
mrb_define_method(mrb, file, "flock", mrb_file_flock, MRB_ARGS_REQ(1));
|
||||
mrb_define_method(mrb, file, "mtime", mrb_file_mtime, MRB_ARGS_NONE());
|
||||
mrb_define_method(mrb, file, "size", mrb_file_size, MRB_ARGS_NONE());
|
||||
mrb_define_method(mrb, file, "truncate", mrb_file_truncate, MRB_ARGS_REQ(1));
|
||||
|
||||
cnst = mrb_define_module_under(mrb, file, "Constants");
|
||||
mrb_define_const(mrb, cnst, "LOCK_SH", mrb_fixnum_value(LOCK_SH));
|
||||
mrb_define_const(mrb, cnst, "LOCK_EX", mrb_fixnum_value(LOCK_EX));
|
||||
mrb_define_const(mrb, cnst, "LOCK_UN", mrb_fixnum_value(LOCK_UN));
|
||||
mrb_define_const(mrb, cnst, "LOCK_NB", mrb_fixnum_value(LOCK_NB));
|
||||
mrb_define_const(mrb, cnst, "SEPARATOR", mrb_str_new_cstr(mrb, FILE_SEPARATOR));
|
||||
mrb_define_const(mrb, cnst, "PATH_SEPARATOR", mrb_str_new_cstr(mrb, PATH_SEPARATOR));
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
mrb_define_const(mrb, cnst, "ALT_SEPARATOR", mrb_str_new_cstr(mrb, FILE_ALT_SEPARATOR));
|
||||
#else
|
||||
mrb_define_const(mrb, cnst, "ALT_SEPARATOR", mrb_nil_value());
|
||||
#endif
|
||||
mrb_define_const(mrb, cnst, "NULL", mrb_str_new_cstr(mrb, NULL_FILE));
|
||||
|
||||
mrb_define_const(mrb, cnst, "RDONLY", mrb_fixnum_value(MRB_O_RDONLY));
|
||||
mrb_define_const(mrb, cnst, "WRONLY", mrb_fixnum_value(MRB_O_WRONLY));
|
||||
mrb_define_const(mrb, cnst, "RDWR", mrb_fixnum_value(MRB_O_RDWR));
|
||||
mrb_define_const(mrb, cnst, "APPEND", mrb_fixnum_value(MRB_O_APPEND));
|
||||
mrb_define_const(mrb, cnst, "CREAT", mrb_fixnum_value(MRB_O_CREAT));
|
||||
mrb_define_const(mrb, cnst, "EXCL", mrb_fixnum_value(MRB_O_EXCL));
|
||||
mrb_define_const(mrb, cnst, "TRUNC", mrb_fixnum_value(MRB_O_TRUNC));
|
||||
mrb_define_const(mrb, cnst, "NONBLOCK", mrb_fixnum_value(MRB_O_NONBLOCK));
|
||||
mrb_define_const(mrb, cnst, "NOCTTY", mrb_fixnum_value(MRB_O_NOCTTY));
|
||||
mrb_define_const(mrb, cnst, "BINARY", mrb_fixnum_value(MRB_O_BINARY));
|
||||
mrb_define_const(mrb, cnst, "SHARE_DELETE", mrb_fixnum_value(MRB_O_SHARE_DELETE));
|
||||
mrb_define_const(mrb, cnst, "SYNC", mrb_fixnum_value(MRB_O_SYNC));
|
||||
mrb_define_const(mrb, cnst, "DSYNC", mrb_fixnum_value(MRB_O_DSYNC));
|
||||
mrb_define_const(mrb, cnst, "RSYNC", mrb_fixnum_value(MRB_O_RSYNC));
|
||||
mrb_define_const(mrb, cnst, "NOFOLLOW", mrb_fixnum_value(MRB_O_NOFOLLOW));
|
||||
mrb_define_const(mrb, cnst, "NOATIME", mrb_fixnum_value(MRB_O_NOATIME));
|
||||
mrb_define_const(mrb, cnst, "DIRECT", mrb_fixnum_value(MRB_O_DIRECT));
|
||||
mrb_define_const(mrb, cnst, "TMPFILE", mrb_fixnum_value(MRB_O_TMPFILE));
|
||||
}
|
||||
@@ -0,0 +1,347 @@
|
||||
/*
|
||||
** file_test.c - FileTest class
|
||||
*/
|
||||
|
||||
#include "mruby.h"
|
||||
#include "mruby/class.h"
|
||||
#include "mruby/data.h"
|
||||
#include "mruby/string.h"
|
||||
#include "mruby/ext/io.h"
|
||||
#include "mruby/error.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#define LSTAT stat
|
||||
#include <winsock.h>
|
||||
#else
|
||||
#define LSTAT lstat
|
||||
#include <sys/file.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/wait.h>
|
||||
#include <libgen.h>
|
||||
#include <pwd.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
extern struct mrb_data_type mrb_io_type;
|
||||
|
||||
static int
|
||||
mrb_stat0(mrb_state *mrb, mrb_value obj, struct stat *st, int do_lstat)
|
||||
{
|
||||
if (mrb_obj_is_kind_of(mrb, obj, mrb_class_get(mrb, "IO"))) {
|
||||
struct mrb_io *fptr;
|
||||
fptr = (struct mrb_io *)mrb_data_get_ptr(mrb, obj, &mrb_io_type);
|
||||
|
||||
if (fptr && fptr->fd >= 0) {
|
||||
return fstat(fptr->fd, st);
|
||||
}
|
||||
|
||||
mrb_raise(mrb, E_IO_ERROR, "closed stream");
|
||||
return -1;
|
||||
}
|
||||
else {
|
||||
char *path = mrb_locale_from_utf8(RSTRING_CSTR(mrb, obj), -1);
|
||||
int ret;
|
||||
if (do_lstat) {
|
||||
ret = LSTAT(path, st);
|
||||
} else {
|
||||
ret = stat(path, st);
|
||||
}
|
||||
mrb_locale_free(path);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
mrb_stat(mrb_state *mrb, mrb_value obj, struct stat *st)
|
||||
{
|
||||
return mrb_stat0(mrb, obj, st, 0);
|
||||
}
|
||||
|
||||
static int
|
||||
mrb_lstat(mrb_state *mrb, mrb_value obj, struct stat *st)
|
||||
{
|
||||
return mrb_stat0(mrb, obj, st, 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Document-method: directory?
|
||||
*
|
||||
* call-seq:
|
||||
* File.directory?(file_name) -> true or false
|
||||
*
|
||||
* Returns <code>true</code> if the named file is a directory,
|
||||
* or a symlink that points at a directory, and <code>false</code>
|
||||
* otherwise.
|
||||
*
|
||||
* File.directory?(".")
|
||||
*/
|
||||
|
||||
static mrb_value
|
||||
mrb_filetest_s_directory_p(mrb_state *mrb, mrb_value klass)
|
||||
{
|
||||
#ifndef S_ISDIR
|
||||
# define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
|
||||
#endif
|
||||
|
||||
struct stat st;
|
||||
mrb_value obj = mrb_get_arg1(mrb);
|
||||
|
||||
if (mrb_stat(mrb, obj, &st) < 0)
|
||||
return mrb_false_value();
|
||||
if (S_ISDIR(st.st_mode))
|
||||
return mrb_true_value();
|
||||
|
||||
return mrb_false_value();
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* File.pipe?(file_name) -> true or false
|
||||
*
|
||||
* Returns <code>true</code> if the named file is a pipe.
|
||||
*/
|
||||
|
||||
static mrb_value
|
||||
mrb_filetest_s_pipe_p(mrb_state *mrb, mrb_value klass)
|
||||
{
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
mrb_raise(mrb, E_NOTIMP_ERROR, "pipe is not supported on this platform");
|
||||
#else
|
||||
#ifdef S_IFIFO
|
||||
# ifndef S_ISFIFO
|
||||
# define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO)
|
||||
# endif
|
||||
|
||||
struct stat st;
|
||||
mrb_value obj = mrb_get_arg1(mrb);
|
||||
|
||||
if (mrb_stat(mrb, obj, &st) < 0)
|
||||
return mrb_false_value();
|
||||
if (S_ISFIFO(st.st_mode))
|
||||
return mrb_true_value();
|
||||
|
||||
#endif
|
||||
return mrb_false_value();
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* File.symlink?(file_name) -> true or false
|
||||
*
|
||||
* Returns <code>true</code> if the named file is a symbolic link.
|
||||
*/
|
||||
|
||||
static mrb_value
|
||||
mrb_filetest_s_symlink_p(mrb_state *mrb, mrb_value klass)
|
||||
{
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
mrb_raise(mrb, E_NOTIMP_ERROR, "symlink is not supported on this platform");
|
||||
#else
|
||||
#ifndef S_ISLNK
|
||||
# ifdef _S_ISLNK
|
||||
# define S_ISLNK(m) _S_ISLNK(m)
|
||||
# else
|
||||
# ifdef _S_IFLNK
|
||||
# define S_ISLNK(m) (((m) & S_IFMT) == _S_IFLNK)
|
||||
# else
|
||||
# ifdef S_IFLNK
|
||||
# define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)
|
||||
# endif
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef S_ISLNK
|
||||
struct stat st;
|
||||
mrb_value obj = mrb_get_arg1(mrb);
|
||||
|
||||
if (mrb_lstat(mrb, obj, &st) == -1)
|
||||
return mrb_false_value();
|
||||
if (S_ISLNK(st.st_mode))
|
||||
return mrb_true_value();
|
||||
#endif
|
||||
|
||||
return mrb_false_value();
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* File.socket?(file_name) -> true or false
|
||||
*
|
||||
* Returns <code>true</code> if the named file is a socket.
|
||||
*/
|
||||
|
||||
static mrb_value
|
||||
mrb_filetest_s_socket_p(mrb_state *mrb, mrb_value klass)
|
||||
{
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
mrb_raise(mrb, E_NOTIMP_ERROR, "socket is not supported on this platform");
|
||||
#else
|
||||
#ifndef S_ISSOCK
|
||||
# ifdef _S_ISSOCK
|
||||
# define S_ISSOCK(m) _S_ISSOCK(m)
|
||||
# else
|
||||
# ifdef _S_IFSOCK
|
||||
# define S_ISSOCK(m) (((m) & S_IFMT) == _S_IFSOCK)
|
||||
# else
|
||||
# ifdef S_IFSOCK
|
||||
# define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK)
|
||||
# endif
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef S_ISSOCK
|
||||
struct stat st;
|
||||
mrb_value obj = mrb_get_arg1(mrb);
|
||||
|
||||
if (mrb_stat(mrb, obj, &st) < 0)
|
||||
return mrb_false_value();
|
||||
if (S_ISSOCK(st.st_mode))
|
||||
return mrb_true_value();
|
||||
#endif
|
||||
|
||||
return mrb_false_value();
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* File.exist?(file_name) -> true or false
|
||||
* File.exists?(file_name) -> true or false
|
||||
*
|
||||
* Return <code>true</code> if the named file exists.
|
||||
*/
|
||||
|
||||
static mrb_value
|
||||
mrb_filetest_s_exist_p(mrb_state *mrb, mrb_value klass)
|
||||
{
|
||||
struct stat st;
|
||||
mrb_value obj = mrb_get_arg1(mrb);
|
||||
|
||||
if (mrb_stat(mrb, obj, &st) < 0)
|
||||
return mrb_false_value();
|
||||
|
||||
return mrb_true_value();
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* File.file?(file_name) -> true or false
|
||||
*
|
||||
* Returns <code>true</code> if the named file exists and is a
|
||||
* regular file.
|
||||
*/
|
||||
|
||||
static mrb_value
|
||||
mrb_filetest_s_file_p(mrb_state *mrb, mrb_value klass)
|
||||
{
|
||||
#ifndef S_ISREG
|
||||
# define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
|
||||
#endif
|
||||
|
||||
struct stat st;
|
||||
mrb_value obj = mrb_get_arg1(mrb);
|
||||
|
||||
if (mrb_stat(mrb, obj, &st) < 0)
|
||||
return mrb_false_value();
|
||||
if (S_ISREG(st.st_mode))
|
||||
return mrb_true_value();
|
||||
|
||||
return mrb_false_value();
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* File.zero?(file_name) -> true or false
|
||||
*
|
||||
* Returns <code>true</code> if the named file exists and has
|
||||
* a zero size.
|
||||
*/
|
||||
|
||||
static mrb_value
|
||||
mrb_filetest_s_zero_p(mrb_state *mrb, mrb_value klass)
|
||||
{
|
||||
struct stat st;
|
||||
mrb_value obj = mrb_get_arg1(mrb);
|
||||
|
||||
if (mrb_stat(mrb, obj, &st) < 0)
|
||||
return mrb_false_value();
|
||||
if (st.st_size == 0)
|
||||
return mrb_true_value();
|
||||
|
||||
return mrb_false_value();
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* File.size(file_name) -> integer
|
||||
*
|
||||
* Returns the size of <code>file_name</code>.
|
||||
*
|
||||
* _file_name_ can be an IO object.
|
||||
*/
|
||||
|
||||
static mrb_value
|
||||
mrb_filetest_s_size(mrb_state *mrb, mrb_value klass)
|
||||
{
|
||||
struct stat st;
|
||||
mrb_value obj = mrb_get_arg1(mrb);
|
||||
|
||||
if (mrb_stat(mrb, obj, &st) < 0)
|
||||
mrb_sys_fail(mrb, "mrb_stat");
|
||||
|
||||
return mrb_fixnum_value(st.st_size);
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* File.size?(file_name) -> Integer or nil
|
||||
*
|
||||
* Returns +nil+ if +file_name+ doesn't exist or has zero size, the size of the
|
||||
* file otherwise.
|
||||
*/
|
||||
|
||||
static mrb_value
|
||||
mrb_filetest_s_size_p(mrb_state *mrb, mrb_value klass)
|
||||
{
|
||||
struct stat st;
|
||||
mrb_value obj = mrb_get_arg1(mrb);
|
||||
|
||||
if (mrb_stat(mrb, obj, &st) < 0)
|
||||
return mrb_nil_value();
|
||||
if (st.st_size == 0)
|
||||
return mrb_nil_value();
|
||||
|
||||
return mrb_fixnum_value(st.st_size);
|
||||
}
|
||||
|
||||
void
|
||||
mrb_init_file_test(mrb_state *mrb)
|
||||
{
|
||||
struct RClass *f;
|
||||
|
||||
f = mrb_define_class(mrb, "FileTest", mrb->object_class);
|
||||
|
||||
mrb_define_class_method(mrb, f, "directory?", mrb_filetest_s_directory_p, MRB_ARGS_REQ(1));
|
||||
mrb_define_class_method(mrb, f, "exist?", mrb_filetest_s_exist_p, MRB_ARGS_REQ(1));
|
||||
mrb_define_class_method(mrb, f, "exists?", mrb_filetest_s_exist_p, MRB_ARGS_REQ(1));
|
||||
mrb_define_class_method(mrb, f, "file?", mrb_filetest_s_file_p, MRB_ARGS_REQ(1));
|
||||
mrb_define_class_method(mrb, f, "pipe?", mrb_filetest_s_pipe_p, MRB_ARGS_REQ(1));
|
||||
mrb_define_class_method(mrb, f, "size", mrb_filetest_s_size, MRB_ARGS_REQ(1));
|
||||
mrb_define_class_method(mrb, f, "size?", mrb_filetest_s_size_p, MRB_ARGS_REQ(1));
|
||||
mrb_define_class_method(mrb, f, "socket?", mrb_filetest_s_socket_p, MRB_ARGS_REQ(1));
|
||||
mrb_define_class_method(mrb, f, "symlink?", mrb_filetest_s_symlink_p, MRB_ARGS_REQ(1));
|
||||
mrb_define_class_method(mrb, f, "zero?", mrb_filetest_s_zero_p, MRB_ARGS_REQ(1));
|
||||
}
|
||||
+1547
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,20 @@
|
||||
#include "mruby.h"
|
||||
|
||||
void mrb_init_io(mrb_state *mrb);
|
||||
void mrb_init_file(mrb_state *mrb);
|
||||
void mrb_init_file_test(mrb_state *mrb);
|
||||
|
||||
#define DONE mrb_gc_arena_restore(mrb, 0)
|
||||
|
||||
void
|
||||
mrb_mruby_io_gem_init(mrb_state* mrb)
|
||||
{
|
||||
mrb_init_io(mrb); DONE;
|
||||
mrb_init_file(mrb); DONE;
|
||||
mrb_init_file_test(mrb); DONE;
|
||||
}
|
||||
|
||||
void
|
||||
mrb_mruby_io_gem_final(mrb_state* mrb)
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
##
|
||||
# File Test
|
||||
|
||||
MRubyIOTestUtil.io_test_setup
|
||||
|
||||
assert('File.class', '15.2.21') do
|
||||
assert_equal Class, File.class
|
||||
end
|
||||
|
||||
assert('File.superclass', '15.2.21.2') do
|
||||
assert_equal IO, File.superclass
|
||||
end
|
||||
|
||||
assert('File#initialize', '15.2.21.4.1') do
|
||||
io = File.open($mrbtest_io_rfname, "r")
|
||||
assert_nil io.close
|
||||
assert_raise IOError do
|
||||
io.close
|
||||
end
|
||||
end
|
||||
|
||||
assert('File#path', '15.2.21.4.2') do
|
||||
io = File.open($mrbtest_io_rfname, "r")
|
||||
assert_equal $mrbtest_io_msg, io.read
|
||||
assert_equal $mrbtest_io_rfname, io.path
|
||||
io.close
|
||||
assert_equal $mrbtest_io_rfname, io.path
|
||||
assert_true io.closed?
|
||||
end
|
||||
|
||||
assert('File.basename') do
|
||||
assert_equal '/', File.basename('//')
|
||||
assert_equal 'a', File.basename('/a/')
|
||||
assert_equal 'b', File.basename('/a/b')
|
||||
assert_equal 'b', File.basename('../a/b')
|
||||
assert_raise(ArgumentError) { File.basename("/a/b\0") }
|
||||
end
|
||||
|
||||
assert('File.dirname') do
|
||||
assert_equal '.', File.dirname('')
|
||||
assert_equal '.', File.dirname('a')
|
||||
assert_equal '/', File.dirname('/a')
|
||||
assert_equal 'a', File.dirname('a/b')
|
||||
assert_equal '/a', File.dirname('/a/b')
|
||||
end
|
||||
|
||||
assert('File.extname') do
|
||||
assert_equal '.txt', File.extname('foo/foo.txt')
|
||||
assert_equal '.gz', File.extname('foo/foo.tar.gz')
|
||||
assert_equal '', File.extname('foo/bar')
|
||||
assert_equal '', File.extname('foo/.bar')
|
||||
assert_equal '', File.extname('foo.txt/bar')
|
||||
assert_equal '', File.extname('.foo')
|
||||
end
|
||||
|
||||
assert('File#flock') do
|
||||
f = File.open $mrbtest_io_rfname
|
||||
begin
|
||||
assert_equal(f.flock(File::LOCK_SH), 0)
|
||||
assert_equal(f.flock(File::LOCK_UN), 0)
|
||||
assert_equal(f.flock(File::LOCK_EX | File::LOCK_NB), 0)
|
||||
assert_equal(f.flock(File::LOCK_UN), 0)
|
||||
rescue NotImplementedError => e
|
||||
skip e.message
|
||||
ensure
|
||||
f.close
|
||||
end
|
||||
end
|
||||
|
||||
assert('File#mtime') do
|
||||
begin
|
||||
File.open("#{$mrbtest_io_wfname}.mtime", 'w') do |f|
|
||||
assert_equal Time, f.mtime.class
|
||||
File.open("#{$mrbtest_io_wfname}.mtime", 'r') do |f2|
|
||||
assert_equal true, f.mtime == f2.mtime
|
||||
end
|
||||
end
|
||||
ensure
|
||||
File.delete("#{$mrbtest_io_wfname}.mtime")
|
||||
end
|
||||
end
|
||||
|
||||
assert('File#size and File#truncate') do
|
||||
fname = "#{$mrbtest_io_wfname}.resize"
|
||||
begin
|
||||
File.open(fname, 'w') do |f|
|
||||
assert_equal 0, f.size
|
||||
assert_equal 0, f.truncate(100)
|
||||
assert_equal 100, f.size
|
||||
assert_equal 0, f.pos
|
||||
assert_equal 0, f.truncate(5)
|
||||
assert_equal 5, f.size
|
||||
end
|
||||
ensure
|
||||
File.delete(fname)
|
||||
end
|
||||
end
|
||||
|
||||
assert('File.join') do
|
||||
assert_equal "", File.join()
|
||||
assert_equal "a", File.join("a")
|
||||
assert_equal "/a", File.join("/a")
|
||||
assert_equal "a/", File.join("a/")
|
||||
assert_equal "a/b/c", File.join("a", "b", "c")
|
||||
assert_equal "/a/b/c", File.join("/a", "b", "c")
|
||||
assert_equal "a/b/c/", File.join("a", "b", "c/")
|
||||
assert_equal "a/b/c", File.join("a/", "/b/", "/c")
|
||||
assert_equal "a/b/c", File.join(["a", "b", "c"])
|
||||
assert_equal "a/b/c", File.join("a", ["b", ["c"]])
|
||||
end
|
||||
|
||||
assert('File.realpath') do
|
||||
dir = MRubyIOTestUtil.mkdtemp("mruby-io-test.XXXXXX")
|
||||
begin
|
||||
sep = File::ALT_SEPARATOR || File::SEPARATOR
|
||||
relative_path = "#{File.basename(dir)}#{sep}realpath_test"
|
||||
path = "#{File._getwd}#{sep}#{relative_path}"
|
||||
File.open(path, "w"){}
|
||||
assert_equal path, File.realpath(relative_path)
|
||||
|
||||
unless MRubyIOTestUtil.win?
|
||||
path1 = File.realpath($mrbtest_io_rfname)
|
||||
path2 = File.realpath($mrbtest_io_symlinkname)
|
||||
assert_equal path1, path2
|
||||
end
|
||||
ensure
|
||||
File.delete path rescue nil
|
||||
MRubyIOTestUtil.rmdir dir
|
||||
end
|
||||
|
||||
assert_raise(ArgumentError) { File.realpath("TO\0DO") }
|
||||
end
|
||||
|
||||
assert("File.readlink") do
|
||||
begin
|
||||
exp = File.basename($mrbtest_io_rfname)
|
||||
act = File.readlink($mrbtest_io_symlinkname)
|
||||
assert_equal exp, act
|
||||
rescue NotImplementedError => e
|
||||
skip e.message
|
||||
end
|
||||
end
|
||||
|
||||
assert("File.readlink fails with non-symlink") do
|
||||
skip "readlink is not supported on this platform" if MRubyIOTestUtil.win?
|
||||
begin
|
||||
e2 = nil
|
||||
assert_raise(RuntimeError) {
|
||||
begin
|
||||
File.readlink($mrbtest_io_rfname)
|
||||
rescue => e
|
||||
if Object.const_defined?(:SystemCallError) and e.kind_of?(SystemCallError)
|
||||
raise RuntimeError, "SystemCallError converted to RuntimeError"
|
||||
end
|
||||
raise e
|
||||
rescue NotImplementedError => e
|
||||
e2 = e
|
||||
end
|
||||
}
|
||||
raise e2 if e2
|
||||
rescue NotImplementedError => e
|
||||
skip e.message
|
||||
end
|
||||
end
|
||||
|
||||
assert('File.expand_path') do
|
||||
assert_equal "/", File.expand_path("..", "/tmp"), "parent path with base_dir (1)"
|
||||
assert_equal "/tmp", File.expand_path("..", "/tmp/mruby"), "parent path with base_dir (2)"
|
||||
|
||||
assert_equal "/home", File.expand_path("/home"), "absolute"
|
||||
assert_equal "/home", File.expand_path("/home", "."), "absolute with base_dir"
|
||||
|
||||
assert_equal "/hoge", File.expand_path("/tmp/..//hoge")
|
||||
assert_equal "/hoge", File.expand_path("////tmp/..///////hoge")
|
||||
|
||||
assert_equal "/", File.expand_path("../../../..", "/")
|
||||
if File._getwd[1] == ":"
|
||||
drive_letter = File._getwd[0]
|
||||
assert_equal drive_letter + ":\\", File.expand_path(([".."] * 100).join("/"))
|
||||
else
|
||||
assert_equal "/", File.expand_path(([".."] * 100).join("/"))
|
||||
end
|
||||
end
|
||||
|
||||
assert('File.expand_path (with ENV)') do
|
||||
skip unless Object.const_defined?(:ENV) && ENV['HOME']
|
||||
|
||||
assert_equal ENV['HOME'], File.expand_path("~/"), "home"
|
||||
assert_equal ENV['HOME'], File.expand_path("~/", "/"), "home with base_dir"
|
||||
|
||||
assert_equal "#{ENV['HOME']}/user", File.expand_path("user", ENV['HOME']), "relative with base_dir"
|
||||
end
|
||||
|
||||
assert('File.path') do
|
||||
assert_equal "", File.path("")
|
||||
assert_equal "a/b/c", File.path("a/b/c")
|
||||
assert_equal "a/../b/./c", File.path("a/../b/./c")
|
||||
assert_raise(TypeError) { File.path(nil) }
|
||||
assert_raise(TypeError) { File.path(123) }
|
||||
end
|
||||
|
||||
assert('File.symlink') do
|
||||
target_name = "/usr/bin"
|
||||
if !File.exist?(target_name)
|
||||
skip("target directory of File.symlink is not found")
|
||||
end
|
||||
|
||||
begin
|
||||
tmpdir = MRubyIOTestUtil.mkdtemp("mruby-io-test.XXXXXX")
|
||||
rescue => e
|
||||
skip e.message
|
||||
end
|
||||
|
||||
symlink_name = "#{tmpdir}/test-bin-dummy"
|
||||
begin
|
||||
assert_equal 0, File.symlink(target_name, symlink_name)
|
||||
assert_equal true, File.symlink?(symlink_name)
|
||||
rescue NotImplementedError => e
|
||||
skip e.message
|
||||
ensure
|
||||
File.delete symlink_name rescue nil
|
||||
MRubyIOTestUtil.rmdir tmpdir rescue nil
|
||||
end
|
||||
end
|
||||
|
||||
assert('File.chmod') do
|
||||
File.open("#{$mrbtest_io_wfname}.chmod-test", 'w') {}
|
||||
begin
|
||||
assert_equal 1, File.chmod(0400, "#{$mrbtest_io_wfname}.chmod-test")
|
||||
ensure
|
||||
File.delete("#{$mrbtest_io_wfname}.chmod-test")
|
||||
end
|
||||
end
|
||||
|
||||
MRubyIOTestUtil.io_test_cleanup
|
||||
@@ -0,0 +1,112 @@
|
||||
##
|
||||
# FileTest
|
||||
|
||||
MRubyIOTestUtil.io_test_setup
|
||||
|
||||
assert("FileTest.directory?") do
|
||||
dir = MRubyIOTestUtil.mkdtemp("mruby-io-test.XXXXXX")
|
||||
begin
|
||||
assert_true FileTest.directory?(dir)
|
||||
assert_false FileTest.directory?($mrbtest_io_rfname)
|
||||
ensure
|
||||
MRubyIOTestUtil.rmdir dir
|
||||
end
|
||||
end
|
||||
|
||||
assert("FileTest.exist?") do
|
||||
assert_equal true, FileTest.exist?($mrbtest_io_rfname), "filename - exist"
|
||||
assert_equal false, FileTest.exist?($mrbtest_io_rfname + "-"), "filename - not exist"
|
||||
io = IO.new(IO.sysopen($mrbtest_io_rfname))
|
||||
assert_equal true, FileTest.exist?(io), "io obj - exist"
|
||||
io.close
|
||||
assert_equal true, io.closed?
|
||||
assert_raise(IOError) { FileTest.exist?(io) }
|
||||
assert_raise(TypeError) { File.exist?($mrbtest_io_rfname.to_sym) }
|
||||
end
|
||||
|
||||
assert("FileTest.file?") do
|
||||
dir = MRubyIOTestUtil.mkdtemp("mruby-io-test.XXXXXX")
|
||||
begin
|
||||
assert_true FileTest.file?($mrbtest_io_rfname)
|
||||
assert_false FileTest.file?(dir)
|
||||
ensure
|
||||
MRubyIOTestUtil.rmdir dir
|
||||
end
|
||||
end
|
||||
|
||||
assert("FileTest.pipe?") do
|
||||
begin
|
||||
assert_equal false, FileTest.pipe?("/tmp")
|
||||
io = IO.popen("ls")
|
||||
assert_equal true, FileTest.pipe?(io)
|
||||
rescue NotImplementedError => e
|
||||
skip e.message
|
||||
end
|
||||
end
|
||||
|
||||
assert('FileTest.size') do
|
||||
assert_equal FileTest.size($mrbtest_io_rfname), $mrbtest_io_msg.size
|
||||
assert_equal FileTest.size($mrbtest_io_wfname), 0
|
||||
end
|
||||
|
||||
assert("FileTest.size?") do
|
||||
assert_equal $mrbtest_io_msg.size, FileTest.size?($mrbtest_io_rfname)
|
||||
assert_equal nil, FileTest.size?($mrbtest_io_wfname)
|
||||
assert_equal nil, FileTest.size?("not-exist-test-target-file")
|
||||
|
||||
fp1 = File.open($mrbtest_io_rfname)
|
||||
fp2 = File.open($mrbtest_io_wfname)
|
||||
assert_equal $mrbtest_io_msg.size, FileTest.size?(fp1)
|
||||
assert_equal nil, FileTest.size?(fp2)
|
||||
fp1.close
|
||||
fp2.close
|
||||
|
||||
assert_raise IOError do
|
||||
FileTest.size?(fp1)
|
||||
end
|
||||
assert_true fp1.closed?
|
||||
assert_raise IOError do
|
||||
FileTest.size?(fp2)
|
||||
end
|
||||
assert_true fp2.closed?
|
||||
end
|
||||
|
||||
assert("FileTest.socket?") do
|
||||
begin
|
||||
assert_true FileTest.socket?($mrbtest_io_socketname)
|
||||
rescue NotImplementedError => e
|
||||
skip e.message
|
||||
end
|
||||
end
|
||||
|
||||
assert("FileTest.symlink?") do
|
||||
begin
|
||||
assert_true FileTest.symlink?($mrbtest_io_symlinkname)
|
||||
rescue NotImplementedError => e
|
||||
skip e.message
|
||||
end
|
||||
end
|
||||
|
||||
assert("FileTest.zero?") do
|
||||
assert_equal false, FileTest.zero?($mrbtest_io_rfname)
|
||||
assert_equal true, FileTest.zero?($mrbtest_io_wfname)
|
||||
assert_equal false, FileTest.zero?("not-exist-test-target-file")
|
||||
|
||||
fp1 = File.open($mrbtest_io_rfname)
|
||||
fp2 = File.open($mrbtest_io_wfname)
|
||||
assert_equal false, FileTest.zero?(fp1)
|
||||
assert_equal true, FileTest.zero?(fp2)
|
||||
fp1.close
|
||||
fp2.close
|
||||
|
||||
assert_raise IOError do
|
||||
FileTest.zero?(fp1)
|
||||
end
|
||||
assert_true fp1.closed?
|
||||
assert_raise IOError do
|
||||
FileTest.zero?(fp2)
|
||||
end
|
||||
assert_true fp2.closed?
|
||||
end
|
||||
|
||||
MRubyIOTestUtil.io_test_cleanup
|
||||
@@ -0,0 +1,647 @@
|
||||
##
|
||||
# IO Test
|
||||
|
||||
MRubyIOTestUtil.io_test_setup
|
||||
$cr, $crlf, $cmd = MRubyIOTestUtil.win? ? [1, "\r\n", "cmd /c "] : [0, "\n", ""]
|
||||
|
||||
def assert_io_open(meth)
|
||||
assert "assert_io_open" do
|
||||
fd = IO.sysopen($mrbtest_io_rfname)
|
||||
assert_equal Fixnum, fd.class
|
||||
io1 = IO.__send__(meth, fd)
|
||||
begin
|
||||
assert_equal IO, io1.class
|
||||
assert_equal $mrbtest_io_msg, io1.read
|
||||
ensure
|
||||
io1.close
|
||||
end
|
||||
|
||||
io2 = IO.__send__(meth, IO.sysopen($mrbtest_io_rfname))do |io|
|
||||
if meth == :open
|
||||
assert_equal $mrbtest_io_msg, io.read
|
||||
else
|
||||
flunk "IO.#{meth} does not take block"
|
||||
end
|
||||
end
|
||||
io2.close unless meth == :open
|
||||
|
||||
assert_raise(RuntimeError) { IO.__send__(meth, 1023) } # For Windows
|
||||
assert_raise(RuntimeError) { IO.__send__(meth, 1 << 26) }
|
||||
assert_raise(RuntimeError) { IO.__send__(meth, 1 << 32) } if (1 << 32).kind_of?(Integer)
|
||||
end
|
||||
end
|
||||
|
||||
assert('IO.class', '15.2.20') do
|
||||
assert_equal(Class, IO.class)
|
||||
end
|
||||
|
||||
assert('IO.superclass', '15.2.20.2') do
|
||||
assert_equal(Object, IO.superclass)
|
||||
end
|
||||
|
||||
assert('IO.ancestors', '15.2.20.3') do
|
||||
assert_include(IO.ancestors, Enumerable)
|
||||
end
|
||||
|
||||
assert('IO.open', '15.2.20.4.1') do
|
||||
assert_io_open(:open)
|
||||
end
|
||||
|
||||
assert('IO#close', '15.2.20.5.1') do
|
||||
io = IO.new(IO.sysopen($mrbtest_io_rfname))
|
||||
assert_nil io.close
|
||||
end
|
||||
|
||||
assert('IO#closed?', '15.2.20.5.2') do
|
||||
io = IO.new(IO.sysopen($mrbtest_io_rfname))
|
||||
assert_false io.closed?
|
||||
io.close
|
||||
assert_true io.closed?
|
||||
end
|
||||
|
||||
#assert('IO#each', '15.2.20.5.3') do
|
||||
#assert('IO#each_byte', '15.2.20.5.4') do
|
||||
#assert('IO#each_line', '15.2.20.5.5') do
|
||||
|
||||
assert('IO#eof?', '15.2.20.5.6') do
|
||||
io = IO.new(IO.sysopen($mrbtest_io_wfname, 'w'), 'w')
|
||||
assert_raise(IOError) do
|
||||
io.eof?
|
||||
end
|
||||
io.close
|
||||
|
||||
# empty file
|
||||
io = IO.open(IO.sysopen($mrbtest_io_wfname, 'w'), 'w')
|
||||
io.close
|
||||
io = IO.open(IO.sysopen($mrbtest_io_wfname, 'r'), 'r')
|
||||
assert_true io.eof?
|
||||
io.close
|
||||
|
||||
# nonempty file
|
||||
io = IO.new(IO.sysopen($mrbtest_io_rfname))
|
||||
assert_false io.eof?
|
||||
io.readchar
|
||||
assert_false io.eof?
|
||||
io.read
|
||||
assert_true io.eof?
|
||||
io.close
|
||||
end
|
||||
|
||||
assert('IO#flush', '15.2.20.5.7') do
|
||||
# Note: mruby-io does not have any buffer to be flushed now.
|
||||
io = IO.new(IO.sysopen($mrbtest_io_wfname))
|
||||
assert_equal io, io.flush
|
||||
io.close
|
||||
assert_raise(IOError) do
|
||||
io.flush
|
||||
end
|
||||
end
|
||||
|
||||
assert('IO#getc', '15.2.20.5.8') do
|
||||
io = IO.new(IO.sysopen($mrbtest_io_rfname))
|
||||
$mrbtest_io_msg.split("").each { |ch|
|
||||
assert_equal ch, io.getc
|
||||
}
|
||||
assert_equal nil, io.getc
|
||||
io.close
|
||||
end
|
||||
|
||||
#assert('IO#gets', '15.2.20.5.9') do
|
||||
#assert('IO#initialize_copy', '15.2.20.5.10') do
|
||||
#assert('IO#print', '15.2.20.5.11') do
|
||||
#assert('IO#putc', '15.2.20.5.12') do
|
||||
#assert('IO#puts', '15.2.20.5.13') do
|
||||
|
||||
assert('IO#read', '15.2.20.5.14') do
|
||||
IO.open(IO.sysopen($mrbtest_io_rfname)) do |io|
|
||||
assert_raise(ArgumentError) { io.read(-5) }
|
||||
assert_raise(TypeError) { io.read("str") }
|
||||
|
||||
len = $mrbtest_io_msg.length
|
||||
assert_equal '', io.read(0)
|
||||
assert_equal 'mruby', io.read(5)
|
||||
assert_equal $mrbtest_io_msg[5,len], io.read(len)
|
||||
|
||||
assert_equal "", io.read
|
||||
assert_nil io.read(1)
|
||||
end
|
||||
|
||||
IO.open(IO.sysopen($mrbtest_io_rfname)) do |io|
|
||||
assert_equal $mrbtest_io_msg, io.read
|
||||
end
|
||||
end
|
||||
|
||||
assert "IO#read(n) with n > IO::BUF_SIZE" do
|
||||
skip "pipe is not supported on this platform" if MRubyIOTestUtil.win?
|
||||
IO.pipe do |r,w|
|
||||
n = IO::BUF_SIZE+1
|
||||
w.write 'a'*n
|
||||
assert_equal 'a'*n, r.read(n)
|
||||
end
|
||||
end
|
||||
|
||||
assert('IO#readchar', '15.2.20.5.15') do
|
||||
# almost same as IO#getc
|
||||
IO.open(IO.sysopen($mrbtest_io_rfname)) do |io|
|
||||
$mrbtest_io_msg.split("").each { |ch|
|
||||
assert_equal ch, io.readchar
|
||||
}
|
||||
assert_raise(EOFError) do
|
||||
io.readchar
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#assert('IO#readline', '15.2.20.5.16') do
|
||||
#assert('IO#readlines', '15.2.20.5.17') do
|
||||
|
||||
assert('IO#sync', '15.2.20.5.18') do
|
||||
io = IO.new(IO.sysopen($mrbtest_io_rfname))
|
||||
s = io.sync
|
||||
assert_true(s == true || s == false)
|
||||
io.close
|
||||
assert_raise(IOError) do
|
||||
io.sync
|
||||
end
|
||||
end
|
||||
|
||||
assert('IO#sync=', '15.2.20.5.19') do
|
||||
io = IO.new(IO.sysopen($mrbtest_io_rfname))
|
||||
io.sync = true
|
||||
assert_true io.sync
|
||||
io.sync = false
|
||||
assert_false io.sync
|
||||
io.close
|
||||
assert_raise(IOError) do
|
||||
io.sync = true
|
||||
end
|
||||
end
|
||||
|
||||
assert('IO#write', '15.2.20.5.20') do
|
||||
io = IO.open(IO.sysopen($mrbtest_io_wfname))
|
||||
assert_equal 0, io.write("")
|
||||
io.close
|
||||
|
||||
io = IO.open(IO.sysopen($mrbtest_io_wfname, "r+"), "r+")
|
||||
assert_equal 7, io.write("abcdefg")
|
||||
io.rewind
|
||||
assert_equal "ab", io.read(2)
|
||||
assert_equal 3, io.write("123")
|
||||
io.rewind
|
||||
assert_equal "ab123fg", io.read
|
||||
io.close
|
||||
end
|
||||
|
||||
assert('IO#<<') do
|
||||
io = IO.open(IO.sysopen($mrbtest_io_wfname))
|
||||
io << "" << ""
|
||||
assert_equal 0, io.pos
|
||||
io.close
|
||||
end
|
||||
|
||||
assert('IO#dup for readable') do
|
||||
io = IO.new(IO.sysopen($mrbtest_io_rfname))
|
||||
dup = io.dup
|
||||
assert_true io != dup
|
||||
assert_true io.fileno != dup.fileno
|
||||
begin
|
||||
assert_true dup.close_on_exec?
|
||||
rescue NotImplementedError
|
||||
end
|
||||
assert_equal 'm', dup.sysread(1)
|
||||
assert_equal 'r', io.sysread(1)
|
||||
assert_equal 'u', dup.sysread(1)
|
||||
assert_equal 'b', io.sysread(1)
|
||||
assert_equal 'y', dup.sysread(1)
|
||||
dup.close
|
||||
assert_false io.closed?
|
||||
io.close
|
||||
end
|
||||
|
||||
assert('IO#dup for writable') do
|
||||
io = IO.open(IO.sysopen($mrbtest_io_wfname, 'w+'), 'w+')
|
||||
dup = io.dup
|
||||
io.syswrite "mruby"
|
||||
assert_equal 5, dup.sysseek(0, IO::SEEK_CUR)
|
||||
io.sysseek 0, IO::SEEK_SET
|
||||
assert_equal 0, dup.sysseek(0, IO::SEEK_CUR)
|
||||
assert_equal "mruby", dup.sysread(5)
|
||||
dup.close
|
||||
io.close
|
||||
end
|
||||
|
||||
assert('IO.for_fd') do
|
||||
assert_io_open(:for_fd)
|
||||
end
|
||||
|
||||
assert('IO.new') do
|
||||
assert_io_open(:new)
|
||||
end
|
||||
|
||||
assert('IO gc check') do
|
||||
assert_nothing_raised { 100.times { IO.new(0) } }
|
||||
end
|
||||
|
||||
assert('IO.sysopen("./nonexistent")') do
|
||||
if Object.const_defined? :Errno
|
||||
eclass = Errno::ENOENT
|
||||
else
|
||||
eclass = RuntimeError
|
||||
end
|
||||
assert_raise eclass do
|
||||
fd = IO.sysopen "./nonexistent"
|
||||
IO._sysclose fd
|
||||
end
|
||||
end
|
||||
|
||||
assert('IO.sysopen, IO#sysread') do
|
||||
fd = IO.sysopen $mrbtest_io_rfname
|
||||
io = IO.new fd
|
||||
str1 = " "
|
||||
str2 = io.sysread(5, str1)
|
||||
assert_equal $mrbtest_io_msg[0,5], str1
|
||||
assert_equal $mrbtest_io_msg[0,5], str2
|
||||
assert_raise EOFError do
|
||||
io.sysread(10000)
|
||||
io.sysread(10000)
|
||||
end
|
||||
|
||||
assert_raise RuntimeError do
|
||||
io.sysread(5, "abcde".freeze)
|
||||
end
|
||||
|
||||
io.close
|
||||
assert_equal "", io.sysread(0)
|
||||
assert_raise(IOError) { io.sysread(1) }
|
||||
assert_raise(ArgumentError) { io.sysread(-1) }
|
||||
io.closed?
|
||||
|
||||
fd = IO.sysopen $mrbtest_io_wfname, "w"
|
||||
io = IO.new fd, "w"
|
||||
assert_raise(IOError) { io.sysread(1) }
|
||||
io.close
|
||||
end
|
||||
|
||||
assert('IO.sysopen, IO#syswrite') do
|
||||
fd = IO.sysopen $mrbtest_io_wfname, "w"
|
||||
io = IO.new fd, "w"
|
||||
str = "abcdefg"
|
||||
len = io.syswrite(str)
|
||||
assert_equal str.size, len
|
||||
io.close
|
||||
|
||||
io = IO.new(IO.sysopen($mrbtest_io_rfname), "r")
|
||||
assert_raise(IOError) { io.syswrite("a") }
|
||||
io.close
|
||||
end
|
||||
|
||||
assert('IO#_read_buf') do
|
||||
fd = IO.sysopen $mrbtest_io_rfname
|
||||
io = IO.new fd
|
||||
def io._buf
|
||||
@buf
|
||||
end
|
||||
msg_len = $mrbtest_io_msg.size
|
||||
assert_equal '', io._buf
|
||||
assert_equal $mrbtest_io_msg, io._read_buf
|
||||
assert_equal $mrbtest_io_msg, io._buf
|
||||
assert_equal 'mruby', io.read(5)
|
||||
assert_equal 5, io.pos
|
||||
assert_equal msg_len - 5, io._buf.size
|
||||
assert_equal $mrbtest_io_msg[5,100], io.read
|
||||
assert_equal 0, io._buf.size
|
||||
assert_raise EOFError do
|
||||
io._read_buf
|
||||
end
|
||||
assert_equal true, io.eof
|
||||
assert_equal true, io.eof?
|
||||
io.close
|
||||
end
|
||||
|
||||
assert('IO#isatty') do
|
||||
skip "isatty is not supported on this platform" if MRubyIOTestUtil.win?
|
||||
begin
|
||||
f = File.open("/dev/tty")
|
||||
rescue RuntimeError => e
|
||||
skip e.message
|
||||
else
|
||||
assert_true f.isatty
|
||||
ensure
|
||||
f&.close
|
||||
end
|
||||
begin
|
||||
f = File.open($mrbtest_io_rfname)
|
||||
assert_false f.isatty
|
||||
ensure
|
||||
f&.close
|
||||
end
|
||||
end
|
||||
|
||||
assert('IO#pos=, IO#seek') do
|
||||
fd = IO.sysopen $mrbtest_io_rfname
|
||||
io = IO.new fd
|
||||
def io._buf
|
||||
@buf
|
||||
end
|
||||
assert_equal 'm', io.getc
|
||||
assert_equal 1, io.pos
|
||||
assert_equal 0, io.seek(0)
|
||||
assert_equal 0, io.pos
|
||||
io.close
|
||||
end
|
||||
|
||||
assert('IO#rewind') do
|
||||
fd = IO.sysopen $mrbtest_io_rfname
|
||||
io = IO.new fd
|
||||
assert_equal 'm', io.getc
|
||||
assert_equal 1, io.pos
|
||||
assert_equal 0, io.rewind
|
||||
assert_equal 0, io.pos
|
||||
io.close
|
||||
end
|
||||
|
||||
assert('IO#gets') do
|
||||
fd = IO.sysopen $mrbtest_io_rfname
|
||||
io = IO.new fd
|
||||
|
||||
# gets without arguments
|
||||
assert_equal $mrbtest_io_msg, io.gets, "gets without arguments"
|
||||
assert_equal nil, io.gets, "gets returns nil, when EOF"
|
||||
|
||||
# gets with limit
|
||||
io.pos = 0
|
||||
assert_equal $mrbtest_io_msg[0, 5], io.gets(5), "gets with limit"
|
||||
|
||||
# gets with rs
|
||||
io.pos = 0
|
||||
assert_equal $mrbtest_io_msg[0, 6], io.gets(' '), "gets with rs"
|
||||
|
||||
# gets with rs, limit
|
||||
io.pos = 0
|
||||
assert_equal $mrbtest_io_msg[0, 5], io.gets(' ', 5), "gets with rs, limit"
|
||||
io.close
|
||||
assert_equal true, io.closed?, "close success"
|
||||
|
||||
# reading many-lines file.
|
||||
fd = IO.sysopen $mrbtest_io_wfname, "w"
|
||||
io = IO.new fd, "w"
|
||||
io.write "0123456789" * 2 + "\na"
|
||||
assert_equal 22 + $cr, io.pos
|
||||
io.close
|
||||
assert_equal true, io.closed?
|
||||
|
||||
fd = IO.sysopen $mrbtest_io_wfname
|
||||
io = IO.new fd
|
||||
line = io.gets
|
||||
|
||||
# gets first line
|
||||
assert_equal "0123456789" * 2 + "\n", line, "gets first line"
|
||||
assert_equal 21, line.size
|
||||
assert_equal 21 + $cr, io.pos
|
||||
|
||||
# gets second line
|
||||
assert_equal "a", io.gets, "gets second line"
|
||||
|
||||
# gets third line
|
||||
assert_equal nil, io.gets, "gets third line; returns nil"
|
||||
|
||||
io.close
|
||||
end
|
||||
|
||||
assert('IO#gets - paragraph mode') do
|
||||
fd = IO.sysopen $mrbtest_io_wfname, "w"
|
||||
io = IO.new fd, "w"
|
||||
io.write "0" * 10 + "\n"
|
||||
io.write "1" * 10 + "\n\n"
|
||||
io.write "2" * 10 + "\n"
|
||||
assert_equal 34 + $cr * 4, io.pos
|
||||
io.close
|
||||
|
||||
fd = IO.sysopen $mrbtest_io_wfname
|
||||
io = IO.new fd
|
||||
para1 = "#{'0' * 10}\n#{'1' * 10}\n\n"
|
||||
text1 = io.gets("")
|
||||
assert_equal para1, text1
|
||||
para2 = "#{'2' * 10}\n"
|
||||
text2 = io.gets("")
|
||||
assert_equal para2, text2
|
||||
io.close
|
||||
end
|
||||
|
||||
assert('IO.popen') do
|
||||
begin
|
||||
$? = nil
|
||||
io = IO.popen("#{$cmd}echo mruby-io")
|
||||
assert_true io.close_on_exec?
|
||||
assert_equal Fixnum, io.pid.class
|
||||
|
||||
out = io.read
|
||||
assert_equal out.class, String
|
||||
assert_include out, 'mruby-io'
|
||||
|
||||
io.close
|
||||
if Object.const_defined? :Process
|
||||
assert_true $?.success?
|
||||
else
|
||||
assert_equal 0, $?
|
||||
end
|
||||
|
||||
assert_true io.closed?
|
||||
rescue NotImplementedError => e
|
||||
skip e.message
|
||||
end
|
||||
end
|
||||
|
||||
assert('IO.popen with in option') do
|
||||
begin
|
||||
IO.pipe do |r, w|
|
||||
w.write 'hello'
|
||||
w.close
|
||||
assert_equal "hello", IO.popen("cat", "r", in: r) { |i| i.read }
|
||||
assert_equal "", r.read
|
||||
end
|
||||
assert_raise(ArgumentError) { IO.popen("hello", "r", in: Object.new) }
|
||||
rescue NotImplementedError => e
|
||||
skip e.message
|
||||
end
|
||||
end
|
||||
|
||||
assert('IO.popen with out option') do
|
||||
begin
|
||||
IO.pipe do |r, w|
|
||||
IO.popen("echo 'hello'", "w", out: w) {}
|
||||
w.close
|
||||
assert_equal "hello\n", r.read
|
||||
end
|
||||
rescue NotImplementedError => e
|
||||
skip e.message
|
||||
end
|
||||
end
|
||||
|
||||
assert('IO.popen with err option') do
|
||||
begin
|
||||
IO.pipe do |r, w|
|
||||
assert_equal "", IO.popen("echo 'hello' 1>&2", "r", err: w) { |i| i.read }
|
||||
w.close
|
||||
assert_equal "hello\n", r.read
|
||||
end
|
||||
rescue NotImplementedError => e
|
||||
skip e.message
|
||||
end
|
||||
end
|
||||
|
||||
assert('IO.read') do
|
||||
# empty file
|
||||
fd = IO.sysopen $mrbtest_io_wfname, "w"
|
||||
io = IO.new fd, "w"
|
||||
io.close
|
||||
assert_equal "", IO.read($mrbtest_io_wfname)
|
||||
assert_equal nil, IO.read($mrbtest_io_wfname, 1)
|
||||
|
||||
# one byte file
|
||||
fd = IO.sysopen $mrbtest_io_wfname, "w"
|
||||
io = IO.new fd, "w"
|
||||
io.write "123"
|
||||
io.close
|
||||
assert_equal "123", IO.read($mrbtest_io_wfname)
|
||||
assert_equal "", IO.read($mrbtest_io_wfname, 0)
|
||||
assert_equal "1", IO.read($mrbtest_io_wfname, 1)
|
||||
assert_equal "", IO.read($mrbtest_io_wfname, 0, 10)
|
||||
assert_equal "23", IO.read($mrbtest_io_wfname, 2, 1)
|
||||
assert_equal "23", IO.read($mrbtest_io_wfname, 10, 1)
|
||||
assert_equal "", IO.read($mrbtest_io_wfname, nil, 10)
|
||||
assert_equal nil, IO.read($mrbtest_io_wfname, 1, 10)
|
||||
end
|
||||
|
||||
assert('IO#fileno') do
|
||||
fd = IO.sysopen $mrbtest_io_rfname
|
||||
io = IO.new fd
|
||||
assert_equal io.fileno, fd
|
||||
assert_equal io.to_i, fd
|
||||
io.close
|
||||
end
|
||||
|
||||
assert('IO#close_on_exec') do
|
||||
fd = IO.sysopen $mrbtest_io_wfname, "w"
|
||||
io = IO.new fd, "w"
|
||||
begin
|
||||
# IO.sysopen opens a file descripter with O_CLOEXEC flag.
|
||||
assert_true io.close_on_exec?
|
||||
rescue ScriptError
|
||||
io.close
|
||||
skip "IO\#close_on_exec is not implemented."
|
||||
end
|
||||
|
||||
io.close_on_exec = false
|
||||
assert_equal(false, io.close_on_exec?)
|
||||
io.close_on_exec = true
|
||||
assert_equal(true, io.close_on_exec?)
|
||||
io.close_on_exec = false
|
||||
assert_equal(false, io.close_on_exec?)
|
||||
|
||||
io.close
|
||||
|
||||
begin
|
||||
r, w = IO.pipe
|
||||
assert_equal(true, r.close_on_exec?)
|
||||
r.close_on_exec = false
|
||||
assert_equal(false, r.close_on_exec?)
|
||||
r.close_on_exec = true
|
||||
assert_equal(true, r.close_on_exec?)
|
||||
|
||||
assert_equal(true, w.close_on_exec?)
|
||||
w.close_on_exec = false
|
||||
assert_equal(false, w.close_on_exec?)
|
||||
w.close_on_exec = true
|
||||
assert_equal(true, w.close_on_exec?)
|
||||
ensure
|
||||
r.close unless r.closed?
|
||||
w.close unless w.closed?
|
||||
end
|
||||
end
|
||||
|
||||
assert('IO#sysseek') do
|
||||
IO.open(IO.sysopen($mrbtest_io_rfname)) do |io|
|
||||
assert_equal 2, io.sysseek(2)
|
||||
assert_equal 5, io.sysseek(3, IO::SEEK_CUR) # 2 + 3 => 5
|
||||
assert_equal $mrbtest_io_msg.size - 4, io.sysseek(-4, IO::SEEK_END)
|
||||
end
|
||||
end
|
||||
|
||||
assert('IO#pread') do
|
||||
skip "IO#pread is not implemented on this configuration" unless MRubyIOTestUtil::MRB_WITH_IO_PREAD_PWRITE
|
||||
|
||||
IO.open(IO.sysopen($mrbtest_io_rfname, 'r'), 'r') do |io|
|
||||
assert_equal $mrbtest_io_msg.byteslice(5, 8), io.pread(8, 5)
|
||||
assert_equal 0, io.pos
|
||||
assert_equal $mrbtest_io_msg.byteslice(1, 5), io.pread(5, 1)
|
||||
assert_equal 0, io.pos
|
||||
assert_raise(RuntimeError) { io.pread(20, -9) }
|
||||
end
|
||||
end
|
||||
|
||||
assert('IO#pwrite') do
|
||||
skip "IO#pwrite is not implemented on this configuration" unless MRubyIOTestUtil::MRB_WITH_IO_PREAD_PWRITE
|
||||
|
||||
IO.open(IO.sysopen($mrbtest_io_wfname, 'w+'), 'w+') do |io|
|
||||
assert_equal 6, io.pwrite("Warld!", 7)
|
||||
assert_equal 0, io.pos
|
||||
assert_equal 7, io.pwrite("Hello, ", 0)
|
||||
assert_equal 0, io.pos
|
||||
assert_equal "Hello, Warld!", io.read
|
||||
assert_equal 6, io.pwrite("world!", 7)
|
||||
assert_equal 13, io.pos
|
||||
io.pos = 0
|
||||
assert_equal "Hello, world!", io.read
|
||||
end
|
||||
end
|
||||
|
||||
assert('IO.pipe') do
|
||||
begin
|
||||
called = false
|
||||
IO.pipe do |r, w|
|
||||
assert_true r.kind_of?(IO)
|
||||
assert_true w.kind_of?(IO)
|
||||
assert_false r.closed?
|
||||
assert_false w.closed?
|
||||
assert_true FileTest.pipe?(r)
|
||||
assert_true FileTest.pipe?(w)
|
||||
assert_nil r.pid
|
||||
assert_nil w.pid
|
||||
assert_true 2 < r.fileno
|
||||
assert_true 2 < w.fileno
|
||||
assert_true r.fileno != w.fileno
|
||||
assert_false r.sync
|
||||
assert_true w.sync
|
||||
assert_equal 8, w.write('test for')
|
||||
assert_equal 'test', r.read(4)
|
||||
assert_equal ' for', r.read(4)
|
||||
assert_equal 5, w.write(' pipe')
|
||||
assert_equal nil, w.close
|
||||
assert_equal ' pipe', r.read
|
||||
called = true
|
||||
assert_raise(IOError) { r.write 'test' }
|
||||
# TODO:
|
||||
# This assert expect raise IOError but got RuntimeError
|
||||
# Because mruby-io not have flag for I/O readable
|
||||
# assert_raise(IOError) { w.read }
|
||||
end
|
||||
assert_true called
|
||||
|
||||
assert_nothing_raised do
|
||||
IO.pipe { |r, w| r.close; w.close }
|
||||
end
|
||||
rescue NotImplementedError => e
|
||||
skip e.message
|
||||
end
|
||||
end
|
||||
|
||||
assert('`cmd`') do
|
||||
begin
|
||||
assert_equal `#{$cmd}echo foo`, "foo#{$crlf}"
|
||||
rescue NotImplementedError => e
|
||||
skip e.message
|
||||
end
|
||||
end
|
||||
|
||||
MRubyIOTestUtil.io_test_cleanup
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user