diff --git a/src/QsFmt/Formatter.Tests/Examples.fs b/src/QsFmt/Formatter.Tests/Examples.fs index 3056117530..5b1094c3e0 100644 --- a/src/QsFmt/Formatter.Tests/Examples.fs +++ b/src/QsFmt/Formatter.Tests/Examples.fs @@ -48,9 +48,10 @@ return x + 1; [] let ``Adds newlines and indents`` = - """namespace Foo { function Bar() : Int { let x = 5; return x; } }""", + """namespace Foo { newtype InternalType = Unit; function Bar() : Int { let x = 5; return x; } }""", """namespace Foo { + newtype InternalType = Unit; function Bar() : Int { let x = 5; return x; @@ -60,6 +61,8 @@ let ``Adds newlines and indents`` = [] let ``Removes extraneous spaces`` = """namespace Foo { + open qualified.name as qn; + internal newtype Complex = (Real: Double, Imaginary : Double); function Bar() : Int [ ] { let x= // Newlines are preserved. (7 * 1) // Comments too. @@ -70,6 +73,8 @@ let ``Removes extraneous spaces`` = }""", """namespace Foo { + open qualified.name as qn; + internal newtype Complex = (Real: Double, Imaginary : Double); function Bar() : Int [ ] { let x = // Newlines are preserved. (7 * 1) // Comments too. diff --git a/src/QsFmt/Formatter/ParseTree/Namespace.fs b/src/QsFmt/Formatter/ParseTree/Namespace.fs index ed8fbb3fc7..8fcff81c6e 100644 --- a/src/QsFmt/Formatter/ParseTree/Namespace.fs +++ b/src/QsFmt/Formatter/ParseTree/Namespace.fs @@ -132,6 +132,50 @@ type CallableBodyVisitor(tokens) = } |> Specializations +type UnderlyingTypeVistor(tokens) = + inherit QSharpParserBaseVisitor() + + let typeVisitor = TypeVisitor tokens + + override _.DefaultResult = failwith "Unknown underlying type." + + override visitor.VisitTupleUnderlyingType context = + context.typeDeclarationTuple () |> visitor.Visit + + override _.VisitUnnamedTypeItem context = + context.``type`` () |> typeVisitor.Visit |> Type + + override _.VisitTypeDeclarationTuple context = + let parameters = context._items |> Seq.map (TypeTupleItemVistor tokens).Visit + let commas = context._commas |> Seq.map (Node.toTerminal tokens) + + { + OpenParen = context.openParen |> Node.toTerminal tokens + Items = Node.tupleItems parameters commas + CloseParen = context.closeParen |> Node.toTerminal tokens + } + |> TypeDeclarationTuple + +and TypeTupleItemVistor(tokens) = + inherit QSharpParserBaseVisitor() + + let typeVisitor = TypeVisitor tokens + let underlyingTypeVistor = UnderlyingTypeVistor tokens + + override _.DefaultResult = failwith "Unknown type tuple item." + + override visitor.VisitNamedTypeItem context = context.namedItem () |> visitor.Visit + + override _.VisitUnderlyingTypeItem context = + context.underlyingType () |> underlyingTypeVistor.Visit |> UnderlyingType + + override _.VisitNamedItem context = + { + Name = context.name |> Node.toTerminal tokens + Type = { Colon = context.colon |> Node.toTerminal tokens; Type = context.itemType |> typeVisitor.Visit } + } + |> TypeBinding + /// /// Creates syntax tree nodes from a parse tree and the list of tokens. /// @@ -141,30 +185,56 @@ type NamespaceItemVisitor(tokens) = let parameterVisitor = ParameterVisitor tokens let typeVisitor = TypeVisitor tokens let callableBodyVisitor = CallableBodyVisitor tokens + let underlyingTypeVistor = UnderlyingTypeVistor tokens override _.DefaultResult = failwith "Unknown namespace element." override _.VisitChildren node = Node.toUnknown tokens node |> Unknown - override _.VisitCallableElement context = + override visitor.VisitOpenElement context = + context.openDirective () |> visitor.Visit + + override visitor.VisitTypeElement context = + context.typeDeclaration () |> visitor.Visit + + override visitor.VisitCallableElement context = + context.callableDeclaration () |> visitor.Visit + + override _.VisitOpenDirective context = { - Attributes = - context.callable.prefix._attributes |> Seq.map (NamespaceContext.toAttribute tokens) |> Seq.toList - Access = context.callable.prefix.access () |> Option.ofObj |> Option.map (Node.toUnknown tokens) - CallableKeyword = context.callable.keyword |> Node.toTerminal tokens - Name = context.callable.name |> Node.toTerminal tokens + OpenKeyword = context.``open`` |> Node.toTerminal tokens + OpenName = context.openName |> Node.toUnknown tokens + AsKeyword = context.``as`` |> Option.ofObj |> Option.map (Node.toTerminal tokens) + AsName = context.asName |> Option.ofObj |> Option.map (Node.toUnknown tokens) + Semicolon = context.semicolon |> Node.toTerminal tokens + } + |> OpenDirective + + override _.VisitTypeDeclaration context = + { + Attributes = context.prefix._attributes |> Seq.map (NamespaceContext.toAttribute tokens) |> Seq.toList + Access = context.prefix.access () |> Option.ofObj |> Option.map (Node.toUnknown tokens) + NewtypeKeyword = context.keyword |> Node.toTerminal tokens + DeclaredType = context.declared |> Node.toTerminal tokens + Equals = context.equals |> Node.toTerminal tokens + UnderlyingType = context.underlying |> underlyingTypeVistor.Visit + Semicolon = context.semicolon |> Node.toTerminal tokens + } + |> TypeDeclaration + + override _.VisitCallableDeclaration context = + { + Attributes = context.prefix._attributes |> Seq.map (NamespaceContext.toAttribute tokens) |> Seq.toList + Access = context.prefix.access () |> Option.ofObj |> Option.map (Node.toUnknown tokens) + CallableKeyword = context.keyword |> Node.toTerminal tokens + Name = context.name |> Node.toTerminal tokens TypeParameters = - Option.ofObj context.callable.typeParameters - |> Option.map (NamespaceContext.toTypeParameterBinding tokens) - Parameters = parameterVisitor.Visit context.callable.tuple + Option.ofObj context.typeParameters |> Option.map (NamespaceContext.toTypeParameterBinding tokens) + Parameters = parameterVisitor.Visit context.tuple ReturnType = - { - Colon = context.callable.colon |> Node.toTerminal tokens - Type = typeVisitor.Visit context.callable.returnType - } - CharacteristicSection = - Option.ofObj context.callable.returnChar |> Option.map (toCharacteristicSection tokens) - Body = callableBodyVisitor.Visit context.callable.body + { Colon = context.colon |> Node.toTerminal tokens; Type = typeVisitor.Visit context.returnType } + CharacteristicSection = Option.ofObj context.returnChar |> Option.map (toCharacteristicSection tokens) + Body = callableBodyVisitor.Visit context.body } |> CallableDeclaration diff --git a/src/QsFmt/Formatter/SyntaxTree/Namespace.fs b/src/QsFmt/Formatter/SyntaxTree/Namespace.fs index 261a474063..f84bf1b289 100644 --- a/src/QsFmt/Formatter/SyntaxTree/Namespace.fs +++ b/src/QsFmt/Formatter/SyntaxTree/Namespace.fs @@ -22,6 +22,34 @@ type SpecializationGenerator = type Specialization = { Names: Terminal list; Generator: SpecializationGenerator } +type OpenDirective = + { + OpenKeyword: Terminal + OpenName: Terminal + AsKeyword: Terminal option + AsName: Terminal option + Semicolon: Terminal + } + +type internal UnderlyingType = + | TypeDeclarationTuple of TypeTupleItem Tuple + | Type of Type + +and internal TypeTupleItem = + | TypeBinding of ParameterDeclaration + | UnderlyingType of UnderlyingType + +type TypeDeclaration = + { + Attributes: Attribute list + Access: Terminal option + NewtypeKeyword: Terminal + DeclaredType: Terminal + Equals: Terminal + UnderlyingType: UnderlyingType + Semicolon: Terminal + } + type CallableBody = | Statements of Statement Block | Specializations of Specialization Block @@ -40,12 +68,23 @@ type CallableDeclaration = } type NamespaceItem = + | OpenDirective of OpenDirective + | TypeDeclaration of TypeDeclaration | CallableDeclaration of CallableDeclaration | Unknown of Terminal module NamespaceItem = let mapPrefix mapper = function + | OpenDirective openDirective -> + { openDirective with OpenKeyword = Terminal.mapPrefix mapper openDirective.OpenKeyword } + |> OpenDirective + | TypeDeclaration declaration -> + match declaration.Attributes, declaration.Access with + | head :: tail, _ -> { declaration with Attributes = Attribute.mapPrefix mapper head :: tail } + | [], Some access -> { declaration with Access = Terminal.mapPrefix mapper access |> Some } + | [], None -> { declaration with NewtypeKeyword = Terminal.mapPrefix mapper declaration.NewtypeKeyword } + |> TypeDeclaration | CallableDeclaration callable -> { callable with CallableKeyword = Terminal.mapPrefix mapper callable.CallableKeyword diff --git a/src/QsFmt/Formatter/SyntaxTree/Namespace.fsi b/src/QsFmt/Formatter/SyntaxTree/Namespace.fsi index 6dfe50b94d..81c262231f 100644 --- a/src/QsFmt/Formatter/SyntaxTree/Namespace.fsi +++ b/src/QsFmt/Formatter/SyntaxTree/Namespace.fsi @@ -44,6 +44,74 @@ type internal Specialization = Generator: SpecializationGenerator } +/// An open directive +type internal OpenDirective = + { + /// + /// The open keyword. + /// + OpenKeyword: Terminal + + /// The name of the opened namespace. + OpenName: Terminal + + /// + /// The optional as keyword. + /// + AsKeyword: Terminal option + + /// The alias name of the opened namespace. + AsName: Terminal option + + /// The semicolon. + Semicolon: Terminal + } + +/// The underlying type of a newly defined type. +type internal UnderlyingType = + /// A tuple of type items. + | TypeDeclarationTuple of TypeTupleItem Tuple + + /// A simple type. + | Type of Type + +/// An item used in type declaration. +and internal TypeTupleItem = + /// A named type item. + | TypeBinding of ParameterDeclaration + + /// An anonymous type item. + | UnderlyingType of UnderlyingType + +/// A type declaration +type internal TypeDeclaration = + { + /// The attributes attached to the type declaration. + Attributes: Attribute list + + /// The access modifier for the callable. + Access: Terminal option + + /// + /// The newtype keyword. + /// + NewtypeKeyword: Terminal + + /// The name of the declared type. + DeclaredType: Terminal + + /// + /// The = symbol. + /// + Equals: Terminal + + /// The underlying type. + UnderlyingType: UnderlyingType + + /// The semicolon. + Semicolon: Terminal + } + /// The body of a callable declaration. type internal CallableBody = /// An implicit body specialization with statements. @@ -88,6 +156,12 @@ type internal CallableDeclaration = /// An item in a namespace. type internal NamespaceItem = + /// An open directive + | OpenDirective of OpenDirective + + /// A type declaration + | TypeDeclaration of TypeDeclaration + /// A callable declaration | CallableDeclaration of CallableDeclaration diff --git a/src/QsFmt/Formatter/SyntaxTree/Reducer.fs b/src/QsFmt/Formatter/SyntaxTree/Reducer.fs index 8a037b0a60..5c68438263 100644 --- a/src/QsFmt/Formatter/SyntaxTree/Reducer.fs +++ b/src/QsFmt/Formatter/SyntaxTree/Reducer.fs @@ -68,14 +68,53 @@ type internal 'result Reducer() as reducer = default _.NamespaceItem item = match item with + | OpenDirective directive -> reducer.OpenDirective directive + | TypeDeclaration delcaration -> reducer.TypeDeclaration delcaration | CallableDeclaration callable -> reducer.CallableDeclaration callable | Unknown terminal -> reducer.Terminal terminal + abstract OpenDirective : directive: OpenDirective -> 'result + + default _.OpenDirective directive = + [ reducer.Terminal directive.OpenKeyword; reducer.Terminal directive.OpenName ] + @ (directive.AsKeyword |> Option.map reducer.Terminal |> Option.toList) + @ (directive.AsName |> Option.map reducer.Terminal |> Option.toList) + @ [ reducer.Terminal directive.Semicolon ] + |> reduce + + abstract TypeDeclaration : declaration: TypeDeclaration -> 'result + + default _.TypeDeclaration declaration = + (declaration.Attributes |> List.map reducer.Attribute) + @ (declaration.Access |> Option.map reducer.Terminal |> Option.toList) + @ [ + reducer.Terminal declaration.NewtypeKeyword + reducer.Terminal declaration.DeclaredType + reducer.Terminal declaration.Equals + reducer.UnderlyingType declaration.UnderlyingType + reducer.Terminal declaration.Semicolon + ] + |> reduce + abstract Attribute : attribute: Attribute -> 'result default _.Attribute attribute = [ reducer.Terminal attribute.At; reducer.Expression attribute.Expression ] |> reduce + abstract UnderlyingType : underlying: UnderlyingType -> 'result + + default _.UnderlyingType underlying = + match underlying with + | TypeDeclarationTuple tuple -> reducer.Tuple(reducer.TypeTupleItem, tuple) + | Type _type -> reducer.Type _type + + abstract TypeTupleItem : item: TypeTupleItem -> 'result + + default _.TypeTupleItem item = + match item with + | TypeBinding binding -> reducer.ParameterDeclaration binding + | UnderlyingType underlying -> reducer.UnderlyingType underlying + abstract CallableDeclaration : callable: CallableDeclaration -> 'result default _.CallableDeclaration callable = diff --git a/src/QsFmt/Formatter/SyntaxTree/Reducer.fsi b/src/QsFmt/Formatter/SyntaxTree/Reducer.fsi index af5b60d926..baba400698 100644 --- a/src/QsFmt/Formatter/SyntaxTree/Reducer.fsi +++ b/src/QsFmt/Formatter/SyntaxTree/Reducer.fsi @@ -35,12 +35,36 @@ type internal 'result Reducer = abstract NamespaceItem: item:NamespaceItem -> 'result default NamespaceItem: item:NamespaceItem -> 'result + /// + /// Reduces an node. + /// + abstract OpenDirective: directive:OpenDirective -> 'result + default OpenDirective: directive:OpenDirective -> 'result + + /// + /// Reduces a node. + /// + abstract TypeDeclaration: declaration:TypeDeclaration -> 'result + default TypeDeclaration: declaration:TypeDeclaration -> 'result + /// /// Reduces an node. /// abstract Attribute: attribute:Attribute -> 'result default Attribute: attribute:Attribute -> 'result + /// + /// Reduces an node. + /// + abstract UnderlyingType: underlying:UnderlyingType -> 'result + default UnderlyingType: underlying:UnderlyingType -> 'result + + /// + /// Reduces a node. + /// + abstract TypeTupleItem: item:TypeTupleItem -> 'result + default TypeTupleItem: item:TypeTupleItem -> 'result + /// /// Reduces a node. /// diff --git a/src/QsFmt/Formatter/SyntaxTree/Rewriter.fs b/src/QsFmt/Formatter/SyntaxTree/Rewriter.fs index a21a98c020..fb3a595623 100644 --- a/src/QsFmt/Formatter/SyntaxTree/Rewriter.fs +++ b/src/QsFmt/Formatter/SyntaxTree/Rewriter.fs @@ -61,9 +61,35 @@ type 'context Rewriter() as rewriter = default _.NamespaceItem(context, item) = match item with + | OpenDirective directive -> rewriter.OpenDirective(context, directive) |> OpenDirective + | TypeDeclaration declaration -> rewriter.TypeDeclaration(context, declaration) |> TypeDeclaration | CallableDeclaration callable -> rewriter.CallableDeclaration(context, callable) |> CallableDeclaration | Unknown terminal -> rewriter.Terminal(context, terminal) |> Unknown + abstract OpenDirective : context: 'context * directive: OpenDirective -> OpenDirective + + default rewriter.OpenDirective(context, directive) = + { + OpenKeyword = rewriter.Terminal(context, directive.OpenKeyword) + OpenName = rewriter.Terminal(context, directive.OpenName) + AsKeyword = directive.AsKeyword |> Option.map (curry rewriter.Terminal context) + AsName = directive.AsName |> Option.map (curry rewriter.Terminal context) + Semicolon = rewriter.Terminal(context, directive.Semicolon) + } + + abstract TypeDeclaration : context: 'context * declaration: TypeDeclaration -> TypeDeclaration + + default rewriter.TypeDeclaration(context, declaration) = + { + Attributes = declaration.Attributes |> List.map (curry rewriter.Attribute context) + Access = declaration.Access |> Option.map (curry rewriter.Terminal context) + NewtypeKeyword = rewriter.Terminal(context, declaration.NewtypeKeyword) + DeclaredType = rewriter.Terminal(context, declaration.DeclaredType) + Equals = rewriter.Terminal(context, declaration.Equals) + UnderlyingType = rewriter.UnderlyingType(context, declaration.UnderlyingType) + Semicolon = rewriter.Terminal(context, declaration.Semicolon) + } + abstract Attribute : context: 'context * attribute: Attribute -> Attribute default _.Attribute(context, attribute) = @@ -72,6 +98,20 @@ type 'context Rewriter() as rewriter = Expression = rewriter.Expression(context, attribute.Expression) } + abstract UnderlyingType : context: 'context * underlying: UnderlyingType -> UnderlyingType + + default rewriter.UnderlyingType(context, underlying) = + match underlying with + | TypeDeclarationTuple tuple -> rewriter.Tuple(context, rewriter.TypeTupleItem, tuple) |> TypeDeclarationTuple + | Type _type -> rewriter.Type(context, _type) |> Type + + abstract TypeTupleItem : context: 'context * item: TypeTupleItem -> TypeTupleItem + + default rewriter.TypeTupleItem(context, item) = + match item with + | TypeBinding binding -> rewriter.ParameterDeclaration(context, binding) |> TypeBinding + | UnderlyingType underlying -> rewriter.UnderlyingType(context, underlying) |> UnderlyingType + abstract CallableDeclaration : context: 'context * callable: CallableDeclaration -> CallableDeclaration default _.CallableDeclaration(context, callable) = diff --git a/src/QsFmt/Formatter/SyntaxTree/Rewriter.fsi b/src/QsFmt/Formatter/SyntaxTree/Rewriter.fsi index d608688389..aa9acfa9ef 100644 --- a/src/QsFmt/Formatter/SyntaxTree/Rewriter.fsi +++ b/src/QsFmt/Formatter/SyntaxTree/Rewriter.fsi @@ -31,12 +31,36 @@ type internal 'context Rewriter = abstract NamespaceItem: context:'context * item:NamespaceItem -> NamespaceItem default NamespaceItem: context:'context * item:NamespaceItem -> NamespaceItem + /// + /// Rewrites an node. + /// + abstract OpenDirective: context:'context * directive:OpenDirective -> OpenDirective + default OpenDirective: context:'context * directive:OpenDirective -> OpenDirective + + /// + /// Rewrites a node. + /// + abstract TypeDeclaration: context:'context * declaration:TypeDeclaration -> TypeDeclaration + default TypeDeclaration: context:'context * declaration:TypeDeclaration -> TypeDeclaration + /// /// Rewrites an node. /// abstract Attribute: context:'context * attribute:Attribute -> Attribute default Attribute: context:'context * attribute:Attribute -> Attribute + /// + /// Rewrites an node. + /// + abstract UnderlyingType: context:'context * underlying:UnderlyingType -> UnderlyingType + default UnderlyingType: context:'context * underlying:UnderlyingType -> UnderlyingType + + /// + /// Rewrites a node. + /// + abstract TypeTupleItem: context:'context * item:TypeTupleItem -> TypeTupleItem + default TypeTupleItem: context:'context * item:TypeTupleItem -> TypeTupleItem + /// /// Rewrites a node. /// diff --git a/src/QsFmt/Parser/QSharpParser.g4 b/src/QsFmt/Parser/QSharpParser.g4 index b23922fb05..ea4b15fee3 100644 --- a/src/QsFmt/Parser/QSharpParser.g4 +++ b/src/QsFmt/Parser/QSharpParser.g4 @@ -23,12 +23,12 @@ qualifiedName : Identifier ('.' Identifier)*; namespaceElement : openDirective # OpenElement | typeDeclaration # TypeElement - | callable=callableDeclaration # CallableElement + | callableDeclaration # CallableElement ; // Open Directive -openDirective : 'open' qualifiedName ('as' qualifiedName)? ';'; +openDirective : open='open' openName=qualifiedName (as='as' asName=qualifiedName)? semicolon=';'; // Declaration @@ -40,18 +40,20 @@ declarationPrefix : attributes+=attribute* access?; // Type Declaration -typeDeclaration : declarationPrefix 'newtype' Identifier '=' underlyingType ';'; +typeDeclaration + : prefix=declarationPrefix keyword='newtype' declared=Identifier equals='=' underlying=underlyingType semicolon=';' + ; underlyingType - : typeDeclarationTuple - | type + : typeDeclarationTuple # TupleUnderlyingType + | type # UnnamedTypeItem ; -typeDeclarationTuple : '(' (typeTupleItem (',' typeTupleItem)*)? ')'; +typeDeclarationTuple : openParen='(' (items+=typeTupleItem (commas+=',' items+=typeTupleItem)*)? closeParen=')'; typeTupleItem - : namedItem - | underlyingType + : namedItem # NamedTypeItem + | underlyingType # UnderlyingTypeItem ; namedItem : name=Identifier colon=':' itemType=type;