From 8171a553c2523906240f5653cd1fa5c169dd89b9 Mon Sep 17 00:00:00 2001 From: Tim Foley Date: Thu, 24 Jan 2019 16:04:10 -0800 Subject: Support "modern" declaration syntax as an option (#792) * Support "modern" declaration syntax as an option Fixed #202 This change adds four new declaration keywords: The `let` and `var` keywords introduce immutable and mutable variables, respectively. They can only be used to declare a single variable at a time (unlike C declaration syntax), and they support inference of the variable's type from its initial-value expression. Examples: ``` let a : int = 1; // immutable with explicit type and initial-value expression let b = a + 1; // immutable, with type inferred var c : float; // mutable, with explicit type var d = b + c; // mutable, with type inferred ``` These declaration forms can be used wherever ordinary global, local, or member variable declarations appeared before. Right now they do not change rules about what is or is not considered a shader parameter. The `static` modifier should work on these forms as expected, but a `static let` variable is *not* the same as a `static const`, so an explicit `const` is still needed if you want that behavior. A `typealias` declaration introduces a named type alias, similar to `typedef`, but with more reasonable syntax. It inherits from the same AST class that `typedef` uses, so all of the code after parsing should be able to treat them as equivalent. To give a simple example: ``` // typedef int MyArray[3]; typealais MyArray = int[3]; ``` A `func` declaration introduces a function. Like `typealias` it re-uses the existing AST class, so there is no need for major changes after parsing. A `func` declaration uses a syntax similar to `let` variables for its parameters, and takes the (optional) result type in a trailing position. For example: ``` func myAdd(a: int, b: int) -> int { return a + b; } ``` If a `func` declaration leaves of the return type clause, the return type is assumed to be `void`. The main difference (beyond the trailing return type) is that the parameters of a `func`-declared function are immutable (unless they are `out`/`inout`). This change doesn't add support for declaring operator overloads with `func`, but that should be added later, and I'd like to make that the only way to declare such operations: ``` func +(left: MyType, right: MyType) -> MyType { ... } ``` The use of `:` for declaring parameter types here means that a function declared with modern syntax currently cannot include HLSL-style semantics on its parameters (or its result). We might consider introducing an `[attribute]`-based syntax for adding semantics to parameters if we think this is important, but for now it is fine to insist that users declare their entry points using traditional syntax. This change strives to avoid unecessary changes after parsing, but if the new syntax catches on with users there are some small ways we can take advantage of it for performance. In particular, since `let` declarations and parameters of modern-style functions are immutable, we do not need to generate read/write local temporaries for them during lowering to the IR (technically we can make the same optimization for `const` locals). In the process of implementing these new forms I also added a few subroutines to help share code better between existing cases in the parser. In particular, parsing of generic parameter lists on declarations that can be generic is now simplified and more unified. * Fixup: remove leftover debugging code * fixup: typos --- source/slang/parser.cpp | 297 +++++++++++++++++++++++++++++++----------------- 1 file changed, 190 insertions(+), 107 deletions(-) (limited to 'source/slang/parser.cpp') diff --git a/source/slang/parser.cpp b/source/slang/parser.cpp index a21d57b05..5cc048cf5 100644 --- a/source/slang/parser.cpp +++ b/source/slang/parser.cpp @@ -1163,6 +1163,7 @@ namespace Slang parser->ReadToken(TokenType::OpGreater); decl->inner = parseInnerFunc(decl); decl->inner->ParentDecl = decl; + // A generic decl hijacks the name of the declaration // it wraps, so that lookup can find it. if (decl->inner) @@ -1172,6 +1173,26 @@ namespace Slang } } + template + static RefPtr parseOptGenericDecl( + Parser* parser, const ParseFunc& parseInner) + { + // TODO: may want more advanced disambiguation than this... + if (parser->LookAheadToken(TokenType::OpLess)) + { + RefPtr genericDecl = new GenericDecl(); + parser->FillPosition(genericDecl); + parser->PushScope(genericDecl); + ParseGenericDeclImpl(parser, genericDecl, parseInner); + parser->PopScope(); + return genericDecl; + } + else + { + return parseInner(nullptr); + } + } + static RefPtr ParseGenericDecl(Parser* parser, void*) { RefPtr decl = new GenericDecl(); @@ -1244,79 +1265,59 @@ namespace Slang {} }; - static RefPtr ParseFuncDeclHeader( - Parser* parser, - DeclaratorInfo const& declaratorInfo, - RefPtr decl, - RefPtr genDecl) + /// Parse an optional body statement for a declaration that can have a body. + static RefPtr parseOptBody(Parser* parser) { - RefPtr retDecl = decl; - - parser->FillPosition(decl.Ptr()); - decl->loc = declaratorInfo.nameAndLoc.loc; - - decl->nameAndLoc = declaratorInfo.nameAndLoc; - - // if return type is a DeclRef type, we need to update its scope to use this function decl's scope - // so that LookUp can find the generic type parameters declared after the function name - ReplaceScopeVisitor replaceScopeVisitor; - replaceScopeVisitor.scope = parser->currentScope; - declaratorInfo.typeSpec->accept(&replaceScopeVisitor, nullptr); - - decl->ReturnType = TypeExp(declaratorInfo.typeSpec); - auto parseFuncDeclHeaderInner = [&](GenericDecl *) - { - parseParameterList(parser, decl); - ParseOptSemantics(parser, decl.Ptr()); - return decl; - }; - - if (parser->LookAheadToken(TokenType::OpLess)) + if (AdvanceIf(parser, TokenType::Semicolon)) { - // parse generic parameters - ParseGenericDeclImpl(parser, genDecl.Ptr(), parseFuncDeclHeaderInner); - retDecl = genDecl; + // empty body + return nullptr; } else - parseFuncDeclHeaderInner(nullptr); - - return retDecl; + { + return parser->parseBlockStatement(); + } } - static RefPtr ParseFuncDecl( + /// Complete parsing of a function using traditional (C-like) declarator syntax + static RefPtr parseTraditionalFuncDecl( Parser* parser, - ContainerDecl* /*containerDecl*/, - DeclaratorInfo const& declaratorInfo, - bool isGeneric) + DeclaratorInfo const& declaratorInfo) { RefPtr decl = new FuncDecl(); - RefPtr retDecl = decl; - RefPtr genDecl; - if (isGeneric) - { - genDecl = new GenericDecl(); - parser->FillPosition(genDecl); - parser->PushScope(genDecl); - retDecl = genDecl; - } - parser->PushScope(decl.Ptr()); - ParseFuncDeclHeader(parser, declaratorInfo, decl, genDecl); + parser->FillPosition(decl.Ptr()); + decl->loc = declaratorInfo.nameAndLoc.loc; + decl->nameAndLoc = declaratorInfo.nameAndLoc; - if (AdvanceIf(parser, TokenType::Semicolon)) + return parseOptGenericDecl(parser, [&](GenericDecl*) { - // empty body - } - else - { - decl->Body = parser->parseBlockStatement(); - } + // HACK: The return type of the function will already have been + // parsed in a scope that didn't include the function's generic + // parameters. + // + // We will use a visitor here to try and replace the scope associated + // with any name expressiosn in the reuslt type. + // + // TODO: This should be fixed by not associating scopes with + // such expressions at parse time, and instead pushing down scopes + // as part of the state during semantic checking. + // + ReplaceScopeVisitor replaceScopeVisitor; + replaceScopeVisitor.scope = parser->currentScope; + declaratorInfo.typeSpec->accept(&replaceScopeVisitor, nullptr); + + decl->ReturnType = TypeExp(declaratorInfo.typeSpec); + + parser->PushScope(decl); + + parseParameterList(parser, decl); + ParseOptSemantics(parser, decl.Ptr()); + decl->Body = parseOptBody(parser); - parser->PopScope(); - if (isGeneric) - { parser->PopScope(); - } - return retDecl; + + return decl; + }); } static RefPtr CreateVarDeclForContext( @@ -1971,7 +1972,7 @@ namespace Slang { // Looks like a function, so parse it like one. UnwrapDeclarator(initDeclarator, &declaratorInfo); - return ParseFuncDecl(parser, containerDecl, declaratorInfo, parser->tokenReader.PeekTokenType() == TokenType::OpLess); + return parseTraditionalFuncDecl(parser, declaratorInfo); } // Otherwise we are looking at a variable declaration, which could be one in a sequence... @@ -2593,14 +2594,8 @@ namespace Slang parseParameterList(parser, decl); - if( AdvanceIf(parser, TokenType::Semicolon) ) - { - // empty body - } - else - { - decl->Body = parser->parseBlockStatement(); - } + decl->Body = parseOptBody(parser); + return decl; } @@ -2675,9 +2670,124 @@ namespace Slang return decl; } - static Token expect(Parser* parser, TokenType tokenType) + static bool expect(Parser* parser, TokenType tokenType) { - return parser->ReadToken(tokenType); + return parser->ReadToken(tokenType).type == tokenType; + } + + static void parseModernVarDeclBaseCommon( + Parser* parser, + RefPtr decl) + { + parser->FillPosition(decl.Ptr()); + decl->nameAndLoc = NameLoc(parser->ReadToken(TokenType::Identifier)); + + if(AdvanceIf(parser, TokenType::Colon)) + { + decl->type = parser->ParseTypeExp(); + } + + if(AdvanceIf(parser, TokenType::OpAssign)) + { + decl->initExpr = parser->ParseInitExpr(); + } + } + + static void parseModernVarDeclCommon( + Parser* parser, + RefPtr decl) + { + parseModernVarDeclBaseCommon(parser, decl); + expect(parser, TokenType::Semicolon); + } + + static RefPtr parseLetDecl( + Parser* parser, void* /*userData*/) + { + RefPtr decl = new LetDecl(); + parseModernVarDeclCommon(parser, decl); + return decl; + } + + static RefPtr parseVarDecl( + Parser* parser, void* /*userData*/) + { + RefPtr decl = new VarDecl(); + parseModernVarDeclCommon(parser, decl); + return decl; + } + + static RefPtr parseModernParamDecl( + Parser* parser) + { + RefPtr decl = new ParamDecl(); + + // TODO: "modern" parameters should not accept keyword-based + // modifiers and should only accept `[attribute]` syntax for + // modifiers to keep the grammar as simple as possible. + // + // Further, they should accept `out` and `in out`/`inout` + // before the type (e.g., `a: inout float4`). + // + decl->modifiers = ParseModifiers(parser); + parseModernVarDeclBaseCommon(parser, decl); + return decl; + } + + static void parseModernParamList( + Parser* parser, + RefPtr decl) + { + parser->ReadToken(TokenType::LParent); + + while (!AdvanceIfMatch(parser, TokenType::RParent)) + { + AddMember(decl, parseModernParamDecl(parser)); + if (AdvanceIf(parser, TokenType::RParent)) + break; + parser->ReadToken(TokenType::Comma); + } + } + + static RefPtr parseFuncDecl( + Parser* parser, void* /*userData*/) + { + RefPtr decl = new FuncDecl(); + + parser->FillPosition(decl.Ptr()); + decl->nameAndLoc = NameLoc(parser->ReadToken(TokenType::Identifier)); + + return parseOptGenericDecl(parser, [&](GenericDecl*) + { + parser->PushScope(decl.Ptr()); + parseModernParamList(parser, decl); + if(AdvanceIf(parser, TokenType::RightArrow)) + { + decl->ReturnType = parser->ParseTypeExp(); + } + decl->Body = parseOptBody(parser); + parser->PopScope(); + return decl; + }); + } + + static RefPtr parseTypeAliasDecl( + Parser* parser, void* /*userData*/) + { + RefPtr decl = new TypeAliasDecl(); + + parser->FillPosition(decl.Ptr()); + decl->nameAndLoc = NameLoc(parser->ReadToken(TokenType::Identifier)); + + return parseOptGenericDecl(parser, [&](GenericDecl*) + { + if( expect(parser, TokenType::OpAssign) ) + { + decl->type = parser->ParseTypeExp(); + } + expect(parser, TokenType::Semicolon); + return decl; + }); } // This is a catch-all syntax-construction callback to handle cases where @@ -3084,36 +3194,20 @@ namespace Slang RefPtr Parser::ParseStruct() { RefPtr rs = new StructDecl(); - RefPtr retDecl = rs; FillPosition(rs.Ptr()); ReadToken("struct"); // TODO: support `struct` declaration without tag rs->nameAndLoc = expectIdentifier(this); - auto parseStructInner = [&](GenericDecl*) + return parseOptGenericDecl(this, [&](GenericDecl*) { // We allow for an inheritance clause on a `struct` // so that it can conform to interfaces. parseOptionalInheritanceClause(this, rs.Ptr()); parseAggTypeDeclBody(this, rs.Ptr()); return rs; - }; - - if (LookAheadToken(TokenType::OpLess)) - { - RefPtr genDecl = new GenericDecl(); - FillPosition(genDecl.Ptr()); - PushScope(genDecl); - ParseGenericDeclImpl(this, genDecl.Ptr(), parseStructInner); - PopScope(); - retDecl = genDecl; - } - else - { - parseStructInner(nullptr); - } - return retDecl; + }); } RefPtr Parser::ParseClass() @@ -3158,7 +3252,8 @@ namespace Slang decl->nameAndLoc = expectIdentifier(parser); - auto parseEnumDeclInner = [&](GenericDecl*) + + return parseOptGenericDecl(parser, [&](GenericDecl*) { parseOptionalInheritanceClause(parser, decl); parser->ReadToken(TokenType::LBrace); @@ -3174,23 +3269,7 @@ namespace Slang parser->ReadToken(TokenType::Comma); } return decl; - }; - - if (parser->LookAheadToken(TokenType::OpLess)) - { - RefPtr genericDecl = new GenericDecl(); - parser->FillPosition(genericDecl); - parser->PushScope(genericDecl); - ParseGenericDeclImpl(parser, genericDecl, parseEnumDeclInner); - parser->PopScope(); - return genericDecl; - } - else - { - parseEnumDeclInner(nullptr); - } - - return decl; + }); } static RefPtr ParseSwitchStmt(Parser* parser) @@ -4717,6 +4796,10 @@ namespace Slang DECL(attribute_syntax,parseAttributeSyntaxDecl); DECL(__import, parseImportDecl); DECL(import, parseImportDecl); + DECL(let, parseLetDecl); + DECL(var, parseVarDecl); + DECL(func, parseFuncDecl); + DECL(typealias, parseTypeAliasDecl); #undef DECL -- cgit v1.2.3