feat(source): expose borrowed read-only mixed source eight validation
This commit is contained in:
@@ -3,10 +3,9 @@ mod raw_tokens;
|
||||
#[path = "language/semantic_source.rs"]
|
||||
mod semantic_source;
|
||||
mod source3_bridge;
|
||||
// Staged typed analysis only. Public mixed source8 admission waits for complete
|
||||
// declaration lowering, source updates and the root-owned resource gate.
|
||||
#[cfg(test)]
|
||||
// Mixed source8 is exposed only through the separate borrowed read-only checker.
|
||||
mod analysis_source;
|
||||
pub use analysis_source::{CheckedMixed, check_mixed_bundle};
|
||||
#[path = "migration_v2/source.rs"]
|
||||
mod v2_source;
|
||||
use crate::*;
|
||||
@@ -480,7 +479,6 @@ fn parse_document(source: &str, limits: ParseLimits) -> Result<ParsedDocument, V
|
||||
cursor: 0,
|
||||
value_depth: 0,
|
||||
limits,
|
||||
#[cfg(test)]
|
||||
mixed: None,
|
||||
}
|
||||
.document()
|
||||
@@ -916,7 +914,6 @@ pub(crate) fn parse_unit(
|
||||
cursor: 0,
|
||||
value_depth: 0,
|
||||
limits,
|
||||
#[cfg(test)]
|
||||
mixed: None,
|
||||
}
|
||||
.unit(entry, true)
|
||||
@@ -1215,12 +1212,10 @@ struct Parser<'a> {
|
||||
cursor: usize,
|
||||
value_depth: usize,
|
||||
limits: ParseLimits,
|
||||
#[cfg(test)]
|
||||
mixed: Option<analysis_source::mixed::State<'a>>,
|
||||
}
|
||||
impl Parser<'_> {
|
||||
fn token(&self, index: usize) -> Option<Lexeme> {
|
||||
#[cfg(test)]
|
||||
if let Some(mixed) = &self.mixed {
|
||||
return mixed.token(index);
|
||||
}
|
||||
@@ -1236,7 +1231,6 @@ impl Parser<'_> {
|
||||
})
|
||||
}
|
||||
fn syntax_entry(&mut self) -> Result<(), Error> {
|
||||
#[cfg(test)]
|
||||
{
|
||||
let span = self.current().span;
|
||||
if let Some(mixed) = &mut self.mixed {
|
||||
@@ -1266,9 +1260,7 @@ impl Parser<'_> {
|
||||
if self.current().class != TokenClass::String {
|
||||
return Err(self.error("Expected a double-quoted string."));
|
||||
}
|
||||
#[cfg(test)]
|
||||
let span = self.current().span;
|
||||
#[cfg(test)]
|
||||
if let Some(mixed) = &mut self.mixed {
|
||||
let raw = &self.source[span.start..span.end];
|
||||
let value = mixed.quoted_text(raw, span)?;
|
||||
@@ -1290,9 +1282,8 @@ impl Parser<'_> {
|
||||
}
|
||||
fn optional_id(&mut self) -> Result<Id, Error> {
|
||||
if self.text() != "id" {
|
||||
#[cfg(test)]
|
||||
if self.mixed.is_some() {
|
||||
return Err(self.error("Private mixed syntax requires explicit IDs."));
|
||||
return Err(self.error("Mixed source8 requires explicit UUIDs."));
|
||||
}
|
||||
return Ok(Id::new_v4());
|
||||
}
|
||||
@@ -1321,21 +1312,15 @@ impl Parser<'_> {
|
||||
Ok(id)
|
||||
}
|
||||
fn own_text(&mut self, bytes: usize, span: Span) -> Result<(), Error> {
|
||||
#[cfg(test)]
|
||||
if let Some(mixed) = &mut self.mixed {
|
||||
mixed.own_text(bytes, span)?;
|
||||
}
|
||||
#[cfg(not(test))]
|
||||
let _ = (bytes, span);
|
||||
Ok(())
|
||||
}
|
||||
fn release_text(&mut self, bytes: usize) {
|
||||
#[cfg(test)]
|
||||
if let Some(mixed) = &mut self.mixed {
|
||||
mixed.release_text(bytes);
|
||||
}
|
||||
#[cfg(not(test))]
|
||||
let _ = bytes;
|
||||
}
|
||||
fn fields(&mut self) -> Result<BTreeMap<String, Ast>, Error> {
|
||||
self.take("{")?;
|
||||
@@ -1383,7 +1368,6 @@ impl Parser<'_> {
|
||||
.span
|
||||
.end;
|
||||
self.take(";")?;
|
||||
#[cfg(test)]
|
||||
if let Some(mixed) = &mut self.mixed {
|
||||
mixed.projection(1, Span { start, end })?;
|
||||
}
|
||||
@@ -1412,7 +1396,6 @@ impl Parser<'_> {
|
||||
self.value_depth += 1;
|
||||
let result = self.value_inner();
|
||||
self.value_depth -= 1;
|
||||
#[cfg(test)]
|
||||
if let Ok(ast) = &result {
|
||||
let end = self
|
||||
.token(self.cursor - 1)
|
||||
@@ -1433,7 +1416,6 @@ impl Parser<'_> {
|
||||
fn value_inner(&mut self) -> Result<Ast, Error> {
|
||||
let token = self.current();
|
||||
let offset = token.span.start;
|
||||
#[cfg(test)]
|
||||
if let Some(mixed) = &self.mixed
|
||||
&& (token.class == TokenClass::Number || matches!(self.text(), "-" | "+"))
|
||||
{
|
||||
@@ -1541,17 +1523,14 @@ impl Parser<'_> {
|
||||
fn unit(&mut self, entry: bool, includes_allowed: bool) -> Result<SourceUnit, Error> {
|
||||
let mut header = if entry {
|
||||
self.take("arcadia")?;
|
||||
#[cfg(test)]
|
||||
let mixed_mode = self.mixed.is_some();
|
||||
#[cfg(not(test))]
|
||||
let mixed_mode = false;
|
||||
if if mixed_mode {
|
||||
self.text() != "8"
|
||||
} else {
|
||||
!matches!(self.text(), "3" | "4" | "5" | "6" | "7" | "8")
|
||||
} {
|
||||
return Err(self.error(if mixed_mode {
|
||||
"Private mixed syntax requires arcadia 8."
|
||||
"Mixed source requires arcadia 8."
|
||||
} else {
|
||||
"Unsupported schema version: expected arcadia 3, 4, 5, 6, 7 or 8."
|
||||
}));
|
||||
@@ -1561,7 +1540,6 @@ impl Parser<'_> {
|
||||
self.take(";")?;
|
||||
let start = self.current().span.start;
|
||||
self.take("project")?;
|
||||
#[cfg(test)]
|
||||
if let Some(mixed) = &mut self.mixed {
|
||||
mixed.record(false, Span { start, end: start })?;
|
||||
}
|
||||
@@ -1606,7 +1584,6 @@ impl Parser<'_> {
|
||||
let mut open: Vec<usize> = Vec::new();
|
||||
loop {
|
||||
let offset = self.current().span.start;
|
||||
#[cfg(test)]
|
||||
if let Some(mixed) = &mut self.mixed
|
||||
&& mixed.typed_header(self.cursor, open.is_empty())
|
||||
{
|
||||
@@ -1658,7 +1635,6 @@ impl Parser<'_> {
|
||||
if !includes_allowed {
|
||||
return Err(self.error("An include requires a source bundle; single-string parsing never reads files."));
|
||||
}
|
||||
#[cfg(test)]
|
||||
if let Some(mixed) = &mut self.mixed {
|
||||
mixed.include(Span {
|
||||
start: offset,
|
||||
@@ -1705,7 +1681,6 @@ impl Parser<'_> {
|
||||
header = Some(entry_header);
|
||||
continue;
|
||||
}
|
||||
#[cfg(test)]
|
||||
if self.mixed.is_some() && analysis_source::mixed::long_analysis_word(self.text()) {
|
||||
return Err(self.error("Use the short source8 analysis declaration word."));
|
||||
}
|
||||
@@ -1713,7 +1688,6 @@ impl Parser<'_> {
|
||||
let word = self.current().text(self.source);
|
||||
let kind = declaration_kind(word)
|
||||
.ok_or_else(|| self.error(format!("Unknown declaration '{word}'.")))?;
|
||||
#[cfg(test)]
|
||||
if let Some(mixed) = &mut self.mixed {
|
||||
mixed.record(
|
||||
matches!(kind, DeclKind::Element(..) | DeclKind::Relation(..)),
|
||||
@@ -1750,7 +1724,6 @@ impl Parser<'_> {
|
||||
let body_start = self.current().span.end;
|
||||
self.take("{")?;
|
||||
let lexical_owner = open.last().map(|&owner| declarations[owner].id);
|
||||
#[cfg(test)]
|
||||
if let Some(mixed) = &mut self.mixed {
|
||||
let span = Span {
|
||||
start: offset,
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
//! Unresolved typed source8 syntax, staged for the complete source adapter.
|
||||
//! This module does not admit a Project or enable public source8.
|
||||
//! Private typed syntax and complete read-only mixed source8 resolution.
|
||||
mod budget;
|
||||
mod cursor;
|
||||
mod declarations;
|
||||
#[cfg(test)]
|
||||
mod index;
|
||||
mod lex;
|
||||
mod lower;
|
||||
pub(super) mod mixed;
|
||||
mod syntax;
|
||||
#[cfg(test)]
|
||||
mod update;
|
||||
#[cfg(test)]
|
||||
mod write;
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -18,6 +20,7 @@ use super::Span;
|
||||
use crate::analysis::{BinaryOp, UnaryOp};
|
||||
use crate::analysis_numeric::Exact;
|
||||
use budget::Budget;
|
||||
pub use mixed::project::{CheckedMixed, check_mixed_bundle};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Error {
|
||||
@@ -104,6 +107,7 @@ pub enum Binding {
|
||||
Expression(Expression),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn parse(source: &str, budget: &mut Budget) -> Result<Binding, Error> {
|
||||
let tokens = lex::tokens(source, budget)?;
|
||||
let mut cursor = cursor::Cursor::new(source, &tokens, budget);
|
||||
|
||||
@@ -294,6 +294,7 @@ impl Budget {
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
pub fn numeric_units(&self) -> u64 {
|
||||
self.numeric.usage().numeric_units
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ impl Cursor<'_, '_> {
|
||||
self.cursor += 1;
|
||||
Ok(span)
|
||||
}
|
||||
#[cfg(test)]
|
||||
pub(super) fn end(&self) -> Result<(), Error> {
|
||||
if self.cursor != self.tokens.len() {
|
||||
Err(error(
|
||||
|
||||
@@ -2,12 +2,15 @@
|
||||
//! single complete-Project semantic admission after merging these additions.
|
||||
use super::declarations::{self as d, Declaration, DeclarationKind};
|
||||
use super::*;
|
||||
#[cfg(test)]
|
||||
use crate::Project;
|
||||
use crate::analysis as a;
|
||||
use crate::analysis_numeric::Dimension;
|
||||
use crate::{Element, ElementBody, Id, Kind, Layer, Project, SharedBody};
|
||||
use crate::{Element, ElementBody, Id, Kind, Layer, SharedBody};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
/// Caller-assigned file key; spans are original offsets within that file.
|
||||
#[cfg(test)]
|
||||
pub(super) struct Unit {
|
||||
pub file: usize,
|
||||
pub declarations: Vec<Declaration>,
|
||||
@@ -180,6 +183,7 @@ impl Tables {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn ordinary(&mut self, project: &Project, budget: &Budget) -> Result<(), Error> {
|
||||
let at = Span::default();
|
||||
self.insert(project.id, &project.alias, Category::Project, at, budget)?;
|
||||
@@ -270,6 +274,7 @@ impl Tables {
|
||||
/// Inputs must come from the same live Budget and stabilization pass. No scan,
|
||||
/// exact conversion or reference charge occurs here. The ordinary skeleton is
|
||||
/// not semantically admitted: its typed forward references may need additions.
|
||||
#[cfg(test)]
|
||||
pub(super) fn lower(
|
||||
ordinary: &Project,
|
||||
units: &[Unit],
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
//! value-end entries, projected value/field/header/declaration positions, lexical
|
||||
//! owner slots and the file table. Checks do not recharge syntax or latch errors;
|
||||
//! every failure must return immediately. Tokens themselves use the token bound.
|
||||
//! This deliberately bounded staging policy is not calibrated public source8.
|
||||
//! The public read checker selects both producers; editing remains separately staged.
|
||||
|
||||
use super::{
|
||||
budget::{Budget, Limits},
|
||||
@@ -53,11 +53,15 @@ pub(super) struct ParsedMixedSyntax<'s> {
|
||||
struct FileSyntax<'s> {
|
||||
path: &'s str,
|
||||
source: &'s str,
|
||||
// Retained original indexing metadata; the read result preserves its accounted ownership.
|
||||
#[cfg_attr(not(test), allow(dead_code))]
|
||||
tokens: Vec<lex::Token>,
|
||||
ordinary: SourceUnit,
|
||||
typed: Vec<declarations::Declaration>,
|
||||
positions: Vec<(Id, EditDeclaration)>,
|
||||
// A lexical owner is an occurrence index, not only a possibly repeated UUID.
|
||||
// Retained original indexing metadata; the read result preserves its accounted ownership.
|
||||
#[cfg_attr(not(test), allow(dead_code))]
|
||||
lexical_owners: Vec<Option<usize>>,
|
||||
}
|
||||
|
||||
@@ -256,7 +260,7 @@ impl State<'_> {
|
||||
return Err(Error {
|
||||
code: "invalid_source",
|
||||
offset: declaration.span.start,
|
||||
message: "Private mixed syntax requires explicit IDs.".into(),
|
||||
message: "Mixed source8 requires explicit UUIDs.".into(),
|
||||
});
|
||||
}
|
||||
self.typed.push(declaration);
|
||||
@@ -276,17 +280,22 @@ pub(in crate::language) fn long_analysis_word(word: &str) -> bool {
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(super) fn parse(
|
||||
bundle: &SourceBundle,
|
||||
limits: ProjectLimits,
|
||||
) -> Result<ParsedMixedSyntax<'_>, Vec<SourceDiagnostic>> {
|
||||
let profile = Limits::source8(ParseLimits {
|
||||
let profile = source_profile(limits);
|
||||
parse_with_profile(bundle, limits, profile)
|
||||
}
|
||||
|
||||
fn source_profile(limits: ProjectLimits) -> Limits {
|
||||
Limits::source8(ParseLimits {
|
||||
// Storage is not the caller's final model record/ordered-step accounting.
|
||||
max_source_bytes: limits.model.max_source_bytes,
|
||||
max_step_references: limits.model.max_step_references,
|
||||
..ParseLimits::UNLIMITED
|
||||
});
|
||||
parse_with_profile(bundle, limits, profile)
|
||||
})
|
||||
}
|
||||
|
||||
fn located(path: &str, source: &str, error: Error) -> Vec<SourceDiagnostic> {
|
||||
@@ -297,6 +306,7 @@ fn located(path: &str, source: &str, error: Error) -> Vec<SourceDiagnostic> {
|
||||
}]
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn parse_with_profile(
|
||||
bundle: &SourceBundle,
|
||||
limits: ProjectLimits,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
//! Checks existing logical phase totals; does not meter every parser allocation.
|
||||
use super::*;
|
||||
use project::accounting::{Ledger, Slots};
|
||||
#[cfg(test)]
|
||||
use project::ordinary_index::rename::emission::PendingRename;
|
||||
|
||||
struct SnapshotPolicy {
|
||||
@@ -10,17 +11,20 @@ struct SnapshotPolicy {
|
||||
}
|
||||
|
||||
/// The allocation-owning bundle guard. No production constructor exists yet.
|
||||
#[cfg(test)]
|
||||
struct RetainedSnapshot {
|
||||
bundle: SourceBundle,
|
||||
policy: SnapshotPolicy,
|
||||
retained: Slots,
|
||||
}
|
||||
enum SnapshotBacking<'s> {
|
||||
#[cfg(test)]
|
||||
Pending(&'s PendingRename<'s>),
|
||||
Borrowed {
|
||||
bundle: &'s SourceBundle,
|
||||
policy: SnapshotPolicy,
|
||||
},
|
||||
#[cfg(test)]
|
||||
Retained(&'s RetainedSnapshot),
|
||||
}
|
||||
|
||||
@@ -29,8 +33,12 @@ pub(super) struct CarriedInput<'s> {
|
||||
backing: SnapshotBacking<'s>,
|
||||
}
|
||||
enum GuardBacking<'s> {
|
||||
#[cfg(test)]
|
||||
Pending(&'s PendingRename<'s>),
|
||||
Borrowed,
|
||||
Borrowed {
|
||||
_bundle: &'s SourceBundle,
|
||||
},
|
||||
#[cfg(test)]
|
||||
Retained(&'s RetainedSnapshot),
|
||||
}
|
||||
|
||||
@@ -40,6 +48,18 @@ pub(super) struct CarriedGuard<'s> {
|
||||
backing: GuardBacking<'s>,
|
||||
}
|
||||
impl<'s> CarriedInput<'s> {
|
||||
fn fresh(bundle: &'s SourceBundle, limits: ProjectLimits) -> Self {
|
||||
Self {
|
||||
backing: SnapshotBacking::Borrowed {
|
||||
bundle,
|
||||
policy: SnapshotPolicy {
|
||||
limits: source_profile(limits),
|
||||
project_limits: limits,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
pub(super) fn from_pending<'t: 's>(pending: &'s PendingRename<'t>) -> Self {
|
||||
Self {
|
||||
backing: SnapshotBacking::Pending(pending),
|
||||
@@ -47,6 +67,7 @@ impl<'s> CarriedInput<'s> {
|
||||
}
|
||||
fn into_parts(self) -> (&'s SourceBundle, SnapshotPolicy, CarriedGuard<'s>) {
|
||||
match self.backing {
|
||||
#[cfg(test)]
|
||||
SnapshotBacking::Pending(pending) => {
|
||||
let (limits, project_limits) = pending.carried_policy();
|
||||
(
|
||||
@@ -64,9 +85,10 @@ impl<'s> CarriedInput<'s> {
|
||||
bundle,
|
||||
policy,
|
||||
CarriedGuard {
|
||||
backing: GuardBacking::Borrowed,
|
||||
backing: GuardBacking::Borrowed { _bundle: bundle },
|
||||
},
|
||||
),
|
||||
#[cfg(test)]
|
||||
SnapshotBacking::Retained(owner) => (
|
||||
&owner.bundle,
|
||||
SnapshotPolicy {
|
||||
@@ -83,8 +105,10 @@ impl<'s> CarriedInput<'s> {
|
||||
impl CarriedGuard<'_> {
|
||||
pub(super) fn slots(&self) -> Slots {
|
||||
match &self.backing {
|
||||
#[cfg(test)]
|
||||
GuardBacking::Pending(pending) => pending.carried_slots(),
|
||||
GuardBacking::Borrowed => Slots::default(),
|
||||
GuardBacking::Borrowed { .. } => Slots::default(),
|
||||
#[cfg(test)]
|
||||
GuardBacking::Retained(owner) => owner.retained,
|
||||
}
|
||||
}
|
||||
@@ -105,6 +129,7 @@ impl CarriedGuard<'_> {
|
||||
}
|
||||
|
||||
/// Input already binds the actual bundle; callers cannot supply a replacement.
|
||||
#[cfg(test)]
|
||||
fn parse_carried(input: CarriedInput<'_>) -> Result<ParsedMixedSyntax<'_>, Vec<SourceDiagnostic>> {
|
||||
let (bundle, policy, guard) = input.into_parts();
|
||||
parse_impl(
|
||||
@@ -205,3 +230,12 @@ mod text_producer;
|
||||
|
||||
#[cfg(test)]
|
||||
mod entry_producer;
|
||||
|
||||
pub(super) fn parse_fresh(
|
||||
bundle: &SourceBundle,
|
||||
limits: ProjectLimits,
|
||||
) -> Result<ParsedMixedSyntax<'_>, Vec<SourceDiagnostic>> {
|
||||
parse_entry_producers(CarriedInput::fresh(bundle, limits))
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod public_read;
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
//! Real public-constructor wiring; arbitrary fixture profiles are not public policy.
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn public_read_fresh_constructor_keeps_zero_carry_both_modes_and_exact_policy() {
|
||||
let bundle = SourceBundle {
|
||||
entry: "main.arcadia".into(),
|
||||
files: [("main.arcadia".into(), "arcadia 8;\nproject p \"Project\" id \"00000000-0000-0000-0000-000000000001\" {\n\n}\n".into())].into(),
|
||||
};
|
||||
let original = bundle.clone();
|
||||
for (source_bytes, references, expected_bytes, expected_tokens, expected_references) in [
|
||||
(4096, 17, 4096, 4096, 17),
|
||||
(usize::MAX, usize::MAX, 64 * 1024 * 1024, 1_048_576, 500_000),
|
||||
] {
|
||||
let limits = ProjectLimits {
|
||||
max_files: 7,
|
||||
max_include_depth: 9,
|
||||
max_include_edges: 11,
|
||||
model: ParseLimits {
|
||||
max_source_bytes: source_bytes,
|
||||
max_graph_objects: 3,
|
||||
max_records: 1,
|
||||
max_step_references: references,
|
||||
},
|
||||
};
|
||||
// This is the same production helper the exported checker calls.
|
||||
let syntax = parse_fresh(&bundle, limits).unwrap();
|
||||
let guard = syntax.carried.as_ref().expect("explicit carried mode");
|
||||
assert!(matches!(guard.backing, GuardBacking::Borrowed { .. }));
|
||||
assert_eq!(guard.slots(), Slots::default());
|
||||
assert_eq!(syntax.budget.producer_text(), Some((8, 0)));
|
||||
assert_eq!(syntax.budget.producer_entries(), Some(11));
|
||||
assert_eq!(syntax.metadata, 11);
|
||||
let profile = syntax.budget.limits();
|
||||
assert_eq!(profile.bytes, expected_bytes);
|
||||
assert_eq!(profile.tokens, expected_tokens);
|
||||
assert_eq!(profile.entries, 262_144);
|
||||
assert_eq!(profile.references, expected_references);
|
||||
assert_eq!(profile.frames, 256);
|
||||
assert_eq!(profile.numeric_units, 50_000_000);
|
||||
assert_eq!(syntax.limits.max_files, 7);
|
||||
assert_eq!(syntax.limits.max_include_depth, 9);
|
||||
assert_eq!(syntax.limits.max_include_edges, 11);
|
||||
assert_eq!(syntax.limits.model.max_source_bytes, source_bytes);
|
||||
assert_eq!(syntax.limits.model.max_graph_objects, 3);
|
||||
assert_eq!(syntax.limits.model.max_records, 1);
|
||||
assert_eq!(syntax.limits.model.max_step_references, references);
|
||||
let prior = parse(&bundle, limits).unwrap();
|
||||
assert_eq!(syntax.budget.usage, prior.budget.usage);
|
||||
assert_eq!(syntax.budget.numeric_units(), prior.budget.numeric_units());
|
||||
let resolved = project::resolve(syntax).unwrap();
|
||||
let prior = project::resolve(prior).unwrap();
|
||||
let checked = project::check_mixed_bundle(&bundle, limits).unwrap();
|
||||
let mut expected = crate::Project::empty();
|
||||
expected.id = Id::from_u128(1);
|
||||
expected.alias = "p".into();
|
||||
expected.name = "Project".into();
|
||||
assert_eq!(resolved.project(), &expected);
|
||||
assert_eq!(prior.project(), &expected);
|
||||
assert_eq!(checked.project(), &expected);
|
||||
assert!(checked.warnings().is_empty());
|
||||
assert_eq!(resolved.warnings(), prior.warnings());
|
||||
}
|
||||
assert_eq!(bundle, original);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
//! Private complete-Project admission from the actual complete syntax witness.
|
||||
//! Consumed read-only typed index; no update authority, public source8 dispatch or replacement policy.
|
||||
//! Complete-Project admission and opaque public read-only mixed source8 witness.
|
||||
//! The separate typed index/edit chain remains test-only; no replacement policy.
|
||||
use super::super::{
|
||||
Binding, Budget, Error as TypedError, Expression, ExpressionKind, Reference, Span,
|
||||
declarations as d, error, lower,
|
||||
@@ -22,17 +22,108 @@ pub(super) fn text_producer_inventory(syntax: &ParsedMixedSyntax<'_>) -> Result<
|
||||
accounting::syntax(syntax, &mut ledger)?;
|
||||
Ok(ledger.live)
|
||||
}
|
||||
#[cfg(test)]
|
||||
pub(in crate::language::analysis_source) mod ordinary_index;
|
||||
|
||||
/// An admitted mixed source8 model borrowing its complete original source bundle.
|
||||
///
|
||||
/// This non-Clone result grants immutable validation queries only. It is not an
|
||||
/// editable source index, stabilized output, or permission to format/save a typed
|
||||
/// model through the ordinary source APIs. All input UUIDs must be explicit.
|
||||
///
|
||||
/// The source cannot be mutated while a later query uses the result:
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// use oa_core::{language::check_mixed_bundle, source_project::{SourceBundle, ProjectLimits}};
|
||||
/// let mut bundle = SourceBundle {
|
||||
/// entry: "main.arcadia".into(),
|
||||
/// files: [("main.arcadia".into(),
|
||||
/// "arcadia 8; project p \"P\" id \"00000000-0000-0000-0000-000000000001\" {}".into())].into(),
|
||||
/// };
|
||||
/// let checked = check_mixed_bundle(&bundle, ProjectLimits::default()).unwrap();
|
||||
/// bundle.files.clear();
|
||||
/// assert_eq!(checked.project().alias, "p");
|
||||
/// ```
|
||||
///
|
||||
/// Nor can the result outlive that source:
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// use oa_core::{language::check_mixed_bundle, source_project::{SourceBundle, ProjectLimits}};
|
||||
/// let checked = {
|
||||
/// let bundle = SourceBundle {
|
||||
/// entry: "main.arcadia".into(),
|
||||
/// files: [("main.arcadia".into(),
|
||||
/// "arcadia 8; project p \"P\" id \"00000000-0000-0000-0000-000000000001\" {}".into())].into(),
|
||||
/// };
|
||||
/// check_mixed_bundle(&bundle, ProjectLimits::default()).unwrap()
|
||||
/// };
|
||||
/// assert_eq!(checked.project().alias, "p");
|
||||
/// ```
|
||||
pub struct CheckedMixed<'s> {
|
||||
resolved: ResolvedMixedProject<'s>,
|
||||
}
|
||||
impl CheckedMixed<'_> {
|
||||
/// The complete admitted model, without mutation or ownership extraction.
|
||||
pub fn project(&self) -> &Project {
|
||||
self.resolved.project()
|
||||
}
|
||||
/// Actual located non-error validation diagnostics in validation order.
|
||||
pub fn warnings(&self) -> &[SourceDiagnostic] {
|
||||
self.resolved.warnings()
|
||||
}
|
||||
}
|
||||
|
||||
/// Validate a complete mixed ordinary/typed source8 bundle without changing bytes.
|
||||
///
|
||||
/// Every project, ordinary/typed declaration and typed parameter requires an
|
||||
/// explicit UUID. Supplied files must be canonical and reachable from the entry.
|
||||
/// Errors return no partial admitted model. There is no migration, ID generation,
|
||||
/// filesystem discovery, formatting or editable-index construction.
|
||||
///
|
||||
/// Caller policy is applied across the entire bundle with the existing bounded
|
||||
/// source8 syntax/storage/numeric policy; this is not an all-heap memory bound.
|
||||
/// Ordinary parse/compile/edit/save APIs and CLI discovery remain separate.
|
||||
///
|
||||
/// ```
|
||||
/// use oa_core::{language::check_mixed_bundle, source_project::{SourceBundle, ProjectLimits}};
|
||||
/// let bundle = SourceBundle {
|
||||
/// entry: "main.arcadia".into(),
|
||||
/// files: [("main.arcadia".into(),
|
||||
/// "arcadia 8; project p \"P\" id \"00000000-0000-0000-0000-000000000001\" {}".into())].into(),
|
||||
/// };
|
||||
/// let checked = check_mixed_bundle(&bundle, ProjectLimits::default()).unwrap();
|
||||
/// assert_eq!(checked.project().alias, "p");
|
||||
/// assert!(checked.warnings().is_empty());
|
||||
/// ```
|
||||
pub fn check_mixed_bundle(
|
||||
bundle: &crate::source_project::SourceBundle,
|
||||
limits: crate::source_project::ProjectLimits,
|
||||
) -> Result<CheckedMixed<'_>, Vec<SourceDiagnostic>> {
|
||||
let syntax = super::carried::parse_fresh(bundle, limits)?;
|
||||
let resolved = resolve(syntax).map_err(|failure| failure.diagnostics)?;
|
||||
Ok(CheckedMixed { resolved })
|
||||
}
|
||||
/// Read-only authority: every component originates in the consumed syntax.
|
||||
pub(in crate::language::analysis_source) struct ResolvedMixedProject<'s> {
|
||||
// Retained ownership/admission evidence; consumed by the private test-only edit chain.
|
||||
#[cfg_attr(not(test), allow(dead_code))]
|
||||
syntax: ParsedMixedSyntax<'s>,
|
||||
project: Project,
|
||||
warnings: Vec<SourceDiagnostic>,
|
||||
// Retained ownership/admission evidence; consumed by the private test-only edit chain.
|
||||
#[cfg_attr(not(test), allow(dead_code))]
|
||||
static_limits: analysis::AdmissionLimits,
|
||||
// Retained ownership/admission evidence; consumed by the private test-only edit chain.
|
||||
#[cfg_attr(not(test), allow(dead_code))]
|
||||
static_usage: analysis::AdmissionUsage,
|
||||
// Retained ownership/admission evidence; consumed by the private test-only edit chain.
|
||||
#[cfg_attr(not(test), allow(dead_code))]
|
||||
expressions: Vec<lower::ExpressionSite>,
|
||||
// Retained ownership/admission evidence; consumed by the private test-only edit chain.
|
||||
#[cfg_attr(not(test), allow(dead_code))]
|
||||
occurrences: Vec<lower::Occurrence>,
|
||||
// Retained ownership/admission evidence; consumed by the private test-only edit chain.
|
||||
#[cfg_attr(not(test), allow(dead_code))]
|
||||
storage: Ledger,
|
||||
}
|
||||
impl ResolvedMixedProject<'_> {
|
||||
@@ -51,10 +142,12 @@ impl ResolvedMixedProject<'_> {
|
||||
/// Complete original bundle authority with a typed-only occurrence view.
|
||||
/// Ordinary globals are catalogued, but their authored occurrences are not indexed.
|
||||
/// This owner cannot be converted into the legacy typed-only edit authority.
|
||||
#[cfg(test)]
|
||||
pub(in crate::language::analysis_source) struct IndexedMixedProject<'s> {
|
||||
resolved: ResolvedMixedProject<'s>,
|
||||
data: super::super::index::IndexData,
|
||||
}
|
||||
#[cfg(test)]
|
||||
impl<'s> ResolvedMixedProject<'s> {
|
||||
pub fn index(mut self) -> Result<IndexedMixedProject<'s>, Failure> {
|
||||
let input = IndexInput {
|
||||
@@ -72,6 +165,7 @@ impl<'s> ResolvedMixedProject<'s> {
|
||||
})
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
impl IndexedMixedProject<'_> {
|
||||
pub fn project(&self) -> &Project {
|
||||
self.resolved.project()
|
||||
@@ -108,12 +202,14 @@ impl IndexedMixedProject<'_> {
|
||||
|
||||
/// Constructor-only borrowed adapter. Its private file table prevents fabrication;
|
||||
/// no API returns it and no alternative source/token table can be installed.
|
||||
#[cfg(test)]
|
||||
pub(in crate::language::analysis_source) struct IndexInput<'a> {
|
||||
files: &'a [FileSyntax<'a>],
|
||||
pub(in crate::language::analysis_source) project: &'a Project,
|
||||
pub(in crate::language::analysis_source) expressions: &'a [lower::ExpressionSite],
|
||||
pub(in crate::language::analysis_source) occurrences: &'a [lower::Occurrence],
|
||||
}
|
||||
#[cfg(test)]
|
||||
impl IndexInput<'_> {
|
||||
pub(in crate::language::analysis_source) fn declarations(
|
||||
&self,
|
||||
@@ -140,6 +236,7 @@ pub(in crate::language::analysis_source) struct Failure {
|
||||
}
|
||||
impl Failure {
|
||||
/// Canonical attribution when justified; otherwise a safe explicit start fallback.
|
||||
#[cfg(test)]
|
||||
fn index(syntax: &ParsedMixedSyntax<'_>, mut failure: lower::Failure) -> Self {
|
||||
failure.file = failure.file.filter(|&f| f < syntax.files.len());
|
||||
if failure.file.is_none_or(|file| {
|
||||
|
||||
@@ -633,6 +633,23 @@ a { text-decoration: none; }
|
||||
<dd class="text-suffix"><p><span class="docutils literal">.arcadia</span></p>
|
||||
</dd>
|
||||
</dl>
|
||||
<section id="staged-source8-rust-read-api">
|
||||
<h2>Staged source8 Rust read API</h2>
|
||||
<p>The core library separately offers <span class="docutils literal"><span class="pre">language::check_mixed_bundle(&SourceBundle,</span> ProjectLimits)</span> for complete ordinary/typed source8 validation. Its opaque
|
||||
<span class="docutils literal">CheckedMixed</span> result borrows the unchanged input and exposes only <span class="docutils literal">project()</span>
|
||||
and <span class="docutils literal">warnings()</span>. Explicit UUIDs are required for the project, every ordinary
|
||||
and typed declaration, and typed parameters; no IDs are generated. All supplied
|
||||
canonical files must be reachable from the entry. Errors return no admitted
|
||||
model; success preserves actual located warnings and the complete model.</p>
|
||||
<p>This bounded API selects both retained text and entry producer checks and carries
|
||||
the caller's whole-project policy through admission. It does not promise an
|
||||
all-heap memory bound. It performs no filesystem discovery, migration, formatting,
|
||||
source stabilization, indexing, saving or history installation. Existing ordinary
|
||||
parse/compile/update APIs, CLI commands, desktop and server flows remain separate;
|
||||
a read result does not grant their typed editing or save authority. Generic and
|
||||
typed depth policies and platform qualification limits remain unchanged. This
|
||||
staged capability does not replace the broader conformance obligations below.</p>
|
||||
</section>
|
||||
<section id="scope-and-conformance">
|
||||
<h2>1. Scope and conformance</h2>
|
||||
<p>This specification defines the shared Operational Analysis (OA) and System
|
||||
|
||||
@@ -6,6 +6,26 @@ Arcadia Studio OA / SA Language and Editing Contract
|
||||
:Core implementation: ``crates/oa-core``
|
||||
:Text suffix: ``.arcadia``
|
||||
|
||||
Staged source8 Rust read API
|
||||
----------------------------------------
|
||||
|
||||
The core library separately offers ``language::check_mixed_bundle(&SourceBundle,
|
||||
ProjectLimits)`` for complete ordinary/typed source8 validation. Its opaque
|
||||
``CheckedMixed`` result borrows the unchanged input and exposes only ``project()``
|
||||
and ``warnings()``. Explicit UUIDs are required for the project, every ordinary
|
||||
and typed declaration, and typed parameters; no IDs are generated. All supplied
|
||||
canonical files must be reachable from the entry. Errors return no admitted
|
||||
model; success preserves actual located warnings and the complete model.
|
||||
|
||||
This bounded API selects both retained text and entry producer checks and carries
|
||||
the caller's whole-project policy through admission. It does not promise an
|
||||
all-heap memory bound. It performs no filesystem discovery, migration, formatting,
|
||||
source stabilization, indexing, saving or history installation. Existing ordinary
|
||||
parse/compile/update APIs, CLI commands, desktop and server flows remain separate;
|
||||
a read result does not grant their typed editing or save authority. Generic and
|
||||
typed depth policies and platform qualification limits remain unchanged. This
|
||||
staged capability does not replace the broader conformance obligations below.
|
||||
|
||||
1. Scope and conformance
|
||||
------------------------
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# Public mixed read implementation
|
||||
|
||||
Base7b06b8ea, selected root contract bcddaba3. Public
|
||||
language::check_mixed_bundle returns opaque non-Clone CheckedMixed borrowing the
|
||||
complete original SourceBundle, with only immutable project/warnings accessors.
|
||||
Its private resolved field retains syntax, expression/occurrence vectors, ledger
|
||||
and admission evidence; there is no index, output, save or extraction authority.
|
||||
|
||||
The shared parser mixed hooks now compile in production. Ordinary callers still
|
||||
select None. The fresh constructor binds the actual bundle and exact ProjectLimits,
|
||||
derives existing internal policy once, selects Some(Borrowed) with zero carry and
|
||||
both producers, then consumes syntax through complete resolution/admission.
|
||||
The borrowed guard retains the actual source reference even when test-only
|
||||
Pending/Retained variants are absent. No duplicate live policy/model is added.
|
||||
Public schema/UUID requirement wording replaces private-staging wording only;
|
||||
all limiter phase/category/span transport remains inherited.
|
||||
|
||||
Private legacy index/update/write, ordinary-index/rename/emission/acceptance and
|
||||
fixture/pending constructors remain test-only. Shared parse/lower code remains
|
||||
private. Necessary retained fields have individual documented non-test dead-code
|
||||
allowances; no module-wide suppression, fake read, or new public lint-only API.
|
||||
Exact production compiler lint findings remain a parent gate, not inferred clean
|
||||
from source inspection. Existing298 tests are not edited.
|
||||
|
||||
One internal public_read test invokes the actual production parse helper and
|
||||
observes borrowedC0, both producer inventories, every inherited/derived policy
|
||||
field (including262144 entries independent of model records1), full empty-Project
|
||||
and warning parity, Usage/numeric parity and unchanged input. Three rustdoc blocks
|
||||
provide a compiling real usage example plus source-drop and source-mutation
|
||||
compile-fail cases with later result use. Independent worker owns the non-test
|
||||
mixed_source8_read integration target and its separate complete-model oracles.
|
||||
|
||||
Established docs/language.rst gains a bounded staged-read note without changing
|
||||
its broader conformance/version obligations. Existing docutils HTML5 generation
|
||||
with halt/report level2 regenerated language.html successfully. An initial local
|
||||
RST generation refused the new section formatting; the section separator and
|
||||
blank line were corrected before successful regeneration. No Rust compiler,
|
||||
Cargo test, CLI, debugger or publication was run by this worker.
|
||||
|
||||
Parent owns pinned compilation, all old/new runtime tests, doctests, strict
|
||||
Clippy, formatting disposition and production Parser stack regression checks.
|
||||
DEPTH_128_VALIDATED=false, existing depth policies, logical-accounting exclusions,
|
||||
full-pipeline limitation and deferred Linux qualification remain unchanged.
|
||||
Reference in New Issue
Block a user