Skip to content
Merged
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
6 changes: 3 additions & 3 deletions compose-cli/src/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,17 @@ pub fn file(args: FileArgs) -> Result<(), CliError> {

let warnings: Vec<_> = source.warnings().into_iter().map(|w| w.into()).collect();
if !warnings.is_empty() {
crate::print_diagnostics(&world, &[], &warnings).unwrap();
crate::print_diagnostics(&world, &[], &warnings).expect("failed to print diagnostics");
}

let Warned { value, warnings } = compose_eval::eval_source(&source, &mut vm);

if let Err(err) = value {
crate::print_diagnostics(&world, &err, &warnings).unwrap();
crate::print_diagnostics(&world, &err, &warnings).expect("failed to print diagnostics");
return Err(CliError::Execution);
}

crate::print_diagnostics(&world, &[], &warnings).unwrap();
crate::print_diagnostics(&world, &[], &warnings).expect("failed to print diagnostics");

Ok(())
}
4 changes: 2 additions & 2 deletions compose-cli/src/world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ impl SystemWorld {
}

// try to read from disk
let path = file_id.path().0.clone();
let text = std::fs::read_to_string(&path).map_err(|e| FileError::from_io(e, &path))?;
let path = file_id.path().as_path();
let text = std::fs::read_to_string(path).map_err(|e| FileError::from_io(e, path))?;
let source = Source::new(file_id, text);
sources.insert(source.id(), source.clone());
Ok(source)
Expand Down
4 changes: 1 addition & 3 deletions compose-error-codes-doc-tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ Documentation and doc-tests for the Compose error codes.
The purpose of this crate is to provide the compose error codes as a Rust module, so that they can be included in docs.rs
and the examples can be tested.

The source of the error codes is in the [`compose-error-codes`] crate. The description of the error code is transformed
The source of the error codes is in the `compose-error-codes` crate. The description of the error code is transformed
via `compose_doc::transform_markdown` into documented items with doc-tests.
*/

include!(concat!(env!("OUT_DIR"), "/Error_Codes"));

pub use compose_error_codes;
4 changes: 2 additions & 2 deletions compose-syntax/src/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ impl VirtualPath {
self.0.display().to_string()
}

pub fn to_path_buf(&self) -> PathBuf {
self.0.clone()
pub fn as_path(&self) -> &PathBuf {
&self.0
}
}

Expand Down
3 changes: 3 additions & 0 deletions compose-syntax/src/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ use crate::{parse, Span, SyntaxError, SyntaxNode};
use std::path::PathBuf;
use std::sync::Arc;

/// A Compose source file.
///
/// Uses reference counting, so it is quite inexpensive to clone
#[derive(Clone, Debug)]
pub struct Source(Arc<Repr>);

Expand Down
237 changes: 235 additions & 2 deletions compose/src/embedding/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,238 @@
/*!
# Embedding Compose

Coming soon!
*/
To learn about embedding Compose in your application, we'll create a CLI similar to the one
provided in compose_cli.

It will support executing a Compose source file, loading other source files, reading from stdin, and printing to stdout.

## Creating a World

We want to create a [`World`](crate::World) implementation backed by the file system working from the current directory.

```rust
use std::collections::HashMap;
use std::fs;
use std::io::{Read, Write};
use std::path::{PathBuf, Path};
use std::sync::Mutex;
use compose::library::diag::{FileError, FileResult};
use compose::library::{Library, World};
use compose::syntax::{FileId, Source};

struct SystemWorld {
entrypoint: FileId,
// Since the world trait works through a shared reference, we need to add a mutex
// to allow mutation through a shared reference.
sources: Mutex<HashMap<FileId, Source>>,
library: Library,
// Keep track of the root directory so that we can resolve relative paths.
root: PathBuf,
}

impl SystemWorld {
fn from_file(path: impl AsRef<Path>) -> FileResult<Self> {
let path = path.as_ref();
let root = path.parent().unwrap().to_path_buf()
.canonicalize()
.map_err(|e| FileError::from_io(e, path))?;

let entrypoint = FileId::new(path);
let text = fs::read_to_string(path)
.map_err(|e| FileError::from_io(e, path))?;
let source = Source::new(entrypoint, text);

let mut sources = HashMap::new();
sources.insert(source.id(), source);

Ok(Self {
entrypoint,
sources: Mutex::new(sources),
library: Library::default(),
root,
})
}

fn add_source(&self, source: Source) {
self.sources.lock().unwrap().insert(source.id(), source);
}

fn get_or_read_from_disk(&self, file_id: FileId) -> FileResult<Source> {
let mut sources = self.sources.lock().unwrap();
if let Some(source) = sources.get(&file_id) {
// Source uses reference counting and is inexpensive to clone, so we can return it directly.
return Ok(source.clone());
}

let path = file_id.path();

let file_contents = fs::read_to_string(path.as_path())
.map_err(|e| FileError::from_io(e, path.as_path()))?;

let source = Source::new(file_id, file_contents);

sources.insert(source.id(), source.clone());

Ok(source)
}
}

impl World for SystemWorld {
fn entry_point(&self) -> FileId {
self.entrypoint
}

fn source(&self, file_id: FileId) -> FileResult<Source> {
self.get_or_read_from_disk(file_id)
}

fn library(&self) -> &Library {
&self.library
}

fn write(&self, f: &mut dyn FnMut(&mut dyn Write) -> std::io::Result<()>) -> std::io::Result<()> {
f(&mut std::io::stdout())
}

fn read(&self, f: &mut dyn FnMut(&mut dyn Read) -> std::io::Result<()>) -> std::io::Result<()> {
f(&mut std::io::stdin())
}

fn name(&self, id: FileId) -> String {
// return the path relative to the root, if within the root.
id.path()
.0
.strip_prefix(&self.root)
.unwrap_or(&id.path().0)
.display()
.to_string()
}
}
```

That was most of the work! All that's left to do is reading the
file path from the command line arguments, creating a world, creating a vm, and evaluating the file.

```rust
# use std::collections::HashMap;
# use std::fs;
# use std::io::{Read, Write};
use std::path::PathBuf;
# use std::path::Path;
# use std::sync::Mutex;
# use compose::library::diag::{FileError, FileResult};
# use compose::library::{Library, World};
# use compose::syntax::{FileId, Source};
#
# struct SystemWorld {
# entrypoint: FileId,
# // Since the world trait works through a shared reference, we need to add a mutex
# // to allow mutation through a shared reference.
# sources: Mutex<HashMap<FileId, Source>>,
# library: Library,
# // Keep track of the root directory so that we can resolve relative paths.
# root: PathBuf,
# }
#
# impl SystemWorld {
# fn from_file(path: impl AsRef<Path>) -> FileResult<Self> {
# let path = path.as_ref();
# let root = path.parent().unwrap().to_path_buf()
# .canonicalize()
# .map_err(|e| FileError::from_io(e, &path))?;
#
# let entrypoint = FileId::new(path);
# let text = fs::read_to_string(path)
# .map_err(|e| FileError::from_io(e, &path))?;
# let source = Source::new(entrypoint, text);
#
# let mut sources = HashMap::new();
# sources.insert(source.id(), source);
#
# Ok(Self {
# entrypoint,
# sources: Mutex::new(sources),
# library: Library::default(),
# root,
# })
# }
#
# fn add_source(&self, source: Source) {
# self.sources.lock().unwrap().insert(source.id(), source);
# }
#
# fn get_or_read_from_disk(&self, file_id: FileId) -> FileResult<Source> {
# let mut sources = self.sources.lock().unwrap();
# if let Some(source) = sources.get(&file_id) {
# // Source uses reference counting and is inexpensive to clone, so we can return it directly.
# return Ok(source.clone());
# }
#
# let path = file_id.path();
#
# let file_contents = fs::read_to_string(path.as_path())
# .map_err(|e| FileError::from_io(e, path.as_path()))?;
#
# let source = Source::new(file_id, file_contents);
#
# sources.insert(source.id(), source.clone());
#
# Ok(source)
# }
# }
#
# impl World for SystemWorld {
# fn entry_point(&self) -> FileId {
# self.entrypoint
# }
#
# fn source(&self, file_id: FileId) -> FileResult<Source> {
# self.get_or_read_from_disk(file_id)
# }
#
# fn library(&self) -> &Library {
# &self.library
# }
#
# fn write(&self, f: &mut dyn FnMut(&mut dyn Write) -> std::io::Result<()>) -> std::io::Result<()> {
# f(&mut std::io::stdout())
# }
#
# fn read(&self, f: &mut dyn FnMut(&mut dyn Read) -> std::io::Result<()>) -> std::io::Result<()> {
# f(&mut std::io::stdin())
# }
#
# fn name(&self, id: FileId) -> String {
# // return the path relative to the root, if within the root.
# id.path()
# .0
# .strip_prefix(&self.root)
# .unwrap_or(&id.path().0)
# .display()
# .to_string()
# }
# }


use std::process::exit;
use compose_eval::{eval, Machine};
use compose_library::diag::print_diagnostics;

fn file(file_path: impl AsRef<Path>) {
let path = file_path.as_ref();
let world = SystemWorld::from_file(&path).expect("Failed to load entrypoint file");
let mut vm = Machine::new(&world);

let result = eval(&mut vm);

if let Err(err) = result.value {
print_diagnostics(&world, &err, &result.warnings, false).expect("failed to print diagnostics");
exit(1);
}

// print warnings
print_diagnostics(&world, &[], &result.warnings, false).expect("failed to print diagnostics");
}
```
*/

4 changes: 2 additions & 2 deletions compose/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ impl World for ExampleWorld {
if file_id == self.main.id() {
Ok(self.main.clone())
} else {
Err(FileError::NotFound(file_id.path().to_path_buf()))
Err(FileError::NotFound(file_id.path().as_path().clone()))
}
}

Expand Down Expand Up @@ -166,7 +166,7 @@ use std::process::exit;
# if file_id == self.main.id() {
# Ok(self.main.clone())
# } else {
# Err(FileError::NotFound(file_id.path().to_path_buf()))
# Err(FileError::NotFound(file_id.path().as_path().clone()))
# }
# }
#
Expand Down