Class: Dentaku::Evaluator

Inherits:
Object
  • Object
show all
Defined in:
lib/dentaku/evaluator.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(rule_set) ⇒ Evaluator

Returns a new instance of Evaluator.



8
9
10
# File 'lib/dentaku/evaluator.rb', line 8

def initialize(rule_set)
  @rule_set = rule_set
end

Instance Attribute Details

#rule_setObject (readonly)

Returns the value of attribute rule_set.



6
7
8
# File 'lib/dentaku/evaluator.rb', line 6

def rule_set
  @rule_set
end

Instance Method Details

#apply(lvalue, operator, rvalue) ⇒ Object



98
99
100
101
102
# File 'lib/dentaku/evaluator.rb', line 98

def apply(lvalue, operator, rvalue)
  operation = BinaryOperation.new(lvalue.value, rvalue.value)
  raise "unknown operation #{ operator.value }" unless operation.respond_to?(operator.value)
  Token.new(*operation.send(operator.value))
end

#evaluate(tokens) ⇒ Object



12
13
14
# File 'lib/dentaku/evaluator.rb', line 12

def evaluate(tokens)
  evaluate_token_stream(tokens).value
end

#evaluate_group(*args) ⇒ Object



94
95
96
# File 'lib/dentaku/evaluator.rb', line 94

def evaluate_group(*args)
  evaluate_token_stream(args[1..-2])
end

#evaluate_step(token_stream, start, length, evaluator) ⇒ Object



69
70
71
72
73
74
75
76
77
78
# File 'lib/dentaku/evaluator.rb', line 69

def evaluate_step(token_stream, start, length, evaluator)
  substream = token_stream.slice!(start, length)

  if self.respond_to?(evaluator)
    token_stream.insert start, *self.send(evaluator, *substream)
  else
    result = user_defined_function(evaluator, substream)
    token_stream.insert start, result
  end
end

#evaluate_token_stream(tokens) ⇒ Object



16
17
18
19
20
21
22
23
24
25
# File 'lib/dentaku/evaluator.rb', line 16

def evaluate_token_stream(tokens)
  while tokens.length > 1
    matched, tokens = match_rule_pattern(tokens)
    raise "no rule matched {{#{ inspect_tokens(tokens) }}}" unless matched
  end

  tokens << Token.new(:numeric, 0) if tokens.empty?

  tokens.first
end

#expand_range(left, oper1, middle, oper2, right) ⇒ Object



120
121
122
# File 'lib/dentaku/evaluator.rb', line 120

def expand_range(left, oper1, middle, oper2, right)
  [left, oper1, middle, Token.new(:combinator, :and), middle, oper2, right]
end

#extract_arguments_from_function_call(tokens) ⇒ Object



89
90
91
92
# File 'lib/dentaku/evaluator.rb', line 89

def extract_arguments_from_function_call(tokens)
  _function_name, _open, *args_and_commas, _close = tokens
  args_and_commas.reject { |token| token.is?(:grouping) }
end

#find_rule_match(pattern, token_stream) ⇒ Object



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/dentaku/evaluator.rb', line 47

def find_rule_match(pattern, token_stream)
  position = 0

  while position <= token_stream.length
    matches = []
    matched = true

    pattern.each do |matcher|
      _matched, match = matcher.match(token_stream, position + matches.length)
      matched &&= _matched
      break unless matched
      matches += match
    end

    return position, matches if matched
    return if pattern.first.caret?
    position += 1
  end

  nil
end

#if(*args) ⇒ Object



124
125
126
127
128
129
130
131
132
# File 'lib/dentaku/evaluator.rb', line 124

def if(*args)
  _if, _open, condition, _, true_value, _, false_value, _close = args

  if condition.value
    true_value
  else
    false_value
  end
end

#inspect_tokens(tokens) ⇒ Object



27
28
29
# File 'lib/dentaku/evaluator.rb', line 27

def inspect_tokens(tokens)
  tokens.map { |t| t.to_s }.join(' ')
end

#match_rule_pattern(tokens) ⇒ Object



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/dentaku/evaluator.rb', line 31

def match_rule_pattern(tokens)
  matched = false

  rule_set.filter(tokens).each do |pattern, evaluator|
    pos, match = find_rule_match(pattern, tokens)

    if pos
      tokens = evaluate_step(tokens, pos, match.length, evaluator)
      matched = true
      break
    end
  end

  [matched, tokens]
end

#mul_negate(val1, _, _, val2) ⇒ Object



112
113
114
# File 'lib/dentaku/evaluator.rb', line 112

def mul_negate(val1, _, _, val2)
  Token.new(val1.category, val1.value * val2.value * -1)
end

#negate(_, token) ⇒ Object



104
105
106
# File 'lib/dentaku/evaluator.rb', line 104

def negate(_, token)
  Token.new(token.category, token.value * -1)
end

#not(*args) ⇒ Object



162
163
164
# File 'lib/dentaku/evaluator.rb', line 162

def not(*args)
  Token.new(:logical, ! evaluate_token_stream(args[2..-2]).value)
end

#percentage(token, _) ⇒ Object



116
117
118
# File 'lib/dentaku/evaluator.rb', line 116

def percentage(token, _)
  Token.new(token.category, token.value / 100.0)
end

#pow_negate(base, _, _, exp) ⇒ Object



108
109
110
# File 'lib/dentaku/evaluator.rb', line 108

def pow_negate(base, _, _, exp)
  Token.new(base.category, base.value ** (exp.value * -1))
end

#round(*args) ⇒ Object



134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/dentaku/evaluator.rb', line 134

def round(*args)
  _, _, *tokens, _ = args

  input_tokens, places_tokens = tokens.chunk { |t| t.category == :grouping }.
                                      reject { |flag, tokens| flag }.
                                         map { |flag, tokens| tokens }

  input_value  = evaluate_token_stream(input_tokens).value
  places       = places_tokens ? evaluate_token_stream(places_tokens).value : 0

  value = input_value.round(places)

  Token.new(:numeric, value)
end

#round_int(*args) ⇒ Object



149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/dentaku/evaluator.rb', line 149

def round_int(*args)
  function, _, *tokens, _ = args

  value = evaluate_token_stream(tokens).value
  rounded = if function.value == :roundup
    value.ceil
  else
    value.floor
  end

  Token.new(:numeric, rounded)
end

#user_defined_function(evaluator, tokens) ⇒ Object



80
81
82
83
84
85
86
87
# File 'lib/dentaku/evaluator.rb', line 80

def user_defined_function(evaluator, tokens)
  function = rule_set.function(evaluator)
  raise "unknown function '#{ evaluator }'" unless function

  arguments = extract_arguments_from_function_call(tokens).map { |t| t.value }
  return_value = function.body.call(*arguments)
  Token.new(function.type, return_value)
end