lib/ruby_less/processor.rb in rubyless-0.4.0 vs lib/ruby_less/processor.rb in rubyless-0.5.0

- old
+ new

@@ -10,10 +10,12 @@ PREFIX_OPERATOR = ['-@'] def self.translate(string, helper) sexp = RubyParser.new.parse(string) self.new(helper).process(sexp) + rescue Racc::ParseError => err + raise RubyLess::SyntaxError.new(err.message) end def initialize(helper) super() @helper = helper @@ -84,12 +86,26 @@ end code end def process_array(exp) - res = process_arglist(exp) - exp.size > 1 ? t("[#{res}]", res.opts) : res + literal = true + list = [] + classes = [] + while !exp.empty? + res = process(exp.shift) + content_class ||= res.opts[:class] + unless res.opts[:class] <= content_class + classes = list.map { content_class.name } + [res.opts[:class].name] + raise RubyLess::Error.new("Mixed Array not supported ([#{classes * ','}]).") + end + list << res + end + + res.opts[:class] = Array + res.opts[:array_content_class] = content_class + t "[#{list * ','}]", res.opts.merge(:literal => nil) end def process_vcall(exp) var_name = exp.shift unless opts = get_method([var_name], @helper, false) @@ -102,15 +118,16 @@ t method, opts end def process_lit(exp) lit = exp.shift - t lit.inspect, get_lit_class(lit.class) + t lit.inspect, get_lit_class(lit) end def process_str(exp) - t exp.shift.inspect, String + lit = exp.shift + t lit.inspect, :class => String, :literal => lit end def process_dstr(exp) t "\"#{parse_dstr(exp)}\"", String end @@ -118,31 +135,28 @@ def process_evstr(exp) exp.empty? ? t('', String) : process(exp.shift) end def process_hash(exp) - result = [] - klass = {} + result = t "", String until exp.empty? key = exp.shift if [:lit, :str].include?(key.first) key = key[1] rhs = exp.shift type = rhs.first rhs = process rhs #rhs = "(#{rhs})" unless [:lit, :str].include? type # TODO: verify better! - - result << "#{key.inspect} => #{rhs}" - klass[key] = rhs.klass + result.set_hash(key, rhs) else # ERROR: invalid key raise RubyLess::SyntaxError.new("Invalid key type for hash (should be a literal value, was #{key.first.inspect})") end end - - t "{#{result.join(', ')}}", :class => klass + result.rebuild_hash + result end def process_ivar(exp) method_call(nil, exp) end @@ -196,34 +210,80 @@ signature = [method] cond = [] end if receiver - if receiver.could_be_nil? - cond += receiver.cond - end opts = get_method(receiver, signature) + method_call_with_receiver(receiver, args, opts, cond, signature) + else + opts = get_method(nil, signature) method = opts[:method] - if method == '/' - t_if cond, "(#{receiver.raw}#{method}#{args.raw} rescue nil)", opts.merge(:nil => true) - elsif INFIX_OPERATOR.include?(method) - t_if cond, "(#{receiver.raw}#{method}#{args.raw})", opts - elsif PREFIX_OPERATOR.include?(method) - t_if cond, "#{method.to_s[0..0]}#{receiver.raw}", opts - elsif method == '[]' - t_if cond, "#{receiver.raw}[#{args.raw}]", opts + args = args_with_prepend(args, opts) + + if (proc = opts[:pre_processor]) && !args.list.detect {|a| !a.literal} + if proc.kind_of?(Proc) + res = proc.call(*args.list.map(&:literal)) + else + res = @helper.send(proc, *args.list.map(&:literal)) + end + + return res.kind_of?(TypedString) ? res : t(res.inspect, :class => String, :literal => res) + end + + if opts[:accept_nil] + method_call_accepting_nil(method, args, opts) else - args = args_with_prepend(args, opts) args = "(#{args.raw})" if args - t_if cond, "#{receiver.raw}.#{method}#{args}", opts + t_if cond, "#{method}#{args}", opts end + end + end + + def method_call_accepting_nil(method, args, opts) + if args + args = args.list.map do |arg| + if !arg.could_be_nil? || arg.raw == arg.cond.to_s + arg.raw + else + "(#{arg.cond} ? #{arg.raw} : nil)" + end + end.join(', ') + + t "#{method}(#{args})", opts else - opts = get_method(nil, signature) - method = opts[:method] + t method, opts + end + end + + def method_call_with_receiver(receiver, args, opts, cond, signature) + method = opts[:method] + arg_list = args ? args.list : [] + + if receiver.could_be_nil? && opts != SafeClass.safe_method_type_for(NilClass, signature) + # Do not add a condition if the method applies on nil + cond += receiver.cond + elsif receiver.literal && (proc = opts[:pre_processor]) && !arg_list.detect {|a| !a.literal} + if proc.kind_of?(Proc) + res = proc.call([receiver.literal] + arg_list.map(&:literal)) + else + res = receiver.literal.send(*([method] + arg_list.map(&:literal))) + end + return res.kind_of?(TypedString) ? res : t(res.inspect, :class => String, :literal => res) + end + + if method == '/' + t_if cond, "(#{receiver.raw}#{method}#{args.raw} rescue nil)", opts.merge(:nil => true) + elsif INFIX_OPERATOR.include?(method) + t_if cond, "(#{receiver.raw}#{method}#{args.raw})", opts + elsif PREFIX_OPERATOR.include?(method) + t_if cond, "#{method.to_s[0..0]}#{receiver.raw}", opts + elsif method == '[]' + t_if cond, "#{receiver.raw}[#{args.raw}]", opts + else args = args_with_prepend(args, opts) args = "(#{args.raw})" if args - t_if cond, "#{method}#{args}", opts + t_if cond, "#{receiver.raw}.#{method}#{args}", opts end end def parse_dstr(exp, in_regex = false) res = escape_str(exp.shift, in_regex) @@ -256,26 +316,38 @@ raise RubyLess::NoMethodError.new(receiver, klass, signature) if !type || type[:class].kind_of?(Symbol) # we cannot send: no object. type[:class].kind_of?(Proc) ? type[:class].call(@helper, signature) : type end - def get_lit_class(klass) - unless lit_class = RubyLess::SafeClass.literal_class_for(klass) + def get_lit_class(lit) + unless lit_class = RubyLess::SafeClass.literal_class_for(lit.class) raise RubyLess::SyntaxError.new("#{klass} literal not supported by RubyLess.") end - lit_class + {:class => lit_class, :literal => lit} end def args_with_prepend(args, opts) if prepend_args = opts[:prepend_args] if args prepend_args.append_argument(args) - prepend_args + args = prepend_args else - prepend_args + args = prepend_args end - else - args end + + if append_hash = opts[:append_hash] + last_arg = args.list.last + unless last_arg.klass.kind_of?(Hash) + last_arg = t "", String + args.append_argument(last_arg) + end + append_hash.each do |key, value| + last_arg.set_hash(key, value) + end + last_arg.rebuild_hash + args.rebuild_arguments + end + args end end end