Skip to content
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
6 changes: 4 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ let package = Package(
dependencies: ["_CAsyncSequenceValidationSupport"],
swiftSettings: [
.unsafeFlags([
"-Xfrontend", "-disable-availability-checking"
"-Xfrontend", "-disable-availability-checking",
"-Xfrontend", "-enable-experimental-pairwise-build-block"
])
]),
.systemLibrary(name: "_CAsyncSequenceValidationSupport"),
Expand All @@ -40,7 +41,8 @@ let package = Package(
dependencies: ["AsyncAlgorithms", "AsyncSequenceValidation"],
swiftSettings: [
.unsafeFlags([
"-Xfrontend", "-disable-availability-checking"
"-Xfrontend", "-disable-availability-checking",
"-Xfrontend", "-enable-experimental-pairwise-build-block"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is now available as of the 3/9 toolchain

])
]),
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,49 +13,46 @@ import _CAsyncSequenceValidationSupport

@resultBuilder
public struct AsyncSequenceValidationDiagram : Sendable {
public static func buildBlock<Operation: AsyncSequence>(
_ sequence: Operation,
_ output: String
) -> some AsyncSequenceValidationTest where Operation.Element == String {
return Test(inputs: [], sequence: sequence, output: output)
public struct Component<T> {
var component: T
var location: SourceLocation
}

public static func buildBlock<Operation: AsyncSequence>(
_ input: String,
_ sequence: Operation,
_ output: String
) -> some AsyncSequenceValidationTest where Operation.Element == String {
return Test(inputs: [input], sequence: sequence, output: output)
public struct AccumulatedInputs {
var inputs: [Specification] = []
}

public static func buildBlock<Operation: AsyncSequence>(
_ input1: String,
_ input2: String,
_ sequence: Operation,
_ output: String
) -> some AsyncSequenceValidationTest where Operation.Element == String {
Test(inputs: [input1, input2], sequence: sequence, output: output)
public struct AccumulatedInputsWithOperation<Operation: AsyncSequence> where Operation.Element == String {
var inputs: [Specification]
var operation: Operation
}

public static func buildBlock<Operation: AsyncSequence>(
_ input1: String,
_ input2: String,
_ input3: String,
_ sequence: Operation,
_ output: String
) -> some AsyncSequenceValidationTest where Operation.Element == String {
Test(inputs: [input1, input2, input3], sequence: sequence, output: output)
public static func buildExpression(_ expr: String, file: StaticString = #file, line: UInt = #line) -> Component<String> {
Component(component: expr, location: SourceLocation(file: file, line: line))
}

public static func buildBlock<Operation: AsyncSequence>(
_ input1: String,
_ input2: String,
_ input3: String,
_ input4: String,
_ sequence: Operation,
_ output: String
) -> some AsyncSequenceValidationTest where Operation.Element == String {
Test(inputs: [input1, input2, input3, input4], sequence: sequence, output: output)

public static func buildExpression<S: AsyncSequence>(_ expr: S, file: StaticString = #file, line: UInt = #line) -> Component<S> {
Component(component: expr, location: SourceLocation(file: file, line: line))
}

public static func buildPartialBlock(first input: Component<String>) -> AccumulatedInputs {
return AccumulatedInputs(inputs: [Specification(specification: input.component, location: input.location)])
}

public static func buildPartialBlock<Operation: AsyncSequence>(first operation: Component<Operation>) -> AccumulatedInputsWithOperation<Operation> where Operation.Element == String {
return AccumulatedInputsWithOperation(inputs: [], operation: operation.component)
}

public static func buildPartialBlock(accumulated: AccumulatedInputs, next input: Component<String>) -> AccumulatedInputs {
return AccumulatedInputs(inputs: accumulated.inputs + [Specification(specification: input.component, location: input.location)])
}

public static func buildPartialBlock<Operation: AsyncSequence>(accumulated: AccumulatedInputs, next operation: Component<Operation>) -> AccumulatedInputsWithOperation<Operation> {
return AccumulatedInputsWithOperation(inputs: accumulated.inputs, operation: operation.component)
}

public static func buildPartialBlock<Operation: AsyncSequence>(accumulated: AccumulatedInputsWithOperation<Operation>, next output: Component<String>) -> some AsyncSequenceValidationTest {
return Test(inputs: accumulated.inputs, sequence: accumulated.operation, output: Specification(specification: output.component, location: output.location))
}

let queue: WorkQueue
Expand Down
26 changes: 17 additions & 9 deletions Sources/AsyncSequenceValidation/Event.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,18 @@
extension AsyncSequenceValidationDiagram {
struct Failure: Error, Equatable { }

enum ParseFailure: Error, CustomStringConvertible {
case stepInGroup(String, String.Index)
case nestedGroup(String, String.Index)
case unbalancedNesting(String, String.Index)
enum ParseFailure: Error, CustomStringConvertible, SourceFailure {
case stepInGroup(String, String.Index, SourceLocation)
case nestedGroup(String, String.Index, SourceLocation)
case unbalancedNesting(String, String.Index, SourceLocation)

var location: SourceLocation {
switch self {
case .stepInGroup(_, _, let location): return location
case .nestedGroup(_, _, let location): return location
case .unbalancedNesting(_, _, let location): return location
}
}

var description: String {
switch self {
Expand Down Expand Up @@ -56,7 +64,7 @@ extension AsyncSequenceValidationDiagram {
}
}

static func parse<Theme: AsyncSequenceValidationTheme>(_ dsl: String, theme: Theme) throws -> [(Clock.Instant, Event)] {
static func parse<Theme: AsyncSequenceValidationTheme>(_ dsl: String, theme: Theme, location: SourceLocation) throws -> [(Clock.Instant, Event)] {
var emissions = [(Clock.Instant, Event)]()
var when = Clock.Instant(when: .steps(0))
var string: String?
Expand All @@ -70,7 +78,7 @@ extension AsyncSequenceValidationDiagram {
if grouping == 0 {
when = when.advanced(by: .steps(1))
} else {
throw ParseFailure.stepInGroup(dsl, index)
throw ParseFailure.stepInGroup(dsl, index, location)
}
} else {
string?.append(ch)
Expand Down Expand Up @@ -125,13 +133,13 @@ extension AsyncSequenceValidationDiagram {
if grouping == 0 {
when = when.advanced(by: .steps(1))
} else {
throw ParseFailure.nestedGroup(dsl, index)
throw ParseFailure.nestedGroup(dsl, index, location)
}
grouping += 1
case .endGroup:
grouping -= 1
if grouping < 0 {
throw ParseFailure.unbalancedNesting(dsl, index)
throw ParseFailure.unbalancedNesting(dsl, index, location)
}
case .skip:
string?.append(ch)
Expand All @@ -148,7 +156,7 @@ extension AsyncSequenceValidationDiagram {
}
}
if grouping != 0 {
throw ParseFailure.unbalancedNesting(dsl, dsl.endIndex)
throw ParseFailure.unbalancedNesting(dsl, dsl.endIndex, location)
}
return emissions
}
Expand Down
21 changes: 19 additions & 2 deletions Sources/AsyncSequenceValidation/Expectation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@

extension AsyncSequenceValidationDiagram {
public struct ExpectationResult: Sendable {
public var expected: [(Clock.Instant, Result<String?, Error>)]
public struct Event: Sendable {
public var when: Clock.Instant
public var result: Result<String?, Error>
public var offset: String.Index
}
public var expected: [Event]
public var actual: [(Clock.Instant, Result<String?, Error>)]

func reconstitute<Theme: AsyncSequenceValidationTheme>(_ result: Result<String?, Error>, theme: Theme) -> String {
Expand Down Expand Up @@ -61,7 +66,9 @@ extension AsyncSequenceValidationDiagram {
var events = [Clock.Instant : [Result<String?, Error>]]()
var end: Clock.Instant = Clock.Instant(when: .zero)

for (when, result) in expected {
for expectation in expected {
let when = expectation.when
let result = expectation.result
events[when, default: []].append(result)
if when > end {
end = when
Expand Down Expand Up @@ -108,6 +115,16 @@ extension AsyncSequenceValidationDiagram {
public var when: Clock.Instant
public var kind: Kind

public var specification: Specification?
public var index: String.Index?

init(when: Clock.Instant, kind: Kind, specification: Specification? = nil, index: String.Index? = nil) {
self.when = when
self.kind = kind
self.specification = specification
self.index = index
}

var reason: String {
switch kind {
case .expectedFinishButGotValue(let actual):
Expand Down
14 changes: 12 additions & 2 deletions Sources/AsyncSequenceValidation/Input.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@
//===----------------------------------------------------------------------===//

extension AsyncSequenceValidationDiagram {
public struct Specification: Sendable {
public let specification: String
public let location: SourceLocation

init(specification: String, location: SourceLocation) {
self.specification = specification
self.location = location
}
}

public struct Input: AsyncSequence, Sendable {
public typealias Element = String

Expand Down Expand Up @@ -74,8 +84,8 @@ extension AsyncSequenceValidationDiagram {
Iterator(state: state, queue: queue, index: index)
}

func parse<Theme: AsyncSequenceValidationTheme>(_ dsl: String, theme: Theme) throws {
let emissions = try Event.parse(dsl, theme: theme)
func parse<Theme: AsyncSequenceValidationTheme>(_ dsl: String, theme: Theme, location: SourceLocation) throws {
let emissions = try Event.parse(dsl, theme: theme, location: location)
state.withCriticalRegion { state in
state.emissions = emissions
}
Expand Down
23 changes: 23 additions & 0 deletions Sources/AsyncSequenceValidation/SourceLocation.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Async Algorithms open source project
//
// Copyright (c) 2022 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
//
//===----------------------------------------------------------------------===//

public struct SourceLocation: Sendable, CustomStringConvertible {
public var file: StaticString
public var line: UInt

public var description: String {
return "\(file):\(line)"
}
}

public protocol SourceFailure: Error {
var location: SourceLocation { get }
}
Loading