Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic lists support #90

Merged
merged 1 commit into from Jun 24, 2020
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
28 changes: 28 additions & 0 deletions src/examples/lists-all-any.ncl
@@ -0,0 +1,28 @@
let Y = fun f => (fun x => f (x x)) (fun x => f (x x)) in
let foldr_ =
fun self => fun f => fun acc => fun l =>
if isZero (length l) then acc
else
let h = head l in
let t = tail l in
let next_acc = self f acc t in
f next_acc h
in
let foldr = Y foldr_ in
let and = Promise(Bool -> Bool -> Bool,
fun x => fun y =>
if x then
if y then true else false
else false)
in
let or = Promise(Bool -> Bool -> Bool,
fun x => fun y =>
if x then
true
else
if y then true else false)
in
let all = fun pred => fun l => foldr and true (map pred l) in
let any = fun pred => fun l => foldr or false (map pred l) in
let isZ = fun x => isZero x in
or (any isZ [1, 1, 1, 1]) (all isZ [0, 0, 0, 0])
14 changes: 14 additions & 0 deletions src/examples/lists-flatten.ncl
@@ -0,0 +1,14 @@
let Y = fun f => (fun x => f (x x)) (fun x => f (x x)) in
let foldl_ =
fun self => fun f => fun acc => fun l =>
if isZero (length l) then acc
else
let h = head l in
let t = tail l in
let next_acc = f acc h in
seq next_acc (self f next_acc t)
in
let foldl = Y foldl_ in
let concat = Promise(List -> List -> List, fun x => fun y => x @ y) in
let flatten = foldl concat [] in
flatten [[1,2],[3,4],[]]
12 changes: 12 additions & 0 deletions src/examples/lists-foldl.ncl
@@ -0,0 +1,12 @@
let Y = fun f => (fun x => f (x x)) (fun x => f (x x)) in
let foldl_ =
fun self => fun f => fun acc => fun l =>
if isZero (length l) then acc
else
let h = head l in
let t = tail l in
let next_acc = f acc h in
seq next_acc (self f next_acc t)
in
let foldl = Y foldl_ in
foldl (fun x => fun y => x + y) 0 ([1, 2, 3, 4, 5])
23 changes: 19 additions & 4 deletions src/grammar.lalrpop
Expand Up @@ -35,7 +35,7 @@ Applicative: RichTerm = {
<op: BOpPre> <t1: SpTerm<Atom>> <t2: SpTerm<Atom>> => RichTerm::new(Term::Op2(op, t1, t2)),
<t: SpTerm<Atom>> "." <id: Ident> => RichTerm::new(Term::Op1(UnaryOp::StaticAccess(id), t)),
<t: SpTerm<Atom>> ".$" <t_id: SpTerm<Atom>> => RichTerm::new(Term::Op2(BinaryOp::DynAccess(), t_id, t)),
<r: SpTerm<Atom>> "[" <id: SpTerm<Atom>> "=" <t: SpTerm<Atom>> "]" =>
<r: SpTerm<Atom>> "$[" <id: SpTerm<Atom>> "=" <t: SpTerm<Atom>> "]" =>
RichTerm::new(Term::Op2(BinaryOp::DynExtend(t), id, r)),
SpTerm<Atom>,
};
Expand Down Expand Up @@ -68,9 +68,15 @@ Atom: RichTerm = {
acc
))
}
)


),
"[" <terms: (SpTerm<Atom> ",")*> <last: SpTerm<Atom>?> "]" => {
let mut terms : Vec<RichTerm> = terms.clone().into_iter().map(|x| x.0).collect();
if let Some(t) = last {
terms.push(t);
}

RichTerm::new(Term::List(terms))
}
};

RecordField: Either<(Ident, RichTerm), (RichTerm, RichTerm)> = {
Expand Down Expand Up @@ -103,6 +109,7 @@ UOp: UnaryOp<RichTerm> = {
"isBool" => UnaryOp::IsBool(),
"isStr" => UnaryOp::IsStr(),
"isFun" => UnaryOp::IsFun(),
"isList" => UnaryOp::IsList(),
"blame" => UnaryOp::Blame(),
"chngPol" => UnaryOp::ChangePolarity(),
"polarity" => UnaryOp::Pol(),
Expand All @@ -119,6 +126,9 @@ UOp: UnaryOp<RichTerm> = {
"mapRec" <Atom> => UnaryOp::MapRec(<>),
"seq" => UnaryOp::Seq(),
"deepSeq" => UnaryOp::DeepSeq(),
"head" => UnaryOp::ListHead(),
"tail" => UnaryOp::ListTail(),
"length" => UnaryOp::ListLength(),
};

switch_case: (Ident, RichTerm) = {
Expand All @@ -134,11 +144,15 @@ BOpIn: BinaryOp<RichTerm> = {
"++" => BinaryOp::PlusStr(),
"=b" => BinaryOp::EqBool(),
"-$" => BinaryOp::DynRemove(),
"@" => BinaryOp::ListConcat(),

};

BOpPre: BinaryOp<RichTerm> = {
"unwrap" => BinaryOp::Unwrap(),
"hasField" => BinaryOp::HasField(),
"map" => BinaryOp::ListMap(),
"elemAt" => BinaryOp::ListElemAt(),
}

Types: Types = {
Expand All @@ -156,6 +170,7 @@ subType : Types = {
"Num" => Types(AbsType::Num()),
"Bool" => Types(AbsType::Bool()),
"Str" => Types(AbsType::Str()),
"List" => Types(AbsType::List()),
<Ident> => Types(AbsType::Var(<>)),
"#" <SpTerm<RichTerm>> => Types(AbsType::Flat(<>)),
"(" <Types> ")" => <>,
Expand Down
180 changes: 164 additions & 16 deletions src/operation.rs
@@ -1,10 +1,12 @@
use crate::eval::Environment;
use crate::eval::{CallStack, Closure, EvalError, IdentKind};
use crate::identifier::Ident;
use crate::label::TyPath;
use crate::stack::Stack;
use crate::term::{BinaryOp, RichTerm, Term, UnaryOp};
use simple_counter::*;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;

generate_counter!(FreshVariableCounter, usize);
Expand Down Expand Up @@ -96,6 +98,13 @@ fn process_unary_operation(
Ok(Closure::atomic_closure(Term::Bool(false).into()))
}
}
UnaryOp::IsList() => {
if let Term::List(_) = *t {
Ok(Closure::atomic_closure(Term::Bool(true).into()))
} else {
Ok(Closure::atomic_closure(Term::Bool(false).into()))
}
}
UnaryOp::Blame() => {
if let Term::Lbl(l) = *t {
Err(EvalError::BlameError(l, None))
Expand Down Expand Up @@ -265,28 +274,89 @@ fn process_unary_operation(
)))
}
}
UnaryOp::DeepSeq() => match *t {
Term::Record(map) if !map.is_empty() => {
let mut fields = map.into_iter().map(|(_, t)| t);

let first = fields.next().expect("Condition already checked.");
let body = fields.fold(Term::Op1(UnaryOp::DeepSeq(), first).into(), |acc, t| {
UnaryOp::DeepSeq() => {
/// Build a closure that forces a given list of terms, and at the end resumes the
/// evaluation of the argument on the top of the stack.
/// Requires its first argument to be non-empty
fn seq_terms<I>(mut terms: I, env: Environment) -> Result<Closure, EvalError>
where
I: Iterator<Item = RichTerm>,
{
let first = terms
.next()
.expect("expected the argument to be a non-empty iterator");
let body = terms.fold(Term::Op1(UnaryOp::DeepSeq(), first).into(), |acc, t| {
Term::App(Term::Op1(UnaryOp::DeepSeq(), t).into(), acc).into()
});

Ok(Closure { body, env })
};

match *t {
Term::Record(map) if !map.is_empty() => {
let terms = map.into_iter().map(|(_, t)| t);
seq_terms(terms, env)
}
Term::List(ts) if !ts.is_empty() => seq_terms(ts.into_iter(), env),
_ => {
if stack.count_args() >= 1 {
let (next, _) = stack.pop_arg().expect("Condition already checked.");
Ok(next)
} else {
Err(EvalError::TypeError(String::from(
"deepSeq: expected two arguments, got only one",
)))
}
}
}
_ => {
if stack.count_args() >= 1 {
let (next, _) = stack.pop_arg().expect("Condition already checked.");
Ok(next)
}
UnaryOp::ListHead() => {
if let Term::List(ts) = *t {
let mut ts_it = ts.into_iter();
if let Some(head) = ts_it.next() {
Ok(Closure { body: head, env })
} else {
Err(EvalError::TypeError(String::from(
"deepSeq: expected two arguments, got only one",
)))
Err(EvalError::TypeError(String::from("head: empty list")))
}
} else {
Err(EvalError::TypeError(format!(
"head: expected List, found {:?} instead",
*t
)))
}
},
}
UnaryOp::ListTail() => {
if let Term::List(ts) = *t {
let mut ts_it = ts.into_iter();
if let Some(_) = ts_it.next() {
Ok(Closure {
body: Term::List(ts_it.collect()).into(),
env,
})
} else {
Err(EvalError::TypeError(String::from("tail: empty list")))
}
} else {
Err(EvalError::TypeError(format!(
"tail: expected List, found {:?} instead",
*t
)))
}
}
UnaryOp::ListLength() => {
if let Term::List(ts) = *t {
// A num does not have any free variable so we can drop the environment
Ok(Closure {
body: Term::Num(ts.len() as f64).into(),
env: HashMap::new(),
})
} else {
Err(EvalError::TypeError(format!(
"tail: expected List, found {:?} instead",
*t
)))
}
}
}
}

Expand All @@ -297,7 +367,10 @@ fn process_binary_operation(
_stack: &mut Stack,
) -> Result<Closure, EvalError> {
let Closure {
body: RichTerm { term: t1, .. },
body: RichTerm {
term: t1,
pos: pos1,
},
env: env1,
} = fst_clos;
let Closure {
Expand Down Expand Up @@ -394,6 +467,7 @@ fn process_binary_operation(
if let Term::Str(id) = *t1 {
if let Term::Record(mut static_map) = *t2 {
// Arnauds trick, make the closure into a fresh variable
// TODO: refactor via closurize() once the PR on merge has landed
let fresh_var = format!("_{}", FreshVariableCounter::next());

env2.insert(
Expand Down Expand Up @@ -460,14 +534,88 @@ fn process_binary_operation(
Err(EvalError::TypeError(format!("Expected Str, got {:?}", *t1)))
}
}
BinaryOp::ListConcat() => match (*t1, *t2) {
(Term::List(mut ts1), Term::List(ts2)) => {
ts1.extend(ts2);
Ok(Closure::atomic_closure(Term::List(ts1).into()))
}
(Term::List(_), t2) => Err(EvalError::TypeError(format!(
"List concatenation: expected the second argument to be a List, got {:?} instead",
t2
))),
(t1, _) => Err(EvalError::TypeError(format!(
"List concatenation: expected the first argument to be a List, got {:?} instead",
t1
))),
},
// This one should not be strict in the first argument (f)
BinaryOp::ListMap() => {
if let Term::List(mut ts) = *t2 {
// TODO: refactor via closurize() once the PR on merge has landed
let fresh_var = format!("_{}", FreshVariableCounter::next());

let f_closure = Closure {
body: RichTerm {
term: t1,
pos: pos1,
},
env: env1,
};
let f_as_var: RichTerm = Term::Var(Ident(fresh_var.clone())).into();

env2.insert(
Ident(fresh_var.clone()),
(Rc::new(RefCell::new(f_closure)), IdentKind::Record()),
);

let ts = ts
.into_iter()
.map(|t| Term::App(f_as_var.clone(), t).into())
.collect();

Ok(Closure {
body: Term::List(ts).into(),
env: env2,
})
} else {
Err(EvalError::TypeError(format!(
"map: expected the second argument to be a List, got {:?} instead",
*t1
)))
}
}
BinaryOp::ListElemAt() => {
match (*t1, *t2) {
(Term::List(mut ts), Term::Num(n)) => {
let n_int = n as usize;
if n.fract() != 0.0 {
Err(EvalError::TypeError(format!("elemAt: expected the second agument to be an integer, got the floating-point value {}", n)))
} else if n_int >= ts.len() {
Err(EvalError::TypeError(format!("elemAt: index out of bounds. Expected a value between 0 and {}, got {})", ts.len(), n_int)))
} else {
Ok(Closure {
body: ts.swap_remove(n_int),
env: env1,
})
}
}
(Term::List(_), t2) => Err(EvalError::TypeError(format!(
"elemAt: expected the second argument to be an integer, got {:?} instead",
t2
))),
(t1, _) => Err(EvalError::TypeError(format!(
"elemAt: expected the first argument to be a List, got {:?} instead",
t1
))),
}
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::eval::{CallStack, Environment};
use std::collections::HashMap;

fn some_env() -> Environment {
HashMap::new()
Expand Down