// // KnownVerbs.swift // Metrika // // Author: Simon-Pierre Boucher // Contact: contact@spboucher.ai // Copyright © 2026 Simon-Pierre Boucher. All rights reserved. // /// Registry of built-in ZQL verbs with their minimum abbreviations, /// Stata-style: `reg` resolves to `regress`, `su` to `summarize`. public struct ZQVerbTable: Sendable { /// canonical verb → minimum abbreviation length public let verbs: [String: Int] /// Verbs whose first argument is a raw file path rather than a varlist. public let fileVerbs: Set /// Verbs taking a `newvar = expression` assignment. public let assignmentVerbs: Set /// Prefix verbs that wrap another command after a colon. public let prefixVerbs: Set /// Compound verbs followed by a sub-verb (`graph scatter …`). public let compoundVerbs: Set public static let builtin = ZQVerbTable( verbs: [ "use": 3, "sysuse": 6, "save": 4, "import": 3, "export": 3, "summarize": 2, "tabulate": 3, "correlate": 3, "describe": 1, "list": 1, "margins": 4, "generate": 3, "replace": 7, "drop": 4, "keep": 4, "regress": 3, "logit": 5, "probit": 6, "poisson": 7, "ivregress": 9, "xtreg": 5, "xtset": 5, "areg": 4, "boost": 5, "elasticnet": 7, "lasso": 5, "bayes": 5, "bootstrap": 9, "permute": 7, "jackknife": 9, "graph": 2, "kdensity": 4, "histogram": 4, "scatter": 7, "display": 2, "log": 3, "set": 3, "clear": 5, "count": 3, "sort": 4, "merge": 5, "append": 6, "reshape": 7, "collapse": 8, "egen": 4, "predict": 7, "test": 4, "help": 4, ], fileVerbs: ["use", "sysuse", "save", "import", "export", "log"], assignmentVerbs: ["generate", "replace", "egen"], prefixVerbs: ["bayes", "bootstrap", "permute", "jackknife"], compoundVerbs: ["graph", "ivregress"] ) public init( verbs: [String: Int], fileVerbs: Set, assignmentVerbs: Set, prefixVerbs: Set, compoundVerbs: Set ) { self.verbs = verbs self.fileVerbs = fileVerbs self.assignmentVerbs = assignmentVerbs self.prefixVerbs = prefixVerbs self.compoundVerbs = compoundVerbs } /// Resolves a typed verb (possibly abbreviated) to its canonical form. /// Returns nil when no verb matches unambiguously. public func resolve(_ typed: String) -> String? { if let minLength = verbs[typed], typed.count >= minLength { return typed } if verbs.keys.contains(typed) { return typed } let candidates = verbs.filter { verb, minLength in typed.count >= minLength && verb.hasPrefix(typed) } return candidates.count == 1 ? candidates.first!.key : nil } /// Closest known verb by edit distance, for error suggestions. /// Only returns matches within distance 2 to avoid absurd hints. public func closest(to typed: String) -> String? { let scored = verbs.keys .map { (verb: $0, distance: levenshteinDistance(typed, $0)) } .sorted { $0.distance < $1.distance } guard let best = scored.first, best.distance <= 2 else { return nil } return best.verb } }