Skip to content

Commit

Permalink
WIT: Add @since, @unstable, and @deprecated attr support
Browse files Browse the repository at this point in the history
  • Loading branch information
kateinoigakukun committed Nov 20, 2024
1 parent a70249a commit dffe511
Show file tree
Hide file tree
Showing 12 changed files with 291 additions and 66 deletions.
31 changes: 31 additions & 0 deletions Sources/WIT/AST.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,15 @@ public struct PackageNameSyntax: Equatable, Hashable, CustomStringConvertible {
}

public struct TopLevelUseSyntax: Equatable, Hashable, SyntaxNodeProtocol {
var attributes: [AttributeSyntax]
var item: UsePathSyntax
var asName: Identifier?
}

public struct WorldSyntax: Equatable, Hashable, SyntaxNodeProtocol {
public typealias Parent = SourceFileSyntax
public var documents: DocumentsSyntax
public var attributes: [AttributeSyntax]
public var name: Identifier
public var items: [WorldItemSyntax]
}
Expand All @@ -89,11 +91,13 @@ public enum WorldItemSyntax: Equatable, Hashable {

public struct ImportSyntax: Equatable, Hashable {
public var documents: DocumentsSyntax
public var attributes: [AttributeSyntax]
public var kind: ExternKindSyntax
}

public struct ExportSyntax: Equatable, Hashable {
public var documents: DocumentsSyntax
public var attributes: [AttributeSyntax]
public var kind: ExternKindSyntax
}

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

public struct InterfaceSyntax: Equatable, Hashable, CustomStringConvertible, SyntaxNodeProtocol {
public var documents: DocumentsSyntax
public var attributes: [AttributeSyntax]
public var name: Identifier
public var items: [InterfaceItemSyntax]

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

public struct TypeDefSyntax: Equatable, Hashable, SyntaxNodeProtocol {
public var documents: DocumentsSyntax
public var attributes: [AttributeSyntax]
public var name: Identifier
public var body: TypeDefBodySyntax
}
Expand Down Expand Up @@ -240,6 +246,7 @@ public struct StreamSyntax: Equatable, Hashable {

public struct NamedFunctionSyntax: Equatable, Hashable, SyntaxNodeProtocol {
public var documents: DocumentsSyntax
public var attributes: [AttributeSyntax]
public var name: Identifier
public var function: FunctionSyntax
}
Expand Down Expand Up @@ -281,6 +288,7 @@ public struct FunctionSyntax: Equatable, Hashable {
}

public struct UseSyntax: Equatable, Hashable, SyntaxNodeProtocol {
public var attributes: [AttributeSyntax]
public var from: UsePathSyntax
public var names: [UseNameSyntax]
}
Expand All @@ -303,6 +311,7 @@ public struct UseNameSyntax: Equatable, Hashable {
}

public struct IncludeSyntax: Equatable, Hashable {
var attributes: [AttributeSyntax]
var from: UsePathSyntax
var names: [IncludeNameSyntax]
}
Expand All @@ -324,3 +333,25 @@ public struct Identifier: Equatable, Hashable, CustomStringConvertible {
public struct DocumentsSyntax: Equatable, Hashable {
var comments: [String]
}

public enum AttributeSyntax: Equatable, Hashable {
case since(SinceAttributeSyntax)
case unstable(UnstableAttributeSyntax)
case deprecated(DeprecatedAttributeSyntax)
}

public struct SinceAttributeSyntax: Equatable, Hashable {
let version: Version
let feature: Identifier?
let textRange: TextRange
}

public struct UnstableAttributeSyntax: Equatable, Hashable {
let textRange: TextRange
let feature: Identifier
}

public struct DeprecatedAttributeSyntax: Equatable, Hashable {
let textRange: TextRange
let version: Version
}
24 changes: 24 additions & 0 deletions Sources/WIT/Lexer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ struct Lexer {
}

var cursor: Cursor
let requireSemicolon: Bool

init(cursor: Cursor, requireSemicolon: Bool = false) {
self.cursor = cursor
self.requireSemicolon = requireSemicolon
}

mutating func advanceToEndOfBlockComment() -> Diagnostic? {
var depth = 1
Expand Down Expand Up @@ -245,6 +251,24 @@ struct Lexer {
return actual
}

@discardableResult
mutating func expectIdentifier(_ expected: String) throws -> Lexer.Lexeme {
let lexme = try self.expect(.id)
let actualText = self.parseText(in: lexme.textRange)
guard actualText == expected else {
throw ParseError(description: "\(expected) expected but got \(actualText)")
}
return lexme
}

mutating func expectSemicolon() throws {
if self.requireSemicolon {
try self.expect(.semicolon)
} else {
self.eat(.semicolon)
}
}

@discardableResult
mutating func eat(_ expected: TokenKind) -> Bool {
var other = self
Expand Down
13 changes: 11 additions & 2 deletions Sources/WIT/TextParser/ParseFunctionDecl.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
extension ResourceFunctionSyntax {
static func parse(lexer: inout Lexer, documents: DocumentsSyntax) throws -> ResourceFunctionSyntax {
static func parse(
lexer: inout Lexer,
documents: DocumentsSyntax,
attributes: [AttributeSyntax]
) throws -> ResourceFunctionSyntax {
guard let token = lexer.peek() else {
throw ParseError(description: "`constructor` or identifier expected but got nothing")
}
Expand All @@ -18,10 +22,12 @@ extension ResourceFunctionSyntax {
let type = try TypeReprSyntax.parse(lexer: &lexer)
return ParameterSyntax(name: name, type: type, textRange: start..<lexer.cursor.nextIndex)
}
try lexer.expectSemicolon()
return .constructor(
.init(
syntax: NamedFunctionSyntax(
documents: documents,
attributes: attributes,
name: Identifier(text: "constructor", textRange: token.textRange),
function: FunctionSyntax(
parameters: params,
Expand All @@ -41,11 +47,13 @@ extension ResourceFunctionSyntax {
ctor = ResourceFunctionSyntax.method
}
let function = try FunctionSyntax.parse(lexer: &lexer)
try lexer.expectSemicolon()
return ctor(
.init(
syntax:
NamedFunctionSyntax(
documents: documents,
attributes: attributes,
name: name,
function: function
)
Expand Down Expand Up @@ -95,6 +103,7 @@ extension NamedFunctionSyntax {
let name = try Identifier.parse(lexer: &lexer)
try lexer.expect(.colon)
let function = try FunctionSyntax.parse(lexer: &lexer)
return .init(syntax: NamedFunctionSyntax(documents: documents, name: name, function: function))
try lexer.expectSemicolon()
return .init(syntax: NamedFunctionSyntax(documents: documents, attributes: [], name: name, function: function))
}
}
26 changes: 15 additions & 11 deletions Sources/WIT/TextParser/ParseInterface.swift
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
extension InterfaceSyntax {
static func parse(
lexer: inout Lexer, documents: DocumentsSyntax
lexer: inout Lexer, documents: DocumentsSyntax, attributes: [AttributeSyntax]
) throws -> SyntaxNode<InterfaceSyntax> {
try lexer.expect(.interface)
let name = try Identifier.parse(lexer: &lexer)
let items = try parseItems(lexer: &lexer)
return .init(syntax: InterfaceSyntax(documents: documents, name: name, items: items))
return .init(
syntax: InterfaceSyntax(
documents: documents, attributes: attributes, name: name, items: items
))
}

static func parseItems(lexer: inout Lexer) throws -> [InterfaceItemSyntax] {
Expand All @@ -16,29 +19,30 @@ extension InterfaceSyntax {
if lexer.eat(.rightBrace) {
break
}
items.append(try InterfaceItemSyntax.parse(lexer: &lexer, documents: docs))
let attributes = try AttributeSyntax.parseItems(lexer: &lexer)
items.append(try InterfaceItemSyntax.parse(lexer: &lexer, documents: docs, attributes: attributes))
}
return items
}
}

extension InterfaceItemSyntax {
static func parse(lexer: inout Lexer, documents: DocumentsSyntax) throws -> InterfaceItemSyntax {
static func parse(lexer: inout Lexer, documents: DocumentsSyntax, attributes: [AttributeSyntax]) throws -> InterfaceItemSyntax {
switch lexer.peek()?.kind {
case .type:
return try .typeDef(.init(syntax: .parse(lexer: &lexer, documents: documents)))
return try .typeDef(.init(syntax: .parse(lexer: &lexer, documents: documents, attributes: attributes)))
case .flags:
return try .typeDef(.init(syntax: .parseFlags(lexer: &lexer, documents: documents)))
return try .typeDef(.init(syntax: .parseFlags(lexer: &lexer, documents: documents, attributes: attributes)))
case .enum:
return try .typeDef(.init(syntax: .parseEnum(lexer: &lexer, documents: documents)))
return try .typeDef(.init(syntax: .parseEnum(lexer: &lexer, documents: documents, attributes: attributes)))
case .variant:
return try .typeDef(.init(syntax: .parseVariant(lexer: &lexer, documents: documents)))
return try .typeDef(.init(syntax: .parseVariant(lexer: &lexer, documents: documents, attributes: attributes)))
case .resource:
return try .typeDef(TypeDefSyntax.parseResource(lexer: &lexer, documents: documents))
return try .typeDef(TypeDefSyntax.parseResource(lexer: &lexer, documents: documents, attributes: attributes))
case .record:
return try .typeDef(.init(syntax: .parseRecord(lexer: &lexer, documents: documents)))
return try .typeDef(.init(syntax: .parseRecord(lexer: &lexer, documents: documents, attributes: attributes)))
case .union:
return try .typeDef(.init(syntax: .parseUnion(lexer: &lexer, documents: documents)))
return try .typeDef(.init(syntax: .parseUnion(lexer: &lexer, documents: documents, attributes: attributes)))
case .id, .explicitId:
return try .function(NamedFunctionSyntax.parse(lexer: &lexer, documents: documents))
case .use:
Expand Down
97 changes: 91 additions & 6 deletions Sources/WIT/TextParser/ParseTop.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ extension SourceFileSyntax {
var packageId: PackageNameSyntax?
if lexer.peek()?.kind == .package {
packageId = try PackageNameSyntax.parse(lexer: &lexer)
try lexer.expectSemicolon()
}

var items: [ASTItemSyntax] = []
Expand Down Expand Up @@ -136,27 +137,43 @@ extension ASTItemSyntax {
static func parse(
lexer: inout Lexer, documents: DocumentsSyntax
) throws -> ASTItemSyntax {
let attributes = try AttributeSyntax.parseItems(lexer: &lexer)
switch lexer.peek()?.kind {
case .interface:
return try .interface(InterfaceSyntax.parse(lexer: &lexer, documents: documents))
return try .interface(
InterfaceSyntax.parse(
lexer: &lexer, documents: documents, attributes: attributes
))
case .world:
return try .world(WorldSyntax.parse(lexer: &lexer, documents: documents))
case .use: return try .use(.init(syntax: .parse(lexer: &lexer, documents: documents)))
return try .world(
WorldSyntax.parse(
lexer: &lexer, documents: documents, attributes: attributes
))
case .use:
return try .use(
.init(
syntax: .parse(
lexer: &lexer, documents: documents, attributes: attributes
)))
default:
throw ParseError(description: "`world`, `interface` or `use` expected")
}
}
}

extension TopLevelUseSyntax {
static func parse(lexer: inout Lexer, documents: DocumentsSyntax) throws -> TopLevelUseSyntax {
static func parse(
lexer: inout Lexer,
documents: DocumentsSyntax, attributes: [AttributeSyntax]
) throws -> TopLevelUseSyntax {
try lexer.expect(.use)
let item = try UsePathSyntax.parse(lexer: &lexer)
var asName: Identifier?
if lexer.eat(.as) {
asName = try .parse(lexer: &lexer)
}
return TopLevelUseSyntax(item: item, asName: asName)
try lexer.expectSemicolon()
return TopLevelUseSyntax(attributes: attributes, item: item, asName: asName)
}
}

Expand All @@ -179,7 +196,8 @@ extension UseSyntax {
break
}
}
return .init(syntax: UseSyntax(from: from, names: names))
try lexer.expectSemicolon()
return .init(syntax: UseSyntax(attributes: [], from: from, names: names))
}
}

Expand Down Expand Up @@ -222,3 +240,70 @@ extension DocumentsSyntax {
return DocumentsSyntax(comments: comments)
}
}

extension AttributeSyntax {
static func parseItems(lexer: inout Lexer) throws -> [AttributeSyntax] {
var items: [AttributeSyntax] = []
while lexer.eat(.at) {
let id = try Identifier.parse(lexer: &lexer)
let item: AttributeSyntax
switch id.text {
case "since": item = .since(try .parse(lexer: &lexer, id: id))
case "unstable": item = .unstable(try .parse(lexer: &lexer, id: id))
case "deprecated": item = .deprecated(try .parse(lexer: &lexer, id: id))
default:
throw ParseError(description: "Unexpected attribute: \(id.text)")
}
items.append(item)
}
return items
}
}

extension SinceAttributeSyntax {
static func parse(lexer: inout Lexer, id: Identifier) throws -> SinceAttributeSyntax {
try lexer.expect(.leftParen)
try lexer.expectIdentifier("version")
try lexer.expect(.equals)
let version = try Version.parse(lexer: &lexer)
var feature: Identifier?
if lexer.eat(.comma) {
try lexer.expectIdentifier("feature")
try lexer.expect(.equals)
feature = try Identifier.parse(lexer: &lexer)
}
try lexer.expect(.rightParen)
return SinceAttributeSyntax(
version: version, feature: feature,
textRange: id.textRange.lowerBound..<lexer.cursor.nextIndex
)
}
}

extension UnstableAttributeSyntax {
static func parse(lexer: inout Lexer, id: Identifier) throws -> UnstableAttributeSyntax {
try lexer.expect(.leftParen)
try lexer.expectIdentifier("feature")
try lexer.expect(.equals)
let feature = try Identifier.parse(lexer: &lexer)
try lexer.expect(.rightParen)
return UnstableAttributeSyntax(
textRange: id.textRange.lowerBound..<lexer.cursor.nextIndex,
feature: feature
)
}
}

extension DeprecatedAttributeSyntax {
static func parse(lexer: inout Lexer, id: Identifier) throws -> DeprecatedAttributeSyntax {
try lexer.expect(.leftParen)
try lexer.expectIdentifier("version")
try lexer.expect(.equals)
let version = try Version.parse(lexer: &lexer)
try lexer.expect(.rightParen)
return DeprecatedAttributeSyntax(
textRange: id.textRange.lowerBound..<lexer.cursor.nextIndex,
version: version
)
}
}
Loading

0 comments on commit dffe511

Please sign in to comment.