├── .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 | 
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 |
--------------------------------------------------------------------------------