Древовидная логическая логика операций
Я реализую DSL, который имеет синтаксис:
"[keyword] or ([other keyword] and not [one more keyword])"
Каждое ключевое слово будет преобразовано в логическое (true
, false
) значение и после этого оно должно быть рассчитано с использованием операторов and, or, not
Мои текущие правила грамматики соответствуют только строкам [keyword] or [other keyword]
и терпит неудачу в укусах [keyword] or [other keyword] or [one more keyword]
Как написать грамматику, соответствующую любому количеству or
, and
конструкции?
Грамматика:
grammar Sexp
rule expression
keyword operand keyword <ExpressionLiteral>
end
rule operand
or / and <OperandLiteral>
end
rule or
'or' <OrLiteral>
end
rule and
'and' <AndLiteral>
end
rule keyword
space '[' ( '\[' / !']' . )* ']' space <KeywordLiteral>
end
rule space
' '*
end
end
Обновления
Парсер класс
class Parser
require 'treetop'
base_path = File.expand_path(File.dirname(__FILE__))
require File.join(base_path, 'node_extensions.rb')
Treetop.load(File.join(base_path, 'sexp_parser.treetop'))
def self.parse(data)
if data.respond_to? :read
data = data.read
end
parser =SexpParser.new
ast = parser.parse data
if ast
#self.clean_tree(ast)
return ast
else
parser.failure_reason =~ /^(Expected .+) after/m
puts "#{$1.gsub("\n", '$NEWLINE')}:"
puts data.lines.to_a[parser.failure_line - 1]
puts "#{'~' * (parser.failure_column - 1)}^"
end
end
private
def self.clean_tree(root_node)
return if(root_node.elements.nil?)
root_node.elements.delete_if{|node| node.class.name == "Treetop::Runtime::SyntaxNode" }
root_node.elements.each {|node| self.clean_tree(node) }
end
end
tree = Parser.parse('[keyword] or [other keyword] or [this]')
p tree
p tree.to_array
расширение узла
module Sexp
class KeywordLiteral < Treetop::Runtime::SyntaxNode
def to_array
self.text_value.gsub(/[\s\[\]]+/, '')
end
end
class OrLiteral < Treetop::Runtime::SyntaxNode
def to_array
self.text_value
end
end
class AndLiteral < Treetop::Runtime::SyntaxNode
def to_array
self.text_value
end
end
class OperandLiteral < Treetop::Runtime::SyntaxNode
def to_array
self.elements.map{|e| e.to_array}
end
end
class ExpressionLiteral < Treetop::Runtime::SyntaxNode
def to_array
self.elements.map{|e| e.to_array}.join(' ')
end
end
end
1 ответ
Хорошо, спасибо за это разъяснение. В Ruby "ложь и истина или истина" - это правда, потому что "и" вычисляется первым (имеет более высокий приоритет). Чтобы разобрать это, вам нужно одно правило для списка "или" (дизъюнкции), которое вызывает другое правило для списка "и" (союзы). Как это:
rule expression
s disjunction s
{ def value; disjunction.value; end }
end
rule disjunction
conjunction tail:(or s conjunction s)*
{ def value
tail.elements.inject(conjunction.value) do |r, e|
r or e.conjunction.value
end
end
}
end
rule conjunction
primitive tail:(and s primitive s)*
{ def value
tail.elements.inject(primitive.value) do |r, e|
r and e.primitive.value
end
end
}
end
rule primitive
'(' expression ')' s { def value; expression.value; end }
/
not expression s { def value; not expression.value; end }
/
symbol s { def value; symbol.value; end }
end
rule or
'or' !symbolchar s
end
rule and
'and' !symbolchar s
end
rule not
'not' !symbolchar s
end
rule symbol
text:([[:alpha:]_] symbolchar*) s
{ def value
lookup_value(text.text_value)
end
}
end
rule symbolchar
[[:alnum:]_]
end
rule s # Optional space
S?
end
rule S # Mandatory space
[ \t\n\r]*
end
Обратите внимание на некоторые вещи:
- за ключевыми словами не должно следовать сразу символ символа. Я использую негативную перспективу для этого.
- Верхнее правило потребляет начальные пробелы, затем почти каждое правило использует следующие пробелы (это моя политика). Вы должны проверить свою грамматику с минимальным и максимальным пробелом.
- Нет необходимости использовать класс синтаксического узла в каждом правиле, как вы это сделали.
- Вы можете увидеть, как продолжить шаблон для сложения, умножения и т. Д.
- Я дал набросок некоторого кода, который оценивает выражение. Используйте что-нибудь красивее, пожалуйста!