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
module Expression
class Equals < Expression::Lexeme
PATTERN = /==/.freeze
TYPE = :operator
def initialize(left, right)
@left = left
......@@ -13,6 +14,10 @@ module Gitlab
def evaluate(**variables)
@left.evaluate(variables) == @right.evaluate(variables)
end
def self.build(value, behind, ahead)
new(behind, ahead)
end
end
end
end
......
......@@ -11,6 +11,10 @@ module Gitlab
raise NotImplementedError
end
def self.type
self::TYPE
end
def self.scan(scanner)
if scanner.scan(self::PATTERN)
Expression::Token.new(scanner.matched, self)
......
......@@ -4,6 +4,7 @@ module Gitlab
module Expression
class Null < Expression::Lexeme
PATTERN = /null/.freeze
TYPE = :value
def initialize(value)
@value = value
......
......@@ -3,20 +3,31 @@ module Gitlab
module Pipeline
module Expression
class Parser
def initialize(syntax)
if syntax.is_a?(Expression::Lexer)
@tokens = syntax.tokens
else
@tokens = syntax.to_a
end
def initialize(tokens)
# raise ArgumentError unless tokens.enumerator?
@tokens = tokens
@nodes = []
end
def tree
if @tokens.many?
Expression::Equals.new(@tokens.first.build, @tokens.last.build)
else
@tokens.first.build
while token = @tokens.next
case token.type
when :operator
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
rescue StopIteration
@nodes.last
end
end
end
......
......@@ -15,7 +15,6 @@ module Gitlab
].freeze
def initialize(statement, pipeline)
@pipeline = pipeline
@lexer = Expression::Lexer.new(statement)
@variables = pipeline.variables.map do |variable|
......@@ -30,7 +29,7 @@ module Gitlab
raise StatementError, 'Unknown pipeline expression!'
end
Expression::Parser.new(@lexer).tree
Expression::Parser.new(@lexer.tokens.to_enum).tree
end
def evaluate
......
......@@ -4,6 +4,7 @@ module Gitlab
module Expression
class String < Expression::Lexeme
PATTERN = /"(?<string>.+?)"/.freeze
TYPE = :value
def initialize(value)
@value = value
......
......@@ -3,19 +3,23 @@ module Gitlab
module Pipeline
module Expression
class Token
attr_reader :value, :type
attr_reader :value, :lexeme
def initialize(value, type)
def initialize(value, lexeme)
@value = value
@type = type
@lexeme = lexeme
end
def build
@type.build(@value)
def build(*args)
@lexeme.build(@value, *args)
end
def type
@lexeme.type
end
def to_lexeme
type.name.demodulize.downcase
@lexeme.name.demodulize.downcase
end
end
end
......
......@@ -4,6 +4,7 @@ module Gitlab
module Expression
class Variable < Expression::Lexeme
PATTERN = /\$(?<name>\w+)/.freeze
TYPE = :value
def initialize(name)
@name = name
......
......@@ -2,5 +2,22 @@ require 'spec_helper'
describe Gitlab::Ci::Pipeline::Expression::Parser 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
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