diff options
| author | Tim Foley <tfoleyNV@users.noreply.github.com> | 2020-08-13 10:36:55 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-08-13 10:36:55 -0700 |
| commit | 09adf10f646f01e177d412ba2d86602a51579b4f (patch) | |
| tree | 496e1d155ca49d1b673047cda5c24b797ca07743 | |
| parent | e1ea7ed2f9b8cfcf19258a05d270de4db03fec22 (diff) | |
Allow both traditional and modern property syntax (#1487)
The initial change to introduce `property` declarations tied them to a "modern" syntax:
property width : float { ... }
In practice, a great majority of users assume that properties in Slang will be declared like those in C#:
property float height { ... }
This change allows both options to parse correctly.
The choice made here is to only parse as the "modern" syntax when it can be detected from lookahead (an identifier followed by a `:`), and fall back to the "traditional" syntax otherwise. That choice might not produce the best diagnostic messages around syntax errors in codebases that use the modern syntax, but it is the easiest trade-offs to make.
We also add similar disambiguation logic for the `newValue` parameter of a `set` declaration (and other "modern"-style parameters). This strategy cannot be applied to all function parameters in general, because traditional parameter lists can still use `:` to introduce a semantic.
Note: the same disambiguation strategy applied here could be used for `let` and `var` declarations:
let a : int = 1;
let int b = 2;
This change does not try to introduce flexibility like that, because it seems unlikely for users to care.
| -rw-r--r-- | source/slang/slang-parser.cpp | 134 | ||||
| -rw-r--r-- | tests/language-feature/properties/property-syntax.slang | 30 |
2 files changed, 144 insertions, 20 deletions
diff --git a/source/slang/slang-parser.cpp b/source/slang/slang-parser.cpp index 179587550..f2bc2eb9a 100644 --- a/source/slang/slang-parser.cpp +++ b/source/slang/slang-parser.cpp @@ -773,6 +773,15 @@ namespace Slang return parser->tokenReader.peekTokenType(); } + /// Peek the token `offset` tokens after the cursor + static TokenType peekTokenType(Parser* parser, int offset) + { + TokenReader r = parser->tokenReader; + for (int ii = 0; ii < offset; ++ii) + r.advanceToken(); + return r.peekTokenType(); + } + static Token advanceToken(Parser* parser) { return parser->ReadToken(); @@ -2746,18 +2755,80 @@ namespace Slang return parser->ReadToken(tokenType).type == tokenType; } + /// Peek in the token stream and return `true` if it looks like a modern-style variable declaration is coming up. + static bool _peekModernStyleVarDecl(Parser* parser) + { + // A modern-style variable declaration always starts with an identifier + if(peekTokenType(parser) != TokenType::Identifier) + return false; + + switch(peekTokenType(parser, 1)) + { + default: + return false; + + case TokenType::Colon: + case TokenType::Comma: + case TokenType::RParent: + case TokenType::RBrace: + case TokenType::RBracket: + case TokenType::LBrace: + return true; + } + } + static NodeBase* ParsePropertyDecl(Parser* parser, void* /*userData*/) { PropertyDecl* decl = parser->astBuilder->create<PropertyDecl>(); parser->FillPosition(decl); parser->PushScope(decl); - decl->nameAndLoc = expectIdentifier(parser); - - if( expect(parser, TokenType::Colon) ) + // We want to support property declarations with two + // different syntaxes. + // + // First, we want to support a syntax that is consistent + // with C-style ("traditional") variable declarations: + // + // int myVar = 2; + // proprerty int myProp { ... } + // + // Second we want to support a syntax that is + // consistent with `let` and `var` declarations: + // + // let myVar : int = 2; + // property myProp : int { ... } + // + // The latter case is more constrained, and we will + // detect with two tokens of lookahead. If the + // next token (after `property`) is an identifier, + // and the token after that is a colon (`:`), then + // we assume we are in the `let`/`var`-style case. + // + if(_peekModernStyleVarDecl(parser)) { + decl->nameAndLoc = expectIdentifier(parser); + expect(parser, TokenType::Colon); decl->type = parser->ParseTypeExp(); } + else + { + // The traditional syntax requires a bit more + // care to parse, since it needs to support + // C declarator syntax. + // + DeclaratorInfo declaratorInfo; + declaratorInfo.typeSpec = parser->ParseType(); + + auto declarator = parseDeclarator(parser, kDeclaratorParseOptions_None); + UnwrapDeclarator(parser->astBuilder, declarator, &declaratorInfo); + + // TODO: We might want to handle the case where the + // resulting declarator is not valid to use for + // declaring a property (e.g., it has function parameters). + + decl->nameAndLoc = declaratorInfo.nameAndLoc; + decl->type = TypeExp(declaratorInfo.typeSpec); + } parseStorageDeclBody(parser, decl); @@ -2807,21 +2878,50 @@ namespace Slang return decl; } + /// Parse the common structured of a traditional-style parameter declaration (excluding the trailing semicolon) + static void _parseTraditionalParamDeclCommonBase(Parser* parser, VarDeclBase* decl, DeclaratorParseOptions options = kDeclaratorParseOptions_None) + { + DeclaratorInfo declaratorInfo; + declaratorInfo.typeSpec = parser->ParseType(); + + InitDeclarator initDeclarator = parseInitDeclarator(parser, options); + UnwrapDeclarator(parser->astBuilder, initDeclarator, &declaratorInfo); + + // Assume it is a variable-like declarator + CompleteVarDecl(parser, decl, declaratorInfo); + } + static ParamDecl* parseModernParamDecl( Parser* parser) { - ParamDecl* decl = parser->astBuilder->create<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. + // TODO: For "modern" parameters, we should probably + // not allow arbitrary keyword-based modifiers (only allowing + // `[attribute]`s), and should require that direction modifiers + // like `in`, `out`, and `in out`/`inout` be applied to the + // type (after the colon). // - // Further, they should accept `out` and `in out`/`inout` - // before the type (e.g., `a: inout float4`). + auto modifiers = ParseModifiers(parser); + + // We want to allow both "modern"-style and traditional-style + // parameters to appear in any modern-style parameter list, + // in order to allow programmers the flexibility to code in + // a way that feels natural and not run into lots of + // errors. // - decl->modifiers = ParseModifiers(parser); - parseModernVarDeclBaseCommon(parser, decl); - return decl; + if(_peekModernStyleVarDecl(parser)) + { + ParamDecl* decl = parser->astBuilder->create<ModernParamDecl>(); + decl->modifiers = modifiers; + parseModernVarDeclBaseCommon(parser, decl); + return decl; + } + else + { + ParamDecl* decl = parser->astBuilder->create<ParamDecl>(); + decl->modifiers = modifiers; + _parseTraditionalParamDeclCommonBase(parser, decl); + return decl; + } } static void parseModernParamList( @@ -3924,14 +4024,8 @@ namespace Slang ParamDecl* parameter = astBuilder->create<ParamDecl>(); parameter->modifiers = ParseModifiers(this); - DeclaratorInfo declaratorInfo; - declaratorInfo.typeSpec = ParseType(); - - InitDeclarator initDeclarator = parseInitDeclarator(this, kDeclaratorParseOption_AllowEmpty); - UnwrapDeclarator(astBuilder, initDeclarator, &declaratorInfo); + _parseTraditionalParamDeclCommonBase(this, parameter, kDeclaratorParseOption_AllowEmpty); - // Assume it is a variable-like declarator - CompleteVarDecl(this, parameter, declaratorInfo); return parameter; } diff --git a/tests/language-feature/properties/property-syntax.slang b/tests/language-feature/properties/property-syntax.slang new file mode 100644 index 000000000..41a2513d3 --- /dev/null +++ b/tests/language-feature/properties/property-syntax.slang @@ -0,0 +1,30 @@ +// property-syntax.slang + +// Confirm that property syntax is parsed correctly. + +//TEST:SIMPLE: + +struct Data +{ + int _a; + int _b; + + // Traditional syntax + property int a + { + get { return _a; } + set(int newValue) { _a = newValue; } + } + + // "Modern" syntax + property b : int + { + get { return _b; } + set(newValue : int) { _a = newValue; } + } +} + +int test(Data d) +{ + return d.a + d.b; +} |
