1 // Copyright 2018 The Amber Authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "src/amberscript/parser.h"
16 
17 #include <algorithm>
18 #include <cassert>
19 #include <limits>
20 #include <map>
21 #include <string>
22 #include <utility>
23 #include <vector>
24 
25 #include "src/image.h"
26 #include "src/make_unique.h"
27 #include "src/sampler.h"
28 #include "src/shader_data.h"
29 #include "src/tokenizer.h"
30 #include "src/type_parser.h"
31 
32 namespace amber {
33 namespace amberscript {
34 namespace {
35 
IsComparator(const std::string & in)36 bool IsComparator(const std::string& in) {
37   return in == "EQ" || in == "NE" || in == "GT" || in == "LT" || in == "GE" ||
38          in == "LE";
39 }
40 
ToComparator(const std::string & in)41 ProbeSSBOCommand::Comparator ToComparator(const std::string& in) {
42   if (in == "EQ")
43     return ProbeSSBOCommand::Comparator::kEqual;
44   if (in == "NE")
45     return ProbeSSBOCommand::Comparator::kNotEqual;
46   if (in == "GT")
47     return ProbeSSBOCommand::Comparator::kGreater;
48   if (in == "LT")
49     return ProbeSSBOCommand::Comparator::kLess;
50   if (in == "GE")
51     return ProbeSSBOCommand::Comparator::kGreaterOrEqual;
52 
53   assert(in == "LE");
54   return ProbeSSBOCommand::Comparator::kLessOrEqual;
55 }
56 
ToType(const std::string & str_in)57 std::unique_ptr<type::Type> ToType(const std::string& str_in) {
58   std::string str = str_in;
59 
60   bool is_array = false;
61   if (str.length() > 2 && str[str.length() - 2] == '[' &&
62       str[str.length() - 1] == ']') {
63     is_array = true;
64     str = str.substr(0, str.length() - 2);
65   }
66 
67   TypeParser parser;
68   std::unique_ptr<type::Type> type;
69   if (str == "int8") {
70     type = parser.Parse("R8_SINT");
71   } else if (str == "int16") {
72     type = parser.Parse("R16_SINT");
73   } else if (str == "int32") {
74     type = parser.Parse("R32_SINT");
75   } else if (str == "int64") {
76     type = parser.Parse("R64_SINT");
77   } else if (str == "uint8") {
78     type = parser.Parse("R8_UINT");
79   } else if (str == "uint16") {
80     type = parser.Parse("R16_UINT");
81   } else if (str == "uint32") {
82     type = parser.Parse("R32_UINT");
83   } else if (str == "uint64") {
84     type = parser.Parse("R64_UINT");
85   } else if (str == "float16") {
86     type = parser.Parse("R16_SFLOAT");
87   } else if (str == "float") {
88     type = parser.Parse("R32_SFLOAT");
89   } else if (str == "double") {
90     type = parser.Parse("R64_SFLOAT");
91   } else if (str.length() > 7 && str.substr(0, 3) == "vec") {
92     if (str[4] != '<' || str[str.length() - 1] != '>')
93       return nullptr;
94 
95     int component_count = str[3] - '0';
96     if (component_count < 2 || component_count > 4)
97       return nullptr;
98 
99     type = ToType(str.substr(5, str.length() - 6));
100     if (!type)
101       return nullptr;
102 
103     if (!type->IsNumber() || type->IsArray() || type->IsVec() ||
104         type->IsMatrix()) {
105       return nullptr;
106     }
107 
108     type->SetRowCount(static_cast<uint32_t>(component_count));
109   } else if (str.length() > 9 && str.substr(0, 3) == "mat") {
110     if (str[4] != 'x' || str[6] != '<' || str[str.length() - 1] != '>')
111       return nullptr;
112 
113     int column_count = str[3] - '0';
114     if (column_count < 2 || column_count > 4)
115       return nullptr;
116 
117     int row_count = str[5] - '0';
118     if (row_count < 2 || row_count > 4)
119       return nullptr;
120 
121     type = ToType(str.substr(7, str.length() - 8));
122     if (!type)
123       return nullptr;
124     if (!type->IsNumber() || type->IsArray() || type->IsVec() ||
125         type->IsMatrix()) {
126       return nullptr;
127     }
128 
129     type->SetRowCount(static_cast<uint32_t>(row_count));
130     type->SetColumnCount(static_cast<uint32_t>(column_count));
131   }
132 
133   if (!type)
134     return nullptr;
135   if (is_array)
136     type->SetIsRuntimeArray();
137 
138   return type;
139 }
140 
StrToAddressMode(std::string str)141 AddressMode StrToAddressMode(std::string str) {
142   if (str == "repeat")
143     return AddressMode::kRepeat;
144   if (str == "mirrored_repeat")
145     return AddressMode::kMirroredRepeat;
146   if (str == "clamp_to_edge")
147     return AddressMode::kClampToEdge;
148   if (str == "clamp_to_border")
149     return AddressMode::kClampToBorder;
150   if (str == "mirror_clamp_to_edge")
151     return AddressMode::kMirrorClampToEdge;
152 
153   return AddressMode::kUnknown;
154 }
155 
StrToCompareOp(const std::string & str)156 CompareOp StrToCompareOp(const std::string& str) {
157   if (str == "never")
158     return CompareOp::kNever;
159   if (str == "less")
160     return CompareOp::kLess;
161   if (str == "equal")
162     return CompareOp::kEqual;
163   if (str == "less_or_equal")
164     return CompareOp::kLessOrEqual;
165   if (str == "greater")
166     return CompareOp::kGreater;
167   if (str == "not_equal")
168     return CompareOp::kNotEqual;
169   if (str == "greater_or_equal")
170     return CompareOp::kGreaterOrEqual;
171   if (str == "always")
172     return CompareOp::kAlways;
173 
174   return CompareOp::kUnknown;
175 }
176 
StrToStencilOp(const std::string & str)177 StencilOp StrToStencilOp(const std::string& str) {
178   if (str == "keep")
179     return StencilOp::kKeep;
180   if (str == "zero")
181     return StencilOp::kZero;
182   if (str == "replace")
183     return StencilOp::kReplace;
184   if (str == "increment_and_clamp")
185     return StencilOp::kIncrementAndClamp;
186   if (str == "decrement_and_clamp")
187     return StencilOp::kDecrementAndClamp;
188   if (str == "invert")
189     return StencilOp::kInvert;
190   if (str == "increment_and_wrap")
191     return StencilOp::kIncrementAndWrap;
192   if (str == "decrement_and_wrap")
193     return StencilOp::kDecrementAndWrap;
194 
195   return StencilOp::kUnknown;
196 }
197 
ParseBufferData(Buffer * buffer,Tokenizer * tokenizer,bool from_data_file)198 Result ParseBufferData(Buffer* buffer,
199                        Tokenizer* tokenizer,
200                        bool from_data_file) {
201   auto fmt = buffer->GetFormat();
202   const auto& segs = fmt->GetSegments();
203   size_t seg_idx = 0;
204   uint32_t value_count = 0;
205 
206   std::vector<Value> values;
207   for (auto token = tokenizer->NextToken();; token = tokenizer->NextToken()) {
208     if (token->IsEOL())
209       continue;
210     if (token->IsEOS()) {
211       if (from_data_file) {
212         break;
213       } else {
214         return Result("missing BUFFER END command");
215       }
216     }
217     if (token->IsIdentifier() && token->AsString() == "END")
218       break;
219     if (!token->IsInteger() && !token->IsDouble() && !token->IsHex())
220       return Result("invalid BUFFER data value: " + token->ToOriginalString());
221 
222     while (segs[seg_idx].IsPadding()) {
223       ++seg_idx;
224       if (seg_idx >= segs.size())
225         seg_idx = 0;
226     }
227 
228     Value v;
229     if (type::Type::IsFloat(segs[seg_idx].GetFormatMode())) {
230       token->ConvertToDouble();
231 
232       double val = token->IsHex() ? static_cast<double>(token->AsHex())
233                                   : token->AsDouble();
234       v.SetDoubleValue(val);
235       ++value_count;
236     } else {
237       if (token->IsDouble()) {
238         return Result("invalid BUFFER data value: " +
239                       token->ToOriginalString());
240       }
241 
242       uint64_t val = token->IsHex() ? token->AsHex() : token->AsUint64();
243       v.SetIntValue(val);
244       ++value_count;
245     }
246     ++seg_idx;
247     if (seg_idx >= segs.size())
248       seg_idx = 0;
249 
250     values.emplace_back(v);
251   }
252   // Write final padding bytes
253   while (segs[seg_idx].IsPadding()) {
254     ++seg_idx;
255     if (seg_idx >= segs.size())
256       break;
257   }
258 
259   buffer->SetValueCount(value_count);
260   Result r = buffer->SetData(std::move(values));
261   if (!r.IsSuccess())
262     return r;
263 
264   return {};
265 }
266 
267 constexpr uint32_t valid_samples[] = {1, 2, 4, 8, 16, 32, 64};
268 
IsValidSampleCount(uint32_t samples)269 bool IsValidSampleCount(uint32_t samples) {
270   return (std::find(std::begin(valid_samples), std::end(valid_samples),
271                     samples) != std::end(valid_samples));
272 }
273 
274 }  // namespace
275 
Parser()276 Parser::Parser() : amber::Parser(nullptr) {}
Parser(Delegate * delegate)277 Parser::Parser(Delegate* delegate) : amber::Parser(delegate) {}
278 
279 Parser::~Parser() = default;
280 
make_error(const std::string & err)281 std::string Parser::make_error(const std::string& err) {
282   return std::to_string(tokenizer_->GetCurrentLine()) + ": " + err;
283 }
284 
Parse(const std::string & data)285 Result Parser::Parse(const std::string& data) {
286   tokenizer_ = MakeUnique<Tokenizer>(data);
287 
288   for (auto token = tokenizer_->NextToken(); !token->IsEOS();
289        token = tokenizer_->NextToken()) {
290     if (token->IsEOL())
291       continue;
292     if (!token->IsIdentifier())
293       return Result(make_error("expected identifier"));
294 
295     Result r;
296     std::string tok = token->AsString();
297     if (IsRepeatable(tok)) {
298       r = ParseRepeatableCommand(tok);
299     } else if (tok == "BUFFER") {
300       r = ParseBuffer();
301     } else if (tok == "DERIVE_PIPELINE") {
302       r = ParseDerivePipelineBlock();
303     } else if (tok == "DEVICE_FEATURE") {
304       r = ParseDeviceFeature();
305     } else if (tok == "DEVICE_EXTENSION") {
306       r = ParseDeviceExtension();
307     } else if (tok == "IMAGE") {
308       r = ParseImage();
309     } else if (tok == "INSTANCE_EXTENSION") {
310       r = ParseInstanceExtension();
311     } else if (tok == "PIPELINE") {
312       r = ParsePipelineBlock();
313     } else if (tok == "REPEAT") {
314       r = ParseRepeat();
315     } else if (tok == "SET") {
316       r = ParseSet();
317     } else if (tok == "SHADER") {
318       r = ParseShaderBlock();
319     } else if (tok == "STRUCT") {
320       r = ParseStruct();
321     } else if (tok == "SAMPLER") {
322       r = ParseSampler();
323     } else if (tok == "VIRTUAL_FILE") {
324       r = ParseVirtualFile();
325     } else {
326       r = Result("unknown token: " + tok);
327     }
328     if (!r.IsSuccess())
329       return Result(make_error(r.Error()));
330   }
331   script_->SetCommands(std::move(command_list_));
332 
333   // Generate any needed color attachments. This is done before
334   // validating in case one of the pipelines specifies the framebuffer size
335   // it needs to be verified against all other pipelines.
336   for (const auto& pipeline : script_->GetPipelines()) {
337     // Add a color attachment if needed
338     if (pipeline->GetColorAttachments().empty()) {
339       auto* buf = script_->GetBuffer(Pipeline::kGeneratedColorBuffer);
340       if (!buf) {
341         auto color_buf = pipeline->GenerateDefaultColorAttachmentBuffer();
342         buf = color_buf.get();
343 
344         Result r = script_->AddBuffer(std::move(color_buf));
345         if (!r.IsSuccess())
346           return r;
347       }
348       Result r = pipeline->AddColorAttachment(buf, 0, 0);
349       if (!r.IsSuccess())
350         return r;
351     }
352   }
353 
354   // Validate all the pipelines at the end. This allows us to verify the
355   // framebuffer sizes are consistent over pipelines.
356   for (const auto& pipeline : script_->GetPipelines()) {
357     Result r = pipeline->Validate();
358     if (!r.IsSuccess())
359       return r;
360   }
361 
362   return {};
363 }
364 
IsRepeatable(const std::string & name) const365 bool Parser::IsRepeatable(const std::string& name) const {
366   return name == "CLEAR" || name == "CLEAR_COLOR" || name == "CLEAR_DEPTH" ||
367          name == "CLEAR_STENCIL" || name == "COPY" || name == "EXPECT" ||
368          name == "RUN";
369 }
370 
371 // The given |name| must be one of the repeatable commands or this method
372 // returns an error result.
ParseRepeatableCommand(const std::string & name)373 Result Parser::ParseRepeatableCommand(const std::string& name) {
374   if (name == "CLEAR")
375     return ParseClear();
376   if (name == "CLEAR_COLOR")
377     return ParseClearColor();
378   if (name == "CLEAR_DEPTH")
379     return ParseClearDepth();
380   if (name == "CLEAR_STENCIL")
381     return ParseClearStencil();
382   if (name == "COPY")
383     return ParseCopy();
384   if (name == "EXPECT")
385     return ParseExpect();
386   if (name == "RUN")
387     return ParseRun();
388 
389   return Result("invalid repeatable command: " + name);
390 }
391 
ToShaderType(const std::string & str,ShaderType * type)392 Result Parser::ToShaderType(const std::string& str, ShaderType* type) {
393   assert(type);
394 
395   if (str == "vertex")
396     *type = kShaderTypeVertex;
397   else if (str == "fragment")
398     *type = kShaderTypeFragment;
399   else if (str == "geometry")
400     *type = kShaderTypeGeometry;
401   else if (str == "tessellation_evaluation")
402     *type = kShaderTypeTessellationEvaluation;
403   else if (str == "tessellation_control")
404     *type = kShaderTypeTessellationControl;
405   else if (str == "compute")
406     *type = kShaderTypeCompute;
407   else if (str == "multi")
408     *type = kShaderTypeMulti;
409   else
410     return Result("unknown shader type: " + str);
411   return {};
412 }
413 
ToShaderFormat(const std::string & str,ShaderFormat * fmt)414 Result Parser::ToShaderFormat(const std::string& str, ShaderFormat* fmt) {
415   assert(fmt);
416 
417   if (str == "GLSL")
418     *fmt = kShaderFormatGlsl;
419   else if (str == "HLSL")
420     *fmt = kShaderFormatHlsl;
421   else if (str == "SPIRV-ASM")
422     *fmt = kShaderFormatSpirvAsm;
423   else if (str == "SPIRV-HEX")
424     *fmt = kShaderFormatSpirvHex;
425   else if (str == "OPENCL-C")
426     *fmt = kShaderFormatOpenCLC;
427   else
428     return Result("unknown shader format: " + str);
429   return {};
430 }
431 
ToPipelineType(const std::string & str,PipelineType * type)432 Result Parser::ToPipelineType(const std::string& str, PipelineType* type) {
433   assert(type);
434 
435   if (str == "compute")
436     *type = PipelineType::kCompute;
437   else if (str == "graphics")
438     *type = PipelineType::kGraphics;
439   else
440     return Result("unknown pipeline type: " + str);
441   return {};
442 }
443 
ValidateEndOfStatement(const std::string & name)444 Result Parser::ValidateEndOfStatement(const std::string& name) {
445   auto token = tokenizer_->NextToken();
446   if (token->IsEOL() || token->IsEOS())
447     return {};
448   return Result("extra parameters after " + name + ": " +
449                 token->ToOriginalString());
450 }
451 
ParseShaderBlock()452 Result Parser::ParseShaderBlock() {
453   auto token = tokenizer_->NextToken();
454   if (!token->IsIdentifier())
455     return Result("invalid token when looking for shader type");
456 
457   ShaderType type = kShaderTypeVertex;
458   Result r = ToShaderType(token->AsString(), &type);
459   if (!r.IsSuccess())
460     return r;
461 
462   auto shader = MakeUnique<Shader>(type);
463 
464   token = tokenizer_->NextToken();
465   if (!token->IsIdentifier())
466     return Result("invalid token when looking for shader name");
467 
468   shader->SetName(token->AsString());
469 
470   token = tokenizer_->NextToken();
471   if (!token->IsIdentifier())
472     return Result("invalid token when looking for shader format");
473 
474   std::string fmt = token->AsString();
475   if (fmt == "PASSTHROUGH") {
476     if (type != kShaderTypeVertex) {
477       return Result(
478           "invalid shader type for PASSTHROUGH. Only vertex "
479           "PASSTHROUGH allowed");
480     }
481     shader->SetFormat(kShaderFormatSpirvAsm);
482     shader->SetData(kPassThroughShader);
483     shader->SetTargetEnv("spv1.0");
484 
485     r = script_->AddShader(std::move(shader));
486     if (!r.IsSuccess())
487       return r;
488 
489     return ValidateEndOfStatement("SHADER PASSTHROUGH");
490   }
491 
492   ShaderFormat format = kShaderFormatGlsl;
493   r = ToShaderFormat(fmt, &format);
494   if (!r.IsSuccess())
495     return r;
496 
497   shader->SetFormat(format);
498 
499   token = tokenizer_->PeekNextToken();
500   if (token->IsIdentifier() && token->AsString() == "TARGET_ENV") {
501     tokenizer_->NextToken();
502     token = tokenizer_->NextToken();
503     if (!token->IsIdentifier() && !token->IsString())
504       return Result("expected target environment after TARGET_ENV");
505     shader->SetTargetEnv(token->AsString());
506   }
507 
508   token = tokenizer_->PeekNextToken();
509   if (token->IsIdentifier() && token->AsString() == "VIRTUAL_FILE") {
510     tokenizer_->NextToken();  // Skip VIRTUAL_FILE
511 
512     token = tokenizer_->NextToken();
513     if (!token->IsIdentifier() && !token->IsString())
514       return Result("expected virtual file path after VIRTUAL_FILE");
515 
516     auto path = token->AsString();
517 
518     std::string data;
519     r = script_->GetVirtualFile(path, &data);
520     if (!r.IsSuccess())
521       return r;
522 
523     shader->SetData(data);
524     shader->SetFilePath(path);
525 
526     r = script_->AddShader(std::move(shader));
527     if (!r.IsSuccess())
528       return r;
529 
530     return ValidateEndOfStatement("SHADER command");
531   }
532 
533   r = ValidateEndOfStatement("SHADER command");
534   if (!r.IsSuccess())
535     return r;
536 
537   std::string data = tokenizer_->ExtractToNext("END");
538   if (data.empty())
539     return Result("SHADER must not be empty");
540 
541   shader->SetData(data);
542 
543   auto path = "embedded-shaders/" + shader->GetName();
544   script_->AddVirtualFile(path, data);
545   shader->SetFilePath(path);
546 
547   token = tokenizer_->NextToken();
548   if (!token->IsIdentifier() || token->AsString() != "END")
549     return Result("SHADER missing END command");
550 
551   r = script_->AddShader(std::move(shader));
552   if (!r.IsSuccess())
553     return r;
554 
555   return ValidateEndOfStatement("END");
556 }
557 
ParsePipelineBlock()558 Result Parser::ParsePipelineBlock() {
559   auto token = tokenizer_->NextToken();
560   if (!token->IsIdentifier())
561     return Result("invalid token when looking for pipeline type");
562 
563   PipelineType type = PipelineType::kCompute;
564   Result r = ToPipelineType(token->AsString(), &type);
565   if (!r.IsSuccess())
566     return r;
567 
568   auto pipeline = MakeUnique<Pipeline>(type);
569 
570   token = tokenizer_->NextToken();
571   if (!token->IsIdentifier())
572     return Result("invalid token when looking for pipeline name");
573 
574   pipeline->SetName(token->AsString());
575 
576   r = ValidateEndOfStatement("PIPELINE command");
577   if (!r.IsSuccess())
578     return r;
579 
580   return ParsePipelineBody("PIPELINE", std::move(pipeline));
581 }
582 
ParsePipelineBody(const std::string & cmd_name,std::unique_ptr<Pipeline> pipeline)583 Result Parser::ParsePipelineBody(const std::string& cmd_name,
584                                  std::unique_ptr<Pipeline> pipeline) {
585   std::unique_ptr<Token> token;
586   for (token = tokenizer_->NextToken(); !token->IsEOS();
587        token = tokenizer_->NextToken()) {
588     if (token->IsEOL())
589       continue;
590     if (!token->IsIdentifier())
591       return Result("expected identifier");
592 
593     Result r;
594     std::string tok = token->AsString();
595     if (tok == "END") {
596       break;
597     } else if (tok == "ATTACH") {
598       r = ParsePipelineAttach(pipeline.get());
599     } else if (tok == "SHADER_OPTIMIZATION") {
600       r = ParsePipelineShaderOptimizations(pipeline.get());
601     } else if (tok == "FRAMEBUFFER_SIZE") {
602       r = ParsePipelineFramebufferSize(pipeline.get());
603     } else if (tok == "VIEWPORT") {
604       r = ParsePipelineViewport(pipeline.get());
605     } else if (tok == "BIND") {
606       r = ParsePipelineBind(pipeline.get());
607     } else if (tok == "VERTEX_DATA") {
608       r = ParsePipelineVertexData(pipeline.get());
609     } else if (tok == "INDEX_DATA") {
610       r = ParsePipelineIndexData(pipeline.get());
611     } else if (tok == "SET") {
612       r = ParsePipelineSet(pipeline.get());
613     } else if (tok == "COMPILE_OPTIONS") {
614       r = ParsePipelineShaderCompileOptions(pipeline.get());
615     } else if (tok == "POLYGON_MODE") {
616       r = ParsePipelinePolygonMode(pipeline.get());
617     } else if (tok == "DEPTH") {
618       r = ParsePipelineDepth(pipeline.get());
619     } else if (tok == "STENCIL") {
620       r = ParsePipelineStencil(pipeline.get());
621     } else if (tok == "SUBGROUP") {
622       r = ParsePipelineSubgroup(pipeline.get());
623     } else if (tok == "PATCH_CONTROL_POINTS") {
624       r = ParsePipelinePatchControlPoints(pipeline.get());
625     } else if (tok == "BLEND") {
626       r = ParsePipelineBlend(pipeline.get());
627     } else {
628       r = Result("unknown token in pipeline block: " + tok);
629     }
630     if (!r.IsSuccess())
631       return r;
632   }
633 
634   if (!token->IsIdentifier() || token->AsString() != "END")
635     return Result(cmd_name + " missing END command");
636 
637   Result r = script_->AddPipeline(std::move(pipeline));
638   if (!r.IsSuccess())
639     return r;
640 
641   return ValidateEndOfStatement("END");
642 }
643 
ParsePipelineAttach(Pipeline * pipeline)644 Result Parser::ParsePipelineAttach(Pipeline* pipeline) {
645   auto token = tokenizer_->NextToken();
646   if (!token->IsIdentifier())
647     return Result("invalid token in ATTACH command");
648 
649   auto* shader = script_->GetShader(token->AsString());
650   if (!shader)
651     return Result("unknown shader in ATTACH command");
652 
653   token = tokenizer_->NextToken();
654   if (token->IsEOL() || token->IsEOS()) {
655     if (shader->GetType() == kShaderTypeMulti)
656       return Result("multi shader ATTACH requires TYPE");
657 
658     Result r = pipeline->AddShader(shader, shader->GetType());
659     if (!r.IsSuccess())
660       return r;
661     return {};
662   }
663   if (!token->IsIdentifier())
664     return Result("invalid token after ATTACH");
665 
666   bool set_shader_type = false;
667   ShaderType shader_type = shader->GetType();
668   auto type = token->AsString();
669   if (type == "TYPE") {
670     token = tokenizer_->NextToken();
671     if (!token->IsIdentifier())
672       return Result("invalid type in ATTACH");
673 
674     Result r = ToShaderType(token->AsString(), &shader_type);
675     if (!r.IsSuccess())
676       return r;
677 
678     set_shader_type = true;
679 
680     token = tokenizer_->NextToken();
681     if (!token->IsIdentifier())
682       return Result("ATTACH TYPE requires an ENTRY_POINT");
683 
684     type = token->AsString();
685   }
686   if (set_shader_type && type != "ENTRY_POINT")
687     return Result("unknown ATTACH parameter: " + type);
688 
689   if (shader->GetType() == ShaderType::kShaderTypeMulti && !set_shader_type)
690     return Result("ATTACH missing TYPE for multi shader");
691 
692   Result r = pipeline->AddShader(shader, shader_type);
693   if (!r.IsSuccess())
694     return r;
695 
696   if (type == "ENTRY_POINT") {
697     token = tokenizer_->NextToken();
698     if (!token->IsIdentifier())
699       return Result("missing shader name in ATTACH ENTRY_POINT command");
700 
701     r = pipeline->SetShaderEntryPoint(shader, token->AsString());
702     if (!r.IsSuccess())
703       return r;
704 
705     token = tokenizer_->NextToken();
706   }
707 
708   while (true) {
709     if (token->IsIdentifier() && token->AsString() == "SPECIALIZE") {
710       r = ParseShaderSpecialization(pipeline);
711       if (!r.IsSuccess())
712         return r;
713 
714       token = tokenizer_->NextToken();
715     } else {
716       if (token->IsEOL() || token->IsEOS())
717         return {};
718       if (token->IsIdentifier())
719         return Result("unknown ATTACH parameter: " + token->AsString());
720       return Result("extra parameters after ATTACH command: " +
721                     token->ToOriginalString());
722     }
723   }
724 }
725 
ParseShaderSpecialization(Pipeline * pipeline)726 Result Parser::ParseShaderSpecialization(Pipeline* pipeline) {
727   auto token = tokenizer_->NextToken();
728   if (!token->IsInteger())
729     return Result("specialization ID must be an integer");
730 
731   auto spec_id = token->AsUint32();
732 
733   token = tokenizer_->NextToken();
734   if (!token->IsIdentifier() || token->AsString() != "AS")
735     return Result("expected AS as next token");
736 
737   token = tokenizer_->NextToken();
738   if (!token->IsIdentifier())
739     return Result("expected data type in SPECIALIZE subcommand");
740 
741   auto type = ToType(token->AsString());
742   if (!type)
743     return Result("invalid data type '" + token->AsString() + "' provided");
744   if (!type->IsNumber())
745     return Result("only numeric types are accepted for specialization values");
746 
747   auto num = type->AsNumber();
748 
749   token = tokenizer_->NextToken();
750   uint32_t value = 0;
751   if (type::Type::IsUint32(num->GetFormatMode(), num->NumBits()) ||
752       type::Type::IsInt32(num->GetFormatMode(), num->NumBits())) {
753     value = token->AsUint32();
754   } else if (type::Type::IsFloat32(num->GetFormatMode(), num->NumBits())) {
755     Result r = token->ConvertToDouble();
756     if (!r.IsSuccess())
757       return Result("value is not a floating point value");
758 
759     union {
760       uint32_t u;
761       float f;
762     } u;
763     u.f = token->AsFloat();
764     value = u.u;
765   } else {
766     return Result(
767         "only 32-bit types are currently accepted for specialization values");
768   }
769 
770   auto& shader = pipeline->GetShaders()[pipeline->GetShaders().size() - 1];
771   shader.AddSpecialization(spec_id, value);
772   return {};
773 }
774 
ParsePipelineShaderOptimizations(Pipeline * pipeline)775 Result Parser::ParsePipelineShaderOptimizations(Pipeline* pipeline) {
776   auto token = tokenizer_->NextToken();
777   if (!token->IsIdentifier())
778     return Result("missing shader name in SHADER_OPTIMIZATION command");
779 
780   auto* shader = script_->GetShader(token->AsString());
781   if (!shader)
782     return Result("unknown shader in SHADER_OPTIMIZATION command");
783 
784   token = tokenizer_->NextToken();
785   if (!token->IsEOL())
786     return Result("extra parameters after SHADER_OPTIMIZATION command: " +
787                   token->ToOriginalString());
788 
789   std::vector<std::string> optimizations;
790   while (true) {
791     token = tokenizer_->NextToken();
792     if (token->IsEOL())
793       continue;
794     if (token->IsEOS())
795       return Result("SHADER_OPTIMIZATION missing END command");
796     if (!token->IsIdentifier())
797       return Result("SHADER_OPTIMIZATION options must be identifiers");
798     if (token->AsString() == "END")
799       break;
800 
801     optimizations.push_back(token->AsString());
802   }
803 
804   Result r = pipeline->SetShaderOptimizations(shader, optimizations);
805   if (!r.IsSuccess())
806     return r;
807 
808   return ValidateEndOfStatement("SHADER_OPTIMIZATION command");
809 }
810 
ParsePipelineShaderCompileOptions(Pipeline * pipeline)811 Result Parser::ParsePipelineShaderCompileOptions(Pipeline* pipeline) {
812   auto token = tokenizer_->NextToken();
813   if (!token->IsIdentifier())
814     return Result("missing shader name in COMPILE_OPTIONS command");
815 
816   auto* shader = script_->GetShader(token->AsString());
817   if (!shader)
818     return Result("unknown shader in COMPILE_OPTIONS command");
819 
820   if (shader->GetFormat() != kShaderFormatOpenCLC) {
821     return Result("COMPILE_OPTIONS currently only supports OPENCL-C shaders");
822   }
823 
824   token = tokenizer_->NextToken();
825   if (!token->IsEOL())
826     return Result("extra parameters after COMPILE_OPTIONS command: " +
827                   token->ToOriginalString());
828 
829   std::vector<std::string> options;
830   while (true) {
831     token = tokenizer_->NextToken();
832     if (token->IsEOL())
833       continue;
834     if (token->IsEOS())
835       return Result("COMPILE_OPTIONS missing END command");
836     if (token->AsString() == "END")
837       break;
838 
839     options.push_back(token->AsString());
840   }
841 
842   Result r = pipeline->SetShaderCompileOptions(shader, options);
843   if (!r.IsSuccess())
844     return r;
845 
846   return ValidateEndOfStatement("COMPILE_OPTIONS command");
847 }
848 
ParsePipelineSubgroup(Pipeline * pipeline)849 Result Parser::ParsePipelineSubgroup(Pipeline* pipeline) {
850   auto token = tokenizer_->NextToken();
851   if (!token->IsIdentifier())
852     return Result("missing shader name in SUBGROUP command");
853 
854   auto* shader = script_->GetShader(token->AsString());
855   if (!shader)
856     return Result("unknown shader in SUBGROUP command");
857 
858   while (true) {
859     token = tokenizer_->NextToken();
860     if (token->IsEOL())
861       continue;
862     if (token->IsEOS())
863       return Result("SUBGROUP missing END command");
864     if (!token->IsIdentifier())
865       return Result("SUBGROUP options must be identifiers");
866     if (token->AsString() == "END")
867       break;
868 
869     if (token->AsString() == "FULLY_POPULATED") {
870       if (!script_->IsRequiredFeature(
871               "SubgroupSizeControl.computeFullSubgroups"))
872         return Result(
873             "missing DEVICE_FEATURE SubgroupSizeControl.computeFullSubgroups");
874       token = tokenizer_->NextToken();
875       if (token->IsEOL() || token->IsEOS())
876         return Result("missing value for FULLY_POPULATED command");
877       bool isOn = false;
878       if (token->AsString() == "on") {
879         isOn = true;
880       } else if (token->AsString() == "off") {
881         isOn = false;
882       } else {
883         return Result("invalid value for FULLY_POPULATED command");
884       }
885       Result r = pipeline->SetShaderRequireFullSubgroups(shader, isOn);
886       if (!r.IsSuccess())
887         return r;
888 
889     } else if (token->AsString() == "VARYING_SIZE") {
890       if (!script_->IsRequiredFeature(
891               "SubgroupSizeControl.subgroupSizeControl"))
892         return Result(
893             "missing DEVICE_FEATURE SubgroupSizeControl.subgroupSizeControl");
894       token = tokenizer_->NextToken();
895       if (token->IsEOL() || token->IsEOS())
896         return Result("missing value for VARYING_SIZE command");
897       bool isOn = false;
898       if (token->AsString() == "on") {
899         isOn = true;
900       } else if (token->AsString() == "off") {
901         isOn = false;
902       } else {
903         return Result("invalid value for VARYING_SIZE command");
904       }
905       Result r = pipeline->SetShaderVaryingSubgroupSize(shader, isOn);
906       if (!r.IsSuccess())
907         return r;
908     } else if (token->AsString() == "REQUIRED_SIZE") {
909       if (!script_->IsRequiredFeature(
910               "SubgroupSizeControl.subgroupSizeControl"))
911         return Result(
912             "missing DEVICE_FEATURE SubgroupSizeControl.subgroupSizeControl");
913       token = tokenizer_->NextToken();
914       if (token->IsEOL() || token->IsEOS())
915         return Result("missing size for REQUIRED_SIZE command");
916       Result r;
917       if (token->IsInteger()) {
918         r = pipeline->SetShaderRequiredSubgroupSize(shader, token->AsUint32());
919       } else if (token->AsString() == "MIN") {
920         r = pipeline->SetShaderRequiredSubgroupSizeToMinimum(shader);
921       } else if (token->AsString() == "MAX") {
922         r = pipeline->SetShaderRequiredSubgroupSizeToMaximum(shader);
923       } else {
924         return Result("invalid size for REQUIRED_SIZE command");
925       }
926       if (!r.IsSuccess())
927         return r;
928     } else {
929       return Result("SUBGROUP invalid value for SUBGROUP " + token->AsString());
930     }
931   }
932 
933   return ValidateEndOfStatement("SUBGROUP command");
934 }
935 
ParsePipelinePatchControlPoints(Pipeline * pipeline)936 Result Parser::ParsePipelinePatchControlPoints(Pipeline* pipeline) {
937   auto token = tokenizer_->NextToken();
938   if (token->IsEOL() || token->IsEOS())
939     return Result(
940         "missing number of control points in PATCH_CONTROL_POINTS command");
941 
942   if (!token->IsInteger())
943     return Result("expecting integer for the number of control points");
944 
945   pipeline->GetPipelineData()->SetPatchControlPoints(token->AsUint32());
946 
947   return ValidateEndOfStatement("PATCH_CONTROL_POINTS command");
948 }
949 
ParsePipelineFramebufferSize(Pipeline * pipeline)950 Result Parser::ParsePipelineFramebufferSize(Pipeline* pipeline) {
951   auto token = tokenizer_->NextToken();
952   if (token->IsEOL() || token->IsEOS())
953     return Result("missing size for FRAMEBUFFER_SIZE command");
954   if (!token->IsInteger())
955     return Result("invalid width for FRAMEBUFFER_SIZE command");
956 
957   pipeline->SetFramebufferWidth(token->AsUint32());
958 
959   token = tokenizer_->NextToken();
960   if (token->IsEOL() || token->IsEOS())
961     return Result("missing height for FRAMEBUFFER_SIZE command");
962   if (!token->IsInteger())
963     return Result("invalid height for FRAMEBUFFER_SIZE command");
964 
965   pipeline->SetFramebufferHeight(token->AsUint32());
966 
967   return ValidateEndOfStatement("FRAMEBUFFER_SIZE command");
968 }
969 
ParsePipelineViewport(Pipeline * pipeline)970 Result Parser::ParsePipelineViewport(Pipeline* pipeline) {
971   Viewport vp;
972   vp.mind = 0.0f;
973   vp.maxd = 1.0f;
974 
975   float val[2];
976   for (int i = 0; i < 2; i++) {
977     auto token = tokenizer_->NextToken();
978     if (token->IsEOL() || token->IsEOS())
979       return Result("missing offset for VIEWPORT command");
980     Result r = token->ConvertToDouble();
981     if (!r.IsSuccess())
982       return Result("invalid offset for VIEWPORT command");
983 
984     val[i] = token->AsFloat();
985   }
986   vp.x = val[0];
987   vp.y = val[1];
988 
989   auto token = tokenizer_->NextToken();
990   if (!token->IsIdentifier() || token->AsString() != "SIZE")
991     return Result("missing SIZE for VIEWPORT command");
992 
993   for (int i = 0; i < 2; i++) {
994     token = tokenizer_->NextToken();
995     if (token->IsEOL() || token->IsEOS())
996       return Result("missing size for VIEWPORT command");
997     Result r = token->ConvertToDouble();
998     if (!r.IsSuccess())
999       return Result("invalid size for VIEWPORT command");
1000 
1001     val[i] = token->AsFloat();
1002   }
1003   vp.w = val[0];
1004   vp.h = val[1];
1005 
1006   token = tokenizer_->PeekNextToken();
1007   while (token->IsIdentifier()) {
1008     if (token->AsString() == "MIN_DEPTH") {
1009       tokenizer_->NextToken();
1010       token = tokenizer_->NextToken();
1011       if (token->IsEOL() || token->IsEOS())
1012         return Result("missing min_depth for VIEWPORT command");
1013       Result r = token->ConvertToDouble();
1014       if (!r.IsSuccess())
1015         return Result("invalid min_depth for VIEWPORT command");
1016 
1017       vp.mind = token->AsFloat();
1018     }
1019     if (token->AsString() == "MAX_DEPTH") {
1020       tokenizer_->NextToken();
1021       token = tokenizer_->NextToken();
1022       if (token->IsEOL() || token->IsEOS())
1023         return Result("missing max_depth for VIEWPORT command");
1024       Result r = token->ConvertToDouble();
1025       if (!r.IsSuccess())
1026         return Result("invalid max_depth for VIEWPORT command");
1027 
1028       vp.maxd = token->AsFloat();
1029     }
1030 
1031     token = tokenizer_->PeekNextToken();
1032   }
1033 
1034   pipeline->GetPipelineData()->SetViewport(vp);
1035 
1036   return ValidateEndOfStatement("VIEWPORT command");
1037 }
1038 
ToBufferType(const std::string & name,BufferType * type)1039 Result Parser::ToBufferType(const std::string& name, BufferType* type) {
1040   assert(type);
1041   if (name == "color")
1042     *type = BufferType::kColor;
1043   else if (name == "depth_stencil")
1044     *type = BufferType::kDepthStencil;
1045   else if (name == "push_constant")
1046     *type = BufferType::kPushConstant;
1047   else if (name == "uniform")
1048     *type = BufferType::kUniform;
1049   else if (name == "uniform_dynamic")
1050     *type = BufferType::kUniformDynamic;
1051   else if (name == "storage")
1052     *type = BufferType::kStorage;
1053   else if (name == "storage_dynamic")
1054     *type = BufferType::kStorageDynamic;
1055   else if (name == "storage_image")
1056     *type = BufferType::kStorageImage;
1057   else if (name == "sampled_image")
1058     *type = BufferType::kSampledImage;
1059   else if (name == "combined_image_sampler")
1060     *type = BufferType::kCombinedImageSampler;
1061   else if (name == "uniform_texel_buffer")
1062     *type = BufferType::kUniformTexelBuffer;
1063   else if (name == "storage_texel_buffer")
1064     *type = BufferType::kStorageTexelBuffer;
1065   else if (name == "resolve")
1066     *type = BufferType::kResolve;
1067   else
1068     return Result("unknown buffer_type: " + name);
1069 
1070   return {};
1071 }
1072 
ParsePipelineBind(Pipeline * pipeline)1073 Result Parser::ParsePipelineBind(Pipeline* pipeline) {
1074   auto token = tokenizer_->NextToken();
1075 
1076   if (!token->IsIdentifier()) {
1077     return Result(
1078         "missing BUFFER, BUFFER_ARRAY, SAMPLER, or SAMPLER_ARRAY in BIND "
1079         "command");
1080   }
1081 
1082   auto object_type = token->AsString();
1083 
1084   if (object_type == "BUFFER" || object_type == "BUFFER_ARRAY") {
1085     bool is_buffer_array = object_type == "BUFFER_ARRAY";
1086     token = tokenizer_->NextToken();
1087     if (!token->IsIdentifier())
1088       return Result("missing buffer name in BIND command");
1089 
1090     auto* buffer = script_->GetBuffer(token->AsString());
1091     if (!buffer)
1092       return Result("unknown buffer: " + token->AsString());
1093     std::vector<Buffer*> buffers = {buffer};
1094 
1095     if (is_buffer_array) {
1096       // Check for additional buffer names
1097       token = tokenizer_->PeekNextToken();
1098       while (token->IsIdentifier() && token->AsString() != "AS" &&
1099              token->AsString() != "KERNEL" &&
1100              token->AsString() != "DESCRIPTOR_SET") {
1101         tokenizer_->NextToken();
1102         buffer = script_->GetBuffer(token->AsString());
1103         if (!buffer)
1104           return Result("unknown buffer: " + token->AsString());
1105         buffers.push_back(buffer);
1106         token = tokenizer_->PeekNextToken();
1107       }
1108 
1109       if (buffers.size() < 2)
1110         return Result("expecting multiple buffer names for BUFFER_ARRAY");
1111     }
1112 
1113     BufferType buffer_type = BufferType::kUnknown;
1114     token = tokenizer_->NextToken();
1115     if (token->IsIdentifier() && token->AsString() == "AS") {
1116       token = tokenizer_->NextToken();
1117       if (!token->IsIdentifier())
1118         return Result("invalid token for BUFFER type");
1119 
1120       Result r = ToBufferType(token->AsString(), &buffer_type);
1121       if (!r.IsSuccess())
1122         return r;
1123 
1124       if (buffer_type == BufferType::kColor) {
1125         token = tokenizer_->NextToken();
1126         if (!token->IsIdentifier() || token->AsString() != "LOCATION")
1127           return Result("BIND missing LOCATION");
1128 
1129         token = tokenizer_->NextToken();
1130         if (!token->IsInteger())
1131           return Result("invalid value for BIND LOCATION");
1132         auto location = token->AsUint32();
1133 
1134         uint32_t base_mip_level = 0;
1135         token = tokenizer_->PeekNextToken();
1136         if (token->IsIdentifier() && token->AsString() == "BASE_MIP_LEVEL") {
1137           tokenizer_->NextToken();
1138           token = tokenizer_->NextToken();
1139 
1140           if (!token->IsInteger())
1141             return Result("invalid value for BASE_MIP_LEVEL");
1142 
1143           base_mip_level = token->AsUint32();
1144 
1145           if (base_mip_level >= buffer->GetMipLevels())
1146             return Result(
1147                 "base mip level (now " + token->AsString() +
1148                 ") needs to be larger than the number of buffer mip maps (" +
1149                 std::to_string(buffer->GetMipLevels()) + ")");
1150         }
1151 
1152         r = pipeline->AddColorAttachment(buffer, location, base_mip_level);
1153         if (!r.IsSuccess())
1154           return r;
1155 
1156       } else if (buffer_type == BufferType::kDepthStencil) {
1157         r = pipeline->SetDepthStencilBuffer(buffer);
1158         if (!r.IsSuccess())
1159           return r;
1160 
1161       } else if (buffer_type == BufferType::kPushConstant) {
1162         r = pipeline->SetPushConstantBuffer(buffer);
1163         if (!r.IsSuccess())
1164           return r;
1165 
1166       } else if (buffer_type == BufferType::kCombinedImageSampler) {
1167         token = tokenizer_->NextToken();
1168         if (!token->IsIdentifier() || token->AsString() != "SAMPLER")
1169           return Result("expecting SAMPLER for combined image sampler");
1170 
1171         token = tokenizer_->NextToken();
1172         if (!token->IsIdentifier())
1173           return Result("missing sampler name in BIND command");
1174 
1175         auto* sampler = script_->GetSampler(token->AsString());
1176         if (!sampler)
1177           return Result("unknown sampler: " + token->AsString());
1178 
1179         for (auto& buf : buffers)
1180           buf->SetSampler(sampler);
1181       } else if (buffer_type == BufferType::kResolve) {
1182         r = pipeline->AddResolveTarget(buffer);
1183       }
1184     }
1185 
1186     // The OpenCL bindings can be typeless which allows for the kUnknown
1187     // buffer type.
1188     if (buffer_type == BufferType::kUnknown ||
1189         buffer_type == BufferType::kStorage ||
1190         buffer_type == BufferType::kUniform ||
1191         buffer_type == BufferType::kStorageDynamic ||
1192         buffer_type == BufferType::kUniformDynamic ||
1193         buffer_type == BufferType::kStorageImage ||
1194         buffer_type == BufferType::kSampledImage ||
1195         buffer_type == BufferType::kCombinedImageSampler ||
1196         buffer_type == BufferType::kUniformTexelBuffer ||
1197         buffer_type == BufferType::kStorageTexelBuffer) {
1198       // If the buffer type is known, then we proccessed the AS block above
1199       // and have to advance to the next token. Otherwise, we're already on
1200       // the next token and don't want to advance.
1201       if (buffer_type != BufferType::kUnknown)
1202         token = tokenizer_->NextToken();
1203 
1204       // DESCRIPTOR_SET requires a buffer type to have been specified.
1205       if (token->IsIdentifier() && token->AsString() == "DESCRIPTOR_SET") {
1206         token = tokenizer_->NextToken();
1207         if (!token->IsInteger())
1208           return Result("invalid value for DESCRIPTOR_SET in BIND command");
1209         uint32_t descriptor_set = token->AsUint32();
1210 
1211         token = tokenizer_->NextToken();
1212         if (!token->IsIdentifier() || token->AsString() != "BINDING")
1213           return Result("missing BINDING for BIND command");
1214 
1215         token = tokenizer_->NextToken();
1216         if (!token->IsInteger())
1217           return Result("invalid value for BINDING in BIND command");
1218 
1219         auto binding = token->AsUint32();
1220         uint32_t base_mip_level = 0;
1221 
1222         if (buffer_type == BufferType::kStorageImage ||
1223             buffer_type == BufferType::kSampledImage ||
1224             buffer_type == BufferType::kCombinedImageSampler) {
1225           token = tokenizer_->PeekNextToken();
1226           if (token->IsIdentifier() && token->AsString() == "BASE_MIP_LEVEL") {
1227             tokenizer_->NextToken();
1228             token = tokenizer_->NextToken();
1229 
1230             if (!token->IsInteger())
1231               return Result("invalid value for BASE_MIP_LEVEL");
1232 
1233             base_mip_level = token->AsUint32();
1234 
1235             if (base_mip_level >= buffer->GetMipLevels())
1236               return Result("base mip level (now " + token->AsString() +
1237                             ") needs to be larger than the number of buffer "
1238                             "mip maps (" +
1239                             std::to_string(buffer->GetMipLevels()) + ")");
1240           }
1241         }
1242 
1243         std::vector<uint32_t> dynamic_offsets(buffers.size(), 0);
1244         if (buffer_type == BufferType::kUniformDynamic ||
1245             buffer_type == BufferType::kStorageDynamic) {
1246           token = tokenizer_->NextToken();
1247           if (!token->IsIdentifier() || token->AsString() != "OFFSET")
1248             return Result("expecting an OFFSET for dynamic buffer type");
1249 
1250           for (size_t i = 0; i < buffers.size(); i++) {
1251             token = tokenizer_->NextToken();
1252 
1253             if (!token->IsInteger()) {
1254               if (i > 0) {
1255                 return Result(
1256                     "expecting an OFFSET value for each buffer in the array");
1257               } else {
1258                 return Result("expecting an integer value for OFFSET");
1259               }
1260             }
1261 
1262             dynamic_offsets[i] = token->AsUint32();
1263           }
1264         }
1265 
1266         // Set default descriptor buffer offsets to 0 and descriptor buffer
1267         // ranges to VK_WHOLE_SIZE (~0ULL).
1268         std::vector<uint64_t> descriptor_offsets(buffers.size(), 0);
1269         std::vector<uint64_t> descriptor_ranges(buffers.size(), ~0ULL);
1270         if (buffer_type == BufferType::kUniformDynamic ||
1271             buffer_type == BufferType::kStorageDynamic ||
1272             buffer_type == BufferType::kStorage ||
1273             buffer_type == BufferType::kUniform) {
1274           token = tokenizer_->PeekNextToken();
1275           if (token->IsIdentifier() &&
1276               token->AsString() == "DESCRIPTOR_OFFSET") {
1277             token = tokenizer_->NextToken();
1278             for (size_t i = 0; i < buffers.size(); i++) {
1279               token = tokenizer_->NextToken();
1280               if (!token->IsInteger()) {
1281                 if (i > 0) {
1282                   return Result(
1283                       "expecting a DESCRIPTOR_OFFSET value for each buffer in "
1284                       "the array");
1285                 } else {
1286                   return Result(
1287                       "expecting an integer value for DESCRIPTOR_OFFSET");
1288                 }
1289               }
1290               descriptor_offsets[i] = token->AsUint64();
1291             }
1292           }
1293 
1294           token = tokenizer_->PeekNextToken();
1295           if (token->IsIdentifier() &&
1296               token->AsString() == "DESCRIPTOR_RANGE") {
1297             token = tokenizer_->NextToken();
1298             for (size_t i = 0; i < buffers.size(); i++) {
1299               token = tokenizer_->NextToken();
1300               if (!token->IsInteger()) {
1301                 if (i > 0) {
1302                   return Result(
1303                       "expecting a DESCRIPTOR_RANGE value for each buffer in "
1304                       "the array");
1305                 } else {
1306                   return Result(
1307                       "expecting an integer value for DESCRIPTOR_RANGE");
1308                 }
1309               }
1310               descriptor_ranges[i] = token->AsUint64();
1311             }
1312           }
1313         }
1314 
1315         pipeline->ClearBuffers(descriptor_set, binding);
1316         for (size_t i = 0; i < buffers.size(); i++) {
1317           pipeline->AddBuffer(buffers[i], buffer_type, descriptor_set, binding,
1318                               base_mip_level, dynamic_offsets[i],
1319                               descriptor_offsets[i], descriptor_ranges[i]);
1320         }
1321       } else if (token->IsIdentifier() && token->AsString() == "KERNEL") {
1322         token = tokenizer_->NextToken();
1323         if (!token->IsIdentifier())
1324           return Result("missing kernel arg identifier");
1325 
1326         if (token->AsString() == "ARG_NAME") {
1327           token = tokenizer_->NextToken();
1328           if (!token->IsIdentifier())
1329             return Result("expected argument identifier");
1330 
1331           pipeline->AddBuffer(buffer, buffer_type, token->AsString());
1332         } else if (token->AsString() == "ARG_NUMBER") {
1333           token = tokenizer_->NextToken();
1334           if (!token->IsInteger())
1335             return Result("expected argument number");
1336 
1337           pipeline->AddBuffer(buffer, buffer_type, token->AsUint32());
1338         } else {
1339           return Result("missing ARG_NAME or ARG_NUMBER keyword");
1340         }
1341       } else {
1342         return Result("missing DESCRIPTOR_SET or KERNEL for BIND command");
1343       }
1344     }
1345   } else if (object_type == "SAMPLER" || object_type == "SAMPLER_ARRAY") {
1346     bool is_sampler_array = object_type == "SAMPLER_ARRAY";
1347     token = tokenizer_->NextToken();
1348     if (!token->IsIdentifier())
1349       return Result("missing sampler name in BIND command");
1350 
1351     auto* sampler = script_->GetSampler(token->AsString());
1352     if (!sampler)
1353       return Result("unknown sampler: " + token->AsString());
1354     std::vector<Sampler*> samplers = {sampler};
1355 
1356     if (is_sampler_array) {
1357       // Check for additional sampler names
1358       token = tokenizer_->PeekNextToken();
1359       while (token->IsIdentifier() && token->AsString() != "KERNEL" &&
1360              token->AsString() != "DESCRIPTOR_SET") {
1361         tokenizer_->NextToken();
1362         sampler = script_->GetSampler(token->AsString());
1363         if (!sampler)
1364           return Result("unknown sampler: " + token->AsString());
1365         samplers.push_back(sampler);
1366         token = tokenizer_->PeekNextToken();
1367       }
1368 
1369       if (samplers.size() < 2)
1370         return Result("expecting multiple sampler names for SAMPLER_ARRAY");
1371     }
1372 
1373     token = tokenizer_->NextToken();
1374     if (!token->IsIdentifier())
1375       return Result("expected a string token for BIND command");
1376 
1377     if (token->AsString() == "DESCRIPTOR_SET") {
1378       token = tokenizer_->NextToken();
1379       if (!token->IsInteger())
1380         return Result("invalid value for DESCRIPTOR_SET in BIND command");
1381       uint32_t descriptor_set = token->AsUint32();
1382 
1383       token = tokenizer_->NextToken();
1384       if (!token->IsIdentifier() || token->AsString() != "BINDING")
1385         return Result("missing BINDING for BIND command");
1386 
1387       token = tokenizer_->NextToken();
1388       if (!token->IsInteger())
1389         return Result("invalid value for BINDING in BIND command");
1390 
1391       uint32_t binding = token->AsUint32();
1392       pipeline->ClearSamplers(descriptor_set, binding);
1393       for (const auto& s : samplers) {
1394         pipeline->AddSampler(s, descriptor_set, binding);
1395       }
1396     } else if (token->AsString() == "KERNEL") {
1397       token = tokenizer_->NextToken();
1398       if (!token->IsIdentifier())
1399         return Result("missing kernel arg identifier");
1400 
1401       if (token->AsString() == "ARG_NAME") {
1402         token = tokenizer_->NextToken();
1403         if (!token->IsIdentifier())
1404           return Result("expected argument identifier");
1405 
1406         pipeline->AddSampler(sampler, token->AsString());
1407       } else if (token->AsString() == "ARG_NUMBER") {
1408         token = tokenizer_->NextToken();
1409         if (!token->IsInteger())
1410           return Result("expected argument number");
1411 
1412         pipeline->AddSampler(sampler, token->AsUint32());
1413       } else {
1414         return Result("missing ARG_NAME or ARG_NUMBER keyword");
1415       }
1416     } else {
1417       return Result("missing DESCRIPTOR_SET or KERNEL for BIND command");
1418     }
1419   } else {
1420     return Result("missing BUFFER or SAMPLER in BIND command");
1421   }
1422 
1423   return ValidateEndOfStatement("BIND command");
1424 }
1425 
ParsePipelineVertexData(Pipeline * pipeline)1426 Result Parser::ParsePipelineVertexData(Pipeline* pipeline) {
1427   auto token = tokenizer_->NextToken();
1428   if (!token->IsIdentifier())
1429     return Result("missing buffer name in VERTEX_DATA command");
1430 
1431   auto* buffer = script_->GetBuffer(token->AsString());
1432   if (!buffer)
1433     return Result("unknown buffer: " + token->AsString());
1434 
1435   token = tokenizer_->NextToken();
1436   if (!token->IsIdentifier() || token->AsString() != "LOCATION")
1437     return Result("VERTEX_DATA missing LOCATION");
1438 
1439   token = tokenizer_->NextToken();
1440   if (!token->IsInteger())
1441     return Result("invalid value for VERTEX_DATA LOCATION");
1442   const uint32_t location = token->AsUint32();
1443 
1444   InputRate rate = InputRate::kVertex;
1445   uint32_t offset = 0;
1446   Format* format = buffer->GetFormat();
1447   uint32_t stride = 0;
1448 
1449   token = tokenizer_->PeekNextToken();
1450   while (token->IsIdentifier()) {
1451     if (token->AsString() == "RATE") {
1452       tokenizer_->NextToken();
1453       token = tokenizer_->NextToken();
1454       if (!token->IsIdentifier())
1455         return Result("missing input rate value for RATE");
1456       if (token->AsString() == "instance") {
1457         rate = InputRate::kInstance;
1458       } else if (token->AsString() != "vertex") {
1459         return Result("expecting 'vertex' or 'instance' for RATE value");
1460       }
1461     } else if (token->AsString() == "OFFSET") {
1462       tokenizer_->NextToken();
1463       token = tokenizer_->NextToken();
1464       if (!token->IsInteger())
1465         return Result("expected unsigned integer for OFFSET");
1466       offset = token->AsUint32();
1467     } else if (token->AsString() == "STRIDE") {
1468       tokenizer_->NextToken();
1469       token = tokenizer_->NextToken();
1470       if (!token->IsInteger())
1471         return Result("expected unsigned integer for STRIDE");
1472       stride = token->AsUint32();
1473       if (stride == 0)
1474         return Result("STRIDE needs to be larger than zero");
1475     } else if (token->AsString() == "FORMAT") {
1476       tokenizer_->NextToken();
1477       token = tokenizer_->NextToken();
1478       if (!token->IsIdentifier())
1479         return Result("vertex data FORMAT must be an identifier");
1480       auto type = script_->ParseType(token->AsString());
1481       if (!type)
1482         return Result("invalid vertex data FORMAT");
1483       auto fmt = MakeUnique<Format>(type);
1484       format = fmt.get();
1485       script_->RegisterFormat(std::move(fmt));
1486     } else {
1487       return Result("unexpected identifier for VERTEX_DATA command: " +
1488                     token->ToOriginalString());
1489     }
1490 
1491     token = tokenizer_->PeekNextToken();
1492   }
1493 
1494   if (stride == 0)
1495     stride = format->SizeInBytes();
1496 
1497   Result r =
1498       pipeline->AddVertexBuffer(buffer, location, rate, format, offset, stride);
1499   if (!r.IsSuccess())
1500     return r;
1501 
1502   return ValidateEndOfStatement("VERTEX_DATA command");
1503 }
1504 
ParsePipelineIndexData(Pipeline * pipeline)1505 Result Parser::ParsePipelineIndexData(Pipeline* pipeline) {
1506   auto token = tokenizer_->NextToken();
1507   if (!token->IsIdentifier())
1508     return Result("missing buffer name in INDEX_DATA command");
1509 
1510   auto* buffer = script_->GetBuffer(token->AsString());
1511   if (!buffer)
1512     return Result("unknown buffer: " + token->AsString());
1513 
1514   Result r = pipeline->SetIndexBuffer(buffer);
1515   if (!r.IsSuccess())
1516     return r;
1517 
1518   return ValidateEndOfStatement("INDEX_DATA command");
1519 }
1520 
ParsePipelineSet(Pipeline * pipeline)1521 Result Parser::ParsePipelineSet(Pipeline* pipeline) {
1522   if (pipeline->GetShaders().empty() ||
1523       pipeline->GetShaders()[0].GetShader()->GetFormat() !=
1524           kShaderFormatOpenCLC) {
1525     return Result("SET can only be used with OPENCL-C shaders");
1526   }
1527 
1528   auto token = tokenizer_->NextToken();
1529   if (!token->IsIdentifier() || token->AsString() != "KERNEL")
1530     return Result("missing KERNEL in SET command");
1531 
1532   token = tokenizer_->NextToken();
1533   if (!token->IsIdentifier())
1534     return Result("expected ARG_NAME or ARG_NUMBER");
1535 
1536   std::string arg_name = "";
1537   uint32_t arg_no = std::numeric_limits<uint32_t>::max();
1538   if (token->AsString() == "ARG_NAME") {
1539     token = tokenizer_->NextToken();
1540     if (!token->IsIdentifier())
1541       return Result("expected argument identifier");
1542 
1543     arg_name = token->AsString();
1544   } else if (token->AsString() == "ARG_NUMBER") {
1545     token = tokenizer_->NextToken();
1546     if (!token->IsInteger())
1547       return Result("expected argument number");
1548 
1549     arg_no = token->AsUint32();
1550   } else {
1551     return Result("expected ARG_NAME or ARG_NUMBER");
1552   }
1553 
1554   token = tokenizer_->NextToken();
1555   if (!token->IsIdentifier() || token->AsString() != "AS")
1556     return Result("missing AS in SET command");
1557 
1558   token = tokenizer_->NextToken();
1559   if (!token->IsIdentifier())
1560     return Result("expected data type");
1561 
1562   auto type = ToType(token->AsString());
1563   if (!type)
1564     return Result("invalid data type '" + token->AsString() + "' provided");
1565 
1566   if (type->IsVec() || type->IsMatrix() || type->IsArray() || type->IsStruct())
1567     return Result("data type must be a scalar type");
1568 
1569   token = tokenizer_->NextToken();
1570   if (!token->IsInteger() && !token->IsDouble())
1571     return Result("expected data value");
1572 
1573   auto fmt = MakeUnique<Format>(type.get());
1574   Value value;
1575   if (fmt->IsFloat32() || fmt->IsFloat64())
1576     value.SetDoubleValue(token->AsDouble());
1577   else
1578     value.SetIntValue(token->AsUint64());
1579 
1580   Pipeline::ArgSetInfo info;
1581   info.name = arg_name;
1582   info.ordinal = arg_no;
1583   info.fmt = fmt.get();
1584   info.value = value;
1585   pipeline->SetArg(std::move(info));
1586   script_->RegisterFormat(std::move(fmt));
1587   script_->RegisterType(std::move(type));
1588 
1589   return ValidateEndOfStatement("SET command");
1590 }
1591 
ParsePipelinePolygonMode(Pipeline * pipeline)1592 Result Parser::ParsePipelinePolygonMode(Pipeline* pipeline) {
1593   auto token = tokenizer_->NextToken();
1594   if (!token->IsIdentifier())
1595     return Result("missing mode in POLYGON_MODE command");
1596 
1597   auto mode = token->AsString();
1598 
1599   if (mode == "fill")
1600     pipeline->GetPipelineData()->SetPolygonMode(PolygonMode::kFill);
1601   else if (mode == "line")
1602     pipeline->GetPipelineData()->SetPolygonMode(PolygonMode::kLine);
1603   else if (mode == "point")
1604     pipeline->GetPipelineData()->SetPolygonMode(PolygonMode::kPoint);
1605   else
1606     return Result("invalid polygon mode: " + mode);
1607 
1608   return ValidateEndOfStatement("POLYGON_MODE command");
1609 }
1610 
ParsePipelineDepth(Pipeline * pipeline)1611 Result Parser::ParsePipelineDepth(Pipeline* pipeline) {
1612   while (true) {
1613     auto token = tokenizer_->NextToken();
1614     if (token->IsEOL())
1615       continue;
1616     if (token->IsEOS())
1617       return Result("DEPTH missing END command");
1618     if (!token->IsIdentifier())
1619       return Result("DEPTH options must be identifiers");
1620     if (token->AsString() == "END")
1621       break;
1622 
1623     if (token->AsString() == "TEST") {
1624       token = tokenizer_->NextToken();
1625 
1626       if (!token->IsIdentifier())
1627         return Result("invalid value for TEST");
1628 
1629       if (token->AsString() == "on")
1630         pipeline->GetPipelineData()->SetEnableDepthTest(true);
1631       else if (token->AsString() == "off")
1632         pipeline->GetPipelineData()->SetEnableDepthTest(false);
1633       else
1634         return Result("invalid value for TEST: " + token->AsString());
1635     } else if (token->AsString() == "CLAMP") {
1636       token = tokenizer_->NextToken();
1637 
1638       if (!token->IsIdentifier())
1639         return Result("invalid value for CLAMP");
1640 
1641       if (token->AsString() == "on")
1642         pipeline->GetPipelineData()->SetEnableDepthClamp(true);
1643       else if (token->AsString() == "off")
1644         pipeline->GetPipelineData()->SetEnableDepthClamp(false);
1645       else
1646         return Result("invalid value for CLAMP: " + token->AsString());
1647     } else if (token->AsString() == "WRITE") {
1648       token = tokenizer_->NextToken();
1649 
1650       if (!token->IsIdentifier())
1651         return Result("invalid value for WRITE");
1652 
1653       if (token->AsString() == "on")
1654         pipeline->GetPipelineData()->SetEnableDepthWrite(true);
1655       else if (token->AsString() == "off")
1656         pipeline->GetPipelineData()->SetEnableDepthWrite(false);
1657       else
1658         return Result("invalid value for WRITE: " + token->AsString());
1659     } else if (token->AsString() == "COMPARE_OP") {
1660       token = tokenizer_->NextToken();
1661 
1662       if (!token->IsIdentifier())
1663         return Result("invalid value for COMPARE_OP");
1664 
1665       CompareOp compare_op = StrToCompareOp(token->AsString());
1666       if (compare_op != CompareOp::kUnknown) {
1667         pipeline->GetPipelineData()->SetDepthCompareOp(compare_op);
1668       } else {
1669         return Result("invalid value for COMPARE_OP: " + token->AsString());
1670       }
1671     } else if (token->AsString() == "BOUNDS") {
1672       token = tokenizer_->NextToken();
1673       if (!token->IsIdentifier() || token->AsString() != "min")
1674         return Result("BOUNDS expecting min");
1675 
1676       token = tokenizer_->NextToken();
1677       if (!token->IsDouble())
1678         return Result("BOUNDS invalid value for min");
1679       pipeline->GetPipelineData()->SetMinDepthBounds(token->AsFloat());
1680 
1681       token = tokenizer_->NextToken();
1682       if (!token->IsIdentifier() || token->AsString() != "max")
1683         return Result("BOUNDS expecting max");
1684 
1685       token = tokenizer_->NextToken();
1686       if (!token->IsDouble())
1687         return Result("BOUNDS invalid value for max");
1688       pipeline->GetPipelineData()->SetMaxDepthBounds(token->AsFloat());
1689     } else if (token->AsString() == "BIAS") {
1690       pipeline->GetPipelineData()->SetEnableDepthBias(true);
1691 
1692       token = tokenizer_->NextToken();
1693       if (!token->IsIdentifier() || token->AsString() != "constant")
1694         return Result("BIAS expecting constant");
1695 
1696       token = tokenizer_->NextToken();
1697       if (!token->IsDouble())
1698         return Result("BIAS invalid value for constant");
1699       pipeline->GetPipelineData()->SetDepthBiasConstantFactor(token->AsFloat());
1700 
1701       token = tokenizer_->NextToken();
1702       if (!token->IsIdentifier() || token->AsString() != "clamp")
1703         return Result("BIAS expecting clamp");
1704 
1705       token = tokenizer_->NextToken();
1706       if (!token->IsDouble())
1707         return Result("BIAS invalid value for clamp");
1708       pipeline->GetPipelineData()->SetDepthBiasClamp(token->AsFloat());
1709 
1710       token = tokenizer_->NextToken();
1711       if (!token->IsIdentifier() || token->AsString() != "slope")
1712         return Result("BIAS expecting slope");
1713 
1714       token = tokenizer_->NextToken();
1715       if (!token->IsDouble())
1716         return Result("BIAS invalid value for slope");
1717       pipeline->GetPipelineData()->SetDepthBiasSlopeFactor(token->AsFloat());
1718     } else {
1719       return Result("invalid value for DEPTH: " + token->AsString());
1720     }
1721   }
1722 
1723   return ValidateEndOfStatement("DEPTH command");
1724 }
1725 
ParsePipelineStencil(Pipeline * pipeline)1726 Result Parser::ParsePipelineStencil(Pipeline* pipeline) {
1727   auto token = tokenizer_->NextToken();
1728   if (!token->IsIdentifier())
1729     return Result("STENCIL missing face");
1730 
1731   bool setFront = false;
1732   bool setBack = false;
1733 
1734   if (token->AsString() == "front") {
1735     setFront = true;
1736   } else if (token->AsString() == "back") {
1737     setBack = true;
1738   } else if (token->AsString() == "front_and_back") {
1739     setFront = true;
1740     setBack = true;
1741   } else {
1742     return Result("STENCIL invalid face: " + token->AsString());
1743   }
1744 
1745   while (true) {
1746     token = tokenizer_->NextToken();
1747     if (token->IsEOL())
1748       continue;
1749     if (token->IsEOS())
1750       return Result("STENCIL missing END command");
1751     if (!token->IsIdentifier())
1752       return Result("STENCIL options must be identifiers");
1753     if (token->AsString() == "END")
1754       break;
1755 
1756     if (token->AsString() == "TEST") {
1757       token = tokenizer_->NextToken();
1758 
1759       if (!token->IsIdentifier())
1760         return Result("STENCIL invalid value for TEST");
1761 
1762       if (token->AsString() == "on")
1763         pipeline->GetPipelineData()->SetEnableStencilTest(true);
1764       else if (token->AsString() == "off")
1765         pipeline->GetPipelineData()->SetEnableStencilTest(false);
1766       else
1767         return Result("STENCIL invalid value for TEST: " + token->AsString());
1768     } else if (token->AsString() == "FAIL_OP") {
1769       token = tokenizer_->NextToken();
1770 
1771       if (!token->IsIdentifier())
1772         return Result("STENCIL invalid value for FAIL_OP");
1773 
1774       StencilOp stencil_op = StrToStencilOp(token->AsString());
1775       if (stencil_op == StencilOp::kUnknown) {
1776         return Result("STENCIL invalid value for FAIL_OP: " +
1777                       token->AsString());
1778       }
1779       if (setFront)
1780         pipeline->GetPipelineData()->SetFrontFailOp(stencil_op);
1781       if (setBack)
1782         pipeline->GetPipelineData()->SetBackFailOp(stencil_op);
1783     } else if (token->AsString() == "PASS_OP") {
1784       token = tokenizer_->NextToken();
1785 
1786       if (!token->IsIdentifier())
1787         return Result("STENCIL invalid value for PASS_OP");
1788 
1789       StencilOp stencil_op = StrToStencilOp(token->AsString());
1790       if (stencil_op == StencilOp::kUnknown) {
1791         return Result("STENCIL invalid value for PASS_OP: " +
1792                       token->AsString());
1793       }
1794       if (setFront)
1795         pipeline->GetPipelineData()->SetFrontPassOp(stencil_op);
1796       if (setBack)
1797         pipeline->GetPipelineData()->SetBackPassOp(stencil_op);
1798     } else if (token->AsString() == "DEPTH_FAIL_OP") {
1799       token = tokenizer_->NextToken();
1800 
1801       if (!token->IsIdentifier())
1802         return Result("STENCIL invalid value for DEPTH_FAIL_OP");
1803 
1804       StencilOp stencil_op = StrToStencilOp(token->AsString());
1805       if (stencil_op == StencilOp::kUnknown) {
1806         return Result("STENCIL invalid value for DEPTH_FAIL_OP: " +
1807                       token->AsString());
1808       }
1809       if (setFront)
1810         pipeline->GetPipelineData()->SetFrontDepthFailOp(stencil_op);
1811       if (setBack)
1812         pipeline->GetPipelineData()->SetBackDepthFailOp(stencil_op);
1813     } else if (token->AsString() == "COMPARE_OP") {
1814       token = tokenizer_->NextToken();
1815 
1816       if (!token->IsIdentifier())
1817         return Result("STENCIL invalid value for COMPARE_OP");
1818 
1819       CompareOp compare_op = StrToCompareOp(token->AsString());
1820       if (compare_op == CompareOp::kUnknown) {
1821         return Result("STENCIL invalid value for COMPARE_OP: " +
1822                       token->AsString());
1823       }
1824       if (setFront)
1825         pipeline->GetPipelineData()->SetFrontCompareOp(compare_op);
1826       if (setBack)
1827         pipeline->GetPipelineData()->SetBackCompareOp(compare_op);
1828     } else if (token->AsString() == "COMPARE_MASK") {
1829       token = tokenizer_->NextToken();
1830 
1831       if (!token->IsInteger())
1832         return Result("STENCIL invalid value for COMPARE_MASK");
1833 
1834       if (setFront)
1835         pipeline->GetPipelineData()->SetFrontCompareMask(token->AsUint32());
1836       if (setBack)
1837         pipeline->GetPipelineData()->SetBackCompareMask(token->AsUint32());
1838     } else if (token->AsString() == "WRITE_MASK") {
1839       token = tokenizer_->NextToken();
1840 
1841       if (!token->IsInteger())
1842         return Result("STENCIL invalid value for WRITE_MASK");
1843 
1844       if (setFront)
1845         pipeline->GetPipelineData()->SetFrontWriteMask(token->AsUint32());
1846       if (setBack)
1847         pipeline->GetPipelineData()->SetBackWriteMask(token->AsUint32());
1848     } else if (token->AsString() == "REFERENCE") {
1849       token = tokenizer_->NextToken();
1850 
1851       if (!token->IsInteger())
1852         return Result("STENCIL invalid value for REFERENCE");
1853 
1854       if (setFront)
1855         pipeline->GetPipelineData()->SetFrontReference(token->AsUint32());
1856       if (setBack)
1857         pipeline->GetPipelineData()->SetBackReference(token->AsUint32());
1858     } else {
1859       return Result("STENCIL invalid value for STENCIL: " + token->AsString());
1860     }
1861   }
1862 
1863   return ValidateEndOfStatement("STENCIL command");
1864 }
1865 
ParsePipelineBlend(Pipeline * pipeline)1866 Result Parser::ParsePipelineBlend(Pipeline* pipeline) {
1867   pipeline->GetPipelineData()->SetEnableBlend(true);
1868 
1869   while (true) {
1870     auto token = tokenizer_->NextToken();
1871     if (token->IsEOL())
1872       continue;
1873     if (token->IsEOS())
1874       return Result("BLEND missing END command");
1875     if (!token->IsIdentifier())
1876       return Result("BLEND options must be identifiers");
1877     if (token->AsString() == "END")
1878       break;
1879 
1880     if (token->AsString() == "SRC_COLOR_FACTOR") {
1881       token = tokenizer_->NextToken();
1882 
1883       if (!token->IsIdentifier())
1884         return Result("BLEND invalid value for SRC_COLOR_FACTOR");
1885 
1886       const auto factor = NameToBlendFactor(token->AsString());
1887       if (factor == BlendFactor::kUnknown)
1888         return Result("BLEND invalid value for SRC_COLOR_FACTOR: " +
1889                       token->AsString());
1890       pipeline->GetPipelineData()->SetSrcColorBlendFactor(
1891           NameToBlendFactor(token->AsString()));
1892     } else if (token->AsString() == "DST_COLOR_FACTOR") {
1893       token = tokenizer_->NextToken();
1894 
1895       if (!token->IsIdentifier())
1896         return Result("BLEND invalid value for DST_COLOR_FACTOR");
1897 
1898       const auto factor = NameToBlendFactor(token->AsString());
1899       if (factor == BlendFactor::kUnknown)
1900         return Result("BLEND invalid value for DST_COLOR_FACTOR: " +
1901                       token->AsString());
1902       pipeline->GetPipelineData()->SetDstColorBlendFactor(
1903           NameToBlendFactor(token->AsString()));
1904     } else if (token->AsString() == "SRC_ALPHA_FACTOR") {
1905       token = tokenizer_->NextToken();
1906 
1907       if (!token->IsIdentifier())
1908         return Result("BLEND invalid value for SRC_ALPHA_FACTOR");
1909 
1910       const auto factor = NameToBlendFactor(token->AsString());
1911       if (factor == BlendFactor::kUnknown)
1912         return Result("BLEND invalid value for SRC_ALPHA_FACTOR: " +
1913                       token->AsString());
1914       pipeline->GetPipelineData()->SetSrcAlphaBlendFactor(
1915           NameToBlendFactor(token->AsString()));
1916     } else if (token->AsString() == "DST_ALPHA_FACTOR") {
1917       token = tokenizer_->NextToken();
1918 
1919       if (!token->IsIdentifier())
1920         return Result("BLEND invalid value for DST_ALPHA_FACTOR");
1921 
1922       const auto factor = NameToBlendFactor(token->AsString());
1923       if (factor == BlendFactor::kUnknown)
1924         return Result("BLEND invalid value for DST_ALPHA_FACTOR: " +
1925                       token->AsString());
1926       pipeline->GetPipelineData()->SetDstAlphaBlendFactor(
1927           NameToBlendFactor(token->AsString()));
1928     } else if (token->AsString() == "COLOR_OP") {
1929       token = tokenizer_->NextToken();
1930 
1931       if (!token->IsIdentifier())
1932         return Result("BLEND invalid value for COLOR_OP");
1933 
1934       const auto op = NameToBlendOp(token->AsString());
1935       if (op == BlendOp::kUnknown)
1936         return Result("BLEND invalid value for COLOR_OP: " + token->AsString());
1937       pipeline->GetPipelineData()->SetColorBlendOp(
1938           NameToBlendOp(token->AsString()));
1939     } else if (token->AsString() == "ALPHA_OP") {
1940       token = tokenizer_->NextToken();
1941 
1942       if (!token->IsIdentifier())
1943         return Result("BLEND invalid value for ALPHA_OP");
1944 
1945       const auto op = NameToBlendOp(token->AsString());
1946       if (op == BlendOp::kUnknown)
1947         return Result("BLEND invalid value for ALPHA_OP: " + token->AsString());
1948       pipeline->GetPipelineData()->SetAlphaBlendOp(
1949           NameToBlendOp(token->AsString()));
1950     } else {
1951       return Result("BLEND invalid value for BLEND: " + token->AsString());
1952     }
1953   }
1954 
1955   return ValidateEndOfStatement("BLEND command");
1956 }
1957 
ParseStruct()1958 Result Parser::ParseStruct() {
1959   auto token = tokenizer_->NextToken();
1960   if (!token->IsIdentifier())
1961     return Result("invalid STRUCT name provided");
1962 
1963   auto struct_name = token->AsString();
1964   if (struct_name == "STRIDE")
1965     return Result("missing STRUCT name");
1966 
1967   auto s = MakeUnique<type::Struct>();
1968   auto type = s.get();
1969 
1970   Result r = script_->AddType(struct_name, std::move(s));
1971   if (!r.IsSuccess())
1972     return r;
1973 
1974   token = tokenizer_->NextToken();
1975   if (token->IsIdentifier()) {
1976     if (token->AsString() != "STRIDE")
1977       return Result("invalid token in STRUCT definition");
1978 
1979     token = tokenizer_->NextToken();
1980     if (token->IsEOL() || token->IsEOS())
1981       return Result("missing value for STRIDE");
1982     if (!token->IsInteger())
1983       return Result("invalid value for STRIDE");
1984 
1985     type->SetStrideInBytes(token->AsUint32());
1986     token = tokenizer_->NextToken();
1987   }
1988   if (!token->IsEOL()) {
1989     return Result("extra token " + token->ToOriginalString() +
1990                   " after STRUCT header");
1991   }
1992 
1993   std::map<std::string, bool> seen;
1994   for (;;) {
1995     token = tokenizer_->NextToken();
1996     if (!token->IsIdentifier())
1997       return Result("invalid type for STRUCT member");
1998     if (token->AsString() == "END")
1999       break;
2000 
2001     if (token->AsString() == struct_name)
2002       return Result("recursive types are not allowed");
2003 
2004     type::Type* member_type = script_->GetType(token->AsString());
2005     if (!member_type) {
2006       auto t = ToType(token->AsString());
2007       if (!t) {
2008         return Result("unknown type '" + token->AsString() +
2009                       "' for STRUCT member");
2010       }
2011 
2012       member_type = t.get();
2013       script_->RegisterType(std::move(t));
2014     }
2015 
2016     token = tokenizer_->NextToken();
2017     if (token->IsEOL())
2018       return Result("missing name for STRUCT member");
2019     if (!token->IsIdentifier())
2020       return Result("invalid name for STRUCT member");
2021 
2022     auto member_name = token->AsString();
2023     if (seen.find(member_name) != seen.end())
2024       return Result("duplicate name for STRUCT member");
2025 
2026     seen[member_name] = true;
2027 
2028     auto m = type->AddMember(member_type);
2029     m->name = member_name;
2030 
2031     token = tokenizer_->NextToken();
2032     while (token->IsIdentifier()) {
2033       if (token->AsString() == "OFFSET") {
2034         token = tokenizer_->NextToken();
2035         if (token->IsEOL())
2036           return Result("missing value for STRUCT member OFFSET");
2037         if (!token->IsInteger())
2038           return Result("invalid value for STRUCT member OFFSET");
2039 
2040         m->offset_in_bytes = token->AsInt32();
2041       } else if (token->AsString() == "ARRAY_STRIDE") {
2042         token = tokenizer_->NextToken();
2043         if (token->IsEOL())
2044           return Result("missing value for STRUCT member ARRAY_STRIDE");
2045         if (!token->IsInteger())
2046           return Result("invalid value for STRUCT member ARRAY_STRIDE");
2047         if (!member_type->IsArray())
2048           return Result("ARRAY_STRIDE only valid on array members");
2049 
2050         m->array_stride_in_bytes = token->AsInt32();
2051       } else if (token->AsString() == "MATRIX_STRIDE") {
2052         token = tokenizer_->NextToken();
2053         if (token->IsEOL())
2054           return Result("missing value for STRUCT member MATRIX_STRIDE");
2055         if (!token->IsInteger())
2056           return Result("invalid value for STRUCT member MATRIX_STRIDE");
2057         if (!member_type->IsMatrix())
2058           return Result("MATRIX_STRIDE only valid on matrix members");
2059 
2060         m->matrix_stride_in_bytes = token->AsInt32();
2061       } else {
2062         return Result("unknown param '" + token->AsString() +
2063                       "' for STRUCT member");
2064       }
2065 
2066       token = tokenizer_->NextToken();
2067     }
2068 
2069     if (!token->IsEOL())
2070       return Result("extra param for STRUCT member");
2071   }
2072 
2073   return {};
2074 }
2075 
ParseBuffer()2076 Result Parser::ParseBuffer() {
2077   auto token = tokenizer_->NextToken();
2078   if (!token->IsIdentifier())
2079     return Result("invalid BUFFER name provided");
2080 
2081   auto name = token->AsString();
2082   if (name == "DATA_TYPE" || name == "FORMAT")
2083     return Result("missing BUFFER name");
2084 
2085   token = tokenizer_->NextToken();
2086   if (!token->IsIdentifier())
2087     return Result("invalid BUFFER command provided");
2088 
2089   std::unique_ptr<Buffer> buffer;
2090   auto& cmd = token->AsString();
2091   if (cmd == "DATA_TYPE") {
2092     buffer = MakeUnique<Buffer>();
2093 
2094     Result r = ParseBufferInitializer(buffer.get());
2095     if (!r.IsSuccess())
2096       return r;
2097   } else if (cmd == "FORMAT") {
2098     token = tokenizer_->NextToken();
2099     if (!token->IsIdentifier())
2100       return Result("BUFFER FORMAT must be an identifier");
2101 
2102     buffer = MakeUnique<Buffer>();
2103 
2104     auto type = script_->ParseType(token->AsString());
2105     if (!type)
2106       return Result("invalid BUFFER FORMAT");
2107 
2108     auto fmt = MakeUnique<Format>(type);
2109     buffer->SetFormat(fmt.get());
2110     script_->RegisterFormat(std::move(fmt));
2111 
2112     token = tokenizer_->PeekNextToken();
2113     while (token->IsIdentifier()) {
2114       if (token->AsString() == "MIP_LEVELS") {
2115         tokenizer_->NextToken();
2116         token = tokenizer_->NextToken();
2117 
2118         if (!token->IsInteger())
2119           return Result("invalid value for MIP_LEVELS");
2120 
2121         buffer->SetMipLevels(token->AsUint32());
2122       } else if (token->AsString() == "FILE") {
2123         tokenizer_->NextToken();
2124         Result r = ParseBufferInitializerFile(buffer.get());
2125 
2126         if (!r.IsSuccess())
2127           return r;
2128       } else if (token->AsString() == "SAMPLES") {
2129         tokenizer_->NextToken();
2130         token = tokenizer_->NextToken();
2131         if (!token->IsInteger())
2132           return Result("expected integer value for SAMPLES");
2133 
2134         const uint32_t samples = token->AsUint32();
2135         if (!IsValidSampleCount(samples))
2136           return Result("invalid sample count: " + token->ToOriginalString());
2137 
2138         buffer->SetSamples(samples);
2139       } else {
2140         break;
2141       }
2142       token = tokenizer_->PeekNextToken();
2143     }
2144   } else {
2145     return Result("unknown BUFFER command provided: " + cmd);
2146   }
2147   buffer->SetName(name);
2148 
2149   Result r = script_->AddBuffer(std::move(buffer));
2150   if (!r.IsSuccess())
2151     return r;
2152 
2153   return {};
2154 }
2155 
ParseImage()2156 Result Parser::ParseImage() {
2157   auto token = tokenizer_->NextToken();
2158   if (!token->IsIdentifier())
2159     return Result("invalid IMAGE name provided");
2160 
2161   auto name = token->AsString();
2162   if (name == "DATA_TYPE" || name == "FORMAT")
2163     return Result("missing IMAGE name");
2164 
2165   std::unique_ptr<Buffer> buffer = MakeUnique<Buffer>();
2166   buffer->SetName(name);
2167   bool width_set = false;
2168   bool height_set = false;
2169   bool depth_set = false;
2170 
2171   token = tokenizer_->PeekNextToken();
2172   while (token->IsIdentifier()) {
2173     if (token->AsString() == "FILL" || token->AsString() == "SERIES_FROM" ||
2174         token->AsString() == "DATA") {
2175       break;
2176     }
2177 
2178     tokenizer_->NextToken();
2179 
2180     if (token->AsString() == "DATA_TYPE") {
2181       token = tokenizer_->NextToken();
2182       if (!token->IsIdentifier())
2183         return Result("IMAGE invalid data type");
2184 
2185       auto type = script_->ParseType(token->AsString());
2186       std::unique_ptr<Format> fmt;
2187       if (type != nullptr) {
2188         fmt = MakeUnique<Format>(type);
2189         buffer->SetFormat(fmt.get());
2190       } else {
2191         auto new_type = ToType(token->AsString());
2192         if (!new_type) {
2193           return Result("invalid data type '" + token->AsString() +
2194                         "' provided");
2195         }
2196 
2197         fmt = MakeUnique<Format>(new_type.get());
2198         buffer->SetFormat(fmt.get());
2199         script_->RegisterType(std::move(new_type));
2200       }
2201       script_->RegisterFormat(std::move(fmt));
2202     } else if (token->AsString() == "FORMAT") {
2203       token = tokenizer_->NextToken();
2204       if (!token->IsIdentifier())
2205         return Result("IMAGE FORMAT must be an identifier");
2206 
2207       auto type = script_->ParseType(token->AsString());
2208       if (!type)
2209         return Result("invalid IMAGE FORMAT");
2210 
2211       auto fmt = MakeUnique<Format>(type);
2212       buffer->SetFormat(fmt.get());
2213       script_->RegisterFormat(std::move(fmt));
2214     } else if (token->AsString() == "MIP_LEVELS") {
2215       token = tokenizer_->NextToken();
2216 
2217       if (!token->IsInteger())
2218         return Result("invalid value for MIP_LEVELS");
2219 
2220       buffer->SetMipLevels(token->AsUint32());
2221     } else if (token->AsString() == "DIM_1D") {
2222       buffer->SetImageDimension(ImageDimension::k1D);
2223     } else if (token->AsString() == "DIM_2D") {
2224       buffer->SetImageDimension(ImageDimension::k2D);
2225     } else if (token->AsString() == "DIM_3D") {
2226       buffer->SetImageDimension(ImageDimension::k3D);
2227     } else if (token->AsString() == "WIDTH") {
2228       token = tokenizer_->NextToken();
2229       if (!token->IsInteger() || token->AsUint32() == 0)
2230         return Result("expected positive IMAGE WIDTH");
2231 
2232       buffer->SetWidth(token->AsUint32());
2233       width_set = true;
2234     } else if (token->AsString() == "HEIGHT") {
2235       token = tokenizer_->NextToken();
2236       if (!token->IsInteger() || token->AsUint32() == 0)
2237         return Result("expected positive IMAGE HEIGHT");
2238 
2239       buffer->SetHeight(token->AsUint32());
2240       height_set = true;
2241     } else if (token->AsString() == "DEPTH") {
2242       token = tokenizer_->NextToken();
2243       if (!token->IsInteger() || token->AsUint32() == 0)
2244         return Result("expected positive IMAGE DEPTH");
2245 
2246       buffer->SetDepth(token->AsUint32());
2247       depth_set = true;
2248     } else if (token->AsString() == "SAMPLES") {
2249       token = tokenizer_->NextToken();
2250       if (!token->IsInteger())
2251         return Result("expected integer value for SAMPLES");
2252 
2253       const uint32_t samples = token->AsUint32();
2254       if (!IsValidSampleCount(samples))
2255         return Result("invalid sample count: " + token->ToOriginalString());
2256 
2257       buffer->SetSamples(samples);
2258     } else {
2259       return Result("unknown IMAGE command provided: " +
2260                     token->ToOriginalString());
2261     }
2262     token = tokenizer_->PeekNextToken();
2263   }
2264 
2265   if (buffer->GetImageDimension() == ImageDimension::k3D && !depth_set)
2266     return Result("expected IMAGE DEPTH");
2267 
2268   if ((buffer->GetImageDimension() == ImageDimension::k3D ||
2269        buffer->GetImageDimension() == ImageDimension::k2D) &&
2270       !height_set) {
2271     return Result("expected IMAGE HEIGHT");
2272   }
2273   if (!width_set)
2274     return Result("expected IMAGE WIDTH");
2275 
2276   const uint32_t size_in_items =
2277       buffer->GetWidth() * buffer->GetHeight() * buffer->GetDepth();
2278   buffer->SetElementCount(size_in_items);
2279 
2280   // Parse initializers.
2281   token = tokenizer_->NextToken();
2282   if (token->IsIdentifier()) {
2283     if (token->AsString() == "DATA") {
2284       Result r = ParseBufferInitializerData(buffer.get());
2285       if (!r.IsSuccess())
2286         return r;
2287 
2288       if (size_in_items != buffer->ElementCount()) {
2289         return Result(
2290             "Elements provided in data does not match size specified: " +
2291             std::to_string(size_in_items) + " specified vs " +
2292             std::to_string(buffer->ElementCount()) + " provided");
2293       }
2294     } else if (token->AsString() == "FILL") {
2295       Result r = ParseBufferInitializerFill(buffer.get(), size_in_items);
2296       if (!r.IsSuccess())
2297         return r;
2298     } else if (token->AsString() == "SERIES_FROM") {
2299       Result r = ParseBufferInitializerSeries(buffer.get(), size_in_items);
2300       if (!r.IsSuccess())
2301         return r;
2302     } else {
2303       return Result("unexpected IMAGE token: " + token->AsString());
2304     }
2305   } else if (!token->IsEOL() && !token->IsEOS()) {
2306     return Result("unexpected IMAGE token: " + token->ToOriginalString());
2307   }
2308 
2309   Result r = script_->AddBuffer(std::move(buffer));
2310   if (!r.IsSuccess())
2311     return r;
2312 
2313   return {};
2314 }
2315 
ParseBufferInitializer(Buffer * buffer)2316 Result Parser::ParseBufferInitializer(Buffer* buffer) {
2317   auto token = tokenizer_->NextToken();
2318   if (!token->IsIdentifier())
2319     return Result("BUFFER invalid data type");
2320 
2321   auto type = script_->ParseType(token->AsString());
2322   std::unique_ptr<Format> fmt;
2323   if (type != nullptr) {
2324     fmt = MakeUnique<Format>(type);
2325     buffer->SetFormat(fmt.get());
2326   } else {
2327     auto new_type = ToType(token->AsString());
2328     if (!new_type)
2329       return Result("invalid data type '" + token->AsString() + "' provided");
2330 
2331     fmt = MakeUnique<Format>(new_type.get());
2332     buffer->SetFormat(fmt.get());
2333     type = new_type.get();
2334     script_->RegisterType(std::move(new_type));
2335   }
2336   script_->RegisterFormat(std::move(fmt));
2337 
2338   token = tokenizer_->NextToken();
2339   if (!token->IsIdentifier())
2340     return Result("BUFFER missing initializer");
2341 
2342   if (token->AsString() == "STD140") {
2343     buffer->GetFormat()->SetLayout(Format::Layout::kStd140);
2344     token = tokenizer_->NextToken();
2345   } else if (token->AsString() == "STD430") {
2346     buffer->GetFormat()->SetLayout(Format::Layout::kStd430);
2347     token = tokenizer_->NextToken();
2348   }
2349 
2350   if (!token->IsIdentifier())
2351     return Result("BUFFER missing initializer");
2352 
2353   if (token->AsString() == "SIZE")
2354     return ParseBufferInitializerSize(buffer);
2355   if (token->AsString() == "WIDTH") {
2356     token = tokenizer_->NextToken();
2357     if (!token->IsInteger())
2358       return Result("expected an integer for WIDTH");
2359     const uint32_t width = token->AsUint32();
2360     if (width == 0)
2361       return Result("expected WIDTH to be positive");
2362     buffer->SetWidth(width);
2363     buffer->SetImageDimension(ImageDimension::k2D);
2364 
2365     token = tokenizer_->NextToken();
2366     if (token->AsString() != "HEIGHT")
2367       return Result("BUFFER HEIGHT missing");
2368     token = tokenizer_->NextToken();
2369     if (!token->IsInteger())
2370       return Result("expected an integer for HEIGHT");
2371     const uint32_t height = token->AsUint32();
2372     if (height == 0)
2373       return Result("expected HEIGHT to be positive");
2374     buffer->SetHeight(height);
2375 
2376     token = tokenizer_->NextToken();
2377     uint32_t size_in_items = width * height;
2378     buffer->SetElementCount(size_in_items);
2379     if (token->AsString() == "FILL")
2380       return ParseBufferInitializerFill(buffer, size_in_items);
2381     if (token->AsString() == "SERIES_FROM")
2382       return ParseBufferInitializerSeries(buffer, size_in_items);
2383     return {};
2384   }
2385   if (token->AsString() == "DATA")
2386     return ParseBufferInitializerData(buffer);
2387 
2388   return Result("unknown initializer for BUFFER");
2389 }
2390 
ParseBufferInitializerSize(Buffer * buffer)2391 Result Parser::ParseBufferInitializerSize(Buffer* buffer) {
2392   auto token = tokenizer_->NextToken();
2393   if (token->IsEOS() || token->IsEOL())
2394     return Result("BUFFER size missing");
2395   if (!token->IsInteger())
2396     return Result("BUFFER size invalid");
2397 
2398   uint32_t size_in_items = token->AsUint32();
2399   buffer->SetElementCount(size_in_items);
2400 
2401   token = tokenizer_->NextToken();
2402   if (!token->IsIdentifier())
2403     return Result("BUFFER invalid initializer");
2404 
2405   if (token->AsString() == "FILL")
2406     return ParseBufferInitializerFill(buffer, size_in_items);
2407   if (token->AsString() == "SERIES_FROM")
2408     return ParseBufferInitializerSeries(buffer, size_in_items);
2409   if (token->AsString() == "FILE")
2410     return ParseBufferInitializerFile(buffer);
2411 
2412   return Result("invalid BUFFER initializer provided");
2413 }
2414 
ParseBufferInitializerFill(Buffer * buffer,uint32_t size_in_items)2415 Result Parser::ParseBufferInitializerFill(Buffer* buffer,
2416                                           uint32_t size_in_items) {
2417   auto token = tokenizer_->NextToken();
2418   if (token->IsEOS() || token->IsEOL())
2419     return Result("missing BUFFER fill value");
2420   if (!token->IsInteger() && !token->IsDouble())
2421     return Result("invalid BUFFER fill value");
2422 
2423   auto fmt = buffer->GetFormat();
2424   bool is_double_data = fmt->IsFloat32() || fmt->IsFloat64();
2425 
2426   // Inflate the size because our items are multi-dimensional.
2427   size_in_items = size_in_items * fmt->InputNeededPerElement();
2428 
2429   std::vector<Value> values;
2430   values.resize(size_in_items);
2431   for (size_t i = 0; i < size_in_items; ++i) {
2432     if (is_double_data)
2433       values[i].SetDoubleValue(token->AsDouble());
2434     else
2435       values[i].SetIntValue(token->AsUint64());
2436   }
2437   Result r = buffer->SetData(std::move(values));
2438   if (!r.IsSuccess())
2439     return r;
2440 
2441   return ValidateEndOfStatement("BUFFER fill command");
2442 }
2443 
ParseBufferInitializerSeries(Buffer * buffer,uint32_t size_in_items)2444 Result Parser::ParseBufferInitializerSeries(Buffer* buffer,
2445                                             uint32_t size_in_items) {
2446   auto token = tokenizer_->NextToken();
2447   if (token->IsEOS() || token->IsEOL())
2448     return Result("missing BUFFER series_from value");
2449   if (!token->IsInteger() && !token->IsDouble())
2450     return Result("invalid BUFFER series_from value");
2451 
2452   auto type = buffer->GetFormat()->GetType();
2453   if (type->IsMatrix() || type->IsVec())
2454     return Result("BUFFER series_from must not be multi-row/column types");
2455 
2456   Value counter;
2457 
2458   auto n = type->AsNumber();
2459   FormatMode mode = n->GetFormatMode();
2460   uint32_t num_bits = n->NumBits();
2461   if (type::Type::IsFloat32(mode, num_bits) ||
2462       type::Type::IsFloat64(mode, num_bits)) {
2463     counter.SetDoubleValue(token->AsDouble());
2464   } else {
2465     counter.SetIntValue(token->AsUint64());
2466   }
2467 
2468   token = tokenizer_->NextToken();
2469   if (!token->IsIdentifier())
2470     return Result("missing BUFFER series_from inc_by");
2471   if (token->AsString() != "INC_BY")
2472     return Result("BUFFER series_from invalid command");
2473 
2474   token = tokenizer_->NextToken();
2475   if (token->IsEOS() || token->IsEOL())
2476     return Result("missing BUFFER series_from inc_by value");
2477   if (!token->IsInteger() && !token->IsDouble())
2478     return Result("invalid BUFFER series_from inc_by value");
2479 
2480   std::vector<Value> values;
2481   values.resize(size_in_items);
2482   for (size_t i = 0; i < size_in_items; ++i) {
2483     if (type::Type::IsFloat32(mode, num_bits) ||
2484         type::Type::IsFloat64(mode, num_bits)) {
2485       double value = counter.AsDouble();
2486       values[i].SetDoubleValue(value);
2487       counter.SetDoubleValue(value + token->AsDouble());
2488     } else {
2489       uint64_t value = counter.AsUint64();
2490       values[i].SetIntValue(value);
2491       counter.SetIntValue(value + token->AsUint64());
2492     }
2493   }
2494   Result r = buffer->SetData(std::move(values));
2495   if (!r.IsSuccess())
2496     return r;
2497 
2498   return ValidateEndOfStatement("BUFFER series_from command");
2499 }
2500 
ParseBufferInitializerData(Buffer * buffer)2501 Result Parser::ParseBufferInitializerData(Buffer* buffer) {
2502   Result r = ParseBufferData(buffer, tokenizer_.get(), false);
2503 
2504   if (!r.IsSuccess())
2505     return r;
2506 
2507   return ValidateEndOfStatement("BUFFER data command");
2508 }
2509 
ParseBufferInitializerFile(Buffer * buffer)2510 Result Parser::ParseBufferInitializerFile(Buffer* buffer) {
2511   auto token = tokenizer_->NextToken();
2512 
2513   if (!token->IsIdentifier())
2514     return Result("invalid value for FILE");
2515 
2516   BufferDataFileType file_type = BufferDataFileType::kPng;
2517 
2518   if (token->AsString() == "TEXT") {
2519     file_type = BufferDataFileType::kText;
2520     token = tokenizer_->NextToken();
2521   } else if (token->AsString() == "BINARY") {
2522     file_type = BufferDataFileType::kBinary;
2523     token = tokenizer_->NextToken();
2524   } else if (token->AsString() == "PNG") {
2525     token = tokenizer_->NextToken();
2526   }
2527 
2528   if (!token->IsIdentifier())
2529     return Result("missing file name for FILE");
2530 
2531   if (!delegate_)
2532     return Result("missing delegate");
2533 
2534   BufferInfo info;
2535   Result r = delegate_->LoadBufferData(token->AsString(), file_type, &info);
2536 
2537   if (!r.IsSuccess())
2538     return r;
2539 
2540   std::vector<uint8_t>* data = buffer->ValuePtr();
2541 
2542   data->clear();
2543   data->reserve(info.values.size());
2544   for (auto v : info.values) {
2545     data->push_back(v.AsUint8());
2546   }
2547 
2548   if (file_type == BufferDataFileType::kText) {
2549     auto s = std::string(data->begin(), data->end());
2550     Tokenizer tok(s);
2551     r = ParseBufferData(buffer, &tok, true);
2552     if (!r.IsSuccess())
2553       return r;
2554   } else {
2555     buffer->SetElementCount(static_cast<uint32_t>(data->size()) /
2556                             buffer->GetFormat()->SizeInBytes());
2557     buffer->SetWidth(info.width);
2558     buffer->SetHeight(info.height);
2559   }
2560 
2561   return {};
2562 }
2563 
ParseRun()2564 Result Parser::ParseRun() {
2565   auto token = tokenizer_->NextToken();
2566   if (!token->IsIdentifier())
2567     return Result("missing pipeline name for RUN command");
2568 
2569   size_t line = tokenizer_->GetCurrentLine();
2570 
2571   auto* pipeline = script_->GetPipeline(token->AsString());
2572   if (!pipeline)
2573     return Result("unknown pipeline for RUN command: " + token->AsString());
2574 
2575   token = tokenizer_->NextToken();
2576   if (token->IsEOL() || token->IsEOS())
2577     return Result("RUN command requires parameters");
2578 
2579   if (token->IsInteger()) {
2580     if (!pipeline->IsCompute())
2581       return Result("RUN command requires compute pipeline");
2582 
2583     auto cmd = MakeUnique<ComputeCommand>(pipeline);
2584     cmd->SetLine(line);
2585     cmd->SetX(token->AsUint32());
2586 
2587     token = tokenizer_->NextToken();
2588     if (!token->IsInteger()) {
2589       return Result("invalid parameter for RUN command: " +
2590                     token->ToOriginalString());
2591     }
2592     cmd->SetY(token->AsUint32());
2593 
2594     token = tokenizer_->NextToken();
2595     if (!token->IsInteger()) {
2596       return Result("invalid parameter for RUN command: " +
2597                     token->ToOriginalString());
2598     }
2599     cmd->SetZ(token->AsUint32());
2600 
2601     command_list_.push_back(std::move(cmd));
2602     return ValidateEndOfStatement("RUN command");
2603   }
2604 
2605   if (!token->IsIdentifier())
2606     return Result("invalid token in RUN command: " + token->ToOriginalString());
2607 
2608   if (token->AsString() == "DRAW_RECT") {
2609     if (!pipeline->IsGraphics())
2610       return Result("RUN command requires graphics pipeline");
2611 
2612     if (pipeline->GetVertexBuffers().size() > 1) {
2613       return Result(
2614           "RUN DRAW_RECT is not supported in a pipeline with more than one "
2615           "vertex buffer attached");
2616     }
2617 
2618     token = tokenizer_->NextToken();
2619     if (token->IsEOS() || token->IsEOL())
2620       return Result("RUN DRAW_RECT command requires parameters");
2621 
2622     if (!token->IsIdentifier() || token->AsString() != "POS") {
2623       return Result("invalid token in RUN command: " +
2624                     token->ToOriginalString() + "; expected POS");
2625     }
2626 
2627     token = tokenizer_->NextToken();
2628     if (!token->IsInteger())
2629       return Result("missing X position for RUN command");
2630 
2631     auto cmd =
2632         MakeUnique<DrawRectCommand>(pipeline, *pipeline->GetPipelineData());
2633     cmd->SetLine(line);
2634     cmd->EnableOrtho();
2635 
2636     Result r = token->ConvertToDouble();
2637     if (!r.IsSuccess())
2638       return r;
2639     cmd->SetX(token->AsFloat());
2640 
2641     token = tokenizer_->NextToken();
2642     if (!token->IsInteger())
2643       return Result("missing Y position for RUN command");
2644 
2645     r = token->ConvertToDouble();
2646     if (!r.IsSuccess())
2647       return r;
2648     cmd->SetY(token->AsFloat());
2649 
2650     token = tokenizer_->NextToken();
2651     if (!token->IsIdentifier() || token->AsString() != "SIZE") {
2652       return Result("invalid token in RUN command: " +
2653                     token->ToOriginalString() + "; expected SIZE");
2654     }
2655 
2656     token = tokenizer_->NextToken();
2657     if (!token->IsInteger())
2658       return Result("missing width value for RUN command");
2659 
2660     r = token->ConvertToDouble();
2661     if (!r.IsSuccess())
2662       return r;
2663     cmd->SetWidth(token->AsFloat());
2664 
2665     token = tokenizer_->NextToken();
2666     if (!token->IsInteger())
2667       return Result("missing height value for RUN command");
2668 
2669     r = token->ConvertToDouble();
2670     if (!r.IsSuccess())
2671       return r;
2672     cmd->SetHeight(token->AsFloat());
2673 
2674     command_list_.push_back(std::move(cmd));
2675     return ValidateEndOfStatement("RUN command");
2676   }
2677 
2678   if (token->AsString() == "DRAW_GRID") {
2679     if (!pipeline->IsGraphics())
2680       return Result("RUN command requires graphics pipeline");
2681 
2682     if (pipeline->GetVertexBuffers().size() > 0) {
2683       return Result(
2684           "RUN DRAW_GRID is not supported in a pipeline with "
2685           "vertex buffers attached");
2686     }
2687 
2688     token = tokenizer_->NextToken();
2689     if (token->IsEOS() || token->IsEOL())
2690       return Result("RUN DRAW_GRID command requires parameters");
2691 
2692     if (!token->IsIdentifier() || token->AsString() != "POS") {
2693       return Result("invalid token in RUN command: " +
2694                     token->ToOriginalString() + "; expected POS");
2695     }
2696 
2697     token = tokenizer_->NextToken();
2698     if (!token->IsInteger())
2699       return Result("missing X position for RUN command");
2700 
2701     auto cmd =
2702         MakeUnique<DrawGridCommand>(pipeline, *pipeline->GetPipelineData());
2703     cmd->SetLine(line);
2704 
2705     Result r = token->ConvertToDouble();
2706     if (!r.IsSuccess())
2707       return r;
2708     cmd->SetX(token->AsFloat());
2709 
2710     token = tokenizer_->NextToken();
2711     if (!token->IsInteger())
2712       return Result("missing Y position for RUN command");
2713 
2714     r = token->ConvertToDouble();
2715     if (!r.IsSuccess())
2716       return r;
2717     cmd->SetY(token->AsFloat());
2718 
2719     token = tokenizer_->NextToken();
2720     if (!token->IsIdentifier() || token->AsString() != "SIZE") {
2721       return Result("invalid token in RUN command: " +
2722                     token->ToOriginalString() + "; expected SIZE");
2723     }
2724 
2725     token = tokenizer_->NextToken();
2726     if (!token->IsInteger())
2727       return Result("missing width value for RUN command");
2728 
2729     r = token->ConvertToDouble();
2730     if (!r.IsSuccess())
2731       return r;
2732     cmd->SetWidth(token->AsFloat());
2733 
2734     token = tokenizer_->NextToken();
2735     if (!token->IsInteger())
2736       return Result("missing height value for RUN command");
2737 
2738     r = token->ConvertToDouble();
2739     if (!r.IsSuccess())
2740       return r;
2741     cmd->SetHeight(token->AsFloat());
2742 
2743     token = tokenizer_->NextToken();
2744     if (!token->IsIdentifier() || token->AsString() != "CELLS") {
2745       return Result("invalid token in RUN command: " +
2746                     token->ToOriginalString() + "; expected CELLS");
2747     }
2748 
2749     token = tokenizer_->NextToken();
2750     if (!token->IsInteger())
2751       return Result("missing columns value for RUN command");
2752 
2753     cmd->SetColumns(token->AsUint32());
2754 
2755     token = tokenizer_->NextToken();
2756     if (!token->IsInteger())
2757       return Result("missing rows value for RUN command");
2758 
2759     cmd->SetRows(token->AsUint32());
2760 
2761     command_list_.push_back(std::move(cmd));
2762     return ValidateEndOfStatement("RUN command");
2763   }
2764 
2765   if (token->AsString() == "DRAW_ARRAY") {
2766     if (!pipeline->IsGraphics())
2767       return Result("RUN command requires graphics pipeline");
2768 
2769     if (pipeline->GetVertexBuffers().empty())
2770       return Result("RUN DRAW_ARRAY requires attached vertex buffer");
2771 
2772     token = tokenizer_->NextToken();
2773     if (!token->IsIdentifier() || token->AsString() != "AS")
2774       return Result("missing AS for RUN command");
2775 
2776     token = tokenizer_->NextToken();
2777     if (!token->IsIdentifier()) {
2778       return Result("invalid topology for RUN command: " +
2779                     token->ToOriginalString());
2780     }
2781 
2782     Topology topo = NameToTopology(token->AsString());
2783     if (topo == Topology::kUnknown)
2784       return Result("invalid topology for RUN command: " + token->AsString());
2785 
2786     bool indexed = false;
2787     uint32_t start_idx = 0;
2788     uint32_t count = 0;
2789     uint32_t start_instance = 0;
2790     uint32_t instance_count = 1;
2791 
2792     token = tokenizer_->PeekNextToken();
2793 
2794     while (!token->IsEOS() && !token->IsEOL()) {
2795       token = tokenizer_->NextToken();
2796 
2797       if (!token->IsIdentifier())
2798         return Result("expecting identifier for RUN command");
2799 
2800       if (token->AsString() == "INDEXED") {
2801         if (!pipeline->GetIndexBuffer()) {
2802           return Result(
2803               "RUN DRAW_ARRAYS INDEXED requires attached index buffer");
2804         }
2805 
2806         indexed = true;
2807       } else if (token->AsString() == "START_IDX") {
2808         token = tokenizer_->NextToken();
2809         if (!token->IsInteger()) {
2810           return Result("invalid START_IDX value for RUN command: " +
2811                         token->ToOriginalString());
2812         }
2813         if (token->AsInt32() < 0)
2814           return Result("START_IDX value must be >= 0 for RUN command");
2815         start_idx = token->AsUint32();
2816       } else if (token->AsString() == "COUNT") {
2817         token = tokenizer_->NextToken();
2818         if (!token->IsInteger()) {
2819           return Result("invalid COUNT value for RUN command: " +
2820                         token->ToOriginalString());
2821         }
2822         if (token->AsInt32() <= 0)
2823           return Result("COUNT value must be > 0 for RUN command");
2824 
2825         count = token->AsUint32();
2826       } else if (token->AsString() == "INSTANCE_COUNT") {
2827         token = tokenizer_->NextToken();
2828         if (!token->IsInteger()) {
2829           return Result("invalid INSTANCE_COUNT value for RUN command: " +
2830                         token->ToOriginalString());
2831         }
2832         if (token->AsInt32() <= 0)
2833           return Result("INSTANCE_COUNT value must be > 0 for RUN command");
2834 
2835         instance_count = token->AsUint32();
2836       } else if (token->AsString() == "START_INSTANCE") {
2837         token = tokenizer_->NextToken();
2838         if (!token->IsInteger()) {
2839           return Result("invalid START_INSTANCE value for RUN command: " +
2840                         token->ToOriginalString());
2841         }
2842         if (token->AsInt32() < 0)
2843           return Result("START_INSTANCE value must be >= 0 for RUN command");
2844         start_instance = token->AsUint32();
2845       } else {
2846         return Result("Unexpected identifier for RUN command: " +
2847                       token->ToOriginalString());
2848       }
2849 
2850       token = tokenizer_->PeekNextToken();
2851     }
2852 
2853     uint32_t vertex_count =
2854         indexed ? pipeline->GetIndexBuffer()->ElementCount()
2855                 : pipeline->GetVertexBuffers()[0].buffer->ElementCount();
2856 
2857     // If we get here then we never set count, as if count was set it must
2858     // be > 0.
2859     if (count == 0)
2860       count = vertex_count - start_idx;
2861 
2862     if (start_idx + count > vertex_count) {
2863       if (indexed)
2864         return Result("START_IDX plus COUNT exceeds index buffer data size");
2865       else
2866         return Result("START_IDX plus COUNT exceeds vertex buffer data size");
2867     }
2868 
2869     auto cmd =
2870         MakeUnique<DrawArraysCommand>(pipeline, *pipeline->GetPipelineData());
2871     cmd->SetLine(line);
2872     cmd->SetTopology(topo);
2873     cmd->SetFirstVertexIndex(start_idx);
2874     cmd->SetVertexCount(count);
2875     cmd->SetInstanceCount(instance_count);
2876     cmd->SetFirstInstance(start_instance);
2877 
2878     if (indexed)
2879       cmd->EnableIndexed();
2880 
2881     command_list_.push_back(std::move(cmd));
2882     return ValidateEndOfStatement("RUN command");
2883   }
2884 
2885   return Result("invalid token in RUN command: " + token->AsString());
2886 }
2887 
ParseClear()2888 Result Parser::ParseClear() {
2889   auto token = tokenizer_->NextToken();
2890   if (!token->IsIdentifier())
2891     return Result("missing pipeline name for CLEAR command");
2892 
2893   size_t line = tokenizer_->GetCurrentLine();
2894 
2895   auto* pipeline = script_->GetPipeline(token->AsString());
2896   if (!pipeline)
2897     return Result("unknown pipeline for CLEAR command: " + token->AsString());
2898   if (!pipeline->IsGraphics())
2899     return Result("CLEAR command requires graphics pipeline");
2900 
2901   auto cmd = MakeUnique<ClearCommand>(pipeline);
2902   cmd->SetLine(line);
2903   command_list_.push_back(std::move(cmd));
2904 
2905   return ValidateEndOfStatement("CLEAR command");
2906 }
2907 
ParseValues(const std::string & name,Format * fmt,std::vector<Value> * values)2908 Result Parser::ParseValues(const std::string& name,
2909                            Format* fmt,
2910                            std::vector<Value>* values) {
2911   assert(values);
2912 
2913   auto token = tokenizer_->NextToken();
2914   const auto& segs = fmt->GetSegments();
2915   size_t seg_idx = 0;
2916   while (!token->IsEOL() && !token->IsEOS()) {
2917     Value v;
2918 
2919     while (segs[seg_idx].IsPadding()) {
2920       ++seg_idx;
2921       if (seg_idx >= segs.size())
2922         seg_idx = 0;
2923     }
2924 
2925     if (type::Type::IsFloat(segs[seg_idx].GetFormatMode())) {
2926       if (!token->IsInteger() && !token->IsDouble() && !token->IsHex()) {
2927         return Result(std::string("Invalid value provided to ") + name +
2928                       " command: " + token->ToOriginalString());
2929       }
2930 
2931       Result r = token->ConvertToDouble();
2932       if (!r.IsSuccess())
2933         return r;
2934 
2935       v.SetDoubleValue(token->AsDouble());
2936     } else {
2937       if (!token->IsInteger() && !token->IsHex()) {
2938         return Result(std::string("Invalid value provided to ") + name +
2939                       " command: " + token->ToOriginalString());
2940       }
2941 
2942       uint64_t val = token->IsHex() ? token->AsHex() : token->AsUint64();
2943       v.SetIntValue(val);
2944     }
2945     ++seg_idx;
2946     if (seg_idx >= segs.size())
2947       seg_idx = 0;
2948 
2949     values->push_back(v);
2950     token = tokenizer_->NextToken();
2951   }
2952   return {};
2953 }
2954 
ParseExpect()2955 Result Parser::ParseExpect() {
2956   auto token = tokenizer_->NextToken();
2957   if (!token->IsIdentifier())
2958     return Result("invalid buffer name in EXPECT command");
2959 
2960   if (token->AsString() == "IDX")
2961     return Result("missing buffer name between EXPECT and IDX");
2962   if (token->AsString() == "EQ_BUFFER")
2963     return Result("missing buffer name between EXPECT and EQ_BUFFER");
2964   if (token->AsString() == "RMSE_BUFFER")
2965     return Result("missing buffer name between EXPECT and RMSE_BUFFER");
2966   if (token->AsString() == "EQ_HISTOGRAM_EMD_BUFFER") {
2967     return Result(
2968         "missing buffer name between EXPECT and EQ_HISTOGRAM_EMD_BUFFER");
2969   }
2970 
2971   size_t line = tokenizer_->GetCurrentLine();
2972   auto* buffer = script_->GetBuffer(token->AsString());
2973   if (!buffer)
2974     return Result("unknown buffer name for EXPECT command: " +
2975                   token->AsString());
2976 
2977   token = tokenizer_->NextToken();
2978 
2979   if (!token->IsIdentifier())
2980     return Result("invalid comparator in EXPECT command");
2981 
2982   if (token->AsString() == "EQ_BUFFER" || token->AsString() == "RMSE_BUFFER" ||
2983       token->AsString() == "EQ_HISTOGRAM_EMD_BUFFER") {
2984     auto type = token->AsString();
2985 
2986     token = tokenizer_->NextToken();
2987     if (!token->IsIdentifier())
2988       return Result("invalid buffer name in EXPECT " + type + " command");
2989 
2990     auto* buffer_2 = script_->GetBuffer(token->AsString());
2991     if (!buffer_2) {
2992       return Result("unknown buffer name for EXPECT " + type +
2993                     " command: " + token->AsString());
2994     }
2995 
2996     if (!buffer->GetFormat()->Equal(buffer_2->GetFormat())) {
2997       return Result("EXPECT " + type +
2998                     " command cannot compare buffers of differing format");
2999     }
3000     if (buffer->ElementCount() != buffer_2->ElementCount()) {
3001       return Result("EXPECT " + type +
3002                     " command cannot compare buffers of different size: " +
3003                     std::to_string(buffer->ElementCount()) + " vs " +
3004                     std::to_string(buffer_2->ElementCount()));
3005     }
3006     if (buffer->GetWidth() != buffer_2->GetWidth()) {
3007       return Result("EXPECT " + type +
3008                     " command cannot compare buffers of different width");
3009     }
3010     if (buffer->GetHeight() != buffer_2->GetHeight()) {
3011       return Result("EXPECT " + type +
3012                     " command cannot compare buffers of different height");
3013     }
3014 
3015     auto cmd = MakeUnique<CompareBufferCommand>(buffer, buffer_2);
3016     if (type == "RMSE_BUFFER") {
3017       cmd->SetComparator(CompareBufferCommand::Comparator::kRmse);
3018 
3019       token = tokenizer_->NextToken();
3020       if (!token->IsIdentifier() && token->AsString() == "TOLERANCE")
3021         return Result("missing TOLERANCE for EXPECT RMSE_BUFFER");
3022 
3023       token = tokenizer_->NextToken();
3024       if (!token->IsInteger() && !token->IsDouble())
3025         return Result("invalid TOLERANCE for EXPECT RMSE_BUFFER");
3026 
3027       Result r = token->ConvertToDouble();
3028       if (!r.IsSuccess())
3029         return r;
3030 
3031       cmd->SetTolerance(token->AsFloat());
3032     } else if (type == "EQ_HISTOGRAM_EMD_BUFFER") {
3033       cmd->SetComparator(CompareBufferCommand::Comparator::kHistogramEmd);
3034 
3035       token = tokenizer_->NextToken();
3036       if (!token->IsIdentifier() && token->AsString() == "TOLERANCE")
3037         return Result("missing TOLERANCE for EXPECT EQ_HISTOGRAM_EMD_BUFFER");
3038 
3039       token = tokenizer_->NextToken();
3040       if (!token->IsInteger() && !token->IsDouble())
3041         return Result("invalid TOLERANCE for EXPECT EQ_HISTOGRAM_EMD_BUFFER");
3042 
3043       Result r = token->ConvertToDouble();
3044       if (!r.IsSuccess())
3045         return r;
3046 
3047       cmd->SetTolerance(token->AsFloat());
3048     }
3049 
3050     command_list_.push_back(std::move(cmd));
3051 
3052     // Early return
3053     return ValidateEndOfStatement("EXPECT " + type + " command");
3054   }
3055 
3056   if (token->AsString() != "IDX")
3057     return Result("missing IDX in EXPECT command");
3058 
3059   token = tokenizer_->NextToken();
3060   if (!token->IsInteger() || token->AsInt32() < 0)
3061     return Result("invalid X value in EXPECT command");
3062   token->ConvertToDouble();
3063   float x = token->AsFloat();
3064 
3065   bool has_y_val = false;
3066   float y = 0;
3067   token = tokenizer_->NextToken();
3068   if (token->IsInteger()) {
3069     has_y_val = true;
3070 
3071     if (token->AsInt32() < 0)
3072       return Result("invalid Y value in EXPECT command");
3073     token->ConvertToDouble();
3074     y = token->AsFloat();
3075 
3076     token = tokenizer_->NextToken();
3077   }
3078 
3079   if (token->IsIdentifier() && token->AsString() == "SIZE") {
3080     if (!has_y_val)
3081       return Result("invalid Y value in EXPECT command");
3082 
3083     auto probe = MakeUnique<ProbeCommand>(buffer);
3084     probe->SetLine(line);
3085     probe->SetX(x);
3086     probe->SetY(y);
3087     probe->SetProbeRect();
3088 
3089     token = tokenizer_->NextToken();
3090     if (!token->IsInteger() || token->AsInt32() <= 0)
3091       return Result("invalid width in EXPECT command");
3092     token->ConvertToDouble();
3093     probe->SetWidth(token->AsFloat());
3094 
3095     token = tokenizer_->NextToken();
3096     if (!token->IsInteger() || token->AsInt32() <= 0)
3097       return Result("invalid height in EXPECT command");
3098     token->ConvertToDouble();
3099     probe->SetHeight(token->AsFloat());
3100 
3101     token = tokenizer_->NextToken();
3102     if (!token->IsIdentifier()) {
3103       return Result("invalid token in EXPECT command:" +
3104                     token->ToOriginalString());
3105     }
3106 
3107     if (token->AsString() == "EQ_RGBA") {
3108       probe->SetIsRGBA();
3109     } else if (token->AsString() != "EQ_RGB") {
3110       return Result("unknown comparator type in EXPECT: " +
3111                     token->ToOriginalString());
3112     }
3113 
3114     token = tokenizer_->NextToken();
3115     if (!token->IsInteger() || token->AsInt32() < 0 || token->AsInt32() > 255)
3116       return Result("invalid R value in EXPECT command");
3117     token->ConvertToDouble();
3118     probe->SetR(token->AsFloat() / 255.f);
3119 
3120     token = tokenizer_->NextToken();
3121     if (!token->IsInteger() || token->AsInt32() < 0 || token->AsInt32() > 255)
3122       return Result("invalid G value in EXPECT command");
3123     token->ConvertToDouble();
3124     probe->SetG(token->AsFloat() / 255.f);
3125 
3126     token = tokenizer_->NextToken();
3127     if (!token->IsInteger() || token->AsInt32() < 0 || token->AsInt32() > 255)
3128       return Result("invalid B value in EXPECT command");
3129     token->ConvertToDouble();
3130     probe->SetB(token->AsFloat() / 255.f);
3131 
3132     if (probe->IsRGBA()) {
3133       token = tokenizer_->NextToken();
3134       if (!token->IsInteger() || token->AsInt32() < 0 || token->AsInt32() > 255)
3135         return Result("invalid A value in EXPECT command");
3136       token->ConvertToDouble();
3137       probe->SetA(token->AsFloat() / 255.f);
3138     }
3139 
3140     token = tokenizer_->NextToken();
3141     if (token->IsIdentifier() && token->AsString() == "TOLERANCE") {
3142       std::vector<Probe::Tolerance> tolerances;
3143 
3144       Result r = ParseTolerances(&tolerances);
3145 
3146       if (!r.IsSuccess())
3147         return r;
3148 
3149       if (tolerances.empty())
3150         return Result("TOLERANCE specified but no tolerances provided");
3151 
3152       if (!probe->IsRGBA() && tolerances.size() > 3) {
3153         return Result(
3154             "TOLERANCE for an RGB comparison has a maximum of 3 values");
3155       }
3156 
3157       if (tolerances.size() > 4) {
3158         return Result(
3159             "TOLERANCE for an RGBA comparison has a maximum of 4 values");
3160       }
3161 
3162       probe->SetTolerances(std::move(tolerances));
3163       token = tokenizer_->NextToken();
3164     }
3165 
3166     if (!token->IsEOL() && !token->IsEOS()) {
3167       return Result("extra parameters after EXPECT command: " +
3168                     token->ToOriginalString());
3169     }
3170 
3171     command_list_.push_back(std::move(probe));
3172 
3173     return {};
3174   }
3175 
3176   auto probe = MakeUnique<ProbeSSBOCommand>(buffer);
3177   probe->SetLine(line);
3178 
3179   if (token->IsIdentifier() && token->AsString() == "TOLERANCE") {
3180     std::vector<Probe::Tolerance> tolerances;
3181 
3182     Result r = ParseTolerances(&tolerances);
3183 
3184     if (!r.IsSuccess())
3185       return r;
3186 
3187     if (tolerances.empty())
3188       return Result("TOLERANCE specified but no tolerances provided");
3189     if (tolerances.size() > 4)
3190       return Result("TOLERANCE has a maximum of 4 values");
3191 
3192     probe->SetTolerances(std::move(tolerances));
3193     token = tokenizer_->NextToken();
3194   }
3195 
3196   if (!token->IsIdentifier() || !IsComparator(token->AsString())) {
3197     return Result("unexpected token in EXPECT command: " +
3198                   token->ToOriginalString());
3199   }
3200 
3201   if (has_y_val)
3202     return Result("Y value not needed for non-color comparator");
3203 
3204   auto cmp = ToComparator(token->AsString());
3205   if (probe->HasTolerances()) {
3206     if (cmp != ProbeSSBOCommand::Comparator::kEqual)
3207       return Result("TOLERANCE only available with EQ probes");
3208 
3209     cmp = ProbeSSBOCommand::Comparator::kFuzzyEqual;
3210   }
3211 
3212   probe->SetComparator(cmp);
3213   probe->SetFormat(buffer->GetFormat());
3214   probe->SetOffset(static_cast<uint32_t>(x));
3215 
3216   std::vector<Value> values;
3217   Result r = ParseValues("EXPECT", buffer->GetFormat(), &values);
3218   if (!r.IsSuccess())
3219     return r;
3220 
3221   if (values.empty())
3222     return Result("missing comparison values for EXPECT command");
3223 
3224   probe->SetValues(std::move(values));
3225   command_list_.push_back(std::move(probe));
3226 
3227   return {};
3228 }
3229 
ParseCopy()3230 Result Parser::ParseCopy() {
3231   auto token = tokenizer_->NextToken();
3232   if (token->IsEOL() || token->IsEOS())
3233     return Result("missing buffer name after COPY");
3234   if (!token->IsIdentifier())
3235     return Result("invalid buffer name after COPY");
3236 
3237   size_t line = tokenizer_->GetCurrentLine();
3238 
3239   auto name = token->AsString();
3240   if (name == "TO")
3241     return Result("missing buffer name between COPY and TO");
3242 
3243   Buffer* buffer_from = script_->GetBuffer(name);
3244   if (!buffer_from)
3245     return Result("COPY origin buffer was not declared");
3246 
3247   token = tokenizer_->NextToken();
3248   if (token->IsEOL() || token->IsEOS())
3249     return Result("missing 'TO' after COPY and buffer name");
3250   if (!token->IsIdentifier())
3251     return Result("expected 'TO' after COPY and buffer name");
3252 
3253   name = token->AsString();
3254   if (name != "TO")
3255     return Result("expected 'TO' after COPY and buffer name");
3256 
3257   token = tokenizer_->NextToken();
3258   if (token->IsEOL() || token->IsEOS())
3259     return Result("missing buffer name after TO");
3260   if (!token->IsIdentifier())
3261     return Result("invalid buffer name after TO");
3262 
3263   name = token->AsString();
3264   Buffer* buffer_to = script_->GetBuffer(name);
3265   if (!buffer_to)
3266     return Result("COPY destination buffer was not declared");
3267 
3268   // Set destination buffer to mirror origin buffer
3269   buffer_to->SetWidth(buffer_from->GetWidth());
3270   buffer_to->SetHeight(buffer_from->GetHeight());
3271   buffer_to->SetElementCount(buffer_from->ElementCount());
3272 
3273   if (buffer_from == buffer_to)
3274     return Result("COPY origin and destination buffers are identical");
3275 
3276   auto cmd = MakeUnique<CopyCommand>(buffer_from, buffer_to);
3277   cmd->SetLine(line);
3278   command_list_.push_back(std::move(cmd));
3279 
3280   return ValidateEndOfStatement("COPY command");
3281 }
3282 
ParseClearColor()3283 Result Parser::ParseClearColor() {
3284   auto token = tokenizer_->NextToken();
3285   if (!token->IsIdentifier())
3286     return Result("missing pipeline name for CLEAR_COLOR command");
3287 
3288   size_t line = tokenizer_->GetCurrentLine();
3289 
3290   auto* pipeline = script_->GetPipeline(token->AsString());
3291   if (!pipeline) {
3292     return Result("unknown pipeline for CLEAR_COLOR command: " +
3293                   token->AsString());
3294   }
3295   if (!pipeline->IsGraphics()) {
3296     return Result("CLEAR_COLOR command requires graphics pipeline");
3297   }
3298 
3299   auto cmd = MakeUnique<ClearColorCommand>(pipeline);
3300   cmd->SetLine(line);
3301 
3302   token = tokenizer_->NextToken();
3303   if (token->IsEOL() || token->IsEOS())
3304     return Result("missing R value for CLEAR_COLOR command");
3305   if (!token->IsInteger() || token->AsInt32() < 0 || token->AsInt32() > 255) {
3306     return Result("invalid R value for CLEAR_COLOR command: " +
3307                   token->ToOriginalString());
3308   }
3309   token->ConvertToDouble();
3310   cmd->SetR(token->AsFloat() / 255.f);
3311 
3312   token = tokenizer_->NextToken();
3313   if (token->IsEOL() || token->IsEOS())
3314     return Result("missing G value for CLEAR_COLOR command");
3315   if (!token->IsInteger() || token->AsInt32() < 0 || token->AsInt32() > 255) {
3316     return Result("invalid G value for CLEAR_COLOR command: " +
3317                   token->ToOriginalString());
3318   }
3319   token->ConvertToDouble();
3320   cmd->SetG(token->AsFloat() / 255.f);
3321 
3322   token = tokenizer_->NextToken();
3323   if (token->IsEOL() || token->IsEOS())
3324     return Result("missing B value for CLEAR_COLOR command");
3325   if (!token->IsInteger() || token->AsInt32() < 0 || token->AsInt32() > 255) {
3326     return Result("invalid B value for CLEAR_COLOR command: " +
3327                   token->ToOriginalString());
3328   }
3329   token->ConvertToDouble();
3330   cmd->SetB(token->AsFloat() / 255.f);
3331 
3332   token = tokenizer_->NextToken();
3333   if (token->IsEOL() || token->IsEOS())
3334     return Result("missing A value for CLEAR_COLOR command");
3335   if (!token->IsInteger() || token->AsInt32() < 0 || token->AsInt32() > 255) {
3336     return Result("invalid A value for CLEAR_COLOR command: " +
3337                   token->ToOriginalString());
3338   }
3339   token->ConvertToDouble();
3340   cmd->SetA(token->AsFloat() / 255.f);
3341 
3342   command_list_.push_back(std::move(cmd));
3343   return ValidateEndOfStatement("CLEAR_COLOR command");
3344 }
3345 
ParseClearDepth()3346 Result Parser::ParseClearDepth() {
3347   auto token = tokenizer_->NextToken();
3348   if (!token->IsIdentifier())
3349     return Result("missing pipeline name for CLEAR_DEPTH command");
3350 
3351   size_t line = tokenizer_->GetCurrentLine();
3352 
3353   auto* pipeline = script_->GetPipeline(token->AsString());
3354   if (!pipeline) {
3355     return Result("unknown pipeline for CLEAR_DEPTH command: " +
3356                   token->AsString());
3357   }
3358   if (!pipeline->IsGraphics()) {
3359     return Result("CLEAR_DEPTH command requires graphics pipeline");
3360   }
3361 
3362   auto cmd = MakeUnique<ClearDepthCommand>(pipeline);
3363   cmd->SetLine(line);
3364 
3365   token = tokenizer_->NextToken();
3366   if (token->IsEOL() || token->IsEOS())
3367     return Result("missing value for CLEAR_DEPTH command");
3368   if (!token->IsDouble()) {
3369     return Result("invalid value for CLEAR_DEPTH command: " +
3370                   token->ToOriginalString());
3371   }
3372   cmd->SetValue(token->AsFloat());
3373 
3374   command_list_.push_back(std::move(cmd));
3375   return ValidateEndOfStatement("CLEAR_DEPTH command");
3376 }
3377 
ParseClearStencil()3378 Result Parser::ParseClearStencil() {
3379   auto token = tokenizer_->NextToken();
3380   if (!token->IsIdentifier())
3381     return Result("missing pipeline name for CLEAR_STENCIL command");
3382 
3383   size_t line = tokenizer_->GetCurrentLine();
3384 
3385   auto* pipeline = script_->GetPipeline(token->AsString());
3386   if (!pipeline) {
3387     return Result("unknown pipeline for CLEAR_STENCIL command: " +
3388                   token->AsString());
3389   }
3390   if (!pipeline->IsGraphics()) {
3391     return Result("CLEAR_STENCIL command requires graphics pipeline");
3392   }
3393 
3394   auto cmd = MakeUnique<ClearStencilCommand>(pipeline);
3395   cmd->SetLine(line);
3396 
3397   token = tokenizer_->NextToken();
3398   if (token->IsEOL() || token->IsEOS())
3399     return Result("missing value for CLEAR_STENCIL command");
3400   if (!token->IsInteger() || token->AsInt32() < 0 || token->AsInt32() > 255) {
3401     return Result("invalid value for CLEAR_STENCIL command: " +
3402                   token->ToOriginalString());
3403   }
3404   cmd->SetValue(token->AsUint32());
3405 
3406   command_list_.push_back(std::move(cmd));
3407   return ValidateEndOfStatement("CLEAR_STENCIL command");
3408 }
3409 
ParseDeviceFeature()3410 Result Parser::ParseDeviceFeature() {
3411   auto token = tokenizer_->NextToken();
3412   if (token->IsEOS() || token->IsEOL())
3413     return Result("missing feature name for DEVICE_FEATURE command");
3414   if (!token->IsIdentifier())
3415     return Result("invalid feature name for DEVICE_FEATURE command");
3416   if (!script_->IsKnownFeature(token->AsString()))
3417     return Result("unknown feature name for DEVICE_FEATURE command");
3418 
3419   script_->AddRequiredFeature(token->AsString());
3420 
3421   return ValidateEndOfStatement("DEVICE_FEATURE command");
3422 }
3423 
ParseRepeat()3424 Result Parser::ParseRepeat() {
3425   auto token = tokenizer_->NextToken();
3426   if (token->IsEOL() || token->IsEOL())
3427     return Result("missing count parameter for REPEAT command");
3428   if (!token->IsInteger()) {
3429     return Result("invalid count parameter for REPEAT command: " +
3430                   token->ToOriginalString());
3431   }
3432   if (token->AsInt32() <= 0)
3433     return Result("count parameter must be > 0 for REPEAT command");
3434 
3435   uint32_t count = token->AsUint32();
3436 
3437   std::vector<std::unique_ptr<Command>> cur_commands;
3438   std::swap(cur_commands, command_list_);
3439 
3440   for (token = tokenizer_->NextToken(); !token->IsEOS();
3441        token = tokenizer_->NextToken()) {
3442     if (token->IsEOL())
3443       continue;
3444     if (!token->IsIdentifier())
3445       return Result("expected identifier");
3446 
3447     std::string tok = token->AsString();
3448     if (tok == "END")
3449       break;
3450     if (!IsRepeatable(tok))
3451       return Result("unknown token: " + tok);
3452 
3453     Result r = ParseRepeatableCommand(tok);
3454     if (!r.IsSuccess())
3455       return r;
3456   }
3457   if (!token->IsIdentifier() || token->AsString() != "END")
3458     return Result("missing END for REPEAT command");
3459 
3460   auto cmd = MakeUnique<RepeatCommand>(count);
3461   cmd->SetCommands(std::move(command_list_));
3462 
3463   std::swap(cur_commands, command_list_);
3464   command_list_.push_back(std::move(cmd));
3465 
3466   return ValidateEndOfStatement("REPEAT command");
3467 }
3468 
ParseDerivePipelineBlock()3469 Result Parser::ParseDerivePipelineBlock() {
3470   auto token = tokenizer_->NextToken();
3471   if (!token->IsIdentifier() || token->AsString() == "FROM")
3472     return Result("missing pipeline name for DERIVE_PIPELINE command");
3473 
3474   std::string name = token->AsString();
3475   if (script_->GetPipeline(name) != nullptr)
3476     return Result("duplicate pipeline name for DERIVE_PIPELINE command");
3477 
3478   token = tokenizer_->NextToken();
3479   if (!token->IsIdentifier() || token->AsString() != "FROM")
3480     return Result("missing FROM in DERIVE_PIPELINE command");
3481 
3482   token = tokenizer_->NextToken();
3483   if (!token->IsIdentifier())
3484     return Result("missing parent pipeline name in DERIVE_PIPELINE command");
3485 
3486   Pipeline* parent = script_->GetPipeline(token->AsString());
3487   if (!parent)
3488     return Result("unknown parent pipeline in DERIVE_PIPELINE command");
3489 
3490   Result r = ValidateEndOfStatement("DERIVE_PIPELINE command");
3491   if (!r.IsSuccess())
3492     return r;
3493 
3494   auto pipeline = parent->Clone();
3495   pipeline->SetName(name);
3496 
3497   return ParsePipelineBody("DERIVE_PIPELINE", std::move(pipeline));
3498 }
3499 
ParseDeviceExtension()3500 Result Parser::ParseDeviceExtension() {
3501   auto token = tokenizer_->NextToken();
3502   if (token->IsEOL() || token->IsEOS())
3503     return Result("DEVICE_EXTENSION missing name");
3504   if (!token->IsIdentifier()) {
3505     return Result("DEVICE_EXTENSION invalid name: " +
3506                   token->ToOriginalString());
3507   }
3508 
3509   script_->AddRequiredDeviceExtension(token->AsString());
3510 
3511   return ValidateEndOfStatement("DEVICE_EXTENSION command");
3512 }
3513 
ParseInstanceExtension()3514 Result Parser::ParseInstanceExtension() {
3515   auto token = tokenizer_->NextToken();
3516   if (token->IsEOL() || token->IsEOS())
3517     return Result("INSTANCE_EXTENSION missing name");
3518   if (!token->IsIdentifier()) {
3519     return Result("INSTANCE_EXTENSION invalid name: " +
3520                   token->ToOriginalString());
3521   }
3522 
3523   script_->AddRequiredInstanceExtension(token->AsString());
3524 
3525   return ValidateEndOfStatement("INSTANCE_EXTENSION command");
3526 }
3527 
ParseSet()3528 Result Parser::ParseSet() {
3529   auto token = tokenizer_->NextToken();
3530   if (!token->IsIdentifier() || token->AsString() != "ENGINE_DATA")
3531     return Result("SET missing ENGINE_DATA");
3532 
3533   token = tokenizer_->NextToken();
3534   if (token->IsEOS() || token->IsEOL())
3535     return Result("SET missing variable to be set");
3536 
3537   if (!token->IsIdentifier())
3538     return Result("SET invalid variable to set: " + token->ToOriginalString());
3539 
3540   if (token->AsString() != "fence_timeout_ms")
3541     return Result("SET unknown variable provided: " + token->AsString());
3542 
3543   token = tokenizer_->NextToken();
3544   if (token->IsEOS() || token->IsEOL())
3545     return Result("SET missing value for fence_timeout_ms");
3546   if (!token->IsInteger())
3547     return Result("SET invalid value for fence_timeout_ms, must be uint32");
3548 
3549   script_->GetEngineData().fence_timeout_ms = token->AsUint32();
3550 
3551   return ValidateEndOfStatement("SET command");
3552 }
3553 
ParseSampler()3554 Result Parser::ParseSampler() {
3555   auto token = tokenizer_->NextToken();
3556   if (!token->IsIdentifier())
3557     return Result("invalid token when looking for sampler name");
3558 
3559   auto sampler = MakeUnique<Sampler>();
3560   sampler->SetName(token->AsString());
3561 
3562   token = tokenizer_->NextToken();
3563   while (!token->IsEOS() && !token->IsEOL()) {
3564     if (!token->IsIdentifier())
3565       return Result("invalid token when looking for sampler parameters");
3566 
3567     auto param = token->AsString();
3568     if (param == "MAG_FILTER") {
3569       token = tokenizer_->NextToken();
3570 
3571       if (!token->IsIdentifier())
3572         return Result("invalid token when looking for MAG_FILTER value");
3573 
3574       auto filter = token->AsString();
3575 
3576       if (filter == "linear")
3577         sampler->SetMagFilter(FilterType::kLinear);
3578       else if (filter == "nearest")
3579         sampler->SetMagFilter(FilterType::kNearest);
3580       else
3581         return Result("invalid MAG_FILTER value " + filter);
3582     } else if (param == "MIN_FILTER") {
3583       token = tokenizer_->NextToken();
3584 
3585       if (!token->IsIdentifier())
3586         return Result("invalid token when looking for MIN_FILTER value");
3587 
3588       auto filter = token->AsString();
3589 
3590       if (filter == "linear")
3591         sampler->SetMinFilter(FilterType::kLinear);
3592       else if (filter == "nearest")
3593         sampler->SetMinFilter(FilterType::kNearest);
3594       else
3595         return Result("invalid MIN_FILTER value " + filter);
3596     } else if (param == "ADDRESS_MODE_U") {
3597       token = tokenizer_->NextToken();
3598 
3599       if (!token->IsIdentifier())
3600         return Result("invalid token when looking for ADDRESS_MODE_U value");
3601 
3602       auto mode_str = token->AsString();
3603       auto mode = StrToAddressMode(mode_str);
3604 
3605       if (mode == AddressMode::kUnknown)
3606         return Result("invalid ADDRESS_MODE_U value " + mode_str);
3607 
3608       sampler->SetAddressModeU(mode);
3609     } else if (param == "ADDRESS_MODE_V") {
3610       token = tokenizer_->NextToken();
3611 
3612       if (!token->IsIdentifier())
3613         return Result("invalid token when looking for ADDRESS_MODE_V value");
3614 
3615       auto mode_str = token->AsString();
3616       auto mode = StrToAddressMode(mode_str);
3617 
3618       if (mode == AddressMode::kUnknown)
3619         return Result("invalid ADDRESS_MODE_V value " + mode_str);
3620 
3621       sampler->SetAddressModeV(mode);
3622     } else if (param == "ADDRESS_MODE_W") {
3623       token = tokenizer_->NextToken();
3624 
3625       if (!token->IsIdentifier())
3626         return Result("invalid token when looking for ADDRESS_MODE_W value");
3627 
3628       auto mode_str = token->AsString();
3629       auto mode = StrToAddressMode(mode_str);
3630 
3631       if (mode == AddressMode::kUnknown)
3632         return Result("invalid ADDRESS_MODE_W value " + mode_str);
3633 
3634       sampler->SetAddressModeW(mode);
3635     } else if (param == "BORDER_COLOR") {
3636       token = tokenizer_->NextToken();
3637 
3638       if (!token->IsIdentifier())
3639         return Result("invalid token when looking for BORDER_COLOR value");
3640 
3641       auto color_str = token->AsString();
3642 
3643       if (color_str == "float_transparent_black")
3644         sampler->SetBorderColor(BorderColor::kFloatTransparentBlack);
3645       else if (color_str == "int_transparent_black")
3646         sampler->SetBorderColor(BorderColor::kIntTransparentBlack);
3647       else if (color_str == "float_opaque_black")
3648         sampler->SetBorderColor(BorderColor::kFloatOpaqueBlack);
3649       else if (color_str == "int_opaque_black")
3650         sampler->SetBorderColor(BorderColor::kIntOpaqueBlack);
3651       else if (color_str == "float_opaque_white")
3652         sampler->SetBorderColor(BorderColor::kFloatOpaqueWhite);
3653       else if (color_str == "int_opaque_white")
3654         sampler->SetBorderColor(BorderColor::kIntOpaqueWhite);
3655       else
3656         return Result("invalid BORDER_COLOR value " + color_str);
3657     } else if (param == "MIN_LOD") {
3658       token = tokenizer_->NextToken();
3659 
3660       if (!token->IsDouble())
3661         return Result("invalid token when looking for MIN_LOD value");
3662 
3663       sampler->SetMinLOD(token->AsFloat());
3664     } else if (param == "MAX_LOD") {
3665       token = tokenizer_->NextToken();
3666 
3667       if (!token->IsDouble())
3668         return Result("invalid token when looking for MAX_LOD value");
3669 
3670       sampler->SetMaxLOD(token->AsFloat());
3671     } else if (param == "NORMALIZED_COORDS") {
3672       sampler->SetNormalizedCoords(true);
3673     } else if (param == "UNNORMALIZED_COORDS") {
3674       sampler->SetNormalizedCoords(false);
3675       sampler->SetMinLOD(0.0f);
3676       sampler->SetMaxLOD(0.0f);
3677     } else if (param == "COMPARE") {
3678       token = tokenizer_->NextToken();
3679 
3680       if (!token->IsIdentifier())
3681         return Result("invalid value for COMPARE");
3682 
3683       if (token->AsString() == "on")
3684         sampler->SetCompareEnable(true);
3685       else if (token->AsString() == "off")
3686         sampler->SetCompareEnable(false);
3687       else
3688         return Result("invalid value for COMPARE: " + token->AsString());
3689     } else if (param == "COMPARE_OP") {
3690       token = tokenizer_->NextToken();
3691 
3692       if (!token->IsIdentifier())
3693         return Result("invalid value for COMPARE_OP");
3694 
3695       CompareOp compare_op = StrToCompareOp(token->AsString());
3696       if (compare_op != CompareOp::kUnknown) {
3697         sampler->SetCompareOp(compare_op);
3698       } else {
3699         return Result("invalid value for COMPARE_OP: " + token->AsString());
3700       }
3701     } else {
3702       return Result("unexpected sampler parameter " + param);
3703     }
3704 
3705     token = tokenizer_->NextToken();
3706   }
3707 
3708   if (sampler->GetMaxLOD() < sampler->GetMinLOD()) {
3709     return Result("max LOD needs to be greater than or equal to min LOD");
3710   }
3711 
3712   return script_->AddSampler(std::move(sampler));
3713 }
3714 
ParseTolerances(std::vector<Probe::Tolerance> * tolerances)3715 Result Parser::ParseTolerances(std::vector<Probe::Tolerance>* tolerances) {
3716   auto token = tokenizer_->PeekNextToken();
3717   while (!token->IsEOL() && !token->IsEOS()) {
3718     if (!token->IsInteger() && !token->IsDouble())
3719       break;
3720 
3721     token = tokenizer_->NextToken();
3722     Result r = token->ConvertToDouble();
3723     if (!r.IsSuccess())
3724       return r;
3725 
3726     double value = token->AsDouble();
3727     token = tokenizer_->PeekNextToken();
3728     if (token->IsIdentifier() && token->AsString() == "%") {
3729       tolerances->push_back(Probe::Tolerance{true, value});
3730       tokenizer_->NextToken();
3731       token = tokenizer_->PeekNextToken();
3732     } else {
3733       tolerances->push_back(Probe::Tolerance{false, value});
3734     }
3735   }
3736 
3737   return {};
3738 }
3739 
ParseVirtualFile()3740 Result Parser::ParseVirtualFile() {
3741   auto token = tokenizer_->NextToken();
3742   if (!token->IsIdentifier() && !token->IsString())
3743     return Result("invalid virtual file path");
3744 
3745   auto path = token->AsString();
3746 
3747   auto r = ValidateEndOfStatement("VIRTUAL_FILE command");
3748   if (!r.IsSuccess())
3749     return r;
3750 
3751   auto data = tokenizer_->ExtractToNext("END");
3752 
3753   token = tokenizer_->NextToken();
3754   if (!token->IsIdentifier() || token->AsString() != "END")
3755     return Result("VIRTUAL_FILE missing END command");
3756 
3757   return script_->AddVirtualFile(path, data);
3758 }
3759 
3760 }  // namespace amberscript
3761 }  // namespace amber
3762