1
1
mirror of https://github.com/oxalica/nil.git synced 2024-10-27 20:44:38 +03:00

Impl incremental analysis framework

This commit is contained in:
oxalica 2022-07-19 00:14:23 +08:00
parent 42c2333b8b
commit fdca59f98a
8 changed files with 652 additions and 0 deletions

331
Cargo.lock generated
View File

@ -2,6 +2,337 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cbitset"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29b6ad25ae296159fb0da12b970b2fe179b234584d7cd294c891e2bbb284466b"
dependencies = [
"num-traits",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "countme"
version = "3.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636"
[[package]]
name = "crossbeam-utils"
version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d82ee10ce34d7bc12c2122495e7593a9c41347ecdd64185af4ecf72cb1a7f83"
dependencies = [
"cfg-if",
]
[[package]]
name = "dissimilar"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c97b9233581d84b8e1e689cdd3a47b6f69770084fc246e86a7f78b0d9c1d4a5"
[[package]]
name = "expect-test"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dced95c9dcd4e3241f95841aad395f9c8d7933a3b0b524bdeb2440885c72a271"
dependencies = [
"dissimilar",
"once_cell",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "heck"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "indexmap"
version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
dependencies = [
"autocfg",
"hashbrown",
]
[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
]
[[package]]
name = "la-arena"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d43db9f547ba2787bdeac81ef97ccc80784f385b3b39b4d50344270629a2bee3"
[[package]]
name = "libc"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
[[package]]
name = "lock_api"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "memoffset"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
dependencies = [
"autocfg",
]
[[package]] [[package]]
name = "nil" name = "nil"
version = "0.0.0" version = "0.0.0"
dependencies = [
"expect-test",
"la-arena",
"rnix",
"salsa",
]
[[package]]
name = "num-traits"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
[[package]]
name = "oorandom"
version = "11.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
[[package]]
name = "parking_lot"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
dependencies = [
"instant",
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
dependencies = [
"cfg-if",
"instant",
"libc",
"redox_syscall",
"smallvec",
"winapi",
]
[[package]]
name = "proc-macro2"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42"
dependencies = [
"bitflags",
]
[[package]]
name = "rnix"
version = "0.11.0-dev"
source = "git+https://github.com/nix-community/rnix-parser.git#f759fce8ac005ed14d13219579e62c06bdfba678"
dependencies = [
"cbitset",
"rowan",
]
[[package]]
name = "rowan"
version = "0.15.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf980f4bf24d4ea266da37ce8f0e6bfd6eaf06120dcfba53c3e2ad6bdfe5f32b"
dependencies = [
"countme",
"hashbrown",
"memoffset",
"rustc-hash",
"text-size",
]
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "salsa"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b84d9f96071f3f3be0dc818eae3327625d8ebc95b58da37d6850724f31d3403"
dependencies = [
"crossbeam-utils",
"indexmap",
"lock_api",
"log",
"oorandom",
"parking_lot",
"rustc-hash",
"salsa-macros",
"smallvec",
]
[[package]]
name = "salsa-macros"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd3904a4ba0a9d0211816177fd34b04c7095443f8cdacd11175064fe541c8fe2"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "smallvec"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1"
[[package]]
name = "syn"
version = "1.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "text-size"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "288cb548dbe72b652243ea797201f3d481a0609a967980fcc5b2315ea811560a"
[[package]]
name = "unicode-ident"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7"
[[package]]
name = "unicode-segmentation"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View File

@ -4,3 +4,9 @@ version = "0.0.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
la-arena = "0.2.1"
rnix = { git = "https://github.com/nix-community/rnix-parser.git" }
salsa = "0.16.1"
[dev-dependencies]
expect-test = "1.3.0"

111
src/def/lower.rs Normal file
View File

@ -0,0 +1,111 @@
use std::collections::HashSet;
use super::{Expr, ExprId, Literal, Module, ModuleSourceMap, Path, PathAnchor};
use crate::source::{FileId, InFile};
use rnix::types::{self as ast, ParsedType, TokenWrapper, TypedNode, Wrapper};
use rnix::value::Anchor;
use rnix::{NixValue, SyntaxNode};
pub(super) fn lower(root: InFile<ast::Root>) -> (Module, ModuleSourceMap) {
let mut ctx = LowerCtx {
file_id: root.file_id,
module: Module::default(),
paths: HashSet::default(),
};
ctx.lower_node_opt(Some(root.value.node().clone()));
(ctx.module, ModuleSourceMap::default())
}
struct LowerCtx {
file_id: FileId,
module: Module,
paths: HashSet<Path>,
}
impl LowerCtx {
fn alloc_expr(&mut self, expr: Expr) -> ExprId {
self.module.exprs.alloc(expr)
}
fn alloc_missing(&mut self) -> ExprId {
self.alloc_expr(Expr::Missing)
}
fn lower_node_opt(&mut self, node: Option<SyntaxNode>) -> ExprId {
if let Some(node) = node {
if let Ok(expr) = ParsedType::try_from(node) {
return self.lower_expr(expr);
}
}
self.alloc_missing()
}
fn lower_path(&mut self, anchor: Anchor, segments: &str) -> ExprId {
let anchor = match anchor {
Anchor::Relative => PathAnchor::Relative(self.file_id),
Anchor::Absolute => todo!(),
Anchor::Home => todo!(),
Anchor::Store => todo!(),
};
let path = Path {
anchor,
raw_segments: segments.into(),
};
self.paths.insert(path.clone());
self.alloc_expr(Expr::Literal(Literal::Path(path)))
}
fn lower_expr(&mut self, expr: ParsedType) -> ExprId {
match expr {
ParsedType::Root(e) => self.lower_node_opt(e.inner()),
ParsedType::Paren(e) => self.lower_node_opt(e.inner()),
ParsedType::Ident(e) => {
let ident = e.to_inner_string();
self.alloc_expr(Expr::Ident(ident.into()))
}
ParsedType::Value(e) => match e.to_value() {
Ok(v) => {
let lit = match v {
NixValue::Integer(x) => Literal::Int(x),
NixValue::Float(_) => todo!(),
NixValue::String(s) => Literal::String(s.into()),
NixValue::Path(anchor, path) => return self.lower_path(anchor, &path),
};
self.alloc_expr(Expr::Literal(lit))
}
Err(_) => self.alloc_missing(),
},
ParsedType::Apply(e) => {
let lam = self.lower_node_opt(e.lambda());
let arg = self.lower_node_opt(e.value());
self.alloc_expr(Expr::Apply(lam, arg))
}
ParsedType::Assert(_) => todo!(),
ParsedType::IfElse(_) => todo!(),
ParsedType::Select(_) => todo!(),
ParsedType::Inherit(_) => todo!(),
ParsedType::InheritFrom(_) => todo!(),
ParsedType::Lambda(_) => todo!(),
ParsedType::LegacyLet(_) => todo!(),
ParsedType::LetIn(_) => todo!(),
ParsedType::List(_) => todo!(),
ParsedType::BinOp(_) => todo!(),
ParsedType::OrDefault(_) => todo!(),
ParsedType::AttrSet(_) => todo!(),
ParsedType::KeyValue(_) => todo!(),
ParsedType::Str(_) => todo!(),
ParsedType::StrInterpol(_) => todo!(),
ParsedType::UnaryOp(_) => todo!(),
ParsedType::With(_) => todo!(),
ParsedType::PathWithInterpol(_) => todo!(),
// Invalid nodes in expression location.
ParsedType::Pattern(_)
| ParsedType::PatBind(_)
| ParsedType::PatEntry(_)
| ParsedType::Key(_)
| ParsedType::Dynamic(_)
| ParsedType::Error(_) => self.alloc_missing(),
}
}
}

89
src/def/mod.rs Normal file
View File

@ -0,0 +1,89 @@
mod lower;
#[cfg(test)]
mod tests;
use crate::source::{FileId, SourceDatabase};
use la_arena::{Arena, Idx};
use std::ops;
use std::sync::Arc;
#[salsa::query_group(DefDatabaseStorage)]
pub trait DefDatabase: SourceDatabase {
#[salsa::invoke(Module::module_with_source_map_query)]
fn module_with_source_map(&self, file_id: FileId) -> (Arc<Module>, Arc<ModuleSourceMap>);
#[salsa::invoke(Module::module_query)]
fn module(&self, file_id: FileId) -> Arc<Module>;
}
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct Module {
exprs: Arena<Expr>,
}
impl Module {
fn module_with_source_map_query(
db: &dyn DefDatabase,
file_id: FileId,
) -> (Arc<Module>, Arc<ModuleSourceMap>) {
let root = db.parse(file_id);
let (module, source_map) = lower::lower(root);
(Arc::new(module), Arc::new(source_map))
}
fn module_query(db: &dyn DefDatabase, file_id: FileId) -> Arc<Module> {
db.module_with_source_map(file_id).0
}
}
impl ops::Index<ExprId> for Module {
type Output = Expr;
fn index(&self, index: ExprId) -> &Self::Output {
&self.exprs[index]
}
}
// TODO
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct ModuleSourceMap {}
pub type ExprId = Idx<Expr>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Expr {
Missing,
Ident(Box<str>),
Literal(Literal),
Apply(ExprId, ExprId),
// TODO
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Literal {
Int(i64),
String(Box<str>),
Path(Path),
// TODO
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Path {
pub anchor: PathAnchor,
// Normalized path separated by `/`, with no `.` or `..` segments.
pub raw_segments: Box<str>,
}
impl Path {
pub fn segments(&self) -> impl Iterator<Item = &str> + '_ {
self.raw_segments.split(' ').filter(|s| !s.is_empty())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum PathAnchor {
Relative(FileId),
Absolute,
Home,
Search(Box<str>),
}

51
src/def/tests.rs Normal file
View File

@ -0,0 +1,51 @@
use super::DefDatabase;
use crate::tests::FixtureExt;
use crate::RootDatabase;
use expect_test::expect;
#[test]
fn module_basic() {
let (db, root_id) = RootDatabase::from_single_file(
r#"
foo 123 ./bar.nix
"#,
);
expect![[r#"
Module {
exprs: Arena {
len: 5,
data: [
Ident(
"foo",
),
Literal(
Int(
123,
),
),
Apply(
Idx::<Expr>(0),
Idx::<Expr>(1),
),
Literal(
Path(
Path {
anchor: Relative(
FileId(
0,
),
),
raw_segments: "./bar.nix",
},
),
),
Apply(
Idx::<Expr>(2),
Idx::<Expr>(3),
),
],
},
}
"#]]
.assert_debug_eq(&db.module(root_id));
}

View File

@ -0,0 +1,15 @@
mod def;
mod source;
#[cfg(test)]
mod tests;
use crate::{def::DefDatabaseStorage, source::SourceDatabaseStorage};
#[salsa::database(SourceDatabaseStorage, DefDatabaseStorage)]
#[derive(Default)]
pub struct RootDatabase {
storage: salsa::Storage<Self>,
}
impl salsa::Database for RootDatabase {}

35
src/source.rs Normal file
View File

@ -0,0 +1,35 @@
use rnix::types as ast;
use std::sync::Arc;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct FileId(pub u32);
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
pub struct InFile<T> {
pub file_id: FileId,
pub value: T,
}
impl<T> InFile<T> {
pub fn new(file_id: FileId, value: T) -> Self {
Self { file_id, value }
}
}
#[salsa::query_group(SourceDatabaseStorage)]
pub trait SourceDatabase {
#[salsa::input]
fn file_source_root(&self) -> FileId;
#[salsa::input]
fn file_content(&self, file_id: FileId) -> Arc<[u8]>;
fn parse(&self, file_id: FileId) -> InFile<ast::Root>;
}
fn parse(db: &dyn SourceDatabase, file_id: FileId) -> InFile<ast::Root> {
let content = db.file_content(file_id);
let content = std::str::from_utf8(&content).unwrap_or_default();
let ast = rnix::parse(content);
InFile::new(file_id, ast.root())
}

14
src/tests.rs Normal file
View File

@ -0,0 +1,14 @@
use crate::source::{FileId, SourceDatabase};
use crate::RootDatabase;
pub trait FixtureExt: Default + SourceDatabase {
fn from_single_file(content: &str) -> (Self, FileId) {
let mut db = Self::default();
let root_id = FileId(0);
db.set_file_content(root_id, content.as_bytes().into());
db.set_file_source_root(root_id);
(db, root_id)
}
}
impl FixtureExt for RootDatabase {}