git.delta.rocks / jrsonnet / refs/commits / d349b9ef06a9

difftreelog

source

crates/jrsonnet-evaluator/src/async_import.rs9.3 KiBsourcehistory
1use std::{cell::RefCell, future::Future, path::Path};23use jrsonnet_gcmodule::Trace;4use jrsonnet_interner::IStr;5use jrsonnet_parser::{6	ArgsDesc, AssertStmt, BindSpec, CompSpec, Destruct, Expr, FieldMember, FieldName, ForSpecData,7	IfSpecData, LocExpr, Member, ObjBody, Param, ParamsDesc, ParserSettings, SliceDesc, Source,8	SourcePath,9};1011use crate::{bail, gc::GcHashMap, FileData, ImportResolver, State};1213pub struct Import {14	path: IStr,15	expression: bool,16}1718pub struct FoundImports(Vec<Import>);1920// Visits all nodes, trying to find import statements21#[allow(clippy::too_many_lines)]22pub fn find_imports(expr: &LocExpr, out: &mut FoundImports) {23	fn in_destruct(dest: &Destruct, #[allow(unused_variables)] out: &mut FoundImports) {24		match dest {25			#[cfg(feature = "exp-destruct")]26			Destruct::Array {27				start,28				rest: _,29				end,30			} => {31				for dest in start {32					in_destruct(dest, out);33				}34				for dest in end {35					in_destruct(dest, out);36				}37			}38			#[cfg(feature = "exp-destruct")]39			Destruct::Object { fields, rest: _ } => {40				for (_, dest, default) in fields {41					if let Some(dest) = dest {42						in_destruct(dest, out);43					}44					if let Some(expr) = default {45						find_imports(expr, out);46					}47				}48			}49			#[cfg(feature = "exp-destruct")]50			Destruct::Skip => {}51			Destruct::Full(_) => {}52		}53	}54	fn in_compspec(specs: &[CompSpec], out: &mut FoundImports) {55		for spec in specs {56			match spec {57				CompSpec::IfSpec(IfSpecData(expr)) => find_imports(expr, out),58				CompSpec::ForSpec(ForSpecData(destruct, expr)) => {59					in_destruct(destruct, out);60					find_imports(expr, out);61				}62			}63		}64	}65	fn in_params(params: &ParamsDesc, out: &mut FoundImports) {66		for Param(dest, default) in &*params.0 {67			in_destruct(dest, out);68			if let Some(expr) = default {69				find_imports(expr, out);70			}71		}72	}73	fn in_bind(specs: &[BindSpec], out: &mut FoundImports) {74		for spec in specs {75			match spec {76				BindSpec::Field {77					into: dest,78					value: expr,79				} => {80					in_destruct(dest, out);81					find_imports(expr, out);82				}83				BindSpec::Function {84					name: _,85					params,86					value: expr,87				} => {88					in_params(params, out);89					find_imports(expr, out);90				}91			}92		}93	}94	fn in_args(ArgsDesc { unnamed, named }: &ArgsDesc, out: &mut FoundImports) {95		for expr in unnamed {96			find_imports(expr, out);97		}98		for (_, expr) in named {99			find_imports(expr, out);100		}101	}102	fn in_obj(obj: &ObjBody, out: &mut FoundImports) {103		match obj {104			ObjBody::MemberList(v) => {105				for member in v {106					match member {107						Member::Field(FieldMember {108							name,109							params,110							value,111							..112						}) => {113							match name {114								FieldName::Fixed(_) => {}115								FieldName::Dyn(expr) => find_imports(expr, out),116							}117							if let Some(params) = params {118								in_params(params, out);119							}120							find_imports(value, out);121						}122						Member::BindStmt(_) => todo!(),123						Member::AssertStmt(AssertStmt(expr, expr2)) => {124							find_imports(expr, out);125							if let Some(expr) = expr2 {126								find_imports(expr, out);127							}128						}129					}130				}131			}132			ObjBody::ObjComp(_) => todo!(),133		}134	}135	match &*expr.0 {136		Expr::Import(v) | Expr::ImportStr(v) | Expr::ImportBin(v) => {137			if let Expr::Str(s) = &*v.0 {138				out.0.push(Import {139					path: s.clone(),140					expression: matches!(&*expr.0, Expr::Import(_)),141				});142			}143			// Non-string import will fail in runtime144		}145146		Expr::Literal(_) | Expr::Str(_) | Expr::Num(_) | Expr::Var(_) => {}147148		Expr::Arr(arr) => {149			for expr in arr {150				find_imports(expr, out);151			}152		}153		Expr::ArrComp(expr, specs) => {154			find_imports(expr, out);155			in_compspec(specs, out);156		}157		Expr::Obj(obj) => in_obj(obj, out),158		Expr::ObjExtend(expr, obj) => {159			find_imports(expr, out);160			in_obj(obj, out);161		}162		Expr::BinaryOp(a, _, b) => {163			find_imports(a, out);164			find_imports(b, out);165		}166		Expr::AssertExpr(AssertStmt(expr, expr2), then) => {167			find_imports(expr, out);168			if let Some(expr) = expr2 {169				find_imports(expr, out);170			}171			find_imports(then, out);172		}173		Expr::LocalExpr(specs, expr) => {174			in_bind(specs, out);175			find_imports(expr, out);176		}177		Expr::Apply(expr, args, _) => {178			find_imports(expr, out);179			in_args(args, out);180		}181		Expr::Index { indexable, parts } => {182			find_imports(indexable, out);183			for part in parts {184				find_imports(&part.value, out);185			}186		}187		Expr::Function(params, expr) => {188			in_params(params, out);189			find_imports(expr, out);190		}191		Expr::IfElse {192			cond: IfSpecData(expr),193			cond_then,194			cond_else,195		} => {196			find_imports(expr, out);197			find_imports(cond_then, out);198			if let Some(expr) = cond_else {199				find_imports(expr, out);200			}201		}202		Expr::Slice(expr, SliceDesc { start, end, step }) => {203			find_imports(expr, out);204			if let Some(expr) = start {205				find_imports(expr, out);206			}207			if let Some(expr) = end {208				find_imports(expr, out);209			}210			if let Some(expr) = step {211				find_imports(expr, out);212			}213		}214		Expr::Parened(expr) | Expr::UnaryOp(_, expr) | Expr::ErrorStmt(expr) => {215			find_imports(expr, out);216		}217	}218}219220pub trait AsyncImportResolver {221	type Error;222	/// Resolves file path, e.g. `(/home/user/manifests, b.libjsonnet)` can correspond223	/// both to `/home/user/manifests/b.libjsonnet` and to `/home/user/${vendor}/b.libjsonnet`224	/// where `${vendor}` is a library path.225	///226	/// `from` should only be returned from [`ImportResolver::resolve`],227	/// or from other defined file, any other value may result in panic228	fn resolve_from(229		&self,230		from: &SourcePath,231		path: &str,232	) -> impl Future<Output = Result<SourcePath, Self::Error>>;233	fn resolve_from_default(234		&self,235		path: &str,236	) -> impl Future<Output = Result<SourcePath, Self::Error>> {237		async { self.resolve_from(&SourcePath::default(), path).await }238	}239	/// Resolves absolute path, doesn't supports jpath and other fancy things240	fn resolve(&self, path: &Path) -> impl Future<Output = Result<SourcePath, Self::Error>>;241242	/// Load resolved file243	/// This should only be called with value returned244	/// from [`ImportResolver::resolve_file`]/[`ImportResolver::resolve`],245	/// this cannot be resolved using associated type,246	/// as the evaluator uses object instead of generic for [`ImportResolver`]247	fn load_file_contents(248		&self,249		resolved: &SourcePath,250	) -> impl Future<Output = Result<Vec<u8>, Self::Error>>;251}252253#[derive(Trace)]254struct ResolvedImportResolver {255	resolved: RefCell<GcHashMap<(SourcePath, IStr), (SourcePath, bool)>>,256}257impl ImportResolver for ResolvedImportResolver {258	fn load_file_contents(&self, _resolved: &SourcePath) -> crate::Result<Vec<u8>> {259		unreachable!("all files should be loaded at this point");260	}261262	fn resolve_from(&self, from: &SourcePath, path: &str) -> crate::Result<SourcePath> {263		Ok(self264			.resolved265			.borrow()266			.get(&(from.clone(), path.into()))267			.expect("all imports should be resolved at this point")268			.0269			.clone())270	}271272	fn resolve_from_default(&self, path: &str) -> crate::Result<SourcePath> {273		self.resolve_from(&SourcePath::default(), path)274	}275276	fn resolve(&self, path: &Path) -> crate::Result<SourcePath> {277		bail!(crate::error::ErrorKind::AbsoluteImportNotSupported(278			path.to_owned()279		))280	}281282	fn as_any(&self) -> &dyn std::any::Any {283		self284	}285}286287enum Job {288	LoadFile { path: SourcePath, parse: bool },289	ParseFile(SourcePath),290	ResolveImport { from: SourcePath, import: Import },291}292293#[allow(clippy::future_not_send)]294pub async fn async_import<H>(s: State, handler: H, path: impl AsRef<Path>) -> Result<(), H::Error>295where296	H: AsyncImportResolver,297{298	let mut resolved = s299		.import_resolver()300		.as_any()301		.downcast_ref::<ResolvedImportResolver>()302		.map_or_else(GcHashMap::new, |resolver| {303			std::mem::take(&mut *resolver.resolved.borrow_mut())304		});305	let mut queue = vec![Job::LoadFile {306		path: handler.resolve(path.as_ref()).await?,307		parse: true,308	}];309	while let Some(job) = queue.pop() {310		match job {311			Job::LoadFile { path, parse } => {312				if !s.0.file_cache.borrow().contains_key(&path) {313					let data = handler.load_file_contents(&path).await?;314					s.0.file_cache315						.borrow_mut()316						.insert(path.clone(), FileData::new_bytes(data.as_slice().into()));317				}318				if parse {319					queue.push(Job::ParseFile(path));320				}321			}322			Job::ParseFile(path) => {323				if let Some(file) = s.0.file_cache.borrow_mut().get_mut(&path) {324					if file.parsed.is_none() {325						let Some(code) = file.get_string() else {326							continue;327						};328						let source = Source::new(path.clone(), code.clone());329						// If failed - then skip import330						file.parsed =331							jrsonnet_parser::parse(&code, &ParserSettings { source }).ok();332						if let Some(parsed) = &file.parsed {333							let mut imports = FoundImports(vec![]);334							find_imports(parsed, &mut imports);335							for import in imports.0 {336								queue.push(Job::ResolveImport {337									from: path.clone(),338									import,339								});340							}341						}342					}343				}344			}345			Job::ResolveImport { from, import } => {346				if let Some((resolved, expression)) =347					resolved.get_mut(&(from.clone(), import.path.clone()))348				{349					if import.expression && !*expression {350						*expression = true;351						queue.push(Job::ParseFile(resolved.clone()));352					}353					continue;354				}355				let resolved = handler.resolve_from(&from, &import.path).await?;356				queue.push(Job::LoadFile {357					path: resolved,358					parse: import.expression,359				});360			}361		}362	}363	s.set_import_resolver(ResolvedImportResolver {364		resolved: RefCell::new(resolved),365	});366	Ok(())367}