Commit 867a4f68 authored by Grzegorz Bizon's avatar Grzegorz Bizon

Extract pipeline expressions parser to a separate class

parent b92ce0cc
...@@ -17,7 +17,9 @@ module Gitlab ...@@ -17,7 +17,9 @@ module Gitlab
@tokens = [] @tokens = []
end end
def tokenize def tokens
return @tokens if @tokens.any?
MAX_CYCLES.times do MAX_CYCLES.times do
LEXEMES.each do |lexeme| LEXEMES.each do |lexeme|
@scanner.skip(/\s+/) # ignore whitespace @scanner.skip(/\s+/) # ignore whitespace
...@@ -32,6 +34,10 @@ module Gitlab ...@@ -32,6 +34,10 @@ module Gitlab
raise Lexer::SyntaxError unless @scanner.eos? raise Lexer::SyntaxError unless @scanner.eos?
end end
def lexemes
tokens.map(&:to_lexeme)
end
end end
end end
end end
......
module Gitlab
module Ci
module Pipeline
module Expression
class Parser
def initialize(syntax)
if syntax.is_a?(Expression::Lexer)
@tokens = syntax.tokens
else
@tokens = syntax.to_a
end
end
def tree
if @tokens.many?
Expression::Equals.new(@tokens.first.build, @tokens.last.build)
else
@tokens.first.build
end
end
end
end
end
end
end
...@@ -23,31 +23,14 @@ module Gitlab ...@@ -23,31 +23,14 @@ module Gitlab
end end
end end
def tokens
@tokens ||= @lexer.tokenize
end
def lexemes
@lexemes ||= tokens.map(&:to_lexeme)
end
##
# Our syntax is very simple, so we don't yet need to implement a
# recursive parser, we can use the most simple approach to create
# a reverse descent parse tree "by hand".
#
def parse_tree def parse_tree
raise StatementError if lexemes.empty? raise StatementError if @lexer.lexemes.empty?
unless GRAMMAR.find { |syntax| syntax == lexemes } unless GRAMMAR.find { |syntax| syntax == @lexer.lexemes }
raise StatementError, 'Unknown pipeline expression!' raise StatementError, 'Unknown pipeline expression!'
end end
if tokens.many? Expression::Parser.new(@lexer).tree
Expression::Equals.new(tokens.first.build, tokens.last.build)
else
tokens.first.build
end
end end
def evaluate def evaluate
......
...@@ -5,30 +5,30 @@ describe Gitlab::Ci::Pipeline::Expression::Lexer do ...@@ -5,30 +5,30 @@ describe Gitlab::Ci::Pipeline::Expression::Lexer do
Gitlab::Ci::Pipeline::Expression::Token Gitlab::Ci::Pipeline::Expression::Token
end end
describe '#tokenize' do describe '#tokens' do
it 'tokenizes single value' do it 'tokenss single value' do
tokens = described_class.new('$VARIABLE').tokenize tokens = described_class.new('$VARIABLE').tokens
expect(tokens).to be_one expect(tokens).to be_one
expect(tokens).to all(be_an_instance_of(token_class)) expect(tokens).to all(be_an_instance_of(token_class))
end end
it 'does ignore whitespace characters' do it 'does ignore whitespace characters' do
tokens = described_class.new("\t$VARIABLE ").tokenize tokens = described_class.new("\t$VARIABLE ").tokens
expect(tokens).to be_one expect(tokens).to be_one
expect(tokens).to all(be_an_instance_of(token_class)) expect(tokens).to all(be_an_instance_of(token_class))
end end
it 'tokenizes multiple values of the same token' do it 'tokenss multiple values of the same token' do
tokens = described_class.new("$VARIABLE1 $VARIABLE2").tokenize tokens = described_class.new("$VARIABLE1 $VARIABLE2").tokens
expect(tokens.size).to eq 2 expect(tokens.size).to eq 2
expect(tokens).to all(be_an_instance_of(token_class)) expect(tokens).to all(be_an_instance_of(token_class))
end end
it 'tokenizes multiple values with different tokens' do it 'tokenss multiple values with different tokens' do
tokens = described_class.new('$VARIABLE "text" "value"').tokenize tokens = described_class.new('$VARIABLE "text" "value"').tokens
expect(tokens.size).to eq 3 expect(tokens.size).to eq 3
expect(tokens.first.value).to eq '$VARIABLE' expect(tokens.first.value).to eq '$VARIABLE'
...@@ -36,8 +36,8 @@ describe Gitlab::Ci::Pipeline::Expression::Lexer do ...@@ -36,8 +36,8 @@ 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 it 'tokenss tokens and operators' do
tokens = described_class.new('$VARIABLE == "text"').tokenize tokens = described_class.new('$VARIABLE == "text"').tokens
expect(tokens.size).to eq 3 expect(tokens.size).to eq 3
expect(tokens.first.value).to eq '$VARIABLE' expect(tokens.first.value).to eq '$VARIABLE'
...@@ -48,15 +48,24 @@ describe Gitlab::Ci::Pipeline::Expression::Lexer do ...@@ -48,15 +48,24 @@ describe Gitlab::Ci::Pipeline::Expression::Lexer do
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")
expect { lexer.tokenize } expect { lexer.tokens }
.to raise_error described_class::SyntaxError .to raise_error described_class::SyntaxError
end end
it 'raises syntax error in case of finding unknown tokens' do it 'raises syntax error in case of finding unknown tokens' do
lexer = described_class.new('$V1 123 $V2') lexer = described_class.new('$V1 123 $V2')
expect { lexer.tokenize } expect { lexer.tokens }
.to raise_error described_class::SyntaxError .to raise_error described_class::SyntaxError
end end
end end
describe '#lexemes' do
it 'returns an array of syntax lexemes' do
lexer = described_class.new('$VAR "text"')
expect(lexer.lexemes).to eq %w[variable string]
end
end
end end
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Expression::Parser do
describe '#tree' do
end
end
...@@ -12,18 +12,6 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do ...@@ -12,18 +12,6 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do
pipeline.variables.build([key: 'VARIABLE', value: 'my variable']) pipeline.variables.build([key: 'VARIABLE', value: 'my variable'])
end 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 describe '#parse_tree' do
context 'when expression is empty' do context 'when expression is empty' do
let(:text) { '' } let(:text) { '' }
......
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