use std::cmp::Ordering;
use std::ops::Rem;
use std::sync::Arc;
use cirru_edn::EdnKwd;
use crate::primes::{Calcit, CalcitErr, CalcitItems};
pub fn new_record(xs: &CalcitItems) -> Result<Calcit, CalcitErr> {
if xs.is_empty() {
return CalcitErr::err_str(format!("new-record expected arguments, got {:?}", xs));
}
let name_id: EdnKwd = match &xs[0] {
Calcit::Symbol { sym, .. } => EdnKwd::from(sym),
Calcit::Keyword(k) => k.to_owned(),
a => return CalcitErr::err_str(format!("new-record expected a name, got {}", a)),
};
let mut fields: Vec<EdnKwd> = Vec::with_capacity(xs.len());
let mut values: Vec<Calcit> = Vec::with_capacity(xs.len());
for (idx, x) in xs.into_iter().enumerate() {
if idx > 0 {
match x {
Calcit::Symbol { sym, .. } | Calcit::Str(sym) => {
fields.push(EdnKwd::from(&*sym));
}
Calcit::Keyword(s) => {
fields.push(s.to_owned());
}
a => return CalcitErr::err_str(format!("new-record fields accepets keyword/string, got a {}", a)),
}
values.push(Calcit::Nil);
}
}
fields.sort_unstable();
let mut prev: EdnKwd = EdnKwd::from(""); for (idx, x) in fields.iter().enumerate() {
if idx > 0 {
if x == &prev {
return CalcitErr::err_str(format!("duplicated field for record: {}", Calcit::Keyword(x.to_owned())));
} else {
prev = x.to_owned();
}
} else {
prev = x.to_owned()
}
}
Ok(Calcit::Record(name_id, Arc::new(fields), Arc::new(values)))
}
pub fn call_record(xs: &CalcitItems) -> Result<Calcit, CalcitErr> {
let args_size = xs.len();
if args_size < 2 {
return CalcitErr::err_str(format!("&%{{}} expected at least 2 arguments, got {:?}", xs));
}
match &xs[0] {
Calcit::Record(name, def_fields, v0) => {
if (args_size - 1).rem(2) == 0 {
let size = (args_size - 1) / 2;
if size != def_fields.len() {
return CalcitErr::err_str(format!("unexpected size in &%{{}}, {} .. {}", size, def_fields.len()));
}
let mut values: Vec<Calcit> = (**v0).to_owned();
for idx in 0..size {
let k_idx = idx * 2 + 1;
let v_idx = k_idx + 1;
match &xs[k_idx] {
Calcit::Keyword(s) => match find_in_fields(def_fields, s) {
Some(pos) => {
values[pos] = xs[v_idx].to_owned();
}
None => return CalcitErr::err_str(format!("unexpected field {} for {:?}", s, def_fields)),
},
Calcit::Symbol { sym: s, .. } | Calcit::Str(s) => match find_in_fields(def_fields, &EdnKwd::from(s)) {
Some(pos) => {
values[pos] = xs[v_idx].to_owned();
}
None => return CalcitErr::err_str(format!("unexpected field {} for {:?}", s, def_fields)),
},
a => return CalcitErr::err_str(format!("expected field in string/keyword, got: {}", a)),
}
}
Ok(Calcit::Record(name.to_owned(), def_fields.to_owned(), Arc::new(values)))
} else {
CalcitErr::err_str(format!("&%{{}} expected pairs, got: {:?}", xs))
}
}
a => CalcitErr::err_str(format!("&%{{}} expected a record as prototype, got {}", a)),
}
}
pub fn record_from_map(xs: &CalcitItems) -> Result<Calcit, CalcitErr> {
if xs.len() != 2 {
return CalcitErr::err_str(format!("&record:from-map expected 2 arguments, got {:?}", xs));
}
match (&xs[0], &xs[1]) {
(Calcit::Record(name, fields, _values), Calcit::Map(ys)) => {
let mut pairs: Vec<(EdnKwd, Calcit)> = Vec::with_capacity(fields.len());
for (k, v) in ys {
match k {
Calcit::Str(s) => {
pairs.push((EdnKwd::from(s), v.to_owned()));
}
Calcit::Keyword(s) => {
pairs.push((s.to_owned(), v.to_owned()));
}
a => return CalcitErr::err_str(format!("unknown field {}", a)),
}
}
if fields.len() != pairs.len() {
return CalcitErr::err_str(format!("invalid fields {:?} for record {:?}", pairs, fields));
}
pairs.sort_by(|(a, _), (b, _)| a.cmp(b));
let mut values: Vec<Calcit> = Vec::with_capacity(fields.len());
for idx in 0..fields.len() {
let (k, v) = &pairs[idx];
if &fields[idx] == k {
values.push(v.to_owned());
} else {
return CalcitErr::err_str(format!("field mismatch: {} {} in {:?} {:?}", k, fields[idx], fields, pairs));
}
}
Ok(Calcit::Record(name.to_owned(), fields.to_owned(), Arc::new(values)))
}
(a, b) => CalcitErr::err_str(format!("&record:from-map expected a record and a map, got {} {}", a, b)),
}
}
pub fn get_record_name(xs: &CalcitItems) -> Result<Calcit, CalcitErr> {
if xs.len() != 1 {
return CalcitErr::err_str(format!("&record:get-name expected record, got: {:?}", xs));
}
match &xs[0] {
Calcit::Record(name, ..) => Ok(Calcit::Keyword(name.to_owned())),
a => CalcitErr::err_str(format!("&record:get-name expected record, got: {}", a)),
}
}
pub fn turn_map(xs: &CalcitItems) -> Result<Calcit, CalcitErr> {
if xs.len() != 1 {
return CalcitErr::err_str(format!("&record:to-map expected 1 argument, got: {:?}", xs));
}
match &xs[0] {
Calcit::Record(_name, fields, values) => {
let mut ys: rpds::HashTrieMapSync<Calcit, Calcit> = rpds::HashTrieMap::new_sync();
for idx in 0..fields.len() {
ys.insert_mut(Calcit::Keyword(fields[idx].to_owned()), values[idx].to_owned());
}
Ok(Calcit::Map(ys))
}
a => CalcitErr::err_str(format!("&record:to-map expected a record, got {}", a)),
}
}
pub fn matches(xs: &CalcitItems) -> Result<Calcit, CalcitErr> {
if xs.len() != 2 {
return CalcitErr::err_str(format!("&record:matches? expected 2 arguments, got {:?}", xs));
}
match (&xs[0], &xs[1]) {
(Calcit::Record(left, left_fields, ..), Calcit::Record(right, right_fields, ..)) => {
Ok(Calcit::Bool(left == right && left_fields == right_fields))
}
(a, b) => CalcitErr::err_str(format!("&record:matches? expected 2 records, got {} {}", a, b)),
}
}
pub fn find_in_fields(xs: &[EdnKwd], y: &EdnKwd) -> Option<usize> {
if xs.is_empty() {
return None;
}
let mut lower = 0;
let mut upper = xs.len() - 1;
while (upper - lower) > 1 {
let pos = (lower + upper) >> 1;
let v = xs[pos].to_owned();
match y.cmp(&v) {
Ordering::Less => upper = pos - 1,
Ordering::Greater => lower = pos + 1,
Ordering::Equal => return Some(pos),
}
}
match y {
_ if y == &xs[lower] => Some(lower),
_ if y == &xs[upper] => Some(upper),
_ => None,
}
}
pub fn count(xs: &CalcitItems) -> Result<Calcit, CalcitErr> {
if xs.len() != 1 {
return CalcitErr::err_str(format!("record count expected 1 argument: {:?}", xs));
}
match &xs[0] {
Calcit::Record(_name, fields, _) => Ok(Calcit::Number(fields.len() as f64)),
a => CalcitErr::err_str(format!("record count expected a record, got: {}", a)),
}
}
pub fn contains_ques(xs: &CalcitItems) -> Result<Calcit, CalcitErr> {
match (xs.get(0), xs.get(1)) {
(Some(Calcit::Record(_name, fields, _)), Some(a)) => match a {
Calcit::Str(k) | Calcit::Symbol { sym: k, .. } => Ok(Calcit::Bool(find_in_fields(fields, &EdnKwd::from(k)).is_some())),
Calcit::Keyword(k) => Ok(Calcit::Bool(find_in_fields(fields, k).is_some())),
a => CalcitErr::err_str(format!("contains? got invalid field for record: {}", a)),
},
(Some(a), ..) => CalcitErr::err_str(format!("record contains? expected a record, got: {}", a)),
(None, ..) => CalcitErr::err_str(format!("record contains? expected 2 arguments, got: {:?}", xs)),
}
}
pub fn get(xs: &CalcitItems) -> Result<Calcit, CalcitErr> {
match (xs.get(0), xs.get(1)) {
(Some(Calcit::Record(_name, fields, values)), Some(a)) => match a {
Calcit::Str(k) | Calcit::Symbol { sym: k, .. } => match find_in_fields(fields, &EdnKwd::from(k)) {
Some(idx) => Ok(values[idx].to_owned()),
None => Ok(Calcit::Nil),
},
Calcit::Keyword(k) => match find_in_fields(fields, k) {
Some(idx) => Ok(values[idx].to_owned()),
None => Ok(Calcit::Nil),
},
a => CalcitErr::err_str(format!("record field expected to be string/keyword, got {}", a)),
},
(Some(a), ..) => CalcitErr::err_str(format!("record &get expected record, got: {}", a)),
(None, ..) => CalcitErr::err_str(format!("record &get expected 2 arguments, got: {:?}", xs)),
}
}
pub fn assoc(xs: &CalcitItems) -> Result<Calcit, CalcitErr> {
match (xs.get(0), xs.get(1), xs.get(2)) {
(Some(Calcit::Record(name, fields, values)), Some(a), Some(b)) => match a {
Calcit::Str(s) | Calcit::Symbol { sym: s, .. } => match find_in_fields(fields, &EdnKwd::from(s)) {
Some(pos) => {
let mut new_values = (**values).to_owned();
new_values[pos] = b.to_owned();
Ok(Calcit::Record(name.to_owned(), fields.to_owned(), Arc::new(new_values)))
}
None => CalcitErr::err_str(format!("invalid field `{}` for {:?}", s, fields)),
},
Calcit::Keyword(s) => match find_in_fields(fields, s) {
Some(pos) => {
let mut new_values = (**values).to_owned();
new_values[pos] = b.to_owned();
Ok(Calcit::Record(name.to_owned(), fields.to_owned(), Arc::new(new_values)))
}
None => CalcitErr::err_str(format!("invalid field `{}` for {:?}", s, fields)),
},
a => CalcitErr::err_str(format!("invalid field `{}` for {:?}", a, fields)),
},
(Some(a), ..) => CalcitErr::err_str(format!("record:assoc expected a record, got: {}", a)),
(None, ..) => CalcitErr::err_str(format!("record:assoc expected 3 arguments, got: {:?}", xs)),
}
}
pub fn extend_as(xs: &CalcitItems) -> Result<Calcit, CalcitErr> {
if xs.len() != 4 {
return CalcitErr::err_str(format!("record:extend-as expected 4 arguments, got: {:?}", xs));
}
match (xs.get(0), xs.get(1), xs.get(2), xs.get(3)) {
(Some(Calcit::Record(_name, fields, values)), Some(n), Some(a), Some(new_value)) => match a {
Calcit::Str(s) | Calcit::Symbol { sym: s, .. } => match find_in_fields(fields, &EdnKwd::from(s)) {
Some(_pos) => CalcitErr::err_str(format!("field `{}` already existed", s)),
None => extend_record_field(&EdnKwd::from(s), n, fields, values, new_value),
},
Calcit::Keyword(s) => match find_in_fields(fields, s) {
Some(_pos) => CalcitErr::err_str(format!("field `{}` already existed", s)),
None => extend_record_field(s, n, fields, values, new_value),
},
a => return CalcitErr::err_str(format!("invalid field `{}` for {:?}", a, fields)),
},
(Some(a), ..) => return CalcitErr::err_str(format!("record:extend-as expected a record, got: {}", a)),
(None, ..) => return CalcitErr::err_str(format!("record:extend-as expected 4 arguments, got: {:?}", xs)),
}
}
fn extend_record_field(
idx_s: &EdnKwd,
n: &Calcit,
fields: &[EdnKwd],
values: &[Calcit],
new_value: &Calcit,
) -> Result<Calcit, CalcitErr> {
let mut next_fields: Vec<EdnKwd> = Vec::with_capacity(fields.len());
let mut next_values: Vec<Calcit> = Vec::with_capacity(fields.len());
let mut inserted: bool = false;
for (i, k) in fields.iter().enumerate() {
if inserted {
next_fields.push(k.to_owned());
next_values.push(values[i].to_owned());
} else {
match idx_s.cmp(k) {
Ordering::Less => {
next_fields.push(idx_s.to_owned());
next_values.push(new_value.to_owned());
next_fields.push(k.to_owned());
next_values.push(values[i].to_owned());
inserted = true;
}
Ordering::Greater => {
next_fields.push(k.to_owned());
next_values.push(values[i].to_owned());
}
Ordering::Equal => {
unreachable!("does not equal")
}
}
}
}
if !inserted {
next_fields.push(idx_s.to_owned());
next_values.push(new_value.to_owned());
}
let new_name_id: EdnKwd = match n {
Calcit::Str(s) | Calcit::Symbol { sym: s, .. } => EdnKwd::from(s),
Calcit::Keyword(s) => s.to_owned(),
_ => return CalcitErr::err_str("expected record name"),
};
Ok(Calcit::Record(new_name_id, Arc::new(next_fields), Arc::new(next_values)))
}