Pudu programming language
Menu
Package

@chrismichaelps / pudu-lang-mcp

Model Context Protocol server for Pudu language documentation and compiler tools

0.1.1Apache-2.01

InstallClose

Toolchain.pudu

Pudu112 lines4.4 KB

GitHub ↗
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}2122/// The executable's file name.23const EXECUTABLE: Str = "pudu"2425/// How many ancestors of the executable's directory are searched for the library.26const LIBRARY_SEARCH_DEPTH: Int = 162728/// What `run` answers when no compiler was found.29const NOT_FOUND: Str = "pudu was not found"3031/// The toolchain this machine has, located from the environment.32export 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}4849/// A toolchain that answers from a function instead of a process.50export 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}5556/// A finished run with a status and output, for scripted toolchains.57export fn finished(status: Int, output: Str) -> Bounded.Finished {58  Bounded.Finished{status: status, output: output, errors: "", timedOut: false, truncated: false, millis: 0}59}6061/// The standard library an installed or development compiler uses.62export 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}8485/// The compiler named by `PUDU_BIN`, else the first on the search path, canonicalized.86fn 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}9596/// The run function for a compiler, or one that always reports it missing.97fn 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}107108/// Whether a directory holds the standard library's `Std` modules.109fn hasStd(directory: Str) -> Bool {110  Io.exists(Path.join(directory, "Std/Io.pudu"))111}112