
Toolchain.pudu
Pudu112 lines4.4 KB
1/** @Services.Toolchain.Seam — locates and runs the installed compiler */2module PuduLangMcp.Services.Toolchain34import Std.Env as Env5import Std.Fs as Fs6import Std.Io as Io7import Std.List as List8import Std.Option as Option9import Std.Path as Path10import Std.Text as Text11import PuduLangMcp.Constants.Server as Server12import PuduLangMcp.Services.Process.Bounded as Bounded1314/** @Services.Toolchain.Toolchain — compiler location, library, version, and runner */15export type Toolchain = {16 compiler: Option[Str],17 library: Option[Str],18 version: Str,19 run: fn(Array[Str], Str, Str, Int, Int) -> Result[Bounded.Finished, Str]20}212223const EXECUTABLE: Str = "pudu"242526const LIBRARY_SEARCH_DEPTH: Int = 16272829const NOT_FOUND: Str = "pudu was not found"303132export fn locate() -> Toolchain {33 let compiler = findCompiler()34 let runner = runnerFor(compiler)35 let version = match compiler {36 case Some(_) => match runner(["version"], "", "", Server.COMMAND_TIMEOUT_MS, Server.OUTPUT_CAP_BYTES) {37 case Ok(done) => Text.stripPrefix(done.output.trim(), "pudu ")38 case Err(_) => ""39 }40 case None => ""41 }42 let library = match Env.variable(Server.ENV_PUDU_LIB) {43 case Some(given) => if hasStd(given) { Some(given) } else { Option.andThen(compiler, |path: Str| libraryFrom(path)) }44 case None => Option.andThen(compiler, |path: Str| libraryFrom(path))45 }46 Toolchain{compiler: compiler, library: library, version: version, run: runner}47}484950export fn scripted(version: Str, library: Option[Str], answer: fn(Array[Str], Str, Str) -> Bounded.Finished) -> Toolchain {51 Toolchain{compiler: Some(EXECUTABLE), library: library, version: version, run: fn(arguments: Array[Str], input: Str, directory: Str, millis: Int, capBytes: Int) -> Result[Bounded.Finished, Str] {52 Ok(answer(arguments, input, directory))53 } }54}555657export fn finished(status: Int, output: Str) -> Bounded.Finished {58 Bounded.Finished{status: status, output: output, errors: "", timedOut: false, truncated: false, millis: 0}59}606162export fn libraryFrom(compiler: Str) -> Option[Str] {63 let ancestors = List.take(&Path.ancestors(Path.directoryOf(compiler)), LIBRARY_SEARCH_DEPTH)64 for ancestor in ancestors {65 for candidate in [Path.join(ancestor, "lib/pudu"), Path.join(ancestor, "lib")] {66 if hasStd(candidate) { return Some(candidate) }67 }68 let series = Path.join(ancestor, "packages/pudu")69 if Io.exists(series) {70 match Io.list(series) {71 case Ok(names) => {72 let newest = List.reversed(&List.sorted(&names.filter(|name: Str| name.startsWith("v"))))73 for name in newest {74 let candidate = Path.join(series, name + "/lib")75 if hasStd(candidate) { return Some(candidate) }76 }77 }78 case Err(_) => ()79 }80 }81 }82 None83}848586fn findCompiler() -> Option[Str] {87 let configured = Option.andThen(Env.variable(Server.ENV_PUDU_BIN), |given: Str| if Io.exists(given) { Some(given) } else { None })88 let onPath = List.find(&Env.searchPath().map(|directory: Str| Path.join(directory, EXECUTABLE)), |candidate: Str| Io.exists(candidate))89 let path = Option.orElse(configured, onPath) ?90 match Fs.canonical(path) {91 case Ok(real) => Some(real)92 case Err(_) => Some(path)93 }94}959697fn runnerFor(compiler: Option[Str]) -> fn(Array[Str], Str, Str, Int, Int) -> Result[Bounded.Finished, Str] {98 match compiler {99 case Some(path) => fn(arguments: Array[Str], input: Str, directory: Str, millis: Int, capBytes: Int) -> Result[Bounded.Finished, Str] {100 Bounded.run(path, &arguments, input, directory, millis, capBytes)101 }102 case None => fn(arguments: Array[Str], input: Str, directory: Str, millis: Int, capBytes: Int) -> Result[Bounded.Finished, Str] {103 Err(NOT_FOUND)104 }105 }106}107108109fn hasStd(directory: Str) -> Bool {110 Io.exists(Path.join(directory, "Std/Io.pudu"))111}112