use microcad_core::hash::HashSet;
use microcad_lang_base::{
Diag, DiagHandler, DiagResult, Diagnostic, FormatTree, GetSourceLocInfoByHash, HashId, Output,
PushDiag, SourceLocInfo, SrcReferrer, TreeDisplay, TreeState,
};
use crate::{
builtin::*,
eval::*,
lower::{SingleIdentifier, ir},
model::*,
symbol::SymbolDef,
};
pub struct EvalContext {
pub root: Symbol,
sources: Sources,
pub(super) stack: Stack,
output: Box<dyn Output>,
exporters: ExporterRegistry,
importers: ImporterRegistry,
pub diag: DiagHandler,
}
impl EvalContext {
pub fn new(
resolve_context: ResolveContext,
output: Box<dyn Output>,
exporters: ExporterRegistry,
importers: ImporterRegistry,
) -> Self {
log::debug!("Creating evaluation context");
Self {
root: resolve_context.root,
sources: resolve_context.sources,
diag: resolve_context.diag,
output,
exporters,
importers,
stack: Stack::default(),
}
}
pub(crate) fn current_symbol(&self) -> Option<Symbol> {
self.stack.current_symbol()
}
pub fn from_source(
root: std::rc::Rc<ir::Source>,
builtin: Option<Symbol>,
search_paths: Vec<std::path::PathBuf>,
output: Box<dyn Output>,
exporters: ExporterRegistry,
importers: ImporterRegistry,
) -> EvalResult<Self> {
Ok(Self::new(
ResolveContext::create(root, search_paths, builtin, DiagHandler::default())?,
output,
exporters,
importers,
))
}
pub fn output(&self) -> Option<String> {
self.output.output()
}
pub fn print(&mut self, what: String) {
self.output.print(what).expect("could not write to output");
}
pub fn eval(&mut self) -> EvalResult<Model> {
if self.diag.error_count() > 0 {
log::error!("Aborting evaluation because of prior resolve errors!");
return Err(EvalError::ResolveFailed.into());
}
let model: Model = self.sources.root().eval(self)?;
log::trace!("Post-evaluation context:\n{self:?}");
log::trace!("Evaluated Model:\n{}", FormatTree(&model));
let unused = self
.root
.unused_private()
.iter()
.map(|symbol| {
(
match self.sources.get_code(&symbol) {
Ok(id) => id,
Err(_) => symbol.id().to_string(),
},
symbol.src_ref(),
)
})
.collect::<indexmap::IndexMap<_, _>>();
unused.into_iter().try_for_each(|(id, src_ref)| {
self.warning(&src_ref, EvalError::UnusedGlobalSymbol(id))
})?;
Ok(model)
}
pub(super) fn scope<T>(
&mut self,
stack_frame: StackFrame,
f: impl FnOnce(&mut EvalContext) -> T,
) -> T {
self.open(stack_frame);
let result = f(self);
let mut unused: Vec<_> = if let Some(frame) = &self.stack.current_frame() {
if let Some(locals) = frame.locals() {
locals
.iter()
.filter(|(_, symbol)| !symbol.is_used())
.filter(|(id, _)| !id.ignore())
.filter(|(_, symbol)| !symbol.src_ref().is_none())
.map(|(id, _)| id.clone())
.collect()
} else {
vec![]
}
} else {
vec![]
};
unused.sort();
unused
.iter()
.try_for_each(|id| self.warning(id, EvalError::UnusedLocal(id.clone())))
.expect("diag error");
self.close();
result
}
pub fn exporters(&self) -> &ExporterRegistry {
&self.exporters
}
pub fn search_paths(&self) -> &Vec<std::path::PathBuf> {
self.sources.search_paths()
}
pub(super) fn get_property(&self, id: &Identifier) -> EvalResult<Value> {
match self.get_model() {
Ok(model) => {
if let Some(value) = model.get_property(id) {
Ok(value.clone())
} else {
Err(EvalError::PropertyNotFound(id.clone()).into())
}
}
Err(err) => Err(err),
}
}
pub(super) fn init_property(&self, id: Identifier, value: Value) -> EvalResult<()> {
match self.get_model() {
Ok(model) => {
if let Some(previous_value) = model.borrow_mut().set_property(id.clone(), value) {
if !previous_value.is_invalid() {
return Err(EvalError::ValueAlreadyDefined {
location: id.src_ref(),
name: id.clone(),
value: previous_value.to_string(),
previous_location: id.src_ref(),
}
.into());
}
}
Ok(())
}
Err(err) => Err(err),
}
}
pub(super) fn is_init(&mut self) -> bool {
matches!(self.stack.current_frame(), Some(StackFrame::Init(_)))
}
fn lookup_property(&self, name: &ir::QualifiedName) -> EvalResult<Symbol> {
log::trace!(
"{lookup} for property {name:?}",
lookup = microcad_lang_base::mark!(LOOKUP)
);
if self.stack.current_call_name().is_some() {
if let Some(id) = name.single_identifier() {
match self.get_property(id) {
Ok(value) => {
log::trace!(
"{found} property '{name:?}'",
found = microcad_lang_base::mark!(FOUND)
);
return Ok(Symbol::new(SymbolDef::Value(id.clone(), value), None));
}
Err(err) => return Err(err),
}
}
}
log::trace!(
"{not_found} Property '{name:?}'",
not_found = microcad_lang_base::mark!(NOT_FOUND)
);
Err(EvalError::NoPropertyId(name.clone()).into())
}
fn lookup_workbench(
&self,
name: &ir::QualifiedName,
target: LookupTarget,
) -> ResolveResult<Symbol> {
if let Some(workbench) = &self.stack.current_call_name() {
log::trace!(
"{lookup} for symbol '{name:?}' in current workbench '{workbench:?}'",
lookup = microcad_lang_base::mark!(LOOKUP)
);
match self.root.lookup_within_name(name, workbench, target) {
Ok(symbol) => {
log::trace!(
"{found} symbol in current module: {symbol:?}",
found = microcad_lang_base::mark!(FOUND),
);
Ok(symbol)
}
Err(err) => {
log::trace!(
"{not_found} symbol '{name:?}': {err}",
not_found = microcad_lang_base::mark!(NOT_FOUND)
);
Err(err)
}
}
} else {
log::trace!(
"{not_found} No current workbench",
not_found = microcad_lang_base::mark!(NOT_FOUND)
);
Err(ResolveError::SymbolNotFound(name.clone()))
}
}
fn lookup_within(
&self,
name: &ir::QualifiedName,
target: LookupTarget,
) -> ResolveResult<Symbol> {
self.root.lookup_within(
name,
&self.root.search(&self.stack.current_module_name(), false)?,
target,
)
}
}
impl Locals for EvalContext {
fn set_local_value(&mut self, id: Identifier, value: Value) -> EvalResult<()> {
self.stack.set_local_value(id, value)
}
fn get_local_value(&self, id: &Identifier) -> EvalResult<Value> {
self.stack.get_local_value(id)
}
fn open(&mut self, frame: StackFrame) {
self.stack.open(frame);
}
fn close(&mut self) -> StackFrame {
self.stack.close()
}
fn fetch_symbol(&self, id: &Identifier) -> EvalResult<Symbol> {
self.stack.fetch_symbol(id)
}
fn get_model(&self) -> EvalResult<Model> {
self.stack.get_model()
}
fn current_name(&self) -> ir::QualifiedName {
self.stack.current_name()
}
}
impl Lookup<Box<EvalError>> for EvalContext {
fn lookup(&self, name: &ir::QualifiedName, target: LookupTarget) -> EvalResult<Symbol> {
log::debug!("Lookup {target} '{name:?}' (at line {:?}):", name.src_ref());
log::trace!("- lookups -------------------------------------------------------");
let results = [
("local", { self.stack.lookup(name, target) }),
("global", {
self.lookup_within(name, target).map_err(|err| err.into())
}),
("property", { self.lookup_property(name) }),
("workbench", {
self.lookup_workbench(name, target)
.map_err(|err| err.into())
}),
]
.into_iter();
log::trace!("- lookup results ------------------------------------------------");
let results = results.inspect(|(from, result)| log::trace!("{from}: {:?}", result));
let (found, mut ambiguities, mut errors) = results.fold(
(vec![], vec![], vec![]),
|(mut oks, mut ambiguities, mut errors), (origin, result)| {
match result {
Ok(symbol) => oks.push((origin, symbol)),
Err(err) => match *err {
EvalError::AmbiguousSymbol(ambiguous, others) => {
ambiguities.push((origin, EvalError::AmbiguousSymbol ( ambiguous, others )));
}
EvalError::SymbolNotFound(_)
| EvalError::LocalNotFound(_)
| EvalError::NoModelInWorkbench
| EvalError::PropertyNotFound(_)
| EvalError::NoPropertyId(_)
| EvalError::ResolveError(ResolveError::SymbolNotFound(_))
| EvalError::ResolveError(ResolveError::SymbolIsPrivate(_))
| EvalError::ResolveError(ResolveError::NulHash)
| EvalError::ResolveError(ResolveError::WrongTarget) => {},
err => errors.push((origin, err)),
}
}
(oks, ambiguities, errors)
},
);
if !errors.is_empty() {
log::error!("Unexpected errors while lookup symbol '{name:?}':");
errors
.iter()
.for_each(|(origin, err)| log::error!("Lookup ({origin}) error: {err}"));
return Err(errors.remove(0).1.into());
}
if !ambiguities.is_empty() {
log::debug!(
"{ambiguous} Symbol '{name:?}':\n{}",
ambiguities
.iter()
.map(|(origin, err)| format!("{origin}: {err}"))
.collect::<Vec<_>>()
.join("\n"),
ambiguous = microcad_lang_base::mark!(AMBIGUOUS)
);
return Err(ambiguities.remove(0).1.into());
}
let found: Vec<_> = found
.iter()
.filter(|(_, symbol)| target.matches(symbol))
.collect();
match found.first() {
Some((origin, symbol)) => {
if found.iter().all(|(_, x)| x == symbol) {
log::debug!(
"{found} symbol '{name:?}' in {origin}",
found = microcad_lang_base::mark!(FOUND!)
);
symbol.set_used();
Ok(symbol.clone())
} else {
let others: ir::QualifiedNames =
found.iter().map(|(_, symbol)| symbol.full_name()).collect();
log::debug!(
"{ambiguous} symbol '{name:?}' in {others:?}:\n{self:?}",
ambiguous = microcad_lang_base::mark!(AMBIGUOUS),
);
Err(EvalError::AmbiguousSymbol(name.clone(), others).into())
}
}
None => {
log::debug!(
"{not_found} Symbol '{name:?}'",
not_found = microcad_lang_base::mark!(NOT_FOUND!)
);
Err(EvalError::SymbolNotFound(name.clone()).into())
}
}
}
fn ambiguity_error(ambiguous: ir::QualifiedName, others: ir::QualifiedNames) -> Box<EvalError> {
EvalError::AmbiguousSymbol(ambiguous, others).into()
}
}
impl microcad_lang_base::Diag for EvalContext {
fn fmt_diagnosis(&self, f: &mut dyn std::fmt::Write) -> std::fmt::Result {
self.diag.pretty_print(f, self)
}
fn warning_count(&self) -> u32 {
self.diag.warning_count()
}
fn error_count(&self) -> u32 {
self.diag.error_count()
}
fn error_lines(&self) -> HashSet<u32> {
self.diag.error_lines()
}
fn warning_lines(&self) -> HashSet<u32> {
self.diag.warning_lines()
}
}
impl PushDiag for EvalContext {
fn push_diag(&mut self, diag: Diagnostic) -> DiagResult<()> {
self.diag.push_diag(diag)
}
}
impl GetSourceByHash for EvalContext {
fn get_by_hash(&self, hash: u64) -> ResolveResult<std::rc::Rc<ir::Source>> {
self.sources.get_by_hash(hash)
}
}
impl GetSourceLocInfoByHash for EvalContext {
fn get_source_loc_info_by_hash(&'_ self, hash: HashId) -> Option<SourceLocInfo<'_>> {
self.sources.get_source_loc_info_by_hash(hash)
}
}
impl std::fmt::Debug for EvalContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Ok(model) = self.get_model() {
write!(f, "\nModel:\n")?;
model.tree_print(f, TreeState::new_debug(4))?;
}
writeln!(f, "\nCurrent: {:?}", self.stack.current_name())?;
writeln!(f, "\nModule: {:?}", self.stack.current_module_name())?;
write!(f, "\nLocals Stack:\n{:?}", self.stack)?;
writeln!(f, "\nCall Stack:")?;
self.stack.pretty_print_call_trace(f, &self.sources)?;
writeln!(f, "\nSources:\n")?;
write!(f, "{:?}", &self.sources)?;
write!(f, "\nSymbol Table:\n")?;
self.root.tree_print(f, TreeState::new_debug(0))?;
match self.error_count() {
0 => write!(f, "No errors")?,
1 => write!(f, "1 error")?,
_ => write!(f, "{} errors", self.error_count())?,
};
match self.warning_count() {
0 => writeln!(
f,
", no warnings{}",
if self.error_count() > 0 { ":" } else { "." }
)?,
1 => writeln!(f, ", 1 warning:")?,
_ => writeln!(f, ", {} warnings:", self.warning_count())?,
};
self.fmt_diagnosis(f)?;
Ok(())
}
}
impl ImporterRegistryAccess for EvalContext {
type Error = Box<EvalError>;
fn import(
&mut self,
arg_map: &Tuple,
search_paths: &[std::path::PathBuf],
) -> Result<Value, Self::Error> {
match self.importers.import(arg_map, search_paths) {
Ok(value) => Ok(value),
Err(err) => {
self.error(arg_map, err)?;
Ok(Value::None)
}
}
}
}
impl ExporterAccess for EvalContext {
fn exporter_by_id(&self, id: &crate::Id) -> Result<std::rc::Rc<dyn Exporter>, ExportError> {
self.exporters.exporter_by_id(id)
}
fn exporter_by_filename(
&self,
filename: &std::path::Path,
) -> Result<std::rc::Rc<dyn Exporter>, ExportError> {
self.exporters.exporter_by_filename(filename)
}
}