lib/dnf/boolean_expression.rb in dnf-0.1.0 vs lib/dnf/boolean_expression.rb in dnf-0.2.0
- old
+ new
@@ -1,56 +1,117 @@
module Dnf
class BooleanExpression
- attr_reader :expression
+ attr_reader :expression, :config
- def initialize(expression)
+ DEFAULT_CONFIG = {
+ variable_regex: /\w+/,
+ not_symbol: '!',
+ and_symbol: '&',
+ or_symbol: '|'
+ }
+
+ def initialize(expression, custom_config = {})
@expression = expression
+ @config = DEFAULT_CONFIG.merge(custom_config)
end
def to_dnf
expr = parse(expression)
dnf = convert_to_dnf(expr)
dnf_to_string(dnf)
end
private
+ def validate_expression(tokens)
+ raise ExpressionSyntaxError, "Expression cannot be empty" if tokens.empty?
+ validate_tokens(tokens)
+ validate_syntax(tokens)
+ validate_parentheses(tokens)
+ end
+
+ def validate_tokens(tokens)
+ valid_tokens = [
+ config[:not_symbol], config[:and_symbol], config[:or_symbol], '(', ')'
+ ]
+ tokens.each do |token|
+ next if valid_tokens.include?(token) || token.match?(/\A#{config[:variable_regex].source}\z/)
+ raise ExpressionSyntaxError, "Invalid token: #{token}"
+ end
+ end
+
+ def validate_syntax(tokens)
+ tokens.each_cons(2) do |prev, curr|
+ case prev
+ when config[:variable_regex], ')'
+ raise ExpressionSyntaxError, "Unexpected token: #{prev} #{curr}" unless curr == config[:and_symbol] || curr == config[:or_symbol] || curr == ')'
+ when config[:not_symbol]
+ raise ExpressionSyntaxError, "Unexpected token: #{prev} #{curr}" unless curr.match?(/\A#{config[:variable_regex].source}\z/) || curr == '('
+ when config[:and_symbol], config[:or_symbol], '('
+ raise ExpressionSyntaxError, "Unexpected token: #{prev} #{curr}" unless curr.match?(/\A#{config[:variable_regex].source}\z/) || curr == config[:not_symbol] || curr == '('
+ end
+ end
+ raise ExpressionSyntaxError, "Unexpected end: #{tokens.last}" unless tokens.last.match?(/\A#{config[:variable_regex].source}\z/) || tokens.last == ')'
+ end
+
+ def validate_parentheses(tokens)
+ stack = []
+ tokens.each do |token|
+ if token == '('
+ stack.push(token)
+ elsif token == ')'
+ raise ExpressionSyntaxError, "Mismatched parentheses" if stack.empty?
+ stack.pop
+ end
+ end
+ raise ExpressionSyntaxError, "Mismatched parentheses" unless stack.empty?
+ end
+
def parse(expr)
tokens = tokenize(expr)
+ validate_expression(tokens)
parse_expression(tokens)
end
def tokenize(expr)
- expr.scan(/\w+|[&|!()]/)
+ regex = Regexp.union(
+ config[:variable_regex],
+ config[:not_symbol],
+ config[:and_symbol],
+ config[:or_symbol],
+ /[()]/,
+ /\S+/
+ )
+ expr.scan(regex)
end
def parse_expression(tokens)
parse_or(tokens)
end
def parse_or(tokens)
left = parse_and(tokens)
- while tokens.first == '|'
+ while tokens.first == config[:or_symbol]
tokens.shift
right = parse_and(tokens)
left = [:or, left, right]
end
left
end
def parse_and(tokens)
left = parse_not(tokens)
- while tokens.first == '&'
+ while tokens.first == config[:and_symbol]
tokens.shift
right = parse_not(tokens)
left = [:and, left, right]
end
left
end
def parse_not(tokens)
- if tokens.first == '!'
+ if tokens.first == config[:not_symbol]
tokens.shift
expr = parse_primary(tokens)
[:not, expr]
else
parse_primary(tokens)
@@ -117,14 +178,14 @@
def dnf_to_string(expr)
case expr[0]
when :var
expr[1]
when :not
- "!#{dnf_to_string(expr[1])}"
+ "#{config[:not_symbol]}#{dnf_to_string(expr[1])}"
when :and
- "#{dnf_to_string(expr[1])} & #{dnf_to_string(expr[2])}"
+ "#{dnf_to_string(expr[1])} #{config[:and_symbol]} #{dnf_to_string(expr[2])}"
when :or
- "#{dnf_to_string(expr[1])} | #{dnf_to_string(expr[2])}"
+ "#{dnf_to_string(expr[1])} #{config[:or_symbol]} #{dnf_to_string(expr[2])}"
end
end
end
end