Skip to content

Commit

Permalink
Move Transpiler options into instances (#1272)
Browse files Browse the repository at this point in the history
* Move Transpiler options into instances

* fix tests
  • Loading branch information
johnynek authored Nov 22, 2024
1 parent 6fd5607 commit 14bc380
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 70 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
- name: "build assembly"
run: "sbt \"++${{matrix.scala}}; cli/assembly\""
- name: "generate python code"
run: "./bosatsuj transpile --input_dir test_workspace/ --package_root test_workspace/ --lang python --outdir pyout --externals test_workspace/Prog.bosatsu_externals --evaluators test_workspace/Prog.bosatsu_eval"
run: "./bosatsuj transpile --input_dir test_workspace/ --package_root test_workspace/ --outdir pyout python --externals test_workspace/Prog.bosatsu_externals --evaluators test_workspace/Prog.bosatsu_eval"
- name: "run python tests"
run: "PYTHONPATH=$PYTHONPATH:$(pwd)/test_workspace:$(pwd)/pyout python3 -m unittest discover pyout -v --pattern \"*.py\""
strategy:
Expand Down Expand Up @@ -127,7 +127,7 @@ jobs:
- name: "build assembly"
run: "sbt \"++${{matrix.scala}}; cli/assembly\""
- name: "generate c code"
run: "./bosatsuj transpile --input_dir test_workspace/ --package_root test_workspace/ --lang c --outdir c_out"
run: "./bosatsuj transpile --input_dir test_workspace/ --package_root test_workspace/ --outdir c_out c"
- name: "compile generated c code"
run: |
cp c_runtime/* c_out
Expand Down
2 changes: 1 addition & 1 deletion cli/src/test/scala/org/bykn/bosatsu/PathModuleTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ class PathModuleTest extends AnyFunSuite {

test("test python transpile on the entire test_workspace") {
val out = run(
"transpile --input_dir test_workspace/ --outdir pyout --lang python --package_root test_workspace --externals test_workspace/Prog.bosatsu_externals --evaluators test_workspace/Prog.bosatsu_eval"
"transpile --input_dir test_workspace/ --outdir pyout --package_root test_workspace python --externals test_workspace/Prog.bosatsu_externals --evaluators test_workspace/Prog.bosatsu_eval"
.split("\\s+")
.toSeq: _*
)
Expand Down
45 changes: 11 additions & 34 deletions core/src/main/scala/org/bykn/bosatsu/MainModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -600,17 +600,11 @@ abstract class MainModule[IO[_]](implicit
}
}

val transOpt: Opts[Transpiler] = {
val allTrans: List[Transpiler] =
codegen.python.PythonTranspiler ::
codegen.clang.ClangTranspiler ::
Nil

implicit val arg = Transpiler.argumentFromTranspilers(allTrans)

Opts.option[Transpiler]("lang",
s"language to transpile to (${allTrans.map(_.name).sorted.mkString(", ")})")
}
val transOpt: Opts[Transpiler.Optioned[Path]] =
cats.Alternative[Opts].combineAllK(
codegen.python.PythonTranspiler.opts(pathArg) ::
codegen.clang.ClangTranspiler.opts(pathArg) ::
Nil)

sealed abstract class JsonInput {
def read: IO[String]
Expand Down Expand Up @@ -864,23 +858,19 @@ abstract class MainModule[IO[_]](implicit
case class TranspileCommand(
inputs: Inputs.Runtime,
errColor: Colorize,
generator: Transpiler,
outDir: Path,
exts: List[Path],
evals: List[Path]
generator: Transpiler.Optioned[Path],
outDir: Path
) extends MainCommand("transpile") {

// case class TranspileOut(outs: Map[PackageName, (List[String], Doc)], base: Path) extends Output
type Result = Output.TranspileOut

def run =
withEC { implicit ec =>
for {
pn <- inputs.packMap(this, Nil, errColor)
(packs, names) = pn
extStrs <- exts.traverse(readPath)
evalStrs <- evals.traverse(readPath)
dataTry = generator.renderAll(packs, extStrs, evalStrs)
optString <- generator.traverse(readPath)
dataTry = optString.transpiler.renderAll(packs, optString.args)
data <- moduleIOMonad.fromTry(dataTry)
} yield Output.TranspileOut(data, outDir)
}
Expand Down Expand Up @@ -1381,22 +1371,9 @@ abstract class MainModule[IO[_]](implicit
Opts.option[Path](
"outdir",
help = "directory to write all output into"
),
Opts
.options[Path](
"externals",
help =
"external descriptors the transpiler uses to rewrite external defs"
)
.orEmpty,
Opts
.options[Path](
"evaluators",
help = "evaluators which run values of certain types"
)
.orEmpty
)
)
.mapN(TranspileCommand(_, _, _, _, _, _))
.mapN(TranspileCommand(_, _, _, _))

val evalOpt = (Inputs.runtimeOpts, mainP, colorOpt)
.mapN(Evaluate(_, _, _))
Expand Down
47 changes: 28 additions & 19 deletions core/src/main/scala/org/bykn/bosatsu/codegen/Transpiler.scala
Original file line number Diff line number Diff line change
@@ -1,36 +1,45 @@
package org.bykn.bosatsu.codegen

import cats.data.{NonEmptyList, ValidatedNel, Validated}
import com.monovore.decline.Argument
import cats.{Applicative, Traverse}
import cats.data.NonEmptyList
import com.monovore.decline.{Argument, Opts}
import org.bykn.bosatsu.{PackageMap, Par}
import org.typelevel.paiges.Doc
import scala.util.Try

import cats.syntax.all._

trait Transpiler {
def name: String
type Args[P]
def traverseArgs: Traverse[Args]

// this gives the argument for reading files into strings
// this is a bit limited, but good enough for now
def opts[P](pathArg: Argument[P]): Opts[Transpiler.Optioned[P]]

def renderAll(
pm: PackageMap.Typed[Any],
externals: List[String],
evaluators: List[String]
args: Args[String]
)(implicit ec: Par.EC): Try[List[(NonEmptyList[String], Doc)]]
}

object Transpiler {
def argumentFromTranspilers(all: List[Transpiler]): Argument[Transpiler] =
new Argument[Transpiler] {
val nameTo = all.iterator.map(t => (t.name, t)).toMap
lazy val keys = nameTo.keys.toList.sorted.mkString(",")

def defaultMetavar: String = "transpiler"
def read(string: String): ValidatedNel[String, Transpiler] =
nameTo.get(string) match {
case Some(t) => Validated.valid(t)
case None =>
Validated.invalidNel(
s"unknown transpiler: $string, expected one of: $keys"
)
}
def optioned[P](t: Transpiler)(argsP: t.Args[P]): Optioned[P] =
new Optioned[P] {
val transpiler: t.type = t
val args = argsP
}

sealed abstract class Optioned[P] { self =>
val transpiler: Transpiler
def args: transpiler.Args[P]
def traverse[F[_]: Applicative](fn: P => F[String]): F[Optioned[String]] =
self.transpiler.traverseArgs.traverse(self.args)(fn)
.map { argsString =>
new Optioned[String] {
val transpiler: self.transpiler.type = self.transpiler
val args = argsString
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,30 +1,38 @@
package org.bykn.bosatsu.codegen.clang

import cats.data.NonEmptyList
import cats.Traverse
import cats.data.{Const, NonEmptyList}
import com.monovore.decline.{Argument, Opts}
import org.bykn.bosatsu.codegen.Transpiler
import org.bykn.bosatsu.{Identifier, MatchlessFromTypedExpr, PackageName, PackageMap, Par}
import org.typelevel.paiges.Doc
import scala.util.{Failure, Success, Try}

case object ClangTranspiler extends Transpiler {

case class Arguments()
type Args[P] = Const[Arguments, P]

def traverseArgs: Traverse[Args] = implicitly

def opts[P](pathArg: Argument[P]): Opts[Transpiler.Optioned[P]] =
Opts.subcommand("c", "generate c code") {
Opts(Transpiler.optioned(this)(Const[Arguments, P](Arguments())))
}

case class GenError(error: ClangGen.Error) extends Exception(s"clang gen error: ${error.display.render(80)}")

case class CircularPackagesFound(loop: NonEmptyList[PackageName])
extends Exception(s"circular dependencies found in packages: ${
loop.map(_.asString).toList.mkString(", ")
}")

def name = "c"

def externalsFor(pm: PackageMap.Typed[Any], arg: List[String]): ClangGen.ExternalResolver =
// TODO: we shouldn't take the arg at the higher level, but customizing args
// for backends hasn't be implemented yet
def externalsFor(pm: PackageMap.Typed[Any]): ClangGen.ExternalResolver =
ClangGen.ExternalResolver.stdExternals(pm)

def renderAll(
pm: PackageMap.Typed[Any],
externals: List[String],
evaluators: List[String]
args: Args[String]
)(implicit ec: Par.EC): Try[List[(NonEmptyList[String], Doc)]] = {
// we have to render the code in sorted order
val sorted = pm.topoSort
Expand All @@ -33,7 +41,7 @@ case object ClangTranspiler extends Transpiler {
case None =>
val matchlessMap = MatchlessFromTypedExpr.compile(pm)

val ext = externalsFor(pm, externals)
val ext = externalsFor(pm)
val doc = ClangGen.renderMain(
sortedEnv = cats.Functor[Vector]
.compose[NonEmptyList]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,68 @@ package org.bykn.bosatsu.codegen.python

import cats.data.NonEmptyList
import cats.implicits.catsKernelOrderingForOrder
import org.bykn.bosatsu.{Package, PackageMap, Par, Parser, MatchlessFromTypedExpr}
import cats.{Eval, Traverse}
import com.monovore.decline.{Argument, Opts}
import org.bykn.bosatsu.CollectionUtils.listToUnique
import org.bykn.bosatsu.codegen.Transpiler
import org.bykn.bosatsu.{Package, PackageMap, Par, Parser, MatchlessFromTypedExpr}
import org.typelevel.paiges.Doc
import scala.util.Try

import org.bykn.bosatsu.CollectionUtils.listToUnique
import cats.syntax.all._

case object PythonTranspiler extends Transpiler {
val name: String = "python"
case class Arguments[P](externals: List[P], evaluators: List[P])

type Args[P] = Arguments[P]
def traverseArgs: Traverse[Args] =
new Traverse[Args] {
def foldLeft[A, B](fa: Args[A], b: B)(f: (B, A) => B): B = {
import fa._
evaluators.foldLeft(externals.foldLeft(b)(f))(f)
}

def foldRight[A, B](fa: Args[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = {
import fa._
externals.foldRight(evaluators.foldRight(lb)(f))(f)
}

def traverse[G[_], A, B](fa: Args[A])(f: A => G[B])(implicit evidence$1: cats.Applicative[G]): G[Args[B]] = {
import fa._
(externals.traverse(f), evaluators.traverse(f)).mapN(Arguments(_, _))
}
}

// this gives the argument for reading files into strings
// this is a bit limited, but good enough for now
def opts[P](pathArg: Argument[P]): Opts[Transpiler.Optioned[P]] =
Opts.subcommand("python", "generate python code") {
implicit val impPathArg: Argument[P] = pathArg
(Opts
.options[P](
"externals",
help =
"external descriptors the transpiler uses to rewrite external defs"
)
.orEmpty,
Opts
.options[P](
"evaluators",
help = "evaluators which run values of certain types"
)
.orEmpty
)
.mapN(Arguments(_, _))
.map { arg => Transpiler.optioned(this)(arg) }
}

def renderAll(
pm: PackageMap.Typed[Any],
externals: List[String],
evaluators: List[String]
args: Args[String]
)(implicit ec: Par.EC): Try[List[(NonEmptyList[String], Doc)]] = {

import args._

val cmp = MatchlessFromTypedExpr.compile(pm)
Try {
val parsedExt =
Expand Down

0 comments on commit 14bc380

Please sign in to comment.