Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
a26d890
Made progress on top port semantics
Kronos3 Oct 8, 2025
adf3427
Merge remote-tracking branch 'origin/topology-ports-syntax' into topo…
Kronos3 Oct 8, 2025
643be73
Merge remote-tracking branch 'origin/feature/topology-ports' into top…
Kronos3 Oct 9, 2025
cc10515
Fix json build
Kronos3 Oct 15, 2025
3bd74d9
Merge remote-tracking branch 'origin/feature/topology-ports' into top…
Kronos3 Oct 15, 2025
668c966
Fix instance resolution
Kronos3 Oct 15, 2025
374c8b5
Update tests with new messages
Kronos3 Oct 21, 2025
0009de3
Add topology instance test
Kronos3 Oct 21, 2025
f707a95
Working topology ports
Kronos3 Oct 22, 2025
47d76d3
Added tests
Kronos3 Oct 23, 2025
7e8ced0
Clean up spec
Kronos3 Oct 23, 2025
5a987b0
Update location specifier spec
Kronos3 Oct 23, 2025
6c49836
Fix scala tests
Kronos3 Oct 23, 2025
9cd056e
Update syntax tests
Kronos3 Oct 23, 2025
2d302df
Fix check uses
Kronos3 Oct 27, 2025
d737ea8
Check implements clause
Kronos3 Oct 27, 2025
2c4ef9f
Fixed port instance compare
Kronos3 Oct 27, 2025
62bb0da
Fix depend, added some depend tests, fix dict naming of comp inst
Kronos3 Oct 27, 2025
aca9453
Update outputs
Kronos3 Oct 27, 2025
1f66709
Update format tests
Kronos3 Oct 27, 2025
d6023a4
All tests passing
Kronos3 Oct 27, 2025
df25af5
Regen trace
Kronos3 Oct 27, 2025
007d484
Merge remote-tracking branch 'origin/feature/topology-ports' into top…
Kronos3 Oct 28, 2025
201dea1
Attempt to make the toList more stable on Map
Kronos3 Oct 28, 2025
a992f02
Sort list
Kronos3 Oct 30, 2025
0463bf4
Regenerate trace
Kronos3 Oct 30, 2025
1638afe
Merge remote-tracking branch 'origin/main' into topology-ports-semantics
Kronos3 Nov 6, 2025
9318c9e
Merge remote-tracking branch 'origin/feature/topology-ports' into top…
Kronos3 Nov 6, 2025
ad6577c
Merge remote-tracking branch 'origin/feature/topology-ports' into top…
Kronos3 Jan 27, 2026
49e2b90
Update refs
Kronos3 Jan 28, 2026
8f187d3
Merge remote-tracking branch 'origin/feature/topology-ports' into top…
Kronos3 Jan 29, 2026
7ecbee2
Update error reporting
Kronos3 Jan 29, 2026
540d0fd
Update error reporting for interface instance namegroup undef
Kronos3 Jan 29, 2026
06ab1b8
Update analysis ref
Kronos3 Jan 29, 2026
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
3,936 changes: 1,612 additions & 2,324 deletions compiler/lib/src/main/resources/META-INF/native-image/reflect-config.json

Large diffs are not rendered by default.

26 changes: 26 additions & 0 deletions compiler/lib/src/main/scala/analysis/Analysis.scala
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ case class Analysis(
interfaceMap: Map[Symbol.Interface, Interface] = Map(),
/** The interface under construction */
interface: Option[Interface] = None,
/** The map from topology symbols to 'partial' topologies
* with only port interface/instance information */
partialTopologyMap: Map[Symbol.Topology, Topology] = Map(),
/** The map from topology symbols to topologies */
topologyMap: Map[Symbol.Topology, Topology] = Map(),
/** The topology under construction */
Expand Down Expand Up @@ -249,6 +252,29 @@ case class Analysis(
for (cis <- getComponentInstanceSymbol(id))
yield this.componentInstanceMap(cis)

/** Gets an interface instance symbol from the use-def map */
def getInterfaceInstanceSymbol(id: AstNode.Id): Result.Result[InterfaceInstanceSymbol] =
this.useDefMap(id) match {
case cis: Symbol.ComponentInstance => Right(cis)
case ts: Symbol.Topology => Right(ts)
case s => Left(
SemanticError.InvalidSymbol(
s.getUnqualifiedName,
Locations.get(id),
"not a component instance or topology symbol",
s.getLoc
)
)
}

/** Gets an interface instance from the topology or component instance map */
def getInterfaceInstance(id: AstNode.Id): Result.Result[InterfaceInstance] =
for (iis <- getInterfaceInstanceSymbol(id))
yield iis match {
case cis: Symbol.ComponentInstance => InterfaceInstance.fromComponentInstance(this.componentInstanceMap(cis))
case top: Symbol.Topology => InterfaceInstance.fromTopology(this.topologyMap(top))
}

/** Gets an interface symbol from use-def map */
def getInterfaceSymbol(id: AstNode.Id): Result.Result[Symbol.Interface] =
this.useDefMap(id) match {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,16 @@ trait BasicUseAnalyzer extends TypeExpressionAnalyzer {

/** A use of a component definition */
def componentUse(a: Analysis, node: AstNode[Ast.QualIdent], use: Name.Qualified): Result = default(a)
/** A use of a component instance definition */
def componentInstanceUse(a: Analysis, node: AstNode[Ast.QualIdent], use: Name.Qualified): Result = default(a)

/** A use of a interface instance (topology def or component instance def) */
def interfaceInstanceUse(a: Analysis, node: AstNode[Ast.QualIdent], use: Name.Qualified): Result = default(a)

/** A use of a constant definition or enumerated constant definition */
def constantUse(a: Analysis, node: AstNode[Ast.Expr], use: Name.Qualified): Result = default(a)

/** A use of a port definition */
def portUse(a: Analysis, node: AstNode[Ast.QualIdent], use: Name.Qualified): Result = default(a)

/** A use of a topology definition */
def topologyUse(a: Analysis, node: AstNode[Ast.QualIdent], use: Name.Qualified): Result = default(a)

/** A use of an interface definition */
def interfaceUse(a: Analysis, node: AstNode[Ast.QualIdent], use: Name.Qualified): Result = default(a)

Expand All @@ -46,6 +43,13 @@ trait BasicUseAnalyzer extends TypeExpressionAnalyzer {
val id = node._2.id
for {
a <- visitImpliedUses(a, id)

// Visit port interface uses in the implements clause
a <- {
Result.foldLeft (node._2.data.implements) (a) ((a, impl) =>
qualIdentNode(interfaceUse) (a, impl)
)}

a <- super.defTopologyAnnotatedNode(a, node)
} yield a
}
Expand Down Expand Up @@ -77,10 +81,10 @@ trait BasicUseAnalyzer extends TypeExpressionAnalyzer {
constantUse(a, node, use)
}

override def specCompInstanceAnnotatedNode(a: Analysis, node: Ast.Annotated[AstNode[Ast.SpecCompInstance]]) = {
override def specInstanceAnnotatedNode(a: Analysis, node: Ast.Annotated[AstNode[Ast.SpecInstance]]) = {
val (_, node1, _) = node
val data = node1.data
qualIdentNode (componentInstanceUse) (a, data.instance)
qualIdentNode (interfaceInstanceUse) (a, data.instance)
}

override def specStateMachineInstanceAnnotatedNode(a: Analysis, node: Ast.Annotated[AstNode[Ast.SpecStateMachineInstance]]) = {
Expand All @@ -107,8 +111,8 @@ trait BasicUseAnalyzer extends TypeExpressionAnalyzer {
data match {
case direct : Ast.SpecConnectionGraph.Direct => visitList(a, direct.connections, connection)
case pattern : Ast.SpecConnectionGraph.Pattern => for {
a <- qualIdentNode (componentInstanceUse) (a, pattern.source)
a <- visitList(a, pattern.targets, qualIdentNode(componentInstanceUse))
a <- qualIdentNode (interfaceInstanceUse) (a, pattern.source)
a <- visitList(a, pattern.targets, qualIdentNode(interfaceInstanceUse))
} yield a
}
}
Expand Down Expand Up @@ -165,16 +169,16 @@ trait BasicUseAnalyzer extends TypeExpressionAnalyzer {
a <- visitList(a, aNode._2.data.omitted, tlmChannelIdentifierNode)
} yield a

override def specTopImportAnnotatedNode(a: Analysis, node: Ast.Annotated[AstNode[Ast.SpecImport]]) = {
override def specInterfaceImportAnnotatedNode(a: Analysis, node: Ast.Annotated[AstNode[Ast.SpecImport]]) = {
val (_, node1, _) = node
val data = node1.data
qualIdentNode(topologyUse)(a, data.sym)
qualIdentNode(interfaceUse)(a, data.sym)
}

override def specInterfaceImportAnnotatedNode(a: Analysis, node: Ast.Annotated[AstNode[Ast.SpecImport]]) = {
override def specTopPortAnnotatedNode(a: Analysis, node: Ast.Annotated[AstNode[Ast.SpecTopPort]]) = {
val (_, node1, _) = node
val data = node1.data
qualIdentNode(interfaceUse)(a, data.sym)
portInstanceIdentifierNode(a, data.underlyingPort)
}

override def typeNameQualIdentNode(a: Analysis, node: AstNode[Ast.TypeName], tn: Ast.TypeNameQualIdent) = {
Expand All @@ -183,7 +187,7 @@ trait BasicUseAnalyzer extends TypeExpressionAnalyzer {
}

private def portInstanceIdentifierNode(a: Analysis, node: AstNode[Ast.PortInstanceIdentifier]): Result =
qualIdentNode (componentInstanceUse) (a, node.data.componentInstance)
qualIdentNode (interfaceInstanceUse) (a, node.data.interfaceInstance)

private def qualIdentNode
(f: (Analysis, AstNode[Ast.QualIdent], Name.Qualified) => Result)
Expand All @@ -196,7 +200,7 @@ trait BasicUseAnalyzer extends TypeExpressionAnalyzer {
a: Analysis,
node: AstNode[Ast.TlmChannelIdentifier]
): Result =
qualIdentNode (componentInstanceUse) (a, node.data.componentInstance)
qualIdentNode (interfaceInstanceUse) (a, node.data.componentInstance)

private def tlmPacketMember(a: Analysis, member: Ast.TlmPacketMember) =
member match {
Expand Down
11 changes: 7 additions & 4 deletions compiler/lib/src/main/scala/analysis/Analyzers/UseAnalyzer.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package fpp.compiler.analysis

import fpp.compiler.ast._
import fpp.compiler.util._
import fpp.compiler.ast.*
import fpp.compiler.util.*

import scala.annotation.tailrec

/**
* Analyze uses
Expand All @@ -10,6 +12,7 @@ import fpp.compiler.util._
trait UseAnalyzer extends BasicUseAnalyzer {

/** Gets a qualified name from a dot expression */
@tailrec
private def getQualifiedName(
e: Ast.Expr,
qualifier: List[Name.Unqualified] = Nil
Expand All @@ -18,7 +21,7 @@ trait UseAnalyzer extends BasicUseAnalyzer {
Name.Qualified.fromIdentList(id :: qualifier)
case Ast.ExprDot(e1, id) =>
getQualifiedName(e1.data, id.data :: qualifier)
case _ => throw new InternalError("expected a qualified name")
case _ => throw InternalError("expected a qualified name")
}

override def exprDotNode(a: Analysis, node: AstNode[Ast.Expr], e: Ast.ExprDot) =
Expand All @@ -29,7 +32,7 @@ trait UseAnalyzer extends BasicUseAnalyzer {
constantUse(a, node, use)
case Some(_) =>
// This is some other type of symbol, which it shouldn't be
throw new InternalError("expected a constant use")
throw InternalError("expected a constant use")
case None =>
// e is not a use, so it selects a member of a struct value
// Analyze the left-hand expression representing the struct value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ object CheckSpecLocs
override def defComponentInstanceAnnotatedNode(
a: Analysis,
aNode: Ast.Annotated[AstNode[Ast.DefComponentInstance]]
) = checkSpecLoc(a, Ast.SpecLoc.ComponentInstance, Symbol.ComponentInstance(aNode))
) = checkSpecLoc(a, Ast.SpecLoc.Instance, Symbol.ComponentInstance(aNode))

override def defConstantAnnotatedNode(
a: Analysis,
Expand Down Expand Up @@ -67,7 +67,7 @@ object CheckSpecLocs
override def defTopologyAnnotatedNode(
a: Analysis,
aNode: Ast.Annotated[AstNode[Ast.DefTopology]]
) = checkSpecLoc(a, Ast.SpecLoc.Topology, Symbol.Topology(aNode))
) = checkSpecLoc(a, Ast.SpecLoc.Instance, Symbol.Topology(aNode))

private def checkSpecLoc(
a: Analysis,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import fpp.compiler.util._

/** Check topology definitions */
object CheckTopologyDefs
extends Analyzer
extends Analyzer
with ModuleAnalyzer
with TopologyAnalyzer
{
Expand All @@ -18,14 +18,14 @@ object CheckTopologyDefs
a.topologyMap.get(symbol) match {
case None =>
// Topology is not in the map: visit it
val a1 = a.copy(topology = Some(Topology(aNode)))
val a1 = a.copy(topology = Some(Topology(aNode, a.getQualifiedName(symbol))))
for {
// Visit topology members and compute unresolved top
a <- super.defTopologyAnnotatedNode(a1, aNode)
top <- Right(a.topology.get)
a <- {
// Resolve topologies directly imported by top, updating a
val tops = top.directImportMap.toList
val tops = top.directTopologies.toList
Result.foldLeft (tops) (a) ((a, tl) => {
defTopologyAnnotatedNode(a, tl._1.node)
})
Expand All @@ -41,18 +41,22 @@ object CheckTopologyDefs
}
}

override def specCompInstanceAnnotatedNode(
override def specTopPortAnnotatedNode(
a: Analysis,
aNode: Ast.Annotated[AstNode[Ast.SpecCompInstance]]
aNode: Ast.Annotated[AstNode[Ast.SpecTopPort]]
) = {
Right(a.copy(topology = Some(a.topology.get.addPortNode(aNode))))
}

override def specInstanceAnnotatedNode(
a: Analysis,
aNode: Ast.Annotated[AstNode[Ast.SpecInstance]]
) = {
val node = aNode._2
val instanceNode = node.data.instance
for {
instance <- a.getComponentInstance(instanceNode.id)
topology <- a.topology.get.addUniqueInstance(
instance,
Locations.get(node.id)
)
symbol <- a.getInterfaceInstanceSymbol(instanceNode.id)
topology <- a.topology.get.addInstanceSymbol(symbol, Locations.get(node.id))
}
yield a.copy(topology = Some(topology))
}
Expand All @@ -77,20 +81,4 @@ object CheckTopologyDefs
} yield a.copy(topology = Some(topology))
}

override def specTopImportAnnotatedNode(
a: Analysis,
aNode: Ast.Annotated[AstNode[Ast.SpecImport]]
) = {
val node = aNode._2
val topNode = node.data.sym
for {
ts <- a.getTopologySymbol(topNode.id)
topology <- a.topology.get.addImportedTopology(
ts,
Locations.get(node.id)
)
}
yield a.copy(topology = Some(topology))
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ object CheckUseDefCycles extends UseAnalyzer {
override def interfaceUse(a: Analysis, node: AstNode[Ast.QualIdent], use: Name.Qualified) =
visitUse(a, node, use)

override def topologyUse(a: Analysis, node: AstNode[Ast.QualIdent], use: Name.Qualified) =
override def interfaceInstanceUse(a: Analysis, node: AstNode[Ast.QualIdent], use: Name.Qualified) =
visitUse(a, node, use)

override def typeUse(a: Analysis, node: AstNode[Ast.TypeName], use: Name.Qualified) =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@ object CheckUses extends BasicUseAnalyzer {
(a: Analysis, udm: Map[AstNode.Id, Symbol]) => a.copy(useDefMap = udm)
)

override def componentInstanceUse(a: Analysis, node: AstNode[Ast.QualIdent], use: Name.Qualified) =
helpers.visitQualIdentNode (NameGroup.ComponentInstance) (a, node)

override def componentUse(a: Analysis, node: AstNode[Ast.QualIdent], use: Name.Qualified) =
helpers.visitQualIdentNode (NameGroup.Component) (a, node)

Expand Down Expand Up @@ -205,11 +202,11 @@ object CheckUses extends BasicUseAnalyzer {
override def portUse(a: Analysis, node: AstNode[Ast.QualIdent], use: Name.Qualified) =
helpers.visitQualIdentNode (NameGroup.Port) (a, node)

override def topologyUse(a: Analysis, node: AstNode[Ast.QualIdent], use: Name.Qualified) =
helpers.visitQualIdentNode (NameGroup.Topology) (a, node)
override def interfaceInstanceUse(a: Analysis, node: AstNode[Ast.QualIdent], use: Name.Qualified) =
helpers.visitQualIdentNode (NameGroup.PortInterfaceInstance) (a, node)

override def interfaceUse(a: Analysis, node: AstNode[Ast.QualIdent], use: Name.Qualified) =
helpers.visitQualIdentNode (NameGroup.Interface) (a, node)
helpers.visitQualIdentNode (NameGroup.PortInterface) (a, node)

override def typeUse(a: Analysis, node: AstNode[Ast.TypeName], use: Name.Qualified) = {
val data = node.data
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ object EnterSymbols
val name = data.name
val symbol = Symbol.ComponentInstance(aNode)
val nestedScope = a.nestedScope
for (nestedScope <- nestedScope.put(NameGroup.ComponentInstance)(name, symbol))
for (nestedScope <- nestedScope.put(NameGroup.PortInterfaceInstance)(name, symbol))
yield updateMap(a, symbol).copy(nestedScope = nestedScope)
}

Expand Down Expand Up @@ -165,7 +165,7 @@ object EnterSymbols
val name = data.name
val symbol = Symbol.Interface(aNode)
val nestedScope = a.nestedScope
for (nestedScope <- nestedScope.put(NameGroup.Interface)(name, symbol))
for (nestedScope <- nestedScope.put(NameGroup.PortInterface)(name, symbol))
yield updateMap(a, symbol).copy(nestedScope = nestedScope)
}

Expand All @@ -181,12 +181,12 @@ object EnterSymbols
val a1 = a.copy(scopeNameList = newScopeNameList)
for {
triple <- a1.nestedScope.innerScope.get (NameGroup.Value) (name) match {
case Some(symbol: Symbol.Module) =>
case Some(symbol: Symbol.Module) =>
// We found a module symbol with the same name at the current level.
// Re-open the scope.
val scope = a1.symbolScopeMap(symbol)
Right((a1, symbol, scope))
case Some(symbol) =>
case Some(symbol) =>
// We found a non-module symbol with the same name at the current level.
// This is an error.
val error = SemanticError.RedefinedSymbol(
Expand All @@ -195,7 +195,7 @@ object EnterSymbols
symbol.getLoc
)
Left(error)
case None =>
case None =>
// We did not find a symbol with the same name at the current level.
// Create a new module symbol now.
val symbol = Symbol.Module(aNode)
Expand Down Expand Up @@ -300,7 +300,7 @@ object EnterSymbols
val name = data.name
val symbol = Symbol.Topology(aNode)
val nestedScope = a.nestedScope
for (nestedScope <- nestedScope.put(NameGroup.Topology)(name, symbol))
for (nestedScope <- nestedScope.put(NameGroup.PortInterfaceInstance)(name, symbol))
yield updateMap(a, symbol).copy(nestedScope = nestedScope)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,6 @@ object AddDependencies extends BasicUseAnalyzer {
} yield a
}

override def componentInstanceUse(a: Analysis, node: AstNode[Ast.QualIdent], use: Name.Qualified) =
analyzeUse(a, Ast.SpecLoc.ComponentInstance, use)

override def stateMachineUse(a: Analysis, node: AstNode[Ast.QualIdent], use: Name.Qualified) =
analyzeUse(a, Ast.SpecLoc.StateMachine, use)

Expand All @@ -75,8 +72,8 @@ object AddDependencies extends BasicUseAnalyzer {
override def portUse(a: Analysis, node: AstNode[Ast.QualIdent], use: Name.Qualified) =
analyzeUse(a, Ast.SpecLoc.Port, use)

override def topologyUse(a: Analysis, node: AstNode[Ast.QualIdent], use: Name.Qualified) =
analyzeUse(a, Ast.SpecLoc.Topology, use)
override def interfaceInstanceUse(a: Analysis, node: AstNode[Ast.QualIdent], use: Name.Qualified) =
analyzeUse(a, Ast.SpecLoc.Instance, use)

override def interfaceUse(a: Analysis, node: AstNode[Ast.QualIdent], use: Name.Qualified) =
analyzeUse(a, Ast.SpecLoc.Interface, use)
Expand Down
Loading