1
1
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:
oxalica 2022-08-09 18:36:47 +08:00
parent a96f80646d
commit 365ec46cdc
3 changed files with 185 additions and 81 deletions

View File

@ -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);
}
}

View File

@ -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
View 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() });
}
}