add control structures: if, for, while, until

- new parser (parser.go) for lists, pipelines, ! and if/elif/else/fi,
  for ... in ... do/done, while/until ... do/done; simple commands are
  kept as text and expanded when they run (in every iteration)
- break [n], continue [n] and the : builtin
- incomplete input (open control structure, trailing &&, |, quote or
  backslash) continues on the next line: prompt "> " in interactive
  shells, scripts and startup files read on
- Ctrl+C aborts the whole command line, including loops
- aliases are expanded at the start of every simple command, also inside
  control structures
- control structures in pipelines, with redirections or in the
  background are reported as not supported
- completion: commands after if, then, do etc.
- main_test.go reformatted with gofmt

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Florian WaltherandClaude Opus 5.5 committed 2026-10-04 00:05:28 +02:00
1 parent e49fe6fe11
commit f79fb0b8d5
7 files changed
+1362 -237

No files matched your search

+1 -13
View File
@@ -2,7 +2,6 @@ package main
import (
"os"
"reflect"
"testing"
)
@@ -29,17 +28,6 @@ func TestSubstEnd(t *testing.T) {
}
}
func TestSplitList_SkipsSubstitution(t *testing.T) {
got, err := splitList(`echo $(a; b && c | d) "$(e || f)" & g`)
if err != nil {
t.Fatal(err)
}
want := []listItem{{"", `echo $(a; b && c | d) "$(e || f)" `, true}, {";", " g", false}}
if !reflect.DeepEqual(got, want) {
t.Errorf("splitList = %+v, want %+v", got, want)
}
}
func TestExpandAliasesAndBraces_SkipSubstitution(t *testing.T) {
withAliases(t, map[string]string{"ll": "ls -l"})
if got := expandAliases("echo $(ll; ll) && ll"); got != "echo $(ll; ll) && ls -l" {
@@ -106,7 +94,7 @@ func TestExecInput_CommandSubstitution(t *testing.T) {
}
for input, wantErr := range map[string]string{
"echo $(echo a": "invalid input: missing ) in command substitution",
"echo $(echo a": "syntax error: unexpected end of input",
"echo $((1 + 2))": "invalid input: arithmetic expansion $((...)) is not supported",
} {
if err := execInput(input); err == nil || err.Error() != wantErr {