# Delorean Delorean is a simple functional scripting language. It is used at [PENNYMAC][] as a scripting language for a financial modeling system. ![](http://i.imgur.com/qiG7Av6.jpg) ## Installation $ gem install delorean_lang Or add it to your `Gemfile`, etc. ## Usage require 'delorean_lang' engine = Delorean::Engine.new("MyModule") my_code =<= teen_min && age <= teen_max IndiaInfo: USInfo teen_min = 10 In this example, node `USInfo` provides a definition of a `is_teenager` when provided with an `age` parameter. Node `IndiaInfo` is derived from `USInfo` and so it shares all of its attribute definitions. However, the `teen_min` attribute has been overridden. This specifies that the computation of `is_teenager` will use the newly defined `teen_min`. Therefore, `IndiaInfo.is_teenager` with input of `age = 10` will evaluate to `true`. Whereas, `USInfo.is_teenager` with input of `age = 10` will evaluate to `false`. ### Debugging You can use `(ERR())` to add a breakpoint: ``` USInfo: age = ? teen_max = 19 teen_min = 13 is_teenager = (ERR()) && age >= teen_min && age <= teen_max ``` Then you can call attributes by using their mangled name (e.g. attr__D) and passing the context. attr__D(_e). Of course, you can use `ls` to list available methods. ``` teen_max__D(_e) # 19 age__D(_e) ``` ### TODO TODO: provide details on the following topics: * Supported data types * Data structures (arrays and hashes) * List comprehension * Built-in functions * Defining Delorean-callable class functions * External modules ## Implementation This implementation of Delorean "compiles" script code to Ruby. ### Calling ruby methods from Delorean There are two ways of calling ruby code from delorean. First one is to whitelist methods: ```ruby ::Delorean::Ruby.whitelist.add__method :any? do |method| method.called_on Enumerable end ::Delorean::Ruby.whitelist.add_method :length do |method| method.called_on String method.called_on Enumerable end ::Delorean::Ruby.whitelist.add_method :first do |method| method.called_on Enumerable, with: [Integer] end ::Delorean::Ruby.whitelist.add_class_method :last do |method| method.called_on ActiveRecord::Base, with: [Integer] end ``` By default Delorean has some methods whitelisted, such as `length`, `min`, `max`, etc. Those can be found in `/lib/delorean/ruby/whitelists/default`. If you don't want to use defaults, you can override whitelist with and empty one. ```ruby require 'delorean/ruby/whitelists/empty' ::Delorean::Ruby.whitelist = ::Delorean::Ruby::Whitelists::Empty.new ``` Another way is to define methods using `delorean_fn` with optional `private` and `cache` flags. Use `extend Delorean::Functions` or `include Delorean::Model` in your module or class. ```ruby class Dummy < ActiveRecord::Base include Delorean::Model delorean_fn(:heres_my_number, sig: [0, Float::INFINITY]) do |*a| a.inject(0, :+) end delorean_fn :private_cached_number, cache: true, private: true do |*a| a.inject(0, :+) end end module DummyModule extend Delorean::Functions delorean_fn(:heres_my_number, sig: [0, Float::INFINITY]) do |*a| a.inject(0, :+) end end ``` `heres_my_number` method will be accessible from Delorean code. ```ruby ExampleScript: a = Dummy.heres_my_number(867, 5309)' b = DummyModule.heres_my_number(867, 5309)' ``` You can use blocks in your Delorean code: ```ruby ExampleScript: a = [1, 2, 3] b = c.any? { |v| v > 2 } b2 = c.any? do |v| v > 2 end c = a.reduce(0) { |sum, el| sum + el } c2 = a.reduce() do |sum, el| sum + el end ``` Note that `do ... end` syntax is not yet supported ### Caching Delorean provides `cache` flag for `delorean_fn` method that will cache result based on arguments. ```ruby delorean_fn :returns_cached_openstruct, cache: true do |timestamp| User.all end ``` If `::Delorean::Cache.adapter.cache_item?(...)` returns `false` then caching will not be performed. By default cache keeps the last 1000 of the results per class. You can override it: ```ruby ::Delorean::Cache.adapter = ::Delorean::Cache::Adapters::RubyCache.new(size_per_class: 10) ``` If you want use other caching method, you can use your own adapter: ```ruby ::Delorean::Cache.adapter = ::My::Custom::Cache::Adapter.new ``` Delorean expects it to have methods with following signatures: ```ruby cache_item(klass:, cache_key:, item:) fetch_item(klass:, cache_key:, default:) cache_key(klass:, method_name:, args:) clear!(klass:) clear_all! cache_item?(klass:, method_name:, args:) # See lib/delorean/cache/adapters/base.rb ``` TODO: provide details ## Development ### Treetop Edit treetop rules in `lib/delorean/delorean.treetop` Use `make treetop-generate` to regenerate `lib/delorean/delorean.rb` based on Treetop logic in `lib/delorean/delorean.treetop` ### Testing Use `rspec` to run the tests. ## License Delorean has been released under the MIT license. Please check the [LICENSE][] file for more details. [license]: https://github.com/rubygems/rubygems.org/blob/master/MIT-LICENSE [pennymac]: http://www.pennymacusa.com [functional programming]: http://en.wikipedia.org/wiki/Functional_programming