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

Reference.pudu

Pudu64 lines3.2 KB

GitHub ↗
1/** @App.Tools.Reference.HandlerAPI search and module reference tools */2module PuduLangMcp.App.Tools.Reference34import Std.List as List5import Std.Option as Option6import PuduLangMcp.App.Context as Context7import PuduLangMcp.Constants.Server as Server8import PuduLangMcp.Domain.Catalog.Arguments as Arguments9import PuduLangMcp.Domain.Reference.Entry as Entry10import PuduLangMcp.Errors.ToolError as ToolError11import PuduLangMcp.Services.Reference as Reference12import PuduLangMcp.Utils.TextBounds as TextBounds1314/// What marks a query as a type shape rather than a name.15const SHAPE_MARK: Str = "->"1617/// Most module names suggested for an unknown module.18const SUGGESTIONS: Int = 81920/// Answers declarations matching a name, description, or type shape.21export fn search(context: &Context.Context, args: &Arguments.Args) -> Result[Str, ToolError.ToolFailure] {22  let query = Option.unwrapOr(Arguments.text(args, "query"), "").trim()23  let limit = Arguments.integerOr(args, "limit", Server.DEFAULT_SEARCH_LIMIT)24  if query.contains(SHAPE_MARK) { return shapeSearch(context, query) }25  let entries = Context.entries(context) ?26  let found = Entry.search(&entries, query, limit)27  if found.isEmpty() { return Ok("No declaration matches '" + query + "'.") }28  Ok(TextBounds.bounded(found.map(|held: Entry.Found| Entry.render(&held.entry)).join("\n\n"), Server.DOC_CHARS))29}3031/// Answers every public declaration of the module the `module` argument names.32export fn moduleTool(context: &Context.Context, args: &Arguments.Args) -> Result[Str, ToolError.ToolFailure] {33  moduleText(context, Option.unwrapOr(Arguments.text(args, "module"), "").trim())34}3536/// Every public declaration of one module, rendered.37export fn moduleText(context: &Context.Context, moduleName: Str) -> Result[Str, ToolError.ToolFailure] {38  let file = match Reference.fileOf(&context.moduleFiles, moduleName) {39    case Some(found) => found40    case None => {41      let wanted = moduleName.toLower()42      let near = List.take(&context.moduleFiles.filter(|held: Reference.ModuleFile| held.moduleName.toLower().contains(wanted)), SUGGESTIONS)43      let hint = if near.isEmpty() { "" } else { " (did you mean: " + near.map(|held: Reference.ModuleFile| held.moduleName).join(", ") + ")" }44      return Err(ToolError.NotFound("Module '" + moduleName + "'" + hint))45    }46  }47  let entries = Reference.moduleEntries(&context.toolchain, &file) ?48  Ok(TextBounds.bounded(Entry.renderModule(&entries, moduleName), Server.DOC_CHARS))49}5051/// The compiler's own type-shape search over every importable module.52fn shapeSearch(context: &Context.Context, query: Str) -> Result[Str, ToolError.ToolFailure] {53  if context.toolchain.compiler == None { return Err(ToolError.ToolchainMissing) }54  let arguments = ["search", query].concat(context.moduleFiles.map(|held: Reference.ModuleFile| held.path))55  match context.toolchain.run(arguments, "", "", Server.REFERENCE_TIMEOUT_MS, Server.OUTPUT_CAP_BYTES) {56    case Ok(done) => {57      if done.timedOut { return Err(ToolError.TimedOut("pudu search", Server.REFERENCE_TIMEOUT_MS)) }58      let said = done.output.trim()59      Ok(if said.isEmpty() { "No declaration has the shape '" + query + "'." } else { TextBounds.bounded(said, Server.DOC_CHARS) })60    }61    case Err(problem) => Err(ToolError.CommandFailed(problem))62  }63}64