Skip to content

Commit

Permalink
[evaluation] support for initial context
Browse files Browse the repository at this point in the history
  • Loading branch information
0exp committed Jun 21, 2019
1 parent ec7bef6 commit 5d4cc10
Show file tree
Hide file tree
Showing 8 changed files with 104 additions and 24 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# Changelog
All notable changes to this project will be documented in this file.

## [0.3.0] - 2019-06-21
- `Jaina.evaluate`: support for initial context (in programs and evaluation processes) (`Jaina.evaluate(program, **initial_context)`)

## [0.2.0] - 2019-06-21
### Added
- `#evaluate(context)` implementation for all core operators (`AND`, `OR`, `NOT`);
Expand Down
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ require 'jaina'
- [Parse your code (build AST)](#parse-your-code-build-ast)
- [Evaluate your code](#evaluate-your-code)
- [List registered operands and operators](#list-and-fetch-registered-operands-and-operators)
- [Full example](#example)

---

Expand Down Expand Up @@ -117,6 +118,9 @@ ast.evaluate

# --- or ---
Jaina.evaluate('A AND B AND (C OR D) OR A AND (C OR E)')

# --- you can set initial context of your program ---
Jaina.evaluate('A AND B', login: 'admin', logged_in: true)
```

---
Expand Down Expand Up @@ -144,6 +148,51 @@ Jaina.fetch_expression("KEK")

---

### Full example

```ruby
# step 1: register new operand
class AddNumber < Jaina::TerminalExpr
token 'ADD'

def evaluate(context)
context.set(:current_value, context.get(:current_value) + 10)
end
end

# step 2: register another new operand
class CheckNumber < Jaina::TerminalExpr
token 'CHECK'

def evaluate(context)
context.get(:current_value) < 0
end
end

class InitState < Jaina::TerminalExpr
token 'INIT'

def evaluate(context)
context.set(:current_value, 0)
end
end

# step 3: register new oeprands
Jaina.register_expression(AddNumber)
Jaina.register_expression(CheckNumber)
Jaina.register_expression(InitState)

# step 4: run your program
# NOTE: with initial context
Jaina.evaluate('CHECK AND ADD', current_value: -1) # => false
Jaina.evaluate('CHECK AND ADD', current_value: 2) # => 12
# NOTE: without initial context
Jaina.evaluate('INIT AND ADD') # => 10
Jaina.evaluate('INIT AND (CHECK OR ADD)') # => 12
```

---

## Contributing

- Fork it ( https://github.com/0exp/jaina/fork )
Expand Down
5 changes: 3 additions & 2 deletions lib/jaina.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,13 @@ def parse(program)
end

# @param program [String]
# @param initial_context [Hash<Symbol,Any>]
# @return [Any]
#
# @api public
# @since 0.1.0
def evaluate(program)
parse(program).evaluate
def evaluate(program, **initial_context)
parse(program).evaluate(**initial_context)
end

# @param expression_klass [Class{Jaina::Parser::Expression::Operator::Abstract}]
Expand Down
5 changes: 3 additions & 2 deletions lib/jaina/parser/ast.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,12 @@ def initialize(program, ast_tree)
@ast_tree = ast_tree
end

# @param initial_context [Hash<Symbol,Any>]
# @return [Any]
#
# @api private
# @since 0.1.0
def evaluate
Jaina::Parser::AST::Evaluator.evaluate(self)
def evaluate(**initial_context)
Jaina::Parser::AST::Evaluator.evaluate(self, **initial_context)
end
end
5 changes: 3 additions & 2 deletions lib/jaina/parser/ast/context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ class Jaina::Parser::AST::Context
# @since 0.1.0
UndefinedContextKeyError = Class.new(Error)

# @param initial_context [Hash<Symbol,Any>]
# @return [void]
#
# @api private
# @since 0.1.0
def initialize
@data = {}
def initialize(**initial_context)
@data = {}.merge!(initial_context)
@access_lock = Mutex.new
end

Expand Down
7 changes: 4 additions & 3 deletions lib/jaina/parser/ast/evaluator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@
# @since 0.1.0
module Jaina::Parser::AST::Evaluator
class << self
# @param initial_context [Hash<Symbol,Any>]
# @param ast [Jaina::Parser::AST]
# @return [Any]
#
# @api private
# @since 0.1.0
def evaluate(ast)
def evaluate(ast, **initial_context)
# NOTE: build shared context for a program
context = Jaina::Parser::AST::Context.new
context = Jaina::Parser::AST::Context.new(**initial_context)

# NOTE: evaluate the root expression of AST
# NOTE: root is an atity of type [Jaina::Parser::Expression::Operator::Abstract]
# NOTE: root is an entity of type [Jaina::Parser::Expression::Operator::Abstract]
ast.root.evaluate(context)
end
end
Expand Down
4 changes: 2 additions & 2 deletions lib/jaina/parser/expression/operator/or.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ def evaluate(context)
# @api private
# @since 0.2.0
def left_expression
expressions.first
expressions[0]
end

# @return [Jaina::Parser::Expression::Operator::Abstract]
#
# @api private
# @since 0.2.0
def right_expression
expressions.second
expressions[1]
end
end
end
50 changes: 37 additions & 13 deletions spec/smoke_test_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,28 +53,52 @@
Jaina::Parser::Expression::Registry::UnregisteredExpressionError
)

# NOTE: new operand: returns true and sets 1 to the global AST context
g = Class.new(Jaina::TerminalExpr) do
token 'G'
# --- Full example ---

# step 1: create first operand
add_number = Class.new(Jaina::TerminalExpr) do
token 'ADD'

def evaluate(context)
context.set(:current_value, context.get(:current_value) + 10)
end
end

# step 2: create second operand
check_number = Class.new(Jaina::TerminalExpr) do
token 'CHECK'

def evaluate(context)
context.set(:g_expression, 1)
context.get(:current_value) < 0
end
end
Jaina.register_expression(g)

# NOTE: new operand: sets (g_expression + 10)
j = Class.new(Jaina::TerminalExpr) do
token 'J'
# step 3: create third operand
init_state = Class.new(Jaina::TerminalExpr) do
token 'INIT'

def evaluate(context)
context.set(:g_expression, (context.get(:g_expression) + 10))
context.set(:current_value, 0)
end
end
Jaina.register_expression(j)

# NOTE: evaluation
expect(Jaina.evaluate('G AND J')).to eq(11)
expect(Jaina.evaluate('G AND (J AND J) OR E')).to eq(21)
# step 4: register new oeprands
Jaina.register_expression(add_number)
Jaina.register_expression(check_number)
Jaina.register_expression(init_state)

# step 5: run your program

# NOTE: with initial context
Jaina.evaluate('CHECK AND ADD', current_value: -1) # => false
Jaina.evaluate('CHECK AND ADD', current_value: 2) # => 12
# NOTE: without initial context
Jaina.evaluate('INIT AND ADD') # => 10
Jaina.evaluate('INIT AND (CHECK OR ADD)') # => 12

# NOTE: fail on incorrect context usage (when the required context key does not exist)
expect { Jaina.evaluate('CHECK') }.to raise_error(
Jaina::Parser::AST::Context::UndefinedContextKeyError
)
end
end

0 comments on commit 5d4cc10

Please sign in to comment.