dentaku 1.2.6 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/README.md +52 -57
 - data/Rakefile +1 -1
 - data/lib/dentaku.rb +8 -0
 - data/lib/dentaku/ast.rb +22 -0
 - data/lib/dentaku/ast/addition.rb +15 -0
 - data/lib/dentaku/ast/combinators.rb +15 -0
 - data/lib/dentaku/ast/comparators.rb +47 -0
 - data/lib/dentaku/ast/division.rb +15 -0
 - data/lib/dentaku/ast/exponentiation.rb +15 -0
 - data/lib/dentaku/ast/function.rb +54 -0
 - data/lib/dentaku/ast/functions/if.rb +26 -0
 - data/lib/dentaku/ast/functions/max.rb +5 -0
 - data/lib/dentaku/ast/functions/min.rb +5 -0
 - data/lib/dentaku/ast/functions/not.rb +5 -0
 - data/lib/dentaku/ast/functions/round.rb +5 -0
 - data/lib/dentaku/ast/functions/rounddown.rb +5 -0
 - data/lib/dentaku/ast/functions/roundup.rb +5 -0
 - data/lib/dentaku/ast/functions/ruby_math.rb +8 -0
 - data/lib/dentaku/ast/grouping.rb +13 -0
 - data/lib/dentaku/ast/identifier.rb +29 -0
 - data/lib/dentaku/ast/multiplication.rb +15 -0
 - data/lib/dentaku/ast/negation.rb +25 -0
 - data/lib/dentaku/ast/nil.rb +9 -0
 - data/lib/dentaku/ast/node.rb +13 -0
 - data/lib/dentaku/ast/numeric.rb +17 -0
 - data/lib/dentaku/ast/operation.rb +20 -0
 - data/lib/dentaku/ast/string.rb +17 -0
 - data/lib/dentaku/ast/subtraction.rb +15 -0
 - data/lib/dentaku/bulk_expression_solver.rb +6 -11
 - data/lib/dentaku/calculator.rb +26 -20
 - data/lib/dentaku/parser.rb +131 -0
 - data/lib/dentaku/token.rb +4 -0
 - data/lib/dentaku/token_matchers.rb +29 -0
 - data/lib/dentaku/token_scanner.rb +18 -3
 - data/lib/dentaku/tokenizer.rb +10 -2
 - data/lib/dentaku/version.rb +1 -1
 - data/spec/ast/function_spec.rb +19 -0
 - data/spec/ast/node_spec.rb +37 -0
 - data/spec/bulk_expression_solver_spec.rb +12 -5
 - data/spec/calculator_spec.rb +14 -1
 - data/spec/external_function_spec.rb +12 -28
 - data/spec/parser_spec.rb +88 -0
 - data/spec/spec_helper.rb +2 -1
 - data/spec/token_scanner_spec.rb +4 -3
 - data/spec/tokenizer_spec.rb +32 -6
 - metadata +36 -16
 - data/lib/dentaku/binary_operation.rb +0 -35
 - data/lib/dentaku/evaluator.rb +0 -166
 - data/lib/dentaku/expression.rb +0 -56
 - data/lib/dentaku/external_function.rb +0 -10
 - data/lib/dentaku/rule_set.rb +0 -153
 - data/spec/binary_operation_spec.rb +0 -45
 - data/spec/evaluator_spec.rb +0 -145
 - data/spec/expression_spec.rb +0 -25
 - data/spec/rule_set_spec.rb +0 -43
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA1:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 85e18c02f88a1fc906c69667776e00ccb1f969f5
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 6fba624e3b93a4e64e2ce55fb6fe13f42d9138be
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: befe922779a83e64b95ed17907846a29901c59badb2810d2c28f2555aabc5359f7a21a1aeac6cca12b63c39bb59dad225ce233bfccc59b91af9eea027f3ab3cf
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 2bac8daaa567d69860f8c5d00ed52375f989bde4893bb7b405ca513c257edf987a742ea7dc97d5817d8fa6023a82a77040bd69e17ca487c4f2fc45c8c6fba4e1
         
     | 
    
        data/README.md
    CHANGED
    
    | 
         @@ -1,6 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            Dentaku
         
     | 
| 
       2 
2 
     | 
    
         
             
            =======
         
     | 
| 
       3 
3 
     | 
    
         | 
| 
      
 4 
     | 
    
         
            +
            [](https://gitter.im/rubysolo/dentaku?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
         
     | 
| 
       4 
5 
     | 
    
         
             
            [](http://badge.fury.io/rb/dentaku)
         
     | 
| 
       5 
6 
     | 
    
         
             
            [](https://travis-ci.org/rubysolo/dentaku)
         
     | 
| 
       6 
7 
     | 
    
         
             
            [](https://codeclimate.com/github/rubysolo/dentaku)
         
     | 
| 
         @@ -66,10 +67,9 @@ calculator.evaluate!('10 * x') 
     | 
|
| 
       66 
67 
     | 
    
         
             
            Dentaku::UnboundVariableError: Dentaku::UnboundVariableError
         
     | 
| 
       67 
68 
     | 
    
         
             
            ```
         
     | 
| 
       68 
69 
     | 
    
         | 
| 
       69 
     | 
    
         
            -
             
     | 
| 
       70 
     | 
    
         
            -
             
     | 
| 
       71 
     | 
    
         
            -
             
     | 
| 
       72 
     | 
    
         
            -
            counterparts in Excel:
         
     | 
| 
      
 70 
     | 
    
         
            +
            Dentaku has built-in functions (including `if`, `not`, `min`, `max`, and
         
     | 
| 
      
 71 
     | 
    
         
            +
            `round`) and the ability to define custom functions (see below). Functions
         
     | 
| 
      
 72 
     | 
    
         
            +
            generally work like their counterparts in Excel:
         
     | 
| 
       73 
73 
     | 
    
         | 
| 
       74 
74 
     | 
    
         
             
            ```ruby
         
     | 
| 
       75 
75 
     | 
    
         
             
            calculator.evaluate('if (pears < 10, 10, 20)', pears: 5)
         
     | 
| 
         @@ -78,7 +78,7 @@ calculator.evaluate('if (pears < 10, 10, 20)', pears: 15) 
     | 
|
| 
       78 
78 
     | 
    
         
             
            #=> 20
         
     | 
| 
       79 
79 
     | 
    
         
             
            ```
         
     | 
| 
       80 
80 
     | 
    
         | 
| 
       81 
     | 
    
         
            -
            `round 
     | 
| 
      
 81 
     | 
    
         
            +
            `round` can be called with or without the number of decimal places:
         
     | 
| 
       82 
82 
     | 
    
         | 
| 
       83 
83 
     | 
    
         
             
            ```ruby
         
     | 
| 
       84 
84 
     | 
    
         
             
            calculator.evaluate('round(8.2)')
         
     | 
| 
         @@ -87,7 +87,8 @@ calculator.evaluate('round(8.2759, 2)') 
     | 
|
| 
       87 
87 
     | 
    
         
             
            #=> 8.28
         
     | 
| 
       88 
88 
     | 
    
         
             
            ```
         
     | 
| 
       89 
89 
     | 
    
         | 
| 
       90 
     | 
    
         
            -
            `round`  
     | 
| 
      
 90 
     | 
    
         
            +
            `round` follows rounding rules, while `roundup` and `rounddown` are `ceil` and
         
     | 
| 
      
 91 
     | 
    
         
            +
            `floor`, respectively.
         
     | 
| 
       91 
92 
     | 
    
         | 
| 
       92 
93 
     | 
    
         
             
            If you're too lazy to be building calculator objects, there's a shortcut just
         
     | 
| 
       93 
94 
     | 
    
         
             
            for you:
         
     | 
| 
         @@ -105,7 +106,9 @@ Math: `+ - * / %` 
     | 
|
| 
       105 
106 
     | 
    
         | 
| 
       106 
107 
     | 
    
         
             
            Logic: `< > <= >= <> != = AND OR`
         
     | 
| 
       107 
108 
     | 
    
         | 
| 
       108 
     | 
    
         
            -
            Functions: `IF NOT ROUND ROUNDDOWN ROUNDUP`
         
     | 
| 
      
 109 
     | 
    
         
            +
            Functions: `IF NOT MIN MAX ROUND ROUNDDOWN ROUNDUP`
         
     | 
| 
      
 110 
     | 
    
         
            +
             
     | 
| 
      
 111 
     | 
    
         
            +
            Math: all functions from Ruby's Math module, including `SIN, COS, TAN, ...`
         
     | 
| 
       109 
112 
     | 
    
         | 
| 
       110 
113 
     | 
    
         
             
            RESOLVING DEPENDENCIES
         
     | 
| 
       111 
114 
     | 
    
         
             
            ----------------------
         
     | 
| 
         @@ -125,7 +128,7 @@ need_to_compute = { 
     | 
|
| 
       125 
128 
     | 
    
         
             
            In the example, `annual_income` needs to be computed (and stored) before
         
     | 
| 
       126 
129 
     | 
    
         
             
            `income_taxes`.
         
     | 
| 
       127 
130 
     | 
    
         | 
| 
       128 
     | 
    
         
            -
            Dentaku provides two methods to help resolve formulas in order 
     | 
| 
      
 131 
     | 
    
         
            +
            Dentaku provides two methods to help resolve formulas in order:
         
     | 
| 
       129 
132 
     | 
    
         | 
| 
       130 
133 
     | 
    
         
             
            #### Calculator.dependencies
         
     | 
| 
       131 
134 
     | 
    
         
             
            Pass a (string) expression to Dependencies and get back a list of variables (as
         
     | 
| 
         @@ -144,7 +147,7 @@ calc.dependencies("annual_income / 5") 
     | 
|
| 
       144 
147 
     | 
    
         
             
            #### Calculator.solve!
         
     | 
| 
       145 
148 
     | 
    
         
             
            Have Dentaku figure out the order in which your formulas need to be evaluated.
         
     | 
| 
       146 
149 
     | 
    
         | 
| 
       147 
     | 
    
         
            -
            Pass in a hash of {eventual_variable_name: "expression"} to `solve!` and
         
     | 
| 
      
 150 
     | 
    
         
            +
            Pass in a hash of `{eventual_variable_name: "expression"}` to `solve!` and
         
     | 
| 
       148 
151 
     | 
    
         
             
            have Dentaku figure out dependencies (using `TSort`) for you.
         
     | 
| 
       149 
152 
     | 
    
         | 
| 
       150 
153 
     | 
    
         
             
            Raises `TSort::Cyclic` when a valid expression order cannot be found.
         
     | 
| 
         @@ -153,7 +156,7 @@ Raises `TSort::Cyclic` when a valid expression order cannot be found. 
     | 
|
| 
       153 
156 
     | 
    
         
             
            calc = Dentaku::Calculator.new
         
     | 
| 
       154 
157 
     | 
    
         
             
            calc.store(monthly_income: 50)
         
     | 
| 
       155 
158 
     | 
    
         
             
            need_to_compute = {
         
     | 
| 
       156 
     | 
    
         
            -
              income_taxes: 
     | 
| 
      
 159 
     | 
    
         
            +
              income_taxes:  "annual_income / 5",
         
     | 
| 
       157 
160 
     | 
    
         
             
              annual_income: "monthly_income * 12"
         
     | 
| 
       158 
161 
     | 
    
         
             
            }
         
     | 
| 
       159 
162 
     | 
    
         
             
            calc.solve!(need_to_compute)
         
     | 
| 
         @@ -166,6 +169,29 @@ calc.solve!( 
     | 
|
| 
       166 
169 
     | 
    
         
             
            #=> raises TSort::Cyclic
         
     | 
| 
       167 
170 
     | 
    
         
             
            ```
         
     | 
| 
       168 
171 
     | 
    
         | 
| 
      
 172 
     | 
    
         
            +
            INLINE COMMENTS
         
     | 
| 
      
 173 
     | 
    
         
            +
            ---------------------------------
         
     | 
| 
      
 174 
     | 
    
         
            +
             
     | 
| 
      
 175 
     | 
    
         
            +
            If your expressions grow long or complex, you may add inline comments for future
         
     | 
| 
      
 176 
     | 
    
         
            +
            reference. This is particularly useful if you save your expressions in a model.
         
     | 
| 
      
 177 
     | 
    
         
            +
             
     | 
| 
      
 178 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 179 
     | 
    
         
            +
            calculator.evaluate('kiwi + 5 /* This is a comment */', kiwi: 2)
         
     | 
| 
      
 180 
     | 
    
         
            +
            #=> 7
         
     | 
| 
      
 181 
     | 
    
         
            +
            ```
         
     | 
| 
      
 182 
     | 
    
         
            +
             
     | 
| 
      
 183 
     | 
    
         
            +
            Comments can be single or multi-line. The following are also valid.
         
     | 
| 
      
 184 
     | 
    
         
            +
             
     | 
| 
      
 185 
     | 
    
         
            +
            ```
         
     | 
| 
      
 186 
     | 
    
         
            +
            /*
         
     | 
| 
      
 187 
     | 
    
         
            +
             * This is a multi-line comment
         
     | 
| 
      
 188 
     | 
    
         
            +
             */
         
     | 
| 
      
 189 
     | 
    
         
            +
             
     | 
| 
      
 190 
     | 
    
         
            +
            /*
         
     | 
| 
      
 191 
     | 
    
         
            +
             This is another type of multi-line comment
         
     | 
| 
      
 192 
     | 
    
         
            +
             */
         
     | 
| 
      
 193 
     | 
    
         
            +
            ```
         
     | 
| 
      
 194 
     | 
    
         
            +
             
     | 
| 
       169 
195 
     | 
    
         
             
            EXTERNAL FUNCTIONS
         
     | 
| 
       170 
196 
     | 
    
         
             
            ------------------
         
     | 
| 
       171 
197 
     | 
    
         | 
| 
         @@ -174,69 +200,39 @@ need.  Please implement your favorites and send a pull request!  Okay, so maybe 
     | 
|
| 
       174 
200 
     | 
    
         
             
            that's not feasible because:
         
     | 
| 
       175 
201 
     | 
    
         | 
| 
       176 
202 
     | 
    
         
             
            1. You can't be bothered to share
         
     | 
| 
       177 
     | 
    
         
            -
             
     | 
| 
       178 
     | 
    
         
            -
             
     | 
| 
      
 203 
     | 
    
         
            +
            1. You can't wait for me to respond to a pull request, you need it `NOW()`
         
     | 
| 
      
 204 
     | 
    
         
            +
            1. The formula is the secret sauce for your startup
         
     | 
| 
       179 
205 
     | 
    
         | 
| 
       180 
206 
     | 
    
         
             
            Whatever your reasons, Dentaku supports adding functions at runtime.  To add a
         
     | 
| 
       181 
     | 
    
         
            -
            function, you'll need to specify 
     | 
| 
       182 
     | 
    
         
            -
             
     | 
| 
       183 
     | 
    
         
            -
            * Name
         
     | 
| 
       184 
     | 
    
         
            -
            * Return type
         
     | 
| 
       185 
     | 
    
         
            -
            * Signature
         
     | 
| 
       186 
     | 
    
         
            -
            * Body
         
     | 
| 
       187 
     | 
    
         
            -
             
     | 
| 
       188 
     | 
    
         
            -
            Naming can be the hardest part, so you're on your own for that.
         
     | 
| 
       189 
     | 
    
         
            -
             
     | 
| 
       190 
     | 
    
         
            -
            `:type` specifies the type of value that will be returned, most likely
         
     | 
| 
       191 
     | 
    
         
            -
            `:numeric`, `:string`, or `:logical`.
         
     | 
| 
       192 
     | 
    
         
            -
             
     | 
| 
       193 
     | 
    
         
            -
            `:signature` specifies the types and order of the parameters for your function.
         
     | 
| 
      
 207 
     | 
    
         
            +
            function, you'll need to specify a name and a lambda that accepts all function
         
     | 
| 
      
 208 
     | 
    
         
            +
            arguments and returns the result value.
         
     | 
| 
       194 
209 
     | 
    
         | 
| 
       195 
     | 
    
         
            -
             
     | 
| 
       196 
     | 
    
         
            -
             
     | 
| 
       197 
     | 
    
         
            -
             
     | 
| 
       198 
     | 
    
         
            -
            As an example, the exponentiation function takes two parameters, the mantissa
         
     | 
| 
       199 
     | 
    
         
            -
            and the exponent, so the token list could be defined as: `[:numeric,
         
     | 
| 
       200 
     | 
    
         
            -
            :numeric]`.  Other functions might be variadic -- consider `max`, a function
         
     | 
| 
       201 
     | 
    
         
            -
            that takes any number of numeric inputs and returns the largest one.  Its token
         
     | 
| 
       202 
     | 
    
         
            -
            list could be defined as: `[:arguments]` (one or more numeric, string, or logical
         
     | 
| 
       203 
     | 
    
         
            -
            values, separated by commas).  See the
         
     | 
| 
       204 
     | 
    
         
            -
            [rules definitions](https://github.com/rubysolo/dentaku/blob/master/lib/dentaku/token_matcher.rb#L87)
         
     | 
| 
       205 
     | 
    
         
            -
            for the names of token patterns you can use.
         
     | 
| 
       206 
     | 
    
         
            -
             
     | 
| 
       207 
     | 
    
         
            -
            Functions can be added individually using Calculator#add_function, or en masse using
         
     | 
| 
       208 
     | 
    
         
            -
            Calculator#add_functions.
         
     | 
| 
       209 
     | 
    
         
            -
             
     | 
| 
       210 
     | 
    
         
            -
            Here's an example of adding the `exp` function:
         
     | 
| 
      
 210 
     | 
    
         
            +
            Here's an example of adding a function named `POW` that implements
         
     | 
| 
      
 211 
     | 
    
         
            +
            exponentiation.
         
     | 
| 
       211 
212 
     | 
    
         | 
| 
       212 
213 
     | 
    
         
             
            ```ruby
         
     | 
| 
       213 
214 
     | 
    
         
             
            > c = Dentaku::Calculator.new
         
     | 
| 
       214 
     | 
    
         
            -
            > c.add_function(
         
     | 
| 
       215 
     | 
    
         
            -
             
     | 
| 
       216 
     | 
    
         
            -
                type: :numeric,
         
     | 
| 
       217 
     | 
    
         
            -
                signature: [:numeric, :numeric],
         
     | 
| 
       218 
     | 
    
         
            -
                body: ->(mantissa, exponent) { mantissa ** exponent }
         
     | 
| 
       219 
     | 
    
         
            -
              )
         
     | 
| 
       220 
     | 
    
         
            -
            > c.evaluate('EXP(3,2)')
         
     | 
| 
      
 215 
     | 
    
         
            +
            > c.add_function(:pow, ->(mantissa, exponent) { mantissa ** exponent })
         
     | 
| 
      
 216 
     | 
    
         
            +
            > c.evaluate('POW(3,2)')
         
     | 
| 
       221 
217 
     | 
    
         
             
            #=> 9
         
     | 
| 
       222 
     | 
    
         
            -
            > c.evaluate(' 
     | 
| 
      
 218 
     | 
    
         
            +
            > c.evaluate('POW(2,3)')
         
     | 
| 
       223 
219 
     | 
    
         
             
            #=> 8
         
     | 
| 
       224 
220 
     | 
    
         
             
            ```
         
     | 
| 
       225 
221 
     | 
    
         | 
| 
       226 
     | 
    
         
            -
            Here's an example of adding  
     | 
| 
      
 222 
     | 
    
         
            +
            Here's an example of adding a variadic function:
         
     | 
| 
       227 
223 
     | 
    
         | 
| 
       228 
224 
     | 
    
         
             
            ```ruby
         
     | 
| 
       229 
225 
     | 
    
         
             
            > c = Dentaku::Calculator.new
         
     | 
| 
       230 
     | 
    
         
            -
            > c.add_function(
         
     | 
| 
       231 
     | 
    
         
            -
                name: :max,
         
     | 
| 
       232 
     | 
    
         
            -
                type: :numeric,
         
     | 
| 
       233 
     | 
    
         
            -
                signature: [:arguments],
         
     | 
| 
       234 
     | 
    
         
            -
                body: ->(*args) { args.max }
         
     | 
| 
       235 
     | 
    
         
            -
              )
         
     | 
| 
      
 226 
     | 
    
         
            +
            > c.add_function(:max, ->(*args) { args.max })
         
     | 
| 
       236 
227 
     | 
    
         
             
            > c.evaluate 'MAX(8,6,7,5,3,0,9)'
         
     | 
| 
       237 
228 
     | 
    
         
             
            #=> 9
         
     | 
| 
       238 
229 
     | 
    
         
             
            ```
         
     | 
| 
       239 
230 
     | 
    
         | 
| 
      
 231 
     | 
    
         
            +
            (However both of these are already built-in -- the `^` operator and the `MAX`
         
     | 
| 
      
 232 
     | 
    
         
            +
            function)
         
     | 
| 
      
 233 
     | 
    
         
            +
             
     | 
| 
      
 234 
     | 
    
         
            +
            Functions can be added individually using Calculator#add_function, or en masse
         
     | 
| 
      
 235 
     | 
    
         
            +
            using Calculator#add_functions.
         
     | 
| 
       240 
236 
     | 
    
         | 
| 
       241 
237 
     | 
    
         
             
            THANKS
         
     | 
| 
       242 
238 
     | 
    
         
             
            ------
         
     | 
| 
         @@ -283,4 +279,3 @@ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 
     | 
|
| 
       283 
279 
     | 
    
         
             
            COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
         
     | 
| 
       284 
280 
     | 
    
         
             
            IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
         
     | 
| 
       285 
281 
     | 
    
         
             
            CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
         
     | 
| 
       286 
     | 
    
         
            -
             
     | 
    
        data/Rakefile
    CHANGED
    
    
    
        data/lib/dentaku.rb
    CHANGED
    
    
    
        data/lib/dentaku/ast.rb
    ADDED
    
    | 
         @@ -0,0 +1,22 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require_relative './ast/node'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require_relative './ast/nil'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require_relative './ast/numeric'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require_relative './ast/string'
         
     | 
| 
      
 5 
     | 
    
         
            +
            require_relative './ast/identifier'
         
     | 
| 
      
 6 
     | 
    
         
            +
            require_relative './ast/addition'
         
     | 
| 
      
 7 
     | 
    
         
            +
            require_relative './ast/subtraction'
         
     | 
| 
      
 8 
     | 
    
         
            +
            require_relative './ast/multiplication'
         
     | 
| 
      
 9 
     | 
    
         
            +
            require_relative './ast/division'
         
     | 
| 
      
 10 
     | 
    
         
            +
            require_relative './ast/exponentiation'
         
     | 
| 
      
 11 
     | 
    
         
            +
            require_relative './ast/negation'
         
     | 
| 
      
 12 
     | 
    
         
            +
            require_relative './ast/comparators'
         
     | 
| 
      
 13 
     | 
    
         
            +
            require_relative './ast/combinators'
         
     | 
| 
      
 14 
     | 
    
         
            +
            require_relative './ast/grouping'
         
     | 
| 
      
 15 
     | 
    
         
            +
            require_relative './ast/functions/if'
         
     | 
| 
      
 16 
     | 
    
         
            +
            require_relative './ast/functions/max'
         
     | 
| 
      
 17 
     | 
    
         
            +
            require_relative './ast/functions/min'
         
     | 
| 
      
 18 
     | 
    
         
            +
            require_relative './ast/functions/not'
         
     | 
| 
      
 19 
     | 
    
         
            +
            require_relative './ast/functions/round'
         
     | 
| 
      
 20 
     | 
    
         
            +
            require_relative './ast/functions/roundup'
         
     | 
| 
      
 21 
     | 
    
         
            +
            require_relative './ast/functions/rounddown'
         
     | 
| 
      
 22 
     | 
    
         
            +
            require_relative './ast/functions/ruby_math'
         
     | 
| 
         @@ -0,0 +1,15 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Dentaku
         
     | 
| 
      
 2 
     | 
    
         
            +
              module AST
         
     | 
| 
      
 3 
     | 
    
         
            +
                class And < Operation
         
     | 
| 
      
 4 
     | 
    
         
            +
                  def value(context={})
         
     | 
| 
      
 5 
     | 
    
         
            +
                    left.value(context) && right.value(context)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  end
         
     | 
| 
      
 7 
     | 
    
         
            +
                end
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                class Or < Operation
         
     | 
| 
      
 10 
     | 
    
         
            +
                  def value(context={})
         
     | 
| 
      
 11 
     | 
    
         
            +
                    left.value(context) || right.value(context)
         
     | 
| 
      
 12 
     | 
    
         
            +
                  end
         
     | 
| 
      
 13 
     | 
    
         
            +
                end
         
     | 
| 
      
 14 
     | 
    
         
            +
              end
         
     | 
| 
      
 15 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,47 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require_relative './operation'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Dentaku
         
     | 
| 
      
 4 
     | 
    
         
            +
              module AST
         
     | 
| 
      
 5 
     | 
    
         
            +
                class Comparator < Operation
         
     | 
| 
      
 6 
     | 
    
         
            +
                  def self.precedence
         
     | 
| 
      
 7 
     | 
    
         
            +
                    5
         
     | 
| 
      
 8 
     | 
    
         
            +
                  end
         
     | 
| 
      
 9 
     | 
    
         
            +
                end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                class LessThan < Comparator
         
     | 
| 
      
 12 
     | 
    
         
            +
                  def value(context={})
         
     | 
| 
      
 13 
     | 
    
         
            +
                    left.value(context) < right.value(context)
         
     | 
| 
      
 14 
     | 
    
         
            +
                  end
         
     | 
| 
      
 15 
     | 
    
         
            +
                end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                class LessThanOrEqual < Comparator
         
     | 
| 
      
 18 
     | 
    
         
            +
                  def value(context={})
         
     | 
| 
      
 19 
     | 
    
         
            +
                    left.value(context) <= right.value(context)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  end
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                class GreaterThan < Comparator
         
     | 
| 
      
 24 
     | 
    
         
            +
                  def value(context={})
         
     | 
| 
      
 25 
     | 
    
         
            +
                    left.value(context) > right.value(context)
         
     | 
| 
      
 26 
     | 
    
         
            +
                  end
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                class GreaterThanOrEqual < Comparator
         
     | 
| 
      
 30 
     | 
    
         
            +
                  def value(context={})
         
     | 
| 
      
 31 
     | 
    
         
            +
                    left.value(context) >= right.value(context)
         
     | 
| 
      
 32 
     | 
    
         
            +
                  end
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                class NotEqual < Comparator
         
     | 
| 
      
 36 
     | 
    
         
            +
                  def value(context={})
         
     | 
| 
      
 37 
     | 
    
         
            +
                    left.value(context) != right.value(context)
         
     | 
| 
      
 38 
     | 
    
         
            +
                  end
         
     | 
| 
      
 39 
     | 
    
         
            +
                end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                class Equal < Comparator
         
     | 
| 
      
 42 
     | 
    
         
            +
                  def value(context={})
         
     | 
| 
      
 43 
     | 
    
         
            +
                    left.value(context) == right.value(context)
         
     | 
| 
      
 44 
     | 
    
         
            +
                  end
         
     | 
| 
      
 45 
     | 
    
         
            +
                end
         
     | 
| 
      
 46 
     | 
    
         
            +
              end
         
     | 
| 
      
 47 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,54 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require_relative 'node'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Dentaku
         
     | 
| 
      
 4 
     | 
    
         
            +
              module AST
         
     | 
| 
      
 5 
     | 
    
         
            +
                class Function < Node
         
     | 
| 
      
 6 
     | 
    
         
            +
                  def initialize(*args)
         
     | 
| 
      
 7 
     | 
    
         
            +
                    @args = args
         
     | 
| 
      
 8 
     | 
    
         
            +
                  end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                  def dependencies(context={})
         
     | 
| 
      
 11 
     | 
    
         
            +
                    @args.flat_map { |a| a.dependencies(context) }
         
     | 
| 
      
 12 
     | 
    
         
            +
                  end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                  def self.get(name)
         
     | 
| 
      
 15 
     | 
    
         
            +
                    registry.fetch(function_name(name)) { fail "Undefined function #{ name } "}
         
     | 
| 
      
 16 
     | 
    
         
            +
                  end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                  def self.register(name, implementation)
         
     | 
| 
      
 19 
     | 
    
         
            +
                    function = Class.new(self) do
         
     | 
| 
      
 20 
     | 
    
         
            +
                      def self.implementation=(impl)
         
     | 
| 
      
 21 
     | 
    
         
            +
                        @implementation = impl
         
     | 
| 
      
 22 
     | 
    
         
            +
                      end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                      def self.implementation
         
     | 
| 
      
 25 
     | 
    
         
            +
                        @implementation
         
     | 
| 
      
 26 
     | 
    
         
            +
                      end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                      def value(context={})
         
     | 
| 
      
 29 
     | 
    
         
            +
                        args = @args.flat_map { |a| a.value(context) }
         
     | 
| 
      
 30 
     | 
    
         
            +
                        self.class.implementation.call(*args)
         
     | 
| 
      
 31 
     | 
    
         
            +
                      end
         
     | 
| 
      
 32 
     | 
    
         
            +
                    end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                    function.implementation = implementation
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                    registry[function_name(name)] = function
         
     | 
| 
      
 37 
     | 
    
         
            +
                  end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                  def self.register_class(name, function_class)
         
     | 
| 
      
 40 
     | 
    
         
            +
                    registry[function_name(name)] = function_class
         
     | 
| 
      
 41 
     | 
    
         
            +
                  end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                  private
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                  def self.function_name(name)
         
     | 
| 
      
 46 
     | 
    
         
            +
                    name.to_s.downcase
         
     | 
| 
      
 47 
     | 
    
         
            +
                  end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                  def self.registry
         
     | 
| 
      
 50 
     | 
    
         
            +
                    @registry ||= {}
         
     | 
| 
      
 51 
     | 
    
         
            +
                  end
         
     | 
| 
      
 52 
     | 
    
         
            +
                end
         
     | 
| 
      
 53 
     | 
    
         
            +
              end
         
     | 
| 
      
 54 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,26 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require_relative '../function'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Dentaku
         
     | 
| 
      
 4 
     | 
    
         
            +
              module AST
         
     | 
| 
      
 5 
     | 
    
         
            +
                class If < Function
         
     | 
| 
      
 6 
     | 
    
         
            +
                  attr_reader :predicate, :left, :right
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  def initialize(predicate, left, right)
         
     | 
| 
      
 9 
     | 
    
         
            +
                    @predicate = predicate
         
     | 
| 
      
 10 
     | 
    
         
            +
                    @left      = left
         
     | 
| 
      
 11 
     | 
    
         
            +
                    @right     = right
         
     | 
| 
      
 12 
     | 
    
         
            +
                  end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                  def value(context={})
         
     | 
| 
      
 15 
     | 
    
         
            +
                    predicate.value(context) ? left.value(context) : right.value(context)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                  def dependencies(context={})
         
     | 
| 
      
 19 
     | 
    
         
            +
                    # TODO : short-circuit?
         
     | 
| 
      
 20 
     | 
    
         
            +
                    (predicate.dependencies(context) + left.dependencies(context) + right.dependencies(context)).uniq
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
              end
         
     | 
| 
      
 24 
     | 
    
         
            +
            end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
            Dentaku::AST::Function.register_class(:if, Dentaku::AST::If)
         
     |