/* * 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/SkSLModule.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 #include #include #include #include #include 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* newSymbolTable, bool enable = true) { if (enable) { fParser = p; SymbolTable*& ctxSymbols = this->contextSymbolTable(); *newSymbolTable = std::make_unique(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 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 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 Parser::programInheritingFrom(const SkSL::Module* module) { this->declarations(); std::unique_ptr result; if (fCompiler.errorReporter().errorCount() == 0) { result = fCompiler.releaseProgram(std::move(fText), std::move(fProgramElements)); } else { fProgramElements.clear(); } return result; } std::unique_ptr Parser::moduleInheritingFrom(const SkSL::Module* parentModule) { this->declarations(); this->symbolTable()->takeOwnershipOfString(std::move(*fText)); auto result = std::make_unique(); result->fParent = parentModule; result->fSymbols = std::move(fCompiler.fGlobalSymbols); result->fElements = std::move(fProgramElements); result->fModuleType = fCompiler.context().fConfig->fModuleType; 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 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 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> 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 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(decl->fPosition, decl)); return true; } bool Parser::defineFunction(SkSL::FunctionDeclaration* decl) { const Context& context = fCompiler.context(); Token bodyStart = this->peek(); std::unique_ptr symbolTable; std::unique_ptr 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 block = std::move(body); SkASSERT(block->is()); Position pos = this->rangeFrom(bodyStart); block->fPosition = pos; std::unique_ptr function = FunctionDefinition::Convert(context, pos, *decl, std::move(block)); 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 sizeLiteral = this->expression(); if (!sizeLiteral) { return false; } if (!sizeLiteral->is()) { 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* initializer) { if (this->checkNext(Token::Kind::TK_EQ)) { *initializer = this->assignmentExpression(); return *initializer != nullptr; } return true; } void Parser::addGlobalVarDeclaration(std::unique_ptr decl) { if (decl) { fProgramElements.push_back(std::make_unique(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 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 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 Parser::localVarDeclarationEnd(Position pos, const Modifiers& mods, const Type* baseType, Token name) { const Type* type = baseType; std::unique_ptr initializer; if (!this->parseArrayDimensions(pos, &type)) { return nullptr; } if (!this->parseInitializer(pos, &initializer)) { return nullptr; } std::unique_ptr 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 anotherInitializer; if (!this->parseInitializer(pos, &anotherInitializer)) { break; } std::unique_ptr 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 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 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 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 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* 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; static SkNoDestructor 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 Parser::statementOrNop(Position pos, std::unique_ptr 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 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()) { this->error(pos, "symbol '" + std::string(name) + "' is not a type"); return context.fTypes.fPoison.get(); } const SkSL::Type* type = &symbol->as(); if (!context.fConfig->isBuiltinCode()) { 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 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 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 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 test = this->expression(); if (!test) { return nullptr; } if (!this->expect(Token::Kind::TK_RPAREN, "')'")) { return nullptr; } std::unique_ptr ifTrue = this->statement(); if (!ifTrue) { return nullptr; } std::unique_ptr 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 Parser::doStatement() { Token start; if (!this->expect(Token::Kind::TK_DO, "'do'", &start)) { return nullptr; } std::unique_ptr 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 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 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 test = this->expression(); if (!test) { return nullptr; } if (!this->expect(Token::Kind::TK_RPAREN, "')'")) { return nullptr; } std::unique_ptr 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 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 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 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 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 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; 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 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; std::unique_ptr initializer; std::unique_ptr test; std::unique_ptr next; std::unique_ptr 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 Parser::returnStatement() { Token start; if (!this->expect(Token::Kind::TK_RETURN, "'return'", &start)) { return nullptr; } std::unique_ptr 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 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 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 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 Parser::block(bool introduceNewScope, std::unique_ptr* 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 newSymbolTable; std::unique_ptr* 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 = 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 Parser::expressionStatement() { std::unique_ptr 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 Parser::poison(Position pos) { return Poison::Make(pos, fCompiler.context()); } std::unique_ptr Parser::expressionOrPoison(Position pos, std::unique_ptr 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& expr) { this->nextToken(); if (!depth.increase()) { return false; } std::unique_ptr 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 Parser::expression() { AutoDepth depth(this); [[maybe_unused]] Token start = this->peek(); std::unique_ptr 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 Parser::assignmentExpression() { AutoDepth depth(this); std::unique_ptr 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 Parser::ternaryExpression() { AutoDepth depth(this); std::unique_ptr base = this->logicalOrExpression(); if (!base) { return nullptr; } if (!this->checkNext(Token::Kind::TK_QUESTION)) { return base; } if (!depth.increase()) { return nullptr; } std::unique_ptr trueExpr = this->expression(); if (!trueExpr) { return nullptr; } if (!this->expect(Token::Kind::TK_COLON, "':'")) { return nullptr; } std::unique_ptr 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 Parser::logicalOrExpression() { AutoDepth depth(this); std::unique_ptr 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 Parser::logicalXorExpression() { AutoDepth depth(this); std::unique_ptr 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 Parser::logicalAndExpression() { AutoDepth depth(this); std::unique_ptr 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 Parser::bitwiseOrExpression() { AutoDepth depth(this); std::unique_ptr 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 Parser::bitwiseXorExpression() { AutoDepth depth(this); std::unique_ptr 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 Parser::bitwiseAndExpression() { AutoDepth depth(this); std::unique_ptr 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 Parser::equalityExpression() { AutoDepth depth(this); std::unique_ptr 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 Parser::relationalExpression() { AutoDepth depth(this); std::unique_ptr 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 Parser::shiftExpression() { AutoDepth depth(this); std::unique_ptr 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 Parser::additiveExpression() { AutoDepth depth(this); std::unique_ptr 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 Parser::multiplicativeExpression() { AutoDepth depth(this); std::unique_ptr 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 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 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 Parser::postfixExpression() { AutoDepth depth(this); std::unique_ptr 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 Parser::swizzle(Position pos, std::unique_ptr 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 Parser::call(Position pos, std::unique_ptr 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 Parser::suffix(std::unique_ptr 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 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 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 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 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