use crate::diagnostic::{Diagnostic, DiagnosticView};
use crate::parser::{run_parse, run_parse_text};
use crate::semantic::{lower_into as lower_semantic_into, SemanticModel, SemanticView};
use crate::source::{SourceFileInput, SourceStore};
use crate::span::SourceId;
use crate::tables::CstTables;
use crate::view::CstView;
use crate::workspace::ParseWorkspace;
#[derive(Debug, Clone, Copy)]
pub struct ParseOptions {
pub recovery: bool,
pub parse_semantic: bool,
pub collect_trivia: bool,
}
impl Default for ParseOptions {
fn default() -> Self {
Self {
recovery: true,
parse_semantic: false,
collect_trivia: true,
}
}
}
#[derive(Debug, Default, Clone)]
pub struct ParseResult {
pub source: SourceId,
pub cst: CstTables,
pub semantic: Option<SemanticModel>,
pub diagnostics: Vec<Diagnostic>,
}
#[derive(Debug, Clone, Copy)]
pub struct ParseSessionResult<'a> {
pub source: SourceId,
pub cst: CstView<'a>,
pub semantic: Option<SemanticView<'a>>,
pub diagnostics: DiagnosticView<'a>,
}
#[derive(Debug, Default, Clone)]
pub struct ParseInput<'a> {
pub source: &'a str,
pub path: Option<&'a str>,
pub locale: Option<&'a str>,
pub message_id: Option<&'a str>,
pub base_offset: Option<u32>,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum BatchExecution {
#[default]
Sequential,
Parallel,
}
#[derive(Debug, Clone, Copy)]
pub struct BatchParseOptions {
pub execution: BatchExecution,
pub max_threads: Option<usize>,
pub preserve_order: bool,
pub parse: ParseOptions,
}
impl Default for BatchParseOptions {
fn default() -> Self {
Self {
execution: BatchExecution::Sequential,
max_threads: None,
preserve_order: true,
parse: ParseOptions::default(),
}
}
}
#[derive(Debug, Default, Clone)]
pub struct BatchParseResult {
pub sources: SourceStore,
pub items: Vec<BatchParseItem>,
pub execution: BatchExecution,
pub degraded: bool,
}
#[derive(Debug, Default, Clone)]
pub struct BatchParseItem {
pub source: SourceId,
pub result: ParseResult,
}
pub fn parse_source(
sources: &SourceStore,
source_id: SourceId,
options: ParseOptions,
) -> ParseResult {
let mut workspace = ParseWorkspace::new();
let source_len = sources.get(source_id).map_or(0, |f| f.text.len());
workspace.reserve_for_source_len(source_len);
run_parse(sources, source_id, &mut workspace, options);
materialise_owned_workspace(sources, source_id, workspace, options)
}
pub fn parse_message(source: &str) -> ParseResult {
assert!(
u32::try_from(source.len()).is_ok(),
"source length fits in u32"
);
let source_id = SourceId::new(0);
let mut workspace = ParseWorkspace::new();
workspace.reserve_for_source_len(source.len());
run_parse_text(source, source_id, &mut workspace, ParseOptions::default());
materialise_one_shot_message(source, source_id, workspace)
}
pub fn parse_source_session<'a>(
sources: &'a SourceStore,
source_id: SourceId,
workspace: &'a mut ParseWorkspace,
options: ParseOptions,
) -> ParseSessionResult<'a> {
workspace.clear();
run_parse(sources, source_id, workspace, options);
if options.parse_semantic {
let model = workspace
.semantic
.model
.get_or_insert_with(SemanticModel::default);
lower_semantic_into(sources, source_id, &workspace.parser.tables, model);
} else {
workspace.semantic.model = None;
}
let cst = CstView::new(sources, source_id, &workspace.parser.tables);
let diagnostics = DiagnosticView {
sources,
records: &workspace.parser.diagnostics,
};
let semantic = workspace
.semantic
.model
.as_ref()
.map(|m| SemanticView::new(m, &workspace.parser.tables));
ParseSessionResult {
source: source_id,
cst,
semantic,
diagnostics,
}
}
pub fn parse_batch(inputs: &[ParseInput<'_>], options: BatchParseOptions) -> BatchParseResult {
let mut sources = SourceStore::with_capacity(inputs.len());
let mut items = Vec::with_capacity(inputs.len());
let mut workspace = ParseWorkspace::new();
for input in inputs {
let source_id = sources.add(SourceFileInput {
source: input.source,
path: input.path,
locale: input.locale,
message_id: input.message_id,
base_offset: input.base_offset,
});
workspace.clear();
workspace.reserve_for_source_len(input.source.len());
run_parse(&sources, source_id, &mut workspace, options.parse);
let result = materialise(&sources, source_id, &workspace, options.parse);
items.push(BatchParseItem {
source: source_id,
result,
});
}
let degraded = !matches!(options.execution, BatchExecution::Sequential);
BatchParseResult {
sources,
items,
execution: BatchExecution::Sequential,
degraded,
}
}
fn materialise(
sources: &SourceStore,
source_id: SourceId,
workspace: &ParseWorkspace,
options: ParseOptions,
) -> ParseResult {
let cst = workspace.parser.tables.clone();
let semantic = if options.parse_semantic {
let mut model = SemanticModel::default();
lower_semantic_into(sources, source_id, &cst, &mut model);
Some(model)
} else {
None
};
let diagnostics = DiagnosticView {
sources,
records: &workspace.parser.diagnostics,
}
.iter()
.collect();
ParseResult {
source: source_id,
cst,
semantic,
diagnostics,
}
}
fn materialise_owned_workspace(
sources: &SourceStore,
source_id: SourceId,
mut workspace: ParseWorkspace,
options: ParseOptions,
) -> ParseResult {
let cst = core::mem::take(&mut workspace.parser.tables);
let semantic = if options.parse_semantic {
let mut model = SemanticModel::default();
lower_semantic_into(sources, source_id, &cst, &mut model);
Some(model)
} else {
None
};
let diagnostics = DiagnosticView {
sources,
records: &workspace.parser.diagnostics,
}
.iter()
.collect();
ParseResult {
source: source_id,
cst,
semantic,
diagnostics,
}
}
fn materialise_one_shot_message(
source: &str,
source_id: SourceId,
mut workspace: ParseWorkspace,
) -> ParseResult {
let cst = core::mem::take(&mut workspace.parser.tables);
let diagnostics = if workspace.parser.diagnostics.is_empty() {
Vec::new()
} else {
let mut sources = SourceStore::with_capacity(1);
let actual_source_id = sources.add(SourceFileInput {
source,
..Default::default()
});
debug_assert_eq!(actual_source_id, source_id);
DiagnosticView {
sources: &sources,
records: &workspace.parser.diagnostics,
}
.iter()
.collect()
};
ParseResult {
source: source_id,
cst,
semantic: None,
diagnostics,
}
}