Skip to content

Commit 3bd4e76

Browse files
WIT: Add @since, @unstable, and @deprecated attr support
1 parent a70249a commit 3bd4e76

12 files changed

+284
-66
lines changed

Sources/WIT/AST.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,15 @@ public struct PackageNameSyntax: Equatable, Hashable, CustomStringConvertible {
6868
}
6969

7070
public struct TopLevelUseSyntax: Equatable, Hashable, SyntaxNodeProtocol {
71+
var attributes: [AttributeSyntax]
7172
var item: UsePathSyntax
7273
var asName: Identifier?
7374
}
7475

7576
public struct WorldSyntax: Equatable, Hashable, SyntaxNodeProtocol {
7677
public typealias Parent = SourceFileSyntax
7778
public var documents: DocumentsSyntax
79+
public var attributes: [AttributeSyntax]
7880
public var name: Identifier
7981
public var items: [WorldItemSyntax]
8082
}
@@ -89,11 +91,13 @@ public enum WorldItemSyntax: Equatable, Hashable {
8991

9092
public struct ImportSyntax: Equatable, Hashable {
9193
public var documents: DocumentsSyntax
94+
public var attributes: [AttributeSyntax]
9295
public var kind: ExternKindSyntax
9396
}
9497

9598
public struct ExportSyntax: Equatable, Hashable {
9699
public var documents: DocumentsSyntax
100+
public var attributes: [AttributeSyntax]
97101
public var kind: ExternKindSyntax
98102
}
99103

@@ -105,6 +109,7 @@ public enum ExternKindSyntax: Equatable, Hashable {
105109

106110
public struct InterfaceSyntax: Equatable, Hashable, CustomStringConvertible, SyntaxNodeProtocol {
107111
public var documents: DocumentsSyntax
112+
public var attributes: [AttributeSyntax]
108113
public var name: Identifier
109114
public var items: [InterfaceItemSyntax]
110115

@@ -121,6 +126,7 @@ public enum InterfaceItemSyntax: Equatable, Hashable, SyntaxNodeProtocol {
121126

122127
public struct TypeDefSyntax: Equatable, Hashable, SyntaxNodeProtocol {
123128
public var documents: DocumentsSyntax
129+
public var attributes: [AttributeSyntax]
124130
public var name: Identifier
125131
public var body: TypeDefBodySyntax
126132
}
@@ -240,6 +246,7 @@ public struct StreamSyntax: Equatable, Hashable {
240246

241247
public struct NamedFunctionSyntax: Equatable, Hashable, SyntaxNodeProtocol {
242248
public var documents: DocumentsSyntax
249+
public var attributes: [AttributeSyntax]
243250
public var name: Identifier
244251
public var function: FunctionSyntax
245252
}
@@ -281,6 +288,7 @@ public struct FunctionSyntax: Equatable, Hashable {
281288
}
282289

283290
public struct UseSyntax: Equatable, Hashable, SyntaxNodeProtocol {
291+
public var attributes: [AttributeSyntax]
284292
public var from: UsePathSyntax
285293
public var names: [UseNameSyntax]
286294
}
@@ -303,6 +311,7 @@ public struct UseNameSyntax: Equatable, Hashable {
303311
}
304312

305313
public struct IncludeSyntax: Equatable, Hashable {
314+
var attributes: [AttributeSyntax]
306315
var from: UsePathSyntax
307316
var names: [IncludeNameSyntax]
308317
}
@@ -324,3 +333,25 @@ public struct Identifier: Equatable, Hashable, CustomStringConvertible {
324333
public struct DocumentsSyntax: Equatable, Hashable {
325334
var comments: [String]
326335
}
336+
337+
public enum AttributeSyntax: Equatable, Hashable {
338+
case since(SinceAttributeSyntax)
339+
case unstable(UnstableAttributeSyntax)
340+
case deprecated(DeprecatedAttributeSyntax)
341+
}
342+
343+
public struct SinceAttributeSyntax: Equatable, Hashable {
344+
let version: Version
345+
let feature: Identifier?
346+
let textRange: TextRange
347+
}
348+
349+
public struct UnstableAttributeSyntax: Equatable, Hashable {
350+
let textRange: TextRange
351+
let feature: Identifier
352+
}
353+
354+
public struct DeprecatedAttributeSyntax: Equatable, Hashable {
355+
let textRange: TextRange
356+
let version: Version
357+
}

Sources/WIT/Lexer.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ struct Lexer {
4444
}
4545

4646
var cursor: Cursor
47+
let requireSemicolon: Bool
48+
49+
init(cursor: Cursor, requireSemicolon: Bool = false) {
50+
self.cursor = cursor
51+
self.requireSemicolon = requireSemicolon
52+
}
4753

4854
mutating func advanceToEndOfBlockComment() -> Diagnostic? {
4955
var depth = 1
@@ -245,6 +251,24 @@ struct Lexer {
245251
return actual
246252
}
247253

254+
@discardableResult
255+
mutating func expectIdentifier(_ expected: String) throws -> Lexer.Lexeme {
256+
let lexme = try self.expect(.id)
257+
let actualText = self.parseText(in: lexme.textRange)
258+
guard actualText == expected else {
259+
throw ParseError(description: "\(expected) expected but got \(actualText)")
260+
}
261+
return lexme
262+
}
263+
264+
mutating func expectSemicolon() throws {
265+
if self.requireSemicolon {
266+
try self.expect(.semicolon)
267+
} else {
268+
self.eat(.semicolon)
269+
}
270+
}
271+
248272
@discardableResult
249273
mutating func eat(_ expected: TokenKind) -> Bool {
250274
var other = self

Sources/WIT/TextParser/ParseFunctionDecl.swift

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
extension ResourceFunctionSyntax {
2-
static func parse(lexer: inout Lexer, documents: DocumentsSyntax) throws -> ResourceFunctionSyntax {
2+
static func parse(
3+
lexer: inout Lexer,
4+
documents: DocumentsSyntax,
5+
attributes: [AttributeSyntax]
6+
) throws -> ResourceFunctionSyntax {
37
guard let token = lexer.peek() else {
48
throw ParseError(description: "`constructor` or identifier expected but got nothing")
59
}
@@ -18,10 +22,12 @@ extension ResourceFunctionSyntax {
1822
let type = try TypeReprSyntax.parse(lexer: &lexer)
1923
return ParameterSyntax(name: name, type: type, textRange: start..<lexer.cursor.nextIndex)
2024
}
25+
try lexer.expectSemicolon()
2126
return .constructor(
2227
.init(
2328
syntax: NamedFunctionSyntax(
2429
documents: documents,
30+
attributes: attributes,
2531
name: Identifier(text: "constructor", textRange: token.textRange),
2632
function: FunctionSyntax(
2733
parameters: params,
@@ -41,11 +47,13 @@ extension ResourceFunctionSyntax {
4147
ctor = ResourceFunctionSyntax.method
4248
}
4349
let function = try FunctionSyntax.parse(lexer: &lexer)
50+
try lexer.expectSemicolon()
4451
return ctor(
4552
.init(
4653
syntax:
4754
NamedFunctionSyntax(
4855
documents: documents,
56+
attributes: attributes,
4957
name: name,
5058
function: function
5159
)
@@ -95,6 +103,7 @@ extension NamedFunctionSyntax {
95103
let name = try Identifier.parse(lexer: &lexer)
96104
try lexer.expect(.colon)
97105
let function = try FunctionSyntax.parse(lexer: &lexer)
98-
return .init(syntax: NamedFunctionSyntax(documents: documents, name: name, function: function))
106+
try lexer.expectSemicolon()
107+
return .init(syntax: NamedFunctionSyntax(documents: documents, attributes: [], name: name, function: function))
99108
}
100109
}

Sources/WIT/TextParser/ParseInterface.swift

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
extension InterfaceSyntax {
22
static func parse(
3-
lexer: inout Lexer, documents: DocumentsSyntax
3+
lexer: inout Lexer, documents: DocumentsSyntax, attributes: [AttributeSyntax]
44
) throws -> SyntaxNode<InterfaceSyntax> {
55
try lexer.expect(.interface)
66
let name = try Identifier.parse(lexer: &lexer)
77
let items = try parseItems(lexer: &lexer)
8-
return .init(syntax: InterfaceSyntax(documents: documents, name: name, items: items))
8+
return .init(syntax: InterfaceSyntax(
9+
documents: documents, attributes: attributes, name: name, items: items
10+
))
911
}
1012

1113
static func parseItems(lexer: inout Lexer) throws -> [InterfaceItemSyntax] {
@@ -16,29 +18,30 @@ extension InterfaceSyntax {
1618
if lexer.eat(.rightBrace) {
1719
break
1820
}
19-
items.append(try InterfaceItemSyntax.parse(lexer: &lexer, documents: docs))
21+
let attributes = try AttributeSyntax.parseItems(lexer: &lexer)
22+
items.append(try InterfaceItemSyntax.parse(lexer: &lexer, documents: docs, attributes: attributes))
2023
}
2124
return items
2225
}
2326
}
2427

2528
extension InterfaceItemSyntax {
26-
static func parse(lexer: inout Lexer, documents: DocumentsSyntax) throws -> InterfaceItemSyntax {
29+
static func parse(lexer: inout Lexer, documents: DocumentsSyntax, attributes: [AttributeSyntax]) throws -> InterfaceItemSyntax {
2730
switch lexer.peek()?.kind {
2831
case .type:
29-
return try .typeDef(.init(syntax: .parse(lexer: &lexer, documents: documents)))
32+
return try .typeDef(.init(syntax: .parse(lexer: &lexer, documents: documents, attributes: attributes)))
3033
case .flags:
31-
return try .typeDef(.init(syntax: .parseFlags(lexer: &lexer, documents: documents)))
34+
return try .typeDef(.init(syntax: .parseFlags(lexer: &lexer, documents: documents, attributes: attributes)))
3235
case .enum:
33-
return try .typeDef(.init(syntax: .parseEnum(lexer: &lexer, documents: documents)))
36+
return try .typeDef(.init(syntax: .parseEnum(lexer: &lexer, documents: documents, attributes: attributes)))
3437
case .variant:
35-
return try .typeDef(.init(syntax: .parseVariant(lexer: &lexer, documents: documents)))
38+
return try .typeDef(.init(syntax: .parseVariant(lexer: &lexer, documents: documents, attributes: attributes)))
3639
case .resource:
37-
return try .typeDef(TypeDefSyntax.parseResource(lexer: &lexer, documents: documents))
40+
return try .typeDef(TypeDefSyntax.parseResource(lexer: &lexer, documents: documents, attributes: attributes))
3841
case .record:
39-
return try .typeDef(.init(syntax: .parseRecord(lexer: &lexer, documents: documents)))
42+
return try .typeDef(.init(syntax: .parseRecord(lexer: &lexer, documents: documents, attributes: attributes)))
4043
case .union:
41-
return try .typeDef(.init(syntax: .parseUnion(lexer: &lexer, documents: documents)))
44+
return try .typeDef(.init(syntax: .parseUnion(lexer: &lexer, documents: documents, attributes: attributes)))
4245
case .id, .explicitId:
4346
return try .function(NamedFunctionSyntax.parse(lexer: &lexer, documents: documents))
4447
case .use:

Sources/WIT/TextParser/ParseTop.swift

Lines changed: 86 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ extension SourceFileSyntax {
33
var packageId: PackageNameSyntax?
44
if lexer.peek()?.kind == .package {
55
packageId = try PackageNameSyntax.parse(lexer: &lexer)
6+
try lexer.expectSemicolon()
67
}
78

89
var items: [ASTItemSyntax] = []
@@ -136,27 +137,38 @@ extension ASTItemSyntax {
136137
static func parse(
137138
lexer: inout Lexer, documents: DocumentsSyntax
138139
) throws -> ASTItemSyntax {
140+
let attributes = try AttributeSyntax.parseItems(lexer: &lexer)
139141
switch lexer.peek()?.kind {
140142
case .interface:
141-
return try .interface(InterfaceSyntax.parse(lexer: &lexer, documents: documents))
143+
return try .interface(InterfaceSyntax.parse(
144+
lexer: &lexer, documents: documents, attributes: attributes
145+
))
142146
case .world:
143-
return try .world(WorldSyntax.parse(lexer: &lexer, documents: documents))
144-
case .use: return try .use(.init(syntax: .parse(lexer: &lexer, documents: documents)))
147+
return try .world(WorldSyntax.parse(
148+
lexer: &lexer, documents: documents, attributes: attributes
149+
))
150+
case .use: return try .use(.init(syntax: .parse(
151+
lexer: &lexer, documents: documents, attributes: attributes
152+
)))
145153
default:
146154
throw ParseError(description: "`world`, `interface` or `use` expected")
147155
}
148156
}
149157
}
150158

151159
extension TopLevelUseSyntax {
152-
static func parse(lexer: inout Lexer, documents: DocumentsSyntax) throws -> TopLevelUseSyntax {
160+
static func parse(
161+
lexer: inout Lexer,
162+
documents: DocumentsSyntax, attributes: [AttributeSyntax]
163+
) throws -> TopLevelUseSyntax {
153164
try lexer.expect(.use)
154165
let item = try UsePathSyntax.parse(lexer: &lexer)
155166
var asName: Identifier?
156167
if lexer.eat(.as) {
157168
asName = try .parse(lexer: &lexer)
158169
}
159-
return TopLevelUseSyntax(item: item, asName: asName)
170+
try lexer.expectSemicolon()
171+
return TopLevelUseSyntax(attributes: attributes, item: item, asName: asName)
160172
}
161173
}
162174

@@ -179,7 +191,8 @@ extension UseSyntax {
179191
break
180192
}
181193
}
182-
return .init(syntax: UseSyntax(from: from, names: names))
194+
try lexer.expectSemicolon()
195+
return .init(syntax: UseSyntax(attributes: [], from: from, names: names))
183196
}
184197
}
185198

@@ -222,3 +235,70 @@ extension DocumentsSyntax {
222235
return DocumentsSyntax(comments: comments)
223236
}
224237
}
238+
239+
extension AttributeSyntax {
240+
static func parseItems(lexer: inout Lexer) throws -> [AttributeSyntax] {
241+
var items: [AttributeSyntax] = []
242+
while lexer.eat(.at) {
243+
let id = try Identifier.parse(lexer: &lexer)
244+
let item: AttributeSyntax
245+
switch id.text {
246+
case "since": item = .since(try .parse(lexer: &lexer, id: id))
247+
case "unstable": item = .unstable(try .parse(lexer: &lexer, id: id))
248+
case "deprecated": item = .deprecated(try .parse(lexer: &lexer, id: id))
249+
default:
250+
throw ParseError(description: "Unexpected attribute: \(id.text)")
251+
}
252+
items.append(item)
253+
}
254+
return items
255+
}
256+
}
257+
258+
extension SinceAttributeSyntax {
259+
static func parse(lexer: inout Lexer, id: Identifier) throws -> SinceAttributeSyntax {
260+
try lexer.expect(.leftParen)
261+
try lexer.expectIdentifier("version")
262+
try lexer.expect(.equals)
263+
let version = try Version.parse(lexer: &lexer)
264+
var feature: Identifier?
265+
if lexer.eat(.comma) {
266+
try lexer.expectIdentifier("feature")
267+
try lexer.expect(.equals)
268+
feature = try Identifier.parse(lexer: &lexer)
269+
}
270+
try lexer.expect(.rightParen)
271+
return SinceAttributeSyntax(
272+
version: version, feature: feature,
273+
textRange: id.textRange.lowerBound..<lexer.cursor.nextIndex
274+
)
275+
}
276+
}
277+
278+
extension UnstableAttributeSyntax {
279+
static func parse(lexer: inout Lexer, id: Identifier) throws -> UnstableAttributeSyntax {
280+
try lexer.expect(.leftParen)
281+
try lexer.expectIdentifier("feature")
282+
try lexer.expect(.equals)
283+
let feature = try Identifier.parse(lexer: &lexer)
284+
try lexer.expect(.rightParen)
285+
return UnstableAttributeSyntax(
286+
textRange: id.textRange.lowerBound..<lexer.cursor.nextIndex,
287+
feature: feature
288+
)
289+
}
290+
}
291+
292+
extension DeprecatedAttributeSyntax {
293+
static func parse(lexer: inout Lexer, id: Identifier) throws -> DeprecatedAttributeSyntax {
294+
try lexer.expect(.leftParen)
295+
try lexer.expectIdentifier("version")
296+
try lexer.expect(.equals)
297+
let version = try Version.parse(lexer: &lexer)
298+
try lexer.expect(.rightParen)
299+
return DeprecatedAttributeSyntax(
300+
textRange: id.textRange.lowerBound..<lexer.cursor.nextIndex,
301+
version: version
302+
)
303+
}
304+
}

0 commit comments

Comments
 (0)