1
1
mirror of https://github.com/oxalica/nil.git synced 2024-11-26 02:48:06 +03:00

Impl more exprs and "with" scopes

This commit is contained in:
oxalica 2022-07-27 01:56:09 +08:00
parent 9ce13ad940
commit 916017b9ec
7 changed files with 359 additions and 144 deletions

View File

@ -1,12 +1,13 @@
use super::{
AstPtr, Expr, ExprId, Literal, Module, ModuleSourceMap, NameDef, NameDefId, Pat, Path,
PathAnchor,
AstPtr, Attrpath, Expr, ExprId, Literal, Module, ModuleSourceMap, NameDef, NameDefId, Pat,
Path, PathAnchor,
};
use crate::base::{FileId, InFile};
use la_arena::Arena;
use rowan::ast::AstNode;
use smol_str::SmolStr;
use syntax::ast::{self, LiteralKind};
use std::str;
use syntax::ast::{self, HasStringParts, LiteralKind};
pub(super) fn lower(root: InFile<ast::SourceFile>) -> (Module, ModuleSourceMap) {
let mut ctx = LowerCtx {
@ -39,17 +40,21 @@ impl LowerCtx {
id
}
fn alloc_name_def(&mut self, node: ast::Name) -> NameDefId {
let name = node
.token()
.map_or_else(Default::default, |tok| tok.text().into());
fn alloc_name_def(&mut self, name: SmolStr, ptr: AstPtr) -> NameDefId {
let id = self.module.name_defs.alloc(NameDef { name });
let ptr = AstPtr::new(node.syntax());
self.source_map.name_def_map.insert(ptr.clone(), id);
self.source_map.name_def_map_rev.insert(id, ptr);
id
}
fn lower_name(&mut self, node: ast::Name) -> NameDefId {
let name = node
.token()
.map_or_else(Default::default, |tok| tok.text().into());
let ptr = AstPtr::new(node.syntax());
self.alloc_name_def(name, ptr)
}
fn lower_expr_opt(&mut self, expr: Option<ast::Expr>) -> ExprId {
if let Some(expr) = expr {
return self.lower_expr(expr);
@ -79,12 +84,12 @@ impl LowerCtx {
ast::Expr::Paren(e) => self.lower_expr_opt(e.expr()),
ast::Expr::Lambda(e) => {
let (param, pat) = e.param().map_or((None, None), |param| {
let name = param.name().map(|n| self.alloc_name_def(n));
let name = param.name().map(|n| self.lower_name(n));
let pat = param.pat().map(|pat| {
let fields = pat
.fields()
.map(|field| {
let field_name = field.name().map(|n| self.alloc_name_def(n));
let field_name = field.name().map(|n| self.lower_name(n));
let default_expr = field.default_expr().map(|e| self.lower_expr(e));
(field_name, default_expr)
})
@ -97,18 +102,52 @@ impl LowerCtx {
let body = self.lower_expr_opt(e.body());
self.alloc_expr(Expr::Lambda(param, pat, body), ptr)
}
ast::Expr::Assert(_) => todo!(),
ast::Expr::Assert(e) => {
let cond = self.lower_expr_opt(e.condition());
let body = self.lower_expr_opt(e.body());
self.alloc_expr(Expr::Assert(cond, body), ptr)
}
ast::Expr::IfThenElse(e) => {
let cond = self.lower_expr_opt(e.condition());
let then_body = self.lower_expr_opt(e.then_body());
let else_body = self.lower_expr_opt(e.else_body());
self.alloc_expr(Expr::IfThenElse(cond, then_body, else_body), ptr)
}
ast::Expr::With(e) => {
let env = self.lower_expr_opt(e.environment());
let body = self.lower_expr_opt(e.body());
self.alloc_expr(Expr::With(env, body), ptr)
}
ast::Expr::BinaryOp(e) => {
let lhs = self.lower_expr_opt(e.lhs());
let op = e.op_kind();
let rhs = self.lower_expr_opt(e.rhs());
self.alloc_expr(Expr::Binary(op, lhs, rhs), ptr)
}
ast::Expr::UnaryOp(e) => {
let op = e.op_kind();
let arg = self.lower_expr_opt(e.arg());
self.alloc_expr(Expr::Unary(op, arg), ptr)
}
ast::Expr::HasAttr(e) => {
let set = self.lower_expr_opt(e.set());
let attrpath = self.lower_attrpath_opt(e.attrpath());
self.alloc_expr(Expr::HasAttr(set, attrpath), ptr)
}
ast::Expr::Select(e) => {
let set = self.lower_expr_opt(e.set());
let attrpath = self.lower_attrpath_opt(e.attrpath());
let default_expr = e.default_expr().map(|e| self.lower_expr(e));
self.alloc_expr(Expr::Select(set, attrpath, default_expr), ptr)
}
ast::Expr::String(s) => self.lower_string(&s),
ast::Expr::IndentString(s) => self.lower_string(&s),
ast::Expr::List(e) => {
let elements = e.elements().map(|e| self.lower_expr(e)).collect();
self.alloc_expr(Expr::List(elements), ptr)
}
ast::Expr::AttrSet(_) => todo!(),
ast::Expr::BinaryOp(_) => todo!(),
ast::Expr::HasAttr(_) => todo!(),
ast::Expr::IfThenElse(_) => todo!(),
ast::Expr::IndentString(_) => todo!(),
ast::Expr::LetIn(_) => todo!(),
ast::Expr::List(_) => todo!(),
ast::Expr::Select(_) => todo!(),
ast::Expr::String(_) => todo!(),
ast::Expr::UnaryOp(_) => todo!(),
ast::Expr::With(_) => todo!(),
}
}
@ -172,4 +211,39 @@ impl LowerCtx {
}
})
}
fn lower_attrpath_opt(&mut self, attrpath: Option<ast::Attrpath>) -> Attrpath {
attrpath
.into_iter()
.flat_map(|attrpath| attrpath.attrs())
.map(|attr| match attr {
ast::Attr::Dynamic(d) => self.lower_expr_opt(d.expr()),
ast::Attr::Name(n) => {
let name = n
.token()
.map_or_else(Default::default, |tok| tok.text().into());
let ptr = AstPtr::new(n.syntax());
self.alloc_expr(Expr::Literal(Literal::String(name)), ptr)
}
ast::Attr::String(s) => self.lower_string(&s),
})
.collect()
}
fn lower_string(&mut self, n: &impl HasStringParts) -> ExprId {
let ptr = AstPtr::new(n.syntax());
// Here we don't need to special case literal strings.
// They would simply become `Expr::StringInterpolation([])`.
let parts = n
.string_parts()
.filter_map(|part| {
match part {
ast::StringPart::Dynamic(d) => Some(self.lower_expr_opt(d.expr())),
// Currently we don't encode literal fragments.
ast::StringPart::Fragment(_) | ast::StringPart::Escape(_) => None,
}
})
.collect();
self.alloc_expr(Expr::StringInterpolation(parts), ptr)
}
}

View File

@ -10,7 +10,8 @@ use ordered_float::OrderedFloat;
use smol_str::SmolStr;
use std::{collections::HashMap, ops, sync::Arc};
pub use self::scope::{ModuleScopes, ScopeData, ScopeId};
pub use self::scope::{ModuleScopes, ResolveResult, ScopeData, ScopeId};
pub use syntax::ast::{BinaryOpKind as BinaryOp, UnaryOpKind as UnaryOp};
#[salsa::query_group(DefDatabaseStorage)]
pub trait DefDatabase: SourceDatabase {
@ -24,7 +25,7 @@ pub trait DefDatabase: SourceDatabase {
fn scopes(&self, file_id: FileId) -> Arc<ModuleScopes>;
#[salsa::invoke(ModuleScopes::resolve_name_query)]
fn resolve_name(&self, file_id: FileId, expr_id: ExprId) -> Option<NameDefId>;
fn resolve_name(&self, file_id: FileId, expr_id: ExprId) -> Option<ResolveResult>;
}
fn module_with_source_map(
@ -97,11 +98,59 @@ pub enum Expr {
Missing,
Reference(SmolStr),
Literal(Literal),
Apply(ExprId, ExprId),
Lambda(Option<NameDefId>, Option<Pat>, ExprId),
With(ExprId, ExprId),
Assert(ExprId, ExprId),
IfThenElse(ExprId, ExprId, ExprId),
Binary(Option<BinaryOp>, ExprId, ExprId),
Apply(ExprId, ExprId),
Unary(Option<UnaryOp>, ExprId),
HasAttr(ExprId, Attrpath),
Select(ExprId, Attrpath, Option<ExprId>),
StringInterpolation(Box<[ExprId]>),
List(Box<[ExprId]>),
// TODO
}
impl Expr {
pub(crate) fn walk_child_exprs(&self, mut f: impl FnMut(ExprId)) {
match self {
Self::Missing | Self::Reference(_) | Self::Literal(_) => {}
Self::Lambda(_, pat, body) => {
if let Some(p) = pat {
p.fields
.iter()
.filter_map(|&(_, default_expr)| default_expr)
.for_each(&mut f);
}
f(*body);
}
Self::Unary(_, a) => f(*a),
Self::Assert(a, b) | Self::With(a, b) | Self::Binary(_, a, b) | Self::Apply(a, b) => {
f(*a);
f(*b);
}
Self::IfThenElse(a, b, c) => {
f(*a);
f(*b);
f(*c);
}
Self::HasAttr(set, path) => {
f(*set);
path.iter().copied().for_each(f);
}
Self::Select(set, path, default_expr) => {
f(*set);
path.iter().copied().for_each(&mut f);
if let &Some(e) = default_expr {
f(e);
}
}
Self::StringInterpolation(xs) | Self::List(xs) => xs.iter().copied().for_each(f),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NameDef {
pub name: SmolStr,
@ -142,3 +191,5 @@ pub struct Pat {
pub fields: Box<[(Option<NameDefId>, Option<ExprId>)]>,
pub ellipsis: bool,
}
pub type Attrpath = Box<[ExprId]>;

View File

@ -12,12 +12,6 @@ pub struct ModuleScopes {
pub type ScopeId = Idx<ScopeData>;
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct ScopeData {
parent: Option<ScopeId>,
name_defs: HashMap<SmolStr, NameDefId>,
}
impl ops::Index<ScopeId> for ModuleScopes {
type Output = ScopeData;
fn index(&self, index: ScopeId) -> &Self::Output {
@ -28,9 +22,12 @@ impl ops::Index<ScopeId> for ModuleScopes {
impl ModuleScopes {
pub(crate) fn module_scopes_query(db: &dyn DefDatabase, file_id: FileId) -> Arc<Self> {
let module = db.module(file_id);
let mut this = ModuleScopes::default();
let root = this.new_root_scope();
this.traverse_expr(&*module, root, module.entry_expr);
let mut this = Self::default();
let root_scope = this.scopes.alloc(ScopeData {
parent: None,
kind: ScopeKind::NameDefs(Default::default()),
});
this.traverse_expr(&*module, module.entry_expr, root_scope);
Arc::new(this)
}
@ -38,13 +35,13 @@ impl ModuleScopes {
db: &dyn DefDatabase,
file_id: FileId,
expr_id: ExprId,
) -> Option<NameDefId> {
) -> Option<ResolveResult> {
let module = db.module(file_id);
let name = match &module[expr_id] {
Expr::Reference(name) => name,
_ => return None,
};
db.scopes(file_id).resolve_anme(expr_id, name)
db.scopes(file_id).resolve_name(expr_id, name)
}
pub fn scope_by_expr(&self, expr_id: ExprId) -> Option<ScopeId> {
@ -55,66 +52,184 @@ impl ModuleScopes {
iter::successors(Some(scope_id), |&i| self[i].parent).map(|i| &self[i])
}
pub fn resolve_anme(&self, expr_id: ExprId, name: &SmolStr) -> Option<NameDefId> {
pub fn resolve_name(&self, expr_id: ExprId, name: &SmolStr) -> Option<ResolveResult> {
let mut inner_env = None;
self.ancestors(self.scope_by_expr(expr_id)?)
.find_map(|scope| scope.resolve(name))
}
fn new_root_scope(&mut self) -> ScopeId {
self.scopes.alloc(ScopeData {
parent: None,
name_defs: HashMap::new(),
})
}
fn new_scope(&mut self, parent: ScopeId, name_defs: HashMap<SmolStr, NameDefId>) -> ScopeId {
self.scopes.alloc(ScopeData {
parent: Some(parent),
name_defs,
})
}
fn traverse_expr(&mut self, module: &Module, scope_id: ScopeId, expr_id: ExprId) {
self.scope_by_expr.insert(expr_id, scope_id);
match module[expr_id] {
Expr::Missing | Expr::Reference(_) | Expr::Literal(_) => {}
Expr::Apply(func, arg) => {
self.traverse_expr(module, scope_id, func);
self.traverse_expr(module, scope_id, arg);
}
Expr::Lambda(param_opt, ref pat_opt, body) => {
let mut name_defs = HashMap::default();
if let Some(name_id) = param_opt {
name_defs.insert(module[name_id].name.clone(), name_id);
.find_map(|data| match &data.kind {
ScopeKind::NameDefs(defs) => defs.get(name).copied(),
ScopeKind::WithEnv(env) => {
inner_env = inner_env.or(Some(*env));
None
}
if let Some(pat) = pat_opt {
})
.map(ResolveResult::NameDef)
.or_else(|| inner_env.map(ResolveResult::WithEnv))
}
fn traverse_expr(&mut self, module: &Module, expr: ExprId, scope: ScopeId) {
self.scope_by_expr.insert(expr, scope);
match &module[expr] {
Expr::Lambda(param, pat, body) => {
let mut defs = HashMap::default();
if let &Some(name_id) = param {
defs.insert(module[name_id].name.clone(), name_id);
}
if let Some(pat) = pat {
for name_id in pat.fields.iter().filter_map(|(opt_id, _)| *opt_id) {
name_defs.insert(module[name_id].name.clone(), name_id);
defs.insert(module[name_id].name.clone(), name_id);
}
}
let scope_id = self.new_scope(scope_id, name_defs);
if let Some(pat) = pat_opt {
for expr_id in pat
.fields
.iter()
.filter_map(|(_, default_expr_id)| *default_expr_id)
{
self.traverse_expr(module, scope_id, expr_id);
let scope = if !defs.is_empty() {
self.scopes.alloc(ScopeData {
parent: Some(scope),
kind: ScopeKind::NameDefs(defs),
})
} else {
scope
};
if let Some(pat) = pat {
for default_expr in pat.fields.iter().filter_map(|(_, e)| *e) {
self.traverse_expr(module, default_expr, scope);
}
}
self.traverse_expr(module, scope_id, body);
self.traverse_expr(module, *body, scope);
}
Expr::With(env, body) => {
self.traverse_expr(module, *env, scope);
let scope = self.scopes.alloc(ScopeData {
parent: Some(scope),
kind: ScopeKind::WithEnv(*env),
});
self.traverse_expr(module, *body, scope);
}
e => e.walk_child_exprs(|e| self.traverse_expr(module, e, scope)),
}
}
}
impl ScopeData {
pub fn name_defs(&self) -> impl Iterator<Item = NameDefId> + '_ {
self.name_defs.values().copied()
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ResolveResult {
NameDef(NameDefId),
WithEnv(ExprId),
}
pub fn resolve(&self, name: &SmolStr) -> Option<NameDefId> {
self.name_defs.get(name).copied()
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ScopeData {
parent: Option<ScopeId>,
kind: ScopeKind,
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum ScopeKind {
NameDefs(HashMap<SmolStr, NameDefId>),
WithEnv(ExprId),
}
impl ScopeData {
pub fn name_defs(&self) -> impl Iterator<Item = (&SmolStr, NameDefId)> + '_ {
match &self.kind {
ScopeKind::NameDefs(defs) => Some(defs),
ScopeKind::WithEnv(_) => None,
}
.into_iter()
.flatten()
.map(|(name, &def)| (name, def))
}
}
#[cfg(test)]
mod tests {
use crate::{
base::SourceDatabase,
def::{AstPtr, DefDatabase, ResolveResult},
tests::TestDB,
};
use rowan::ast::AstNode;
use syntax::ast;
#[track_caller]
fn check_scopes(src: &str, expect: &str) {
let (db, file_id, pos) = TestDB::from_file_with_pos(src);
let ptr = AstPtr::new(db.node_at::<ast::Expr>(file_id, pos).syntax());
let source_map = db.source_map(file_id);
let expr_id = source_map.expr_map[&ptr];
let scopes = db.scopes(file_id);
// "innermost var abc | middle var | outmost var"
let scope_id = scopes.scope_by_expr(expr_id).expect("No scope data");
let scope_defs = scopes
.ancestors(scope_id)
.filter_map(|scope| {
let mut names = scope
.name_defs()
.map(|(name, _)| &**name)
.collect::<Vec<_>>();
if names.is_empty() {
return None;
}
names.sort();
Some(names.join(" "))
})
.collect::<Vec<_>>();
let got = scope_defs.join(" | ");
assert_eq!(got, expect);
}
#[track_caller]
fn check_resolve(src: &str, expect: Option<u32>) {
let (db, file_id, pos) = TestDB::from_file_with_pos(src);
let ptr = AstPtr::new(db.node_at::<ast::Expr>(file_id, pos).syntax());
let parse = db.parse(file_id).value;
let source_map = db.source_map(file_id);
let expr_id = source_map.expr_map[&ptr];
let got = db
.resolve_name(file_id, expr_id)
.map(|ret| match ret {
ResolveResult::NameDef(def) => source_map
.name_def_node(def)
.unwrap()
.to_node(&parse.syntax_node()),
ResolveResult::WithEnv(env) => source_map
.expr_node(env)
.unwrap()
.to_node(&parse.syntax_node()),
})
.map(|n| u32::from(n.text_range().start()));
assert_eq!(got, expect);
}
#[test]
fn top_level() {
check_scopes(r"$0a", "");
check_resolve(r"$0a", None);
}
#[test]
fn lambda() {
check_scopes(r"(a: b: (c: 0) $0a (d: 0)) (e: 0)", "b | a");
check_scopes(r"{ a, b ? c, ... }@d: $0x (y: x)", "a b d");
check_scopes(r"a: { a, b ? $0c, ... }@d: y: a", "a b d | a");
check_resolve(r"a: b: b$0", Some(3));
check_resolve(r"a: { a, b ? $0d, ... }@d: y: a", Some(21));
check_resolve(r"a: { a, b ? $0y, ... }@d: y: a", None);
check_resolve(r"a: { a, b ? $0a, ... }@d: y: a", Some(5));
check_resolve(r"a: { x, b ? $0a, ... }@d: y: a", Some(0));
}
#[test]
fn with() {
check_scopes(r"a: with b; c: $0a", "c | a");
check_resolve(r"a: with b; c: $0a", Some(0));
check_resolve(r"a: with b; c: $0c", Some(11));
check_resolve(r"a: with b; c: $0x", Some(8));
check_resolve(r"x: with a; with b; $0x", Some(0));
check_resolve(r"x: with a; with b; $0y", Some(16));
}
}

View File

@ -3,7 +3,6 @@ use crate::tests::TestDB;
use expect_test::expect;
mod lower;
mod scope;
#[test]
fn source_map() {

View File

@ -1,49 +0,0 @@
use super::DefDatabase;
use crate::def::AstPtr;
use crate::tests::TestDB;
use expect_test::{expect, Expect};
use rowan::ast::AstNode;
use syntax::ast;
#[track_caller]
fn check_scopes(src: &str, expect: Expect) {
let (db, file_id, pos) = TestDB::from_file_with_pos(src);
let ptr = AstPtr::new(db.node_at::<ast::Expr>(file_id, pos).syntax());
let module = db.module(file_id);
let source_map = db.source_map(file_id);
let expr_id = source_map.expr_map[&ptr];
let scopes = db.scopes(file_id);
let scope_id = scopes.scope_by_expr(expr_id).expect("No scope data");
// "innermost var abc | middle var | outmost var"
let scope_defs = scopes
.ancestors(scope_id)
.filter_map(|scope| {
let mut names = scope
.name_defs()
.map(|def_id| module[def_id].name.clone())
.collect::<Vec<_>>();
if names.is_empty() {
return None;
}
names.sort();
Some(names.join(" "))
})
.collect::<Vec<_>>();
let got = scope_defs.join(" | ");
expect.assert_eq(&got);
}
#[test]
fn top_level() {
check_scopes(r"42$0", expect![""]);
}
#[test]
fn lambda() {
check_scopes(r"(a: b: (c: 0) 42$0 (d: 0)) (e: 0)", expect!["b | a"]);
check_scopes(r"{ a, b ? c, ... }@d: $0x (y: x)", expect!["a b d"]);
check_scopes(r"a: { a, b ? $0c, ... }@d: y: a", expect!["a b d | a"]);
}

View File

@ -1,5 +1,5 @@
use super::NavigationTarget;
use crate::def::{AstPtr, DefDatabase};
use crate::def::{AstPtr, DefDatabase, ResolveResult};
use crate::FileId;
use rowan::ast::AstNode;
use rowan::TextSize;
@ -19,21 +19,36 @@ pub(crate) fn goto_definition(
let source_map = db.source_map(file_id);
let expr_id = source_map.node_expr(AstPtr::new(node.syntax()))?;
let def_id = db.resolve_name(file_id, expr_id)?;
let def_node = source_map
.name_def_node(def_id)?
.to_node(&parse.syntax_node());
let full_node = def_node.ancestors().find(|n| {
matches!(
n.kind(),
SyntaxKind::LAMBDA | SyntaxKind::ATTR_PATH_VALUE | SyntaxKind::INHERIT
)
})?;
let (focus_range, full_range) = match db.resolve_name(file_id, expr_id)? {
ResolveResult::NameDef(def) => {
let name_node = source_map.name_def_node(def)?.to_node(&parse.syntax_node());
let full_node = name_node.ancestors().find(|n| {
matches!(
n.kind(),
SyntaxKind::LAMBDA | SyntaxKind::ATTR_PATH_VALUE | SyntaxKind::INHERIT
)
})?;
(name_node.text_range(), full_node.text_range())
}
ResolveResult::WithEnv(env) => {
// with expr; body
// ^--^ focus
// ^--------^ full
let env_node = source_map.expr_node(env)?.to_node(&parse.syntax_node());
let with_node = ast::With::cast(env_node.parent()?)?;
let with_token_range = with_node.with_token()?.text_range();
let with_header_end = with_node
.semicolon_token()
.map_or_else(|| env_node.text_range(), |tok| tok.text_range());
let with_header = with_token_range.cover(with_header_end);
(with_token_range, with_header)
}
};
Some(NavigationTarget {
file_id,
full_range: full_node.text_range(),
focus_range: def_node.text_range(),
focus_range,
full_range,
})
}
@ -74,4 +89,9 @@ mod tests {
check("a: ({ x ? $0a }@a: a) 1", expect!["{ x ? a }@<a>: a"]);
check("a: ({ x ? $0x }@a: a) 1", expect!["{ <x> ? x }@a: a"]);
}
#[test]
fn with_env() {
check("with a; $0b", expect!["<with> a;"]);
}
}

View File

@ -90,6 +90,7 @@ trait NodeWrapper {
macro_rules! enums {
($($name:ident { $($variant:ident,)* },)*) => {
$(
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum $name {
$($variant($variant),)*
}
@ -309,6 +310,7 @@ asts! {
},
HAS_ATTR = HasAttr {
set: Expr,
question_token: T![?],
attrpath: Attrpath,
},
IF_THEN_ELSE = IfThenElse {
@ -336,6 +338,7 @@ asts! {
LET_IN = LetIn [HasBindings] {
let_token: T![let],
in_token: T![in],
body: Expr,
},
LIST = List {
l_brack_token: T!['['],
@ -422,7 +425,9 @@ asts! {
}
},
WITH = With {
with_token: T![with],
environment: Expr,
semicolon_token: T![;],
body[1]: Expr,
},
}