# frozen_string_literal: true require "parslet" require "autoproj/exceptions" require "autoproj/variable_expansion" module Autoproj # Parses a conditional expression # Syntax and rules as defined in https://www.ros.org/reps/rep-0149.html#id20 class RosConditionParser < Parslet::Parser def initialize(context) @context = context super() end def expand(var) Autoproj.expand(var, @context) rescue StandardError "" end # Evaluates the Abstract Syntax Tree generated by the parser class Transform < Parslet::Transform rule(literal: simple(:x)) { String(x) } rule(variable: simple(:x)) { expander.call(String(x)) } rule(op: simple(:o), lhs: simple(:l), rhs: simple(:r)) { l.send(o, r) } rule(op: "and", lhs: simple(:l), rhs: simple(:r)) { l && r } rule(op: "or", lhs: simple(:l), rhs: simple(:r)) { l || r } end def evaluate(expr) Transform.new.apply(parse(expr.strip), expander: method(:expand)) rescue Parslet::ParseFailed => e raise Autoproj::ConfigError, e.message end def any_of(strings) strings = strings.dup strings.reduce(str(strings.shift)) { |acc, s| acc | str(s) } end def quoted_literal(quote) str(quote) >> ( str(quote).absent? >> any ).repeat.maybe.as(:literal) >> str(quote) end def chain(lhs, operator, operation) (lhs.as(:lhs) >> operator >> operation.as(:rhs)) | lhs end RESERVED = %w[and or].freeze rule(:space) { match['\s\t'].repeat(1) } rule(:space?) { space.maybe } rule(:lparen) { str("(") >> space? } rule(:rparen) { str(")") >> space? } rule(:comp_operator) { any_of(%w[== != >= <= > <]).as(:op) >> space? } rule(:and_operator) { str("and").as(:op) >> space? } rule(:or_operator) { str("or").as(:op) >> space? } rule(:literal) do (any_of(RESERVED) >> space).absent? >> (match('\w') | match["_-"]).repeat(1) .as(:literal) end rule(:double_quote) { quoted_literal('"') } rule(:single_quote) { quoted_literal("'") } rule(:variable) do ( str("$") >> match('\d').absent? >> ( match('\w') | match["_"] ).repeat(1) ).as(:variable) end rule(:term) { (literal | single_quote | double_quote | variable) >> space? } rule(:primary) { (lparen >> or_operation >> rparen) | term } rule(:comp_operation) { chain(primary, comp_operator, comp_operation) } rule(:and_operation) { chain(comp_operation, and_operator, and_operation) } rule(:or_operation) { chain(and_operation, or_operator, or_operation) } root(:or_operation) end end