Skip to main content

Budget

Struct Budget 

Source
pub struct Budget(/* private fields */);
Expand description

How much backtracking one parse may do before AtomStream declares the input unparseable and reports end of input.

The unit is a serve: one atom handed out by ParseStream::next, counting every re-read a rollback causes. A count and not a clock, so the same source produces the same verdict on a fast machine, on a slow one, in a test and in a browser.

§Why a compiler has one at all

Because without it the compiler does not report anything. Measured on a release build, over chains of let vN = N in ending in a let with no right-hand side:

fileerror onbefore
9 linesline 6exit 1, 7 ms
15 linesline 12exit 1, 32 ms
35 linesline 32still running after 100 s

The cause was a plain unfactored common prefix: Expr::LetIn and Expr::LetPatternIn both began let ‹target› = ‹expr› in ‹body›, so a failure in the innermost body was re-derived exactly twice per enclosing let. Measured, serves against chain length: 1,115 at 3, 9,459 at 6, 76,211 at 9, 610,227 at 12, 4,882,355 at 15 — ×2.000 each time.

That prefix is now factored and the table above is history: cst::PatNonVarErased refuses a bare-variable destructuring target — the restriction upstream 0.1 spells pattern_non_var — which makes the two alternatives disjoint at the token after let. The same chain costs 411, 750, 1,089, 1,428, 1,767: an arithmetic progression, +113 per let, pinned as an exact equality by parse_errors.rs’s one_more_let_costs_a_constant_number_of_serves. No input is known that reaches this cap any more.

The cap stays anyway, and the row above is why it was worth having: the 0.1 grammar was separately observed to blow up ×5 per 200 bytes on truncated prefixes of the bundled std-ja.satyh, from a different prefix that has not been chased down. What a budget buys, and a grammar fix does not, is that the next such prefix is a slow error instead of a hang.

The give-up is reported as a give-up (crate::ParseFailureKind::GaveUp), never as a claim about the token the parse happened to stop at — and it still carries the high-water mark’s position, which in every case measured is the line the author must look at.

§Why it scales with the input

A cap has to be unreachable by any honest parse of any honest file, and “honest” is a property per token, not per file: a fixed ceiling that a 300-line file cannot reach is one a generated 30,000-line file can. So the cap is a per-atom allowance, and only superlinear backtracking can outrun it.

Implementations§

Source§

impl Budget

Source

pub const PER_ATOM: u64 = 2_048

Serves per atom an honest parse is allowed.

Calibrated from measurement, not guessed: a clean parse costs 14–17 serves per atom, and the worst of the 77 files in the bundled corpus (dist-v01/packages/tabular.satyh) costs 34.7. This is roughly sixty times that, and parse_errors.rs’s the_bundled_corpus_stays_far_under_the_per_atom_budget re-measures the corpus on every run rather than trusting the figure.

Source

pub const FLOOR: u64 = 8_000_000

Floor, so that a small file still gets the allowance a mid-sized one would.

Without it a ten-line file would be capped at a few thousand serves and would give up on constructs a hundred-line file resolves. At roughly 10M serves per second this is about a second of trying.

Do not raise this to fix a give-up. While the let prefix above was unfactored, the floor bought a broken chain of fifteen lets a real verdict and each further doubling bought exactly one more let — which is the shape of the argument in general: against a superlinear grammar the budget cannot buy diagnostic quality, only bound the damage. A give-up means a production needs left-factoring; the number to change is in cst.rs, not here.

Source

pub const fn for_atoms(atoms: usize) -> Self

The allowance for a token vector of atoms atoms.

Source

pub const fn exactly(serves: u64) -> Self

An explicit allowance, for a caller with its own responsiveness requirement — a language server spends less than a compiler, because a human is waiting on every keystroke.

Source

pub const fn unlimited() -> Self

No cap at all: the parse runs to a verdict or forever.

For a caller that has bounded the work some other way, and for pinning the unbounded behaviour in a test.

Source

pub const fn serves(self) -> u64

The allowance, in serves.

Trait Implementations§

Source§

impl Clone for Budget

Source§

fn clone(&self) -> Budget

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Copy for Budget

Source§

impl Debug for Budget

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Eq for Budget

Source§

impl PartialEq for Budget

Source§

fn eq(&self, other: &Budget) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for Budget

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> Clone for T
where T: Clone,

Source§

fn clone(&self) -> T

Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> Copy for T
where T: Copy,

Source§

impl<T> Debug for T
where T: Debug,

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.