README.md in dentaku-0.2.14 vs README.md in dentaku-1.0.0

- old
+ new

@@ -3,12 +3,10 @@ [![Gem Version](https://badge.fury.io/rb/dentaku.png)](http://badge.fury.io/rb/dentaku) [![Build Status](https://travis-ci.org/rubysolo/dentaku.png?branch=master)](https://travis-ci.org/rubysolo/dentaku) [![Code Climate](https://codeclimate.com/github/rubysolo/dentaku.png)](https://codeclimate.com/github/rubysolo/dentaku) -http://github.com/rubysolo/dentaku - DESCRIPTION ----------- Dentaku is a parser and evaluator for a mathematical and logical formula language that allows run-time binding of values to variables referenced in the @@ -17,100 +15,155 @@ EXAMPLE ------- This is probably simplest to illustrate in code: - calculator = Dentaku::Calculator.new - calculator.evaluate('10 * 2') - => 20 +```ruby +calculator = Dentaku::Calculator.new +calculator.evaluate('10 * 2') +=> 20 +``` Okay, not terribly exciting. But what if you want to have a reference to a variable, and evaluate it at run-time? Here's how that would look: - calculator.evaluate('kiwi + 5', :kiwi => 2) - => 7 +```ruby +calculator.evaluate('kiwi + 5', kiwi: 2) +=> 7 +``` You can also store the variable values in the calculator's memory and then evaluate expressions against those stored values: - calculator.store(:peaches => 15) - calculator.evaluate('peaches - 5') - => 10 - calculator.evaluate('peaches >= 15') - => true +```ruby +calculator.store(peaches: 15) +calculator.evaluate('peaches - 5') +=> 10 +calculator.evaluate('peaches >= 15') +=> true +``` For maximum CS geekery, `bind` is an alias of `store`. Dentaku understands precedence order and using parentheses to group expressions to ensure proper evaluation: - calculator.evaluate('5 + 3 * 2') - => 11 - calculator.evaluate('(5 + 3) * 2') - => 16 +```ruby +calculator.evaluate('5 + 3 * 2') +=> 11 +calculator.evaluate('(5 + 3) * 2') +=> 16 +``` A number of functions are also supported. Okay, the number is currently five, but more will be added soon. The current functions are `if`, `not`, `round`, `rounddown`, and `roundup`, and they work like their counterparts in Excel: - calculator.evaluate('if (pears < 10, 10, 20)', :pears => 5) - => 10 - calculator.evaluate('if (pears < 10, 10, 20)', :pears => 15) - => 20 +```ruby +calculator.evaluate('if (pears < 10, 10, 20)', pears: 5) +=> 10 +calculator.evaluate('if (pears < 10, 10, 20)', pears: 15) +=> 20 +``` `round`, `rounddown`, and `roundup` can be called with or without the number of decimal places: - calculator.evaluate('round(8.2)') - => 8 - calculator.evaluate('round(8.2759, 2)') - => 8.28 +```ruby +calculator.evaluate('round(8.2)') +=> 8 +calculator.evaluate('round(8.2759, 2)') +=> 8.28 +``` `round` and `rounddown` round down, while `roundup` rounds up. If you're too lazy to be building calculator objects, there's a shortcut just for you: - Dentaku('plums * 1.5', {:plums => 2}) - => 3.0 +```ruby +Dentaku('plums * 1.5', plums: 2) +=> 3.0 +``` BUILT-IN OPERATORS AND FUNCTIONS --------------------------------- Math: `+ - * / %` + Logic: `< > <= >= <> != = AND OR` + Functions: `IF NOT ROUND ROUNDDOWN ROUNDUP` EXTERNAL FUNCTIONS ------------------ -See `spec/external_function_spec.rb` for examples of how to add your own functions. +I don't know everything, so I might not have implemented all the functions you +need. Please implement your favorites and send a pull request! Okay, so maybe +that's not feasible because: -The short, dense version: +1. You can't be bothered to share +2. You can't wait for me to respond to a pull request, you need it `NOW()` +3. The formula is the secret sauce for your startup -Each rule for an external function consists of three parts: the function's name, -a list of tokens describing its signature (parameters), and a lambda representing the -function's body. +Whatever your reasons, Dentaku supports adding functions at runtime. To add a +function, you'll need to specify: -The function name should be passed as a symbol (for example, `:func`). +* Name +* Return type +* Signature +* Body -The token list should consist of `:numeric` or `:string` if a single value of the named -type should be passed; `:non_group` or `:non_group_star` for grouped expressions. +Naming can be the hardest part, so you're on your own for that. -> (what's the difference? when would you use one instead of the other?) +`:type` specifies the type of value that will be returned, most likely +`:numeric`, `:string`, or `:logical`. -The function body should accept a list of parameters. Each function body will be passed -a sequence of tokens, in order: +`:signature` specifies the types and order of the parameters for your function. -1. The function's name -2. A token representing the opening parenthesis -3. Tokens representing the parameter values, separated by tokens representing the commas between parameters -4. A token representing the closing parenthesis +`:body` is a lambda that implements your function. It is passed the arguments +and should return the calculated value. -It should return a token (either `:numeric` or `:string`) representing the return value. +As an example, the exponentiation function takes two parameters, the mantissa +and the exponent, so the token list could be defined as: `[:numeric, +:numeric]`. Other functions might be variadic -- consider `max`, a function +that takes any number of numeric inputs and returns the largest one. Its token +list could be defined as: `[:non_close_plus]` (one or more tokens that are not +closing parentheses. -Rules can be set individually using Calculator#add_rule, or en masse using Calculator#add_rules. +Functions can be added individually using Calculator#add_function, or en masse using +Calculator#add_functions. + +Here's an example of adding the `exp` function: + +```ruby +> c = Dentaku::Calculator.new +> c.add_function( + name: :exp, + type: :numeric, + signature: [:numeric, :numeric], + body: ->(mantissa, exponent) { mantissa ** exponent } + ) +> c.evaluate('EXP(3,2)') +=> 9 +> c.evaluate('EXP(2,3)') +=> 8 +``` + +Here's an example of adding the `max` function: + +```ruby +> c = Dentaku::Calculator.new +> c.add_function( + name: :max, + type: :numeric, + signature: [:non_close_plus], + body: ->(*args) { args.max } + ) +> c.evaluate 'MAX(5,3,9,6,2)' +=> 9 +``` THANKS ------