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

Scala Hammer IR library #388

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions hammer_ir/scalalib/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
out/
*.jar
amm
mill
test/tmp_*
9 changes: 9 additions & 0 deletions hammer_ir/scalalib/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Hammer IR Scala library
=======================

This API is experimental and subject to change at any time.

To get a JAR, run `./build.sh`.
You can find the output JAR in `out/hammer_ir/assembly/dest/out.jar`.

TODO(edwardw): write documentation for what this is and how to use it.
17 changes: 17 additions & 0 deletions hammer_ir/scalalib/build.sc
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import mill._, scalalib._

object hammer_ir extends SbtModule {
def scalaVersion = "2.12.6"

// Unrooted submodule
override def millSourcePath = super.millSourcePath / ammonite.ops.up

def ivyDeps = Agg(
ivy"com.typesafe.play::play-json:2.6.10"
)

object test extends Tests {
def ivyDeps = Agg(ivy"org.scalatest::scalatest:3.0.4")
def testFrameworks = Seq("org.scalatest.tools.Framework")
}
}
6 changes: 6 additions & 0 deletions hammer_ir/scalalib/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/sh
if [ ! -f "./mill" ]; then
./get_mill.sh
fi
./mill hammer_ir.assembly
cp out/hammer_ir/assembly/dest/out.jar hammer_ir.jar
4 changes: 4 additions & 0 deletions hammer_ir/scalalib/get_ammonite.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/sh
set -ex
wget -nv https://github.com/lihaoyi/Ammonite/releases/download/1.6.6/2.12-1.6.6 -O amm
chmod +x ./amm
4 changes: 4 additions & 0 deletions hammer_ir/scalalib/get_mill.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/sh
set -ex
wget -nv https://github.com/lihaoyi/mill/releases/download/0.3.6/0.3.6 -O mill
chmod +x ./mill
2 changes: 2 additions & 0 deletions hammer_ir/scalalib/scalatest.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/sh
./mill hammer_ir.test
62 changes: 62 additions & 0 deletions hammer_ir/scalalib/src/main/scala/HammerObject.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// See LICENSE for licence details.

package hammer_ir

import scala.reflect.ClassTag

import play.api.libs.json.{Json, JsObject, JsValue}

/**
* All Hammer IR types that can be converted to a JsValue.
*/
trait JSONConvertible {
/**
* Turn this object into a Play JSON object.
*/
private[hammer_ir] def toJSON: JsValue
}

/**
* All Hammer IR objects implement this trait which allows them to be
* serialized to/deserialized from JSON.
*/
trait HammerObject {
/**
* Turn this object into a Play JSON object.
*/
private[hammer_ir] def toJSON: JsObject

/**
* Turn this object into a JSON string.
*/
override def toString = Json.prettyPrint(toJSON)

/**
* Write this object as a JSON string into the given file.
*/
def toFile(filename: String) = reflect.io.File(filename).writeAll(toString)
}

/**
* Abstract class mixed into companion classes of Hammer IR objects.
*/
abstract class HammerObjectCompanion[T <: HammerObject : ClassTag] {
/**
* Create this object from a Play JSON object.
*/
private[hammer_ir] def fromJSON(json: JsObject): T

/**
* Create this object from a JSON string.
*/
def fromString(json: String): T = fromJSON(Json.parse(json).as[JsObject])

/**
* Create this object from a JSON file.
*/
def fromFile(filename: String): T = {
val source = scala.io.Source.fromFile(filename)
val lines = try source.mkString finally source.close()
fromString(lines)
}
}
136 changes: 136 additions & 0 deletions hammer_ir/scalalib/src/main/scala/PlacementConstraint.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// See LICENSE for licence details.

package hammer_ir

import play.api.libs.json.{JsNumber, JsObject, JsString}

sealed abstract class PlacementConstraintType extends JSONConvertible {
override def toJSON = JsString(stringVal)
// Force objects to implement this since toString is defined by
// default.
def stringVal: String
override def toString = stringVal
}
object PlacementConstraintType {
def fromString(input: String): PlacementConstraintType = {
values.foreach { i =>
if (i.stringVal == input) return i
}
throw new IllegalArgumentException(s"Illegal PlacementConstraintType $input")
}

case object Dummy extends PlacementConstraintType {
override def stringVal = "dummy"
}
case object Placement extends PlacementConstraintType {
override def stringVal = "placement"
}
case object TopLevel extends PlacementConstraintType {
override def stringVal = "toplevel"
}
case object HardMacro extends PlacementConstraintType {
override def stringVal = "hardmacro"
}
case object Hierarchical extends PlacementConstraintType {
override def stringVal = "hierarchical"
}
case object Obstruction extends PlacementConstraintType {
override def stringVal = "obstruction"
}

def values = Seq(
Dummy, Placement, TopLevel, HardMacro, Hierarchical, Obstruction
)
}

sealed abstract class ObstructionType
object ObstructionType {
case object Place extends ObstructionType
case object Route extends ObstructionType
case object Power extends ObstructionType
}

case class Margins(
left: Double,
bottom: Double,
right: Double,
top: Double
)

case class PlacementConstraint(
path: String,
`type`: PlacementConstraintType,
x: Double,
y: Double,
width: Double,
height: Double,
orientation: Option[String],
margins: Option[Margins],
top_layer: Option[String],
layers: Option[Seq[String]],
obs_types: Option[Seq[ObstructionType]]
) extends HammerObject {
override def toJSON = {
JsObject(Seq(
"path" -> JsString(path),
"type" -> `type`.toJSON,
"x" -> JsNumber(x),
"y" -> JsNumber(y),
"width" -> JsNumber(width),
"height" -> JsNumber(height)
// TODO(edwardw): FIXME
))
}
}

object PlacementConstraint extends HammerObjectCompanion[PlacementConstraint] {
def apply(
path: String,
`type`: PlacementConstraintType,
x: Double,
y: Double,
width: Double,
height: Double
): PlacementConstraint = {
new PlacementConstraint(
path,
`type`,
x,
y,
width,
height,
None, // TODO(edwardw): FIXME
None,
None,
None,
None
)
}

/* Helper function used to convert strings to Double
* for forwards-compatibility with Decimal type. */
def doubleOrString(value: play.api.libs.json.JsLookupResult): Double = {
try {
value.as[Double]
} catch {
case _: play.api.libs.json.JsResultException =>
value.as[String].toDouble
}
}

override def fromJSON(json: JsObject): PlacementConstraint = {
new PlacementConstraint(
(json \ "path").as[String],
PlacementConstraintType.fromString((json \ "type").as[String]),
doubleOrString(json \ "x"),
doubleOrString(json \ "y"),
doubleOrString(json \ "width"),
doubleOrString(json \ "height"),
None, // TODO(edwardw): FIXME
None,
None,
None,
None
)
}
}
34 changes: 34 additions & 0 deletions hammer_ir/scalalib/src/test/scala/HammerObjectSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// See LICENSE for licence details.

package hammer_ir.test

import org.scalatest.{FlatSpec, Matchers}
import play.api.libs.json.{JsObject, JsString}

import hammer_ir._

class HammerObjectSpec extends FlatSpec with Matchers {
behavior of "HammerObject"

case class Test(foobar: String) extends HammerObject {
override def toJSON = JsObject(Seq(
"foobar" -> JsString(foobar)
))
}
object Test extends HammerObjectCompanion[Test] {
override def fromJSON(json: JsObject): Test = {
new Test(
(json \ "foobar").as[String]
)
}
}

it should "serialize and deserialize correctly" in {
val t = Test("helloworld")
// Need these replaces to be robust to spacing variations
assert(t.toString
.replaceAll("\n", "").replaceAll(" ", "")
=== """{"foobar":"helloworld"}""")
assert(Test.fromJSON(t.toJSON) == t)
}
}
15 changes: 15 additions & 0 deletions hammer_ir/scalalib/test/placement_test/script.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// See LICENSE for licence details.

import $cp.`hammer_ir.jar`

import hammer_ir._

// Read and write back
val p = PlacementConstraint.fromFile("c1.json")

p.`type` match {
case PlacementConstraintType.Placement => println("Am placement")
case _ => println("Am not placement")
}

p.toFile("c1-out.json")
24 changes: 24 additions & 0 deletions hammer_ir/scalalib/test/prep.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/bash
# Test preparation script.
# Ensures that ammonite and the Hammer IR JAR are built.

set -e
set -euo pipefail

script_dir=$(dirname $0)
cd $script_dir

# Build Hammer IR
pushd ..
./build.sh

# Ensure ammonite exists
if [ ! -f "amm" ]; then
./get_ammonite.sh
fi

popd

# Create ammonite wrapper to import hammer_ir JAR.
echo "../amm --predef-code 'import ammonite.ops._; interp.load.cp(pwd/os.up/\"hammer_ir.jar\")' \$1" > amm
chmod +x amm
4 changes: 4 additions & 0 deletions hammer_ir/scalalib/test/run_tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/sh
set -ex
cd $(dirname $0)
./test_*
Loading