Skip to content

Commit

Permalink
Predicate clean-up
Browse files Browse the repository at this point in the history
- Changed the match method signature to more concise match(_:in)
- Added documentation
- Added some tests
- change list of objects in constraint+requirement from `any` to `some`
  • Loading branch information
Stiivi committed Nov 13, 2024
1 parent aaf2f01 commit 1b224d8
Show file tree
Hide file tree
Showing 9 changed files with 170 additions and 60 deletions.
8 changes: 4 additions & 4 deletions Sources/PoieticCore/Constraints/Constraint+Edge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public final class EdgeEndpointRequirement: ConstraintRequirement {
self.edge = edge
}

public func check(frame: some Frame, objects: [any ObjectSnapshot]) -> [ObjectID] {
public func check(frame: some Frame, objects: [some ObjectSnapshot]) -> [ObjectID] {
var violations: [ObjectID] = []

for object in objects {
Expand All @@ -49,19 +49,19 @@ public final class EdgeEndpointRequirement: ConstraintRequirement {

if let predicate = self.origin {
let node = frame.node(edge.origin)
if !predicate.match(frame: frame, object: node) {
if !predicate.match(node, in: frame) {
violations.append(edge.id)
continue
}
}
if let predicate = self.target {
let node = frame.node(edge.target)
if !predicate.match(frame: frame, object: node) {
if !predicate.match(node, in: frame) {
violations.append(edge.id)
continue
}
}
if let predicate = self.edge, !predicate.match(frame: frame, object: edge.snapshot) {
if let predicate = self.edge, !predicate.match(edge.snapshot, in: frame) {
violations.append(edge.id)
continue
}
Expand Down
15 changes: 11 additions & 4 deletions Sources/PoieticCore/Constraints/Constraint+Node.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,17 @@
// Created by Stefan Urbanek on 16/06/2022.
//

/// Requirement that there must be at most one edge adjacent to a tested node.
///
public final class UniqueNeighbourRequirement: ConstraintRequirement {
/// Predicate to test the adjacent edges.
public let predicate: Predicate

/// Direction of the edge relative to the node being tested for the requirement.
public let direction: EdgeDirection

/// Flag whether at least one edge is required. If true, then the edge matching
/// the predicate must exist.
public let isRequired: Bool

/// Creates a constraint for unique neighbour.
Expand All @@ -18,24 +26,23 @@ public final class UniqueNeighbourRequirement: ConstraintRequirement {
/// one neighbour or when there is none.
///
/// - Parameters:
/// - selector: neigborhood selector that has to be unique for the
/// matching node
/// - predicate: Predicate to select neighbourhood edges.
/// - direction: Edge direction to consider relative to the object tested.
/// - required: Wether the unique neighbour is required.
///
public init(_ predicate: Predicate, direction: EdgeDirection = .outgoing, required: Bool=false) {
self.predicate = predicate
self.direction = direction
self.isRequired = required
}


public func check(frame: some Frame, objects: [any ObjectSnapshot]) -> [ObjectID] {
return objects.filter {
guard $0.structure.type == .node else {
return false
}
let hood = frame.hood($0.id, direction: direction) { edge in
predicate.match(frame: frame, object: edge.snapshot)
predicate.match(edge.snapshot, in: frame)
}
let count = hood.edges.count

Expand Down
23 changes: 9 additions & 14 deletions Sources/PoieticCore/Constraints/Constraint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ public struct ConstraintViolation: Error, CustomDebugStringConvertible {
/// )
/// )
/// ```

public final class Constraint: Sendable {
/// Identifier of the constraint.
///
Expand Down Expand Up @@ -110,9 +109,9 @@ public final class Constraint: Sendable {
/// Check the frame for the constraint and return a list of nodes that
/// violate the constraint
///
public func check(_ frame: any Frame) -> [ObjectID] {
public func check(_ frame: some Frame) -> [ObjectID] {
let matched = frame.snapshots.filter {
match.match(frame: frame, object: $0)
match.match($0, in: frame)
}
// .map { $0.snapshot }
return requirement.check(frame: frame, objects: matched)
Expand All @@ -123,7 +122,7 @@ public final class Constraint: Sendable {
///
public protocol ConstraintRequirement: Sendable {
/// - Returns: List of IDs of objects that do not satisfy the requirement.
func check(frame: some Frame, objects: [any ObjectSnapshot]) -> [ObjectID]
func check(frame: some Frame, objects: [some ObjectSnapshot]) -> [ObjectID]
}

/// Requirement that all matched objects satisfy a given predicate.
Expand All @@ -137,8 +136,8 @@ public final class AllSatisfy: ConstraintRequirement {
self.predicate = predicate
}

public func check(frame: some Frame, objects: [any ObjectSnapshot]) -> [ObjectID] {
objects.filter { !predicate.match(frame: frame, object: $0) }
public func check(frame: some Frame, objects: [some ObjectSnapshot]) -> [ObjectID] {
objects.filter { !predicate.match($0, in: frame) }
.map { $0.id }
}
}
Expand All @@ -157,7 +156,7 @@ public final class RejectAll: ConstraintRequirement {
/// Returns all objects it is provided – meaning, that all of them are
/// violating the constraint.
///
public func check(frame: some Frame, objects: [any ObjectSnapshot]) -> [ObjectID] {
public func check(frame: some Frame, objects: [some ObjectSnapshot]) -> [ObjectID] {
/// We reject whatever comes in
return objects.map { $0.id }
}
Expand All @@ -176,7 +175,7 @@ public final class AcceptAll: ConstraintRequirement {
/// Returns an empty list, meaning that none of the objects are violating
/// the constraint.
///
public func check(frame: some Frame, objects: [any ObjectSnapshot]) -> [ObjectID] {
public func check(frame: some Frame, objects: [some ObjectSnapshot]) -> [ObjectID] {
// We accept everything, therefore we do not return any violations.
return []
}
Expand All @@ -201,7 +200,7 @@ public final class UniqueProperty: ConstraintRequirement {
/// value from each of the objects and returns a list of those objects
/// that have duplicate values.
///
public func check(frame: some Frame, objects: [any ObjectSnapshot]) -> [ObjectID] {
public func check(frame: some Frame, objects: [some ObjectSnapshot]) -> [ObjectID] {
var seen: [Variant:[ObjectID]] = [:]

for object in objects {
Expand All @@ -211,11 +210,7 @@ public final class UniqueProperty: ConstraintRequirement {
seen[value, default: []].append(object.id)
}

let duplicates = seen.filter {
$0.value.count > 1
}.flatMap {
$0.value
}
let duplicates = seen.filter { $0.value.count > 1 }.flatMap { $0.value }
return duplicates
}
}
7 changes: 0 additions & 7 deletions Sources/PoieticCore/Design/Errors.swift

This file was deleted.

2 changes: 1 addition & 1 deletion Sources/PoieticCore/Design/Frame.swift
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ extension Frame {

public func filter(_ predicate: Predicate) -> [Snapshot] {
return snapshots.filter {
predicate.match(frame: self, object: $0)
predicate.match($0, in: self)
}
}
}
Expand Down
15 changes: 13 additions & 2 deletions Sources/PoieticCore/Design/TransientObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,17 @@ public let ReservedAttributeNames = [
]


/// Transient object that can be modified.
///
/// Mutable objects are temporary and typically exist only during a change
/// transaction. New objects are created within a ``TransientFrame`` by ``TransientFrame/create(_:id:snapshotID:structure:parent:children:attributes:components:)``.
/// Mutable versions of existing stable objects are created with``TransientFrame/mutate(_:)``.
///
/// Mutable objects are converted to stable objects with ``TransientFrame/accept()``.
///
/// - SeeAlso: ``TransientFrame``
///
public class MutableObject: ObjectSnapshot {

public let id: ObjectID
public let snapshotID: ObjectID
public let type: ObjectType
Expand Down Expand Up @@ -120,11 +129,13 @@ public class MutableObject: ObjectSnapshot {

// MARK: - Transient Object

/// A proxy for for an object in a transient frame.
/// A wrapper for for an object in a transient frame.
///
/// The transient object refers to one of two possible target objects: original
/// stable snapshot or a mutable snapshot in a ``TransientFrame``.
///
/// - SeeAlso: ``MutableObject``
///
public struct TransientObject: ObjectSnapshot {
let frame: TransientFrame
public let id: ObjectID
Expand Down
8 changes: 4 additions & 4 deletions Sources/PoieticCore/Predicate/Predicate+Edge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,24 +29,24 @@ public final class EdgePredicate: Predicate {
self.edgePredicate = edge
}

public func match(frame: some Frame, object: some ObjectSnapshot) -> Bool {
public func match(_ object: some ObjectSnapshot, in frame: some Frame) -> Bool {
guard let edge = EdgeSnapshot(object) else {
return false
}
if let predicate = originPredicate {
let node = frame.node(edge.origin)
if !predicate.match(frame: frame, object: node) {
if !predicate.match(node, in: frame) {
return false
}
}
if let predicate = targetPredicate {
let node = frame.node(edge.target)
if !predicate.match(frame: frame, object: node) {
if !predicate.match(node, in: frame) {
return false
}
}
if let predicate = edgePredicate {
if !predicate.match(frame: frame, object: edge.snapshot) {
if !predicate.match(edge.snapshot, in: frame) {
return false
}
}
Expand Down
Loading

0 comments on commit 1b224d8

Please sign in to comment.