mirror of
https://github.com/mistergibson/gxg-web-client.git
synced 2026-08-15 03:26:00 -07:00
1134 lines
25 KiB
Ruby
1134 lines
25 KiB
Ruby
module Rubinius
|
|
class Sprinter
|
|
def self.cache
|
|
@cache
|
|
end
|
|
|
|
def self.get(format)
|
|
if ec = @cache
|
|
if sprinter = ec.retrieve(format)
|
|
return sprinter
|
|
end
|
|
end
|
|
|
|
sprinter = new(format)
|
|
|
|
ec.set(format, sprinter) if ec
|
|
|
|
sprinter
|
|
end
|
|
|
|
def initialize(format)
|
|
begin
|
|
Rubinius::Type.object_singleton_class(self).dynamic_method :call do |g|
|
|
Builder.new(self, format, g).build
|
|
end
|
|
rescue Rubinius::ToolSets::Runtime::CompileError => e
|
|
raise RuntimeError, "failed to compile printf format: #{format}", e
|
|
end
|
|
end
|
|
|
|
private :initialize
|
|
|
|
def debug_print
|
|
printer = Rubinius::ToolSets::Runtime::Compiler::MethodPrinter.new
|
|
printer.input(method(:call).executable)
|
|
printer.bytecode = true
|
|
printer.run
|
|
end
|
|
|
|
def zero_two_expand_integer(int)
|
|
int = Integer(int) unless int.kind_of? Fixnum
|
|
|
|
return int.to_s if int < 0 or int >= 10
|
|
|
|
"0#{int}"
|
|
end
|
|
|
|
def zero_expand_integer(int, width)
|
|
int = Integer(int) unless int.kind_of? Fixnum
|
|
|
|
str = int.to_s
|
|
return str if width <= str.size
|
|
diff = width - str.size
|
|
|
|
if int < 0
|
|
case diff
|
|
when 1
|
|
"-0#{-int}"
|
|
when 2
|
|
"-00#{-int}"
|
|
when 3
|
|
"-000#{-int}"
|
|
else
|
|
"-#{'0' * diff}#{-int}"
|
|
end
|
|
else
|
|
case diff
|
|
when 1
|
|
"0#{str}"
|
|
when 2
|
|
"00#{str}"
|
|
when 3
|
|
"000#{str}"
|
|
when 4
|
|
"0000#{str}"
|
|
else
|
|
("0" * diff) << str
|
|
end
|
|
end
|
|
end
|
|
|
|
def zero_expand_leader(int, width)
|
|
if int < 0
|
|
zero_expand_integer(int, width)
|
|
else
|
|
zero_expand_integer(int, width-1)
|
|
end
|
|
end
|
|
|
|
def space_expand_integer(int, width)
|
|
int = Integer(int) unless int.kind_of? Fixnum
|
|
|
|
str = int.to_s
|
|
|
|
width = -width if width < 0
|
|
return str if width <= str.size
|
|
diff = width - str.size
|
|
|
|
case diff
|
|
when 1
|
|
" #{str}"
|
|
when 2
|
|
" #{str}"
|
|
when 3
|
|
" #{str}"
|
|
when 4
|
|
" #{str}"
|
|
else
|
|
(" " * diff) << str
|
|
end
|
|
end
|
|
|
|
def space_expand_leader(int, width)
|
|
if int < 0
|
|
space_expand_integer(int, width)
|
|
else
|
|
space_expand_integer(int, width-1)
|
|
end
|
|
end
|
|
|
|
def space_expand_integer_left(int, width)
|
|
int = Integer(int) unless int.kind_of? Fixnum
|
|
|
|
str = int.to_s
|
|
return str if width <= str.size
|
|
diff = width - str.size
|
|
|
|
case diff
|
|
when 1
|
|
"#{str} "
|
|
when 2
|
|
"#{str} "
|
|
when 3
|
|
"#{str} "
|
|
when 4
|
|
"#{str} "
|
|
else
|
|
str + (" " * diff)
|
|
end
|
|
end
|
|
|
|
def space_expand_leader_left(int, width)
|
|
if int < 0
|
|
space_expand_integer_left(int, width)
|
|
else
|
|
space_expand_integer_left(int, width-1)
|
|
end
|
|
end
|
|
|
|
def as_int(int)
|
|
if int.kind_of? Integer
|
|
return int
|
|
else
|
|
Integer(int)
|
|
end
|
|
end
|
|
|
|
def compute_space(int)
|
|
if int >= 0
|
|
" "
|
|
else
|
|
""
|
|
end
|
|
end
|
|
|
|
def compute_plus(int)
|
|
if int >= 0
|
|
"+"
|
|
else
|
|
""
|
|
end
|
|
end
|
|
|
|
def digit_expand_precision(int, precision)
|
|
str = int.to_s
|
|
|
|
diff = Integer(precision) - str.size
|
|
|
|
if int < 0
|
|
# account for the - sign infront
|
|
diff += 1
|
|
|
|
return str if diff <= 0
|
|
|
|
case diff
|
|
when 1
|
|
"-0#{-int}"
|
|
when 2
|
|
"-00#{-int}"
|
|
when 3
|
|
"-000#{-int}"
|
|
else
|
|
"-#{'0' * diff}#{-int}"
|
|
end
|
|
else
|
|
return str if diff <= 0
|
|
case diff
|
|
when 1
|
|
"0#{str}"
|
|
when 2
|
|
"00#{str}"
|
|
when 3
|
|
"000#{str}"
|
|
when 4
|
|
"0000#{str}"
|
|
else
|
|
("0" * diff) << str
|
|
end
|
|
end
|
|
end
|
|
|
|
def space_expand_left(str, width)
|
|
width = Integer(width)
|
|
|
|
sz = str.size
|
|
|
|
width = -width if width < 0
|
|
return str if sz >= width
|
|
|
|
diff = width - sz
|
|
|
|
case diff
|
|
when 1
|
|
"#{str} "
|
|
when 2
|
|
"#{str} "
|
|
when 3
|
|
"#{str} "
|
|
when 4
|
|
"#{str} "
|
|
else
|
|
str + (" " * diff)
|
|
end
|
|
end
|
|
|
|
def space_expand(str, width)
|
|
width = Integer(width)
|
|
return space_expand_left(str, -width) if width < 0
|
|
|
|
sz = str.size
|
|
|
|
return str if sz >= width
|
|
|
|
diff = width - sz
|
|
|
|
case diff
|
|
when 1
|
|
" #{str}"
|
|
when 2
|
|
" #{str}"
|
|
when 3
|
|
" #{str}"
|
|
when 4
|
|
" #{str}"
|
|
else
|
|
(" " * diff) << str
|
|
end
|
|
end
|
|
|
|
class Builder
|
|
def initialize(code, format, g)
|
|
@code, @format = code, format
|
|
|
|
@append_parts = 0
|
|
|
|
@g = g
|
|
end
|
|
|
|
private :initialize
|
|
|
|
def build
|
|
self.parse
|
|
|
|
@g.required_args = @arg_count
|
|
|
|
@g.total_args = @arg_count + 1
|
|
# We won't use it, but we accept a splat; our semantics require
|
|
# that we ignore any excess arguments provided.
|
|
@g.splat_index = @arg_count
|
|
|
|
@g.local_count = @arg_count + 1
|
|
if @index_mode == :absolute
|
|
@g.local_names = (0...@arg_count).map { |i| :"#{i + 1}$" } + [:splat]
|
|
else
|
|
@g.local_names = (0...@arg_count).map { |i| :"arg#{i}" } + [:splat]
|
|
end
|
|
|
|
@g.string_build @append_parts
|
|
@g.ret
|
|
end
|
|
|
|
def invert
|
|
@g.push_int 0
|
|
@g.swap
|
|
@g.send :-, 1
|
|
end
|
|
|
|
def is_negative
|
|
@g.push_int 0
|
|
@g.send :<, 1
|
|
end
|
|
|
|
def justify(direction, may_be_negative=true)
|
|
if may_be_negative && direction != :ljust
|
|
width_done = @g.new_label
|
|
|
|
@g.dup
|
|
is_negative
|
|
if_false do
|
|
@g.send direction, 1
|
|
@g.goto width_done
|
|
end
|
|
|
|
invert
|
|
@g.send :ljust, 1
|
|
|
|
width_done.set!
|
|
else
|
|
@g.send direction, 1
|
|
end
|
|
end
|
|
|
|
def next_index(specified=nil)
|
|
if specified
|
|
specified = specified.to_i
|
|
raise ArgumentError, "invalid positional index" if specified == 0
|
|
|
|
if @index_mode == :relative
|
|
raise ArgumentError, "unnumbered mixed with numbered"
|
|
end
|
|
|
|
@index_mode = :absolute
|
|
@arg_count = specified if specified > @arg_count
|
|
specified - 1
|
|
else
|
|
if @index_mode == :absolute
|
|
raise ArgumentError, "unnumbered mixed with numbered"
|
|
end
|
|
@index_mode = :relative
|
|
(@arg_count += 1) - 1
|
|
end
|
|
end
|
|
|
|
def append_literal(str)
|
|
@g.push_literal str
|
|
append_str
|
|
end
|
|
|
|
def append_str
|
|
@append_parts += 1
|
|
end
|
|
|
|
def encode_value(str)
|
|
str.force_encoding(@format.encoding)
|
|
str
|
|
end
|
|
|
|
def is_zero
|
|
@g.push_int 0
|
|
@g.send :equal?, 1
|
|
end
|
|
|
|
class Atom
|
|
def initialize(b, g, format_code, flags, name = nil)
|
|
@b, @g = b, g
|
|
@format_code, @flags = format_code, flags
|
|
@name = name
|
|
|
|
@f_alt = flags.index(?#)
|
|
@f_zero = flags.index(?0)
|
|
@f_plus = flags.index(?+)
|
|
@f_ljust = flags.index(?-)
|
|
@f_space = flags.index(?\ )
|
|
|
|
@just_dir = @f_ljust ? :ljust : :rjust
|
|
|
|
@prefix = PREFIX[@format_code] if @f_alt
|
|
|
|
@full_leader_size = @prefix ? @prefix.size : 0
|
|
@full_leader_size += 1 if @f_plus || @f_space
|
|
end
|
|
|
|
private :initialize
|
|
|
|
def prepend_prefix
|
|
@g.push_literal @prefix
|
|
@g.string_dup
|
|
@g.string_append
|
|
end
|
|
|
|
def set_value(ref)
|
|
unless @name
|
|
@field_index = @b.next_index(ref)
|
|
end
|
|
end
|
|
|
|
def set_width(full, ref, static)
|
|
@width_static = static && static.to_i
|
|
if !@name && full && !static
|
|
@width_index = @b.next_index(ref)
|
|
end
|
|
|
|
@has_width = @width_static || @width_index
|
|
end
|
|
|
|
def set_precision(full, ref, static)
|
|
@prec_static = static && static.to_i
|
|
if !@name && full && !static
|
|
@prec_index = @b.next_index(ref)
|
|
end
|
|
|
|
@has_precision = @prec_static || @prec_index
|
|
end
|
|
|
|
def push_value
|
|
if @name
|
|
@g.push_local 0
|
|
@g.push_literal @name
|
|
@g.send :fetch, 1
|
|
else
|
|
@g.push_local @field_index
|
|
end
|
|
end
|
|
|
|
def push_width_value
|
|
if @width_static
|
|
@g.push_int @width_static
|
|
elsif
|
|
@g.push_local @width_index
|
|
else
|
|
raise "No width to push"
|
|
end
|
|
end
|
|
|
|
def push_width(adjust=true)
|
|
if @width_static
|
|
raise ArgumentError, "width too big" unless @width_static.class == Fixnum
|
|
if adjust && @full_leader_size > 0
|
|
@g.push_int(@width_static - @full_leader_size)
|
|
else
|
|
@g.push_int @width_static
|
|
end
|
|
|
|
elsif @width_index
|
|
@g.push_local @width_index
|
|
|
|
@b.force_type :Fixnum, :Integer do
|
|
# If we had to do a conversion, we check we ended up
|
|
# with a Fixnum
|
|
@g.dup
|
|
@b.push_Fixnum
|
|
@g.swap
|
|
@g.kind_of
|
|
|
|
@b.if_false do
|
|
@b.raise_ArgumentError "width too big"
|
|
end
|
|
end
|
|
|
|
else
|
|
raise "push without a width"
|
|
|
|
end
|
|
end
|
|
|
|
def push_precision_value
|
|
if @prec_static
|
|
@g.push_int @prec_static
|
|
else
|
|
@g.push_local @prec_index
|
|
end
|
|
end
|
|
|
|
def push_precision
|
|
if @prec_static
|
|
raise ArgumentError, "precision too big" unless @prec_static.class == Fixnum
|
|
@g.push_int @prec_static
|
|
|
|
elsif @prec_index
|
|
@g.push_local @prec_index
|
|
|
|
@b.force_type :Fixnum, :Integer do
|
|
# If we had to do a conversion, we check we ended up
|
|
# with a Fixnum
|
|
@g.dup
|
|
@b.push_Fixnum
|
|
@g.swap
|
|
@g.kind_of
|
|
|
|
@b.if_false do
|
|
@b.raise_ArgumentError "precision too big"
|
|
end
|
|
end
|
|
|
|
else
|
|
raise "push without a precision"
|
|
|
|
end
|
|
end
|
|
|
|
def push_format_string
|
|
float_format_code = @format_code
|
|
|
|
leader = "%#{@flags}"
|
|
if !@width_index && !@prec_index
|
|
leader << @width_static.to_s if @width_static
|
|
leader << ".#{@prec_static}" if @prec_static
|
|
@g.push_literal "#{leader}#{float_format_code}"
|
|
else
|
|
format_parts = 1
|
|
if @prec_static
|
|
@g.push_literal ".#{@prec_static}#{float_format_code}"
|
|
else
|
|
@g.push_literal(float_format_code)
|
|
if @prec_index
|
|
push_precision
|
|
@g.send :to_s, 0
|
|
format_parts += 1
|
|
|
|
if @width_index
|
|
@g.push_literal "."
|
|
format_parts += 1
|
|
end
|
|
end
|
|
end
|
|
|
|
if @width_static
|
|
leader << @width_static.to_s
|
|
elsif @width_index
|
|
push_width
|
|
@g.send :to_s, 0
|
|
format_parts += 1
|
|
end
|
|
leader << "." if @prec_index && !@width_index
|
|
|
|
@g.push_literal leader
|
|
@g.string_dup
|
|
format_parts.times do
|
|
@g.string_append
|
|
end
|
|
end
|
|
end
|
|
|
|
def positive_sign
|
|
if @f_plus
|
|
'+'
|
|
elsif @f_space
|
|
' '
|
|
else
|
|
''
|
|
end
|
|
end
|
|
|
|
def justify_width(adjust=true)
|
|
if @has_width
|
|
push_width adjust
|
|
@b.justify @just_dir, @width_static.nil?
|
|
end
|
|
end
|
|
|
|
def zero_pad(pad="0")
|
|
if @has_precision
|
|
push_precision
|
|
elsif @has_width && @f_zero
|
|
push_width true
|
|
else
|
|
# exit early if no width has been pushed
|
|
return
|
|
end
|
|
|
|
# let the caller adjust the width if needed
|
|
yield if block_given?
|
|
|
|
@g.push_literal pad
|
|
@g.send :rjust, 2
|
|
end
|
|
|
|
attr_reader :width_static
|
|
|
|
def leader?
|
|
@full_leader_size > 0
|
|
end
|
|
end
|
|
|
|
class CharAtom < Atom
|
|
def bytecode
|
|
push_value
|
|
# We need to push the original value here
|
|
# because we have to possibly make multiple
|
|
# attempts to coerce it to either a string
|
|
# or an integer. If we don't duplicate we can't
|
|
# retry it for another type with the same original
|
|
# value.
|
|
@g.dup
|
|
|
|
@b.try_type :String, :to_str do
|
|
# Attempt failed, pop the nil value
|
|
# and retry for Integer
|
|
@g.pop
|
|
@g.dup
|
|
@b.force_type :Fixnum, :Integer
|
|
@g.send :chr, 0
|
|
end
|
|
|
|
@g.dup
|
|
@g.send :length, 0
|
|
@g.push_int 1
|
|
@g.send :equal?, 1
|
|
@b.if_false do
|
|
@b.raise_ArgumentError "%c requires a character"
|
|
end
|
|
|
|
justify_width
|
|
|
|
# Swap the given value with the coerced value and
|
|
# pop off the given value afterwards
|
|
@g.swap
|
|
@g.pop
|
|
@b.append_str
|
|
end
|
|
end
|
|
|
|
class IntegerAtom < Atom
|
|
def fast_common_case?
|
|
@f_zero && @width_static && !@has_precision && !@f_space && !@f_plus && !@f_ljust
|
|
end
|
|
|
|
def expand_with_width
|
|
if @f_ljust
|
|
if @f_space || @f_plus
|
|
@g.send :space_expand_leader_left, 2
|
|
else
|
|
@g.send :space_expand_integer_left, 2
|
|
end
|
|
else
|
|
if @f_zero
|
|
if @f_space || @f_plus
|
|
@g.send :zero_expand_leader, 2
|
|
else
|
|
@g.send :zero_expand_integer, 2
|
|
end
|
|
else
|
|
if @f_space || @f_plus
|
|
@g.send :space_expand_leader, 2
|
|
else
|
|
@g.send :space_expand_integer, 2
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def bytecode
|
|
if fast_common_case?
|
|
@g.push_self
|
|
|
|
push_value
|
|
|
|
if @width_static == 2
|
|
@g.send :zero_two_expand_integer, 1
|
|
else
|
|
@g.push_int @width_static
|
|
@g.send :zero_expand_integer, 2
|
|
end
|
|
|
|
@b.append_str
|
|
else
|
|
@g.push_self
|
|
push_value
|
|
@g.send :as_int, 1
|
|
|
|
val_idx = @g.new_stack_local
|
|
@g.set_stack_local val_idx
|
|
@g.pop
|
|
|
|
# generic case
|
|
|
|
if @f_space
|
|
@g.push_self
|
|
@g.push_stack_local val_idx
|
|
@g.send :compute_space, 1
|
|
@b.append_str
|
|
elsif @f_plus
|
|
@g.push_self
|
|
@g.push_stack_local val_idx
|
|
@g.send :compute_plus, 1
|
|
@b.append_str
|
|
end
|
|
|
|
if @has_precision
|
|
@g.push_self
|
|
@g.push_stack_local val_idx
|
|
|
|
push_precision_value
|
|
|
|
@g.send :digit_expand_precision, 2
|
|
|
|
if @has_width
|
|
@g.push_self
|
|
@g.swap
|
|
push_width_value
|
|
|
|
if @f_ljust
|
|
@g.send :space_expand_left, 2
|
|
else
|
|
@g.send :space_expand, 2
|
|
end
|
|
end
|
|
|
|
@b.append_str
|
|
|
|
elsif @has_width
|
|
@g.push_self
|
|
@g.push_stack_local val_idx
|
|
|
|
push_width_value
|
|
|
|
expand_with_width
|
|
|
|
@b.append_str
|
|
else
|
|
@g.push_stack_local val_idx
|
|
@g.send :to_s, 0
|
|
|
|
@b.append_str
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
class ExtIntegerAtom < Atom
|
|
def pad_negative_int(padding)
|
|
zero_pad(padding) do
|
|
# decrease the width by 2 to account for the ".." below
|
|
@g.push_int 2
|
|
@g.send :-, 1
|
|
end
|
|
|
|
@g.push_literal ".."
|
|
@g.string_dup
|
|
@g.string_append
|
|
end
|
|
|
|
def prepend_prefix_bytecode
|
|
skip_prefix = @g.new_label
|
|
|
|
# For 'x' and 'X', don't prepend the "0x" prefix
|
|
# if the value is zero
|
|
if @format_code == 'x' || @format_code == 'X'
|
|
push_value
|
|
@b.is_zero
|
|
@g.goto_if_true skip_prefix
|
|
end
|
|
|
|
prepend_prefix
|
|
|
|
skip_prefix.set!
|
|
end
|
|
|
|
def format_negative_int(radix)
|
|
# (num + radix ** num.to_s(radix).size).to_s(radix)
|
|
@g.push_int radix
|
|
@g.dup_many 2
|
|
@g.send :to_s, 1
|
|
@g.send :size, 0
|
|
@g.send :**, 1
|
|
@g.send :+, 1
|
|
@g.push_int radix
|
|
@g.send :to_s, 1
|
|
|
|
(radix - 1).to_s(radix)
|
|
end
|
|
|
|
def bytecode
|
|
radix = RADIX[@format_code]
|
|
|
|
push_value
|
|
|
|
# Bignum is obviously also perfectly acceptable. But we
|
|
# just address the most common case by avoiding the call
|
|
# if we've been given a Fixnum. The call is enough
|
|
# overhead to bother, but not something to panic about.
|
|
@b.force_type :Fixnum, :Integer
|
|
|
|
|
|
if @f_plus || @f_space
|
|
@g.dup
|
|
|
|
# stash away whether it's negative
|
|
@b.is_negative
|
|
@g.dup
|
|
@g.move_down 2
|
|
|
|
@b.if_true do
|
|
# but treat it as positive for now
|
|
@b.invert
|
|
end
|
|
|
|
@g.push_int radix
|
|
@g.send :to_s, 1
|
|
else
|
|
have_formatted = @g.new_label
|
|
|
|
@g.dup
|
|
@b.is_negative
|
|
|
|
@b.if_false do
|
|
@g.push_int radix
|
|
@g.send :to_s, 1
|
|
@g.goto have_formatted
|
|
end
|
|
|
|
padding = format_negative_int(radix)
|
|
pad_negative_int(padding)
|
|
|
|
have_formatted.set!
|
|
end
|
|
|
|
# 'B' also returns an uppercase string, but there, the
|
|
# only alpha character is in the prefix -- and that's
|
|
# already uppercase
|
|
if @format_code == 'X'
|
|
@g.send :upcase, 0
|
|
end
|
|
|
|
zero_pad
|
|
|
|
if @prefix
|
|
prepend_prefix_bytecode
|
|
end
|
|
|
|
if @f_plus || @f_space
|
|
append_sign = @g.new_label
|
|
|
|
@g.swap
|
|
@b.if_true do
|
|
@g.push_literal '-'
|
|
|
|
@g.goto append_sign
|
|
end
|
|
|
|
@g.push_literal positive_sign
|
|
|
|
append_sign.set!
|
|
@g.string_dup
|
|
@g.string_append
|
|
end
|
|
|
|
|
|
if @has_precision || !@f_zero
|
|
justify_width false
|
|
end
|
|
|
|
@b.append_str
|
|
end
|
|
end
|
|
|
|
class StringAtom < Atom
|
|
def string_justify
|
|
# by default, f_space changes the padding introduced
|
|
# by justify_width, but for string, we want to go
|
|
# ahead and actually ignore it not have it count
|
|
# against the justification space width.
|
|
if @f_space
|
|
@full_leader_size -= 1
|
|
end
|
|
|
|
justify_width
|
|
|
|
if @has_precision
|
|
@g.push_int 0
|
|
push_precision
|
|
@g.send :[], 2
|
|
end
|
|
end
|
|
|
|
def bytecode
|
|
push_value
|
|
@b.force_type :String
|
|
|
|
string_justify
|
|
|
|
@b.append_str
|
|
end
|
|
end
|
|
|
|
class InspectAtom < StringAtom
|
|
def bytecode
|
|
push_value
|
|
@g.send :inspect, 0
|
|
|
|
string_justify
|
|
|
|
@b.append_str
|
|
end
|
|
end
|
|
|
|
class FloatAtom < Atom
|
|
def bytecode
|
|
push_value
|
|
@b.force_type :Float
|
|
|
|
format_done = @g.new_label
|
|
|
|
@g.dup
|
|
@g.send :finite?, 0
|
|
|
|
@b.if_true do
|
|
push_format_string
|
|
@g.send :to_s_formatted, 1, true
|
|
|
|
@g.goto format_done
|
|
end
|
|
|
|
formatted_non_finite = @g.new_label
|
|
|
|
@g.dup
|
|
@g.send :nan?, 0
|
|
|
|
@b.if_false do
|
|
@b.is_negative
|
|
|
|
@b.if_false do
|
|
@g.push_literal "#{positive_sign}Inf"
|
|
@g.goto formatted_non_finite
|
|
end
|
|
@g.push_literal '-Inf'
|
|
@g.goto formatted_non_finite
|
|
end
|
|
|
|
@g.pop
|
|
@g.push_literal 'NaN'
|
|
|
|
formatted_non_finite.set!
|
|
justify_width false
|
|
|
|
format_done.set!
|
|
|
|
@b.append_str
|
|
end
|
|
end
|
|
|
|
class LiteralAtom < Atom
|
|
def set_value(ref)
|
|
@value = ref
|
|
end
|
|
|
|
def bytecode
|
|
@b.append_literal(@value)
|
|
end
|
|
end
|
|
|
|
def push_Kernel
|
|
@g.push_const :Kernel
|
|
end
|
|
|
|
def push_Fixnum
|
|
@g.push_const :Fixnum
|
|
end
|
|
|
|
def push_String
|
|
@g.push_const :String
|
|
end
|
|
|
|
def push_Hash
|
|
@g.push_const :Hash
|
|
end
|
|
|
|
def raise_ArgumentError(msg)
|
|
@g.push_const :ArgumentError
|
|
@g.push_literal msg
|
|
@g.send :new, 1
|
|
@g.raise_exc
|
|
end
|
|
|
|
def force_type(klass, method=klass)
|
|
@g.dup
|
|
@g.push_const klass
|
|
@g.swap
|
|
@g.kind_of
|
|
if_false do
|
|
@g.push_self
|
|
@g.swap
|
|
@g.send method, 1, true
|
|
|
|
yield if block_given?
|
|
end
|
|
end
|
|
|
|
def try_type(klass, method)
|
|
@g.dup
|
|
@g.push_const klass
|
|
@g.swap
|
|
@g.kind_of
|
|
if_false do
|
|
@g.push_type
|
|
@g.swap
|
|
@g.push_const klass
|
|
@g.push_literal method
|
|
@g.send :check_convert_type, 3
|
|
@g.dup
|
|
if_false do
|
|
yield if block_given?
|
|
end
|
|
end
|
|
end
|
|
|
|
def if_true
|
|
l = @g.new_label
|
|
@g.goto_if_false l
|
|
yield
|
|
l.set!
|
|
end
|
|
|
|
def if_false
|
|
l = @g.new_label
|
|
@g.goto_if_true l
|
|
yield
|
|
l.set!
|
|
end
|
|
|
|
def parse
|
|
@arg_count = 0
|
|
@index_mode = nil
|
|
|
|
atoms = []
|
|
|
|
# Always push an empty string at first for correct
|
|
# encoding protocols
|
|
atom = LiteralAtom.new(self, @g, "", "")
|
|
atom.set_value(encode_value(""))
|
|
|
|
atoms << atom
|
|
pos = 0
|
|
while match = RE.match_start(@format, pos)
|
|
pos = match.full.at(1)
|
|
|
|
_,
|
|
plain_string,
|
|
whole_format,
|
|
name_format,
|
|
flags_a,
|
|
field_ref_a,
|
|
flags_b,
|
|
width_full, width_ref, width_static,
|
|
prec_full, prec_ref, prec_static,
|
|
field_ref_b,
|
|
format_code,
|
|
literal_char,
|
|
name_reference,
|
|
invalid_format = *match
|
|
|
|
flags = "#{flags_a}#{flags_b}"
|
|
|
|
|
|
if plain_string
|
|
atom = LiteralAtom.new(self, @g, format_code, flags)
|
|
atom.set_value(plain_string)
|
|
atoms << atom
|
|
elsif literal_char
|
|
atom = LiteralAtom.new(self, @g, format_code, flags)
|
|
atom.set_value(literal_char)
|
|
atoms << atom
|
|
elsif invalid_format || (field_ref_a && field_ref_b)
|
|
raise ArgumentError, "malformed format string: #{@format.inspect}"
|
|
else
|
|
field_ref = field_ref_a || field_ref_b
|
|
|
|
if name_reference
|
|
format_code = "s"
|
|
@index_mode = :name
|
|
name = name_reference[2...-1].to_sym
|
|
elsif name_format
|
|
@index_mode = :name
|
|
name = name_format[1...-1].to_sym
|
|
end
|
|
|
|
klass = AtomMap[format_code[0]]
|
|
|
|
unless klass
|
|
raise ArgumentError, "unknown format type - #{format_code}"
|
|
end
|
|
|
|
atom = klass.new(self, @g, format_code, flags, name)
|
|
atom.set_width width_full, width_ref, width_static
|
|
atom.set_precision prec_full, prec_ref, prec_static
|
|
atom.set_value field_ref
|
|
atoms << atom
|
|
end
|
|
end
|
|
|
|
if @index_mode == :name
|
|
exception = @g.new_label
|
|
continue = @g.new_label
|
|
|
|
@arg_count = 1
|
|
@g.passed_arg @arg_count
|
|
@g.goto_if_true exception
|
|
push_Hash
|
|
@g.push_local 0
|
|
@g.kind_of
|
|
@g.goto_if_false exception
|
|
@g.goto continue
|
|
|
|
exception.set!
|
|
|
|
raise_ArgumentError "named format string needs single Hash argument"
|
|
|
|
continue.set!
|
|
elsif @index_mode != :absolute
|
|
no_exception = @g.new_label
|
|
|
|
# If we've used relative arguments, and $DEBUG is true, we
|
|
# throw an exception if passed more arguments than we need.
|
|
|
|
# Check this first; it's much faster, and generally false
|
|
@g.passed_arg @arg_count
|
|
@g.goto_if_false no_exception
|
|
|
|
gva = Rubinius::ToolSets::Runtime::AST::GlobalVariableAccess
|
|
gva.new(1, :$DEBUG).bytecode(@g)
|
|
@g.goto_if_false no_exception
|
|
|
|
raise_ArgumentError "too many arguments for format string"
|
|
|
|
no_exception.set!
|
|
end
|
|
|
|
atoms.each do |atom|
|
|
atom.bytecode
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|