Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ keywords = ["ara", "file-source", "source-map"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
rustc-hash = { version = "1.1.0" }
28 changes: 28 additions & 0 deletions src/hash.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use std::hash::Hasher;

pub trait ContentHasher: Send + Sync {
fn hash(&self, content: &str) -> u64;
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct FxHasher;

impl FxHasher {
pub fn new() -> Self {
Self
}
}

impl Default for FxHasher {
fn default() -> Self {
Self::new()
}
}

impl ContentHasher for FxHasher {
fn hash(&self, content: &str) -> u64 {
let mut hasher = rustc_hash::FxHasher::default();
hasher.write(content.as_bytes());
hasher.finish()
}
}
7 changes: 4 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::error::Error;
use crate::source::Source;

pub mod error;
pub mod hash;
pub mod loader;
pub mod source;

Expand Down Expand Up @@ -62,13 +63,13 @@ mod tests {

map.add(Source::new(
SourceKind::Script,
"/Documents/Project",
"foo.ara",
"function foo(): void {}",
));
map.add(Source::new(
SourceKind::Script,
"/Documents/Project",
"bar.ara",
"function bar(): void {}",
));

assert_eq!(map.get(1).unwrap().origin, Some("foo.ara".to_string()));
Expand All @@ -89,8 +90,8 @@ mod tests {

other.add(Source::new(
SourceKind::Script,
"/Documents/Project",
"baz.ara",
"function baz(): void {}",
));

map.merge(&mut other);
Expand Down
4 changes: 2 additions & 2 deletions src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,18 +109,18 @@ impl SourceLoader for FileSourceLoader {
file.to_path_buf()
};

let content = std::fs::read_to_string(&file)?;
let origin = file
.strip_prefix(&self.root)
.map(|path| path.to_string_lossy())
.unwrap();

let kind = if origin.ends_with(ARA_DEFINTION_EXTENSION) {
SourceKind::Definition
} else {
SourceKind::Script
};

Ok(SourceMap::new(vec![Source::new(kind, origin, content)]))
Ok(SourceMap::new(vec![Source::new(kind, &self.root, origin)]))
}
}

Expand Down
118 changes: 84 additions & 34 deletions src/source.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
use std::fs;
use std::io::BufReader;
use std::io::Read;
use std::path::PathBuf;
use std::sync::Arc;

use crate::hash::ContentHasher;
use crate::hash::FxHasher;

pub const DEFAULT_NAME: &str = "<unknown>";

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum SourceKind {
/// A definition is a piece of code that is not executed, but can be used
/// to define foriegn symbols ( e.g from PHP ).
/// to define foreign symbols ( e.g from PHP ).
Definition,

/// A script is a piece of code that is executed.
Expand All @@ -13,8 +22,10 @@ pub enum SourceKind {
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Source {
pub kind: SourceKind,
pub root: Option<PathBuf>,
pub origin: Option<String>,
pub content: String,
pub content: Option<Arc<String>>,
hasher: FxHasher,
}

/// A source.
Expand All @@ -27,40 +38,24 @@ pub struct Source {
/// use ara_source::source::Source;
/// use ara_source::source::SourceKind;
///
/// let source = Source::new(SourceKind::Script, "main.ara", "function main(): void {}");
/// let source = Source::new(SourceKind::Script, "/Documents/Project", "src/main.ara");
///
/// assert_eq!(source.kind, SourceKind::Script);
/// assert_eq!(source.origin, Some("main.ara".to_string()));
/// assert_eq!(source.content, "function main(): void {}");
/// assert_eq!(source.origin, Some("src/main.ara".to_string()));
/// assert_eq!(source.root, Some("/Documents/Project".into()));
/// assert_eq!(source.content, None);
///
/// assert_eq!(source.name(), "main.ara");
/// assert_eq!(source.name(), "src/main.ara");
/// ```
impl Source {
/// Create a new source with the given content.
///
/// Example:
///
/// ```rust
/// use ara_source::source::Source;
/// use ara_source::source::SourceKind;
///
/// let source = Source::inline(SourceKind::Definition, "function main(): void {}");
///
/// assert_eq!(source.kind, SourceKind::Definition);
/// assert_eq!(source.origin, None);
/// assert_eq!(source.content, "function main(): void {}");
///
/// assert_eq!(source.name(), "<unknown>");
/// ```
pub fn new<O: Into<String>, C: Into<String>>(
kind: SourceKind,
origin: O,
content: C,
) -> Source {
/// Create a new source with the given origin.
pub fn new<O: Into<String>, R: Into<PathBuf>>(kind: SourceKind, root: R, origin: O) -> Source {
Source {
kind,
root: Some(root.into()),
origin: Some(origin.into()),
content: content.into(),
content: None,
hasher: FxHasher::new(),
}
}

Expand All @@ -75,14 +70,19 @@ impl Source {
/// let source = Source::inline(SourceKind::Definition, "function main(): void {}");
///
/// assert_eq!(source.kind, SourceKind::Definition);
/// assert_eq!(source.root, None);
/// assert_eq!(source.origin, None);
/// assert_eq!(source.content, "function main(): void {}");
/// assert_eq!(source.content.as_ref().unwrap().as_str(), "function main(): void {}");
///
/// assert_eq!(source.name(), "<unknown>");
/// ```
pub fn inline<C: Into<String>>(kind: SourceKind, content: C) -> Source {
Source {
kind,
root: None,
origin: None,
content: content.into(),
content: Some(Arc::new(content.into())),
hasher: FxHasher::new(),
}
}

Expand All @@ -97,16 +97,66 @@ impl Source {
/// use ara_source::source::Source;
/// use ara_source::source::SourceKind;
///
/// let source = Source::new(SourceKind::Definition, "main.ara", "function main(): void {}");
/// assert_eq!(source.name(), "main.ara");
/// let source = Source::new(SourceKind::Definition, "/Documents/Project", "src/Foo/main.ara");
/// assert_eq!(source.name(), "src/Foo/main.ara");
///
/// let source = Source::inline(SourceKind::Definition, "function main(): void {}");
/// assert_eq!(source.name(), "<unknown>");
/// ```
pub fn name(&self) -> &str {
match self.origin {
Some(ref origin) => origin,
match &self.origin {
Some(origin) => origin,
None => DEFAULT_NAME,
}
}

/// Returns the complete path of the source.
///
/// Example:
///
/// ```rust
/// use ara_source::source::Source;
/// use ara_source::source::SourceKind;
///
/// let source = Source::new(SourceKind::Definition, "/Documents/Project", "src/Foo/main.ara");
/// assert_eq!(source.source_path(), Some("/Documents/Project/src/Foo/main.ara".into()));
/// ```
pub fn source_path(&self) -> Option<PathBuf> {
self.root
.as_ref()
.map(|root| root.join(self.origin.as_ref().unwrap()))
}

/// Returns the content of the source.
/// If the source has no content, the content is read from the file system.
pub fn content(&mut self) -> std::io::Result<Arc<String>> {
if let Some(content) = self.content.as_ref() {
return Ok(content.clone());
}

let path = self
.source_path()
.expect("Both root and origin must be present in order to read the source content");

let mut reader = BufReader::new(fs::File::open(path)?);
let mut file_contents = String::new();
reader.read_to_string(&mut file_contents)?;

let content_reference = Arc::new(file_contents);
self.content = Some(content_reference.clone());

Ok(content_reference)
}

/// Returns the hash of the source content.
pub fn hash(&mut self) -> std::io::Result<u64> {
let content = self.content()?;

Ok(self.hasher.hash(&content))
}

/// Dispose the content of the source.
pub fn dispose_content(&mut self) {
self.content = None;
}
}