--- a/crates/jrsonnet-parser/src/lib.rs +++ b/crates/jrsonnet-parser/src/lib.rs @@ -11,7 +11,7 @@ mod source; mod unescape; pub use location::CodeLocation; -pub use source::{Source, SourcePath}; +pub use source::{Source, SourceDirectory, SourceFile, SourcePath, SourcePathT, SourceVirtual}; pub struct ParserSettings { pub file_name: Source, --- 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::() { + v + } else { + return false; + }; + let this = ::as_any(self) + .downcast_ref::() + .expect("restricted by impl"); + this == other + } + fn dyn_debug(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + ::fmt(self, fmt) + } + }; +} +macro_rules! any_ext { + ($T:ident) => { + impl Hash for dyn $T { + fn hash(&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); +impl SourcePath { + pub fn new(inner: impl SourcePathT) -> Self { + Self(Rc::new(inner)) + } + pub fn downcast_ref(&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(&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::() + .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, "") + } +} +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 { - 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 { @@ -100,21 +294,5 @@ } pub fn map_from_source_location(&self, line: usize, column: usize) -> Option { 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), - } } }