blues-lsp 0.1.0

LSP language server for the Bluespec SystemVerilog language
Documentation
use std::{collections::HashMap, sync::Arc};

use arcstr::Substr;
use tracing::trace;

use crate::{
    symbols::name::SymName,
    syntax::{
        buildctx::BuildCtx, cst::{
            tree::{Cst, NodeHandle},
            view::{self, ExportItemKind, StmtKind, TypedefKind, View},
        }, lexer::{bsv::BsvLexer, token::Token}, location::ROOT_ORIGIN, parser::{self, parser::Parser}, preprocessor::{
            vpp::{VppPreprocessor, VppSpan}, Preprocessor
        }, streams::TextCharStream
    },
    util::data::either::Either,
};

use super::{PkgHandle, Project, file::FileContents, querry::Queryable};

/// Results of parsing package source
pub struct Syntax {
    pub ctx: BuildCtx,
    pub vpp: SyntaxVpp,
    pub tokens: Vec<Token>, // TODO: Temp
    pub cst: Cst,
    pub top: PkgTop,
}

pub struct SyntaxVpp {
    pub spans: Vec<VppSpan>,
}

pub struct PkgTop {
    pub defs: HashMap<SymName, NodeHandle>,
    pub imports: Vec<PkgHandle>,
    pub exports: Vec<Either<Substr, PkgHandle>>,
}

impl Queryable for Syntax {
    type Input = PkgHandle;
    type Ctx = ();
    type Backref = ();

    fn compute(input: Self::Input, _ctx: &Self::Ctx, proj: &Project) -> Arc<Self> {
        let pkg = proj.get_pkg(input);
        let file = proj.file_contents(pkg.file, input);
        trace!("compiling syntax for {}", pkg);
        Arc::new(Syntax::parse(proj, input, file))
    }

    fn invalidate(&self, input: &Self::Input, _ctx: &Self::Ctx, proj: &Project) {
        proj.exports.invalidate(proj, input);
        proj.bindings.invalidate(proj, input);
    }

    fn invalidate_backref(&self, _backref: Self::Backref, _proj: &Project) {}
}

impl Syntax {
    pub fn parse(proj: &Project, pkg: PkgHandle, file: Arc<FileContents>) -> Self {
        let mut ctx = BuildCtx::new(pkg, file.clone());

        let source = file.chunks(ROOT_ORIGIN);
        let source = TextCharStream::new(source.iter());

        let mut preprocessed = Vec::new();
        let mut vpp = VppPreprocessor::new(proj, &mut ctx);
        vpp.process(source, &mut preprocessed);
        let preprocessed = TextCharStream::new(preprocessed.iter());

        let vpp = SyntaxVpp { spans: vpp.spans };

        let mut tokens = Vec::new();
        let mut lexer = BsvLexer::new(&mut ctx);
        lexer.run(preprocessed, &mut tokens);

        let tokens = tokens.clone(); // Temp

        let mut parser = Parser::new(&mut ctx, tokens.clone());
        parser::bsv::package(&mut parser);
        let nodes = parser.make_tree();
        let cst = Cst::new(nodes);

        let top = Self::compute_top(proj, pkg, &cst);

        Syntax {
            ctx,
            vpp,
            tokens,
            cst,
            top,
        }
    }

    fn compute_top(proj: &Project, pkg: PkgHandle, cst: &Cst) -> PkgTop {
        let mut top = PkgTop {
            defs: HashMap::new(),
            imports: Vec::new(),
            exports: Vec::new(),
        };

        let root = view::Package::cast(cst.root());

        let Some(body) = root.body() else {
            return top;
        };

        for stmt in body.stmts() {
            let Some(stmt) = stmt.kind() else {
                continue;
            };
            match stmt {
                StmtKind::Function(stmt_function) => {
                    let Some(name) = stmt_function.typed_name().and_then(|n| n.name()) else {
                        continue;
                    };
                    top.defs
                        .insert(SymName::Value(name.str()), stmt_function.syntax().handle());
                }
                StmtKind::Interface(intf) => {
                    intf.name().map(|n| {
                        top.defs
                            .insert(SymName::Type(n.str()), intf.syntax().handle())
                    });
                }
                StmtKind::Module(modl) => {
                    modl.name().map(|n| {
                        top.defs
                            .insert(SymName::Value(n.str()), modl.syntax().handle())
                    });
                }
                StmtKind::Import(stmt_import) => {
                    for import in stmt_import.items() {
                        let Some(name) = import.name() else { continue };
                        let Some(imported) = proj.get_pkg(pkg).resolve_import(proj, &name.str())
                        else {
                            continue;
                        };
                        top.imports.push(imported);
                    }
                }
                StmtKind::Export(stmt_export) => {
                    for export in stmt_export.items() {
                        let Some(kind) = export.kind() else { continue };
                        match kind {
                            ExportItemKind::Def(name) | ExportItemKind::Struct(name) => {
                                top.exports.push(Either::Left(name.str()));
                            }
                            ExportItemKind::Package(name) => {
                                // TODO: check for matching import
                                let Some(exported) =
                                    proj.get_pkg(pkg).resolve_import(proj, &name.str())
                                else {
                                    continue;
                                };
                                top.exports.push(Either::Right(exported));
                            }
                        }
                    }
                }
                StmtKind::Typedef(tydef) => {
                    tydef.kind().map(|kind| match kind {
                        TypedefKind::Enum(tydef) => tydef.name().map(|n| {
                            top.defs
                                .insert(SymName::Type(n.str()), tydef.syntax().handle())
                        }),
                        TypedefKind::Struct(tydef) => tydef.name().map(|n| {
                            top.defs
                                .insert(SymName::Type(n.str()), tydef.syntax().handle())
                        }),
                        TypedefKind::Union(tydef) => tydef.name().map(|n| {
                            top.defs
                                .insert(SymName::Type(n.str()), tydef.syntax().handle())
                        }),
                        TypedefKind::Alias(tydef) => tydef.name().map(|n| {
                            top.defs
                                .insert(SymName::Type(n.str()), tydef.syntax().handle())
                        }),
                    });
                }
                StmtKind::Typeclass(tyclass) => {
                    if let Some(name) = tyclass.name() {
                        top.defs
                            .insert(SymName::Type(name.str()), tyclass.syntax().handle());
                    };
                }
                StmtKind::ExprOrBind(_) => (),
                StmtKind::Instance(_) => (),
                StmtKind::Method(_) => (),
            }
        }

        top
    }

    pub fn root(&self) -> view::Package {
        view::Package::try_cast(self.cst.root()).expect("CST root node is not 'Package'")
    }

    pub fn compute_globals(&self, proj: &Project) -> HashMap<SymName, (PkgHandle, NodeHandle)> {
        let pkg = self.ctx.pkg;
        let mut defs: HashMap<SymName, (PkgHandle, NodeHandle)> = HashMap::new();

        for imported in &self.top.imports {
            let exports = proj.exports.get_as(proj, *imported, pkg);
            defs.extend(exports.defs.iter().map(|(k, v)| (k.clone(), *v)));
        }

        defs.extend(self.top.defs.iter().map(|(k, v)| (k.clone(), (pkg, *v))));
        proj.get_pkg(pkg)
            .name
            .clone()
            .map(|name| defs.insert(SymName::Pkg(name), (pkg, self.root().syntax().handle())));

        defs
    }
}