Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIT: Add @since, @unstable, and @deprecated attr support #163

Merged
merged 1 commit into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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