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 19 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
24 changes: 20 additions & 4 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 to see if 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 Expand Up @@ -184,7 +202,6 @@ object Connection {
else Left(SemanticError.InvalidPortNumber(loc, n, name, size, specLoc))
case None => Right(())
}

}

object Endpoint {
Expand All @@ -204,7 +221,6 @@ object Connection {
case None => Right(())
}
} yield endpoint

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ object GeneralPortNumbering {
val pi = pii.portInstance
val cs = t.getConnectionsFrom(pii).toArray.sorted
val usedPortNumbers = t.getUsedPortNumbers(pi, cs)
val state = PortNumberingState.initial(usedPortNumbers)
// Initialize the PortNumberingState with an empty set as one
// of its args since in GeneralPortNumbering we are only
// working with one set of port numbers at a time
val state = PortNumberingState.initial(Set(), usedPortNumbers)
val (_, t1) = cs.foldLeft ((state, t)) ({ case ((s,t), c) =>
t.getPortNumber(pi, c) match {
case Some(n) => (s, t)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,18 +60,45 @@ 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))
// Check to see if the port number is already in use
if PortNumberingState.checkPortNumberInUse(n1, state.numbering.usedPorts2)
then Left(SemanticError.PortNumberAlreadyInUse(n1))
else {
val t1 = t.assignPortNumber(pi2, c2, n1)
// Update the set of used ports so that the new port number is tracked
val num = state.numbering.setUsedPorts(state.numbering.usedPorts1, state.numbering.usedPorts2 + n1)
Right(state.copy(t = t1, numbering = num))
}
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))
// Check to see if the port number is already in use
if PortNumberingState.checkPortNumberInUse(n2, state.numbering.usedPorts1)
then Left(SemanticError.PortNumberAlreadyInUse(n2))
else {
val t1 = t.assignPortNumber(pi1, c1, n2)
// Update the set of used ports so that the new port number is tracked
val num = state.numbering.setUsedPorts(state.numbering.usedPorts1 + n2, state.numbering.usedPorts2)
Right(state.copy(t = t1, numbering = num))
}
case (None, None) =>
// Neither port has a number: assign a new one
val maxSize1 = pi1.getArraySize
val maxSize2 = pi2.getArraySize
val (numbering, n) = state.numbering.getPortNumber
val t1 = t.assignPortNumber(pi1, c1, n).
assignPortNumber(pi2, c2, n)
Right(state.copy(t = t1, numbering = numbering))
// Check to see if the port number is out of range for either of the port array sizes
if(n > maxSize1 - 1)
then Left(SemanticError.InvalidPortNumber(pi1.getLoc, n, pi1.toString, maxSize1, pi1.getLoc))
else if(n > maxSize2 - 1)
then Left(SemanticError.InvalidPortNumber(pi2.getLoc, n, pi2.toString, maxSize2, pi2.getLoc))
// Check to see if the port number is already in use
else if PortNumberingState.checkPortNumberInUse(n, state.numbering.usedPortNumbers)
then Left(SemanticError.PortNumberAlreadyInUse(n))
else {
val t1 = t.assignPortNumber(pi1, c1, n).assignPortNumber(pi2, c2, n)
// Update the set of used ports so that the new port number is tracked
val num = state.numbering.setUsedPorts(state.numbering.usedPorts1 + n, state.numbering.usedPorts2 + n)
Right(state.copy(t = t1, numbering = num))
}
}
}

Expand Down Expand Up @@ -99,18 +126,17 @@ object MatchedPortNumbering {
pi1: PortInstance,
map1: ConnectionMap,
pi2: PortInstance,
map2: ConnectionMap
map2: ConnectionMap,
usedPorts1: Set[Int],
usedPorts2: Set[Int]
): State = {
// Compute the used port numbers
val usedPortNumbers = t.getUsedPortNumbers(pi1, map1.values) ++
t.getUsedPortNumbers(pi2, map2.values)
State(
t,
pi1,
map1,
pi2,
map2,
PortNumberingState.initial(usedPortNumbers)
PortNumberingState.initial(usedPorts1, usedPorts2)
)
}

Expand Down Expand Up @@ -161,29 +187,52 @@ object MatchedPortNumbering {
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))
}
}
})
}

// Computes the set of used ports for all connections at a specific port instance
def computeUsedPortNumbers(pi: PortInstance): Result.Result[Set[Int]] = {
val empty: Set[Int] = Set()
val pii = PortInstanceIdentifier(ci, pi)
val cs = t.getConnectionsAt(pii).toList.sorted
Result.foldLeft (cs) (empty) ((s, c) => {
val used = t.getUsedPortNumbers(pi, List(c))
bocchino marked this conversation as resolved.
Show resolved Hide resolved
val intersection = s.intersect(used)
// Do not allow duplicate port numbers
if intersection.nonEmpty
bocchino marked this conversation as resolved.
Show resolved Hide resolved
then Left(SemanticError.MultipleConnectionsAtPortIndex(intersection.mkString(", ")))
else
Right(s ++ used)
})
}

val pi1 = portMatching.instance1
val pi2 = portMatching.instance2
val loc = portMatching.getLoc
for {
map1 <- constructMap(loc, pi1)
map2 <- constructMap(loc, pi2)
usedPorts1 <- computeUsedPortNumbers(pi1)
usedPorts2 <- computeUsedPortNumbers(pi2)
_ <- checkForMissingConnections(loc, map1, map2)
state <- {
val state = State.initial(t, pi1, map1, pi2, map2)
val state = State.initial(t, pi1, map1, pi2, map2, usedPorts1, usedPorts2)
State.assignNumbers(loc, state)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import fpp.compiler.util._

/** Port numbering state */
case class PortNumberingState private (
/** First set of used ports */
usedPorts1: Set[Int],
/** Second set of used ports */
usedPorts2: Set[Int],
/** The used port numbers */
usedPortNumbers: Set[Int],
/** The next port number */
Expand All @@ -19,7 +23,7 @@ case class PortNumberingState private (
nextPortNumber,
s
)
PortNumberingState(s, n)
PortNumberingState(usedPorts1, usedPorts2, s, n)
}

/** Gets the next port number and updates the state */
Expand All @@ -28,14 +32,26 @@ case class PortNumberingState private (
(s, nextPortNumber)
}

// Takes in the updated sets, updated the usedPortNumbers set
// (ie: union of usedPorts1 and usedPorts2) and figure out the new next port number
def setUsedPorts(u1: Set[Int], u2: Set[Int]): PortNumberingState = {
val updatedUsedPortNumbers = u1 ++ u2
val updatedNextPortNumber = PortNumberingState.getNextNumber(
nextPortNumber,
updatedUsedPortNumbers
)
PortNumberingState(u1, u2, updatedUsedPortNumbers, updatedNextPortNumber)
}

}

object PortNumberingState {

/** Construct an initial state */
def initial(usedPortNumbers: Set[Int]): PortNumberingState = {
def initial(usedPorts1: Set[Int], usedPorts2: Set[Int]): PortNumberingState = {
val usedPortNumbers = usedPorts1 ++ usedPorts2
val nextPortNumber = getNextNumber(0, usedPortNumbers)
PortNumberingState(usedPortNumbers, nextPortNumber)
PortNumberingState(usedPorts1, usedPorts2, usedPortNumbers, nextPortNumber)
}

/** Gets the next available port number */
Expand All @@ -47,4 +63,8 @@ object PortNumberingState {
helper(from)
}

// Checks to see if a port number is already in use
def checkPortNumberInUse(n: Int, used: Set[Int]) =
used.contains(n)

}
2 changes: 1 addition & 1 deletion compiler/lib/src/main/scala/ast/Ast.scala
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,7 @@ object Ast {

/** Connection */
final case class Connection(
isUnmatched: Boolean,
fromPort: AstNode[PortInstanceIdentifier],
fromIndex: Option[AstNode[Expr]],
toPort: AstNode[PortInstanceIdentifier],
Expand Down Expand Up @@ -724,5 +725,4 @@ object Ast {
override def toString = "public"
}
}

}
2 changes: 1 addition & 1 deletion compiler/lib/src/main/scala/codegen/AstWriter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ object AstWriter extends AstVisitor with LineUtils {
) = {
def direct(g: Ast.SpecConnectionGraph.Direct) = {
def connection(c: Ast.SpecConnectionGraph.Connection) = {
lines("connection") ++ (
lines(if c.isUnmatched then "unmatched connection" else "connection") ++ (
addPrefix("from port", portInstanceIdentifier) (c.fromPort.data) ++
linesOpt(addPrefix("index", exprNode), c.fromIndex) ++
addPrefix("to port", portInstanceIdentifier) (c.toPort.data) ++
Expand Down
9 changes: 5 additions & 4 deletions compiler/lib/src/main/scala/codegen/FppWriter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -575,10 +575,11 @@ object FppWriter extends AstVisitor with LineUtils {
Line.addPrefixAndSuffix("[", exprNode(en), "]")

private def connection(c: Ast.SpecConnectionGraph.Connection) =
portInstanceId(c.fromPort.data).
joinOpt (c.fromIndex) ("") (bracketExprNode).
join (" -> ") (portInstanceId(c.toPort.data)).
joinOpt (c.toIndex) ("") (bracketExprNode)
lines(if c.isUnmatched then "unmatched " else "").
join ("") (portInstanceId(c.fromPort.data)).
joinOpt (c.fromIndex) ("") (bracketExprNode).
join (" -> ") (portInstanceId(c.toPort.data)).
joinOpt (c.toIndex) ("") (bracketExprNode)

private def defEnumConstant(dec: Ast.DefEnumConstant) =
lines(ident(dec.name)).joinOpt (dec.value) (" = ") (exprNode)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ object TopologyXmlFppWriter extends LineUtils {
}
yield {
Ast.SpecConnectionGraph.Connection(
false,
from._1,
from._2,
to._1,
Expand Down
1 change: 1 addition & 0 deletions compiler/lib/src/main/scala/syntax/Lexer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ object Lexer extends RegexParsers {
("topology", (u: Unit) => Token.TOPOLOGY()),
("true", (u: Unit) => Token.TRUE()),
("type", (u: Unit) => Token.TYPE()),
("unmatched", (u: Unit) => Token.UNMATCHED()),
("update", (u: Unit) => Token.UPDATE()),
("warning", (u: Unit) => Token.WARNING()),
("with", (u: Unit) => Token.WITH()),
Expand Down
11 changes: 8 additions & 3 deletions compiler/lib/src/main/scala/syntax/Parser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,10 @@ object Parser extends Parsers {

def connection: Parser[Ast.SpecConnectionGraph.Connection] = {
def connectionPort = node(portInstanceIdentifier) ~! opt(index)
connectionPort ~! (rarrow ~>! connectionPort) ^^ {
case (fromPort ~ fromIndex) ~ (toPort ~ toIndex) => {
opt(unmatched) ~ connectionPort ~! (rarrow ~>! connectionPort) ^^ {
case unmatched ~ (fromPort ~ fromIndex) ~ (toPort ~ toIndex) => {
Ast.SpecConnectionGraph.Connection(
unmatched.isDefined,
fromPort,
fromIndex,
toPort,
Expand Down Expand Up @@ -397,7 +398,9 @@ object Parser extends Parsers {
def specConnectionGraph: Parser[Ast.SpecConnectionGraph] = {
def directGraph = {
(connections ~> ident) ~! (lbrace ~>! elementSequence(connection, comma) <~! rbrace) ^^ {
case ident ~ connections => Ast.SpecConnectionGraph.Direct(ident, connections)
case ident ~ connections => {
Ast.SpecConnectionGraph.Direct(ident, connections)
}
}
}
def patternGraph = {
Expand Down Expand Up @@ -934,6 +937,8 @@ object Parser extends Parsers {

private def typeToken = accept("type", { case t : Token.TYPE => t })

private def unmatched = accept("unmatched", { case t : Token.UNMATCHED => t })

private def update = accept("update", { case t : Token.UPDATE => t })

private def warning = accept("warning", { case t : Token.WARNING => t })
Expand Down
1 change: 1 addition & 0 deletions compiler/lib/src/main/scala/syntax/Token.scala
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ object Token {
final case class U32() extends Token
final case class U64() extends Token
final case class U8() extends Token
final case class UNMATCHED() extends Token
final case class UNUSED() extends Token
final case class UPDATE() extends Token
final case class WARNING() extends Token
Expand Down
Loading
Loading