bend-lang 0.2.38

A high-level, massively parallel programming language
Documentation
use crate::{
  diagnostics::{Diagnostics, WarningType},
  fun::Name,
};
use indexmap::{IndexMap, IndexSet};
use itertools::Itertools;
use std::fmt::Display;

pub mod book;
pub mod loader;
pub mod packages;

pub use loader::*;

pub type BindMap = IndexMap<Name, Name>;

#[derive(Debug, Clone, Default)]
pub struct ImportCtx {
  /// Imports declared in the program source.
  imports: Vec<Import>,

  /// Map from bound names to source package.
  map: ImportsMap,
}

impl ImportCtx {
  pub fn add_import(&mut self, import: Import) {
    self.imports.push(import);
  }

  pub fn to_imports(self) -> Vec<Import> {
    self.imports
  }

  pub fn sources(&self) -> Vec<&Name> {
    let mut names = Vec::new();
    for imps in &self.imports {
      match &imps.src {
        BoundSource::None => {}
        BoundSource::File(f) => names.push(f),
        BoundSource::Dir(v) => names.extend(v.values()),
        BoundSource::Either(_, _) => unreachable!("This should be resolved into `File` or `Dir` by now"),
      }
    }
    names
  }
}

#[derive(Debug, Clone)]
pub struct Import {
  pub path: Name,
  pub imp_type: ImportType,
  pub relative: bool,
  pub src: BoundSource,
}

impl Import {
  pub fn new(path: Name, imp_type: ImportType, relative: bool) -> Self {
    Self { path, imp_type, relative, src: BoundSource::None }
  }
}

#[derive(Debug, Clone)]
pub enum ImportType {
  Single(Name, Option<Name>),
  List(Vec<(Name, Option<Name>)>),
  Glob,
}

impl Display for ImportType {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    match self {
      ImportType::Single(n, _) => write!(f, "{n}"),
      ImportType::List(l) => write!(f, "({})", l.iter().map(|(n, _)| n).join(", ")),
      ImportType::Glob => write!(f, "*"),
    }
  }
}

#[derive(Debug, Clone)]
pub enum BoundSource {
  None,
  File(Name),
  Dir(IndexMap<Name, Name>),
  /// If the bound source is ambiguous between a file or a directory
  Either(Name, IndexMap<Name, Name>),
}

#[derive(Debug, Clone, Default)]
struct ImportsMap {
  binds: BindMap,
}

impl ImportsMap {
  pub fn contains_source(&self, s: &Name) -> bool {
    self.binds.values().contains(s)
  }

  fn add_bind(&mut self, src: &str, bind: Name, diag: &mut Diagnostics) {
    if let Some(old) = self.binds.get(&bind) {
      let warn = format!("The import '{src}' shadows the imported name '{old}'");
      diag.add_book_warning(warn, WarningType::ImportShadow);
    }

    self.binds.insert(bind, Name::new(src));
  }

  fn add_aliased_bind(&mut self, src: &Name, sub: &Name, alias: Option<&Name>, diag: &mut Diagnostics) {
    let src = format!("{}/{}", src, sub);
    let aliased = alias.unwrap_or(sub);
    self.add_bind(&src, aliased.clone(), diag);
  }

  fn add_binds(&mut self, names: &IndexSet<Name>, src: &Name, diag: &mut Diagnostics) {
    for sub in names {
      self.add_aliased_bind(src, sub, None, diag);
    }
  }

  /// Adds all names to the ImportMap in the form `alias/name`.
  /// If one of the names is equal to the file name, adds as `alias` instead.
  fn add_file_nested_binds(
    &mut self,
    src: &Name,
    file: &Name,
    alias: Option<&Name>,
    names: IndexSet<Name>,
    diag: &mut Diagnostics,
  ) {
    let aliased = alias.unwrap_or(file);

    self.add_nested_binds(src, aliased, names.iter().filter(|&n| n != file), diag);

    if names.contains(file) {
      let src = format!("{}/{}", src, file);
      self.add_bind(&src, aliased.clone(), diag);
    }
  }

  /// Adds all names to the ImportMap in the form `bind/name`.
  fn add_nested_binds<'a>(
    &mut self,
    src: &Name,
    bind: &Name,
    names: impl Iterator<Item = &'a Name>,
    diag: &mut Diagnostics,
  ) {
    for name in names {
      let src = format!("{}/{}", src, name);
      let bind = Name::new(format!("{bind}/{name}"));
      self.add_bind(&src, bind, diag);
    }
  }
}