Skip to content

Commit

Permalink
refactor: remove filesystem dependencies from oal-model
Browse files Browse the repository at this point in the history
  • Loading branch information
ebastien committed Nov 1, 2023
1 parent 98c5523 commit 7306f05
Show file tree
Hide file tree
Showing 11 changed files with 155 additions and 72 deletions.
1 change: 1 addition & 0 deletions oal-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ lsp-server = "0.7"
lsp-types = "0.94"
url = "2.4"
crossbeam-channel = "0.5"
thiserror = "1.0"
4 changes: 1 addition & 3 deletions oal-client/src/bin/oal-cli.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use oal_client::cli::Processor;
use oal_client::{config, DefaultFileSystem, FileSystem};
use std::path::PathBuf;

fn main() -> anyhow::Result<()> {
let proc = Processor::new();
Expand All @@ -17,8 +16,7 @@ fn main() -> anyhow::Result<()> {
let mut builder = oal_openapi::Builder::new(spec);

if let Some(ref loc) = base {
let path: PathBuf = loc.try_into()?;
let file = std::fs::File::open(path)?;
let file = DefaultFileSystem.open_file(loc)?;
let base = serde_yaml::from_reader(file)?;
builder = builder.with_base(base);
}
Expand Down
16 changes: 11 additions & 5 deletions oal-client/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ use oal_compiler::tree::Tree;
use oal_model::locator::Locator;
use oal_model::span::Span;
use std::fmt::{Display, Formatter};
use std::io;

#[derive(Default)]
/// The CLI compilation processor.
Expand All @@ -21,7 +20,7 @@ impl Processor {

impl Processor {
/// Reports an error.
pub fn report<M: ToString>(&self, span: Span, msg: M) -> io::Result<()> {
pub fn report<M: ToString>(&self, span: Span, msg: M) -> anyhow::Result<()> {
let mut colors = ColorGenerator::new();
let color = colors.next();
let loc = span.locator().clone();
Expand All @@ -32,7 +31,8 @@ impl Processor {
let s = CharSpan::from(&input, span);
builder.add_label(Label::new(s).with_color(color))
}
builder.finish().eprint((loc, Source::from(input)))
builder.finish().eprint((loc, Source::from(input)))?;
Ok(())
}

pub fn load(&self, main: &Locator) -> anyhow::Result<ModuleSet> {
Expand Down Expand Up @@ -63,9 +63,15 @@ impl Processor {
struct ProcLoader<'a>(&'a Processor);

impl<'a> Loader<anyhow::Error> for ProcLoader<'a> {
/// Returns true if the given locator points to a valid source file.
fn is_valid(&mut self, loc: &Locator) -> bool {
DefaultFileSystem.is_valid(loc)
}

/// Loads a source file.
fn load(&mut self, loc: &Locator) -> std::io::Result<String> {
DefaultFileSystem.read_file(loc)
fn load(&mut self, loc: &Locator) -> anyhow::Result<String> {
let code = DefaultFileSystem.read_file(loc)?;
Ok(code)
}

/// Parses a source file into a concrete syntax tree.
Expand Down
11 changes: 9 additions & 2 deletions oal-client/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use clap::Parser as ClapParser;
use oal_model::locator::Locator;
use serde::Deserialize;
use std::path::{Path, PathBuf};
use url::Url;

/// Compiles a program into an OpenAPI description in YAML.
#[derive(ClapParser, Debug)]
Expand Down Expand Up @@ -42,20 +43,26 @@ pub struct Config {
root: Locator,
}

fn path_locator(p: &Path) -> anyhow::Result<Locator> {
let path = p.canonicalize()?;
let url = Url::from_file_path(path).expect("absolute path should convert to URL");
Ok(Locator::from(url))
}

impl Config {
pub fn new(cfg: Option<&Path>) -> anyhow::Result<Self> {
let args: Args = Args::parse();

let config = cfg.or(args.config.as_deref());

let (root, file) = if let Some(path) = config {
let root = Locator::try_from(path)?;
let root = path_locator(path)?;
let cfg = std::fs::read_to_string(path)?;
let file = toml::from_str::<File>(&cfg)?;
(root, file)
} else {
let cwd = std::env::current_dir()?;
let loc = Locator::try_from(cwd.as_path())?;
let loc = path_locator(cwd.as_path())?;
let root = loc.as_base();
let file = File::default();
(root, file)
Expand Down
55 changes: 43 additions & 12 deletions oal-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,56 @@ use oal_model::locator::Locator;
use std::io;
use std::path::PathBuf;

#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("invalid path: {0}")]
InvalidPath(String),
#[error("input/output error")]
IO(#[from] std::io::Error),
}

pub trait FileSystem {
fn read_file(&self, loc: &Locator) -> io::Result<String>;
fn write_file(&self, loc: &Locator, buf: String) -> io::Result<()>;
fn is_valid(&self, loc: &Locator) -> bool;
fn open_file(&self, loc: &Locator) -> Result<Box<dyn io::Read>, Error>;
fn read_file(&self, loc: &Locator) -> Result<String, Error>;
fn write_file(&self, loc: &Locator, buf: String) -> Result<(), Error>;
}

pub struct DefaultFileSystem;

fn locator_path(loc: &Locator) -> Result<PathBuf, Error> {
let url = loc.url();
if url.scheme() == "file" {
if let Ok(p) = url.to_file_path() {
return Ok(p);
}
}
Err(Error::InvalidPath(url.as_str().to_owned()))
}

impl FileSystem for DefaultFileSystem {
fn read_file(&self, loc: &Locator) -> io::Result<String> {
let path: PathBuf = loc
.try_into()
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
std::fs::read_to_string(path)
fn is_valid(&self, loc: &Locator) -> bool {
match locator_path(loc) {
Ok(p) => p.exists(),
Err(_) => false,
}
}

fn open_file(&self, loc: &Locator) -> Result<Box<dyn io::Read>, Error> {
let path = locator_path(loc)?;
let file = std::fs::File::open(path)?;
Ok(Box::new(file))
}

fn read_file(&self, loc: &Locator) -> Result<String, Error> {
let path = locator_path(loc)?;
let string = std::fs::read_to_string(path)?;
Ok(string)
}

fn write_file(&self, loc: &Locator, buf: String) -> io::Result<()> {
let path: PathBuf = loc
.try_into()
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
std::fs::write(path, buf)
fn write_file(&self, loc: &Locator, buf: String) -> Result<(), Error> {
let path = locator_path(loc)?;
std::fs::write(path, buf)?;
Ok(())
}
}
10 changes: 7 additions & 3 deletions oal-client/src/lsp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ use oal_compiler::tree::Tree;
use oal_model::{locator::Locator, span::Span};
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::io;
use unicode::{position_to_utf8, utf8_range_to_position};

/// A folder in the workspace.
Expand Down Expand Up @@ -210,7 +209,7 @@ impl Workspace {
}

/// Reads a file from the workspace.
fn read_file(&mut self, loc: &Locator) -> io::Result<String> {
fn read_file(&mut self, loc: &Locator) -> anyhow::Result<String> {
match self.docs.entry(loc.clone()) {
Entry::Occupied(e) => Ok(e.get().clone()),
Entry::Vacant(e) => {
Expand All @@ -225,8 +224,13 @@ impl Workspace {
struct WorkspaceLoader<'a>(&'a mut Workspace);

impl<'a> Loader<anyhow::Error> for WorkspaceLoader<'a> {
/// Returns true if the given locator points to a valid source file.
fn is_valid(&mut self, loc: &Locator) -> bool {
DefaultFileSystem.is_valid(loc)
}

/// Loads a source file.
fn load(&mut self, loc: &Locator) -> std::io::Result<String> {
fn load(&mut self, loc: &Locator) -> anyhow::Result<String> {
self.0.read_file(loc)
}

Expand Down
5 changes: 3 additions & 2 deletions oal-compiler/src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use oal_model::locator::Locator;
use oal_model::span::Span;
use std::fmt::{Debug, Display, Formatter};

Expand All @@ -19,8 +20,8 @@ pub enum Kind {
InvalidLiteral,
#[error("invalid identifier")]
InvalidIdentifier,
#[error("io error: {0}")]
IoError(#[from] std::io::Error),
#[error("invalid module: {0}")]
InvalidModule(Locator),
}

#[derive(Debug)]
Expand Down
27 changes: 17 additions & 10 deletions oal-compiler/src/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,10 @@ impl ModuleSet {
}

pub trait Loader<E: From<Error>> {
/// Returns true if the given locator points to a valid source file.
fn is_valid(&mut self, loc: &Locator) -> bool;
/// Loads a source file.
fn load(&mut self, loc: &Locator) -> std::io::Result<String>;
fn load(&mut self, loc: &Locator) -> std::result::Result<String, E>;
/// Parses a source file into a concrete syntax tree.
fn parse(&mut self, loc: Locator, input: String) -> std::result::Result<Tree, E>;
/// Compiles a module.
Expand All @@ -73,7 +75,7 @@ where
let mut graph = Graph::new();
let mut queue = Vec::new();

let input = loader.load(base).map_err(Error::from)?;
let input = loader.load(base)?;
let main = loader.parse(base.clone(), input)?;
let mut mods = ModuleSet::new(main);

Expand All @@ -88,20 +90,25 @@ where
let mut imports = Vec::new();
let prog = Program::cast(module.root()).expect("expected a program");
for import in prog.imports() {
let s = import.node().span();
let i = loc
let span = import.node().span();
let target = loc
.join(import.module())
.map_err(|err| Error::from(err).at(s.clone()))?;
imports.push((i, s));
.map_err(|err| Error::from(err).at(span.clone()))?;
if !loader.is_valid(&target) {
return Err(
Error::new(Kind::InvalidModule(target), "cannot load import")
.at(span)
.into(),
);
}
imports.push(target);
}

for (import, span) in imports {
for import in imports {
if let Some(m) = deps.get(&import) {
graph.add_edge(*m, n, ());
} else {
let input = loader
.load(&import)
.map_err(|err| Error::from(err).at(span))?;
let input = loader.load(&import)?;
let module = loader.parse(import.clone(), input)?;
mods.insert(module);

Expand Down
63 changes: 59 additions & 4 deletions oal-compiler/src/module_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ struct ContextCycle {
}

impl Loader<anyhow::Error> for ContextCycle {
fn load(&mut self, loc: &Locator) -> std::io::Result<String> {
fn is_valid(&mut self, loc: &Locator) -> bool {
let s = loc.url().as_str();
s == "file:///module.oal" || s == "file:///base.oal"
}

fn load(&mut self, loc: &Locator) -> anyhow::Result<String> {
let code = if *loc == self.base {
r#"use "module.oal";"#
} else if *loc == self.module {
Expand Down Expand Up @@ -62,7 +67,12 @@ struct ContextSort {
}

impl Loader<anyhow::Error> for ContextSort {
fn load(&mut self, loc: &Locator) -> std::io::Result<String> {
fn is_valid(&mut self, loc: &Locator) -> bool {
let s = loc.url().as_str();
s == "file:///module1.oal" || s == "file:///module2.oal"
}

fn load(&mut self, loc: &Locator) -> anyhow::Result<String> {
let code = if *loc == self.base {
r#"
use "module2.oal" as mod;
Expand Down Expand Up @@ -114,8 +124,53 @@ fn module_sort() -> anyhow::Result<()> {

assert_eq!(order.len(), 3, "expected 3 compilation units");
assert_eq!(order[0], module1, "expect module1 to be compiled first");
assert_eq!(order[1], module2, "expect module1 to be compiled first");
assert_eq!(order[2], base, "expect module1 to be compiled first");
assert_eq!(order[1], module2, "expect module2 to be compiled second");
assert_eq!(order[2], base, "expect base to be compiled last");

Ok(())
}

struct ContextInvalid;

impl Loader<anyhow::Error> for ContextInvalid {
fn is_valid(&mut self, loc: &Locator) -> bool {
assert_eq!(loc.url().as_str(), "file:///invalid.oal");
false
}

fn load(&mut self, _loc: &Locator) -> anyhow::Result<String> {
let code = r#"
use "invalid.oal";
"#;
Ok(code.to_owned())
}

fn parse(&mut self, loc: Locator, input: String) -> anyhow::Result<Tree> {
let (tree, errs) = oal_syntax::parse(loc, input);
assert!(errs.is_empty());
let tree = tree.expect("parsing failed");
Ok(tree)
}

fn compile(&mut self, _mods: &ModuleSet, _loc: &Locator) -> anyhow::Result<()> {
Ok(())
}
}

#[test]
fn module_invalid() -> anyhow::Result<()> {
let base = Locator::try_from("file:base.oal")?;

let mut ctx = ContextInvalid;

let err = load(&mut ctx, &base).expect_err("expected an error");

assert!(matches!(
err.downcast_ref::<Error>()
.expect("expected compiler error")
.kind,
Kind::InvalidModule(_)
));

Ok(())
}
Loading

0 comments on commit 7306f05

Please sign in to comment.