Skip to content

Commit

Permalink
Fix closures for Alan functions, special-case Maybe and Fallible, and…
Browse files Browse the repository at this point in the history
… get one-arm 'cond' working (#819)

* Fix closures for Alan functions, special-case Maybe and Fallible, and get one-arm 'cond' working

* Get the full test suite working again

* Fix benchmarks *and* remove two categories of derived functions, simplifying backend implementation
  • Loading branch information
dfellis authored Jul 29, 2024
1 parent e335b85 commit 97b0c9c
Show file tree
Hide file tree
Showing 7 changed files with 424 additions and 124 deletions.
5 changes: 3 additions & 2 deletions src/compile/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1535,9 +1535,10 @@ to barto bar3
test!(cond_fn => r#"
export fn main {
cond(1 == 0, fn = print('What!?'), fn = print('Math is sane...'));
// cond(1 == 2, fn () -> string = 'Uhh...').print;
cond(1 == 2, fn () -> string = 'Uhh...').print;
cond(1 == 1, fn () -> string = 'Correct!').print;
}"#;
stdout "Math is sane...\n";
stdout "Math is sane...\nvoid\nCorrect!\n";
);

test_ignore!(basic_conditionals => r#"
Expand Down
303 changes: 260 additions & 43 deletions src/lntors/function.rs

Large diffs are not rendered by default.

37 changes: 31 additions & 6 deletions src/lntors/typen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,17 @@ pub fn ctype_to_rtype(
CType::Bool(b) => Ok(b.to_string()),
CType::TString(s) => Ok(s.clone()),
CType::Group(g) => Ok(format!("({})", ctype_to_rtype(g, in_function_type)?)),
CType::Function(i, o) => Ok(format!(
"fn({}) -> {}",
ctype_to_rtype(i, true)?,
ctype_to_rtype(o, true)?
)),
CType::Function(i, o) => {
if let CType::Void = **i {
Ok(format!("impl Fn() -> {}", ctype_to_rtype(o, true)?))
} else {
Ok(format!(
"impl Fn({}) -> {}",
ctype_to_rtype(i, true)?,
ctype_to_rtype(o, true)?
))
}
},
CType::Tuple(ts) => {
let mut out = Vec::new();
for t in ts {
Expand All @@ -92,7 +98,25 @@ pub fn ctype_to_rtype(
Ok(format!("{}: {}", k, ctype_to_rtype(v, in_function_type)?))
}
}
CType::Either(ts) => Ok(CType::Either(ts.clone()).to_callable_string()),
CType::Either(ts) => {
// Special handling to convert `Either{T, void}` to `Option<T>` and `Either{T, Error}`
// to `Result<T, AlanError>`
if ts.len() == 2 {
if let CType::Void = &ts[1] {
Ok(format!("Option<{}>", ctype_to_rtype(&ts[0], in_function_type)?))
} else if let CType::Bound(name, rustname) = &ts[1] {
if name == "Error" {
Ok(format!("Result<{}, {}>", ctype_to_rtype(&ts[0], in_function_type)?, rustname))
} else {
Ok(CType::Either(ts.clone()).to_callable_string())
}
} else {
Ok(CType::Either(ts.clone()).to_callable_string())
}
} else {
Ok(CType::Either(ts.clone()).to_callable_string())
}
}
CType::AnyOf(_) => Ok("".to_string()), // Does this make any sense in Rust?
CType::Buffer(t, s) => Ok(format!(
"[{};{}]",
Expand Down Expand Up @@ -136,6 +160,7 @@ pub fn generate(
let res = generate(t, out)?;
out = res.1;
}

let out_str = ctype_to_rtype(typen, false)?;
Ok((out_str, out)) // TODO: Put something into out?
}
Expand Down
33 changes: 0 additions & 33 deletions src/program/ctype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1541,22 +1541,6 @@ impl CType {
microstatements: Vec::new(),
kind: FnKind::Derived,
});
fs.push(Function {
name: "get".to_string(),
args: vec![
("arg0".to_string(), t.clone()),
(
"arg1".to_string(),
CType::Bound("i64".to_string(), "i64".to_string()),
),
],
rettype: CType::Type(
format!("Maybe_{}_", b.to_string()),
Box::new(CType::Either(vec![*b.clone(), CType::Void])),
),
microstatements: Vec::new(),
kind: FnKind::Derived,
});
let size = match **s {
CType::Int(s) => s as usize,
_ => 0, // TODO: Make this function fallible, instead?
Expand Down Expand Up @@ -1592,23 +1576,6 @@ impl CType {
microstatements: Vec::new(),
kind: FnKind::DerivedVariadic,
});
fs.push(Function {
name: "get".to_string(),
args: vec![
("arg0".to_string(), t.clone()),
(
"arg1".to_string(),
CType::Bound("i64".to_string(), "i64".to_string()),
),
],
rettype: CType::Type(
format!("Maybe_{}_", a.to_string()),
Box::new(CType::Either(vec![*a.clone(), CType::Void])),
),
microstatements: Vec::new(),
kind: FnKind::Derived,
});
// TODO: Add 'set' function
}
CType::Int(i) => {
// TODO: Support construction of other integer types
Expand Down
144 changes: 106 additions & 38 deletions src/program/microstatement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ pub enum Microstatement {
Closure {
function: Function,
},
VarCall {
name: String,
typen: CType,
args: Vec<Microstatement>,
},
Value {
typen: CType,
representation: String,
Expand Down Expand Up @@ -71,6 +76,7 @@ impl Microstatement {
);
Ok(fn_type)
}
Self::VarCall { typen, .. } => Ok(typen.clone()),
}
}
}
Expand Down Expand Up @@ -817,48 +823,111 @@ pub fn baseassignablelist_to_microstatements(
for arg in &arg_microstatements {
arg_types.push(arg.get_type()?);
}
// Now confirm that there's actually a function with this name that takes these
// types
let mut temp_scope = scope.child();
let func = temp_scope.resolve_function(f, &arg_types);
match func {
Some(fun) => {
// Success! Let's emit this
// TODO: Do a better job at type rewriting here
#[allow(clippy::needless_range_loop)]
for i in 0..fun.args.len() {
match &arg_microstatements[i] {
Microstatement::Value {
typen,
representation,
} => {
if typen != &fun.args[i].1 {
arg_microstatements[i] = Microstatement::Value {
typen: fun.args[i].1.clone(),
representation: representation.clone(),
};
// Look for closure functions in the microstatement array first to see if that's
// what should be called, scanning in reverse order to find the most recent
// definition that matches, if multiple match
let mut closure_fn = None;
let mut var_fn = None;
for ms in microstatements.iter().rev() {
match ms {
Microstatement::Closure { function } => {
if &function.name == f && function.args.len() == arg_types.len() {
let mut works = true;
for ((_, a), b) in function.args.iter().zip(&arg_types) {
if !a.accepts(b) {
works = false;
}
}
_ => { /* Do nothing */ }
if works {
closure_fn = Some(function.clone());
break;
}
}
}

prior_value = Some(Microstatement::FnCall {
function: fun.clone(), // TODO: Drop the clone
args: arg_microstatements,
});
Microstatement::Arg { name, typen } => {
if name == f {
if let CType::Function(i, o) = typen {
let mut works = true;
// TODO: Really need to just have the Function use the Function
// CType instead of this stuff
let farg_types = match &**i {
CType::Void => Vec::new(),
CType::Tuple(ts) => ts.clone(),
other => vec![other.clone()],
};
for (a, b) in farg_types.iter().zip(&arg_types) {
if !a.accepts(b) {
works = false;
}
}
if works {
var_fn = Some((name.clone(), (**o).clone()));
break;
}
}
}
}
Microstatement::Assignment { .. } => {
// TODO
}
_ => { /* Do nothing */ }
}
None => {
return Err(format!(
"Could not find a function with a call signature of {}({})",
f,
arg_types
.iter()
.map(|a| a.to_string())
.collect::<Vec<String>>()
.join(", ")
)
.into());
}
if let Some(func) = closure_fn {
prior_value = Some(Microstatement::FnCall {
function: func,
args: arg_microstatements,
});
} else if let Some((name, typen)) = var_fn {
prior_value = Some(Microstatement::VarCall {
name,
typen,
args: arg_microstatements,
});
} else {
// Now confirm that there's actually a function with this name that takes these
// types
let mut temp_scope = scope.child();
let func = temp_scope.resolve_function(f, &arg_types);
match func {
Some(fun) => {
// Success! Let's emit this
// TODO: Do a better job at type rewriting here
#[allow(clippy::needless_range_loop)]
for i in 0..fun.args.len() {
match &arg_microstatements[i] {
Microstatement::Value {
typen,
representation,
} => {
if typen != &fun.args[i].1 {
arg_microstatements[i] = Microstatement::Value {
typen: fun.args[i].1.clone(),
representation: representation.clone(),
};
}
}
_ => { /* Do nothing */ }
}
}

prior_value = Some(Microstatement::FnCall {
function: fun.clone(), // TODO: Drop the clone
args: arg_microstatements,
});
}
None => {
return Err(format!(
"Could not find a function with a call signature of {}({})",
f,
arg_types
.iter()
.map(|a| a.to_string())
.collect::<Vec<String>>()
.join(", ")
)
.into());
}
}
}
}
Expand Down Expand Up @@ -952,7 +1021,6 @@ pub fn baseassignablelist_to_microstatements(
}
(Some(_), Some(_)) => {
// If this hits, it matched on the argument
println!("what");
}
(None, Some(func)) => {
// TODO: Do we need the merge here? It looks like it will happen later
Expand Down
6 changes: 5 additions & 1 deletion src/std/root.ln
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ export fn hash{T}(v: T) -> i64 binds hash;
export fn getOr{T}(v: Maybe{T}, d: T) -> T binds maybe_get_or;
export fn getOr{T}(v: Fallible{T}, d: T) -> T binds fallible_get_or;
export fn getOr{T, U}(v: U, d: T) -> T = {T}(v).getOr(d);
export fn none{T}() -> Maybe{T} binds maybe_none;
export fn error{T}(m: string) -> Fallible{T} binds fallible_error;

/// Signed Integer-related functions and function bindings
export fn i8(i: i8) -> i8 = i;
Expand Down Expand Up @@ -553,9 +555,10 @@ export fn xnor(a: bool, b: bool) -> bool binds xnorbool;
export fn eq(a: bool, b: bool) -> bool binds eqbool;
export fn neq(a: bool, b: bool) -> bool binds neqbool;
export fn cond{T}(c: bool, t: () -> T, f: () -> T) -> T binds condbool;
export fn cond{T}(c: bool, t: () -> T) -> Maybe{T} = cond(c, fn = Maybe{T}(t()), fn = Maybe{T}(void));
export fn cond{T}(c: bool, t: () -> T) -> Maybe{T} = cond(c, fn = Maybe{T}(t()), fn = none{T}());

/// Array related bindings
export fn get{T}(a: T[], i: i64) -> Maybe{T} binds getarray;
export fn len{T}(a: T[]) -> i64 binds lenarray;
export fn push{T}(a: Array{T}, v: T) -> () binds pusharray;
export fn pop{T}(a: T[]) -> Maybe{T} binds poparray;
Expand All @@ -575,6 +578,7 @@ export fn every{T}(a: Array{T}, f: (T) -> bool) -> bool binds everyarray;
export fn repeat{T}(a: Array{T}, c: i64) -> Array{T} binds repeatarray;

/// Buffer related bindings
export fn get{T, S}(b: T[S], i: i64) -> Maybe{T} binds getbuffer;
export fn len{T, S}(T[S]) -> i64 = {S}();
export fn map{T, S, U}(a: Buffer{T, S}, m: T -> U) -> Buffer{U, S} binds mapbuffer_onearg;
export fn map{T, S, U}(a: Buffer{T, S}, m: (T, i64) -> U) -> Buffer{U, S} binds mapbuffer_twoarg;
Expand Down
20 changes: 19 additions & 1 deletion src/std/root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,18 @@ fn fallible_get_or<T: std::clone::Clone>(v: &Result<T, AlanError>, d: &T) -> T {
}
}

/// `maybe_none` creates a None for the given maybe type
#[inline(always)]
fn maybe_none<T>() -> Option<T> {
None
}

/// `fallible_error` create an Err for the given fallible type
#[inline(always)]
fn fallible_error<T>(m: &String) -> Result<T, AlanError> {
Err(m.clone().into())
}

/// Signed Integer-related functions

/// `stringtoi8` tries to convert a string into an i8
Expand Down Expand Up @@ -2582,7 +2594,7 @@ fn neqbool(a: &bool, b: &bool) -> bool {
/// `condbool` executes the true function on true, and the false function on false, returning the
/// value returned by either function
#[inline(always)]
fn condbool<T>(c: &bool, t: fn() -> T, f: fn() -> T) -> T {
fn condbool<T>(c: &bool, t: impl Fn() -> T, f: impl Fn() -> T) -> T {
if *c {
t()
} else {
Expand Down Expand Up @@ -2850,6 +2862,12 @@ fn repeatarray<T: std::clone::Clone>(a: &Vec<T>, c: &i64) -> Vec<T> {

/// Buffer-related functions

/// `getbuffer` returns the value at the given index presuming it exists
#[inline(always)]
fn getbuffer<T: std::clone::Clone, const S: usize>(b: &[T; S], i: &i64) -> Option<T> {
b.get(*i as usize).cloned()
}

/// `mapbuffer_onearg` runs the provided single-argument function on each element of the buffer,
/// returning a new buffer
#[inline(always)]
Expand Down

0 comments on commit 97b0c9c

Please sign in to comment.