Commit 6fe4d2c6 authored by Grzegorz Bizon's avatar Grzegorz Bizon

Build a recursive parser for pipeline expressions

parent 867a4f68
...@@ -4,6 +4,7 @@ module Gitlab ...@@ -4,6 +4,7 @@ module Gitlab
module Expression module Expression
class Equals < Expression::Lexeme class Equals < Expression::Lexeme
PATTERN = /==/.freeze PATTERN = /==/.freeze
TYPE = :operator
def initialize(left, right) def initialize(left, right)
@left = left @left = left
...@@ -13,6 +14,10 @@ module Gitlab ...@@ -13,6 +14,10 @@ module Gitlab
def evaluate(**variables) def evaluate(**variables)
@left.evaluate(variables) == @right.evaluate(variables) @left.evaluate(variables) == @right.evaluate(variables)
end end
def self.build(value, behind, ahead)
new(behind, ahead)
end
end end
end end
end end
......
...@@ -11,6 +11,10 @@ module Gitlab ...@@ -11,6 +11,10 @@ module Gitlab
raise NotImplementedError raise NotImplementedError
end end
def self.type
self::TYPE
end
def self.scan(scanner) def self.scan(scanner)
if scanner.scan(self::PATTERN) if scanner.scan(self::PATTERN)
Expression::Token.new(scanner.matched, self) Expression::Token.new(scanner.matched, self)
......
...@@ -4,6 +4,7 @@ module Gitlab ...@@ -4,6 +4,7 @@ module Gitlab
module Expression module Expression
class Null < Expression::Lexeme class Null < Expression::Lexeme
PATTERN = /null/.freeze PATTERN = /null/.freeze
TYPE = :value
def initialize(value) def initialize(value)
@value = value @value = value
......
...@@ -3,20 +3,31 @@ module Gitlab ...@@ -3,20 +3,31 @@ module Gitlab
module Pipeline module Pipeline
module Expression module Expression
class Parser class Parser
def initialize(syntax) def initialize(tokens)
if syntax.is_a?(Expression::Lexer) # raise ArgumentError unless tokens.enumerator?
@tokens = syntax.tokens
else @tokens = tokens
@tokens = syntax.to_a @nodes = []
end
end end
def tree def tree
if @tokens.many? while token = @tokens.next
Expression::Equals.new(@tokens.first.build, @tokens.last.build) case token.type
else when :operator
@tokens.first.build lookbehind = @nodes.last
lookahead = Parser.new(@tokens).tree
token.build(lookbehind, lookahead).tap do |node|
@nodes.push(node)
end
when :value
token.build.tap do |leaf|
@nodes.push(leaf)
end
end
end end
rescue StopIteration
@nodes.last
end end
end end
end end
......
...@@ -15,7 +15,6 @@ module Gitlab ...@@ -15,7 +15,6 @@ module Gitlab
].freeze ].freeze
def initialize(statement, pipeline) def initialize(statement, pipeline)
@pipeline = pipeline
@lexer = Expression::Lexer.new(statement) @lexer = Expression::Lexer.new(statement)
@variables = pipeline.variables.map do |variable| @variables = pipeline.variables.map do |variable|
...@@ -30,7 +29,7 @@ module Gitlab ...@@ -30,7 +29,7 @@ module Gitlab
raise StatementError, 'Unknown pipeline expression!' raise StatementError, 'Unknown pipeline expression!'
end end
Expression::Parser.new(@lexer).tree Expression::Parser.new(@lexer.tokens.to_enum).tree
end end
def evaluate def evaluate
......
...@@ -4,6 +4,7 @@ module Gitlab ...@@ -4,6 +4,7 @@ module Gitlab
module Expression module Expression
class String < Expression::Lexeme class String < Expression::Lexeme
PATTERN = /"(?<string>.+?)"/.freeze PATTERN = /"(?<string>.+?)"/.freeze
TYPE = :value
def initialize(value) def initialize(value)
@value = value @value = value
......
...@@ -3,19 +3,23 @@ module Gitlab ...@@ -3,19 +3,23 @@ module Gitlab
module Pipeline module Pipeline
module Expression module Expression
class Token class Token
attr_reader :value, :type attr_reader :value, :lexeme
def initialize(value, type) def initialize(value, lexeme)
@value = value @value = value
@type = type @lexeme = lexeme
end end
def build def build(*args)
@type.build(@value) @lexeme.build(@value, *args)
end
def type
@lexeme.type
end end
def to_lexeme def to_lexeme
type.name.demodulize.downcase @lexeme.name.demodulize.downcase
end end
end end
end end
......
...@@ -4,6 +4,7 @@ module Gitlab ...@@ -4,6 +4,7 @@ module Gitlab
module Expression module Expression
class Variable < Expression::Lexeme class Variable < Expression::Lexeme
PATTERN = /\$(?<name>\w+)/.freeze PATTERN = /\$(?<name>\w+)/.freeze
TYPE = :value
def initialize(name) def initialize(name)
@name = name @name = name
......
...@@ -2,5 +2,22 @@ require 'spec_helper' ...@@ -2,5 +2,22 @@ require 'spec_helper'
describe Gitlab::Ci::Pipeline::Expression::Parser do describe Gitlab::Ci::Pipeline::Expression::Parser do
describe '#tree' do describe '#tree' do
context 'when using an operator' do
it 'returns a reverse descent parse tree' do
expect(described_class.new(tokens('$VAR == "123"')).tree)
.to be_a Gitlab::Ci::Pipeline::Expression::Equals
end
end
context 'when using a single token' do
it 'returns a single token instance' do
expect(described_class.new(tokens('$VAR')).tree)
.to be_a Gitlab::Ci::Pipeline::Expression::Variable
end
end
end
def tokens(statement)
Gitlab::Ci::Pipeline::Expression::Lexer.new(statement).tokens.to_enum
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment