Skip to content

Commit 2efc974

Browse files
committed
Documented how code generation works
1 parent ae71699 commit 2efc974

File tree

2 files changed

+55
-4
lines changed

2 files changed

+55
-4
lines changed

Tests/CodeGeneration/CodeGeneration.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,6 @@ func forEverySourceFile(_ closure: (URL) -> ()) {
6565

6666
let repoPath = testsPath.deletingLastPathComponent()
6767
let sourcesPath = repoPath.appendingPathComponent("Sources")
68-
print(repoPath)
69-
print(sourcesPath)
7068

7169
let resourceKeys : [URLResourceKey] = [.creationDateKey, .isDirectoryKey]
7270
let enumerator = FileManager.default.enumerator(at: sourcesPath, includingPropertiesForKeys: resourceKeys, options: [.skipsHiddenFiles, .skipsPackageDescendants])!

Tests/CodeGeneration/README.md

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,60 @@
11
# Code Generation
22

33
## Why is code generation needed?
4-
## Why is this a test?
5-
## What is the syntax?
4+
5+
This library requires a lot of near-duplicated code to implement. Many methods are near identical except there is a difference of async, throws, or @escaping. For `.then` there are also differences of whether the body returns a value or not.
6+
7+
In order to keep the code readible, flexible, and free of duplication typos, a simple code generation (CG) system is used, based on running find-and-replace on method bodies and signatures. In particular, this helps with implementation of Then.swift, which would be highly unweildy without CG. Care has been taken to avoid overusing CG in too many places, so that the code can remain readible.
8+
9+
## Why is this in the `Tests` directory?
10+
11+
I wanted the Swift code for the CG engine to have syntax highlighting and be accessible from the same Xcode project, but not be included in the bundle when this package is installed as a 3rd party. The Tests directory seems like a safe place for this kind of code.
12+
13+
I couldn't find a way of getting code generation to run on every build in Xcode, so instead it is runnable as a "test" (even though it is not a test). To run code generation, just click the play button in the `CodeGeneration.swift` file, and hit "Switch and run tests", or switch the scheme yourself and run the "test".
14+
15+
## How does it work?
16+
17+
* Generated code is added to each top-level declaration that requires code generation, between `// GENERATED` and `// END GENERATED` (these comments are added if they do not exist already).
18+
19+
* Inside a top-level declaration, you can use code generation by first defining a pattern using:
20+
21+
```swift
22+
// pattern:myPattern
23+
<code>
24+
// endpattern
25+
```
26+
27+
* Then you can use this pattern to generate code in the same top-level declaration with `// generate`
28+
29+
```swift
30+
// pattern:myPattern
31+
<code>
32+
// endpattern
33+
34+
// generate:myPattern(find1 => replace1, find2 => replace2)
35+
```
36+
37+
Within the parentheses you can define substitution rules. They must be separated by comma-space (`, `), just like the `=>` operation must have spaces on both sides.
38+
39+
The example above does not only look for the literal `find1` and `find2` but also `// find1`, `// find2`, `/* find1 */`, and `/* find2 */`. An error is raised if any of the finds are not found. To make the find-replace rule optional you can use `?=>` rather than `=>`. To treat the find expression as a regex, use `R=>`, and `R?=>` (although using regex too much will hurt readability).
40+
41+
* Certain find-replace patterns appear together frequently, so you can group them into a ruleset with `// ruleset`. As of now, all rulesets are global no matter where they are defined (as opposed to everything else which is scoped to the containing top-level declaration (TLD)). To define a global ruleset use:
42+
43+
```swift
44+
// ruleset:myRuleset(find1 => replace1, find2 => replace2)
45+
```
46+
47+
Then you can use it among find-replace patterns e.g.
48+
49+
```swift
50+
// generate:myPattern(find1 => replace1, myRuleset, find2 => replace2, myOtherRuleset)
51+
```
52+
53+
Rulesets can use other rulesets in their definitions.
54+
55+
* There is a special ruleset that is available in a TLD. It is called `...`. It is expanded to whatever rules/replacements were defined in the last call to `// generate`. Use this to pile on further replacements to the previously-generated result.
56+
657
## Why not use Sourcery?
758

59+
Sourcery was tried and it failed. It cannot handle complicated function signatures, such as two functions that are overloaded with different return types, or (more relevant to our case) overloaded with different `async` or `throws` intricacies.
60+

0 commit comments

Comments
 (0)