keisan 0.2.1 → 0.3.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 +92 -0
- data/lib/keisan.rb +12 -0
- data/lib/keisan/ast/bitwise_and.rb +1 -5
- data/lib/keisan/ast/bitwise_or.rb +1 -5
- data/lib/keisan/ast/bitwise_xor.rb +1 -5
- data/lib/keisan/ast/builder.rb +57 -22
- data/lib/keisan/ast/exponent.rb +1 -5
- data/lib/keisan/ast/function.rb +50 -1
- data/lib/keisan/ast/logical_and.rb +1 -5
- data/lib/keisan/ast/logical_equal.rb +18 -0
- data/lib/keisan/ast/logical_greater_than.rb +4 -4
- data/lib/keisan/ast/logical_greater_than_or_equal_to.rb +4 -4
- data/lib/keisan/ast/logical_less_than.rb +4 -4
- data/lib/keisan/ast/logical_less_than_or_equal_to.rb +4 -4
- data/lib/keisan/ast/logical_not_equal.rb +18 -0
- data/lib/keisan/ast/logical_or.rb +1 -5
- data/lib/keisan/ast/modulo.rb +18 -0
- data/lib/keisan/ast/node.rb +26 -0
- data/lib/keisan/ast/operator.rb +9 -2
- data/lib/keisan/ast/plus.rb +1 -5
- data/lib/keisan/ast/priorities.rb +27 -0
- data/lib/keisan/ast/times.rb +1 -5
- data/lib/keisan/ast/variable.rb +5 -0
- data/lib/keisan/calculator.rb +1 -12
- data/lib/keisan/context.rb +20 -5
- data/lib/keisan/evaluator.rb +79 -0
- data/lib/keisan/exceptions.rb +1 -0
- data/lib/keisan/functions/default_registry.rb +0 -5
- data/lib/keisan/functions/registry.rb +7 -0
- data/lib/keisan/parser.rb +42 -29
- data/lib/keisan/parsing/dot.rb +6 -0
- data/lib/keisan/parsing/dot_operator.rb +12 -0
- data/lib/keisan/parsing/dot_word.rb +14 -0
- data/lib/keisan/parsing/logical_equal.rb +9 -0
- data/lib/keisan/parsing/logical_not_equal.rb +9 -0
- data/lib/keisan/parsing/modulo.rb +9 -0
- data/lib/keisan/tokenizer.rb +2 -1
- data/lib/keisan/tokens/arithmetic_operator.rb +4 -1
- data/lib/keisan/tokens/dot.rb +11 -0
- data/lib/keisan/tokens/logical_operator.rb +7 -1
- data/lib/keisan/variables/registry.rb +7 -0
- data/lib/keisan/version.rb +1 -1
- metadata +14 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: ca29cf77de26f26d32436c705ad47f229e215c43
         | 
| 4 | 
            +
              data.tar.gz: cedc1ffcca1d1a46fbca4cee21750f2398c84ee5
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: fae0c6a0db729b392ea89440dc0b4dff033a9f0e7159f725ba7bcaa0b3e16861c1aebfec2d2cdcfc5cde719aee995caabfb7fafa865797732a8db6c4b184ced7
         | 
| 7 | 
            +
              data.tar.gz: 02257ba4395e73e2950ff09eefa272cdd49e1299f4bcec5a558c73e3a87147a3e09b4cde59bc4fa1ab935d801d88e9282dc4cea97e53b497cb94155154c23fd8
         | 
    
        data/README.md
    CHANGED
    
    | @@ -51,6 +51,14 @@ calculator.evaluate("x + 1") | |
| 51 51 | 
             
            #=> Keisan::Exceptions::UndefinedVariableError: x
         | 
| 52 52 | 
             
            ```
         | 
| 53 53 |  | 
| 54 | 
            +
            It is also possible to define variables in the string expression itself
         | 
| 55 | 
            +
             | 
| 56 | 
            +
            ```ruby
         | 
| 57 | 
            +
            calculator.evaluate("x = 10*n", n: 2)
         | 
| 58 | 
            +
            calculator.evaluate("3*x + 1")
         | 
| 59 | 
            +
            #=> 61
         | 
| 60 | 
            +
            ```
         | 
| 61 | 
            +
             | 
| 54 62 | 
             
            ##### Specifying functions
         | 
| 55 63 |  | 
| 56 64 | 
             
            Just like variables, functions can be defined by passing a `Proc` object as follows
         | 
| @@ -67,6 +75,57 @@ calculator.evaluate("f(2) + 1") | |
| 67 75 | 
             
            #=> Keisan::Exceptions::UndefinedFunctionError: f
         | 
| 68 76 | 
             
            ```
         | 
| 69 77 |  | 
| 78 | 
            +
            Note that functions work in both regular (`f(x)`) and postfix (`x.f()`) notation.  The postfix notation requires the function to take at least one argument.  In the case of `a.f(b,c)`, this is translated internally to `f(a,b,c)`.  If there is only a single argument to the function, the braces can be left off: `x.f`.
         | 
| 79 | 
            +
             | 
| 80 | 
            +
            ```ruby
         | 
| 81 | 
            +
            calculator.evaluate("[1,3,5,7].size()")
         | 
| 82 | 
            +
            #=> 4
         | 
| 83 | 
            +
            calculator.evaluate("[1,3,5,7].size")
         | 
| 84 | 
            +
            #=> 4
         | 
| 85 | 
            +
            ```
         | 
| 86 | 
            +
             | 
| 87 | 
            +
            It is even possible to do more complicated things like follows
         | 
| 88 | 
            +
             | 
| 89 | 
            +
            ```ruby
         | 
| 90 | 
            +
            calculator.define_function!("f", Proc.new {|x| [[x-1,x+1], [x-2,x,x+2]]})
         | 
| 91 | 
            +
            calculator.evaluate("4.f")
         | 
| 92 | 
            +
            #=> [[3,5], [2,4,6]]
         | 
| 93 | 
            +
            calculator.evaluate("4.f[0]")
         | 
| 94 | 
            +
            #=> [3,5]
         | 
| 95 | 
            +
            calculator.evaluate("4.f[0].size")
         | 
| 96 | 
            +
            #=> 2
         | 
| 97 | 
            +
            calculator.evaluate("4.f[1]")
         | 
| 98 | 
            +
            #=> [2,4,6]
         | 
| 99 | 
            +
            calculator.evaluate("4.f[1].size")
         | 
| 100 | 
            +
            #=> 3
         | 
| 101 | 
            +
            ```
         | 
| 102 | 
            +
             | 
| 103 | 
            +
            Like variables, it is also possible to define functions in the string expression itself.
         | 
| 104 | 
            +
             | 
| 105 | 
            +
            ```ruby
         | 
| 106 | 
            +
            calculator.evaluate("f(x) = n*x", n: 10) # n is local to this definition only
         | 
| 107 | 
            +
            calculator.evaluate("f(3)")
         | 
| 108 | 
            +
            #=> 30
         | 
| 109 | 
            +
            calculator.evaluate("f(0-a)", a: 2)
         | 
| 110 | 
            +
            #=> -20
         | 
| 111 | 
            +
            calculator.evaluate("n") # n only exists in the definition of f(x)
         | 
| 112 | 
            +
            #=> Keisan::Exceptions::UndefinedVariableError: n
         | 
| 113 | 
            +
            ```
         | 
| 114 | 
            +
             | 
| 115 | 
            +
            This form even supports recursion!
         | 
| 116 | 
            +
             | 
| 117 | 
            +
            ```ruby
         | 
| 118 | 
            +
            calculator.evaluate("my_fact(n) = if (n > 1, n*my_fact(n-1), 1)")
         | 
| 119 | 
            +
            calculator.evaluate("my_fact(0)")
         | 
| 120 | 
            +
            #=> 1
         | 
| 121 | 
            +
            calculator.evaluate("my_fact(1)")
         | 
| 122 | 
            +
            #=> 1
         | 
| 123 | 
            +
            calculator.evaluate("my_fact(2)")
         | 
| 124 | 
            +
            #=> 2
         | 
| 125 | 
            +
            calculator.evaluate("my_fact(5)")
         | 
| 126 | 
            +
            #=> 120
         | 
| 127 | 
            +
            ```
         | 
| 128 | 
            +
             | 
| 70 129 | 
             
            ##### Lists
         | 
| 71 130 |  | 
| 72 131 | 
             
            Just like in Ruby, lists can be defined using square brackets, and indexed using square brackets
         | 
| @@ -213,6 +272,39 @@ calculator.evaluate("f(2)") | |
| 213 272 | 
             
            #=> 6
         | 
| 214 273 | 
             
            ```
         | 
| 215 274 |  | 
| 275 | 
            +
            ## Supported elements/operators
         | 
| 276 | 
            +
             | 
| 277 | 
            +
            `keisan` supports the following operators and elements.
         | 
| 278 | 
            +
             | 
| 279 | 
            +
            #### Numbers, variables, functions, lists
         | 
| 280 | 
            +
            - `150`, `-5.67`, `6e-5`: regular numbers
         | 
| 281 | 
            +
            - `x`, `_myvar1`: variables
         | 
| 282 | 
            +
            - `(` and `)`: round brackets for grouping parts to evaluate first
         | 
| 283 | 
            +
            - `[0, 3, 6, 9]`: square brackets with comma separated values to denote lists
         | 
| 284 | 
            +
            - `f(x,y,z)`, `my_function(max([2.5, 5.5]))`, `[2,4,6,8].size`: functions using `(` `)` brackets (optional if using postfix notation and only takes a single argument)
         | 
| 285 | 
            +
             | 
| 286 | 
            +
            #### Arithmetic operators
         | 
| 287 | 
            +
            - `+`, `-`, `*`, `/`: regular arithmetic operators
         | 
| 288 | 
            +
            - `**`: Ruby style exponent notation (to avoid conflict with bitwise xor `^`)
         | 
| 289 | 
            +
            - `%`: Ruby modulo operator, sign of `a % b` is same as sign of `b`
         | 
| 290 | 
            +
            - `+`, `-`: Unary plus and minus
         | 
| 291 | 
            +
             | 
| 292 | 
            +
            #### Logical operators
         | 
| 293 | 
            +
            - `<`, `>`, `<=`, `>=`: comparison operators
         | 
| 294 | 
            +
            - `==` and `!=`: logical equality check operators
         | 
| 295 | 
            +
            - `&&` and `||`: logical operators, **and** and **or**
         | 
| 296 | 
            +
            - `!`: unary logical not
         | 
| 297 | 
            +
             | 
| 298 | 
            +
            #### Bitwise operators
         | 
| 299 | 
            +
            - `&`, `|`, `^`: bitwise **and**, **or**, **xor** operators
         | 
| 300 | 
            +
            - `~`: unary bitwise not
         | 
| 301 | 
            +
             | 
| 302 | 
            +
            #### Indexing of arrays
         | 
| 303 | 
            +
            - `list[i]`: for accessing elements in an array
         | 
| 304 | 
            +
             | 
| 305 | 
            +
            #### Assignment
         | 
| 306 | 
            +
            - `=`: can be used to define variables and functions
         | 
| 307 | 
            +
             | 
| 216 308 | 
             
            ## Development
         | 
| 217 309 |  | 
| 218 310 | 
             
            After checking out the repository, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.  You can also run `bin/console` for an interactive prompt that will allow you to experiment.
         | 
    
        data/lib/keisan.rb
    CHANGED
    
    | @@ -11,6 +11,7 @@ require "keisan/variables/registry" | |
| 11 11 | 
             
            require "keisan/variables/default_registry"
         | 
| 12 12 | 
             
            require "keisan/context"
         | 
| 13 13 |  | 
| 14 | 
            +
            require "keisan/ast/priorities"
         | 
| 14 15 | 
             
            require "keisan/ast/node"
         | 
| 15 16 |  | 
| 16 17 | 
             
            require "keisan/ast/literal"
         | 
| @@ -33,6 +34,7 @@ require "keisan/ast/arithmetic_operator" | |
| 33 34 | 
             
            require "keisan/ast/plus"
         | 
| 34 35 | 
             
            require "keisan/ast/times"
         | 
| 35 36 | 
             
            require "keisan/ast/exponent"
         | 
| 37 | 
            +
            require "keisan/ast/modulo"
         | 
| 36 38 | 
             
            require "keisan/ast/function"
         | 
| 37 39 | 
             
            require "keisan/ast/bitwise_operator"
         | 
| 38 40 | 
             
            require "keisan/ast/bitwise_and"
         | 
| @@ -41,6 +43,8 @@ require "keisan/ast/bitwise_xor" | |
| 41 43 | 
             
            require "keisan/ast/logical_operator"
         | 
| 42 44 | 
             
            require "keisan/ast/logical_and"
         | 
| 43 45 | 
             
            require "keisan/ast/logical_or"
         | 
| 46 | 
            +
            require "keisan/ast/logical_equal"
         | 
| 47 | 
            +
            require "keisan/ast/logical_not_equal"
         | 
| 44 48 | 
             
            require "keisan/ast/logical_less_than"
         | 
| 45 49 | 
             
            require "keisan/ast/logical_greater_than"
         | 
| 46 50 | 
             
            require "keisan/ast/logical_less_than_or_equal_to"
         | 
| @@ -53,6 +57,7 @@ require "keisan/ast/builder" | |
| 53 57 |  | 
| 54 58 | 
             
            require "keisan/token"
         | 
| 55 59 | 
             
            require "keisan/tokens/comma"
         | 
| 60 | 
            +
            require "keisan/tokens/dot"
         | 
| 56 61 | 
             
            require "keisan/tokens/group"
         | 
| 57 62 | 
             
            require "keisan/tokens/number"
         | 
| 58 63 | 
             
            require "keisan/tokens/operator"
         | 
| @@ -73,6 +78,9 @@ require "keisan/parsing/number" | |
| 73 78 | 
             
            require "keisan/parsing/string"
         | 
| 74 79 | 
             
            require "keisan/parsing/null"
         | 
| 75 80 | 
             
            require "keisan/parsing/boolean"
         | 
| 81 | 
            +
            require "keisan/parsing/dot"
         | 
| 82 | 
            +
            require "keisan/parsing/dot_word"
         | 
| 83 | 
            +
            require "keisan/parsing/dot_operator"
         | 
| 76 84 | 
             
            require "keisan/parsing/variable"
         | 
| 77 85 | 
             
            require "keisan/parsing/function"
         | 
| 78 86 | 
             
            require "keisan/parsing/group"
         | 
| @@ -93,6 +101,7 @@ require "keisan/parsing/minus" | |
| 93 101 | 
             
            require "keisan/parsing/times"
         | 
| 94 102 | 
             
            require "keisan/parsing/divide"
         | 
| 95 103 | 
             
            require "keisan/parsing/exponent"
         | 
| 104 | 
            +
            require "keisan/parsing/modulo"
         | 
| 96 105 | 
             
            require "keisan/parsing/bitwise_operator"
         | 
| 97 106 | 
             
            require "keisan/parsing/bitwise_and"
         | 
| 98 107 | 
             
            require "keisan/parsing/bitwise_or"
         | 
| @@ -106,12 +115,15 @@ require "keisan/parsing/logical_less_than_or_equal_to" | |
| 106 115 | 
             
            require "keisan/parsing/logical_greater_than_or_equal_to"
         | 
| 107 116 | 
             
            require "keisan/parsing/logical_and"
         | 
| 108 117 | 
             
            require "keisan/parsing/logical_or"
         | 
| 118 | 
            +
            require "keisan/parsing/logical_equal"
         | 
| 119 | 
            +
            require "keisan/parsing/logical_not_equal"
         | 
| 109 120 | 
             
            require "keisan/parsing/logical_not"
         | 
| 110 121 | 
             
            require "keisan/parsing/logical_not_not"
         | 
| 111 122 |  | 
| 112 123 | 
             
            require "keisan/parser"
         | 
| 113 124 |  | 
| 114 125 | 
             
            require "keisan/calculator"
         | 
| 126 | 
            +
            require "keisan/evaluator"
         | 
| 115 127 |  | 
| 116 128 | 
             
            module Keisan
         | 
| 117 129 | 
             
            end
         | 
    
        data/lib/keisan/ast/builder.rb
    CHANGED
    
    | @@ -44,16 +44,11 @@ module Keisan | |
| 44 44 | 
             
                  end
         | 
| 45 45 |  | 
| 46 46 | 
             
                  def node_from_components(components)
         | 
| 47 | 
            -
                    unary_components, node,  | 
| 47 | 
            +
                    unary_components, node, postfix_components = *unarys_node_postfixes(components)
         | 
| 48 48 |  | 
| 49 | 
            -
                    # Apply postfix  | 
| 50 | 
            -
                     | 
| 51 | 
            -
                      node =  | 
| 52 | 
            -
                        node,
         | 
| 53 | 
            -
                        indexing_component.arguments.map {|parsing_argument|
         | 
| 54 | 
            -
                          Builder.new(components: parsing_argument.components).node
         | 
| 55 | 
            -
                        }
         | 
| 56 | 
            -
                      )
         | 
| 49 | 
            +
                    # Apply postfix operators
         | 
| 50 | 
            +
                    postfix_components.each do |postfix_component|
         | 
| 51 | 
            +
                      node = apply_postfix_component_to_node(postfix_component, node)
         | 
| 57 52 | 
             
                    end
         | 
| 58 53 |  | 
| 59 54 | 
             
                    # Apply prefix unary operators
         | 
| @@ -64,34 +59,62 @@ module Keisan | |
| 64 59 | 
             
                    node
         | 
| 65 60 | 
             
                  end
         | 
| 66 61 |  | 
| 62 | 
            +
                  def apply_postfix_component_to_node(postfix_component, node)
         | 
| 63 | 
            +
                    case postfix_component
         | 
| 64 | 
            +
                    when Keisan::Parsing::Indexing
         | 
| 65 | 
            +
                      postfix_component.node_class.new(
         | 
| 66 | 
            +
                        node,
         | 
| 67 | 
            +
                        postfix_component.arguments.map {|parsing_argument|
         | 
| 68 | 
            +
                          Builder.new(components: parsing_argument.components).node
         | 
| 69 | 
            +
                        }
         | 
| 70 | 
            +
                      )
         | 
| 71 | 
            +
                    when Keisan::Parsing::DotWord
         | 
| 72 | 
            +
                      Keisan::AST::Function.build(
         | 
| 73 | 
            +
                        postfix_component.name,
         | 
| 74 | 
            +
                        [node]
         | 
| 75 | 
            +
                      )
         | 
| 76 | 
            +
                    when Keisan::Parsing::DotOperator
         | 
| 77 | 
            +
                      Keisan::AST::Function.build(
         | 
| 78 | 
            +
                        postfix_component.name,
         | 
| 79 | 
            +
                        [node] + postfix_component.arguments.map {|parsing_argument|
         | 
| 80 | 
            +
                          Builder.new(components: parsing_argument.components).node
         | 
| 81 | 
            +
                        }
         | 
| 82 | 
            +
                      )
         | 
| 83 | 
            +
                    else
         | 
| 84 | 
            +
                      raise Keisan::Exceptions::ASTError.new("Invalid postfix component #{postfix_component}")
         | 
| 85 | 
            +
                    end
         | 
| 86 | 
            +
                  end
         | 
| 87 | 
            +
             | 
| 67 88 | 
             
                  # Returns an array of the form
         | 
| 68 | 
            -
                  # [unary_operators, middle_node,  | 
| 89 | 
            +
                  # [unary_operators, middle_node, postfix_operators]
         | 
| 69 90 | 
             
                  # unary_operators is an array of Keisan::Parsing::UnaryOperator objects
         | 
| 70 91 | 
             
                  # middle_node is the main node which will be modified by prefix and postfix operators
         | 
| 71 | 
            -
                  #  | 
| 72 | 
            -
                  def  | 
| 92 | 
            +
                  # postfix_operators is an array of Keisan::Parsing::Indexing, DotWord, and DotOperator objects
         | 
| 93 | 
            +
                  def unarys_node_postfixes(components)
         | 
| 73 94 | 
             
                    index_of_unary_components = components.map.with_index {|c,i| [c,i]}.select {|c,i| c.is_a?(Keisan::Parsing::UnaryOperator)}.map(&:last)
         | 
| 74 95 | 
             
                    # Must be all in the front
         | 
| 75 96 | 
             
                    unless index_of_unary_components.map.with_index.all? {|i,j| i == j}
         | 
| 76 97 | 
             
                      raise Keisan::Exceptions::ASTError.new("unary operators must be in front")
         | 
| 77 98 | 
             
                    end
         | 
| 78 99 |  | 
| 79 | 
            -
                     | 
| 80 | 
            -
             | 
| 81 | 
            -
             | 
| 100 | 
            +
                    index_of_postfix_components = components.map.with_index {|c,i| [c,i]}.select {|c,i|
         | 
| 101 | 
            +
                      c.is_a?(Keisan::Parsing::Indexing) || c.is_a?(Keisan::Parsing::DotWord) || c.is_a?(Keisan::Parsing::DotOperator)
         | 
| 102 | 
            +
                    }.map(&:last)
         | 
| 103 | 
            +
                    unless index_of_postfix_components.reverse.map.with_index.all? {|i,j| i + j == components.size - 1 }
         | 
| 104 | 
            +
                      raise Keisan::Exceptions::ASTError.new("postfix components must be in back")
         | 
| 82 105 | 
             
                    end
         | 
| 83 106 |  | 
| 84 | 
            -
                    num_unary | 
| 85 | 
            -
                     | 
| 107 | 
            +
                    num_unary   = index_of_unary_components.size
         | 
| 108 | 
            +
                    num_postfix = index_of_postfix_components.size
         | 
| 86 109 |  | 
| 87 | 
            -
                    unless num_unary + 1 +  | 
| 110 | 
            +
                    unless num_unary + 1 + num_postfix == components.size
         | 
| 88 111 | 
             
                      raise Keisan::Exceptions::ASTError.new("have too many components")
         | 
| 89 112 | 
             
                    end
         | 
| 90 113 |  | 
| 91 114 | 
             
                    [
         | 
| 92 115 | 
             
                      index_of_unary_components.map {|i| components[i]},
         | 
| 93 116 | 
             
                      node_of_component(components[index_of_unary_components.size]),
         | 
| 94 | 
            -
                       | 
| 117 | 
            +
                      index_of_postfix_components.map {|i| components[i]}
         | 
| 95 118 | 
             
                    ]
         | 
| 96 119 | 
             
                  end
         | 
| 97 120 |  | 
| @@ -116,11 +139,23 @@ module Keisan | |
| 116 139 | 
             
                    when Keisan::Parsing::Group
         | 
| 117 140 | 
             
                      Builder.new(components: component.components).node
         | 
| 118 141 | 
             
                    when Keisan::Parsing::Function
         | 
| 119 | 
            -
                      Keisan::AST::Function. | 
| 142 | 
            +
                      Keisan::AST::Function.build(
         | 
| 143 | 
            +
                        component.name,
         | 
| 120 144 | 
             
                        component.arguments.map {|parsing_argument|
         | 
| 121 145 | 
             
                          Builder.new(components: parsing_argument.components).node
         | 
| 122 | 
            -
                        } | 
| 123 | 
            -
             | 
| 146 | 
            +
                        }
         | 
| 147 | 
            +
                      )
         | 
| 148 | 
            +
                    when Keisan::Parsing::DotWord
         | 
| 149 | 
            +
                      Keisan::AST::Function.build(
         | 
| 150 | 
            +
                        component.name,
         | 
| 151 | 
            +
                        [node_of_component(component.target)]
         | 
| 152 | 
            +
                      )
         | 
| 153 | 
            +
                    when Keisan::Parsing::DotOperator
         | 
| 154 | 
            +
                      Keisan::AST::Function.build(
         | 
| 155 | 
            +
                        component.name,
         | 
| 156 | 
            +
                        [node_of_component(component.target)] + component.arguments.map {|parsing_argument|
         | 
| 157 | 
            +
                          Builder.new(components: parsing_argument.components).node
         | 
| 158 | 
            +
                        }
         | 
| 124 159 | 
             
                      )
         | 
| 125 160 | 
             
                    else
         | 
| 126 161 | 
             
                      raise Keisan::Exceptions::ASTError.new("Unhandled component, #{component}")
         | 
    
        data/lib/keisan/ast/exponent.rb
    CHANGED
    
    
    
        data/lib/keisan/ast/function.rb
    CHANGED
    
    | @@ -11,9 +11,58 @@ module Keisan | |
| 11 11 | 
             
                  def value(context = nil)
         | 
| 12 12 | 
             
                    context = Keisan::Context.new if context.nil?
         | 
| 13 13 | 
             
                    argument_values = children.map {|child| child.value(context)}
         | 
| 14 | 
            -
                    function = context | 
| 14 | 
            +
                    function = function_from_context(context)
         | 
| 15 15 | 
             
                    function.call(context, *argument_values)
         | 
| 16 16 | 
             
                  end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  def unbound_functions(context = nil)
         | 
| 19 | 
            +
                    context ||= Keisan::Context.new
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                    functions = children.inject(Set.new) do |res, child|
         | 
| 22 | 
            +
                      res | child.unbound_functions(context)
         | 
| 23 | 
            +
                    end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                    context.has_function?(name) ? functions : functions | Set.new([name])
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  def function_from_context(context)
         | 
| 29 | 
            +
                    @override || context.function(name)
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                class If < Function
         | 
| 34 | 
            +
                  def value(context = nil)
         | 
| 35 | 
            +
                    unless (2..3).cover? children.size
         | 
| 36 | 
            +
                      raise Keisan::Exceptions::InvalidFunctionError.new("Require 2 or 3 arguments to if")
         | 
| 37 | 
            +
                    end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                    bool = children[0].value(context)
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                    if bool
         | 
| 42 | 
            +
                      children[1].value(context)
         | 
| 43 | 
            +
                    else
         | 
| 44 | 
            +
                      children.size == 3 ? children[2].value(context) : nil
         | 
| 45 | 
            +
                    end
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  def unbound_functions(context = nil)
         | 
| 49 | 
            +
                    context ||= Keisan::Context.new
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                    children.inject(Set.new) do |res, child|
         | 
| 52 | 
            +
                      res | child.unbound_functions(context)
         | 
| 53 | 
            +
                    end
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                class Function
         | 
| 58 | 
            +
                  def self.build(name, arguments = [])
         | 
| 59 | 
            +
                    case name.downcase
         | 
| 60 | 
            +
                    when "if"
         | 
| 61 | 
            +
                      If.new(arguments, name)
         | 
| 62 | 
            +
                    else
         | 
| 63 | 
            +
                      Function.new(arguments, name)
         | 
| 64 | 
            +
                    end
         | 
| 65 | 
            +
                  end
         | 
| 17 66 | 
             
                end
         | 
| 18 67 | 
             
              end
         | 
| 19 68 | 
             
            end
         |