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

Compiler.pudu

Pudu84 lines4.2 KB

GitHub ↗
1/** @App.Tools.Compiler.Handler — check, format, lint, run, and test tools */2module PuduLangMcp.App.Tools.Compiler34import Std.Option as Option5import Std.Path as Path6import PuduLangMcp.App.Context as Context7import PuduLangMcp.Constants.Server as Server8import PuduLangMcp.Domain.Catalog.Arguments as Arguments9import PuduLangMcp.Errors.ToolError as ToolError10import PuduLangMcp.Services.Workspace as Workspace11import PuduLangMcp.Utils.TextBounds as TextBounds1213/// Answers the compiler's diagnostics for the code.14export fn check(context: &Context.Context, args: &Arguments.Args) -> Result[Str, ToolError.ToolFailure] {15  onTarget(context, args, ["check"], Server.COMMAND_TIMEOUT_MS, fn(status: Int, said: Str) -> Str { said })16}1718/// Answers the code as the formatter lays it out.19export fn format(context: &Context.Context, args: &Arguments.Args) -> Result[Str, ToolError.ToolFailure] {20  onTarget(context, args, ["fmt", "--stdout"], Server.COMMAND_TIMEOUT_MS, fn(status: Int, said: Str) -> Str {21      if status == 0 { said } else { "The code could not be formatted:\n" + said }22    })23}2425/// Answers the linter's findings for the code.26export fn lint(context: &Context.Context, args: &Arguments.Args) -> Result[Str, ToolError.ToolFailure] {27  onTarget(context, args, ["lint"], Server.COMMAND_TIMEOUT_MS, fn(status: Int, said: Str) -> Str { said })28}2930/// Answers a confined run's exit status and output.31export fn run(context: &Context.Context, args: &Arguments.Args) -> Result[Str, ToolError.ToolFailure] {32  let millis = Arguments.integerOr(args, "timeoutMs", Server.RUN_TIMEOUT_MS)33  onTarget(context, args, ["run", "--confined"], millis, fn(status: Int, said: Str) -> Str {34      "exit status " + show(status) + (if said.isEmpty() { "" } else { "\n" + said })35    })36}3738/// Answers the report of the workspace's tests, or of those under one path.39export fn test(context: &Context.Context, args: &Arguments.Args) -> Result[Str, ToolError.ToolFailure] {40  if context.toolchain.compiler == None { return Err(ToolError.ToolchainMissing) }41  let millis = Arguments.integerOr(args, "timeoutMs", Server.RUN_TIMEOUT_MS)42  let arguments = match Arguments.text(args, "path") {43    case Some(given) => {44      let real = Workspace.resolve(&context.workspace, given) ?45      ["test", Workspace.relative(&context.workspace, real)]46    }47    case None => ["test"]48  }49  let done = match context.toolchain.run(arguments, "", context.workspace.root, millis, Server.OUTPUT_CAP_BYTES) {50    case Ok(finished) => finished51    case Err(problem) => { return Err(ToolError.CommandFailed(problem)) }52  }53  Ok(stopped(done.timedOut, millis) + combined(done.output, done.errors, ""))54}5556/// Runs a command on the code `source` or `path` names and renders what it wrote.57fn onTarget(context: &Context.Context, args: &Arguments.Args, command: Array[Str], millis: Int, render: fn(Int, Str) -> Str) -> Result[Str, ToolError.ToolFailure] {58  if context.toolchain.compiler == None { return Err(ToolError.ToolchainMissing) }59  let toolchain = context.toolchain60  let answered = Workspace.withTarget(&context.workspace, Arguments.text(args, "source"), Arguments.text(args, "path"), fn(target: Workspace.Target) -> Result[Str, ToolError.ToolFailure] {61      let file = Option.unwrapOr(Path.relativeTo(target.directory, target.file), target.file)62      match toolchain.run(command.push(file), "", target.directory, millis, Server.OUTPUT_CAP_BYTES) {63        case Ok(done) => {64          let hidden = if target.scratch { target.directory + "/" } else { "" }65          Ok(stopped(done.timedOut, millis) + render(done.status, combined(done.output, done.errors, hidden)))66        }67        case Err(problem) => Err(ToolError.CommandFailed(problem))68      }69    }) ?70  answered71}7273/// A line saying a command was stopped, or nothing.74fn stopped(timedOut: Bool, millis: Int) -> Str {75  if timedOut { "Stopped after " + show(millis) + " ms; the output so far follows.\n" } else { "" }76}7778/// Standard output then standard error, trimmed, bounded, without a hidden path prefix.79fn combined(output: Str, errors: Str, hidden: Str) -> Str {80  let both = if output.trim().isEmpty() { errors.trim() } else if errors.trim().isEmpty() { output.trim() } else { output.trim() + "\n" + errors.trim() }81  let shown = if hidden.isEmpty() { both } else { both.replace(hidden, "") }82  TextBounds.bounded(shown, Server.DOC_CHARS)83}84