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

Allow unmatched connections to matched ports #512

Merged
merged 76 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from 75 commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
c423fec
update spec, add unmatched keyword
jwest115 Aug 29, 2024
c734be2
fix formatting
jwest115 Aug 29, 2024
1b8ff6b
update matched port numbering, add connection matching semantics
jwest115 Sep 6, 2024
d3c3de3
update docs readme
jwest115 Sep 6, 2024
ed4e051
cleanup
jwest115 Sep 16, 2024
a04f484
added unit tests, cleanup
jwest115 Sep 19, 2024
1ee946a
update user guide
jwest115 Sep 20, 2024
0b1ea5f
Merge branch 'main' into issue-249
jwest115 Sep 24, 2024
5f8c365
revert doc changes
jwest115 Sep 24, 2024
e092019
revert user guide
jwest115 Sep 24, 2024
ccaf6e0
revert user guide
jwest115 Sep 24, 2024
20e925f
update user guide
jwest115 Sep 25, 2024
d21b0be
refactor matched port numbering
jwest115 Sep 26, 2024
3cb1793
add comments, cleanup
jwest115 Sep 26, 2024
54ea1ff
check use ports for the case of implicit port number
jwest115 Sep 26, 2024
0664581
set next available port
jwest115 Sep 28, 2024
01280b5
update `fpp-to-json` test ref files
jwest115 Sep 28, 2024
41191a5
remove redundant error, fix error wording
jwest115 Sep 30, 2024
1f7aae6
fix unmatched connection parser
jwest115 Sep 30, 2024
2e3ad40
refactor how we compute used ports in matched port numbering
jwest115 Oct 10, 2024
295b72d
fix remote used port check
jwest115 Oct 10, 2024
3a0bf37
cleanup and additional test cases
jwest115 Oct 11, 2024
5a08328
Merge branch 'main' into issue-249
jwest115 Oct 11, 2024
12679b5
cleanup, add test case
jwest115 Oct 17, 2024
382152f
Lock CI runs-on to ubuntu-22.04
thomas-bc Oct 15, 2024
ff003a9
make sure we update the used ports after assigning them
jwest115 Oct 17, 2024
629c4fe
updated used port num set after assigning a port number
jwest115 Oct 17, 2024
8ea86f1
Merge remote-tracking branch 'upstream/main' into issue-249
bocchino Oct 28, 2024
a50fc48
Regenerate docs
bocchino Oct 28, 2024
4cb8cc3
Revise spec
bocchino Oct 28, 2024
41f64a6
Revise spec
bocchino Oct 28, 2024
2d6b24f
Remove unused token
bocchino Oct 28, 2024
2098bf7
Restore blank lines
bocchino Oct 28, 2024
2795b70
Code formatting
bocchino Oct 28, 2024
2d585e4
Refactor ResolveTopology
bocchino Oct 29, 2024
c2637a0
Code cleanup
bocchino Oct 29, 2024
860149d
Revise error reporting
bocchino Oct 29, 2024
78fa66b
Fix error in User's Guide
bocchino Oct 29, 2024
2d20d24
Refactor MatchedPortNumbering
bocchino Oct 29, 2024
253e783
Revise error reporting
bocchino Oct 29, 2024
c020fab
Revise error reporting and tests
bocchino Oct 29, 2024
a08274f
Revise tests
bocchino Oct 29, 2024
5dc7285
Revert change to Version.scala
bocchino Oct 29, 2024
9be0894
Revise tests
bocchino Oct 29, 2024
b4f7f1a
Revise tests
bocchino Oct 29, 2024
4da4b03
Refactor matched numbering
bocchino Oct 29, 2024
74a3273
Refactor matched numbering
bocchino Oct 29, 2024
d0d154c
Refactor matched numbering
bocchino Oct 29, 2024
7d026a4
Refactor matched numbering
bocchino Oct 29, 2024
626fc97
Refactor matched port numbering
bocchino Oct 29, 2024
d2ad2c7
Refactor matched port numbering
bocchino Oct 29, 2024
50ad916
Refactor matched port numbering
bocchino Oct 29, 2024
3aaf786
Refactor matched port numbering
bocchino Oct 29, 2024
202ea97
Refactor matched port numbering
bocchino Oct 30, 2024
9dae02f
Refactor matched numbering
bocchino Oct 30, 2024
e0e3c8d
Refactor matched port numbering
bocchino Oct 30, 2024
b6a41df
Refactor matched port numbering
bocchino Oct 30, 2024
6e1eb5f
Refactor matched numbering
bocchino Oct 30, 2024
613ccd1
Refactor matched port numbering
bocchino Oct 30, 2024
1e2c797
Delete unused file
bocchino Oct 30, 2024
175e691
Refactor matched port numbering
bocchino Oct 30, 2024
96bfb6e
Revise comment
bocchino Oct 30, 2024
893f3a8
Refactor matched port numbering
bocchino Oct 30, 2024
ef8b76d
Revise tests
bocchino Oct 30, 2024
3bb23aa
Revise tests
bocchino Oct 30, 2024
153e7c6
Revise tests
bocchino Oct 30, 2024
19bab67
Revise error message
bocchino Oct 30, 2024
e8f3ce5
Revise tests
bocchino Oct 30, 2024
c1a6449
Revise tests
bocchino Oct 30, 2024
09d467c
Revise tests
bocchino Oct 30, 2024
829f17b
Revise tests
bocchino Oct 30, 2024
6a0a139
Remove trailing spaces
bocchino Oct 30, 2024
4cf870c
Remove unneeded ref files
bocchino Oct 30, 2024
23647d6
Revise User's Guide
bocchino Oct 30, 2024
bcc11c0
Revise User's Guide
bocchino Oct 30, 2024
b1f68c8
Update spec (#525)
bocchino Oct 30, 2024
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
22 changes: 20 additions & 2 deletions compiler/lib/src/main/scala/analysis/Semantics/Connection.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ case class Connection(
/** The from endpoint */
from: Connection.Endpoint,
/** The to endpoint */
to: Connection.Endpoint
to: Connection.Endpoint,
isUnmatched: Boolean = false
) extends Ordered[Connection] {

override def toString = s"${from.toString} -> ${to.toString}"
Expand Down Expand Up @@ -103,6 +104,19 @@ case class Connection(
}
}

/** Checks whether a connection is match constrained */
def isMatchConstrained: Boolean = {
def portMatchingExists(pml: List[Component.PortMatching], pi: PortInstance): Boolean =
pml.exists(pm => pi.equals(pm.instance1) || pi.equals(pm.instance2))

val fromPi = from.port.portInstance
val toPi = to.port.portInstance
val fromPml = from.port.componentInstance.component.portMatchingList
val toPml = to.port.componentInstance.component.portMatchingList

portMatchingExists(fromPml, fromPi) || portMatchingExists(toPml, toPi)
}

/** Compare two connections */
override def compare(that: Connection) = {
val fromCompare = this.from.compare(that.from)
Expand Down Expand Up @@ -141,10 +155,14 @@ object Connection {
for {
from <- Endpoint.fromAst(a, connection.fromPort, connection.fromIndex)
to <- Endpoint.fromAst(a, connection.toPort, connection.toIndex)
connection <- Right(Connection(from, to))
connection <- Right(Connection(from, to, connection.isUnmatched))
_ <- connection.checkDirections
_ <- connection.checkTypes
_ <- connection.checkSerialWithTypedInput
_ <-
if !connection.isMatchConstrained && connection.isUnmatched
then Left(SemanticError.MissingPortMatching(connection.getLoc))
else Right(())
}
yield connection

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,57 @@ import fpp.compiler.util._
/** Apply matched port numbering */
object MatchedPortNumbering {

// A mapping from component instances to connections
private type ConnectionMap = Map[ComponentInstance, Connection]
// A map from component instances to connections for tracking
// matching pairs of connections
private type InstanceConnectionMap = Map[ComponentInstance, Connection]

// State for matched numbering
// A map from port numbers to connections for tracking port
// assignments
private type PortConnectionMap = Map[Int, Connection]

// State for matched port numbering
private case class State private(
// The topology
t: Topology,
// The port instance for port 1
pi1: PortInstance,
// The map from component instances to connections for port 1
map1: ConnectionMap,
icm1: InstanceConnectionMap,
// The map from port numbers to connections for port 1
pcm1: PortConnectionMap,
// The port instance for port 2
pi2: PortInstance,
// The map from component instances to connections for port 2
map2: ConnectionMap,
icm2: InstanceConnectionMap,
// The map from port numbers to connections for port 2
pcm2: PortConnectionMap,
// Port numbering state
numbering: PortNumberingState
)
) {

// Marks the specified port number as used and generates a new one
def usePortNumber(n: Int): State =
this.copy(numbering = numbering.usePortNumber(n))

// Marks the next port number as used and generates a new one
def useNextPortNumber: State =
this.copy(numbering = numbering.useNextPortNumber)

// Gets the next port number and updates the port numbring state
def getPortNumber: (State, Int) = {
val (s, n) = numbering.getPortNumber
(this.copy(numbering = s), n)
}

// Adds a mapping to pcm1 and updates the port numbering state
def updatePortConnectionMap1(n: Int, c: Connection): State =
usePortNumber(n).copy(pcm1 = this.pcm1 + (n -> c))

// Adds a mapping to pcm2 and updates the port numbering state
def updatePortConnectionMap2(n: Int, c: Connection): State =
usePortNumber(n).copy(pcm2 = this.pcm2 + (n -> c))

}

private object State {

Expand All @@ -41,11 +74,12 @@ object MatchedPortNumbering {
val n2Opt = t.getPortNumber(pi2, c2)
(n1Opt, n2Opt) match {
case (Some(n1), Some(n2)) =>
// Both ports have a number: check that they match
// Both ports have a number
if (n1 == n2)
// Numbers match: OK, nothing to do
Right(state)
else {
// Error: numbers don't match
// Numbers don't match: error
val p1Loc = c1.getThisEndpoint(pi1).loc
val p2Loc = c2.getThisEndpoint(pi2).loc
Left(
Expand All @@ -58,35 +92,82 @@ object MatchedPortNumbering {
)
)
}
case (Some(n1), None) =>
// Only pi1 has a number: assign it to pi2
val t1 = t.assignPortNumber(pi2, c2, n1)
Right(state.copy(t = t1))
case (None, Some(n2)) =>
// Only pi2 has a number: assign it to pi1
val t1 = t.assignPortNumber(pi1, c1, n2)
Right(state.copy(t = t1))
case (Some(n), None) =>
// Only pi1 has a number
state.pcm2.get(n) match {
case Some(prevC) =>
// Number is already assigned at pi2: error
Left(
SemanticError.ImplicitDuplicateConnectionAtMatchedPort(
c2.getLoc,
pi2.toString,
n,
c1.getLoc,
matchingLoc,
prevC.getLoc,
)
)
case None =>
// Assign the number to c2 at pi2 and update the state
val t1 = t.assignPortNumber(pi2, c2, n)
val state1 = state.updatePortConnectionMap2(n, c2)
Right(state1.copy(t = t1))
}
case (None, Some(n)) =>
// Only pi2 has a number
state.pcm1.get(n) match {
case Some(prevC) =>
// Number is already assigned at pi1: error
Left(
SemanticError.ImplicitDuplicateConnectionAtMatchedPort(
c1.getLoc,
pi1.toString,
n,
c2.getLoc,
matchingLoc,
prevC.getLoc
)
)
case None =>
// Assign the number to c1 at pi1 and update the state
val t1 = t.assignPortNumber(pi1, c1, n)
val state1 = state.updatePortConnectionMap1(n, c1)
Right(state1.copy(t = t1))
}
case (None, None) =>
// Neither port has a number: assign a new one
val (numbering, n) = state.numbering.getPortNumber
val t1 = t.assignPortNumber(pi1, c1, n).
assignPortNumber(pi2, c2, n)
Right(state.copy(t = t1, numbering = numbering))
// Neither port has a number; get a new one
val (state1, n) = state.getPortNumber
if(n >= pi1.getArraySize)
// Port number is out of range: error
Left(
SemanticError.NoPortAvailableForMatchedNumbering(
c1.getLoc,
c2.getLoc,
matchingLoc
)
)
else {
// Assign the number to both sides and update the state
val t1 = t.assignPortNumber(pi1, c1, n).assignPortNumber(pi2, c2, n)
val state2 = state1.updatePortConnectionMap1(n, c1).
updatePortConnectionMap2(n, c2)
Right(state2.copy(t = t1))
}
}
}

// For each pair of connections (c1, c2), check that numbers
// For each pair of connections (c1, c2), check that numbers
// match and/or assign numbers
def assignNumbers(
matchingLoc: Location,
state: State
): Result.Result[State] = {
val (map1, map2) = (state.map1, state.map2)
val list1 = map1.toList.sortWith(_._2 < _._2)
val (icm1, icm2) = (state.icm1, state.icm2)
val list1 = icm1.toList.sortWith(_._2 < _._2)
for {
result <- Result.foldLeft (list1) (state) ({
case (s, (ci, c1)) => {
val c2 = map2(ci)
val c2 = icm2(ci)
numberConnectionPair(matchingLoc, s, c1, c2)
}
})
Expand All @@ -97,19 +178,22 @@ object MatchedPortNumbering {
def initial(
t: Topology,
pi1: PortInstance,
map1: ConnectionMap,
icm1: InstanceConnectionMap,
pcm1: PortConnectionMap,
pi2: PortInstance,
map2: ConnectionMap
icm2: InstanceConnectionMap,
pcm2: PortConnectionMap
): State = {
// Compute the used port numbers
val usedPortNumbers = t.getUsedPortNumbers(pi1, map1.values) ++
t.getUsedPortNumbers(pi2, map2.values)
val usedPortNumbers = pcm1.keys.toSet ++ pcm2.keys.toSet
State(
t,
pi1,
map1,
icm1,
pcm1,
pi2,
map2,
icm2,
pcm2,
PortNumberingState.initial(usedPortNumbers)
)
}
Expand All @@ -129,24 +213,24 @@ object MatchedPortNumbering {
// Check for missing connections
private def checkForMissingConnections(
matchingLoc: Location,
map1: ConnectionMap,
map2: ConnectionMap
icm1: InstanceConnectionMap,
icm2: InstanceConnectionMap
): Result.Result[Unit] = {
// Ensure that map2 contains everything in map1
def helper(map1: ConnectionMap, map2: ConnectionMap) =
Result.foldLeft (map1.toList) (()) ({ case (u, (ci, c)) =>
if (map2.contains(ci))
// Ensure that icm2 contains everything in icm1
def helper(icm1: InstanceConnectionMap, icm2: InstanceConnectionMap) =
Result.foldLeft (icm1.toList) (()) ({ case (u, (ci, c)) =>
if (icm2.contains(ci))
Right(())
else {
val loc = c.getLoc
Left(SemanticError.MissingConnection(loc, matchingLoc))
}
})
// Ensure that the two sets of keys match
if (map1.size >= map2.size)
helper(map1, map2)
if (icm1.size >= icm2.size)
helper(icm1, icm2)
else
helper(map2, map1)
helper(icm2, icm1)
}

// Handle one port matching
Expand All @@ -155,35 +239,72 @@ object MatchedPortNumbering {
ci: ComponentInstance,
portMatching: Component.PortMatching
) = {
// Map remote components to connections at pi
def constructMap(loc: Location, pi: PortInstance) = {
val empty: ConnectionMap = Map()
// Map remote component instances to connections at pi
def computeInstanceConnectionMap(pi: PortInstance): Result.Result[InstanceConnectionMap] = {
val empty: InstanceConnectionMap = Map()
val pii = PortInstanceIdentifier(ci, pi)
val cs = t.getConnectionsAt(pii).toList.sorted
Result.foldLeft (cs) (empty) ((m, c) => {
val piiRemote = c.getOtherEndpoint(pi).port
val ciRemote = piiRemote.componentInstance
m.get(ciRemote) match {
case Some(cPrev) => Left(
SemanticError.DuplicateMatchedConnection(
c.getLoc,
cPrev.getLoc,
portMatching.getLoc
if(c.isUnmatched)
Right(m)
else {
val piiRemote = c.getOtherEndpoint(pi).port
val ciRemote = piiRemote.componentInstance
m.get(ciRemote) match {
case Some(cPrev) => Left(
SemanticError.DuplicateMatchedConnection(
c.getLoc,
cPrev.getLoc,
portMatching.getLoc
)
)
)
case None => Right(m + (ciRemote -> c))
case None => Right(m + (ciRemote -> c))
}
}
})
}

// Map port numbers to connections at pi
// While computing the map, enforce the rule against duplicate connections
def computePortConnectionMap(pi: PortInstance): Result.Result[PortConnectionMap] = {
val pii = PortInstanceIdentifier(ci, pi)
val cs = t.getConnectionsAt(pii).toList.sorted
val empty: PortConnectionMap = Map()
Result.foldLeft (cs) (empty) ((m, c) => {
val piiRemote = c.getOtherEndpoint(pi).port
t.getPortNumber(pi, c) match {
case Some(n) =>
m.get(n) match {
case Some(prevC) =>
val loc = c.getLoc
val prevLoc = prevC.getLoc
Left(
SemanticError.DuplicateConnectionAtMatchedPort(
loc,
pi.toString,
n,
prevLoc,
portMatching.getLoc
)
)
case None => Right(m + (n -> c))
}
case None => Right(m)
}
})
}

val pi1 = portMatching.instance1
val pi2 = portMatching.instance2
val loc = portMatching.getLoc
for {
map1 <- constructMap(loc, pi1)
map2 <- constructMap(loc, pi2)
_ <- checkForMissingConnections(loc, map1, map2)
pcm1 <- computePortConnectionMap(pi1)
pcm2 <- computePortConnectionMap(pi2)
icm1 <- computeInstanceConnectionMap(pi1)
icm2 <- computeInstanceConnectionMap(pi2)
_ <- checkForMissingConnections(loc, icm1, icm2)
state <- {
val state = State.initial(t, pi1, map1, pi2, map2)
val state = State.initial(t, pi1, icm1, pcm1, pi2, icm2, pcm2)
State.assignNumbers(loc, state)
}
}
Expand Down
Loading
Loading