Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 3e982f9

Browse files
committed
Add Java CLI backend integration
1 parent 8778292 commit 3e982f9

12 files changed

Lines changed: 139 additions & 24 deletions

File tree

examples/go/go-greet.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,8 @@ type GoImport_go struct {
3333
}
3434

3535
func greet(name string) struct {} {
36-
return func() {
37-
go.fmt.Printf(name)
38-
return nil
39-
}()
36+
fmt.Printf(name)
37+
return struct {}{}
4038
}
4139

4240
func main() {

examples/go/go-hello.go

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,6 @@ type GoImport_go struct {
3232
fmt GoImport_fmt
3333
}
3434

35-
func main() struct {} {
36-
return func() {
37-
go.fmt.Printf("Hello from Chester with Go FFI!")
38-
return nil
39-
}()
40-
}
41-
4235
func main() {
43-
fmt.Println(main())
36+
fmt.Printf("Hello from Chester with Go FFI!")
4437
}

examples/go/go-test-field.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,5 @@ type GoImport_go struct {
3333
}
3434

3535
func main() {
36-
fmt.Println(go.fmt)
36+
fmt.Println(fmt)
3737
}

examples/go/go-test-printf-field.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,5 @@ type GoImport_go struct {
3333
}
3434

3535
func main() {
36-
fmt.Println(go.fmt.Printf)
36+
fmt.Println(fmt.Printf)
3737
}

examples/go/go-wordcount.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,8 @@ func countWords(text string) int {
66
}
77

88
func processFile(filename string) int {
9-
return func() {
10-
func() {
11-
text := "sample text content"
12-
text
13-
}()
9+
text := "sample text content"
1410
return countWords(text)
15-
}()
1611
}
1712

1813
func main() {

examples/ts/ts-simple.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
function add(x: number, y: number): number {
22
return x + y;
33
};
4-
add(40, 2);
5-
undefined;
4+
add(40, 2);

examples/ts/ts-wordcount.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,4 @@ function countWords(text: string): number {
44
function processFile(filename: string): number {
55
return countWords("sample text content");
66
};
7-
processFile("test.txt");
8-
undefined;
7+
processFile("test.txt");

modules/cli/jvm/src/test/scala/chester/cli/CLITest.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,22 @@ class CLITest extends FunSuite:
5454
assert(content.nonEmpty, clue = "TypeScript output should not be empty")
5555
}
5656

57+
test("java command writes output file for single input") {
58+
val tmpDir = os.temp.dir()
59+
val src = tmpDir / "hello.chester"
60+
val code =
61+
"""def id(x: Integer) = x;
62+
|id""".stripMargin
63+
os.write.over(src, code)
64+
65+
CLI.run[Id](Config.CompileJava(src.toString, Some(tmpDir.toString)))
66+
67+
val outFile = tmpDir / "hello.java"
68+
assert(os.exists(outFile), clue = s"Expected $outFile to be created")
69+
val content = os.read(outFile)
70+
assert(content.nonEmpty, clue = "Java output should not be empty")
71+
}
72+
5773
test("help prints usage") {
5874
val output = capture {
5975
CLI.run[Id](Config.Help)

modules/cli/jvm/src/test/scala/chester/cli/ExampleIntegrationTest.scala

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ class ExampleIntegrationTest extends FunSuite:
2727
private val goExamples: os.Path = repoRoot / "examples" / "go"
2828
private val goSigs: os.Path = repoRoot / "go-signatures.json"
2929
private val pureTsExamples: Vector[String] = Vector("ts-simple", "ts-wordcount")
30+
private val pureJavaExamples: Vector[String] = Vector("ts-simple", "ts-wordcount")
31+
32+
private def javaFileName(base: String): String =
33+
s"${base.replaceAll("[^A-Za-z0-9_$]", "_")}.java"
3034

3135
private def commandExists(cmd: String): Boolean =
3236
os.proc("sh", "-c", s"command -v $cmd").call(check = false).exitCode == 0
@@ -52,6 +56,12 @@ class ExampleIntegrationTest extends FunSuite:
5256
CLI.run[Id](Config.CompileTS(src.toString, Some(outDir.toString)))
5357
}
5458

59+
private def compilePureJavaExamples(outDir: os.Path): Unit =
60+
pureJavaExamples.foreach { base =>
61+
val src = tsExamples / s"$base.chester"
62+
CLI.run[Id](Config.CompileJava(src.toString, Some(outDir.toString)))
63+
}
64+
5565
private def compareGeneratedSubset(srcDir: os.Path, outDir: os.Path, ext: String, bases: Vector[String]): Unit =
5666
bases.foreach { base =>
5767
val expected = srcDir / s"$base.$ext"
@@ -82,6 +92,19 @@ class ExampleIntegrationTest extends FunSuite:
8292
}
8393
}
8494

95+
test("Java pure examples: generated files compile with javac") {
96+
assume(commandExists("javac"), "Skipping: javac is not available in PATH")
97+
val outDir = os.temp.dir(prefix = "chester-java-run-")
98+
compilePureJavaExamples(outDir)
99+
100+
val generated = pureJavaExamples.map(base => outDir / javaFileName(base))
101+
assert(generated.nonEmpty, s"No generated .java files in $outDir")
102+
generated.foreach { file =>
103+
val compile = os.proc("javac", file.toString).call(cwd = outDir, check = false, mergeErrIntoOut = true)
104+
assertEquals(compile.exitCode, 0, clue = s"javac failed for $file:\n${compile.out.text()}")
105+
}
106+
}
107+
85108
test("Go examples: compile and match checked-in outputs") {
86109
val outDir = os.temp.dir(prefix = "chester-go-examples-")
87110
CLI.run[Id](Config.CompileGo(goExamples.toString, Some(outDir.toString), Some(goSigs.toString)))

modules/cli/shared/src/main/scala/chester/cli/CLI.scala

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import chester.error.*
77
import chester.reader.{CharReader, FileNameAndContent, ParseError, Parser, Source, Tokenizer}
88
import chester.backend.TypeScriptBackend
99
import chester.backend.GoBackend
10+
import chester.backend.JavaBackend
1011
import chester.tyck.{CoreTypeChecker, ElabConstraint, ElabContext, ElabHandlerConf, ElabProblem, substituteSolutions}
1112
import chester.error.VectorReporter
1213
import chester.interop.typescript.TypeScriptToChester
@@ -321,6 +322,20 @@ class CLI[F[_]](using runner: Runner[F], terminal: Terminal[F], io: IO[F]) {
321322
io.pathOps.join(outDir, tsName)
322323
}
323324

325+
private def targetJavaPath(inputPath: io.Path, outDir: io.Path): io.Path = {
326+
val name = io.pathOps.baseName(inputPath)
327+
val javaBase = javaClassNameFor(io.pathOps.of(if name.contains(".") then name.replaceAll("\\.[^.]+$", ".java") else s"$name.java"))
328+
io.pathOps.join(outDir, s"$javaBase.java")
329+
}
330+
331+
private def javaClassNameFor(path: io.Path): String = {
332+
val raw = io.pathOps.baseName(path).replaceAll("\\.java$", "")
333+
val cleaned = raw.replaceAll("[^A-Za-z0-9_$]", "_")
334+
val withLead =
335+
if cleaned.headOption.exists(ch => ch.isLetter || ch == '_' || ch == '$') then cleaned else s"_$cleaned"
336+
if withLead.isEmpty then "Main" else withLead
337+
}
338+
324339
private def compileToTypeScript(input: String, output: Option[String]): F[Unit] = {
325340
val inPath = io.pathOps.of(input)
326341
for {
@@ -372,6 +387,57 @@ class CLI[F[_]](using runner: Runner[F], terminal: Terminal[F], io: IO[F]) {
372387
} yield ()
373388
}
374389

390+
private def compileToJava(input: String, output: Option[String]): F[Unit] = {
391+
val inPath = io.pathOps.of(input)
392+
for {
393+
exists <- IO.exists(inPath)
394+
_ <-
395+
if !exists then IO.println(s"Input path '$input' does not exist.", toStderr = true)
396+
else {
397+
IO.isDirectory(inPath).flatMap { isDir =>
398+
val defaultOut = {
399+
if isDir then io.pathOps.join(inPath, "java-out")
400+
else {
401+
val inStr = io.pathOps.asString(inPath)
402+
val idx = inStr.lastIndexOf('/')
403+
val javaName = if inStr.contains(".") then inStr.replaceAll("\\.[^.]+$", ".java") else s"$inStr.java"
404+
if idx >= 0 then io.pathOps.of(inStr.take(idx + 1) + javaName.split('/').last) else io.pathOps.of(javaName)
405+
}
406+
}
407+
val outBaseStr = output.getOrElse(io.pathOps.asString(defaultOut))
408+
ensureDir(outBaseStr).flatMap { outDir =>
409+
val inputsF: F[Seq[io.Path]] = {
410+
if isDir then IO.listFiles(inPath).map(_.filter(p => io.pathOps.baseName(p).endsWith(".chester")))
411+
else Runner.pure(Seq(inPath))
412+
}
413+
414+
inputsF.flatMap { paths =>
415+
paths.foldLeft(Runner.pure[F, Unit](())) { (acc, p) =>
416+
acc.flatMap { _ =>
417+
IO.readString(p).flatMap { content =>
418+
val src = Source(FileNameAndContent(io.pathOps.asString(p), content))
419+
resolveJSImportSignatures(src, content).flatMap { jsImports =>
420+
analyze(src, jsImports = jsImports) match
421+
case Left(errs) =>
422+
printLines(errs, toStderr = true)
423+
case Right((ast, _)) =>
424+
val className = javaClassNameFor(targetJavaPath(p, outDir))
425+
val javaUnit = JavaBackend.lowerProgram(ast, className = className)
426+
val rendered = javaUnit.toDoc.toString
427+
val outPath = targetJavaPath(p, outDir)
428+
IO.writeString(outPath, rendered, writeMode = WriteMode.Overwrite)
429+
.flatMap(_ => IO.println(s"Wrote Java to '${io.pathOps.asString(outPath)}'."))
430+
}
431+
}
432+
}
433+
}
434+
}
435+
}
436+
}
437+
}
438+
} yield ()
439+
}
440+
375441
private def resolveJSImportSignatures(source: Source, content: String): F[Map[String, JSImportSignature]] = {
376442
val specifiers = extractJSImportSpecifiers(source).getOrElse(Set.empty)
377443
specifiers.foldLeft(Runner.pure[F, Map[String, JSImportSignature]](Map.empty)) { (accF, spec) =>
@@ -739,6 +805,8 @@ class CLI[F[_]](using runner: Runner[F], terminal: Terminal[F], io: IO[F]) {
739805
compileFile(input, output)
740806
case Config.CompileTS(input, output) =>
741807
compileToTypeScript(input, output)
808+
case Config.CompileJava(input, output) =>
809+
compileToJava(input, output)
742810
case Config.CompileGo(input, output, goSigs) =>
743811
compileToGo(input, output, goSigs)
744812
case Config.ExtractGoTypes(packages, output) =>

0 commit comments

Comments
 (0)