11(ns clojure-mcp.sexp.paren-utils
22 (:require
3- [clojure-mcp.delimiter :as delimiter]
4- [rewrite-clj.parser :as parser]
5- [rewrite-clj.node :as node]
6- [clojure.string :as string])
3+ [clojure-mcp.delimiter :as delimiter])
74 (:import [com.oakmac.parinfer Parinfer]))
85
9- ; ; Tokenizer that breaks code into expressions and delimiter tokens
10- (defn tokenize-code
11- " Tokenize Clojure code into valid expressions and individual tokens.
12- Uses parse-string to extract complete valid expressions."
13- [code-str]
14- (loop [remaining code-str
15- tokens []]
16- (if (string/blank? remaining)
17- tokens
18- (let [result
19- (try
20- (let [expr (parser/parse-string remaining)
21- expr-str (node/string expr)]
22- {:success true
23- :expr-str expr-str
24- :token {:type :expression , :value expr-str}})
25- (catch Exception _
26- (let [first-char (first remaining)
27- delimiters #{\( \ ) \[ \] \{ \}}]
28- {:success false
29- :token (if (contains? delimiters first-char)
30- {:type :delimiter , :value first-char}
31- {:type :invalid , :value (str first-char)})})))]
32- (if (:success result)
33- (recur (subs remaining (count (:expr-str result)))
34- (conj tokens (:token result)))
35- (recur (subs remaining 1 )
36- (conj tokens (:token result))))))))
37-
38- (def delim-map {\( \ ) \[ \] \{ \}})
39-
40- (def open-delim #{\( \[ \{})
41-
42- (defn invert-delim [{:keys [type value] :as x}]
43- {:pre [(= :delimiter type) (open-delim value)]}
44- (update x :value delim-map))
45-
46- (defn open-delim? [{:keys [type value] :as x}]
47- (when (= :delimiter type)
48- (open-delim value)))
49-
50- (defn fix-parens
51- ([tokens]
52- (fix-parens 0 nil [] [] tokens))
53- ([extra-closes open-stack bads accum [tk & xs :as tokens]]
54- (cond
55- (and (empty? tokens) (empty? bads))
56- {:success true
57- :removed-closing-delims extra-closes
58- :added-closing-delims (count open-stack)
59- :result (concat accum (map invert-delim open-stack))}
60-
61- (empty? tokens)
62- {:success false
63- :extra-closes extra-closes
64- :bad-code bads
65- :partial-result accum}
66-
67- (open-delim? tk)
68- (fix-parens extra-closes (cons tk open-stack) bads (conj accum tk) xs)
69-
70- (= :delimiter (:type tk)) ; ; ignore closing delim
71- (fix-parens (inc extra-closes) open-stack bads accum xs)
72-
73- (= :expression (:type tk))
74- (fix-parens extra-closes open-stack bads (conj accum tk) xs)
75-
76- :else
77- (fix-parens extra-closes open-stack (conj bads tk) accum xs))))
78-
79- (defn repair-parens [code-str]
80- (let [{:keys [success result removed-closing-delims added-closing-delims]}
81- (fix-parens (tokenize-code code-str))]
82- (when success
83- {:repaired? true
84- :form (string/join (map :value result))
85- :message
86- (cond-> nil
87- (> removed-closing-delims 0 )
88- (str (format " Removed %s extra closing parentheses. " removed-closing-delims))
89- (> added-closing-delims 0 )
90- (str (format " Added %s missing closing parentheses" added-closing-delims)))})))
91-
926(defn parinfer-repair [code-str]
937 (let [res (Parinfer/indentMode code-str nil nil nil false )]
948 (when (and (.success res)
959 (not (delimiter/delimiter-error? (.text res))))
96- (.text res))))
97-
98- (defn code-has-delimiter-errors?
99- " Returns true if the given Clojure code string has delimiter errors
100- (unbalanced parentheses, brackets, braces, or string quotes).
101- Returns false otherwise."
102- [code-str]
103- (delimiter/delimiter-error? code-str))
10+ (.text res))))
0 commit comments