Skip to content

Commit

Permalink
harden codegen: ensure that users can start with a blank page... (#247)
Browse files Browse the repository at this point in the history
* harden codegen: ensure that users can start with a blank page...

... without any compiler errors or warnings in the generated domain classes

* fmt
  • Loading branch information
mpollmeier authored Aug 2, 2024
1 parent 82cbc42 commit 9f16c9b
Show file tree
Hide file tree
Showing 21 changed files with 452 additions and 137 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,9 @@ class DomainClassesGenerator(schema: Schema) {
|""".stripMargin
)

os.write(nodesOutputDir / "RootTypesTraversals.scala", generateRootTypesTraversals(schema))
generateRootTypesTraversals(schema).foreach { source =>
os.write(nodesOutputDir / "RootTypesTraversals.scala", source)
}

val markerTraitsForProperties = relevantProperties
.map { p =>
Expand All @@ -146,75 +148,76 @@ class DomainClassesGenerator(schema: Schema) {
|trait Has${p.className}EMT""".stripMargin
}
.mkString("\n")
val basetypefile = schema.nodeBaseTypes
.map { baseType =>
val newExtendz = newExtendzMap(baseType)
val mixinsBase = List("AbstractNode") ++ newExtendz.map(_.className + "Base") ++ baseType.markerTraits.map(_.name)

val mixinsStored =
List("StoredNode", s"${baseType.className}Base") ++ newExtendz.map(_.className) ++ baseType.markerTraits.map(_.name)
val mixinsNew =
List("NewNode", s"${baseType.className}Base") ++ baseType.extendz.map(_.className + "New") ++ baseType.markerTraits.map(_.name)
val newProperties = newPropsAtNodeList(baseType)
val propertyDefaults = newProperties
.collect {
case p if p.hasDefault =>
s"""val ${p.className} = ${Helpers.defaultValueImpl(p.default.get)}"""
}
.mkString("\n")
val mixinsEMT =
(List("AnyRef") ++ newExtendz.map { p => s"${p.className}EMT" } ++ newProperties.map { p => s"Has${p.className}EMT" })
.mkString(" with ")
val oldProperties = baseType.properties.toSet.diff(newProperties.toSet).toList.sortBy(_.name)
val oldExtendz = baseType.extendzRecursively.toSet.diff(newExtendz.toSet).toList.sortBy(_.name)

val newNodeDefs: Seq[String] = {
for {
property <- newProperties
pname = camelCase(property.name)
ptyp = unpackTypeUnboxed(property.valueType, isStored = false, raised = false)
} yield property.cardinality match {
case Cardinality.List =>
Seq(
s"def ${pname}: IndexedSeq[$ptyp]",
s"def ${pname}_=(value: IndexedSeq[$ptyp]): Unit",
s"def ${pname}(value: IterableOnce[$ptyp]): this.type"
)
case Cardinality.ZeroOrOne =>
Seq(
s"def ${pname}: Option[$ptyp]",
s"def ${pname}_=(value: Option[$ptyp]): Unit",
s"def ${pname}(value: Option[$ptyp]): this.type",
s"def ${pname}(value: $ptyp): this.type"
)
case one: Cardinality.One[?] =>
Seq(s"def ${pname}: $ptyp", s"def ${pname}_=(value: $ptyp): Unit", s"def ${pname}(value: $ptyp): this.type")
}
}.flatten

s"""trait ${baseType.className}EMT extends $mixinsEMT
|
|trait ${baseType.className}Base extends ${mixinsBase.mkString(" with ")} with StaticType[${baseType.className}EMT]
| // new properties: ${newProperties.map { _.name }.mkString(", ")}
| // inherited properties: ${oldProperties.map { _.name }.mkString(", ")}
| // inherited interfaces: ${oldExtendz.map(_.name).mkString(", ")}
| // implementing nodes: ${nodeTypes
.filter { n => n.extendzRecursively.contains(baseType) }
.map(_.name)
.mkString(", ")}
|trait ${baseType.className} extends ${mixinsStored.mkString(" with ")} with StaticType[${baseType.className}EMT]
|
|object ${baseType.className} {
| object PropertyDefaults {
| $propertyDefaults
| }
|}
|
|trait ${baseType.className}New extends ${mixinsNew.mkString(" with ")} with StaticType[${baseType.className}EMT]{
| ${newNodeDefs.mkString("\n")}
|}
|""".stripMargin
// format: off
val basetypefile = schema.nodeBaseTypes.map { baseType =>
val newExtendz = newExtendzMap(baseType)
val mixinsBase = List("AbstractNode") ++ newExtendz.map(_.className + "Base") ++ baseType.markerTraits.map(_.name)

val mixinsStored =
List("StoredNode", s"${baseType.className}Base") ++ newExtendz.map(_.className) ++ baseType.markerTraits.map(_.name)
val mixinsNew =
List("NewNode", s"${baseType.className}Base") ++ baseType.extendz.map(_.className + "New") ++ baseType.markerTraits.map(_.name)
val newProperties = newPropsAtNodeList(baseType)
val propertyDefaults = newProperties
.collect {
case p if p.hasDefault =>
s"""val ${p.className} = ${Helpers.defaultValueImpl(p.default.get)}"""
}
.mkString("\n")
val mixinsEMT =
(List("AnyRef") ++ newExtendz.map { p => s"${p.className}EMT" } ++ newProperties.map { p => s"Has${p.className}EMT" })
.mkString(" with ")
val oldProperties = baseType.properties.toSet.diff(newProperties.toSet).toList.sortBy(_.name)
val oldExtendz = baseType.extendzRecursively.toSet.diff(newExtendz.toSet).toList.sortBy(_.name)

val newNodeDefs: Seq[String] = {
for {
property <- newProperties
pname = camelCase(property.name)
ptyp = unpackTypeUnboxed(property.valueType, isStored = false, raised = false)
} yield property.cardinality match {
case Cardinality.List =>
Seq(
s"def ${pname}: IndexedSeq[$ptyp]",
s"def ${pname}_=(value: IndexedSeq[$ptyp]): Unit",
s"def ${pname}(value: IterableOnce[$ptyp]): this.type"
)
case Cardinality.ZeroOrOne =>
Seq(
s"def ${pname}: Option[$ptyp]",
s"def ${pname}_=(value: Option[$ptyp]): Unit",
s"def ${pname}(value: Option[$ptyp]): this.type",
s"def ${pname}(value: $ptyp): this.type"
)
case one: Cardinality.One[?] =>
Seq(s"def ${pname}: $ptyp", s"def ${pname}_=(value: $ptyp): Unit", s"def ${pname}(value: $ptyp): this.type")
}
}.flatten

s"""trait ${baseType.className}EMT extends $mixinsEMT
|
|trait ${baseType.className}Base extends ${mixinsBase.mkString(" with ")} with StaticType[${baseType.className}EMT]
| // new properties: ${newProperties.map { _.name }.mkString(", ")}
| // inherited properties: ${oldProperties.map { _.name }.mkString(", ")}
| // inherited interfaces: ${oldExtendz.map(_.name).mkString(", ")}
| // implementing nodes: ${nodeTypes
.filter { n => n.extendzRecursively.contains(baseType) }
.map(_.name)
.mkString(", ")}
|trait ${baseType.className} extends ${mixinsStored.mkString(" with ")} with StaticType[${baseType.className}EMT]
|
|object ${baseType.className} {
| object PropertyDefaults {
| $propertyDefaults
| }
|}
|
|trait ${baseType.className}New extends ${mixinsNew.mkString(" with ")} with StaticType[${baseType.className}EMT]{
| ${newNodeDefs.mkString("\n")}
|}
|""".stripMargin
}
// format: on
.mkString(
s"""package $basePackage.nodes
|
Expand All @@ -225,49 +228,50 @@ class DomainClassesGenerator(schema: Schema) {
os.write(nodesOutputDir / "BaseTypes.scala", basetypefile)

val edgeKindByEdgeType = edgeTypes.iterator.zipWithIndex.toMap
val edgeTypesSource = edgeTypes.iterator
.map { edgeType =>
// format: off
val propertyAccessorMaybe: Option[String] = edgeType.property.map { p =>
p.cardinality match {
case _: Cardinality.One[?] =>
s"""def ${camelCase(p.name)}: ${unpackTypeUnboxed(p.valueType, true )} =
| this.property.asInstanceOf[${unpackTypeUnboxed(p.valueType, true)}]""".stripMargin
case Cardinality.ZeroOrOne =>
s"""def ${camelCase(p.name)}: Option[${unpackTypeUnboxed(p.valueType, true)}] =
| Option(this.property.asInstanceOf[${unpackTypeBoxed(p.valueType, true)}])""".stripMargin
case Cardinality.List =>
throw new RuntimeException("edge properties are only supported with cardinality one or optional")
}
// format: off
val edgeTypesSource = edgeTypes.iterator.map { edgeType =>
val propertyAccessorMaybe: Option[String] = edgeType.property.map { p =>
p.cardinality match {
case _: Cardinality.One[?] =>
s"""def ${camelCase(p.name)}: ${unpackTypeUnboxed(p.valueType, true )} =
| this.property.asInstanceOf[${unpackTypeUnboxed(p.valueType, true)}]""".stripMargin
case Cardinality.ZeroOrOne =>
s"""def ${camelCase(p.name)}: Option[${unpackTypeUnboxed(p.valueType, true)}] =
| Option(this.property.asInstanceOf[${unpackTypeBoxed(p.valueType, true)}])""".stripMargin
case Cardinality.List =>
throw new RuntimeException("edge properties are only supported with cardinality one or optional")
}
}

val propertyNameImplForObject = edgeType.property.map { property=>
s"""val propertyName: Option[String] = Some("${property.name}")"""
}
val propertyNameImplForClass = propertyNameImplForObject.map { _ =>
s"override def propertyName: Option[String] = ${edgeType.className}.propertyName"
}
val propertyNameImplForObject = edgeType.property.map { property=>
s"""val propertyName: Option[String] = Some("${property.name}")"""
}
val propertyNameImplForClass = propertyNameImplForObject.map { _ =>
s"override def propertyName: Option[String] = ${edgeType.className}.propertyName"
}

s"""object ${edgeType.className} {
| val Label = "${edgeType.name}"
| ${propertyNameImplForObject.getOrElse("")}
|}
|
|class ${edgeType.className}(src_4762: flatgraph.GNode, dst_4762: flatgraph.GNode, subSeq_4862: Int, property_4862: Any)
| extends flatgraph.Edge(src_4762, dst_4762, ${edgeKindByEdgeType(edgeType)}.toShort, subSeq_4862, property_4862) {
| ${propertyNameImplForClass.getOrElse("")}
|}
|""".stripMargin
// format: on
s"""object ${edgeType.className} {
| val Label = "${edgeType.name}"
| ${propertyNameImplForObject.getOrElse("")}
|}
|
|class ${edgeType.className}(src_4762: flatgraph.GNode, dst_4762: flatgraph.GNode, subSeq_4862: Int, property_4862: Any)
| extends flatgraph.Edge(src_4762, dst_4762, ${edgeKindByEdgeType(edgeType)}.toShort, subSeq_4862, property_4862) {
| ${propertyNameImplForClass.getOrElse("")}
|}
|""".stripMargin
// format: on
}
.mkString(
s"""package $basePackage.edges
|
|""".stripMargin,
|
|""".stripMargin,
"\n",
"\n"
)
os.write(edgesOutputDir / "EdgeTypes.scala", edgeTypesSource)
if (edgeTypes.nonEmpty) {
os.write(edgesOutputDir / "EdgeTypes.scala", edgeTypesSource)
}

nodeTypes.iterator.zipWithIndex.foreach { case (nodeType, kind) =>
val newExtendz = newExtendzMap(nodeType)
Expand Down Expand Up @@ -697,13 +701,13 @@ class DomainClassesGenerator(schema: Schema) {
|object GraphSchema extends flatgraph.Schema {
| private val nodeLabels = IndexedSeq($nodeLabelsSrc)
| val nodeKindByLabel = nodeLabels.zipWithIndex.toMap
| val edgeLabels = Array(${edgeTypes.map { e => s""""${e.name}"""" }.mkString(", ")})
| val edgeLabels: Array[String] = Array(${edgeTypes.map { e => s""""${e.name}"""" }.mkString(", ")})
| val edgeKindByLabel = edgeLabels.zipWithIndex.toMap
| val edgePropertyAllocators: Array[Int => Array[?]] = Array($edgePropertyAllocatorsSrc)
| val nodeFactories: Array[(flatgraph.Graph, Int) => nodes.StoredNode] = Array($nodeFactoriesSrc)
| val edgeFactories: Array[(flatgraph.GNode, flatgraph.GNode, Int, Any) => flatgraph.Edge] = Array($edgeFactoriesSrc)
| val nodePropertyAllocators: Array[Int => Array[?]] = Array($nodePropertyAllocatorsSrc)
| val normalNodePropertyNames = Array(${relevantProperties.map { p => s""""${p.name}"""" }.mkString(", ")})
| val normalNodePropertyNames: Array[String] = Array(${relevantProperties.map { p => s""""${p.name}"""" }.mkString(", ")})
| val nodePropertyByLabel = normalNodePropertyNames.zipWithIndex.toMap$nodePropertyByLabelSrc
| val nodePropertyDescriptors: Array[FormalQtyType.FormalQuantity | FormalQtyType.FormalType] = ${nodePropertyDescriptorsSource.mkString("\n")}
| ${newNodePropertyHelpers}
Expand Down Expand Up @@ -1392,24 +1396,26 @@ class DomainClassesGenerator(schema: Schema) {

/** Generate accessors for all edge types on all Traversals for each node type. Analogous to the steps directly on StoredNode
*/
def generateRootTypesTraversals(schema: Schema): String = {
val neighborSteps = schema.edgeTypes
.map { edgeType =>
val stepNameBase = s"_${camelCase(edgeType.name)}"
s"""
|final def ${stepNameBase}Out: Iterator[StoredNode] = iterator.flatMap(_.${stepNameBase}Out)
|final def ${stepNameBase}In: Iterator[StoredNode] = iterator.flatMap(_.${stepNameBase}In)
|""".stripMargin
}
.mkString("\n")
def generateRootTypesTraversals(schema: Schema): Option[String] = {
val neighborSteps = schema.edgeTypes.map { edgeType =>
val stepNameBase = s"_${camelCase(edgeType.name)}"
s"""
|final def ${stepNameBase}Out: Iterator[StoredNode] = iterator.flatMap(_.${stepNameBase}Out)
|final def ${stepNameBase}In: Iterator[StoredNode] = iterator.flatMap(_.${stepNameBase}In)
|""".stripMargin
}

s"""
|package ${schema.basePackage}.nodes
|
|extension (iterator: Iterator[StoredNode]) {
| $neighborSteps
|}
|""".stripMargin
if (neighborSteps.isEmpty) {
None
} else {
Some(s"""
|package ${schema.basePackage}.nodes
|
|extension (iterator: Iterator[StoredNode]) {
| ${neighborSteps.mkString("\n")}
|}
|""".stripMargin)
}
}

def generatePropertyTraversals(property: Property[?], propertyId: Int): String = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import flatgraph.FormalQtyType
object GraphSchema extends flatgraph.Schema {
private val nodeLabels = IndexedSeq("CALL", "METHOD")
val nodeKindByLabel = nodeLabels.zipWithIndex.toMap
val edgeLabels = Array("CALL")
val edgeLabels: Array[String] = Array("CALL")
val edgeKindByLabel = edgeLabels.zipWithIndex.toMap
val edgePropertyAllocators: Array[Int => Array[?]] = Array(size => null)
val nodeFactories: Array[(flatgraph.Graph, Int) => nodes.StoredNode] =
Expand All @@ -14,8 +14,8 @@ object GraphSchema extends flatgraph.Schema {
Array((s, d, subseq, p) => new edges.Call(s, d, subseq, p))
val nodePropertyAllocators: Array[Int => Array[?]] =
Array(size => new Array[String](size), size => new Array[String](size), size => new Array[Int](size))
val normalNodePropertyNames = Array("DISPATCH_TYPE", "NAME", "ORDER")
val nodePropertyByLabel = normalNodePropertyNames.zipWithIndex.toMap
val normalNodePropertyNames: Array[String] = Array("DISPATCH_TYPE", "NAME", "ORDER")
val nodePropertyByLabel = normalNodePropertyNames.zipWithIndex.toMap
val nodePropertyDescriptors: Array[FormalQtyType.FormalQuantity | FormalQtyType.FormalType] = {
val nodePropertyDescriptors = new Array[FormalQtyType.FormalQuantity | FormalQtyType.FormalType](12)
for (idx <- Range(0, 12)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package testdomains.empty;

import java.util.HashSet;
import java.util.Set;

public class EdgeTypes {


public static Set<String> ALL = new HashSet<String>() {{

}};

}
Loading

0 comments on commit 9f16c9b

Please sign in to comment.