Skip to content
Merged
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
9 changes: 6 additions & 3 deletions IMPLEMENTATION_PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -356,9 +356,12 @@ priorities.
syntax node with independent origin, phase, timing, and cardinality rather than a
false shared/isolated scope flag. The inert additive public API skeleton,
enum/default snapshots, recorded local PowerShell probe evidence, and
design-corpus categories are delivered; no parser emits a region yet and
automated execution-region oracle coverage remains in task 7.7. Continue in
small slices: projection; pinned receiver/parameter binding including
design-corpus categories are delivered. The shared projector,
compatibility flattener, depth guard, decoded-wrapper cloning, and
executable-corpus DTOs now preserve direct and command-owned regions in
the locked substitution-host-region order. No parser emits a region yet,
and automated execution-region oracle coverage remains in task 7.7.
Continue in small slices: pinned receiver/parameter binding including
ForEach-Object multi-block phases; direct `&` / `.` and synchronous
current-runspace callbacks; child process/runspace jobs and parallel
blocks; deferred breakpoint/event/completion actions; then unknown
Expand Down
2 changes: 1 addition & 1 deletion openspec/changes/v0-3-structured-shell-analysis/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
- [x] 3.14 Add `ExecutionRegionSyntax`, its four discriminant enums,
`SimpleCommandSyntax.ExecutionRegions`, and appended occurrence/ancestry enum
members to the public API and snapshot without changing existing enum values.
- [ ] 3.15 Extend the structural projector, compatibility flattener, depth
- [x] 3.15 Extend the structural projector, compatibility flattener, depth
validation, cloning, and corpus DTOs so direct and command-owned execution
regions emit every body command exactly once in the locked order.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1832,6 +1832,25 @@ private static bool TryCloneDecodedNode(

var clauseOperator = firstLeaf ? firstOperator : simple.Clause.Operator;
firstLeaf = false;
var executionRegions = new List<ExecutionRegionSyntax>(
simple.ExecutionRegions.Count);
foreach (var executionRegion in simple.ExecutionRegions)
{
if (!TryCloneDecodedNode(
executionRegion,
firstOperator,
outerSubshell,
ref firstLeaf,
referenceMap,
out var clonedExecutionRegion) ||
clonedExecutionRegion is not ExecutionRegionSyntax typedExecutionRegion)
{
return false;
}

executionRegions.Add(typedExecutionRegion);
}

var clonedClause = simple.Clause with
{
Operator = clauseOperator,
Expand All @@ -1844,6 +1863,7 @@ private static bool TryCloneDecodedNode(
{
Clause = clonedClause,
Substitutions = substitutions,
ExecutionRegions = executionRegions,
};
referenceMap.Add(simple.Clause, clonedClause);
return true;
Expand Down Expand Up @@ -2025,6 +2045,28 @@ private static bool TryCloneDecodedNode(

clone = new CommandSubstitutionSyntax { Body = substitutionBody };
return true;
case ExecutionRegionSyntax executionRegion:
if (!TryCloneDecodedBlock(
executionRegion.Body,
firstOperator,
outerSubshell,
ref firstLeaf,
referenceMap,
out var executionBody))
{
return false;
}

clone = new ExecutionRegionSyntax
{
Origin = executionRegion.Origin,
HostClauseElementIndex = executionRegion.HostClauseElementIndex,
Phase = executionRegion.Phase,
Timing = executionRegion.Timing,
Cardinality = executionRegion.Cardinality,
Body = executionBody,
};
return true;
default:
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1755,6 +1755,22 @@ private static bool TryCloneDecodedNode(
var isFirst = state.LeafIndex == 0;
var isLast = state.LeafIndex == state.LeafCount - 1;
state.LeafIndex++;
var executionRegions = new List<ExecutionRegionSyntax>(
simple.ExecutionRegions.Count);
foreach (var executionRegion in simple.ExecutionRegions)
{
if (!TryCloneDecodedNode(
executionRegion,
state,
out var clonedExecutionRegion) ||
clonedExecutionRegion is not ExecutionRegionSyntax typedExecutionRegion)
{
return false;
}

executionRegions.Add(typedExecutionRegion);
}

var redirects = new List<Redirect>(simple.Clause.Redirects.Count +
(isLast ? state.WrapperRedirects.Count : 0));
redirects.AddRange(simple.Clause.Redirects);
Expand Down Expand Up @@ -1789,6 +1805,7 @@ private static bool TryCloneDecodedNode(
IsCommandStringWrapped = true,
},
Substitutions = substitutions,
ExecutionRegions = executionRegions,
};
return true;
case PipelineSyntax pipeline:
Expand Down Expand Up @@ -1900,6 +1917,22 @@ private static bool TryCloneDecodedNode(

clone = new CommandSubstitutionSyntax { Body = substitutionBody };
return true;
case ExecutionRegionSyntax executionRegion:
if (!TryCloneDecodedBlock(executionRegion.Body, state, out var executionBody))
{
return false;
}

clone = new ExecutionRegionSyntax
{
Origin = executionRegion.Origin,
HostClauseElementIndex = executionRegion.HostClauseElementIndex,
Phase = executionRegion.Phase,
Timing = executionRegion.Timing,
Cardinality = executionRegion.Cardinality,
Body = executionBody,
};
return true;
default:
return false;
}
Expand Down
133 changes: 123 additions & 10 deletions src/ShellSyntaxTree/ShellSyntaxProjection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ private sealed class ProjectionWalker
private readonly List<CommandAncestryFrame> _ancestry = new();
private readonly List<CommandOccurrence> _commands = new();
private readonly List<Clause> _clauses = new();
private bool _structuralContextIsComplete = true;
private readonly HashSet<ShellSyntaxNode> _visitedNodes =
new(NodeReferenceComparer.Instance);
private readonly HashSet<Clause> _visitedClauses =
Expand Down Expand Up @@ -138,7 +139,8 @@ private bool TryVisit(
CommandOccurrenceRole role,
int structuralDepth,
bool isRoot = false,
int? substitutionChildIndex = null)
int? nestedCollectionChildIndex = null,
bool isAttachedExecutionRegion = false)
{
if (node is null ||
!IsValidSpan(node.SourceStart, node.SourceLength) ||
Expand Down Expand Up @@ -178,8 +180,14 @@ loop.LoopKind is ConditionLoopKind.While or ConditionLoopKind.Until &&
CommandSubstitutionSyntax substitution =>
TryVisitCommandSubstitution(
substitution,
substitutionChildIndex,
nestedCollectionChildIndex,
nextDepth),
ExecutionRegionSyntax executionRegion =>
TryVisitExecutionRegion(
executionRegion,
nestedCollectionChildIndex,
nextDepth,
isAttachedExecutionRegion),
_ => false,
};

Expand Down Expand Up @@ -224,7 +232,11 @@ private bool TryVisitSimple(
CommandOccurrenceRole role,
int structuralDepth)
{
if (simple.Substitutions is null)
if (simple.Substitutions is null ||
simple.ExecutionRegions is null ||
simple.Clause is null ||
!IsValidClauseShape(simple.Clause) ||
!AreValidAttachedExecutionRegions(simple.Clause, simple.ExecutionRegions))
{
return false;
}
Expand All @@ -237,15 +249,13 @@ private bool TryVisitSimple(
substitution,
CommandOccurrenceRole.Substitution,
structuralDepth,
substitutionChildIndex: index))
nestedCollectionChildIndex: index))
{
return false;
}
}

if (simple.Clause is null ||
!IsValidClauseShape(simple.Clause) ||
!_visitedClauses.Add(simple.Clause))
if (!_visitedClauses.Add(simple.Clause))
{
return false;
}
Expand All @@ -269,9 +279,24 @@ private bool TryVisitSimple(
EffectiveArguments = effectiveArguments,
WorkingDirectory = workingDirectory,
Redirects = redirects,
IsComplete = facts.IsComplete,
IsComplete = facts.IsComplete && _structuralContextIsComplete &&
AreExecutionRegionFactsComplete(simple.ExecutionRegions),
});
_clauses.Add(simple.Clause);

for (var index = 0; index < simple.ExecutionRegions.Count; index++)
{
if (!TryVisit(
simple.ExecutionRegions[index],
CommandOccurrenceRole.ExecutionRegion,
structuralDepth,
nestedCollectionChildIndex: index,
isAttachedExecutionRegion: true))
{
return false;
}
}

return true;
}

Expand All @@ -288,6 +313,30 @@ substitution.Body is not null &&
CommandOccurrenceRole.Substitution,
structuralDepth);

private bool TryVisitExecutionRegion(
ExecutionRegionSyntax executionRegion,
int? childIndex,
int structuralDepth,
bool isAttachedToSimple)
{
if (!IsValidExecutionRegion(executionRegion, isAttachedToSimple))
{
return false;
}

var priorCompleteness = _structuralContextIsComplete;
_structuralContextIsComplete &= IsExecutionRegionFactComplete(executionRegion);
var succeeded = TryVisitChild(
executionRegion,
executionRegion.Body,
CommandAncestryRegion.ExecutionRegion,
childIndex,
CommandOccurrenceRole.ExecutionRegion,
structuralDepth);
_structuralContextIsComplete = priorCompleteness;
return succeeded;
}

private bool TryVisitPipeline(
PipelineSyntax pipeline,
int structuralDepth)
Expand Down Expand Up @@ -485,7 +534,7 @@ private bool TryVisitChild(
child,
role,
structuralDepth,
substitutionChildIndex: child is CommandSubstitutionSyntax
nestedCollectionChildIndex: child is CommandSubstitutionSyntax or ExecutionRegionSyntax
? childIndex
: null);
_ancestry.RemoveAt(_ancestry.Count - 1);
Expand All @@ -497,7 +546,71 @@ node is ForEachSyntax or
ConditionLoopSyntax or
ConditionalSyntax or
GroupSyntax or
CommandSubstitutionSyntax;
CommandSubstitutionSyntax or
ExecutionRegionSyntax;

private static bool AreValidAttachedExecutionRegions(
Clause clause,
IReadOnlyList<ExecutionRegionSyntax> executionRegions)
{
var hostCoordinates = new HashSet<int>();
for (var index = 0; index < executionRegions.Count; index++)
{
var region = executionRegions[index];
if (region is null ||
!IsValidExecutionRegion(region, isAttachedToSimple: true) ||
region.HostClauseElementIndex >= clause.Elements.Count ||
!hostCoordinates.Add(region.HostClauseElementIndex!.Value) ||
clause.Elements[region.HostClauseElementIndex.Value].Role !=
ClauseElementRole.Argument ||
clause.Elements[region.HostClauseElementIndex.Value].Kind !=
ArgKind.DynamicSkip)
{
return false;
}
}

return true;
}

private static bool IsValidExecutionRegion(
ExecutionRegionSyntax executionRegion,
bool isAttachedToSimple) =>
executionRegion.Body is not null &&
Enum.IsDefined(typeof(ExecutionRegionOrigin), executionRegion.Origin) &&
Enum.IsDefined(typeof(ExecutionRegionPhase), executionRegion.Phase) &&
Enum.IsDefined(typeof(ExecutionRegionTiming), executionRegion.Timing) &&
Enum.IsDefined(
typeof(ExecutionRegionCardinality),
executionRegion.Cardinality) &&
executionRegion.Origin switch
{
ExecutionRegionOrigin.DirectCall or ExecutionRegionOrigin.DotSource =>
!isAttachedToSimple && executionRegion.HostClauseElementIndex is null,
ExecutionRegionOrigin.CommandArgument =>
isAttachedToSimple && executionRegion.HostClauseElementIndex >= 0,
_ => false,
};

private static bool AreExecutionRegionFactsComplete(
IReadOnlyList<ExecutionRegionSyntax> executionRegions)
{
for (var index = 0; index < executionRegions.Count; index++)
{
if (!IsExecutionRegionFactComplete(executionRegions[index]))
{
return false;
}
}

return true;
}

private static bool IsExecutionRegionFactComplete(
ExecutionRegionSyntax executionRegion) =>
executionRegion.Phase != ExecutionRegionPhase.Unknown &&
executionRegion.Timing != ExecutionRegionTiming.Unknown &&
executionRegion.Cardinality != ExecutionRegionCardinality.Unknown;

private static bool TryCopyFacts(
Clause clause,
Expand Down
Loading