use alloc::{boxed::Box, format, vec::Vec};
use core::str::FromStr;
use diag_lang::{Diagnostic, Label, Severity};
use pass_lang::{Outcome, Pass, PassError, PassManager};
use syntax_lang::{Node, Span, Token};
use crate::{
Error, Parse, codes,
error::Report,
grammar::{self, Grammar, Lex},
kind::Kind,
noml, parser, schematic,
};
pub type Capability = Box<dyn for<'a> Pass<Parse<'a>>>;
#[derive(Clone, Debug)]
pub struct Language {
grammar: Grammar,
}
impl Language {
pub fn from_lsf(schematic: &str) -> Result<Self, Error> {
let mut report = Report::default();
let root = match noml::read(schematic) {
Ok(root) => root,
Err(diagnostic) => {
report.diagnostic(*diagnostic);
return Err(report.into_error(schematic));
}
};
let modules = crate::spec2::modules(&root);
if let Some((_, span)) = modules.first() {
report.error_help(
codes::MODULE_NOT_FOUND,
*span,
"this sketch lists `modules`, which `Language::from_lsf` cannot load",
"forge it from a `Sketch` holding the entry and every module",
);
return Err(report.into_error_v2(schematic));
}
let spec = schematic::interpret(root, &mut report);
let format = spec.as_ref().map_or(1, |s| s.format);
if let Some(role) = spec.as_ref().and_then(|s| s.v2.as_ref()).map(|v| v.role) {
if role != crate::spec2::Role::Language {
report.error(
codes::FILE_ROLE,
Span::empty(0),
"a part or mixin cannot be forged on its own; forge the entry that lists it",
);
}
}
let grammar = spec.and_then(|spec| {
if spec.format == 2 {
let locate =
|span: Span| crate::error::line_col(schematic, span.start().to_usize());
crate::grammar2::compile(&spec, &locate, &mut report)
} else {
grammar::compile(&spec, schematic, &mut report)
}
});
Self::finish(grammar, report, format, |report| {
if format == 2 {
(report.into_error_v2(schematic), Vec::new())
} else {
(report.into_error(schematic), Vec::new())
}
})
}
pub(crate) fn finish(
grammar: Option<Grammar>,
mut report: Report,
format: u8,
fail: impl FnOnce(Report) -> (Error, Vec<Diagnostic>),
) -> Result<Self, Error> {
match grammar {
Some(mut grammar) if report.is_clean() => {
if let Some(extra) = grammar.extra.as_mut() {
let warnings = if format == 2 {
report.into_warnings_v2()
} else {
report.into_warnings()
};
extra.warnings = warnings.into();
}
Ok(Self { grammar })
}
_ => {
if report.is_clean() {
report.error(
codes::MISSING,
Span::empty(0),
"the schematic could not be forged",
);
}
Err(fail(report).0)
}
}
}
pub(crate) fn from_grammar(grammar: Grammar) -> Self {
Self { grammar }
}
pub(crate) fn tables(&self) -> &Grammar {
&self.grammar
}
#[cfg(test)]
pub(crate) fn grammar(&self) -> &Grammar {
&self.grammar
}
#[must_use]
pub fn format(&self) -> u8 {
if self.grammar.extra.is_some() { 2 } else { 1 }
}
#[inline]
#[must_use]
pub fn name(&self) -> &str {
&self.grammar.name
}
#[inline]
#[must_use]
pub fn version(&self) -> Option<&str> {
self.grammar.version.as_deref()
}
pub fn extensions(&self) -> impl ExactSizeIterator<Item = &str> {
self.grammar.extensions.iter().map(|e| &**e)
}
pub fn capabilities(&self) -> impl ExactSizeIterator<Item = &str> {
self.grammar.capabilities.iter().map(|c| &*c.name)
}
#[must_use]
pub fn kind(&self, name: &str) -> Option<Kind> {
let kinds = &self.grammar.kinds;
if self.grammar.extra.is_some() {
if let Some(node) = name.strip_prefix("kind:") {
return kinds
.all_named(node)
.find(|k| kinds.cats[k.slot()] == grammar::CAT_NODE);
}
if let Some(literal) = name
.strip_prefix('\'')
.and_then(|n| n.strip_suffix('\''))
.filter(|n| !n.is_empty())
{
return kinds
.all_named(literal)
.find(|k| kinds.cats[k.slot()] == grammar::CAT_LITERAL);
}
}
kinds.get(name)
}
#[must_use]
pub fn kind_name(&self, kind: Kind) -> &str {
self.grammar.kinds.name(kind)
}
#[must_use]
pub fn kind_count(&self) -> usize {
self.grammar.kinds.len()
}
#[must_use]
pub fn kind_at(&self, index: u16) -> Option<Kind> {
let kinds = &self.grammar.kinds;
let i = usize::from(index);
(i < kinds.len() && kinds.cats[i] != grammar::CAT_EOF).then(|| kinds.at(i))
}
#[must_use]
pub fn root_kind(&self) -> Kind {
let program = &self.grammar.program;
program.rules[program.start as usize]
.node
.unwrap_or(program.error)
}
#[must_use]
pub fn label_name(&self, label: u16) -> Option<&str> {
let extra = self.grammar.extra.as_ref()?;
extra.labels.get(usize::from(label)).map(|l| &**l)
}
#[must_use]
pub fn label_id(&self, name: &str) -> Option<u16> {
let extra = self.grammar.extra.as_ref()?;
extra
.labels
.iter()
.position(|l| &**l == name)
.map(|i| i as u16)
}
#[must_use]
pub fn field_label(&self, parent: &Node<Kind>, index: usize) -> Option<u16> {
parent.children().nth(index).and_then(|c| c.kind().label())
}
pub fn fields(&self, kind: Kind) -> impl Iterator<Item = crate::Field<'_>> {
let defs: &[grammar::FieldDef] = self
.grammar
.extra
.as_ref()
.and_then(|e| {
e.fields
.binary_search_by_key(&kind.index(), |(k, _)| *k)
.ok()
.map(|at| &*e.fields[at].1)
})
.unwrap_or(&[]);
defs.iter().map(move |d| crate::Field::new(self, d))
}
pub fn supertype(&self, name: &str) -> Option<impl ExactSizeIterator<Item = Kind> + '_> {
let extra = self.grammar.extra.as_ref()?;
let (_, members) = extra.supertypes.iter().find(|(n, _)| &**n == name)?;
Some(
members
.iter()
.map(|&i| self.grammar.kinds.at(usize::from(i))),
)
}
pub fn supertypes(&self) -> impl Iterator<Item = &str> {
self.grammar
.extra
.iter()
.flat_map(|e| e.supertypes.iter().map(|(n, _)| &**n))
}
#[must_use]
pub fn warnings(&self) -> &[Diagnostic] {
self.grammar.extra.as_ref().map_or(&[], |e| &e.warnings)
}
#[must_use]
pub fn display_name(&self) -> &str {
self.grammar
.extra
.as_ref()
.and_then(|e| e.display_name.as_deref())
.unwrap_or(&self.grammar.name)
}
#[must_use]
pub fn description(&self) -> Option<&str> {
self.grammar.extra.as_ref()?.description.as_deref()
}
#[must_use]
pub fn edition(&self) -> Option<&str> {
self.grammar.extra.as_ref()?.edition.as_deref()
}
pub fn shebang_names(&self) -> impl Iterator<Item = &str> {
self.grammar
.extra
.iter()
.flat_map(|e| e.shebang_names.iter().map(|s| &**s))
}
#[must_use]
pub fn lex(&self, source: &str) -> Vec<Token<Kind>> {
let mut tokens = Vec::new();
if u32::try_from(source.len()).is_ok() {
let mut diagnostics = Vec::new();
self.grammar
.lexer
.run(source, &mut tokens, &mut diagnostics);
}
tokens
}
#[must_use]
pub fn parse<'a>(&'a self, source: &'a str) -> Parse<'a> {
let (tree, diagnostics) = parser::parse(&self.grammar, source);
crate::inject::finish(self, source, tree, diagnostics)
}
pub fn parse_file<'a>(&'a self, extension: &str, source: &'a str) -> Parse<'a> {
let entry = self.grammar.extra.as_ref().and_then(|e| {
e.files
.iter()
.find(|(ext, _, _)| &**ext == extension)
.map(|(_, mode, start)| (*mode, *start))
});
let (Some((mode, start)), Lex::V2(scanner)) = (entry, &self.grammar.lexer) else {
return self.parse(source);
};
if u32::try_from(source.len()).is_err() {
return self.parse(source);
}
let mut tokens = Vec::new();
let mut diagnostics = Vec::new();
scanner.run_range(source, 0, source.len(), mode, &mut tokens, &mut diagnostics);
let start = if start == u32::MAX {
self.grammar.program.start
} else {
start
};
let (tree, diagnostics) =
parser::parse_tokens(&self.grammar, source, &tokens, start, diagnostics);
crate::inject::finish(self, source, tree, diagnostics)
}
pub fn pipeline<'a>(
&self,
passes: impl IntoIterator<Item = Capability>,
) -> Result<PassManager<Parse<'a>>, Error> {
let mut available: Vec<Option<Capability>> = passes.into_iter().map(Some).collect();
let mut manager = PassManager::new();
let mut problems = Vec::new();
let mut location = None;
for capability in self.grammar.capabilities.iter() {
let mut matching = available
.iter()
.enumerate()
.filter(|(_, p)| p.as_ref().is_some_and(|p| p.name() == &*capability.name))
.map(|(i, _)| i);
let (first, second) = (matching.next(), matching.next());
let problem = match (first, second) {
(Some(i), None) => {
if let Some(pass) = available[i].take() {
let _ = manager.add(Plugged(pass));
}
continue;
}
(None, _) => Diagnostic::new(
Severity::Error,
format!("capability `{}` has no pass", capability.name),
Label::unlabelled(capability.span),
)
.with_help(format!(
"pass a capability whose `name()` is \"{}\"",
capability.name
))
.with_code(codes::CAPABILITY_MISSING),
(Some(_), Some(_)) => Diagnostic::new(
Severity::Error,
format!("capability `{}` has more than one pass", capability.name),
Label::unlabelled(capability.span),
)
.with_code(codes::CAPABILITY_AMBIGUOUS),
};
if location.is_none() {
location = Some((capability.line, capability.column));
}
problems.push(problem);
}
match location {
None => Ok(manager),
Some((line, column)) => Err(Error::located(problems, line, column)),
}
}
}
impl FromStr for Language {
type Err = Error;
fn from_str(schematic: &str) -> Result<Self, Error> {
Self::from_lsf(schematic)
}
}
struct Plugged(Capability);
impl<'a> Pass<Parse<'a>> for Plugged {
fn name(&self) -> &'static str {
self.0.name()
}
fn run(&mut self, unit: &mut Parse<'a>) -> Result<Outcome, PassError> {
self.0.run(unit)
}
}