rush-shell 0.0.2
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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +94 -0
- data/exe/rush +10 -0
- data/grammar/shell.y +272 -0
- data/grammar/shell.y.output +2354 -0
- data/lib/rush/alias_table.rb +48 -0
- data/lib/rush/assignments.rb +48 -0
- data/lib/rush/ast/and_or.rb +37 -0
- data/lib/rush/ast/assignment.rb +20 -0
- data/lib/rush/ast/brace_group.rb +25 -0
- data/lib/rush/ast/case_node.rb +41 -0
- data/lib/rush/ast/condition_loop.rb +34 -0
- data/lib/rush/ast/for_node.rb +34 -0
- data/lib/rush/ast/function_def.rb +26 -0
- data/lib/rush/ast/if_node.rb +29 -0
- data/lib/rush/ast/list.rb +47 -0
- data/lib/rush/ast/node.rb +17 -0
- data/lib/rush/ast/param_ref.rb +59 -0
- data/lib/rush/ast/pipeline.rb +45 -0
- data/lib/rush/ast/redirect.rb +21 -0
- data/lib/rush/ast/redirected.rb +26 -0
- data/lib/rush/ast/simple_command.rb +62 -0
- data/lib/rush/ast/subshell.rb +24 -0
- data/lib/rush/ast/until_node.rb +18 -0
- data/lib/rush/ast/while_node.rb +18 -0
- data/lib/rush/ast/word.rb +44 -0
- data/lib/rush/ast/word_segment.rb +203 -0
- data/lib/rush/background_runner.rb +68 -0
- data/lib/rush/bracket_expression.rb +107 -0
- data/lib/rush/bracket_scanner.rb +64 -0
- data/lib/rush/builtins/alias_.rb +74 -0
- data/lib/rush/builtins/base.rb +96 -0
- data/lib/rush/builtins/break_.rb +19 -0
- data/lib/rush/builtins/cd.rb +127 -0
- data/lib/rush/builtins/colon.rb +16 -0
- data/lib/rush/builtins/command.rb +137 -0
- data/lib/rush/builtins/command_options.rb +103 -0
- data/lib/rush/builtins/continue_.rb +19 -0
- data/lib/rush/builtins/declare.rb +27 -0
- data/lib/rush/builtins/defaults.rb +28 -0
- data/lib/rush/builtins/dot.rb +52 -0
- data/lib/rush/builtins/echo.rb +37 -0
- data/lib/rush/builtins/eval.rb +25 -0
- data/lib/rush/builtins/exec.rb +54 -0
- data/lib/rush/builtins/exit.rb +45 -0
- data/lib/rush/builtins/export.rb +20 -0
- data/lib/rush/builtins/false_.rb +16 -0
- data/lib/rush/builtins/fd_operand.rb +87 -0
- data/lib/rush/builtins/getopts.rb +96 -0
- data/lib/rush/builtins/hash.rb +64 -0
- data/lib/rush/builtins/job_resume.rb +107 -0
- data/lib/rush/builtins/jobs.rb +86 -0
- data/lib/rush/builtins/kill.rb +152 -0
- data/lib/rush/builtins/local.rb +40 -0
- data/lib/rush/builtins/loop_jump.rb +45 -0
- data/lib/rush/builtins/printf.rb +39 -0
- data/lib/rush/builtins/printf_formatter.rb +111 -0
- data/lib/rush/builtins/pwd.rb +17 -0
- data/lib/rush/builtins/read.rb +93 -0
- data/lib/rush/builtins/read_input.rb +146 -0
- data/lib/rush/builtins/readonly.rb +20 -0
- data/lib/rush/builtins/registry.rb +36 -0
- data/lib/rush/builtins/return_.rb +24 -0
- data/lib/rush/builtins/set.rb +101 -0
- data/lib/rush/builtins/shift.rb +35 -0
- data/lib/rush/builtins/test_.rb +48 -0
- data/lib/rush/builtins/test_context.rb +24 -0
- data/lib/rush/builtins/test_expr.rb +74 -0
- data/lib/rush/builtins/test_grammar.rb +117 -0
- data/lib/rush/builtins/test_operators.rb +94 -0
- data/lib/rush/builtins/test_tokens.rb +69 -0
- data/lib/rush/builtins/times.rb +35 -0
- data/lib/rush/builtins/trap.rb +85 -0
- data/lib/rush/builtins/true_.rb +16 -0
- data/lib/rush/builtins/type_.rb +28 -0
- data/lib/rush/builtins/ulimit.rb +258 -0
- data/lib/rush/builtins/umask.rb +81 -0
- data/lib/rush/builtins/unalias.rb +46 -0
- data/lib/rush/builtins/unset.rb +40 -0
- data/lib/rush/builtins/wait.rb +127 -0
- data/lib/rush/cli.rb +31 -0
- data/lib/rush/command_assignments.rb +56 -0
- data/lib/rush/command_lookup.rb +228 -0
- data/lib/rush/command_resolution.rb +115 -0
- data/lib/rush/command_runner.rb +152 -0
- data/lib/rush/command_text.rb +140 -0
- data/lib/rush/environment.rb +134 -0
- data/lib/rush/errexit_context.rb +68 -0
- data/lib/rush/error_policy.rb +64 -0
- data/lib/rush/errors.rb +100 -0
- data/lib/rush/escape_table.rb +22 -0
- data/lib/rush/escaped_bracket.rb +49 -0
- data/lib/rush/executor.rb +164 -0
- data/lib/rush/exit_trap.rb +109 -0
- data/lib/rush/expansion/arithmetic/evaluator.rb +43 -0
- data/lib/rush/expansion/arithmetic/nodes.rb +180 -0
- data/lib/rush/expansion/arithmetic/number.rb +104 -0
- data/lib/rush/expansion/arithmetic/parser.rb +155 -0
- data/lib/rush/expansion/arithmetic/tokenizer.rb +56 -0
- data/lib/rush/expansion/arithmetic_expander.rb +38 -0
- data/lib/rush/expansion/command_substitution.rb +87 -0
- data/lib/rush/expansion/field_part.rb +12 -0
- data/lib/rush/expansion/field_splitter.rb +23 -0
- data/lib/rush/expansion/glob_expander.rb +48 -0
- data/lib/rush/expansion/ifs.rb +62 -0
- data/lib/rush/expansion/ifs_scanner.rb +168 -0
- data/lib/rush/expansion/parameter_expander.rb +204 -0
- data/lib/rush/expansion/parameter_forms.rb +22 -0
- data/lib/rush/expansion/pattern_removal.rb +63 -0
- data/lib/rush/expansion/pipeline.rb +124 -0
- data/lib/rush/expansion/read_char.rb +13 -0
- data/lib/rush/expansion/read_field_scanner.rb +159 -0
- data/lib/rush/expansion/read_splitter.rb +40 -0
- data/lib/rush/expansion/resolver.rb +23 -0
- data/lib/rush/expansion/tilde_expander.rb +102 -0
- data/lib/rush/external.rb +75 -0
- data/lib/rush/fd_entry.rb +60 -0
- data/lib/rush/for_runner.rb +50 -0
- data/lib/rush/function_frame.rb +30 -0
- data/lib/rush/function_runner.rb +37 -0
- data/lib/rush/function_table.rb +35 -0
- data/lib/rush/getopts_parser.rb +195 -0
- data/lib/rush/getopts_state.rb +104 -0
- data/lib/rush/here_doc.rb +57 -0
- data/lib/rush/interactive_signals.rb +26 -0
- data/lib/rush/invocation.rb +190 -0
- data/lib/rush/io_table.rb +94 -0
- data/lib/rush/job_control.rb +197 -0
- data/lib/rush/job_report.rb +45 -0
- data/lib/rush/job_spec.rb +51 -0
- data/lib/rush/job_table/control.rb +103 -0
- data/lib/rush/job_table/interruptible_wait.rb +70 -0
- data/lib/rush/job_table/job.rb +181 -0
- data/lib/rush/job_table.rb +194 -0
- data/lib/rush/lexer/alias_expander.rb +129 -0
- data/lib/rush/lexer/braced_reader.rb +86 -0
- data/lib/rush/lexer/case_frame.rb +114 -0
- data/lib/rush/lexer/case_tracker.rb +146 -0
- data/lib/rush/lexer/dollar_scanner.rb +53 -0
- data/lib/rush/lexer/double_quote_scanner.rb +71 -0
- data/lib/rush/lexer/heredoc_body.rb +91 -0
- data/lib/rush/lexer/heredoc_reader.rb +93 -0
- data/lib/rush/lexer/lex_state.rb +128 -0
- data/lib/rush/lexer/operator_table.rb +32 -0
- data/lib/rush/lexer/param_scanner.rb +43 -0
- data/lib/rush/lexer/paren_reader.rb +113 -0
- data/lib/rush/lexer/paren_regions.rb +74 -0
- data/lib/rush/lexer/quote_skips.rb +77 -0
- data/lib/rush/lexer/quoted_word.rb +67 -0
- data/lib/rush/lexer/scanner_predicates.rb +20 -0
- data/lib/rush/lexer/source_lines.rb +43 -0
- data/lib/rush/lexer/substitution_reader.rb +77 -0
- data/lib/rush/lexer/token_classifier.rb +144 -0
- data/lib/rush/lexer/token_predicates.rb +23 -0
- data/lib/rush/lexer/word_scanner.rb +134 -0
- data/lib/rush/lexer.rb +174 -0
- data/lib/rush/loop_control_handling.rb +29 -0
- data/lib/rush/loop_nesting.rb +50 -0
- data/lib/rush/loop_runner.rb +55 -0
- data/lib/rush/option_cluster.rb +39 -0
- data/lib/rush/options.rb +76 -0
- data/lib/rush/param_text.rb +39 -0
- data/lib/rush/parser.rb +1213 -0
- data/lib/rush/parser_support.rb +112 -0
- data/lib/rush/pattern_scanner.rb +35 -0
- data/lib/rush/pending_signals.rb +57 -0
- data/lib/rush/pipeline_runner.rb +173 -0
- data/lib/rush/pipeline_statuses.rb +45 -0
- data/lib/rush/positional.rb +45 -0
- data/lib/rush/posix_pattern.rb +78 -0
- data/lib/rush/program_input.rb +89 -0
- data/lib/rush/program_reader.rb +69 -0
- data/lib/rush/program_session.rb +83 -0
- data/lib/rush/prompt.rb +52 -0
- data/lib/rush/redirect_scope.rb +64 -0
- data/lib/rush/redirection/dup_redirect.rb +58 -0
- data/lib/rush/redirection/file_redirect.rb +50 -0
- data/lib/rush/redirection/here_doc_redirect.rb +17 -0
- data/lib/rush/redirection/registry.rb +53 -0
- data/lib/rush/repl.rb +133 -0
- data/lib/rush/runtime_type_checks.rb +16 -0
- data/lib/rush/scope.rb +79 -0
- data/lib/rush/segment_buffer.rb +47 -0
- data/lib/rush/shell_parameters.rb +77 -0
- data/lib/rush/shell_pattern.rb +80 -0
- data/lib/rush/shell_state.rb +163 -0
- data/lib/rush/shell_variables.rb +67 -0
- data/lib/rush/signal_report.rb +41 -0
- data/lib/rush/signals.rb +68 -0
- data/lib/rush/source.rb +82 -0
- data/lib/rush/source_line_counter.rb +41 -0
- data/lib/rush/source_runner.rb +47 -0
- data/lib/rush/startup.rb +75 -0
- data/lib/rush/status.rb +93 -0
- data/lib/rush/stop_relay.rb +32 -0
- data/lib/rush/subshell_runner.rb +70 -0
- data/lib/rush/system_calls/collation.rb +155 -0
- data/lib/rush/system_calls/file_tests.rb +95 -0
- data/lib/rush/system_calls/process_control.rb +165 -0
- data/lib/rush/system_calls/process_identity.rb +43 -0
- data/lib/rush/system_calls/resource_limits.rb +51 -0
- data/lib/rush/system_calls.rb +206 -0
- data/lib/rush/terminal.rb +126 -0
- data/lib/rush/trap_runner.rb +169 -0
- data/lib/rush/trap_table.rb +49 -0
- data/lib/rush/umask_mode.rb +133 -0
- data/lib/rush/version.rb +6 -0
- data/lib/rush.rb +190 -0
- metadata +527 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: ac76c8cbacbecad88ea54c697d25de3b9b69bf3e5397e93990da08fa01151c32
|
|
4
|
+
data.tar.gz: 44e3a86f79287b401c83ee5f464b2a3480dc667a2bfe48577c944adb27de2d06
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: c2e1bfebaabe2a1645affc7ed369d929a5015ee3e1430548bf6f38db1a098ea0cd2bf73e8ff609b761e6750b860b48800ca3f8c417b9fcac1214550aec81f2b2
|
|
7
|
+
data.tar.gz: d0ce2156b115d6ebe7ec118723de1406598c3c61ec49f9e804568ab4870aa32825d44aa0aa26d66a078d8cd0b2cc43449e951f391f3b72d0bbd6ccc240f04816
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alexey Nikitin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# rush
|
|
2
|
+
|
|
3
|
+
[](https://github.com/tank-bohr/rush/actions/workflows/ci.yml)
|
|
4
|
+
[](https://rubygems.org/gems/rush-shell)
|
|
5
|
+
|
|
6
|
+
A **POSIX shell written in pure Ruby**. rush implements the POSIX.1-2017 *Shell
|
|
7
|
+
Command Language* (IEEE Std 1003.1, §2) to the letter, with **dash** as its
|
|
8
|
+
differential oracle: a large corpus of language tests verifies that rush and
|
|
9
|
+
`dash -c` produce the same `[stdout, exit status]` byte for byte. Where dash
|
|
10
|
+
itself diverges from the standard, the standard wins and the divergence is
|
|
11
|
+
documented.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
gem install rush-shell
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Ruby >= 4.0 is required — the floor is what CI actually proves. The gem is
|
|
20
|
+
`rush-shell`; the executable is `rush`.
|
|
21
|
+
|
|
22
|
+
## Use
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
rush # interactive shell
|
|
26
|
+
rush -c 'echo hello' # run a command string
|
|
27
|
+
rush script.sh arg1 arg2 # run a script
|
|
28
|
+
echo 'ls | wc -l' | rush # read commands from stdin
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
rush accepts the standard `sh` invocation: `-c`, `-o option`/`+o option`, the
|
|
32
|
+
`set`-builtin flag letters (`-e`, `-x`, `-u`, `-m`, …), `--` to end options,
|
|
33
|
+
and behaves as a login shell when invoked with a leading-dash `argv[0]`.
|
|
34
|
+
Interactive mode (auto-detected on a terminal) has Reline line editing and
|
|
35
|
+
full job control: process groups, terminal handover, `Ctrl-Z`, `jobs`, `fg`,
|
|
36
|
+
`bg`, and pre-prompt status change reports — matching dash's picture
|
|
37
|
+
byte-for-byte in the pty smoke tests. Persistent history and the POSIX `fc`
|
|
38
|
+
builtin are on the roadmap.
|
|
39
|
+
|
|
40
|
+
The full §2 language is implemented: quoting, all parameter-expansion forms,
|
|
41
|
+
command substitution, arithmetic expansion, field splitting, pathname
|
|
42
|
+
expansion (including bracket expressions and locale collation via glibc),
|
|
43
|
+
redirections and here-documents, pipelines and lists, compound commands,
|
|
44
|
+
functions, traps, aliases, `getopts`, and the special builtins.
|
|
45
|
+
|
|
46
|
+
## Why trust a shell?
|
|
47
|
+
|
|
48
|
+
Correctness in rush is not a claim, it is machinery:
|
|
49
|
+
|
|
50
|
+
- **Differential testing** — the language corpus runs every case against a
|
|
51
|
+
checksum-pinned dash 0.5.13.4 and compares `[stdout, exit status]`; CI
|
|
52
|
+
repeats this natively and inside a Docker oracle image.
|
|
53
|
+
- **2831 examples, ~99.8% line / ~98.8% branch coverage** — irreducible
|
|
54
|
+
fork/exec paths are pinned by out-of-process differential tests instead.
|
|
55
|
+
- **Mutation testing** — [mutant](https://github.com/mbj/mutant) over ~38,800
|
|
56
|
+
mutations gates CI at a ratcheted threshold.
|
|
57
|
+
- **Two independent type systems** — inline Sorbet `sig {}` and RBS checked by
|
|
58
|
+
Steep, kept deliberately separate so each catches what the other misses.
|
|
59
|
+
|
|
60
|
+
## Development
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
mise install # Ruby from .tool-versions
|
|
64
|
+
bundle install
|
|
65
|
+
make install-hooks # once per clone: Conventional Commits hook (cog)
|
|
66
|
+
bundle exec rake # the full parallel quality gate
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`bundle exec rake` compiles the Racc parser from `grammar/shell.y`, then runs
|
|
70
|
+
rubocop, reek, flay/flog, Steep, Sorbet and the sharded RSpec suite
|
|
71
|
+
concurrently. `bin/test-in-docker` runs the same gate plus container-only
|
|
72
|
+
smokes against the pinned oracle image. `lib/rush/parser.rb` is generated and
|
|
73
|
+
committed; regenerate via `rake compile`, audit with `rake check_parser_drift`.
|
|
74
|
+
|
|
75
|
+
The architecture is a unidirectional pipeline over shared shell state:
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
Source → Lexer → Racc Parser → AST → Expander → Executor
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
with one feedback wire (the parser nudges lexer state for POSIX grammar rules
|
|
82
|
+
1–9) and all OS access funneled through one injectable port
|
|
83
|
+
(`Rush::SystemCalls`). Design decisions and per-slice lessons live in
|
|
84
|
+
[docs/architecture/](docs/architecture) and [docs/journal.md](docs/journal.md);
|
|
85
|
+
agent/contributor workflow lives in [AGENTS.md](AGENTS.md). Notable research:
|
|
86
|
+
[Steep vs Sorbet under extreme quality pressure](docs/steep-vs-sorbet.md).
|
|
87
|
+
|
|
88
|
+
Releases are cut from the `live` branch: semantic-release computes the version
|
|
89
|
+
from Conventional Commits and the gem is published to RubyGems via OIDC
|
|
90
|
+
trusted publishing with sigstore attestations — no long-lived credentials.
|
|
91
|
+
|
|
92
|
+
## License
|
|
93
|
+
|
|
94
|
+
[MIT](LICENSE)
|
data/exe/rush
ADDED
data/grammar/shell.y
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
# rush — POSIX sh grammar (Racc), transcribed from POSIX.1-2017 §2.10.2.
|
|
2
|
+
#
|
|
3
|
+
# Phase 1: lists / and_or / pipelines (with ! negation) / simple commands with
|
|
4
|
+
# assignments and file redirections / multi-stage pipes, the compound commands
|
|
5
|
+
# (brace groups, subshells, if, while/until, for, case) and function
|
|
6
|
+
# definitions. Action bodies are one-liners calling Rush::ParserSupport
|
|
7
|
+
# factories; the grammar currently compiles with zero conflicts.
|
|
8
|
+
|
|
9
|
+
class Rush::Parser
|
|
10
|
+
|
|
11
|
+
token WORD ASSIGNMENT_WORD IO_NUMBER NEWLINE
|
|
12
|
+
token AND_IF OR_IF
|
|
13
|
+
token DGREAT LESSGREAT CLOBBER DLESS DLESSDASH GREATAND LESSAND
|
|
14
|
+
token If Then Else Elif Fi Lbrace Rbrace Bang
|
|
15
|
+
token While Until Do Done
|
|
16
|
+
token For In NAME
|
|
17
|
+
token Case Esac DSEMI
|
|
18
|
+
|
|
19
|
+
start program
|
|
20
|
+
|
|
21
|
+
rule
|
|
22
|
+
|
|
23
|
+
program
|
|
24
|
+
: linebreak { result = make_list([]) }
|
|
25
|
+
| linebreak complete_commands linebreak { result = make_list(val[1]) }
|
|
26
|
+
;
|
|
27
|
+
|
|
28
|
+
complete_commands
|
|
29
|
+
: complete_command { result = val[0] }
|
|
30
|
+
| complete_commands newline_list complete_command { result = val[0] + val[2] }
|
|
31
|
+
;
|
|
32
|
+
|
|
33
|
+
complete_command
|
|
34
|
+
: list separator_op { result = terminate_list(val[0], val[1]) }
|
|
35
|
+
| list { result = terminate_list(val[0], ';') }
|
|
36
|
+
;
|
|
37
|
+
|
|
38
|
+
list
|
|
39
|
+
: list separator_op and_or { result = append_and_or(val[0], val[1], val[2]) }
|
|
40
|
+
| and_or { result = [pending_entry(val[0])] }
|
|
41
|
+
;
|
|
42
|
+
|
|
43
|
+
and_or
|
|
44
|
+
: pipeline { result = val[0] }
|
|
45
|
+
| and_or AND_IF linebreak pipeline { result = make_and_or(val[0], :and, val[3]) }
|
|
46
|
+
| and_or OR_IF linebreak pipeline { result = make_and_or(val[0], :or, val[3]) }
|
|
47
|
+
;
|
|
48
|
+
|
|
49
|
+
pipeline
|
|
50
|
+
: pipe_sequence { result = make_pipeline(val[0], false) }
|
|
51
|
+
| Bang pipe_sequence { result = make_pipeline(val[1], true) }
|
|
52
|
+
;
|
|
53
|
+
|
|
54
|
+
pipe_sequence
|
|
55
|
+
: command { result = [val[0]] }
|
|
56
|
+
| pipe_sequence '|' linebreak command { result = val[0] << val[3] }
|
|
57
|
+
;
|
|
58
|
+
|
|
59
|
+
command
|
|
60
|
+
: simple_command { result = val[0] }
|
|
61
|
+
| compound_command { result = val[0] }
|
|
62
|
+
| compound_command redirect_list { result = make_redirected(val[0], val[1]) }
|
|
63
|
+
| function_definition { result = val[0] }
|
|
64
|
+
;
|
|
65
|
+
|
|
66
|
+
redirect_list
|
|
67
|
+
: io_redirect { result = [val[0]] }
|
|
68
|
+
| redirect_list io_redirect { result = val[0] << val[1] }
|
|
69
|
+
;
|
|
70
|
+
|
|
71
|
+
# fname '(' ')' — cmd_name vs fname is resolved by the LALR lookahead on '('
|
|
72
|
+
# (no '(' follows a command name), keeping the lexer unchanged. The body is a
|
|
73
|
+
# compound command per POSIX Rule 9.
|
|
74
|
+
function_definition
|
|
75
|
+
: WORD '(' ')' linebreak compound_command { result = make_function(val[0], val[4]) }
|
|
76
|
+
;
|
|
77
|
+
|
|
78
|
+
compound_command
|
|
79
|
+
: brace_group { result = val[0] }
|
|
80
|
+
| subshell { result = val[0] }
|
|
81
|
+
| if_clause { result = val[0] }
|
|
82
|
+
| while_clause { result = val[0] }
|
|
83
|
+
| until_clause { result = val[0] }
|
|
84
|
+
| for_clause { result = val[0] }
|
|
85
|
+
| case_clause { result = val[0] }
|
|
86
|
+
;
|
|
87
|
+
|
|
88
|
+
brace_group
|
|
89
|
+
: Lbrace compound_list Rbrace { result = make_brace_group(val[1]) }
|
|
90
|
+
;
|
|
91
|
+
|
|
92
|
+
subshell
|
|
93
|
+
: '(' compound_list ')' { result = make_subshell(val[1]) }
|
|
94
|
+
;
|
|
95
|
+
|
|
96
|
+
while_clause
|
|
97
|
+
: While compound_list do_group { result = make_while(val[1], val[2]) }
|
|
98
|
+
;
|
|
99
|
+
|
|
100
|
+
until_clause
|
|
101
|
+
: Until compound_list do_group { result = make_until(val[1], val[2]) }
|
|
102
|
+
;
|
|
103
|
+
|
|
104
|
+
do_group
|
|
105
|
+
: Do compound_list Done { result = val[1] }
|
|
106
|
+
;
|
|
107
|
+
|
|
108
|
+
for_clause
|
|
109
|
+
: For name do_group { result = make_for(val[1], nil, val[2]) }
|
|
110
|
+
| For name sequential_sep do_group { result = make_for(val[1], nil, val[3]) }
|
|
111
|
+
| For name linebreak In sequential_sep do_group { result = make_for(val[1], [], val[5]) }
|
|
112
|
+
| For name linebreak In wordlist sequential_sep do_group { result = make_for(val[1], val[4], val[6]) }
|
|
113
|
+
;
|
|
114
|
+
|
|
115
|
+
name
|
|
116
|
+
: NAME { result = val[0].literal_text }
|
|
117
|
+
;
|
|
118
|
+
|
|
119
|
+
wordlist
|
|
120
|
+
: WORD { result = [val[0]] }
|
|
121
|
+
| wordlist WORD { result = val[0] << val[1] }
|
|
122
|
+
;
|
|
123
|
+
|
|
124
|
+
sequential_sep
|
|
125
|
+
: ';' linebreak
|
|
126
|
+
| newline_list
|
|
127
|
+
;
|
|
128
|
+
|
|
129
|
+
case_clause
|
|
130
|
+
: Case WORD linebreak In linebreak case_list Esac { result = make_case(val[1], val[5]) }
|
|
131
|
+
| Case WORD linebreak In linebreak case_list_ns Esac { result = make_case(val[1], val[5]) }
|
|
132
|
+
| Case WORD linebreak In linebreak Esac { result = make_case(val[1], []) }
|
|
133
|
+
;
|
|
134
|
+
|
|
135
|
+
case_list
|
|
136
|
+
: case_item { result = [val[0]] }
|
|
137
|
+
| case_list case_item { result = val[0] << val[1] }
|
|
138
|
+
;
|
|
139
|
+
|
|
140
|
+
/* POSIX 2.10.2: the last case item may omit its DSEMI (`ns` = no separator). */
|
|
141
|
+
case_list_ns
|
|
142
|
+
: case_item_ns { result = [val[0]] }
|
|
143
|
+
| case_list case_item_ns { result = val[0] << val[1] }
|
|
144
|
+
;
|
|
145
|
+
|
|
146
|
+
case_item
|
|
147
|
+
: patterns ')' compound_list DSEMI linebreak { result = make_case_item(val[0], val[2]) }
|
|
148
|
+
| patterns ')' linebreak DSEMI linebreak { result = make_case_item(val[0], make_list([])) }
|
|
149
|
+
| '(' patterns ')' compound_list DSEMI linebreak { result = make_case_item(val[1], val[3]) }
|
|
150
|
+
| '(' patterns ')' linebreak DSEMI linebreak { result = make_case_item(val[1], make_list([])) }
|
|
151
|
+
;
|
|
152
|
+
|
|
153
|
+
case_item_ns
|
|
154
|
+
: patterns ')' compound_list { result = make_case_item(val[0], val[2]) }
|
|
155
|
+
| patterns ')' linebreak { result = make_case_item(val[0], make_list([])) }
|
|
156
|
+
| '(' patterns ')' compound_list { result = make_case_item(val[1], val[3]) }
|
|
157
|
+
| '(' patterns ')' linebreak { result = make_case_item(val[1], make_list([])) }
|
|
158
|
+
;
|
|
159
|
+
|
|
160
|
+
patterns
|
|
161
|
+
: WORD { result = [val[0]] }
|
|
162
|
+
| patterns '|' WORD { result = val[0] << val[2] }
|
|
163
|
+
;
|
|
164
|
+
|
|
165
|
+
if_clause
|
|
166
|
+
: If compound_list Then compound_list else_part Fi { result = make_if(val[1], val[3], val[4]) }
|
|
167
|
+
| If compound_list Then compound_list Fi { result = make_if(val[1], val[3], nil) }
|
|
168
|
+
;
|
|
169
|
+
|
|
170
|
+
else_part
|
|
171
|
+
: Elif compound_list Then compound_list { result = make_if(val[1], val[3], nil) }
|
|
172
|
+
| Elif compound_list Then compound_list else_part { result = make_if(val[1], val[3], val[4]) }
|
|
173
|
+
| Else compound_list { result = val[1] }
|
|
174
|
+
;
|
|
175
|
+
|
|
176
|
+
compound_list
|
|
177
|
+
: linebreak term { result = make_list(val[1]) }
|
|
178
|
+
| linebreak term separator { result = make_list(terminate_list(val[1], val[2])) }
|
|
179
|
+
;
|
|
180
|
+
|
|
181
|
+
term
|
|
182
|
+
: term separator and_or { result = append_and_or(val[0], val[1], val[2]) }
|
|
183
|
+
| and_or { result = [pending_entry(val[0])] }
|
|
184
|
+
;
|
|
185
|
+
|
|
186
|
+
separator
|
|
187
|
+
: separator_op linebreak { result = val[0] }
|
|
188
|
+
| newline_list { result = ';' }
|
|
189
|
+
;
|
|
190
|
+
|
|
191
|
+
simple_command
|
|
192
|
+
: cmd_prefix cmd_word cmd_suffix { result = make_simple_command(val[0], val[1], val[2]) }
|
|
193
|
+
| cmd_prefix cmd_word { result = make_simple_command(val[0], val[1], []) }
|
|
194
|
+
| cmd_prefix { result = make_simple_command(val[0], nil, []) }
|
|
195
|
+
| cmd_name cmd_suffix { result = make_simple_command([], val[0], val[1]) }
|
|
196
|
+
| cmd_name { result = make_simple_command([], val[0], []) }
|
|
197
|
+
;
|
|
198
|
+
|
|
199
|
+
cmd_name
|
|
200
|
+
: WORD { result = val[0] }
|
|
201
|
+
;
|
|
202
|
+
|
|
203
|
+
cmd_word
|
|
204
|
+
: WORD { result = val[0] }
|
|
205
|
+
;
|
|
206
|
+
|
|
207
|
+
cmd_prefix
|
|
208
|
+
: io_redirect { result = [val[0]] }
|
|
209
|
+
| cmd_prefix io_redirect { result = val[0] << val[1] }
|
|
210
|
+
| ASSIGNMENT_WORD { result = [val[0]] }
|
|
211
|
+
| cmd_prefix ASSIGNMENT_WORD { result = val[0] << val[1] }
|
|
212
|
+
;
|
|
213
|
+
|
|
214
|
+
cmd_suffix
|
|
215
|
+
: io_redirect { result = [val[0]] }
|
|
216
|
+
| cmd_suffix io_redirect { result = val[0] << val[1] }
|
|
217
|
+
| WORD { result = [val[0]] }
|
|
218
|
+
| cmd_suffix WORD { result = val[0] << val[1] }
|
|
219
|
+
;
|
|
220
|
+
|
|
221
|
+
io_redirect
|
|
222
|
+
: io_file { result = val[0] }
|
|
223
|
+
| IO_NUMBER io_file { result = with_io_number(val[1], val[0]) }
|
|
224
|
+
| io_here { result = val[0] }
|
|
225
|
+
| IO_NUMBER io_here { result = with_io_number(val[1], val[0]) }
|
|
226
|
+
;
|
|
227
|
+
|
|
228
|
+
# The WORD after DLESS is the here-doc delimiter; the lexer has replaced it
|
|
229
|
+
# with the HereDoc holder whose body it fills at the following newline.
|
|
230
|
+
io_here
|
|
231
|
+
: DLESS WORD { result = make_heredoc(val[1]) }
|
|
232
|
+
| DLESSDASH WORD { result = make_heredoc(val[1]) }
|
|
233
|
+
;
|
|
234
|
+
|
|
235
|
+
io_file
|
|
236
|
+
: '<' filename { result = make_redirect(:in, val[1]) }
|
|
237
|
+
| '>' filename { result = make_redirect(:out, val[1]) }
|
|
238
|
+
| DGREAT filename { result = make_redirect(:append, val[1]) }
|
|
239
|
+
| LESSGREAT filename { result = make_redirect(:readwrite, val[1]) }
|
|
240
|
+
| CLOBBER filename { result = make_redirect(:clobber, val[1]) }
|
|
241
|
+
| GREATAND filename { result = make_redirect(:dup_out, val[1]) }
|
|
242
|
+
| LESSAND filename { result = make_redirect(:dup_in, val[1]) }
|
|
243
|
+
;
|
|
244
|
+
|
|
245
|
+
filename
|
|
246
|
+
: WORD { result = val[0] }
|
|
247
|
+
;
|
|
248
|
+
|
|
249
|
+
separator_op
|
|
250
|
+
: '&' { result = '&' }
|
|
251
|
+
| ';' { result = ';' }
|
|
252
|
+
;
|
|
253
|
+
|
|
254
|
+
newline_list
|
|
255
|
+
: NEWLINE
|
|
256
|
+
| newline_list NEWLINE
|
|
257
|
+
;
|
|
258
|
+
|
|
259
|
+
linebreak
|
|
260
|
+
: newline_list
|
|
261
|
+
| # empty
|
|
262
|
+
;
|
|
263
|
+
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
---- header
|
|
267
|
+
|
|
268
|
+
require_relative 'parser_support'
|
|
269
|
+
|
|
270
|
+
---- inner
|
|
271
|
+
|
|
272
|
+
include Rush::ParserSupport
|