mirror of
https://github.com/oxalica/nil.git
synced 2024-11-27 00:06:26 +03:00
Split Path mod and intern it
This commit is contained in:
parent
a96f80646d
commit
365ec46cdc
108
src/def/lower.rs
108
src/def/lower.rs
@ -1,6 +1,7 @@
|
||||
use super::{
|
||||
AstPtr, Attrpath, Binding, BindingId, BindingKey, BindingValue, Bindings, Expr, ExprId,
|
||||
Literal, Module, ModuleSourceMap, NameDef, NameDefId, NameDefKind, Pat, Path, PathAnchor,
|
||||
AstPtr, Attrpath, Binding, BindingId, BindingKey, BindingValue, Bindings, DefDatabase, Expr,
|
||||
ExprId, Literal, Module, ModuleSourceMap, NameDef, NameDefId, NameDefKind, Pat, PathAnchor,
|
||||
PathData,
|
||||
};
|
||||
use crate::{Diagnostic, DiagnosticKind, FileId};
|
||||
use indexmap::IndexMap;
|
||||
@ -11,8 +12,13 @@ use std::{mem, str};
|
||||
use syntax::ast::{self, HasBindings, HasStringParts, LiteralKind};
|
||||
use syntax::{Parse, TextRange};
|
||||
|
||||
pub(super) fn lower(file_id: FileId, parse: Parse) -> (Module, ModuleSourceMap) {
|
||||
pub(super) fn lower(
|
||||
db: &dyn DefDatabase,
|
||||
file_id: FileId,
|
||||
parse: Parse,
|
||||
) -> (Module, ModuleSourceMap) {
|
||||
let mut ctx = LowerCtx {
|
||||
db,
|
||||
file_id,
|
||||
module: Module {
|
||||
exprs: Arena::new(),
|
||||
@ -31,13 +37,14 @@ pub(super) fn lower(file_id: FileId, parse: Parse) -> (Module, ModuleSourceMap)
|
||||
(module, ctx.source_map)
|
||||
}
|
||||
|
||||
struct LowerCtx {
|
||||
struct LowerCtx<'a> {
|
||||
db: &'a dyn DefDatabase,
|
||||
file_id: FileId,
|
||||
module: Module,
|
||||
source_map: ModuleSourceMap,
|
||||
}
|
||||
|
||||
impl LowerCtx {
|
||||
impl LowerCtx<'_> {
|
||||
fn alloc_expr(&mut self, expr: Expr, ptr: AstPtr) -> ExprId {
|
||||
let id = self.module.exprs.alloc(expr);
|
||||
self.source_map.expr_map.insert(ptr.clone(), id);
|
||||
@ -210,25 +217,6 @@ impl LowerCtx {
|
||||
let tok = lit.token().unwrap();
|
||||
let mut text = tok.text();
|
||||
|
||||
fn normalize_path(path: &str) -> (usize, SmolStr) {
|
||||
let mut ret = String::new();
|
||||
let mut supers = 0usize;
|
||||
for seg in path.split('/').filter(|&seg| !seg.is_empty() && seg != ".") {
|
||||
if seg != ".." {
|
||||
if !ret.is_empty() {
|
||||
ret.push('/');
|
||||
}
|
||||
ret.push_str(seg);
|
||||
} else if ret.is_empty() {
|
||||
supers += 1;
|
||||
} else {
|
||||
let last_slash = ret.bytes().rposition(|c| c != b'/').unwrap_or(0);
|
||||
ret.truncate(last_slash);
|
||||
}
|
||||
}
|
||||
(supers, ret.into())
|
||||
}
|
||||
|
||||
Some(match kind {
|
||||
LiteralKind::Int => Literal::Int(text.parse::<i64>().ok()?),
|
||||
LiteralKind::Float => Literal::Float(text.parse::<f64>().unwrap().into()),
|
||||
@ -238,14 +226,10 @@ impl LowerCtx {
|
||||
}
|
||||
LiteralKind::SearchPath => {
|
||||
text = &text[1..text.len() - 1]; // Strip '<' and '>'.
|
||||
let (search_name, relative_path) = text.split_once('/').unwrap_or((text, ""));
|
||||
let (search_name, text) = text.split_once('/').unwrap_or((text, ""));
|
||||
let anchor = PathAnchor::Search(search_name.into());
|
||||
let (supers, raw_segments) = normalize_path(relative_path);
|
||||
Literal::Path(Path {
|
||||
anchor,
|
||||
supers,
|
||||
raw_segments,
|
||||
})
|
||||
let path = self.db.intern_path(PathData::normalize(anchor, text));
|
||||
Literal::Path(path)
|
||||
}
|
||||
LiteralKind::Path => {
|
||||
let anchor = match text.as_bytes()[0] {
|
||||
@ -256,16 +240,8 @@ impl LowerCtx {
|
||||
}
|
||||
_ => PathAnchor::Relative(self.file_id),
|
||||
};
|
||||
let (mut supers, raw_segments) = normalize_path(text);
|
||||
// Extra ".." has no effect for absolute path.
|
||||
if anchor == PathAnchor::Absolute {
|
||||
supers = 0;
|
||||
}
|
||||
Literal::Path(Path {
|
||||
anchor,
|
||||
supers,
|
||||
raw_segments,
|
||||
})
|
||||
let path = self.db.intern_path(PathData::normalize(anchor, text));
|
||||
Literal::Path(path)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -637,13 +613,16 @@ impl MergingEntry {
|
||||
mod tests {
|
||||
use super::lower;
|
||||
use crate::base::FileId;
|
||||
use crate::def::DefDatabase;
|
||||
use crate::tests::TestDB;
|
||||
use expect_test::{expect, Expect};
|
||||
use std::fmt::Write;
|
||||
use syntax::parse_file;
|
||||
|
||||
#[track_caller]
|
||||
fn check_lower(src: &str, expect: Expect) {
|
||||
let parse = parse_file(src);
|
||||
let (module, _source_map) = lower(FileId(0), parse);
|
||||
let (db, file_id, []) = TestDB::single_file(src).unwrap();
|
||||
let module = db.module(file_id);
|
||||
let mut got = String::new();
|
||||
for diag in module.diagnostics() {
|
||||
writeln!(got, "{:?}", diag).unwrap();
|
||||
@ -669,9 +648,21 @@ mod tests {
|
||||
expect.assert_eq(&got);
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn check_path(src: &str, expect: Expect) {
|
||||
let (db, file_id, []) = TestDB::single_file(src).unwrap();
|
||||
let _module = db.module(file_id);
|
||||
let mut got = String::new();
|
||||
for &p in db.module_paths(file_id).iter() {
|
||||
writeln!(got, "{:?}", p.data(&db)).unwrap();
|
||||
}
|
||||
expect.assert_eq(&got);
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn check_error(src: &str, expect: Expect) {
|
||||
let parse = parse_file(src);
|
||||
let (module, _source_map) = lower(FileId(0), parse);
|
||||
let (db, file_id, []) = TestDB::single_file(src).unwrap();
|
||||
let module = db.module(file_id);
|
||||
let mut got = String::new();
|
||||
for diag in module.diagnostics() {
|
||||
writeln!(got, "{:?}", diag).unwrap();
|
||||
@ -705,40 +696,40 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn path() {
|
||||
check_lower(
|
||||
check_path(
|
||||
"./.",
|
||||
expect![[r#"
|
||||
0: Literal(Path(Path { anchor: Relative(FileId(0)), supers: 0, raw_segments: "" }))
|
||||
PathData { anchor: Relative(FileId(0)), supers: 0, raw_segments: "" }
|
||||
"#]],
|
||||
);
|
||||
check_lower(
|
||||
check_path(
|
||||
"../.",
|
||||
expect![[r#"
|
||||
0: Literal(Path(Path { anchor: Relative(FileId(0)), supers: 1, raw_segments: "" }))
|
||||
PathData { anchor: Relative(FileId(0)), supers: 1, raw_segments: "" }
|
||||
"#]],
|
||||
);
|
||||
check_lower(
|
||||
check_path(
|
||||
"../a/../../.b/./c",
|
||||
expect![[r#"
|
||||
0: Literal(Path(Path { anchor: Relative(FileId(0)), supers: 2, raw_segments: ".b/c" }))
|
||||
PathData { anchor: Relative(FileId(0)), supers: 2, raw_segments: ".b/c" }
|
||||
"#]],
|
||||
);
|
||||
check_lower(
|
||||
check_path(
|
||||
"/../a/../../.b/./c",
|
||||
expect![[r#"
|
||||
0: Literal(Path(Path { anchor: Absolute, supers: 0, raw_segments: ".b/c" }))
|
||||
PathData { anchor: Absolute, supers: 0, raw_segments: ".b/c" }
|
||||
"#]],
|
||||
);
|
||||
check_lower(
|
||||
check_path(
|
||||
"~/../a/../../.b/./c",
|
||||
expect![[r#"
|
||||
0: Literal(Path(Path { anchor: Home, supers: 2, raw_segments: ".b/c" }))
|
||||
PathData { anchor: Home, supers: 2, raw_segments: ".b/c" }
|
||||
"#]],
|
||||
);
|
||||
check_lower(
|
||||
check_path(
|
||||
"<p/../a/../../.b/./c>",
|
||||
expect![[r#"
|
||||
0: Literal(Path(Path { anchor: Search("p"), supers: 2, raw_segments: ".b/c" }))
|
||||
PathData { anchor: Search("p"), supers: 2, raw_segments: ".b/c" }
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
@ -1321,6 +1312,7 @@ mod tests {
|
||||
fn attrset_malformed_no_panic() {
|
||||
let src = "{ } @ y: y { cc, extraPackages ? optional (cc.isGNU) }: 1";
|
||||
let parse = parse_file(src);
|
||||
let (_module, _source_map) = lower(FileId(0), parse);
|
||||
let db = TestDB::default();
|
||||
let _ = lower(&db, FileId(0), parse);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
mod liveness;
|
||||
mod lower;
|
||||
mod path;
|
||||
mod scope;
|
||||
|
||||
#[cfg(test)]
|
||||
@ -15,17 +16,24 @@ use std::ops;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub use self::liveness::LivenessCheck;
|
||||
pub use self::path::{Path, PathAnchor, PathData};
|
||||
pub use self::scope::{ModuleScopes, NameReferenceMap, ResolveResult, ScopeData, ScopeId};
|
||||
pub use syntax::ast::{BinaryOpKind as BinaryOp, UnaryOpKind as UnaryOp};
|
||||
|
||||
#[salsa::query_group(DefDatabaseStorage)]
|
||||
pub trait DefDatabase: SourceDatabase {
|
||||
#[salsa::interned]
|
||||
fn intern_path(&self, path: PathData) -> Path;
|
||||
|
||||
fn module_with_source_map(&self, file_id: FileId) -> (Arc<Module>, Arc<ModuleSourceMap>);
|
||||
|
||||
fn module(&self, file_id: FileId) -> Arc<Module>;
|
||||
|
||||
fn source_map(&self, file_id: FileId) -> Arc<ModuleSourceMap>;
|
||||
|
||||
#[salsa::invoke(Module::module_paths_query)]
|
||||
fn module_paths(&self, file_id: FileId) -> Arc<[Path]>;
|
||||
|
||||
#[salsa::invoke(ModuleScopes::module_scopes_query)]
|
||||
fn scopes(&self, file_id: FileId) -> Arc<ModuleScopes>;
|
||||
|
||||
@ -44,7 +52,7 @@ fn module_with_source_map(
|
||||
file_id: FileId,
|
||||
) -> (Arc<Module>, Arc<ModuleSourceMap>) {
|
||||
let parse = db.parse(file_id);
|
||||
let (module, source_map) = lower::lower(file_id, parse);
|
||||
let (module, source_map) = lower::lower(db, file_id, parse);
|
||||
(Arc::new(module), Arc::new(source_map))
|
||||
}
|
||||
|
||||
@ -110,6 +118,20 @@ impl Module {
|
||||
) -> impl Iterator<Item = (BindingId, &'_ Binding)> + ExactSizeIterator + '_ {
|
||||
self.bindings.iter()
|
||||
}
|
||||
|
||||
pub(crate) fn module_paths_query(db: &dyn DefDatabase, file_id: FileId) -> Arc<[Path]> {
|
||||
let module = db.module(file_id);
|
||||
let mut paths = module
|
||||
.exprs()
|
||||
.filter_map(|(_, kind)| match kind {
|
||||
&Expr::Literal(Literal::Path(p)) => Some(p),
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
paths.sort_unstable();
|
||||
paths.dedup();
|
||||
paths.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub type AstPtr = rowan::ast::SyntaxNodePtr<syntax::NixLanguage>;
|
||||
@ -233,28 +255,6 @@ pub enum Literal {
|
||||
Path(Path),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct Path {
|
||||
pub anchor: PathAnchor,
|
||||
pub supers: usize,
|
||||
// Normalized path separated by `/`, with no `.` or `..` segments.
|
||||
pub raw_segments: SmolStr,
|
||||
}
|
||||
|
||||
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(SmolStr),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct Pat {
|
||||
pub fields: Box<[(Option<NameDefId>, Option<ExprId>)]>,
|
||||
|
112
src/def/path.rs
Normal file
112
src/def/path.rs
Normal file
@ -0,0 +1,112 @@
|
||||
use super::DefDatabase;
|
||||
use crate::FileId;
|
||||
use smol_str::SmolStr;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Path(salsa::InternId);
|
||||
|
||||
impl salsa::InternKey for Path {
|
||||
fn from_intern_id(v: salsa::InternId) -> Self {
|
||||
Self(v)
|
||||
}
|
||||
fn as_intern_id(&self) -> salsa::InternId {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Path {
|
||||
pub fn data(self, db: &dyn DefDatabase) -> PathData {
|
||||
db.lookup_intern_path(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct PathData {
|
||||
anchor: PathAnchor,
|
||||
supers: u8,
|
||||
// Normalized path separated by `/`, with no `.` or `..` segments.
|
||||
raw_segments: SmolStr,
|
||||
}
|
||||
|
||||
impl PathData {
|
||||
pub(crate) fn normalize(anchor: PathAnchor, segments: &str) -> Self {
|
||||
let mut raw_segments = String::new();
|
||||
let mut supers = 0u8;
|
||||
for seg in segments
|
||||
.split('/')
|
||||
.filter(|&seg| !seg.is_empty() && seg != ".")
|
||||
{
|
||||
if seg != ".." {
|
||||
if !raw_segments.is_empty() {
|
||||
raw_segments.push('/');
|
||||
}
|
||||
raw_segments.push_str(seg);
|
||||
} else if raw_segments.is_empty() {
|
||||
// Extra ".." has no effect for absolute path.
|
||||
if anchor != PathAnchor::Absolute {
|
||||
supers = supers.saturating_add(1);
|
||||
}
|
||||
} else {
|
||||
let last_slash = raw_segments.bytes().rposition(|c| c == b'/').unwrap_or(0);
|
||||
raw_segments.truncate(last_slash);
|
||||
}
|
||||
}
|
||||
Self {
|
||||
anchor,
|
||||
supers,
|
||||
raw_segments: raw_segments.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn anchor(&self) -> &PathAnchor {
|
||||
&self.anchor
|
||||
}
|
||||
|
||||
pub fn supers(&self) -> u8 {
|
||||
self.supers
|
||||
}
|
||||
|
||||
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(SmolStr),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{PathAnchor, PathData};
|
||||
use crate::FileId;
|
||||
|
||||
#[test]
|
||||
#[rustfmt::skip]
|
||||
fn normalize_relative() {
|
||||
for anchor in [PathAnchor::Relative(FileId(0)), PathAnchor::Home, PathAnchor::Search("foo".into())] {
|
||||
let norm = |s| PathData::normalize(anchor.clone(), s);
|
||||
assert_eq!(norm(""), PathData { anchor: anchor.clone(), supers: 0, raw_segments: "".into() });
|
||||
assert_eq!(norm("./."), PathData { anchor: anchor.clone(), supers: 0, raw_segments: "".into() });
|
||||
assert_eq!(norm("./.."), PathData { anchor: anchor.clone(), supers: 1, raw_segments: "".into() });
|
||||
assert_eq!(norm("../."), PathData { anchor: anchor.clone(), supers: 1, raw_segments: "".into() });
|
||||
assert_eq!(norm("foo/./bar/../.baz"), PathData { anchor: anchor.clone(), supers: 0, raw_segments: "foo/.baz".into() });
|
||||
assert_eq!(norm("../../foo"), PathData { anchor: anchor.clone(), supers: 2, raw_segments: "foo".into() });
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[rustfmt::skip]
|
||||
fn normalize_absolute() {
|
||||
let anchor = PathAnchor::Absolute;
|
||||
let norm = |s| PathData::normalize(anchor.clone(), s);
|
||||
assert_eq!(norm("/./."), PathData { anchor: anchor.clone(), supers: 0, raw_segments: "".into() });
|
||||
assert_eq!(norm("/./.."), PathData { anchor: anchor.clone(), supers: 0, raw_segments: "".into() });
|
||||
assert_eq!(norm("/../."), PathData { anchor: anchor.clone(), supers: 0, raw_segments: "".into() });
|
||||
assert_eq!(norm("/foo/./bar/../.baz"), PathData { anchor: anchor.clone(), supers: 0, raw_segments: "foo/.baz".into() });
|
||||
assert_eq!(norm("/../../foo"), PathData { anchor: anchor.clone(), supers: 0, raw_segments: "foo".into() });
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user