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

Entry.pudu

Pudu92 lines3.4 KB

GitHub ↗
1/** @Domain.Reference.Entry.Aggregate — public declarations and name search */2module PuduLangMcp.Domain.Reference.Entry34import Std.List as List5import Std.Text as Text67/** @Domain.Reference.Entry.Entry — one public declaration */8export type Entry = { moduleName: Str, kind: Str, name: Str, signature: Str, doc: Str }910/** @Domain.Reference.Entry.Found — a ranked declaration match */11export type Found = { entry: Entry, score: Int }1213/// Score of an exact name or qualified-name match.14const EXACT: Int = 10015/// Score when the name starts with the query.16const NAME_PREFIX: Int = 6017/// Score when the qualified name contains the query.18const QUALIFIED_CONTAINS: Int = 4519/// Score when the name contains the query.20const NAME_CONTAINS: Int = 4021/// Score when the signature contains the query.22const SIGNATURE_CONTAINS: Int = 2523/// Score when the documentation contains the query.24const DOC_CONTAINS: Int = 1025/// Added to a matching function, which is what most lookups want.26const FUNCTION_BONUS: Int = 52728/// Every module named by the entries, sorted.29export fn modules(entries: &Array[Entry]) -> Array[Str] {30  var seen: Set[Str] = setOf([])31  var names: Array[Str] = []32  for entry in *entries {33    if !seen.contains(entry.moduleName) {34      seen = seen.insert(entry.moduleName)35      names = names.push(entry.moduleName)36    }37  }38  List.sorted(&names)39}4041/// The entries of one module, in catalogue order.42export fn ofModule(entries: &Array[Entry], moduleName: Str) -> Array[Entry] {43  entries.filter(|entry: Entry| entry.moduleName == moduleName)44}4546/// One declaration with its signature and indented documentation.47export fn render(entry: &Entry) -> Str {48  let head = if entry.signature.isEmpty() { entry.kind + " " + entry.moduleName + "." + entry.name } else { entry.kind + " " + entry.moduleName + "." + entry.name + " :: " + entry.signature }49  if entry.doc.isEmpty() { head } else { head + "\n    " + entry.doc.replace("\n", "\n    ") }50}5152/// Every declaration of one module; empty when the module is unknown.53export fn renderModule(entries: &Array[Entry], moduleName: Str) -> Str {54  let held = ofModule(entries, moduleName)55  if held.isEmpty() { return "" }56  "module " + moduleName + " — " + show(held.length()) + " public declarations\n\n" + Text.join(&held.map(|entry: Entry| render(&entry)), "\n\n")57}5859/// The best-matching declarations, highest score first.60export fn search(entries: &Array[Entry], query: Str, limit: Int) -> Array[Found] {61  let wanted = query.trim().toLower()62  if wanted.isEmpty() { return [] }63  var ranked: Array[Found] = []64  for entry in *entries {65    let points = score(&entry, wanted)66    if points > 0 { ranked = ranked.push(Found{entry: entry, score: points}) }67  }68  List.take(&List.sortOn(&ranked, |held: Found| 0 - held.score), limit)69}7071/// How well one entry matches a lowercased query; zero for no match.72fn score(entry: &Entry, wanted: Str) -> Int {73  let name = entry.name.toLower()74  let qualified = entry.moduleName.toLower() + "." + name75  let best = if name == wanted || qualified == wanted {76    EXACT77  } else if name.startsWith(wanted) {78    NAME_PREFIX79  } else if qualified.contains(wanted) {80    QUALIFIED_CONTAINS81  } else if name.contains(wanted) {82    NAME_CONTAINS83  } else if entry.signature.toLower().contains(wanted) {84    SIGNATURE_CONTAINS85  } else if entry.doc.toLower().contains(wanted) {86    DOC_CONTAINS87  } else {88    089  }90  if best > 0 && entry.kind.startsWith("fn") { best + FUNCTION_BONUS } else { best }91}92