use vize_l0::id::NodeId;
use vize_l2::{expr::ExprRef, file::FileArtifact, resolution::Occurrence};
use vize_l3::decision::{NativeFileAnalysis, dom::DomFacts, native::NativeTemplateDomAnalysis};
use super::expression::ExpressionWriter;
use super::{DomError, DomErrorKind, encode};
use crate::expr::{
AccessError, AccessProvider, AccessSpelling, EmitError, EmitErrorKind, write_expression,
};
use crate::write::{LinkSink, Writer};
pub fn emit_file<L: LinkSink>(
analysis: &NativeFileAnalysis<'_, '_>,
) -> Result<Writer<L>, DomError> {
encode(
analysis.artifact().source(),
analysis.policy(),
analysis.tables(),
analysis.dom(),
FileExpressions {
file: analysis.file(),
facts: analysis.dom(),
},
)
}
pub fn emit_template<L: LinkSink>(
analysis: &NativeTemplateDomAnalysis<'_, '_>,
) -> Result<Writer<L>, DomError> {
encode(
analysis.artifact().source(),
analysis.policy(),
analysis.tables(),
analysis.dom(),
FileExpressions {
file: analysis.file(),
facts: analysis.dom(),
},
)
}
struct FileExpressions<'s, 'owner, 'arena> {
file: &'owner FileArtifact<'arena>,
facts: Option<&'s DomFacts<'owner, 'arena>>,
}
impl ExpressionWriter for FileExpressions<'_, '_, '_> {
fn write_handler<L: LinkSink>(
&self,
writer: &mut Writer<L>,
node: NodeId,
on: &vize_l2::op::OnOp<'_>,
) -> Result<(), DomError> {
super::handler::write(writer, self.file, self.facts, node, on)
}
fn write<L: LinkSink>(
&self,
writer: &mut Writer<L>,
node: NodeId,
expression: ExprRef<'_>,
) -> Result<(), DomError> {
let fail = |kind| DomError {
node: Some(node),
span: expression.span(),
kind,
};
let ExprRef::Js(expression) = expression else {
return Err(fail(DomErrorKind::UnsupportedExpression));
};
let row = self
.facts
.and_then(|facts| facts.file_expression(node))
.ok_or_else(|| fail(DomErrorKind::MissingFileExpression))?;
let resolution = row.resolution();
if !core::ptr::eq(resolution.file(), self.file) || resolution.node() != node {
return Err(fail(DomErrorKind::FileOwnerMismatch));
}
if !resolution.scope().is_some_and(|scope| {
self.file
.scopes()
.get(scope.index() as usize)
.is_some_and(|record| record.id == scope)
}) {
return Err(fail(DomErrorKind::MissingFileScope));
}
let table = resolution
.table()
.ok_or_else(|| fail(DomErrorKind::MissingFileExpression))?;
let retained = table.expression();
let coordinates = match (retained.coordinates, expression.coordinates) {
(None, None) => true,
(Some(left), Some(right)) => core::ptr::eq(left, right),
_ => false,
};
if !core::ptr::eq(retained.ast, expression.ast)
|| !core::ptr::eq(retained.source, expression.source)
|| retained.span != expression.span
|| !coordinates
{
return Err(fail(DomErrorKind::Expression(EmitError {
span: expression.span,
kind: EmitErrorKind::SourceMismatch,
})));
}
if !expression.ast.is_literal() || !table.occurrences().is_empty() {
return Err(fail(DomErrorKind::RuntimeAccessUnavailable));
}
write_expression(writer, self.file.artifact().source(), table, &NoReferences).map_err(
|error| DomError {
node: Some(node),
span: error.span,
kind: DomErrorKind::Expression(error),
},
)
}
}
struct NoReferences;
impl AccessProvider for NoReferences {
fn spelling(&self, _: &Occurrence<'_>) -> Result<AccessSpelling<'_>, AccessError> {
Err(AccessError::MissingBinding)
}
}
#[cfg(test)]
mod tests;