1// Copyright (c) 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "base/test/expectations/parser.h"
6
7#include "base/strings/string_util.h"
8
9namespace test_expectations {
10
11Parser::Parser(Delegate* delegate, const std::string& input)
12    : delegate_(delegate),
13      input_(input),
14      pos_(NULL),
15      end_(NULL),
16      line_number_(0),
17      data_error_(false) {
18}
19
20Parser::~Parser() {
21}
22
23void Parser::Parse() {
24  pos_ = &input_[0];
25  end_ = pos_ + input_.length();
26
27  line_number_ = 1;
28
29  StateFuncPtr state = &Parser::Start;
30  while (state) {
31    state = (this->*state)();
32  }
33}
34
35inline bool Parser::HasNext() {
36  return pos_ < end_;
37}
38
39Parser::StateFunc Parser::Start() {
40  // If at the start of a line is whitespace, skip it and arrange to come back
41  // here.
42  if (IsAsciiWhitespace(*pos_))
43    return SkipWhitespaceAndNewLines(&Parser::Start);
44
45  // Handle comments at the start of lines.
46  if (*pos_ == '#')
47    return &Parser::ParseComment;
48
49  // After arranging to come back here from skipping whitespace and comments,
50  // the parser may be at the end of the input.
51  if (pos_ >= end_)
52    return NULL;
53
54  current_ = Expectation();
55  data_error_ = false;
56
57  return &Parser::ParseBugURL;
58}
59
60Parser::StateFunc Parser::ParseComment() {
61  if (*pos_ != '#')
62    return SyntaxError("Invalid start of comment");
63
64  do {
65    ++pos_;
66  } while (HasNext() && *pos_ != '\n');
67
68  return &Parser::Start;
69}
70
71Parser::StateFunc Parser::ParseBugURL() {
72  return SkipWhitespace(ExtractString(
73      &Parser::BeginModifiers));
74}
75
76Parser::StateFunc Parser::BeginModifiers() {
77  if (*pos_ != '[' || !HasNext())
78    return SyntaxError("Expected '[' for start of modifiers");
79
80  ++pos_;
81  return SkipWhitespace(&Parser::InModifiers);
82}
83
84Parser::StateFunc Parser::InModifiers() {
85  if (*pos_ == ']')
86    return &Parser::EndModifiers;
87
88  return ExtractString(SkipWhitespace(
89      &Parser::SaveModifier));
90}
91
92Parser::StateFunc Parser::SaveModifier() {
93  if (extracted_string_.empty())
94    return SyntaxError("Invalid modifier list");
95
96  Configuration config;
97  if (ConfigurationFromString(extracted_string_, &config)) {
98    if (current_.configuration != CONFIGURATION_UNSPECIFIED)
99      DataError("Cannot use more than one configuration modifier");
100    else
101      current_.configuration = config;
102  } else {
103    Platform platform;
104    if (PlatformFromString(extracted_string_, &platform))
105      current_.platforms.push_back(platform);
106    else
107      DataError("Invalid modifier string");
108  }
109
110  return SkipWhitespace(&Parser::InModifiers);
111}
112
113Parser::StateFunc Parser::EndModifiers() {
114 if (*pos_ != ']' || !HasNext())
115    return SyntaxError("Expected ']' for end of modifiers list");
116
117  ++pos_;
118  return SkipWhitespace(&Parser::ParseTestName);
119}
120
121Parser::StateFunc Parser::ParseTestName() {
122  return ExtractString(&Parser::SaveTestName);
123}
124
125Parser::StateFunc Parser::SaveTestName() {
126  if (extracted_string_.empty())
127    return SyntaxError("Invalid test name");
128
129  current_.test_name = extracted_string_.as_string();
130  return SkipWhitespace(&Parser::ParseExpectation);
131}
132
133Parser::StateFunc Parser::ParseExpectation() {
134  if (*pos_ != '=' || !HasNext())
135    return SyntaxError("Expected '=' for expectation result");
136
137  ++pos_;
138  return SkipWhitespace(&Parser::ParseExpectationType);
139}
140
141Parser::StateFunc Parser::ParseExpectationType() {
142  return ExtractString(&Parser::SaveExpectationType);
143}
144
145Parser::StateFunc Parser::SaveExpectationType() {
146  if (!ResultFromString(extracted_string_, &current_.result))
147    DataError("Unknown expectation type");
148
149  return SkipWhitespace(&Parser::End);
150}
151
152Parser::StateFunc Parser::End() {
153  if (!data_error_)
154    delegate_->EmitExpectation(current_);
155
156  if (HasNext())
157    return SkipWhitespaceAndNewLines(&Parser::Start);
158
159  return NULL;
160}
161
162Parser::StateFunc Parser::ExtractString(StateFunc success) {
163  const char* start = pos_;
164  while (!IsAsciiWhitespace(*pos_) && *pos_ != ']' && HasNext()) {
165    ++pos_;
166    if (*pos_ == '#') {
167      return SyntaxError("Unexpected start of comment");
168    }
169  }
170  extracted_string_ = base::StringPiece(start, pos_ - start);
171  return success;
172}
173
174Parser::StateFunc Parser::SkipWhitespace(Parser::StateFunc next) {
175  while ((*pos_ == ' ' || *pos_ == '\t') && HasNext()) {
176    ++pos_;
177  }
178  return next;
179}
180
181Parser::StateFunc Parser::SkipWhitespaceAndNewLines(Parser::StateFunc next) {
182  while (IsAsciiWhitespace(*pos_) && HasNext()) {
183    if (*pos_ == '\n') {
184      ++line_number_;
185    }
186    ++pos_;
187  }
188  return next;
189}
190
191Parser::StateFunc Parser::SyntaxError(const std::string& message) {
192  delegate_->OnSyntaxError(message);
193  return NULL;
194}
195
196void Parser::DataError(const std::string& error) {
197  data_error_ = true;
198  delegate_->OnDataError(error);
199}
200
201}  // namespace test_expectations
202