You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Hi, I'm glad I found a language server for SMT-LIB.
Currently, the most important feature for me is go-to-definition
("textDocument/definition", so I can quickly navigate my formulas.
This includes mainly names declared by declare-* or define-* statements,
and maybe also let bindings.
I've hacked together my own language server in Python that works pretty well
for me. But I'm always happy to get rid of that and move to a real solution.
I'm not sure when I'll get to delve into OCaml, but for now I'll leave my
Python implementation here, in case it helps anyone. There's some boilerplate -
the actual routine to find definitions is really simple (and inefficient).
Let me know if I can help with examples/explanation.
(The next logical step would be to implement "textDocument/references", to list all uses of a name)
#!/usr/bin/env python3# Dependencies:# pip install python-language-serverimportsysimportloggingimportthreadingfrompylsimport_utils, urisfrompyls_jsonrpc.dispatchersimportMethodDispatcherfrompyls_jsonrpc.endpointimportEndpointfrompyls_jsonrpc.streamsimportJsonRpcStreamReader, JsonRpcStreamWriterfrompyls.workspaceimportWorkspace"""Toy language server that implements textDocument/definitionFor example, given this file```smt2(declare-const x Int)(assert (= x 123))```if the cursor is on the "x" in line 3, textDocument/definition will returnthe position of the x in line 1."""# SMTLIBLanguageServer is adadpted from pylslog=logging.getLogger(__name__)
PARENT_PROCESS_WATCH_INTERVAL=10# 10 sMAX_WORKERS=64classSMTLIBLanguageServer(MethodDispatcher):
""" Implementation of the Microsoft VSCode Language Server Protocol https://github.com/Microsoft/language-server-protocol/blob/master/versions/protocol-1-x.md """def__init__(self, rx, tx, check_parent_process=False):
self.workspace=Noneself.root_uri=Noneself.watching_thread=Noneself.workspaces= {}
self.uri_workspace_mapper= {}
self._jsonrpc_stream_reader=JsonRpcStreamReader(rx)
self._jsonrpc_stream_writer=JsonRpcStreamWriter(tx)
self._check_parent_process=check_parent_processself._endpoint=Endpoint(
self, self._jsonrpc_stream_writer.write, max_workers=MAX_WORKERS)
self._dispatchers= []
self._shutdown=Falsedefstart(self):
"""Entry point for the server."""self._jsonrpc_stream_reader.listen(self._endpoint.consume)
def__getitem__(self, item):
"""Override getitem to fallback through multiple dispatchers."""ifself._shutdownanditem!='exit':
# exit is the only allowed method during shutdownlog.debug("Ignoring non-exit method during shutdown: %s", item)
raiseKeyErrortry:
returnsuper(SMTLIBLanguageServer, self).__getitem__(item)
exceptKeyError:
# Fallback through extra dispatchersfordispatcherinself._dispatchers:
try:
returndispatcher[item]
exceptKeyError:
continueraiseKeyError()
defm_shutdown(self, **_kwargs):
self._shutdown=TruereturnNonedefm_exit(self, **_kwargs):
self._endpoint.shutdown()
self._jsonrpc_stream_reader.close()
self._jsonrpc_stream_writer.close()
def_match_uri_to_workspace(self, uri):
workspace_uri=_utils.match_uri_to_workspace(uri, self.workspaces)
returnself.workspaces.get(workspace_uri, self.workspace)
defcapabilities(self):
server_capabilities={
"definitionProvider": True,
}
log.info('Server capabilities: %s', server_capabilities)
returnserver_capabilitiesdefm_initialize(self, processId=None, rootUri=None, rootPath=None, initializationOptions=None, **_kwargs):
log.debug('Language server initialized with %s %s %s %s',
processId, rootUri, rootPath, initializationOptions)
ifrootUriisNone:
rootUri=uris.from_fs_path(
rootPath) ifrootPathisnotNoneelse''self.workspaces.pop(self.root_uri, None)
self.root_uri=rootUriself.workspace=Workspace(rootUri, self._endpoint, None)
self.workspaces[rootUri] =self.workspaceifself._check_parent_processandprocessIdisnotNoneandself.watching_threadisNone:
defwatch_parent_process(pid):
# exit when the given pid is not aliveifnot_utils.is_process_alive(pid):
log.info("parent process %s is not alive, exiting!", pid)
self.m_exit()
else:
threading.Timer(PARENT_PROCESS_WATCH_INTERVAL,
watch_parent_process, args=[pid]).start()
self.watching_thread=threading.Thread(
target=watch_parent_process, args=(processId,))
self.watching_thread.daemon=Trueself.watching_thread.start()
return {'capabilities': self.capabilities()}
defm_initialized(self, **_kwargs):
passdefm_text_document__definition(self, textDocument=None, position=None, **_kwargs):
doc_uri=textDocument["uri"]
workspace=self._match_uri_to_workspace(doc_uri)
doc=workspace.get_document(doc_uri) ifdoc_urielseNonereturnsmt_definition(doc, position)
defm_text_document__did_close(self, textDocument=None, **_kwargs):
passdefm_text_document__did_open(self, textDocument=None, **_kwargs):
passdefm_text_document__did_change(self, contentChanges=None, textDocument=None, **_kwargs):
passdefm_text_document__did_save(self, textDocument=None, **_kwargs):
passdefm_text_document__completion(self, textDocument=None, **_kwargs):
passdefflatten(list_of_lists):
return [itemforlstinlist_of_listsforiteminlst]
defmerge(list_of_dicts):
return {k: vfordictionaryinlist_of_dictsfork, vindictionary.items()}
defsmt_definition(document, position):
pos=definition(document.source, position["line"], position["character"])
ifposisNone:
returnNoneline, col, token=posoffset=1iflen(token) ==1else (len(token) +1)
ifcol==0:
line-=1col=len(document.lines[line]) -offsetelse:
col=col-offsetreturn {
'uri': document.uri,
'range': {
'start': {'line': line, 'character': col},
'end': {'line': line, 'character': col},
}
}
defdefinition(source, cursor_line, cursor_character):
nodes=list(parser().parse_smtlib(source))
node_at_cursor=find_leaf_node_at(cursor_line, cursor_character, nodes)
line, col, node=find_definition_for(node_at_cursor, nodes)
ifnodeisNone:
returnNonereturnline, col, node_at_cursordeffind_leaf_node_at(line, col, nodes):
prev_line_end=-1prev_col_end=-1needle= (line, col)
forline_end, col_end, nodeinnodes:
prev_range= (prev_line_end-1, prev_col_end)
cur_range= (line_end, col_end)
ifprev_range<needle<cur_range:
ifisinstance(node, str):
returnnodeelse:
node_at=find_leaf_node_at(line, col, node)
assertnode_atisnotNonereturnnode_atprev_line_end=line_endprev_col_end=col_endreturnNonedefstripprefix(x, prefix):
ifx.startswith(prefix):
returnx[len(prefix):]
returnxdeffind_definition_for(needle, nodes):
fornodeinnodes:
line_end, col_end, n=node_, _, head=n[0]
ifnothead.startswith("declare-") andnothead.startswith("define-"):
continue_, _, symbol=n[1]
ifheadin ("declare-const", "define-const", "declare-fun", "define-fun", "define-fun-rec", "declare-datatype"):
ifsymbol==needle:
returnn[1]
continueifheadin ("declare-datatypes", "define-funs-rec"):
fori, tmpinenumerate(symbol):
_, _, type_parameter_declaration=tmp_, _, type_name=type_parameter_declaration[0]
iftype_name==needle:
returntype_parameter_declaration[0]
ifhead=="declare-datatypes":
constructor=dfs(needle, node)
ifconstructorisnotNone:
returnconstructorconstructor=dfs(stripprefix(needle, "is-"), node)
ifconstructorisnotNone:
returnconstructorcontinueassertf"unsupported form: {head}"return-1, -1, Nonedefdfs(needle, node):
assertisinstance(node, tuple)
_, _, n=nodeifisinstance(n, str):
ifn==needle:
returnnodeelse:
returnNoneforchildinn:
found=dfs(needle, child)
iffoundisnotNone:
returnfoundreturnNoneclassparser:
def__init__(self):
self.pos=0self.line=0self.col=-1self.text=Nonedefnextch(self):
char=self.text[self.pos]
self.pos+=1self.col+=1ifchar=="\n":
self.line+=1self.col=0returnchardefparse_smtlib(self, text):
assertself.textisNoneself.text=textreturnself.parse_smtlib_aux()
defparse_smtlib_aux(self):
exprs= []
cur_expr=Nonesize=len(self.text)
whileself.pos<size:
char=self.nextch()
# Stolen from ddSMT's parser. Not fully SMT-LIB compliant but good enough.# String literals/quoted symbolsifcharin ('"', '|'):
first_char=charliteral= [char]
# Read until terminating " or |whileTrue:
ifself.pos>=size:
returnchar=self.nextch()
literal.append(char)
ifchar==first_char:
# Check is quote is escaped "a "" b" is one string literalifchar=='"'andself.pos<sizeandself.text[self.pos] =='"':
literal.append(self.text[self.pos])
self.nextch()
continuebreakcur_expr.append((self.line, self.col, literal))
continue# Commentsifchar==';':
# Read until newlinewhileself.pos<size:
char=self.nextch()
ifchar=='\n':
breakcontinue# Open s-expressionifchar=='(':
cur_expr= []
exprs.append(cur_expr)
continue# Close s-expressionifchar==')':
cur_expr=exprs.pop()
# Do we have nested s-expressions?ifexprs:
exprs[-1].append((self.line, self.col, cur_expr))
cur_expr=exprs[-1]
else:
yieldself.line, self.col, cur_exprcur_expr=Nonecontinue# Identifierifcharnotin (' ', '\t', '\n'):
token= [char]
whileTrue:
ifself.pos>=size:
returnchar=self.text[self.pos]
ifcharin ('(', ')', ';'):
breakself.nextch()
ifcharin (' ', '\t', '\n'):
breaktoken.append(char)
token=''.join(token)
# Append to current s-expressionifcur_exprisnotNone:
cur_expr.append((self.line, self.col, token))
else:
yieldself.line, self.col, tokendefserve():
stdin=sys.stdin.bufferstdout=sys.stdout.bufferserver=SMTLIBLanguageServer(stdin, stdout)
server.start()
if__name__=="__main__":
iflen(sys.argv) >=2andsys.argv[1] =="definition":
line=int(sys.argv[2])
col=int(sys.argv[3])
print(definition(sys.stdin.read(), line, col))
else:
serve()
The text was updated successfully, but these errors were encountered:
Hi,
That's very interesting to know ! It's very useful to me to know what features to prioritize, so don't hesitate to report what you need/want Dolmen (and the LSP server) to do, ^^
After thinking about it this week, I now have a fairly good idea of how I want to implement the goto definition in Dolmen, and I'll get on that as soon as I have the time.
Hi, I'm glad I found a language server for SMT-LIB.
Currently, the most important feature for me is go-to-definition
("textDocument/definition", so I can quickly navigate my formulas.
This includes mainly names declared by
declare-*
ordefine-*
statements,and maybe also
let
bindings.I've hacked together my own language server in Python that works pretty well
for me. But I'm always happy to get rid of that and move to a real solution.
I'm not sure when I'll get to delve into OCaml, but for now I'll leave my
Python implementation here, in case it helps anyone. There's some boilerplate -
the actual routine to find definitions is really simple (and inefficient).
Let me know if I can help with examples/explanation.
(The next logical step would be to implement "textDocument/references", to list all uses of a name)
The text was updated successfully, but these errors were encountered: