Commit 2c4aa504 authored by Grzegorz Bizon's avatar Grzegorz Bizon

Implement pipeline expressions parser

parent 91a42a1a
...@@ -7,6 +7,10 @@ module Gitlab ...@@ -7,6 +7,10 @@ module Gitlab
raise NotImplementedError raise NotImplementedError
end end
def self.build(token)
raise NotImplementedError
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)
......
...@@ -5,7 +5,8 @@ module Gitlab ...@@ -5,7 +5,8 @@ module Gitlab
class Lexer class Lexer
LEXEMES = [ LEXEMES = [
Expression::Variable, Expression::Variable,
Expression::String Expression::String,
Expression::Equals
] ]
MAX_CYCLES = 5 MAX_CYCLES = 5
......
...@@ -3,6 +3,8 @@ module Gitlab ...@@ -3,6 +3,8 @@ module Gitlab
module Pipeline module Pipeline
module Expression module Expression
class Statement class Statement
ParserError = Class.new(StandardError)
GRAMMAR = [ GRAMMAR = [
%w[variable equals string], %w[variable equals string],
%w[variable equals variable], %w[variable equals variable],
...@@ -12,14 +14,41 @@ module Gitlab ...@@ -12,14 +14,41 @@ module Gitlab
%w[variable] %w[variable]
] ]
def initialize(pipeline, statement) def initialize(statement, pipeline)
@pipeline = pipeline @pipeline = pipeline
@statement = statement @lexer = Expression::Lexer.new(statement)
end end
def variables def variables
end end
def tokens
@lexer.tokenize
end
def lexemes
@lexemes ||= tokens.map(&:to_lexeme)
end
##
# Our syntax is very simple, so we don't need yet to implement a
# recurisive parser, we can use the most simple approach to create
# a reverse descent parse tree "by hand".
#
def parse_tree
raise ParserError if lexemes.empty?
unless GRAMMAR.find { |syntax| syntax == lexemes }
raise ParserError, 'Unknown pipeline expression!'
end
if lexemes.many?
Expression::Equals.new(tokens.first.build, tokens.last.build)
else
tokens.first.build
end
end
def evaluate def evaluate
end end
end end
......
...@@ -14,6 +14,7 @@ module Gitlab ...@@ -14,6 +14,7 @@ module Gitlab
end end
def self.build(string) def self.build(string)
new(string.match(PATTERN)[:string])
end end
end end
end end
......
...@@ -10,7 +10,12 @@ module Gitlab ...@@ -10,7 +10,12 @@ module Gitlab
@type = type @type = type
end end
def build
@type.build(@value)
end
def to_lexeme def to_lexeme
type.name.demodulize.downcase
end end
end end
end end
......
...@@ -5,12 +5,16 @@ module Gitlab ...@@ -5,12 +5,16 @@ module Gitlab
class Variable < Expression::Lexeme class Variable < Expression::Lexeme
PATTERN = /\$(?<name>\w+)/.freeze PATTERN = /\$(?<name>\w+)/.freeze
def initialize(value) def initialize(name)
@value = value @name = name
end end
def evaluate(**variables) def evaluate(**variables)
end end
def self.build(string)
new(string.match(PATTERN)[:name])
end
end end
end end
end end
......
...@@ -36,6 +36,15 @@ describe Gitlab::Ci::Pipeline::Expression::Lexer do ...@@ -36,6 +36,15 @@ describe Gitlab::Ci::Pipeline::Expression::Lexer do
expect(tokens.third.value).to eq '"value"' expect(tokens.third.value).to eq '"value"'
end end
it 'tokenizes tokens and operators' do
tokens = described_class.new('$VARIABLE == "text"').tokenize
expect(tokens.size).to eq 3
expect(tokens.first.value).to eq '$VARIABLE'
expect(tokens.second.value).to eq '=='
expect(tokens.third.value).to eq '"text"'
end
it 'limits statement to 5 tokens' do it 'limits statement to 5 tokens' do
lexer = described_class.new("$V1 $V2 $V3 $V4 $V5 $V6") lexer = described_class.new("$V1 $V2 $V3 $V4 $V5 $V6")
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::Ci::Pipeline::Expression::Statement do describe Gitlab::Ci::Pipeline::Expression::Statement do
let(:pipeline) { build(:ci_pipeline) }
let(:text) { '$VAR "text"' }
subject do
described_class.new(text, pipeline)
end
describe '#tokens' do
it 'returns raw tokens' do
expect(subject.tokens.size).to eq 2
end
end
describe '#lexemes' do
it 'returns an array of syntax lexemes' do
expect(subject.lexemes).to eq %w[variable string]
end
end
describe '#parse_tree' do
context 'when expression grammar is incorrect' do
it 'raises an error' do
expect { subject.parse_tree }
.to raise_error described_class::ParserError
end
end
context 'when expression grammar is correct' do
let(:text) { '$VAR == "value"' }
it 'returns a reverse descent parse tree when using operator' do
expect(subject.parse_tree)
.to be_a Gitlab::Ci::Pipeline::Expression::Equals
end
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