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

difftreelog

feat custom SourcePath trait

Yaroslav Bolyukin2022-08-27parent: #3c7fc76.patch.diff
in: master
For usage in new import resolvers, this change allows to use custom
types within importers, allowing to allow imports from internet

2 files changed

modifiedcrates/jrsonnet-parser/src/lib.rsdiffbeforeafterboth
11mod source;11mod source;
12mod unescape;12mod unescape;
13pub use location::CodeLocation;13pub use location::CodeLocation;
14pub use source::{Source, SourcePath};14pub use source::{Source, SourceDirectory, SourceFile, SourcePath, SourcePathT, SourceVirtual};
1515
16pub struct ParserSettings {16pub struct ParserSettings {
17 pub file_name: Source,17 pub file_name: Source,
modifiedcrates/jrsonnet-parser/src/source.rsdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/source.rs
+++ b/crates/jrsonnet-parser/src/source.rs
@@ -1,7 +1,8 @@
 use std::{
-	borrow::Cow,
-	fmt,
-	path::{Component, Path, PathBuf},
+	any::Any,
+	fmt::{self, Debug, Display},
+	hash::{Hash, Hasher},
+	path::{Path, PathBuf},
 	rc::Rc,
 };
 
@@ -14,31 +15,246 @@
 
 use crate::location::{location_to_offset, offset_to_location, CodeLocation};
 
-#[cfg_attr(feature = "structdump", derive(Codegen))]
-#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
-#[derive(PartialEq, Eq, Debug, Hash, Clone)]
-pub enum SourcePath {
-	/// This file is located on disk
-	Path(PathBuf),
-	/// This file is located somewhere else (I.e http), but it can refer to relative paths, and is egilible for caching
-	Custom(String),
-	/// This file is only located in memory, and can't be cached
-	Virtual(Cow<'static, str>),
+macro_rules! any_ext_methods {
+	($T:ident) => {
+		fn as_any(&self) -> &dyn Any;
+		fn dyn_hash(&self, hasher: &mut dyn Hasher);
+		fn dyn_eq(&self, other: &dyn $T) -> bool;
+		fn dyn_debug(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
+	};
+}
+macro_rules! any_ext_impl {
+	($T:ident) => {
+		fn as_any(&self) -> &dyn Any {
+			self
+		}
+		fn dyn_hash(&self, mut hasher: &mut dyn Hasher) {
+			self.hash(&mut hasher)
+		}
+		fn dyn_eq(&self, other: &dyn $T) -> bool {
+			let other = if let Some(v) = other.as_any().downcast_ref::<Self>() {
+				v
+			} else {
+				return false;
+			};
+			let this = <Self as $T>::as_any(self)
+				.downcast_ref::<Self>()
+				.expect("restricted by impl");
+			this == other
+		}
+		fn dyn_debug(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+			<Self as std::fmt::Debug>::fmt(self, fmt)
+		}
+	};
+}
+macro_rules! any_ext {
+	($T:ident) => {
+		impl Hash for dyn $T {
+			fn hash<H: Hasher>(&self, state: &mut H) {
+				self.dyn_hash(state)
+			}
+		}
+		impl PartialEq for dyn $T {
+			fn eq(&self, other: &Self) -> bool {
+				self.dyn_eq(other)
+			}
+		}
+		impl Eq for dyn $T {}
+	};
+}
+pub trait SourcePathT: Trace + Debug + Display {
+	/// This method should be checked by resolver before panicking with bad SourcePath input
+	/// if `true` - then resolver may threat this path as default, and default is usally a CWD
+	fn is_default(&self) -> bool;
+	fn path(&self) -> Option<&Path>;
+	any_ext_methods!(SourcePathT);
 }
+any_ext!(SourcePathT);
+
+/// Represents location of a file
+///
+/// Standard CLI only operates using
+/// - [`SourceFile`] - for any file
+/// - [`SourceDirectory`] - for resolution from CWD
+/// - [`SourceVirtual`] - for stdlib/ext-str
+///
+/// From all of those, only [`SourceVirtual`] may be constructed manually, any other path kind should be only obtained
+/// from assigned `ImportResolver`
+/// However, you should always check `is_default` method return, as it will return true for any paths, where default
+/// search location is applicable
+///
+/// Resolver may also return custom implementations of this trait, for example it may return http url in case of remotely loaded files
+#[derive(Eq, Debug, Clone)]
+pub struct SourcePath(Rc<dyn SourcePathT>);
+impl SourcePath {
+	pub fn new(inner: impl SourcePathT) -> Self {
+		Self(Rc::new(inner))
+	}
+	pub fn downcast_ref<T: SourcePathT>(&self) -> Option<&T> {
+		self.0.as_any().downcast_ref()
+	}
+	pub fn is_default(&self) -> bool {
+		self.0.is_default()
+	}
+	pub fn path(&self) -> Option<&Path> {
+		self.0.path()
+	}
+}
+impl Hash for SourcePath {
+	fn hash<H: Hasher>(&self, state: &mut H) {
+		self.0.hash(state);
+	}
+}
+impl PartialEq for SourcePath {
+	#[allow(clippy::op_ref)]
+	fn eq(&self, other: &Self) -> bool {
+		&*self.0 == &*other.0
+	}
+}
 impl Trace for SourcePath {
-	fn trace(&self, _tracer: &mut Tracer) {}
+	fn trace(&self, tracer: &mut Tracer) {
+		(*self.0).trace(tracer)
+	}
+
+	fn is_type_tracked() -> bool
+	where
+		Self: Sized,
+	{
+		true
+	}
+}
+impl Display for SourcePath {
+	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+		write!(f, "{}", self.0)
+	}
+}
+impl Default for SourcePath {
+	fn default() -> Self {
+		Self(Rc::new(SourceDefault))
+	}
+}
+
+#[cfg(feature = "structdump")]
+impl Codegen for SourcePath {
+	fn gen_code(
+		&self,
+		res: &mut structdump::CodegenResult,
+		unique: bool,
+	) -> structdump::TokenStream {
+		let source_virtual = self
+			.0
+			.as_any()
+			.downcast_ref::<SourceVirtual>()
+			.expect("can only codegen for virtual source paths!")
+			.0
+			.clone();
+		let val = res.add_value(source_virtual, false);
+		res.add_code(
+			structdump::quote! {
+				structdump_import::SourcePath::new(structdump_import::SourceVirtual(#val))
+			},
+			Some(structdump::quote!(SourcePath)),
+			unique,
+		)
+	}
+}
+
+#[derive(Trace, Hash, PartialEq, Eq, Debug)]
+struct SourceDefault;
+impl Display for SourceDefault {
+	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+		write!(f, "<default>")
+	}
+}
+impl SourcePathT for SourceDefault {
+	fn is_default(&self) -> bool {
+		true
+	}
+	fn path(&self) -> Option<&Path> {
+		None
+	}
+	any_ext_impl!(SourcePathT);
+}
 
-	fn is_type_tracked() -> bool {
+/// Represents path to the file on the disk
+/// Directories shouldn't be put here, as resolution for files differs from resolution for directories:
+///
+/// When `file` is being resolved from `SourceFile(a/b/c)`, it should be resolved to `SourceFile(a/b/file)`,
+/// however if it is being resolved from `SourceDirectory(a/b/c)`, then it should be resolved to `SourceDirectory(a/b/c/file)`
+#[derive(Trace, Hash, PartialEq, Eq, Debug)]
+pub struct SourceFile(PathBuf);
+impl SourceFile {
+	pub fn new(path: PathBuf) -> Self {
+		Self(path)
+	}
+	pub fn path(&self) -> &Path {
+		&self.0
+	}
+}
+impl Display for SourceFile {
+	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+		write!(f, "{}", self.0.display())
+	}
+}
+impl SourcePathT for SourceFile {
+	fn is_default(&self) -> bool {
 		false
 	}
+	fn path(&self) -> Option<&Path> {
+		Some(&self.0)
+	}
+	any_ext_impl!(SourcePathT);
 }
 
-impl SourcePath {
-	/// Should import resolver be able to read file by this path?
-	pub fn can_load(&self) -> bool {
-		matches!(self, Self::Path(_) | Self::Custom(_))
+/// Represents path to the directory on the disk
+///
+/// See also [`SourceFile`]
+#[derive(Trace, Hash, PartialEq, Eq, Debug)]
+pub struct SourceDirectory(PathBuf);
+impl SourceDirectory {
+	pub fn new(path: PathBuf) -> Self {
+		Self(path)
+	}
+	pub fn path(&self) -> &Path {
+		&self.0
 	}
 }
+impl Display for SourceDirectory {
+	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+		write!(f, "{}", self.0.display())
+	}
+}
+impl SourcePathT for SourceDirectory {
+	fn is_default(&self) -> bool {
+		false
+	}
+	fn path(&self) -> Option<&Path> {
+		Some(&self.0)
+	}
+	any_ext_impl!(SourcePathT);
+}
+
+/// Represents virtual file, whose are located in memory, and shouldn't be cached
+///
+/// It is used for --ext-code=.../--tla-code=.../standard library source code by default,
+/// and user can construct arbitrary values by hand, without asking import resolver
+#[cfg_attr(feature = "structdump", derive(Codegen))]
+#[derive(Trace, Hash, PartialEq, Eq, Debug, Clone)]
+pub struct SourceVirtual(pub IStr);
+impl Display for SourceVirtual {
+	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+		write!(f, "{}", self.0)
+	}
+}
+impl SourcePathT for SourceVirtual {
+	fn is_default(&self) -> bool {
+		true
+	}
+	fn path(&self) -> Option<&Path> {
+		None
+	}
+	any_ext_impl!(SourcePathT);
+}
 
 /// Either real file, or virtual
 /// Hash of FileName always have same value as raw Path, to make it possible to use with raw_entry_mut
@@ -57,42 +273,20 @@
 }
 
 impl Source {
-	/// Fails when path contains inner /../ or /./ references, or not absolute
-	pub fn new(path: SourcePath, code: IStr) -> Option<Self> {
-		if let SourcePath::Path(path) = &path {
-			if !path.is_absolute()
-				|| path
-					.components()
-					.any(|c| matches!(c, Component::CurDir | Component::ParentDir))
-			{
-				return None;
-			}
-		}
-		Some(Self(Rc::new((path, code))))
+	pub fn new(path: SourcePath, code: IStr) -> Self {
+		Self(Rc::new((path, code)))
 	}
 
-	pub fn new_virtual(n: Cow<'static, str>, code: IStr) -> Self {
-		Self(Rc::new((SourcePath::Virtual(n), code)))
-	}
-
-	pub fn short_display(&self) -> ShortDisplay {
-		ShortDisplay(self.clone())
+	pub fn new_virtual(name: IStr, code: IStr) -> Self {
+		Self::new(SourcePath::new(SourceVirtual(name)), code)
 	}
 
-	/// Returns Some if this file is loaded from FS
-	pub fn path(&self) -> Option<&Path> {
-		match self.source_path() {
-			SourcePath::Path(r) => Some(r),
-			SourcePath::Custom(_) => None,
-			SourcePath::Virtual(_) => None,
-		}
-	}
 	pub fn code(&self) -> &str {
 		&self.0 .1
 	}
 
 	pub fn source_path(&self) -> &SourcePath {
-		&self.0 .0 as &SourcePath
+		&self.0 .0
 	}
 
 	pub fn map_source_locations(&self, locs: &[u32]) -> Vec<CodeLocation> {
@@ -100,21 +294,5 @@
 	}
 	pub fn map_from_source_location(&self, line: usize, column: usize) -> Option<usize> {
 		location_to_offset(&self.0 .1, line, column)
-	}
-}
-pub struct ShortDisplay(Source);
-impl fmt::Display for ShortDisplay {
-	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-		match &self.0 .0 .0 as &SourcePath {
-			SourcePath::Path(r) => {
-				write!(
-					f,
-					"{}",
-					r.file_name().expect("path is valid").to_string_lossy()
-				)
-			}
-			SourcePath::Custom(r) => write!(f, "{}", r),
-			SourcePath::Virtual(n) => write!(f, "{}", n),
-		}
 	}
 }