@caupulican/pi-adaptative 0.84.1 → 0.85.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +53 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/index.d.ts.map +1 -1
- package/dist/bundled-resources/extensions/tmux-agent-manager/index.js +14 -8
- package/dist/bundled-resources/extensions/tmux-agent-manager/index.js.map +1 -1
- package/dist/bundled-resources/extensions/tmux-agent-manager/index.ts +14 -8
- package/dist/bundled-resources/runtimes/pi-shell-engine/arithmetic.py +364 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/context.py +2 -1
- package/dist/bundled-resources/runtimes/pi-shell-engine/errors.py +16 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/exec.py +137 -14
- package/dist/bundled-resources/runtimes/pi-shell-engine/nodes.py +23 -2
- package/dist/bundled-resources/runtimes/pi-shell-engine/parser.py +134 -6
- package/dist/bundled-resources/runtimes/pi-shell-engine/tokens.py +25 -2
- package/dist/bundled-resources/skills/deduplicate-by-evidence/SKILL.md +7 -5
- package/dist/bundled-resources/skills/worker-profile-writer/SKILL.md +63 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +8 -0
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/autonomy/lane-tool-surface.d.ts.map +1 -1
- package/dist/core/autonomy/lane-tool-surface.js +4 -2
- package/dist/core/autonomy/lane-tool-surface.js.map +1 -1
- package/dist/core/background-lane-controller.d.ts +3 -0
- package/dist/core/background-lane-controller.d.ts.map +1 -1
- package/dist/core/background-lane-controller.js +6 -0
- package/dist/core/background-lane-controller.js.map +1 -1
- package/dist/core/compaction-controller.d.ts +2 -0
- package/dist/core/compaction-controller.d.ts.map +1 -1
- package/dist/core/compaction-controller.js +35 -13
- package/dist/core/compaction-controller.js.map +1 -1
- package/dist/core/default-tool-surface.d.ts.map +1 -1
- package/dist/core/default-tool-surface.js +1 -0
- package/dist/core/default-tool-surface.js.map +1 -1
- package/dist/core/delegation/worker-delegation-controller.d.ts +7 -0
- package/dist/core/delegation/worker-delegation-controller.d.ts.map +1 -1
- package/dist/core/delegation/worker-delegation-controller.js +28 -0
- package/dist/core/delegation/worker-delegation-controller.js.map +1 -1
- package/dist/core/delegation/worker-profile-resolver.d.ts +2 -0
- package/dist/core/delegation/worker-profile-resolver.d.ts.map +1 -1
- package/dist/core/delegation/worker-profile-resolver.js +46 -11
- package/dist/core/delegation/worker-profile-resolver.js.map +1 -1
- package/dist/core/doctor.d.ts +1 -1
- package/dist/core/doctor.d.ts.map +1 -1
- package/dist/core/doctor.js +5 -4
- package/dist/core/doctor.js.map +1 -1
- package/dist/core/failure-corpus.d.ts +26 -1
- package/dist/core/failure-corpus.d.ts.map +1 -1
- package/dist/core/failure-corpus.js +17 -0
- package/dist/core/failure-corpus.js.map +1 -1
- package/dist/core/model-capability.d.ts.map +1 -1
- package/dist/core/model-capability.js +1 -0
- package/dist/core/model-capability.js.map +1 -1
- package/dist/core/model-tool-protocol.d.ts +3 -2
- package/dist/core/model-tool-protocol.d.ts.map +1 -1
- package/dist/core/model-tool-protocol.js +5 -1
- package/dist/core/model-tool-protocol.js.map +1 -1
- package/dist/core/models/runtime-process.d.ts +7 -1
- package/dist/core/models/runtime-process.d.ts.map +1 -1
- package/dist/core/models/runtime-process.js +4 -2
- package/dist/core/models/runtime-process.js.map +1 -1
- package/dist/core/orchestration/lane-tool-manifests.d.ts +1 -1
- package/dist/core/orchestration/lane-tool-manifests.d.ts.map +1 -1
- package/dist/core/orchestration/lane-tool-manifests.js +6 -1
- package/dist/core/orchestration/lane-tool-manifests.js.map +1 -1
- package/dist/core/orchestration/session-task-profile-store.d.ts +31 -0
- package/dist/core/orchestration/session-task-profile-store.d.ts.map +1 -0
- package/dist/core/orchestration/session-task-profile-store.js +138 -0
- package/dist/core/orchestration/session-task-profile-store.js.map +1 -0
- package/dist/core/orchestration/task-profile-writer.d.ts +58 -0
- package/dist/core/orchestration/task-profile-writer.d.ts.map +1 -0
- package/dist/core/orchestration/task-profile-writer.js +165 -0
- package/dist/core/orchestration/task-profile-writer.js.map +1 -0
- package/dist/core/process-matrix/runtime.js +43 -11
- package/dist/core/process-matrix/runtime.js.map +1 -1
- package/dist/core/resource-loader.d.ts +2 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +18 -6
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/runtime-builder.d.ts +3 -1
- package/dist/core/runtime-builder.d.ts.map +1 -1
- package/dist/core/runtime-builder.js +14 -0
- package/dist/core/runtime-builder.js.map +1 -1
- package/dist/core/security/untrusted-boundary.d.ts.map +1 -1
- package/dist/core/security/untrusted-boundary.js +4 -6
- package/dist/core/security/untrusted-boundary.js.map +1 -1
- package/dist/core/session-role.d.ts.map +1 -1
- package/dist/core/session-role.js +1 -0
- package/dist/core/session-role.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +21 -23
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tool-capability-policy.js +1 -1
- package/dist/core/tool-capability-policy.js.map +1 -1
- package/dist/core/tool-recovery-log-records.d.ts +33 -1
- package/dist/core/tool-recovery-log-records.d.ts.map +1 -1
- package/dist/core/tool-recovery-log-records.js +59 -12
- package/dist/core/tool-recovery-log-records.js.map +1 -1
- package/dist/core/tool-recovery-logger.d.ts +12 -1
- package/dist/core/tool-recovery-logger.d.ts.map +1 -1
- package/dist/core/tool-recovery-logger.js +24 -1
- package/dist/core/tool-recovery-logger.js.map +1 -1
- package/dist/core/tool-repair-health.d.ts.map +1 -1
- package/dist/core/tool-repair-health.js +6 -0
- package/dist/core/tool-repair-health.js.map +1 -1
- package/dist/core/tools/bash.d.ts +3 -8
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +4 -28
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/delegate.js +4 -4
- package/dist/core/tools/delegate.js.map +1 -1
- package/dist/core/tools/edit-diff.d.ts +32 -5
- package/dist/core/tools/edit-diff.d.ts.map +1 -1
- package/dist/core/tools/edit-diff.js +137 -69
- package/dist/core/tools/edit-diff.js.map +1 -1
- package/dist/core/tools/edit.d.ts +22 -7
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +128 -55
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/file-mutation-intent.d.ts +70 -0
- package/dist/core/tools/file-mutation-intent.d.ts.map +1 -0
- package/dist/core/tools/file-mutation-intent.js +325 -0
- package/dist/core/tools/file-mutation-intent.js.map +1 -0
- package/dist/core/tools/find.d.ts +2 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +2 -6
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/goal.d.ts.map +1 -1
- package/dist/core/tools/goal.js +41 -18
- package/dist/core/tools/goal.js.map +1 -1
- package/dist/core/tools/grep.d.ts +2 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +2 -6
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/index.d.ts +3 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +31 -8
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/managed-search-tool.d.ts +8 -0
- package/dist/core/tools/managed-search-tool.d.ts.map +1 -0
- package/dist/core/tools/managed-search-tool.js +10 -0
- package/dist/core/tools/managed-search-tool.js.map +1 -0
- package/dist/core/tools/profile-writer.d.ts +33 -0
- package/dist/core/tools/profile-writer.d.ts.map +1 -0
- package/dist/core/tools/profile-writer.js +67 -0
- package/dist/core/tools/profile-writer.js.map +1 -0
- package/dist/core/tools/shell-command-parser.d.ts +4 -3
- package/dist/core/tools/shell-command-parser.d.ts.map +1 -1
- package/dist/core/tools/shell-command-parser.js +32 -7
- package/dist/core/tools/shell-command-parser.js.map +1 -1
- package/dist/core/tools/shell-contract-router.d.ts.map +1 -1
- package/dist/core/tools/shell-contract-router.js +23 -1
- package/dist/core/tools/shell-contract-router.js.map +1 -1
- package/dist/core/tools/task-steps.d.ts.map +1 -1
- package/dist/core/tools/task-steps.js +6 -11
- package/dist/core/tools/task-steps.js.map +1 -1
- package/dist/core/tools/tmux-dispatch.d.ts +2 -0
- package/dist/core/tools/tmux-dispatch.d.ts.map +1 -1
- package/dist/core/tools/tmux-dispatch.js +16 -2
- package/dist/core/tools/tmux-dispatch.js.map +1 -1
- package/dist/core/tools/windows-shell-engine.js +1 -1
- package/dist/core/tools/windows-shell-engine.js.map +1 -1
- package/dist/core/tools/write.d.ts +24 -7
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +182 -107
- package/dist/core/tools/write.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/jscpd-cli.d.ts +3 -0
- package/dist/jscpd-cli.d.ts.map +1 -0
- package/dist/jscpd-cli.js +22 -0
- package/dist/jscpd-cli.js.map +1 -0
- package/dist/modes/interactive/components/profile-resource-editor.js +2 -2
- package/dist/modes/interactive/components/profile-resource-editor.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +1 -0
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +85 -3
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +1 -1
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/utils/bundled-jscpd.d.ts +20 -0
- package/dist/utils/bundled-jscpd.d.ts.map +1 -0
- package/dist/utils/bundled-jscpd.js +154 -0
- package/dist/utils/bundled-jscpd.js.map +1 -0
- package/dist/utils/paths.js +1 -1
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/shell.d.ts.map +1 -1
- package/dist/utils/shell.js +11 -0
- package/dist/utils/shell.js.map +1 -1
- package/dist/utils/tools-manager.d.ts +19 -2
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +92 -40
- package/dist/utils/tools-manager.js.map +1 -1
- package/docs/index.md +3 -0
- package/docs/managed-data-tools.md +15 -0
- package/docs/models.md +1 -1
- package/docs/quickstart.md +2 -2
- package/docs/settings.md +1 -1
- package/docs/tool-boundary-performance-roadmap-2026-08-02.md +166 -0
- package/docs/tool-repair.md +4 -4
- package/docs/windows.md +7 -5
- package/docs/worker-profiles.md +16 -0
- package/examples/extensions/built-in-tool-renderer.ts +12 -5
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/minimal-mode.ts +8 -7
- package/examples/extensions/prompt-customizer.ts +4 -2
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/ssh.ts +113 -24
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +112 -12
- package/package.json +7 -5
|
@@ -592,25 +592,29 @@ function formatDetection(d: TmuxDetection): string {
|
|
|
592
592
|
}
|
|
593
593
|
function setupHelp(d: TmuxDetection): string {
|
|
594
594
|
if (d.cliAvailable)
|
|
595
|
-
return `tmux CLI available: ${d.tmuxBin || "tmux"}\nUse
|
|
595
|
+
return `tmux CLI available: ${d.tmuxBin || "tmux"}\nUse native delegate for ordinary subagents. Use tmux_agent_manager only for an owner-requested persistent interactive pane; set dryRun=true only when a preview is useful.`;
|
|
596
596
|
return [
|
|
597
597
|
"tmux CLI not found.",
|
|
598
|
-
"
|
|
598
|
+
"Ordinary subagents remain available through native delegate; do not abort delegation.",
|
|
599
|
+
"Install tmux only if persistent interactive provider panes are required, then reload Pi:",
|
|
599
600
|
"- Debian/Ubuntu/WSL: sudo apt-get install tmux",
|
|
600
601
|
"- Fedora: sudo dnf install tmux",
|
|
601
602
|
"- Arch: sudo pacman -S tmux",
|
|
602
603
|
"- macOS: brew install tmux",
|
|
603
|
-
"- Windows: use WSL
|
|
604
|
+
"- Native Windows: use native delegate. WSL may use tmux when it is installed on PATH.",
|
|
604
605
|
"cmux is macOS-only and intentionally not used by this tmux manager.",
|
|
605
606
|
].join("\n");
|
|
606
607
|
}
|
|
607
608
|
async function guardTmux(_ctx: ExtensionContext, detection: TmuxDetection, purpose: string) {
|
|
608
609
|
if (detection.cliAvailable) return { allowed: true, text: `tmux available for ${purpose}.` };
|
|
609
|
-
return {
|
|
610
|
+
return {
|
|
611
|
+
allowed: false,
|
|
612
|
+
text: `${setupHelp(detection)}\n\nTmux route unavailable. Continue ordinary subagent work with native delegate.`,
|
|
613
|
+
};
|
|
610
614
|
}
|
|
611
615
|
function tmuxManagerInstructions(toolName: string, detection: TmuxDetection): string {
|
|
612
616
|
return [
|
|
613
|
-
`tmux is available (${detection.version || detection.tmuxBin || "tmux"}).
|
|
617
|
+
`tmux is available (${detection.version || detection.tmuxBin || "tmux"}). Native delegate remains the standard subagent route. Use tmux_agent_manager for managed ${toolName} panes only when the owner explicitly requested persistent interactive provider processes.`,
|
|
614
618
|
"Suggested next call:",
|
|
615
619
|
'tmux_agent_manager({ action: "fire_task", teamTemplate: "builder-validator", task: "<objective>" })',
|
|
616
620
|
"The launch returns immediately after panes, event watchers, and prompt handoffs are armed.",
|
|
@@ -2195,11 +2199,13 @@ export default function tmuxAgentManagerExtension(pi: ExtensionAPI) {
|
|
|
2195
2199
|
name: "tmux_agent_manager",
|
|
2196
2200
|
label: "tmux Agent Manager",
|
|
2197
2201
|
description:
|
|
2198
|
-
"tmux-
|
|
2202
|
+
"Specialized tmux-backed interactive provider/workspace manager. Native delegate is the standard cross-platform subagent path. Use this tool only when the owner explicitly requests persistent interactive CLI panes and tmux is available.",
|
|
2199
2203
|
promptSnippet:
|
|
2200
|
-
"Use
|
|
2204
|
+
"Use native delegate for ordinary subagents. Use tmux only for owner-requested persistent interactive provider panes after action=guard confirms availability.",
|
|
2201
2205
|
promptGuidelines: [
|
|
2202
|
-
"Use
|
|
2206
|
+
"Use native delegate/delegate_status for ordinary subagent work on every OS. Use tmux_agent_manager only when the owner explicitly requests persistent interactive provider panes and action=guard confirms tmux is available.",
|
|
2207
|
+
"On native Windows, do not attempt tmux dispatch. Native delegate is the supported backend; WSL may use tmux only when installed and explicitly requested.",
|
|
2208
|
+
"Never create or edit orchestration profiles during dispatch; native delegation's owner-configured profile resolver is authoritative.",
|
|
2203
2209
|
"Prefer action=fire_task for interactive worker batches. It returns after event watchers and prompts are armed; do not wait, poll, or peek for completion.",
|
|
2204
2210
|
"Use action=list_templates/show_template before assembling repeated teams; pass teamTemplate when a built-in team fits.",
|
|
2205
2211
|
"Use action=workspace_plan before launch_workspace when designing a pane layout.",
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
"""Safe integer-expression parser/evaluator for arithmetic ``for ((...))`` loops.
|
|
2
|
+
|
|
3
|
+
The evaluator deliberately does not call Python ``eval``. It accepts the integer,
|
|
4
|
+
variable, update, assignment, comparison, logical, bitwise, and conditional operators
|
|
5
|
+
used by Bash loop headers, then applies signed 64-bit wrapping after arithmetic writes.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
|
|
13
|
+
from state import ShellState
|
|
14
|
+
|
|
15
|
+
_MASK = (1 << 64) - 1
|
|
16
|
+
_SIGN_BIT = 1 << 63
|
|
17
|
+
_ASSIGNMENT_OPERATORS = frozenset(
|
|
18
|
+
{"=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", "&=", "^=", "|="}
|
|
19
|
+
)
|
|
20
|
+
_TOKEN_RE = re.compile(
|
|
21
|
+
r"\s*(?:"
|
|
22
|
+
r"(?P<number>0[xX][0-9A-Fa-f]+|0[bB][01]+|0[0-7]+|[0-9]+)"
|
|
23
|
+
r"|(?P<variable>\$?[A-Za-z_][A-Za-z0-9_]*)"
|
|
24
|
+
r"|(?P<operator>\*\*|\+\+|--|<<=|>>=|\+=|-=|\*=|/=|%=|&=|\^=|\|="
|
|
25
|
+
r"|\|\||&&|==|!=|<=|>=|<<|>>|[=+\-*/%<>()!~&^|?:,])"
|
|
26
|
+
r")"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ArithmeticError(ValueError):
|
|
31
|
+
"""A bounded syntax or runtime failure in a loop arithmetic expression."""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(frozen=True)
|
|
35
|
+
class _Token:
|
|
36
|
+
kind: str
|
|
37
|
+
text: str
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(frozen=True)
|
|
41
|
+
class _Number:
|
|
42
|
+
value: int
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass(frozen=True)
|
|
46
|
+
class _Variable:
|
|
47
|
+
name: str
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass(frozen=True)
|
|
51
|
+
class _Unary:
|
|
52
|
+
operator: str
|
|
53
|
+
operand: "_Expression"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass(frozen=True)
|
|
57
|
+
class _Binary:
|
|
58
|
+
operator: str
|
|
59
|
+
left: "_Expression"
|
|
60
|
+
right: "_Expression"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass(frozen=True)
|
|
64
|
+
class _Conditional:
|
|
65
|
+
condition: "_Expression"
|
|
66
|
+
when_true: "_Expression"
|
|
67
|
+
when_false: "_Expression"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass(frozen=True)
|
|
71
|
+
class _Assignment:
|
|
72
|
+
operator: str
|
|
73
|
+
target: _Variable
|
|
74
|
+
value: "_Expression"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@dataclass(frozen=True)
|
|
78
|
+
class _Update:
|
|
79
|
+
operator: str
|
|
80
|
+
target: _Variable
|
|
81
|
+
prefix: bool
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@dataclass(frozen=True)
|
|
85
|
+
class _Sequence:
|
|
86
|
+
expressions: tuple["_Expression", ...]
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
_Expression = _Number | _Variable | _Unary | _Binary | _Conditional | _Assignment | _Update | _Sequence
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _wrap(value: int) -> int:
|
|
93
|
+
unsigned = value & _MASK
|
|
94
|
+
return unsigned - (1 << 64) if unsigned & _SIGN_BIT else unsigned
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _parse_integer(text: str) -> int:
|
|
98
|
+
if text.lower().startswith(("0x", "0b")):
|
|
99
|
+
return _wrap(int(text, 0))
|
|
100
|
+
if len(text) > 1 and text.startswith("0"):
|
|
101
|
+
return _wrap(int(text, 8))
|
|
102
|
+
return _wrap(int(text, 10))
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _tokenize(source: str) -> list[_Token]:
|
|
106
|
+
tokens: list[_Token] = []
|
|
107
|
+
position = 0
|
|
108
|
+
while position < len(source):
|
|
109
|
+
match = _TOKEN_RE.match(source, position)
|
|
110
|
+
if match is None:
|
|
111
|
+
excerpt = source[position : position + 16]
|
|
112
|
+
raise ArithmeticError(f"unexpected arithmetic token near {excerpt!r}")
|
|
113
|
+
kind = match.lastgroup
|
|
114
|
+
assert kind is not None
|
|
115
|
+
text = match.group(kind)
|
|
116
|
+
if kind == "variable" and text.startswith("$"):
|
|
117
|
+
text = text[1:]
|
|
118
|
+
tokens.append(_Token(kind, text))
|
|
119
|
+
position = match.end()
|
|
120
|
+
tokens.append(_Token("end", ""))
|
|
121
|
+
return tokens
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class _Parser:
|
|
125
|
+
def __init__(self, source: str) -> None:
|
|
126
|
+
self.tokens = _tokenize(source)
|
|
127
|
+
self.index = 0
|
|
128
|
+
|
|
129
|
+
def current(self) -> _Token:
|
|
130
|
+
return self.tokens[self.index]
|
|
131
|
+
|
|
132
|
+
def accept(self, operator: str) -> bool:
|
|
133
|
+
if self.current().kind == "operator" and self.current().text == operator:
|
|
134
|
+
self.index += 1
|
|
135
|
+
return True
|
|
136
|
+
return False
|
|
137
|
+
|
|
138
|
+
def require(self, operator: str) -> None:
|
|
139
|
+
if not self.accept(operator):
|
|
140
|
+
raise ArithmeticError(f"expected {operator!r} in arithmetic expression")
|
|
141
|
+
|
|
142
|
+
def parse(self) -> _Expression:
|
|
143
|
+
expression = self.parse_sequence()
|
|
144
|
+
if self.current().kind != "end":
|
|
145
|
+
raise ArithmeticError(f"unexpected arithmetic token {self.current().text!r}")
|
|
146
|
+
return expression
|
|
147
|
+
|
|
148
|
+
def parse_sequence(self) -> _Expression:
|
|
149
|
+
expressions = [self.parse_assignment()]
|
|
150
|
+
while self.accept(","):
|
|
151
|
+
expressions.append(self.parse_assignment())
|
|
152
|
+
return expressions[0] if len(expressions) == 1 else _Sequence(tuple(expressions))
|
|
153
|
+
|
|
154
|
+
def parse_assignment(self) -> _Expression:
|
|
155
|
+
target = self.parse_conditional()
|
|
156
|
+
token = self.current()
|
|
157
|
+
if token.kind != "operator" or token.text not in _ASSIGNMENT_OPERATORS:
|
|
158
|
+
return target
|
|
159
|
+
self.index += 1
|
|
160
|
+
if not isinstance(target, _Variable):
|
|
161
|
+
raise ArithmeticError("the left side of an arithmetic assignment must be a variable")
|
|
162
|
+
return _Assignment(token.text, target, self.parse_assignment())
|
|
163
|
+
|
|
164
|
+
def parse_conditional(self) -> _Expression:
|
|
165
|
+
condition = self.parse_logical_or()
|
|
166
|
+
if not self.accept("?"):
|
|
167
|
+
return condition
|
|
168
|
+
when_true = self.parse_sequence()
|
|
169
|
+
self.require(":")
|
|
170
|
+
return _Conditional(condition, when_true, self.parse_assignment())
|
|
171
|
+
|
|
172
|
+
def parse_logical_or(self) -> _Expression:
|
|
173
|
+
return self._parse_left_associative(self.parse_logical_and, frozenset({"||"}))
|
|
174
|
+
|
|
175
|
+
def parse_logical_and(self) -> _Expression:
|
|
176
|
+
return self._parse_left_associative(self.parse_bitwise_or, frozenset({"&&"}))
|
|
177
|
+
|
|
178
|
+
def parse_bitwise_or(self) -> _Expression:
|
|
179
|
+
return self._parse_left_associative(self.parse_bitwise_xor, frozenset({"|"}))
|
|
180
|
+
|
|
181
|
+
def parse_bitwise_xor(self) -> _Expression:
|
|
182
|
+
return self._parse_left_associative(self.parse_bitwise_and, frozenset({"^"}))
|
|
183
|
+
|
|
184
|
+
def parse_bitwise_and(self) -> _Expression:
|
|
185
|
+
return self._parse_left_associative(self.parse_equality, frozenset({"&"}))
|
|
186
|
+
|
|
187
|
+
def parse_equality(self) -> _Expression:
|
|
188
|
+
return self._parse_left_associative(self.parse_relational, frozenset({"==", "!="}))
|
|
189
|
+
|
|
190
|
+
def parse_relational(self) -> _Expression:
|
|
191
|
+
return self._parse_left_associative(self.parse_shift, frozenset({"<", "<=", ">", ">="}))
|
|
192
|
+
|
|
193
|
+
def parse_shift(self) -> _Expression:
|
|
194
|
+
return self._parse_left_associative(self.parse_additive, frozenset({"<<", ">>"}))
|
|
195
|
+
|
|
196
|
+
def parse_additive(self) -> _Expression:
|
|
197
|
+
return self._parse_left_associative(self.parse_multiplicative, frozenset({"+", "-"}))
|
|
198
|
+
|
|
199
|
+
def parse_multiplicative(self) -> _Expression:
|
|
200
|
+
return self._parse_left_associative(self.parse_power, frozenset({"*", "/", "%"}))
|
|
201
|
+
|
|
202
|
+
def parse_power(self) -> _Expression:
|
|
203
|
+
left = self.parse_unary()
|
|
204
|
+
if self.accept("**"):
|
|
205
|
+
return _Binary("**", left, self.parse_power())
|
|
206
|
+
return left
|
|
207
|
+
|
|
208
|
+
def parse_unary(self) -> _Expression:
|
|
209
|
+
token = self.current()
|
|
210
|
+
if token.kind == "operator" and token.text in ("+", "-", "!", "~"):
|
|
211
|
+
self.index += 1
|
|
212
|
+
return _Unary(token.text, self.parse_unary())
|
|
213
|
+
if token.kind == "operator" and token.text in ("++", "--"):
|
|
214
|
+
self.index += 1
|
|
215
|
+
target = self.parse_unary()
|
|
216
|
+
if not isinstance(target, _Variable):
|
|
217
|
+
raise ArithmeticError(f"{token.text} requires a variable")
|
|
218
|
+
return _Update(token.text, target, True)
|
|
219
|
+
return self.parse_postfix()
|
|
220
|
+
|
|
221
|
+
def parse_postfix(self) -> _Expression:
|
|
222
|
+
expression = self.parse_primary()
|
|
223
|
+
token = self.current()
|
|
224
|
+
if token.kind == "operator" and token.text in ("++", "--"):
|
|
225
|
+
self.index += 1
|
|
226
|
+
if not isinstance(expression, _Variable):
|
|
227
|
+
raise ArithmeticError(f"{token.text} requires a variable")
|
|
228
|
+
return _Update(token.text, expression, False)
|
|
229
|
+
return expression
|
|
230
|
+
|
|
231
|
+
def parse_primary(self) -> _Expression:
|
|
232
|
+
token = self.current()
|
|
233
|
+
if token.kind == "number":
|
|
234
|
+
self.index += 1
|
|
235
|
+
return _Number(_parse_integer(token.text))
|
|
236
|
+
if token.kind == "variable":
|
|
237
|
+
self.index += 1
|
|
238
|
+
return _Variable(token.text)
|
|
239
|
+
if self.accept("("):
|
|
240
|
+
expression = self.parse_sequence()
|
|
241
|
+
self.require(")")
|
|
242
|
+
return expression
|
|
243
|
+
raise ArithmeticError(f"expected an integer, variable, or '('; found {token.text!r}")
|
|
244
|
+
|
|
245
|
+
def _parse_left_associative(self, operand_parser, operators: frozenset[str]) -> _Expression:
|
|
246
|
+
expression = operand_parser()
|
|
247
|
+
while self.current().kind == "operator" and self.current().text in operators:
|
|
248
|
+
operator = self.current().text
|
|
249
|
+
self.index += 1
|
|
250
|
+
expression = _Binary(operator, expression, operand_parser())
|
|
251
|
+
return expression
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def compile_arithmetic(source: str) -> _Expression | None:
|
|
255
|
+
stripped = source.strip()
|
|
256
|
+
return None if not stripped else _Parser(stripped).parse()
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _variable_value(variable: _Variable, state: ShellState) -> int:
|
|
260
|
+
text = state.env.get(variable.name, "").strip()
|
|
261
|
+
if not text:
|
|
262
|
+
return 0
|
|
263
|
+
try:
|
|
264
|
+
return _parse_integer(text)
|
|
265
|
+
except ValueError:
|
|
266
|
+
return 0
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _set_variable(variable: _Variable, value: int, state: ShellState) -> int:
|
|
270
|
+
wrapped = _wrap(value)
|
|
271
|
+
state.setenv(variable.name, str(wrapped))
|
|
272
|
+
return wrapped
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _divide(left: int, right: int) -> int:
|
|
276
|
+
if right == 0:
|
|
277
|
+
raise ArithmeticError("division by zero")
|
|
278
|
+
quotient = abs(left) // abs(right)
|
|
279
|
+
return -quotient if (left < 0) != (right < 0) else quotient
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _binary(operator: str, left: int, right: int) -> int:
|
|
283
|
+
if operator == "+":
|
|
284
|
+
return _wrap(left + right)
|
|
285
|
+
if operator == "-":
|
|
286
|
+
return _wrap(left - right)
|
|
287
|
+
if operator == "*":
|
|
288
|
+
return _wrap(left * right)
|
|
289
|
+
if operator == "/":
|
|
290
|
+
return _wrap(_divide(left, right))
|
|
291
|
+
if operator == "%":
|
|
292
|
+
return _wrap(left - _divide(left, right) * right)
|
|
293
|
+
if operator == "**":
|
|
294
|
+
if right < 0:
|
|
295
|
+
raise ArithmeticError("negative exponent")
|
|
296
|
+
return _wrap(pow(left & _MASK, right, 1 << 64))
|
|
297
|
+
if operator in ("<<", ">>"):
|
|
298
|
+
if right < 0 or right >= 64:
|
|
299
|
+
raise ArithmeticError("shift count must be between 0 and 63")
|
|
300
|
+
return _wrap(left << right) if operator == "<<" else _wrap(left >> right)
|
|
301
|
+
if operator == "&":
|
|
302
|
+
return _wrap(left & right)
|
|
303
|
+
if operator == "^":
|
|
304
|
+
return _wrap(left ^ right)
|
|
305
|
+
if operator == "|":
|
|
306
|
+
return _wrap(left | right)
|
|
307
|
+
if operator == "==":
|
|
308
|
+
return int(left == right)
|
|
309
|
+
if operator == "!=":
|
|
310
|
+
return int(left != right)
|
|
311
|
+
if operator == "<":
|
|
312
|
+
return int(left < right)
|
|
313
|
+
if operator == "<=":
|
|
314
|
+
return int(left <= right)
|
|
315
|
+
if operator == ">":
|
|
316
|
+
return int(left > right)
|
|
317
|
+
if operator == ">=":
|
|
318
|
+
return int(left >= right)
|
|
319
|
+
raise ArithmeticError(f"unsupported arithmetic operator {operator!r}")
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def evaluate_arithmetic(expression: _Expression | None, state: ShellState) -> int:
|
|
323
|
+
if expression is None:
|
|
324
|
+
return 0
|
|
325
|
+
if isinstance(expression, _Number):
|
|
326
|
+
return expression.value
|
|
327
|
+
if isinstance(expression, _Variable):
|
|
328
|
+
return _variable_value(expression, state)
|
|
329
|
+
if isinstance(expression, _Sequence):
|
|
330
|
+
result = 0
|
|
331
|
+
for item in expression.expressions:
|
|
332
|
+
result = evaluate_arithmetic(item, state)
|
|
333
|
+
return result
|
|
334
|
+
if isinstance(expression, _Conditional):
|
|
335
|
+
branch = expression.when_true if evaluate_arithmetic(expression.condition, state) != 0 else expression.when_false
|
|
336
|
+
return evaluate_arithmetic(branch, state)
|
|
337
|
+
if isinstance(expression, _Unary):
|
|
338
|
+
value = evaluate_arithmetic(expression.operand, state)
|
|
339
|
+
if expression.operator == "+":
|
|
340
|
+
return value
|
|
341
|
+
if expression.operator == "-":
|
|
342
|
+
return _wrap(-value)
|
|
343
|
+
if expression.operator == "!":
|
|
344
|
+
return int(value == 0)
|
|
345
|
+
if expression.operator == "~":
|
|
346
|
+
return _wrap(~value)
|
|
347
|
+
if isinstance(expression, _Binary):
|
|
348
|
+
left = evaluate_arithmetic(expression.left, state)
|
|
349
|
+
if expression.operator == "&&":
|
|
350
|
+
return int(left != 0 and evaluate_arithmetic(expression.right, state) != 0)
|
|
351
|
+
if expression.operator == "||":
|
|
352
|
+
return int(left != 0 or evaluate_arithmetic(expression.right, state) != 0)
|
|
353
|
+
return _binary(expression.operator, left, evaluate_arithmetic(expression.right, state))
|
|
354
|
+
if isinstance(expression, _Assignment):
|
|
355
|
+
right = evaluate_arithmetic(expression.value, state)
|
|
356
|
+
if expression.operator == "=":
|
|
357
|
+
return _set_variable(expression.target, right, state)
|
|
358
|
+
left = _variable_value(expression.target, state)
|
|
359
|
+
return _set_variable(expression.target, _binary(expression.operator[:-1], left, right), state)
|
|
360
|
+
if isinstance(expression, _Update):
|
|
361
|
+
old = _variable_value(expression.target, state)
|
|
362
|
+
new = _set_variable(expression.target, old + (1 if expression.operator == "++" else -1), state)
|
|
363
|
+
return new if expression.prefix else old
|
|
364
|
+
raise ArithmeticError("unrecognized arithmetic expression")
|
|
@@ -44,9 +44,10 @@ class ExecContext:
|
|
|
44
44
|
builtins: Mapping[str, BuiltinFn]
|
|
45
45
|
deadline: float | None
|
|
46
46
|
stderr: BinaryIO | int | None = None
|
|
47
|
+
loop_depth: int = 0
|
|
47
48
|
|
|
48
49
|
|
|
49
50
|
# Executor-owned builtins are NOT in commands/REGISTRY: state mutators and `exit`
|
|
50
51
|
# need ShellState/control-flow access, while the runner needs the executor.
|
|
51
|
-
STATE_BUILTINS = {"cd", "export", "unset", "exit"}
|
|
52
|
+
STATE_BUILTINS = {"cd", "export", "unset", "exit", "break", "continue"}
|
|
52
53
|
RUNNER_BUILTINS = {"xargs"}
|
|
@@ -55,3 +55,19 @@ class ShellExit(Exception):
|
|
|
55
55
|
def __init__(self, exit_code: int) -> None:
|
|
56
56
|
super().__init__(exit_code)
|
|
57
57
|
self.exit_code = exit_code
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class LoopControl(Exception):
|
|
61
|
+
"""Internal non-local control transfer consumed by enclosing loop nodes."""
|
|
62
|
+
|
|
63
|
+
def __init__(self, levels: int) -> None:
|
|
64
|
+
super().__init__(levels)
|
|
65
|
+
self.levels = levels
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class LoopBreak(LoopControl):
|
|
69
|
+
"""Leave one or more enclosing loops."""
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class LoopContinue(LoopControl):
|
|
73
|
+
"""Continue one or more enclosing loops."""
|