summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGrzegorz Bizon <grzesiek.bizon@gmail.com>2018-02-21 13:38:37 +0100
committerGrzegorz Bizon <grzesiek.bizon@gmail.com>2018-02-21 13:38:37 +0100
commit2c4aa50463ed411b14003f9d929c1e03518953bf (patch)
treedf950739c517127280aa1b301bb884470fe511de
parent91a42a1a8f0d4bfc63e41b2f7b29a2b0fee1a60c (diff)
downloadgitlab-ce-2c4aa50463ed411b14003f9d929c1e03518953bf.tar.gz
Implement pipeline expressions parser
-rw-r--r--lib/gitlab/ci/pipeline/expression/lexeme.rb4
-rw-r--r--lib/gitlab/ci/pipeline/expression/lexer.rb3
-rw-r--r--lib/gitlab/ci/pipeline/expression/statement.rb33
-rw-r--r--lib/gitlab/ci/pipeline/expression/string.rb1
-rw-r--r--lib/gitlab/ci/pipeline/expression/token.rb5
-rw-r--r--lib/gitlab/ci/pipeline/expression/variable.rb8
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb9
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb36
8 files changed, 94 insertions, 5 deletions
diff --git a/lib/gitlab/ci/pipeline/expression/lexeme.rb b/lib/gitlab/ci/pipeline/expression/lexeme.rb
index bbf3fec7407..91ba333d8dd 100644
--- a/lib/gitlab/ci/pipeline/expression/lexeme.rb
+++ b/lib/gitlab/ci/pipeline/expression/lexeme.rb
@@ -7,6 +7,10 @@ module Gitlab
raise NotImplementedError
end
+ def self.build(token)
+ raise NotImplementedError
+ end
+
def self.scan(scanner)
if scanner.scan(self::PATTERN)
Expression::Token.new(scanner.matched, self)
diff --git a/lib/gitlab/ci/pipeline/expression/lexer.rb b/lib/gitlab/ci/pipeline/expression/lexer.rb
index 8432b36b066..ee202ebce69 100644
--- a/lib/gitlab/ci/pipeline/expression/lexer.rb
+++ b/lib/gitlab/ci/pipeline/expression/lexer.rb
@@ -5,7 +5,8 @@ module Gitlab
class Lexer
LEXEMES = [
Expression::Variable,
- Expression::String
+ Expression::String,
+ Expression::Equals
]
MAX_CYCLES = 5
diff --git a/lib/gitlab/ci/pipeline/expression/statement.rb b/lib/gitlab/ci/pipeline/expression/statement.rb
index 91d91195f5a..417b3f806b1 100644
--- a/lib/gitlab/ci/pipeline/expression/statement.rb
+++ b/lib/gitlab/ci/pipeline/expression/statement.rb
@@ -3,6 +3,8 @@ module Gitlab
module Pipeline
module Expression
class Statement
+ ParserError = Class.new(StandardError)
+
GRAMMAR = [
%w[variable equals string],
%w[variable equals variable],
@@ -12,14 +14,41 @@ module Gitlab
%w[variable]
]
- def initialize(pipeline, statement)
+ def initialize(statement, pipeline)
@pipeline = pipeline
- @statement = statement
+ @lexer = Expression::Lexer.new(statement)
end
def variables
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
end
end
diff --git a/lib/gitlab/ci/pipeline/expression/string.rb b/lib/gitlab/ci/pipeline/expression/string.rb
index e6cc50c56d8..6a59b81467c 100644
--- a/lib/gitlab/ci/pipeline/expression/string.rb
+++ b/lib/gitlab/ci/pipeline/expression/string.rb
@@ -14,6 +14,7 @@ module Gitlab
end
def self.build(string)
+ new(string.match(PATTERN)[:string])
end
end
end
diff --git a/lib/gitlab/ci/pipeline/expression/token.rb b/lib/gitlab/ci/pipeline/expression/token.rb
index 03a47a17d40..3b957c83016 100644
--- a/lib/gitlab/ci/pipeline/expression/token.rb
+++ b/lib/gitlab/ci/pipeline/expression/token.rb
@@ -10,7 +10,12 @@ module Gitlab
@type = type
end
+ def build
+ @type.build(@value)
+ end
+
def to_lexeme
+ type.name.demodulize.downcase
end
end
end
diff --git a/lib/gitlab/ci/pipeline/expression/variable.rb b/lib/gitlab/ci/pipeline/expression/variable.rb
index ec6c03d336a..d6a7a655220 100644
--- a/lib/gitlab/ci/pipeline/expression/variable.rb
+++ b/lib/gitlab/ci/pipeline/expression/variable.rb
@@ -5,12 +5,16 @@ module Gitlab
class Variable < Expression::Lexeme
PATTERN = /\$(?<name>\w+)/.freeze
- def initialize(value)
- @value = value
+ def initialize(name)
+ @name = name
end
def evaluate(**variables)
end
+
+ def self.build(string)
+ new(string.match(PATTERN)[:name])
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb
index bdd07b3ae4c..137cad500d7 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb
@@ -36,6 +36,15 @@ describe Gitlab::Ci::Pipeline::Expression::Lexer do
expect(tokens.third.value).to eq '"value"'
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
lexer = described_class.new("$V1 $V2 $V3 $V4 $V5 $V6")
diff --git a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
index 76d0163e44b..7f5f55660be 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
@@ -1,4 +1,40 @@
require 'spec_helper'
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