use crate::api::{
parse_batch as run_parse_batch, parse_source as run_parse_source, BatchParseOptions,
BatchParseResult, ParseInput, ParseOptions, ParseResult, ParseSessionResult,
};
use crate::diagnostic::{Diagnostic, DiagnosticCode, MESSAGE_REF_CATALOG};
use crate::snapshot::error::SnapshotWriteError;
use crate::snapshot::format::{
checked_u32, write_u16_le, write_u32_le, write_u8, RootId, SectionKind, StringId,
DIAGNOSTIC_LABEL_RECORD_SIZE, DIAGNOSTIC_RECORD_SIZE, EDGE_KIND_NODE, EDGE_KIND_TOKEN,
EDGE_RECORD_SIZE, NODE_RECORD_SIZE, NONE_REF, TOKEN_RECORD_SIZE, TRIVIA_RECORD_SIZE,
};
use crate::snapshot::sections::{EmittedSection, SnapshotAssembler};
use crate::snapshot::string_table::StringTableBuilder;
use crate::source::{SourceFileInput, SourceStore};
use crate::span::SourceId as PhaseOneSourceId;
use crate::tables::CstTables;
#[derive(Debug, Clone, Copy)]
pub struct SnapshotOptions {
pub include_diagnostics: bool,
pub include_source_text: bool,
pub include_trivia: bool,
}
impl Default for SnapshotOptions {
fn default() -> Self {
Self {
include_diagnostics: true,
include_source_text: false,
include_trivia: true,
}
}
}
#[derive(Debug, Clone)]
pub struct SnapshotResult {
pub bytes: Vec<u8>,
pub root: RootId,
pub diagnostics: Vec<Diagnostic>,
}
#[derive(Debug, Clone)]
pub struct BatchSnapshotResult {
pub bytes: Vec<u8>,
pub roots: Vec<RootId>,
pub diagnostics: Vec<Diagnostic>,
pub execution: crate::api::BatchExecution,
pub degraded: bool,
}
pub fn parse_result_to_snapshot(
sources: &SourceStore,
result: &ParseResult,
options: SnapshotOptions,
) -> Result<SnapshotResult, SnapshotWriteError> {
let diagnostics = result.diagnostics.clone();
encode_single(sources, result.source, &result.cst, diagnostics, options)
}
#[derive(Debug, Default, Clone, Copy)]
pub struct SnapshotSourceMetadata<'a> {
pub path: Option<&'a str>,
pub locale: Option<&'a str>,
pub message_id: Option<&'a str>,
pub base_offset: Option<u32>,
}
pub fn parse_message_to_snapshot(
source: &str,
metadata: Option<SnapshotSourceMetadata<'_>>,
parse_options: ParseOptions,
snapshot_options: SnapshotOptions,
) -> Result<SnapshotResult, SnapshotWriteError> {
let mut sources = SourceStore::with_capacity(1);
let metadata = metadata.unwrap_or_default();
let input = SourceFileInput {
source,
path: metadata.path,
locale: metadata.locale,
message_id: metadata.message_id,
base_offset: metadata.base_offset,
};
let id = sources
.try_add(input)
.map_err(|_| SnapshotWriteError::SourceTooLarge)?;
let result = run_parse_source(&sources, id, parse_options);
parse_result_to_snapshot(&sources, &result, snapshot_options)
}
pub fn parse_source_to_snapshot(
sources: &SourceStore,
source_id: PhaseOneSourceId,
parse_options: ParseOptions,
snapshot_options: SnapshotOptions,
) -> Result<SnapshotResult, SnapshotWriteError> {
let result = run_parse_source(sources, source_id, parse_options);
parse_result_to_snapshot(sources, &result, snapshot_options)
}
pub fn parse_session_to_snapshot(
session: &ParseSessionResult<'_>,
options: SnapshotOptions,
) -> Result<SnapshotResult, SnapshotWriteError> {
let diagnostics: Vec<Diagnostic> = session.diagnostics.iter().collect();
encode_single(
session.cst.sources(),
session.source,
session.cst.tables(),
diagnostics,
options,
)
}
pub fn parse_batch_to_snapshot(
inputs: &[ParseInput<'_>],
batch_options: BatchParseOptions,
snapshot_options: SnapshotOptions,
) -> Result<BatchSnapshotResult, SnapshotWriteError> {
for input in inputs {
if u32::try_from(input.source.len()).is_err() {
return Err(SnapshotWriteError::SourceTooLarge);
}
}
let batch = run_parse_batch(inputs, batch_options);
parse_batch_result_to_snapshot(&batch, snapshot_options)
}
pub fn parse_batch_result_to_snapshot(
result: &BatchParseResult,
options: SnapshotOptions,
) -> Result<BatchSnapshotResult, SnapshotWriteError> {
for item in &result.items {
if item.source != item.result.source {
return Err(SnapshotWriteError::InconsistentSourceId);
}
}
let mut writer = SnapshotWriter::with_root_hint(options, result.items.len());
writer.pre_intern_root_sources(&result.sources, result.items.iter().map(|item| item.source))?;
for item in &result.items {
writer.add_root(
&result.sources,
item.source,
&item.result.cst,
&item.result.diagnostics,
)?;
}
let bytes = writer.finish(&result.sources)?;
let roots_count = checked_u32(result.items.len()).ok_or(SnapshotWriteError::TooManyRoots)?;
let roots = (0..roots_count).map(RootId::new).collect();
let diagnostic_total: usize = result
.items
.iter()
.map(|item| item.result.diagnostics.len())
.sum();
let mut diagnostics = Vec::with_capacity(diagnostic_total);
for item in &result.items {
diagnostics.extend(item.result.diagnostics.iter().cloned());
}
Ok(BatchSnapshotResult {
bytes,
roots,
diagnostics,
execution: result.execution,
degraded: result.degraded,
})
}
fn encode_single(
sources: &SourceStore,
source: PhaseOneSourceId,
cst: &CstTables,
diagnostics: Vec<Diagnostic>,
options: SnapshotOptions,
) -> Result<SnapshotResult, SnapshotWriteError> {
let mut writer = SnapshotWriter::new(options);
writer.pre_intern_root_sources(sources, [source])?;
writer.add_root(sources, source, cst, &diagnostics)?;
let bytes = writer.finish(sources)?;
Ok(SnapshotResult {
bytes,
root: RootId::new(0),
diagnostics,
})
}
struct PendingRoot {
source_local: u32,
root_node: u32,
diag_start: u32,
diag_count: u32,
}
#[derive(Debug, Clone, Copy)]
struct SourceMetaIds {
path: StringId,
locale: StringId,
message_id: StringId,
}
struct SnapshotWriter {
options: SnapshotOptions,
string_table: StringTableBuilder,
source_phase_one: Vec<PhaseOneSourceId>,
source_meta: Vec<SourceMetaIds>,
nodes_bytes: Vec<u8>,
nodes_count: u32,
edges_bytes: Vec<u8>,
edges_count: u32,
tokens_bytes: Vec<u8>,
tokens_count: u32,
trivia_bytes: Vec<u8>,
trivia_count: u32,
diagnostics_bytes: Vec<u8>,
diagnostics_count: u32,
diagnostic_labels_bytes: Vec<u8>,
diagnostic_labels_count: u32,
roots: Vec<PendingRoot>,
}
impl SnapshotWriter {
fn new(options: SnapshotOptions) -> Self {
Self::with_root_hint(options, 1)
}
fn with_root_hint(options: SnapshotOptions, root_hint: usize) -> Self {
let string_hint = root_hint.saturating_mul(3).saturating_add(16);
let data_hint = root_hint.saturating_mul(64);
Self {
options,
string_table: StringTableBuilder::with_capacity(string_hint, data_hint),
source_phase_one: Vec::with_capacity(root_hint),
source_meta: Vec::with_capacity(root_hint),
nodes_bytes: Vec::new(),
nodes_count: 0,
edges_bytes: Vec::new(),
edges_count: 0,
tokens_bytes: Vec::new(),
tokens_count: 0,
trivia_bytes: Vec::new(),
trivia_count: 0,
diagnostics_bytes: Vec::new(),
diagnostics_count: 0,
diagnostic_labels_bytes: Vec::new(),
diagnostic_labels_count: 0,
roots: Vec::with_capacity(root_hint),
}
}
fn allocate_root_source(
&mut self,
sources: &SourceStore,
root_source: PhaseOneSourceId,
) -> Result<u32, SnapshotWriteError> {
let file = sources
.get(root_source)
.ok_or(SnapshotWriteError::InvalidSourceId)?;
let local =
checked_u32(self.source_phase_one.len()).ok_or(SnapshotWriteError::TooManySources)?;
let path = self.string_table.intern_optional(file.path.as_deref())?;
let locale = self.string_table.intern_optional(file.locale.as_deref())?;
let message_id = self
.string_table
.intern_optional(file.message_id.as_deref())?;
self.source_phase_one.push(root_source);
self.source_meta.push(SourceMetaIds {
path,
locale,
message_id,
});
debug_assert_eq!(self.source_meta.len(), self.source_phase_one.len());
Ok(local)
}
fn pre_intern_root_sources<I>(
&mut self,
sources: &SourceStore,
source_ids: I,
) -> Result<(), SnapshotWriteError>
where
I: IntoIterator<Item = PhaseOneSourceId>,
{
for id in source_ids {
let file = sources.get(id).ok_or(SnapshotWriteError::InvalidSourceId)?;
self.string_table.intern_optional(file.path.as_deref())?;
self.string_table.intern_optional(file.locale.as_deref())?;
self.string_table
.intern_optional(file.message_id.as_deref())?;
}
Ok(())
}
fn add_root(
&mut self,
sources: &SourceStore,
source: PhaseOneSourceId,
cst: &CstTables,
diagnostics: &[Diagnostic],
) -> Result<(), SnapshotWriteError> {
self.nodes_bytes
.reserve(cst.node_count() * NODE_RECORD_SIZE as usize);
self.edges_bytes
.reserve(cst.edge_count() * EDGE_RECORD_SIZE as usize);
self.tokens_bytes
.reserve(cst.token_count() * TOKEN_RECORD_SIZE as usize);
if self.options.include_trivia {
self.trivia_bytes
.reserve(cst.trivia_count() * TRIVIA_RECORD_SIZE as usize);
}
if self.options.include_diagnostics {
self.diagnostics_bytes
.reserve(diagnostics.len() * DIAGNOSTIC_RECORD_SIZE as usize);
let label_total: usize = diagnostics.iter().map(|d| d.labels.len()).sum();
self.diagnostic_labels_bytes
.reserve(label_total * DIAGNOSTIC_LABEL_RECORD_SIZE as usize);
}
let source_local = self.allocate_root_source(sources, source)?;
let trivia_remap = self.emit_trivia(cst, source_local)?;
let token_remap = self.emit_tokens(cst, &trivia_remap, source_local)?;
let node_remap = self.emit_nodes_and_edges(cst, &token_remap)?;
let root_node = match cst.root_id() {
Some(parser_root) => node_remap[parser_root.index()],
None => return Err(SnapshotWriteError::MissingRoot),
};
let (diag_start, diag_count) = if self.options.include_diagnostics {
let start = self.diagnostics_count;
for diag in diagnostics {
self.emit_diagnostic(diag, source_local)?;
}
let count = self
.diagnostics_count
.checked_sub(start)
.expect("diagnostics_count only grows");
(start, count)
} else {
(0, 0)
};
let _ = node_remap;
let _ = token_remap;
let _ = trivia_remap;
self.roots.push(PendingRoot {
source_local,
root_node,
diag_start,
diag_count,
});
Ok(())
}
fn emit_trivia(
&mut self,
cst: &CstTables,
root_source_local: u32,
) -> Result<Vec<u32>, SnapshotWriteError> {
let trivia_count = cst.trivia_count();
let mut remap = Vec::with_capacity(trivia_count);
if !self.options.include_trivia || trivia_count == 0 {
remap.resize(trivia_count, NONE_REF);
return Ok(remap);
}
for trivia in &cst.trivia {
let local = self.next_trivia_id()?;
write_u16_le(&mut self.trivia_bytes, trivia.kind);
write_u16_le(&mut self.trivia_bytes, 0); write_u32_le(&mut self.trivia_bytes, trivia.span_start);
write_u32_le(&mut self.trivia_bytes, trivia.span_end);
write_u32_le(&mut self.trivia_bytes, root_source_local);
remap.push(local);
}
Ok(remap)
}
fn emit_tokens(
&mut self,
cst: &CstTables,
trivia_remap: &[u32],
root_source_local: u32,
) -> Result<Vec<u32>, SnapshotWriteError> {
let mut remap = Vec::with_capacity(cst.token_count());
for token in &cst.tokens {
let local = self.next_token_id()?;
let source_local = root_source_local;
let (leading_start, leading_count, trailing_start, trailing_count) =
if self.options.include_trivia
&& (token.leading_trivia_count != 0 || token.trailing_trivia_count != 0)
{
let lead_start_parser = token.first_trivia;
let trail_start_parser = lead_start_parser + token.leading_trivia_count as u32;
let lead_start_snap = if token.leading_trivia_count == 0 {
0
} else {
trivia_remap[lead_start_parser as usize]
};
let trail_start_snap = if token.trailing_trivia_count == 0 {
0
} else {
trivia_remap[trail_start_parser as usize]
};
(
lead_start_snap,
token.leading_trivia_count as u32,
trail_start_snap,
token.trailing_trivia_count as u32,
)
} else {
(0, 0, 0, 0)
};
write_u16_le(&mut self.tokens_bytes, token.kind);
write_u16_le(&mut self.tokens_bytes, 0); write_u32_le(&mut self.tokens_bytes, token.span_start);
write_u32_le(&mut self.tokens_bytes, token.span_end);
write_u32_le(&mut self.tokens_bytes, source_local);
write_u32_le(&mut self.tokens_bytes, leading_start);
write_u32_le(&mut self.tokens_bytes, leading_count);
write_u32_le(&mut self.tokens_bytes, trailing_start);
write_u32_le(&mut self.tokens_bytes, trailing_count);
write_u32_le(&mut self.tokens_bytes, 0); remap.push(local);
}
Ok(remap)
}
fn emit_nodes_and_edges(
&mut self,
cst: &CstTables,
token_remap: &[u32],
) -> Result<Vec<u32>, SnapshotWriteError> {
let mut remap = Vec::with_capacity(cst.node_count());
for node in &cst.nodes {
let snapshot_node_id = self.next_node_id()?;
let child_start = self.edges_count;
for edge in cst.edges_for(node) {
let snapshot_edge_id = self.next_edge_id()?;
let _ = snapshot_edge_id;
match edge.kind {
k if k == EDGE_KIND_NODE => {
let snap_id = remap[edge.ref_id as usize];
write_u16_le(&mut self.edges_bytes, EDGE_KIND_NODE);
write_u16_le(&mut self.edges_bytes, 0); write_u32_le(&mut self.edges_bytes, snap_id);
}
k if k == EDGE_KIND_TOKEN => {
let snap_id = token_remap[edge.ref_id as usize];
write_u16_le(&mut self.edges_bytes, EDGE_KIND_TOKEN);
write_u16_le(&mut self.edges_bytes, 0); write_u32_le(&mut self.edges_bytes, snap_id);
}
_ => {
return Err(SnapshotWriteError::InvalidSourceId);
}
}
}
let child_count = self
.edges_count
.checked_sub(child_start)
.expect("edges_count only grows");
write_u16_le(&mut self.nodes_bytes, node.kind);
write_u16_le(&mut self.nodes_bytes, 0); write_u32_le(&mut self.nodes_bytes, node.span_start);
write_u32_le(&mut self.nodes_bytes, node.span_end);
write_u32_le(&mut self.nodes_bytes, child_start);
write_u32_le(&mut self.nodes_bytes, child_count);
write_u32_le(&mut self.nodes_bytes, NONE_REF); remap.push(snapshot_node_id);
}
Ok(remap)
}
fn emit_diagnostic(
&mut self,
diagnostic: &Diagnostic,
root_source_local: u32,
) -> Result<(), SnapshotWriteError> {
let source_local = root_source_local;
let _ = diagnostic.source; let label_start = self.diagnostic_labels_count;
for label in &diagnostic.labels {
let label_source = root_source_local;
let _ = label.source; let msg_id = if self.options.include_diagnostics {
self.string_table.intern(label.message)?
} else {
StringId::NONE
};
self.next_diagnostic_label_id()?;
write_u32_le(&mut self.diagnostic_labels_bytes, label_source);
write_u32_le(&mut self.diagnostic_labels_bytes, label.span.start);
write_u32_le(&mut self.diagnostic_labels_bytes, label.span.end);
write_u32_le(&mut self.diagnostic_labels_bytes, msg_id.raw());
}
let label_count = self
.diagnostic_labels_count
.checked_sub(label_start)
.expect("label count only grows");
let message_id = if self.options.include_diagnostics {
self.string_table.intern(diagnostic.message)?
} else {
StringId::NONE
};
self.next_diagnostic_id()?;
write_u32_le(&mut self.diagnostics_bytes, source_local);
write_u32_le(&mut self.diagnostics_bytes, diagnostic.span.start);
write_u32_le(&mut self.diagnostics_bytes, diagnostic.span.end);
write_u8(&mut self.diagnostics_bytes, diagnostic.severity as u8);
write_u8(&mut self.diagnostics_bytes, 0); write_u16_le(&mut self.diagnostics_bytes, diagnostic.code.as_u16());
write_u32_le(&mut self.diagnostics_bytes, message_id.raw());
write_u32_le(&mut self.diagnostics_bytes, label_start);
write_u32_le(&mut self.diagnostics_bytes, label_count);
Ok(())
}
fn finish(self, sources: &SourceStore) -> Result<Vec<u8>, SnapshotWriteError> {
let Self {
options,
string_table,
source_phase_one,
source_meta,
nodes_bytes,
nodes_count,
edges_bytes,
edges_count,
tokens_bytes,
tokens_count,
trivia_bytes,
trivia_count,
diagnostics_bytes,
diagnostics_count,
diagnostic_labels_bytes,
diagnostic_labels_count,
roots,
} = self;
if roots.is_empty() {
return Err(SnapshotWriteError::MissingRoot);
}
debug_assert_eq!(source_meta.len(), source_phase_one.len());
let mut sources_bytes = Vec::with_capacity(source_phase_one.len() * 32);
let mut sources_count: u32 = 0;
let include_source_text = options.include_source_text;
let mut source_text_bytes: Vec<u8> = if include_source_text {
let mut total: u32 = 0;
for &phase_one in &source_phase_one {
let file = sources
.get(phase_one)
.ok_or(SnapshotWriteError::InvalidSourceId)?;
total = total
.checked_add(file.len())
.ok_or(SnapshotWriteError::SectionTooLarge)?;
}
Vec::with_capacity(total as usize)
} else {
Vec::new()
};
for (snapshot_local, (&phase_one, meta)) in
source_phase_one.iter().zip(source_meta.iter()).enumerate()
{
let snapshot_local =
checked_u32(snapshot_local).ok_or(SnapshotWriteError::TooManySources)?;
let file = sources
.get(phase_one)
.ok_or(SnapshotWriteError::InvalidSourceId)?;
let (text_source, text_offset, text_len) = if include_source_text {
let offset = checked_u32(source_text_bytes.len())
.ok_or(SnapshotWriteError::SectionTooLarge)?;
let len = file.len();
source_text_bytes.extend_from_slice(file.text.as_bytes());
(snapshot_local, offset, len)
} else {
(NONE_REF, 0, 0)
};
write_u32_le(&mut sources_bytes, snapshot_local);
write_u32_le(&mut sources_bytes, meta.path.raw());
write_u32_le(&mut sources_bytes, meta.locale.raw());
write_u32_le(&mut sources_bytes, meta.message_id.raw());
write_u32_le(&mut sources_bytes, file.base_offset);
write_u32_le(&mut sources_bytes, text_source);
write_u32_le(&mut sources_bytes, text_offset);
write_u32_le(&mut sources_bytes, text_len);
sources_count = sources_count
.checked_add(1)
.ok_or(SnapshotWriteError::TooManySources)?;
}
let roots_count = checked_u32(roots.len()).ok_or(SnapshotWriteError::TooManyRoots)?;
let mut roots_bytes = Vec::with_capacity(roots.len() * 16);
for root in &roots {
write_u32_le(&mut roots_bytes, root.root_node);
write_u32_le(&mut roots_bytes, root.source_local);
if options.include_diagnostics {
write_u32_le(&mut roots_bytes, root.diag_start);
write_u32_le(&mut roots_bytes, root.diag_count);
} else {
write_u32_le(&mut roots_bytes, 0);
write_u32_le(&mut roots_bytes, 0);
}
}
let offsets = string_table.offsets();
let strings_count = checked_u32(offsets.len()).ok_or(SnapshotWriteError::TooManyStrings)?;
let mut string_offsets_bytes = Vec::with_capacity(offsets.len() * 8);
for entry in offsets {
write_u32_le(&mut string_offsets_bytes, entry.offset);
write_u32_le(&mut string_offsets_bytes, entry.len);
}
let string_data = string_table.data().to_vec();
let mut assembler = SnapshotAssembler::new();
assembler.push(EmittedSection {
kind: SectionKind::Roots,
bytes: roots_bytes,
count: roots_count,
});
assembler.push(EmittedSection {
kind: SectionKind::Sources,
bytes: sources_bytes,
count: sources_count,
});
assembler.push(EmittedSection {
kind: SectionKind::Nodes,
bytes: nodes_bytes,
count: nodes_count,
});
assembler.push(EmittedSection {
kind: SectionKind::Edges,
bytes: edges_bytes,
count: edges_count,
});
assembler.push(EmittedSection {
kind: SectionKind::Tokens,
bytes: tokens_bytes,
count: tokens_count,
});
if options.include_trivia && trivia_count > 0 {
assembler.push(EmittedSection {
kind: SectionKind::Trivia,
bytes: trivia_bytes,
count: trivia_count,
});
}
if options.include_diagnostics && diagnostics_count > 0 {
assembler.push(EmittedSection {
kind: SectionKind::Diagnostics,
bytes: diagnostics_bytes,
count: diagnostics_count,
});
}
if options.include_diagnostics && diagnostic_labels_count > 0 {
assembler.push(EmittedSection {
kind: SectionKind::DiagnosticLabels,
bytes: diagnostic_labels_bytes,
count: diagnostic_labels_count,
});
}
assembler.push(EmittedSection {
kind: SectionKind::StringOffsets,
bytes: string_offsets_bytes,
count: strings_count,
});
assembler.push(EmittedSection {
kind: SectionKind::StringData,
bytes: string_data,
count: 0,
});
if include_source_text {
assembler.push(EmittedSection {
kind: SectionKind::SourceTextData,
bytes: source_text_bytes,
count: 0,
});
}
assembler.finish()
}
fn next_node_id(&mut self) -> Result<u32, SnapshotWriteError> {
let id = self.nodes_count;
self.nodes_count = self
.nodes_count
.checked_add(1)
.ok_or(SnapshotWriteError::TooManyNodes)?;
Ok(id)
}
fn next_edge_id(&mut self) -> Result<u32, SnapshotWriteError> {
let id = self.edges_count;
self.edges_count = self
.edges_count
.checked_add(1)
.ok_or(SnapshotWriteError::TooManyEdges)?;
Ok(id)
}
fn next_token_id(&mut self) -> Result<u32, SnapshotWriteError> {
let id = self.tokens_count;
self.tokens_count = self
.tokens_count
.checked_add(1)
.ok_or(SnapshotWriteError::TooManyTokens)?;
Ok(id)
}
fn next_trivia_id(&mut self) -> Result<u32, SnapshotWriteError> {
let id = self.trivia_count;
self.trivia_count = self
.trivia_count
.checked_add(1)
.ok_or(SnapshotWriteError::TooManyTrivia)?;
Ok(id)
}
fn next_diagnostic_id(&mut self) -> Result<u32, SnapshotWriteError> {
let id = self.diagnostics_count;
self.diagnostics_count = self
.diagnostics_count
.checked_add(1)
.ok_or(SnapshotWriteError::TooManyDiagnostics)?;
Ok(id)
}
fn next_diagnostic_label_id(&mut self) -> Result<u32, SnapshotWriteError> {
let id = self.diagnostic_labels_count;
self.diagnostic_labels_count = self
.diagnostic_labels_count
.checked_add(1)
.ok_or(SnapshotWriteError::TooManyDiagnosticLabels)?;
Ok(id)
}
}
#[allow(dead_code)] fn diagnostic_catalog_str(code: DiagnosticCode) -> &'static str {
code.static_message()
}
#[allow(dead_code)]
const _ASSERT_MESSAGE_REF_CATALOG: u32 = MESSAGE_REF_CATALOG;