class Dentaku::Evaluator
def apply(lvalue, operator, rvalue)
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
def evaluate(tokens)
def evaluate(tokens) evaluate_token_stream(tokens).value end
def evaluate_group(*args)
def evaluate_group(*args) evaluate_token_stream(args[1..-2]) end
def evaluate_step(token_stream, start, length, evaluator)
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
def evaluate_token_stream(tokens)
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
def expand_range(left, oper1, middle, oper2, right)
def expand_range(left, oper1, middle, oper2, right) [left, oper1, middle, Token.new(:combinator, :and), middle, oper2, right] end
def extract_arguments_from_function_call(tokens)
def extract_arguments_from_function_call(tokens) _function_name, _open, *args_and_commas, _close = tokens args_and_commas.reject { |token| token.is?(:grouping) } end
def find_rule_match(pattern, token_stream)
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
def if(*args)
def if(*args) _if, _open, condition, _, true_value, _, false_value, _close = args if condition.value true_value else false_value end end
def inspect_tokens(tokens)
def inspect_tokens(tokens) tokens.map { |t| t.to_s }.join(' ') end
def match_rule_pattern(tokens)
def match_rule_pattern(tokens) matched = false Rules.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
def negate(_, token)
def negate(_, token) Token.new(token.category, token.value * -1) end
def not(*args)
def not(*args) Token.new(:logical, ! evaluate_token_stream(args[2..-2]).value) end
def percentage(token, _)
def percentage(token, _) Token.new(token.category, token.value / 100.0) end
def round(*args)
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
def round_int(*args)
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
def user_defined_function(evaluator, tokens)
def user_defined_function(evaluator, tokens) function = Rules.func(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