diff options
| author | Tim Foley <tfoleyNV@users.noreply.github.com> | 2018-06-12 14:59:13 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-06-12 14:59:13 -0700 |
| commit | 167d8579870db18756c234755b197e4ded930b0e (patch) | |
| tree | 942f07efe9572699a62df80cadb136a1789780a6 /source/slang/parser.cpp | |
| parent | 7852a2bf0ef5aad0f4f318507c300352c25199f2 (diff) | |
Initial support for enum declarations (#599)
Slang `enum` declarations will always be scoped, e.g.:
```hlsl
enum Color
{
Red,
Green = 2,
Blue,
}
Color c = Color.Red; // Not just `Red`
```
A user can write `enum class` as a placebo for now (to ease sharing of headers with C++).
Slang does not currently support the `::` operator for static member lookup, so it must be `Color.Green` and not `Color::Green`. Support for `::` as an alternate syntax could be added later if there is strong user demand.
An `enum` type can have a declared "tag type" using syntax like C++ `enum class`:
```hlsl
enum MyThings : uint
{
First = 0,
// ...
}
```
The `enum` cases will store their values using that type. An `enum` that doesn't declare a tag type will use the type `int` by default.
Enum cases are assigned values just like in C/C++: cases can have explicit values, but otherwise default to one more than the previous case, or zero for the first case.
All `enum` types will automatically conform to a standard-library `interface` called `__EnumType`, which is used so that basic operators like equality testing can be defined generically for all `enum` types.
This change only adds one operator at first (the `==` comparison), but other should be added later.
An `enum` case needs to be explicitly converted to an integer where needed (e.g., `int(Color.Red)`).
This is implemented by having the main integer types (`int` and `uint`) support built-in initializers that can work for *any* `enum` type (or rather, anything conforming to `__EnumType`).
Eventually these will be restricted so that an `enum` type can only be converted to its associated tag type.
IR code generation completely eliminates `enum` types and their cases.
The `enum` type will be replaced with its tag type, and the cases will be replaced with the tag values.
Currently this could leave some mess in the IR where cast operations are applied between values that actually have the same type.
Diffstat (limited to 'source/slang/parser.cpp')
| -rw-r--r-- | source/slang/parser.cpp | 158 |
1 files changed, 145 insertions, 13 deletions
diff --git a/source/slang/parser.cpp b/source/slang/parser.cpp index ad4eef9a6..fee5d04b6 100644 --- a/source/slang/parser.cpp +++ b/source/slang/parser.cpp @@ -127,6 +127,8 @@ namespace Slang ContainerDecl* containerDecl, TokenType closingToken); + static RefPtr<Decl> parseEnumDecl(Parser* parser); + // Parse the `{}`-delimeted body of an aggregate type declaration static void parseAggTypeDeclBody( Parser* parser, @@ -215,7 +217,7 @@ namespace Slang return tokenType; } - // Skip balanced + // Skip balanced static TokenType SkipToMatchingToken( TokenReader* reader, TokenType tokenType) @@ -403,7 +405,7 @@ namespace Slang // Skip balanced tokens and try again. TokenType skipped = SkipBalancedToken(tokenReader); - + // If we happened to find a matched pair of tokens, and // the end of it was a token we were looking for, // then recover here @@ -1097,7 +1099,7 @@ namespace Slang { public: RefPtr<Scope> scope; - void visitDeclRefExpr(DeclRefExpr* expr) + void visitDeclRefExpr(DeclRefExpr* expr) { expr->scope = scope; } @@ -1161,7 +1163,7 @@ namespace Slang } else parseFuncDeclHeaderInner(nullptr); - + return retDecl; } @@ -1471,7 +1473,7 @@ namespace Slang RefPtr<Decl> newDecl) { SLANG_ASSERT(newDecl); - + if( decl ) { group = new DeclGroup(); @@ -1655,11 +1657,24 @@ namespace Slang { TypeSpec typeSpec; - // We may see a `struct` type specified here, and need to act accordingly + // We may see a `struct` (or `enum` or `class`) tag specified here, and need to act accordingly // // TODO(tfoley): Handle the case where the user is just using `struct` // as a way to name an existing struct "tag" (e.g., `struct Foo foo;`) // + // TODO: We should really make these keywords be registered like any other + // syntax category, rather than be special-cased here. The main issue here + // is that we need to allow them to be used as type specififers, as in: + // + // struct Foo { int x } foo; + // + // The ideal answer would be to register certain keywords as being able + // to parse a type specififer, and look for those keywords here. + // We should ideally add special case logic that bails out of declarator + // parsing iff we have one of these kinds of type specififers and the + // closing `}` is at the end of its line, as a bit of a special case + // to allow the common idiom. + // if( parser->LookAheadToken("struct") ) { auto decl = parser->ParseStruct(); @@ -1674,6 +1689,13 @@ namespace Slang typeSpec.expr = createDeclRefType(parser, decl); return typeSpec; } + else if(parser->LookAheadToken("enum")) + { + auto decl = parseEnumDecl(parser); + typeSpec.decl = decl; + typeSpec.expr = createDeclRefType(parser, decl); + return typeSpec; + } Token typeName = parser->ReadToken(TokenType::Identifier); @@ -1742,6 +1764,51 @@ namespace Slang return result; } + // It is possible that we have a plain `struct`, `enum`, + // or similar declaration that isn't being used to declare + // any variable, and the user didn't put a trailing + // semicolon on it: + // + // struct Batman + // { + // int cape; + // } + // + // We want to allow this syntax (rather than give an + // inscrutable error), but also support the less common + // idiom where that declaration is used as part of + // a variable declaration: + // + // struct Robin + // { + // float tights; + // } boyWonder; + // + // As a bit of a hack (insofar as it means we aren't + // *really* compatible with arbitrary HLSL code), we + // will check if there are any more tokens on the + // same line as the closing `}`, and if not, we + // will treat it like the end of the declaration. + // + // Just as a safety net, only apply this logic for + // a file that is being passed in as "true" Slang code. + // + if(parser->translationUnit->sourceLanguage == SourceLanguage::Slang) + { + if(typeSpec.decl) + { + if(peekToken(parser).flags & TokenFlag::AtStartOfLine) + { + // The token after the `}` is at the start of its + // own line, which means it can't be on the same line. + // + // This means the programmer probably wants to + // just treat this as a declaration. + return declGroupBuilder.getResult(); + } + } + } + InitDeclarator initDeclarator = ParseInitDeclarator(parser); @@ -1978,7 +2045,7 @@ namespace Slang // // However, that is an uncommon occurence, and trying // to continue parsing semantics here even if we didn't - // see a colon forces us to be careful about + // see a colon forces us to be careful about // avoiding an infinite loop here. if (!AdvanceIf(parser, TokenType::Colon)) { @@ -2921,6 +2988,71 @@ namespace Slang return rs; } + static RefPtr<EnumCaseDecl> parseEnumCaseDecl(Parser* parser) + { + RefPtr<EnumCaseDecl> decl = new EnumCaseDecl(); + decl->nameAndLoc = expectIdentifier(parser); + + if(AdvanceIf(parser, TokenType::OpAssign)) + { + decl->initExpr = parser->ParseArgExpr(); + } + + return decl; + } + + static RefPtr<Decl> parseEnumDecl(Parser* parser) + { + RefPtr<EnumDecl> decl = new EnumDecl(); + parser->FillPosition(decl); + + parser->ReadToken("enum"); + + // HACK: allow the user to write `enum class` in case + // they are trying to share a header between C++ and Slang. + // + // TODO: diagnose this with a warning some day, and move + // toward deprecating it. + // + AdvanceIf(parser, "class"); + + decl->nameAndLoc = expectIdentifier(parser); + + auto parseEnumDeclInner = [&](GenericDecl*) + { + parseOptionalInheritanceClause(parser, decl); + parser->ReadToken(TokenType::LBrace); + + while(!AdvanceIfMatch(parser, TokenType::RBrace)) + { + RefPtr<EnumCaseDecl> caseDecl = parseEnumCaseDecl(parser); + AddMember(decl, caseDecl); + + if(AdvanceIf(parser, TokenType::RBrace)) + break; + + parser->ReadToken(TokenType::Comma); + } + return decl; + }; + + if (parser->LookAheadToken(TokenType::OpLess)) + { + RefPtr<GenericDecl> genericDecl = new GenericDecl(); + parser->FillPosition(genericDecl); + parser->PushScope(genericDecl); + ParseGenericDeclImpl(parser, genericDecl, parseEnumDeclInner); + parser->PopScope(); + return genericDecl; + } + else + { + parseEnumDeclInner(nullptr); + } + + return decl; + } + static RefPtr<Stmt> ParseSwitchStmt(Parser* parser) { RefPtr<SwitchStmt> stmt = new SwitchStmt(); @@ -3205,7 +3337,7 @@ namespace Slang Modifiers modifiers) { RefPtr<DeclStmt>varDeclrStatement = new DeclStmt(); - + FillPosition(varDeclrStatement.Ptr()); auto decl = ParseDeclWithModifiers(this, currentScope->containerDecl, modifiers); varDeclrStatement->decl = decl; @@ -3348,10 +3480,10 @@ namespace Slang RefPtr<ExpressionStmt> Parser::ParseExpressionStatement() { RefPtr<ExpressionStmt> statement = new ExpressionStmt(); - + FillPosition(statement.Ptr()); statement->Expression = ParseExpression(); - + ReadToken(TokenType::Semicolon); return statement; } @@ -3544,7 +3676,7 @@ namespace Slang for(;;) { auto nextOpPrec = GetOpLevel(parser, parser->tokenReader.PeekTokenType()); - + if((GetAssociativityFromLevel(nextOpPrec) == Associativity::Right) ? (nextOpPrec < opPrec) : (nextOpPrec <= opPrec)) break; @@ -3632,7 +3764,7 @@ namespace Slang } #endif } - + // We *might* be looking at an application of a generic to arguments, // but we need to disambiguate to make sure. static RefPtr<Expr> maybeParseGenericApp( @@ -4021,7 +4153,7 @@ namespace Slang parser->FillPosition(memberExpr.Ptr()); memberExpr->BaseExpression = expr; - parser->ReadToken(TokenType::Dot); + parser->ReadToken(TokenType::Dot); memberExpr->name = expectIdentifier(parser).name; if (peekTokenType(parser) == TokenType::OpLess) |
