Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 11 additions & 2 deletions dag/gunbc/scm/checkout.dag
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ type CheckoutOutcome
// the same name, substitute a plausible one, or fall back to whatever the store currently holds --
// each of those is the fabricated-plausible-output arm DESIGN section 5 forbids, and each would make
// the program's meaning depend on the store's current population rather than on the commit.
// EDGES ACCUMULATE BY PREPEND AND ARE REVERSED ONCE AT THE CONSUMER. concat-at-end inside a fold
// copies the whole accumulator per child, so rebuilding one node cost O(children^2) for work that is
// O(children) -- the cost shape DESIGN section 6 says is always fixed regardless of the realized n,
// because "n is small here" is not a fact that stays true under reuse.
//
// The reverse is load-bearing, not cosmetic: these edges are handed to node_synthetic in order, and
// under a positional discipline the order IS the program. A dropped or doubled reverse would rebuild
// `f(x, y)` as `f(y, x)` silently.
type CheckoutEdgeAcc {
edges: List<Edge>
refusal: CheckoutRefusal?
Expand All @@ -49,7 +57,7 @@ fn checkout_edge_step(store: ObjectStore, acc: CheckoutEdgeAcc, stored: StoredEd
CheckoutEdgeAcc { edges: acc.edges, refusal: Present { value: cause } }
CheckedOut { program: child } =>
CheckoutEdgeAcc {
edges: concat(acc.edges, [Edge { label: stored.label, target: child }]),
edges: concat([Edge { label: stored.label, target: child }], acc.edges),
refusal: acc.refusal,
}
}
Expand All @@ -65,7 +73,8 @@ fn checkout_record(store: ObjectStore, record: ObjectRecord) -> CheckoutOutcome
CheckoutEdgeAcc { edges: edges, refusal: refusal } =>
match refusal {
Present { value: cause } => CheckoutRefused { cause: cause }
Absent => CheckedOut { program: node_synthetic(kind: record.kind, children: edges) }
Absent =>
CheckedOut { program: node_synthetic(kind: record.kind, children: reverse(edges)) }
}
}
}
Expand Down
86 changes: 54 additions & 32 deletions dag/gunbc/scm/merge.dag
Original file line number Diff line number Diff line change
Expand Up @@ -300,16 +300,16 @@ fn target_roles(store: ObjectStore, target: Commit) -> TargetReadOutcome {
Absent => TargetUnreadable { cause: TargetRootMissing { identity: target.root } }
Present { value: record } =>
TargetRead {
roles: fold(record.children, init: [], f: fn(acc, stored) {
roles: reverse(fold(record.children, init: [], f: fn(acc, stored) {
match edge_label_role(label: stored.label) {
Absent => acc
Present { value: role } =>
concat(acc, [ResolvedRole {
concat([ResolvedRole {
role: role,
value: PreservedFromTarget { object: stored.target },
}])
}], acc)
}
}),
})),
}
}
}
Expand Down Expand Up @@ -339,14 +339,16 @@ fn role_is_present(roles: List<ResolvedRole>, role: Symbol) -> Bool {
}
}

// A REPLACEMENT IS A MAP, NOT A FOLD THAT REBUILDS. Written as a fold with concat-at-end this copied
// the accumulator once per role, making a single replacement O(roles^2); as a map it is O(roles) and
// the intent -- every role kept, one substituted -- is visible in the shape rather than reconstructed
// by reading the accumulator.
fn replaced_role_or_original(r: ResolvedRole, incoming: ResolvedRole) -> ResolvedRole {
if (r.role as String) == (incoming.role as String) { incoming } else { r }
}

fn replace_role(roles: List<ResolvedRole>, incoming: ResolvedRole) -> List<ResolvedRole> {
fold(roles, init: [], f: fn(acc, r) {
if (r.role as String) == (incoming.role as String) {
concat(acc, [incoming])
} else {
concat(acc, [r])
}
})
map(roles, r => replaced_role_or_original(r: r, incoming: incoming))
}

fn upsert_role(roles: List<ResolvedRole>, incoming: ResolvedRole) -> List<ResolvedRole> {
Expand Down Expand Up @@ -395,26 +397,30 @@ fn alternatives_contain(alternatives: List<DesiredRoleValue>, desired: DesiredRo
// for one ordering and (F3,F2) for another. Both are true statements and neither is the fact, and
// order deciding which one survives is precisely what this lane forbids. Accumulating every
// distinct alternative makes the reported set complete under any permutation.
fn authored_with_alternative(
a: AuthoredRoleState,
role: Symbol,
desired: DesiredRoleValue,
) -> AuthoredRoleState {
if (a.role as String) != (role as String) {
a
} else if alternatives_contain(alternatives: a.alternatives, desired: desired) {
a
} else {
AuthoredRoleState {
role: a.role,
alternatives: concat(a.alternatives, [desired]),
}
}
}

fn record_alternative(
authored: List<AuthoredRoleState>,
role: Symbol,
desired: DesiredRoleValue,
) -> List<AuthoredRoleState> {
if already_authored(authored: authored, role: role) {
fold(authored, init: [], f: fn(acc, a) {
if (a.role as String) == (role as String) {
if alternatives_contain(alternatives: a.alternatives, desired: desired) {
concat(acc, [a])
} else {
concat(acc, [AuthoredRoleState {
role: a.role,
alternatives: concat(a.alternatives, [desired]),
}])
}
} else {
concat(acc, [a])
}
})
map(authored, a => authored_with_alternative(a: a, role: role, desired: desired))
} else {
concat(authored, [AuthoredRoleState { role: role, alternatives: [desired] }])
}
Expand Down Expand Up @@ -529,13 +535,13 @@ fn effective_dependencies(
authored: List<Symbol>,
) -> List<MergeDependency> {
concat(
fold(target_dependencies, init: [], f: fn(acc, dep) {
reverse(fold(target_dependencies, init: [], f: fn(acc, dep) {
if role_name_listed(names: authored, role: dep.dependent) {
acc
} else {
concat(acc, [dep])
concat([dep], acc)
}
}),
})),
proposal_dependencies,
)
}
Expand Down Expand Up @@ -612,7 +618,7 @@ fn root_edge_for(store: ObjectStore, acc: MergeRootAcc, role: Symbol, object: Ob
}
CheckedOut { program: program } =>
MergeRootAcc {
edges: concat(acc.edges, [Edge { label: Named { name: role }, target: program }]),
edges: concat([Edge { label: Named { name: role }, target: program }], acc.edges),
refusal: none,
}
}
Expand Down Expand Up @@ -672,12 +678,28 @@ fn merge(
}
}

// A PROJECTION IS A MAP. As a fold with concat-at-end this was O(authored^2) to answer a question
// that is one field read per element.
fn authored_role_names(authored: List<AuthoredRoleState>) -> List<Symbol> {
fold(authored, init: [], f: fn(acc, a) { concat(acc, [a.role]) })
map(authored, a => authored_role_name(a: a))
}

fn authored_role_name(a: AuthoredRoleState) -> Symbol {
a.role
}

// A FLATTEN IS STILL A COPY PER STEP. An earlier revision of this sweep left this fold alone with a
// comment claiming that appending a LIST rather than an element made it linear. That is false --
// concat(acc, xs) copies acc whatever xs is, so the total stayed quadratic in the number of
// dependencies -- and it is recorded here because the wrong reason was more dangerous than the
// defect: a justified exception stops being re-examined.
//
// Prepending each proposal's dependencies REVERSED, then reversing once at the end, restores the
// original order at a total cost proportional to the output.
fn all_proposal_dependencies(proposals: List<Proposal>) -> List<MergeDependency> {
fold(proposals, init: [], f: fn(acc, p) { concat(acc, p.dependencies) })
reverse(
fold(proposals, init: [], f: fn(acc, p) { concat(reverse(p.dependencies), acc) })
)
}

fn finish_merge(
Expand All @@ -703,7 +725,7 @@ fn finish_merge(
Absent =>
match store_node(
store: applied.store,
n: root_node_of(edges: built.edges),
n: root_node_of(edges: reverse(built.edges)),
) {
LocatorCollision { identity: identity, existing: _, incoming: _ } =>
MergeRefused {
Expand Down
2 changes: 1 addition & 1 deletion src/v2/compiler/02_parse.dag
Original file line number Diff line number Diff line change
Expand Up @@ -2059,7 +2059,7 @@ fn parse_expr_terminal(
)
match dag_int_literal_node_from_lexeme(
lexeme: tok.lexeme,
occurrence_id: minted.id
occurrence_id: node_occurrence_minted(id: minted.id)
) {
Accepted { value: captured, diagnostics: _ } =>
ParseExprAccepted {
Expand Down
11 changes: 8 additions & 3 deletions src/v2/extdeps/languages/dag.dag
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ import v2.std.compilers.lexing {
lex_rule_set_token_class_member
}
import v2.std.node {
NodeOccurrenceId,
Arrow,
Atom,
Bind,
Expand All @@ -104,7 +105,6 @@ import v2.std.node {
SyntheticOccurrence,
is_empty_conj_root,
}
import std.occurrence_identity { OccurrenceId }
import v2.std.node_query { find_named_child }
import v2.std.compilers.target_model {
ProducedDeclUnwired,
Expand Down Expand Up @@ -4208,7 +4208,12 @@ fn dag_node_is_eval_literal_atom(node: Node) -> Bool {
}
}

fn dag_int_literal_node_from_magnitude(magnitude: DecimalMagnitude, occurrence_id: OccurrenceId) -> Node {
// THE PARAMETER IS THE NODE-CARRIER TYPE, NOT THE SHARED IDENTITY. `Node.occurrence_id` is a
// `NodeOccurrenceId` -- the coproduct of SyntheticOccurrence and MintedOccurrence -- and a bare
// `std.occurrence_identity.OccurrenceId` is the PAYLOAD INSIDE one of its arms, not a member of it.
// Declaring the parameter as the payload let a raw id be stored where the coproduct was expected,
// and every later total match over that field then failed at runtime on a value it had no arm for.
fn dag_int_literal_node_from_magnitude(magnitude: DecimalMagnitude, occurrence_id: NodeOccurrenceId) -> Node {
Node {
kind: TypeNode { connective: Atom { identity: ^dag_token_int_literal } },
children: [
Expand All @@ -4221,7 +4226,7 @@ fn dag_int_literal_node_from_magnitude(magnitude: DecimalMagnitude, occurrence_i
}
}

fn dag_int_literal_node_from_lexeme(lexeme: String, occurrence_id: OccurrenceId) -> Outcome<Node> {
fn dag_int_literal_node_from_lexeme(lexeme: String, occurrence_id: NodeOccurrenceId) -> Outcome<Node> {
match integer_string_to_decimal_digits_optional(s: lexeme) {
Present { value: digits } =>
outcome_accepted(
Expand Down