first commit
CI / Build (clang, clang++, ubuntu-22.04, linux, 3.10, master, x86,x86_64) (push) Canceled after 0s
CI / Build (clang, clang++, windows-2022, windows, 3.10, master, x86,x86_64) (push) Canceled after 0s
CI / Release (push) Canceled after 0s

This commit is contained in:
2026-09-13 22:18:11 +01:00
commit 08c03ac533
6701 changed files with 1577314 additions and 0 deletions
+101
View File
@@ -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
+9
View File
@@ -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
@@ -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
+188
View File
@@ -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
+192
View File
@@ -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 */
+12
View File
@@ -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
+369
View File
@@ -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
+647
View File
@@ -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));
}
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)
{
}
+235
View File
@@ -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
+647
View File
@@ -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