license = "MIT OR Apache-2.0"
[workspace]
-members = [ "tools", "cli", "libeditor", "libanalysis" ]
+members = [ "tools", "cli", "libeditor", "libanalysis", "codeless/server" ]
[dependencies]
unicode-xid = "0.1.0"
};
use clap::{App, Arg, SubCommand};
use tools::collect_tests;
-use libeditor::File;
+use libeditor::{ast, syntax_tree, symbols};
type Result<T> = ::std::result::Result<T, failure::Error>;
let file = file()?;
let elapsed = start.elapsed();
if !matches.is_present("no-dump") {
- println!("{}", file.syntax_tree());
+ println!("{}", syntax_tree(&file));
}
eprintln!("parsing: {:?}", elapsed);
::std::mem::forget(file);
}
("symbols", _) => {
let file = file()?;
- for s in file.symbols() {
+ for s in symbols(&file) {
println!("{:?}", s);
}
}
Ok(())
}
-fn file() -> Result<File> {
+fn file() -> Result<ast::File> {
let text = read_stdin()?;
- Ok(File::new(&text))
+ Ok(ast::File::parse(&text))
}
fn read_stdin() -> Result<String> {
None => bail!("No test found at line {} at {}", line, file.display()),
Some((_start_line, test)) => test,
};
- let file = File::new(&test.text);
- let tree = file.syntax_tree();
+ let file = ast::File::parse(&test.text);
+ let tree = syntax_tree(&file);
Ok((test.text, tree))
}
name = "m"
version = "0.1.0"
authors = ["Aleksey Kladov <aleksey.kladov@gmail.com>"]
-[workspace]
[dependencies]
failure = "0.1.2"
serde_derive = "1.0.71"
drop_bomb = "0.1.0"
crossbeam-channel = "0.2.4"
+threadpool = "1.7.1"
+flexi_logger = "0.9.0"
+log = "0.4.3"
libeditor = { path = "../../libeditor" }
libanalysis = { path = "../../libanalysis" }
R::Params: DeserializeOwned,
R::Result: Serialize,
{
- pub fn respond_with(self, io: &mut Io, f: impl FnOnce() -> Result<R::Result>) -> Result<()> {
- match f() {
+ pub fn response(self, io: &mut Io, resp: Result<R::Result>) -> Result<()> {
+ match resp {
Ok(res) => self.result(io, res)?,
Err(e) => {
self.error(io)?;
match self.chan.recv() {
Some(msg) => Ok(msg),
None => {
- self.thread
- .take()
- .ok_or_else(|| format_err!("MsgReceiver thread panicked"))?
- .join()
- .map_err(|_| format_err!("MsgReceiver thread panicked"))??;
- bail!("client disconnected")
+ self.cleanup()?;
+ unreachable!()
}
}
}
+ fn cleanup(&mut self) -> Result<()> {
+ self.thread
+ .take()
+ .ok_or_else(|| format_err!("MsgReceiver thread panicked"))?
+ .join()
+ .map_err(|_| format_err!("MsgReceiver thread panicked"))??;
+ bail!("client disconnected")
+ }
+
fn stop(self) -> Result<()> {
// Can't really self.thread.join() here, b/c it might be
// blocking on read
struct MsgSender {
chan: Sender<RawMsg>,
- thread: Option<thread::JoinHandle<Result<()>>>,
+ thread: thread::JoinHandle<Result<()>>,
}
impl MsgSender {
self.chan.send(msg)
}
- fn stop(mut self) -> Result<()> {
- if let Some(thread) = self.thread.take() {
- thread.join()
- .map_err(|_| format_err!("MsgSender thread panicked"))??
- }
+ fn stop(self) -> Result<()> {
+ drop(self.chan);
+ self.thread.join()
+ .map_err(|_| format_err!("MsgSender thread panicked"))??;
Ok(())
}
}
-impl Drop for MsgSender {
- fn drop(&mut self) {
- if let Some(thread) = self.thread.take() {
- let res = thread.join();
- if thread::panicking() {
- drop(res)
- } else {
- res.unwrap().unwrap()
- }
- }
- }
-}
-
pub struct Io {
receiver: MsgReceiver,
sender: MsgSender,
let (tx, rx) = bounded(16);
MsgSender {
chan: tx,
- thread: Some(thread::spawn(move || {
+ thread: thread::spawn(move || {
let stdout = stdout();
let mut stdout = stdout.lock();
for msg in rx {
write_msg_text(&mut stdout, &text)?;
}
Ok(())
- })),
+ }),
}
};
let receiver = {
self.receiver.recv()
}
+ pub fn receiver(&mut self) -> &mut Receiver<RawMsg> {
+ &mut self.receiver.chan
+ }
+
+ pub fn cleanup_receiver(&mut self) -> Result<()> {
+ self.receiver.cleanup()
+ }
+
pub fn stop(self) -> Result<()> {
self.receiver.stop()?;
self.sender.stop()?;
buf.resize(size, 0);
inp.read_exact(&mut buf)?;
let buf = String::from_utf8(buf)?;
+ debug!("< {}", buf);
Ok(Some(buf))
}
fn write_msg_text(out: &mut impl Write, msg: &str) -> Result<()> {
+ debug!("> {}", msg);
write!(out, "Content-Length: {}\r\n\r\n", msg.len())?;
out.write_all(msg.as_bytes())?;
out.flush()?;
extern crate serde_json;
extern crate languageserver_types;
extern crate drop_bomb;
+#[macro_use]
extern crate crossbeam_channel;
+extern crate threadpool;
+#[macro_use]
+extern crate log;
+extern crate flexi_logger;
extern crate libeditor;
extern crate libanalysis;
mod dispatch;
use languageserver_types::InitializeResult;
+use threadpool::ThreadPool;
+use crossbeam_channel::{bounded, Sender, Receiver};
+use flexi_logger::Logger;
use libanalysis::WorldState;
-use self::io::{Io, RawMsg};
+
+use ::{
+ io::{Io, RawMsg},
+};
pub type Result<T> = ::std::result::Result<T, ::failure::Error>;
fn main() -> Result<()> {
+ Logger::with_env_or_str("m=trace")
+ .log_to_file()
+ .directory("log")
+ .start()?;
+ info!("starting server");
+ match ::std::panic::catch_unwind(|| main_inner()) {
+ Ok(res) => {
+ info!("shutting down: {:?}", res);
+ res
+ }
+ Err(_) => {
+ error!("server panicked");
+ bail!("server panicked")
+ },
+ }
+}
+
+fn main_inner() -> Result<()> {
let mut io = Io::from_stdio();
- initialize(&mut io)?;
- io.stop()?;
- Ok(())
+ let res = initialize(&mut io);
+ info!("shutting down IO...");
+ let io_res = io.stop();
+ info!("... IO is down");
+ match (res, io_res) {
+ (Ok(()), Ok(())) => Ok(()),
+ (res, Ok(())) => res,
+ (Ok(()), io_res) => io_res,
+ (res, Err(io_err)) => {
+ error!("shutdown error: {:?}", io_err);
+ res
+ }
+ }
}
fn initialize(io: &mut Io) -> Result<()> {
}
}
+type Thunk = Box<for<'a> FnBox<&'a mut Io, Result<()>>>;
+
fn initialized(io: &mut Io) -> Result<()> {
- eprintln!("initialized");
- let world = WorldState::new();
+ let mut world = WorldState::new();
+ let mut pool = ThreadPool::new(4);
+ let (sender, receiver) = bounded::<Thunk>(16);
+ let res = main_loop(io, &mut world, &mut pool, sender, receiver.clone());
+ info!("waiting for background jobs to finish...");
+ receiver.for_each(drop);
+ info!("...background jobs have finished");
+ res
+}
+
+fn main_loop(
+ io: &mut Io,
+ world: &mut WorldState,
+ pool: &mut ThreadPool,
+ sender: Sender<Thunk>,
+ receiver: Receiver<Thunk>,
+) -> Result<()> {
+ info!("server initialized, serving requests");
loop {
- match io.recv()? {
+ enum Event {
+ Msg(RawMsg),
+ Thunk(Thunk),
+ ReceiverDead,
+ }
+
+ let event = select! {
+ recv(io.receiver(), msg) => match msg {
+ Some(msg) => Event::Msg(msg),
+ None => Event::ReceiverDead,
+ },
+ recv(receiver, thunk) => Event::Thunk(thunk.unwrap()),
+ };
+
+ let msg = match event {
+ Event::ReceiverDead => {
+ io.cleanup_receiver()?;
+ unreachable!();
+ }
+ Event::Thunk(thunk) => {
+ thunk.call_box(io)?;
+ continue;
+ }
+ Event::Msg(msg) => msg,
+ };
+
+ match msg {
RawMsg::Request(req) => {
- let world = world.snapshot();
if let Some((params, resp)) = dispatch::expect::<req::SyntaxTree>(io, req)? {
- resp.respond_with(io, || {
- let path = params.text_document.uri.to_file_path()
- .map_err(|()| format_err!("invalid path"))?;
- let file = world.file_syntax(&path)?;
- Ok(libeditor::syntax_tree(&file))
- })?
+ let world = world.snapshot();
+ let sender = sender.clone();
+ pool.execute(move || {
+ let res: Result<String> = (|| {
+ let path = params.text_document.uri.to_file_path()
+ .map_err(|()| format_err!("invalid path"))?;
+ let file = world.file_syntax(&path)?;
+ Ok(libeditor::syntax_tree(&file))
+ })();
+
+ sender.send(Box::new(|io: &mut Io| resp.response(io, res)))
+ });
}
}
msg => {
}
}
+
+trait FnBox<A, R>: Send {
+ fn call_box(self: Box<Self>, a: A) -> R;
+}
+
+impl<A, R, F: FnOnce(A) -> R + Send> FnBox<A, R> for F {
+ fn call_box(self: Box<F>, a: A) -> R {
+ (*self)(a)
+ }
+}
+++ /dev/null
-{"rustc_fingerprint":11898242945176772229,"outputs":{"15337506775154344876":["___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/home/matklad/.rustup/toolchains/stable-x86_64-unknown-linux-gnu\ndebug_assertions\nproc_macro\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_os=\"linux\"\ntarget_pointer_width=\"64\"\nunix\n",""],"1617349019360157463":["___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/home/matklad/.rustup/toolchains/stable-x86_64-unknown-linux-gnu\ndebug_assertions\nproc_macro\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_os=\"linux\"\ntarget_pointer_width=\"64\"\nunix\n",""],"1164083562126845933":["rustc 1.28.0 (9634041f0 2018-07-30)\nbinary: rustc\ncommit-hash: 9634041f0e8c0f3191d2867311276f19d0a42564\ncommit-date: 2018-07-30\nhost: x86_64-unknown-linux-gnu\nrelease: 1.28.0\nLLVM version: 6.0\n",""]}}
\ No newline at end of file
+++ /dev/null
-/home/matklad/projects/libsyntax2/codeless/server/target/debug/libm.rmeta: /home/matklad/projects/libsyntax2/codeless/server/src/caps.rs /home/matklad/projects/libsyntax2/codeless/server/src/dispatch.rs /home/matklad/projects/libsyntax2/codeless/server/src/io.rs /home/matklad/projects/libsyntax2/codeless/server/src/main.rs /home/matklad/projects/libsyntax2/codeless/server/src/req.rs /home/matklad/projects/libsyntax2/libanalysis/src/lib.rs /home/matklad/projects/libsyntax2/libeditor/src/extend_selection.rs /home/matklad/projects/libsyntax2/libeditor/src/lib.rs /home/matklad/projects/libsyntax2/src/algo/mod.rs /home/matklad/projects/libsyntax2/src/algo/walk.rs /home/matklad/projects/libsyntax2/src/ast/generated.rs /home/matklad/projects/libsyntax2/src/ast/mod.rs /home/matklad/projects/libsyntax2/src/grammar/attributes.rs /home/matklad/projects/libsyntax2/src/grammar/expressions/atom.rs /home/matklad/projects/libsyntax2/src/grammar/expressions/mod.rs /home/matklad/projects/libsyntax2/src/grammar/items/consts.rs /home/matklad/projects/libsyntax2/src/grammar/items/mod.rs /home/matklad/projects/libsyntax2/src/grammar/items/structs.rs /home/matklad/projects/libsyntax2/src/grammar/items/traits.rs /home/matklad/projects/libsyntax2/src/grammar/items/use_item.rs /home/matklad/projects/libsyntax2/src/grammar/mod.rs /home/matklad/projects/libsyntax2/src/grammar/params.rs /home/matklad/projects/libsyntax2/src/grammar/paths.rs /home/matklad/projects/libsyntax2/src/grammar/patterns.rs /home/matklad/projects/libsyntax2/src/grammar/type_args.rs /home/matklad/projects/libsyntax2/src/grammar/type_params.rs /home/matklad/projects/libsyntax2/src/grammar/types.rs /home/matklad/projects/libsyntax2/src/lexer/classes.rs /home/matklad/projects/libsyntax2/src/lexer/comments.rs /home/matklad/projects/libsyntax2/src/lexer/mod.rs /home/matklad/projects/libsyntax2/src/lexer/numbers.rs /home/matklad/projects/libsyntax2/src/lexer/ptr.rs /home/matklad/projects/libsyntax2/src/lexer/strings.rs /home/matklad/projects/libsyntax2/src/lib.rs /home/matklad/projects/libsyntax2/src/parser_api.rs /home/matklad/projects/libsyntax2/src/parser_impl/event.rs /home/matklad/projects/libsyntax2/src/parser_impl/input.rs /home/matklad/projects/libsyntax2/src/parser_impl/mod.rs /home/matklad/projects/libsyntax2/src/smol_str.rs /home/matklad/projects/libsyntax2/src/syntax_kinds/generated.rs /home/matklad/projects/libsyntax2/src/syntax_kinds/mod.rs /home/matklad/projects/libsyntax2/src/utils.rs /home/matklad/projects/libsyntax2/src/yellow/builder.rs /home/matklad/projects/libsyntax2/src/yellow/green.rs /home/matklad/projects/libsyntax2/src/yellow/mod.rs /home/matklad/projects/libsyntax2/src/yellow/red.rs /home/matklad/projects/libsyntax2/src/yellow/syntax.rs
function startServer() {
let run: Executable = {
command: "cargo",
- args: ["run"],
- options: {
- cwd: "./server"
- }
+ args: ["run", "--package", "m"],
+ options: { cwd: "." }
}
let serverOptions: ServerOptions = {
run,
SyntaxNodeRef, AstNode,
algo::walk,
SyntaxKind::*,
- ast,
};
-pub use libsyntax2::{TextRange, TextUnit};
+pub use libsyntax2::{TextRange, TextUnit, ast};
#[derive(Debug)]
pub struct HighlightedRange {
use std::fmt;
use itertools::Itertools;
-use libeditor::{File, TextRange};
+use libeditor::{ast, highlight, runnables, extend_selection, TextRange};
#[test]
fn test_extend_selection() {
}
"#);
let range = TextRange::offset_len(18.into(), 0.into());
- let range = file.extend_selection(range).unwrap();
+ let range = extend_selection(&file, range).unwrap();
assert_eq!(range, TextRange::from_to(17.into(), 18.into()));
- let range = file.extend_selection(range).unwrap();
+ let range = extend_selection(&file, range).unwrap();
assert_eq!(range, TextRange::from_to(15.into(), 20.into()));
}
fn main() {}
println!("Hello, {}!", 92);
"#);
- let hls = file.highlight();
+ let hls = highlight(&file);
dbg_eq(
&hls,
r#"[HighlightedRange { range: [1; 11), tag: "comment" },
#[ignore]
fn test_foo() {}
"#);
- let runnables = file.runnables();
+ let runnables = runnables(&file);
dbg_eq(
&runnables,
r#"[Runnable { range: [1; 13), kind: Bin },
)
}
-fn file(text: &str) -> File {
- File::new(text)
+fn file(text: &str) -> ast::File {
+ ast::File::parse(text)
}
fn dbg_eq(actual: &impl fmt::Debug, expected: &str) {
}
}
-#[test]
-fn assert_send_sync() {
- fn f<T: Send + Sync>() {}
- f::<GreenNode>();
-}
-
#[derive(Clone, Debug)]
pub(crate) struct GreenBranch {
text_len: TextUnit,
mod red;
mod syntax;
-pub use self::syntax::{SyntaxNode, SyntaxNodeRef, SyntaxRoot, TreeRoot, SyntaxError};
+use std::{
+ ops::Deref,
+ sync::Arc,
+ ptr,
+};
+pub use self::syntax::{SyntaxNode, SyntaxNodeRef, SyntaxError};
pub(crate) use self::{
builder::GreenBuilder,
green::GreenNode,
red::RedNode,
};
+
+pub trait TreeRoot: Deref<Target=SyntaxRoot> + Clone + Send + Sync {}
+
+#[derive(Debug)]
+pub struct SyntaxRoot {
+ red: RedNode,
+ pub(crate) errors: Vec<SyntaxError>,
+}
+
+impl TreeRoot for Arc<SyntaxRoot> {}
+
+impl<'a> TreeRoot for &'a SyntaxRoot {}
+
+impl SyntaxRoot {
+ pub(crate) fn new(green: GreenNode, errors: Vec<SyntaxError>) -> SyntaxRoot {
+ SyntaxRoot {
+ red: RedNode::new_root(green),
+ errors,
+ }
+ }
+}
+
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+pub(crate) struct RedPtr(ptr::NonNull<RedNode>);
+
+unsafe impl Send for RedPtr {}
+
+unsafe impl Sync for RedPtr {}
+
+impl RedPtr {
+ fn new(red: &RedNode) -> RedPtr {
+ RedPtr(red.into())
+ }
+
+ unsafe fn get<'a>(self, _root: &'a impl TreeRoot) -> &'a RedNode {
+ &*self.0.as_ptr()
+ }
+}
+
+#[test]
+fn assert_send_sync() {
+ fn f<T: Send + Sync>() {}
+ f::<GreenNode>();
+ f::<RedNode>();
+ f::<SyntaxNode>();
+}
-use std::ptr;
-
use parking_lot::RwLock;
-use {yellow::GreenNode, TextUnit};
+use {yellow::{GreenNode, RedPtr}, TextUnit};
#[derive(Debug)]
pub(crate) struct RedNode {
#[derive(Debug)]
struct ParentData {
- parent: ptr::NonNull<RedNode>,
+ parent: RedPtr,
start_offset: TextUnit,
index_in_parent: usize,
}
fn new_child(
green: GreenNode,
- parent: ptr::NonNull<RedNode>,
+ parent: RedPtr,
start_offset: TextUnit,
index_in_parent: usize,
) -> RedNode {
self.green.children().len()
}
- pub(crate) fn get_child(&self, idx: usize) -> Option<ptr::NonNull<RedNode>> {
+ pub(crate) fn get_child(&self, idx: usize) -> Option<RedPtr> {
if idx >= self.n_children() {
return None;
}
match &self.children.read()[idx] {
- Some(child) => return Some(child.into()),
+ Some(child) => return Some(RedPtr::new(child)),
None => (),
};
let green_children = self.green.children();
.map(|x| x.text_len())
.sum::<TextUnit>();
let child =
- RedNode::new_child(green_children[idx].clone(), self.into(), start_offset, idx);
+ RedNode::new_child(green_children[idx].clone(), RedPtr::new(self), start_offset, idx);
let mut children = self.children.write();
if children[idx].is_none() {
children[idx] = Some(child)
}
- Some(children[idx].as_ref().unwrap().into())
+ Some(RedPtr::new(children[idx].as_ref().unwrap()))
}
- pub(crate) fn parent(&self) -> Option<ptr::NonNull<RedNode>> {
+ pub(crate) fn parent(&self) -> Option<RedPtr> {
Some(self.parent.as_ref()?.parent)
}
pub(crate) fn index_in_parent(&self) -> Option<usize> {
-use std::{fmt, ops::Deref, ptr, sync::Arc};
+use std::{fmt, sync::Arc};
use {
- yellow::{GreenNode, RedNode},
+ yellow::{RedNode, TreeRoot, SyntaxRoot, RedPtr},
SyntaxKind::{self, *},
TextRange, TextUnit,
};
-pub trait TreeRoot: Deref<Target = SyntaxRoot> + Clone {}
-
-impl TreeRoot for Arc<SyntaxRoot> {}
-
-impl<'a> TreeRoot for &'a SyntaxRoot {}
#[derive(Clone, Copy)]
pub struct SyntaxNode<R: TreeRoot = Arc<SyntaxRoot>> {
pub(crate) root: R,
// Guaranteed to not dangle, because `root` holds a
// strong reference to red's ancestor
- red: ptr::NonNull<RedNode>,
+ red: RedPtr,
}
+unsafe impl<R: TreeRoot> Send for SyntaxNode<R> {}
+unsafe impl<R: TreeRoot> Sync for SyntaxNode<R> {}
+
impl<R1: TreeRoot, R2: TreeRoot> PartialEq<SyntaxNode<R1>> for SyntaxNode<R2> {
fn eq(&self, other: &SyntaxNode<R1>) -> bool {
self.red == other.red
pub type SyntaxNodeRef<'a> = SyntaxNode<&'a SyntaxRoot>;
-#[derive(Debug)]
-pub struct SyntaxRoot {
- red: RedNode,
- pub(crate) errors: Vec<SyntaxError>,
-}
-
-impl SyntaxRoot {
- pub(crate) fn new(green: GreenNode, errors: Vec<SyntaxError>) -> SyntaxRoot {
- SyntaxRoot {
- red: RedNode::new_root(green),
- errors,
- }
- }
-}
-
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub struct SyntaxError {
pub msg: String,
impl SyntaxNode<Arc<SyntaxRoot>> {
pub(crate) fn new_owned(root: SyntaxRoot) -> Self {
let root = Arc::new(root);
- let red_weak = ptr::NonNull::from(&root.red);
- SyntaxNode {
- root,
- red: red_weak,
- }
+ let red = RedPtr::new(&root.red);
+ SyntaxNode { root, red }
}
}
pub fn as_ref<'a>(&'a self) -> SyntaxNode<&'a SyntaxRoot> {
SyntaxNode {
root: &*self.root,
- red: ptr::NonNull::clone(&self.red),
+ red: self.red,
}
}
}
fn red(&self) -> &RedNode {
- unsafe { self.red.as_ref() }
+ unsafe { self.red.get(&self.root) }
}
}