From 66f01776a343d147fb0823b5eea83397e5548173 Mon Sep 17 00:00:00 2001 From: Joshua Viszlai Date: Wed, 24 Jul 2019 16:36:55 -0700 Subject: [PATCH] Parsing for attributes with error handling and context verification --- src/QsCompiler/DataStructures/Diagnostics.fs | 10 ++++- src/QsCompiler/DataStructures/SyntaxTokens.fs | 5 ++- .../SyntaxProcessor/ContextVerification.fs | 8 ++++ .../SyntaxProcessor/SyntaxExtensions.fs | 3 +- .../TextProcessor/QsExpressionParsing.fs | 7 ++++ .../TextProcessor/QsFragmentParsing.fs | 6 ++- src/QsCompiler/TextProcessor/SyntaxBuilder.fs | 39 +++++++++++++++++++ 7 files changed, 74 insertions(+), 4 deletions(-) diff --git a/src/QsCompiler/DataStructures/Diagnostics.fs b/src/QsCompiler/DataStructures/Diagnostics.fs index 4d7b3ab77d..dc7e744055 100644 --- a/src/QsCompiler/DataStructures/Diagnostics.fs +++ b/src/QsCompiler/DataStructures/Diagnostics.fs @@ -66,6 +66,7 @@ type ErrorCode = | InvalidOperationCharacteristics = 3115 | MissingOperationCharacteristics = 3116 | ExpectingUpdateExpression = 3117 + | InvalidAttribute = 3118 | InvalidIdentifierDeclaration = 3201 | MissingIdentifierDeclaration = 3202 @@ -103,6 +104,8 @@ type ErrorCode = | MissingUdtItemDeclaration = 3234 | InvalidUdtItemNameDeclaration = 3235 | MissingUdtItemNameDeclaration = 3236 + | MissingClosingAttributeSign = 3237 + | MissingAttributeArgs = 3238 | EmptyValueArray = 3300 | InvalidValueArray = 3301 @@ -134,6 +137,7 @@ type ErrorCode = | InvertControlledGenerator = 4110 | ControlledGenArgMismatch = 4111 | ControlledAdjointGenArgMismatch = 4112 + | MissingFollowingDeclaration = 4113 | MissingExprInArray = 5001 | MultipleTypesInArray = 5002 @@ -356,6 +360,7 @@ type DiagnosticItem = | ErrorCode.ExcessContinuation -> "Unexpected code fragment." | ErrorCode.NonCallExprAsStatement -> "An expression used as a statement must be a call expression." + | ErrorCode.InvalidAttribute -> "Syntax error in attribute." | ErrorCode.InvalidExpression -> "Syntax error in expression." | ErrorCode.MissingExpression -> "Expecting expression." | ErrorCode.InvalidIdentifierName -> "Identifiers need to start with an ASCII letter or an underscore, and need to contain at least one non-underscore character." @@ -441,7 +446,10 @@ type DiagnosticItem = | ErrorCode.InvertControlledGenerator -> "Invalid generator for controlled specialization. Valid generators are \"distributed\" and \"auto\"." | ErrorCode.ControlledGenArgMismatch -> "The argument to a user-defined controlled specialization must must be of the form \"(ctlQsName, ...)\"." | ErrorCode.ControlledAdjointGenArgMismatch -> "The argument to a user-defined controlled-adjoint specialization must must be of the form \"(ctlQsName, ...)\"." - + | ErrorCode.MissingFollowingDeclaration -> "The attribute must be above another attribute, a type definition, an operator declaration, or a function declaration." + | ErrorCode.MissingClosingAttributeSign -> "Missing closing sign for attribute, did you mean to type @ ?" + | ErrorCode.MissingAttributeArgs -> "Missing arguments for specified attribute." + | ErrorCode.MissingExprInArray -> "Underscores cannot be used to denote missing array elements." | ErrorCode.MultipleTypesInArray -> "Array items must have a common base type." | ErrorCode.InvalidArrayItemIndex -> "Expecting an expression of type Int or Range. Got an expression of type {0}." diff --git a/src/QsCompiler/DataStructures/SyntaxTokens.fs b/src/QsCompiler/DataStructures/SyntaxTokens.fs index e0e3cff25a..c530766389 100644 --- a/src/QsCompiler/DataStructures/SyntaxTokens.fs +++ b/src/QsCompiler/DataStructures/SyntaxTokens.fs @@ -212,6 +212,7 @@ type QsFragmentKind = | AdjointDeclaration of QsSpecializationGenerator | ControlledDeclaration of QsSpecializationGenerator | ControlledAdjointDeclaration of QsSpecializationGenerator +| AttributeDeclaration of QsExpression * QsExpression | OperationDeclaration of QsSymbol * CallableSignature | FunctionDeclaration of QsSymbol * CallableSignature | TypeDefinition of QsSymbol * QsTuple @@ -223,7 +224,8 @@ with /// returns the error code for an invalid fragment of the given kind member this.ErrorCode = match this with - | ExpressionStatement _ -> ErrorCode.InvalidExpressionStatement + | ExpressionStatement _ -> ErrorCode.InvalidExpressionStatement + | AttributeDeclaration _ -> ErrorCode.InvalidAttribute | ReturnStatement _ -> ErrorCode.InvalidReturnStatement | FailStatement _ -> ErrorCode.InvalidFailStatement | ImmutableBinding _ -> ErrorCode.InvalidImmutableBinding @@ -252,6 +254,7 @@ with /// returns the error code for an invalid fragment ending on the given kind member this.InvalidEnding = match this with + | AttributeDeclaration _ -> ErrorCode.UnexpectedFragmentDelimiter | ExpressionStatement _ | ReturnStatement _ | FailStatement _ diff --git a/src/QsCompiler/SyntaxProcessor/ContextVerification.fs b/src/QsCompiler/SyntaxProcessor/ContextVerification.fs index 6304792bcd..e0bc2309d4 100644 --- a/src/QsCompiler/SyntaxProcessor/ContextVerification.fs +++ b/src/QsCompiler/SyntaxProcessor/ContextVerification.fs @@ -43,6 +43,13 @@ let private verifyDeclaration (context : SyntaxTokenContext) = | _ -> errMsg context.Parents |> Array.toList |> isNamespace +/// Used by attributes to check if it is followed by another attribute, a function, an operation, or type definition/declaration. +let private followedByNamespaceDeclaration (context : SyntaxTokenContext) = + match context.Next with + | Value (FunctionDeclaration _) | Value (OperationDeclaration _) | Value (TypeDefinition _) | Value (AttributeDeclaration _) -> verifyDeclaration context + | Value InvalidFragment -> false, [||] + | _ -> false, [| (ErrorCode.MissingFollowingDeclaration |> Error, context.Range) |] + /// Verifies that the given generator is a valid generator for the callable body - /// i.e. verifies that the generator is either a user defined implementation, or intrinsic. /// Does *not* verify whether the symbol tuple for a user defined implementation is correct. @@ -212,6 +219,7 @@ let VerifySyntaxTokenContext = | FunctionDeclaration _ -> verifyDeclaration context | TypeDefinition _ -> verifyDeclaration context | OpenDirective _ -> verifyOpenDirective context + | AttributeDeclaration _ -> followedByNamespaceDeclaration context | NamespaceDeclaration _ -> verifyNamespace context | InvalidFragment _ -> false, [||] // excluded from the compilation |> fun (kind, tuple) -> kind, tuple |> Array.map (fun (x,y) -> QsCompilerDiagnostic.New (x, []) y) diff --git a/src/QsCompiler/SyntaxProcessor/SyntaxExtensions.fs b/src/QsCompiler/SyntaxProcessor/SyntaxExtensions.fs index 087fac92b2..8590cc00a7 100644 --- a/src/QsCompiler/SyntaxProcessor/SyntaxExtensions.fs +++ b/src/QsCompiler/SyntaxProcessor/SyntaxExtensions.fs @@ -147,7 +147,8 @@ type SymbolInformation = { [] let public SymbolInformation fragmentKind = let chooseValues = QsNullable<_>.Choose id >> Seq.toArray - fragmentKind |> function + fragmentKind |> function + | QsFragmentKind.AttributeDeclaration _ -> [||], ([||], [||], [||]) | QsFragmentKind.ExpressionStatement ex -> [||], ([ex] , []) |> collectWith SymbolsFromExpr | QsFragmentKind.ReturnStatement ex -> [||], ([ex] , []) |> collectWith SymbolsFromExpr | QsFragmentKind.FailStatement ex -> [||], ([ex] , []) |> collectWith SymbolsFromExpr diff --git a/src/QsCompiler/TextProcessor/QsExpressionParsing.fs b/src/QsCompiler/TextProcessor/QsExpressionParsing.fs index 0971a061a4..668d338824 100644 --- a/src/QsCompiler/TextProcessor/QsExpressionParsing.fs +++ b/src/QsCompiler/TextProcessor/QsExpressionParsing.fs @@ -354,6 +354,13 @@ let internal callLikeExpr = attempt ((itemAccessExpr <|> identifier <|> tupledItem expr) .>>. argumentTuple) // identifier needs to come *after* arrayItemExpr |>> fun (callable, arg) -> applyBinary CallLikeExpression () callable arg +/// Parses a Q# attribute identifier (separated from arguments to better handle errors) +let internal attributeId = + attempt identifier + +/// Parses Q# attribute arguments +let internal attributeArgs = + attempt argumentTuple // processing terms of operator precedence parsers diff --git a/src/QsCompiler/TextProcessor/QsFragmentParsing.fs b/src/QsCompiler/TextProcessor/QsFragmentParsing.fs index 6f240a61d4..63beec166f 100644 --- a/src/QsCompiler/TextProcessor/QsFragmentParsing.fs +++ b/src/QsCompiler/TextProcessor/QsFragmentParsing.fs @@ -238,6 +238,9 @@ and private namespaceDeclaration = let invalid = NamespaceDeclaration invalidSymbol buildFragment namespaceDeclHeader.parse (expectedNamespaceName eof) invalid NamespaceDeclaration +/// Uses buildAttribute to parse a Q# AttributeDeclaration as a QsFragment. +and private attributeDeclaration = + buildAttribute attributeId attributeArgs AttributeDeclaration // operation and function parsing @@ -442,7 +445,8 @@ let private expressionStatement = let internal codeFragment = let validFragment = choice (getFragments() |> List.map snd) - <|> expressionStatement // the expressionStatement needs to be last + <|> attributeDeclaration + <|> expressionStatement// the expressionStatement needs to be last let invalidFragment = let valid = fun _ -> InvalidFragment buildFragment (preturn ()) (fail "invalid syntax") InvalidFragment valid diff --git a/src/QsCompiler/TextProcessor/SyntaxBuilder.fs b/src/QsCompiler/TextProcessor/SyntaxBuilder.fs index 53fd22524f..0bd425538e 100644 --- a/src/QsCompiler/TextProcessor/SyntaxBuilder.fs +++ b/src/QsCompiler/TextProcessor/SyntaxBuilder.fs @@ -458,5 +458,44 @@ let internal buildFragment header body (invalid : QsFragmentKind) (fragmentKind header >>. (attempt (validBody state) <|> invalidBody state) >>= build +/// Construts a QsFragment for a Q# attribute. +/// Adds error handling capability to suggest a missing end symbol and missing arguments. +let internal buildAttribute id args (fragmentKind : 'a * QsExpression -> QsFragmentKind) = + let build (kind, (startPos, (text, endPos))) = + getUserState .>> clearDiagnostics + |>> fun diagnostics -> + QsFragment.New(kind, (startPos, endPos), (filterAndAdapt diagnostics (endPos |> QsPositionInfo.New)).ToImmutableArray(), NonNullable.New text) + + let delimiters state = + let fragmentEnd = + let allWS = emptySpace .>>? eof + manyCharsTill anyChar (followedBy allWS) .>>. getPosition + (getPosition .>>. fragmentEnd) |> runOnSubstream state + + let nextValid = + (skipChar '@') <|> (qsFragmentHeader |>> ignore) + + let parseOverError = + skipInvalidUntil nextValid + + let checkArgs = + let processMissingArgs = buildError parseOverError ErrorCode.MissingAttributeArgs >>% () + (lookAhead nextValid >>. processMissingArgs >>. preturn ((InvalidExpr, Null) |> QsExpression.New)) <|> args + + let validBody state = + let body = id .>>. checkArgs + (body |>> fragmentKind .>>. delimiters state) + + let checkEnding = + let processMissingSymbol = buildError parseOverError ErrorCode.MissingClosingAttributeSign >>% () + (skipChar '@') <|> processMissingSymbol + + let header = + (skipChar '@' |> term) + + getCharStreamState >>= fun state -> + header >>. attempt (validBody state) .>> checkEnding >>= build + +