Skip to content

Commit

Permalink
Make path-based inputs generic of AsRef<Path> (#73)
Browse files Browse the repository at this point in the history
This simplifies calling code from outside the library in various places;
various components no longer need to be compelled or recollected into
the exact typing that the input requires, but instead can be used in
anything that can be treated as a path slice.
  • Loading branch information
jakelishman authored Jan 22, 2024
1 parent 2d8e34f commit 4d1bcb5
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 115 deletions.
9 changes: 5 additions & 4 deletions crates/oq3_semantics/examples/semdemo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ fn main() {
Some(Commands::SemanticString { file_name }) => {
let source = read_example_source(file_name);
let file_name = Some("giraffe");
let result = syntax_to_semantics::parse_source_string(source, file_name, None);
let result =
syntax_to_semantics::parse_source_string(source, file_name, None::<&[PathBuf]>);
if result.any_errors() {
result.print_errors();
}
Expand All @@ -91,7 +92,7 @@ fn main() {

#[allow(clippy::dbg_macro)]
Some(Commands::Semantic { file_name }) => {
let result = syntax_to_semantics::parse_source_file(file_name, None);
let result = syntax_to_semantics::parse_source_file(file_name, None::<&[PathBuf]>);
let have_errors = result.any_errors();
if have_errors {
println!("Found errors: {}", have_errors);
Expand All @@ -106,7 +107,7 @@ fn main() {
}

Some(Commands::SemanticPretty { file_name }) => {
let result = syntax_to_semantics::parse_source_file(file_name, None);
let result = syntax_to_semantics::parse_source_file(file_name, None::<&[PathBuf]>);
println!("Found errors: {}", result.any_errors());
result.print_errors();
result.program().print_asg_debug_pretty();
Expand All @@ -118,7 +119,7 @@ fn main() {
// context.program().print_asg_debug_pretty();
// }
Some(Commands::Parse { file_name }) => {
let parsed_source = oq3_source_file::parse_source_file(file_name, None);
let parsed_source = oq3_source_file::parse_source_file(file_name, None::<&[PathBuf]>);
let parse_tree = parsed_source.syntax_ast().tree();
println!(
"Found {} items",
Expand Down
24 changes: 16 additions & 8 deletions crates/oq3_semantics/src/syntax_to_semantics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

use std;
use std::mem::replace;
use std::path::PathBuf;
use std::path::Path;

use crate::asg;
use crate::types;
Expand Down Expand Up @@ -87,21 +87,29 @@ impl ParseResult<SourceString> {

/// Parse string containing source to semantic ASG.
/// Fake file name is used for printing diagnostics.
pub fn parse_source_string<T: ToString>(
pub fn parse_source_string<T, P>(
source: T,
fake_file_path: Option<&str>,
search_path_list: Option<&Vec<PathBuf>>,
) -> ParseResult<SourceString> {
search_path_list: Option<&[P]>,
) -> ParseResult<SourceString>
where
T: AsRef<str>,
P: AsRef<Path>,
{
let parsed_source =
oq3_source_file::parse_source_string(source, fake_file_path, search_path_list);
analyze_source(parsed_source)
}

/// Parse source file to semantic ASG
pub fn parse_source_file(
file_path: &PathBuf,
search_path_list: Option<&Vec<PathBuf>>,
) -> ParseResult<SourceFile> {
pub fn parse_source_file<T, P>(
file_path: T,
search_path_list: Option<&[P]>,
) -> ParseResult<SourceFile>
where
T: AsRef<Path>,
P: AsRef<Path>,
{
let parsed_source = oq3_source_file::parse_source_file(file_path, search_path_list);
analyze_source(parsed_source)
}
Expand Down
2 changes: 1 addition & 1 deletion crates/oq3_semantics/tests/from_string_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use oq3_semantics::syntax_to_semantics::parse_source_string;
use oq3_semantics::types::{ArrayDims, IsConst, Type};

fn parse_string(code: &str) -> (asg::Program, SemanticErrorList, SymbolTable) {
parse_source_string(code, None, None)
parse_source_string(code, None, None::<&[&std::path::Path]>)
.take_context()
.as_tuple()
}
Expand Down
27 changes: 16 additions & 11 deletions crates/oq3_source_file/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,35 @@ use std::path::{Path, PathBuf};

use crate::source_file::{
expand_path, parse_source_and_includes, range_to_span, read_source_file, ErrorTrait,
Normalizeable, SourceFile, SourceString,
SourceFile, SourceString,
};

/// Read source from `file_path` and parse to the syntactic AST.
/// Parse and store included files recursively.
pub fn parse_source_file(
file_path: &PathBuf,
search_path_list: Option<&Vec<PathBuf>>,
) -> SourceFile {
let full_path = expand_path(file_path, search_path_list).normalize();
pub fn parse_source_file<T, P>(file_path: T, search_path_list: Option<&[P]>) -> SourceFile
where
T: AsRef<Path>,
P: AsRef<Path>,
{
let full_path = expand_path(file_path, search_path_list);
let (syntax_ast, included) =
parse_source_and_includes(read_source_file(&full_path).as_str(), search_path_list);
SourceFile::new(full_path, syntax_ast, included)
}

/// Read source from `file_path` and parse to the syntactic AST.
/// Parse and store included files recursively.
pub fn parse_source_string<T: ToString>(
pub fn parse_source_string<T, P>(
source: T,
fake_file_path: Option<&str>,
search_path_list: Option<&Vec<PathBuf>>,
) -> SourceString {
let source = source.to_string();
let (syntax_ast, included) = parse_source_and_includes(source.as_str(), search_path_list);
search_path_list: Option<&[P]>,
) -> SourceString
where
T: AsRef<str>,
P: AsRef<Path>,
{
let source = source.as_ref();
let (syntax_ast, included) = parse_source_and_includes(source, search_path_list);
let fake_file_path = PathBuf::from(fake_file_path.unwrap_or("no file"));
SourceString::new(source, fake_file_path, syntax_ast, included)
}
Expand Down
128 changes: 37 additions & 91 deletions crates/oq3_source_file/src/source_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use oq3_syntax::ast as synast; // Syntactic AST
use oq3_syntax::Parse;
use oq3_syntax::TextRange;
use std::env;
use std::ffi::OsStr;
use std::fs;
use std::path::{Path, PathBuf};

Expand All @@ -14,9 +13,9 @@ use oq3_syntax::ast::HasModuleItem;

use crate::api::{inner_print_compiler_errors, parse_source_file, print_compiler_errors};

pub(crate) fn parse_source_and_includes(
pub(crate) fn parse_source_and_includes<P: AsRef<Path>>(
source: &str,
search_path_list: Option<&Vec<PathBuf>>,
search_path_list: Option<&[P]>,
) -> (ParsedSource, Vec<SourceFile>) {
let syntax_ast: ParsedSource = synast::SourceFile::parse(source);
let included = parse_included_files(&syntax_ast, search_path_list);
Expand All @@ -27,64 +26,6 @@ pub(crate) fn parse_source_and_includes(
// Knowledge of any file is not used by synast::SourceFile;
pub(crate) type ParsedSource = Parse<synast::SourceFile>;

/// This trait requires implementing `normalize` which returns an absolute path. `normalize`
/// essentially wraps `fs::canonicalize`. It exists so the consumer can initialize paths
/// with `PathBuf`, `&PathBuf`, `&str`, `&OsStr` etc.
/// The method `to_path_buf` which converts to `PathBuf` without `canonicalize` is also required.
pub trait Normalizeable {
fn normalize(&self) -> PathBuf;
fn to_path_buf(self) -> PathBuf;
}

impl Normalizeable for PathBuf {
fn to_path_buf(self) -> Self {
self
}

/// `normalize` essentially wraps `fs::canonicalize`. See `Normalizeable`.
fn normalize(&self) -> PathBuf {
match fs::canonicalize(self) {
Ok(file_path) => file_path,
Err(e) => panic!("Unable to find {}\n{e}", self.display()),
}
}
}

impl Normalizeable for &PathBuf {
fn to_path_buf(self) -> PathBuf {
self.clone()
}

fn normalize(&self) -> PathBuf {
match fs::canonicalize(self) {
Ok(file_path) => file_path,
Err(e) => panic!("Unable to find {}\n{e}", self.display()),
}
}
}

// FIXME: Can probably use From trait in order to minimize code supporting
// this trait, or perhaps eliminate it.
impl Normalizeable for &str {
fn to_path_buf(self) -> PathBuf {
PathBuf::from(self)
}

fn normalize(&self) -> PathBuf {
PathBuf::from(self).normalize()
}
}

impl Normalizeable for &OsStr {
fn to_path_buf(self) -> PathBuf {
PathBuf::from(&self)
}

fn normalize(&self) -> PathBuf {
PathBuf::from(self).normalize()
}
}

pub trait ErrorTrait {
fn message(&self) -> String;

Expand Down Expand Up @@ -149,13 +90,17 @@ impl SourceTrait for SourceFile {
}

impl SourceFile {
pub fn new<F: Normalizeable>(
pub fn new<F: AsRef<Path>>(
file_path: F,
syntax_ast: ParsedSource,
included: Vec<SourceFile>,
) -> SourceFile {
let file_path = match fs::canonicalize(file_path.as_ref()) {
Ok(file_path) => file_path,
Err(e) => panic!("Unable to find {:?}\n{:?}", file_path.as_ref(), e),
};
SourceFile {
file_path: file_path.normalize(),
file_path,
syntax_ast,
included,
}
Expand All @@ -167,32 +112,37 @@ impl SourceFile {
}

pub fn search_paths() -> Option<Vec<PathBuf>> {
env::var_os("QASM3_PATH").map(|paths| env::split_paths(&paths).collect::<Vec<_>>())
env::var_os("QASM3_PATH").map(|paths| env::split_paths(&paths).collect())
}

/// Expand path with search paths. Return input if expansion fails.
pub(crate) fn expand_path(file_path: &PathBuf, search_path_list: Option<&Vec<PathBuf>>) -> PathBuf {
pub(crate) fn expand_path<T: AsRef<Path>, P: AsRef<Path>>(
file_path: T,
search_path_list: Option<&[P]>,
) -> PathBuf {
let file_path = PathBuf::from(file_path.as_ref());
if file_path.is_absolute() {
return file_path.clone();
return file_path;
}
let mut maybe_full_path = file_path.clone();
let env_paths = search_paths();
// Prefer paths passed to this function over env variable.
let paths = if search_path_list.is_some() {
search_path_list
} else {
env_paths.as_ref()
let try_path = |dir: &Path| {
let full_path = dir.join(&file_path);
full_path.is_file().then_some(full_path)
};
if let Some(paths1) = paths {
for path in paths1 {
let fqpn = path.join(file_path);
if fqpn.is_file() {
maybe_full_path = fqpn;
break;

if let Some(paths) = search_path_list {
for path in paths {
if let Some(full_path) = try_path(path.as_ref()) {
return full_path;
}
}
} else if let Some(paths) = search_paths() {
for path in paths {
if let Some(full_path) = try_path(path.as_ref()) {
return full_path;
}
}
}
maybe_full_path
file_path
}

/// Read QASM3 source file, respecting env variable `QASM3_PATH` if set.
Expand All @@ -209,9 +159,9 @@ pub(crate) fn read_source_file(file_path: &Path) -> String {

// FIXME: prevent a file from including itself. Then there are two-file cycles, etc.
/// Recursively parse any files `include`d in the program `syntax_ast`.
pub(crate) fn parse_included_files(
pub(crate) fn parse_included_files<P: AsRef<Path>>(
syntax_ast: &ParsedSource,
search_path_list: Option<&Vec<PathBuf>>,
search_path_list: Option<&[P]>,
) -> Vec<SourceFile> {
syntax_ast
.tree()
Expand All @@ -220,10 +170,7 @@ pub(crate) fn parse_included_files(
synast::Stmt::Item(synast::Item::Include(include)) => {
let file: synast::FilePath = include.file().unwrap();
let file_path = file.to_string().unwrap();
Some(parse_source_file(
&PathBuf::from(file_path),
search_path_list,
))
Some(parse_source_file(file_path, search_path_list))
}
_ => None,
})
Expand Down Expand Up @@ -277,16 +224,15 @@ impl SourceTrait for SourceString {
}

impl SourceString {
pub fn new<T: ToString, F: Normalizeable>(
pub fn new<T: AsRef<str>, P: AsRef<Path>>(
source: T,
fake_file_path: F,
fake_file_path: P,
syntax_ast: ParsedSource,
included: Vec<SourceFile>,
) -> SourceString {
let fake_file_path_buf = fake_file_path.to_path_buf();
SourceString {
source: source.to_string(),
fake_file_path: fake_file_path_buf,
source: source.as_ref().to_owned(),
fake_file_path: fake_file_path.as_ref().to_owned(),
syntax_ast,
included,
}
Expand Down

0 comments on commit 4d1bcb5

Please sign in to comment.