plum

#treesitter#compiler#wasm

git clone https://git.pyrossh.dev/plum

A statically typed, imperative programming language inspired by rust, python


plum-core/src/loader.rs
use std::collections::{HashMap, HashSet};
use std::path::{Path, PathBuf};

use crate::ast::{Import, Item, Source};
use crate::parser::AstParser;

/// Resolves `entry`'s `import` statements (transitively, and tolerating
/// diamond imports and cycles) against `lib_path`, and merges every reachable
/// file's top-level items into a single `Source`. The result's `module` and
/// `imports` fields are `entry`'s own (imported files' `module`/`imports`
/// fields are not otherwise used once their `items` have been merged in) —
/// only `items` accumulates across files.
///
/// `import "std/option"` resolves to `<lib_path>/std/option.plum`.
pub fn loadAndMerge(entry: &Path, lib_path: &Path) -> Result<Source, String> {
    let entry_canon = std::fs::canonicalize(entry)
        .map_err(|e| format!("cannot read entry file '{}': {}", entry.display(), e))?;
    let entry_source = parseFile(&entry_canon)?;

    let mut visited: HashSet<PathBuf> = HashSet::new();
    let mut items: Vec<Item> = Vec::new();
    let mut names: HashMap<String, PathBuf> = HashMap::new();

    visited.insert(entry_canon.clone());
    mergeItems(&entry_canon, &entry_source, &mut items, &mut names)?;
    for import in &entry_source.imports {
        loadImport(import, lib_path, &mut visited, &mut items, &mut names)?;
    }

    Ok(Source {
        module: entry_source.module,
        imports: entry_source.imports,
        items,
    })
}

fn loadImport(
    import: &Import,
    lib_path: &Path,
    visited: &mut HashSet<PathBuf>,
    items: &mut Vec<Item>,
    names: &mut HashMap<String, PathBuf>,
) -> Result<(), String> {
    let target = lib_path.join(format!("{}.plum", import.path));
    let target_canon = std::fs::canonicalize(&target)
        .map_err(|_| format!("import '{}': no such file '{}'", import.path, target.display()))?;

    if !visited.insert(target_canon.clone()) {
        // Already loaded — a diamond import or a cycle. Either way, its items
        // are already in `items`; nothing more to do.
        return Ok(());
    }

    let source = parseFile(&target_canon)?;
    mergeItems(&target_canon, &source, items, names)?;
    for nested in &source.imports {
        loadImport(nested, lib_path, visited, items, names)?;
    }
    Ok(())
}

fn parseFile(path: &Path) -> Result<Source, String> {
    let text = std::fs::read_to_string(path)
        .map_err(|e| format!("cannot read '{}': {}", path.display(), e))?;
    let mut parser = tree_sitter::Parser::new();
    parser
        .set_language(&tree_sitter_plum::LANGUAGE.into())
        .map_err(|e| format!("language error: {}", e))?;
    let tree = parser
        .parse(&text, None)
        .ok_or_else(|| format!("parse failed for '{}'", path.display()))?;
    let ap = AstParser::new(&text);
    Ok(ap.parseSource(tree.root_node()))
}

fn mergeItems(
    file: &Path,
    source: &Source,
    items: &mut Vec<Item>,
    names: &mut HashMap<String, PathBuf>,
) -> Result<(), String> {
    for item in &source.items {
        let key = itemNameKey(item);
        if let Some(existing_file) = names.get(&key) {
            return Err(format!(
                "duplicate declaration '{}': declared in both '{}' and '{}'",
                key,
                existing_file.display(),
                file.display()
            ));
        }
        names.insert(key, file.to_path_buf());
        items.push(item.clone());
    }
    Ok(())
}

/// A collision key that mirrors `plum-checker`'s own separation of
/// namespaces: methods are keyed by `(receiver, name)` (so `length<Cat>` and
/// `length<Box>` never collide, exactly like `plum_checker::MethodEnv`),
/// while classes/traits/enums/consts/free-functions are each their own
/// flat, kind-qualified namespace.
fn itemNameKey(item: &Item) -> String {
    match item {
        Item::Class(c) => format!("class::{}", c.name),
        Item::Trait(t) => format!("trait::{}", t.name),
        Item::Enum(e) => format!("enum::{}", e.name),
        Item::Const(c) => format!("const::{}", c.name),
        Item::Fn(f) => match &f.type_param {
            Some(recv) => format!("method::{}::{}", recv, f.name),
            None => format!("fn::{}", f.name),
        },
    }
}