Skip to content

Commit 9be07b7

Browse files
committed
Use SwiftSyntaxBuilder to generate SwiftSyntaxBuilder Tokens
1 parent e3d3a83 commit 9be07b7

File tree

6 files changed

+511
-1
lines changed

6 files changed

+511
-1
lines changed

Package.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// swift-tools-version:5.3
1+
// swift-tools-version:5.4
22

33
import PackageDescription
44
import Foundation
@@ -97,6 +97,11 @@ let package = Package(
9797
name: "lit-test-helper",
9898
dependencies: ["SwiftSyntax", "SwiftSyntaxParser"]
9999
),
100+
.executableTarget(
101+
name: "SwiftSyntaxBuilderGeneration",
102+
dependencies: ["SwiftSyntaxBuilder"],
103+
path: "Sources/SwiftSyntaxBuilderGeneration"
104+
),
100105
.testTarget(
101106
name: "SwiftSyntaxTest",
102107
dependencies: ["SwiftSyntax"]
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2022 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+
import Foundation
14+
15+
extension StringProtocol {
16+
var firstLowercased: String { prefix(1).lowercased() + dropFirst() }
17+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2022 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+
import Foundation
14+
import SwiftSyntax
15+
import SwiftSyntaxBuilder
16+
17+
@main
18+
struct SwiftSyntaxBuilderGeneration {
19+
private static var format = Format(indentWidth: 2)
20+
21+
static func main() throws {
22+
let tokensFile = SourceFile {
23+
ImportDecl(path: "SwiftSyntax")
24+
25+
ExtensionDecl(extendedType: "TokenSyntax", modifiersBuilder: { TokenSyntax.public }, membersBuilder: {
26+
for token in SYNTAX_TOKENS {
27+
if token.isKeyword {
28+
VariableDecl(letOrVarKeyword: .var,
29+
modifiersBuilder: { TokenSyntax.static },
30+
bindingsBuilder: {
31+
// We need to use `CodeBlock` here to ensure there is braces around.
32+
let body = CodeBlock {
33+
FunctionCallExpr("SyntaxFactory.make\(token.name)Keyword",
34+
leftParen: .leftParen,
35+
rightParen: .rightParen)
36+
37+
}
38+
39+
PatternBinding(pattern: "`\(token.name.firstLowercased)`",
40+
typeAnnotation: "TokenSyntax",
41+
initializer: nil,
42+
accessor: body)
43+
44+
})
45+
} else if token.text != nil {
46+
VariableDecl(letOrVarKeyword: .var,
47+
modifiersBuilder: { TokenSyntax.static },
48+
bindingsBuilder: {
49+
// We need to use `CodeBlock` here to ensure there is braces around.
50+
let body = CodeBlock {
51+
FunctionCallExpr("SyntaxFactory.make\(token.name)Token",
52+
leftParen: .leftParen,
53+
rightParen: .rightParen)
54+
55+
}
56+
57+
PatternBinding(pattern: "`\(token.name.firstLowercased)`",
58+
typeAnnotation: "TokenSyntax",
59+
initializer: nil,
60+
accessor: body)
61+
62+
})
63+
} else {
64+
FunctionDecl(identifier: .identifier(token.name.firstLowercased), signature: FunctionSignature(input: ParameterClause(leftParen: TokenSyntax.leftParen, parameterList: FunctionParameter(attributes: nil, firstName: .wildcard, secondName: .identifier("text"), colon: .colon, type: "String"), rightParen: .rightParen)), modifiersBuilder: { TokenSyntax.static }, bodyBuilder: {
65+
FunctionCallExpr(calledExpression: IdentifierExpr("SyntaxFactory.make\(token.name)"), leftParen: .leftParen, rightParen: .rightParen, trailingClosure: nil, argumentListBuilder: {
66+
TupleExprElement.init(expression: IdentifierExpr("text"))
67+
})
68+
})
69+
}
70+
}
71+
VariableDecl(letOrVarKeyword: .var,
72+
modifiersBuilder: { TokenSyntax.static },
73+
bindingsBuilder: {
74+
// We need to use `CodeBlock` here to ensure there is braces around.
75+
let body = CodeBlock {
76+
FunctionCallExpr("SyntaxFactory.makeToken",
77+
leftParen: .leftParen,
78+
rightParen: .rightParen,
79+
argumentListBuilder: {
80+
TupleExprElement(expression: FunctionCallExpr("eof"), trailingComma: .comma)
81+
TupleExprElement(label: TokenSyntax.identifier("presence"), colon: .colon, expression: FunctionCallExpr("present"))
82+
})
83+
84+
}
85+
86+
PatternBinding(pattern: "`eof`",
87+
typeAnnotation: "TokenSyntax",
88+
initializer: nil,
89+
accessor: body)
90+
91+
})
92+
})
93+
}
94+
95+
let tokenSyntax = tokensFile.buildSyntax(format: format)
96+
97+
let generatedPath = URL(fileURLWithPath: #file)
98+
.deletingLastPathComponent()
99+
.deletingLastPathComponent()
100+
.appendingPathComponent("SwiftSyntaxBuilder")
101+
.appendingPathComponent("gyb_generated")
102+
103+
try FileManager.default.createDirectory(atPath: generatedPath.path, withIntermediateDirectories: true, attributes: nil)
104+
105+
let filePath = generatedPath.appendingPathComponent("Tokens.swift")
106+
try tokenSyntax.description.write(to: filePath, atomically: true, encoding: .utf8)
107+
}
108+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
%{
2+
from gyb_syntax_support import *
3+
from gyb_syntax_support.Token import *
4+
from gyb_syntax_support.kinds import lowercase_first_word
5+
# -*- mode: Swift -*-
6+
# Ignore the following admonition it applies to the resulting .swift file only
7+
}%
8+
//// Automatically Generated From Tokens.swift.gyb.
9+
//// Do Not Edit Directly!
10+
//===----------------------------------------------------------------------===//
11+
//
12+
// This source file is part of the Swift.org open source project
13+
//
14+
// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
15+
// Licensed under Apache License v2.0 with Runtime Library Exception
16+
//
17+
// See https://swift.org/LICENSE.txt for license information
18+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
19+
//
20+
//===----------------------------------------------------------------------===//
21+
22+
/// Represents the specification for a Token in the TokenSyntax file.
23+
public class Token {
24+
public let name: String
25+
public let kind: String
26+
public let serializationCode: Int
27+
public let unprefixedKind: String
28+
public let text: String?
29+
public let classification: String
30+
public let isKeyword: Bool
31+
public let requiresLeadingSpace: Bool
32+
public let requiresTrailingSpace: Bool
33+
34+
public var swiftKind: String {
35+
let name = self.name
36+
37+
if isKeyword {
38+
return name + "Keyword"
39+
} else {
40+
return name
41+
}
42+
}
43+
}
44+
45+
public init(name: String, kind: String, serializationCode: Int, unprefixedKind: String? = nil, text: String? = nil, classification: String = "None", isKeyword: Bool = false, requiresLeadingSpace: Bool = false, requiresTrailingSpace: Bool = false) {
46+
self.name = name
47+
self.kind = kind
48+
self.serializationCode = serializationCode
49+
if let unprefixedKind = unprefixedKind {
50+
self.unprefixedKind = unprefixedKind
51+
} else {
52+
self.unprefixedKind = kind
53+
}
54+
self.text = text
55+
self.classification = classification
56+
self.isKeyword = isKeyword
57+
self.requiresLeadingSpace = requiresLeadingSpace
58+
self.requiresTrailingSpace = requiresTrailingSpace
59+
}
60+
}
61+
62+
/// Represents a keyword token.
63+
public class Keyword: Token {
64+
public init(name: String, serializationCode: Int, text: String, classification: String = "Keyword") {
65+
super.init(name: name, kind: "kw_\(text)", serializationCode: serializationCode, unprefixedKind: text, text: text, classification: classification, isKeyword: true, requiresTrailingSpace: true)
66+
}
67+
}
68+
69+
public class SwiftKeyword: Keyword { }
70+
71+
public class DeclKeyword: SwiftKeyword { }
72+
73+
public class StmtKeyword: SwiftKeyword { }
74+
75+
public class ExprKeyword: SwiftKeyword { }
76+
77+
public class PatternKeyword: SwiftKeyword { }
78+
79+
public class SilKeyword: Keyword { }
80+
81+
public class PoundKeyword: Token {
82+
public init(name: String, kind: String, serializationCode: Int, text: String, classification: String = "Keyword") {
83+
super.init(name: name, kind: "pound_\(kind)", serializationCode: serializationCode, unprefixedKind: kind, text: text, classification: classification, isKeyword: true, requiresTrailingSpace: true)
84+
}
85+
}
86+
87+
public class PoundObjectLiteral: PoundKeyword {
88+
public let `description`: String
89+
public let `protocol`: String
90+
91+
public init(name: String, kind: String, serializationCode: Int, text: String, classification: String = "ObjectLiteral", description: String, `protocol`: String) {
92+
self.`description` = `description`
93+
self.`protocol` = `protocol`
94+
super.init(name: name, kind: kind, serializationCode: serializationCode, text: text, classification: classification)
95+
}
96+
}
97+
98+
public class PoundConfig: PoundKeyword { }
99+
100+
public class PoundDirectiveKeyword: PoundKeyword {
101+
public override init(name: String, kind: String, serializationCode: Int, text: String, classification: String = "PoundDirectiveKeyword") {
102+
super.init(name: name, kind: kind, serializationCode: serializationCode, text: text, classification: classification)
103+
}
104+
}
105+
106+
public class PoundConditionalDirectiveKeyword: PoundDirectiveKeyword {
107+
public override init(name: String, kind: String, serializationCode: Int, text: String, classification: String = "PoundDirectiveKeyword") {
108+
super.init(name: name, kind: kind, serializationCode: serializationCode, text: text, classification: classification)
109+
}
110+
}
111+
112+
public class Punctuator: Token { }
113+
114+
public class Literal: Token { }
115+
116+
public class Misc: Token { }
117+
118+
let SYNTAX_TOKENS: [Token] = [
119+
% for token in SYNTAX_TOKENS:
120+
% class_name = type(token).__name__
121+
% text = 'text: "' + token.text + '", ' if token.text else ""
122+
% if class_name in ['Keyword', 'SwiftKeyword', 'DeclKeyword', 'StmtKeyword', 'ExprKeyword', 'PatternKeyword', 'SilKeyword']:
123+
${class_name}(name: "${token.name}", serializationCode: ${token.serialization_code}, text: "${token.text}"),
124+
% elif class_name in ['PoundKeyword', 'PoundConfig', 'PoundDirectiveKeyword', 'PoundConditionalDirectiveKeyword']:
125+
${class_name}(name: "${token.name}", kind: "${token.kind}", serializationCode: ${token.serialization_code}, text: "${token.text}"),
126+
% elif class_name in ['Punctuator', 'Misc']:
127+
${class_name}(name: "${token.name}", kind: "${token.kind}", serializationCode: ${token.serialization_code}, ${text}requiresLeadingSpace: ${str(token.requires_leading_space).lower()}, requiresTrailingSpace: ${str(token.requires_trailing_space).lower()}),
128+
% elif class_name is 'Literal':
129+
${class_name}(name: "${token.name}", kind: "${token.kind}", serializationCode: ${token.serialization_code}),
130+
% elif class_name is 'PoundObjectLiteral':
131+
${class_name}(name: "${token.name}", kind: "${token.kind}", serializationCode: ${token.serialization_code}, ${text}description: "${token.description}", protocol: "${token.protocol}"),
132+
% else:
133+
fatalError("Unknown token `${class_name}`")
134+
% end
135+
% end
136+
]

0 commit comments

Comments
 (0)