Big refactoring

This commit is contained in:
XeroOl 2022-08-07 22:46:34 -05:00
parent bb13e9b809
commit f610201b9d
No known key found for this signature in database
GPG Key ID: 9DD4B4B4DAED0322
16 changed files with 156 additions and 185 deletions

View File

@ -14,7 +14,7 @@ Features / To Do List / Things I would enjoy patches for:
- [ ] directly on require statements - [ ] directly on require statements
- [X] for definitions in the same file - [X] for definitions in the same file
- [ ] for definitions in other files - [ ] for definitions in other files
- [ ] follows multisyms through table constructor - [X] follows multisyms through table constructor
- [ ] follows multisyms through mutations (difficult) - [ ] follows multisyms through mutations (difficult)
- [ ] for methods/metamethods (difficult, in the general case may require type annotations or some insane global type inference logic) - [ ] for methods/metamethods (difficult, in the general case may require type annotations or some insane global type inference logic)
- [ ] into lua files (maybe cheeseable with antifennel if it has --correlate?) - [ ] into lua files (maybe cheeseable with antifennel if it has --correlate?)

View File

@ -1,15 +1,12 @@
(local fennel (require :fennel)) (local core (require :fennel-ls.core))
(local dispatch (require :fennel-ls.dispatch))
(local json-rpc (require :fennel-ls.json-rpc)) (local json-rpc (require :fennel-ls.json-rpc))
(local {: log} (require :fennel-ls.log))
(λ main-loop [in out] (λ main-loop [in out]
(local send (partial json-rpc.write out)) (local send (partial json-rpc.write out))
(local state []) (local state [])
(while true (while true
(let [msg (json-rpc.read in)] (let [msg (json-rpc.read in)]
;; (log msg) (core.handle state send msg))))
(dispatch.handle state send msg))))
(λ main [] (λ main []
(main-loop (main-loop

View File

@ -1,6 +1,11 @@
(local fennel (require :fennel)) (local fennel (require :fennel))
(local fennelutils (require :fennel.utils))
(local utils (require :fennel-ls.utils))
(local insert table.insert) (local insert table.insert)
(local sym? fennel.sym?)
(local list? fennel.list?)
;; words surrounded by - are symbols, ;; words surrounded by - are symbols,
;; because fennel doesn't allow 'require in a runtime file ;; because fennel doesn't allow 'require in a runtime file
(local -require- (fennel.sym :require)) (local -require- (fennel.sym :require))
@ -8,6 +13,33 @@
(local -λ- (fennel.sym :λ)) (local -λ- (fennel.sym :λ))
(local -lambda- (fennel.sym :lambda)) (local -lambda- (fennel.sym :lambda))
(λ contains? [?ast byte]
;; check if an ast contains a byte
(and (= (type ?ast) :table)
(utils.get-ast-info ?ast :bytestart)
(utils.get-ast-info ?ast :byteend)
(<= (utils.get-ast-info ?ast :bytestart)
byte
(+ 1 (utils.get-ast-info ?ast :byteend)))))
(λ does-not-contain? [?ast byte]
;; check if a byte is in range of the ast
(and (= (type ?ast) :table)
(utils.get-ast-info ?ast :bytestart)
(utils.get-ast-info ?ast :byteend)
(not
(<= (utils.get-ast-info ?ast :bytestart)
byte
(+ 1 (utils.get-ast-info ?ast :byteend))))))
(λ past? [?ast byte]
;; check if a byte is past an ast object
(and (= (type ?ast) :table)
(utils.get-ast-info ?ast :bytestart)
(< byte (utils.get-ast-info ?ast :bytestart))
false))
(λ multisym? [t] (λ multisym? [t]
;; check if t is a symbol with multiple parts, eg. foo.bar.baz ;; check if t is a symbol with multiple parts, eg. foo.bar.baz
(and (fennel.sym? t) (and (fennel.sym? t)
@ -126,5 +158,66 @@
(set file.ast ast)) (set file.ast ast))
;; (set file.analyzed? true)) ;; (set file.analyzed? true))
(var
(search-assignment
search-symbol
search)
nil)
{: analyze} (set search-assignment
(λ search-assignment [self file binding ?definition stack]
(if (= 0 (length stack))
binding
;; TODO sift down the binding
(search self file ?definition stack))))
(set search-symbol
(λ search-symbol [self file symbol stack]
(let [split (utils.multi-sym-split symbol)]
(for [i (length split) 2 -1]
(table.insert stack (. split i))))
(match (. file.references symbol)
to (search-assignment self file to.binding to.definition stack)
nil nil)))
(set search
(λ search [self file item stack]
(if (fennelutils.table? item)
(if (. item (. stack (length stack)))
(search self file (. item (table.remove stack)) stack)
nil)
(sym? item)
(search-symbol self file item stack)
;; TODO
;; functioncall (continue searching in body)
;; require call (search in the new module)
:else (error (.. "I don't know what to do with " (fennel.view item))))))
(λ find-symbol [ast byte ?recursively-called]
(if (not= :table (type ast))
nil
(does-not-contain? ast byte)
nil
(sym? ast)
ast
(or (not ?recursively-called)
(fennel.list? ast)
(fennel.sequence? ast))
;; TODO binary search
(accumulate
[result nil
_ v (ipairs ast)
&until (or result (past? v byte))]
(find-symbol v byte true))
:else
(accumulate
[result nil
k v (pairs ast)
&until result]
(or
(find-symbol k byte true)
(find-symbol v byte true)))))
{: analyze
: find-symbol
: search-symbol}

View File

@ -1,4 +1,4 @@
"Dispatch "core
This module is responsible for deciding which code to call in response This module is responsible for deciding which code to call in response
to a given LSP request from the client. to a given LSP request from the client.
@ -7,12 +7,12 @@ In general, this involves:
* determining the type of the message * determining the type of the message
* calling the appropriate handler" * calling the appropriate handler"
(local handlers (require :fennel-ls.the-actual-code)) (local handlers (require :fennel-ls.handlers))
(local message (require :fennel-ls.message)) (local message (require :fennel-ls.message))
(λ handle-request [self send id method ?params] (λ handle-request [self send id method ?params]
"Call the appropriate request handler. ;; Call the appropriate request handler.
The return value of the request is sent back to the server." ;; The return value of the request is sent back to the server.
(match (. handlers.requests method) (match (. handlers.requests method)
callback callback
(match (callback self send ?params) (match (callback self send ?params)
@ -26,15 +26,15 @@ The return value of the request is sent back to the server."
id)))) id))))
(λ handle-response [self send id result] (λ handle-response [self send id result]
"I don't care about responses yet" ;; I don't care about responses yet
nil) nil)
(λ handle-bad-response [self send id err] (λ handle-bad-response [self send id err]
"Handle a message indicating an error. Right now, it just crashes the server." ;; Handle a message indicating an error. Right now, it just crashes the server.
(error (.. "oopsie: " err.code))) (error (.. "oopsie: " err.code)))
(λ handle-notification [self send method ?params] (λ handle-notification [self send method ?params]
"Call the appropriate notification handler." ;; Call the appropriate notification handler.
(match (. handlers.notifications method) (match (. handlers.notifications method)
callback (callback self send ?params))) callback (callback self send ?params)))
;; Silent error for unknown notifications ;; Silent error for unknown notifications

View File

@ -1,19 +1,14 @@
"The actual code "Big dispatch
You finally made it. Here is the main code that implements the language server protocol You finally made it. Here is the main code that implements the language server protocol
Every time the client sends a message, it gets handled by a function in the corresponding table type. Every time the client sends a message, it gets handled by a function in the corresponding table type.
(ie, a textDocument/didChange notification will call notifications.textDocument/didChange (ie, a textDocument/didChange notification will call notifications.textDocument/didChange
and a textDocument/defintion request will call requests.textDocument/didChange)" and a textDocument/defintion request will call requests.textDocument/didChange)"
(local fennel (require :fennel)) (local utils (require :fennel-ls.utils))
(local sym? fennel.sym?) (local message (require :fennel-ls.message))
(local list? fennel.list?)
(local fennelutils (require :fennel.utils))
(local parser (require :fennel-ls.parser))
(local util (require :fennel-ls.util))
(local mod (require :fennel-ls.mod))
(local {: log} (require :fennel-ls.log))
(local state (require :fennel-ls.state)) (local state (require :fennel-ls.state))
(local analyze (require :fennel-ls.analyze))
(local requests []) (local requests [])
(local notifications []) (local notifications [])
@ -64,81 +59,19 @@ Every time the client sends a message, it gets handled by a function in the corr
{:capabilities capabilities {:capabilities capabilities
:serverInfo {:name "fennel-ls" :version "0.0.0"}}) :serverInfo {:name "fennel-ls" :version "0.0.0"}})
;; These three functions are mutually recursive
(var
(search-assignment
search-symbol
search)
nil)
(set search-assignment
(λ search-assignment [self file binding ?definition stack]
(if (= 0 (length stack))
binding
;; TODO sift down the binding
(search self file ?definition stack))))
(set search-symbol
(λ search-symbol [self file symbol stack]
(let [split (util.multi-sym-split symbol)]
(for [i (length split) 2 -1]
(table.insert stack (. split i))))
(match (. file.references symbol)
to (search-assignment self file to.binding to.definition stack)
nil nil)))
(set search
(λ search [self file item stack]
(if (fennelutils.table? item)
(if (. item (. stack (length stack)))
(search self file (. item (table.remove stack)) stack)
nil)
(sym? item)
(search-symbol self file item stack)
;; TODO
;; functioncall (continue searching in body)
;; require call (search in the new module)
:else (error (.. "I don't know what to do with " (fennel.view item))))))
(λ find-symbol [ast byte ?recursively-called]
(if (not= :table (type ast))
nil
(parser.does-not-contain? ast byte)
nil
(sym? ast)
ast
(or (not ?recursively-called)
(fennel.list? ast)
(fennel.sequence? ast))
;; TODO binary search
(accumulate
[result nil
_ v (ipairs ast)
&until (or result (parser.past? v byte))]
(find-symbol v byte true))
:else
(accumulate
[result nil
k v (pairs ast)
&until result]
(or
(find-symbol k byte true)
(find-symbol v byte true)))))
(λ requests.textDocument/definition [self send {: position :textDocument {: uri}}] (λ requests.textDocument/definition [self send {: position :textDocument {: uri}}]
(local file (state.get-by-uri self uri)) (local file (state.get-by-uri self uri))
(local byte (util.pos->byte file.text position.line position.character)) (local byte (utils.pos->byte file.text position.line position.character))
(match (find-symbol file.ast byte) (match (analyze.find-symbol file.ast byte)
symbol symbol
(match (search-symbol self file symbol []) (match (analyze.search-symbol self file symbol [])
definition definition
{:range (parser.range file.text definition) {:range (message.range file.text definition)
:uri uri}))) :uri uri})))
(λ notifications.textDocument/didChange [self send {: contentChanges :textDocument {: uri}}] (λ notifications.textDocument/didChange [self send {: contentChanges :textDocument {: uri}}]
(local file (state.get-by-uri self uri)) (local file (state.get-by-uri self uri))
(assert file.open?) (assert file.open?)
(util.apply-changes (. self.files uri) contentChanges)) (utils.apply-changes (. self.files uri) contentChanges))
(λ notifications.textDocument/didOpen [self send {:textDocument {: languageId : text : uri}}] (λ notifications.textDocument/didOpen [self send {:textDocument {: languageId : text : uri}}]
(local file (state.set-uri-contents self uri text)) (local file (state.set-uri-contents self uri text))

View File

@ -1,20 +0,0 @@
"Log
In the Language Server Protocol, io.stdout is used to send messages to the client.
Because of this, I need another way to do print-debugging."
(local fennel (require :fennel))
(local disable-logs false)
(if disable-logs
{:log #nil}
(let [logdocument (io.open "/tmp/fennel.log" "w")]
(assert logdocument)
(fn log [...]
(let [args []]
(for [i 1 (select :# ...)]
(table.insert args
(let [item (select i ...)]
(match (values item (type item))
(str :string) str
?any (fennel.view ?any)))))
(logdocument:write (table.concat args) "\n")))
{: log}))

View File

@ -4,6 +4,7 @@ that may need to be sent to the client.
I have them all here because I have a feeling I am conflating missing fields with null fields, I have them all here because I have a feeling I am conflating missing fields with null fields,
and I want to have one location to look to fix this in the future." and I want to have one location to look to fix this in the future."
(local utils (require :fennel-ls.utils))
(local error-codes (local error-codes
{;; JSON-RPC errors {;; JSON-RPC errors
@ -43,8 +44,18 @@ and I want to have one location to look to fix this in the future."
: id : id
:result ?result}) :result ?result})
(λ range [text ?ast]
"create a LSP range representing the span of an AST object"
(if (= (type ?ast) :table)
(match (values (utils.get-ast-info ?ast :bytestart) (utils.get-ast-info ?ast :byteend))
(i j)
(let [(start-line start-col) (utils.byte->pos text i)
(end-line end-col) (utils.byte->pos text (+ j 1))]
{:start {:line start-line :character start-col}
:end {:line end-line :character end-col}}))))
{: create-notification {: create-notification
: create-request : create-request
: create-response : create-response
: create-error} : create-error
: range}

View File

@ -1,49 +0,0 @@
(local fennel (require :fennel))
(local util (require :fennel-ls.util))
(λ get-ast-info [?ast info]
;; find a given key of info from an AST object
(or (?. (getmetatable ?ast) info)
(. ?ast info)))
(λ contains? [?ast byte]
"check if a byte is in range of the AST object"
(and (= (type ?ast) :table)
(get-ast-info ?ast :bytestart)
(get-ast-info ?ast :byteend)
(<= (get-ast-info ?ast :bytestart)
byte
(+ 1 (get-ast-info ?ast :byteend)))))
(λ does-not-contain? [?ast byte]
"check if a byte is in range of the AST object"
(and (= (type ?ast) :table)
(get-ast-info ?ast :bytestart)
(get-ast-info ?ast :byteend)
(not
(<= (get-ast-info ?ast :bytestart)
byte
(+ 1 (get-ast-info ?ast :byteend))))))
(λ past? [?ast byte]
"check if a byte is past the range of the AST object"
(and (= (type ?ast) :table)
(get-ast-info ?ast :bytestart)
(< byte (get-ast-info ?ast :bytestart))
false))
(λ range [text ?ast]
"create a LSP range representing the span of an AST object"
(if (= (type ?ast) :table)
(match (values (get-ast-info ?ast :bytestart) (get-ast-info ?ast :byteend))
(i j)
(let [(start-line start-col) (util.byte->pos text i)
(end-line end-col) (util.byte->pos text (+ j 1))]
{:start {:line start-line :character start-col}
:end {:line end-line :character end-col}}))))
{: contains?
: does-not-contain?
: past?
: range}

View File

@ -1,9 +1,9 @@
"Mod "Searcher
This file has all the logic needed to take the name of a module and find the corresponding URI. This file has all the logic needed to take the name of a module and find the corresponding URI.
I suspect this file is going to be gone after a bit of refactoring." I suspect this file is going to be gone after a bit of refactoring."
(local fennel (require :fennel)) (local fennel (require :fennel))
(local util (require :fennel-ls.util)) (local utils (require :fennel-ls.utils))
"works on my machine >:)" "works on my machine >:)"
(local luapath "?.lua;src/?.lua") (local luapath "?.lua;src/?.lua")
@ -36,13 +36,13 @@ I suspect this file is going to be gone after a bit of refactoring."
(if (is_absolute path) (if (is_absolute path)
(table.insert result path) (table.insert result path)
(each [_ workspace (ipairs (or ?workspaces []))] (each [_ workspace (ipairs (or ?workspaces []))]
(table.insert result (join (util.uri->path workspace) path))))) (table.insert result (join (utils.uri->path workspace) path)))))
(table.concat result ";"))) (table.concat result ";")))
(λ lookup [{: root-uri} mod] (λ lookup [{: root-uri} mod]
(match (or (fennel.searchModule mod (add-workspaces-to-path luapath [root-uri])) (match (or (fennel.searchModule mod (add-workspaces-to-path luapath [root-uri]))
(fennel.searchModule mod (add-workspaces-to-path fennelpath [root-uri]))) (fennel.searchModule mod (add-workspaces-to-path fennelpath [root-uri])))
modname (util.path->uri modname) modname (utils.path->uri modname)
nil nil)) nil nil))
{: lookup} {: lookup}

View File

@ -1,7 +1,7 @@
(local util (require :fennel-ls.util)) (local utils (require :fennel-ls.utils))
(local mod (require :fennel-ls.mod)) (local searcher (require :fennel-ls.searcher))
(local {: analyze} (require :fennel-ls.plugin)) (local {: analyze} (require :fennel-ls.analyze))
(λ init-state [self params] (λ init-state [self params]
(set self.files {}) (set self.files {})
@ -9,7 +9,7 @@
(set self.root-uri params.rootUri)) (set self.root-uri params.rootUri))
(λ read-file [uri] (λ read-file [uri]
(with-open [fd (io.open (util.uri->path uri))] (with-open [fd (io.open (utils.uri->path uri))]
{:uri uri {:uri uri
:text (fd:read :*a)})) :text (fd:read :*a)}))
@ -26,7 +26,7 @@
;; if the cached uri isn't found, clear the cache and try again ;; if the cached uri isn't found, clear the cache and try again
(do (tset self.modules module nil) (do (tset self.modules module nil)
(get-by-module self module))) (get-by-module self module)))
nil (let [uri (mod.lookup self module)] nil (let [uri (searcher.lookup self module)]
(tset self.modules module uri) (tset self.modules module uri)
(get-by-uri self uri)))) (get-by-uri self uri))))

View File

@ -70,6 +70,11 @@ These functions are all pure functions, which makes me happy."
{: text} {: text}
text))) text)))
(λ get-ast-info [?ast info]
;; find a given key of info from an AST object
(or (?. (getmetatable ?ast) info)
(. ?ast info)))
(fn multi-sym-split [sym ?offset] (fn multi-sym-split [sym ?offset]
(local sym (tostring sym)) (local sym (tostring sym))
(local offset (or ?offset (length sym))) (local offset (or ?offset (length sym)))
@ -84,4 +89,5 @@ These functions are all pure functions, which makes me happy."
: pos->byte : pos->byte
: byte->pos : byte->pos
: apply-changes : apply-changes
: multi-sym-split} : multi-sym-split
: get-ast-info}

View File

@ -5,14 +5,14 @@
(local {: ROOT-URI (local {: ROOT-URI
: setup-server} (require :test.util)) : setup-server} (require :test.util))
(local dispatch (require :fennel-ls.dispatch)) (local core (require :fennel-ls.core))
(local message (require :fennel-ls.message)) (local message (require :fennel-ls.message))
(describe "jump to definition" (describe "jump to definition"
(fn check [request-file line char response-file start-line start-col end-line end-col] (fn check [request-file line char response-file start-line start-col end-line end-col]
(local state (doto [] setup-server)) (local state (doto [] setup-server))
(let [message (dispatch.handle* state (let [message (core.handle* state
(message.create-request 2 "textDocument/definition" (message.create-request 2 "textDocument/definition"
{:position {:character char :line line} {:position {:character char :line line}
:textDocument {:uri (.. ROOT-URI "/" request-file)}})) :textDocument {:uri (.. ROOT-URI "/" request-file)}}))

View File

@ -2,7 +2,7 @@
(local is (require :luassert)) (local is (require :luassert))
(local {: ROOT-PATH : ROOT-URI} (require :test.util)) (local {: ROOT-PATH : ROOT-URI} (require :test.util))
(local dispatch (require :fennel-ls.dispatch)) (local core (require :fennel-ls.core))
(local server-initialize-message (local server-initialize-message
{:id 1 {:id 1
@ -22,7 +22,7 @@
(describe "language server" (describe "language server"
(it "responds to initialize" (it "responds to initialize"
(is-matching (is-matching
(dispatch.handle* [] server-initialize-message) (core.handle* [] server-initialize-message)
[{:jsonrpc "2.0" :id 1 [{:jsonrpc "2.0" :id 1
:result {:capabilities {} :result {:capabilities {}
:serverInfo {:name "fennel-ls" : version}}}]))) :serverInfo {:name "fennel-ls" : version}}}])))

View File

@ -2,7 +2,7 @@
(local is (require :luassert)) (local is (require :luassert))
(local fennel (require :fennel)) (local fennel (require :fennel))
(local {: multi-sym-split} (require :fennel-ls.util)) (local {: multi-sym-split} (require :fennel-ls.utils))
(describe "multi-sym-split" (describe "multi-sym-split"
(it "should be 1 on regular syms" (it "should be 1 on regular syms"

View File

@ -2,9 +2,9 @@
(local is (require :luassert)) (local is (require :luassert))
(local fennel (require :fennel)) (local fennel (require :fennel))
(local util (require :fennel-ls.util)) (local utils (require :fennel-ls.utils))
(describe "util" (describe "utils"
;; fixme: ;; fixme:
;; test for errors on out of bounds ;; test for errors on out of bounds
@ -27,7 +27,7 @@
(it "updates the start of a line" (it "updates the start of a line"
(is.equal (is.equal
(util.apply-changes (utils.apply-changes
"replace beginning" "replace beginning"
[{:range (range 0 0 0 7) [{:range (range 0 0 0 7)
:text "the"}]) :text "the"}])
@ -35,7 +35,7 @@
(it "updates the end of a line" (it "updates the end of a line"
(is.equal (is.equal
(util.apply-changes (utils.apply-changes
"first line\nsecond line\nreplace end" "first line\nsecond line\nreplace end"
[{:range (range 2 7 2 11) [{:range (range 2 7 2 11)
:text "ment"}]) :text "ment"}])
@ -43,7 +43,7 @@
(it "replaces a line" (it "replaces a line"
(is.equal (is.equal
(util.apply-changes (utils.apply-changes
"replace all" "replace all"
[{:range (range 0 0 0 11) [{:range (range 0 0 0 11)
:text "new string"}]) :text "new string"}])
@ -51,7 +51,7 @@
(it "can handle substituting things" (it "can handle substituting things"
(is.equal (is.equal
(util.apply-changes (utils.apply-changes
"replace beginning" "replace beginning"
[{:range {:start {:line 0 :character 0} [{:range {:start {:line 0 :character 0}
:end {:line 0 :character 7}} :end {:line 0 :character 7}}
@ -60,7 +60,7 @@
(it "can handle replacing everything" (it "can handle replacing everything"
(is.equal (is.equal
(util.apply-changes (utils.apply-changes
"this is the\nold file" "this is the\nold file"
[{:text "And this is the\nnew file"}]) [{:text "And this is the\nnew file"}])
"And this is the\nnew file")))) "And this is the\nnew file"))))

View File

@ -1,4 +1,4 @@
(local dispatch (require :fennel-ls.dispatch)) (local core (require :fennel-ls.core))
(local ROOT-PATH (local ROOT-PATH
(-> (io.popen "pwd") (-> (io.popen "pwd")
@ -24,6 +24,6 @@
:uri ROOT-URI}]}}) :uri ROOT-URI}]}})
(fn setup-server [state] (fn setup-server [state]
(dispatch.handle* state initialization-message)) (core.handle* state initialization-message))
{: ROOT-URI : setup-server} {: ROOT-URI : setup-server}