mod import;
mod task;
mod wildcard_import;
use rust_alloc::rc::Rc;
use crate::alloc::prelude::*;
use crate::alloc::{self, HashMap, Vec, VecDeque};
use crate::ast::{self, Kind, Span, Spanned};
use crate::compile::{self, ItemId, ModId, WithSpan};
use crate::grammar::{Node, Stream};
use crate::indexing::{index, index2};
use crate::macros::{MacroContext, TokenStream};
use crate::parse::Resolve;
use crate::query::{
BuiltInLiteral, BuiltInMacro2, DeferEntry, ExpandMacroBuiltin, ExpandedMacro,
GenericsParameters, ImplItem, ImplItemKind, Query, Used,
};
use crate::SourceId;
pub(crate) use self::import::{Import, ImportState};
pub(crate) use self::task::{LoadFileKind, Task};
pub(crate) use self::wildcard_import::WildcardImport;
pub(crate) struct Worker<'a, 'arena> {
pub(crate) q: Query<'a, 'arena>,
pub(crate) loaded: HashMap<ModId, (SourceId, Span)>,
pub(crate) queue: VecDeque<Task>,
}
impl<'a, 'arena> Worker<'a, 'arena> {
pub(crate) fn new(q: Query<'a, 'arena>) -> Self {
Self {
q,
loaded: HashMap::new(),
queue: VecDeque::new(),
}
}
#[tracing::instrument(skip_all)]
pub(crate) fn index(&mut self) -> alloc::Result<()> {
let mut wildcard_imports = Vec::new();
while !self.queue.is_empty() {
while let Some(task) = self.queue.pop_front() {
match task {
Task::LoadFile {
kind,
source_id,
mod_item,
mod_item_id,
} => {
let result = self.load_file(kind, source_id, mod_item, mod_item_id);
if let Err(error) = result {
self.q.diagnostics.error(source_id, error)?;
}
}
Task::ExpandImport(import) => {
tracing::trace!("expand import");
let source_id = import.source_id;
let queue = &mut self.queue;
let result = import.process(&mut self.q, &mut |task| {
queue.try_push_back(task)?;
Ok(())
});
if let Err(error) = result {
self.q.diagnostics.error(source_id, error)?;
}
}
Task::ExpandWildcardImport(wildcard_import) => {
tracing::trace!("expand wildcard import");
let source_id = wildcard_import.location.source_id;
if let Err(error) = wildcard_imports.try_push(wildcard_import) {
self.q
.diagnostics
.error(source_id, compile::Error::from(error))?;
}
}
}
}
for mut wildcard_import in wildcard_imports.drain(..) {
if let Err(error) = wildcard_import.process_local(&mut self.q) {
self.q
.diagnostics
.error(wildcard_import.location.source_id, error)?;
}
}
while let Some(entry) = self.q.next_defer_entry() {
match entry {
DeferEntry::ImplItem(item) => {
let source_id = item.location.source_id;
if let Err(error) = self.impl_item(item) {
self.q.diagnostics.error(source_id, error)?;
}
}
DeferEntry::ExpandMacroBuiltin(this) => {
let source_id = this.location.source_id;
if let Err(error) = self.expand_macro_builtin(this) {
self.q.diagnostics.error(source_id, error)?;
}
}
DeferEntry::ExpandMacroCall(this) => {
let source_id = this.location.source_id;
if let Err(error) = self.expand_macro_call(this) {
self.q.diagnostics.error(source_id, error)?;
}
}
}
}
}
Ok(())
}
#[tracing::instrument(skip_all)]
fn load_file(
&mut self,
kind: LoadFileKind,
source_id: SourceId,
mod_item: ModId,
mod_item_id: ItemId,
) -> compile::Result<()> {
let Some(source) = self.q.sources.get(source_id) else {
self.q
.diagnostics
.internal(source_id, "Missing queued source by id")?;
return Ok(());
};
let (root, is_module) = match kind {
LoadFileKind::Root => (source.path().map(|p| p.try_to_owned()).transpose()?, false),
LoadFileKind::Module { root } => (root, true),
};
macro_rules! indexer {
($tree:expr) => {{
let item = self.q.pool.module_item(mod_item);
let items = $crate::indexing::Items::new(item)?;
tracing::trace!(?item, "load file: {}", item);
$crate::indexing::Indexer {
q: self.q.borrow(),
root: root.as_deref(),
source_id,
items,
scopes: $crate::indexing::Scopes::new()?,
item: $crate::indexing::IndexItem::new(mod_item, mod_item_id),
nested_item: None,
macro_depth: 0,
loaded: Some(&mut self.loaded),
queue: Some(&mut self.queue),
tree: $tree,
}
}};
}
let as_function_body = self.q.options.function_body && !is_module;
#[allow(clippy::collapsible_else_if)]
if self.q.options.v2 {
let tree = crate::grammar::text(source_id, source.as_str()).root()?;
let tree = Rc::new(tree);
#[cfg(feature = "std")]
if self.q.options.print_tree {
tree.print_with_source(
&Span::empty(),
format_args!("Loading file (source: {source_id})"),
source.as_str(),
)?;
}
if as_function_body {
let mut idx = indexer!(&tree);
tree.parse_all(|p: &mut crate::grammar::Stream| index2::bare(&mut idx, p))?;
} else {
let mut idx = indexer!(&tree);
tree.parse_all(|p| index2::file(&mut idx, p))?;
}
} else {
if as_function_body {
let ast =
crate::parse::parse_all::<ast::EmptyBlock>(source.as_str(), source_id, true)?;
let span = Span::new(0, source.len());
let empty = Rc::default();
let mut idx = indexer!(&empty);
index::empty_block_fn(&mut idx, ast, &span)?;
} else {
let mut ast =
crate::parse::parse_all::<ast::File>(source.as_str(), source_id, true)?;
let empty = Rc::default();
let mut idx = indexer!(&empty);
index::file(&mut idx, &mut ast)?;
}
}
Ok(())
}
#[tracing::instrument(skip_all)]
fn impl_item(&mut self, this: ImplItem) -> compile::Result<()> {
macro_rules! indexer {
($tree:expr, $named:expr, $meta:expr) => {{
let items =
$crate::indexing::Items::new({ self.q.pool.item($meta.item_meta.item) })?;
$crate::indexing::Indexer {
q: self.q.borrow(),
root: this.root.as_deref(),
source_id: this.location.source_id,
items,
scopes: $crate::indexing::Scopes::new()?,
item: $crate::indexing::IndexItem::with_impl_item(
$named.module,
$named.item,
$meta.item_meta.item,
),
nested_item: this.nested_item,
macro_depth: this.macro_depth,
loaded: Some(&mut self.loaded),
queue: Some(&mut self.queue),
tree: $tree,
}
}};
}
match this.kind {
ImplItemKind::Ast { path, functions } => {
let named = self
.q
.convert_path_with(&path, true, Used::Used, Used::Unused)?;
if let Some((spanned, _)) = named.parameters.into_iter().flatten().next() {
return Err(compile::Error::new(
spanned.span(),
compile::ErrorKind::UnsupportedGenerics,
));
}
tracing::trace!(item = ?self.q.pool.item(named.item), "next impl item entry");
let meta = self.q.lookup_meta(
&this.location,
named.item,
GenericsParameters::default(),
)?;
let empty = Rc::default();
let mut idx = indexer!(&empty, named, meta);
for f in functions {
index::item_fn(&mut idx, f)?;
}
}
ImplItemKind::Node { path, functions } => {
let named =
path.parse(|p| self.q.convert_path2_with(p, true, Used::Used, Used::Unused))?;
if let Some(spanned) = named.parameters.into_iter().flatten().next() {
return Err(compile::Error::new(
spanned.span(),
compile::ErrorKind::UnsupportedGenerics,
));
}
tracing::trace!(item = ?self.q.pool.item(named.item), "next impl item entry");
let meta = self.q.lookup_meta(
&this.location,
named.item,
GenericsParameters::default(),
)?;
let mut idx = indexer!(path.tree(), named, meta);
for (id, attrs) in functions {
path.parse_id(id, |p| index2::item(&mut idx, p, attrs))?;
}
}
}
Ok(())
}
#[tracing::instrument(skip_all)]
fn expand_macro_builtin(&mut self, mut this: ExpandMacroBuiltin) -> compile::Result<()> {
let (name, stream) = this.node.parse(|p| {
let Some([ident]) = p.pump()?.nodes::<1>() else {
return Err(compile::Error::msg(&*p, "missing macro identifier"));
};
let ident = ident.ast::<ast::Ident>()?;
let name = ident.resolve(resolve_context!(self.q))?;
p.expect(K![!])?;
let close = match p.peek() {
K!['{'] => K!['}'],
K!['('] => K![')'],
token => {
return Err(compile::Error::msg(
p.peek_span(),
try_format!("expected `{{` or `(`, found {token}"),
));
}
};
p.pump()?;
let stream = p.expect(Kind::TokenStream)?;
p.expect(close)?;
Ok((name, stream))
})?;
let expanded = match name {
"file" => stream.parse(|p| self.expand_file_macro(&this, p))?,
"line" => stream.parse(|p| self.expand_line_macro(&this, p))?,
"format" => self.expand_format_macro(&this, stream)?,
"template" => {
let literal = this.literal.take();
self.expand_template_macro(&this, literal, stream)?
}
name => {
return Err(compile::Error::msg(
&this.node,
try_format!("no internal macro named `{name}`"),
));
}
};
let id = this.finish()?;
self.q
.insert_expanded_macro(id, ExpandedMacro::Builtin(expanded))?;
Ok(())
}
#[tracing::instrument(skip_all)]
fn expand_file_macro(
&mut self,
this: &ExpandMacroBuiltin,
p: &mut Stream<'_>,
) -> compile::Result<BuiltInMacro2> {
let name = self
.q
.sources
.name(this.location.source_id)
.ok_or_else(|| {
compile::Error::new(
&*p,
compile::ErrorKind::MissingSourceId {
source_id: this.location.source_id,
},
)
})?;
let id = self.q.storage.insert_str(name)?;
let value = ast::LitStr {
span: p.span(),
source: ast::StrSource::Synthetic(id),
};
Ok(BuiltInMacro2::File(value))
}
fn expand_line_macro(
&mut self,
this: &ExpandMacroBuiltin,
p: &mut Stream<'_>,
) -> compile::Result<BuiltInMacro2> {
let (l, _) = self
.q
.sources
.get(this.location.source_id)
.map(|s| s.pos_to_utf8_linecol(p.span().start.into_usize()))
.unwrap_or_default();
Ok(BuiltInMacro2::Line(l + 1))
}
fn expand_format_macro(
&mut self,
this: &ExpandMacroBuiltin,
stream: Node<'_>,
) -> compile::Result<BuiltInMacro2> {
let tree = crate::grammar::node(stream).format()?;
let tree = Rc::new(tree);
let items = crate::indexing::Items::new(self.q.pool.item(this.item.id))?;
let mut idx = crate::indexing::Indexer {
q: self.q.borrow(),
root: this.root.as_deref(),
source_id: this.location.source_id,
items,
scopes: crate::indexing::Scopes::new()?,
item: this.item,
nested_item: None,
macro_depth: this.macro_depth + 1,
loaded: Some(&mut self.loaded),
queue: Some(&mut self.queue),
tree: &tree,
};
tree.parse_all(|p| index2::any(&mut idx, p))?;
#[cfg(feature = "std")]
if self.q.options.print_tree {
tree.print(
&this.node,
format_args!("Expanded format!() macro {}", this.id),
)?;
}
Ok(BuiltInMacro2::Format(tree))
}
fn expand_template_macro(
&mut self,
this: &ExpandMacroBuiltin,
literal: BuiltInLiteral,
stream: Node<'_>,
) -> compile::Result<BuiltInMacro2> {
let tree = crate::grammar::node(stream).exprs(K![,])?;
let tree = Rc::new(tree);
let items = crate::indexing::Items::new(self.q.pool.item(this.item.id))?;
let mut idx = crate::indexing::Indexer {
q: self.q.borrow(),
root: this.root.as_deref(),
source_id: this.location.source_id,
items,
scopes: crate::indexing::Scopes::new()?,
item: this.item,
nested_item: None,
macro_depth: this.macro_depth + 1,
loaded: Some(&mut self.loaded),
queue: Some(&mut self.queue),
tree: &tree,
};
tree.parse_all(|p| index2::any(&mut idx, p))?;
#[cfg(feature = "std")]
if self.q.options.print_tree {
tree.print(
&this.node,
format_args!("Expanded template!() macro {}", this.id),
)?;
}
Ok(BuiltInMacro2::Template(tree, literal))
}
#[tracing::instrument(skip_all)]
fn expand_macro_call(&mut self, this: ExpandMacroBuiltin) -> compile::Result<()> {
if this.macro_depth >= self.q.options.max_macro_depth {
return Err(compile::Error::new(
this.node.span(),
compile::ErrorKind::MaxMacroRecursion {
depth: this.macro_depth,
max: self.q.options.max_macro_depth,
},
));
}
let item_meta = self
.q
.item_for("macro call", this.item.id)
.with_span(&this.node)?;
let (named, stream) = this.node.parse(|p| {
let named = p
.pump()?
.parse(|p| self.q.convert_path2_with(p, true, Used::Used, Used::Unused))?;
p.expect(K![!])?;
let close = match p.peek() {
K!['{'] => K!['}'],
K!['('] => K![')'],
token => {
return Err(compile::Error::msg(
p.peek_span(),
try_format!("expected `{{` or `(`, found {token}"),
));
}
};
p.pump()?;
let stream = p.expect(Kind::TokenStream)?;
p.expect(close)?;
Ok((named, stream))
})?;
if let Some(spanned) = named.parameters.into_iter().flatten().next() {
return Err(compile::Error::new(
spanned.span(),
compile::ErrorKind::UnsupportedGenerics,
));
}
let hash = self.q.pool.item_type_hash(named.item);
let Some(handler) = self.q.context.lookup_macro(hash) else {
return Err(compile::Error::new(
&this.node,
compile::ErrorKind::MissingMacro {
item: self.q.pool.item(named.item).try_to_owned()?,
},
));
};
let items = crate::indexing::Items::new(self.q.pool.item(this.item.id))?;
let mut idx = crate::indexing::Indexer {
q: self.q.borrow(),
root: this.root.as_deref(),
source_id: this.location.source_id,
items,
scopes: crate::indexing::Scopes::new()?,
item: this.item,
nested_item: None,
macro_depth: this.macro_depth + 1,
loaded: Some(&mut self.loaded),
queue: Some(&mut self.queue),
tree: this.node.tree(),
};
let mut input_stream = TokenStream::new();
for node in stream
.children()
.flat_map(|c| c.walk())
.filter(|n| n.is_empty())
{
input_stream.push(node.token())?;
}
let output_stream = {
let mut macro_context = MacroContext {
macro_span: this.node.span(),
input_span: stream.span(),
item_meta,
idx: &mut idx,
};
handler(&mut macro_context, &input_stream)?
};
let inner_tree = crate::grammar::token_stream(&output_stream).root()?;
let tree = Rc::new(inner_tree);
idx.tree = &tree;
tree.parse_all(|p| index2::any(&mut idx, p))?;
#[cfg(feature = "std")]
if self.q.options.print_tree {
tree.print(
&this.node,
format_args!("Expanded macro {} from output stream", this.id),
)?;
}
let id = this.finish()?;
self.q
.insert_expanded_macro(id, ExpandedMacro::Tree(tree))?;
Ok(())
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum ImportKind {
Local,
Global,
}