| // Copyright 2020 The Tint Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "src/reader/wgsl/parser_impl.h" |
| |
| #include "src/ast/access_decoration.h" |
| #include "src/ast/bitcast_expression.h" |
| #include "src/ast/constant_id_decoration.h" |
| #include "src/ast/discard_statement.h" |
| #include "src/ast/fallthrough_statement.h" |
| #include "src/ast/stage_decoration.h" |
| #include "src/ast/struct_block_decoration.h" |
| #include "src/ast/unary_op_expression.h" |
| #include "src/ast/workgroup_decoration.h" |
| #include "src/reader/wgsl/lexer.h" |
| #include "src/type/access_control_type.h" |
| #include "src/type/depth_texture_type.h" |
| #include "src/type/multisampled_texture_type.h" |
| #include "src/type/sampled_texture_type.h" |
| |
| namespace tint { |
| namespace reader { |
| namespace wgsl { |
| namespace { |
| |
| template <typename T> |
| using Expect = ParserImpl::Expect<T>; |
| |
| template <typename T> |
| using Maybe = ParserImpl::Maybe<T>; |
| |
| /// Controls the maximum number of times we'll call into the sync() function |
| /// from itself. This is to guard against stack overflow when there is an |
| /// excessive number of blocks. |
| constexpr uint32_t kMaxSyncDepth = 128; |
| |
| /// The maximum number of tokens to look ahead to try and sync the |
| /// parser on error. |
| constexpr size_t const kMaxResynchronizeLookahead = 32; |
| |
| const char kVertexStage[] = "vertex"; |
| const char kFragmentStage[] = "fragment"; |
| const char kComputeStage[] = "compute"; |
| |
| const char kReadAccessControl[] = "read"; |
| const char kWriteAccessControl[] = "write"; |
| const char kReadWriteAccessControl[] = "read_write"; |
| |
| ast::Builtin ident_to_builtin(const std::string& str) { |
| if (str == "position") { |
| return ast::Builtin::kPosition; |
| } |
| if (str == "vertex_idx" || str == "vertex_index") { |
| return ast::Builtin::kVertexIndex; |
| } |
| if (str == "instance_idx" || str == "instance_index") { |
| return ast::Builtin::kInstanceIndex; |
| } |
| if (str == "front_facing") { |
| return ast::Builtin::kFrontFacing; |
| } |
| if (str == "frag_coord") { |
| return ast::Builtin::kFragCoord; |
| } |
| if (str == "frag_depth") { |
| return ast::Builtin::kFragDepth; |
| } |
| if (str == "local_invocation_id") { |
| return ast::Builtin::kLocalInvocationId; |
| } |
| if (str == "local_invocation_idx" || str == "local_invocation_index") { |
| return ast::Builtin::kLocalInvocationIndex; |
| } |
| if (str == "global_invocation_id") { |
| return ast::Builtin::kGlobalInvocationId; |
| } |
| if (str == "sample_index") { |
| return ast::Builtin::kSampleIndex; |
| } |
| if (str == "sample_mask_in") { |
| return ast::Builtin::kSampleMaskIn; |
| } |
| if (str == "sample_mask_out") { |
| return ast::Builtin::kSampleMaskOut; |
| } |
| return ast::Builtin::kNone; |
| } |
| |
| const char kAccessDecoration[] = "access"; |
| const char kBindingDecoration[] = "binding"; |
| const char kBlockDecoration[] = "block"; |
| const char kBuiltinDecoration[] = "builtin"; |
| const char kConstantIdDecoration[] = "constant_id"; |
| const char kGroupDecoration[] = "group"; |
| const char kLocationDecoration[] = "location"; |
| const char kOffsetDecoration[] = "offset"; |
| const char kSetDecoration[] = "set"; |
| const char kStageDecoration[] = "stage"; |
| const char kStrideDecoration[] = "stride"; |
| const char kWorkgroupSizeDecoration[] = "workgroup_size"; |
| |
| bool is_decoration(Token t) { |
| if (!t.IsIdentifier()) |
| return false; |
| |
| auto s = t.to_str(); |
| return s == kAccessDecoration || s == kBindingDecoration || |
| s == kBlockDecoration || s == kBuiltinDecoration || |
| s == kConstantIdDecoration || s == kLocationDecoration || |
| s == kOffsetDecoration || s == kSetDecoration || |
| s == kGroupDecoration || s == kStageDecoration || |
| s == kStrideDecoration || s == kWorkgroupSizeDecoration; |
| } |
| |
| /// Enter-exit counters for block token types. |
| /// Used by sync_to() to skip over closing block tokens that were opened during |
| /// the forward scan. |
| struct BlockCounters { |
| int attrs = 0; // [[ ]] |
| int brace = 0; // { } |
| int bracket = 0; // [ ] |
| int paren = 0; // ( ) |
| |
| /// @return the current enter-exit depth for the given block token type. If |
| /// `t` is not a block token type, then 0 is always returned. |
| int consume(const Token& t) { |
| if (t.Is(Token::Type::kAttrLeft)) |
| return attrs++; |
| if (t.Is(Token::Type::kAttrRight)) |
| return attrs--; |
| if (t.Is(Token::Type::kBraceLeft)) |
| return brace++; |
| if (t.Is(Token::Type::kBraceRight)) |
| return brace--; |
| if (t.Is(Token::Type::kBracketLeft)) |
| return bracket++; |
| if (t.Is(Token::Type::kBracketRight)) |
| return bracket--; |
| if (t.Is(Token::Type::kParenLeft)) |
| return paren++; |
| if (t.Is(Token::Type::kParenRight)) |
| return paren--; |
| return 0; |
| } |
| }; |
| |
| } // namespace |
| |
| ParserImpl::FunctionHeader::FunctionHeader() = default; |
| |
| ParserImpl::FunctionHeader::FunctionHeader(const FunctionHeader&) = default; |
| |
| ParserImpl::FunctionHeader::FunctionHeader(Source src, |
| std::string n, |
| ast::VariableList p, |
| type::Type* ret_ty) |
| : source(src), name(n), params(p), return_type(ret_ty) {} |
| |
| ParserImpl::FunctionHeader::~FunctionHeader() = default; |
| |
| ParserImpl::FunctionHeader& ParserImpl::FunctionHeader::operator=( |
| const FunctionHeader& rhs) = default; |
| |
| ParserImpl::ParserImpl(Source::File const* file) |
| : lexer_(std::make_unique<Lexer>(file->path, &file->content)) {} |
| |
| ParserImpl::~ParserImpl() = default; |
| |
| ParserImpl::Failure::Errored ParserImpl::add_error(const Source& source, |
| const std::string& err, |
| const std::string& use) { |
| std::stringstream msg; |
| msg << err; |
| if (!use.empty()) { |
| msg << " for " << use; |
| } |
| add_error(source, msg.str()); |
| return Failure::kErrored; |
| } |
| |
| ParserImpl::Failure::Errored ParserImpl::add_error(const Token& t, |
| const std::string& err) { |
| add_error(t.source(), err); |
| return Failure::kErrored; |
| } |
| |
| ParserImpl::Failure::Errored ParserImpl::add_error(const Source& source, |
| const std::string& err) { |
| if (silence_errors_ == 0) { |
| builder_.Diagnostics().add_error(err, source); |
| } |
| return Failure::kErrored; |
| } |
| |
| Token ParserImpl::next() { |
| if (!token_queue_.empty()) { |
| auto t = token_queue_.front(); |
| token_queue_.pop_front(); |
| return t; |
| } |
| return lexer_->next(); |
| } |
| |
| Token ParserImpl::peek(size_t idx) { |
| while (token_queue_.size() < (idx + 1)) |
| token_queue_.push_back(lexer_->next()); |
| |
| return token_queue_[idx]; |
| } |
| |
| Token ParserImpl::peek() { |
| return peek(0); |
| } |
| |
| void ParserImpl::register_constructed(const std::string& name, |
| type::Type* type) { |
| assert(type); |
| registered_constructs_[name] = type; |
| } |
| |
| type::Type* ParserImpl::get_constructed(const std::string& name) { |
| if (registered_constructs_.find(name) == registered_constructs_.end()) { |
| return nullptr; |
| } |
| return registered_constructs_[name]; |
| } |
| |
| bool ParserImpl::Parse() { |
| translation_unit(); |
| return !has_error(); |
| } |
| |
| // translation_unit |
| // : global_decl* EOF |
| void ParserImpl::translation_unit() { |
| while (synchronized_) { |
| auto p = peek(); |
| if (p.IsEof()) { |
| break; |
| } |
| expect_global_decl(); |
| if (builder_.Diagnostics().error_count() >= max_errors_) { |
| add_error(Source{{}, p.source().file_path}, |
| "stopping after " + std::to_string(max_errors_) + " errors"); |
| break; |
| } |
| } |
| } |
| |
| // global_decl |
| // : SEMICOLON |
| // | global_variable_decl SEMICLON |
| // | global_constant_decl SEMICOLON |
| // | type_alias SEMICOLON |
| // | struct_decl SEMICOLON |
| // | function_decl |
| Expect<bool> ParserImpl::expect_global_decl() { |
| if (match(Token::Type::kSemicolon) || match(Token::Type::kEOF)) |
| return true; |
| |
| bool errored = false; |
| |
| auto decos = decoration_list(); |
| if (decos.errored) |
| errored = true; |
| if (!synchronized_) |
| return Failure::kErrored; |
| |
| auto decl = sync(Token::Type::kSemicolon, [&]() -> Maybe<bool> { |
| auto gv = global_variable_decl(decos.value); |
| if (gv.errored) |
| return Failure::kErrored; |
| if (gv.matched) { |
| if (!expect("variable declaration", Token::Type::kSemicolon)) |
| return Failure::kErrored; |
| |
| builder_.AST().AddGlobalVariable(gv.value); |
| return true; |
| } |
| |
| auto gc = global_constant_decl(decos.value); |
| if (gc.errored) |
| return Failure::kErrored; |
| |
| if (gc.matched) { |
| if (!expect("constant declaration", Token::Type::kSemicolon)) |
| return Failure::kErrored; |
| |
| builder_.AST().AddGlobalVariable(gc.value); |
| return true; |
| } |
| |
| auto ta = type_alias(); |
| if (ta.errored) |
| return Failure::kErrored; |
| |
| if (ta.matched) { |
| if (!expect("type alias", Token::Type::kSemicolon)) |
| return Failure::kErrored; |
| |
| builder_.AST().AddConstructedType(ta.value); |
| return true; |
| } |
| |
| auto str = struct_decl(decos.value); |
| if (str.errored) |
| return Failure::kErrored; |
| |
| if (str.matched) { |
| if (!expect("struct declaration", Token::Type::kSemicolon)) |
| return Failure::kErrored; |
| |
| register_constructed(builder_.Symbols().NameFor(str.value->symbol()), |
| str.value); |
| builder_.AST().AddConstructedType(str.value); |
| return true; |
| } |
| |
| return Failure::kNoMatch; |
| }); |
| |
| if (decl.errored) |
| errored = true; |
| if (decl.matched) |
| return true; |
| |
| auto func = function_decl(decos.value); |
| if (func.errored) |
| errored = true; |
| if (func.matched) { |
| builder_.AST().AddFunction(func.value); |
| return true; |
| } |
| |
| if (errored) |
| return Failure::kErrored; |
| |
| // Invalid syntax found - try and determine the best error message |
| |
| // We have decorations parsed, but nothing to consume them? |
| if (decos.value.size() > 0) |
| return add_error(next(), "expected declaration after decorations"); |
| |
| // We have a statement outside of a function? |
| auto t = peek(); |
| auto stat = without_error([&] { return statement(); }); |
| if (stat.matched) { |
| // Attempt to jump to the next '}' - the function might have just been |
| // missing an opening line. |
| sync_to(Token::Type::kBraceRight, true); |
| return add_error(t, "statement found outside of function body"); |
| } |
| if (!stat.errored) { |
| // No match, no error - the parser might not have progressed. |
| // Ensure we always make _some_ forward progress. |
| next(); |
| } |
| |
| // Exhausted all attempts to make sense of where we're at. |
| // Spew a generic error. |
| |
| return add_error(t, "unexpected token"); |
| } |
| |
| // global_variable_decl |
| // : variable_decoration_list* variable_decl |
| // | variable_decoration_list* variable_decl EQUAL const_expr |
| Maybe<ast::Variable*> ParserImpl::global_variable_decl( |
| ast::DecorationList& decos) { |
| auto decl = variable_decl(); |
| if (decl.errored) |
| return Failure::kErrored; |
| if (!decl.matched) |
| return Failure::kNoMatch; |
| |
| ast::Expression* constructor = nullptr; |
| if (match(Token::Type::kEqual)) { |
| auto expr = expect_const_expr(); |
| if (expr.errored) |
| return Failure::kErrored; |
| constructor = expr.value; |
| } |
| |
| return create<ast::Variable>( |
| decl->source, // source |
| builder_.Symbols().Register(decl->name), // symbol |
| decl->storage_class, // storage_class |
| decl->type, // type |
| false, // is_const |
| constructor, // constructor |
| std::move(decos)); // decorations |
| } |
| |
| // global_constant_decl |
| // : variable_decoration_list* CONST variable_ident_decl EQUAL const_expr |
| Maybe<ast::Variable*> ParserImpl::global_constant_decl( |
| ast::DecorationList& decos) { |
| if (!match(Token::Type::kConst)) |
| return Failure::kNoMatch; |
| |
| const char* use = "constant declaration"; |
| |
| auto decl = expect_variable_ident_decl(use); |
| if (decl.errored) |
| return Failure::kErrored; |
| |
| if (!expect(use, Token::Type::kEqual)) |
| return Failure::kErrored; |
| |
| auto init = expect_const_expr(); |
| if (init.errored) |
| return Failure::kErrored; |
| |
| return create<ast::Variable>( |
| decl->source, // source |
| builder_.Symbols().Register(decl->name), // symbol |
| ast::StorageClass::kNone, // storage_class |
| decl->type, // type |
| true, // is_const |
| init.value, // constructor |
| std::move(decos)); // decorations |
| } |
| |
| // variable_decl |
| // : VAR variable_storage_decoration? variable_ident_decl |
| Maybe<ParserImpl::VarDeclInfo> ParserImpl::variable_decl() { |
| if (!match(Token::Type::kVar)) |
| return Failure::kNoMatch; |
| |
| ast::StorageClass sc = ast::StorageClass::kNone; |
| auto explicit_sc = variable_storage_decoration(); |
| if (explicit_sc.errored) |
| return Failure::kErrored; |
| if (explicit_sc.matched) |
| sc = explicit_sc.value; |
| |
| auto decl = expect_variable_ident_decl("variable declaration"); |
| if (decl.errored) |
| return Failure::kErrored; |
| |
| if (decl->type->UnwrapAll()->is_handle()) { |
| // handle types implicitly have the `UniformConstant` storage class. |
| // TODO(jrprice): Produce an error if an explicit storage class is provided. |
| sc = ast::StorageClass::kUniformConstant; |
| } |
| |
| return VarDeclInfo{decl->source, decl->name, sc, decl->type}; |
| } |
| |
| // texture_sampler_types |
| // : sampler_type |
| // | depth_texture_type |
| // | sampled_texture_type LESS_THAN type_decl GREATER_THAN |
| // | multisampled_texture_type LESS_THAN type_decl GREATER_THAN |
| // | storage_texture_type LESS_THAN image_storage_type GREATER_THAN |
| Maybe<type::Type*> ParserImpl::texture_sampler_types() { |
| auto type = sampler_type(); |
| if (type.matched) |
| return type; |
| |
| type = depth_texture_type(); |
| if (type.matched) |
| return type.value; |
| |
| auto dim = sampled_texture_type(); |
| if (dim.matched) { |
| const char* use = "sampled texture type"; |
| |
| auto subtype = expect_lt_gt_block(use, [&] { return expect_type(use); }); |
| if (subtype.errored) |
| return Failure::kErrored; |
| |
| return builder_.create<type::SampledTexture>(dim.value, subtype.value); |
| } |
| |
| auto ms_dim = multisampled_texture_type(); |
| if (ms_dim.matched) { |
| const char* use = "multisampled texture type"; |
| |
| auto subtype = expect_lt_gt_block(use, [&] { return expect_type(use); }); |
| if (subtype.errored) |
| return Failure::kErrored; |
| |
| return builder_.create<type::MultisampledTexture>(ms_dim.value, |
| subtype.value); |
| } |
| |
| auto storage = storage_texture_type(); |
| if (storage.matched) { |
| const char* use = "storage texture type"; |
| |
| auto format = |
| expect_lt_gt_block(use, [&] { return expect_image_storage_type(use); }); |
| |
| if (format.errored) |
| return Failure::kErrored; |
| |
| auto* subtype = |
| type::StorageTexture::SubtypeFor(format.value, builder_.Types()); |
| return builder_.create<type::StorageTexture>(storage.value, format.value, |
| subtype); |
| } |
| |
| return Failure::kNoMatch; |
| } |
| |
| // sampler_type |
| // : SAMPLER |
| // | SAMPLER_COMPARISON |
| Maybe<type::Type*> ParserImpl::sampler_type() { |
| if (match(Token::Type::kSampler)) |
| return builder_.create<type::Sampler>(type::SamplerKind::kSampler); |
| |
| if (match(Token::Type::kComparisonSampler)) |
| return builder_.create<type::Sampler>( |
| type::SamplerKind::kComparisonSampler); |
| |
| return Failure::kNoMatch; |
| } |
| |
| // sampled_texture_type |
| // : TEXTURE_SAMPLED_1D |
| // | TEXTURE_SAMPLED_2D |
| // | TEXTURE_SAMPLED_2D_ARRAY |
| // | TEXTURE_SAMPLED_3D |
| // | TEXTURE_SAMPLED_CUBE |
| // | TEXTURE_SAMPLED_CUBE_ARRAY |
| Maybe<type::TextureDimension> ParserImpl::sampled_texture_type() { |
| if (match(Token::Type::kTextureSampled1d)) |
| return type::TextureDimension::k1d; |
| |
| if (match(Token::Type::kTextureSampled2d)) |
| return type::TextureDimension::k2d; |
| |
| if (match(Token::Type::kTextureSampled2dArray)) |
| return type::TextureDimension::k2dArray; |
| |
| if (match(Token::Type::kTextureSampled3d)) |
| return type::TextureDimension::k3d; |
| |
| if (match(Token::Type::kTextureSampledCube)) |
| return type::TextureDimension::kCube; |
| |
| if (match(Token::Type::kTextureSampledCubeArray)) |
| return type::TextureDimension::kCubeArray; |
| |
| return Failure::kNoMatch; |
| } |
| |
| // multisampled_texture_type |
| // : TEXTURE_MULTISAMPLED_2D |
| Maybe<type::TextureDimension> ParserImpl::multisampled_texture_type() { |
| if (match(Token::Type::kTextureMultisampled2d)) |
| return type::TextureDimension::k2d; |
| |
| return Failure::kNoMatch; |
| } |
| |
| // storage_texture_type |
| // : TEXTURE_STORAGE_1D |
| // | TEXTURE_STORAGE_2D |
| // | TEXTURE_STORAGE_2D_ARRAY |
| // | TEXTURE_STORAGE_3D |
| Maybe<type::TextureDimension> ParserImpl::storage_texture_type() { |
| if (match(Token::Type::kTextureStorage1d)) |
| return type::TextureDimension::k1d; |
| if (match(Token::Type::kTextureStorage2d)) |
| return type::TextureDimension::k2d; |
| if (match(Token::Type::kTextureStorage2dArray)) |
| return type::TextureDimension::k2dArray; |
| if (match(Token::Type::kTextureStorage3d)) |
| return type::TextureDimension::k3d; |
| |
| return Failure::kNoMatch; |
| } |
| |
| // depth_texture_type |
| // : TEXTURE_DEPTH_2D |
| // | TEXTURE_DEPTH_2D_ARRAY |
| // | TEXTURE_DEPTH_CUBE |
| // | TEXTURE_DEPTH_CUBE_ARRAY |
| Maybe<type::Type*> ParserImpl::depth_texture_type() { |
| if (match(Token::Type::kTextureDepth2d)) |
| return builder_.create<type::DepthTexture>(type::TextureDimension::k2d); |
| |
| if (match(Token::Type::kTextureDepth2dArray)) |
| return builder_.create<type::DepthTexture>( |
| type::TextureDimension::k2dArray); |
| |
| if (match(Token::Type::kTextureDepthCube)) |
| return builder_.create<type::DepthTexture>(type::TextureDimension::kCube); |
| |
| if (match(Token::Type::kTextureDepthCubeArray)) |
| return builder_.create<type::DepthTexture>( |
| type::TextureDimension::kCubeArray); |
| |
| return Failure::kNoMatch; |
| } |
| |
| // image_storage_type |
| // : R8UNORM |
| // | R8SNORM |
| // | R8UINT |
| // | R8SINT |
| // | R16UINT |
| // | R16SINT |
| // | R16FLOAT |
| // | RG8UNORM |
| // | RG8SNORM |
| // | RG8UINT |
| // | RG8SINT |
| // | R32UINT |
| // | R32SINT |
| // | R32FLOAT |
| // | RG16UINT |
| // | RG16SINT |
| // | RG16FLOAT |
| // | RGBA8UNORM |
| /// | RGBA8UNORM-SRGB |
| // | RGBA8SNORM |
| // | RGBA8UINT |
| // | RGBA8SINT |
| // | BGRA8UNORM |
| // | BGRA8UNORM-SRGB |
| // | RGB10A2UNORM |
| // | RG11B10FLOAT |
| // | RG32UINT |
| // | RG32SINT |
| // | RG32FLOAT |
| // | RGBA16UINT |
| // | RGBA16SINT |
| // | RGBA16FLOAT |
| // | RGBA32UINT |
| // | RGBA32SINT |
| // | RGBA32FLOAT |
| Expect<type::ImageFormat> ParserImpl::expect_image_storage_type( |
| const std::string& use) { |
| if (match(Token::Type::kFormatR8Unorm)) |
| return type::ImageFormat::kR8Unorm; |
| |
| if (match(Token::Type::kFormatR8Snorm)) |
| return type::ImageFormat::kR8Snorm; |
| |
| if (match(Token::Type::kFormatR8Uint)) |
| return type::ImageFormat::kR8Uint; |
| |
| if (match(Token::Type::kFormatR8Sint)) |
| return type::ImageFormat::kR8Sint; |
| |
| if (match(Token::Type::kFormatR16Uint)) |
| return type::ImageFormat::kR16Uint; |
| |
| if (match(Token::Type::kFormatR16Sint)) |
| return type::ImageFormat::kR16Sint; |
| |
| if (match(Token::Type::kFormatR16Float)) |
| return type::ImageFormat::kR16Float; |
| |
| if (match(Token::Type::kFormatRg8Unorm)) |
| return type::ImageFormat::kRg8Unorm; |
| |
| if (match(Token::Type::kFormatRg8Snorm)) |
| return type::ImageFormat::kRg8Snorm; |
| |
| if (match(Token::Type::kFormatRg8Uint)) |
| return type::ImageFormat::kRg8Uint; |
| |
| if (match(Token::Type::kFormatRg8Sint)) |
| return type::ImageFormat::kRg8Sint; |
| |
| if (match(Token::Type::kFormatR32Uint)) |
| return type::ImageFormat::kR32Uint; |
| |
| if (match(Token::Type::kFormatR32Sint)) |
| return type::ImageFormat::kR32Sint; |
| |
| if (match(Token::Type::kFormatR32Float)) |
| return type::ImageFormat::kR32Float; |
| |
| if (match(Token::Type::kFormatRg16Uint)) |
| return type::ImageFormat::kRg16Uint; |
| |
| if (match(Token::Type::kFormatRg16Sint)) |
| return type::ImageFormat::kRg16Sint; |
| |
| if (match(Token::Type::kFormatRg16Float)) |
| return type::ImageFormat::kRg16Float; |
| |
| if (match(Token::Type::kFormatRgba8Unorm)) |
| return type::ImageFormat::kRgba8Unorm; |
| |
| if (match(Token::Type::kFormatRgba8UnormSrgb)) |
| return type::ImageFormat::kRgba8UnormSrgb; |
| |
| if (match(Token::Type::kFormatRgba8Snorm)) |
| return type::ImageFormat::kRgba8Snorm; |
| |
| if (match(Token::Type::kFormatRgba8Uint)) |
| return type::ImageFormat::kRgba8Uint; |
| |
| if (match(Token::Type::kFormatRgba8Sint)) |
| return type::ImageFormat::kRgba8Sint; |
| |
| if (match(Token::Type::kFormatBgra8Unorm)) |
| return type::ImageFormat::kBgra8Unorm; |
| |
| if (match(Token::Type::kFormatBgra8UnormSrgb)) |
| return type::ImageFormat::kBgra8UnormSrgb; |
| |
| if (match(Token::Type::kFormatRgb10A2Unorm)) |
| return type::ImageFormat::kRgb10A2Unorm; |
| |
| if (match(Token::Type::kFormatRg11B10Float)) |
| return type::ImageFormat::kRg11B10Float; |
| |
| if (match(Token::Type::kFormatRg32Uint)) |
| return type::ImageFormat::kRg32Uint; |
| |
| if (match(Token::Type::kFormatRg32Sint)) |
| return type::ImageFormat::kRg32Sint; |
| |
| if (match(Token::Type::kFormatRg32Float)) |
| return type::ImageFormat::kRg32Float; |
| |
| if (match(Token::Type::kFormatRgba16Uint)) |
| return type::ImageFormat::kRgba16Uint; |
| |
| if (match(Token::Type::kFormatRgba16Sint)) |
| return type::ImageFormat::kRgba16Sint; |
| |
| if (match(Token::Type::kFormatRgba16Float)) |
| return type::ImageFormat::kRgba16Float; |
| |
| if (match(Token::Type::kFormatRgba32Uint)) |
| return type::ImageFormat::kRgba32Uint; |
| |
| if (match(Token::Type::kFormatRgba32Sint)) |
| return type::ImageFormat::kRgba32Sint; |
| |
| if (match(Token::Type::kFormatRgba32Float)) |
| return type::ImageFormat::kRgba32Float; |
| |
| return add_error(peek().source(), "invalid format", use); |
| } |
| |
| // variable_ident_decl |
| // : IDENT COLON variable_decoration_list* type_decl |
| Expect<ParserImpl::TypedIdentifier> ParserImpl::expect_variable_ident_decl( |
| const std::string& use) { |
| auto ident = expect_ident(use); |
| if (ident.errored) |
| return Failure::kErrored; |
| |
| if (!expect(use, Token::Type::kColon)) |
| return Failure::kErrored; |
| |
| auto decos = decoration_list(); |
| if (decos.errored) |
| return Failure::kErrored; |
| |
| auto access_decos = take_decorations<ast::AccessDecoration>(decos.value); |
| |
| auto t = peek(); |
| auto type = type_decl(decos.value); |
| if (type.errored) |
| return Failure::kErrored; |
| if (!type.matched) |
| return add_error(t.source(), "invalid type", use); |
| |
| if (!expect_decorations_consumed(decos.value)) |
| return Failure::kErrored; |
| |
| if (access_decos.size() > 1) |
| return add_error(ident.source, "multiple access decorations not allowed"); |
| |
| auto* ty = type.value; |
| for (auto* deco : access_decos) { |
| // If we have an access control decoration then we take it and wrap our |
| // type up with that decoration |
| ty = builder_.create<type::AccessControl>( |
| deco->As<ast::AccessDecoration>()->value(), ty); |
| } |
| |
| return TypedIdentifier{ty, ident.value, ident.source}; |
| } |
| |
| Expect<ast::AccessControl> ParserImpl::expect_access_type() { |
| auto ident = expect_ident("access_type"); |
| if (ident.errored) |
| return Failure::kErrored; |
| |
| if (ident.value == kReadAccessControl) |
| return {ast::AccessControl::kReadOnly, ident.source}; |
| if (ident.value == kWriteAccessControl) |
| return {ast::AccessControl::kWriteOnly, ident.source}; |
| if (ident.value == kReadWriteAccessControl) |
| return {ast::AccessControl::kReadWrite, ident.source}; |
| |
| return add_error(ident.source, "invalid value for access decoration"); |
| } |
| |
| // variable_storage_decoration |
| // : LESS_THAN storage_class GREATER_THAN |
| Maybe<ast::StorageClass> ParserImpl::variable_storage_decoration() { |
| if (!peek().IsLessThan()) |
| return Failure::kNoMatch; |
| |
| const char* use = "variable decoration"; |
| |
| auto sc = expect_lt_gt_block(use, [&] { return expect_storage_class(use); }); |
| |
| if (sc.errored) |
| return Failure::kErrored; |
| |
| return sc; |
| } |
| |
| // type_alias |
| // : TYPE IDENT EQUAL type_decl |
| Maybe<type::Type*> ParserImpl::type_alias() { |
| auto t = peek(); |
| if (!t.IsType()) |
| return Failure::kNoMatch; |
| |
| next(); // Consume the peek |
| |
| const char* use = "type alias"; |
| |
| auto name = expect_ident(use); |
| if (name.errored) |
| return Failure::kErrored; |
| |
| if (!expect(use, Token::Type::kEqual)) |
| return Failure::kErrored; |
| |
| auto type = type_decl(); |
| if (type.errored) |
| return Failure::kErrored; |
| if (!type.matched) |
| return add_error(peek(), "invalid type alias"); |
| |
| auto* alias = builder_.create<type::Alias>( |
| builder_.Symbols().Register(name.value), type.value); |
| register_constructed(name.value, alias); |
| |
| return alias; |
| } |
| |
| // type_decl |
| // : IDENTIFIER |
| // | BOOL |
| // | FLOAT32 |
| // | INT32 |
| // | UINT32 |
| // | VEC2 LESS_THAN type_decl GREATER_THAN |
| // | VEC3 LESS_THAN type_decl GREATER_THAN |
| // | VEC4 LESS_THAN type_decl GREATER_THAN |
| // | PTR LESS_THAN storage_class, type_decl GREATER_THAN |
| // | array_decoration_list* ARRAY LESS_THAN type_decl COMMA |
| // INT_LITERAL GREATER_THAN |
| // | array_decoration_list* ARRAY LESS_THAN type_decl |
| // GREATER_THAN |
| // | MAT2x2 LESS_THAN type_decl GREATER_THAN |
| // | MAT2x3 LESS_THAN type_decl GREATER_THAN |
| // | MAT2x4 LESS_THAN type_decl GREATER_THAN |
| // | MAT3x2 LESS_THAN type_decl GREATER_THAN |
| // | MAT3x3 LESS_THAN type_decl GREATER_THAN |
| // | MAT3x4 LESS_THAN type_decl GREATER_THAN |
| // | MAT4x2 LESS_THAN type_decl GREATER_THAN |
| // | MAT4x3 LESS_THAN type_decl GREATER_THAN |
| // | MAT4x4 LESS_THAN type_decl GREATER_THAN |
| // | texture_sampler_types |
| Maybe<type::Type*> ParserImpl::type_decl() { |
| auto decos = decoration_list(); |
| if (decos.errored) |
| return Failure::kErrored; |
| |
| auto type = type_decl(decos.value); |
| if (type.errored) |
| return Failure::kErrored; |
| if (!type.matched) |
| return Failure::kNoMatch; |
| |
| if (!expect_decorations_consumed(decos.value)) |
| return Failure::kErrored; |
| |
| return type.value; |
| } |
| |
| Maybe<type::Type*> ParserImpl::type_decl(ast::DecorationList& decos) { |
| auto t = peek(); |
| if (match(Token::Type::kIdentifier)) { |
| auto* ty = get_constructed(t.to_str()); |
| if (ty == nullptr) |
| return add_error(t, "unknown constructed type '" + t.to_str() + "'"); |
| |
| return ty; |
| } |
| |
| if (match(Token::Type::kBool)) |
| return builder_.create<type::Bool>(); |
| |
| if (match(Token::Type::kF32)) |
| return builder_.create<type::F32>(); |
| |
| if (match(Token::Type::kI32)) |
| return builder_.create<type::I32>(); |
| |
| if (match(Token::Type::kU32)) |
| return builder_.create<type::U32>(); |
| |
| if (t.IsVec2() || t.IsVec3() || t.IsVec4()) { |
| next(); // Consume the peek |
| return expect_type_decl_vector(t); |
| } |
| |
| if (match(Token::Type::kPtr)) |
| return expect_type_decl_pointer(); |
| |
| if (match(Token::Type::kArray)) { |
| return expect_type_decl_array(std::move(decos)); |
| } |
| |
| if (t.IsMat2x2() || t.IsMat2x3() || t.IsMat2x4() || t.IsMat3x2() || |
| t.IsMat3x3() || t.IsMat3x4() || t.IsMat4x2() || t.IsMat4x3() || |
| t.IsMat4x4()) { |
| next(); // Consume the peek |
| return expect_type_decl_matrix(t); |
| } |
| |
| auto texture_or_sampler = texture_sampler_types(); |
| if (texture_or_sampler.errored) |
| return Failure::kErrored; |
| if (texture_or_sampler.matched) |
| return texture_or_sampler.value; |
| |
| return Failure::kNoMatch; |
| } |
| |
| Expect<type::Type*> ParserImpl::expect_type(const std::string& use) { |
| auto type = type_decl(); |
| if (type.errored) |
| return Failure::kErrored; |
| if (!type.matched) |
| return add_error(peek().source(), "invalid type", use); |
| return type.value; |
| } |
| |
| Expect<type::Type*> ParserImpl::expect_type_decl_pointer() { |
| const char* use = "ptr declaration"; |
| |
| return expect_lt_gt_block(use, [&]() -> Expect<type::Type*> { |
| auto sc = expect_storage_class(use); |
| if (sc.errored) |
| return Failure::kErrored; |
| |
| if (!expect(use, Token::Type::kComma)) |
| return Failure::kErrored; |
| |
| auto subtype = expect_type(use); |
| if (subtype.errored) |
| return Failure::kErrored; |
| |
| return builder_.create<type::Pointer>(subtype.value, sc.value); |
| }); |
| } |
| |
| Expect<type::Type*> ParserImpl::expect_type_decl_vector(Token t) { |
| uint32_t count = 2; |
| if (t.IsVec3()) |
| count = 3; |
| else if (t.IsVec4()) |
| count = 4; |
| |
| const char* use = "vector"; |
| |
| auto subtype = expect_lt_gt_block(use, [&] { return expect_type(use); }); |
| if (subtype.errored) |
| return Failure::kErrored; |
| |
| return builder_.create<type::Vector>(subtype.value, count); |
| } |
| |
| Expect<type::Type*> ParserImpl::expect_type_decl_array( |
| ast::DecorationList decos) { |
| const char* use = "array declaration"; |
| |
| return expect_lt_gt_block(use, [&]() -> Expect<type::Type*> { |
| auto subtype = expect_type(use); |
| if (subtype.errored) |
| return Failure::kErrored; |
| |
| uint32_t size = 0; |
| if (match(Token::Type::kComma)) { |
| auto val = expect_nonzero_positive_sint("array size"); |
| if (val.errored) |
| return Failure::kErrored; |
| size = val.value; |
| } |
| |
| return create<type::Array>(subtype.value, size, std::move(decos)); |
| }); |
| } |
| |
| Expect<type::Type*> ParserImpl::expect_type_decl_matrix(Token t) { |
| uint32_t rows = 2; |
| uint32_t columns = 2; |
| if (t.IsMat3x2() || t.IsMat3x3() || t.IsMat3x4()) { |
| columns = 3; |
| } else if (t.IsMat4x2() || t.IsMat4x3() || t.IsMat4x4()) { |
| columns = 4; |
| } |
| if (t.IsMat2x3() || t.IsMat3x3() || t.IsMat4x3()) { |
| rows = 3; |
| } else if (t.IsMat2x4() || t.IsMat3x4() || t.IsMat4x4()) { |
| rows = 4; |
| } |
| |
| const char* use = "matrix"; |
| |
| auto subtype = expect_lt_gt_block(use, [&] { return expect_type(use); }); |
| if (subtype.errored) |
| return Failure::kErrored; |
| |
| return builder_.create<type::Matrix>(subtype.value, rows, columns); |
| } |
| |
| // storage_class |
| // : INPUT |
| // | OUTPUT |
| // | UNIFORM |
| // | WORKGROUP |
| // | UNIFORM_CONSTANT |
| // | STORAGE |
| // | IMAGE |
| // | PRIVATE |
| // | FUNCTION |
| Expect<ast::StorageClass> ParserImpl::expect_storage_class( |
| const std::string& use) { |
| auto source = peek().source(); |
| |
| if (match(Token::Type::kIn)) |
| return {ast::StorageClass::kInput, source}; |
| |
| if (match(Token::Type::kOut)) |
| return {ast::StorageClass::kOutput, source}; |
| |
| if (match(Token::Type::kUniform)) |
| return {ast::StorageClass::kUniform, source}; |
| |
| if (match(Token::Type::kWorkgroup)) |
| return {ast::StorageClass::kWorkgroup, source}; |
| |
| if (match(Token::Type::kUniformConstant)) |
| return {ast::StorageClass::kUniformConstant, source}; |
| |
| if (match(Token::Type::kStorage)) |
| return {ast::StorageClass::kStorage, source}; |
| |
| if (match(Token::Type::kImage)) |
| return {ast::StorageClass::kImage, source}; |
| |
| if (match(Token::Type::kPrivate)) |
| return {ast::StorageClass::kPrivate, source}; |
| |
| if (match(Token::Type::kFunction)) |
| return {ast::StorageClass::kFunction, source}; |
| |
| return add_error(source, "invalid storage class", use); |
| } |
| |
| // struct_decl |
| // : struct_decoration_decl* STRUCT IDENT struct_body_decl |
| Maybe<type::Struct*> ParserImpl::struct_decl(ast::DecorationList& decos) { |
| auto t = peek(); |
| auto source = t.source(); |
| |
| if (!match(Token::Type::kStruct)) |
| return Failure::kNoMatch; |
| |
| auto name = expect_ident("struct declaration"); |
| if (name.errored) |
| return Failure::kErrored; |
| |
| auto body = expect_struct_body_decl(); |
| if (body.errored) |
| return Failure::kErrored; |
| |
| return create<type::Struct>( |
| builder_.Symbols().Register(name.value), |
| create<ast::Struct>(source, std::move(body.value), std::move(decos))); |
| } |
| |
| // struct_body_decl |
| // : BRACKET_LEFT struct_member* BRACKET_RIGHT |
| Expect<ast::StructMemberList> ParserImpl::expect_struct_body_decl() { |
| return expect_brace_block( |
| "struct declaration", [&]() -> Expect<ast::StructMemberList> { |
| bool errored = false; |
| |
| ast::StructMemberList members; |
| |
| while (synchronized_ && !peek().IsBraceRight() && !peek().IsEof()) { |
| auto member = sync(Token::Type::kSemicolon, |
| [&]() -> Expect<ast::StructMember*> { |
| auto decos = decoration_list(); |
| if (decos.errored) |
| errored = true; |
| if (!synchronized_) |
| return Failure::kErrored; |
| return expect_struct_member(decos.value); |
| }); |
| |
| if (member.errored) { |
| errored = true; |
| } else { |
| members.push_back(member.value); |
| } |
| } |
| |
| if (errored) |
| return Failure::kErrored; |
| |
| return members; |
| }); |
| } |
| |
| // struct_member |
| // : struct_member_decoration_decl+ variable_ident_decl SEMICOLON |
| Expect<ast::StructMember*> ParserImpl::expect_struct_member( |
| ast::DecorationList& decos) { |
| auto decl = expect_variable_ident_decl("struct member"); |
| if (decl.errored) |
| return Failure::kErrored; |
| |
| if (!expect("struct member", Token::Type::kSemicolon)) |
| return Failure::kErrored; |
| |
| return create<ast::StructMember>(decl->source, |
| builder_.Symbols().Register(decl->name), |
| decl->type, std::move(decos)); |
| } |
| |
| // function_decl |
| // : function_header body_stmt |
| Maybe<ast::Function*> ParserImpl::function_decl(ast::DecorationList& decos) { |
| auto header = function_header(); |
| if (header.errored) { |
| if (sync_to(Token::Type::kBraceLeft, /* consume: */ false)) { |
| // There were errors in the function header, but the parser has managed to |
| // resynchronize with the opening brace. As there's no outer |
| // synchronization token for function declarations, attempt to parse the |
| // function body. The AST isn't used as we've already errored, but this |
| // catches any errors inside the body, and can help keep the parser in |
| // sync. |
| expect_body_stmt(); |
| } |
| return Failure::kErrored; |
| } |
| if (!header.matched) |
| return Failure::kNoMatch; |
| |
| bool errored = false; |
| |
| auto body = expect_body_stmt(); |
| if (body.errored) |
| errored = true; |
| |
| if (errored) |
| return Failure::kErrored; |
| |
| return create<ast::Function>( |
| header->source, builder_.Symbols().Register(header->name), header->params, |
| header->return_type, body.value, decos); |
| } |
| |
| // function_type_decl |
| // : type_decl |
| // | VOID |
| Maybe<type::Type*> ParserImpl::function_type_decl() { |
| if (match(Token::Type::kVoid)) |
| return builder_.create<type::Void>(); |
| |
| return type_decl(); |
| } |
| |
| // function_header |
| // : FN IDENT PAREN_LEFT param_list PAREN_RIGHT ARROW function_type_decl |
| Maybe<ParserImpl::FunctionHeader> ParserImpl::function_header() { |
| Source source; |
| if (!match(Token::Type::kFn, &source)) |
| return Failure::kNoMatch; |
| |
| const char* use = "function declaration"; |
| bool errored = false; |
| |
| auto name = expect_ident(use); |
| if (name.errored) { |
| errored = true; |
| if (!sync_to(Token::Type::kParenLeft, /* consume: */ false)) |
| return Failure::kErrored; |
| } |
| |
| auto params = expect_paren_block(use, [&] { return expect_param_list(); }); |
| if (params.errored) { |
| errored = true; |
| if (!synchronized_) |
| return Failure::kErrored; |
| } |
| |
| if (!expect(use, Token::Type::kArrow)) |
| return Failure::kErrored; |
| |
| auto type = function_type_decl(); |
| if (type.errored) { |
| errored = true; |
| } else if (!type.matched) { |
| return add_error(peek(), "unable to determine function return type"); |
| } |
| |
| if (errored) |
| return Failure::kErrored; |
| |
| return FunctionHeader{source, name.value, std::move(params.value), |
| type.value}; |
| } |
| |
| // param_list |
| // : |
| // | (param COMMA)* param |
| Expect<ast::VariableList> ParserImpl::expect_param_list() { |
| // Check for an empty list. |
| auto t = peek(); |
| if (!t.IsIdentifier() && !t.IsAttrLeft()) { |
| return ast::VariableList{}; |
| } |
| |
| ast::VariableList ret; |
| while (synchronized_) { |
| auto param = expect_param(); |
| if (param.errored) |
| return Failure::kErrored; |
| ret.push_back(param.value); |
| |
| if (!match(Token::Type::kComma)) |
| break; |
| } |
| |
| return ret; |
| } |
| |
| // param |
| // : decoration_list* variable_ident_decl |
| Expect<ast::Variable*> ParserImpl::expect_param() { |
| auto decos = decoration_list(); |
| |
| auto decl = expect_variable_ident_decl("parameter"); |
| if (decl.errored) |
| return Failure::kErrored; |
| |
| auto* var = |
| create<ast::Variable>(decl->source, // source |
| builder_.Symbols().Register(decl->name), // symbol |
| ast::StorageClass::kNone, // storage_class |
| decl->type, // type |
| true, // is_const |
| nullptr, // constructor |
| std::move(decos.value)); // decorations |
| // Formal parameters are treated like a const declaration where the |
| // initializer value is provided by the call's argument. The key point is |
| // that it's not updatable after initially set. This is unlike C or GLSL |
| // which treat formal parameters like local variables that can be updated. |
| |
| return var; |
| } |
| |
| // pipeline_stage |
| // : VERTEX |
| // | FRAGMENT |
| // | COMPUTE |
| Expect<ast::PipelineStage> ParserImpl::expect_pipeline_stage() { |
| auto t = peek(); |
| if (!t.IsIdentifier()) { |
| return add_error(t, "invalid value for stage decoration"); |
| } |
| |
| auto s = t.to_str(); |
| if (s == kVertexStage) { |
| next(); // Consume the peek |
| return {ast::PipelineStage::kVertex, t.source()}; |
| } |
| if (s == kFragmentStage) { |
| next(); // Consume the peek |
| return {ast::PipelineStage::kFragment, t.source()}; |
| } |
| if (s == kComputeStage) { |
| next(); // Consume the peek |
| return {ast::PipelineStage::kCompute, t.source()}; |
| } |
| |
| return add_error(peek(), "invalid value for stage decoration"); |
| } |
| |
| Expect<ast::Builtin> ParserImpl::expect_builtin() { |
| auto ident = expect_ident("builtin"); |
| if (ident.errored) |
| return Failure::kErrored; |
| |
| ast::Builtin builtin = ident_to_builtin(ident.value); |
| if (builtin == ast::Builtin::kNone) |
| return add_error(ident.source, "invalid value for builtin decoration"); |
| |
| return {builtin, ident.source}; |
| } |
| |
| // body_stmt |
| // : BRACKET_LEFT statements BRACKET_RIGHT |
| Expect<ast::BlockStatement*> ParserImpl::expect_body_stmt() { |
| return expect_brace_block("", [&]() -> Expect<ast::BlockStatement*> { |
| auto stmts = expect_statements(); |
| if (stmts.errored) |
| return Failure::kErrored; |
| return create<ast::BlockStatement>(Source{}, stmts.value); |
| }); |
| } |
| |
| // paren_rhs_stmt |
| // : PAREN_LEFT logical_or_expression PAREN_RIGHT |
| Expect<ast::Expression*> ParserImpl::expect_paren_rhs_stmt() { |
| return expect_paren_block("", [&]() -> Expect<ast::Expression*> { |
| auto expr = logical_or_expression(); |
| if (expr.errored) |
| return Failure::kErrored; |
| if (!expr.matched) |
| return add_error(peek(), "unable to parse expression"); |
| |
| return expr.value; |
| }); |
| } |
| |
| // statements |
| // : statement* |
| Expect<ast::StatementList> ParserImpl::expect_statements() { |
| bool errored = false; |
| ast::StatementList stmts; |
| |
| while (synchronized_) { |
| auto stmt = statement(); |
| if (stmt.errored) { |
| errored = true; |
| } else if (stmt.matched) { |
| stmts.emplace_back(stmt.value); |
| } else { |
| break; |
| } |
| } |
| |
| if (errored) |
| return Failure::kErrored; |
| |
| return stmts; |
| } |
| |
| // statement |
| // : SEMICOLON |
| // | body_stmt? |
| // | if_stmt |
| // | switch_stmt |
| // | loop_stmt |
| // | for_stmt |
| // | non_block_statement |
| // : return_stmt SEMICOLON |
| // | func_call_stmt SEMICOLON |
| // | variable_stmt SEMICOLON |
| // | break_stmt SEMICOLON |
| // | continue_stmt SEMICOLON |
| // | DISCARD SEMICOLON |
| // | assignment_stmt SEMICOLON |
| Maybe<ast::Statement*> ParserImpl::statement() { |
| while (match(Token::Type::kSemicolon)) { |
| // Skip empty statements |
| } |
| |
| // Non-block statments that error can resynchronize on semicolon. |
| auto stmt = |
| sync(Token::Type::kSemicolon, [&] { return non_block_statement(); }); |
| |
| if (stmt.errored) |
| return Failure::kErrored; |
| if (stmt.matched) |
| return stmt; |
| |
| auto stmt_if = if_stmt(); |
| if (stmt_if.errored) |
| return Failure::kErrored; |
| if (stmt_if.matched) |
| return stmt_if.value; |
| |
| auto sw = switch_stmt(); |
| if (sw.errored) |
| return Failure::kErrored; |
| if (sw.matched) |
| return sw.value; |
| |
| auto loop = loop_stmt(); |
| if (loop.errored) |
| return Failure::kErrored; |
| if (loop.matched) |
| return loop.value; |
| |
| auto stmt_for = for_stmt(); |
| if (stmt_for.errored) |
| return Failure::kErrored; |
| if (stmt_for.matched) |
| return stmt_for.value; |
| |
| if (peek().IsBraceLeft()) { |
| auto body = expect_body_stmt(); |
| if (body.errored) |
| return Failure::kErrored; |
| return body.value; |
| } |
| |
| return Failure::kNoMatch; |
| } |
| |
| // statement (continued) |
| // : return_stmt SEMICOLON |
| // | func_call_stmt SEMICOLON |
| // | variable_stmt SEMICOLON |
| // | break_stmt SEMICOLON |
| // | continue_stmt SEMICOLON |
| // | DISCARD SEMICOLON |
| // | assignment_stmt SEMICOLON |
| Maybe<ast::Statement*> ParserImpl::non_block_statement() { |
| auto stmt = [&]() -> Maybe<ast::Statement*> { |
| auto ret_stmt = return_stmt(); |
| if (ret_stmt.errored) |
| return Failure::kErrored; |
| if (ret_stmt.matched) |
| return ret_stmt.value; |
| |
| auto func = func_call_stmt(); |
| if (func.errored) |
| return Failure::kErrored; |
| if (func.matched) |
| return func.value; |
| |
| auto var = variable_stmt(); |
| if (var.errored) |
| return Failure::kErrored; |
| if (var.matched) |
| return var.value; |
| |
| auto b = break_stmt(); |
| if (b.errored) |
| return Failure::kErrored; |
| if (b.matched) |
| return b.value; |
| |
| auto cont = continue_stmt(); |
| if (cont.errored) |
| return Failure::kErrored; |
| if (cont.matched) |
| return cont.value; |
| |
| auto assign = assignment_stmt(); |
| if (assign.errored) |
| return Failure::kErrored; |
| if (assign.matched) |
| return assign.value; |
| |
| Source source; |
| if (match(Token::Type::kDiscard, &source)) |
| return create<ast::DiscardStatement>(source); |
| |
| return Failure::kNoMatch; |
| }(); |
| |
| if (stmt.matched && !expect(stmt->Name(), Token::Type::kSemicolon)) |
| return Failure::kErrored; |
| |
| return stmt; |
| } |
| |
| // return_stmt |
| // : RETURN logical_or_expression? |
| Maybe<ast::ReturnStatement*> ParserImpl::return_stmt() { |
| Source source; |
| if (!match(Token::Type::kReturn, &source)) |
| return Failure::kNoMatch; |
| |
| if (peek().IsSemicolon()) |
| return create<ast::ReturnStatement>(source, nullptr); |
| |
| auto expr = logical_or_expression(); |
| if (expr.errored) |
| return Failure::kErrored; |
| |
| // TODO(bclayton): Check matched? |
| return create<ast::ReturnStatement>(source, expr.value); |
| } |
| |
| // variable_stmt |
| // : variable_decl |
| // | variable_decl EQUAL logical_or_expression |
| // | CONST variable_ident_decl EQUAL logical_or_expression |
| Maybe<ast::VariableDeclStatement*> ParserImpl::variable_stmt() { |
| if (match(Token::Type::kConst)) { |
| auto decl = expect_variable_ident_decl("constant declaration"); |
| if (decl.errored) |
| return Failure::kErrored; |
| |
| if (!expect("constant declaration", Token::Type::kEqual)) |
| return Failure::kErrored; |
| |
| auto constructor = logical_or_expression(); |
| if (constructor.errored) |
| return Failure::kErrored; |
| if (!constructor.matched) |
| return add_error(peek(), "missing constructor for const declaration"); |
| |
| auto* var = create<ast::Variable>( |
| decl->source, // source |
| builder_.Symbols().Register(decl->name), // symbol |
| ast::StorageClass::kNone, // storage_class |
| decl->type, // type |
| true, // is_const |
| constructor.value, // constructor |
| ast::DecorationList{}); // decorations |
| |
| return create<ast::VariableDeclStatement>(decl->source, var); |
| } |
| |
| auto decl = variable_decl(); |
| if (decl.errored) |
| return Failure::kErrored; |
| if (!decl.matched) |
| return Failure::kNoMatch; |
| |
| ast::Expression* constructor = nullptr; |
| if (match(Token::Type::kEqual)) { |
| auto constructor_expr = logical_or_expression(); |
| if (constructor_expr.errored) |
| return Failure::kErrored; |
| if (!constructor_expr.matched) |
| return add_error(peek(), "missing constructor for variable declaration"); |
| |
| constructor = constructor_expr.value; |
| } |
| |
| auto* var = |
| create<ast::Variable>(decl->source, // source |
| builder_.Symbols().Register(decl->name), // symbol |
| decl->storage_class, // storage_class |
| decl->type, // type |
| false, // is_const |
| constructor, // constructor |
| ast::DecorationList{}); // decorations |
| |
| return create<ast::VariableDeclStatement>(var->source(), var); |
| } |
| |
| // if_stmt |
| // : IF paren_rhs_stmt body_stmt elseif_stmt? else_stmt? |
| Maybe<ast::IfStatement*> ParserImpl::if_stmt() { |
| Source source; |
| if (!match(Token::Type::kIf, &source)) |
| return Failure::kNoMatch; |
| |
| auto condition = expect_paren_rhs_stmt(); |
| if (condition.errored) |
| return Failure::kErrored; |
| |
| auto body = expect_body_stmt(); |
| if (body.errored) |
| return Failure::kErrored; |
| |
| auto elseif = elseif_stmt(); |
| if (elseif.errored) |
| return Failure::kErrored; |
| |
| auto el = else_stmt(); |
| if (el.errored) |
| return Failure::kErrored; |
| if (el.matched) |
| elseif.value.push_back(el.value); |
| |
| return create<ast::IfStatement>(source, condition.value, body.value, |
| elseif.value); |
| } |
| |
| // elseif_stmt |
| // : ELSE_IF paren_rhs_stmt body_stmt elseif_stmt? |
| Maybe<ast::ElseStatementList> ParserImpl::elseif_stmt() { |
| Source source; |
| if (!match(Token::Type::kElseIf, &source)) |
| return Failure::kNoMatch; |
| |
| ast::ElseStatementList ret; |
| for (;;) { |
| auto condition = expect_paren_rhs_stmt(); |
| if (condition.errored) |
| return Failure::kErrored; |
| |
| auto body = expect_body_stmt(); |
| if (body.errored) |
| return Failure::kErrored; |
| |
| ret.push_back( |
| create<ast::ElseStatement>(source, condition.value, body.value)); |
| |
| if (!match(Token::Type::kElseIf, &source)) |
| break; |
| } |
| |
| return ret; |
| } |
| |
| // else_stmt |
| // : ELSE body_stmt |
| Maybe<ast::ElseStatement*> ParserImpl::else_stmt() { |
| Source source; |
| if (!match(Token::Type::kElse, &source)) |
| return Failure::kNoMatch; |
| |
| auto body = expect_body_stmt(); |
| if (body.errored) |
| return Failure::kErrored; |
| |
| return create<ast::ElseStatement>(source, nullptr, body.value); |
| } |
| |
| // switch_stmt |
| // : SWITCH paren_rhs_stmt BRACKET_LEFT switch_body+ BRACKET_RIGHT |
| Maybe<ast::SwitchStatement*> ParserImpl::switch_stmt() { |
| Source source; |
| if (!match(Token::Type::kSwitch, &source)) |
| return Failure::kNoMatch; |
| |
| auto condition = expect_paren_rhs_stmt(); |
| if (condition.errored) |
| return Failure::kErrored; |
| |
| auto body = expect_brace_block("switch statement", |
| [&]() -> Expect<ast::CaseStatementList> { |
| bool errored = false; |
| ast::CaseStatementList list; |
| while (synchronized_) { |
| auto stmt = switch_body(); |
| if (stmt.errored) { |
| errored = true; |
| continue; |
| } |
| if (!stmt.matched) |
| break; |
| list.push_back(stmt.value); |
| } |
| if (errored) |
| return Failure::kErrored; |
| return list; |
| }); |
| |
| if (body.errored) |
| return Failure::kErrored; |
| |
| return create<ast::SwitchStatement>(source, condition.value, body.value); |
| } |
| |
| // switch_body |
| // : CASE case_selectors COLON BRACKET_LEFT case_body BRACKET_RIGHT |
| // | DEFAULT COLON BRACKET_LEFT case_body BRACKET_RIGHT |
| Maybe<ast::CaseStatement*> ParserImpl::switch_body() { |
| auto t = peek(); |
| if (!t.IsCase() && !t.IsDefault()) |
| return Failure::kNoMatch; |
| |
| auto source = t.source(); |
| next(); // Consume the peek |
| |
| ast::CaseSelectorList selector_list; |
| if (t.IsCase()) { |
| auto selectors = expect_case_selectors(); |
| if (selectors.errored) |
| return Failure::kErrored; |
| |
| selector_list = std::move(selectors.value); |
| } |
| |
| const char* use = "case statement"; |
| |
| if (!expect(use, Token::Type::kColon)) |
| return Failure::kErrored; |
| |
| auto body = expect_brace_block(use, [&] { return case_body(); }); |
| |
| if (body.errored) |
| return Failure::kErrored; |
| if (!body.matched) |
| return add_error(body.source, "expected case body"); |
| |
| return create<ast::CaseStatement>(source, selector_list, body.value); |
| } |
| |
| // case_selectors |
| // : const_literal (COMMA const_literal)* |
| Expect<ast::CaseSelectorList> ParserImpl::expect_case_selectors() { |
| ast::CaseSelectorList selectors; |
| |
| for (;;) { |
| auto t = peek(); |
| auto matched_comma = match(Token::Type::kComma); |
| |
| if (selectors.empty() && matched_comma) |
| return add_error(t, "a selector is expected before the comma"); |
| if (matched_comma) |
| t = peek(); |
| |
| auto cond = const_literal(); |
| if (cond.errored) |
| return Failure::kErrored; |
| if (!cond.matched) { |
| if (matched_comma) { |
| return add_error(t, "a selector is expected after the comma"); |
| } |
| break; |
| } |
| if (!cond->Is<ast::IntLiteral>()) |
| return add_error(t, "invalid case selector must be an integer value"); |
| if (!selectors.empty() && !matched_comma) |
| return add_error(t, "expected a comma after the previous selector"); |
| |
| selectors.push_back(cond.value->As<ast::IntLiteral>()); |
| } |
| |
| if (selectors.empty()) |
| return add_error(peek(), "unable to parse case selectors"); |
| |
| return selectors; |
| } |
| |
| // case_body |
| // : |
| // | statement case_body |
| // | FALLTHROUGH SEMICOLON |
| Maybe<ast::BlockStatement*> ParserImpl::case_body() { |
| ast::StatementList stmts; |
| for (;;) { |
| Source source; |
| if (match(Token::Type::kFallthrough, &source)) { |
| if (!expect("fallthrough statement", Token::Type::kSemicolon)) |
| return Failure::kErrored; |
| |
| stmts.emplace_back(create<ast::FallthroughStatement>(source)); |
| break; |
| } |
| |
| auto stmt = statement(); |
| if (stmt.errored) |
| return Failure::kErrored; |
| if (!stmt.matched) |
| break; |
| |
| stmts.emplace_back(stmt.value); |
| } |
| |
| return create<ast::BlockStatement>(Source{}, stmts); |
| } |
| |
| // loop_stmt |
| // : LOOP BRACKET_LEFT statements continuing_stmt? BRACKET_RIGHT |
| Maybe<ast::LoopStatement*> ParserImpl::loop_stmt() { |
| Source source; |
| if (!match(Token::Type::kLoop, &source)) |
| return Failure::kNoMatch; |
| |
| return expect_brace_block("loop", [&]() -> Maybe<ast::LoopStatement*> { |
| auto stmts = expect_statements(); |
| if (stmts.errored) |
| return Failure::kErrored; |
| |
| auto continuing = continuing_stmt(); |
| if (continuing.errored) |
| return Failure::kErrored; |
| |
| auto* body = create<ast::BlockStatement>(source, stmts.value); |
| return create<ast::LoopStatement>(source, body, continuing.value); |
| }); |
| } |
| |
| ForHeader::ForHeader(ast::Statement* init, |
| ast::Expression* cond, |
| ast::Statement* cont) |
| : initializer(init), condition(cond), continuing(cont) {} |
| |
| ForHeader::~ForHeader() = default; |
| |
| // (variable_stmt | assignment_stmt | func_call_stmt)? |
| Maybe<ast::Statement*> ParserImpl::for_header_initializer() { |
| auto call = func_call_stmt(); |
| if (call.errored) |
| return Failure::kErrored; |
| if (call.matched) |
| return call.value; |
| |
| auto var = variable_stmt(); |
| if (var.errored) |
| return Failure::kErrored; |
| if (var.matched) |
| return var.value; |
| |
| auto assign = assignment_stmt(); |
| if (assign.errored) |
| return Failure::kErrored; |
| if (assign.matched) |
| return assign.value; |
| |
| return Failure::kNoMatch; |
| } |
| |
| // (assignment_stmt | func_call_stmt)? |
| Maybe<ast::Statement*> ParserImpl::for_header_continuing() { |
| auto call_stmt = func_call_stmt(); |
| if (call_stmt.errored) |
| return Failure::kErrored; |
| if (call_stmt.matched) |
| return call_stmt.value; |
| |
| auto assign = assignment_stmt(); |
| if (assign.errored) |
| return Failure::kErrored; |
| if (assign.matched) |
| return assign.value; |
| |
| return Failure::kNoMatch; |
| } |
| |
| // for_header |
| // : (variable_stmt | assignment_stmt | func_call_stmt)? |
| // SEMICOLON |
| // logical_or_expression? SEMICOLON |
| // (assignment_stmt | func_call_stmt)? |
| Expect<std::unique_ptr<ForHeader>> ParserImpl::expect_for_header() { |
| auto initializer = for_header_initializer(); |
| if (initializer.errored) |
| return Failure::kErrored; |
| |
| if (!expect("initializer in for loop", Token::Type::kSemicolon)) |
| return Failure::kErrored; |
| |
| auto condition = logical_or_expression(); |
| if (condition.errored) |
| return Failure::kErrored; |
| |
| if (!expect("condition in for loop", Token::Type::kSemicolon)) |
| return Failure::kErrored; |
| |
| auto continuing = for_header_continuing(); |
| if (continuing.errored) |
| return Failure::kErrored; |
| |
| return std::make_unique<ForHeader>(initializer.value, condition.value, |
| continuing.value); |
| } |
| |
| // for_statement |
| // : FOR PAREN_LEFT for_header PAREN_RIGHT BRACE_LEFT statements BRACE_RIGHT |
| Maybe<ast::Statement*> ParserImpl::for_stmt() { |
| Source source; |
| if (!match(Token::Type::kFor, &source)) |
| return Failure::kNoMatch; |
| |
| auto header = |
| expect_paren_block("for loop", [&] { return expect_for_header(); }); |
| if (header.errored) |
| return Failure::kErrored; |
| |
| auto stmts = |
| expect_brace_block("for loop", [&] { return expect_statements(); }); |
| if (stmts.errored) |
| return Failure::kErrored; |
| |
| // The for statement is a syntactic sugar on top of the loop statement. |
| // We create corresponding nodes in ast with the exact same behaviour |
| // as we would expect from the loop statement. |
| if (header->condition != nullptr) { |
| // !condition |
| auto* not_condition = create<ast::UnaryOpExpression>( |
| header->condition->source(), ast::UnaryOp::kNot, header->condition); |
| // { break; } |
| auto* break_stmt = create<ast::BreakStatement>(not_condition->source()); |
| auto* break_body = |
| create<ast::BlockStatement>(not_condition->source(), ast::StatementList{ |
| break_stmt, |
| }); |
| // if (!condition) { break; } |
| auto* break_if_not_condition = |
| create<ast::IfStatement>(not_condition->source(), not_condition, |
| break_body, ast::ElseStatementList{}); |
| stmts.value.insert(stmts.value.begin(), break_if_not_condition); |
| } |
| |
| ast::BlockStatement* continuing_body = nullptr; |
| if (header->continuing != nullptr) { |
| continuing_body = create<ast::BlockStatement>(header->continuing->source(), |
| ast::StatementList{ |
| header->continuing, |
| }); |
| } |
| |
| auto* body = create<ast::BlockStatement>(source, stmts.value); |
| auto* loop = create<ast::LoopStatement>(source, body, continuing_body); |
| |
| if (header->initializer != nullptr) { |
| return create<ast::BlockStatement>(source, ast::StatementList{ |
| header->initializer, |
| loop, |
| }); |
| } |
| |
| return loop; |
| } |
| |
| // func_call_stmt |
| // : IDENT PAREN_LEFT argument_expression_list* PAREN_RIGHT |
| Maybe<ast::CallStatement*> ParserImpl::func_call_stmt() { |
| auto t = peek(); |
| auto t2 = peek(1); |
| if (!t.IsIdentifier() || !t2.IsParenLeft()) |
| return Failure::kNoMatch; |
| |
| auto source = t.source(); |
| |
| next(); // Consume the peek |
| next(); // Consume the 2nd peek |
| |
| auto name = t.to_str(); |
| |
| ast::ExpressionList params; |
| |
| t = peek(); |
| if (!t.IsParenRight() && !t.IsEof()) { |
| auto list = expect_argument_expression_list(); |
| if (list.errored) |
| return Failure::kErrored; |
| params = std::move(list.value); |
| } |
| |
| if (!expect("call statement", Token::Type::kParenRight)) |
| return Failure::kErrored; |
| |
| return create<ast::CallStatement>( |
| Source{}, create<ast::CallExpression>( |
| source, |
| create<ast::IdentifierExpression>( |
| source, builder_.Symbols().Register(name)), |
| std::move(params))); |
| } |
| |
| // break_stmt |
| // : BREAK |
| Maybe<ast::BreakStatement*> ParserImpl::break_stmt() { |
| Source source; |
| if (!match(Token::Type::kBreak, &source)) |
| return Failure::kNoMatch; |
| |
| return create<ast::BreakStatement>(source); |
| } |
| |
| // continue_stmt |
| // : CONTINUE |
| Maybe<ast::ContinueStatement*> ParserImpl::continue_stmt() { |
| Source source; |
| if (!match(Token::Type::kContinue, &source)) |
| return Failure::kNoMatch; |
| |
| return create<ast::ContinueStatement>(source); |
| } |
| |
| // continuing_stmt |
| // : CONTINUING body_stmt |
| Maybe<ast::BlockStatement*> ParserImpl::continuing_stmt() { |
| if (!match(Token::Type::kContinuing)) |
| return create<ast::BlockStatement>(Source{}, ast::StatementList{}); |
| |
| return expect_body_stmt(); |
| } |
| |
| // primary_expression |
| // : IDENT |
| // | type_decl PAREN_LEFT argument_expression_list* PAREN_RIGHT |
| // | const_literal |
| // | paren_rhs_stmt |
| // | BITCAST LESS_THAN type_decl GREATER_THAN paren_rhs_stmt |
| Maybe<ast::Expression*> ParserImpl::primary_expression() { |
| auto t = peek(); |
| auto source = t.source(); |
| |
| auto lit = const_literal(); |
| if (lit.errored) |
| return Failure::kErrored; |
| if (lit.matched) |
| return create<ast::ScalarConstructorExpression>(source, lit.value); |
| |
| if (t.IsParenLeft()) { |
| auto paren = expect_paren_rhs_stmt(); |
| if (paren.errored) |
| return Failure::kErrored; |
| |
| return paren.value; |
| } |
| |
| if (match(Token::Type::kBitcast)) { |
| const char* use = "bitcast expression"; |
| |
| auto type = expect_lt_gt_block(use, [&] { return expect_type(use); }); |
| if (type.errored) |
| return Failure::kErrored; |
| |
| auto params = expect_paren_rhs_stmt(); |
| if (params.errored) |
| return Failure::kErrored; |
| |
| return create<ast::BitcastExpression>(source, type.value, params.value); |
| } |
| |
| if (t.IsIdentifier() && !get_constructed(t.to_str())) { |
| next(); |
| return create<ast::IdentifierExpression>( |
| t.source(), builder_.Symbols().Register(t.to_str())); |
| } |
| |
| auto type = type_decl(); |
| if (type.errored) |
| return Failure::kErrored; |
| if (type.matched) { |
| auto expr = expect_paren_block( |
| "type constructor", [&]() -> Expect<ast::TypeConstructorExpression*> { |
| t = peek(); |
| if (t.IsParenRight() || t.IsEof()) |
| return create<ast::TypeConstructorExpression>( |
| source, type.value, ast::ExpressionList{}); |
| |
| auto params = expect_argument_expression_list(); |
| if (params.errored) |
| return Failure::kErrored; |
| |
| return create<ast::TypeConstructorExpression>(source, type.value, |
| params.value); |
| }); |
| |
| if (expr.errored) |
| return Failure::kErrored; |
| |
| return expr.value; |
| } |
| |
| return Failure::kNoMatch; |
| } |
| |
| // postfix_expr |
| // : |
| // | BRACE_LEFT logical_or_expression BRACE_RIGHT postfix_expr |
| // | PAREN_LEFT argument_expression_list* PAREN_RIGHT postfix_expr |
| // | PERIOD IDENTIFIER postfix_expr |
| Maybe<ast::Expression*> ParserImpl::postfix_expr(ast::Expression* prefix) { |
| Source source; |
| if (match(Token::Type::kBracketLeft, &source)) { |
| return sync(Token::Type::kBracketRight, [&]() -> Maybe<ast::Expression*> { |
| auto param = logical_or_expression(); |
| if (param.errored) |
| return Failure::kErrored; |
| if (!param.matched) |
| return add_error(peek(), "unable to parse expression inside []"); |
| |
| if (!expect("array accessor", Token::Type::kBracketRight)) |
| return Failure::kErrored; |
| |
| return postfix_expr( |
| create<ast::ArrayAccessorExpression>(source, prefix, param.value)); |
| }); |
| } |
| |
| if (match(Token::Type::kParenLeft, &source)) { |
| return sync(Token::Type::kParenRight, [&]() -> Maybe<ast::Expression*> { |
| ast::ExpressionList params; |
| |
| auto t = peek(); |
| if (!t.IsParenRight() && !t.IsEof()) { |
| auto list = expect_argument_expression_list(); |
| if (list.errored) |
| return Failure::kErrored; |
| params = list.value; |
| } |
| |
| if (!expect("call expression", Token::Type::kParenRight)) |
| return Failure::kErrored; |
| |
| return postfix_expr(create<ast::CallExpression>(source, prefix, params)); |
| }); |
| } |
| |
| if (match(Token::Type::kPeriod)) { |
| auto ident = expect_ident("member accessor"); |
| if (ident.errored) |
| return Failure::kErrored; |
| |
| return postfix_expr(create<ast::MemberAccessorExpression>( |
| ident.source, prefix, |
| create<ast::IdentifierExpression>( |
| ident.source, builder_.Symbols().Register(ident.value)))); |
| } |
| |
| return prefix; |
| } |
| |
| // postfix_expression |
| // : primary_expression postfix_expr |
| Maybe<ast::Expression*> ParserImpl::postfix_expression() { |
| auto prefix = primary_expression(); |
| if (prefix.errored) |
| return Failure::kErrored; |
| if (!prefix.matched) |
| return Failure::kNoMatch; |
| |
| return postfix_expr(prefix.value); |
| } |
| |
| // argument_expression_list |
| // : (logical_or_expression COMMA)* logical_or_expression |
| Expect<ast::ExpressionList> ParserImpl::expect_argument_expression_list() { |
| auto arg = logical_or_expression(); |
| if (arg.errored) |
| return Failure::kErrored; |
| if (!arg.matched) |
| return add_error(peek(), "unable to parse argument expression"); |
| |
| ast::ExpressionList ret; |
| ret.push_back(arg.value); |
| |
| while (match(Token::Type::kComma)) { |
| arg = logical_or_expression(); |
| if (arg.errored) |
| return Failure::kErrored; |
| if (!arg.matched) { |
| return add_error(peek(), |
| "unable to parse argument expression after comma"); |
| } |
| ret.push_back(arg.value); |
| } |
| return ret; |
| } |
| |
| // unary_expression |
| // : postfix_expression |
| // | MINUS unary_expression |
| // | BANG unary_expression |
| Maybe<ast::Expression*> ParserImpl::unary_expression() { |
| auto t = peek(); |
| auto source = t.source(); |
| if (t.IsMinus() || t.IsBang()) { |
| auto name = t.to_name(); |
| |
| next(); // Consume the peek |
| |
| auto op = ast::UnaryOp::kNegation; |
| if (t.IsBang()) |
| op = ast::UnaryOp::kNot; |
| |
| auto expr = unary_expression(); |
| if (expr.errored) |
| return Failure::kErrored; |
| if (!expr.matched) |
| return add_error(peek(), |
| "unable to parse right side of " + name + " expression"); |
| |
| return create<ast::UnaryOpExpression>(source, op, expr.value); |
| } |
| return postfix_expression(); |
| } |
| |
| // multiplicative_expr |
| // : |
| // | STAR unary_expression multiplicative_expr |
| // | FORWARD_SLASH unary_expression multiplicative_expr |
| // | MODULO unary_expression multiplicative_expr |
| Expect<ast::Expression*> ParserImpl::expect_multiplicative_expr( |
| ast::Expression* lhs) { |
| while (synchronized_) { |
| auto t = peek(); |
| |
| ast::BinaryOp op = ast::BinaryOp::kNone; |
| if (t.IsStar()) |
| op = ast::BinaryOp::kMultiply; |
| else if (t.IsForwardSlash()) |
| op = ast::BinaryOp::kDivide; |
| else if (t.IsMod()) |
| op = ast::BinaryOp::kModulo; |
| else |
| return lhs; |
| |
| auto source = t.source(); |
| auto name = t.to_name(); |
| next(); // Consume the peek |
| |
| auto rhs = unary_expression(); |
| if (rhs.errored) |
| return Failure::kErrored; |
| if (!rhs.matched) { |
| return add_error(peek(), |
| "unable to parse right side of " + name + " expression"); |
| } |
| |
| lhs = create<ast::BinaryExpression>(source, op, lhs, rhs.value); |
| } |
| return Failure::kErrored; |
| } |
| |
| // multiplicative_expression |
| // : unary_expression multiplicative_expr |
| Maybe<ast::Expression*> ParserImpl::multiplicative_expression() { |
| auto lhs = unary_expression(); |
| if (lhs.errored) |
| return Failure::kErrored; |
| if (!lhs.matched) |
| return Failure::kNoMatch; |
| |
| return expect_multiplicative_expr(lhs.value); |
| } |
| |
| // additive_expr |
| // : |
| // | PLUS multiplicative_expression additive_expr |
| // | MINUS multiplicative_expression additive_expr |
| Expect<ast::Expression*> ParserImpl::expect_additive_expr( |
| ast::Expression* lhs) { |
| while (synchronized_) { |
| auto t = peek(); |
| |
| ast::BinaryOp op = ast::BinaryOp::kNone; |
| if (t.IsPlus()) |
| op = ast::BinaryOp::kAdd; |
| else if (t.IsMinus()) |
| op = ast::BinaryOp::kSubtract; |
| else |
| return lhs; |
| |
| auto source = t.source(); |
| next(); // Consume the peek |
| |
| auto rhs = multiplicative_expression(); |
| if (rhs.errored) |
| return Failure::kErrored; |
| if (!rhs.matched) |
| return add_error(peek(), "unable to parse right side of + expression"); |
| |
| lhs = create<ast::BinaryExpression>(source, op, lhs, rhs.value); |
| } |
| return Failure::kErrored; |
| } |
| |
| // additive_expression |
| // : multiplicative_expression additive_expr |
| Maybe<ast::Expression*> ParserImpl::additive_expression() { |
| auto lhs = multiplicative_expression(); |
| if (lhs.errored) |
| return Failure::kErrored; |
| if (!lhs.matched) |
| return Failure::kNoMatch; |
| |
| return expect_additive_expr(lhs.value); |
| } |
| |
| // shift_expr |
| // : |
| // | SHIFT_LEFT additive_expression shift_expr |
| // | SHIFT_RIGHT additive_expression shift_expr |
| Expect<ast::Expression*> ParserImpl::expect_shift_expr(ast::Expression* lhs) { |
| while (synchronized_) { |
| auto t = peek(); |
| auto source = t.source(); |
| |
| auto* name = ""; |
| ast::BinaryOp op = ast::BinaryOp::kNone; |
| if (t.IsShiftLeft()) { |
| next(); // Consume the peek |
| op = ast::BinaryOp::kShiftLeft; |
| name = "<<"; |
| } else if (t.IsShiftRight()) { |
| next(); // Consume the peek |
| op = ast::BinaryOp::kShiftRight; |
| name = ">>"; |
| } else { |
| return lhs; |
| } |
| |
| auto rhs = additive_expression(); |
| if (rhs.errored) |
| return Failure::kErrored; |
| if (!rhs.matched) { |
| return add_error(peek(), std::string("unable to parse right side of ") + |
| name + " expression"); |
| } |
| |
| return lhs = create<ast::BinaryExpression>(source, op, lhs, rhs.value); |
| } |
| return Failure::kErrored; |
| } |
| |
| // shift_expression |
| // : additive_expression shift_expr |
| Maybe<ast::Expression*> ParserImpl::shift_expression() { |
| auto lhs = additive_expression(); |
| if (lhs.errored) |
| return Failure::kErrored; |
| if (!lhs.matched) |
| return Failure::kNoMatch; |
| |
| return expect_shift_expr(lhs.value); |
| } |
| |
| // relational_expr |
| // : |
| // | LESS_THAN shift_expression relational_expr |
| // | GREATER_THAN shift_expression relational_expr |
| // | LESS_THAN_EQUAL shift_expression relational_expr |
| // | GREATER_THAN_EQUAL shift_expression relational_expr |
| Expect<ast::Expression*> ParserImpl::expect_relational_expr( |
| ast::Expression* lhs) { |
| while (synchronized_) { |
| auto t = peek(); |
| ast::BinaryOp op = ast::BinaryOp::kNone; |
| if (t.IsLessThan()) |
| op = ast::BinaryOp::kLessThan; |
| else if (t.IsGreaterThan()) |
| op = ast::BinaryOp::kGreaterThan; |
| else if (t.IsLessThanEqual()) |
| op = ast::BinaryOp::kLessThanEqual; |
| else if (t.IsGreaterThanEqual()) |
| op = ast::BinaryOp::kGreaterThanEqual; |
| else |
| return lhs; |
| |
| auto source = t.source(); |
| auto name = t.to_name(); |
| next(); // Consume the peek |
| |
| auto rhs = shift_expression(); |
| if (rhs.errored) |
| return Failure::kErrored; |
| if (!rhs.matched) { |
| return add_error(peek(), |
| "unable to parse right side of " + name + " expression"); |
| } |
| |
| lhs = create<ast::BinaryExpression>(source, op, lhs, rhs.value); |
| } |
| return Failure::kErrored; |
| } |
| |
| // relational_expression |
| // : shift_expression relational_expr |
| Maybe<ast::Expression*> ParserImpl::relational_expression() { |
| auto lhs = shift_expression(); |
| if (lhs.errored) |
| return Failure::kErrored; |
| if (!lhs.matched) |
| return Failure::kNoMatch; |
| |
| return expect_relational_expr(lhs.value); |
| } |
| |
| // equality_expr |
| // : |
| // | EQUAL_EQUAL relational_expression equality_expr |
| // | NOT_EQUAL relational_expression equality_expr |
| Expect<ast::Expression*> ParserImpl::expect_equality_expr( |
| ast::Expression* lhs) { |
| while (synchronized_) { |
| auto t = peek(); |
| ast::BinaryOp op = ast::BinaryOp::kNone; |
| if (t.IsEqualEqual()) |
| op = ast::BinaryOp::kEqual; |
| else if (t.IsNotEqual()) |
| op = ast::BinaryOp::kNotEqual; |
| else |
| return lhs; |
| |
| auto source = t.source(); |
| auto name = t.to_name(); |
| next(); // Consume the peek |
| |
| auto rhs = relational_expression(); |
| if (rhs.errored) |
| return Failure::kErrored; |
| if (!rhs.matched) { |
| return add_error(peek(), |
| "unable to parse right side of " + name + " expression"); |
| } |
| |
| lhs = create<ast::BinaryExpression>(source, op, lhs, rhs.value); |
| } |
| return Failure::kErrored; |
| } |
| |
| // equality_expression |
| // : relational_expression equality_expr |
| Maybe<ast::Expression*> ParserImpl::equality_expression() { |
| auto lhs = relational_expression(); |
| if (lhs.errored) |
| return Failure::kErrored; |
| if (!lhs.matched) |
| return Failure::kNoMatch; |
| |
| return expect_equality_expr(lhs.value); |
| } |
| |
| // and_expr |
| // : |
| // | AND equality_expression and_expr |
| Expect<ast::Expression*> ParserImpl::expect_and_expr(ast::Expression* lhs) { |
| while (synchronized_) { |
| auto t = peek(); |
| if (!t.IsAnd()) |
| return lhs; |
| |
| auto source = t.source(); |
| next(); // Consume the peek |
| |
| auto rhs = equality_expression(); |
| if (rhs.errored) |
| return Failure::kErrored; |
| if (!rhs.matched) |
| return add_error(peek(), "unable to parse right side of & expression"); |
| |
| lhs = create<ast::BinaryExpression>(source, ast::BinaryOp::kAnd, lhs, |
| rhs.value); |
| } |
| return Failure::kErrored; |
| } |
| |
| // and_expression |
| // : equality_expression and_expr |
| Maybe<ast::Expression*> ParserImpl::and_expression() { |
| auto lhs = equality_expression(); |
| if (lhs.errored) |
| return Failure::kErrored; |
| if (!lhs.matched) |
| return Failure::kNoMatch; |
| |
| return expect_and_expr(lhs.value); |
| } |
| |
| // exclusive_or_expr |
| // : |
| // | XOR and_expression exclusive_or_expr |
| Expect<ast::Expression*> ParserImpl::expect_exclusive_or_expr( |
| ast::Expression* lhs) { |
| while (synchronized_) { |
| Source source; |
| if (!match(Token::Type::kXor, &source)) |
| return lhs; |
| |
| auto rhs = and_expression(); |
| if (rhs.errored) |
| return Failure::kErrored; |
| if (!rhs.matched) |
| return add_error(peek(), "unable to parse right side of ^ expression"); |
| |
| lhs = create<ast::BinaryExpression>(source, ast::BinaryOp::kXor, lhs, |
| rhs.value); |
| } |
| return Failure::kErrored; |
| } |
| |
| // exclusive_or_expression |
| // : and_expression exclusive_or_expr |
| Maybe<ast::Expression*> ParserImpl::exclusive_or_expression() { |
| auto lhs = and_expression(); |
| if (lhs.errored) |
| return Failure::kErrored; |
| if (!lhs.matched) |
| return Failure::kNoMatch; |
| |
| return expect_exclusive_or_expr(lhs.value); |
| } |
| |
| // inclusive_or_expr |
| // : |
| // | OR exclusive_or_expression inclusive_or_expr |
| Expect<ast::Expression*> ParserImpl::expect_inclusive_or_expr( |
| ast::Expression* lhs) { |
| while (synchronized_) { |
| Source source; |
| if (!match(Token::Type::kOr)) |
| return lhs; |
| |
| auto rhs = exclusive_or_expression(); |
| if (rhs.errored) |
| return Failure::kErrored; |
| if (!rhs.matched) |
| return add_error(peek(), "unable to parse right side of | expression"); |
| |
| lhs = create<ast::BinaryExpression>(source, ast::BinaryOp::kOr, lhs, |
| rhs.value); |
| } |
| return Failure::kErrored; |
| } |
| |
| // inclusive_or_expression |
| // : exclusive_or_expression inclusive_or_expr |
| Maybe<ast::Expression*> ParserImpl::inclusive_or_expression() { |
| auto lhs = exclusive_or_expression(); |
| if (lhs.errored) |
| return Failure::kErrored; |
| if (!lhs.matched) |
| return Failure::kNoMatch; |
| |
| return expect_inclusive_or_expr(lhs.value); |
| } |
| |
| // logical_and_expr |
| // : |
| // | AND_AND inclusive_or_expression logical_and_expr |
| Expect<ast::Expression*> ParserImpl::expect_logical_and_expr( |
| ast::Expression* lhs) { |
| while (synchronized_) { |
| auto t = peek(); |
| if (!t.IsAndAnd()) |
| return lhs; |
| |
| auto source = t.source(); |
| next(); // Consume the peek |
| |
| auto rhs = inclusive_or_expression(); |
| if (rhs.errored) |
| return Failure::kErrored; |
| if (!rhs.matched) |
| return add_error(peek(), "unable to parse right side of && expression"); |
| |
| lhs = create<ast::BinaryExpression>(source, ast::BinaryOp::kLogicalAnd, lhs, |
| rhs.value); |
| } |
| return Failure::kErrored; |
| } |
| |
| // logical_and_expression |
| // : inclusive_or_expression logical_and_expr |
| Maybe<ast::Expression*> ParserImpl::logical_and_expression() { |
| auto lhs = inclusive_or_expression(); |
| if (lhs.errored) |
| return Failure::kErrored; |
| if (!lhs.matched) |
| return Failure::kNoMatch; |
| |
| return expect_logical_and_expr(lhs.value); |
| } |
| |
| // logical_or_expr |
| // : |
| // | OR_OR logical_and_expression logical_or_expr |
| Expect<ast::Expression*> ParserImpl::expect_logical_or_expr( |
| ast::Expression* lhs) { |
| while (synchronized_) { |
| Source source; |
| if (!match(Token::Type::kOrOr)) |
| return lhs; |
| |
| auto rhs = logical_and_expression(); |
| if (rhs.errored) |
| return Failure::kErrored; |
| if (!rhs.matched) |
| return add_error(peek(), "unable to parse right side of || expression"); |
| |
| lhs = create<ast::BinaryExpression>(source, ast::BinaryOp::kLogicalOr, lhs, |
| rhs.value); |
| } |
| return Failure::kErrored; |
| } |
| |
| // logical_or_expression |
| // : logical_and_expression logical_or_expr |
| Maybe<ast::Expression*> ParserImpl::logical_or_expression() { |
| auto lhs = logical_and_expression(); |
| if (lhs.errored) |
| return Failure::kErrored; |
| if (!lhs.matched) |
| return Failure::kNoMatch; |
| |
| return expect_logical_or_expr(lhs.value); |
| } |
| |
| // assignment_stmt |
| // : unary_expression EQUAL logical_or_expression |
| Maybe<ast::AssignmentStatement*> ParserImpl::assignment_stmt() { |
| auto t = peek(); |
| auto source = t.source(); |
| |
| // tint:295 - Test for `ident COLON` - this is invalid grammar, and without |
| // special casing will error as "missing = for assignment", which is less |
| // helpful than this error message: |
| if (peek(0).IsIdentifier() && peek(1).IsColon()) { |
| return add_error(peek(0).source(), |
| "expected 'var' for variable declaration"); |
| } |
| |
| auto lhs = unary_expression(); |
| if (lhs.errored) |
| return Failure::kErrored; |
| if (!lhs.matched) |
| return Failure::kNoMatch; |
| |
| if (!expect("assignment", Token::Type::kEqual)) |
| return Failure::kErrored; |
| |
| auto rhs = logical_or_expression(); |
| if (rhs.errored) |
| return Failure::kErrored; |
| if (!rhs.matched) |
| return add_error(peek(), "unable to parse right side of assignment"); |
| |
| return create<ast::AssignmentStatement>(source, lhs.value, rhs.value); |
| } |
| |
| // const_literal |
| // : INT_LITERAL |
| // | UINT_LITERAL |
| // | FLOAT_LITERAL |
| // | TRUE |
| // | FALSE |
| Maybe<ast::Literal*> ParserImpl::const_literal() { |
| auto t = peek(); |
| if (match(Token::Type::kTrue)) { |
| auto* type = builder_.create<type::Bool>(); |
| return create<ast::BoolLiteral>(Source{}, type, true); |
| } |
| if (match(Token::Type::kFalse)) { |
| auto* type = builder_.create<type::Bool>(); |
| return create<ast::BoolLiteral>(Source{}, type, false); |
| } |
| if (match(Token::Type::kSintLiteral)) { |
| auto* type = builder_.create<type::I32>(); |
| return create<ast::SintLiteral>(Source{}, type, t.to_i32()); |
| } |
| if (match(Token::Type::kUintLiteral)) { |
| auto* type = builder_.create<type::U32>(); |
| return create<ast::UintLiteral>(Source{}, type, t.to_u32()); |
| } |
| if (match(Token::Type::kFloatLiteral)) { |
| auto p = peek(); |
| if (p.IsIdentifier() && p.to_str() == "f") { |
| next(); // Consume 'f' |
| add_error(p.source(), "float literals must not be suffixed with 'f'"); |
| } |
| auto* type = builder_.create<type::F32>(); |
| return create<ast::FloatLiteral>(Source{}, type, t.to_f32()); |
| } |
| return Failure::kNoMatch; |
| } |
| |
| // const_expr |
| // : type_decl PAREN_LEFT (const_expr COMMA)? const_expr PAREN_RIGHT |
| // | const_literal |
| Expect<ast::ConstructorExpression*> ParserImpl::expect_const_expr() { |
| auto t = peek(); |
| |
| auto source = t.source(); |
| |
| auto type = type_decl(); |
| if (type.errored) |
| return Failure::kErrored; |
| if (type.matched) { |
| auto params = expect_paren_block("type constructor", |
| [&]() -> Expect<ast::ExpressionList> { |
| ast::ExpressionList list; |
| auto param = expect_const_expr(); |
| if (param.errored) |
| return Failure::kErrored; |
| list.emplace_back(param.value); |
| while (match(Token::Type::kComma)) { |
| param = expect_const_expr(); |
| if (param.errored) |
| return Failure::kErrored; |
| list.emplace_back(param.value); |
| } |
| return list; |
| }); |
| |
| if (params.errored) |
| return Failure::kErrored; |
| |
| return create<ast::TypeConstructorExpression>(source, type.value, |
| params.value); |
| } |
| |
| auto lit = const_literal(); |
| if (lit.errored) |
| return Failure::kErrored; |
| if (!lit.matched) |
| return add_error(peek(), "unable to parse const literal"); |
| |
| return create<ast::ScalarConstructorExpression>(source, lit.value); |
| } |
| |
| Maybe<ast::DecorationList> ParserImpl::decoration_list() { |
| bool errored = false; |
| bool matched = false; |
| ast::DecorationList decos; |
| |
| while (synchronized_) { |
| auto list = decoration_bracketed_list(decos); |
| if (list.errored) |
| errored = true; |
| if (!list.matched) |
| break; |
| |
| matched = true; |
| } |
| |
| if (errored) |
| return Failure::kErrored; |
| |
| if (!matched) |
| return Failure::kNoMatch; |
| |
| return decos; |
| } |
| |
| Maybe<bool> ParserImpl::decoration_bracketed_list(ast::DecorationList& decos) { |
| const char* use = "decoration list"; |
| |
| if (!match(Token::Type::kAttrLeft)) { |
| return Failure::kNoMatch; |
| } |
| |
| Source source; |
| if (match(Token::Type::kAttrRight, &source)) |
| return add_error(source, "empty decoration list"); |
| |
| return sync(Token::Type::kAttrRight, [&]() -> Expect<bool> { |
| bool errored = false; |
| |
| while (synchronized_) { |
| auto deco = expect_decoration(); |
| if (deco.errored) |
| errored = true; |
| decos.emplace_back(deco.value); |
| |
| if (match(Token::Type::kComma)) |
| continue; |
| |
| if (is_decoration(peek())) { |
| // We have two decorations in a bracket without a separating comma. |
| // e.g. [[location(1) group(2)]] |
| // ^^^ expected comma |
| expect(use, Token::Type::kComma); |
| return Failure::kErrored; |
| } |
| |
| break; |
| } |
| |
| if (errored) |
| return Failure::kErrored; |
| |
| if (!expect(use, Token::Type::kAttrRight)) |
| return Failure::kErrored; |
| |
| return true; |
| }); |
| } |
| |
| Expect<ast::Decoration*> ParserImpl::expect_decoration() { |
| auto t = peek(); |
| auto deco = decoration(); |
| if (deco.errored) |
| return Failure::kErrored; |
| if (deco.matched) |
| return deco.value; |
| return add_error(t, "expected decoration"); |
| } |
| |
| Maybe<ast::Decoration*> ParserImpl::decoration() { |
| using Result = Maybe<ast::Decoration*>; |
| auto t = next(); |
| |
| if (!t.IsIdentifier()) { |
| return Failure::kNoMatch; |
| } |
| |
| auto s = t.to_str(); |
| if (s == kAccessDecoration) { |
| const char* use = "access decoration"; |
| return expect_paren_block(use, [&]() -> Result { |
| auto val = expect_access_type(); |
| if (val.errored) |
| return Failure::kErrored; |
| |
| return create<ast::AccessDecoration>(t.source(), val.value); |
| }); |
| } |
| |
| if (s == kLocationDecoration) { |
| const char* use = "location decoration"; |
| return expect_paren_block(use, [&]() -> Result { |
| auto val = expect_positive_sint(use); |
| if (val.errored) |
| return Failure::kErrored; |
| |
| return create<ast::LocationDecoration>(t.source(), val.value); |
| }); |
| } |
| |
| if (s == kBindingDecoration) { |
| const char* use = "binding decoration"; |
| return expect_paren_block(use, [&]() -> Result { |
| auto val = expect_positive_sint(use); |
| if (val.errored) |
| return Failure::kErrored; |
| |
| return create<ast::BindingDecoration>(t.source(), val.value); |
| }); |
| } |
| |
| if (s == kSetDecoration || s == kGroupDecoration) { |
| const char* use = "group decoration"; |
| return expect_paren_block(use, [&]() -> Result { |
| auto val = expect_positive_sint(use); |
| if (val.errored) |
| return Failure::kErrored; |
| |
| return create<ast::GroupDecoration>(t.source(), val.value); |
| }); |
| } |
| |
| if (s == kBuiltinDecoration) { |
| return expect_paren_block("builtin decoration", [&]() -> Result { |
| auto builtin = expect_builtin(); |
| if (builtin.errored) |
| return Failure::kErrored; |
| |
| return create<ast::BuiltinDecoration>(t.source(), builtin.value); |
| }); |
| } |
| |
| if (s == kWorkgroupSizeDecoration) { |
| return expect_paren_block("workgroup_size decoration", [&]() -> Result { |
| uint32_t x; |
| uint32_t y = 1; |
| uint32_t z = 1; |
| |
| auto val = expect_nonzero_positive_sint("workgroup_size x parameter"); |
| if (val.errored) |
| return Failure::kErrored; |
| x = val.value; |
| |
| if (match(Token::Type::kComma)) { |
| val = expect_nonzero_positive_sint("workgroup_size y parameter"); |
| if (val.errored) |
| return Failure::kErrored; |
| y = val.value; |
| |
| if (match(Token::Type::kComma)) { |
| val = expect_nonzero_positive_sint("workgroup_size z parameter"); |
| if (val.errored) |
| return Failure::kErrored; |
| z = val.value; |
| } |
| } |
| |
| return create<ast::WorkgroupDecoration>(t.source(), x, y, z); |
| }); |
| } |
| |
| if (s == kStageDecoration) { |
| return expect_paren_block("stage decoration", [&]() -> Result { |
| auto stage = expect_pipeline_stage(); |
| if (stage.errored) |
| return Failure::kErrored; |
| |
| return create<ast::StageDecoration>(t.source(), stage.value); |
| }); |
| } |
| |
| if (s == kBlockDecoration) { |
| return create<ast::StructBlockDecoration>(t.source()); |
| } |
| |
| if (s == kStrideDecoration) { |
| const char* use = "stride decoration"; |
| return expect_paren_block(use, [&]() -> Result { |
| auto val = expect_nonzero_positive_sint(use); |
| if (val.errored) |
| return Failure::kErrored; |
| |
| return create<ast::StrideDecoration>(t.source(), val.value); |
| }); |
| } |
| |
| if (s == kOffsetDecoration) { |
| const char* use = "offset decoration"; |
| return expect_paren_block(use, [&]() -> Result { |
| auto val = expect_positive_sint(use); |
| if (val.errored) |
| return Failure::kErrored; |
| |
| return create<ast::StructMemberOffsetDecoration>(t.source(), val.value); |
| }); |
| } |
| |
| if (s == kConstantIdDecoration) { |
| const char* use = "constant_id decoration"; |
| return expect_paren_block(use, [&]() -> Result { |
| auto val = expect_positive_sint(use); |
| if (val.errored) |
| return Failure::kErrored; |
| |
| return create<ast::ConstantIdDecoration>(t.source(), val.value); |
| }); |
| } |
| |
| return Failure::kNoMatch; |
| } |
| |
| template <typename T> |
| std::vector<T*> ParserImpl::take_decorations(ast::DecorationList& in) { |
| ast::DecorationList remaining; |
| std::vector<T*> out; |
| out.reserve(in.size()); |
| for (auto* deco : in) { |
| if (auto* t = deco->As<T>()) { |
| out.emplace_back(t); |
| } else { |
| remaining.emplace_back(deco); |
| } |
| } |
| |
| in = std::move(remaining); |
| return out; |
| } |
| |
| bool ParserImpl::expect_decorations_consumed(const ast::DecorationList& in) { |
| if (in.empty()) { |
| return true; |
| } |
| add_error(in[0]->source(), "unexpected decorations"); |
| return false; |
| } |
| |
| bool ParserImpl::match(Token::Type tok, Source* source /*= nullptr*/) { |
| auto t = peek(); |
| |
| if (source != nullptr) |
| *source = t.source(); |
| |
| if (t.Is(tok)) { |
| next(); |
| return true; |
| } |
| return false; |
| } |
| |
| bool ParserImpl::expect(const std::string& use, Token::Type tok) { |
| auto t = peek(); |
| if (t.Is(tok)) { |
| next(); |
| synchronized_ = true; |
| return true; |
| } |
| |
| // Special case to split `>>` and `>=` tokens if we are looking for a `>`. |
| if (tok == Token::Type::kGreaterThan && |
| (t.IsShiftRight() || t.IsGreaterThanEqual())) { |
| next(); |
| |
| // Push the second character to the token queue. |
| auto source = t.source(); |
| source.range.begin.column++; |
| if (t.IsShiftRight()) |
| token_queue_.push_front(Token(Token::Type::kGreaterThan, source)); |
| else if (t.IsGreaterThanEqual()) |
| token_queue_.push_front(Token(Token::Type::kEqual, source)); |
| |
| synchronized_ = true; |
| return true; |
| } |
| |
| // Handle the case when `]` is expected but the actual token is `]]`. |
| // For example, in `arr1[arr2[0]]`. |
| if (tok == Token::Type::kBracketRight && t.IsAttrRight()) { |
| next(); |
| auto source = t.source(); |
| source.range.begin.column++; |
| token_queue_.push_front({Token::Type::kBracketRight, source}); |
| synchronized_ = true; |
| return true; |
| } |
| |
| std::stringstream err; |
| err << "expected '" << Token::TypeToName(tok) << "'"; |
| if (!use.empty()) { |
| err << " for " << use; |
| } |
| add_error(t, err.str()); |
| synchronized_ = false; |
| return false; |
| } |
| |
| Expect<int32_t> ParserImpl::expect_sint(const std::string& use) { |
| auto t = peek(); |
| if (!t.IsSintLiteral()) |
| return add_error(t.source(), "expected signed integer literal", use); |
| |
| next(); |
| return {t.to_i32(), t.source()}; |
| } |
| |
| Expect<uint32_t> ParserImpl::expect_positive_sint(const std::string& use) { |
| auto sint = expect_sint(use); |
| if (sint.errored) |
| return Failure::kErrored; |
| |
| if (sint.value < 0) |
| return add_error(sint.source, use + " must be positive"); |
| |
| return {static_cast<uint32_t>(sint.value), sint.source}; |
| } |
| |
| Expect<uint32_t> ParserImpl::expect_nonzero_positive_sint( |
| const std::string& use) { |
| auto sint = expect_sint(use); |
| if (sint.errored) |
| return Failure::kErrored; |
| |
| if (sint.value <= 0) |
| return add_error(sint.source, use + " must be greater than 0"); |
| |
| return {static_cast<uint32_t>(sint.value), sint.source}; |
| } |
| |
| Expect<std::string> ParserImpl::expect_ident(const std::string& use) { |
| auto t = peek(); |
| if (t.IsIdentifier()) { |
| synchronized_ = true; |
| next(); |
| return {t.to_str(), t.source()}; |
| } |
| synchronized_ = false; |
| return add_error(t.source(), "expected identifier", use); |
| } |
| |
| template <typename F, typename T> |
| T ParserImpl::expect_block(Token::Type start, |
| Token::Type end, |
| const std::string& use, |
| F&& body) { |
| if (!expect(use, start)) { |
| return Failure::kErrored; |
| } |
| |
| return sync(end, [&]() -> T { |
| auto res = body(); |
| |
| if (res.errored) |
| return Failure::kErrored; |
| |
| if (!expect(use, end)) |
| return Failure::kErrored; |
| |
| return res; |
| }); |
| } |
| |
| template <typename F, typename T> |
| T ParserImpl::expect_paren_block(const std::string& use, F&& body) { |
| return expect_block(Token::Type::kParenLeft, Token::Type::kParenRight, use, |
| std::forward<F>(body)); |
| } |
| |
| template <typename F, typename T> |
| T ParserImpl::expect_brace_block(const std::string& use, F&& body) { |
| return expect_block(Token::Type::kBraceLeft, Token::Type::kBraceRight, use, |
| std::forward<F>(body)); |
| } |
| |
| template <typename F, typename T> |
| T ParserImpl::expect_lt_gt_block(const std::string& use, F&& body) { |
| return expect_block(Token::Type::kLessThan, Token::Type::kGreaterThan, use, |
| std::forward<F>(body)); |
| } |
| |
| template <typename F, typename T> |
| T ParserImpl::sync(Token::Type tok, F&& body) { |
| if (sync_depth_ >= kMaxSyncDepth) { |
| // We've hit a maximum parser recursive depth. |
| // We can't call into body() as we might stack overflow. |
| // Instead, report an error... |
| add_error(peek(), "maximum parser recursive depth reached"); |
| // ...and try to resynchronize. If we cannot resynchronize to `tok` then |
| // synchronized_ is set to false, and the parser knows that forward progress |
| // is not being made. |
| sync_to(tok, /* consume: */ true); |
| return Failure::kErrored; |
| } |
| |
| sync_tokens_.push_back(tok); |
| |
| ++sync_depth_; |
| auto result = body(); |
| --sync_depth_; |
| |
| assert(sync_tokens_.back() == tok); |
| sync_tokens_.pop_back(); |
| |
| if (result.errored) { |
| sync_to(tok, /* consume: */ true); |
| } |
| |
| return result; |
| } |
| |
| bool ParserImpl::sync_to(Token::Type tok, bool consume) { |
| // Clear the synchronized state - gets set to true again on success. |
| synchronized_ = false; |
| |
| BlockCounters counters; |
| |
| for (size_t i = 0; i < kMaxResynchronizeLookahead; i++) { |
| auto t = peek(i); |
| if (counters.consume(t) > 0) |
| continue; // Nested block |
| if (!t.Is(tok) && !is_sync_token(t)) |
| continue; // Not a synchronization point |
| |
| // Synchronization point found. |
| |
| // Skip any tokens we don't understand, bringing us to just before the |
| // resync point. |
| while (i-- > 0) { |
| next(); |
| } |
| |
| // Is this synchronization token |tok|? |
| if (t.Is(tok)) { |
| if (consume) |
| next(); |
| synchronized_ = true; |
| return true; |
| } |
| break; |
| } |
| |
| return false; |
| } |
| |
| bool ParserImpl::is_sync_token(const Token& t) const { |
| for (auto r : sync_tokens_) { |
| if (t.Is(r)) |
| return true; |
| } |
| return false; |
| } |
| |
| template <typename F, typename T> |
| T ParserImpl::without_error(F&& body) { |
| silence_errors_++; |
| auto result = body(); |
| silence_errors_--; |
| return result; |
| } |
| |
| } // namespace wgsl |
| } // namespace reader |
| } // namespace tint |