blob: 17bcfa02877a7262f08c122141a5b1dd2a4a859b [file] [log] [blame]
Ethan Nicholas762466e2017-06-29 10:03:38 -04001/*
2 * Copyright 2016 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "SkSLHCodeGenerator.h"
9
Ethan Nicholas130fb3f2018-02-01 12:14:34 -050010#include "SkSLParser.h"
Ethan Nicholas762466e2017-06-29 10:03:38 -040011#include "SkSLUtil.h"
Ethan Nicholasaae47c82017-11-10 15:34:03 -050012#include "ir/SkSLEnum.h"
Ethan Nicholas762466e2017-06-29 10:03:38 -040013#include "ir/SkSLFunctionDeclaration.h"
14#include "ir/SkSLFunctionDefinition.h"
15#include "ir/SkSLSection.h"
16#include "ir/SkSLVarDeclarations.h"
17
Michael Ludwiga4275592018-08-31 10:52:47 -040018#include <set>
19
Ethan Nicholas762466e2017-06-29 10:03:38 -040020namespace SkSL {
21
Ethan Nicholasc9472af2017-10-10 16:30:21 -040022HCodeGenerator::HCodeGenerator(const Context* context, const Program* program,
23 ErrorReporter* errors, String name, OutputStream* out)
Ethan Nicholas762466e2017-06-29 10:03:38 -040024: INHERITED(program, errors, out)
Ethan Nicholasc9472af2017-10-10 16:30:21 -040025, fContext(*context)
Ethan Nicholas762466e2017-06-29 10:03:38 -040026, fName(std::move(name))
27, fFullName(String::printf("Gr%s", fName.c_str()))
28, fSectionAndParameterHelper(*program, *errors) {}
29
Ethan Nicholasd608c092017-10-26 09:30:08 -040030String HCodeGenerator::ParameterType(const Context& context, const Type& type,
31 const Layout& layout) {
32 if (layout.fCType != "") {
33 return layout.fCType;
34 } else if (type == *context.fFloat_Type || type == *context.fHalf_Type) {
Ethan Nicholasf7b88202017-09-18 14:10:39 -040035 return "float";
Michael Ludwiga4275592018-08-31 10:52:47 -040036 } else if (type == *context.fInt_Type ||
37 type == *context.fShort_Type ||
38 type == *context.fByte_Type) {
39 return "int32_t";
Ethan Nicholasc9472af2017-10-10 16:30:21 -040040 } else if (type == *context.fFloat2_Type || type == *context.fHalf2_Type) {
Ethan Nicholas762466e2017-06-29 10:03:38 -040041 return "SkPoint";
Michael Ludwiga4275592018-08-31 10:52:47 -040042 } else if (type == *context.fInt2_Type ||
43 type == *context.fShort2_Type ||
44 type == *context.fByte2_Type) {
45 return "SkIPoint";
Ruiqi Maob609e6d2018-07-17 10:19:38 -040046 } else if (type == *context.fInt4_Type ||
47 type == *context.fShort4_Type ||
48 type == *context.fByte4_Type) {
Ethan Nicholas762466e2017-06-29 10:03:38 -040049 return "SkIRect";
Ethan Nicholasc9472af2017-10-10 16:30:21 -040050 } else if (type == *context.fFloat4_Type || type == *context.fHalf4_Type) {
Ethan Nicholas762466e2017-06-29 10:03:38 -040051 return "SkRect";
Michael Ludwiga4275592018-08-31 10:52:47 -040052 } else if (type == *context.fFloat3x3_Type || type == *context.fHalf3x3_Type) {
53 return "SkMatrix";
Ethan Nicholasc9472af2017-10-10 16:30:21 -040054 } else if (type == *context.fFloat4x4_Type || type == *context.fHalf4x4_Type) {
Ethan Nicholas762466e2017-06-29 10:03:38 -040055 return "SkMatrix44";
56 } else if (type.kind() == Type::kSampler_Kind) {
57 return "sk_sp<GrTextureProxy>";
Ethan Nicholasc9472af2017-10-10 16:30:21 -040058 } else if (type == *context.fFragmentProcessor_Type) {
59 return "std::unique_ptr<GrFragmentProcessor>";
Ethan Nicholas762466e2017-06-29 10:03:38 -040060 }
61 return type.name();
62}
63
Ethan Nicholasd608c092017-10-26 09:30:08 -040064String HCodeGenerator::FieldType(const Context& context, const Type& type,
65 const Layout& layout) {
Ethan Nicholas762466e2017-06-29 10:03:38 -040066 if (type.kind() == Type::kSampler_Kind) {
67 return "TextureSampler";
Ethan Nicholasc9472af2017-10-10 16:30:21 -040068 } else if (type == *context.fFragmentProcessor_Type) {
69 // we don't store fragment processors in fields, they get registered via
70 // registerChildProcessor instead
Ethan Nicholasd9d33c32018-06-12 11:05:59 -040071 SkASSERT(false);
Ethan Nicholasc9472af2017-10-10 16:30:21 -040072 return "<error>";
Ethan Nicholas762466e2017-06-29 10:03:38 -040073 }
Ethan Nicholasd608c092017-10-26 09:30:08 -040074 return ParameterType(context, type, layout);
Ethan Nicholas762466e2017-06-29 10:03:38 -040075}
76
Michael Ludwiga4275592018-08-31 10:52:47 -040077String HCodeGenerator::AccessType(const Context& context, const Type& type,
78 const Layout& layout) {
Michael Ludwig72efd802018-08-31 13:26:19 -040079 static const std::set<String> primitiveTypes = { "int32_t", "float", "bool", "SkPMColor" };
Michael Ludwiga4275592018-08-31 10:52:47 -040080
81 String fieldType = FieldType(context, type, layout);
82 bool isPrimitive = primitiveTypes.find(fieldType) != primitiveTypes.end();
83 if (isPrimitive) {
84 return fieldType;
85 } else {
86 return String::printf("const %s&", fieldType.c_str());
87 }
88}
89
Ethan Nicholas762466e2017-06-29 10:03:38 -040090void HCodeGenerator::writef(const char* s, va_list va) {
91 static constexpr int BUFFER_SIZE = 1024;
Ethan Nicholas9fb036f2017-07-05 16:19:09 -040092 va_list copy;
93 va_copy(copy, va);
Ethan Nicholas762466e2017-06-29 10:03:38 -040094 char buffer[BUFFER_SIZE];
95 int length = vsnprintf(buffer, BUFFER_SIZE, s, va);
96 if (length < BUFFER_SIZE) {
97 fOut->write(buffer, length);
98 } else {
99 std::unique_ptr<char[]> heap(new char[length + 1]);
Ethan Nicholas9fb036f2017-07-05 16:19:09 -0400100 vsprintf(heap.get(), s, copy);
Ethan Nicholas762466e2017-06-29 10:03:38 -0400101 fOut->write(heap.get(), length);
102 }
103}
104
105void HCodeGenerator::writef(const char* s, ...) {
106 va_list va;
107 va_start(va, s);
108 this->writef(s, va);
109 va_end(va);
110}
111
112bool HCodeGenerator::writeSection(const char* name, const char* prefix) {
Ethan Nicholas68990be2017-07-13 09:36:52 -0400113 const Section* s = fSectionAndParameterHelper.getSection(name);
114 if (s) {
115 this->writef("%s%s", prefix, s->fText.c_str());
Ethan Nicholas762466e2017-06-29 10:03:38 -0400116 return true;
117 }
118 return false;
119}
120
121void HCodeGenerator::writeExtraConstructorParams(const char* separator) {
122 // super-simple parse, just assume the last token before a comma is the name of a parameter
123 // (which is true as long as there are no multi-parameter template types involved). Will replace
124 // this with something more robust if the need arises.
Ethan Nicholas68990be2017-07-13 09:36:52 -0400125 const Section* section = fSectionAndParameterHelper.getSection(CONSTRUCTOR_PARAMS_SECTION);
126 if (section) {
127 const char* s = section->fText.c_str();
Ethan Nicholas762466e2017-06-29 10:03:38 -0400128 #define BUFFER_SIZE 64
129 char lastIdentifier[BUFFER_SIZE];
130 int lastIdentifierLength = 0;
131 bool foundBreak = false;
132 while (*s) {
133 char c = *s;
134 ++s;
135 if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||
136 c == '_') {
137 if (foundBreak) {
138 lastIdentifierLength = 0;
139 foundBreak = false;
140 }
Ethan Nicholasd9d33c32018-06-12 11:05:59 -0400141 SkASSERT(lastIdentifierLength < BUFFER_SIZE);
Ethan Nicholas762466e2017-06-29 10:03:38 -0400142 lastIdentifier[lastIdentifierLength] = c;
143 ++lastIdentifierLength;
144 } else {
145 foundBreak = true;
146 if (c == ',') {
Ethan Nicholasd9d33c32018-06-12 11:05:59 -0400147 SkASSERT(lastIdentifierLength < BUFFER_SIZE);
Ethan Nicholas762466e2017-06-29 10:03:38 -0400148 lastIdentifier[lastIdentifierLength] = 0;
149 this->writef("%s%s", separator, lastIdentifier);
150 separator = ", ";
151 } else if (c != ' ' && c != '\t' && c != '\n' && c != '\r') {
152 lastIdentifierLength = 0;
153 }
154 }
155 }
156 if (lastIdentifierLength) {
Ethan Nicholasd9d33c32018-06-12 11:05:59 -0400157 SkASSERT(lastIdentifierLength < BUFFER_SIZE);
Ethan Nicholas762466e2017-06-29 10:03:38 -0400158 lastIdentifier[lastIdentifierLength] = 0;
159 this->writef("%s%s", separator, lastIdentifier);
160 }
161 }
162}
163
164void HCodeGenerator::writeMake() {
165 const char* separator;
166 if (!this->writeSection(MAKE_SECTION)) {
Brian Salomonaff329b2017-08-11 09:40:37 -0400167 this->writef(" static std::unique_ptr<GrFragmentProcessor> Make(");
Ethan Nicholas762466e2017-06-29 10:03:38 -0400168 separator = "";
Ethan Nicholas68990be2017-07-13 09:36:52 -0400169 for (const auto& param : fSectionAndParameterHelper.getParameters()) {
Ethan Nicholasd608c092017-10-26 09:30:08 -0400170 this->writef("%s%s %s", separator, ParameterType(fContext, param->fType,
171 param->fModifiers.fLayout).c_str(),
Ethan Nicholas5b5f0962017-09-11 13:50:14 -0700172 String(param->fName).c_str());
Ethan Nicholas762466e2017-06-29 10:03:38 -0400173 separator = ", ";
174 }
175 this->writeSection(CONSTRUCTOR_PARAMS_SECTION, separator);
176 this->writef(") {\n"
Brian Salomonaff329b2017-08-11 09:40:37 -0400177 " return std::unique_ptr<GrFragmentProcessor>(new %s(",
Ethan Nicholas762466e2017-06-29 10:03:38 -0400178 fFullName.c_str());
179 separator = "";
Ethan Nicholas68990be2017-07-13 09:36:52 -0400180 for (const auto& param : fSectionAndParameterHelper.getParameters()) {
Ethan Nicholasc9472af2017-10-10 16:30:21 -0400181 if (param->fType == *fContext.fFragmentProcessor_Type) {
Robert Phillips7a59f232017-11-08 15:31:30 -0500182 this->writef("%sstd::move(%s)", separator, String(param->fName).c_str());
Ethan Nicholasc9472af2017-10-10 16:30:21 -0400183 } else {
184 this->writef("%s%s", separator, String(param->fName).c_str());
185 }
Ethan Nicholas762466e2017-06-29 10:03:38 -0400186 separator = ", ";
187 }
188 this->writeExtraConstructorParams(separator);
189 this->writef("));\n"
190 " }\n");
191 }
192}
193
Ethan Nicholas68990be2017-07-13 09:36:52 -0400194void HCodeGenerator::failOnSection(const char* section, const char* msg) {
195 std::vector<const Section*> s = fSectionAndParameterHelper.getSections(section);
196 if (s.size()) {
Ethan Nicholas5b5f0962017-09-11 13:50:14 -0700197 fErrors.error(s[0]->fOffset, String("@") + section + " " + msg);
Ethan Nicholas68990be2017-07-13 09:36:52 -0400198 }
199}
200
Ethan Nicholas762466e2017-06-29 10:03:38 -0400201void HCodeGenerator::writeConstructor() {
202 if (this->writeSection(CONSTRUCTOR_SECTION)) {
Ethan Nicholas68990be2017-07-13 09:36:52 -0400203 const char* msg = "may not be present when constructor is overridden";
204 this->failOnSection(CONSTRUCTOR_CODE_SECTION, msg);
205 this->failOnSection(CONSTRUCTOR_PARAMS_SECTION, msg);
Ethan Nicholas68990be2017-07-13 09:36:52 -0400206 this->failOnSection(INITIALIZERS_SECTION, msg);
207 this->failOnSection(OPTIMIZATION_FLAGS_SECTION, msg);
Robert Phillips1e8501e2018-03-23 15:00:20 -0400208 return;
Ethan Nicholas762466e2017-06-29 10:03:38 -0400209 }
210 this->writef(" %s(", fFullName.c_str());
211 const char* separator = "";
Ethan Nicholas68990be2017-07-13 09:36:52 -0400212 for (const auto& param : fSectionAndParameterHelper.getParameters()) {
Ethan Nicholasd608c092017-10-26 09:30:08 -0400213 this->writef("%s%s %s", separator, ParameterType(fContext, param->fType,
214 param->fModifiers.fLayout).c_str(),
Ethan Nicholas5b5f0962017-09-11 13:50:14 -0700215 String(param->fName).c_str());
Ethan Nicholas762466e2017-06-29 10:03:38 -0400216 separator = ", ";
217 }
218 this->writeSection(CONSTRUCTOR_PARAMS_SECTION, separator);
219 this->writef(")\n"
Ethan Nicholasabff9562017-10-09 10:54:08 -0400220 " : INHERITED(k%s_ClassID", fFullName.c_str());
221 if (!this->writeSection(OPTIMIZATION_FLAGS_SECTION, ", (OptimizationFlags) ")) {
222 this->writef(", kNone_OptimizationFlags");
Ethan Nicholas762466e2017-06-29 10:03:38 -0400223 }
224 this->writef(")");
225 this->writeSection(INITIALIZERS_SECTION, "\n , ");
Ethan Nicholas68990be2017-07-13 09:36:52 -0400226 for (const auto& param : fSectionAndParameterHelper.getParameters()) {
Ethan Nicholas5b5f0962017-09-11 13:50:14 -0700227 String nameString(param->fName);
228 const char* name = nameString.c_str();
Ethan Nicholas762466e2017-06-29 10:03:38 -0400229 if (param->fType.kind() == Type::kSampler_Kind) {
Ethan Nicholas68990be2017-07-13 09:36:52 -0400230 this->writef("\n , %s(std::move(%s)", FieldName(name).c_str(), name);
231 for (const Section* s : fSectionAndParameterHelper.getSections(
232 SAMPLER_PARAMS_SECTION)) {
233 if (s->fArgument == name) {
234 this->writef(", %s", s->fText.c_str());
235 }
236 }
237 this->writef(")");
Ethan Nicholasc9472af2017-10-10 16:30:21 -0400238 } else if (param->fType == *fContext.fFragmentProcessor_Type) {
239 // do nothing
Ethan Nicholas762466e2017-06-29 10:03:38 -0400240 } else {
241 this->writef("\n , %s(%s)", FieldName(name).c_str(), name);
242 }
243 }
Ethan Nicholas929a6812018-08-06 14:56:59 -0400244 const auto transforms = fSectionAndParameterHelper.getSections(COORD_TRANSFORM_SECTION);
245 for (size_t i = 0; i < transforms.size(); ++i) {
246 const Section& s = *transforms[i];
247 String field = CoordTransformName(s.fArgument.c_str(), i);
248 if (s.fArgument.size()) {
249 this->writef("\n , %s(%s, %s.proxy())", field.c_str(), s.fText.c_str(),
250 FieldName(s.fArgument.c_str()).c_str());
251 }
252 else {
253 this->writef("\n , %s(%s)", field.c_str(), s.fText.c_str());
254 }
Ethan Nicholas68990be2017-07-13 09:36:52 -0400255 }
Ethan Nicholas762466e2017-06-29 10:03:38 -0400256 this->writef(" {\n");
257 this->writeSection(CONSTRUCTOR_CODE_SECTION);
Brian Salomonf7dcd762018-07-30 14:48:15 -0400258 int samplerCount = 0;
Ethan Nicholas68990be2017-07-13 09:36:52 -0400259 for (const auto& param : fSectionAndParameterHelper.getParameters()) {
Ethan Nicholas762466e2017-06-29 10:03:38 -0400260 if (param->fType.kind() == Type::kSampler_Kind) {
Brian Salomonf7dcd762018-07-30 14:48:15 -0400261 ++samplerCount;
Ethan Nicholasc9472af2017-10-10 16:30:21 -0400262 } else if (param->fType == *fContext.fFragmentProcessor_Type) {
263 this->writef(" this->registerChildProcessor(std::move(%s));",
264 String(param->fName).c_str());
Ethan Nicholas762466e2017-06-29 10:03:38 -0400265 }
266 }
Brian Salomonf7dcd762018-07-30 14:48:15 -0400267 if (samplerCount) {
268 this->writef(" this->setTextureSamplerCnt(%d);", samplerCount);
269 }
Ethan Nicholas929a6812018-08-06 14:56:59 -0400270 for (size_t i = 0; i < transforms.size(); ++i) {
271 const Section& s = *transforms[i];
272 String field = CoordTransformName(s.fArgument.c_str(), i);
273 this->writef(" this->addCoordTransform(&%s);\n", field.c_str());
Ethan Nicholas68990be2017-07-13 09:36:52 -0400274 }
Ethan Nicholasabff9562017-10-09 10:54:08 -0400275 this->writef(" }\n");
Ethan Nicholas762466e2017-06-29 10:03:38 -0400276}
277
278void HCodeGenerator::writeFields() {
279 this->writeSection(FIELDS_SECTION);
Ethan Nicholas68990be2017-07-13 09:36:52 -0400280 for (const auto& param : fSectionAndParameterHelper.getParameters()) {
Ethan Nicholasc9472af2017-10-10 16:30:21 -0400281 if (param->fType == *fContext.fFragmentProcessor_Type) {
282 continue;
283 }
Ethan Nicholasd608c092017-10-26 09:30:08 -0400284 this->writef(" %s %s;\n", FieldType(fContext, param->fType,
285 param->fModifiers.fLayout).c_str(),
Ethan Nicholas5b5f0962017-09-11 13:50:14 -0700286 FieldName(String(param->fName).c_str()).c_str());
Ethan Nicholas762466e2017-06-29 10:03:38 -0400287 }
Ethan Nicholas929a6812018-08-06 14:56:59 -0400288 const auto transforms = fSectionAndParameterHelper.getSections(COORD_TRANSFORM_SECTION);
289 for (size_t i = 0; i < transforms.size(); ++i) {
290 const Section& s = *transforms[i];
291 this->writef(" GrCoordTransform %s;\n",
292 CoordTransformName(s.fArgument.c_str(), i).c_str());
Ethan Nicholas68990be2017-07-13 09:36:52 -0400293 }
Ethan Nicholas762466e2017-06-29 10:03:38 -0400294}
295
Ethan Nicholas130fb3f2018-02-01 12:14:34 -0500296String HCodeGenerator::GetHeader(const Program& program, ErrorReporter& errors) {
297 SymbolTable types(&errors);
298 Parser parser(program.fSource->c_str(), program.fSource->length(), types, errors);
299 for (;;) {
300 Token header = parser.nextRawToken();
301 switch (header.fKind) {
302 case Token::WHITESPACE:
303 break;
304 case Token::BLOCK_COMMENT:
305 return String(program.fSource->c_str() + header.fOffset, header.fLength);
306 default:
307 return "";
308 }
309 }
310}
311
Ethan Nicholas762466e2017-06-29 10:03:38 -0400312bool HCodeGenerator::generateCode() {
Ethan Nicholas130fb3f2018-02-01 12:14:34 -0500313 this->writef("%s\n", GetHeader(fProgram, fErrors).c_str());
Ethan Nicholas762466e2017-06-29 10:03:38 -0400314 this->writef(kFragmentProcessorHeader, fFullName.c_str());
315 this->writef("#ifndef %s_DEFINED\n"
Ethan Nicholas9fb036f2017-07-05 16:19:09 -0400316 "#define %s_DEFINED\n",
317 fFullName.c_str(),
318 fFullName.c_str());
Greg Daniel3e8c3452018-04-06 10:37:55 -0400319 this->writef("#include \"SkTypes.h\"\n");
Ethan Nicholas762466e2017-06-29 10:03:38 -0400320 this->writeSection(HEADER_SECTION);
Ethan Nicholas9fb036f2017-07-05 16:19:09 -0400321 this->writef("#include \"GrFragmentProcessor.h\"\n"
Brian Osman1cb41712017-10-19 12:54:52 -0400322 "#include \"GrCoordTransform.h\"\n");
Ethan Nicholas762466e2017-06-29 10:03:38 -0400323 this->writef("class %s : public GrFragmentProcessor {\n"
324 "public:\n",
325 fFullName.c_str());
Ethan Nicholas3c6ae622018-04-24 13:06:09 -0400326 for (const auto& p : fProgram) {
327 if (ProgramElement::kEnum_Kind == p.fKind && !((Enum&) p).fBuiltin) {
328 this->writef("%s\n", p.description().c_str());
Ethan Nicholasaae47c82017-11-10 15:34:03 -0500329 }
330 }
Ethan Nicholase9d172a2017-11-20 12:12:24 -0500331 this->writeSection(CLASS_SECTION);
Ethan Nicholas68990be2017-07-13 09:36:52 -0400332 for (const auto& param : fSectionAndParameterHelper.getParameters()) {
Ethan Nicholasc9472af2017-10-10 16:30:21 -0400333 if (param->fType.kind() == Type::kSampler_Kind ||
334 param->fType.kind() == Type::kOther_Kind) {
Ethan Nicholas762466e2017-06-29 10:03:38 -0400335 continue;
336 }
Ethan Nicholas5b5f0962017-09-11 13:50:14 -0700337 String nameString(param->fName);
338 const char* name = nameString.c_str();
Ethan Nicholas9fb036f2017-07-05 16:19:09 -0400339 this->writef(" %s %s() const { return %s; }\n",
Michael Ludwiga4275592018-08-31 10:52:47 -0400340 AccessType(fContext, param->fType, param->fModifiers.fLayout).c_str(), name,
Ethan Nicholasd608c092017-10-26 09:30:08 -0400341 FieldName(name).c_str());
Ethan Nicholas762466e2017-06-29 10:03:38 -0400342 }
343 this->writeMake();
Ethan Nicholasf57c0d62017-07-31 11:18:22 -0400344 this->writef(" %s(const %s& src);\n"
Brian Salomonaff329b2017-08-11 09:40:37 -0400345 " std::unique_ptr<GrFragmentProcessor> clone() const override;\n"
Ethan Nicholasf57c0d62017-07-31 11:18:22 -0400346 " const char* name() const override { return \"%s\"; }\n"
Ethan Nicholas762466e2017-06-29 10:03:38 -0400347 "private:\n",
Ethan Nicholasf57c0d62017-07-31 11:18:22 -0400348 fFullName.c_str(), fFullName.c_str(), fName.c_str());
Ethan Nicholas762466e2017-06-29 10:03:38 -0400349 this->writeConstructor();
350 this->writef(" GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;\n"
351 " void onGetGLSLProcessorKey(const GrShaderCaps&,"
352 "GrProcessorKeyBuilder*) const override;\n"
Brian Salomonf7dcd762018-07-30 14:48:15 -0400353 " bool onIsEqual(const GrFragmentProcessor&) const override;\n");
354 for (const auto& param : fSectionAndParameterHelper.getParameters()) {
355 if (param->fType.kind() == Type::kSampler_Kind) {
356 this->writef(" const TextureSampler& onTextureSampler(int) const override;");
357 break;
358 }
359 }
360 this->writef(" GR_DECLARE_FRAGMENT_PROCESSOR_TEST\n");
Ethan Nicholas762466e2017-06-29 10:03:38 -0400361 this->writeFields();
362 this->writef(" typedef GrFragmentProcessor INHERITED;\n"
Ethan Nicholas9fb036f2017-07-05 16:19:09 -0400363 "};\n");
364 this->writeSection(HEADER_END_SECTION);
Greg Daniel3e8c3452018-04-06 10:37:55 -0400365 this->writef("#endif\n");
Ethan Nicholas762466e2017-06-29 10:03:38 -0400366 return 0 == fErrors.errorCount();
367}
368
369} // namespace