Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
71b6dee
WIP: R2 Substrate
briansrls Apr 26, 2026
4fc8f34
WIP: R2 Substrate
briansrls Apr 27, 2026
efb76b0
fix(v3): preserve opacity through instantiation walks
briansrls Apr 27, 2026
7fd0c43
WIP: R2 Substrate
briansrls Apr 27, 2026
fbef4d4
fix(v3): include opacity in instantiation interning
briansrls Apr 27, 2026
47ceb07
WIP: R2 Substrate
briansrls Apr 27, 2026
827d2ba
chore: apply cargo fmt
briansrls Apr 27, 2026
aac71d1
WIP: R2 Substrate
briansrls Apr 27, 2026
35edd13
Update map carrier bootstrap snapshot
briansrls Apr 27, 2026
85a5ce5
Trigger CI for ready map carrier PR
briansrls Apr 27, 2026
3d6a05f
WIP: R2 Substrate
briansrls Apr 27, 2026
9750289
WIP: R2 Substrate
briansrls Apr 27, 2026
b8dec10
Fix tokenize regen after map carrier
briansrls Apr 27, 2026
e706a8a
Clarify map carrier rustdoc debt
briansrls Apr 27, 2026
925e09a
Fix regen tokenize clippy
briansrls Apr 27, 2026
3f5bdca
WIP: R2 Substrate
briansrls Apr 27, 2026
2f898c9
WIP: R2 Substrate
briansrls Apr 27, 2026
e8f1dbc
Fix map substrate reflection snapshots
briansrls Apr 27, 2026
2ce685f
WIP: R2 Substrate
briansrls Apr 27, 2026
45aa423
WIP: R2 Substrate
briansrls Apr 27, 2026
7f97808
Fix substrate reflection CI expectations
briansrls Apr 27, 2026
98e3613
Merge remote-tracking branch 'origin/main' into session/lively-ferret-24
briansrls Apr 27, 2026
81fbec4
WIP: R2 Substrate
briansrls Apr 27, 2026
4c9bfa2
Fix surface item reflection expectation
briansrls Apr 27, 2026
b9cfb6d
Allow generated parse RHS helper arity
briansrls Apr 27, 2026
1e2fa64
WIP: R2 Substrate
briansrls Apr 27, 2026
6ecb53d
Fix v2 parser authority for nominal opaque
briansrls Apr 27, 2026
254521e
WIP: R2 Substrate
briansrls Apr 27, 2026
b4cbf0e
Refresh v3 bootstrap after main merge
briansrls Apr 27, 2026
46d0e28
Merge remote-tracking branch 'origin/main' into session/lively-ferret-24
briansrls Apr 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dsl/std/types.dag
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ type HttpStatus = Int where range(min: 100, max: 599)
type Email = String where pattern("^[^@]+@[^@]+\\.[^@]+$")
type Port = Int where range(min: 1, max: 65535)
type GistId = String where format(uuid)
type Secret = String
type Secret nominal_opaque = String
type SecretValue = Secret where non_empty
type Url = String where pattern("^https?://")
type SemVer = String where pattern("^\\d+\\.\\d+\\.\\d+")
Expand Down
15 changes: 14 additions & 1 deletion src/v2/02_parse.dag
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,19 @@ fn tok_keyword_text(tok: Token?) -> String {
fn tok_is_ident(tok: Token?) -> Bool {
match tok { Some { value: t } => is_ident_shape(shape: t.shape) None => false }
}
fn tok_is_ident_text(tok: Token?, text: String) -> Bool {
match tok { Some { value: t } => is_ident_shape(shape: t.shape) && t.text == text None => false }
}
fn drop_leading_type_modifier(tokens: List<Token>, modifier: String) -> List<Token> {
if tok_is_ident_text(tok: tokens |> first, text: modifier) {
skip_newlines(tokens: tokens |> skip(1))
} else {
tokens
}
}
fn type_body_tokens_after_modifiers(tokens: List<Token>) -> List<Token> {
drop_leading_type_modifier(tokens: tokens, modifier: "nominal_opaque")
}
fn tok_is_newline(tok: Token?) -> Bool {
match tok { Some { value: t } => is_newline_shape(shape: t.shape) None => false }
}
Expand Down Expand Up @@ -1302,7 +1315,7 @@ fn parse_type_body_from_prefix(prefix: ItemPrefixResult, start_span: SourceSpan)
let type_params = prefix.type_params
let ctx = prefix.ctx
let named_dummy = Node { name: name, span: start_span, ident_span: Some { value: name_span }, children: [], params: type_params, inferred: none, return_cardinality: Required, uses: [], body: none, connective: NoConnective, transport: none, properties: [], type_annotation: none, is_self_recursive: false, has_non_tail_self_call: false, match_pattern: none, expr_data: NoExprData }
let tokens = skip_newlines(tokens: prefix.tokens)
let tokens = type_body_tokens_after_modifiers(tokens: skip_newlines(tokens: prefix.tokens))

// Record type: type Name { fields }
match eat(tokens: tokens, expected: ExpectLBrace) {
Expand Down
31 changes: 30 additions & 1 deletion src/v2/stage0/src/v2_compiler_parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1164,6 +1164,35 @@ pub fn tok_is_ident(tok: Option<Rc<Token>>) -> bool {
}
}

pub fn tok_is_ident_text(tok: Option<Rc<Token>>, text: String) -> bool {
match tok {
Some(t) => (is_ident_shape(t.shape.clone()) && (t.text.clone().as_str() == text.as_str())),
None => false,
}
}

pub fn drop_leading_type_modifier(
tokens: &Rc<Vec<Rc<Token>>>,
modifier: String,
) -> Rc<Vec<Rc<Token>>> {
if tok_is_ident_text(tokens.clone().first().cloned(), modifier) {
skip_newlines(Rc::new(
tokens
.clone()
.iter()
.cloned()
.skip(1 as usize)
.collect::<Vec<_>>(),
))
} else {
tokens.clone()
}
}

pub fn type_body_tokens_after_modifiers(tokens: Rc<Vec<Rc<Token>>>) -> Rc<Vec<Rc<Token>>> {
drop_leading_type_modifier(&tokens, "nominal_opaque".to_string())
}

pub fn tok_is_newline(tok: Option<Rc<Token>>) -> bool {
match tok {
Some(t) => is_newline_shape(t.shape.clone()),
Expand Down Expand Up @@ -3120,7 +3149,7 @@ pub fn parse_type_body_from_prefix(
expr_data: Rc::new(ExprData::NoExprData),
ident: None,
});
let tokens = skip_newlines(prefix.tokens.clone());
let tokens = type_body_tokens_after_modifiers(skip_newlines(prefix.tokens.clone()));
match (*eat(&tokens, Rc::new(ExpectedToken::ExpectLBrace))).clone() {
EatResult::EatConsumed { tokens: __ec, .. } => {
let r = parse_field_list(skip_newlines(__ec.clone()), ctx.clone());
Expand Down
32 changes: 31 additions & 1 deletion src/v3/compiler/parse_parser_body.txt
Original file line number Diff line number Diff line change
Expand Up @@ -620,11 +620,26 @@ impl<'a> Parser<'a> {
type_kw.span,
Some(inhabits),
Some(inhabits_clause_span),
false,
None,
)
}
TokenKind::Ident(ref s) if s == "nominal_opaque" => {
self.bump();
let eq = self.expect_kind(TokenKind::Eq)?;
self.parse_type_rhs_after_eq(
name,
type_params,
type_kw.span,
None,
None,
true,
Some(eq.span.clone()),
)
}
TokenKind::Eq => {
self.bump();
self.parse_type_rhs_after_eq(name, type_params, type_kw.span, None, None)
self.parse_type_rhs_after_eq(name, type_params, type_kw.span, None, None, false, None)
}
_ => Ok(SurfaceItem::TypeAtom {
name,
Expand Down Expand Up @@ -690,13 +705,16 @@ impl<'a> Parser<'a> {
///
/// Optional alias-RHS `where` (DB-11): `where <expr> [, <expr>]*` parses
/// to `SurfaceExpr` (comma-separated parts fold as left-associated `&&`).
#[allow(clippy::too_many_arguments)]
fn parse_type_rhs_after_eq(
&mut self,
name: String,
type_params: Vec<String>,
type_kw_span: SourceSpan,
inhabits: Option<SurfaceType>,
inhabits_clause_span: Option<SourceSpan>,
nominal_opaque: bool,
nominal_opaque_clause_span: Option<SourceSpan>,
) -> Result<SurfaceItem, Diagnostic> {
if !self.rhs_is_sum() {
if inhabits.is_some() {
Expand All @@ -719,11 +737,23 @@ impl<'a> Parser<'a> {
return Ok(SurfaceItem::TypeAlias {
name,
type_params,
nominal_opaque,
target,
refinement,
span: SourceSpan::new(self.file, type_kw_span.byte_start, end),
});
}
if nominal_opaque {
return Err(Diagnostic::ParseError {
message: String::from(
"`nominal_opaque` is only supported when the type RHS is an alias",
),
span: nominal_opaque_clause_span
.clone()
.unwrap_or_else(|| type_kw_span.clone()),
fixes: Vec::new(),
});
}

let variants = self.parse_sum_variants()?;
let end = variants
Expand Down
97 changes: 38 additions & 59 deletions src/v3/compiler/src/bin/regen_tokenize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,9 @@
//!
//! Scanner controls and tokenizer-local punctuation come from the lowered
//! tokenizer Dag, while token types are imported from `src/v3/std/tokenize.dag`.
//! Dedicated keywords and shared operators are derived from the shared syntax
//! authority text at `dsl/extdeps/languages/dag/syntax.dag`. The shared file's
//! `dag_keyword_set` / `dag_operators` data bodies still lower as `Unparsed`,
//! so this driver reads those two sections directly from source text rather
//! than inventing duplicate authored rows in `tokenize.dag`.
//! Named dissolution trigger: once those two shared syntax bodies lower as
//! `ValueBody::Structural`, this raw-source scaffold must be deleted and the
//! derivation must read the lowered Dag directly.
//! Dedicated keywords are derived from the lowered shared syntax authority at
//! `dsl/extdeps/languages/dag/syntax.dag`. Shared operators still use the
//! bounded raw-source bridge until `dag_operators` lowers structurally.

use std::collections::BTreeSet;
use std::io::Write;
Expand Down Expand Up @@ -51,8 +46,9 @@ fn main() {
let source = std::fs::read_to_string(&dag_path).expect("read tokenize.dag");
let dag = compile_authority_dag(&source, TOKENIZE_AUTHORITY_FILE);
let shared_syntax_source = read_shared_syntax_source(&manifest_dir);
assert_shared_syntax_raw_source_scaffold_still_required(&shared_syntax_source);
let shared_syntax = SharedSyntaxAuthority::parse(&shared_syntax_source);
let shared_syntax_dag = compile_shared_syntax_dag(&shared_syntax_source);
let shared_syntax =
SharedSyntaxAuthority::from_authority(&shared_syntax_dag, &shared_syntax_source);
let rust = generate(&dag, &shared_syntax);
let combined = format!("{HEADER}{rust}");

Expand Down Expand Up @@ -107,6 +103,14 @@ fn read_shared_syntax_source(manifest_dir: &std::path::Path) -> String {
})
}

fn compile_shared_syntax_dag(source: &str) -> Dag {
match compile_to_dag(source, SHARED_SYNTAX_FILE) {
Ok(dag) => dag,
Err(CompileError::Semantic(dag)) => dag,
Err(other) => panic!("compile {SHARED_SYNTAX_FILE}: {other:?}"),
}
}

fn generate(dag: &Dag, shared_syntax: &SharedSyntaxAuthority) -> String {
let keywords = collect_keyword_rows(dag, shared_syntax);
let ascii_scan_order = collect_ascii_scan_order(dag);
Expand Down Expand Up @@ -260,27 +264,6 @@ fn collect_ascii_scan_order(dag: &Dag) -> Vec<String> {
out
}

fn assert_shared_syntax_raw_source_scaffold_still_required(shared_syntax_source: &str) {
let lowered = match compile_to_dag(shared_syntax_source, SHARED_SYNTAX_FILE) {
Ok(dag) => dag,
Err(CompileError::Semantic(dag)) => dag,
Err(other) => panic!("compile {SHARED_SYNTAX_FILE}: {other:?}"),
};

for name in ["dag_keyword_set", "dag_operators"] {
let decl = lowered
.declaration_by_name(name)
.unwrap_or_else(|| panic!("missing `{name}` in `{SHARED_SYNTAX_FILE}`"));
if !matches!(decl.value_body, Some(ValueBody::Unparsed(_))) {
panic!(
"`{SHARED_SYNTAX_FILE}` data `{name}` no longer lowers as `ValueBody::Unparsed`; \
SG-1a raw-source scaffold must dissolve now. Delete the text extractor in \
`regen_tokenize` and derive from the lowered Dag directly."
);
}
}
}

fn string_data_named(dag: &Dag, expected_name: &str) -> String {
let decl = dag
.declarations()
Expand Down Expand Up @@ -549,10 +532,10 @@ fn keyword_spelling_for_token_kind(kind: &str) -> String {
.to_ascii_lowercase()
}

// SG-1a scaffold boundary: shared syntax still lowers through raw-source reads,
// SG-1a operator bridge: shared operators still lower through raw-source reads,
// so every `dag_operators` symbol must be classified explicitly here as either
// tokenizer punctuation or parser-only debt. Unknown symbols panic so upstream
// authority edits cannot silently disappear through the bridge.
// authority edits cannot silently disappear.
enum SharedOperatorTokenizerBoundary {
Tokenized { kind: &'static str },
ParserOnlyDebt { reason: &'static str },
Expand Down Expand Up @@ -596,22 +579,33 @@ struct SharedSyntaxAuthority {
}

impl SharedSyntaxAuthority {
fn parse(source: &str) -> Self {
fn from_authority(dag: &Dag, source: &str) -> Self {
let keywords = match data_body_named(dag, "dag_keyword_set") {
ValueBody::Map(entries) => entries.iter().map(|(key, _)| key.clone()).collect(),
other => panic!("dag_keyword_set: expected ValueBody::Map, got {other:?}"),
};
let operators = parse_named_string_fields(
extract_balanced_section(source, "data dag_operators", '[', ']'),
"symbol",
);
Self {
keywords: parse_map_string_keys(extract_balanced_section(
source,
"data dag_keyword_set",
'{',
'}',
)),
operators: parse_named_string_fields(
extract_balanced_section(source, "data dag_operators", '[', ']'),
"symbol",
),
keywords,
operators,
}
}
}

fn data_body_named<'a>(dag: &'a Dag, expected_name: &str) -> &'a ValueBody {
let decl = dag
.declarations()
.iter()
.find(|d| d.name.as_deref() == Some(expected_name))
.unwrap_or_else(|| panic!("missing `{expected_name}` data in `{SHARED_SYNTAX_FILE}`"));
decl.value_body
.as_ref()
.unwrap_or_else(|| panic!("`{expected_name}` has no lowered value body"))
}

fn extract_balanced_section<'a>(source: &'a str, anchor: &str, open: char, close: char) -> &'a str {
let anchor_idx = source
.find(anchor)
Expand All @@ -638,10 +632,6 @@ fn extract_balanced_section<'a>(source: &'a str, anchor: &str, open: char, close
);
}

fn parse_map_string_keys(section: &str) -> Vec<String> {
parse_all_string_literals(section)
}

fn parse_named_string_fields(section: &str, field_name: &str) -> Vec<String> {
let needle = format!("{field_name}:");
let mut out = Vec::new();
Expand All @@ -658,17 +648,6 @@ fn parse_named_string_fields(section: &str, field_name: &str) -> Vec<String> {
out
}

fn parse_all_string_literals(section: &str) -> Vec<String> {
let mut out = Vec::new();
let mut rest = section;
while let Some(idx) = rest.find('"') {
let (value, consumed) = parse_string_literal(&rest[idx..]);
out.push(value);
rest = &rest[idx + consumed..];
}
out
}

fn parse_string_literal(source: &str) -> (String, usize) {
assert!(
source.starts_with('"'),
Expand Down
3 changes: 1 addition & 2 deletions src/v3/compiler/src/bootstrap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,7 @@
// lowers with `value_body = ValueBody::List(_)`: both the type structure and
// the 10-element pilot enumeration are walkable by downstream grounding
// consumers. Map-shaped bootstrap data such as `kernel_algebra_profile`
// remains future debt for the map-shaped T-Substrate sibling lane; today it
// still lowers to `ValueBody::Unparsed`.
// now lowers through the sibling `ValueBody::Map` carrier.
//
// Transitional shape: PB-Bootstrap-Process lane (Zero-Floor program;
// tracked in `docs/design-pure-bootstrap-zero.md` §"New lanes") absorbs
Expand Down
2,184 changes: 1,094 additions & 1,090 deletions src/v3/compiler/src/bootstrap_generated.rs

Large diffs are not rendered by default.

1,984 changes: 994 additions & 990 deletions src/v3/compiler/src/bootstrap_generated_without_parse_surface.rs

Large diffs are not rendered by default.

Loading
Loading