├── .gitignore ├── example.png ├── SourceIsView.playground ├── Resources │ ├── cells │ │ ├── 'S.png │ │ ├── IS.png │ │ ├── OF.png │ │ ├── TO.png │ │ ├── ….png │ │ ├── ALIAS.png │ │ ├── AND.png │ │ ├── ANY.png │ │ ├── ARRAY.png │ │ ├── ASSOC.png │ │ ├── CAN.png │ │ ├── CASE.png │ │ ├── CLASS.png │ │ ├── CONV.png │ │ ├── DICT.png │ │ ├── EMPTY.png │ │ ├── ENUM.png │ │ ├── FAIL!.png │ │ ├── FAIL.png │ │ ├── FILE.png │ │ ├── FINAL.png │ │ ├── FUNC.png │ │ ├── HAS.png │ │ ├── INIT.png │ │ ├── INOUT.png │ │ ├── ISA.png │ │ ├── IUO.png │ │ ├── LAZY.png │ │ ├── LET.png │ │ ├── OPEN.png │ │ ├── OPT.png │ │ ├── OVER.png │ │ ├── PROTO.png │ │ ├── TAKES.png │ │ ├── THEN.png │ │ ├── THROW.png │ │ ├── TUPLE.png │ │ ├── VAR.png │ │ ├── VIEW.png │ │ ├── WHERE.png │ │ ├── CLOSUR.png │ │ ├── DEINIT.png │ │ ├── IMPORT.png │ │ ├── INTERN.png │ │ ├── MUTATE.png │ │ ├── PRIVAT.png │ │ ├── PUBLIC.png │ │ ├── RETHROW.png │ │ ├── RETURN.png │ │ ├── SOURCE.png │ │ ├── STATIC.png │ │ ├── STRUCT.png │ │ ├── INDIRECT.png │ │ └── SUBSCRIPT.png │ └── Result.swift ├── contents.xcplayground └── Contents.swift ├── SourceIsView.xcworkspace ├── xcshareddata │ └── IDEWorkspaceChecks.plist └── contents.xcworkspacedata ├── Sources ├── SourceIsView │ ├── Cell.swift │ ├── Model.swift │ ├── Draw.swift │ ├── Render.swift │ └── Parse.swift └── source-is-view │ └── main.swift ├── Package.resolved ├── Package.swift ├── README.md └── LICENSE.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata 6 | -------------------------------------------------------------------------------- /example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/example.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/'S.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/'S.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/IS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/IS.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/OF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/OF.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/TO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/TO.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/….png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/….png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/ALIAS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/ALIAS.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/AND.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/AND.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/ANY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/ANY.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/ARRAY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/ARRAY.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/ASSOC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/ASSOC.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/CAN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/CAN.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/CASE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/CASE.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/CLASS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/CLASS.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/CONV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/CONV.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/DICT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/DICT.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/EMPTY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/EMPTY.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/ENUM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/ENUM.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/FAIL!.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/FAIL!.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/FAIL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/FAIL.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/FILE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/FILE.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/FINAL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/FINAL.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/FUNC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/FUNC.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/HAS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/HAS.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/INIT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/INIT.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/INOUT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/INOUT.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/ISA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/ISA.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/IUO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/IUO.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/LAZY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/LAZY.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/LET.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/LET.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/OPEN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/OPEN.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/OPT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/OPT.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/OVER.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/OVER.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/PROTO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/PROTO.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/TAKES.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/TAKES.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/THEN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/THEN.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/THROW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/THROW.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/TUPLE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/TUPLE.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/VAR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/VAR.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/VIEW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/VIEW.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/WHERE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/WHERE.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/CLOSUR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/CLOSUR.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/DEINIT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/DEINIT.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/IMPORT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/IMPORT.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/INTERN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/INTERN.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/MUTATE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/MUTATE.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/PRIVAT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/PRIVAT.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/PUBLIC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/PUBLIC.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/RETHROW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/RETHROW.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/RETURN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/RETURN.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/SOURCE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/SOURCE.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/STATIC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/STATIC.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/STRUCT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/STRUCT.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/INDIRECT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/INDIRECT.png -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/cells/SUBSCRIPT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrose-apple/SourceIsView/HEAD/SourceIsView.playground/Resources/cells/SUBSCRIPT.png -------------------------------------------------------------------------------- /SourceIsView.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /SourceIsView.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SourceIsView.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Sources/SourceIsView/Cell.swift: -------------------------------------------------------------------------------- 1 | public struct Cell: ExpressibleByStringLiteral, CustomStringConvertible { 2 | public var value: String 3 | 4 | public init(_ value: String) { self.value = value } 5 | public init(stringLiteral: String) { self.init(stringLiteral) } 6 | 7 | public static var empty: Cell { return Cell("") } 8 | 9 | public var description: String { 10 | return value 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "SwiftSyntax", 6 | "repositoryURL": "https://github.com/apple/swift-syntax.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "43aa4a19b8105a803d8149ad2a86aa53a77efef3", 10 | "version": "0.50000.0" 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /SourceIsView.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | import SourceIsView 2 | import SwiftSyntax 3 | 4 | import AppKit 5 | 6 | // Add your own file here! 7 | let source = #fileLiteral(resourceName: "Result.swift") 8 | 9 | let sourceFile = try SyntaxTreeParser.parse(source) 10 | let cells = generateCells(from: sourceFile) 11 | 12 | let image = cells.toImage(prerenderedCellDirectory: #fileLiteral(resourceName: "cells")) 13 | 14 | // Copy to clipboard because this image is potentially huge... 15 | NSPasteboard.general.clearContents() 16 | NSPasteboard.general.writeObjects([image]) 17 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "SourceIsView", 7 | products: [ 8 | .library( 9 | name: "SourceIsView", 10 | targets: ["SourceIsView"]), 11 | .executable( 12 | name: "source-is-view", 13 | targets: ["source-is-view"]), 14 | ], 15 | dependencies: [ 16 | .package( 17 | url: "https://github.com/apple/swift-syntax.git", 18 | .exact("0.50000.0")), 19 | ], 20 | targets: [ 21 | .target( 22 | name: "SourceIsView", 23 | dependencies: ["SwiftSyntax"]), 24 | .target( 25 | name: "source-is-view", 26 | dependencies: ["SourceIsView", "SwiftSyntax"]), 27 | ] 28 | ) 29 | -------------------------------------------------------------------------------- /Sources/source-is-view/main.swift: -------------------------------------------------------------------------------- 1 | import SourceIsView 2 | import SwiftSyntax 3 | 4 | import AppKit 5 | 6 | guard CommandLine.arguments.count == 2 else { 7 | // FIXME: should go to stderr 8 | print("usage: source-is-view file.swift") 9 | exit(1) 10 | } 11 | 12 | // Hack: will only work in the package layout. 13 | let prerenderedCellDirectory = URL(fileURLWithPath: #file) 14 | .deletingLastPathComponent() 15 | .deletingLastPathComponent() 16 | .deletingLastPathComponent() 17 | .appendingPathComponent("SourceIsView.playground", isDirectory: true) 18 | .appendingPathComponent("Resources", isDirectory: true) 19 | .appendingPathComponent("cells", isDirectory: true) 20 | 21 | let source = URL(fileURLWithPath: CommandLine.arguments[1]) 22 | let sourceFile = try SyntaxTreeParser.parse(source) 23 | let cells = generateCells(from: sourceFile) 24 | let image = cells.toImage(prerenderedCellDirectory: prerenderedCellDirectory) 25 | 26 | NSPasteboard.general.clearContents() 27 | NSPasteboard.general.writeObjects([image]) 28 | print("Copied to clipboard!") 29 | -------------------------------------------------------------------------------- /Sources/SourceIsView/Model.swift: -------------------------------------------------------------------------------- 1 | struct Entity { 2 | enum Kind { 3 | case `struct` 4 | case `class` 5 | case `enum` 6 | case `protocol` 7 | case assocType 8 | case `typealias` 9 | case `import` 10 | case `func` 11 | case initializer 12 | case `let` 13 | case `var` 14 | case `case` 15 | case `subscript` 16 | case `deinit` 17 | case `extension` 18 | } 19 | 20 | var name: String 21 | var kind: Kind 22 | var genericArguments: [String] 23 | var children: [Entity] 24 | var descriptors: [Descriptor] 25 | var genericRequirements: [Requirement] 26 | var predicates: [Predicate] 27 | 28 | var isExtension: Bool { 29 | if case .extension = self.kind { return true } 30 | return false 31 | } 32 | } 33 | 34 | struct Predicate { 35 | var name: String 36 | var arguments: [RenderableValue] 37 | 38 | init(_ name: String, _ arguments: [RenderableValue]) { 39 | self.name = name 40 | self.arguments = arguments 41 | } 42 | } 43 | 44 | struct Requirement { 45 | enum Kind { 46 | case equals 47 | case isa 48 | } 49 | 50 | var typeA: TypeRepr 51 | var typeB: TypeRepr 52 | var kind: Kind 53 | 54 | init(_ typeA: TypeRepr, _ kind: Kind, _ typeB: TypeRepr) { 55 | self.typeA = typeA 56 | self.kind = kind 57 | self.typeB = typeB 58 | } 59 | } 60 | 61 | struct Descriptor { 62 | var name: String 63 | 64 | init(_ name: String) { 65 | self.name = name 66 | } 67 | } 68 | 69 | struct TypeRepr { 70 | enum Kind { 71 | case named(String) 72 | case any 73 | case existential 74 | case optional 75 | case iuo 76 | case array 77 | case dictionary 78 | case tuple 79 | case `inout` 80 | indirect case function(returning: TypeRepr) 81 | } 82 | var kind: Kind 83 | var arguments: [TypeRepr] 84 | 85 | init(_ kind: Kind, _ arguments: [TypeRepr]) { 86 | self.kind = kind 87 | self.arguments = arguments 88 | } 89 | init(_ kind: Kind, _ singleArgument: TypeRepr) { 90 | self.init(kind, [singleArgument]) 91 | } 92 | init(_ kind: Kind) { 93 | self.init(kind, []) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A code visualizer for Swift, which you can read about [on the Swift forums][forums]. Built on [SwiftSyntax][]. Made for April Fools' Day 2019. 2 | 3 | Not supported in any way. Licensed under the Apache 2.0 License with Runtime Library Exception as a contribution to the Swift Open Source project. (That is, treat this repo like a pull request to [apple/swift-syntax][SwiftSyntax] or something.) 4 | 5 | The font used is [Bryndan Write][], available for free. The (Mac-only) rendering logic is set up to fall back to macOS's "Chalkboard" font if you don't have Bryndan Write installed. 6 | 7 | [forums]: https://forums.swift.org/t/new-code-visualizer-for-swift-source-is-view/22454 8 | [SwiftSyntax]: https://github.com/apple/swift-syntax 9 | [Bryndan Write]: https://www.fontspace.com/brynda1231-industries/bryndan-write 10 | 11 | !["RESULT IS ENUM OF SUCCESS AND FAILURE WHERE FAILURE ISA ERROR"](example.png) 12 | 13 | 14 | ## Disclaimer 15 | 16 | I wrote "not supported in any way" up there! This was a for-fun project for April Fools'; the code is not intended to be a "best practices" guide or a "how to use SwiftSyntax" tutorial. You can certainly look at it if you want to see how I used SwiftSyntax, but the [SwiftSyntax repo][SwiftSyntax] has many better examples of this. At best it's a "how Jordan hacks things together when he knows he's going to post the code" guide. 17 | 18 | I do recommend Kishikawa Katsumi's [Swift AST Explorer][] if you're trying to build a SwiftSyntax-based tool. 19 | 20 | [Swift AST Explorer]: https://swift-ast-explorer.kishikawakatsumi.com 21 | 22 | 23 | ## Questions & Answers 24 | 25 | ### Where did this idea come from? 26 | 27 | Quoting from the [forum post][forums], I was "taking cues from [a recent pioneer in experimental predicate logic][hempuli]". 28 | 29 | [hempuli]: https://twitter.com/ESAdevlog 30 | 31 | 32 | ### Why are the prerendered cells using PNG instead of PDF? 33 | 34 | I was actually going to use PDF! But the app I used to make the prerendered cells didn't handle the blend modes I was using in its PDF export, and I didn't feel like redoing the layers in a more PDF-friendly fashion. PNG is fine. 35 | 36 | (PNG also produces smaller files, it turns out, when you're dealing with such a small image and limited palette.) 37 | 38 | 39 | ### Why no Linux support? Swift runs on Linux! 40 | 41 | Well, first of all, the library should work fine on Linux to produce a [ragged array][] of Cells. Turning that into a bitmap of some kind (or rendering to an ASCII grid!) is left as an exercise for the reader. 42 | 43 | But really I used to make Mac apps, so I knew how to do this with AppKit, and wasn't going to worry about anything else. Remember, for-fun project! 44 | 45 | [ragged array]: https://en.wikipedia.org/wiki/Jagged_array 46 | 47 | 48 | ### What didn't you get to implementing? 49 | 50 | Hm, insightful question there. 51 | 52 | If you check out the prerendered cells (they're inside the playground), you can see an entry for `'S`. I originally wanted to properly qualify members, to be more in keeping with the way the \*cough\* source material works. It'd also produce a cooler-looking pattern beyond the simple [acrostic][]. 53 | 54 | Because I never got to this, I didn't bother to implement nested types either, although that would be much easier. 55 | 56 | The other prerendered cell that I didn't use was `IMPORT`, but that one's not so interesting. It would have just been "APPKIT IS IMPORT" or whatever. 57 | 58 | There's a lot more that could be done with the automatic rendering of identifiers: take word boundaries into account, put a closing paren after the ellipsis for inits and subscripts, balance the number of letters on each line, probably more. I also threw in the coloring of non-special identifiers as a last-minute thing, but didn't think about how using Swift's native hashing would result in picking different colors each run. 59 | 60 | (On the other hand, the logic to do that coloring—buried in Draw.swift—is more evidence for [HSB being the most useful color format for programmers][HSB].) 61 | 62 | All the blank space in the resulting images looks a little bland. I never really planned to do this, but it would be cool™ to have background doodads, like...I dunno, circuitboard patterns. Logic gates. Xcode hammers. 63 | 64 | More comments. More doc comments especially. More attention to formatting. More helper functions. Fewer magic numbers. Any sort of efficiency (you'll notice I just concatenate Arrays all the time instead of modifying existing ones, which in theory is something the compiler could optimize but I don't think it does yet). 65 | 66 | Tests. If this were a proper project I'd be unit-testing each transformation individually: Syntax -> Model (Parse), Model -> Cell (Render), Cell -> image (Draw). Instead I just punted and now the Tests directory is empty. 67 | 68 | Oh, and originally for the forum post I was hoping to have the last image be animated, and some little critter would push a Swift logo block in to replace the "VIEW". Then everything would dissolve back into regular Swift source. Ah well! 69 | 70 | [acrostic]: https://en.wikipedia.org/wiki/Acrostic 71 | [HSB]: https://twitter.com/mikolajdobrucki/status/1098130228193476608 72 | 73 | 74 | ### Do you plan to maintain this? 75 | 76 | No. "Not supported in any way." I might delete the repository tomorrow. 77 | 78 | 79 | ### Can I send you a pull request? 80 | 81 | ...I'm tempted, but no. I don't want to deal with licensing. 82 | -------------------------------------------------------------------------------- /Sources/SourceIsView/Draw.swift: -------------------------------------------------------------------------------- 1 | #if canImport(AppKit) 2 | import AppKit 3 | 4 | extension Cell { 5 | public static let renderedDimension: CGFloat = 32 6 | 7 | public func draw(at origin: CGPoint) { 8 | guard !self.value.isEmpty else { return } 9 | var stringToDraw = self.value.uppercased(with: Locale.current) 10 | 11 | // Figure out the best drawing options for this string. 12 | // Basically chosen by me eyeballing what looks good. 13 | let lineHeight: CGFloat 14 | let fontSize: CGFloat 15 | let xOffset: CGFloat 16 | var truncateOption: NSString.DrawingOptions = [] 17 | switch stringToDraw.count { 18 | case 0: 19 | fatalError("handled above") 20 | case 1...2: 21 | lineHeight = 30 22 | fontSize = 24 23 | xOffset = 0 24 | case 3: 25 | lineHeight = 21 26 | fontSize = 14 27 | xOffset = 1 28 | case 4: 29 | lineHeight = 14 30 | fontSize = 14 31 | xOffset = 1 32 | stringToDraw = """ 33 | \(stringToDraw.prefix(2)) 34 | \(stringToDraw.suffix(2)) 35 | """ 36 | case 5...6: 37 | lineHeight = 14 38 | fontSize = 14 39 | xOffset = 1 40 | case 7: 41 | lineHeight = 10 42 | fontSize = 11 43 | xOffset = 2 44 | stringToDraw = """ 45 | \(stringToDraw.prefix(3)) 46 | \(stringToDraw.dropFirst(3).prefix(2)) 47 | \(stringToDraw.dropFirst(5)) 48 | """ 49 | case 8...9: 50 | lineHeight = 10 51 | fontSize = 11 52 | xOffset = 2 53 | stringToDraw = """ 54 | \(stringToDraw.prefix(3)) 55 | \(stringToDraw.dropFirst(3).prefix(3)) 56 | \(stringToDraw.dropFirst(6)) 57 | """ 58 | case 10...: 59 | lineHeight = 10 60 | fontSize = 11 61 | xOffset = 2 62 | truncateOption = .truncatesLastVisibleLine 63 | default: 64 | fatalError("count cannot be negative") 65 | } 66 | 67 | let style = NSMutableParagraphStyle() 68 | style.alignment = .center 69 | style.maximumLineHeight = lineHeight 70 | style.minimumLineHeight = lineHeight 71 | 72 | // Fall back to Chalkboard, installed by default on macOS, if Bryndan Write 73 | // isn't installed. 74 | let font = NSFont(name: "Bryndan Write", size: fontSize) ?? 75 | NSFont(name: "Chalkboard", size: fontSize)! 76 | 77 | // FIXME: hashValue is different on each run, but this should really pick 78 | // a deterministic color for each identifier. 79 | let hueHash = stringToDraw.hashValue 80 | // Extremely hacky way to form a moderately-well-distributed Double value 81 | // in the range 0..<1. I don't know why I did it this way instead of looking 82 | // up a proper way to do it. 83 | let hue = Double(sign: .plus, 84 | exponentBitPattern: 1.0.exponentBitPattern, 85 | significandBitPattern: UInt64(bitPattern: Int64(hueHash))) - 1.0 86 | let color = NSColor(hue: CGFloat(hue), saturation: 0.2, brightness: 1.0, alpha: 1.0) 87 | 88 | // Note: These have fully-qualified keys to help with code completion. 89 | // Hopefully that gets better in the future! 90 | let attStr = NSAttributedString(string: stringToDraw, attributes: [ 91 | NSAttributedString.Key.paragraphStyle: style, 92 | NSAttributedString.Key.font: font, 93 | NSAttributedString.Key.foregroundColor: color 94 | ]) 95 | 96 | attStr.draw(with: NSRect(x: origin.x + xOffset, 97 | y: origin.y, 98 | width: Cell.renderedDimension - (xOffset * 2), 99 | height: Cell.renderedDimension - 2), 100 | options: [.usesLineFragmentOrigin, truncateOption]) 101 | } 102 | } 103 | 104 | extension CGPoint { 105 | func offsetBy(x deltaX: CGFloat = 0, y deltaY: CGFloat = 0) -> CGPoint { 106 | return CGPoint(x: self.x + deltaX, y: self.y + deltaY) 107 | } 108 | } 109 | extension CGSize { 110 | init(square dimension: CGFloat) { 111 | self.init(width: dimension, height: dimension) 112 | } 113 | } 114 | 115 | private func loadPrerenderedCell(_ name: String, from directory: URL?) -> NSImage? { 116 | guard let directory = directory else { return nil } 117 | guard !name.isEmpty else { return nil } 118 | return NSImage(contentsOf: directory.appendingPathComponent(name).appendingPathExtension("png")) 119 | } 120 | 121 | extension Array where Element == Cell { 122 | public func draw(at origin: CGPoint, prerenderedCellDirectory: URL? = nil) { 123 | for (i, cell) in self.enumerated() { 124 | let nextOrigin = origin.offsetBy(x: Cell.renderedDimension * CGFloat(i)) 125 | if let prerenderedCell = loadPrerenderedCell(cell.value, from: prerenderedCellDirectory) { 126 | prerenderedCell.draw(in: CGRect(origin: nextOrigin, size: CGSize(square: Cell.renderedDimension))) 127 | } else { 128 | cell.draw(at: nextOrigin) 129 | } 130 | } 131 | } 132 | } 133 | 134 | extension NSImage { 135 | func withLockedFocus(_ block: () -> ResultType) -> ResultType { 136 | self.lockFocus() 137 | defer { self.unlockFocus() } 138 | return block() 139 | } 140 | } 141 | 142 | extension Array where Element == [Cell] { 143 | public func draw(at origin: CGPoint, prerenderedCellDirectory: URL? = nil) { 144 | for (i, line) in self.lazy.reversed().enumerated() { 145 | line.draw(at: CGPoint(x: 0, y: Cell.renderedDimension * CGFloat(i)), 146 | prerenderedCellDirectory: prerenderedCellDirectory) 147 | } 148 | } 149 | 150 | public func toImage(prerenderedCellDirectory: URL? = nil) -> NSImage { 151 | let longestLineLength = self.lazy.map { $0.count }.max() ?? 0 152 | 153 | let image = NSImage(size: CGSize(width: Cell.renderedDimension * CGFloat(longestLineLength), 154 | height: Cell.renderedDimension * CGFloat(self.count))) 155 | guard longestLineLength != 0 else { return image } 156 | 157 | image.withLockedFocus { 158 | NSColor.black.setFill() 159 | CGRect(origin: .zero, size: image.size).fill() 160 | self.draw(at: .zero, prerenderedCellDirectory: prerenderedCellDirectory) 161 | } 162 | return image 163 | } 164 | } 165 | #endif // canImport(AppKit) 166 | -------------------------------------------------------------------------------- /SourceIsView.playground/Resources/Result.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the Swift.org open source project 4 | // 5 | // Copyright (c) 2018 Apple Inc. and the Swift project authors 6 | // Licensed under Apache License v2.0 with Runtime Library Exception 7 | // 8 | // See https://swift.org/LICENSE.txt for license information 9 | // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors 10 | // 11 | //===----------------------------------------------------------------------===// 12 | 13 | /// A value that represents either a success or a failure, including an 14 | /// associated value in each case. 15 | @_frozen 16 | public enum Result { 17 | /// A success, storing a `Success` value. 18 | case success(Success) 19 | 20 | /// A failure, storing a `Failure` value. 21 | case failure(Failure) 22 | 23 | /// Returns a new result, mapping any success value using the given 24 | /// transformation. 25 | /// 26 | /// Use this method when you need to transform the value of a `Result` 27 | /// instance when it represents a success. The following example transforms 28 | /// the integer success value of a result into a string: 29 | /// 30 | /// func getNextInteger() -> Result { /* ... */ } 31 | /// 32 | /// let integerResult = getNextInteger() 33 | /// // integerResult == .success(5) 34 | /// let stringResult = integerResult.map({ String($0) }) 35 | /// // stringResult == .success("5") 36 | /// 37 | /// - Parameter transform: A closure that takes the success value of this 38 | /// instance. 39 | /// - Returns: A `Result` instance with the result of evaluating `transform` 40 | /// as the new success value if this instance represents a success. 41 | public func map( 42 | _ transform: (Success) -> NewSuccess 43 | ) -> Result { 44 | switch self { 45 | case let .success(success): 46 | return .success(transform(success)) 47 | case let .failure(failure): 48 | return .failure(failure) 49 | } 50 | } 51 | 52 | /// Returns a new result, mapping any failure value using the given 53 | /// transformation. 54 | /// 55 | /// Use this method when you need to transform the value of a `Result` 56 | /// instance when it represents a failure. The following example transforms 57 | /// the error value of a result by wrapping it in a custom `Error` type: 58 | /// 59 | /// struct DatedError: Error { 60 | /// var error: Error 61 | /// var date: Date 62 | /// 63 | /// init(_ error: Error) { 64 | /// self.error = error 65 | /// self.date = Date() 66 | /// } 67 | /// } 68 | /// 69 | /// let result: Result = // ... 70 | /// // result == .failure() 71 | /// let resultWithDatedError = result.mapError({ e in DatedError(e) }) 72 | /// // result == .failure(DatedError(error: , date: )) 73 | /// 74 | /// - Parameter transform: A closure that takes the failure value of the 75 | /// instance. 76 | /// - Returns: A `Result` instance with the result of evaluating `transform` 77 | /// as the new failure value if this instance represents a failure. 78 | public func mapError( 79 | _ transform: (Failure) -> NewFailure 80 | ) -> Result { 81 | switch self { 82 | case let .success(success): 83 | return .success(success) 84 | case let .failure(failure): 85 | return .failure(transform(failure)) 86 | } 87 | } 88 | 89 | /// Returns a new result, mapping any success value using the given 90 | /// transformation and unwrapping the produced result. 91 | /// 92 | /// - Parameter transform: A closure that takes the success value of the 93 | /// instance. 94 | /// - Returns: A `Result` instance with the result of evaluating `transform` 95 | /// as the new failure value if this instance represents a failure. 96 | public func flatMap( 97 | _ transform: (Success) -> Result 98 | ) -> Result { 99 | switch self { 100 | case let .success(success): 101 | return transform(success) 102 | case let .failure(failure): 103 | return .failure(failure) 104 | } 105 | } 106 | 107 | /// Returns a new result, mapping any failure value using the given 108 | /// transformation and unwrapping the produced result. 109 | /// 110 | /// - Parameter transform: A closure that takes the failure value of the 111 | /// instance. 112 | /// - Returns: A `Result` instance, either from the closure or the previous 113 | /// `.success`. 114 | public func flatMapError( 115 | _ transform: (Failure) -> Result 116 | ) -> Result { 117 | switch self { 118 | case let .success(success): 119 | return .success(success) 120 | case let .failure(failure): 121 | return transform(failure) 122 | } 123 | } 124 | 125 | /// Returns the success value as a throwing expression. 126 | /// 127 | /// Use this method to retrieve the value of this result if it represents a 128 | /// success, or to catch the value if it represents a failure. 129 | /// 130 | /// let integerResult: Result = .success(5) 131 | /// do { 132 | /// let value = try integerResult.get() 133 | /// print("The value is \(value).") 134 | /// } catch error { 135 | /// print("Error retrieving the value: \(error)") 136 | /// } 137 | /// // Prints "The value is 5." 138 | /// 139 | /// - Returns: The success value, if the instance represents a success. 140 | /// - Throws: The failure value, if the instance represents a failure. 141 | public func get() throws -> Success { 142 | switch self { 143 | case let .success(success): 144 | return success 145 | case let .failure(failure): 146 | throw failure 147 | } 148 | } 149 | } 150 | 151 | extension Result where Failure == Swift.Error { 152 | /// Creates a new result by evaluating a throwing closure, capturing the 153 | /// returned value as a success, or any thrown error as a failure. 154 | /// 155 | /// - Parameter body: A throwing closure to evaluate. 156 | @_transparent 157 | public init(catching body: () throws -> Success) { 158 | do { 159 | self = .success(try body()) 160 | } catch { 161 | self = .failure(error) 162 | } 163 | } 164 | } 165 | 166 | extension Result: Equatable where Success: Equatable, Failure: Equatable { } 167 | 168 | extension Result: Hashable where Success: Hashable, Failure: Hashable { } 169 | -------------------------------------------------------------------------------- /Sources/SourceIsView/Render.swift: -------------------------------------------------------------------------------- 1 | protocol RenderableValue { 2 | var asCells: [Cell] { get } 3 | var hasTrailingArguments: Bool { get } 4 | } 5 | 6 | extension Entity.Kind { 7 | var asCell: Cell { 8 | switch self { 9 | case .struct: return "STRUCT" 10 | case .class: return "CLASS" 11 | case .enum: return "ENUM" 12 | case .protocol: return "PROTO" 13 | case .assocType: return "ASSOC" 14 | case .typealias: return "ALIAS" 15 | case .import: return "IMPORT" 16 | case .func: return "FUNC" 17 | case .initializer: return "INIT" 18 | case .let: return "LET" 19 | case .var: return "VAR" 20 | case .case: return "CASE" 21 | case .subscript: return "SUBSCRIPT" 22 | case .deinit: return "DEINIT" 23 | case .extension: fatalError("extensions don't get cells") 24 | } 25 | } 26 | } 27 | 28 | extension Entity { 29 | var basePredicateAsCells: [Cell] { 30 | assert(!self.isExtension) 31 | var basePredicate = [Cell(name), "IS", kind.asCell] 32 | if !genericArguments.isEmpty || !genericRequirements.isEmpty { 33 | if !genericArguments.isEmpty { 34 | var genericArgsAsCells: [Cell] = genericArguments.flatMap { ["AND", Cell($0)] } 35 | genericArgsAsCells[0] = "OF" 36 | basePredicate += genericArgsAsCells 37 | } 38 | if !genericRequirements.isEmpty { 39 | basePredicate += ["WHERE"] + joinArgumentsAsCells(genericRequirements) 40 | } 41 | } else if !descriptors.isEmpty { 42 | basePredicate += ["AND"] + joinArgumentsAsCells(descriptors) 43 | } 44 | return basePredicate 45 | } 46 | 47 | var childrenPredicateAsCells: [Cell]? { 48 | guard !children.isEmpty else { return nil } 49 | var childNamesAsCells: [Cell] = children.flatMap { ["AND", Cell($0.name)] } 50 | childNamesAsCells[0] = "HAS" 51 | return [Cell(name)] + childNamesAsCells 52 | } 53 | 54 | var flatPredicatesAsCells: [[Cell]] { 55 | var descriptorPredicate: [Cell]? 56 | if !genericArguments.isEmpty || !genericRequirements.isEmpty || self.isExtension { 57 | if !descriptors.isEmpty { 58 | descriptorPredicate = [Cell(name), "IS"] + joinArgumentsAsCells(descriptors) 59 | } 60 | } 61 | 62 | var result: [[Cell]] = [] 63 | if let descriptorPredicate = descriptorPredicate { 64 | result += [descriptorPredicate] 65 | } 66 | result += predicates.map { [Cell(name)] + $0.asCells } 67 | return result 68 | } 69 | 70 | var flatPredicatesAsCellsIncludingChildren: [[Cell]] { 71 | if let children = self.childrenPredicateAsCells { 72 | return self.flatPredicatesAsCells + [children] 73 | } 74 | return self.flatPredicatesAsCells 75 | } 76 | 77 | var asFlatCells: [[Cell]] { 78 | guard !self.isExtension else { 79 | return self.flatPredicatesAsCellsIncludingChildren.flatMap { [$0, []] }.dropLast() 80 | } 81 | return [self.basePredicateAsCells] + self.flatPredicatesAsCellsIncludingChildren.flatMap { [[], [.empty] + $0] } 82 | } 83 | 84 | var asHierarchicalCells: [[Cell]] { 85 | var result: [[Cell]] = [] 86 | if self.isExtension { 87 | result.append([Cell(self.name), .empty] + (self.flatPredicatesAsCells.first ?? [])) 88 | } else { 89 | result.append(self.basePredicateAsCells) 90 | } 91 | guard !self.children.isEmpty else { 92 | if self.isExtension { 93 | return self.asFlatCells + [[]] 94 | } 95 | for subpredicate in self.flatPredicatesAsCells { 96 | result.append([]) 97 | result.append([.empty] + subpredicate) 98 | } 99 | result.append([]) 100 | return result 101 | } 102 | result.append(["HAS"]) 103 | 104 | let remainingFlatPredicates: ArraySlice<[Cell]> 105 | if self.isExtension { 106 | remainingFlatPredicates = self.flatPredicatesAsCells.dropFirst() 107 | } else { 108 | remainingFlatPredicates = self.flatPredicatesAsCells[...] 109 | } 110 | for subpredicate in remainingFlatPredicates { 111 | result.append(["…", .empty] + subpredicate) 112 | result.append(["…"]) 113 | } 114 | 115 | for child in children { 116 | result.append(child.basePredicateAsCells) 117 | result.append(["AND"]) 118 | for subpredicate in child.flatPredicatesAsCells { 119 | result.append(["…", .empty] + subpredicate) 120 | result.append(["…"]) 121 | } 122 | } 123 | // Clean up extra … and AND. 124 | for i in result.indices.lazy.reversed() { 125 | if result[i].count == 1 { 126 | result[i] = [] 127 | continue 128 | } 129 | if result[i][0].value == "…" { 130 | result[i][0] = .empty 131 | continue 132 | } 133 | break 134 | } 135 | // One extra blank line to end the section. 136 | result.append([]) 137 | return result 138 | } 139 | } 140 | 141 | extension Requirement: RenderableValue { 142 | var asCells: [Cell] { 143 | let op: Cell 144 | switch kind { 145 | case .equals: op = "IS" 146 | case .isa: op = "ISA" 147 | } 148 | return typeA.asCells + [op] + typeB.asCells 149 | } 150 | 151 | var hasTrailingArguments: Bool { 152 | return typeB.hasTrailingArguments 153 | } 154 | } 155 | 156 | extension Predicate { 157 | var asCells: [Cell] { 158 | return [Cell(name)] + joinArgumentsAsCells(arguments) 159 | } 160 | } 161 | 162 | extension Descriptor: RenderableValue { 163 | var asCells: [Cell] { return [Cell(name)] } 164 | var hasTrailingArguments: Bool { return false } 165 | } 166 | 167 | extension TypeRepr: RenderableValue { 168 | var hasTrailingArguments: Bool { 169 | switch kind { 170 | case .function(returning: let result): 171 | return result.hasTrailingArguments 172 | case .inout: 173 | return arguments.first!.hasTrailingArguments 174 | default: 175 | return !arguments.isEmpty 176 | } 177 | } 178 | 179 | var asCells: [Cell] { 180 | let base: [Cell] 181 | switch kind { 182 | case .any: 183 | return ["ANY"] 184 | case .inout: 185 | assert(arguments.count == 1) 186 | return ["INOUT"] + arguments.first!.asCells 187 | case .function(returning: let resultType): 188 | return ["CLOSUR", "OF"] + joinArgumentsAsCells(arguments) + ["TO"] + resultType.asCells 189 | 190 | case .named(let name): 191 | base = [Cell(name)] 192 | case .existential: 193 | base = ["ANY"] 194 | case .optional: 195 | base = ["OPT"] 196 | case .iuo: 197 | base = ["IUO"] 198 | case .array: 199 | base = ["ARRAY"] 200 | case .dictionary: 201 | base = ["DICT"] 202 | case .tuple: 203 | if arguments.isEmpty { 204 | return ["EMPTY"] 205 | } 206 | base = ["TUPLE"] 207 | } 208 | 209 | guard !arguments.isEmpty else { 210 | return base 211 | } 212 | return base + ["OF"] + joinArgumentsAsCells(arguments) 213 | } 214 | } 215 | 216 | private func joinArgumentsAsCells(_ arguments: [RenderableValue]) -> [Cell] { 217 | let allCellsButLast = arguments.dropLast().lazy.map { (next: RenderableValue) -> [Cell] in 218 | let joiner: [Cell] = next.hasTrailingArguments ? ["AND", "THEN"] : ["AND"] 219 | return next.asCells + joiner 220 | }.joined() 221 | return Array(allCellsButLast) + (arguments.last?.asCells ?? []) 222 | } 223 | 224 | extension RenderableValue { 225 | var debugDescription: String { 226 | return self.asCells.lazy.map { $0.value }.joined(separator: " ") 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | 204 | 205 | ## Runtime Library Exception to the Apache 2.0 License: ## 206 | 207 | 208 | As an exception, if you use this Software to compile your source code and 209 | portions of this Software are embedded into the binary product as a result, 210 | you may redistribute such product without providing attribution as would 211 | otherwise be required by Sections 4(a), 4(b) and 4(d) of the License. 212 | -------------------------------------------------------------------------------- /Sources/SourceIsView/Parse.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | 3 | enum TranslationError: Error { 4 | case badType(TypeSyntax) 5 | case badRequirement(Syntax) 6 | case otherBadness(Syntax) 7 | case incompleteSource(Syntax) 8 | } 9 | 10 | protocol NominalTypeSyntax { 11 | var identifier: TokenSyntax { get } 12 | var genericParameterClause: GenericParameterClauseSyntax? { get } 13 | var genericWhereClause: GenericWhereClauseSyntax? { get } 14 | var modifiers: ModifierListSyntax? { get } 15 | var inheritanceClause: TypeInheritanceClauseSyntax? { get } 16 | var members: MemberDeclBlockSyntax { get } 17 | } 18 | extension StructDeclSyntax: NominalTypeSyntax {} 19 | extension EnumDeclSyntax: NominalTypeSyntax { 20 | // FIXME: SwiftSyntax should be uniform here 21 | var genericParameterClause: GenericParameterClauseSyntax? { 22 | return self.genericParameters 23 | } 24 | } 25 | extension ClassDeclSyntax: NominalTypeSyntax {} 26 | extension ProtocolDeclSyntax: NominalTypeSyntax { 27 | var genericParameterClause: GenericParameterClauseSyntax? { 28 | return nil 29 | } 30 | } 31 | 32 | class Translator: SyntaxVisitor { 33 | var entities: [Entity] = [] 34 | 35 | func translateType(_ node: TypeSyntax) throws -> TypeRepr { 36 | switch node { 37 | case let ident as SimpleTypeIdentifierSyntax: 38 | return TypeRepr(.named(ident.name.text), 39 | try ident.genericArgumentClause?.arguments.map { 40 | try translateType($0.argumentType) 41 | } ?? []) 42 | case let array as ArrayTypeSyntax: 43 | return TypeRepr(.array, try translateType(array.elementType)) 44 | case let dict as DictionaryTypeSyntax: 45 | return TypeRepr(.dictionary, [ 46 | try translateType(dict.keyType), 47 | try translateType(dict.valueType)]) 48 | case let opt as OptionalTypeSyntax: 49 | return TypeRepr(.optional, try translateType(opt.wrappedType)) 50 | case let iuo as ImplicitlyUnwrappedOptionalTypeSyntax: 51 | return TypeRepr(.iuo, try translateType(iuo.wrappedType)) 52 | case let tuple as TupleTypeSyntax: 53 | return TypeRepr(.tuple, try tuple.elements.map { 54 | try translateType($0.type) 55 | }) 56 | case let attr as AttributedTypeSyntax: 57 | if attr.specifier?.tokenKind == .inoutKeyword { 58 | return TypeRepr(.inout, try translateType(attr.baseType)) 59 | } 60 | // Ignore other attributes for now. 61 | return try translateType(attr.baseType) 62 | case let fn as FunctionTypeSyntax: 63 | // Ignore "throws" for now. 64 | let arguments = try fn.arguments.map { 65 | try translateType($0.type) 66 | } 67 | return TypeRepr(.function(returning: try translateType(fn.returnType)), 68 | arguments.isEmpty ? [TypeRepr(.tuple)] : arguments) 69 | case let comp as CompositionTypeSyntax: 70 | return TypeRepr(.existential, try comp.elements.map { 71 | try translateType($0.type) 72 | }) 73 | case is ClassRestrictionTypeSyntax: 74 | return TypeRepr(.named("AnyObject")) 75 | default: 76 | // FIXME: Metatypes and member types 77 | throw TranslationError.badType(node) 78 | } 79 | } 80 | 81 | // Hack for optional inout parameter. 82 | func collectModifiers(_ modifiers: ModifierListSyntax?, isMutating: UnsafeMutablePointer? = nil) -> [Descriptor] { 83 | guard let modifiers = modifiers else { 84 | isMutating?.pointee = false 85 | return [] 86 | } 87 | 88 | var descriptors: [Descriptor] = [] 89 | for modifier in modifiers { 90 | switch modifier.name.text { 91 | case "open": 92 | descriptors.append(Descriptor("OPEN")) 93 | case "public": 94 | descriptors.append(Descriptor("PUBLIC")) 95 | case "internal": 96 | descriptors.append(Descriptor("INTERN")) 97 | case "fileprivate": 98 | descriptors.append(Descriptor("FILE")) 99 | case "private": 100 | descriptors.append(Descriptor("PRIVAT")) 101 | 102 | case "class": 103 | descriptors.append(Descriptor("CLASS")) 104 | case "convenience": 105 | descriptors.append(Descriptor("CONV")) 106 | case "final": 107 | descriptors.append(Descriptor("FINAL")) 108 | case "indirect": 109 | descriptors.append(Descriptor("INDIRECT")) 110 | case "lazy": 111 | descriptors.append(Descriptor("LAZY")) 112 | case "override": 113 | descriptors.append(Descriptor("OVER")) 114 | case "static": 115 | descriptors.append(Descriptor("STATIC")) 116 | 117 | case "mutating": 118 | // Note the special treatment here! 119 | isMutating?.pointee = true 120 | default: 121 | break 122 | } 123 | } 124 | return descriptors 125 | } 126 | 127 | func collectGenericRequirements(_ genericParamList: GenericParameterClauseSyntax?, 128 | _ whereClause: GenericWhereClauseSyntax?) throws -> [Requirement] { 129 | let implicitGenericRequirements: [Requirement] = try genericParamList?.genericParameterList.compactMap { 130 | guard let inheritedType = $0.inheritedType else { 131 | return nil 132 | } 133 | 134 | return Requirement(TypeRepr(.named($0.name.text)), .isa, try translateType(inheritedType)) 135 | } ?? [] 136 | 137 | let explicitGenericRequirements: [Requirement] = try whereClause?.requirementList.map { 138 | switch $0 { 139 | case let isaRequirement as ConformanceRequirementSyntax: 140 | return Requirement(try translateType(isaRequirement.leftTypeIdentifier), 141 | .isa, 142 | try translateType(isaRequirement.rightTypeIdentifier)) 143 | case let eqRequirement as SameTypeRequirementSyntax: 144 | return Requirement(try translateType(eqRequirement.leftTypeIdentifier), 145 | .equals, 146 | try translateType(eqRequirement.rightTypeIdentifier)) 147 | default: 148 | throw TranslationError.badRequirement($0) 149 | } 150 | } ?? [] 151 | 152 | return implicitGenericRequirements + explicitGenericRequirements 153 | } 154 | 155 | func ignoringTranslationErrors(_ block: () throws -> Void) { 156 | do { 157 | try block() 158 | } catch is TranslationError { 159 | // Ignore this node and move on. 160 | } catch let error { 161 | fatalError("Unexpected error: \(error)") 162 | } 163 | } 164 | 165 | override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind { 166 | ignoringTranslationErrors { 167 | let genericArgs = node.genericParameterClause?.genericParameterList.map { $0.name.text } ?? [] 168 | let genericRequirements = try collectGenericRequirements(node.genericParameterClause, node.genericWhereClause) 169 | 170 | var predicates: [Predicate] = [] 171 | 172 | let params: [TypeRepr] = try node.signature.input.parameterList.map { 173 | guard let paramType = $0.type else { 174 | throw TranslationError.incompleteSource($0) 175 | } 176 | return try translateType(paramType) 177 | } 178 | if params.isEmpty { 179 | // FIXME: there should still be a predicate? 180 | } else { 181 | predicates.append(Predicate("TAKES", params)) 182 | } 183 | 184 | if let resultSyntax = node.signature.output?.returnType { 185 | predicates.append(Predicate("RETURN", [try translateType(resultSyntax)])) 186 | } 187 | 188 | var descriptors: [Descriptor] = [] 189 | var isMutating = false 190 | descriptors += collectModifiers(node.modifiers, isMutating: &isMutating) 191 | 192 | var capabilities: [Descriptor] = [] 193 | if isMutating { 194 | capabilities.append(Descriptor("MUTATE")) 195 | } 196 | switch node.signature.throwsOrRethrowsKeyword?.tokenKind { 197 | case .throwsKeyword?: 198 | capabilities.append(Descriptor("THROW")) 199 | case .rethrowsKeyword?: 200 | capabilities.append(Descriptor("RETHROW")) 201 | default: 202 | break 203 | } 204 | if !capabilities.isEmpty { 205 | predicates.append(Predicate("CAN", capabilities)) 206 | } 207 | 208 | let function = Entity(name: node.identifier.text, 209 | kind: .func, 210 | genericArguments: genericArgs, 211 | children: [], 212 | descriptors: descriptors, 213 | genericRequirements: genericRequirements, 214 | predicates: predicates) 215 | entities.append(function) 216 | } 217 | return .skipChildren 218 | } 219 | 220 | func visitNominal(_ node: Nominal, kind: Entity.Kind) -> SyntaxVisitorContinueKind { 221 | ignoringTranslationErrors { 222 | let genericArgs = node.genericParameterClause?.genericParameterList.map { $0.name.text } ?? [] 223 | let genericRequirements = try collectGenericRequirements(node.genericParameterClause, node.genericWhereClause) 224 | 225 | let descriptors = collectModifiers(node.modifiers) 226 | 227 | var predicates: [Predicate] = [] 228 | if let inheritanceClause = node.inheritanceClause { 229 | predicates.append(Predicate("ISA", try inheritanceClause.inheritedTypeCollection.map { 230 | try translateType($0.typeName) 231 | })) 232 | } 233 | 234 | let childrenTranslator = Translator() 235 | node.members.walk(childrenTranslator) 236 | 237 | let nominal = Entity(name: node.identifier.text, 238 | kind: kind, 239 | genericArguments: genericArgs, 240 | children: childrenTranslator.entities, 241 | descriptors: descriptors, 242 | genericRequirements: genericRequirements, 243 | predicates: predicates) 244 | entities.append(nominal) 245 | } 246 | return .skipChildren 247 | } 248 | 249 | override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { 250 | return visitNominal(node, kind: .struct) 251 | } 252 | 253 | override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { 254 | return visitNominal(node, kind: .class) 255 | } 256 | 257 | override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind { 258 | return visitNominal(node, kind: .enum) 259 | } 260 | 261 | override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind { 262 | return visitNominal(node, kind: .protocol) 263 | } 264 | 265 | override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind { 266 | ignoringTranslationErrors { 267 | let descriptors = collectModifiers(node.modifiers) 268 | 269 | let kind: Entity.Kind 270 | switch node.letOrVarKeyword.tokenKind { 271 | case .varKeyword: 272 | kind = .var 273 | case .letKeyword: 274 | kind = .let 275 | default: 276 | throw TranslationError.otherBadness(node.letOrVarKeyword) 277 | } 278 | 279 | node.bindings.forEach { bindingNode in 280 | ignoringTranslationErrors { 281 | // FIXME: Handle more complicated patterns? 282 | guard let simplePattern = bindingNode.pattern as? IdentifierPatternSyntax, 283 | let type = bindingNode.typeAnnotation?.type else { 284 | return 285 | } 286 | 287 | var predicates: [Predicate] = [] 288 | predicates.append(Predicate("HAS", [try translateType(type)])) 289 | 290 | let binding = Entity(name: simplePattern.identifier.text, 291 | kind: kind, 292 | genericArguments: [], 293 | children: [], 294 | descriptors: descriptors, 295 | genericRequirements: [], 296 | predicates: predicates) 297 | entities.append(binding) 298 | } 299 | } 300 | } 301 | return .skipChildren 302 | } 303 | 304 | override func visit(_ node: DeinitializerDeclSyntax) -> SyntaxVisitorContinueKind { 305 | ignoringTranslationErrors { 306 | let descriptors = collectModifiers(node.modifiers) 307 | let deinitializer = Entity(name: "deinit", 308 | kind: .deinit, 309 | genericArguments: [], 310 | children: [], 311 | descriptors: descriptors, 312 | genericRequirements: [], 313 | predicates: []) 314 | entities.append(deinitializer) 315 | } 316 | return .skipChildren 317 | } 318 | 319 | override func visit(_ node: InitializerDeclSyntax) -> SyntaxVisitorContinueKind { 320 | ignoringTranslationErrors { 321 | let genericArgs = node.genericParameterClause?.genericParameterList.map { $0.name.text } ?? [] 322 | let genericRequirements = try collectGenericRequirements(node.genericParameterClause, node.genericWhereClause) 323 | 324 | var predicates: [Predicate] = [] 325 | 326 | let params: [TypeRepr] = try node.parameters.parameterList.map { 327 | guard let paramType = $0.type else { 328 | throw TranslationError.incompleteSource($0) 329 | } 330 | return try translateType(paramType) 331 | } 332 | if params.isEmpty { 333 | // FIXME: there should still be a predicate? 334 | } else { 335 | predicates.append(Predicate("TAKES", params)) 336 | } 337 | 338 | let descriptors: [Descriptor] = collectModifiers(node.modifiers) 339 | 340 | var capabilities: [Descriptor] = [] 341 | switch node.throwsOrRethrowsKeyword?.tokenKind { 342 | case .throwsKeyword?: 343 | capabilities.append(Descriptor("THROW")) 344 | case .rethrowsKeyword?: 345 | capabilities.append(Descriptor("RETHROW")) 346 | default: 347 | break 348 | } 349 | switch node.optionalMark?.tokenKind { 350 | case .postfixQuestionMark?: 351 | capabilities.append(Descriptor("FAIL")) 352 | case .exclamationMark?: 353 | capabilities.append(Descriptor("FAIL!")) 354 | default: 355 | break 356 | } 357 | if !capabilities.isEmpty { 358 | predicates.append(Predicate("CAN", capabilities)) 359 | } 360 | 361 | let name = "init(" + node.parameters.parameterList.lazy.map { 362 | (($0.firstName ?? $0.secondName)?.text ?? "_") + ":" 363 | }.joined() + ")" 364 | let initializer = Entity(name: name, 365 | kind: .initializer, 366 | genericArguments: genericArgs, 367 | children: [], 368 | descriptors: descriptors, 369 | genericRequirements: genericRequirements, 370 | predicates: predicates) 371 | entities.append(initializer) 372 | } 373 | return .skipChildren 374 | } 375 | 376 | override func visit(_ node: SubscriptDeclSyntax) -> SyntaxVisitorContinueKind { 377 | ignoringTranslationErrors { 378 | let genericArgs = node.genericParameterClause?.genericParameterList.map { $0.name.text } ?? [] 379 | let genericRequirements = try collectGenericRequirements(node.genericParameterClause, node.genericWhereClause) 380 | 381 | var predicates: [Predicate] = [] 382 | 383 | let params: [TypeRepr] = try node.indices.parameterList.map { 384 | guard let paramType = $0.type else { 385 | throw TranslationError.incompleteSource($0) 386 | } 387 | return try translateType(paramType) 388 | } 389 | if params.isEmpty { 390 | // FIXME: there should still be a predicate? 391 | } else { 392 | predicates.append(Predicate("TAKES", params)) 393 | } 394 | 395 | predicates.append(Predicate("HAS", [try translateType(node.result.returnType)])) 396 | 397 | let descriptors: [Descriptor] = collectModifiers(node.modifiers) 398 | 399 | let name = "subs(" + node.indices.parameterList.lazy.map { 400 | ($0.firstName?.text ?? "_") + ":" 401 | }.joined() + ")" 402 | let subs = Entity(name: name, 403 | kind: .subscript, 404 | genericArguments: genericArgs, 405 | children: [], 406 | descriptors: descriptors, 407 | genericRequirements: genericRequirements, 408 | predicates: predicates) 409 | entities.append(subs) 410 | } 411 | return .skipChildren 412 | } 413 | 414 | override func visit(_ node: EnumCaseDeclSyntax) -> SyntaxVisitorContinueKind { 415 | ignoringTranslationErrors { 416 | let descriptors = collectModifiers(node.modifiers) 417 | 418 | node.elements.forEach { elementNode in 419 | ignoringTranslationErrors { 420 | var predicates: [Predicate] = [] 421 | if let assocType = elementNode.associatedValue { 422 | predicates.append(Predicate("HAS", try assocType.parameterList.map { 423 | guard let type = $0.type else { 424 | throw TranslationError.incompleteSource($0) 425 | } 426 | return try translateType(type) 427 | })) 428 | } 429 | 430 | let binding = Entity(name: elementNode.identifier.text, 431 | kind: .case, 432 | genericArguments: [], 433 | children: [], 434 | descriptors: descriptors, 435 | genericRequirements: [], 436 | predicates: predicates) 437 | entities.append(binding) 438 | } 439 | } 440 | } 441 | return .skipChildren 442 | } 443 | 444 | override func visit(_ node: AssociatedtypeDeclSyntax) -> SyntaxVisitorContinueKind { 445 | ignoringTranslationErrors { 446 | let descriptors = collectModifiers(node.modifiers) 447 | 448 | let implicitRequirements: [Requirement] = try node.inheritanceClause?.inheritedTypeCollection.map { 449 | Requirement(TypeRepr(.named(node.identifier.text)), .isa, try translateType($0.typeName)) 450 | } ?? [] 451 | 452 | let whereClauseRequirements = try collectGenericRequirements(nil, node.genericWhereClause) 453 | 454 | let binding = Entity(name: node.identifier.text, 455 | kind: .assocType, 456 | genericArguments: [], 457 | children: [], 458 | descriptors: descriptors, 459 | genericRequirements: implicitRequirements + whereClauseRequirements, 460 | predicates: []) 461 | entities.append(binding) 462 | } 463 | return .skipChildren 464 | } 465 | 466 | override func visit(_ node: TypealiasDeclSyntax) -> SyntaxVisitorContinueKind { 467 | ignoringTranslationErrors { 468 | let genericArgs = node.genericParameterClause?.genericParameterList.map { $0.name.text } ?? [] 469 | let genericRequirements = try collectGenericRequirements(node.genericParameterClause, node.genericWhereClause) 470 | 471 | let descriptors = collectModifiers(node.modifiers) 472 | 473 | var predicates: [Predicate] = [] 474 | guard let underlyingType = node.initializer?.value else { 475 | throw TranslationError.incompleteSource(node) 476 | } 477 | predicates.append(Predicate("HAS", [try translateType(underlyingType)])) 478 | 479 | let nominal = Entity(name: node.identifier.text, 480 | kind: .typealias, 481 | genericArguments: genericArgs, 482 | children: [], 483 | descriptors: descriptors, 484 | genericRequirements: genericRequirements, 485 | predicates: predicates) 486 | entities.append(nominal) 487 | } 488 | return .skipChildren 489 | } 490 | 491 | override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind { 492 | ignoringTranslationErrors { 493 | var predicates: [Predicate] = [] 494 | if let inheritanceClause = node.inheritanceClause { 495 | predicates.append(Predicate("ISA", try inheritanceClause.inheritedTypeCollection.map { 496 | try translateType($0.typeName) 497 | })) 498 | } 499 | 500 | let childrenTranslator = Translator() 501 | node.members.walk(childrenTranslator) 502 | 503 | let extendedType = try translateType(node.extendedType) 504 | guard case .named(let name) = extendedType.kind else { 505 | // FIXME: Support extensions with type sugar? 506 | return 507 | } 508 | 509 | // FIXME: Ignoring constraints! 510 | let ext = Entity(name: name, 511 | kind: .extension, 512 | genericArguments: [], 513 | children: childrenTranslator.entities, 514 | descriptors: [], 515 | genericRequirements: [], 516 | predicates: predicates) 517 | entities.append(ext) 518 | } 519 | return .skipChildren 520 | } 521 | } 522 | 523 | extension Array { 524 | fileprivate func padded(to newCount: Int, with element: Element) -> [Element] { 525 | return self + Array(repeating: element, count: newCount - self.count) 526 | } 527 | } 528 | 529 | public func generateCells(from parsedFile: SourceFileSyntax) -> [[Cell]] { 530 | let translator = Translator() 531 | parsedFile.walk(translator) 532 | var result = [[]] + translator.entities.flatMap { $0.asHierarchicalCells } 533 | 534 | let longestLineLength = result.lazy.map { $0.count }.max() ?? 0 535 | 536 | // The check for 3 vs. 4 here is not a typo; if we have *more* than 3 lines, 537 | // we need to leave a blank space below "VIEW". (Well, we want to.) 538 | if result.count >= 3 && result.lazy.prefix(4).allSatisfy({ $0.count < longestLineLength - 1 }) { 539 | result[0] = result[0].padded(to: longestLineLength - 1, with: .empty) + ["SOURCE"] 540 | result[1] = result[1].padded(to: longestLineLength - 1, with: .empty) + ["IS"] 541 | result[2] = result[2].padded(to: longestLineLength - 1, with: .empty) + ["VIEW"] 542 | } 543 | 544 | return result 545 | } 546 | --------------------------------------------------------------------------------