From b3b52af7f892e17e69a603f729798c2be0c1595f Mon Sep 17 00:00:00 2001 From: Alexander Yakushev Date: Sat, 23 Feb 2019 00:29:39 +0200 Subject: [PATCH] [context] Try to complete unfinished forms when parsing context Addresses #53. --- src/compliment/context.clj | 57 ++++++++++++++------ test/compliment/sources/t_local_bindings.clj | 6 +++ test/compliment/t_context.clj | 6 +++ 3 files changed, 54 insertions(+), 15 deletions(-) diff --git a/src/compliment/context.clj b/src/compliment/context.clj index 3bb713e..3c898b1 100644 --- a/src/compliment/context.clj +++ b/src/compliment/context.clj @@ -1,24 +1,51 @@ (ns compliment.context "Utilities for parsing and storing the current completion context." - (:require [clojure.walk :refer [walk]])) + (:require [clojure.string :as str] + [clojure.walk :as walk])) (defn- restore-map-literals [context] - (clojure.walk/postwalk (fn [el] - (if (and (sequential? el) - (= (first el) 'compliment-hashmap)) - (apply hash-map - (if (even? (count el)) - (concat (rest el) [nil]) - (rest el))) - el)) context)) + (walk/postwalk (fn [el] + (if (and (sequential? el) + (= (first el) 'compliment-hashmap)) + (apply hash-map + (if (even? (count el)) + (concat (rest el) [nil]) + (rest el))) + el)) context)) + +(defn- try-read-replacing-maps [s] + (try (binding [*read-eval* false] + (-> s + (str/replace "{" "(compliment-hashmap ") + (str/replace "}" ")") + read-string + restore-map-literals)) + (catch Exception ex))) + +(defn- dumb-read-form + "Take a presumably unfinished Clojure form and try to \"complete\" it so that it + can be read. The algorithm is incredibly stupid, but is better than nothing." + [unfinished-form-str] + (let [open->close {\( \), \[ \], \{ \}}, + close->open {\) \(, \] \[, \} \{}] + (loop [[c & r] (reverse (filter (set "([{}])") unfinished-form-str)) + to-append []] + (if c + (cond (open->close c) + (recur r (conj to-append (open->close c))) + + (close->open c) + (if (= c (open->close (first r))) + (recur (rest r) to-append) + ;; Everything is bad - just give up + nil)) + (try-read-replacing-maps (apply str unfinished-form-str to-append)))))) + +#_(dumb-read-form "(let [a {:b 1}, c {__prefix__") (defn- safe-read-context-string [^String context] - (try (-> context - (.replace "{" "(compliment-hashmap ") - (.replace "}" ")") - read-string - restore-map-literals) - (catch Exception ex nil))) + (or (try-read-replacing-maps context) + (dumb-read-form context))) (def ^{:doc "Stores the last completion context." :private true} diff --git a/test/compliment/sources/t_local_bindings.clj b/test/compliment/sources/t_local_bindings.clj index 78013df..b40074e 100644 --- a/test/compliment/sources/t_local_bindings.clj +++ b/test/compliment/sources/t_local_bindings.clj @@ -85,6 +85,12 @@ __prefix__)))))) => (just ["afunction" "arg1" "arg2" "foo"] :in-any-order)) + (fact "bindings will be completed even if the form is unfinished" + (strip-tags (src/candidates "" *ns* (ctx/parse-context + (#'ctx/safe-read-context-string + "(let [foo 42, [bar baz] 17, qux __prefix__")))) + => (just ["foo" "bar" "baz" "qux"] :in-any-order)) + (fact "source silently fails if context is malformed" (src/candidates "" *ns* "(let __prefix__)") => [] (src/candidates "" *ns* "(let [() 1]__prefix__)") => [] diff --git a/test/compliment/t_context.clj b/test/compliment/t_context.clj index 6ca883a..8fcfdf9 100644 --- a/test/compliment/t_context.clj +++ b/test/compliment/t_context.clj @@ -50,6 +50,12 @@ (ctx/parse-context (#'ctx/safe-read-context-string "{:foo __prefix__ :bar}")) => '({:idx :foo, :map-role :value, :form {:foo __prefix__, :bar nil}})) + (fact "failing to parse an unfinished form will try to recover by completing it" + (ctx/parse-context (#'ctx/safe-read-context-string "(let [a {:b 1}, c {__prefix__")) + => '({:idx nil, :map-role :key, :form {__prefix__ nil}} + {:idx 3, :form [a {:b 1} c {__prefix__ nil}]} + {:idx 1, :form (let [a {:b 1} c {__prefix__ nil}])})) + (fact "in maps :map-role shows which role in key-value pair the __prefix__ (or the form with it) has, and :idx shows the opposite element of its key-value pair."