@@ -7,6 +7,7 @@ import chester.error.*
77import chester .reader .{CharReader , FileNameAndContent , ParseError , Parser , Source , Tokenizer }
88import chester .backend .TypeScriptBackend
99import chester .backend .GoBackend
10+ import chester .backend .JavaBackend
1011import chester .tyck .{CoreTypeChecker , ElabConstraint , ElabContext , ElabHandlerConf , ElabProblem , substituteSolutions }
1112import chester .error .VectorReporter
1213import 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