Reserve retained parser entries at private producer boundaries
This commit is contained in:
@@ -1028,6 +1028,18 @@ fn edit_positions_from_tokens_with_text(
|
|||||||
count: usize,
|
count: usize,
|
||||||
value_ends: Option<&BTreeMap<usize, usize>>,
|
value_ends: Option<&BTreeMap<usize, usize>>,
|
||||||
text: impl FnMut(usize, Span) -> Result<(), Box<Diagnostic>>,
|
text: impl FnMut(usize, Span) -> Result<(), Box<Diagnostic>>,
|
||||||
|
) -> Result<Vec<(Id, EditDeclaration)>, Box<Diagnostic>> {
|
||||||
|
edit_positions_from_tokens_with_producers(source, unit, token, count, value_ends, None, text)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn edit_positions_from_tokens_with_producers(
|
||||||
|
source: &str,
|
||||||
|
unit: &SourceUnit,
|
||||||
|
token: impl Fn(usize) -> Lexeme,
|
||||||
|
count: usize,
|
||||||
|
value_ends: Option<&BTreeMap<usize, usize>>,
|
||||||
|
mut quota: Option<usize>,
|
||||||
|
text: impl FnMut(usize, Span) -> Result<(), Box<Diagnostic>>,
|
||||||
) -> Result<Vec<(Id, EditDeclaration)>, Box<Diagnostic>> {
|
) -> Result<Vec<(Id, EditDeclaration)>, Box<Diagnostic>> {
|
||||||
let mut ends: Vec<_> = (0..count).map(|at| token(at).span.end).collect();
|
let mut ends: Vec<_> = (0..count).map(|at| token(at).span.end).collect();
|
||||||
let mut stack = Vec::new();
|
let mut stack = Vec::new();
|
||||||
@@ -1044,10 +1056,27 @@ fn edit_positions_from_tokens_with_text(
|
|||||||
ends: Vec<usize>,
|
ends: Vec<usize>,
|
||||||
value_ends: Option<&'a BTreeMap<usize, usize>>,
|
value_ends: Option<&'a BTreeMap<usize, usize>>,
|
||||||
text: H,
|
text: H,
|
||||||
|
source: &'a str,
|
||||||
|
quota: &'a mut Option<usize>,
|
||||||
}
|
}
|
||||||
impl<F: Fn(usize) -> Lexeme, H: FnMut(usize, Span) -> Result<(), Box<Diagnostic>>>
|
impl<F: Fn(usize) -> Lexeme, H: FnMut(usize, Span) -> Result<(), Box<Diagnostic>>>
|
||||||
Positions<'_, F, H>
|
Positions<'_, F, H>
|
||||||
{
|
{
|
||||||
|
fn consume(&mut self, slots: usize, offset: usize) -> Result<(), Box<Diagnostic>> {
|
||||||
|
if let Some(remaining) = self.quota {
|
||||||
|
*remaining = remaining.checked_sub(slots).ok_or_else(|| {
|
||||||
|
Box::new(diagnostic(
|
||||||
|
self.source,
|
||||||
|
Error {
|
||||||
|
code: "invalid_source",
|
||||||
|
offset,
|
||||||
|
message: "Source8 projection quota underflow.".into(),
|
||||||
|
},
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
fn token(&self, start: usize) -> usize {
|
fn token(&self, start: usize) -> usize {
|
||||||
let mut low = 0;
|
let mut low = 0;
|
||||||
let mut high = self.ends.len();
|
let mut high = self.ends.len();
|
||||||
@@ -1068,6 +1097,7 @@ fn edit_positions_from_tokens_with_text(
|
|||||||
fields
|
fields
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(key, ast)| {
|
.map(|(key, ast)| {
|
||||||
|
self.consume(1, ast.offset())?;
|
||||||
let value = self.value(ast)?;
|
let value = self.value(ast)?;
|
||||||
let start = (self.token_at)(self.token(value.span.start) - 1).span.start;
|
let start = (self.token_at)(self.token(value.span.start) - 1).span.start;
|
||||||
let end = (self.token_at)(self.token(value.span.end)).span.end;
|
let end = (self.token_at)(self.token(value.span.end)).span.end;
|
||||||
@@ -1083,6 +1113,7 @@ fn edit_positions_from_tokens_with_text(
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
fn value(&mut self, ast: &Ast) -> Result<EditValue, Box<Diagnostic>> {
|
fn value(&mut self, ast: &Ast) -> Result<EditValue, Box<Diagnostic>> {
|
||||||
|
self.consume(1, ast.offset())?;
|
||||||
let index = self.token(ast.offset());
|
let index = self.token(ast.offset());
|
||||||
let span = Span {
|
let span = Span {
|
||||||
start: ast.offset(),
|
start: ast.offset(),
|
||||||
@@ -1119,8 +1150,11 @@ fn edit_positions_from_tokens_with_text(
|
|||||||
ends,
|
ends,
|
||||||
value_ends,
|
value_ends,
|
||||||
text,
|
text,
|
||||||
|
source,
|
||||||
|
quota: &mut quota,
|
||||||
};
|
};
|
||||||
unit.declarations
|
let result = unit
|
||||||
|
.declarations
|
||||||
.iter()
|
.iter()
|
||||||
.map(|declaration| {
|
.map(|declaration| {
|
||||||
if declaration.id_insertion.is_some() {
|
if declaration.id_insertion.is_some() {
|
||||||
@@ -1133,6 +1167,7 @@ fn edit_positions_from_tokens_with_text(
|
|||||||
},
|
},
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
positions.consume(7, declaration.span.start)?;
|
||||||
let roles: &[&str] = if matches!(declaration.kind, DeclKind::Node) {
|
let roles: &[&str] = if matches!(declaration.kind, DeclKind::Node) {
|
||||||
&["kind", "alias", "id_keyword", "id"]
|
&["kind", "alias", "id_keyword", "id"]
|
||||||
} else {
|
} else {
|
||||||
@@ -1161,7 +1196,18 @@ fn edit_positions_from_tokens_with_text(
|
|||||||
},
|
},
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
.collect()
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
if quota.is_some_and(|remaining| remaining != 0) {
|
||||||
|
return Err(Box::new(diagnostic(
|
||||||
|
source,
|
||||||
|
Error {
|
||||||
|
code: "invalid_source",
|
||||||
|
offset: 0,
|
||||||
|
message: "Source8 projection quota leftover.".into(),
|
||||||
|
},
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
Ok(result)
|
||||||
}
|
}
|
||||||
struct Parser<'a> {
|
struct Parser<'a> {
|
||||||
source: &'a str,
|
source: &'a str,
|
||||||
@@ -1337,6 +1383,10 @@ impl Parser<'_> {
|
|||||||
.span
|
.span
|
||||||
.end;
|
.end;
|
||||||
self.take(";")?;
|
self.take(";")?;
|
||||||
|
#[cfg(test)]
|
||||||
|
if let Some(mixed) = &mut self.mixed {
|
||||||
|
mixed.projection(1, Span { start, end })?;
|
||||||
|
}
|
||||||
Ok((key, value, Span { start, end }))
|
Ok((key, value, Span { start, end }))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1370,7 +1420,12 @@ impl Parser<'_> {
|
|||||||
.span
|
.span
|
||||||
.end;
|
.end;
|
||||||
if let Some(mixed) = &mut self.mixed {
|
if let Some(mixed) = &mut self.mixed {
|
||||||
mixed.value_ends.insert(ast.offset(), end);
|
let span = Span {
|
||||||
|
start: ast.offset(),
|
||||||
|
end,
|
||||||
|
};
|
||||||
|
mixed.value_end(span)?;
|
||||||
|
mixed.projection(1, span)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
@@ -1697,7 +1752,12 @@ impl Parser<'_> {
|
|||||||
let lexical_owner = open.last().map(|&owner| declarations[owner].id);
|
let lexical_owner = open.last().map(|&owner| declarations[owner].id);
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
if let Some(mixed) = &mut self.mixed {
|
if let Some(mixed) = &mut self.mixed {
|
||||||
mixed.lexical_owners.push(open.last().copied());
|
let span = Span {
|
||||||
|
start: offset,
|
||||||
|
end: body_start,
|
||||||
|
};
|
||||||
|
mixed.lexical_owner(open.last().copied(), span)?;
|
||||||
|
mixed.projection(7, span)?;
|
||||||
}
|
}
|
||||||
declarations.push(Declaration {
|
declarations.push(Declaration {
|
||||||
kind,
|
kind,
|
||||||
|
|||||||
@@ -39,6 +39,11 @@ pub struct Budget {
|
|||||||
numeric: WorkMeter,
|
numeric: WorkMeter,
|
||||||
failed: Option<Error>,
|
failed: Option<Error>,
|
||||||
text: Option<ProducerText>,
|
text: Option<ProducerText>,
|
||||||
|
entries: Option<ProducerEntries>,
|
||||||
|
}
|
||||||
|
struct ProducerEntries {
|
||||||
|
carried: usize,
|
||||||
|
metadata: usize,
|
||||||
}
|
}
|
||||||
struct ProducerText {
|
struct ProducerText {
|
||||||
carried: usize,
|
carried: usize,
|
||||||
@@ -69,8 +74,64 @@ impl Budget {
|
|||||||
),
|
),
|
||||||
failed: None,
|
failed: None,
|
||||||
text: None,
|
text: None,
|
||||||
|
entries: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
pub(super) fn enable_entry_producers(
|
||||||
|
&mut self,
|
||||||
|
carried: usize,
|
||||||
|
files: usize,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
assert!(self.entries.is_none());
|
||||||
|
self.check_producer_entries(carried, files, 0, Span::default())?;
|
||||||
|
self.entries = Some(ProducerEntries {
|
||||||
|
carried,
|
||||||
|
metadata: files,
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn check_producer_entries(
|
||||||
|
&mut self,
|
||||||
|
carried: usize,
|
||||||
|
metadata: usize,
|
||||||
|
delta: usize,
|
||||||
|
span: Span,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
self.active()?;
|
||||||
|
let total = carried
|
||||||
|
.checked_add(self.usage.entries)
|
||||||
|
.and_then(|n| n.checked_add(metadata))
|
||||||
|
.and_then(|n| n.checked_add(delta));
|
||||||
|
let message = match total {
|
||||||
|
None => Some("Source8 producer entry size overflow."),
|
||||||
|
Some(n) if n > self.limits.entries => Some("Source8 lowering entry limit exceeded."),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
if let Some(message) = message {
|
||||||
|
let failure = error(span, message);
|
||||||
|
self.failed = Some(failure.clone());
|
||||||
|
return Err(failure);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub(super) fn producer_entries(&self) -> Option<usize> {
|
||||||
|
self.entries.as_ref().map(|e| e.metadata)
|
||||||
|
}
|
||||||
|
pub(super) fn reserve_metadata(&mut self, delta: usize, span: Span) -> Result<(), Error> {
|
||||||
|
if let Some(e) = &self.entries {
|
||||||
|
self.check_producer_entries(e.carried, e.metadata, delta, span)?;
|
||||||
|
self.entries.as_mut().expect("enabled entries").metadata += delta;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub(super) fn producer_entry_overflow(&mut self, span: Span) -> Error {
|
||||||
|
if let Some(failure) = &self.failed {
|
||||||
|
return failure.clone();
|
||||||
|
}
|
||||||
|
let failure = error(span, "Source8 producer entry size overflow.");
|
||||||
|
self.failed = Some(failure.clone());
|
||||||
|
failure
|
||||||
|
}
|
||||||
pub(super) fn enable_text_producers(&mut self, carried: usize) -> Result<(), Error> {
|
pub(super) fn enable_text_producers(&mut self, carried: usize) -> Result<(), Error> {
|
||||||
self.index_bytes(carried, Span::default())?;
|
self.index_bytes(carried, Span::default())?;
|
||||||
assert!(self.text.is_none());
|
assert!(self.text.is_none());
|
||||||
@@ -200,6 +261,9 @@ impl Budget {
|
|||||||
span,
|
span,
|
||||||
"syntax entries",
|
"syntax entries",
|
||||||
)?;
|
)?;
|
||||||
|
if let Some(e) = &self.entries {
|
||||||
|
self.check_producer_entries(e.carried, e.metadata, 1, span)?;
|
||||||
|
}
|
||||||
self.usage.entries += 1;
|
self.usage.entries += 1;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,8 +68,11 @@ pub(in crate::language) struct State<'a> {
|
|||||||
budget: &'a mut Budget,
|
budget: &'a mut Budget,
|
||||||
model: &'a mut ModelCounts,
|
model: &'a mut ModelCounts,
|
||||||
typed: Vec<declarations::Declaration>,
|
typed: Vec<declarations::Declaration>,
|
||||||
pub(in crate::language) value_ends: BTreeMap<usize, usize>,
|
value_ends: BTreeMap<usize, usize>,
|
||||||
pub(in crate::language) lexical_owners: Vec<Option<usize>>,
|
lexical_owners: Vec<Option<usize>>,
|
||||||
|
pending_projection: usize,
|
||||||
|
reserved_values: usize,
|
||||||
|
reserved_owners: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ordinary_error(error: super::Error) -> Error {
|
fn ordinary_error(error: super::Error) -> Error {
|
||||||
@@ -93,6 +96,42 @@ fn lexeme(token: lex::Token) -> Lexeme {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl State<'_> {
|
impl State<'_> {
|
||||||
|
pub(in crate::language) fn projection(
|
||||||
|
&mut self,
|
||||||
|
slots: usize,
|
||||||
|
span: Span,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
if self.budget.producer_entries().is_some() {
|
||||||
|
self.pending_projection = self
|
||||||
|
.pending_projection
|
||||||
|
.checked_add(slots)
|
||||||
|
.ok_or_else(|| ordinary_error(self.budget.producer_entry_overflow(span)))?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub(in crate::language) fn value_end(&mut self, span: Span) -> Result<(), Error> {
|
||||||
|
let new_key = !self.value_ends.contains_key(&span.start);
|
||||||
|
self.budget
|
||||||
|
.reserve_metadata(usize::from(new_key), span)
|
||||||
|
.map_err(ordinary_error)?;
|
||||||
|
if new_key {
|
||||||
|
self.reserved_values += 1;
|
||||||
|
}
|
||||||
|
self.value_ends.insert(span.start, span.end);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub(in crate::language) fn lexical_owner(
|
||||||
|
&mut self,
|
||||||
|
owner: Option<usize>,
|
||||||
|
span: Span,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
self.budget
|
||||||
|
.reserve_metadata(1, span)
|
||||||
|
.map_err(ordinary_error)?;
|
||||||
|
self.lexical_owners.push(owner);
|
||||||
|
self.reserved_owners += 1;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
pub(in crate::language) fn own_text(&mut self, bytes: usize, span: Span) -> Result<(), Error> {
|
pub(in crate::language) fn own_text(&mut self, bytes: usize, span: Span) -> Result<(), Error> {
|
||||||
self.budget.own_text(bytes, span).map_err(ordinary_error)
|
self.budget.own_text(bytes, span).map_err(ordinary_error)
|
||||||
}
|
}
|
||||||
@@ -263,7 +302,7 @@ fn parse_with_profile(
|
|||||||
limits: ProjectLimits,
|
limits: ProjectLimits,
|
||||||
profile: Limits,
|
profile: Limits,
|
||||||
) -> Result<ParsedMixedSyntax<'_>, Vec<SourceDiagnostic>> {
|
) -> Result<ParsedMixedSyntax<'_>, Vec<SourceDiagnostic>> {
|
||||||
parse_impl(bundle, limits, profile, None, false)
|
parse_impl(bundle, limits, profile, None, false, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_impl<'s>(
|
fn parse_impl<'s>(
|
||||||
@@ -272,6 +311,7 @@ fn parse_impl<'s>(
|
|||||||
profile: Limits,
|
profile: Limits,
|
||||||
carried: Option<carried::CarriedGuard<'s>>,
|
carried: Option<carried::CarriedGuard<'s>>,
|
||||||
text_producers: bool,
|
text_producers: bool,
|
||||||
|
entry_producers: bool,
|
||||||
) -> Result<ParsedMixedSyntax<'s>, Vec<SourceDiagnostic>> {
|
) -> Result<ParsedMixedSyntax<'s>, Vec<SourceDiagnostic>> {
|
||||||
let entry_source = bundle.files.get(&bundle.entry).map_or("", String::as_str);
|
let entry_source = bundle.files.get(&bundle.entry).map_or("", String::as_str);
|
||||||
let at_entry = |error| located(&bundle.entry, entry_source, ordinary_error(error));
|
let at_entry = |error| located(&bundle.entry, entry_source, ordinary_error(error));
|
||||||
@@ -313,7 +353,14 @@ fn parse_impl<'s>(
|
|||||||
.index_bytes(bytes, Span::default())
|
.index_bytes(bytes, Span::default())
|
||||||
.map_err(at_entry)?;
|
.map_err(at_entry)?;
|
||||||
let mut metadata = bundle.files.len();
|
let mut metadata = bundle.files.len();
|
||||||
if let Some(input) = &carried {
|
if entry_producers {
|
||||||
|
let input = carried
|
||||||
|
.as_ref()
|
||||||
|
.expect("entry mode requires carried authority");
|
||||||
|
budget
|
||||||
|
.enable_entry_producers(input.slots().entries, metadata)
|
||||||
|
.map_err(at_entry)?;
|
||||||
|
} else if let Some(input) = &carried {
|
||||||
input.check(&budget, 0, metadata).map_err(at_entry)?;
|
input.check(&budget, 0, metadata).map_err(at_entry)?;
|
||||||
} else {
|
} else {
|
||||||
budget
|
budget
|
||||||
@@ -337,6 +384,7 @@ fn parse_impl<'s>(
|
|||||||
.expect("traversal checked source");
|
.expect("traversal checked source");
|
||||||
let failure = |error| located(path, source, ordinary_error(error));
|
let failure = |error| located(path, source, ordinary_error(error));
|
||||||
let tokens = lex::tokens(source, &mut budget).map_err(failure)?;
|
let tokens = lex::tokens(source, &mut budget).map_err(failure)?;
|
||||||
|
let metadata_before = budget.producer_entries();
|
||||||
let mut parser = Parser {
|
let mut parser = Parser {
|
||||||
source,
|
source,
|
||||||
tokens: Vec::new(),
|
tokens: Vec::new(),
|
||||||
@@ -351,6 +399,9 @@ fn parse_impl<'s>(
|
|||||||
typed: Vec::new(),
|
typed: Vec::new(),
|
||||||
value_ends: BTreeMap::new(),
|
value_ends: BTreeMap::new(),
|
||||||
lexical_owners: Vec::new(),
|
lexical_owners: Vec::new(),
|
||||||
|
pending_projection: 0,
|
||||||
|
reserved_values: 0,
|
||||||
|
reserved_owners: 0,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
let ordinary = parser
|
let ordinary = parser
|
||||||
@@ -360,7 +411,19 @@ fn parse_impl<'s>(
|
|||||||
drop(parser);
|
drop(parser);
|
||||||
// Projected ordinary AST nodes/fields are bounded before constructing a
|
// Projected ordinary AST nodes/fields are bounded before constructing a
|
||||||
// second position representation. Count references as slots, not syntax.
|
// second position representation. Count references as slots, not syntax.
|
||||||
let projection_slots = ordinary_position_slots(&ordinary);
|
let projection_slots = if entry_producers {
|
||||||
|
let slots = tokens
|
||||||
|
.len()
|
||||||
|
.checked_add(state.pending_projection)
|
||||||
|
.ok_or_else(|| failure(state.budget.producer_entry_overflow(Span::default())))?;
|
||||||
|
state
|
||||||
|
.budget
|
||||||
|
.reserve_metadata(slots, Span::default())
|
||||||
|
.map_err(failure)?;
|
||||||
|
state.pending_projection
|
||||||
|
} else {
|
||||||
|
ordinary_position_slots(&ordinary)
|
||||||
|
};
|
||||||
metadata = metadata
|
metadata = metadata
|
||||||
.checked_add(tokens.len())
|
.checked_add(tokens.len())
|
||||||
.and_then(|n| n.checked_add(state.value_ends.len()))
|
.and_then(|n| n.checked_add(state.value_ends.len()))
|
||||||
@@ -377,12 +440,13 @@ fn parse_impl<'s>(
|
|||||||
.lowering_entries(metadata, Span::default())
|
.lowering_entries(metadata, Span::default())
|
||||||
.map_err(failure)?;
|
.map_err(failure)?;
|
||||||
}
|
}
|
||||||
let positions = language::edit_positions_from_tokens_with_text(
|
let positions = language::edit_positions_from_tokens_with_producers(
|
||||||
source,
|
source,
|
||||||
&ordinary,
|
&ordinary,
|
||||||
|at| lexeme(tokens[at]),
|
|at| lexeme(tokens[at]),
|
||||||
tokens.len(),
|
tokens.len(),
|
||||||
Some(&state.value_ends),
|
Some(&state.value_ends),
|
||||||
|
entry_producers.then_some(projection_slots),
|
||||||
|bytes, span| {
|
|bytes, span| {
|
||||||
state
|
state
|
||||||
.budget
|
.budget
|
||||||
@@ -397,6 +461,23 @@ fn parse_impl<'s>(
|
|||||||
related: Vec::new(),
|
related: Vec::new(),
|
||||||
}]
|
}]
|
||||||
})?;
|
})?;
|
||||||
|
if let Some(before) = metadata_before {
|
||||||
|
let expected = tokens
|
||||||
|
.len()
|
||||||
|
.checked_add(state.value_ends.len())
|
||||||
|
.and_then(|n| n.checked_add(state.lexical_owners.len()))
|
||||||
|
.and_then(|n| n.checked_add(projection_slots))
|
||||||
|
.and_then(|n| n.checked_add(before));
|
||||||
|
if state.reserved_values != state.value_ends.len()
|
||||||
|
|| state.reserved_owners != state.lexical_owners.len()
|
||||||
|
|| expected != state.budget.producer_entries()
|
||||||
|
{
|
||||||
|
return Err(failure(super::error(
|
||||||
|
Span::default(),
|
||||||
|
"Source8 producer entry inventory mismatch.",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
for (raw, span) in &ordinary.includes {
|
for (raw, span) in &ordinary.includes {
|
||||||
traversal.include(path, raw, *span)?;
|
traversal.include(path, raw, *span)?;
|
||||||
}
|
}
|
||||||
@@ -421,6 +502,12 @@ fn parse_impl<'s>(
|
|||||||
}
|
}
|
||||||
traversal.finish()?;
|
traversal.finish()?;
|
||||||
budget.active().map_err(at_entry)?;
|
budget.active().map_err(at_entry)?;
|
||||||
|
if budget.producer_entries().is_some_and(|m| m != metadata) {
|
||||||
|
return Err(at_entry(super::error(
|
||||||
|
Span::default(),
|
||||||
|
"Source8 producer entry inventory mismatch.",
|
||||||
|
)));
|
||||||
|
}
|
||||||
if let Some((_, temporary)) = budget.producer_text() {
|
if let Some((_, temporary)) = budget.producer_text() {
|
||||||
assert_eq!(temporary, 0, "completed syntax has no temporary text");
|
assert_eq!(temporary, 0, "completed syntax has no temporary text");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ fn parse_carried(input: CarriedInput<'_>) -> Result<ParsedMixedSyntax<'_>, Vec<S
|
|||||||
policy.limits,
|
policy.limits,
|
||||||
Some(guard),
|
Some(guard),
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,6 +107,23 @@ fn parse_text_producers(
|
|||||||
policy.limits,
|
policy.limits,
|
||||||
Some(guard),
|
Some(guard),
|
||||||
true,
|
true,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Explicit retained text and entry producer fixture route.
|
||||||
|
#[cfg(test)]
|
||||||
|
fn parse_entry_producers(
|
||||||
|
input: CarriedInput<'_>,
|
||||||
|
) -> Result<ParsedMixedSyntax<'_>, Vec<SourceDiagnostic>> {
|
||||||
|
let (bundle, policy, guard) = input.into_parts();
|
||||||
|
parse_impl(
|
||||||
|
bundle,
|
||||||
|
policy.project_limits,
|
||||||
|
policy.limits,
|
||||||
|
Some(guard),
|
||||||
|
true,
|
||||||
|
true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,3 +181,6 @@ mod tests;
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod text_producer;
|
mod text_producer;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod entry_producer;
|
||||||
|
|||||||
@@ -0,0 +1,443 @@
|
|||||||
|
//! Literal entry oracles, independently authored before the producer implementation.
|
||||||
|
use super::*;
|
||||||
|
const LIMIT: &str = "Source8 lowering entry limit exceeded.";
|
||||||
|
fn doc(body: &str) -> String {
|
||||||
|
format!(
|
||||||
|
"arcadia 8;\nproject p \"Project\" id \"00000000-0000-0000-0000-000000000001\" {{\n{body}\n}}\n"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn bundle(body: &str) -> SourceBundle {
|
||||||
|
SourceBundle {
|
||||||
|
entry: "main.arcadia".into(),
|
||||||
|
files: BTreeMap::from([("main.arcadia".into(), doc(body))]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn chain() -> SourceBundle {
|
||||||
|
let mut b = bundle("include \"part.arcadia\";");
|
||||||
|
b.files.insert(
|
||||||
|
"part.arcadia".into(),
|
||||||
|
"include \"last.arcadia\";\r\n".into(),
|
||||||
|
);
|
||||||
|
b.files.insert("last.arcadia".into(), "//é\r\n".into());
|
||||||
|
b
|
||||||
|
}
|
||||||
|
fn held(bundle: SourceBundle, entries: usize) -> RetainedSnapshot {
|
||||||
|
RetainedSnapshot::fixture(
|
||||||
|
bundle,
|
||||||
|
SnapshotPolicy {
|
||||||
|
limits: Limits {
|
||||||
|
entries,
|
||||||
|
bytes: 4096,
|
||||||
|
..Limits::source8(ParseLimits::UNLIMITED)
|
||||||
|
},
|
||||||
|
project_limits: ProjectLimits::default(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
fn failed(result: Result<ParsedMixedSyntax<'_>, Vec<SourceDiagnostic>>) -> SourceDiagnostic {
|
||||||
|
let errors = match result {
|
||||||
|
Ok(_) => panic!("unexpected syntax success"),
|
||||||
|
Err(e) => e,
|
||||||
|
};
|
||||||
|
assert_eq!(errors.len(), 1);
|
||||||
|
errors.into_iter().next().unwrap()
|
||||||
|
}
|
||||||
|
fn diagnostic(d: &SourceDiagnostic, path: &str, start: usize, end: usize) {
|
||||||
|
assert_eq!(d.path, path);
|
||||||
|
assert_eq!(d.diagnostic.code, "invalid_source");
|
||||||
|
assert_eq!(d.diagnostic.message, LIMIT);
|
||||||
|
assert_eq!(d.diagnostic.span, Span { start, end });
|
||||||
|
assert!(d.related.is_empty());
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn real_owner_syntax_and_resolver_boundaries_are_separate() {
|
||||||
|
for (b, c, u, m, syntax_cap, resolve_cap, refused_path) in [
|
||||||
|
(bundle(""), 1, 1, 11, 13, 17, "main.arcadia"),
|
||||||
|
(chain(), 3, 3, 19, 25, 29, "part.arcadia"),
|
||||||
|
] {
|
||||||
|
let owner = held(b.clone(), syntax_cap);
|
||||||
|
let syntax = parse_entry_producers(owner.input()).unwrap();
|
||||||
|
assert_eq!(owner.retained.entries, c);
|
||||||
|
assert_eq!(syntax.budget.usage.entries, u);
|
||||||
|
assert_eq!(syntax.budget.producer_entries(), Some(m));
|
||||||
|
assert_eq!(syntax.metadata, m);
|
||||||
|
assert_eq!(
|
||||||
|
project::text_producer_inventory(&syntax).unwrap().entries,
|
||||||
|
c + u + m
|
||||||
|
);
|
||||||
|
drop(syntax);
|
||||||
|
let low = held(b.clone(), syntax_cap - 1);
|
||||||
|
diagnostic(
|
||||||
|
&failed(parse_entry_producers(low.input())),
|
||||||
|
refused_path,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
for cap in [resolve_cap, resolve_cap - 1] {
|
||||||
|
let owner = held(b.clone(), cap);
|
||||||
|
let syntax = parse_entry_producers(owner.input()).unwrap();
|
||||||
|
let usage = syntax.budget.usage;
|
||||||
|
let numeric = syntax.budget.numeric_units();
|
||||||
|
match project::resolve(syntax) {
|
||||||
|
Ok(result) => {
|
||||||
|
assert_eq!(cap, resolve_cap);
|
||||||
|
assert_eq!(result.phase_state().1.entries, c + u + m);
|
||||||
|
assert_eq!(result.phase_state().0.budget.usage, usage);
|
||||||
|
assert_eq!(result.phase_state().0.budget.numeric_units(), numeric);
|
||||||
|
let text =
|
||||||
|
project::resolve(parse_text_producers(owner.input()).unwrap()).unwrap();
|
||||||
|
assert_eq!(result.project(), text.project());
|
||||||
|
assert_eq!(result.warnings(), text.warnings());
|
||||||
|
assert_eq!(result.phase_state().1, text.phase_state().1);
|
||||||
|
}
|
||||||
|
Err(failure) => {
|
||||||
|
assert_eq!(cap, resolve_cap - 1);
|
||||||
|
assert_eq!(failure.diagnostics.len(), 1);
|
||||||
|
diagnostic(&failure.diagnostics[0], "main.arcadia", 11, 73);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_eq!(owner.bundle, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn owner_and_value_refuse_at_the_original_producer() {
|
||||||
|
for (body, cap, offset, complete) in [
|
||||||
|
(
|
||||||
|
"oa_activity a \"A\" id \"00000000-0000-0000-0000-000000000002\" { }",
|
||||||
|
4,
|
||||||
|
75,
|
||||||
|
29,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"oa_activity a \"A\" id \"00000000-0000-0000-0000-000000000002\" { description \"\"; }",
|
||||||
|
7,
|
||||||
|
149,
|
||||||
|
37,
|
||||||
|
),
|
||||||
|
] {
|
||||||
|
let owner = held(bundle(body), cap);
|
||||||
|
diagnostic(
|
||||||
|
&failed(parse_entry_producers(owner.input())),
|
||||||
|
"main.arcadia",
|
||||||
|
offset,
|
||||||
|
offset + 1,
|
||||||
|
);
|
||||||
|
let owner = held(bundle(body), complete);
|
||||||
|
let syntax = parse_entry_producers(owner.input()).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
owner.retained.entries + syntax.budget.usage.entries + syntax.metadata,
|
||||||
|
complete
|
||||||
|
);
|
||||||
|
// The independent inventory walker has its own existing scratch peak.
|
||||||
|
let roomy = held(bundle(body), 4096);
|
||||||
|
let syntax = parse_entry_producers(roomy.input()).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
project::text_producer_inventory(&syntax).unwrap().entries,
|
||||||
|
complete
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const NESTED: &str = "arcadia 8;\nproject p \"Project\" id \"00000000-0000-0000-0000-000000000001\" {\noa_activity a \"A\" id \"00000000-0000-0000-0000-000000000002\" { payload { items [@p, uuid \"00000000-0000-0000-0000-000000000001\", { flag true; }]; };\noa_activity b \"B\" id \"00000000-0000-0000-0000-000000000003\" { }\n}\nnode n id \"00000000-0000-0000-0000-000000000004\" { }\n}\n";
|
||||||
|
fn nested() -> SourceBundle {
|
||||||
|
SourceBundle {
|
||||||
|
entry: "main.arcadia".into(),
|
||||||
|
files: BTreeMap::from([("main.arcadia".into(), NESTED.into())]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn nested_inventory_and_prepaid_projection_quota() {
|
||||||
|
assert_eq!(NESTED.len(), 344);
|
||||||
|
let owner = held(nested(), 103);
|
||||||
|
let syntax = parse_entry_producers(owner.input()).unwrap();
|
||||||
|
assert_eq!(syntax.budget.usage.entries, 13);
|
||||||
|
assert_eq!(syntax.budget.producer_entries(), Some(89));
|
||||||
|
assert_eq!(syntax.metadata, 89);
|
||||||
|
assert_eq!(
|
||||||
|
owner.retained.entries + syntax.budget.usage.entries + syntax.metadata,
|
||||||
|
103
|
||||||
|
);
|
||||||
|
let roomy = held(nested(), 4096);
|
||||||
|
let inventory_syntax = parse_entry_producers(roomy.input()).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
project::text_producer_inventory(&inventory_syntax)
|
||||||
|
.unwrap()
|
||||||
|
.entries,
|
||||||
|
103
|
||||||
|
);
|
||||||
|
let file = &syntax.files[0];
|
||||||
|
assert_eq!(file.tokens.len(), 49);
|
||||||
|
assert_eq!(file.lexical_owners, [None, Some(0), None]);
|
||||||
|
assert_eq!(file.positions.len(), 3);
|
||||||
|
assert_eq!(file.positions[0].1.header.len(), 5);
|
||||||
|
assert_eq!(file.positions[2].1.header.len(), 4);
|
||||||
|
let language::EditKind::Record(fields) = &file.positions[0].1.fields.kind else {
|
||||||
|
panic!("body")
|
||||||
|
};
|
||||||
|
let payload = &fields["payload"].value;
|
||||||
|
assert_eq!(
|
||||||
|
payload.span,
|
||||||
|
Span {
|
||||||
|
start: 145,
|
||||||
|
end: 221
|
||||||
|
}
|
||||||
|
);
|
||||||
|
let language::EditKind::Record(fields) = &payload.kind else {
|
||||||
|
panic!("payload")
|
||||||
|
};
|
||||||
|
let items = &fields["items"].value;
|
||||||
|
assert_eq!(
|
||||||
|
items.span,
|
||||||
|
Span {
|
||||||
|
start: 153,
|
||||||
|
end: 218
|
||||||
|
}
|
||||||
|
);
|
||||||
|
let language::EditKind::List(items) = &items.kind else {
|
||||||
|
panic!("items")
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
items.iter().map(|v| v.span).collect::<Vec<_>>(),
|
||||||
|
[
|
||||||
|
Span {
|
||||||
|
start: 154,
|
||||||
|
end: 156
|
||||||
|
},
|
||||||
|
Span {
|
||||||
|
start: 158,
|
||||||
|
end: 201
|
||||||
|
},
|
||||||
|
Span {
|
||||||
|
start: 203,
|
||||||
|
end: 217
|
||||||
|
}
|
||||||
|
]
|
||||||
|
);
|
||||||
|
let language::EditKind::Record(fields) = &items[2].kind else {
|
||||||
|
panic!("member")
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
fields["flag"].value.span,
|
||||||
|
Span {
|
||||||
|
start: 210,
|
||||||
|
end: 214
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// Injected prepaid quota errors, not valid alternate source inventories.
|
||||||
|
for (quota, message) in [
|
||||||
|
(29, "Source8 projection quota underflow."),
|
||||||
|
(31, "Source8 projection quota leftover."),
|
||||||
|
] {
|
||||||
|
let error = language::edit_positions_from_tokens_with_producers(
|
||||||
|
file.source,
|
||||||
|
&file.ordinary,
|
||||||
|
|at| lexeme(file.tokens[at]),
|
||||||
|
file.tokens.len(),
|
||||||
|
None,
|
||||||
|
Some(quota),
|
||||||
|
|_, _| Ok(()),
|
||||||
|
)
|
||||||
|
.unwrap_err();
|
||||||
|
assert_eq!(error.message, message);
|
||||||
|
}
|
||||||
|
assert_eq!(syntax.budget.producer_entries(), Some(89));
|
||||||
|
let low = held(nested(), 102);
|
||||||
|
diagnostic(
|
||||||
|
&failed(parse_entry_producers(low.input())),
|
||||||
|
"main.arcadia",
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
fn make_state<'a>(budget: &'a mut Budget, model: &'a mut ModelCounts) -> State<'a> {
|
||||||
|
State {
|
||||||
|
source: "",
|
||||||
|
tokens: &[],
|
||||||
|
budget,
|
||||||
|
model,
|
||||||
|
typed: Vec::new(),
|
||||||
|
value_ends: BTreeMap::new(),
|
||||||
|
lexical_owners: Vec::new(),
|
||||||
|
pending_projection: 0,
|
||||||
|
reserved_values: 0,
|
||||||
|
reserved_owners: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn internal_budget(cap: usize) -> Budget {
|
||||||
|
Budget::new(Limits {
|
||||||
|
entries: cap,
|
||||||
|
..Limits::source8(ParseLimits::UNLIMITED)
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn internal_reserve_and_mutate_helpers_leave_collections_unchanged() {
|
||||||
|
// Scalar fault fixtures exercise atomic mutation; these do not invent real owners.
|
||||||
|
let mut model = ModelCounts {
|
||||||
|
limits: ProjectLimits::default(),
|
||||||
|
graph: 0,
|
||||||
|
records: 0,
|
||||||
|
includes: 0,
|
||||||
|
};
|
||||||
|
let mut budget = internal_budget(3);
|
||||||
|
budget.enable_entry_producers(1, 1).unwrap();
|
||||||
|
let mut state = make_state(&mut budget, &mut model);
|
||||||
|
state.value_end(Span { start: 10, end: 11 }).unwrap();
|
||||||
|
state.value_end(Span { start: 10, end: 12 }).unwrap();
|
||||||
|
assert_eq!(state.value_ends, BTreeMap::from([(10, 12)]));
|
||||||
|
assert_eq!(state.reserved_values, 1);
|
||||||
|
assert_eq!(state.budget.producer_entries(), Some(2));
|
||||||
|
let error = state.value_end(Span { start: 20, end: 21 }).unwrap_err();
|
||||||
|
assert_eq!(error.message, LIMIT);
|
||||||
|
assert_eq!(error.offset, 20);
|
||||||
|
let repeated = state.value_end(Span { start: 10, end: 99 }).unwrap_err();
|
||||||
|
assert_eq!(repeated.offset, 20);
|
||||||
|
assert_eq!(repeated.message, LIMIT);
|
||||||
|
assert_eq!(state.value_ends, BTreeMap::from([(10, 12)]));
|
||||||
|
assert_eq!(state.budget.producer_entries(), Some(2));
|
||||||
|
let error = state
|
||||||
|
.lexical_owner(Some(0), Span { start: 30, end: 31 })
|
||||||
|
.unwrap_err();
|
||||||
|
assert_eq!(error.offset, 20); // Original latched refusal survives a later producer.
|
||||||
|
assert!(state.lexical_owners.is_empty());
|
||||||
|
assert_eq!(state.reserved_owners, 0);
|
||||||
|
let mut budget = internal_budget(3);
|
||||||
|
budget.enable_entry_producers(1, 1).unwrap();
|
||||||
|
let mut state = make_state(&mut budget, &mut model);
|
||||||
|
state.lexical_owner(None, Span::default()).unwrap();
|
||||||
|
assert!(
|
||||||
|
state
|
||||||
|
.lexical_owner(None, Span { start: 40, end: 41 })
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
assert_eq!(state.lexical_owners, [None]);
|
||||||
|
assert_eq!(state.reserved_owners, 1);
|
||||||
|
assert_eq!(state.budget.producer_entries(), Some(2));
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn internal_overflow_latch_and_syntax_work_precedence() {
|
||||||
|
let at = Span { start: 9, end: 10 };
|
||||||
|
let mut budget = internal_budget(2);
|
||||||
|
budget.enable_entry_producers(1, 1).unwrap();
|
||||||
|
let e = budget.entry(at).unwrap_err();
|
||||||
|
assert_eq!(e.message, LIMIT);
|
||||||
|
assert_eq!(budget.producer_entry_overflow(Span::default()).span, at);
|
||||||
|
assert_eq!(budget.active().unwrap_err().message, LIMIT);
|
||||||
|
assert_eq!(budget.usage.entries, 0);
|
||||||
|
assert_eq!(budget.producer_entries(), Some(1));
|
||||||
|
assert_eq!(
|
||||||
|
budget
|
||||||
|
.reserve_metadata(0, Span::default())
|
||||||
|
.unwrap_err()
|
||||||
|
.span,
|
||||||
|
at
|
||||||
|
);
|
||||||
|
let mut budget = internal_budget(0);
|
||||||
|
budget.enable_entry_producers(0, 0).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
budget.entry(at).unwrap_err().message,
|
||||||
|
"Source8 syntax entries limit exceeded."
|
||||||
|
);
|
||||||
|
let mut budget = internal_budget(100);
|
||||||
|
budget.enable_entry_producers(1, 1).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
budget.reserve_metadata(usize::MAX, at).unwrap_err().message,
|
||||||
|
"Source8 producer entry size overflow."
|
||||||
|
);
|
||||||
|
assert_eq!(budget.producer_entries(), Some(1));
|
||||||
|
assert_eq!(budget.active().unwrap_err().span, at);
|
||||||
|
let mut budget = internal_budget(100);
|
||||||
|
assert!(budget.enable_entry_producers(usize::MAX, 1).is_err());
|
||||||
|
assert_eq!(budget.producer_entries(), None);
|
||||||
|
assert!(budget.active().is_err());
|
||||||
|
let mut budget = internal_budget(100);
|
||||||
|
budget.enable_entry_producers(0, 0).unwrap();
|
||||||
|
let mut model = ModelCounts {
|
||||||
|
limits: ProjectLimits::default(),
|
||||||
|
graph: 0,
|
||||||
|
records: 0,
|
||||||
|
includes: 0,
|
||||||
|
};
|
||||||
|
let mut state = make_state(&mut budget, &mut model);
|
||||||
|
state.pending_projection = usize::MAX;
|
||||||
|
assert_eq!(
|
||||||
|
state.projection(1, at).unwrap_err().message,
|
||||||
|
"Source8 producer entry size overflow."
|
||||||
|
);
|
||||||
|
assert_eq!(state.pending_projection, usize::MAX);
|
||||||
|
assert!(state.budget.active().is_err());
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn explicit_zero_and_actual_owner_preserve_complete_oa_sa_and_old_routes() {
|
||||||
|
for (work, property) in [
|
||||||
|
("oa_activity", "oa_property"),
|
||||||
|
("sa_function", "sa_property"),
|
||||||
|
] {
|
||||||
|
let body = format!(
|
||||||
|
"calculation c \"C\" id \"00000000-0000-0000-0000-000000000003\" {{ input x id \"00000000-0000-0000-0000-000000000004\" : Number; result Number; body $x; }}\n{property} v \"V\" id \"00000000-0000-0000-0000-000000000002\" {{ subject @w; type Number; base @c(x: 1); }}\n{work} w \"W\" id \"00000000-0000-0000-0000-000000000005\" {{}}"
|
||||||
|
);
|
||||||
|
let original = bundle(&body);
|
||||||
|
let owner = held(original.clone(), 4096);
|
||||||
|
for carried in [false, true] {
|
||||||
|
let seed = parse_text_producers(owner.input()).unwrap();
|
||||||
|
let input = if carried {
|
||||||
|
owner.input()
|
||||||
|
} else {
|
||||||
|
CarriedInput::borrowed(&owner.bundle, &seed)
|
||||||
|
};
|
||||||
|
let syntax = parse_entry_producers(input).unwrap();
|
||||||
|
let input = if carried {
|
||||||
|
owner.input()
|
||||||
|
} else {
|
||||||
|
CarriedInput::borrowed(&owner.bundle, &seed)
|
||||||
|
};
|
||||||
|
let prior = parse_text_producers(input).unwrap();
|
||||||
|
assert_eq!(syntax.budget.usage, prior.budget.usage);
|
||||||
|
assert_eq!(syntax.budget.numeric_units(), prior.budget.numeric_units());
|
||||||
|
assert_eq!(syntax.metadata, prior.metadata);
|
||||||
|
assert_eq!(syntax.budget.producer_text(), prior.budget.producer_text());
|
||||||
|
assert_eq!(syntax.budget.producer_entries(), Some(syntax.metadata));
|
||||||
|
let result = project::resolve(syntax).unwrap();
|
||||||
|
let prior = project::resolve(prior).unwrap();
|
||||||
|
assert_eq!(result.project(), prior.project());
|
||||||
|
assert_eq!(result.warnings(), prior.warnings());
|
||||||
|
assert_eq!(result.phase_state().1, prior.phase_state().1);
|
||||||
|
}
|
||||||
|
assert_eq!(owner.bundle, original);
|
||||||
|
}
|
||||||
|
// Text-only and phase entry acceptance retain their old checkpoints.
|
||||||
|
for cap in [12, 11] {
|
||||||
|
let owner = held(bundle(""), cap);
|
||||||
|
let seed = parse_with_profile(
|
||||||
|
&owner.bundle,
|
||||||
|
owner.policy.project_limits,
|
||||||
|
owner.policy.limits,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert!(seed.budget.producer_entries().is_none());
|
||||||
|
let result = parse_entry_producers(CarriedInput::borrowed(&owner.bundle, &seed));
|
||||||
|
if cap == 12 {
|
||||||
|
assert_eq!(result.unwrap().metadata, 11);
|
||||||
|
} else {
|
||||||
|
diagnostic(&failed(result), "main.arcadia", 0, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let low = held(
|
||||||
|
bundle("oa_activity a \"A\" id \"00000000-0000-0000-0000-000000000002\" { }"),
|
||||||
|
4,
|
||||||
|
);
|
||||||
|
diagnostic(
|
||||||
|
&failed(parse_entry_producers(low.input())),
|
||||||
|
"main.arcadia",
|
||||||
|
75,
|
||||||
|
76,
|
||||||
|
);
|
||||||
|
diagnostic(
|
||||||
|
&failed(parse_text_producers(low.input())),
|
||||||
|
"main.arcadia",
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
diagnostic(&failed(parse_carried(low.input())), "main.arcadia", 0, 1);
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# Private retained-entry producer prerequisite
|
||||||
|
|
||||||
|
Base `97a8703adc75467a7f8e75f5c6cb00ca542b707c`; implements the accepted
|
||||||
|
`harness/analysis-entry-producer-contract-20260925.md` in the parent source tree.
|
||||||
|
This is fixture-only phase/producer evidence, not emitted-output admission.
|
||||||
|
|
||||||
|
The explicit entry route also enables text producers and derives C and exact
|
||||||
|
policy from the existing carried guard. Budget adds only optional scalar C/M.
|
||||||
|
Existing legacy, carried-phase and text-only entry paths retain their checkpoints.
|
||||||
|
U keeps its original event sites and work-limit precedence; prospective aggregate
|
||||||
|
checks latch without changing totals. F is installed before traversal. Private
|
||||||
|
State helpers reserve unique value-end keys and lexical owner occurrences before
|
||||||
|
mutation. Successful grammar events count projection slots with checked addition.
|
||||||
|
The new route reserves token-end slots and projection together, without the old
|
||||||
|
borrowed-work counting Vec. A scalar quota checks each projected envelope, field
|
||||||
|
and actual value before growth. Synthetic bodies remain included in seven.
|
||||||
|
Per-file actual map/owner lengths and final independent metadata reconcile M;
|
||||||
|
resolver C then U+M transfer and text reconciliation are unchanged.
|
||||||
|
|
||||||
|
Six tests in `mixed::carried::entry_producer` cover independent literal E and H
|
||||||
|
syntax caps 13/12 and 25/24 separately from resolver 17/16 and 29/28, owner/value
|
||||||
|
prefix refusals 4/7, and the 344-byte nested syntax-only fixture (U13, M89, P30,
|
||||||
|
C1; 103/102). They check node/element envelopes, nested positions and owner order,
|
||||||
|
quota underflow/leftover, direct unchanged collections on refusal and repeated
|
||||||
|
map-key replacement. Labeled arithmetic controls exercise overflow, sticky-first
|
||||||
|
failure, prospective U and work-limit precedence. Whole OA/SA Project, warnings,
|
||||||
|
Usage, numeric work and retained totals are compared with text-only under actual
|
||||||
|
and explicit zero guards. Old-route diagnostics and original bytes are checked.
|
||||||
|
All byte policies are roomy 4096 so earlier text limits do not mask entry tests.
|
||||||
|
|
||||||
|
Source review identified a possible internal overflow call overwriting an already
|
||||||
|
latched error. The helper now returns the first failure; a direct fault control
|
||||||
|
checks that behavior. Repeated map keys now perform a zero-delta reservation
|
||||||
|
before replacement, preserving a latched refusal without modifying the map;
|
||||||
|
a direct control checks that branch too. These were corrected before compilation,
|
||||||
|
not a runtime bug
|
||||||
|
observation. Test review also caught that the independent inventory walker adds
|
||||||
|
its own existing scratch slots: exact-cap ordinary syntax controls remain at
|
||||||
|
29/37/103, while a separate roomy owner of the same literal runs that walk.
|
||||||
|
No live policy is widened and no exact-cap resolver success is implied.
|
||||||
|
No prior tests, stack sizes, policies, inherited failure dispositions
|
||||||
|
or unrelated formatting are changed. Only touched Rust files were directly
|
||||||
|
formatted with skip_children. Parent owns compiler, full disposition, Clippy,
|
||||||
|
format and prologue/stack acceptance; no runtime result is claimed here.
|
||||||
|
|
||||||
|
Allocator overhead, token storage under its existing token ceiling, delimiter and
|
||||||
|
other work stacks, numeric/decoder internals and model/validator heap remain
|
||||||
|
outside this retained metric. The extra shared projector wrapper and scalar
|
||||||
|
Budget/State fields require measured parent stack checks; no heap indirection or
|
||||||
|
larger stack is introduced. Final output acceptance remains a separate task.
|
||||||
Reference in New Issue
Block a user