use crate::*;
use std::hash::{DefaultHasher, Hash, Hasher};
const MECH_ERROR_HTML_PREFIX: &str = "__MECH_ERROR_HTML__:";
#[cfg(feature = "symbol_table")]
fn update_ans_symbol(value: &Value, p: &Interpreter) {
let resolved_value = match value {
Value::MutableReference(reference) => reference.borrow().clone(),
_ => value.clone(),
};
let ans_id = hash_str("ans");
let symbols = p.symbols();
let mut symbols_brrw = symbols.borrow_mut();
symbols_brrw.insert(ans_id, resolved_value, false);
symbols_brrw
.dictionary
.borrow_mut()
.insert(ans_id, "ans".to_string());
p.dictionary().borrow_mut().insert(ans_id, "ans".to_string());
}
pub fn program(program: &Program, p: &Interpreter) -> MResult<Value> {
body(&program.body, p)
}
pub fn body(body: &Body, p: &Interpreter) -> MResult<Value> {
let mut result = Ok(Value::Empty);
for sec in &body.sections {
result = Ok(section(&sec, p)?);
}
result
}
pub fn section(section: &Section, p: &Interpreter) -> MResult<Value> {
let mut result = Ok(Value::Empty);
for el in §ion.elements {
result = Ok(section_element(&el, p)?);
}
result
}
pub fn section_element(element: &SectionElement, p: &Interpreter) -> MResult<Value> {
let mut hasher = DefaultHasher::new();
let mut out = Value::Empty;
match element {
SectionElement::Prompt(x) => x.hash(&mut hasher),
SectionElement::InfoBlock(x) => x.hash(&mut hasher),
SectionElement::QuestionBlock(x) => x.hash(&mut hasher),
SectionElement::WarningBlock(x) => x.hash(&mut hasher),
SectionElement::ErrorBlock(x) => x.hash(&mut hasher),
SectionElement::IdeaBlock(x) => x.hash(&mut hasher),
SectionElement::Image(x) => x.hash(&mut hasher),
SectionElement::Float((el, _direction)) => {
return section_element(el, p);
},
SectionElement::Citation(x) => x.hash(&mut hasher),
SectionElement::Equation(x) => x.hash(&mut hasher),
SectionElement::Abstract(x) => x.hash(&mut hasher),
SectionElement::Diagram(x) => x.hash(&mut hasher),
SectionElement::MechCode(code) => {
for (c,cmmnt) in code {
out = mech_code(&c, p)?;
match cmmnt {
Some(cmmnt) => {
let cmmnt_value = comment(cmmnt, p)?;
}
None => {}
}
}
return Ok(out)
},
#[cfg(feature = "functions")]
SectionElement::FencedMechCode(block) => {
if block.config.disabled == true {
return Ok(Value::Empty);
}
let code_id = block.config.namespace;
if code_id == 0 {
out = eval_fenced_code_block(&block.code, p, false)?;
let (last_code, _) = block.code.last().unwrap();
let out_id = hash_str(&format!("{:?}", last_code));
p.out_values.borrow_mut().insert(out_id, out.clone());
} else {
let mut sub_interpreters = p.sub_interpreters.borrow_mut();
let mut new_sub_interpreter = Interpreter::new(code_id);
new_sub_interpreter.set_functions(p.functions().clone());
let mut pp = sub_interpreters
.entry(code_id)
.or_insert(Box::new(new_sub_interpreter))
.as_mut();
out = eval_fenced_code_block(&block.code, pp, true)?;
let (last_code,_) = block.code.last().unwrap();
let out_id = hash_str(&format!("{:?}", last_code));
pp.out_values.borrow_mut().insert(out_id, out.clone());
}
return Ok(out)
},
SectionElement::Subtitle(x) => x.hash(&mut hasher),
SectionElement::CodeBlock(x) => x.hash(&mut hasher),
SectionElement::Comment(par) => {
for el in par.paragraph.elements.iter() {
let (code_id,value) = match paragraph_element(&el, p) {
Ok(val) => val,
_ => continue,
};
p.out_values.borrow_mut().insert(code_id, value.clone());
}
return Ok(Value::Empty);
},
SectionElement::Footnote(x) => x.hash(&mut hasher),
SectionElement::Paragraph(x) => {
for el in x.elements.iter() {
let (code_id,value) = match paragraph_element(&el, p) {
Ok(val) => val,
_ => continue,
};
p.out_values.borrow_mut().insert(code_id, value.clone());
}
},
SectionElement::Grammar(x) => x.hash(&mut hasher),
SectionElement::Table(x) => {
for row in &x.rows {
for cell in row {
for el in &cell.elements {
let (code_id,value) = match paragraph_element(&el, p) {
Ok(val) => val,
_ => continue,
};
p.out_values.borrow_mut().insert(code_id, value.clone());
}
}
}
x.hash(&mut hasher);
},
SectionElement::QuoteBlock(x) => x.hash(&mut hasher),
SectionElement::ThematicBreak => {return Ok(Value::Empty);}
SectionElement::List(x) => x.hash(&mut hasher),
SectionElement::SuccessBlock(x) => x.hash(&mut hasher),
SectionElement::ErrorBlock(x) => x.hash(&mut hasher),
SectionElement::WarningBlock(x) => x.hash(&mut hasher),
SectionElement::InfoBlock(x) => x.hash(&mut hasher),
SectionElement::IdeaBlock(x) => x.hash(&mut hasher),
SectionElement::FigureTable(x) => {
for row in &x.rows {
for figure in row {
for el in &figure.caption.elements {
let (code_id, value) = match paragraph_element(el, p) {
Ok(val) => val,
_ => continue,
};
p.out_values.borrow_mut().insert(code_id, value.clone());
}
}
}
x.hash(&mut hasher);
},
#[cfg(feature = "mika")]
SectionElement::Mika((m,s)) => {
if let Some(mika_section) = s {
let mika_interp_id = mika_interpreter_id(p.id, m, s);
let mut sub_interpreters = p.sub_interpreters.borrow_mut();
let mut new_sub_interpreter = Interpreter::new(mika_interp_id);
new_sub_interpreter.set_functions(p.functions().clone());
let pp = sub_interpreters
.entry(mika_interp_id)
.or_insert(Box::new(new_sub_interpreter))
.as_mut();
let _ = section(&mika_section.elements, pp)?;
}
return Ok(Value::Atom(Ref::new(MechAtom::from_name(&m.to_string()))));
},
x => {return Err(MechError::new(
FeatureNotEnabledError,
Some(format!("Feature not enabled for section element: {:?}", x)),
).with_compiler_loc().with_tokens(x.tokens())
);}
};
let hash = hasher.finish();
Ok(Value::Id(hash))
}
#[cfg(feature = "functions")]
fn eval_fenced_code_block(
code: &Vec<(MechCode, Option<Comment>)>,
interpreter: &Interpreter,
isolate_errors: bool,
) -> MResult<Value> {
let mut out = Value::Empty;
for (c, cmmnt) in code {
match mech_code(c, interpreter) {
Ok(value) => out = value,
Err(err) => {
if isolate_errors {
#[cfg(feature = "pretty_print")]
return Ok(Value::String(Ref::new(format!(
"{MECH_ERROR_HTML_PREFIX}{}",
err.to_html()
))));
#[cfg(not(feature = "pretty_print"))]
return Ok(Value::String(Ref::new(format!(
"{MECH_ERROR_HTML_PREFIX}{}",
err.full_chain_message()
))));
}
return Err(err);
}
}
if let Some(cmmnt) = cmmnt {
match comment(cmmnt, interpreter) {
Ok(_) => {}
Err(err) => {
if isolate_errors {
#[cfg(feature = "pretty_print")]
return Ok(Value::String(Ref::new(format!(
"{MECH_ERROR_HTML_PREFIX}{}",
err.to_html()
))));
#[cfg(not(feature = "pretty_print"))]
return Ok(Value::String(Ref::new(format!(
"{MECH_ERROR_HTML_PREFIX}{}",
err.full_chain_message()
))));
}
return Err(err);
}
}
}
}
Ok(out)
}
fn inline_eval_id(p: &Interpreter) -> u64 {
let next_ix = {
let mut counter = p.inline_eval_counter.borrow_mut();
let current = *counter;
*counter += 1;
current
};
hash_str(&format!("inline-eval:{}:{}", p.id, next_ix))
}
#[cfg(feature = "mika")]
fn mika_interpreter_id(parent_id: u64, mika: &Mika, section: &Option<MikaSection>) -> u64 {
hash_str(&format!("mika:{}:{:?}", parent_id, (mika, section)))
}
pub fn paragraph_element(element: &ParagraphElement, p: &Interpreter) -> MResult<(u64,Value)> {
let result = match element {
ParagraphElement::EvalInlineMechCode(expr) => {
let code_id = inline_eval_id(p);
match expression(&expr, None, p) {
Ok(val) => (code_id,val),
Err(e) => (code_id,Value::Empty), _ => todo!(), }
}
_ => {return Err(MechError::new(
NotExecutableError{},
None
).with_compiler_loc().with_tokens(element.tokens())
);}
};
Ok(result)
}
pub fn comment(cmmt: &Comment, p: &Interpreter) -> MResult<Value> {
let par = &cmmt.paragraph;
for el in par.elements.iter() {
let (code_id,value) = match paragraph_element(&el, p) {
Ok(val) => val,
_ => continue,
};
p.out_values.borrow_mut().insert(code_id, value.clone());
}
Ok(Value::Empty)
}
pub fn mech_code(code: &MechCode, p: &Interpreter) -> MResult<Value> {
let out = match &code {
MechCode::Expression(expr) => expression(&expr, None, p),
MechCode::Statement(stmt) => statement(&stmt, None, p),
#[cfg(feature = "state_machines")]
MechCode::FsmSpecification(fsm_spec) => {
crate::state_machines::register_fsm_specification(fsm_spec, p)?;
Ok(Value::Empty)
},
#[cfg(not(feature = "state_machines"))]
MechCode::FsmSpecification(_) => Ok(Value::Empty),
#[cfg(feature = "state_machines")]
MechCode::FsmImplementation(fsm_impl) => {
crate::state_machines::register_fsm_implementation(fsm_impl, p)?;
Ok(Value::Empty)
},
#[cfg(feature = "functions")]
MechCode::FunctionDefine(fxn_def) => {
function_define(&fxn_def, p)?;
Ok(Value::Empty)
},
MechCode::Comment(cmmt) => comment(&cmmt, p),
x => Err(MechError::new(
FeatureNotEnabledError,
None
).with_compiler_loc().with_tokens(x.tokens())
),
}?;
#[cfg(feature = "symbol_table")]
update_ans_symbol(&out, p);
Ok(out)
}