Reputation: 315
I am implementing DSL which has syntax:
"[keyword] or ([other keyword] and not [one more keyword])"
Each keyword will transform to boolean (true
, false
) value and after that it should be calculated using operators and, or, not
My current grammar rules match only strings [keyword] or [other keyword]
and fails on stings [keyword] or [other keyword] or [one more keyword]
How to write grammar that match any ammount of or
, and
constructions?
Grammar:
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
Updates
Parser class
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
node extension
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
Upvotes: 3
Views: 388
Reputation: 2606
Ok, thanks for that clarification. In Ruby, "false and true or true" is true, because the "and" is evaluated first (it has higher precedence). To parse this, you need one rule for the "or" list (the disjunctions) which calls another rule for the "and" list (the conjunctions). Like this:
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
Note some things:
Upvotes: 4