1/* Copyright 2019 The TensorFlow Authors. All Rights Reserved.
2
3Licensed under the Apache License, Version 2.0 (the "License");
4you may not use this file except in compliance with the License.
5You may obtain a copy of the License at
6
7 http://www.apache.org/licenses/LICENSE-2.0
8
9Unless required by applicable law or agreed to in writing, software
10distributed under the License is distributed on an "AS IS" BASIS,
11WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12See the License for the specific language governing permissions and
13limitations under the License.
14==============================================================================*/
15
16#include "tensorflow/lite/toco/args.h"
17
18#include <string>
19
20#include "absl/strings/str_split.h"
21
22namespace toco {
23namespace {
24
25// Helper class for SplitStructuredLine parsing.
26class ClosingSymbolLookup {
27 public:
28 explicit ClosingSymbolLookup(const char* symbol_pairs)
29 : closing_(), valid_closing_() {
30 // Initialize the opening/closing arrays.
31 for (const char* symbol = symbol_pairs; *symbol != 0; ++symbol) {
32 unsigned char opening = *symbol;
33 ++symbol;
34 // If the string ends before the closing character has been found,
35 // use the opening character as the closing character.
36 unsigned char closing = *symbol != 0 ? *symbol : opening;
37 closing_[opening] = closing;
38 valid_closing_[closing] = true;
39 if (*symbol == 0) break;
40 }
41 }
42
43 ClosingSymbolLookup(const ClosingSymbolLookup&) = delete;
44 ClosingSymbolLookup& operator=(const ClosingSymbolLookup&) = delete;
45
46 // Returns the closing character corresponding to an opening one,
47 // or 0 if the argument is not an opening character.
48 char GetClosingChar(char opening) const {
49 return closing_[static_cast<unsigned char>(opening)];
50 }
51
52 // Returns true if the argument is a closing character.
53 bool IsClosing(char c) const {
54 return valid_closing_[static_cast<unsigned char>(c)];
55 }
56
57 private:
58 // Maps an opening character to its closing. If the entry contains 0,
59 // the character is not in the opening set.
60 char closing_[256];
61 // Valid closing characters.
62 bool valid_closing_[256];
63};
64
65bool SplitStructuredLine(absl::string_view line, char delimiter,
66 const char* symbol_pairs,
67 std::vector<absl::string_view>* cols) {
68 ClosingSymbolLookup lookup(symbol_pairs);
69
70 // Stack of symbols expected to close the current opened expressions.
71 std::vector<char> expected_to_close;
72
73 ABSL_RAW_CHECK(cols != nullptr, "");
74 cols->push_back(line);
75 for (size_t i = 0; i < line.size(); ++i) {
76 char c = line[i];
77 if (expected_to_close.empty() && c == delimiter) {
78 // We don't have any open expression, this is a valid separator.
79 cols->back().remove_suffix(line.size() - i);
80 cols->push_back(line.substr(i + 1));
81 } else if (!expected_to_close.empty() && c == expected_to_close.back()) {
82 // Can we close the currently open expression?
83 expected_to_close.pop_back();
84 } else if (lookup.GetClosingChar(c)) {
85 // If this is an opening symbol, we open a new expression and push
86 // the expected closing symbol on the stack.
87 expected_to_close.push_back(lookup.GetClosingChar(c));
88 } else if (lookup.IsClosing(c)) {
89 // Error: mismatched closing symbol.
90 return false;
91 }
92 }
93 if (!expected_to_close.empty()) {
94 return false; // Missing closing symbol(s)
95 }
96 return true; // Success
97}
98
99inline bool TryStripPrefixString(absl::string_view str,
100 absl::string_view prefix,
101 std::string* result) {
102 bool res = absl::ConsumePrefix(&str, prefix);
103 result->assign(str.begin(), str.end());
104 return res;
105}
106
107inline bool TryStripSuffixString(absl::string_view str,
108 absl::string_view suffix,
109 std::string* result) {
110 bool res = absl::ConsumeSuffix(&str, suffix);
111 result->assign(str.begin(), str.end());
112 return res;
113}
114
115} // namespace
116
117bool Arg<toco::IntList>::Parse(std::string text) {
118 parsed_value_.elements.clear();
119 specified_ = true;
120 // absl::StrSplit("") produces {""}, but we need {} on empty input.
121 // TODO(aselle): Moved this from elsewhere, but ahentz recommends we could
122 // use absl::SplitLeadingDec32Values(text.c_str(), &parsed_values_.elements)
123 if (!text.empty()) {
124 int32_t element;
125 for (absl::string_view part : absl::StrSplit(text, ',')) {
126 if (!absl::SimpleAtoi(part, &element)) return false;
127 parsed_value_.elements.push_back(element);
128 }
129 }
130 return true;
131}
132
133bool Arg<toco::StringMapList>::Parse(std::string text) {
134 parsed_value_.elements.clear();
135 specified_ = true;
136
137 if (text.empty()) {
138 return true;
139 }
140
141 std::vector<absl::string_view> outer_vector;
142 absl::string_view text_disposable_copy = text;
143 // TODO(aselle): Change argument parsing when absl supports structuredline.
144 SplitStructuredLine(text_disposable_copy, ',', "{}", &outer_vector);
145 for (const absl::string_view& outer_member_stringpiece : outer_vector) {
146 std::string outer_member(outer_member_stringpiece);
147 if (outer_member.empty()) {
148 continue;
149 }
150 std::string outer_member_copy = outer_member;
151 absl::StripAsciiWhitespace(&outer_member);
152 if (!TryStripPrefixString(outer_member, "{", &outer_member)) return false;
153 if (!TryStripSuffixString(outer_member, "}", &outer_member)) return false;
154 const std::vector<std::string> inner_fields_vector =
155 absl::StrSplit(outer_member, ',');
156
157 std::unordered_map<std::string, std::string> element;
158 for (const std::string& member_field : inner_fields_vector) {
159 std::vector<std::string> outer_member_key_value =
160 absl::StrSplit(member_field, ':');
161 if (outer_member_key_value.size() != 2) return false;
162 std::string& key = outer_member_key_value[0];
163 std::string& value = outer_member_key_value[1];
164 absl::StripAsciiWhitespace(&key);
165 absl::StripAsciiWhitespace(&value);
166 if (element.count(key) != 0) return false;
167 element[key] = value;
168 }
169 parsed_value_.elements.push_back(element);
170 }
171 return true;
172}
173
174} // namespace toco
175