
Compiler.pudu
Pudu84 lines4.2 KB
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 TextBounds121314export 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}171819export 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}242526export 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}293031export 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}373839export 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}555657fn 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}727374fn stopped(timedOut: Bool, millis: Int) -> Str {75 if timedOut { "Stopped after " + show(millis) + " ms; the output so far follows.\n" } else { "" }76}777879fn 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