lib/delorean/base.rb in delorean_lang-0.3.24 vs lib/delorean/base.rb in delorean_lang-0.3.25

- old
+ new

@@ -2,25 +2,26 @@ require 'active_record' require 'bigdecimal' module Delorean - # FIXME: add string.gsub; Also, should be able to index regex - # matches. Need string.length. + DT_TYPES = [Date, Time, ActiveSupport::TimeWithZone] + NUM_OR_STR = [Numeric, String] + NUM_OR_NIL = [nil, Fixnum] # FIXME: the whitelist is quite hacky. It's currently difficult to # override it. A user will likely want to directly modify this # hash. The whole whitelist mechanism should be eventually # rethought. RUBY_WHITELIST = { attributes: [ActiveRecord::Base], - between?: [[Numeric, String],[Numeric, String],[Numeric, String]], + between?: [NUM_OR_STR, NUM_OR_STR, NUM_OR_STR], between: "between?", compact: [Array], to_set: [Array], - flatten: [Array, [Fixnum, nil]], - length: [Enumerable], + flatten: [Array, NUM_OR_NIL], + length: [[String, Enumerable]], max: [Array], member: "member?", member?: [Enumerable, [Object]], empty: "empty?", empty?: [Enumerable], @@ -31,55 +32,46 @@ split: [String, String], uniq: [Array], sum: [Array], transpose: [Array], join: [Array, String], - zip: [Array, [Array, Array, Array]], + zip: [Array, Array, [Array, nil], [Array, nil]], index: [Array, [Object]], product: [Array, Array], - first: [[ActiveRecord::Relation, Enumerable], [nil, Fixnum]], - last: [[ActiveRecord::Relation, Enumerable], [nil, Fixnum]], + first: [[ActiveRecord::Relation, Enumerable], NUM_OR_NIL], + last: [[ActiveRecord::Relation, Enumerable], NUM_OR_NIL], intersection: [Set, Enumerable], union: [Set, Enumerable], keys: [Hash], values: [Hash], upcase: [String], downcase: [String], - match: [String, [String], [nil, Fixnum]], + match: [String, [String], NUM_OR_NIL], - iso8601: [[Date, Time, ActiveSupport::TimeWithZone]], - hour: [[Date, Time, ActiveSupport::TimeWithZone]], - min: [[Date, Time, ActiveSupport::TimeWithZone, Array]], - sec: [[Date, Time, ActiveSupport::TimeWithZone]], - to_date: [[Date, Time, ActiveSupport::TimeWithZone, String]], + iso8601: [DT_TYPES], + hour: [DT_TYPES], + min: [DT_TYPES+[Array]], + sec: [DT_TYPES], + to_date: [DT_TYPES+[String]], - month: [[Date, Time, ActiveSupport::TimeWithZone]], - day: [[Date, Time, ActiveSupport::TimeWithZone]], - year: [[Date, Time, ActiveSupport::TimeWithZone]], + month: [DT_TYPES], + day: [DT_TYPES], + year: [DT_TYPES], - next_month: [[Date, Time, ActiveSupport::TimeWithZone], - [nil, Fixnum], - ], - prev_month: [[Date, Time, ActiveSupport::TimeWithZone], - [nil, Fixnum], - ], + next_month: [DT_TYPES, NUM_OR_NIL], + prev_month: [DT_TYPES, NUM_OR_NIL], - beginning_of_month: [[Date, Time, ActiveSupport::TimeWithZone]], + beginning_of_month: [DT_TYPES], + end_of_month: [DT_TYPES], - end_of_month: [[Date, Time, ActiveSupport::TimeWithZone]], + next_day: [DT_TYPES, NUM_OR_NIL], + prev_day: [DT_TYPES, NUM_OR_NIL], - next_day: [[Date, Time, ActiveSupport::TimeWithZone], - [nil, Fixnum], - ], - prev_day: [[Date, Time, ActiveSupport::TimeWithZone], - [nil, Fixnum], - ], - - to_i: [[Numeric, String]], - to_f: [[Numeric, String]], - to_d: [[Numeric, String]], + to_i: [NUM_OR_STR], + to_f: [NUM_OR_STR], + to_d: [NUM_OR_STR], to_s: [Object], to_a: [Object], to_json: [Object], abs: [Numeric], round: [Numeric, [nil, Integer]], @@ -95,19 +87,18 @@ # This whole thing needs to be redone. engine.evaluate(node, attr, params.clone) end def /(args) - raise "non-array/string arg to /" unless - args.is_a?(Array) || args.is_a?(String) - begin case args when Array engine.eval_to_hash(node, args, params.clone) when String engine.evaluate(node, args, params.clone) + else + raise "non-array/string arg to /" end rescue => exc Delorean::Engine.grok_runtime_exception(exc) end end @@ -132,34 +123,29 @@ end end class BaseClass def self._get_attr(obj, attr, _e) - # FIXME: even Javascript which is superpermissive raises an - # exception on null getattr. - return nil if obj.nil? - # NOTE: should keep this function consistent with _index - - if obj.kind_of? ActiveRecord::Base - klass = obj.class - - return obj.read_attribute(attr) if - klass.attribute_names.member? attr - - return obj.send(attr.to_sym) if - klass.reflect_on_all_associations.map(&:name).member? attr.to_sym - elsif obj.instance_of?(NodeCall) + case obj + when nil + # FIXME: even Javascript which is superpermissive raises an + # exception on null getattr. + return nil + when ActiveRecord::Base + return obj.read_attribute(attr) if obj.has_attribute?(attr) + return obj.send(attr.to_sym) if obj.class.reflections[attr] + when NodeCall return obj.evaluate(attr) - elsif obj.instance_of?(Hash) + when Hash # FIXME: this implementation doesn't handle something like # {}.length. i.e. length is a whitelisted function, but not # an attr. This implementation returns nil instead of 0. return obj[attr] if obj.member?(attr) return attr.is_a?(String) ? obj[attr.to_sym] : nil - elsif obj.instance_of?(Class) && (obj < BaseClass) - return obj.send((attr + POST).to_sym, _e) + when Class + return obj.send((attr + POST).to_sym, _e) if obj < BaseClass end begin return _instance_call(obj, attr, [], _e) rescue => exc @@ -169,22 +155,23 @@ end ###################################################################### def self._index(obj, args, _e) - return nil if obj.nil? - # NOTE: should keep this function consistent with _get_attr - - if obj.instance_of?(Hash) || obj.kind_of?(ActiveRecord::Base) || - obj.instance_of?(NodeCall) || obj.instance_of?(Class) + case obj + when nil + # FIXME: even Javascript which is superpermissive raises an + # exception on null getattr. + return nil + when Hash, ActiveRecord::Base, NodeCall, Class raise InvalidIndex unless args.length == 1 _get_attr(obj, args[0], _e) - elsif obj.instance_of?(Array) || obj.instance_of?(String) - raise InvalidIndex unless args.length <= 2 - raise InvalidIndex unless - args[0].is_a?(Fixnum) && (!args[1] || args[1].is_a?(Fixnum)) + when Array, String, MatchData + raise InvalidIndex unless args.length <= 2 && + args[0].is_a?(Fixnum) && + (args[1].nil? || args[1].is_a?(Fixnum)) obj[*args] else raise InvalidIndex end end @@ -232,37 +219,31 @@ if obj.is_a?(Class) _e[:_engine].parse_check_call_fn(method, args.count, obj) return obj.send(msg, *args) end - if obj.class.include?(Delorean::Model) - sig = obj.class.delorean_instance_methods[msg] - end + cls = obj.class + sig = (cls < Delorean::Model && cls.delorean_instance_methods[msg]) || + RUBY_WHITELIST[msg] - sig = RUBY_WHITELIST[msg] unless sig - raise "no such method #{method}" unless sig # if sig is a string, then method mapped to another name return _instance_call(obj, sig, args, _e) if sig.is_a? String raise "too many args to #{method}" if args.length>(sig.length-1) arglist = [obj] + args - sig.each_with_index { |s, i| + sig.each_with_index do |s, i| s = [s] unless s.is_a?(Array) - ok, ai = false, arglist[i] + ai = arglist[i] - s.each { |sc| - if (sc.nil? && i>=arglist.length) || (sc && ai.class <= sc) - ok = true - break - end - } - raise "bad arg #{i}, method #{method}: #{ai}/#{ai.class} #{s}" if !ok - } + raise "bad arg #{i}, method #{method}: #{ai}/#{ai.class} #{s}" unless + (s.member?(nil) && i>=arglist.length) || + s.detect {|sc| sc && ai.class <= sc} + end res = obj.send(msg, *args) # FIXME: can't freeze AR relations since then we can't chain # calls (e.g. the chaining modifies the relation object. Not # sure what this side-effect means. Delorean code which