Import and export improved

- Import paths are now vname and not sym
- Imports and exports accept multiple space-delimited operators in []

as a result, we can now reliably import and export the operator *

- error reporting ergonomics
This commit is contained in:
2023-08-18 21:10:29 +01:00
parent 3fdabc29da
commit 9186bce956
33 changed files with 269 additions and 228 deletions

View File

@@ -1,9 +1,7 @@
use std::rc::Rc; use std::rc::Rc;
use super::{ErrorPosition, ProjectError}; use super::ProjectError;
use crate::representations::location::Location; use crate::representations::location::Location;
use crate::utils::iter::box_once;
use crate::utils::BoxedIter;
use crate::{Interner, VName}; use crate::{Interner, VName};
/// Error produced for the statement `import *` /// Error produced for the statement `import *`
@@ -22,13 +20,7 @@ impl ProjectError for ImportAll {
format!("{} imports *", i.extern_all(&self.offender_mod).join("::")) format!("{} imports *", i.extern_all(&self.offender_mod).join("::"))
} }
fn positions(&self, i: &Interner) -> BoxedIter<ErrorPosition> { fn one_position(&self, _i: &Interner) -> Location {
box_once(ErrorPosition { Location::File(self.offender_file.clone())
location: Location::File(self.offender_file.clone()),
message: Some(format!(
"{} imports *",
i.extern_all(&self.offender_mod).join("::")
)),
})
} }
} }

View File

@@ -1,11 +1,11 @@
use super::{ErrorPosition, ProjectError}; use std::rc::Rc;
use super::ProjectError;
use crate::representations::project::ProjectModule; use crate::representations::project::ProjectModule;
#[allow(unused)] // For doc #[allow(unused)] // For doc
use crate::tree::Module; use crate::tree::Module;
use crate::tree::WalkError; use crate::tree::WalkError;
use crate::utils::iter::box_once; use crate::{Interner, Location, NameLike, Tok, VName};
use crate::utils::BoxedIter;
use crate::{Interner, NameLike, Tok, VName};
/// Error produced when an import refers to a nonexistent module /// Error produced when an import refers to a nonexistent module
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
@@ -64,7 +64,7 @@ impl ProjectError for NotFound {
i.extern_all(&self.file).join("/"), i.extern_all(&self.file).join("/"),
) )
} }
fn positions(&self, i: &Interner) -> BoxedIter<ErrorPosition> { fn one_position(&self, i: &Interner) -> crate::Location {
box_once(ErrorPosition::just_file(i.extern_all(&self.file))) Location::File(Rc::new(i.extern_all(&self.file)))
} }
} }

View File

@@ -2,6 +2,7 @@ use std::rc::Rc;
use crate::interner::InternedDisplay; use crate::interner::InternedDisplay;
use crate::representations::location::Location; use crate::representations::location::Location;
use crate::utils::iter::box_once;
use crate::utils::BoxedIter; use crate::utils::BoxedIter;
use crate::Interner; use crate::Interner;
@@ -14,13 +15,6 @@ pub struct ErrorPosition {
pub message: Option<String>, pub message: Option<String>,
} }
impl ErrorPosition {
/// An error position referring to an entire file with no comment
pub fn just_file(file: Vec<String>) -> Self {
Self { message: None, location: Location::File(Rc::new(file)) }
}
}
/// Errors addressed to the developer which are to be resolved with /// Errors addressed to the developer which are to be resolved with
/// code changes /// code changes
pub trait ProjectError { pub trait ProjectError {
@@ -30,8 +24,16 @@ pub trait ProjectError {
fn message(&self, _i: &Interner) -> String { fn message(&self, _i: &Interner) -> String {
self.description().to_string() self.description().to_string()
} }
/// Code positions relevant to this error /// Code positions relevant to this error. If you don't implement this, you
fn positions(&self, i: &Interner) -> BoxedIter<ErrorPosition>; /// must implement [ProjectError::one_position]
fn positions(&self, i: &Interner) -> BoxedIter<ErrorPosition> {
box_once(ErrorPosition { location: self.one_position(i), message: None })
}
/// Short way to provide a single location. If you don't implement this, you
/// must implement [ProjectError::positions]
fn one_position(&self, _i: &Interner) -> Location {
unimplemented!()
}
/// Convert the error into an `Rc<dyn ProjectError>` to be able to /// Convert the error into an `Rc<dyn ProjectError>` to be able to
/// handle various errors together /// handle various errors together
fn rc(self) -> Rc<dyn ProjectError> fn rc(self) -> Rc<dyn ProjectError>

View File

@@ -1,9 +1,7 @@
use std::rc::Rc; use std::rc::Rc;
use super::{ErrorPosition, ProjectError}; use super::ProjectError;
use crate::representations::location::Location; use crate::representations::location::Location;
use crate::utils::iter::box_once;
use crate::utils::BoxedIter;
use crate::{Interner, VName}; use crate::{Interner, VName};
/// Error produced when an import path starts with more `super` segments /// Error produced when an import path starts with more `super` segments
@@ -30,14 +28,7 @@ impl ProjectError for TooManySupers {
) )
} }
fn positions(&self, i: &Interner) -> BoxedIter<ErrorPosition> { fn one_position(&self, i: &Interner) -> Location {
box_once(ErrorPosition { Location::File(Rc::new(i.extern_all(&self.offender_file)))
location: Location::File(Rc::new(i.extern_all(&self.offender_file))),
message: Some(format!(
"path {} in {} contains too many `super` steps.",
i.extern_all(&self.path).join("::"),
i.extern_all(&self.offender_mod).join("::")
)),
})
} }
} }

View File

@@ -1,7 +1,7 @@
use super::{ErrorPosition, ProjectError}; use std::rc::Rc;
use crate::utils::iter::box_once;
use crate::utils::BoxedIter; use super::ProjectError;
use crate::{Interner, VName}; use crate::{Interner, Location, VName};
/// Produced when a stage that deals specifically with code encounters /// Produced when a stage that deals specifically with code encounters
/// a path that refers to a directory /// a path that refers to a directory
@@ -15,9 +15,10 @@ impl ProjectError for UnexpectedDirectory {
"A stage that deals specifically with code encountered a path that refers \ "A stage that deals specifically with code encountered a path that refers \
to a directory" to a directory"
} }
fn positions(&self, i: &Interner) -> BoxedIter<ErrorPosition> { fn one_position(&self, i: &Interner) -> crate::Location {
box_once(ErrorPosition::just_file(i.extern_all(&self.path))) Location::File(Rc::new(i.extern_all(&self.path)))
} }
fn message(&self, i: &Interner) -> String { fn message(&self, i: &Interner) -> String {
format!( format!(
"{} was expected to be a file but a directory was found", "{} was expected to be a file but a directory was found",

View File

@@ -1,9 +1,7 @@
use std::rc::Rc; use std::rc::Rc;
use super::project_error::{ErrorPosition, ProjectError}; use super::project_error::ProjectError;
use crate::representations::location::Location; use crate::representations::location::Location;
use crate::utils::iter::box_once;
use crate::utils::BoxedIter;
use crate::{Interner, VName}; use crate::{Interner, VName};
/// Multiple occurences of the same namespace with different visibility /// Multiple occurences of the same namespace with different visibility
@@ -18,13 +16,13 @@ impl ProjectError for VisibilityMismatch {
fn description(&self) -> &str { fn description(&self) -> &str {
"Some occurences of a namespace are exported but others are not" "Some occurences of a namespace are exported but others are not"
} }
fn positions(&self, i: &Interner) -> BoxedIter<ErrorPosition> { fn message(&self, i: &Interner) -> String {
box_once(ErrorPosition { format!(
location: Location::File(self.file.clone()), "{} is opened multiple times with different visibilities",
message: Some(format!( i.extern_all(&self.namespace).join("::")
"{} is opened multiple times with different visibilities", )
i.extern_all(&self.namespace).join("::") }
)), fn one_position(&self, _i: &Interner) -> Location {
}) Location::File(self.file.clone())
} }
} }

View File

@@ -4,11 +4,9 @@ use std::rc::Rc;
use hashbrown::HashMap; use hashbrown::HashMap;
use super::{Process, System}; use super::{Process, System};
use crate::error::{ErrorPosition, ProjectError, ProjectResult}; use crate::error::{ProjectError, ProjectResult};
use crate::interpreter::HandlerTable; use crate::interpreter::HandlerTable;
use crate::rule::Repo; use crate::rule::Repo;
use crate::utils::iter::box_once;
use crate::utils::BoxedIter;
use crate::{ use crate::{
ast, ast_to_interpreted, collect_consts, collect_rules, rule, Interner, ast, ast_to_interpreted, collect_consts, collect_rules, rule, Interner,
Location, ProjectTree, Sym, Location, ProjectTree, Sym,
@@ -128,7 +126,7 @@ impl ProjectError for MacroTimeout {
) )
} }
fn positions(&self, _i: &Interner) -> BoxedIter<ErrorPosition> { fn one_position(&self, _i: &Interner) -> Location {
box_once(ErrorPosition { location: self.location.clone(), message: None }) self.location.clone()
} }
} }

View File

@@ -1,14 +1,12 @@
use hashbrown::HashMap; use hashbrown::HashMap;
use crate::error::{ErrorPosition, ProjectError, ProjectResult}; use crate::error::{ProjectError, ProjectResult};
use crate::interpreted::{self, ExprInst}; use crate::interpreted::{self, ExprInst};
#[allow(unused)] // for doc #[allow(unused)] // for doc
use crate::interpreter; use crate::interpreter;
use crate::interpreter::{ use crate::interpreter::{
run_handler, Context, HandlerTable, Return, RuntimeError, run_handler, Context, HandlerTable, Return, RuntimeError,
}; };
use crate::utils::iter::box_once;
use crate::utils::BoxedIter;
use crate::{Interner, Location, Sym}; use crate::{Interner, Location, Sym};
/// This struct ties the state of systems to loaded code, and allows to call /// This struct ties the state of systems to loaded code, and allows to call
@@ -84,7 +82,7 @@ impl ProjectError for MissingSymbol {
) )
} }
fn positions(&self, _i: &Interner) -> BoxedIter<ErrorPosition> { fn one_position(&self, _i: &Interner) -> Location {
box_once(ErrorPosition { location: self.location.clone(), message: None }) self.location.clone()
} }
} }

View File

@@ -41,7 +41,7 @@ use crate::Primitive;
/// ``` /// ```
/// use orchidlang::{Literal}; /// use orchidlang::{Literal};
/// use orchidlang::interpreted::ExprInst; /// use orchidlang::interpreted::ExprInst;
/// use orchidlang::stl::litconv::with_lit; /// use orchidlang::systems::cast_exprinst::with_lit;
/// use orchidlang::{atomic_impl, atomic_redirect, externfn_impl}; /// use orchidlang::{atomic_impl, atomic_redirect, externfn_impl};
/// ///
/// /// Convert a literal to a string using Rust's conversions for floats, chars and /// /// Convert a literal to a string using Rust's conversions for floats, chars and

View File

@@ -41,7 +41,7 @@ use crate::write_fn_step;
/// ///
/// ``` /// ```
/// use orchidlang::interpreted::Clause; /// use orchidlang::interpreted::Clause;
/// use orchidlang::stl::litconv::with_str; /// use orchidlang::systems::cast_exprinst::with_str;
/// use orchidlang::{define_fn, Literal, Primitive}; /// use orchidlang::{define_fn, Literal, Primitive};
/// ///
/// define_fn! {expr=x in /// define_fn! {expr=x in
@@ -58,7 +58,7 @@ use crate::write_fn_step;
/// A simpler format is also offered for unary functions: /// A simpler format is also offered for unary functions:
/// ///
/// ``` /// ```
/// use orchidlang::stl::litconv::with_lit; /// use orchidlang::systems::cast_exprinst::with_lit;
/// use orchidlang::{define_fn, Literal}; /// use orchidlang::{define_fn, Literal};
/// ///
/// define_fn! { /// define_fn! {

View File

@@ -18,14 +18,14 @@ use crate::interpreted::ExprInst;
/// discussed below. The newly bound names (here `s` and `i` before `=`) can /// discussed below. The newly bound names (here `s` and `i` before `=`) can
/// also receive type annotations. /// also receive type annotations.
/// ///
/// ```no_run /// ```
/// // FIXME this is a very old example that wouldn't compile now /// // FIXME this is a very old example that wouldn't compile now
/// use unicode_segmentation::UnicodeSegmentation; /// use unicode_segmentation::UnicodeSegmentation;
/// ///
/// use orchidlang::{write_fn_step, Literal, Primitive}; /// use orchidlang::{write_fn_step, Literal, Primitive};
/// use orchidlang::interpreted::Clause; /// use orchidlang::interpreted::Clause;
/// use orchidlang::stl::litconv::{with_str, with_uint}; /// use orchidlang::systems::cast_exprinst::{with_str, with_uint};
/// use orchidlang::stl::RuntimeError; /// use orchidlang::systems::RuntimeError;
/// ///
/// // Initial state /// // Initial state
/// write_fn_step!(pub CharAt2 > CharAt1); /// write_fn_step!(pub CharAt2 > CharAt1);
@@ -40,7 +40,7 @@ use crate::interpreted::ExprInst;
/// i = x => with_uint(x, Ok); /// i = x => with_uint(x, Ok);
/// { /// {
/// if let Some(c) = s.graphemes(true).nth(*i as usize) { /// if let Some(c) = s.graphemes(true).nth(*i as usize) {
/// Ok(Literal::Char(c.to_string()).into()) /// Ok(Literal::Str(c.to_string()).into())
/// } else { /// } else {
/// RuntimeError::fail( /// RuntimeError::fail(
/// "Character index out of bounds".to_string(), /// "Character index out of bounds".to_string(),

View File

@@ -1,5 +1,4 @@
use std::any::{Any, TypeId}; use std::any::{Any, TypeId};
use std::mem;
use std::rc::Rc; use std::rc::Rc;
use hashbrown::HashMap; use hashbrown::HashMap;
@@ -8,7 +7,6 @@ use trait_set::trait_set;
use super::{run, Context, Return, RuntimeError}; use super::{run, Context, Return, RuntimeError};
use crate::foreign::ExternError; use crate::foreign::ExternError;
use crate::interpreted::{Clause, ExprInst}; use crate::interpreted::{Clause, ExprInst};
use crate::utils::unwrap_or;
use crate::Primitive; use crate::Primitive;
trait_set! { trait_set! {
@@ -65,20 +63,15 @@ pub fn run_handler(
) -> Result<Return, RuntimeError> { ) -> Result<Return, RuntimeError> {
loop { loop {
let ret = run(expr.clone(), ctx.clone())?; let ret = run(expr.clone(), ctx.clone())?;
if ret.gas == Some(0) { if let Clause::P(Primitive::Atom(a)) = &ret.state.expr().clause {
return Ok(ret); if let Some(e) = handlers.dispatch(a.0.as_any()) {
expr = e?;
ctx.gas = ret.gas;
if ret.gas.map_or(true, |g| g > 0) {
continue;
}
}
} }
let state_ex = ret.state.expr(); return Ok(ret);
let a = if let Clause::P(Primitive::Atom(a)) = &state_ex.clause {
a
} else {
mem::drop(state_ex);
return Ok(ret);
};
expr = unwrap_or!(handlers.dispatch(a.0.as_any()); {
mem::drop(state_ex);
return Ok(ret)
})?;
ctx.gas = ret.gas;
} }
} }

View File

@@ -34,4 +34,4 @@ pub use representations::{
ast, from_const_tree, interpreted, sourcefile, tree, ConstTree, Literal, ast, from_const_tree, interpreted, sourcefile, tree, ConstTree, Literal,
Location, PathSet, Primitive, Location, PathSet, Primitive,
}; };
pub use utils::{Side, Substack, ThreadPool}; pub use utils::{thread_pool, Side, Substack};

View File

@@ -6,7 +6,6 @@ use itertools::Itertools;
use super::{Entry, Lexeme}; use super::{Entry, Lexeme};
use crate::error::{ErrorPosition, ProjectError}; use crate::error::{ErrorPosition, ProjectError};
use crate::interner::InternedDisplay; use crate::interner::InternedDisplay;
use crate::utils::iter::box_once;
use crate::utils::BoxedIter; use crate::utils::BoxedIter;
use crate::{Interner, Location, Tok}; use crate::{Interner, Location, Tok};
@@ -21,8 +20,8 @@ impl ProjectError for LineNeedsPrefix {
fn message(&self, i: &Interner) -> String { fn message(&self, i: &Interner) -> String {
format!("{} cannot appear at the beginning of a line", self.entry.bundle(i)) format!("{} cannot appear at the beginning of a line", self.entry.bundle(i))
} }
fn positions(&self, _i: &Interner) -> BoxedIter<ErrorPosition> { fn one_position(&self, _i: &Interner) -> Location {
box_once(ErrorPosition { message: None, location: self.entry.location() }) self.entry.location()
} }
} }
@@ -42,8 +41,8 @@ impl ProjectError for UnexpectedEOL {
.to_string() .to_string()
} }
fn positions(&self, _i: &Interner) -> BoxedIter<ErrorPosition> { fn one_position(&self, _i: &Interner) -> Location {
box_once(ErrorPosition { message: None, location: self.entry.location() }) self.entry.location()
} }
} }
@@ -54,8 +53,8 @@ impl ProjectError for ExpectedEOL {
fn description(&self) -> &str { fn description(&self) -> &str {
"Expected the end of the line" "Expected the end of the line"
} }
fn positions(&self, _i: &Interner) -> BoxedIter<ErrorPosition> { fn one_position(&self, _i: &Interner) -> Location {
box_once(ErrorPosition { location: self.location.clone(), message: None }) self.location.clone()
} }
} }
@@ -87,8 +86,8 @@ impl ProjectError for ExpectedName {
} }
} }
fn positions(&self, _i: &Interner) -> BoxedIter<ErrorPosition> { fn one_position(&self, _i: &Interner) -> Location {
box_once(ErrorPosition { location: self.entry.location(), message: None }) self.entry.location()
} }
} }
@@ -126,8 +125,9 @@ impl ProjectError for Expected {
let or_name = if self.or_name { " or a name" } else { "" }; let or_name = if self.or_name { " or a name" } else { "" };
format!("Expected {}{} but found {}", list, or_name, self.found.bundle(i)) format!("Expected {}{} but found {}", list, or_name, self.found.bundle(i))
} }
fn positions(&self, _i: &Interner) -> BoxedIter<ErrorPosition> {
box_once(ErrorPosition { location: self.found.location(), message: None }) fn one_position(&self, _i: &Interner) -> Location {
self.found.location()
} }
} }
@@ -143,8 +143,8 @@ impl ProjectError for ReservedToken {
format!("{} is a reserved token", self.entry.bundle(i)) format!("{} is a reserved token", self.entry.bundle(i))
} }
fn positions(&self, _i: &Interner) -> BoxedIter<ErrorPosition> { fn one_position(&self, _i: &Interner) -> Location {
box_once(ErrorPosition { location: self.entry.location(), message: None }) self.entry.location()
} }
} }
@@ -161,8 +161,8 @@ impl ProjectError for BadTokenInRegion {
format!("{} cannot appear in {}", self.entry.bundle(i), self.region) format!("{} cannot appear in {}", self.entry.bundle(i), self.region)
} }
fn positions(&self, _i: &Interner) -> BoxedIter<ErrorPosition> { fn one_position(&self, _i: &Interner) -> Location {
box_once(ErrorPosition { location: self.entry.location(), message: None }) self.entry.location()
} }
} }
@@ -179,8 +179,8 @@ impl ProjectError for NotFound {
format!("{} was expected", self.expected) format!("{} was expected", self.expected)
} }
fn positions(&self, _i: &Interner) -> BoxedIter<ErrorPosition> { fn one_position(&self, _i: &Interner) -> Location {
box_once(ErrorPosition { location: self.location.clone(), message: None }) self.location.clone()
} }
} }
@@ -191,8 +191,8 @@ impl ProjectError for LeadingNS {
fn description(&self) -> &str { fn description(&self) -> &str {
":: can only follow a name token" ":: can only follow a name token"
} }
fn positions(&self, _i: &Interner) -> BoxedIter<ErrorPosition> { fn one_position(&self, _i: &Interner) -> Location {
box_once(ErrorPosition { location: self.location.clone(), message: None }) self.location.clone()
} }
} }
@@ -206,8 +206,8 @@ impl ProjectError for MisalignedParen {
fn message(&self, i: &Interner) -> String { fn message(&self, i: &Interner) -> String {
format!("This {} has no pair", self.entry.bundle(i)) format!("This {} has no pair", self.entry.bundle(i))
} }
fn positions(&self, _i: &Interner) -> BoxedIter<ErrorPosition> { fn one_position(&self, _i: &Interner) -> Location {
box_once(ErrorPosition { location: self.entry.location(), message: None }) self.entry.location()
} }
} }
@@ -218,8 +218,20 @@ impl ProjectError for NamespacedExport {
fn description(&self) -> &str { fn description(&self) -> &str {
"Exports can only refer to unnamespaced names in the local namespace" "Exports can only refer to unnamespaced names in the local namespace"
} }
fn positions(&self, _i: &Interner) -> BoxedIter<ErrorPosition> { fn one_position(&self, _i: &Interner) -> Location {
box_once(ErrorPosition { location: self.location.clone(), message: None }) self.location.clone()
}
}
pub struct GlobExport {
pub location: Location,
}
impl ProjectError for GlobExport {
fn description(&self) -> &str {
"Exports can only refer to concrete names, globstars are not allowed"
}
fn one_position(&self, _i: &Interner) -> Location {
self.location.clone()
} }
} }

View File

@@ -1,18 +1,45 @@
use std::collections::VecDeque; use std::collections::VecDeque;
use std::iter;
use super::context::Context; use super::context::Context;
use super::errors::{Expected, ExpectedName}; use super::errors::Expected;
use super::stream::Stream; use super::stream::Stream;
use super::Lexeme; use super::Lexeme;
use crate::error::{ProjectError, ProjectResult}; use crate::error::{ProjectError, ProjectResult};
use crate::utils::iter::{box_chain, box_once, BoxedIterIter}; use crate::sourcefile::Import;
use crate::utils::iter::{box_chain, box_once};
use crate::utils::BoxedIter;
use crate::Tok; use crate::Tok;
struct Subresult {
glob: bool,
deque: VecDeque<Tok<String>>,
}
impl Subresult {
fn new_glob() -> Self {
Self { glob: true, deque: VecDeque::new() }
}
fn new_named(name: Tok<String>) -> Self {
Self { glob: false, deque: VecDeque::from([name]) }
}
fn push_front(mut self, name: Tok<String>) -> Self {
self.deque.push_front(name);
self
}
fn finalize(self) -> Import {
let Self { mut deque, glob } = self;
debug_assert!(glob || !deque.is_empty(), "The constructors forbid this");
let name = if glob { None } else { deque.pop_back() };
Import { name, path: deque.into() }
}
}
fn parse_multiname_branch( fn parse_multiname_branch(
cursor: Stream<'_>, cursor: Stream<'_>,
ctx: impl Context, ctx: impl Context,
) -> ProjectResult<(BoxedIterIter<Tok<String>>, Stream<'_>)> { ) -> ProjectResult<(BoxedIter<Subresult>, Stream<'_>)> {
let comma = ctx.interner().i(","); let comma = ctx.interner().i(",");
let (subnames, cursor) = parse_multiname_rec(cursor, ctx.clone())?; let (subnames, cursor) = parse_multiname_rec(cursor, ctx.clone())?;
let (delim, cursor) = cursor.trim().pop()?; let (delim, cursor) = cursor.trim().pop()?;
@@ -33,30 +60,46 @@ fn parse_multiname_branch(
} }
} }
pub fn parse_multiname_rec( fn parse_multiname_rec(
curosr: Stream<'_>, curosr: Stream<'_>,
ctx: impl Context, ctx: impl Context,
) -> ProjectResult<(BoxedIterIter<Tok<String>>, Stream<'_>)> { ) -> ProjectResult<(BoxedIter<Subresult>, Stream<'_>)> {
let star = ctx.interner().i("*");
let comma = ctx.interner().i(","); let comma = ctx.interner().i(",");
let (head, cursor) = curosr.trim().pop()?; let (head, mut cursor) = curosr.trim().pop()?;
match &head.lexeme { match &head.lexeme {
Lexeme::LP('(') => parse_multiname_branch(cursor, ctx), Lexeme::LP('(') => parse_multiname_branch(cursor, ctx),
Lexeme::LP('[') => { Lexeme::LP('[') => {
let (op_ent, cursor) = cursor.trim().pop()?; let mut names = Vec::new();
let op = ExpectedName::expect(op_ent)?; loop {
let (rp_ent, cursor) = cursor.trim().pop()?; let head;
Expected::expect(Lexeme::RP('['), rp_ent)?; (head, cursor) = cursor.trim().pop()?;
Ok((box_once(box_once(op)), cursor)) match head.lexeme {
Lexeme::Name(n) => names.push(n),
Lexeme::RP('[') => break,
_ => {
let err = Expected {
expected: vec![Lexeme::RP('[')],
or_name: true,
found: head.clone(),
};
return Err(err.rc());
},
}
}
Ok((Box::new(names.into_iter().map(Subresult::new_named)), cursor))
}, },
Lexeme::Name(n) if *n != comma => { Lexeme::Name(n) if *n == star =>
Ok((box_once(Subresult::new_glob()), cursor)),
Lexeme::Name(n) if ![comma, star].contains(n) => {
let cursor = cursor.trim(); let cursor = cursor.trim();
if cursor.get(0).ok().map(|e| &e.lexeme) == Some(&Lexeme::NS) { if cursor.get(0).ok().map(|e| &e.lexeme) == Some(&Lexeme::NS) {
let cursor = cursor.step()?; let cursor = cursor.step()?;
let (out, cursor) = parse_multiname_rec(cursor, ctx)?; let (out, cursor) = parse_multiname_rec(cursor, ctx)?;
let out = Box::new(out.map(|i| box_chain!(i, iter::once(*n)))); let out = Box::new(out.map(|sr| sr.push_front(*n)));
Ok((out, cursor)) Ok((out, cursor))
} else { } else {
Ok((box_once(box_once(*n)), cursor)) Ok((box_once(Subresult::new_named(*n)), cursor))
} }
}, },
_ => Err( _ => Err(
@@ -73,16 +116,7 @@ pub fn parse_multiname_rec(
pub fn parse_multiname( pub fn parse_multiname(
cursor: Stream<'_>, cursor: Stream<'_>,
ctx: impl Context, ctx: impl Context,
) -> ProjectResult<(Vec<Vec<Tok<String>>>, Stream<'_>)> { ) -> ProjectResult<(Vec<Import>, Stream<'_>)> {
let (output, cont) = parse_multiname_rec(cursor, ctx)?; let (output, cont) = parse_multiname_rec(cursor, ctx)?;
let output = output Ok((output.map(|sr| sr.finalize()).collect(), cont))
.map(|it| {
let mut deque = VecDeque::with_capacity(it.size_hint().0);
for item in it {
deque.push_front(item)
}
deque.into()
})
.collect();
Ok((output, cont))
} }

View File

@@ -5,8 +5,8 @@ use itertools::Itertools;
use super::context::Context; use super::context::Context;
use super::errors::{ use super::errors::{
BadTokenInRegion, Expected, ExpectedName, LeadingNS, MisalignedParen, BadTokenInRegion, Expected, ExpectedName, GlobExport, LeadingNS,
NamespacedExport, ReservedToken, UnexpectedEOL, MisalignedParen, NamespacedExport, ReservedToken, UnexpectedEOL,
}; };
use super::lexer::Lexeme; use super::lexer::Lexeme;
use super::multiname::parse_multiname; use super::multiname::parse_multiname;
@@ -71,18 +71,8 @@ pub fn parse_line(
Lexeme::Const | Lexeme::Macro | Lexeme::Module => Lexeme::Const | Lexeme::Macro | Lexeme::Module =>
Ok(FileEntry::Internal(parse_member(cursor, ctx)?)), Ok(FileEntry::Internal(parse_member(cursor, ctx)?)),
Lexeme::Import => { Lexeme::Import => {
let globstar = ctx.interner().i("*"); let (imports, cont) = parse_multiname(cursor.step()?, ctx)?;
let (names, cont) = parse_multiname(cursor.step()?, ctx.clone())?;
cont.expect_empty()?; cont.expect_empty()?;
let imports = (names.into_iter())
.map(|mut nsname| {
let name = nsname.pop().expect("multinames cannot be zero-length");
Import {
path: ctx.interner().i(&nsname),
name: if name == globstar { None } else { Some(name) },
}
})
.collect();
Ok(FileEntry::Import(imports)) Ok(FileEntry::Import(imports))
}, },
_ => { _ => {
@@ -105,9 +95,12 @@ pub fn parse_export_line(
let (names, cont) = parse_multiname(cursor.step()?, ctx)?; let (names, cont) = parse_multiname(cursor.step()?, ctx)?;
cont.expect_empty()?; cont.expect_empty()?;
let names = (names.into_iter()) let names = (names.into_iter())
.map(|i| if i.len() == 1 { Some(i[0]) } else { None }) .map(|Import { name, path }| match (name, &path[..]) {
.collect::<Option<Vec<_>>>() (Some(n), []) => Ok(n),
.ok_or_else(|| NamespacedExport { location: cursor.location() }.rc())?; (None, _) => Err(GlobExport { location: cursor.location() }.rc()),
_ => Err(NamespacedExport { location: cursor.location() }.rc()),
})
.collect::<Result<Vec<_>, _>>()?;
Ok(FileEntry::Export(names)) Ok(FileEntry::Export(names))
}, },
Lexeme::Const | Lexeme::Macro | Lexeme::Module => Lexeme::Const | Lexeme::Macro | Lexeme::Module =>

View File

@@ -6,13 +6,12 @@ use std::{fs, io};
use hashbrown::{HashMap, HashSet}; use hashbrown::{HashMap, HashSet};
use rust_embed::RustEmbed; use rust_embed::RustEmbed;
use crate::error::{ErrorPosition, ProjectError, ProjectResult}; use crate::error::{ProjectError, ProjectResult};
#[allow(unused)] // for doc #[allow(unused)] // for doc
use crate::facade::System; use crate::facade::System;
use crate::interner::Interner; use crate::interner::Interner;
use crate::utils::iter::box_once; use crate::utils::Cache;
use crate::utils::{BoxedIter, Cache}; use crate::{Location, Stok, VName};
use crate::{Stok, VName};
/// All the data available about a failed source load call /// All the data available about a failed source load call
#[derive(Debug)] #[derive(Debug)]
@@ -25,8 +24,8 @@ impl ProjectError for FileLoadingError {
fn description(&self) -> &str { fn description(&self) -> &str {
"Neither a file nor a directory could be read from the requested path" "Neither a file nor a directory could be read from the requested path"
} }
fn positions(&self, _i: &Interner) -> BoxedIter<ErrorPosition> { fn one_position(&self, _i: &Interner) -> crate::Location {
box_once(ErrorPosition::just_file(self.path.clone())) Location::File(Rc::new(self.path.clone()))
} }
fn message(&self, _i: &Interner) -> String { fn message(&self, _i: &Interner) -> String {
format!("File: {}\nDirectory: {}", self.file, self.dir) format!("File: {}\nDirectory: {}", self.file, self.dir)

View File

@@ -14,7 +14,10 @@ fn assert_visible(
target: &[Tok<String>], // may point to a symbol or module of any kind target: &[Tok<String>], // may point to a symbol or module of any kind
project: &ProjectTree<VName>, project: &ProjectTree<VName>,
) -> ProjectResult<()> { ) -> ProjectResult<()> {
let (tgt_item, tgt_path) = unwrap_or!(target.split_last(); return Ok(())); let (tgt_item, tgt_path) = unwrap_or! {
target.split_last();
return Ok(())
};
let shared_len = let shared_len =
source.iter().zip(tgt_path.iter()).take_while(|(a, b)| a == b).count(); source.iter().zip(tgt_path.iter()).take_while(|(a, b)| a == b).count();
let vis_ignored_len = usize::min(tgt_path.len(), shared_len + 1); let vis_ignored_len = usize::min(tgt_path.len(), shared_len + 1);

View File

@@ -75,7 +75,7 @@ fn source_to_module(
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let imports_from = (imports.iter()) let imports_from = (imports.iter())
.map(|imp| -> ProjectResult<_> { .map(|imp| -> ProjectResult<_> {
let mut imp_path_v = i.r(imp.path).clone(); let mut imp_path_v = imp.path.clone();
imp_path_v.push(imp.name.expect("glob imports had just been resolved")); imp_path_v.push(imp.name.expect("glob imports had just been resolved"));
let mut abs_path = absolute_path(&path_v, &imp_path_v, i) let mut abs_path = absolute_path(&path_v, &imp_path_v, i)
.expect("should have failed in preparsing"); .expect("should have failed in preparsing");

View File

@@ -7,7 +7,7 @@ use crate::error::{NotFound, ProjectError, ProjectResult};
use crate::interner::{Interner, Tok}; use crate::interner::{Interner, Tok};
use crate::pipeline::source_loader::LoadedSourceTable; use crate::pipeline::source_loader::LoadedSourceTable;
use crate::representations::tree::WalkErrorKind; use crate::representations::tree::WalkErrorKind;
use crate::utils::{split_max_prefix, unwrap_or, Cache}; use crate::utils::{split_max_prefix, Cache};
use crate::Sym; use crate::Sym;
pub type OpsResult = ProjectResult<Rc<HashSet<Tok<String>>>>; pub type OpsResult = ProjectResult<Rc<HashSet<Tok<String>>>>;
@@ -33,40 +33,43 @@ pub fn collect_exported_ops(
) -> OpsResult { ) -> OpsResult {
let injected = injected(path).unwrap_or_else(|| Rc::new(HashSet::new())); let injected = injected(path).unwrap_or_else(|| Rc::new(HashSet::new()));
let path_s = &i.r(path)[..]; let path_s = &i.r(path)[..];
let name_split = split_max_prefix(path_s, &|n| loaded.contains_key(n)); match split_max_prefix(path_s, &|n| loaded.contains_key(n)) {
let (fpath, subpath) = unwrap_or!(name_split; return Ok(Rc::new( None => {
(loaded.keys()) let ops = (loaded.keys())
.filter_map(|modname| { .filter_map(|modname| {
if path_s.len() == coprefix(path_s.iter(), modname.iter()) { if path_s.len() == coprefix(path_s.iter(), modname.iter()) {
Some(modname[path_s.len()]) Some(modname[path_s.len()])
} else { } else {
None None
} }
}) })
.chain(injected.iter().copied()) .chain(injected.iter().copied())
.collect::<HashSet<_>>(), .collect::<HashSet<_>>();
))); Ok(Rc::new(ops))
let preparsed = &loaded[fpath].preparsed; },
let module = Some((fpath, subpath)) => {
preparsed.0.walk_ref(subpath, false).map_err( let preparsed = &loaded[fpath].preparsed;
|walk_err| match walk_err.kind { let module = preparsed.0.walk_ref(subpath, false).map_err(
WalkErrorKind::Private => { |walk_err| match walk_err.kind {
unreachable!("visibility is not being checked here") WalkErrorKind::Private => {
unreachable!("visibility is not being checked here")
},
WalkErrorKind::Missing => NotFound {
source: None,
file: fpath.to_vec(),
subpath: subpath[..walk_err.pos].to_vec(),
}
.rc(),
}, },
WalkErrorKind::Missing => NotFound { )?;
source: None, let out = (module.items.iter())
file: fpath.to_vec(), .filter(|(_, v)| v.exported)
subpath: subpath[..walk_err.pos].to_vec(), .map(|(k, _)| *k)
} .chain(injected.iter().copied())
.rc(), .collect::<HashSet<_>>();
}, Ok(Rc::new(out))
)?; },
let out = (module.items.iter()) }
.filter(|(_, v)| v.exported)
.map(|(k, _)| *k)
.chain(injected.iter().copied())
.collect::<HashSet<_>>();
Ok(Rc::new(out))
} }
pub fn mk_cache<'a>( pub fn mk_cache<'a>(

View File

@@ -44,7 +44,7 @@ pub fn collect_ops_for(
ret.insert(n); ret.insert(n);
} else { } else {
let path = i.expect( let path = i.expect(
import_abs_path(file, modpath, &i.r(import.path)[..], i), import_abs_path(file, modpath, &import.path, i),
"This error should have been caught during loading", "This error should have been caught during loading",
); );
ret.extend(ops_cache.find(&i.i(&path))?.iter().copied()); ret.extend(ops_cache.find(&i.i(&path))?.iter().copied());

View File

@@ -59,7 +59,7 @@ fn entv_rec(
.flat_map(|import| { .flat_map(|import| {
if let Import { name: None, path } = import { if let Import { name: None, path } = import {
let p = i.expect( let p = i.expect(
import_abs_path(mod_path, mod_stack, &i.r(path)[..], i), import_abs_path(mod_path, mod_stack, &path, i),
"Should have emerged in preparsing", "Should have emerged in preparsing",
); );
let names = i.expect( let names = i.expect(
@@ -67,7 +67,7 @@ fn entv_rec(
"Should have emerged in second parsing", "Should have emerged in second parsing",
); );
let imports = (names.iter()) let imports = (names.iter())
.map(move |&n| Import { name: Some(n), path }) .map(|&n| Import { name: Some(n), path: path.clone() })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
Box::new(imports.into_iter()) as BoxedIter<Import> Box::new(imports.into_iter()) as BoxedIter<Import>
} else { } else {

View File

@@ -56,7 +56,7 @@ fn load_abs_path_rec(
// recurse on all imported modules // recurse on all imported modules
preparsed.0.visit_all_imports(&mut |modpath, _module, import| { preparsed.0.visit_all_imports(&mut |modpath, _module, import| {
let abs_pathv = let abs_pathv =
import_abs_path(filename, modpath, &import.nonglob_path(i), i)?; import_abs_path(filename, modpath, &import.nonglob_path(), i)?;
if abs_path.starts_with(&abs_pathv) { if abs_path.starts_with(&abs_pathv) {
return Ok(()); return Ok(());
} }

View File

@@ -2,9 +2,8 @@ use std::rc::Rc;
use super::location::Location; use super::location::Location;
use super::{ast, postmacro}; use super::{ast, postmacro};
use crate::error::{ErrorPosition, ProjectError}; use crate::error::ProjectError;
use crate::utils::iter::box_once; use crate::utils::Substack;
use crate::utils::{BoxedIter, Substack};
use crate::{Interner, Sym}; use crate::{Interner, Sym};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -51,9 +50,8 @@ impl ProjectError for Error {
_ => self.description().to_string(), _ => self.description().to_string(),
} }
} }
fn one_position(&self, _i: &Interner) -> Location {
fn positions(&self, _i: &Interner) -> BoxedIter<ErrorPosition> { self.location.clone()
box_once(ErrorPosition { location: self.location.clone(), message: None })
} }
} }

View File

@@ -7,11 +7,10 @@ use super::namelike::VName;
use crate::ast::{Constant, Rule}; use crate::ast::{Constant, Rule};
use crate::interner::{Interner, Tok}; use crate::interner::{Interner, Tok};
use crate::utils::{unwrap_or, BoxedIter}; use crate::utils::{unwrap_or, BoxedIter};
use crate::Sym;
/// An import pointing at another module, either specifying the symbol to be /// An import pointing at another module, either specifying the symbol to be
/// imported or importing all available symbols with a globstar (*) /// imported or importing all available symbols with a globstar (*)
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Import { pub struct Import {
/// Import path, a sequence of module names. Can either start with /// Import path, a sequence of module names. Can either start with
/// ///
@@ -19,7 +18,7 @@ pub struct Import {
/// - any number of `super` to reference the parent module of the implied /// - any number of `super` to reference the parent module of the implied
/// `self` /// `self`
/// - a root name /// - a root name
pub path: Sym, pub path: VName,
/// If name is None, this is a wildcard import /// If name is None, this is a wildcard import
pub name: Option<Tok<String>>, pub name: Option<Tok<String>>,
} }
@@ -29,8 +28,8 @@ impl Import {
/// ///
/// Returns the path if this is a glob import, or the path plus the /// Returns the path if this is a glob import, or the path plus the
/// name if this is a specific import /// name if this is a specific import
pub fn nonglob_path(&self, i: &Interner) -> Vec<Tok<String>> { pub fn nonglob_path(&self) -> Vec<Tok<String>> {
let mut path_vec = i.r(self.path).clone(); let mut path_vec = self.path.clone();
if let Some(n) = self.name { if let Some(n) = self.name {
path_vec.push(n) path_vec.push(n)
} }

View File

@@ -145,7 +145,7 @@ impl<'a, P: MessagePort, ST: StreamTable + 'a> IntoSystem<'a>
constants: io_bindings(i, streams).unwrap_tree(), constants: io_bindings(i, streams).unwrap_tree(),
code: embed_to_map::<IOEmbed>(".orc", i), code: embed_to_map::<IOEmbed>(".orc", i),
prelude: vec![FileEntry::Import(vec![Import { prelude: vec![FileEntry::Import(vec![Import {
path: i.i(&vec![i.i("system"), i.i("io"), i.i("prelude")]), path: vec![i.i("system"), i.i("io"), i.i("prelude")],
name: None, name: None,
}])], }])],
handlers, handlers,

View File

@@ -5,8 +5,8 @@ use hashbrown::HashMap;
use crate::foreign::ExternError; use crate::foreign::ExternError;
use crate::systems::asynch::MessagePort; use crate::systems::asynch::MessagePort;
use crate::utils::{take_with_output, Task}; use crate::thread_pool::{Task, ThreadPool};
use crate::ThreadPool; use crate::utils::take_with_output;
pub trait StreamHandle: Clone + Send { pub trait StreamHandle: Clone + Send {
fn new(id: usize) -> Self; fn new(id: usize) -> Self;

View File

@@ -1,11 +1,11 @@
import std::num::* import std::num::*
export ::(+, -, *, /, %) export ::[+ - * / %]
import std::str::* import std::str::*
export ::[++] export ::[++]
import std::bool::* import std::bool::*
export ::(==, if, then, else, true, false) export ::([==], if, then, else, true, false)
import std::fn::* import std::fn::*
export ::($, |>, =>, identity, pass, pass2, return) export ::([$ |> =>], identity, pass, pass2, return)
import std::list import std::list
import std::map import std::map
import std::option import std::option

View File

@@ -41,7 +41,7 @@ impl IntoSystem<'static> for StlConfig {
constants: HashMap::from([(i.i("std"), fns)]), constants: HashMap::from([(i.i("std"), fns)]),
code: embed_to_map::<StlEmbed>(".orc", i), code: embed_to_map::<StlEmbed>(".orc", i),
prelude: vec![FileEntry::Import(vec![Import { prelude: vec![FileEntry::Import(vec![Import {
path: i.i(&[i.i("std"), i.i("prelude")][..]), path: vec![i.i("std"), i.i("prelude")],
name: None, name: None,
}])], }])],
handlers: HandlerTable::new(), handlers: HandlerTable::new(),

View File

@@ -5,8 +5,6 @@ use std::iter;
/// A trait object of [Iterator] to be assigned to variables that may be /// A trait object of [Iterator] to be assigned to variables that may be
/// initialized form multiple iterators of incompatible types /// initialized form multiple iterators of incompatible types
pub type BoxedIter<'a, T> = Box<dyn Iterator<Item = T> + 'a>; pub type BoxedIter<'a, T> = Box<dyn Iterator<Item = T> + 'a>;
/// A [BoxedIter] of [BoxedIter].
pub type BoxedIterIter<'a, T> = BoxedIter<'a, BoxedIter<'a, T>>;
/// creates a [BoxedIter] of a single element /// creates a [BoxedIter] of a single element
pub fn box_once<'a, T: 'a>(t: T) -> BoxedIter<'a, T> { pub fn box_once<'a, T: 'a>(t: T) -> BoxedIter<'a, T> {
Box::new(iter::once(t)) Box::new(iter::once(t))

View File

@@ -11,7 +11,7 @@ mod split_max_prefix;
mod string_from_charset; mod string_from_charset;
mod substack; mod substack;
mod take_with_output; mod take_with_output;
mod thread_pool; pub mod thread_pool;
mod unwrap_or; mod unwrap_or;
pub use cache::Cache; pub use cache::Cache;
@@ -30,4 +30,3 @@ pub use iter::BoxedIter;
pub use iter_find::iter_find; pub use iter_find::iter_find;
pub use string_from_charset::string_from_charset; pub use string_from_charset::string_from_charset;
pub use take_with_output::take_with_output; pub use take_with_output::take_with_output;
pub use thread_pool::{Query, QueryTask, Task, ThreadPool};

View File

@@ -1,3 +1,7 @@
//! A thread pool for executing tasks in parallel, spawning threads as workload
//! increases and terminating them as tasks finish. This is not terribly
//! efficient, its main design goal is to parallelize blocking I/O calls.
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{sync_channel, SyncSender}; use std::sync::mpsc::{sync_channel, SyncSender};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@@ -6,6 +10,8 @@ use std::thread::spawn;
/// A trait for a task dispatched on a [ThreadPool]. The task owns all relevant /// A trait for a task dispatched on a [ThreadPool]. The task owns all relevant
/// data, is safe to pass between threads and is executed only once. /// data, is safe to pass between threads and is executed only once.
pub trait Task: Send + 'static { pub trait Task: Send + 'static {
/// Execute the task. At a minimum, this involves signaling some other thread,
/// otherwise the task has no effect.
fn run(self); fn run(self);
} }
@@ -15,10 +21,21 @@ impl<F: FnOnce() + Send + 'static> Task for F {
} }
} }
/// An async unit of work that produces some result, see [Task]. This can be
/// wrapped in a generic reporter to create a task.
pub trait Query: Send + 'static { pub trait Query: Send + 'static {
/// The value produced by the query
type Result: Send + 'static; type Result: Send + 'static;
/// Execute the query, producing some value which can then be sent to another
/// thread
fn run(self) -> Self::Result; fn run(self) -> Self::Result;
/// Associate the query with a reporter expressed in a plain function.
/// Note that because every lambda has a distinct type and every thread pool
/// runs exactly one type of task, this can appear only once in the code for
/// a given thread pool. It is practical in a narrow set of cases, most of the
/// time however you are better off defining an explicit reporter.
fn then<F: FnOnce(Self::Result) + Send + 'static>( fn then<F: FnOnce(Self::Result) + Send + 'static>(
self, self,
callback: F, callback: F,
@@ -37,6 +54,8 @@ impl<F: FnOnce() -> R + Send + 'static, R: Send + 'static> Query for F {
} }
} }
/// A reporter that calls a statically known function with the result of a
/// query. Constructed with [Query::then]
pub struct QueryTask<Q: Query, F: FnOnce(Q::Result) + Send + 'static> { pub struct QueryTask<Q: Query, F: FnOnce(Q::Result) + Send + 'static> {
query: Q, query: Q,
callback: F, callback: F,
@@ -68,16 +87,23 @@ struct ThreadPoolData<T: Task> {
/// arrive. To get rid of the last waiting thread, drop the thread pool. /// arrive. To get rid of the last waiting thread, drop the thread pool.
/// ///
/// ``` /// ```
/// use orchidlang::ThreadPool; /// use orchidlang::thread_pool::{Task, ThreadPool};
/// ///
/// let pool = ThreadPool::new(|s: String, _| println!("{}", s)); /// struct MyTask(&'static str);
/// impl Task for MyTask {
/// fn run(self) {
/// println!("{}", self.0)
/// }
/// }
///
/// let pool = ThreadPool::new();
/// ///
/// // spawns first thread /// // spawns first thread
/// pool.submit("foo".to_string()); /// pool.submit(MyTask("foo"));
/// // probably spawns second thread /// // probably spawns second thread
/// pool.submit("bar".to_string()); /// pool.submit(MyTask("bar"));
/// // either spawns third thread or reuses first /// // either spawns third thread or reuses first
/// pool.submit("baz".to_string()); /// pool.submit(MyTask("baz"));
/// ``` /// ```
pub struct ThreadPool<T: Task> { pub struct ThreadPool<T: Task> {
data: Arc<ThreadPoolData<T>>, data: Arc<ThreadPoolData<T>>,

View File

@@ -8,10 +8,14 @@
/// It also supports unwrapping concrete variants of other enums /// It also supports unwrapping concrete variants of other enums
/// ///
/// ```ignore /// ```ignore
/// use crate::representations::Literal; /// use crate::Literal;
/// ///
/// crate::unwrap_or!(Literal::Usize(2) => Literal::Number; return) /// crate::unwrap_or!(Literal::Usize(2) => Literal::Number; return)
/// ``` /// ```
///
/// Note: this macro influences the control flow of the surrounding code
/// without an `if`, which can be misleading. It should only be used for small,
/// straightforward jumps.
macro_rules! unwrap_or { macro_rules! unwrap_or {
($m:expr; $fail:expr) => {{ ($m:expr; $fail:expr) => {{
if let Some(res) = ($m) { res } else { $fail } if let Some(res) = ($m) { res } else { $fail }