Module: Morpheus::Cli::ExpressionParser

Defined in:
lib/morpheus/cli/expression_parser.rb

Overview

Provides parsing of user input into an Array of expressions for execution Syntax currently supports the && and || operators, and the use of parenthesis returns an Array of objects. Each object might be a command (String), an operator (well known String) or another expression (Array)

Defined Under Namespace

Classes: InvalidExpression

Constant Summary collapse

OPEN_PARENTHESIS =

constants used internally for parsing morpheus expressions note: the space padding in the _TOKEN strings is important for splitting

"("
OPEN_PARENTHESIS_TOKEN =
"___+++OPEN_PARENTHESIS+++___"
OPEN_PARENTHESIS_REGEX =
/(\()(?=(?:[^"']|"[^'"]*")*$)/
CLOSED_PARENTHESIS =
")"
CLOSED_PARENTHESIS_TOKEN =
"___+++CLOSED_PARENTHESIS+++___"
CLOSED_PARENTHESIS_REGEX =
/(\))(?=(?:[^"']|"[^'"]*")*$)/
AND_OPERATOR =
"&&"
AND_OPERATOR_TOKEN =
"___+++AND_OPERATOR+++___"
AND_OPERATOR_REGEX =
/(\&\&)(?=(?:[^"']|"[^'"]*")*$)/
OR_OPERATOR =
"||"
OR_OPERATOR_TOKEN =
"___+++OR_OPERATOR+++___"
OR_OPERATOR_REGEX =
/(\|\|)(?=(?:[^"']|"[^'"]*")*$)/
PIPE_OPERATOR =
"|"
PIPE_OPERATOR_TOKEN =
"___+++PIPE_OPERATOR+++___"
PIPE_OPERATOR_REGEX =
/(\|)(?=(?:[^"']|"[^'"]*")*$)/
COMMAND_DELIMETER =
";"
COMMAND_DELIMETER_REGEX =
/(\;)(?=(?:[^"']|"[^'"]*")*$)/
COMMAND_DELIMETER_TOKEN =
"___+++COMMAND_DELIMETER+++___"

Class Method Summary collapse

Class Method Details

.parse(input) ⇒ Object

parse an expression of morpheus commands into a list of expressions



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/morpheus/cli/expression_parser.rb', line 42

def self.parse(input)
  # the input is a comment
  if input.to_s =~ /^\s*#/
    return [input]
  end
  result = []
  # first, build up a temporary command string
  # swap in well known tokens so we can split it safely
  expression_str = input.dup.to_s
  expression_str.gsub!(OPEN_PARENTHESIS_REGEX, " #{OPEN_PARENTHESIS_TOKEN} ")
  expression_str.gsub!(CLOSED_PARENTHESIS_REGEX, " #{CLOSED_PARENTHESIS_TOKEN} ")
  expression_str.gsub!(AND_OPERATOR_REGEX, " #{AND_OPERATOR_TOKEN} ")
  expression_str.gsub!(OR_OPERATOR_REGEX, " #{OR_OPERATOR_TOKEN} ")
  expression_str.gsub!(PIPE_OPERATOR_REGEX, " #{PIPE_OPERATOR_TOKEN} ")
  expression_str.gsub!(COMMAND_DELIMETER_REGEX, " #{COMMAND_DELIMETER_TOKEN} ")
  # split on unquoted whitespace
  tokens = expression_str.split(/(\s)(?=(?:[^"']|"[^'"]*")*$)/).collect {|it| it.to_s.strip }.select {|it| !it.empty?  }.compact
  # swap back for nice looking tokens
  tokens = tokens.map do |t|
    case t
    when OPEN_PARENTHESIS_TOKEN then OPEN_PARENTHESIS
    when CLOSED_PARENTHESIS_TOKEN then CLOSED_PARENTHESIS
    when AND_OPERATOR_TOKEN then AND_OPERATOR
    when OR_OPERATOR_TOKEN then OR_OPERATOR
    when PIPE_OPERATOR_TOKEN then PIPE_OPERATOR
    when COMMAND_DELIMETER_TOKEN then COMMAND_DELIMETER
    else
      t
    end
  end
  
  # result = parse_expression_from_tokens(tokens)
  begin
    result = parse_expression_from_tokens(tokens)
  rescue InvalidExpression => ex
    raise InvalidExpression.new("#{ex}. Invalid Expression: #{input}")
  end
  return result
end

.parse_expression_from_tokens(tokens) ⇒ Object

turn a flat list of tokens into an array of expressions



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/morpheus/cli/expression_parser.rb', line 83

def self.parse_expression_from_tokens(tokens)
  result = []
  remaining_tokens = tokens.dup
  current_command_tokens = []
  while !remaining_tokens.empty?
    token = remaining_tokens.shift
    if token == CLOSED_PARENTHESIS
      raise InvalidExpression.new("Encountered a closed parenthesis ')' with no matching open parenthesis '('")
    elsif token == OPEN_PARENTHESIS
      # add the command
      if current_command_tokens.size != 0
        result << current_command_tokens.join(" ")
        current_command_tokens = []
      end
      # start of a new sub expression
      # find the index of the matching closed parenthesis
      cur_expression_index = 0
      closed_parenthesis_index = nil
      remaining_tokens.each_with_index do |t, index|
        if t == OPEN_PARENTHESIS
          cur_expression_index += 1
        elsif t == CLOSED_PARENTHESIS
          if cur_expression_index == 0
            closed_parenthesis_index = index
            break
          else
            cur_expression_index -= 1
          end
        end
        if cur_expression_index < 0
          raise InvalidExpression.new("Encountered a closed parenthesis ')' with no matching open parenthesis '('")
        end
      end
      if !closed_parenthesis_index
        raise InvalidExpression.new("Encountered an open parenthesis '(' with no matching closed parenthesis ')'")
      end
      # ok, parse a subexpression for the tokens up that index
      sub_tokens = remaining_tokens[0..closed_parenthesis_index-1]
      result << parse_expression_from_tokens(sub_tokens)
      # continue on parsing the remaining tokens
      remaining_tokens = remaining_tokens[closed_parenthesis_index + 1..remaining_tokens.size - 1]
    elsif token == AND_OPERATOR || token == OR_OPERATOR || token == PIPE_OPERATOR
      # add the command
      if current_command_tokens.size != 0
        result << current_command_tokens.join(" ")
        current_command_tokens = []
      end
      # add the operator
      result << token
    elsif token == COMMAND_DELIMETER
      # add the command
      if current_command_tokens.size != 0
        result << current_command_tokens.join(" ")
        current_command_tokens = []
      end
    else
      # everything else is assumed to be part of a command, inject it
      current_command_tokens << token
    end
  end
  
  # add the command
  if current_command_tokens.size != 0
    result << current_command_tokens.join(" ")
    current_command_tokens = []
  end

  return result
end