lib/delorean/base.rb in delorean_lang-0.0.43 vs lib/delorean/base.rb in delorean_lang-0.1.00

- old
+ new

@@ -1,31 +1,80 @@ -require 'delorean/functions' - module Delorean + + # 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 = { + compact: [Array], + flatten: [Array, [Fixnum, nil]], + length: [[Array, String]], + max: [Array], + member: "member?", + member?: [Array, [Fixnum, String]], + reverse: [Array], + slice: [Array, Fixnum, Fixnum], + sort: [Array], + split: [String, String], + uniq: [Array], + + hour: [[Date, Time, ActiveSupport::TimeWithZone]], + min: [[Date, Time, ActiveSupport::TimeWithZone, Array]], + sec: [[Date, Time, ActiveSupport::TimeWithZone]], + to_date: [[Date, Time, ActiveSupport::TimeWithZone]], + + month: [[Date, Time, ActiveSupport::TimeWithZone]], + day: [[Date, Time, ActiveSupport::TimeWithZone]], + year: [[Date, Time, ActiveSupport::TimeWithZone]], + + to_i: [[Numeric, String]], + to_f: [[Numeric, String]], + to_d: [[Numeric, String]], + to_s: [Object], + abs: [Numeric], + round: [Numeric, [nil, Integer]], + } + module BaseModule + class NodeCall + attr_reader :engine, :node, :params + def initialize(engine, node, params) + @engine, @node, @params = engine, node, params + end - class BaseClass - # Using extend and include to get both constants and methods. - # Not sure if how to do this only with extend. - extend Delorean::Functions - include Delorean::Functions + def evaluate(attr) + engine.evaluate(node, attr, params) + end - ###################################################################### + def %(args) + raise "bad arg to %" unless args.is_a?(Array) + args.each_with_object({}) { |attr, h| + h[attr] = evaluate(attr) + } + end + end + + class BaseClass + def self._get_attr(obj, attr, _e) 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 raise InvalidGetAttribute, "ActiveRecord lookup '#{attr}' on #{obj}" + elsif obj.instance_of?(NodeCall) + return obj.evaluate(attr) elsif obj.instance_of?(Hash) return obj.member?(attr) ? obj[attr] : obj[attr.to_sym] elsif obj.instance_of?(Class) && (obj < BaseClass) return obj.send((attr + POST).to_sym, _e) end @@ -36,13 +85,16 @@ ###################################################################### def self._index(obj, args, _e) return nil if obj.nil? - if obj.instance_of?(Hash) + # 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) raise InvalidIndex unless args.length == 1 - obj[args[0]] + _get_attr(obj, args[0], _e) elsif obj.instance_of?(Array) raise InvalidIndex unless args.length < 2 raise InvalidIndex unless args[0].is_a?(Fixnum) && (!args[1] || args[1].is_a?(Fixnum)) obj[*args] @@ -51,15 +103,56 @@ end end ###################################################################### - def self._script_call(node, mname, _e, attrs, params) + def self._err(*args) + str = args.map(&:to_s).join(", ") + raise str + end + + def self._node_call(node, mname, _e, params) context = _e[:_engine] node ||= self engine = mname ? context.get_import_engine(mname) : context - engine.evaluate_attrs_hash(node, attrs, params) + NodeCall.new(engine, node, params) + end + + ###################################################################### + + def self._instance_call(obj, method, args) + begin + msg = method.to_sym + rescue NoMethodError + raise "bad method #{method}" + end + + sig = RUBY_WHITELIST[msg] + + raise "no such method #{method}" unless sig + + # if sig is a string, then method mapped to another name + return _instance_call(obj, sig, args) if sig.respond_to?(:to_sym) + + raise "too many args to #{method}" if args.length>(sig.length-1) + + arglist = [obj] + args + + sig.each_with_index { |s, i| + s = [s] unless s.is_a?(Array) + + ok, ai = false, arglist[i] + s.each { |sc| + if (sc.nil? && i>=arglist.length) || (sc && ai.class <= sc) + ok = true + break + end + } + raise "bad arg #{i} to method #{method}: #{ai}/#{ai.class} #{s}" unless ok + } + + obj.send(msg, *args) end ###################################################################### end end