fpp/compiler/lib/src/main/scala/codegen/JsonEncoder/AnalysisJsonEncoder.scala
Rob Bocchino 177b366704 Revise checking of spec locs
* Store the Ast node in the map
* Report the location of the Ast node when the 'dictionary'
  specifier is inconsistent
2025-10-28 17:03:40 -07:00

362 lines
13 KiB
Scala

package fpp.compiler.codegen
import fpp.compiler.analysis._
import fpp.compiler.ast._
import fpp.compiler.syntax._
import fpp.compiler.util._
import io.circe.syntax._
import io.circe._
import io.circe.generic.semiauto._
import io.circe.generic.auto._
import scala.util.parsing.input.Position
import AstJsonEncoder._
import LocMapJsonEncoder._
object AnalysisJsonEncoder extends JsonEncoder{
// ----------------------------------------------------------------------
// Special cases
// ----------------------------------------------------------------------
// JSON encoder for AST nodes
// Summarize the entire node with the ID, to avoid repeating info.
// We can look up the node in the AST JSON.
private implicit def astNodeEncoder[T: Encoder]: Encoder[AstNode[T]] =
new Encoder[AstNode[T]] {
override def apply(astNode: AstNode[T]): Json =
Json.obj("astNodeId" -> astNode.id.asJson)
}
// JSON encoder for annotated AST nodes
// Omit the annotations when translating analysis.
// We can look them up in the AST JSON.
private implicit def astNodeAnnotatedEncoder[T: Encoder]:
Encoder[Ast.Annotated[AstNode[T]]] =
new Encoder[Ast.Annotated[AstNode[T]]] {
override def apply(aNode: Ast.Annotated[AstNode[T]]): Json = aNode._2.asJson
}
// JSON encoder for symbols
// Report the symbol kind and the info in the SymbolInterface trait
private def symbolAsJson(symbol: SymbolInterface) = addTypeNameKey(
symbol,
Json.obj(
"nodeId" -> symbol.getNodeId.asJson,
"unqualifiedName" -> symbol.getUnqualifiedName.asJson
)
)
// JSON encoder for component instances
// Use the default Circe encoding, but replace the component instance
// with its AST node. We can use the ID to look up the component
// in the component map.
private implicit val componentInstanceEncoder: Encoder[ComponentInstance] =
Encoder.instance {
compInstance => io.circe.generic.semiauto.deriveEncoder[ComponentInstance].
apply(compInstance).asObject.get.
add("component", compInstance.component.aNode.asJson).asJson
}
// ----------------------------------------------------------------------
// Encoders for helping Circe with recursive types
// ----------------------------------------------------------------------
private implicit val enumConstantEncoder: Encoder[Value.EnumConstant] =
io.circe.generic.semiauto.deriveEncoder[Value.EnumConstant]
private implicit val generalPortInstanceKindEncoder: Encoder[PortInstance.General.Kind] =
Encoder.encodeString.contramap(getUnqualifiedClassName(_))
private implicit val portInstanceIdentifierEncoder: Encoder[PortInstanceIdentifier] =
io.circe.generic.semiauto.deriveEncoder[PortInstanceIdentifier]
private implicit val portSymbolEncoder: Encoder[Symbol.Port] =
Encoder.instance(symbolAsJson(_))
private implicit val stateMachineSymbolEncoder: Encoder[StateMachineSymbol] =
Encoder.instance(symbolAsJson(_))
private implicit val symbolEncoder: Encoder[Symbol] =
Encoder.instance(symbolAsJson(_))
private implicit val typeEncoder: Encoder[Type] =
io.circe.generic.semiauto.deriveEncoder[Type]
private implicit val valueAnonArrayEncoder: Encoder[Value.AnonArray] =
io.circe.generic.semiauto.deriveEncoder[Value.AnonArray]
private implicit val valueAnonStructEncoder: Encoder[Value.AnonStruct] =
io.circe.generic.semiauto.deriveEncoder[Value.AnonStruct]
private implicit val valueArrayEncoder: Encoder[Value.Array] =
io.circe.generic.semiauto.deriveEncoder[Value.Array]
private implicit val valueStructEncoder: Encoder[Value.Struct] =
io.circe.generic.semiauto.deriveEncoder[Value.Struct]
// ----------------------------------------------------------------------
// Methods for converting Scala maps to JSON maps
// We use this conversion when the keys can be converted to strings
// ----------------------------------------------------------------------
private def astNodeIdToString(id: AstNode.Id) = id.toString
private def mapAsJsonMap[A,B] (f1: A => String) (f2: B => Json) (map: Map[A,B]): Json =
(map.map { case (key, value) => (f1(key), f2(value)) }).asJson
private def stateMachineSymbolToIdString(s: StateMachineSymbol) = s.getNodeId.toString
private def symbolToIdString(s: Symbol) = s.getNodeId.toString
private implicit val commandMapEncoder: Encoder[Map[Command.Opcode, Command]] = {
def f1(opcode: Command.Opcode) = opcode.toString
def f2(command: Command) = command.asJson
Encoder.instance (mapAsJsonMap (f1) (f2) _)
}
private implicit val componentInstanceMapEncoder:
Encoder[Map[Symbol.ComponentInstance, ComponentInstance]] =
{
def f2(ci: ComponentInstance) = ci.asJson
Encoder.instance (mapAsJsonMap (symbolToIdString) (f2) _)
}
private implicit val componentMapEncoder:
Encoder[Map[Symbol.Component, Component]] =
{
def f2(c: Component) = c.asJson
Encoder.instance (mapAsJsonMap (symbolToIdString) (f2) _)
}
private implicit val containerMapEncoder:
Encoder[Map[Container.Id, Container]] =
{
def f1(id: Container.Id) = id.toString
def f2(c: Container) = c.asJson
Encoder.instance (mapAsJsonMap (f1) (f2) _)
}
private implicit val directImportMapEncoder:
Encoder[Map[Symbol.Topology, Location]] =
{
def f2(loc: Location) = loc.asJson
Encoder.instance (mapAsJsonMap (symbolToIdString) (f2) _)
}
private implicit val eventMapEncoder: Encoder[Map[Event.Id, Event]] = {
def f1(id: Event.Id) = id.toString
def f2(event: Event) = event.asJson
Encoder.instance (mapAsJsonMap (f1) (f2) _)
}
private implicit val limitsEncoder: Encoder[TlmChannel.Limits] = {
def f1(kind: Ast.SpecTlmChannel.LimitKind) = kind.toString
def f2(tlmPoint: (AstNode.Id, Value)) = tlmPoint.asJson
Encoder.instance (mapAsJsonMap (f1) (f2) _)
}
private implicit val nameGroupSymbolMapEncoder:
Encoder[Map[NameGroup, NameSymbolMap]] =
{
def f1(nameGroup: NameGroup) = getUnqualifiedClassName(nameGroup)
def f2(map: NameSymbolMap) = map.asJson
Encoder.instance (mapAsJsonMap (f1) (f2) _)
}
private implicit val nameSymbolMapEncoder:
Encoder[Map[Name.Unqualified, Symbol]] =
{
def f1(name: Name.Unqualified) = name.toString
def f2(symbol: Symbol) = symbol.asJson
Encoder.instance (mapAsJsonMap (f1) (f2) _)
}
private implicit val paramMapEncoder: Encoder[Map[Param.Id, Param]] = {
def f1(id: Param.Id) = id.toString
def f2(param: Param) = param.asJson
Encoder.instance (mapAsJsonMap (f1) (f2) _)
}
private implicit val patternMapEncoder:
Encoder[Map[Ast.SpecConnectionGraph.Pattern.Kind, ConnectionPattern]] =
{
def f1(kind: Ast.SpecConnectionGraph.Pattern.Kind) =
getUnqualifiedClassName(kind)
def f2(pattern: ConnectionPattern) = pattern.asJson
Encoder.instance (mapAsJsonMap (f1) (f2) _)
}
private implicit val recordMapEncoder:
Encoder[Map[Record.Id, Record]] =
{
def f1(id: Record.Id) = id.toString
def f2(c: Record) = c.asJson
Encoder.instance (mapAsJsonMap (f1) (f2) _)
}
private implicit val signalStateTransitionMapEncoder: Encoder[StateMachineAnalysis.SignalStateTransitionMap] = {
def f1(s: StateMachineSymbol.Signal) = s.getNodeId.toString
def f2(s: StateMachineSymbol.State) = s.getUnqualifiedName
def f3(t: Transition.Guarded) = t.asJson
Encoder.instance (mapAsJsonMap (f1) (mapAsJsonMap (f2) (f3) _) _)
}
private implicit val specialKindMapEncoder:
Encoder[Map[Ast.SpecPortInstance.SpecialKind, PortInstance.Special]] =
{
def f1(kind: Ast.SpecPortInstance.SpecialKind) = kind.toString
def f2(pi: PortInstance.Special) = pi.asJson
Encoder.instance (mapAsJsonMap (f1) (f2) _)
}
private implicit val stateMachineMapEncoder:
Encoder[Map[Symbol.StateMachine, StateMachine]] =
{
def f2(sm: StateMachine): Json = sm.asJson
Encoder.instance (mapAsJsonMap (symbolToIdString) (f2) _)
}
private implicit val stateMachineNameGroupMapEncoder:
Encoder[Map[StateMachineNameGroup, SmNameSymbolMap]] =
{
def f1(nameGroup: StateMachineNameGroup) = getUnqualifiedClassName(nameGroup)
def f2(map: SmNameSymbolMap) = map.asJson
Encoder.instance (mapAsJsonMap (f1) (f2) _)
}
private implicit val stateMachineNameSymbolMapEncoder:
Encoder[Map[Name.Unqualified, StateMachineSymbol]] =
{
def f1(name: Name.Unqualified) = name.toString
def f2(symbol: StateMachineSymbol) = symbol.asJson
Encoder.instance (mapAsJsonMap (f1) (f2) _)
}
private implicit val stateMachineSymbolScopeMapEncoder: Encoder[Map[StateMachineSymbol, StateMachineScope]] = {
def f2(s: StateMachineScope) = s.asJson
Encoder.instance (mapAsJsonMap (stateMachineSymbolToIdString) (f2) _)
}
private implicit val stateMachineUseDefMapEncoder: Encoder[Map[AstNode.Id, StateMachineSymbol]] = {
def f2(s: StateMachineSymbol) = s.asJson
Encoder.instance (mapAsJsonMap (astNodeIdToString) (f2) _)
}
private implicit val symbolMapEncoder: Encoder[Map[Symbol, Symbol]] = {
def f2(s: Symbol) = symbolAsJson(s)
Encoder.instance (mapAsJsonMap (symbolToIdString) (f2) _)
}
private implicit val symbolScopeMapEncoder: Encoder[Map[Symbol, Scope]] = {
def f2(s: Scope) = s.asJson
Encoder.instance (mapAsJsonMap (symbolToIdString) (f2) _)
}
private implicit val tlmChannelMapEncoder:
Encoder[Map[TlmChannel.Id, TlmChannel]] =
{
def f1(id: TlmChannel.Id) = id.toString
def f2(channel: TlmChannel) = channel.asJson
Encoder.instance (mapAsJsonMap (f1) (f2) _)
}
private implicit val topologyMapEncoder:
Encoder[Map[Symbol.Topology, Topology]] =
{
def f2(t: Topology) = t.asJson
Encoder.instance (mapAsJsonMap (symbolToIdString) (f2) _)
}
private implicit val transitionExprMapEncoder: Encoder[StateMachineAnalysis.TransitionExprMap] = {
def f1(n: AstNode[Ast.TransitionExpr]) = n.id.toString
def f2(t: Transition) = t.asJson
Encoder.instance (mapAsJsonMap (f1) (f2) _)
}
private implicit val transitionGraphArcMapEncoder: Encoder[TransitionGraph.ArcMap] = {
def f1(n: TransitionGraph.Node) = n.soc.getName
def f2(as: Set[TransitionGraph.Arc]) = (as.map(elem => elem.asJson)).toList.asJson
Encoder.instance (mapAsJsonMap (f1) (f2) _)
}
private implicit val typeMapEncoder: Encoder[Map[AstNode.Id, Type]] = {
def f2(t: Type) = t.asJson
Encoder.instance (mapAsJsonMap (astNodeIdToString) (f2) _)
}
private implicit val typeOptionMap: Encoder[Map[StateMachineTypedElement, Option[Type]]] = {
def f1(e: StateMachineTypedElement) = e.getNodeId.toString
def f2(t: Option[Type]) = t.asJson
Encoder.instance (mapAsJsonMap (f1) (f2) _)
}
private implicit val useDefMapEncoder: Encoder[Map[AstNode.Id, Symbol]] = {
def f2(s: Symbol) = s.asJson
Encoder.instance (mapAsJsonMap (astNodeIdToString) (f2) _)
}
private implicit val valueMapEncoder: Encoder[Map[AstNode.Id, Value]] = {
def f2(value: Value) = value.asJson
Encoder.instance (mapAsJsonMap (astNodeIdToString) (f2) _)
}
// ----------------------------------------------------------------------
// Methods for converting Scala maps to JSON lists
// We use this conversion when the keys cannot be converted to strings
// ----------------------------------------------------------------------
private implicit val componentInstanceLocationMapEncoder:
Encoder[Map[ComponentInstance, (Ast.Visibility, Location)]] =
Encoder.instance(_.toList.asJson)
private implicit val connectionMapEncoder:
Encoder[Map[PortInstanceIdentifier, Set[Connection]]] =
Encoder.instance(_.toList.asJson)
private implicit val locationSpecifierMapEncoder:
Encoder[Map[(Ast.SpecLoc.Kind, Name.Qualified), AstNode[Ast.SpecLoc]]] =
Encoder.instance(_.toList.asJson)
private implicit val portNumberMapEncoder: Encoder[Map[Connection, Int]] =
Encoder.instance(_.toList.asJson)
private implicit val stateMachineAnalysisEncoder: Encoder[StateMachineAnalysis] = {
def stateMachineAnalysisToJson(sma: StateMachineAnalysis): Json =
Json.obj(
"symbol" -> sma.symbol.asJson,
"symbolScopeMap" -> sma.symbolScopeMap.asJson,
"useDefMap" -> sma.useDefMap.asJson,
"transitionGraph" -> sma.transitionGraph.asJson,
"reverseTransitionGraph" -> sma.reverseTransitionGraph.asJson,
"typeOptionMap" -> sma.typeOptionMap.asJson,
"flattenedStateTransitionMap" -> sma.flattenedStateTransitionMap.asJson,
"flattenedChoiceTransitionMap" -> sma.flattenedChoiceTransitionMap.asJson
)
Encoder.instance(stateMachineAnalysisToJson(_))
}
// ----------------------------------------------------------------------
// The public encoder interface
// ----------------------------------------------------------------------
/** Converts the Analysis data structure to JSON */
def analysisToJson(a: Analysis): Json = {
Json.obj(
"componentInstanceMap" -> a.componentInstanceMap.asJson,
"componentMap" -> a.componentMap.asJson,
"includedFileSet" -> a.includedFileSet.asJson,
"inputFileSet" -> a.inputFileSet.asJson,
"locationSpecifierMap" -> a.locationSpecifierMap.asJson,
"parentSymbolMap" -> a.parentSymbolMap.asJson,
"symbolScopeMap" -> a.symbolScopeMap.asJson,
"topologyMap" -> a.topologyMap.asJson,
"typeMap" -> a.typeMap.asJson,
"useDefMap" -> a.useDefMap.asJson,
"valueMap" -> a.valueMap.asJson,
"stateMachineMap" -> a.stateMachineMap.asJson,
"dictionarySymbolSet" -> a.dictionarySymbolSet.asJson
)
}
}