| /* |
| * Copyright 2021 Google LLC. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "src/sksl/SkSLParser.h" |
| |
| #include "include/core/SkSpan.h" |
| #include "include/private/base/SkTArray.h" |
| #include "include/sksl/SkSLVersion.h" |
| #include "src/base/SkEnumBitMask.h" |
| #include "src/base/SkNoDestructor.h" |
| #include "src/core/SkTHash.h" |
| #include "src/sksl/SkSLBuiltinTypes.h" |
| #include "src/sksl/SkSLCompiler.h" |
| #include "src/sksl/SkSLConstantFolder.h" |
| #include "src/sksl/SkSLContext.h" |
| #include "src/sksl/SkSLErrorReporter.h" |
| #include "src/sksl/SkSLOperator.h" |
| #include "src/sksl/SkSLString.h" |
| #include "src/sksl/ir/SkSLBinaryExpression.h" |
| #include "src/sksl/ir/SkSLBlock.h" |
| #include "src/sksl/ir/SkSLBreakStatement.h" |
| #include "src/sksl/ir/SkSLContinueStatement.h" |
| #include "src/sksl/ir/SkSLDiscardStatement.h" |
| #include "src/sksl/ir/SkSLDoStatement.h" |
| #include "src/sksl/ir/SkSLExpression.h" |
| #include "src/sksl/ir/SkSLExpressionStatement.h" |
| #include "src/sksl/ir/SkSLExtension.h" |
| #include "src/sksl/ir/SkSLFieldAccess.h" |
| #include "src/sksl/ir/SkSLForStatement.h" |
| #include "src/sksl/ir/SkSLFunctionCall.h" |
| #include "src/sksl/ir/SkSLFunctionDeclaration.h" |
| #include "src/sksl/ir/SkSLFunctionDefinition.h" |
| #include "src/sksl/ir/SkSLFunctionPrototype.h" |
| #include "src/sksl/ir/SkSLIfStatement.h" |
| #include "src/sksl/ir/SkSLIndexExpression.h" |
| #include "src/sksl/ir/SkSLInterfaceBlock.h" |
| #include "src/sksl/ir/SkSLLayout.h" |
| #include "src/sksl/ir/SkSLLiteral.h" |
| #include "src/sksl/ir/SkSLModifierFlags.h" |
| #include "src/sksl/ir/SkSLModifiersDeclaration.h" |
| #include "src/sksl/ir/SkSLNop.h" |
| #include "src/sksl/ir/SkSLPoison.h" |
| #include "src/sksl/ir/SkSLPostfixExpression.h" |
| #include "src/sksl/ir/SkSLPrefixExpression.h" |
| #include "src/sksl/ir/SkSLProgram.h" |
| #include "src/sksl/ir/SkSLProgramElement.h" |
| #include "src/sksl/ir/SkSLReturnStatement.h" |
| #include "src/sksl/ir/SkSLStatement.h" |
| #include "src/sksl/ir/SkSLStructDefinition.h" |
| #include "src/sksl/ir/SkSLSwitchStatement.h" |
| #include "src/sksl/ir/SkSLSwizzle.h" |
| #include "src/sksl/ir/SkSLSymbol.h" |
| #include "src/sksl/ir/SkSLSymbolTable.h" |
| #include "src/sksl/ir/SkSLTernaryExpression.h" |
| #include "src/sksl/ir/SkSLType.h" |
| #include "src/sksl/ir/SkSLTypeReference.h" |
| #include "src/sksl/ir/SkSLVarDeclarations.h" |
| #include "src/sksl/ir/SkSLVariable.h" |
| |
| #include <algorithm> |
| #include <climits> |
| #include <initializer_list> |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| using namespace skia_private; |
| |
| namespace SkSL { |
| |
| static constexpr int kMaxParseDepth = 50; |
| |
| static ModifierFlags parse_modifier_token(Token::Kind token) { |
| switch (token) { |
| case Token::Kind::TK_UNIFORM: return ModifierFlag::kUniform; |
| case Token::Kind::TK_CONST: return ModifierFlag::kConst; |
| case Token::Kind::TK_IN: return ModifierFlag::kIn; |
| case Token::Kind::TK_OUT: return ModifierFlag::kOut; |
| case Token::Kind::TK_INOUT: return ModifierFlag::kIn | ModifierFlag::kOut; |
| case Token::Kind::TK_FLAT: return ModifierFlag::kFlat; |
| case Token::Kind::TK_NOPERSPECTIVE: return ModifierFlag::kNoPerspective; |
| case Token::Kind::TK_PURE: return ModifierFlag::kPure; |
| case Token::Kind::TK_INLINE: return ModifierFlag::kInline; |
| case Token::Kind::TK_NOINLINE: return ModifierFlag::kNoInline; |
| case Token::Kind::TK_HIGHP: return ModifierFlag::kHighp; |
| case Token::Kind::TK_MEDIUMP: return ModifierFlag::kMediump; |
| case Token::Kind::TK_LOWP: return ModifierFlag::kLowp; |
| case Token::Kind::TK_EXPORT: return ModifierFlag::kExport; |
| case Token::Kind::TK_ES3: return ModifierFlag::kES3; |
| case Token::Kind::TK_WORKGROUP: return ModifierFlag::kWorkgroup; |
| case Token::Kind::TK_READONLY: return ModifierFlag::kReadOnly; |
| case Token::Kind::TK_WRITEONLY: return ModifierFlag::kWriteOnly; |
| case Token::Kind::TK_BUFFER: return ModifierFlag::kBuffer; |
| case Token::Kind::TK_PIXELLOCAL: return ModifierFlag::kPixelLocal; |
| default: return ModifierFlag::kNone; |
| } |
| } |
| |
| class Parser::AutoDepth { |
| public: |
| AutoDepth(Parser* p) : fParser(p), fDepth(0) {} |
| |
| ~AutoDepth() { |
| fParser->fDepth -= fDepth; |
| } |
| |
| bool increase() { |
| ++fDepth; |
| ++fParser->fDepth; |
| if (fParser->fDepth > kMaxParseDepth) { |
| fParser->error(fParser->peek(), "exceeded max parse depth"); |
| fParser->fEncounteredFatalError = true; |
| return false; |
| } |
| return true; |
| } |
| |
| private: |
| Parser* fParser; |
| int fDepth; |
| }; |
| |
| class Parser::AutoSymbolTable { |
| public: |
| AutoSymbolTable(Parser* p, std::unique_ptr<SymbolTable>* newSymbolTable, bool enable = true) { |
| if (enable) { |
| fParser = p; |
| SymbolTable*& ctxSymbols = this->contextSymbolTable(); |
| *newSymbolTable = std::make_unique<SymbolTable>(ctxSymbols, ctxSymbols->isBuiltin()); |
| ctxSymbols = newSymbolTable->get(); |
| } |
| } |
| |
| ~AutoSymbolTable() { |
| if (fParser) { |
| SymbolTable*& ctxSymbols = this->contextSymbolTable(); |
| ctxSymbols = ctxSymbols->fParent; |
| } |
| } |
| |
| private: |
| SymbolTable*& contextSymbolTable() { return fParser->fCompiler.context().fSymbolTable; } |
| |
| Parser* fParser = nullptr; |
| }; |
| |
| class Parser::Checkpoint { |
| public: |
| Checkpoint(Parser* p) : fParser(p) { |
| Context& context = fParser->fCompiler.context(); |
| fPushbackCheckpoint = fParser->fPushback; |
| fLexerCheckpoint = fParser->fLexer.getCheckpoint(); |
| fOldErrorReporter = context.fErrors; |
| fOldEncounteredFatalError = fParser->fEncounteredFatalError; |
| SkASSERT(fOldErrorReporter); |
| context.setErrorReporter(&fErrorReporter); |
| } |
| |
| ~Checkpoint() { |
| SkASSERTF(!fOldErrorReporter, "Checkpoint was not accepted or rewound before destruction"); |
| } |
| |
| void accept() { |
| this->restoreErrorReporter(); |
| // Parser errors should have been fatal, but we can encounter other errors like type |
| // mismatches despite accepting the parse. Forward those messages to the actual error |
| // handler now. |
| fErrorReporter.forwardErrors(fParser); |
| } |
| |
| void rewind() { |
| this->restoreErrorReporter(); |
| fParser->fPushback = fPushbackCheckpoint; |
| fParser->fLexer.rewindToCheckpoint(fLexerCheckpoint); |
| fParser->fEncounteredFatalError = fOldEncounteredFatalError; |
| } |
| |
| private: |
| class ForwardingErrorReporter : public ErrorReporter { |
| public: |
| void handleError(std::string_view msg, Position pos) override { |
| fErrors.push_back({std::string(msg), pos}); |
| } |
| |
| void forwardErrors(Parser* parser) { |
| for (const Error& error : fErrors) { |
| parser->error(error.fPos, error.fMsg); |
| } |
| } |
| |
| private: |
| struct Error { |
| std::string fMsg; |
| Position fPos; |
| }; |
| |
| skia_private::TArray<Error> fErrors; |
| }; |
| |
| void restoreErrorReporter() { |
| SkASSERT(fOldErrorReporter); |
| fParser->fCompiler.context().setErrorReporter(fOldErrorReporter); |
| fOldErrorReporter = nullptr; |
| } |
| |
| Parser* fParser; |
| Token fPushbackCheckpoint; |
| SkSL::Lexer::Checkpoint fLexerCheckpoint; |
| ForwardingErrorReporter fErrorReporter; |
| ErrorReporter* fOldErrorReporter; |
| bool fOldEncounteredFatalError; |
| }; |
| |
| Parser::Parser(Compiler* compiler, |
| const ProgramSettings& settings, |
| ProgramKind kind, |
| std::unique_ptr<std::string> text) |
| : fCompiler(*compiler) |
| , fSettings(settings) |
| , fKind(kind) |
| , fText(std::move(text)) |
| , fPushback(Token::Kind::TK_NONE, /*offset=*/-1, /*length=*/-1) { |
| fLexer.start(*fText); |
| } |
| |
| Parser::~Parser() = default; |
| |
| SymbolTable* Parser::symbolTable() { |
| return fCompiler.symbolTable(); |
| } |
| |
| Token Parser::nextRawToken() { |
| Token token; |
| if (fPushback.fKind != Token::Kind::TK_NONE) { |
| // Retrieve the token from the pushback buffer. |
| token = fPushback; |
| fPushback.fKind = Token::Kind::TK_NONE; |
| } else { |
| // Fetch a token from the lexer. |
| token = fLexer.next(); |
| |
| // Some tokens are always invalid, so we detect and report them here. |
| switch (token.fKind) { |
| case Token::Kind::TK_PRIVATE_IDENTIFIER: |
| if (ProgramConfig::AllowsPrivateIdentifiers(fKind)) { |
| token.fKind = Token::Kind::TK_IDENTIFIER; |
| break; |
| } |
| [[fallthrough]]; |
| |
| case Token::Kind::TK_RESERVED: |
| this->error(token, "name '" + std::string(this->text(token)) + "' is reserved"); |
| token.fKind = Token::Kind::TK_IDENTIFIER; // reduces additional follow-up errors |
| break; |
| |
| case Token::Kind::TK_BAD_OCTAL: |
| this->error(token, "'" + std::string(this->text(token)) + |
| "' is not a valid octal number"); |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| return token; |
| } |
| |
| static bool is_whitespace(Token::Kind kind) { |
| switch (kind) { |
| case Token::Kind::TK_WHITESPACE: |
| case Token::Kind::TK_LINE_COMMENT: |
| case Token::Kind::TK_BLOCK_COMMENT: |
| return true; |
| |
| default: |
| return false; |
| } |
| } |
| |
| bool Parser::expectNewline() { |
| Token token = this->nextRawToken(); |
| if (token.fKind == Token::Kind::TK_WHITESPACE) { |
| // The lexer doesn't distinguish newlines from other forms of whitespace, so we check |
| // for newlines by searching through the token text. |
| std::string_view tokenText = this->text(token); |
| if (tokenText.find_first_of('\r') != std::string_view::npos || |
| tokenText.find_first_of('\n') != std::string_view::npos) { |
| return true; |
| } |
| } |
| // We didn't find a newline. |
| this->pushback(token); |
| return false; |
| } |
| |
| Token Parser::nextToken() { |
| for (;;) { |
| Token token = this->nextRawToken(); |
| if (!is_whitespace(token.fKind)) { |
| return token; |
| } |
| } |
| } |
| |
| void Parser::pushback(Token t) { |
| SkASSERT(fPushback.fKind == Token::Kind::TK_NONE); |
| fPushback = t; |
| } |
| |
| Token Parser::peek() { |
| if (fPushback.fKind == Token::Kind::TK_NONE) { |
| fPushback = this->nextToken(); |
| } |
| return fPushback; |
| } |
| |
| bool Parser::checkNext(Token::Kind kind, Token* result) { |
| if (fPushback.fKind != Token::Kind::TK_NONE && fPushback.fKind != kind) { |
| return false; |
| } |
| Token next = this->nextToken(); |
| if (next.fKind == kind) { |
| if (result) { |
| *result = next; |
| } |
| return true; |
| } |
| this->pushback(next); |
| return false; |
| } |
| |
| bool Parser::expect(Token::Kind kind, const char* expected, Token* result) { |
| Token next = this->nextToken(); |
| if (next.fKind == kind) { |
| if (result) { |
| *result = next; |
| } |
| return true; |
| } else { |
| this->error(next, "expected " + std::string(expected) + ", but found '" + |
| std::string(this->text(next)) + "'"); |
| this->fEncounteredFatalError = true; |
| return false; |
| } |
| } |
| |
| bool Parser::expectIdentifier(Token* result) { |
| if (!this->expect(Token::Kind::TK_IDENTIFIER, "an identifier", result)) { |
| return false; |
| } |
| if (this->symbolTable()->isBuiltinType(this->text(*result))) { |
| this->error(*result, "expected an identifier, but found type '" + |
| std::string(this->text(*result)) + "'"); |
| this->fEncounteredFatalError = true; |
| return false; |
| } |
| return true; |
| } |
| |
| bool Parser::checkIdentifier(Token* result) { |
| if (!this->checkNext(Token::Kind::TK_IDENTIFIER, result)) { |
| return false; |
| } |
| if (this->symbolTable()->isBuiltinType(this->text(*result))) { |
| this->pushback(*result); |
| return false; |
| } |
| return true; |
| } |
| |
| std::string_view Parser::text(Token token) { |
| return std::string_view(fText->data() + token.fOffset, token.fLength); |
| } |
| |
| Position Parser::position(Token t) { |
| if (t.fOffset >= 0) { |
| return Position::Range(t.fOffset, t.fOffset + t.fLength); |
| } else { |
| return Position(); |
| } |
| } |
| |
| void Parser::error(Token token, std::string_view msg) { |
| this->error(this->position(token), msg); |
| } |
| |
| void Parser::error(Position position, std::string_view msg) { |
| fCompiler.context().fErrors->error(position, msg); |
| } |
| |
| Position Parser::rangeFrom(Position start) { |
| int offset = fPushback.fKind != Token::Kind::TK_NONE ? fPushback.fOffset |
| : fLexer.getCheckpoint().fOffset; |
| return Position::Range(start.startOffset(), offset); |
| } |
| |
| Position Parser::rangeFrom(Token start) { |
| return this->rangeFrom(this->position(start)); |
| } |
| |
| /* declaration* END_OF_FILE */ |
| std::unique_ptr<Program> Parser::programInheritingFrom(const SkSL::Module* module) { |
| this->declarations(); |
| std::unique_ptr<Program> result; |
| if (fCompiler.errorReporter().errorCount() == 0) { |
| result = fCompiler.releaseProgram(std::move(fText), std::move(fProgramElements)); |
| } else { |
| fProgramElements.clear(); |
| } |
| return result; |
| } |
| |
| std::unique_ptr<SkSL::Module> Parser::moduleInheritingFrom(const SkSL::Module* parentModule) { |
| this->declarations(); |
| this->symbolTable()->takeOwnershipOfString(std::move(*fText)); |
| auto result = std::make_unique<SkSL::Module>(); |
| result->fParent = parentModule; |
| result->fSymbols = std::move(fCompiler.fGlobalSymbols); |
| result->fElements = std::move(fProgramElements); |
| return result; |
| } |
| |
| void Parser::declarations() { |
| fEncounteredFatalError = false; |
| |
| // If the program is 8MB or longer (Position::kMaxOffset), error reporting goes off the rails. |
| // At any rate, there's no good reason for a program to be this long. |
| if (fText->size() >= Position::kMaxOffset) { |
| this->error(Position(), "program is too large"); |
| return; |
| } |
| |
| // Any #version directive must appear as the first thing in a file |
| if (this->peek().fKind == Token::Kind::TK_DIRECTIVE) { |
| this->directive(/*allowVersion=*/true); |
| } |
| |
| while (!fEncounteredFatalError) { |
| // We should always be at global scope when processing top-level declarations. |
| SkASSERT(fCompiler.context().fSymbolTable == fCompiler.globalSymbols()); |
| |
| switch (this->peek().fKind) { |
| case Token::Kind::TK_END_OF_FILE: |
| return; |
| |
| case Token::Kind::TK_INVALID: |
| this->error(this->peek(), "invalid token"); |
| return; |
| |
| case Token::Kind::TK_DIRECTIVE: |
| this->directive(/*allowVersion=*/false); |
| break; |
| |
| default: |
| this->declaration(); |
| break; |
| } |
| } |
| } |
| |
| /* DIRECTIVE(#extension) IDENTIFIER COLON IDENTIFIER NEWLINE */ |
| void Parser::extensionDirective(Position start) { |
| Token name; |
| if (!this->expectIdentifier(&name)) { |
| return; |
| } |
| if (!this->expect(Token::Kind::TK_COLON, "':'")) { |
| return; |
| } |
| Token behavior; |
| if (!this->expect(Token::Kind::TK_IDENTIFIER, "an identifier", &behavior)) { |
| return; |
| } |
| // We expect a newline immediately after `#extension name : behavior`. |
| if (this->expectNewline()) { |
| std::unique_ptr<SkSL::Extension> ext = Extension::Convert(fCompiler.context(), |
| this->rangeFrom(start), |
| this->text(name), |
| this->text(behavior)); |
| if (ext) { |
| fProgramElements.push_back(std::move(ext)); |
| } |
| } else { |
| this->error(start, "invalid #extension directive"); |
| } |
| } |
| |
| /* DIRECTIVE(#version) INTLITERAL NEWLINE */ |
| void Parser::versionDirective(Position start, bool allowVersion) { |
| if (!allowVersion) { |
| this->error(start, "#version directive must appear before anything else"); |
| return; |
| } |
| SKSL_INT version; |
| if (!this->intLiteral(&version)) { |
| return; |
| } |
| switch (version) { |
| case 100: |
| fCompiler.context().fConfig->fRequiredSkSLVersion = Version::k100; |
| break; |
| case 300: |
| fCompiler.context().fConfig->fRequiredSkSLVersion = Version::k300; |
| break; |
| default: |
| this->error(start, "unsupported version number"); |
| return; |
| } |
| // We expect a newline after a #version directive. |
| if (!this->expectNewline()) { |
| this->error(start, "invalid #version directive"); |
| } |
| } |
| |
| /* DIRECTIVE(#extension) IDENTIFIER COLON IDENTIFIER NEWLINE | |
| DIRECTIVE(#version) INTLITERAL NEWLINE */ |
| void Parser::directive(bool allowVersion) { |
| Token start; |
| if (!this->expect(Token::Kind::TK_DIRECTIVE, "a directive", &start)) { |
| return; |
| } |
| std::string_view text = this->text(start); |
| if (text == "#extension") { |
| return this->extensionDirective(this->position(start)); |
| } |
| if (text == "#version") { |
| return this->versionDirective(this->position(start), allowVersion); |
| } |
| this->error(start, "unsupported directive '" + std::string(this->text(start)) + "'"); |
| } |
| |
| bool Parser::modifiersDeclarationEnd(const SkSL::Modifiers& mods) { |
| std::unique_ptr<ModifiersDeclaration> decl = ModifiersDeclaration::Convert(fCompiler.context(), |
| mods); |
| if (!decl) { |
| return false; |
| } |
| fProgramElements.push_back(std::move(decl)); |
| return true; |
| } |
| |
| /* modifiers (structVarDeclaration | type IDENTIFIER ((LPAREN parameter (COMMA parameter)* RPAREN |
| (block | SEMICOLON)) | SEMICOLON) | interfaceBlock) */ |
| bool Parser::declaration() { |
| Token start = this->peek(); |
| if (start.fKind == Token::Kind::TK_SEMICOLON) { |
| this->nextToken(); |
| this->error(start, "expected a declaration, but found ';'"); |
| return false; |
| } |
| Modifiers modifiers = this->modifiers(); |
| Token lookahead = this->peek(); |
| if (lookahead.fKind == Token::Kind::TK_IDENTIFIER && |
| !this->symbolTable()->isType(this->text(lookahead))) { |
| // we have an identifier that's not a type, could be the start of an interface block |
| return this->interfaceBlock(modifiers); |
| } |
| if (lookahead.fKind == Token::Kind::TK_SEMICOLON) { |
| this->nextToken(); |
| return this->modifiersDeclarationEnd(modifiers); |
| } |
| if (lookahead.fKind == Token::Kind::TK_STRUCT) { |
| this->structVarDeclaration(this->position(start), modifiers); |
| return true; |
| } |
| const Type* type = this->type(&modifiers); |
| if (!type) { |
| return false; |
| } |
| Token name; |
| if (!this->expectIdentifier(&name)) { |
| return false; |
| } |
| if (this->checkNext(Token::Kind::TK_LPAREN)) { |
| return this->functionDeclarationEnd(this->position(start), modifiers, type, name); |
| } else { |
| this->globalVarDeclarationEnd(this->position(start), modifiers, type, name); |
| return true; |
| } |
| } |
| |
| /* (RPAREN | VOID RPAREN | parameter (COMMA parameter)* RPAREN) (block | SEMICOLON) */ |
| bool Parser::functionDeclarationEnd(Position start, |
| Modifiers& modifiers, |
| const Type* returnType, |
| const Token& name) { |
| Token lookahead = this->peek(); |
| bool validParams = true; |
| STArray<8, std::unique_ptr<Variable>> parameters; |
| if (lookahead.fKind == Token::Kind::TK_RPAREN) { |
| // `()` means no parameters at all. |
| } else if (lookahead.fKind == Token::Kind::TK_IDENTIFIER && this->text(lookahead) == "void") { |
| // `(void)` also means no parameters at all. |
| this->nextToken(); |
| } else { |
| for (;;) { |
| std::unique_ptr<SkSL::Variable> param; |
| if (!this->parameter(¶m)) { |
| return false; |
| } |
| validParams = validParams && param; |
| parameters.push_back(std::move(param)); |
| if (!this->checkNext(Token::Kind::TK_COMMA)) { |
| break; |
| } |
| } |
| } |
| if (!this->expect(Token::Kind::TK_RPAREN, "')'")) { |
| return false; |
| } |
| |
| SkSL::FunctionDeclaration* decl = nullptr; |
| if (validParams) { |
| decl = SkSL::FunctionDeclaration::Convert(fCompiler.context(), |
| this->rangeFrom(start), |
| modifiers, |
| this->text(name), |
| std::move(parameters), |
| start, |
| returnType); |
| } |
| |
| if (this->checkNext(Token::Kind::TK_SEMICOLON)) { |
| return this->prototypeFunction(decl); |
| } else { |
| return this->defineFunction(decl); |
| } |
| } |
| |
| bool Parser::prototypeFunction(SkSL::FunctionDeclaration* decl) { |
| if (!decl) { |
| return false; |
| } |
| fProgramElements.push_back(std::make_unique<SkSL::FunctionPrototype>( |
| decl->fPosition, decl, fCompiler.context().fConfig->fIsBuiltinCode)); |
| return true; |
| } |
| |
| bool Parser::defineFunction(SkSL::FunctionDeclaration* decl) { |
| const Context& context = fCompiler.context(); |
| Token bodyStart = this->peek(); |
| |
| std::unique_ptr<SymbolTable> symbolTable; |
| std::unique_ptr<Statement> body; |
| { |
| // Create a symbol table for the function which includes the parameters. |
| AutoSymbolTable symbols(this, &symbolTable); |
| if (decl) { |
| for (Variable* param : decl->parameters()) { |
| symbolTable->addWithoutOwnership(fCompiler.context(), param); |
| } |
| } |
| |
| // Parse the function body. |
| body = this->block(/*introduceNewScope=*/false, /*adoptExistingSymbolTable=*/&symbolTable); |
| } |
| |
| // If there was a problem with the declarations or body, don't actually create a definition. |
| if (!decl || !body) { |
| return false; |
| } |
| |
| std::unique_ptr<SkSL::Statement> block = std::move(body); |
| SkASSERT(block->is<Block>()); |
| Position pos = this->rangeFrom(bodyStart); |
| block->fPosition = pos; |
| |
| std::unique_ptr<FunctionDefinition> function = FunctionDefinition::Convert(context, |
| pos, |
| *decl, |
| std::move(block), |
| /*builtin=*/false); |
| if (!function) { |
| return false; |
| } |
| decl->setDefinition(function.get()); |
| fProgramElements.push_back(std::move(function)); |
| return true; |
| } |
| |
| bool Parser::arraySize(SKSL_INT* outResult) { |
| // Start out with a safe value that won't generate any errors downstream |
| *outResult = 1; |
| Token next = this->peek(); |
| if (next.fKind == Token::Kind::TK_RBRACKET) { |
| this->error(this->position(next), "unsized arrays are not permitted here"); |
| return true; |
| } |
| std::unique_ptr<Expression> sizeLiteral = this->expression(); |
| if (!sizeLiteral) { |
| return false; |
| } |
| if (!sizeLiteral->is<Poison>()) { |
| SKSL_INT size; |
| if (!ConstantFolder::GetConstantInt(*sizeLiteral, &size)) { |
| this->error(sizeLiteral->fPosition, "array size must be an integer"); |
| return true; |
| } |
| if (size > INT32_MAX) { |
| this->error(sizeLiteral->fPosition, "array size out of bounds"); |
| return true; |
| } |
| if (size <= 0) { |
| this->error(sizeLiteral->fPosition, "array size must be positive"); |
| return true; |
| } |
| // Now that we've validated it, output the real value |
| *outResult = size; |
| } |
| return true; |
| } |
| |
| const Type* Parser::arrayType(const Type* base, int count, Position pos) { |
| const Context& context = fCompiler.context(); |
| count = base->convertArraySize(context, pos, pos, count); |
| if (!count) { |
| return context.fTypes.fPoison.get(); |
| } |
| return this->symbolTable()->addArrayDimension(fCompiler.context(), base, count); |
| } |
| |
| const Type* Parser::unsizedArrayType(const Type* base, Position pos) { |
| const Context& context = fCompiler.context(); |
| if (!base->checkIfUsableInArray(context, pos)) { |
| return context.fTypes.fPoison.get(); |
| } |
| return this->symbolTable()->addArrayDimension(fCompiler.context(), base, |
| SkSL::Type::kUnsizedArray); |
| } |
| |
| bool Parser::parseArrayDimensions(Position pos, const Type** type) { |
| Token next; |
| while (this->checkNext(Token::Kind::TK_LBRACKET, &next)) { |
| if (this->checkNext(Token::Kind::TK_RBRACKET)) { |
| if (this->allowUnsizedArrays()) { |
| *type = this->unsizedArrayType(*type, this->rangeFrom(pos)); |
| } else { |
| this->error(this->rangeFrom(pos), "unsized arrays are not permitted here"); |
| } |
| } else { |
| SKSL_INT size; |
| if (!this->arraySize(&size)) { |
| return false; |
| } |
| if (!this->expect(Token::Kind::TK_RBRACKET, "']'")) { |
| return false; |
| } |
| *type = this->arrayType(*type, size, this->rangeFrom(pos)); |
| } |
| } |
| return true; |
| } |
| |
| bool Parser::parseInitializer(Position pos, std::unique_ptr<Expression>* initializer) { |
| if (this->checkNext(Token::Kind::TK_EQ)) { |
| *initializer = this->assignmentExpression(); |
| return *initializer != nullptr; |
| } |
| return true; |
| } |
| |
| void Parser::addGlobalVarDeclaration(std::unique_ptr<VarDeclaration> decl) { |
| if (decl) { |
| fProgramElements.push_back(std::make_unique<SkSL::GlobalVarDeclaration>(std::move(decl))); |
| } |
| } |
| |
| /* (LBRACKET expression? RBRACKET)* (EQ assignmentExpression)? (COMMA IDENTIFER |
| (LBRACKET expression? RBRACKET)* (EQ assignmentExpression)?)* SEMICOLON */ |
| void Parser::globalVarDeclarationEnd(Position pos, |
| const Modifiers& mods, |
| const Type* baseType, |
| Token name) { |
| const Type* type = baseType; |
| std::unique_ptr<Expression> initializer; |
| if (!this->parseArrayDimensions(pos, &type)) { |
| return; |
| } |
| if (!this->parseInitializer(pos, &initializer)) { |
| return; |
| } |
| this->addGlobalVarDeclaration(VarDeclaration::Convert(fCompiler.context(), |
| this->rangeFrom(pos), |
| mods, |
| *type, |
| this->position(name), |
| this->text(name), |
| VariableStorage::kGlobal, |
| std::move(initializer))); |
| while (this->checkNext(Token::Kind::TK_COMMA)) { |
| type = baseType; |
| Token identifierName; |
| if (!this->expectIdentifier(&identifierName)) { |
| return; |
| } |
| if (!this->parseArrayDimensions(pos, &type)) { |
| return; |
| } |
| std::unique_ptr<Expression> anotherInitializer; |
| if (!this->parseInitializer(pos, &anotherInitializer)) { |
| return; |
| } |
| this->addGlobalVarDeclaration(VarDeclaration::Convert(fCompiler.context(), |
| this->rangeFrom(identifierName), |
| mods, |
| *type, |
| this->position(identifierName), |
| this->text(identifierName), |
| VariableStorage::kGlobal, |
| std::move(anotherInitializer))); |
| } |
| this->expect(Token::Kind::TK_SEMICOLON, "';'"); |
| } |
| |
| /* (LBRACKET expression? RBRACKET)* (EQ assignmentExpression)? (COMMA IDENTIFER |
| (LBRACKET expression? RBRACKET)* (EQ assignmentExpression)?)* SEMICOLON */ |
| std::unique_ptr<Statement> Parser::localVarDeclarationEnd(Position pos, |
| const Modifiers& mods, |
| const Type* baseType, |
| Token name) { |
| const Type* type = baseType; |
| std::unique_ptr<Expression> initializer; |
| if (!this->parseArrayDimensions(pos, &type)) { |
| return nullptr; |
| } |
| if (!this->parseInitializer(pos, &initializer)) { |
| return nullptr; |
| } |
| std::unique_ptr<Statement> result = VarDeclaration::Convert(fCompiler.context(), |
| this->rangeFrom(pos), |
| mods, |
| *type, |
| this->position(name), |
| this->text(name), |
| VariableStorage::kLocal, |
| std::move(initializer)); |
| for (;;) { |
| if (!this->checkNext(Token::Kind::TK_COMMA)) { |
| this->expect(Token::Kind::TK_SEMICOLON, "';'"); |
| break; |
| } |
| type = baseType; |
| Token identifierName; |
| if (!this->expectIdentifier(&identifierName)) { |
| break; |
| } |
| if (!this->parseArrayDimensions(pos, &type)) { |
| break; |
| } |
| std::unique_ptr<Expression> anotherInitializer; |
| if (!this->parseInitializer(pos, &anotherInitializer)) { |
| break; |
| } |
| std::unique_ptr<Statement> next = VarDeclaration::Convert(fCompiler.context(), |
| this->rangeFrom(identifierName), |
| mods, |
| *type, |
| this->position(identifierName), |
| this->text(identifierName), |
| VariableStorage::kLocal, |
| std::move(anotherInitializer)); |
| |
| result = Block::MakeCompoundStatement(std::move(result), std::move(next)); |
| } |
| pos = this->rangeFrom(pos); |
| return this->statementOrNop(pos, std::move(result)); |
| } |
| |
| /* (varDeclarations | expressionStatement) */ |
| std::unique_ptr<Statement> Parser::varDeclarationsOrExpressionStatement() { |
| Token nextToken = this->peek(); |
| if (nextToken.fKind == Token::Kind::TK_CONST) { |
| // Statements that begin with `const` might be variable declarations, but can't be legal |
| // SkSL expression-statements. (SkSL constructors don't take a `const` modifier.) |
| return this->varDeclarations(); |
| } |
| |
| if (nextToken.fKind == Token::Kind::TK_HIGHP || |
| nextToken.fKind == Token::Kind::TK_MEDIUMP || |
| nextToken.fKind == Token::Kind::TK_LOWP || |
| this->symbolTable()->isType(this->text(nextToken))) { |
| // Statements that begin with a typename are most often variable declarations, but |
| // occasionally the type is part of a constructor, and these are actually expression- |
| // statements in disguise. First, attempt the common case: parse it as a vardecl. |
| Checkpoint checkpoint(this); |
| VarDeclarationsPrefix prefix; |
| if (this->varDeclarationsPrefix(&prefix)) { |
| checkpoint.accept(); |
| return this->localVarDeclarationEnd(prefix.fPosition, prefix.fModifiers, prefix.fType, |
| prefix.fName); |
| } |
| |
| // If this statement wasn't actually a vardecl after all, rewind and try parsing it as an |
| // expression-statement instead. |
| checkpoint.rewind(); |
| } |
| return this->expressionStatement(); |
| } |
| |
| // Helper function for varDeclarations(). If this function succeeds, we assume that the rest of the |
| // statement is a variable-declaration statement, not an expression-statement. |
| bool Parser::varDeclarationsPrefix(VarDeclarationsPrefix* prefixData) { |
| prefixData->fPosition = this->position(this->peek()); |
| prefixData->fModifiers = this->modifiers(); |
| prefixData->fType = this->type(&prefixData->fModifiers); |
| if (!prefixData->fType) { |
| return false; |
| } |
| return this->expectIdentifier(&prefixData->fName); |
| } |
| |
| /* modifiers type IDENTIFIER varDeclarationEnd */ |
| std::unique_ptr<Statement> Parser::varDeclarations() { |
| VarDeclarationsPrefix prefix; |
| if (!this->varDeclarationsPrefix(&prefix)) { |
| return nullptr; |
| } |
| return this->localVarDeclarationEnd(prefix.fPosition, prefix.fModifiers, prefix.fType, |
| prefix.fName); |
| } |
| |
| /* STRUCT IDENTIFIER LBRACE varDeclaration* RBRACE */ |
| const Type* Parser::structDeclaration() { |
| AutoDepth depth(this); |
| Position start = this->position(this->peek()); |
| if (!this->expect(Token::Kind::TK_STRUCT, "'struct'")) { |
| return nullptr; |
| } |
| Token name; |
| if (!this->expectIdentifier(&name)) { |
| return nullptr; |
| } |
| if (!this->expect(Token::Kind::TK_LBRACE, "'{'")) { |
| return nullptr; |
| } |
| if (!depth.increase()) { |
| return nullptr; |
| } |
| TArray<SkSL::Field> fields; |
| while (!this->checkNext(Token::Kind::TK_RBRACE)) { |
| Token fieldStart = this->peek(); |
| Modifiers modifiers = this->modifiers(); |
| const Type* type = this->type(&modifiers); |
| if (!type) { |
| return nullptr; |
| } |
| |
| do { |
| const Type* actualType = type; |
| Token memberName; |
| if (!this->expectIdentifier(&memberName)) { |
| return nullptr; |
| } |
| |
| while (this->checkNext(Token::Kind::TK_LBRACKET)) { |
| SKSL_INT size; |
| if (!this->arraySize(&size)) { |
| return nullptr; |
| } |
| if (!this->expect(Token::Kind::TK_RBRACKET, "']'")) { |
| return nullptr; |
| } |
| actualType = this->arrayType(actualType, size, |
| this->rangeFrom(this->position(fieldStart))); |
| } |
| |
| fields.push_back(SkSL::Field(this->rangeFrom(fieldStart), |
| modifiers.fLayout, |
| modifiers.fFlags, |
| this->text(memberName), |
| actualType)); |
| } while (this->checkNext(Token::Kind::TK_COMMA)); |
| |
| if (!this->expect(Token::Kind::TK_SEMICOLON, "';'")) { |
| return nullptr; |
| } |
| } |
| std::unique_ptr<SkSL::StructDefinition> def = StructDefinition::Convert(fCompiler.context(), |
| this->rangeFrom(start), |
| this->text(name), |
| std::move(fields)); |
| if (!def) { |
| return nullptr; |
| } |
| |
| const Type* result = &def->type(); |
| fProgramElements.push_back(std::move(def)); |
| return result; |
| } |
| |
| /* structDeclaration ((IDENTIFIER varDeclarationEnd) | SEMICOLON) */ |
| void Parser::structVarDeclaration(Position start, const Modifiers& modifiers) { |
| const Type* type = this->structDeclaration(); |
| if (!type) { |
| return; |
| } |
| Token name; |
| if (this->checkIdentifier(&name)) { |
| this->globalVarDeclarationEnd(this->rangeFrom(name), modifiers, type, name); |
| } else { |
| this->expect(Token::Kind::TK_SEMICOLON, "';'"); |
| } |
| } |
| |
| /* modifiers type IDENTIFIER (LBRACKET INT_LITERAL RBRACKET)? */ |
| bool Parser::parameter(std::unique_ptr<SkSL::Variable>* outParam) { |
| Position pos = this->position(this->peek()); |
| Modifiers modifiers = this->modifiers(); |
| const Type* type = this->type(&modifiers); |
| if (!type) { |
| return false; |
| } |
| Token name; |
| std::string_view nameText; |
| Position namePos; |
| if (this->checkIdentifier(&name)) { |
| nameText = this->text(name); |
| namePos = this->position(name); |
| } else { |
| namePos = this->rangeFrom(pos); |
| } |
| if (!this->parseArrayDimensions(pos, &type)) { |
| return false; |
| } |
| *outParam = SkSL::Variable::Convert(fCompiler.context(), |
| this->rangeFrom(pos), |
| modifiers.fPosition, |
| modifiers.fLayout, |
| modifiers.fFlags, |
| type, |
| namePos, |
| nameText, |
| VariableStorage::kParameter); |
| return true; |
| } |
| |
| /** EQ INT_LITERAL */ |
| int Parser::layoutInt() { |
| if (!this->expect(Token::Kind::TK_EQ, "'='")) { |
| return -1; |
| } |
| Token resultToken; |
| if (!this->expect(Token::Kind::TK_INT_LITERAL, "a non-negative integer", &resultToken)) { |
| return -1; |
| } |
| std::string_view resultFrag = this->text(resultToken); |
| SKSL_INT resultValue; |
| if (!SkSL::stoi(resultFrag, &resultValue)) { |
| this->error(resultToken, "value in layout is too large: " + std::string(resultFrag)); |
| return -1; |
| } |
| return resultValue; |
| } |
| |
| /** EQ IDENTIFIER */ |
| std::string_view Parser::layoutIdentifier() { |
| if (!this->expect(Token::Kind::TK_EQ, "'='")) { |
| return {}; |
| } |
| Token resultToken; |
| if (!this->expectIdentifier(&resultToken)) { |
| return {}; |
| } |
| return this->text(resultToken); |
| } |
| |
| /* LAYOUT LPAREN IDENTIFIER (EQ INT_LITERAL)? (COMMA IDENTIFIER (EQ INT_LITERAL)?)* RPAREN */ |
| SkSL::Layout Parser::layout() { |
| using LayoutMap = THashMap<std::string_view, SkSL::LayoutFlag>; |
| static SkNoDestructor<LayoutMap> sLayoutTokens(LayoutMap{ |
| {"location", SkSL::LayoutFlag::kLocation}, |
| {"offset", SkSL::LayoutFlag::kOffset}, |
| {"binding", SkSL::LayoutFlag::kBinding}, |
| {"texture", SkSL::LayoutFlag::kTexture}, |
| {"sampler", SkSL::LayoutFlag::kSampler}, |
| {"index", SkSL::LayoutFlag::kIndex}, |
| {"set", SkSL::LayoutFlag::kSet}, |
| {"builtin", SkSL::LayoutFlag::kBuiltin}, |
| {"input_attachment_index", SkSL::LayoutFlag::kInputAttachmentIndex}, |
| {"origin_upper_left", SkSL::LayoutFlag::kOriginUpperLeft}, |
| {"blend_support_all_equations", SkSL::LayoutFlag::kBlendSupportAllEquations}, |
| {"push_constant", SkSL::LayoutFlag::kPushConstant}, |
| {"color", SkSL::LayoutFlag::kColor}, |
| {"vulkan", SkSL::LayoutFlag::kVulkan}, |
| {"metal", SkSL::LayoutFlag::kMetal}, |
| {"webgpu", SkSL::LayoutFlag::kWebGPU}, |
| {"direct3d", SkSL::LayoutFlag::kDirect3D}, |
| {"rgba8", SkSL::LayoutFlag::kRGBA8}, |
| {"rgba32f", SkSL::LayoutFlag::kRGBA32F}, |
| {"r32f", SkSL::LayoutFlag::kR32F}, |
| {"local_size_x", SkSL::LayoutFlag::kLocalSizeX}, |
| {"local_size_y", SkSL::LayoutFlag::kLocalSizeY}, |
| {"local_size_z", SkSL::LayoutFlag::kLocalSizeZ}, |
| }); |
| |
| Layout result; |
| if (this->checkNext(Token::Kind::TK_LAYOUT) && |
| this->expect(Token::Kind::TK_LPAREN, "'('")) { |
| |
| for (;;) { |
| Token t = this->nextToken(); |
| std::string_view text = this->text(t); |
| SkSL::LayoutFlag* found = sLayoutTokens->find(text); |
| |
| if (!found) { |
| this->error(t, "'" + std::string(text) + "' is not a valid layout qualifier"); |
| } else { |
| if (result.fFlags & *found) { |
| this->error(t, "layout qualifier '" + std::string(text) + |
| "' appears more than once"); |
| } |
| |
| result.fFlags |= *found; |
| |
| switch (*found) { |
| case SkSL::LayoutFlag::kLocation: |
| result.fLocation = this->layoutInt(); |
| break; |
| case SkSL::LayoutFlag::kOffset: |
| result.fOffset = this->layoutInt(); |
| break; |
| case SkSL::LayoutFlag::kBinding: |
| result.fBinding = this->layoutInt(); |
| break; |
| case SkSL::LayoutFlag::kIndex: |
| result.fIndex = this->layoutInt(); |
| break; |
| case SkSL::LayoutFlag::kSet: |
| result.fSet = this->layoutInt(); |
| break; |
| case SkSL::LayoutFlag::kTexture: |
| result.fTexture = this->layoutInt(); |
| break; |
| case SkSL::LayoutFlag::kSampler: |
| result.fSampler = this->layoutInt(); |
| break; |
| case SkSL::LayoutFlag::kBuiltin: |
| result.fBuiltin = this->layoutInt(); |
| break; |
| case SkSL::LayoutFlag::kInputAttachmentIndex: |
| result.fInputAttachmentIndex = this->layoutInt(); |
| break; |
| case SkSL::LayoutFlag::kLocalSizeX: |
| result.fLocalSizeX = this->layoutInt(); |
| break; |
| case SkSL::LayoutFlag::kLocalSizeY: |
| result.fLocalSizeY = this->layoutInt(); |
| break; |
| case SkSL::LayoutFlag::kLocalSizeZ: |
| result.fLocalSizeZ = this->layoutInt(); |
| break; |
| default: |
| break; |
| } |
| } |
| if (this->checkNext(Token::Kind::TK_RPAREN)) { |
| break; |
| } |
| if (!this->expect(Token::Kind::TK_COMMA, "','")) { |
| break; |
| } |
| } |
| } |
| return result; |
| } |
| |
| /* layout? (UNIFORM | CONST | IN | OUT | INOUT | LOWP | MEDIUMP | HIGHP | FLAT | NOPERSPECTIVE | |
| VARYING | INLINE | WORKGROUP | READONLY | WRITEONLY | BUFFER)* */ |
| Modifiers Parser::modifiers() { |
| int start = this->peek().fOffset; |
| SkSL::Layout layout = this->layout(); |
| Token raw = this->nextRawToken(); |
| int end = raw.fOffset; |
| if (!is_whitespace(raw.fKind)) { |
| this->pushback(raw); |
| } |
| ModifierFlags flags = ModifierFlag::kNone; |
| for (;;) { |
| ModifierFlags tokenFlag = parse_modifier_token(peek().fKind); |
| if (tokenFlag == ModifierFlag::kNone) { |
| break; |
| } |
| Token modifier = this->nextToken(); |
| if (ModifierFlags duplicateFlags = (tokenFlag & flags)) { |
| this->error(modifier, "'" + duplicateFlags.description() + "' appears more than once"); |
| } |
| flags |= tokenFlag; |
| end = this->position(modifier).endOffset(); |
| } |
| return Modifiers{Position::Range(start, end), layout, flags}; |
| } |
| |
| std::unique_ptr<Statement> Parser::statementOrNop(Position pos, std::unique_ptr<Statement> stmt) { |
| if (!stmt) { |
| stmt = Nop::Make(); |
| } |
| if (pos.valid() && !stmt->position().valid()) { |
| stmt->setPosition(pos); |
| } |
| return stmt; |
| } |
| |
| /* ifStatement | forStatement | doStatement | whileStatement | block | expression */ |
| std::unique_ptr<Statement> Parser::statement(bool bracesIntroduceNewScope) { |
| AutoDepth depth(this); |
| if (!depth.increase()) { |
| return nullptr; |
| } |
| switch (this->peek().fKind) { |
| case Token::Kind::TK_IF: |
| return this->ifStatement(); |
| case Token::Kind::TK_FOR: |
| return this->forStatement(); |
| case Token::Kind::TK_DO: |
| return this->doStatement(); |
| case Token::Kind::TK_WHILE: |
| return this->whileStatement(); |
| case Token::Kind::TK_SWITCH: |
| return this->switchStatement(); |
| case Token::Kind::TK_RETURN: |
| return this->returnStatement(); |
| case Token::Kind::TK_BREAK: |
| return this->breakStatement(); |
| case Token::Kind::TK_CONTINUE: |
| return this->continueStatement(); |
| case Token::Kind::TK_DISCARD: |
| return this->discardStatement(); |
| case Token::Kind::TK_LBRACE: |
| return this->block(bracesIntroduceNewScope, /*adoptExistingSymbolTable=*/nullptr); |
| case Token::Kind::TK_SEMICOLON: |
| this->nextToken(); |
| return Nop::Make(); |
| case Token::Kind::TK_CONST: |
| return this->varDeclarations(); |
| case Token::Kind::TK_HIGHP: |
| case Token::Kind::TK_MEDIUMP: |
| case Token::Kind::TK_LOWP: |
| case Token::Kind::TK_IDENTIFIER: |
| return this->varDeclarationsOrExpressionStatement(); |
| default: |
| return this->expressionStatement(); |
| } |
| } |
| |
| const Type* Parser::findType(Position pos, |
| Modifiers* modifiers, |
| std::string_view name) { |
| const Context& context = fCompiler.context(); |
| const Symbol* symbol = this->symbolTable()->find(name); |
| if (!symbol) { |
| this->error(pos, "no symbol named '" + std::string(name) + "'"); |
| return context.fTypes.fPoison.get(); |
| } |
| if (!symbol->is<Type>()) { |
| this->error(pos, "symbol '" + std::string(name) + "' is not a type"); |
| return context.fTypes.fPoison.get(); |
| } |
| const SkSL::Type* type = &symbol->as<Type>(); |
| if (!context.fConfig->fIsBuiltinCode) { |
| if (!TypeReference::VerifyType(context, type, pos)) { |
| return context.fTypes.fPoison.get(); |
| } |
| } |
| Position qualifierRange = modifiers->fPosition; |
| if (qualifierRange.startOffset() == qualifierRange.endOffset()) { |
| qualifierRange = this->rangeFrom(qualifierRange); |
| } |
| return modifiers ? type->applyQualifiers(context, &modifiers->fFlags, qualifierRange) |
| : type; |
| } |
| |
| /* IDENTIFIER(type) (LBRACKET intLiteral? RBRACKET)* QUESTION? */ |
| const Type* Parser::type(Modifiers* modifiers) { |
| Token type; |
| if (!this->expect(Token::Kind::TK_IDENTIFIER, "a type", &type)) { |
| return nullptr; |
| } |
| if (!this->symbolTable()->isType(this->text(type))) { |
| this->error(type, "no type named '" + std::string(this->text(type)) + "'"); |
| return fCompiler.context().fTypes.fInvalid.get(); |
| } |
| const Type* result = this->findType(this->position(type), modifiers, this->text(type)); |
| if (result->isInterfaceBlock()) { |
| // SkSL puts interface blocks into the symbol table, but they aren't general-purpose types; |
| // you can't use them to declare a variable type or a function return type. |
| this->error(type, "expected a type, found '" + std::string(this->text(type)) + "'"); |
| return fCompiler.context().fTypes.fInvalid.get(); |
| } |
| Token bracket; |
| while (this->checkNext(Token::Kind::TK_LBRACKET, &bracket)) { |
| if (this->checkNext(Token::Kind::TK_RBRACKET)) { |
| if (this->allowUnsizedArrays()) { |
| result = this->unsizedArrayType(result, this->rangeFrom(type)); |
| } else { |
| this->error(this->rangeFrom(bracket), "unsized arrays are not permitted here"); |
| } |
| } else { |
| SKSL_INT size; |
| if (!this->arraySize(&size)) { |
| return nullptr; |
| } |
| this->expect(Token::Kind::TK_RBRACKET, "']'"); |
| result = this->arrayType(result, size, this->rangeFrom(type)); |
| } |
| } |
| return result; |
| } |
| |
| /* IDENTIFIER LBRACE |
| varDeclaration+ |
| RBRACE (IDENTIFIER (LBRACKET expression RBRACKET)*)? SEMICOLON */ |
| bool Parser::interfaceBlock(const Modifiers& modifiers) { |
| Token typeName; |
| if (!this->expectIdentifier(&typeName)) { |
| return false; |
| } |
| if (this->peek().fKind != Token::Kind::TK_LBRACE) { |
| // we only get into interfaceBlock if we found a top-level identifier which was not a type. |
| // 99% of the time, the user was not actually intending to create an interface block, so |
| // it's better to report it as an unknown type |
| this->error(typeName, "no type named '" + std::string(this->text(typeName)) + "'"); |
| return false; |
| } |
| this->nextToken(); |
| TArray<SkSL::Field> fields; |
| while (!this->checkNext(Token::Kind::TK_RBRACE)) { |
| Position fieldPos = this->position(this->peek()); |
| Modifiers fieldModifiers = this->modifiers(); |
| const Type* type = this->type(&fieldModifiers); |
| if (!type) { |
| return false; |
| } |
| do { |
| Token fieldName; |
| if (!this->expectIdentifier(&fieldName)) { |
| return false; |
| } |
| const Type* actualType = type; |
| if (this->checkNext(Token::Kind::TK_LBRACKET)) { |
| Token sizeToken = this->peek(); |
| if (sizeToken.fKind != Token::Kind::TK_RBRACKET) { |
| SKSL_INT size; |
| if (!this->arraySize(&size)) { |
| return false; |
| } |
| actualType = this->arrayType(actualType, size, this->position(typeName)); |
| } else if (this->allowUnsizedArrays()) { |
| actualType = this->unsizedArrayType(actualType, this->position(typeName)); |
| } else { |
| this->error(sizeToken, "unsized arrays are not permitted here"); |
| } |
| this->expect(Token::Kind::TK_RBRACKET, "']'"); |
| } |
| if (!this->expect(Token::Kind::TK_SEMICOLON, "';'")) { |
| return false; |
| } |
| |
| fields.push_back(SkSL::Field(this->rangeFrom(fieldPos), |
| fieldModifiers.fLayout, |
| fieldModifiers.fFlags, |
| this->text(fieldName), |
| actualType)); |
| } while (this->checkNext(Token::Kind::TK_COMMA)); |
| } |
| std::string_view instanceName; |
| Token instanceNameToken; |
| SKSL_INT size = 0; |
| if (this->checkIdentifier(&instanceNameToken)) { |
| instanceName = this->text(instanceNameToken); |
| if (this->checkNext(Token::Kind::TK_LBRACKET)) { |
| if (!this->arraySize(&size)) { |
| return false; |
| } |
| this->expect(Token::Kind::TK_RBRACKET, "']'"); |
| } |
| } |
| this->expect(Token::Kind::TK_SEMICOLON, "';'"); |
| |
| if (std::unique_ptr<SkSL::InterfaceBlock> ib = InterfaceBlock::Convert(fCompiler.context(), |
| this->position(typeName), |
| modifiers, |
| this->text(typeName), |
| std::move(fields), |
| instanceName, |
| size)) { |
| fProgramElements.push_back(std::move(ib)); |
| return true; |
| } |
| return false; |
| } |
| |
| /* IF LPAREN expression RPAREN statement (ELSE statement)? */ |
| std::unique_ptr<Statement> Parser::ifStatement() { |
| Token start; |
| if (!this->expect(Token::Kind::TK_IF, "'if'", &start)) { |
| return nullptr; |
| } |
| if (!this->expect(Token::Kind::TK_LPAREN, "'('")) { |
| return nullptr; |
| } |
| std::unique_ptr<Expression> test = this->expression(); |
| if (!test) { |
| return nullptr; |
| } |
| if (!this->expect(Token::Kind::TK_RPAREN, "')'")) { |
| return nullptr; |
| } |
| std::unique_ptr<Statement> ifTrue = this->statement(); |
| if (!ifTrue) { |
| return nullptr; |
| } |
| std::unique_ptr<Statement> ifFalse; |
| if (this->checkNext(Token::Kind::TK_ELSE)) { |
| ifFalse = this->statement(); |
| if (!ifFalse) { |
| return nullptr; |
| } |
| } |
| Position pos = this->rangeFrom(start); |
| return this->statementOrNop(pos, IfStatement::Convert(fCompiler.context(), |
| pos, |
| std::move(test), |
| std::move(ifTrue), |
| std::move(ifFalse))); |
| } |
| |
| /* DO statement WHILE LPAREN expression RPAREN SEMICOLON */ |
| std::unique_ptr<Statement> Parser::doStatement() { |
| Token start; |
| if (!this->expect(Token::Kind::TK_DO, "'do'", &start)) { |
| return nullptr; |
| } |
| std::unique_ptr<Statement> statement = this->statement(); |
| if (!statement) { |
| return nullptr; |
| } |
| if (!this->expect(Token::Kind::TK_WHILE, "'while'")) { |
| return nullptr; |
| } |
| if (!this->expect(Token::Kind::TK_LPAREN, "'('")) { |
| return nullptr; |
| } |
| std::unique_ptr<Expression> test = this->expression(); |
| if (!test) { |
| return nullptr; |
| } |
| if (!this->expect(Token::Kind::TK_RPAREN, "')'")) { |
| return nullptr; |
| } |
| if (!this->expect(Token::Kind::TK_SEMICOLON, "';'")) { |
| return nullptr; |
| } |
| Position pos = this->rangeFrom(start); |
| return this->statementOrNop(pos, DoStatement::Convert(fCompiler.context(), pos, |
| std::move(statement), std::move(test))); |
| } |
| |
| /* WHILE LPAREN expression RPAREN STATEMENT */ |
| std::unique_ptr<Statement> Parser::whileStatement() { |
| Token start; |
| if (!this->expect(Token::Kind::TK_WHILE, "'while'", &start)) { |
| return nullptr; |
| } |
| if (!this->expect(Token::Kind::TK_LPAREN, "'('")) { |
| return nullptr; |
| } |
| std::unique_ptr<Expression> test = this->expression(); |
| if (!test) { |
| return nullptr; |
| } |
| if (!this->expect(Token::Kind::TK_RPAREN, "')'")) { |
| return nullptr; |
| } |
| std::unique_ptr<Statement> statement = this->statement(); |
| if (!statement) { |
| return nullptr; |
| } |
| Position pos = this->rangeFrom(start); |
| return this->statementOrNop(pos, ForStatement::ConvertWhile(fCompiler.context(), pos, |
| std::move(test), |
| std::move(statement))); |
| } |
| |
| /* COLON statement* */ |
| bool Parser::switchCaseBody(ExpressionArray* values, |
| StatementArray* caseBlocks, |
| std::unique_ptr<Expression> caseValue) { |
| if (!this->expect(Token::Kind::TK_COLON, "':'")) { |
| return false; |
| } |
| StatementArray statements; |
| while (this->peek().fKind != Token::Kind::TK_RBRACE && |
| this->peek().fKind != Token::Kind::TK_CASE && |
| this->peek().fKind != Token::Kind::TK_DEFAULT) { |
| std::unique_ptr<Statement> s = this->statement(); |
| if (!s) { |
| return false; |
| } |
| statements.push_back(std::move(s)); |
| } |
| values->push_back(std::move(caseValue)); |
| caseBlocks->push_back(SkSL::Block::Make(Position(), std::move(statements), |
| Block::Kind::kUnbracedBlock)); |
| return true; |
| } |
| |
| /* CASE expression COLON statement* */ |
| bool Parser::switchCase(ExpressionArray* values, StatementArray* caseBlocks) { |
| Token start; |
| if (!this->expect(Token::Kind::TK_CASE, "'case'", &start)) { |
| return false; |
| } |
| std::unique_ptr<Expression> caseValue = this->expression(); |
| if (!caseValue) { |
| return false; |
| } |
| return this->switchCaseBody(values, caseBlocks, std::move(caseValue)); |
| } |
| |
| /* SWITCH LPAREN expression RPAREN LBRACE switchCase* (DEFAULT COLON statement*)? RBRACE */ |
| std::unique_ptr<Statement> Parser::switchStatement() { |
| Token start; |
| if (!this->expect(Token::Kind::TK_SWITCH, "'switch'", &start)) { |
| return nullptr; |
| } |
| if (!this->expect(Token::Kind::TK_LPAREN, "'('")) { |
| return nullptr; |
| } |
| std::unique_ptr<Expression> value = this->expression(); |
| if (!value) { |
| return nullptr; |
| } |
| if (!this->expect(Token::Kind::TK_RPAREN, "')'")) { |
| return nullptr; |
| } |
| if (!this->expect(Token::Kind::TK_LBRACE, "'{'")) { |
| return nullptr; |
| } |
| |
| std::unique_ptr<SymbolTable> symbolTable; |
| ExpressionArray values; |
| StatementArray caseBlocks; |
| { |
| // Keeping a tight scope around AutoSymbolTable is important here. SwitchStatement::Convert |
| // may end up creating a new symbol table if the HoistSwitchVarDeclarationsAtTopLevel |
| // transform is used. We want ~AutoSymbolTable to happen first, so it can restore the |
| // context's active symbol table to the enclosing block instead of the switch's inner block. |
| AutoSymbolTable symbols(this, &symbolTable); |
| |
| while (this->peek().fKind == Token::Kind::TK_CASE) { |
| if (!this->switchCase(&values, &caseBlocks)) { |
| return nullptr; |
| } |
| } |
| // Requiring `default:` to be last (in defiance of C and GLSL) was a deliberate decision. |
| // Other parts of the compiler are allowed to rely upon this assumption. |
| if (this->checkNext(Token::Kind::TK_DEFAULT)) { |
| if (!this->switchCaseBody(&values, &caseBlocks, /*value=*/nullptr)) { |
| return nullptr; |
| } |
| } |
| if (!this->expect(Token::Kind::TK_RBRACE, "'}'")) { |
| return nullptr; |
| } |
| } |
| |
| Position pos = this->rangeFrom(start); |
| return this->statementOrNop(pos, SwitchStatement::Convert(fCompiler.context(), pos, |
| std::move(value), |
| std::move(values), |
| std::move(caseBlocks), |
| std::move(symbolTable))); |
| } |
| |
| static Position range_of_at_least_one_char(int start, int end) { |
| return Position::Range(start, std::max(end, start + 1)); |
| } |
| |
| /* FOR LPAREN (declaration | expression)? SEMICOLON expression? SEMICOLON expression? RPAREN |
| STATEMENT */ |
| std::unique_ptr<Statement> Parser::forStatement() { |
| Token start; |
| if (!this->expect(Token::Kind::TK_FOR, "'for'", &start)) { |
| return nullptr; |
| } |
| Token lparen; |
| if (!this->expect(Token::Kind::TK_LPAREN, "'('", &lparen)) { |
| return nullptr; |
| } |
| std::unique_ptr<SymbolTable> symbolTable; |
| std::unique_ptr<Statement> initializer; |
| std::unique_ptr<Expression> test; |
| std::unique_ptr<Expression> next; |
| std::unique_ptr<Statement> statement; |
| int firstSemicolonOffset; |
| Token secondSemicolon; |
| Token rparen; |
| { |
| AutoSymbolTable symbols(this, &symbolTable); |
| |
| Token nextToken = this->peek(); |
| if (nextToken.fKind == Token::Kind::TK_SEMICOLON) { |
| // An empty init-statement. |
| firstSemicolonOffset = this->nextToken().fOffset; |
| } else { |
| // The init-statement must be an expression or variable declaration. |
| initializer = this->varDeclarationsOrExpressionStatement(); |
| if (!initializer) { |
| return nullptr; |
| } |
| firstSemicolonOffset = fLexer.getCheckpoint().fOffset - 1; |
| } |
| if (this->peek().fKind != Token::Kind::TK_SEMICOLON) { |
| test = this->expression(); |
| if (!test) { |
| return nullptr; |
| } |
| } |
| if (!this->expect(Token::Kind::TK_SEMICOLON, "';'", &secondSemicolon)) { |
| return nullptr; |
| } |
| if (this->peek().fKind != Token::Kind::TK_RPAREN) { |
| next = this->expression(); |
| if (!next) { |
| return nullptr; |
| } |
| } |
| if (!this->expect(Token::Kind::TK_RPAREN, "')'", &rparen)) { |
| return nullptr; |
| } |
| statement = this->statement(/*bracesIntroduceNewScope=*/false); |
| if (!statement) { |
| return nullptr; |
| } |
| } |
| Position pos = this->rangeFrom(start); |
| ForLoopPositions loopPositions{ |
| range_of_at_least_one_char(lparen.fOffset + 1, firstSemicolonOffset), |
| range_of_at_least_one_char(firstSemicolonOffset + 1, secondSemicolon.fOffset), |
| range_of_at_least_one_char(secondSemicolon.fOffset + 1, rparen.fOffset), |
| }; |
| return this->statementOrNop(pos, ForStatement::Convert(fCompiler.context(), pos, loopPositions, |
| std::move(initializer), |
| std::move(test), |
| std::move(next), |
| std::move(statement), |
| std::move(symbolTable))); |
| } |
| |
| /* RETURN expression? SEMICOLON */ |
| std::unique_ptr<Statement> Parser::returnStatement() { |
| Token start; |
| if (!this->expect(Token::Kind::TK_RETURN, "'return'", &start)) { |
| return nullptr; |
| } |
| std::unique_ptr<Expression> expression; |
| if (this->peek().fKind != Token::Kind::TK_SEMICOLON) { |
| expression = this->expression(); |
| if (!expression) { |
| return nullptr; |
| } |
| } |
| if (!this->expect(Token::Kind::TK_SEMICOLON, "';'")) { |
| return nullptr; |
| } |
| // We do not check for errors, or coerce the value to the correct type, until the return |
| // statement is actually added to a function. (This is done in FunctionDefinition::Convert.) |
| return ReturnStatement::Make(this->rangeFrom(start), std::move(expression)); |
| } |
| |
| /* BREAK SEMICOLON */ |
| std::unique_ptr<Statement> Parser::breakStatement() { |
| Token start; |
| if (!this->expect(Token::Kind::TK_BREAK, "'break'", &start)) { |
| return nullptr; |
| } |
| if (!this->expect(Token::Kind::TK_SEMICOLON, "';'")) { |
| return nullptr; |
| } |
| return SkSL::BreakStatement::Make(this->position(start)); |
| } |
| |
| /* CONTINUE SEMICOLON */ |
| std::unique_ptr<Statement> Parser::continueStatement() { |
| Token start; |
| if (!this->expect(Token::Kind::TK_CONTINUE, "'continue'", &start)) { |
| return nullptr; |
| } |
| if (!this->expect(Token::Kind::TK_SEMICOLON, "';'")) { |
| return nullptr; |
| } |
| return SkSL::ContinueStatement::Make(this->position(start)); |
| } |
| |
| /* DISCARD SEMICOLON */ |
| std::unique_ptr<Statement> Parser::discardStatement() { |
| Token start; |
| if (!this->expect(Token::Kind::TK_DISCARD, "'continue'", &start)) { |
| return nullptr; |
| } |
| if (!this->expect(Token::Kind::TK_SEMICOLON, "';'")) { |
| return nullptr; |
| } |
| Position pos = this->position(start); |
| return this->statementOrNop(pos, SkSL::DiscardStatement::Convert(fCompiler.context(), pos)); |
| } |
| |
| /* LBRACE statement* RBRACE */ |
| std::unique_ptr<Statement> Parser::block(bool introduceNewScope, |
| std::unique_ptr<SymbolTable>* adoptExistingSymbolTable) { |
| // We can't introduce a new scope _and_ adopt an existing symbol table. |
| SkASSERT(!(introduceNewScope && adoptExistingSymbolTable)); |
| |
| AutoDepth depth(this); |
| Token start; |
| if (!this->expect(Token::Kind::TK_LBRACE, "'{'", &start)) { |
| return nullptr; |
| } |
| if (!depth.increase()) { |
| return nullptr; |
| } |
| |
| std::unique_ptr<SymbolTable> newSymbolTable; |
| std::unique_ptr<SymbolTable>* symbolTableToUse = |
| adoptExistingSymbolTable ? adoptExistingSymbolTable : &newSymbolTable; |
| |
| StatementArray statements; |
| { |
| AutoSymbolTable symbols(this, symbolTableToUse, /*enable=*/introduceNewScope); |
| |
| // Consume statements until we reach the closing brace. |
| for (;;) { |
| Token::Kind tokenKind = this->peek().fKind; |
| if (tokenKind == Token::Kind::TK_RBRACE) { |
| this->nextToken(); |
| break; |
| } |
| if (tokenKind == Token::Kind::TK_END_OF_FILE) { |
| this->error(this->peek(), "expected '}', but found end of file"); |
| return nullptr; |
| } |
| if (std::unique_ptr<Statement> statement = this->statement()) { |
| statements.push_back(std::move(statement)); |
| } |
| if (fEncounteredFatalError) { |
| return nullptr; |
| } |
| } |
| } |
| return SkSL::Block::MakeBlock(this->rangeFrom(start), |
| std::move(statements), |
| Block::Kind::kBracedScope, |
| std::move(*symbolTableToUse)); |
| } |
| |
| /* expression SEMICOLON */ |
| std::unique_ptr<Statement> Parser::expressionStatement() { |
| std::unique_ptr<Expression> expr = this->expression(); |
| if (!expr) { |
| return nullptr; |
| } |
| if (!this->expect(Token::Kind::TK_SEMICOLON, "';'")) { |
| return nullptr; |
| } |
| Position pos = expr->position(); |
| return this->statementOrNop(pos, SkSL::ExpressionStatement::Convert(fCompiler.context(), |
| std::move(expr))); |
| } |
| |
| std::unique_ptr<Expression> Parser::poison(Position pos) { |
| return Poison::Make(pos, fCompiler.context()); |
| } |
| |
| std::unique_ptr<Expression> Parser::expressionOrPoison(Position pos, |
| std::unique_ptr<Expression> expr) { |
| if (!expr) { |
| // If no expression was passed in, create a poison expression. |
| expr = this->poison(pos); |
| } |
| // If a valid position was passed in, it must match the expression's position. |
| SkASSERTF(!pos.valid() || expr->position() == pos, |
| "expected expression position (%d-%d), but received (%d-%d)", |
| pos.startOffset(), |
| pos.endOffset(), |
| expr->position().startOffset(), |
| expr->position().endOffset()); |
| return expr; |
| } |
| |
| bool Parser::operatorRight(Parser::AutoDepth& depth, |
| Operator::Kind op, |
| BinaryParseFn rightFn, |
| std::unique_ptr<Expression>& expr) { |
| this->nextToken(); |
| if (!depth.increase()) { |
| return false; |
| } |
| std::unique_ptr<Expression> right = (this->*rightFn)(); |
| if (!right) { |
| return false; |
| } |
| Position pos = expr->position().rangeThrough(right->position()); |
| expr = this->expressionOrPoison(pos, BinaryExpression::Convert(fCompiler.context(), pos, |
| std::move(expr), op, |
| std::move(right))); |
| return true; |
| } |
| |
| /* assignmentExpression (COMMA assignmentExpression)* */ |
| std::unique_ptr<Expression> Parser::expression() { |
| AutoDepth depth(this); |
| [[maybe_unused]] Token start = this->peek(); |
| std::unique_ptr<Expression> result = this->assignmentExpression(); |
| if (!result) { |
| return nullptr; |
| } |
| while (this->peek().fKind == Token::Kind::TK_COMMA) { |
| if (!this->operatorRight(depth, Operator::Kind::COMMA, &Parser::assignmentExpression, |
| result)) { |
| return nullptr; |
| } |
| } |
| SkASSERTF(result->position().valid(), "Expression %s has invalid position", |
| result->description().c_str()); |
| SkASSERTF(result->position().startOffset() == this->position(start).startOffset(), |
| "Expected %s to start at %d (first token: '%.*s'), but it has range %d-%d\n", |
| result->description().c_str(), this->position(start).startOffset(), |
| (int)this->text(start).length(), this->text(start).data(), |
| result->position().startOffset(), result->position().endOffset()); |
| return result; |
| } |
| |
| /* ternaryExpression ((EQEQ | STAREQ | SLASHEQ | PERCENTEQ | PLUSEQ | MINUSEQ | SHLEQ | SHREQ | |
| BITWISEANDEQ | BITWISEXOREQ | BITWISEOREQ | LOGICALANDEQ | LOGICALXOREQ | LOGICALOREQ) |
| assignmentExpression)* |
| */ |
| std::unique_ptr<Expression> Parser::assignmentExpression() { |
| AutoDepth depth(this); |
| std::unique_ptr<Expression> result = this->ternaryExpression(); |
| if (!result) { |
| return nullptr; |
| } |
| for (;;) { |
| Operator::Kind op; |
| switch (this->peek().fKind) { |
| case Token::Kind::TK_EQ: op = Operator::Kind::EQ; break; |
| case Token::Kind::TK_STAREQ: op = Operator::Kind::STAREQ; break; |
| case Token::Kind::TK_SLASHEQ: op = Operator::Kind::SLASHEQ; break; |
| case Token::Kind::TK_PERCENTEQ: op = Operator::Kind::PERCENTEQ; break; |
| case Token::Kind::TK_PLUSEQ: op = Operator::Kind::PLUSEQ; break; |
| case Token::Kind::TK_MINUSEQ: op = Operator::Kind::MINUSEQ; break; |
| case Token::Kind::TK_SHLEQ: op = Operator::Kind::SHLEQ; break; |
| case Token::Kind::TK_SHREQ: op = Operator::Kind::SHREQ; break; |
| case Token::Kind::TK_BITWISEANDEQ: op = Operator::Kind::BITWISEANDEQ; break; |
| case Token::Kind::TK_BITWISEXOREQ: op = Operator::Kind::BITWISEXOREQ; break; |
| case Token::Kind::TK_BITWISEOREQ: op = Operator::Kind::BITWISEOREQ; break; |
| default: return result; |
| } |
| if (!this->operatorRight(depth, op, &Parser::assignmentExpression, result)) { |
| return nullptr; |
| } |
| } |
| } |
| |
| /* logicalOrExpression ('?' expression ':' assignmentExpression)? */ |
| std::unique_ptr<Expression> Parser::ternaryExpression() { |
| AutoDepth depth(this); |
| std::unique_ptr<Expression> base = this->logicalOrExpression(); |
| if (!base) { |
| return nullptr; |
| } |
| if (!this->checkNext(Token::Kind::TK_QUESTION)) { |
| return base; |
| } |
| if (!depth.increase()) { |
| return nullptr; |
| } |
| std::unique_ptr<Expression> trueExpr = this->expression(); |
| if (!trueExpr) { |
| return nullptr; |
| } |
| if (!this->expect(Token::Kind::TK_COLON, "':'")) { |
| return nullptr; |
| } |
| std::unique_ptr<Expression> falseExpr = this->assignmentExpression(); |
| if (!falseExpr) { |
| return nullptr; |
| } |
| Position pos = base->position().rangeThrough(falseExpr->position()); |
| return this->expressionOrPoison(pos, TernaryExpression::Convert(fCompiler.context(), |
| pos, std::move(base), |
| std::move(trueExpr), |
| std::move(falseExpr))); |
| } |
| |
| /* logicalXorExpression (LOGICALOR logicalXorExpression)* */ |
| std::unique_ptr<Expression> Parser::logicalOrExpression() { |
| AutoDepth depth(this); |
| std::unique_ptr<Expression> result = this->logicalXorExpression(); |
| if (!result) { |
| return nullptr; |
| } |
| while (this->peek().fKind == Token::Kind::TK_LOGICALOR) { |
| if (!this->operatorRight(depth, Operator::Kind::LOGICALOR, &Parser::logicalXorExpression, |
| result)) { |
| return nullptr; |
| } |
| } |
| return result; |
| } |
| |
| /* logicalAndExpression (LOGICALXOR logicalAndExpression)* */ |
| std::unique_ptr<Expression> Parser::logicalXorExpression() { |
| AutoDepth depth(this); |
| std::unique_ptr<Expression> result = this->logicalAndExpression(); |
| if (!result) { |
| return nullptr; |
| } |
| while (this->peek().fKind == Token::Kind::TK_LOGICALXOR) { |
| if (!this->operatorRight(depth, Operator::Kind::LOGICALXOR, &Parser::logicalAndExpression, |
| result)) { |
| return nullptr; |
| } |
| } |
| return result; |
| } |
| |
| /* bitwiseOrExpression (LOGICALAND bitwiseOrExpression)* */ |
| std::unique_ptr<Expression> Parser::logicalAndExpression() { |
| AutoDepth depth(this); |
| std::unique_ptr<Expression> result = this->bitwiseOrExpression(); |
| if (!result) { |
| return nullptr; |
| } |
| while (this->peek().fKind == Token::Kind::TK_LOGICALAND) { |
| if (!this->operatorRight(depth, Operator::Kind::LOGICALAND, &Parser::bitwiseOrExpression, |
| result)) { |
| return nullptr; |
| } |
| } |
| return result; |
| } |
| |
| /* bitwiseXorExpression (BITWISEOR bitwiseXorExpression)* */ |
| std::unique_ptr<Expression> Parser::bitwiseOrExpression() { |
| AutoDepth depth(this); |
| std::unique_ptr<Expression> result = this->bitwiseXorExpression(); |
| if (!result) { |
| return nullptr; |
| } |
| while (this->peek().fKind == Token::Kind::TK_BITWISEOR) { |
| if (!this->operatorRight(depth, Operator::Kind::BITWISEOR, &Parser::bitwiseXorExpression, |
| result)) { |
| return nullptr; |
| } |
| } |
| return result; |
| } |
| |
| /* bitwiseAndExpression (BITWISEXOR bitwiseAndExpression)* */ |
| std::unique_ptr<Expression> Parser::bitwiseXorExpression() { |
| AutoDepth depth(this); |
| std::unique_ptr<Expression> result = this->bitwiseAndExpression(); |
| if (!result) { |
| return nullptr; |
| } |
| while (this->peek().fKind == Token::Kind::TK_BITWISEXOR) { |
| if (!this->operatorRight(depth, Operator::Kind::BITWISEXOR, &Parser::bitwiseAndExpression, |
| result)) { |
| return nullptr; |
| } |
| } |
| return result; |
| } |
| |
| /* equalityExpression (BITWISEAND equalityExpression)* */ |
| std::unique_ptr<Expression> Parser::bitwiseAndExpression() { |
| AutoDepth depth(this); |
| std::unique_ptr<Expression> result = this->equalityExpression(); |
| if (!result) { |
| return nullptr; |
| } |
| while (this->peek().fKind == Token::Kind::TK_BITWISEAND) { |
| if (!this->operatorRight(depth, Operator::Kind::BITWISEAND, &Parser::equalityExpression, |
| result)) { |
| return nullptr; |
| } |
| } |
| return result; |
| } |
| |
| /* relationalExpression ((EQEQ | NEQ) relationalExpression)* */ |
| std::unique_ptr<Expression> Parser::equalityExpression() { |
| AutoDepth depth(this); |
| std::unique_ptr<Expression> result = this->relationalExpression(); |
| if (!result) { |
| return nullptr; |
| } |
| for (;;) { |
| Operator::Kind op; |
| switch (this->peek().fKind) { |
| case Token::Kind::TK_EQEQ: op = Operator::Kind::EQEQ; break; |
| case Token::Kind::TK_NEQ: op = Operator::Kind::NEQ; break; |
| default: return result; |
| } |
| if (!this->operatorRight(depth, op, &Parser::relationalExpression, result)) { |
| return nullptr; |
| } |
| } |
| } |
| |
| /* shiftExpression ((LT | GT | LTEQ | GTEQ) shiftExpression)* */ |
| std::unique_ptr<Expression> Parser::relationalExpression() { |
| AutoDepth depth(this); |
| std::unique_ptr<Expression> result = this->shiftExpression(); |
| if (!result) { |
| return nullptr; |
| } |
| for (;;) { |
| Operator::Kind op; |
| switch (this->peek().fKind) { |
| case Token::Kind::TK_LT: op = Operator::Kind::LT; break; |
| case Token::Kind::TK_GT: op = Operator::Kind::GT; break; |
| case Token::Kind::TK_LTEQ: op = Operator::Kind::LTEQ; break; |
| case Token::Kind::TK_GTEQ: op = Operator::Kind::GTEQ; break; |
| default: return result; |
| } |
| if (!this->operatorRight(depth, op, &Parser::shiftExpression, result)) { |
| return nullptr; |
| } |
| } |
| } |
| |
| /* additiveExpression ((SHL | SHR) additiveExpression)* */ |
| std::unique_ptr<Expression> Parser::shiftExpression() { |
| AutoDepth depth(this); |
| std::unique_ptr<Expression> result = this->additiveExpression(); |
| if (!result) { |
| return nullptr; |
| } |
| for (;;) { |
| Operator::Kind op; |
| switch (this->peek().fKind) { |
| case Token::Kind::TK_SHL: op = Operator::Kind::SHL; break; |
| case Token::Kind::TK_SHR: op = Operator::Kind::SHR; break; |
| default: return result; |
| } |
| if (!this->operatorRight(depth, op, &Parser::additiveExpression, result)) { |
| return nullptr; |
| } |
| } |
| } |
| |
| /* multiplicativeExpression ((PLUS | MINUS) multiplicativeExpression)* */ |
| std::unique_ptr<Expression> Parser::additiveExpression() { |
| AutoDepth depth(this); |
| std::unique_ptr<Expression> result = this->multiplicativeExpression(); |
| if (!result) { |
| return nullptr; |
| } |
| for (;;) { |
| Operator::Kind op; |
| switch (this->peek().fKind) { |
| case Token::Kind::TK_PLUS: op = Operator::Kind::PLUS; break; |
| case Token::Kind::TK_MINUS: op = Operator::Kind::MINUS; break; |
| default: return result; |
| } |
| if (!this->operatorRight(depth, op, &Parser::multiplicativeExpression, result)) { |
| return nullptr; |
| } |
| } |
| } |
| |
| /* unaryExpression ((STAR | SLASH | PERCENT) unaryExpression)* */ |
| std::unique_ptr<Expression> Parser::multiplicativeExpression() { |
| AutoDepth depth(this); |
| std::unique_ptr<Expression> result = this->unaryExpression(); |
| if (!result) { |
| return nullptr; |
| } |
| for (;;) { |
| Operator::Kind op; |
| switch (this->peek().fKind) { |
| case Token::Kind::TK_STAR: op = Operator::Kind::STAR; break; |
| case Token::Kind::TK_SLASH: op = Operator::Kind::SLASH; break; |
| case Token::Kind::TK_PERCENT: op = Operator::Kind::PERCENT; break; |
| default: return result; |
| } |
| if (!this->operatorRight(depth, op, &Parser::unaryExpression, result)) { |
| return nullptr; |
| } |
| } |
| } |
| |
| /* postfixExpression | (PLUS | MINUS | NOT | PLUSPLUS | MINUSMINUS) unaryExpression */ |
| std::unique_ptr<Expression> Parser::unaryExpression() { |
| AutoDepth depth(this); |
| Operator::Kind op; |
| Token start = this->peek(); |
| switch (start.fKind) { |
| case Token::Kind::TK_PLUS: op = Operator::Kind::PLUS; break; |
| case Token::Kind::TK_MINUS: op = Operator::Kind::MINUS; break; |
| case Token::Kind::TK_LOGICALNOT: op = Operator::Kind::LOGICALNOT; break; |
| case Token::Kind::TK_BITWISENOT: op = Operator::Kind::BITWISENOT; break; |
| case Token::Kind::TK_PLUSPLUS: op = Operator::Kind::PLUSPLUS; break; |
| case Token::Kind::TK_MINUSMINUS: op = Operator::Kind::MINUSMINUS; break; |
| default: return this->postfixExpression(); |
| } |
| this->nextToken(); |
| if (!depth.increase()) { |
| return nullptr; |
| } |
| std::unique_ptr<Expression> expr = this->unaryExpression(); |
| if (!expr) { |
| return nullptr; |
| } |
| Position pos = Position::Range(start.fOffset, expr->position().endOffset()); |
| return this->expressionOrPoison(pos, PrefixExpression::Convert(fCompiler.context(), |
| pos, op, std::move(expr))); |
| } |
| |
| /* term suffix* */ |
| std::unique_ptr<Expression> Parser::postfixExpression() { |
| AutoDepth depth(this); |
| std::unique_ptr<Expression> result = this->term(); |
| if (!result) { |
| return nullptr; |
| } |
| for (;;) { |
| Token t = this->peek(); |
| switch (t.fKind) { |
| case Token::Kind::TK_FLOAT_LITERAL: |
| if (this->text(t)[0] != '.') { |
| return result; |
| } |
| [[fallthrough]]; |
| case Token::Kind::TK_LBRACKET: |
| case Token::Kind::TK_DOT: |
| case Token::Kind::TK_LPAREN: |
| case Token::Kind::TK_PLUSPLUS: |
| case Token::Kind::TK_MINUSMINUS: { |
| if (!depth.increase()) { |
| return nullptr; |
| } |
| result = this->suffix(std::move(result)); |
| if (!result) { |
| return nullptr; |
| } |
| break; |
| } |
| default: |
| return result; |
| } |
| } |
| } |
| |
| std::unique_ptr<Expression> Parser::swizzle(Position pos, |
| std::unique_ptr<Expression> base, |
| std::string_view swizzleMask, |
| Position maskPos) { |
| SkASSERT(!swizzleMask.empty()); |
| if (!base->type().isVector() && !base->type().isScalar()) { |
| return this->expressionOrPoison(pos, FieldAccess::Convert(fCompiler.context(), pos, |
| std::move(base), swizzleMask)); |
| |
| } |
| return this->expressionOrPoison(pos, Swizzle::Convert(fCompiler.context(), pos, maskPos, |
| std::move(base), swizzleMask)); |
| } |
| |
| std::unique_ptr<Expression> Parser::call(Position pos, |
| std::unique_ptr<Expression> base, |
| ExpressionArray args) { |
| return this->expressionOrPoison(pos, SkSL::FunctionCall::Convert(fCompiler.context(), pos, |
| std::move(base), |
| std::move(args))); |
| } |
| |
| /* LBRACKET expression? RBRACKET | DOT IDENTIFIER | LPAREN arguments RPAREN | |
| PLUSPLUS | MINUSMINUS | COLONCOLON IDENTIFIER | FLOAT_LITERAL [IDENTIFIER] */ |
| std::unique_ptr<Expression> Parser::suffix(std::unique_ptr<Expression> base) { |
| AutoDepth depth(this); |
| Token next = this->nextToken(); |
| if (!depth.increase()) { |
| return nullptr; |
| } |
| switch (next.fKind) { |
| case Token::Kind::TK_LBRACKET: { |
| if (this->checkNext(Token::Kind::TK_RBRACKET)) { |
| this->error(this->rangeFrom(next), "missing index in '[]'"); |
| return this->poison(this->rangeFrom(base->position())); |
| } |
| std::unique_ptr<Expression> index = this->expression(); |
| if (!index) { |
| return nullptr; |
| } |
| this->expect(Token::Kind::TK_RBRACKET, "']' to complete array access expression"); |
| |
| Position pos = this->rangeFrom(base->position()); |
| return this->expressionOrPoison(pos, IndexExpression::Convert(fCompiler.context(), pos, |
| std::move(base), |
| std::move(index))); |
| } |
| case Token::Kind::TK_DOT: { |
| std::string_view text; |
| if (this->identifier(&text)) { |
| Position pos = this->rangeFrom(base->position()); |
| return this->swizzle(pos, std::move(base), text, |
| this->rangeFrom(this->position(next).after())); |
| } |
| [[fallthrough]]; |
| } |
| case Token::Kind::TK_FLOAT_LITERAL: { |
| // Swizzles that start with a constant number, e.g. '.000r', will be tokenized as |
| // floating point literals, possibly followed by an identifier. Handle that here. |
| std::string_view field = this->text(next); |
| SkASSERT(field[0] == '.'); |
| field.remove_prefix(1); |
| // use the next *raw* token so we don't ignore whitespace - we only care about |
| // identifiers that directly follow the float |
| Position pos = this->rangeFrom(base->position()); |
| Position start = this->position(next); |
| // skip past the "." |
| start = Position::Range(start.startOffset() + 1, start.endOffset()); |
| Position maskPos = this->rangeFrom(start); |
| Token id = this->nextRawToken(); |
| if (id.fKind == Token::Kind::TK_IDENTIFIER) { |
| pos = this->rangeFrom(base->position()); |
| maskPos = this->rangeFrom(start); |
| return this->swizzle(pos, |
| std::move(base), |
| std::string(field) + std::string(this->text(id)), |
| maskPos); |
| } |
| if (field.empty()) { |
| this->error(pos, "expected field name or swizzle mask after '.'"); |
| return this->poison(pos); |
| } |
| this->pushback(id); |
| return this->swizzle(pos, std::move(base), field, maskPos); |
| } |
| case Token::Kind::TK_LPAREN: { |
| ExpressionArray args; |
| if (this->peek().fKind != Token::Kind::TK_RPAREN) { |
| for (;;) { |
| std::unique_ptr<Expression> expr = this->assignmentExpression(); |
| if (!expr) { |
| return nullptr; |
| } |
| args.push_back(std::move(expr)); |
| if (!this->checkNext(Token::Kind::TK_COMMA)) { |
| break; |
| } |
| } |
| } |
| this->expect(Token::Kind::TK_RPAREN, "')' to complete function arguments"); |
| Position pos = this->rangeFrom(base->position()); |
| return this->call(pos, std::move(base), std::move(args)); |
| } |
| case Token::Kind::TK_PLUSPLUS: |
| case Token::Kind::TK_MINUSMINUS: { |
| Operator::Kind op = (next.fKind == Token::Kind::TK_PLUSPLUS) |
| ? Operator::Kind::PLUSPLUS |
| : Operator::Kind::MINUSMINUS; |
| Position pos = this->rangeFrom(base->position()); |
| return this->expressionOrPoison(pos, PostfixExpression::Convert(fCompiler.context(), |
| pos, std::move(base), |
| op)); |
| } |
| default: { |
| this->error(next, "expected expression suffix, but found '" + |
| std::string(this->text(next)) + "'"); |
| return nullptr; |
| } |
| } |
| } |
| |
| /* IDENTIFIER | intLiteral | floatLiteral | boolLiteral | '(' expression ')' */ |
| std::unique_ptr<Expression> Parser::term() { |
| AutoDepth depth(this); |
| Token t = this->peek(); |
| switch (t.fKind) { |
| case Token::Kind::TK_IDENTIFIER: { |
| std::string_view text; |
| if (this->identifier(&text)) { |
| Position pos = this->position(t); |
| return this->expressionOrPoison( |
| pos, |
| this->symbolTable()->instantiateSymbolRef(fCompiler.context(), text, pos)); |
| } |
| break; |
| } |
| case Token::Kind::TK_INT_LITERAL: { |
| SKSL_INT i; |
| if (!this->intLiteral(&i)) { |
| i = 0; |
| } |
| Position pos = this->position(t); |
| return this->expressionOrPoison(pos, SkSL::Literal::MakeInt(fCompiler.context(), |
| pos, i)); |
| } |
| case Token::Kind::TK_FLOAT_LITERAL: { |
| SKSL_FLOAT f; |
| if (!this->floatLiteral(&f)) { |
| f = 0.0f; |
| } |
| Position pos = this->position(t); |
| return this->expressionOrPoison(pos, SkSL::Literal::MakeFloat(fCompiler.context(), |
| pos, f)); |
| } |
| case Token::Kind::TK_TRUE_LITERAL: // fall through |
| case Token::Kind::TK_FALSE_LITERAL: { |
| bool b; |
| SkAssertResult(this->boolLiteral(&b)); |
| Position pos = this->position(t); |
| return this->expressionOrPoison(pos, SkSL::Literal::MakeBool(fCompiler.context(), |
| pos, b)); |
| } |
| case Token::Kind::TK_LPAREN: { |
| this->nextToken(); |
| if (!depth.increase()) { |
| return nullptr; |
| } |
| std::unique_ptr<Expression> result = this->expression(); |
| if (result != nullptr) { |
| this->expect(Token::Kind::TK_RPAREN, "')' to complete expression"); |
| result->setPosition(this->rangeFrom(this->position(t))); |
| return result; |
| } |
| break; |
| } |
| default: |
| this->nextToken(); |
| this->error(t, "expected expression, but found '" + std::string(this->text(t)) + "'"); |
| fEncounteredFatalError = true; |
| break; |
| } |
| return nullptr; |
| } |
| |
| /* INT_LITERAL */ |
| bool Parser::intLiteral(SKSL_INT* dest) { |
| Token t; |
| if (!this->expect(Token::Kind::TK_INT_LITERAL, "integer literal", &t)) { |
| return false; |
| } |
| std::string_view s = this->text(t); |
| if (!SkSL::stoi(s, dest)) { |
| this->error(t, "integer is too large: " + std::string(s)); |
| return false; |
| } |
| return true; |
| } |
| |
| /* FLOAT_LITERAL */ |
| bool Parser::floatLiteral(SKSL_FLOAT* dest) { |
| Token t; |
| if (!this->expect(Token::Kind::TK_FLOAT_LITERAL, "float literal", &t)) { |
| return false; |
| } |
| std::string_view s = this->text(t); |
| if (!SkSL::stod(s, dest)) { |
| this->error(t, "floating-point value is too large: " + std::string(s)); |
| return false; |
| } |
| return true; |
| } |
| |
| /* TRUE_LITERAL | FALSE_LITERAL */ |
| bool Parser::boolLiteral(bool* dest) { |
| Token t = this->nextToken(); |
| switch (t.fKind) { |
| case Token::Kind::TK_TRUE_LITERAL: |
| *dest = true; |
| return true; |
| case Token::Kind::TK_FALSE_LITERAL: |
| *dest = false; |
| return true; |
| default: |
| this->error(t, "expected 'true' or 'false', but found '" + |
| std::string(this->text(t)) + "'"); |
| return false; |
| } |
| } |
| |
| /* IDENTIFIER */ |
| bool Parser::identifier(std::string_view* dest) { |
| Token t; |
| if (this->expect(Token::Kind::TK_IDENTIFIER, "identifier", &t)) { |
| *dest = this->text(t); |
| return true; |
| } |
| return false; |
| } |
| |
| } // namespace SkSL |