├── .envrc ├── testing ├── empty_file ├── executable_file ├── symlink └── file_with_text ├── tests ├── check.out ├── issues.out ├── command.out ├── fail.out ├── help.params ├── builtin_status.out ├── basic_condition.out ├── builtin_status.ion ├── command.params ├── inplace_ops.out ├── regex_replace.out ├── replace.out ├── check.params ├── continue.out ├── herestring.out ├── if_with_builtins.out ├── multiline-arrays.out ├── empty_loop_test.out ├── replacen.out ├── else_if.out ├── multiarg_fail.params ├── builtin_piping.out ├── builtin_set.ion ├── cmdfile_fail.out ├── cmdfile_fail.params ├── keybinding_fail.out ├── comments.out ├── keybinding_fail.params ├── multiarg_fail.out ├── subst.out ├── function_piping.out ├── invalid_forward_index_array.ion ├── nested_conditions.out ├── scopes-3.out ├── scopes.out ├── invalid_backward_index_array.ion ├── pipe_fail_builtin.ion ├── break.out ├── fail.ion ├── invalid_range_on_array.ion ├── scopes-2.out ├── square_brackets.out ├── arrays_with_braces.out ├── basic_condition.ion ├── replace.ion ├── builtin_set.out ├── nested_for.ion ├── regex_replace.ion ├── invalid_backward_index_array.out ├── invalid_forward_index_array.out ├── return.out ├── process_exp.out ├── script_exec │ ├── basic_condition.ion │ ├── builtin_piping.ion │ ├── break.ion │ ├── array_methods.ion │ ├── braces.ion │ └── arrays.ion ├── invalid_range_on_array.out ├── pipe_fail_builtin.out ├── inner_expansions.out ├── script_exec.ion ├── optional_assignment.out ├── quotes.out ├── replacen.ion ├── scopes-4.out ├── space_parenthese_start_end.out ├── continue.ion ├── color_test.ion ├── fn-root-vars.ion ├── issues.ion ├── square_brackets.ion ├── advanced │ ├── cpu-model.ion │ └── rxtx-stats.ion ├── alias.out ├── arrays_with_braces.ion ├── not.out ├── comments.ion ├── multiple-lines.out ├── unicode.ion ├── and.out ├── inplace_ops.ion ├── builtin_piping.ion ├── conditionals.out ├── scopes-4.ion ├── command-substitutions.out ├── unicode.out ├── or.out ├── empty_loop_test.ion ├── scopes-3.ion ├── scopes.ion ├── scopes-2.ion ├── fn.out ├── pipelines.out ├── strings.out ├── multiline-arrays.ion ├── pipelines.ion ├── let.out ├── break.ion ├── multiple-lines.ion ├── command-substitutions.ion ├── fibonacci.ion ├── herestring.ion ├── process_exp.ion ├── arithmetic_vars.out ├── if_with_builtins.ion ├── nested_conditions.ion ├── optional_assignment.ion ├── not.ion ├── fn.ion ├── for.out ├── strings.ion ├── variable_exp.out ├── subst.ion ├── string_vars.out ├── variables.out ├── while.out ├── match.out ├── glob.ion ├── fibonacci.sh ├── function_piping.ion ├── quotes.ion ├── or.ion ├── and.ion ├── alias.ion ├── map_vars.out ├── inner_expansions.ion ├── README.md ├── glob.out ├── variable_exp.ion ├── arithmetic_vars.ion ├── nested_for.out ├── braces.ion ├── array_methods.out ├── brace_exp.out ├── brace_exp.ion ├── for.ion ├── else_if.ion ├── let.ion ├── methods.out ├── conditionals.ion ├── while.ion ├── array_vars.out ├── braces.out ├── string_vars.ion ├── return.ion ├── array_test.out ├── space_parenthese_start_end.ion ├── help.out ├── run_benches ├── string_methods.out ├── methods.ion ├── array_methods.ion ├── variables.ion ├── map_vars.ion ├── script_exec.out ├── exists.ion ├── array_test.ion ├── array_vars.ion ├── match.ion └── string_methods.ion ├── debian ├── compat ├── source │ └── format ├── copyright ├── postinst ├── control ├── rules └── changelog ├── src ├── lib │ ├── shell │ │ ├── sys │ │ │ ├── signals.rs │ │ │ └── mod.rs │ │ └── pipe_exec │ │ │ ├── README.md │ │ │ ├── streams.rs │ │ │ ├── foreground.rs │ │ │ └── pipes.rs │ ├── assignments │ │ └── mod.rs │ ├── parser │ │ ├── lexers │ │ │ ├── mod.rs │ │ │ └── assignments │ │ │ │ └── primitive.rs │ │ └── mod.rs │ ├── memory.rs │ ├── builtins │ │ ├── functions.rs │ │ ├── source.rs │ │ ├── man_pages.rs │ │ ├── conditionals.rs │ │ ├── command_info.rs │ │ └── random.rs │ ├── expansion │ │ └── loops.rs │ └── types.rs └── binary │ ├── README.md │ ├── huponexit.rs │ ├── keybindings.rs │ ├── readln.rs │ └── designators.rs ├── sh-interrupt ├── makestuff │ ├── Makefile3 │ ├── Makefile1 │ ├── Makefile2 │ ├── test01.sh │ ├── test02.sh │ └── README ├── .cvsignore ├── QUESTIONS ├── catcher ├── test20a.sh ├── test23a.sh ├── test20.sh ├── test23.sh ├── test10.sh ├── Makefile ├── cpu.c ├── test12.sh ├── test27.sh ├── test29.sh ├── test10.ion ├── deopt.sh ├── test12.ion ├── enveval.sh ├── test21.sh ├── test06.sh ├── test22.sh ├── test26.sh ├── hardguy.c ├── test25.sh ├── test24.sh ├── test24a.sh ├── test30.sh ├── lib.ion ├── lib.sh ├── test13.sh ├── test11.ion ├── test11.sh ├── test28a.sh ├── test09.sh ├── test20b.sh ├── test01.ion ├── test04.sh ├── test02.sh ├── test23b.sh ├── test28.sh ├── test04.ion ├── test01.sh ├── test08.ion ├── test08.sh ├── test03.sh ├── test03.ion ├── test05.sh ├── test07.ion ├── test05.ion ├── test07.sh ├── hash-error.sh ├── README └── mkdep.sh ├── examples ├── rust.png └── window-config.ion ├── bash ├── version.sh ├── distclean.sh └── install.sh ├── redox_linker ├── .gitlab ├── vim_syntax.png ├── emacs_syntax.png ├── merge_request_templates │ ├── fix.md │ ├── feat.md │ └── BREAKING_CHANGE.md └── issue_templates │ ├── rfc.md │ ├── syntax.md │ └── bug.md ├── clippy.toml ├── members ├── scopes-rs │ ├── README.md │ └── Cargo.toml ├── types-rs │ ├── README.md │ ├── Cargo.toml │ └── src │ │ ├── modification.rs │ │ ├── lib.rs │ │ └── types.rs ├── ranges │ ├── Cargo.toml │ └── src │ │ ├── index.rs │ │ ├── range.rs │ │ └── select.rs └── builtins-proc │ ├── Cargo.toml │ └── tests │ └── test.rs ├── fuzz ├── run_fuzzer.ion ├── fuzz_targets │ └── parser.rs └── Cargo.toml ├── shell.nix ├── manual ├── src │ ├── signals.md │ ├── variables │ │ ├── 05-exporting.md │ │ ├── 03-maps.md │ │ ├── 01-strings.md │ │ ├── 04-arithmetic.md │ │ ├── 06-scopes.md │ │ └── 00-variables.md │ ├── control │ │ ├── 00-flow.md │ │ └── 03-matches.md │ ├── expansions │ │ ├── 00-expansions.md │ │ ├── 08-redox-os.md │ │ ├── 02-process.md │ │ ├── 04-arithmetic.md │ │ └── 03-brace.md │ ├── scripts │ │ ├── 01-sourcing_another_file.md │ │ ├── 02-init_file.md │ │ └── 00-scripts.md │ ├── general.md │ ├── migrating.md │ ├── pipelines.md │ ├── jobs.md │ ├── SUMMARY.md │ └── repl.md ├── book.toml └── README.md ├── rustfmt.toml ├── .gitignore ├── benches ├── statement.rs ├── terminator.rs ├── herestring.ion └── calls.txt ├── LICENSE ├── .gitlab-ci.yml ├── Makefile └── ci └── run_benchmark.sh /.envrc: -------------------------------------------------------------------------------- 1 | use_nix 2 | -------------------------------------------------------------------------------- /testing/empty_file: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/check.out: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/issues.out: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /testing/executable_file: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/command.out: -------------------------------------------------------------------------------- 1 | 1 2 | -------------------------------------------------------------------------------- /tests/fail.out: -------------------------------------------------------------------------------- 1 | fail 2 | -------------------------------------------------------------------------------- /src/lib/shell/sys/signals.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /testing/symlink: -------------------------------------------------------------------------------- 1 | empty_file -------------------------------------------------------------------------------- /tests/help.params: -------------------------------------------------------------------------------- 1 | -h 2 | -------------------------------------------------------------------------------- /tests/builtin_status.out: -------------------------------------------------------------------------------- 1 | 1 2 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /tests/basic_condition.out: -------------------------------------------------------------------------------- 1 | pass 2 | -------------------------------------------------------------------------------- /tests/builtin_status.ion: -------------------------------------------------------------------------------- 1 | status -f 2 | -------------------------------------------------------------------------------- /tests/command.params: -------------------------------------------------------------------------------- 1 | -c 2 | echo 1 3 | -------------------------------------------------------------------------------- /tests/inplace_ops.out: -------------------------------------------------------------------------------- 1 | 1 2 | 21 3 | 12 4 | -------------------------------------------------------------------------------- /tests/regex_replace.out: -------------------------------------------------------------------------------- 1 | ob 2 | bo 3 | -------------------------------------------------------------------------------- /tests/replace.out: -------------------------------------------------------------------------------- 1 | 1two 1 three 2 | bc 3 | -------------------------------------------------------------------------------- /testing/file_with_text: -------------------------------------------------------------------------------- 1 | FILE IS NOT EMPTY 2 | -------------------------------------------------------------------------------- /tests/check.params: -------------------------------------------------------------------------------- 1 | -n 2 | -c 3 | echo silent 4 | -------------------------------------------------------------------------------- /tests/continue.out: -------------------------------------------------------------------------------- 1 | 1 2 | 3 3 | 5 4 | 7 5 | 9 6 | -------------------------------------------------------------------------------- /tests/herestring.out: -------------------------------------------------------------------------------- 1 | FOO 2 | true 3 | false 4 | -------------------------------------------------------------------------------- /tests/if_with_builtins.out: -------------------------------------------------------------------------------- 1 | a 2 | c 3 | YAY 4 | -------------------------------------------------------------------------------- /tests/multiline-arrays.out: -------------------------------------------------------------------------------- 1 | a b ab#cd 2 | a 3 | -------------------------------------------------------------------------------- /sh-interrupt/makestuff/Makefile3: -------------------------------------------------------------------------------- 1 | all: 2 | ../cpu 3 | -------------------------------------------------------------------------------- /tests/empty_loop_test.out: -------------------------------------------------------------------------------- 1 | one 2 | two 3 | three 4 | -------------------------------------------------------------------------------- /tests/replacen.out: -------------------------------------------------------------------------------- 1 | one two one two 2 | two two 3 | -------------------------------------------------------------------------------- /sh-interrupt/.cvsignore: -------------------------------------------------------------------------------- 1 | hardguy 2 | catcher 3 | cpu 4 | -------------------------------------------------------------------------------- /tests/else_if.out: -------------------------------------------------------------------------------- 1 | two 2 | four 3 | a < 10 4 | a < b 5 | -------------------------------------------------------------------------------- /tests/multiarg_fail.params: -------------------------------------------------------------------------------- 1 | -n 2 | -n 3 | -c 4 | echo 1 5 | -------------------------------------------------------------------------------- /tests/builtin_piping.out: -------------------------------------------------------------------------------- 1 | true 2 | false 3 | foo 4 | bar 5 | -------------------------------------------------------------------------------- /tests/builtin_set.ion: -------------------------------------------------------------------------------- 1 | set -e 2 | false 3 | echo unreachable 4 | -------------------------------------------------------------------------------- /tests/cmdfile_fail.out: -------------------------------------------------------------------------------- 1 | either execute command or file(s) 2 | -------------------------------------------------------------------------------- /tests/cmdfile_fail.params: -------------------------------------------------------------------------------- 1 | -c 2 | echo 1 3 | Hello 4 | World 5 | -------------------------------------------------------------------------------- /tests/keybinding_fail.out: -------------------------------------------------------------------------------- 1 | invalid keybinding, see --help 2 | -------------------------------------------------------------------------------- /tests/comments.out: -------------------------------------------------------------------------------- 1 | Hello world 2 | tabs ok 3 | not#a#comment 4 | -------------------------------------------------------------------------------- /tests/keybinding_fail.params: -------------------------------------------------------------------------------- 1 | -o 2 | viemacs 3 | -c 4 | echo 1 5 | -------------------------------------------------------------------------------- /tests/multiarg_fail.out: -------------------------------------------------------------------------------- 1 | flag or option set twice, see --help 2 | -------------------------------------------------------------------------------- /tests/subst.out: -------------------------------------------------------------------------------- 1 | foo bar 2 | baz 3 | foobar 4 | faz 5 | 2 6 | 3 7 | -------------------------------------------------------------------------------- /sh-interrupt/QUESTIONS: -------------------------------------------------------------------------------- 1 | Should the background job be killed in test 3? 2 | -------------------------------------------------------------------------------- /tests/function_piping.out: -------------------------------------------------------------------------------- 1 | 25 2 | 25 3 | one-two-three-four-five 4 | 75 5 | -------------------------------------------------------------------------------- /tests/invalid_forward_index_array.ion: -------------------------------------------------------------------------------- 1 | let array = [ 1 ] 2 | echo @array[1] 3 | -------------------------------------------------------------------------------- /tests/nested_conditions.out: -------------------------------------------------------------------------------- 1 | true a == a 2 | false b != b 3 | true 3 > 2 4 | -------------------------------------------------------------------------------- /tests/scopes-3.out: -------------------------------------------------------------------------------- 1 | 5 2 | ion: expansion error: Variable "x" does not exist 3 | -------------------------------------------------------------------------------- /tests/scopes.out: -------------------------------------------------------------------------------- 1 | 2 2 | ion: expansion error: Variable "y" does not exist 3 | -------------------------------------------------------------------------------- /examples/rust.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redox-os/ion/HEAD/examples/rust.png -------------------------------------------------------------------------------- /sh-interrupt/makestuff/Makefile1: -------------------------------------------------------------------------------- 1 | all: 2 | $(MAKE) -s -f Makefile2 3 | ../cpu 4 | -------------------------------------------------------------------------------- /sh-interrupt/makestuff/Makefile2: -------------------------------------------------------------------------------- 1 | all: 2 | $(MAKE) -s -f Makefile3 3 | ../cpu 4 | -------------------------------------------------------------------------------- /tests/invalid_backward_index_array.ion: -------------------------------------------------------------------------------- 1 | let array = [ 2 4 ] 2 | echo @array[-3] 3 | -------------------------------------------------------------------------------- /tests/pipe_fail_builtin.ion: -------------------------------------------------------------------------------- 1 | set -e -p 2 | echo 1 3 | false | true 4 | echo 2 5 | -------------------------------------------------------------------------------- /bash/version.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | git rev-parse HEAD > git_revision.txt 4 | -------------------------------------------------------------------------------- /redox_linker: -------------------------------------------------------------------------------- 1 | [target.x86_64-unknown-redox] 2 | linker = "x86_64-unknown-redox-gcc" 3 | -------------------------------------------------------------------------------- /sh-interrupt/catcher: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redox-os/ion/HEAD/sh-interrupt/catcher -------------------------------------------------------------------------------- /tests/break.out: -------------------------------------------------------------------------------- 1 | 1 2 | 2 3 | 3 4 | 4 5 | 5 6 | 1 7 | 2 8 | 3 9 | 4 10 | 5 11 | -------------------------------------------------------------------------------- /tests/fail.ion: -------------------------------------------------------------------------------- 1 | if test 3 -eq 2 2 | echo pass 3 | else 4 | echo fail 5 | end 6 | -------------------------------------------------------------------------------- /tests/invalid_range_on_array.ion: -------------------------------------------------------------------------------- 1 | let array = [ 1 2 3 4 5 ] 2 | echo @array[5..12] 3 | -------------------------------------------------------------------------------- /tests/scopes-2.out: -------------------------------------------------------------------------------- 1 | 1 2 | ion: expansion error: Variable "super::y" does not exist 3 | -------------------------------------------------------------------------------- /.gitlab/vim_syntax.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redox-os/ion/HEAD/.gitlab/vim_syntax.png -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | cognitive-complexity-threshold = 100 2 | type-complexity-threshold = 1000 3 | -------------------------------------------------------------------------------- /tests/square_brackets.out: -------------------------------------------------------------------------------- 1 | [asdf] 2 | [asdf 3 | asdf] 4 | -------------------------------------------------------------------------------- /.gitlab/emacs_syntax.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redox-os/ion/HEAD/.gitlab/emacs_syntax.png -------------------------------------------------------------------------------- /tests/arrays_with_braces.out: -------------------------------------------------------------------------------- 1 | 2 | a b c 3 | ab c d 4 | ab ac d e 5 | , 6 | a,b 7 | [a,b]c d e 8 | -------------------------------------------------------------------------------- /tests/basic_condition.ion: -------------------------------------------------------------------------------- 1 | if test 3 -eq 3 2 | echo pass 3 | else 4 | echo fail 5 | end 6 | -------------------------------------------------------------------------------- /tests/replace.ion: -------------------------------------------------------------------------------- 1 | echo $replace("onetwo one three" "one" "1") 2 | echo $replace("abc" "a" "") 3 | -------------------------------------------------------------------------------- /tests/builtin_set.out: -------------------------------------------------------------------------------- 1 | ion: pipeline execution error: early exit: pipeline failed with error code 1 2 | -------------------------------------------------------------------------------- /tests/nested_for.ion: -------------------------------------------------------------------------------- 1 | for a in 0..10 2 | for b in 1..10 3 | echo $a$b 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /tests/regex_replace.ion: -------------------------------------------------------------------------------- 1 | echo $regex_replace("bob" "^b" " ") 2 | echo $regex_replace("bob" 'b$' "") 3 | -------------------------------------------------------------------------------- /tests/invalid_backward_index_array.out: -------------------------------------------------------------------------------- 1 | ion: expansion error: Invalid index -3 for sequence with length 2 2 | -------------------------------------------------------------------------------- /tests/invalid_forward_index_array.out: -------------------------------------------------------------------------------- 1 | ion: expansion error: Invalid index 1 for sequence with length 1 2 | -------------------------------------------------------------------------------- /tests/return.out: -------------------------------------------------------------------------------- 1 | should be printed 2 | should be printed 3 | should be printed 4 | should be printed 5 | -------------------------------------------------------------------------------- /members/scopes-rs/README.md: -------------------------------------------------------------------------------- 1 | # Scopes-rs 2 | 3 | A generic data structure to organize variables in scopes 4 | -------------------------------------------------------------------------------- /tests/process_exp.out: -------------------------------------------------------------------------------- 1 | # ANCHOR: process_expansion 2 | t1 3 | t2 4 | t1 t2 5 | # ANCHOR_END: process_expansion 6 | -------------------------------------------------------------------------------- /tests/script_exec/basic_condition.ion: -------------------------------------------------------------------------------- 1 | if test 3 -eq 3 2 | echo pass 3 | else 4 | echo fail 5 | end 6 | -------------------------------------------------------------------------------- /bash/distclean.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | rm -rf vendor vendor.tar.xz .cargo git_revision.txt 4 | cargo clean 5 | -------------------------------------------------------------------------------- /tests/invalid_range_on_array.out: -------------------------------------------------------------------------------- 1 | ion: expansion error: Invalid range between (5,12) for sequence with length 5 2 | -------------------------------------------------------------------------------- /tests/pipe_fail_builtin.out: -------------------------------------------------------------------------------- 1 | 1 2 | ion: pipeline execution error: early exit: pipeline failed with error code 1 3 | -------------------------------------------------------------------------------- /tests/inner_expansions.out: -------------------------------------------------------------------------------- 1 | me myself I 2 | me I myself 3 | myself I me 4 | myself me I 5 | I myself me 6 | I me myself 7 | -------------------------------------------------------------------------------- /tests/script_exec.ion: -------------------------------------------------------------------------------- 1 | for script in tests/script_exec/a* tests/script_exec/b* 2 | target/debug/ion $script 3 | end 4 | -------------------------------------------------------------------------------- /sh-interrupt/test20a.sh: -------------------------------------------------------------------------------- 1 | #! ./testshell 2 | 3 | #echo Second level is pid $$ 4 | ./test20b.sh 5 | echo Broken, exit code $? 6 | -------------------------------------------------------------------------------- /sh-interrupt/test23a.sh: -------------------------------------------------------------------------------- 1 | #! ./testshell 2 | 3 | #echo Second level is pid $$ 4 | ./test23b.sh 5 | echo Broken, exit code $? 6 | -------------------------------------------------------------------------------- /tests/optional_assignment.out: -------------------------------------------------------------------------------- 1 | 5 2 | 5 3 | 1 2 3 4 | 1 2 3 5 | 1 2 3 6 | hello 7 | 5 8 | 1 2 3 9 | hello 10 | 5 11 | -------------------------------------------------------------------------------- /tests/quotes.out: -------------------------------------------------------------------------------- 1 | auc 2 | a< /dev/null ; done) 5 | -------------------------------------------------------------------------------- /tests/advanced/cpu-model.ion: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ion 2 | 3 | # Prints the model name of the CPU 4 | 5 | echo @(grep 'model name' /proc/cpuinfo | head -1)[3..5] 6 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | let 2 | pkgs = import {}; 3 | in 4 | pkgs.mkShell { 5 | buildInputs = with pkgs; [ 6 | binutils.bintools 7 | ]; 8 | } 9 | -------------------------------------------------------------------------------- /sh-interrupt/Makefile: -------------------------------------------------------------------------------- 1 | ALL = catcher cpu hardguy 2 | 3 | all: $(ALL) 4 | 5 | CFLAGS = -O -g -Wall -D_POSIX_SOURCE 6 | 7 | clean: 8 | -rm $(ALL) *.core core *.o 9 | -------------------------------------------------------------------------------- /tests/alias.out: -------------------------------------------------------------------------------- 1 | PIPED 2 | LOGICAL1 3 | LOGICAL2 4 | PIPED_AND_LOGICAL1 5 | PIPED_AND_LOGICAL2 6 | PIPED_OK 7 | LOGICAL1 8 | PIPED_AND_LOGICAL1 9 | DONE 10 | -------------------------------------------------------------------------------- /tests/arrays_with_braces.ion: -------------------------------------------------------------------------------- 1 | echo [{ }] 2 | echo [{a b c}] 3 | echo [a{b c} d] 4 | echo [a{b,c d} e] 5 | 6 | echo {[,]} 7 | echo {[a,b]} 8 | echo {[a,b]c,{d,e}} 9 | -------------------------------------------------------------------------------- /tests/not.out: -------------------------------------------------------------------------------- 1 | not true 2 | not true 3 | not false 4 | not false 5 | true and not false 6 | true and not false 7 | not test 8 | not test 9 | hello 10 | 1 11 | -------------------------------------------------------------------------------- /tests/comments.ion: -------------------------------------------------------------------------------- 1 | echo Hello world # End of line comments are ignored 2 | 3 | #echo Goodbye world 4 | #echo Nada 5 | echo tabs ok #comment 6 | echo not#a#comment 7 | -------------------------------------------------------------------------------- /src/lib/assignments/mod.rs: -------------------------------------------------------------------------------- 1 | mod actions; 2 | mod checker; 3 | pub use self::{ 4 | actions::{Action, AssignmentActions}, 5 | checker::{is_array, value_check}, 6 | }; 7 | -------------------------------------------------------------------------------- /tests/multiple-lines.out: -------------------------------------------------------------------------------- 1 | one two three 2 | one two 3 | three four 4 | five 5 | one two' 6 | three 7 | one two" 8 | three 9 | one two three four five six 10 | -------------------------------------------------------------------------------- /tests/unicode.ion: -------------------------------------------------------------------------------- 1 | fn Җ Ҕ:int 2 | echo $Ҕ 3 | end 4 | 5 | echo Ҥ 6 | Җ 1 7 | let Ҳ = ӌ 8 | echo $Ҳ 9 | let 1Ҳ = ӌ 10 | echo $1Ҳ 11 | echo Should not be reached 12 | -------------------------------------------------------------------------------- /src/lib/shell/sys/mod.rs: -------------------------------------------------------------------------------- 1 | //! System specific shell variables for NULL_PATH 2 | 3 | #[cfg(unix)] 4 | /// NULL_PATH on Unix systems 5 | pub const NULL_PATH: &str = "/dev/null"; 6 | -------------------------------------------------------------------------------- /tests/and.out: -------------------------------------------------------------------------------- 1 | true and true 2 | Answer in life is 42 3 | # ANCHOR: and_demonstration 4 | Will be printed: Both commands returned successfully 5 | # ANCHOR_END: and_demonstration 6 | -------------------------------------------------------------------------------- /tests/inplace_ops.ion: -------------------------------------------------------------------------------- 1 | let a = [ 0 1 ] 2 | let b = [ 0 1 ] 3 | let a b \\= 0 @a[0] 4 | echo @b[0] 5 | 6 | let a b = 1 2 7 | let a b ::= $b $a 8 | echo $a 9 | echo $b 10 | -------------------------------------------------------------------------------- /members/types-rs/README.md: -------------------------------------------------------------------------------- 1 | # Types-rs 2 | 3 | A dynamically-typed datatype. Supports string, array, hashmap, binary-tree-map, 128 bit integer, decimal floating point and functions. 4 | -------------------------------------------------------------------------------- /sh-interrupt/cpu.c: -------------------------------------------------------------------------------- 1 | /* Eats up CPU time, never does voluntary context switch */ 2 | /* Exits on signals as usual */ 3 | 4 | int main(void) 5 | { 6 | while(1); 7 | return 0; 8 | } 9 | -------------------------------------------------------------------------------- /sh-interrupt/test12.sh: -------------------------------------------------------------------------------- 1 | #!./testshell 2 | 3 | echo 'Test 12 (Variant of test10):' 4 | echo 'This script should not be killable by SIGINT or SIGQUIT' 5 | (while :; do ./catcher ; done) 6 | -------------------------------------------------------------------------------- /sh-interrupt/test27.sh: -------------------------------------------------------------------------------- 1 | #! ./testshell 2 | 3 | echo "You should be able to end this script by SIGQUIT, but not by SIGINT" 4 | trap : 2 5 | trap 'echo SIGQUIT ; exit 1' 3 6 | ./hardguy 7 | -------------------------------------------------------------------------------- /sh-interrupt/test29.sh: -------------------------------------------------------------------------------- 1 | #! ./testshell 2 | 3 | echo "You should be able to end this script by SIGQUIT, but not by SIGINT" 4 | trap '' 2 5 | trap 'echo SIGQUIT ; exit 1' 3 6 | ./hardguy 7 | -------------------------------------------------------------------------------- /tests/builtin_piping.ion: -------------------------------------------------------------------------------- 1 | matches Foo '([A-Z])[[:word:]]+' && echo true 2 | matches foo '([A-Z])[[:word:]]+' || echo false 3 | echo foo | cat 4 | read foo <<< $(echo bar) 5 | echo $foo 6 | -------------------------------------------------------------------------------- /sh-interrupt/test10.ion: -------------------------------------------------------------------------------- 1 | #!./testshell 2 | 3 | echo 'You should be able to end the script with one SIGINT' 4 | ../target/debug/ion -c 'while true; wc ../target/debug/ion > /dev/null ; end' 5 | -------------------------------------------------------------------------------- /tests/conditionals.out: -------------------------------------------------------------------------------- 1 | and 2 | or 3 | false 4 | true 5 | double or 6 | double and 7 | or and 8 | true 9 | true 10 | false 11 | true 12 | true 13 | false 14 | true 15 | true 16 | false 17 | -------------------------------------------------------------------------------- /tests/scopes-4.ion: -------------------------------------------------------------------------------- 1 | fn demo 2 | echo ${super::foo} 3 | drop foo 4 | 5 | fn bar 6 | super::demo 7 | end 8 | bar 9 | end 10 | 11 | let foo = bar 12 | echo $foo 13 | demo 14 | -------------------------------------------------------------------------------- /src/lib/parser/lexers/mod.rs: -------------------------------------------------------------------------------- 1 | /// Check functions & methods arguments 2 | pub mod arguments; 3 | /// Check assignements 4 | pub mod assignments; 5 | 6 | pub use self::{arguments::*, assignments::*}; 7 | -------------------------------------------------------------------------------- /tests/command-substitutions.out: -------------------------------------------------------------------------------- 1 | one two three four 'five' six 2 | 0 one two three 1 3 | 0 one 4 | two 5 | three 1 6 | 0 one two three 1 7 | 0 one 8 | two 9 | three 1 10 | -------------------------------------------------------------------------------- /tests/script_exec/builtin_piping.ion: -------------------------------------------------------------------------------- 1 | matches Foo '([A-Z])[[:word:]]+' && echo true 2 | matches foo '([A-Z])[[:word:]]+' || echo false 3 | echo foo | cat 4 | read foo <<< $(echo bar) 5 | echo $foo 6 | -------------------------------------------------------------------------------- /tests/unicode.out: -------------------------------------------------------------------------------- 1 | Ҥ 2 | 1 3 | ӌ 4 | ion: assignment error: invalid variable name: only alphanumerical characters and underscores are supported 5 | ion: expansion error: Variable "1Ҳ" does not exist 6 | -------------------------------------------------------------------------------- /sh-interrupt/deopt.sh: -------------------------------------------------------------------------------- 1 | #! ./testshell 2 | 3 | (trap 'echo SIGINT happend ; exit 1' 2; ./hardguy ; echo -n) 4 | echo survived 5 | (trap 'echo SIGINT happend ; exit 1' 2; ./hardguy) 6 | echo survived 7 | 8 | -------------------------------------------------------------------------------- /tests/or.out: -------------------------------------------------------------------------------- 1 | false or true 2 | true or true 3 | if: or true 4 | # ANCHOR: or_demonstration 5 | Will be printed: At least one of the two commands returns successfully 6 | # ANCHOR_END: or_demonstration 7 | -------------------------------------------------------------------------------- /sh-interrupt/test12.ion: -------------------------------------------------------------------------------- 1 | #!./testshell 2 | 3 | echo 'Test 12 (Variant of test10):' 4 | echo 'This script should not be killable by SIGINT or SIGQUIT' 5 | ion -c 'source lib.ion; while true; ./catcher ; end' 6 | -------------------------------------------------------------------------------- /manual/src/signals.md: -------------------------------------------------------------------------------- 1 | # Signal Handling 2 | 3 | - **SIGINT** (Ctrl + C): Interrupt the running program with a signal to terminate. 4 | - **SIGTSTP** (Ctrl + Z): Send the running job to the background, pausing it. -------------------------------------------------------------------------------- /tests/empty_loop_test.ion: -------------------------------------------------------------------------------- 1 | #Test that an empty loop does not crash ion. 2 | 3 | fn looper stf:[str] 4 | for item in @stf 5 | echo "$item" 6 | end 7 | end 8 | 9 | looper [one two three] 10 | looper [] 11 | -------------------------------------------------------------------------------- /tests/scopes-3.ion: -------------------------------------------------------------------------------- 1 | if test 1 == 1 2 | let x = 5 3 | 4 | fn print 5 | echo ${super::x} 6 | end 7 | 8 | if test 1 == 1 9 | print 10 | end 11 | end 12 | 13 | echo $x 14 | -------------------------------------------------------------------------------- /tests/scopes.ion: -------------------------------------------------------------------------------- 1 | if test 1 == 1 2 | let x = 5 3 | 4 | if test 1 == 1 5 | let x = 2 6 | let y = 3 7 | end 8 | 9 | echo $x 10 | echo $y 11 | echo Should not be printed 12 | end 13 | 14 | -------------------------------------------------------------------------------- /tests/scopes-2.ion: -------------------------------------------------------------------------------- 1 | if test 1 == 1 2 | let x = 1 3 | 4 | fn print 5 | echo ${super::x} 6 | echo ${super::y} 7 | end 8 | 9 | if test 1 == 1 10 | let y = 2 11 | print 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /src/binary/README.md: -------------------------------------------------------------------------------- 1 | # shell::binary 2 | 3 | This module contains all of the logic associated with the shell's application. This logic 4 | consists primarily of the REPL, script and argument executions, and word designators. -------------------------------------------------------------------------------- /sh-interrupt/enveval.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | unset FOO 3 | FOO=foocorrect eval echo \$FOO 4 | 5 | FOO=wrong 6 | BAR=barcorrect 7 | FOO=foocorrect eval echo \$FOO \$BAR 8 | 9 | FOO=foocorrect sh -c 'echo "$FOO $BAR"' 10 | 11 | -------------------------------------------------------------------------------- /sh-interrupt/test21.sh: -------------------------------------------------------------------------------- 1 | #! ./testshell 2 | 3 | traph () 4 | { 5 | echo 'Survived!' 6 | } 7 | trap traph 2 8 | 9 | echo 'You should need 5 SIGINT to end this script' 10 | cat 11 | cat 12 | cat 13 | cat 14 | cat 15 | 16 | -------------------------------------------------------------------------------- /tests/fn.out: -------------------------------------------------------------------------------- 1 | hello 2 | world 3 | goodbye 4 | 1 5 | 2 6 | 3 7 | 4 8 | 5 9 | 6 10 | 7 11 | 8 12 | 9 13 | 1 14 | 4 15 | 9 16 | 16 17 | 25 18 | ion: function error: argument has invalid type: expected int, found value '$num' 19 | -------------------------------------------------------------------------------- /tests/pipelines.out: -------------------------------------------------------------------------------- 1 | test 2 | one 3 | two 4 | foo 5 | found foo 6 | did not find bar 7 | test 8 | found test 9 | ion: expansion error: Could not expand subprocess: pipeline execution error: command not found: im_not_a_command 10 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: ion 3 | Source: https://gitlab.redox-os.org/redox-os/ion 4 | 5 | Files: * 6 | Copyright: Copyright 2018 Redox OS 7 | License: MIT 8 | -------------------------------------------------------------------------------- /tests/strings.out: -------------------------------------------------------------------------------- 1 | hello?world 2 | hello*world 3 | hello^world 4 | hello%world 5 | $hello 6 | \ 7 | \n 8 | one\ 9 | two\ 10 | three 11 | " 12 | < 13 | < 14 | << 15 | << 16 | <<< 17 | <<< 18 | > 19 | > 20 | >> 21 | >> 22 | >>> 23 | >>> 24 | -------------------------------------------------------------------------------- /sh-interrupt/test06.sh: -------------------------------------------------------------------------------- 1 | #!./testshell 2 | 3 | echo 'Test 2: You should not be able to exit `cat` with SIGQUIT.' 4 | echo ' SIGINT and SIGTERM should exit the whole script.' 5 | 6 | set -x 7 | trap '' 3 8 | while : ; do cat ; echo -n $? ; done 9 | -------------------------------------------------------------------------------- /tests/multiline-arrays.ion: -------------------------------------------------------------------------------- 1 | let x = [ 2 | a # a comment 3 | b # another comment 4 | ab#cd #with a comment 5 | ] 6 | 7 | echo @x 8 | 9 | let y = [ 10 | a 11 | # a comment 12 | #another comment 13 | ] 14 | 15 | echo @y 16 | -------------------------------------------------------------------------------- /tests/pipelines.ion: -------------------------------------------------------------------------------- 1 | echo test | cat | cat 2 | echo one | cat <<< two 3 | echo foo | grep foo && echo found foo 4 | echo foo | grep bar || echo did not find bar 5 | echo test | grep test && echo found test | cat 6 | 7 | echo $(im_not_a_command | echo 1) 8 | -------------------------------------------------------------------------------- /sh-interrupt/test22.sh: -------------------------------------------------------------------------------- 1 | #! ./testshell 2 | 3 | traph () 4 | { 5 | trap 2 6 | kill -2 $$ 7 | echo 'Survived!' 8 | } 9 | 10 | echo 'You should be able to kill this script with just one SIGINT' 11 | cat 12 | cat 13 | cat 14 | cat 15 | cat 16 | 17 | -------------------------------------------------------------------------------- /tests/let.out: -------------------------------------------------------------------------------- 1 | 5 2 | 10 3 | 5 4 | 1 5 | 2 6 | 3 7 | one two 8 | three four 9 | five six 10 | 5 11 | 25 12 | 6.25 13 | one two 14 | three 15 | four five 16 | 6 3 5 17 | 4 1 1 18 | 3 1 2 19 | 3 1 2 20 | one 21 | two 22 | three 23 | four 24 | five 25 | six 26 | -------------------------------------------------------------------------------- /manual/src/variables/05-exporting.md: -------------------------------------------------------------------------------- 1 | # Exporting Variables 2 | 3 | The `export` builtin operates identical to the `let` builtin, but it does not support arrays, 4 | and variables are exported to the OS environment. 5 | 6 | ```sh 7 | export GLOBAL_VAL = "this" 8 | ``` 9 | -------------------------------------------------------------------------------- /tests/break.ion: -------------------------------------------------------------------------------- 1 | for i in 1..10 2 | echo $i 3 | if test $i -eq 5 4 | break 5 | end 6 | end 7 | 8 | let a = 1 9 | while test $a -lt 10 10 | echo $a 11 | if test $a -eq 5 12 | break 13 | end 14 | let a += 1 15 | end 16 | -------------------------------------------------------------------------------- /tests/multiple-lines.ion: -------------------------------------------------------------------------------- 1 | echo \ 2 | one two \ 3 | three 4 | 5 | echo "one two 6 | three four 7 | five" 8 | 9 | echo "one two' 10 | three" 11 | 12 | echo 'one two" 13 | three' 14 | 15 | echo "one two \ 16 | three four \ 17 | five six" 18 | -------------------------------------------------------------------------------- /sh-interrupt/test26.sh: -------------------------------------------------------------------------------- 1 | #! ./testshell 2 | 3 | echo "On SIGINT, this script should print a line and NIT exit, on SIGQUIT, it should exit" 4 | trap 'echo This should not just display something, not exit' 2 5 | trap 'echo SIGQUIT ; exit 1' 3 6 | #trap : 3 7 | ./hardguy 8 | 9 | -------------------------------------------------------------------------------- /tests/command-substitutions.ion: -------------------------------------------------------------------------------- 1 | echo $(echo "one two three" "four 'five' six") 2 | echo 0 "$(echo -e ' one two three ')" 1 3 | echo 0 "$(echo -e ' one\ntwo\nthree ')" 1 4 | echo 0 $(echo -e ' one two three ') 1 5 | echo 0 $(echo -e ' one\ntwo\nthree ') 1 6 | -------------------------------------------------------------------------------- /tests/script_exec/break.ion: -------------------------------------------------------------------------------- 1 | for i in 1..10 2 | echo $i 3 | if test $i -eq 5 4 | break 5 | end 6 | end 7 | 8 | let a = 1 9 | while test $a -lt 10 10 | echo $a 11 | if test $a -eq 5 12 | break 13 | end 14 | let a += 1 15 | end 16 | -------------------------------------------------------------------------------- /sh-interrupt/hardguy.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main(void) 6 | { 7 | int i; 8 | signal(SIGINT, SIG_IGN); 9 | signal(SIGQUIT, SIG_IGN); 10 | for (i=20 ; i>0; i--) { 11 | sleep(1); 12 | } 13 | return 0; 14 | } 15 | -------------------------------------------------------------------------------- /tests/fibonacci.ion: -------------------------------------------------------------------------------- 1 | fn fib n 2 | let output previous = 0 1 3 | for _ in 0..$n 4 | let temp = $output 5 | let output += $previous 6 | let previous = $temp 7 | end 8 | echo $n $output 9 | end 10 | 11 | for i in 0..184 12 | fib $i 13 | end 14 | -------------------------------------------------------------------------------- /sh-interrupt/test25.sh: -------------------------------------------------------------------------------- 1 | #! ./testshell 2 | 3 | traph () 4 | { 5 | trap 2 6 | kill -2 $$ 7 | echo 'Error, survived!' 8 | sleep 1 9 | echo 'Survived even longer.' 10 | } 11 | echo 'You should be able to end the script with just one SIGINT' 12 | trap traph 2 13 | ./hardguy 14 | 15 | -------------------------------------------------------------------------------- /sh-interrupt/test24.sh: -------------------------------------------------------------------------------- 1 | #! ./testshell 2 | 3 | echo pid $$ 4 | trap : 2 5 | echo "You should be able to end this script with two SIGINT" 6 | echo 7 | (echo pid $$ ; trap "echo exit pid $$ ; exit 1" 2 ; ./hardguy ; echo -n) 8 | (echo pid $$ ; trap "echo exit pid $$ ; exit 1" 2 ; ./hardguy ; echo -n) 9 | -------------------------------------------------------------------------------- /tests/herestring.ion: -------------------------------------------------------------------------------- 1 | echo $(tr '[a-z]' '[A-Z]' <<< foo) 2 | 3 | let output = [foo bar baz bing] 4 | 5 | fn find elem array 6 | if grep -q $elem <<< $array 7 | echo "true" 8 | else 9 | echo "false" 10 | end 11 | end 12 | 13 | find "bar" "@output" 14 | find "zar" "@output" 15 | -------------------------------------------------------------------------------- /sh-interrupt/test24a.sh: -------------------------------------------------------------------------------- 1 | #! ./testshell 2 | 3 | set -T 4 | echo pid $$ 5 | trap : 2 6 | echo "You should be able to end this script with two SIGINT" 7 | echo 8 | (echo pid $$ ; trap "echo exit pid $$ ; exit 1" 2 ; ./hardguy) 9 | (echo pid $$ ; trap "echo exit pid $$ ; exit 1" 2 ; ./hardguy) 10 | -------------------------------------------------------------------------------- /sh-interrupt/test30.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | # Don't ask what is the right thing here... 4 | 5 | IFS=" :" 6 | var="bla:fasel:blubb:" 7 | for i in foo:bla:fasel:blubb: ; do 8 | echo val: "'"$i"'" 9 | done 10 | echo 11 | for i in foo:$var ; do 12 | echo val: "'"$i"'" 13 | done 14 | 15 | -------------------------------------------------------------------------------- /tests/process_exp.ion: -------------------------------------------------------------------------------- 1 | echo '# ANCHOR: process_expansion' 2 | mkdir -p _tmp _tmp/t1 _tmp/t2 3 | cd _tmp 4 | let res = $(ls) 5 | let res2 = [ @(ls) ] 6 | echo $res # output the string 7 | echo @res2 # output the array 8 | cd .. 9 | rm -fr _tmp 10 | echo '# ANCHOR_END: process_expansion' 11 | -------------------------------------------------------------------------------- /.gitlab/merge_request_templates/fix.md: -------------------------------------------------------------------------------- 1 | fix: description 2 | 3 | closes issue: #bug 4 | 5 | test: regression tested in (default in `tests`, otherwise explain concise) 6 | 7 | refactor: (affects other tests or data structures?) 8 | 9 | docs: (documented?) 10 | 11 | perf: (performance impact?) 12 | -------------------------------------------------------------------------------- /sh-interrupt/lib.ion: -------------------------------------------------------------------------------- 1 | fn docatcher 2 | echo 'Trigger some async actions, shell should not exit' 3 | echo 'Then exit catcher with C-d' 4 | if not exists -f catcher 5 | make catcher 6 | end 7 | ./catcher 8 | end 9 | 10 | fn endless 11 | while true 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /sh-interrupt/lib.sh: -------------------------------------------------------------------------------- 1 | docatcher() { 2 | echo 'Trigger some async actions, shell should not exit' 3 | echo 'Then exit catcher with C-d' 4 | if [ ! -f ./catcher ]; then 5 | make catcher 6 | fi 7 | ./catcher 8 | } 9 | 10 | endless() { 11 | while : ; do foo=a; done 12 | } 13 | -------------------------------------------------------------------------------- /sh-interrupt/test13.sh: -------------------------------------------------------------------------------- 1 | #!./testshell 2 | 3 | echo 'Test 13 (variant of Test 1):' 4 | echo 'On SIGINT, cat should exit (and be restarted by the shell loop)' 5 | echo 'and the Text "I am a trap" should be printed' 6 | 7 | set -x 8 | trap 'echo I am a trap' 2 9 | while : ; do cat ; echo -n $? ; done 10 | -------------------------------------------------------------------------------- /debian/postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | BINARY="/usr/bin/ion" 3 | if ! grep ion /etc/shells >/dev/null; then 4 | echo ${BINARY} >> /etc/shells 5 | else 6 | shell=$(grep ion /etc/shells) 7 | if [ "$shell" != "${BINARY}" ]; then 8 | sed -i -e "s#$shell#${BINARY}#g" /etc/shells 9 | fi 10 | fi 11 | -------------------------------------------------------------------------------- /tests/arithmetic_vars.out: -------------------------------------------------------------------------------- 1 | # ANCHOR: individual_assignments 2 | 5 3 | 10 4 | 8 5 | 16 6 | 8 7 | 64.0 8 | 32.0 9 | # ANCHOR_END: individual_assignments 10 | # ANCHOR: multiple_assignments 11 | 5 5 12 | 10 10 13 | 8 8 14 | 16 16 15 | 8 8 16 | 64.0 64.0 17 | 32.0 32.0 18 | # ANCHOR_END: multiple_assignments 19 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/parser.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #[macro_use] extern crate libfuzzer_sys; 3 | extern crate ion_shell; 4 | 5 | use ion_shell::parser::fuzzing::*; 6 | 7 | fuzz_target!(|data: &[u8]| { 8 | if let Ok(s) = std::str::from_utf8(data) { 9 | let _ = statement_parse(&s); 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /sh-interrupt/test11.ion: -------------------------------------------------------------------------------- 1 | #!./testshell 2 | 3 | echo 'Test 11 (variant of 9):' 4 | echo 'Try to break wait using SIGINT before wc completes' 5 | echo 'After you break wait, it should NOT print "Going on"' 6 | 7 | echo 'wc &' 8 | gzip < ../target/debug/ion | wc & 9 | echo wait 10 | wait 11 | echo "Going on" 12 | -------------------------------------------------------------------------------- /tests/if_with_builtins.ion: -------------------------------------------------------------------------------- 1 | if not false 2 | echo a 3 | end 4 | 5 | if not true 6 | echo b 7 | else if not false 8 | echo c 9 | end 10 | 11 | if true && false 12 | echo NOPE 13 | else if true && not true 14 | echo AHHH 15 | else if false || true 16 | echo YAY 17 | else 18 | echo PLEASE 19 | end 20 | -------------------------------------------------------------------------------- /tests/nested_conditions.ion: -------------------------------------------------------------------------------- 1 | if test a = a 2 | echo true a == a 3 | if test b != b 4 | echo true b != b 5 | else 6 | echo false b != b 7 | if test 3 -gt 2 8 | echo "true 3 > 2" 9 | else 10 | echo "false 3 > 2" 11 | end 12 | end 13 | else 14 | echo false a == a 15 | end 16 | -------------------------------------------------------------------------------- /tests/optional_assignment.ion: -------------------------------------------------------------------------------- 1 | let a ?= 5 2 | echo $a 3 | let a ?= 10 4 | echo $a 5 | 6 | let b ?= [ 1 2 3 ] 7 | echo @b 8 | let b ?= [ 4 5 6 ] 9 | echo @b 10 | 11 | let c d e ?= [ 1 2 3 ] hello 5 12 | echo @c 13 | echo $d 14 | echo $e 15 | let c d e ?= [ 4 5 6 ] world 10 16 | echo @c 17 | echo $d 18 | echo $e 19 | -------------------------------------------------------------------------------- /sh-interrupt/test11.sh: -------------------------------------------------------------------------------- 1 | #!./testshell 2 | 3 | echo 'Test 11 (variant of 9):' 4 | echo 'Try to break wait using SIGINT before wc completes' 5 | echo 'After you break wait, it should NOT print "Going on"' 6 | 7 | echo 'wc &' 8 | gzip < /kernel | wc & 9 | p=$! 10 | echo wait 11 | wait 12 | echo "Going on" 13 | kill $p 14 | -------------------------------------------------------------------------------- /sh-interrupt/test28a.sh: -------------------------------------------------------------------------------- 1 | #! ./testshell 2 | 3 | echo "This script is the same as the last, but it enables the asynchronous" 4 | echo "trap switch (-T) in FreeBSD's sh from April 1999." 5 | echo "Other shells should not exit on SIGINT or SIGQUIT." 6 | set -T 7 | trap : 3 8 | trap 'echo SIGINT ; exit 1' 2 9 | ./hardguy 10 | -------------------------------------------------------------------------------- /tests/not.ion: -------------------------------------------------------------------------------- 1 | not true || echo not true 2 | ! true || echo not true 3 | not false && echo not false 4 | ! false && echo not false 5 | true && not false && echo true and not false 6 | true && ! false && echo true and not false 7 | not test -z "a" && echo not test 8 | ! test -z "a" && echo not test 9 | not echo hello 10 | echo $? 11 | -------------------------------------------------------------------------------- /sh-interrupt/test09.sh: -------------------------------------------------------------------------------- 1 | #!./testshell 2 | 3 | trap 'echo I am a trap' 2 4 | 5 | echo 'Try to break wait using SIGINT before wc completes' 6 | echo 'After you break wait, it should print "I am a trap"' 7 | echo 'and then "Going on"' 8 | echo 'wc &' 9 | wc /dev/zero & 10 | p=$! 11 | echo wait 12 | wait 13 | echo "Going on" 14 | kill $p 15 | -------------------------------------------------------------------------------- /tests/script_exec/array_methods.ion: -------------------------------------------------------------------------------- 1 | echo @split("onetwoone" "two") 2 | echo @split_at("onetwoone" "3") 3 | echo @graphemes("onetwo" "3") 4 | echo @bytes("onetwo") 5 | echo @chars("onetwo") 6 | echo @lines($unescape("firstline\nsecondline")) 7 | echo @reverse([1 2 3]) 8 | echo @reverse(["a"]) 9 | let foo = [1 2 3] 10 | echo @reverse(@foo) 11 | -------------------------------------------------------------------------------- /sh-interrupt/test20b.sh: -------------------------------------------------------------------------------- 1 | #! ./testshell 2 | 3 | traph () 4 | { 5 | trap 2 6 | kill -2 $$ 7 | echo 'Error, survived!' 8 | sleep 1 9 | echo 'Survived even longer.' 10 | } 11 | trap traph 2 12 | 13 | #echo called is pid $$ 14 | echo 'You should be able to kill this script with just one SIGINT' 15 | cat 16 | cat 17 | cat 18 | cat 19 | cat 20 | 21 | -------------------------------------------------------------------------------- /.gitlab/merge_request_templates/feat.md: -------------------------------------------------------------------------------- 1 | feat: description 2 | 3 | closes issue: #featurediscussion 4 | 5 | test: unit and integration 6 | 7 | unit test? 8 | 9 | integration test in (default in `tests`, otherwise explain concise) 10 | 11 | refactor: affects other tests or data structures? 12 | 13 | docs: documented? 14 | 15 | perf: performance impact? 16 | -------------------------------------------------------------------------------- /sh-interrupt/makestuff/test01.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | export TESTMAKE 4 | if [ ! "$TESTMAKE" ] ; then 5 | TESTMAKE=make 6 | fi 7 | 8 | echo 'You should be able to kill this script with just one SIGINT' 9 | echo 'Problematic shell/make - Kombinations will enter make a second time' 10 | set -x 11 | $TESTMAKE -s -f Makefile1 12 | $TESTMAKE -s -f Makefile1 13 | -------------------------------------------------------------------------------- /sh-interrupt/test01.ion: -------------------------------------------------------------------------------- 1 | #!./testshell 2 | 3 | echo 'Test 1: See whether child can work on SIGINT and SIGQUIT without' 4 | echo ' terminating the shell around it. See if the shell is' 5 | echo ' interruptable afterwards' 6 | 7 | source ./lib.ion 8 | 9 | docatcher 10 | echo 'Now try to exit shell loop with C-c, C-\ or SIGTERM' 11 | endless 12 | -------------------------------------------------------------------------------- /sh-interrupt/test04.sh: -------------------------------------------------------------------------------- 1 | #!./testshell 2 | 3 | echo 'Test 4: Three shells all loop. You should be able to terminate this' 4 | echo ' script with just one SIGINT or SIGQUIT' 5 | 6 | if [ $ZSH_VERSION ] ; then 7 | source lib.sh 8 | else 9 | . ./lib.sh 10 | fi 11 | 12 | ( 13 | ( 14 | endless 15 | ) 16 | endless 17 | ) 18 | endless 19 | -------------------------------------------------------------------------------- /tests/fn.ion: -------------------------------------------------------------------------------- 1 | fn first_test a b c 2 | echo $a 3 | echo $b 4 | echo $c 5 | end 6 | 7 | first_test hello world goodbye 8 | 9 | fn another_test 10 | for i in 1..10 11 | echo $i 12 | end 13 | end 14 | 15 | another_test 16 | 17 | fn square n:int 18 | math $n \* $n 19 | end 20 | 21 | for num in 1 2 3 4 5 a 1.5 22 | square $num 23 | end 24 | -------------------------------------------------------------------------------- /tests/for.out: -------------------------------------------------------------------------------- 1 | 1 2 | 2 3 | 3 4 | 4 5 | 5 6 | 1 7 | 2 8 | 3 9 | 4 10 | 5 11 | 1 12 | 2 13 | 3 14 | 4 15 | 5 16 | 1 17 | 2 18 | 3 19 | 4 20 | 5 21 | 1 22 | 2 23 | 3 24 | 4 25 | 5 26 | 1A1 27 | 1A2 28 | 1B1 29 | 1B2 30 | 1, 2, 3 31 | 4, 5, 6 32 | 7, 8, 9 33 | 10, , 34 | 1 35 | 2 36 | 3 37 | 4 38 | 5 39 | ; one 40 | two 41 | three 42 | a b c 43 | d e f 44 | g 45 | h i 46 | -------------------------------------------------------------------------------- /sh-interrupt/test02.sh: -------------------------------------------------------------------------------- 1 | #!./testshell 2 | 3 | echo 'Test 2: You should not be able to exit `cat` with SIGINT.' 4 | echo ' SIGQUIT should abort `cat` (with coredump) while' 5 | echo ' the shell should continue and call `cat` again.' 6 | echo ' SIGTERM should exit the whole script.' 7 | 8 | set -x 9 | trap '' 2 10 | while : ; do cat ; echo -n $? ; done 11 | -------------------------------------------------------------------------------- /sh-interrupt/test23b.sh: -------------------------------------------------------------------------------- 1 | #! ./testshell 2 | 3 | 4 | 5 | trap 'echo trap ; trap 2 ; kill -2 $$' 2 6 | 7 | #echo called is pid $$ 8 | echo 'test23, variant of 20, differs in that the trap handler is defined' 9 | echo ' without a shell function' 10 | echo 'You should be able to kill this script with just one SIGINT' 11 | cat 12 | cat 13 | cat 14 | cat 15 | cat 16 | 17 | -------------------------------------------------------------------------------- /sh-interrupt/test28.sh: -------------------------------------------------------------------------------- 1 | #! ./testshell 2 | 3 | echo "This script should be breakable by SIGINT if you run a shell with" 4 | echo "asynchrnous traps enabled. Examples: FreeBSD's sh with switch -T" 5 | echo "from April, 1999 or FreeBSD's sh between September 1998 and March" 6 | echo "1999. SIGQUIT should do nothing" 7 | trap : 3 8 | trap 'echo SIGINT ; exit 1' 2 9 | ./hardguy 10 | -------------------------------------------------------------------------------- /tests/strings.ion: -------------------------------------------------------------------------------- 1 | echo hello?world 2 | echo hello*world 3 | echo hello^world 4 | echo hello%world 5 | echo "\$hello" 6 | echo "\\" 7 | echo "\n" 8 | echo -e "one\\\\\ntwo\\\\\nthree" 9 | echo "\"" 10 | echo "<" 11 | echo '<' 12 | echo "<<" 13 | echo '<<' 14 | echo "<<<" 15 | echo '<<<' 16 | echo ">" 17 | echo '>' 18 | echo ">>" 19 | echo '>>' 20 | echo ">>>" 21 | echo '>>>' 22 | -------------------------------------------------------------------------------- /tests/variable_exp.out: -------------------------------------------------------------------------------- 1 | # ANCHOR: string_variables 2 | example string 3 | example string:example string 4 | # ANCHOR_END: string_variables 5 | # ANCHOR: array_variables 6 | one two three 7 | # ANCHOR_END: array_variables 8 | # ANCHOR: braced_variables 9 | hello123world 10 | hello 123 world 11 | # ANCHOR_END: braced_variables 12 | # ANCHOR: aliases 13 | # ANCHOR_END: aliases 14 | -------------------------------------------------------------------------------- /sh-interrupt/test04.ion: -------------------------------------------------------------------------------- 1 | #!./testshell 2 | 3 | echo 'Test 4: Three shells all loop. You should be able to terminate this' 4 | echo ' script with just one SIGINT or SIGQUIT' 5 | 6 | source lib.ion 7 | 8 | ../target/debug/ion -c ' 9 | source lib.ion 10 | ../target/debug/ion -c " 11 | source lib.ion 12 | endless 13 | " 14 | endless 15 | ' 16 | endless 17 | -------------------------------------------------------------------------------- /.gitlab/merge_request_templates/BREAKING_CHANGE.md: -------------------------------------------------------------------------------- 1 | feat: description 2 | 3 | closes issue: #RFC 4 | 5 | closes issue: #bug 6 | 7 | BREAKING CHANGE: (where how and what breaks) 8 | 9 | test: unit? 10 | 11 | test: integration in (default in `tests`, otherwise explain concise) 12 | 13 | refactor: affects other tests or data structures? 14 | 15 | docs: documented? 16 | 17 | perf: performance impact? 18 | -------------------------------------------------------------------------------- /tests/subst.ion: -------------------------------------------------------------------------------- 1 | let array = [] 2 | 3 | # Inline array in the method 4 | echo @subst(@array [foo bar]) 5 | 6 | # single value 7 | echo @subst(@array [baz]) 8 | 9 | # variable expansion 10 | let default = [foobar] 11 | echo @subst(@array @default) 12 | 13 | # method would not trigger 14 | let array ++= faz 15 | echo @subst(@array @default) 16 | 17 | for number in @subst([] [2 3]) 18 | echo $number 19 | end 20 | -------------------------------------------------------------------------------- /sh-interrupt/makestuff/test02.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | export TESTMAKE 4 | if [ ! "$TESTMAKE" ] ; then 5 | TESTMAKE=make 6 | fi 7 | 8 | echo 'You should be able to kill this script with just one SIGINT' 9 | echo 'Problematic shell/make - Kombinations will enter make a second time' 10 | echo "On SIGINT, make should print 'Interrupt target runs'" 11 | set -x 12 | $TESTMAKE -s -f Makefile1a 13 | $TESTMAKE -s -f Makefile1a 14 | -------------------------------------------------------------------------------- /members/ranges/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Michael Murphy "] 3 | categories = ["development-tools"] 4 | description = "extra traits to parse the stdlib's range" 5 | edition = "2018" 6 | keywords = ["shell", "range"] 7 | license = "MIT" 8 | name = "ion-ranges" 9 | readme = "README.md" 10 | repository = "https://gitlab.redox-os.org/redox-os/ion" 11 | version = "0.1.0" 12 | 13 | [dependencies] 14 | -------------------------------------------------------------------------------- /sh-interrupt/test01.sh: -------------------------------------------------------------------------------- 1 | #!./testshell 2 | 3 | echo 'Test 1: See whether child can work on SIGINT and SIGQUIT without' 4 | echo ' terminating the shell around it. See if the shell is' 5 | echo ' interruptable afterwards' 6 | 7 | if [ $ZSH_VERSION ] ; then 8 | source lib.sh 9 | else 10 | . ./lib.sh 11 | fi 12 | 13 | docatcher 14 | echo 'Now try to exit shell loop with C-c, C-\ or SIGTERM' 15 | endless 16 | -------------------------------------------------------------------------------- /sh-interrupt/test08.ion: -------------------------------------------------------------------------------- 1 | #!./testshell 2 | 3 | echo 'Test 8: Start a child that is to be interrupted by SIGINT.' 4 | echo ' The shell should not continue with the script' 5 | 6 | source lib.ion 7 | 8 | echo 9 | echo 'Should exit immedeatly after you break cat by SIGINT' 10 | echo cat 11 | cat 12 | sleep 1 13 | echo 'If you see this, you have a problem' 14 | sleep 1 15 | echo 'If you see this, you have even more problems' 16 | -------------------------------------------------------------------------------- /src/lib/shell/pipe_exec/README.md: -------------------------------------------------------------------------------- 1 | # Pipeline Execution Module 2 | 3 | The purpose of the pipeline execution module is to create commands from supplied pieplines, and 4 | manage their execution thereof. That includes forking, executing commands, managing process group 5 | IDs, watching foreground and background tasks, sending foreground tasks to the background, 6 | handling pipeline and conditional operators, and std{in,out,err} redirections. 7 | -------------------------------------------------------------------------------- /tests/string_vars.out: -------------------------------------------------------------------------------- 1 | # ANCHOR: string_variables 2 | t1 t2 3 | # ANCHOR_END: string_variables 4 | # ANCHOR: string_slicing 5 | Hello 6 | World 7 | llo, Wo 8 | # ANCHOR_END: string_slicing 9 | # ANCHOR: string_concatenation 10 | world 11 | world! 12 | Hello, world! 13 | # ANCHOR_END: string_concatenation 14 | # ANCHOR: string_operations 15 | Doctor 16 | order 17 | Doctor's 18 | orders 19 | Doctor's orders 20 | # ANCHOR_END: string_operations 21 | -------------------------------------------------------------------------------- /tests/variables.out: -------------------------------------------------------------------------------- 1 | hello leading underscores ! 2 | 3 | 4 | hello 5 | variables: 6 | hello string 7 | hello array 8 | multiple_assignment: 9 | one 10 | two 11 | one 12 | two three four 13 | type_checked_assignment: 14 | true true false 15 | ion: assignment error: fail: expected bool 16 | one 17 | two three 18 | 4 19 | 5.1 6.2 7.3 20 | dropping_variables: 21 | command_local_variables: 22 | test_var_value 23 | 2025-10-01 05:00:00 24 | three two one 25 | -------------------------------------------------------------------------------- /tests/while.out: -------------------------------------------------------------------------------- 1 | 1 2 | 2 3 | 3 4 | 4 5 | 5 6 | 6 7 | 7 8 | 8 9 | 9 10 | 1 11 | 2 12 | 3 13 | 4 14 | found 5 15 | 6 16 | 7 17 | 8 18 | 9 19 | outer loop #1 20 | inner loop #1 21 | inner loop #2 22 | inner loop #3 23 | outer loop #2 24 | inner loop #1 25 | inner loop #2 26 | inner loop #3 27 | outer loop #3 28 | inner loop #1 29 | inner loop #2 30 | inner loop #3 31 | 1 1 32 | 1 2 33 | 2 3 34 | 3 5 35 | 5 8 36 | 8 13 37 | -------------------------------------------------------------------------------- /manual/src/control/00-flow.md: -------------------------------------------------------------------------------- 1 | # Flow Control 2 | 3 | As Ion features an imperative paradigm, the order that statements are evaluated and executed is 4 | determined by various control flow keywords, such as `if`, `while`, `for`, `break`, and 5 | `continue`. Ion's control flow logic is very similar to POSIX shells, but there are a few major 6 | differences, such as that all blocks are ended with the `end` keyword; and the `do`/`then` 7 | keywords aren't necessary. 8 | -------------------------------------------------------------------------------- /bash/install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | RELEASE='target/release/ion' 4 | 5 | if [[ ! -f "$RELEASE" ]]; then 6 | echo "$RELEASE does not exit. Please run (cargo build --release) before" 7 | exit 1 8 | fi 9 | 10 | if [[ ! -d "${DESTDIR}" ]]; then 11 | echo "Target folder ${DESTDIR} where the ion shell executable is to be installed, does not exits. Please ensure the folder exits" 12 | exit 1 13 | fi 14 | 15 | install -Dm0755 "$RELEASE" "${DESTDIR}/ion" 16 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: ion-shell 2 | Section: admin 3 | Priority: optional 4 | Maintainer: Michael Aaron Murphy 5 | Build-Depends: debhelper (>=9), cargo 6 | Standards-Version: 4.1.4 7 | Homepage: https://gitlab.redox-os.org/redox-os/ion 8 | 9 | Package: ion-shell 10 | Architecture: any 11 | Depends: ${shlibs:Depends} 12 | Description: A next generation system shell, written for Redox in Rust. 13 | A next generation system shell, written for Redox in Rust. 14 | -------------------------------------------------------------------------------- /tests/match.out: -------------------------------------------------------------------------------- 1 | Odd! 2 | Even! 3 | Odd! 4 | Even! 5 | Odd! 6 | Even! 7 | Use tar -xzf 8 | Unknown file type 9 | Use tar -xzf 10 | Unknown file type 11 | WILDCARD! 12 | WILDCARD! 13 | Almost half full (or half empty) 14 | Getting close to full :O 15 | Time for spring cleaning, almost full! 16 | How did you even do this... 17 | Static :( 18 | Static :( 19 | Animated :D 20 | 5 is between 0 and 10 21 | 0 at minimum 22 | 10 at maximum 23 | 0 is less than min=1 24 | 10 is more than max=9 25 | -------------------------------------------------------------------------------- /.gitlab/issue_templates/rfc.md: -------------------------------------------------------------------------------- 1 | feat: (description with motivation) 2 | 3 | BREAKING CHANGE: (effect on current programs or datastructures) 4 | 5 | perf: impact 6 | 7 | performance 8 | 9 | usability 10 | 11 | maintainability 12 | 13 | code: input 14 | ``` 15 | shell code 16 | ``` 17 | 18 | expect: output 19 | ``` 20 | result 21 | ``` 22 | 23 | reason: (for what use case is this important) 24 | 25 | context: (links, text and further literature) 26 | 27 | behavior of bash/dash/zsh/fish/oil 28 | -------------------------------------------------------------------------------- /members/scopes-rs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "scopes" 3 | version = "0.1.0" 4 | authors = ["Michael Murphy ", "AdminXVII "] 5 | edition = "2018" 6 | description = "A type for dynamically typed variables." 7 | license = "MIT" 8 | repository = "https://gitlab.redox-os.org/redox-os/ion" 9 | keywords = ["types", "variables", "dynamic typing", "json"] 10 | readme = "README.md" 11 | categories = ["parsing"] 12 | 13 | [dependencies] 14 | lexical = "5.2" 15 | small = { git = "https://gitlab.redox-os.org/redox-os/small", features = ["std"] } 16 | itertools = "0.9" 17 | -------------------------------------------------------------------------------- /sh-interrupt/test03.sh: -------------------------------------------------------------------------------- 1 | #!./testshell 2 | 3 | echo 'Test 3: A background job is being started, then the shell loops.' 4 | echo ' You should be able to break the shell loop with SIGINT.' 5 | echo ' This goes wrong if the shell blocks signals when' 6 | echo ' starting any child. It should do so only for foreground' 7 | echo ' jobs.' 8 | echo ' Make sure you type SIGINT before wc completes' 9 | 10 | . ./lib.sh 11 | 12 | echo Starting job 13 | tar cvf - --xz ../target/debug/ion | tar xvfJ - | wc & 14 | echo 'Now try to break this loop' 15 | endless 16 | -------------------------------------------------------------------------------- /tests/or.ion: -------------------------------------------------------------------------------- 1 | false 2 | or true && echo "false or true" 3 | 4 | true 5 | or true && echo "true or true" 6 | 7 | false 8 | or false && echo "false or false" 9 | 10 | if or true 11 | echo "if: or true" 12 | else 13 | echo "else: or true" 14 | end 15 | 16 | echo '# ANCHOR: or_demonstration' 17 | test "42" = "4" 18 | or test 4 == 4 && echo "Will be printed: At least one of the two commands returns successfully" 19 | test "42" = "4" 20 | or test 4 = 2 && echo "Will not be printed: None of the two commands returns successfully" 21 | echo '# ANCHOR_END: or_demonstration' 22 | -------------------------------------------------------------------------------- /sh-interrupt/test03.ion: -------------------------------------------------------------------------------- 1 | #!./testshell 2 | 3 | echo 'Test 3: A background job is being started, then the shell loops.' 4 | echo ' You should be able to break the shell loop with SIGINT.' 5 | echo ' This goes wrong if the shell blocks signals when' 6 | echo ' starting any child. It should do so only for foreground' 7 | echo ' jobs.' 8 | echo ' Make sure you type SIGINT before wc completes' 9 | 10 | source lib.ion 11 | 12 | echo Starting job 13 | tar cvf - --xz ../target/debug/ion | tar xvfJ - | wc & 14 | echo 'Now try to break this loop' 15 | endless 16 | -------------------------------------------------------------------------------- /tests/and.ion: -------------------------------------------------------------------------------- 1 | true 2 | and true && echo "true and true" 3 | 4 | false 5 | and true && echo "false and true" 6 | 7 | let answer = 42 8 | 9 | if or test $answer == 42 10 | echo "Answer in life is $answer" 11 | else 12 | echo "Not expected output here" 13 | end 14 | 15 | echo "# ANCHOR: and_demonstration" 16 | test "42" == "42" 17 | and test 4 == 4 && echo "Will be printed: Both commands returned successfully" 18 | test "42" = "4" 19 | and test 4 == 4 && echo "Will not be printed: One of the two command did not return successfully" 20 | echo "# ANCHOR_END: and_demonstration" 21 | -------------------------------------------------------------------------------- /tests/advanced/rxtx-stats.ion: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ion 2 | 3 | # This script will print the current RX and TX statistics of each 4 | # network interface on Linux, minus the loopback interface. 5 | 6 | for interface in /sys/class/net/* 7 | let name = @split(interface, '/')[4] 8 | 9 | if test $name = "lo" 10 | continue 11 | end 12 | 13 | let tx = $(cat $interface/statistics/tx_bytes) 14 | let rx = $(cat $interface/statistics/rx_bytes) 15 | let tx /= 1048576 16 | let rx /= 1048576 17 | 18 | echo "$name 19 | RX: $rx MiB 20 | TX: $tx MiB 21 | " 22 | end 23 | -------------------------------------------------------------------------------- /tests/alias.ion: -------------------------------------------------------------------------------- 1 | alias piped = "sed s/A/B/ | sed s/B/C/" 2 | alias logical = "echo LOGICAL1 && echo B" 3 | alias piped_and_logical = "sed s/A/B/ | sed s/B/PIPED_AND_LOGICAL1/ && echo B" 4 | 5 | # Alias contains only pipes 6 | echo A | piped | sed s/C/PIPED/ 7 | 8 | # Alias containes logical operator 9 | logical | sed s/B/LOGICAL2/ 10 | 11 | # Alias breaks pipeline into half by logical operator 12 | echo A | piped_and_logical | sed s/B/PIPED_AND_LOGICAL2/ 13 | 14 | # Expanding multiple aliases in a singel pipeline 15 | echo A | piped | sed s/C/PIPED_OK/ && logical | piped_and_logical | sed s/B/DONE/ 16 | -------------------------------------------------------------------------------- /tests/map_vars.out: -------------------------------------------------------------------------------- 1 | # ANCHOR: testing_maps 2 | one 3 | two 4 | three 5 | 1 6 | 3 7 | 2 8 | 1 2 3 4 5 9 | 6 7 8 10 | key1 one key3 three 11 | key1 one key2 two key3 three 12 | one 13 | two 14 | three 15 | ichi 1.0 ni 2.0 san 3.0 16 | ichi foo ni 2.0 san 3.0 17 | ichi foo ni bar san 3.0 18 | # ANCHOR_END: testing_maps 19 | # ANCHOR: hashmap 20 | pc27 pc2 21 | # ANCHOR_END: hashmap 22 | # ANCHOR: btreemap 23 | red green 24 | orange pc15 pc2 pc27 25 | pc22 green red blue 26 | orange pc22 pc15 green pc2 red pc27 blue 27 | orange: pc22 28 | pc15: green 29 | pc2: red 30 | pc27: blue 31 | # ANCHOR_END: btreemap 32 | -------------------------------------------------------------------------------- /tests/inner_expansions.ion: -------------------------------------------------------------------------------- 1 | echo $(let echo_array=[@(echo me myself I)]; echo @echo_array[0] @echo_array[1] @echo_array[2]) 2 | echo $(let echo_array=[@(echo me myself I)]; echo @echo_array[0] @echo_array[2] @echo_array[1]) 3 | echo $(let echo_array=[@(echo me myself I)]; echo @echo_array[1] @echo_array[2] @echo_array[0]) 4 | echo $(let echo_array=[@(echo me myself I)]; echo @echo_array[1] @echo_array[0] @echo_array[2]) 5 | echo $(let echo_array=[@(echo me myself I)]; echo @echo_array[2] @echo_array[1] @echo_array[0]) 6 | echo $(let echo_array=[@(echo me myself I)]; echo @echo_array[2] @echo_array[0] @echo_array[1]) 7 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | export RUSTUP = 0 4 | 5 | # When building packages locally, `env VENDORED=0 CLEAN=0 debuild` 6 | # will build a package using non-vendored dependencies, and 7 | # re-using build artifacts from the last build. Use this in 8 | # local testing. 9 | 10 | export VENDORED ?= 1 11 | CLEAN ?= 1 12 | 13 | %: 14 | dh $@ 15 | 16 | override_dh_auto_clean: 17 | ifeq ($(CLEAN),1) 18 | make clean 19 | endif 20 | make version 21 | ifeq ($(VENDORED),1) 22 | if ! ischroot; then \ 23 | make vendor; \ 24 | fi 25 | endif 26 | 27 | override_dh_auto_install: 28 | dh_auto_install -- prefix=/usr 29 | -------------------------------------------------------------------------------- /manual/src/expansions/08-redox-os.md: -------------------------------------------------------------------------------- 1 | ## Redox OS 2 | 3 | ### File scheme 4 | 5 | you can also specify absolute paths via the file scheme. 6 | 7 | The file scheme has the prefix "file:". 8 | A path staring with the prefix "file:" is same as giving an absolute path 9 | which starts with "/" on a Linux system for example. 10 | 11 | The following command on Redox Os 12 | 13 | ```sh 14 | ls file:home/user/*.txt 15 | ``` 16 | is same as 17 | ```sh 18 | ls /home/user/*.txt 19 | ``` 20 | 21 | Leading "/" will be ignored after the prefix "file:". 22 | A path like "file:/something" is the same as "file:something" for ion. 23 | 24 | -------------------------------------------------------------------------------- /sh-interrupt/test05.sh: -------------------------------------------------------------------------------- 1 | #!./testshell 2 | 3 | echo 'Test 5: The SIGINT/SIGQUIT-catching program is being run' 4 | echo ' A: The shell should not exit on signals while this program runs.' 5 | echo ' B: After you exited it via C-d, you should be able to end this' 6 | echo ' script with its 3 subhells with just one signal' 7 | echo ' script with just one SIGINT or SIGQUIT' 8 | 9 | if [ $ZSH_VERSION ] ; then 10 | source lib.sh 11 | else 12 | . ./lib.sh 13 | fi 14 | 15 | ( 16 | ( 17 | docatcher 18 | echo "Now try to exit with one SIGINT" 19 | endless 20 | ) 21 | endless 22 | ) 23 | endless 24 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | ## Changing tests 2 | 3 | This tests are also used to generate the manual. 4 | Please make sure to update the manual, if you change tests. 5 | 6 | ## Examples 7 | 8 | The files in this directory are simple example scripts that are used to test 9 | the state of the shell as it is developed. When the **run_examples.sh** script 10 | is executed, it will build Ion and execute each of the ion scripts here, and 11 | compare their outputs to their assoicated **out** files. 12 | 13 | ``` 14 | TOOLCHAIN=stable ./run_examples.sh 15 | ``` 16 | 17 | For more elaborate examples of Ion usage, check out the **advanced** directory. 18 | -------------------------------------------------------------------------------- /manual/src/scripts/01-sourcing_another_file.md: -------------------------------------------------------------------------------- 1 | # Sourcing another file 2 | 3 | A ion shell script can also execute another ion shell script and inherit its environment and 4 | variables while doing so. 5 | 6 | ```sh 7 | source to_source.ion 8 | ``` 9 | 10 | When sourcing another Ion shell file you can also supply it with positional arguments. 11 | 12 | ```sh 13 | # to_source.ion will now have "first" as the first positional and "second" second positional argument 14 | # Remeber the zeroth postional argument is file name of the executed script. 15 | # Here "to_source.ion" in this example. 16 | source to_source.ion "first" "second" 17 | ``` 18 | -------------------------------------------------------------------------------- /tests/glob.out: -------------------------------------------------------------------------------- 1 | Cargo.toml 2 | tests/brace_exp.ion tests/brace_exp.out tests/braces.ion tests/braces.out tests/break.ion tests/break.out 3 | Cargo.toml 4 | Cargo.toml 5 | Cargo.lock Cargo.toml 6 | Cargo.toml 7 | Cargo.toml 8 | tests/else_if.ion tests/empty_loop_test.ion tests/exists.ion tests/fail.ion tests/fibonacci.ion tests/fn-root-vars.ion tests/fn.ion tests/for.ion tests/function_piping.ion 9 | [] 10 | [] [] 11 | one three two 12 | three two 13 | three two 14 | Cargo.lock Cargo.toml clippy.toml rustfmt.toml 15 | LICENSE README.md 16 | Cargo.lock Cargo.toml clippy.toml rustfmt.toml 17 | LICENSE 18 | Cargo.toml 19 | Cargo.toml 20 | Cargo.toml 21 | -------------------------------------------------------------------------------- /sh-interrupt/test07.ion: -------------------------------------------------------------------------------- 1 | #!./testshell 2 | 3 | echo 'Test 7: See whether child can work on SIGINT and SIGQUIT without' 4 | echo ' terminating the shell around it. See if the shell' 5 | echo ' continues the script after the child exits. Do not' 6 | echo ' send SIGINT after the child exits.' 7 | 8 | source lib.ion 9 | 10 | echo 11 | echo 'After the catching program, you should see 4 lines of text, sent' 12 | echo 'with one second delay each' 13 | docatcher 14 | echo 'You should see 3 more lines' 15 | sleep 1 16 | echo 'You should see 2 more lines' 17 | sleep 1 18 | echo 'You should see 1 more line' 19 | sleep 1 20 | echo 'Done' 21 | -------------------------------------------------------------------------------- /sh-interrupt/test05.ion: -------------------------------------------------------------------------------- 1 | #!./testshell 2 | 3 | echo 'Test 5: The SIGINT/SIGQUIT-catching program is being run' 4 | echo ' A: The shell should not exit on signals while this program runs.' 5 | echo ' B: After you exited it via C-d, you should be able to end this' 6 | echo ' script with its 3 subhells with just one signal' 7 | echo ' script with just one SIGINT or SIGQUIT' 8 | 9 | source lib.ion 10 | 11 | ../target/debug/ion -c ' 12 | source lib.ion 13 | ../target/debug/ion -c " 14 | source lib.ion 15 | docatcher 16 | echo "Now try to exit with one SIGINT" 17 | endless 18 | " 19 | endless 20 | ' 21 | endless 22 | -------------------------------------------------------------------------------- /tests/variable_exp.ion: -------------------------------------------------------------------------------- 1 | echo '# ANCHOR: string_variables' 2 | let string = "example string" 3 | echo $string 4 | echo $string:$string 5 | echo '# ANCHOR_END: string_variables' 6 | echo '# ANCHOR: array_variables' 7 | let array = [one two three] 8 | echo @array 9 | echo '# ANCHOR_END: array_variables' 10 | echo '# ANCHOR: braced_variables' 11 | let hello = "hello123" 12 | echo ${hello}world 13 | let hello = [hello 123 ' '] 14 | echo @{hello}world 15 | echo '# ANCHOR_END: braced_variables' 16 | echo '# ANCHOR: aliases' 17 | alias ls = "ls --color" 18 | #echo $ls #ion: expansion error: Variable "ls" does not exist 19 | #aliase are stored separately 20 | echo '# ANCHOR_END: aliases' 21 | -------------------------------------------------------------------------------- /tests/arithmetic_vars.ion: -------------------------------------------------------------------------------- 1 | echo '# ANCHOR: individual_assignments' 2 | let value = 5 3 | echo $value 4 | let value += 5 5 | echo $value 6 | let value -= 2 7 | echo $value 8 | let value *= 2 9 | echo $value 10 | let value //= 2 11 | echo $value 12 | let value **= 2 13 | echo $value 14 | let value /= 2 15 | echo $value 16 | echo '# ANCHOR_END: individual_assignments' 17 | echo '# ANCHOR: multiple_assignments' 18 | let a b = 5 5 19 | echo $a $b 20 | let a b += 5 5 21 | echo $a $b 22 | let a b -= 2 2 23 | echo $a $b 24 | let a b *= 2 2 25 | echo $a $b 26 | let a b //= 2 2 27 | echo $a $b 28 | let a b **= 2 2 29 | echo $a $b 30 | let a b /= 2 2 31 | echo $a $b 32 | echo '# ANCHOR_END: multiple_assignments' 33 | -------------------------------------------------------------------------------- /members/builtins-proc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Xavier L'Heureux "] 3 | categories = ["development-tools::procedural-macro-helpers"] 4 | description = "Expose author list of Ion Shell as macro" 5 | edition = "2018" 6 | keywords = ["ion", "shell", "builtin"] 7 | license = "MIT" 8 | name = "builtins-proc" 9 | readme = "README.md" 10 | repository = "https://gitlab.redox-os.org/redox-os/ion" 11 | version = "0.1.0" 12 | 13 | [dependencies] 14 | quote = "1.0" 15 | syn = { version = "1.0", features = ["full"] } 16 | darling = "0.10" 17 | 18 | [dev-dependencies] 19 | ion-shell = { path = "../.." } 20 | 21 | [lib] 22 | proc-macro = true 23 | 24 | [features] 25 | man = [] 26 | -------------------------------------------------------------------------------- /sh-interrupt/test07.sh: -------------------------------------------------------------------------------- 1 | #!./testshell 2 | 3 | echo 'Test 7: See whether child can work on SIGINT and SIGQUIT without' 4 | echo ' terminating the shell around it. See if the shell' 5 | echo ' continues the script after the child exits. Do not' 6 | echo ' send SIGINT after the child exits.' 7 | 8 | if [ $ZSH_VERSION ] ; then 9 | source lib.sh 10 | else 11 | . ./lib.sh 12 | fi 13 | 14 | echo 15 | echo 'After the catching program, you should see 4 lines of text, sent' 16 | echo 'with one second delay each' 17 | docatcher 18 | echo 'You should see 3 more lines' 19 | sleep 1 20 | echo 'You should see 2 more lines' 21 | sleep 1 22 | echo 'You should see 1 more line' 23 | sleep 1 24 | echo 'Done' 25 | -------------------------------------------------------------------------------- /tests/nested_for.out: -------------------------------------------------------------------------------- 1 | 01 2 | 02 3 | 03 4 | 04 5 | 05 6 | 06 7 | 07 8 | 08 9 | 09 10 | 11 11 | 12 12 | 13 13 | 14 14 | 15 15 | 16 16 | 17 17 | 18 18 | 19 19 | 21 20 | 22 21 | 23 22 | 24 23 | 25 24 | 26 25 | 27 26 | 28 27 | 29 28 | 31 29 | 32 30 | 33 31 | 34 32 | 35 33 | 36 34 | 37 35 | 38 36 | 39 37 | 41 38 | 42 39 | 43 40 | 44 41 | 45 42 | 46 43 | 47 44 | 48 45 | 49 46 | 51 47 | 52 48 | 53 49 | 54 50 | 55 51 | 56 52 | 57 53 | 58 54 | 59 55 | 61 56 | 62 57 | 63 58 | 64 59 | 65 60 | 66 61 | 67 62 | 68 63 | 69 64 | 71 65 | 72 66 | 73 67 | 74 68 | 75 69 | 76 70 | 77 71 | 78 72 | 79 73 | 81 74 | 82 75 | 83 76 | 84 77 | 85 78 | 86 79 | 87 80 | 88 81 | 89 82 | 91 83 | 92 84 | 93 85 | 94 86 | 95 87 | 96 88 | 97 89 | 98 90 | 99 91 | -------------------------------------------------------------------------------- /tests/script_exec/braces.ion: -------------------------------------------------------------------------------- 1 | # nested braces 2 | echo 1{A{1,2},B{1,2}} 3 | 4 | # permutating braces 5 | echo {0,1}abc{2,3,4}def{5,6,7}{g,h,i} 6 | 7 | # inclusive ranges 8 | echo {-1...1} 9 | echo {2...0} 10 | echo {a...c} 11 | echo {d...b} 12 | echo {A...C} 13 | echo {D...B} 14 | 15 | # exclusive ranges 16 | echo {-1..2} 17 | echo {2..-1} 18 | echo {a..d} 19 | echo {d..a} 20 | echo {A..D} 21 | echo {D..A} 22 | 23 | # inclusive stepped ranges 24 | echo {0..2...4} 25 | echo {a..2...e} 26 | echo {A..2...E} 27 | echo {0..-2...-4} 28 | echo {e..-2...a} 29 | echo {E..-2...A} 30 | 31 | # exclusive stepped ranges 32 | echo {0..2..5} 33 | echo {a..2..f} 34 | echo {A..2..F} 35 | echo {0..-2..-5} 36 | echo {e..-2..a} 37 | echo {E..-2..A} -------------------------------------------------------------------------------- /tests/braces.ion: -------------------------------------------------------------------------------- 1 | # nested braces 2 | echo 1{A{1,2},B{1,2}} 3 | 4 | # permutating braces 5 | echo {0,1}abc{2,3,4}def{5,6,7}{g,h,i} 6 | echo It{{em,alic}iz,erat}e{d,} 7 | 8 | # inclusive ranges 9 | echo {-1...1} 10 | echo {2..=0} 11 | echo {a...c} 12 | echo {d..=b} 13 | echo {A..=C} 14 | echo {D...B} 15 | 16 | # exclusive ranges 17 | echo {-1..2} 18 | echo {2..-1} 19 | echo {a..d} 20 | echo {d..a} 21 | echo {A..D} 22 | echo {D..A} 23 | 24 | # inclusive stepped ranges 25 | echo {0..2...4} 26 | echo {a..2...e} 27 | echo {A..2...E} 28 | echo {0..-2...-4} 29 | echo {e..-2...a} 30 | echo {E..-2...A} 31 | 32 | # exclusive stepped ranges 33 | echo {0..2..5} 34 | echo {a..2..f} 35 | echo {A..2..F} 36 | echo {0..-2..-5} 37 | echo {e..-2..a} 38 | echo {E..-2..A} 39 | -------------------------------------------------------------------------------- /tests/array_methods.out: -------------------------------------------------------------------------------- 1 | # ANCHOR: lines 2 | firstline secondline 3 | third 4 | fourth 5 | fifth 6 | # ANCHOR_END: lines 7 | # ANCHOR: split 8 | one one 9 | person 10 | age 11 | some data 12 | person 13 | age 14 | data 15 | # ANCHOR_END: split 16 | # ANCHOR: split_at 17 | one twoone 18 | FOO BAR 19 | # ANCHOR_END: split_at 20 | # ANCHOR: bytes 21 | 111 110 101 116 119 111 22 | 97 98 99 23 | # ANCHOR_END: bytes 24 | # ANCHOR: chars 25 | o n e t w o 26 | f 27 | o 28 | o 29 | b 30 | a 31 | r 32 | # ANCHOR_END: chars 33 | # ANCHOR: graphemes 34 | o n e t w o 35 | f 36 | o 37 | o 38 | b 39 | a 40 | r 41 | # ANCHOR_END: graphemes 42 | # ANCHOR: reverse 43 | 3 2 1 44 | a 45 | 3 2 1 46 | # ANCHOR_END: reverse 47 | # ANCHOR: subst 48 | 1 49 | 2 50 | 3 51 | # ANCHOR_END: subst 52 | -------------------------------------------------------------------------------- /tests/brace_exp.out: -------------------------------------------------------------------------------- 1 | # ANCHOR: single_brace_expansion 2 | filename.ext1 filename.ext2 3 | # ANCHOR_END: single_brace_expansion 4 | # ANCHOR: multi_brace_expansion 5 | job_01.ext1 job_01.ext2 job_02.ext1 job_02.ext2 6 | # ANCHOR_END: multi_brace_expansion 7 | # ANCHOR: nested_brace_expansion 8 | job_01_out.txt job_01_err.txt job_02_out.txt job_02_err.txt 9 | # ANCHOR_END: nested_brace_expansion 10 | # ANCHOR: range_brace_expansion 11 | 1 2 3 4 5 6 7 8 9 12 | 10 9 8 7 6 5 4 3 2 13 | 1 2 3 4 5 6 7 8 9 10 14 | 10 9 8 7 6 5 4 3 2 1 15 | a b c 16 | d c b 17 | a b c d 18 | d c b a 19 | # ANCHOR_END: range_brace_expansion 20 | # ANCHOR: range_brace_expansion_as_array 21 | 1 22 | 2 23 | 3 24 | 4 25 | 5 26 | 6 27 | 7 28 | 8 29 | 9 30 | # ANCHOR_END: range_brace_expansion_as_array 31 | -------------------------------------------------------------------------------- /src/lib/parser/mod.rs: -------------------------------------------------------------------------------- 1 | //! Take a Read instance and output statements 2 | //! 3 | //! The `Terminator` takes input data and creates string with the good size 4 | //! The `StatementSplitter` than takes the data and produces statements, with the help of 5 | //! `parse_and_validate` 6 | 7 | /// The terminal tokens associated with the parsing process 8 | pub mod lexers; 9 | /// Parse the pipelines to a Pipeline struct 10 | pub mod pipelines; 11 | mod statement; 12 | mod terminator; 13 | 14 | pub use self::{ 15 | statement::{parse_and_validate, Error, StatementSplitter}, 16 | terminator::Terminator, 17 | }; 18 | 19 | #[cfg(fuzzing)] 20 | pub mod fuzzing { 21 | use super::*; 22 | 23 | pub fn statement_parse(data: &str) { statement::parse::parse(data); } 24 | } 25 | -------------------------------------------------------------------------------- /tests/brace_exp.ion: -------------------------------------------------------------------------------- 1 | echo '# ANCHOR: single_brace_expansion' 2 | echo filename.{ext1,ext2} 3 | echo '# ANCHOR_END: single_brace_expansion' 4 | echo '# ANCHOR: multi_brace_expansion' 5 | echo job_{01,02}.{ext1,ext2} 6 | echo '# ANCHOR_END: multi_brace_expansion' 7 | echo '# ANCHOR: nested_brace_expansion' 8 | echo job_{01_{out,err},02_{out,err}}.txt 9 | echo '# ANCHOR_END: nested_brace_expansion' 10 | echo '# ANCHOR: range_brace_expansion' 11 | echo {1..10} 12 | echo {10..1} 13 | echo {1...10} 14 | echo {10...1} 15 | echo {a..d} 16 | echo {d..a} 17 | echo {a...d} 18 | echo {d...a} 19 | echo '# ANCHOR_END: range_brace_expansion' 20 | echo '# ANCHOR: range_brace_expansion_as_array' 21 | for num in {1..10} 22 | echo $num 23 | end 24 | echo '# ANCHOR_END: range_brace_expansion_as_array' 25 | -------------------------------------------------------------------------------- /tests/for.ion: -------------------------------------------------------------------------------- 1 | for i in 1..6 2 | echo $i 3 | end 4 | 5 | for i in 1...5 6 | echo $i 7 | end 8 | 9 | for i in 1 2 3 4 5 10 | echo $i 11 | end 12 | 13 | for i in @(echo 1 2 3 4 5) 14 | echo $i 15 | end 16 | 17 | for i in [1 2 3 4 5] 18 | echo $i 19 | end 20 | 21 | for i in 1{A{1,2},B{1,2}} 22 | echo $i 23 | end 24 | 25 | for x y z in 1..=10 26 | echo $x, $y, $z 27 | end 28 | 29 | let array = [ 1 2 3 4 5 ] 30 | for i in @array 31 | echo $i 32 | end 33 | 34 | let multi_line_input = "one 35 | two 36 | three" 37 | 38 | for word in $multi_line_input 39 | echo "; $word" 40 | end 41 | 42 | mkdir for_test 43 | cd for_test 44 | touch "a b c" 45 | touch "d e f" 46 | touch "g" 47 | touch "h i" 48 | for file in * 49 | echo $file 50 | end 51 | cd .. 52 | rm for_test -R 53 | -------------------------------------------------------------------------------- /tests/else_if.ion: -------------------------------------------------------------------------------- 1 | # Single Else If Condition 2 | 3 | if test 1 -gt 5 4 | echo one 5 | else if test 1 -eq 1 6 | echo two 7 | else 8 | echo three 9 | end 10 | 11 | # Multiple Else Conditions 12 | 13 | let a = 4 14 | if test $a -eq 1 15 | echo one 16 | else if test $a -eq 2 17 | echo two 18 | else if test $a -eq 3 19 | echo three 20 | else if test $a -eq 4 21 | echo four 22 | else 23 | echo five 24 | end 25 | 26 | # Nested Else If Conditions 27 | 28 | let a = 5 29 | let b = 10 30 | 31 | if test $a -gt 10 32 | echo "a > 10" 33 | else if test $a -lt 10 34 | echo "a < 10" 35 | if test $a -gt $b 36 | echo "a > b" 37 | else if test $a -eq $b 38 | echo "a == b" 39 | else 40 | echo "a < b" 41 | end 42 | else 43 | echo "a == 10" 44 | end 45 | -------------------------------------------------------------------------------- /tests/let.ion: -------------------------------------------------------------------------------- 1 | let a = 5 2 | echo $a 3 | 4 | let a b = 10 $a 5 | echo $a 6 | echo $b 7 | 8 | let a b c = 1 2 3 9 | echo $a 10 | echo $b 11 | echo $c 12 | 13 | let a b c = "one two" "three four" "five six" 14 | echo $a 15 | echo $b 16 | echo $c 17 | 18 | let a = 1; 19 | let a += 4 20 | echo $a 21 | let a *= 5 22 | echo $a 23 | let a /= 4 24 | echo $a 25 | 26 | let a[] b c[] = [one two] three [four five] 27 | echo @a 28 | echo $b 29 | echo @c 30 | 31 | let a b c = 6 3 5 32 | echo $a $b $c 33 | let a b c -= 2 2 4 34 | echo $a $b $c 35 | 36 | let x y z = 1 2 3 37 | let x y z = $z $x $y 38 | echo $x $y $z 39 | 40 | let x y z = 1 2 3 41 | let z x y = $y $z $x 42 | echo $x $y $z 43 | 44 | let a = [ 45 | one # a comment 46 | two # another comment 47 | three 48 | four five six 49 | 50 | ] 51 | echo $join(a "\n") 52 | -------------------------------------------------------------------------------- /members/builtins-proc/tests/test.rs: -------------------------------------------------------------------------------- 1 | use ion_shell::{builtins::Status, types, Shell}; 2 | 3 | #[builtins_proc::builtin( 4 | desc = "prints 42 to the screen", 5 | man = " 6 | SYNOPSIS 7 | gimme_the_answer_to_life_to_the_universe_and_to_everything_else [-h | --help] 8 | 9 | DESCRIPTION 10 | Who doesn't want 42 printed to screen? 11 | " 12 | )] 13 | fn gimme_the_answer_to_life_to_the_universe_and_to_everything_else( 14 | args: &[types::Str], 15 | _shell: &mut Shell<'_>, 16 | ) -> Status { 17 | println!("42"); 18 | Status::SUCCESS 19 | } 20 | 21 | #[test] 22 | fn works() { 23 | assert_eq!( 24 | builtin_gimme_the_answer_to_life_to_the_universe_and_to_everything_else( 25 | &[], 26 | &mut Shell::default() 27 | ), 28 | Status::SUCCESS 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /benches/statement.rs: -------------------------------------------------------------------------------- 1 | use criterion::*; 2 | use ion_shell::parser::{StatementSplitter, Terminator}; 3 | use itertools::Itertools; 4 | 5 | const TEXT: &[u8] = include_bytes!("test.ion"); 6 | 7 | fn criterion_benchmark(c: &mut Criterion) { 8 | let stmts = TEXT 9 | .iter() 10 | .copied() 11 | .batching(|lines| Terminator::new(lines).terminate()) 12 | .collect::>(); 13 | 14 | let mut group = c.benchmark_group("statement_splitter_throughput"); 15 | 16 | group.throughput(Throughput::Bytes(stmts.len() as u64)); 17 | group.bench_function("statement_splitter", |b| { 18 | b.iter(|| stmts.iter().flat_map(|cmd| StatementSplitter::new(cmd)).collect::>()) 19 | }); 20 | 21 | group.finish(); 22 | } 23 | 24 | criterion_group!(benches, criterion_benchmark); 25 | criterion_main!(benches); 26 | -------------------------------------------------------------------------------- /tests/methods.out: -------------------------------------------------------------------------------- 1 | one two three four 2 | one, two, three, four 3 | one two three four 4 | one two three four 5 | 5 6 | one two 7 | three four 8 | five six 9 | seven eight 10 | nine ten 11 | 39 75 12 | o 13 | n 14 | e 15 | 16 | 😉 17 | 😉 18 | 😉 19 | 20 | t 21 | w 22 | o 23 | 24 | 😉 25 | 😉 26 | 😉 27 | 28 | t 29 | h 30 | r 31 | e 32 | e 33 | 34 | 😉 35 | 😉 36 | 😉 37 | 38 | f 39 | o 40 | u 41 | r 42 | 43 | 😉 44 | 😉 45 | 😉 46 | 47 | f 48 | i 49 | v 50 | e 51 | 1 two 1 two 52 | 1 1 1 one 53 | one one one one one 54 | one 55 | two 56 | three 57 | one two three 58 | one\ntwo\nthree 59 | one\ntwo\nthree 60 | one two three 61 | apple 62 | sauce 63 | 11 2 3 4 521 2 3 4 531 2 3 4 541 2 3 4 55 64 | one two enemy enemy town 65 | one two" one 66 | \'one two\' 67 | So Space! 68 | Spacey! 69 | So Space! 70 | Spacey! 71 | So Space ! 72 | Spacey ! 73 | -------------------------------------------------------------------------------- /tests/conditionals.ion: -------------------------------------------------------------------------------- 1 | true && echo and || echo or 2 | false && echo and || echo or 3 | false || echo false 4 | true && echo true 5 | true || echo cant get here 6 | false && echo cant get here 7 | false || false || echo double or 8 | true && true && echo double and 9 | false || true && echo or and 10 | contains "one two three" two && echo true || echo false 11 | contains "one two three" abc two && echo true || echo false 12 | contains "one two three" foo bar && echo true || echo false 13 | starts-with "one two three" one && echo true || echo false 14 | starts-with "one two three" abc one && echo true || echo false 15 | starts-with "one two three" three && echo true || echo false 16 | ends-with "one two three" three && echo true || echo false 17 | ends-with "one two three" abc three && echo true || echo false 18 | ends-with "one two three" one two && echo true || echo false 19 | -------------------------------------------------------------------------------- /manual/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | title = "Ion Documentation" 3 | description = "A modern system shell that features a simple, yet powerful, syntax. Written entirely in Rust." 4 | authors = ["Michael Murphy", "Xavier L'Heureux", "Jeremy Soller"] 5 | language = "en" 6 | 7 | [build] 8 | create-missing = false 9 | 10 | [output.html] 11 | git-repository-url = "https://gitlab.redox-os.org/redox-os/ion" 12 | git-repository-icon = "fa-github" 13 | 14 | # Disabled until issues can be fixed 15 | # [output.linkcheck] 16 | # # Should we check links on the internet? Enabling this option adds a 17 | # # non-negligible performance impact 18 | # follow-web-links = false 19 | # optional = true 20 | # 21 | # # Are we allowed to link to files outside of the book's root directory? This 22 | # # may help prevent linking to sensitive files (e.g. "../../../../etc/shadow") 23 | # traverse-parent-directories = false 24 | -------------------------------------------------------------------------------- /tests/while.ion: -------------------------------------------------------------------------------- 1 | # Simple While Loop 2 | 3 | let a = 1 4 | while test $a -lt 10 5 | echo $a 6 | let a += 1 7 | end 8 | 9 | # While Loop With If Conditions 10 | 11 | let a = 1 12 | while test $a -lt 10 13 | if test $a -eq 5 14 | echo found 5 15 | else 16 | echo $a 17 | end 18 | let a += 1 19 | end 20 | 21 | # Nested While Loops 22 | 23 | let outer_count = 1; 24 | while test $outer_count -lt 4 25 | echo "outer loop #$outer_count" 26 | let outer_count += 1 27 | let inner_count = 1 28 | while test $inner_count -lt 4 29 | echo " inner loop #$inner_count" 30 | let inner_count += 1 31 | end 32 | end 33 | 34 | # Chained predicates 35 | 36 | let first = 1 37 | let second = 1 38 | while test $first -lt 13 && test $second -lt 20 39 | echo $first $second 40 | let first second = $second $((first + second)) 41 | end 42 | -------------------------------------------------------------------------------- /manual/README.md: -------------------------------------------------------------------------------- 1 | # Ion Manual 2 | 3 | This manual is meant to be generated via [mdBook](https://github.com/azerupi/mdBook). Simply 4 | executing `mdbook build` in this directory will build all the HTML/JS/CSS files into the 5 | **book** directory. Executing `mdbook serve` will have **mdbook** act has a web service 6 | which can be accessed at http://localhost:3000. 7 | 8 | At some point, Ion may provide a native builtin for quick access to reading the documentation 9 | of the shell, similarly to what Fish does with it's own documentation. This will require 10 | integrating **mdbook**'s library interface into Ion. 11 | 12 | 13 | ## Buitin file 14 | 15 | The file under "manual/src/buitin.md" is created and updated via the command 16 | 17 | ``` 18 | make manual 19 | ``` 20 | 21 | Before opening/updating the mdbook, execute this command. 22 | Do not edit this file "manual/src/buitin.md" manually ! 23 | 24 | -------------------------------------------------------------------------------- /src/lib/memory.rs: -------------------------------------------------------------------------------- 1 | use crate::types::Str; 2 | use object_pool::Pool; 3 | 4 | const MAX_SIZE: usize = 64; 5 | 6 | macro_rules! call_and_shrink { 7 | ($value:ident, $callback:ident) => {{ 8 | let result = $callback($value); 9 | if $value.len() > MAX_SIZE { 10 | $value.truncate(MAX_SIZE); 11 | $value.shrink_to_fit(); 12 | } 13 | 14 | $value.clear(); 15 | result 16 | }}; 17 | } 18 | 19 | thread_local! { 20 | static STRINGS: Pool = Pool::new(256, || Str::with_capacity(MAX_SIZE)); 21 | } 22 | 23 | pub struct IonPool; 24 | 25 | impl IonPool { 26 | pub fn string T>(mut callback: F) -> T { 27 | STRINGS.with(|pool| match pool.try_pull() { 28 | Some(ref mut string) => call_and_shrink!(string, callback), 29 | None => callback(&mut Str::new()), 30 | }) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/array_vars.out: -------------------------------------------------------------------------------- 1 | # ANCHOR: array_assignments 2 | 4 2 3 3 | 4 5 3 4 | 4 5 6 5 | # ANCHOR_END: array_assignments 6 | # ANCHOR: array_ops 7 | 4 4 5 5 5 6 6 6 6 8 | 4 4 5 5 5 6 6 6 6 1 2 3 9 | 1 2 3 4 4 5 5 5 6 6 6 6 1 2 3 10 | 1 2 3 1 2 3 11 | 1 2 2 3 3 3 12 | 1 2 2 3 3 3 4 13 | 0 1 2 2 3 3 3 4 14 | 0 1 2 2 4 15 | # ANCHOR_END: array_ops 16 | # ANCHOR: create_array 17 | one two three four 18 | # ANCHOR_END: create_array 19 | # ANCHOR: index_array 20 | 1 21 | 3 4 5 22 | # ANCHOR_END: index_array 23 | # ANCHOR: array_copy 24 | 1 2 3 25 | # ANCHOR_END: array_copy 26 | # ANCHOR: array_join 27 | hello world this is the ion shell 28 | hello world this is the ion shell 29 | # ANCHOR_END: array_join 30 | # ANCHOR: array_concat_var_strip 31 | 0 1 4 5 32 | -1 1 4 5 6 33 | # ANCHOR_END: array_concat_var_strip 34 | # ANCHOR: practical_array 35 | ./ 36 | ../ 37 | t1/ 38 | t2/ 39 | t1 t2 40 | # ANCHOR_END: practical_array 41 | -------------------------------------------------------------------------------- /manual/src/variables/03-maps.md: -------------------------------------------------------------------------------- 1 | # Maps 2 | 3 | Maps, (AKA dictionaries), provide key-value data association. Ion has two variants of maps: Hash and BTree. Hash maps are fast but store data in a random order. BTree maps are slower, but keep their data in a sorted order. If not sure what to use, go with Hash maps. 4 | 5 | Creating maps uses the same right-hand-side array syntax. However for design simplicity, users must annotate the type to translate the array into a map. 6 | 7 | Please note, the map's inner type specifies the value's type and not of the key. Keys will always be typed `str`. 8 | 9 | ## HashMap 10 | ```sh 11 | {{#include ../../../tests/map_vars.ion:hashmap}} 12 | ``` 13 | ```txt 14 | {{#include ../../../tests/map_vars.out:hashmap}} 15 | ``` 16 | 17 | ## BTreeMap 18 | ```sh 19 | {{#include ../../../tests/map_vars.ion:btreemap}} 20 | ``` 21 | ```txt 22 | {{#include ../../../tests/map_vars.out:btreemap}} 23 | ``` 24 | -------------------------------------------------------------------------------- /tests/braces.out: -------------------------------------------------------------------------------- 1 | 1A1 1A2 1B1 1B2 2 | 0abc2def5g 0abc2def5h 0abc2def5i 0abc2def6g 0abc2def6h 0abc2def6i 0abc2def7g 0abc2def7h 0abc2def7i 0abc3def5g 0abc3def5h 0abc3def5i 0abc3def6g 0abc3def6h 0abc3def6i 0abc3def7g 0abc3def7h 0abc3def7i 0abc4def5g 0abc4def5h 0abc4def5i 0abc4def6g 0abc4def6h 0abc4def6i 0abc4def7g 0abc4def7h 0abc4def7i 1abc2def5g 1abc2def5h 1abc2def5i 1abc2def6g 1abc2def6h 1abc2def6i 1abc2def7g 1abc2def7h 1abc2def7i 1abc3def5g 1abc3def5h 1abc3def5i 1abc3def6g 1abc3def6h 1abc3def6i 1abc3def7g 1abc3def7h 1abc3def7i 1abc4def5g 1abc4def5h 1abc4def5i 1abc4def6g 1abc4def6h 1abc4def6i 1abc4def7g 1abc4def7h 1abc4def7i 3 | Itemized Itemize Italicized Italicize Iterated Iterate 4 | -1 0 1 5 | 2 1 0 6 | a b c 7 | d c b 8 | A B C 9 | D C B 10 | -1 0 1 11 | 2 1 0 12 | a b c 13 | d c b 14 | A B C 15 | D C B 16 | 0 2 4 17 | a c e 18 | A C E 19 | 0 -2 -4 20 | e c a 21 | E C A 22 | 0 2 4 23 | a c e 24 | A C E 25 | 0 -2 -4 26 | e c 27 | E C 28 | -------------------------------------------------------------------------------- /examples/window-config.ion: -------------------------------------------------------------------------------- 1 | echo 'Hey you!' 2 | echo 'Press any key to pause/resume the animation' 3 | echo 'Move the mouse over the screen to change its color' 4 | 5 | # Stop the animation up-front 6 | toggle 7 | 8 | # When the application registers a key, it calls the on_key function with the key number in parameter 9 | fn on_key key 10 | toggle # toggle the animation 11 | echo "${c::yellow}key: ${c::reset}$key" # Print colored text 12 | end 13 | 14 | fn on_mouse x y 15 | set_background $(( x / WINDOW_WIDTH )) $(( y / WINDOW_HEIGHT )) .5 # Change the background color 16 | echo "${c::green}position: ${c::reset}($x, $y)" 17 | end 18 | 19 | # Extra handlers can be defined, like on_render. The application defines the callbacks and the user 20 | # can decide which one to implement 21 | 22 | # Try it yourself: copy this file to the root of the git folder and modify the content: you have 23 | # now a completely reactive config file 24 | -------------------------------------------------------------------------------- /manual/src/general.md: -------------------------------------------------------------------------------- 1 | # General rules 2 | 3 | ## Performance: Let Arithmetic vs Arithmetic Expansions 4 | **let** arithmetic is generally faster than **$(())** expansions. The arithmetic expansions 5 | should be used for increasing readability, or more complex arithmetic. If speed is important: 6 | Multiple *let arithmetic statements will tend to be faster* than a single arithmetic expansion. 7 | 8 | ## Quoting Rules 9 | - Variables are expanded in double quotes, but not single quotes. 10 | - Braces are expanded when unquoted, but not when quoted. 11 | 12 | ## XDG App Dirs Support 13 | All files created by Ion can be found in their respective XDG application directories. For example, 14 | the init file for Ion can be found in **$HOME/.config/ion/initrc** on Linux systems; and the 15 | history file can be found at **$HOME/.local/share/ion/history**. On the first launch of Ion, a 16 | message will be given to indicate the location of these files. 17 | -------------------------------------------------------------------------------- /manual/src/variables/01-strings.md: -------------------------------------------------------------------------------- 1 | # String Variables 2 | We can evaluate expressions to assign their result to the variable and print with with **$** sigil. 3 | Read the chapter expansions for more information about the expansion behavior. 4 | ```sh 5 | {{#include ../../../tests/string_vars.ion:string_variables}} 6 | ``` 7 | ```txt 8 | {{#include ../../../tests/string_vars.out:string_variables}} 9 | ``` 10 | 11 | ## Slicing a string. 12 | Strings can be sliced in Ion using a range. 13 | ```sh 14 | {{#include ../../../tests/string_vars.ion:string_slicing}} 15 | ``` 16 | ```txt 17 | {{#include ../../../tests/string_vars.out:string_slicing}} 18 | ``` 19 | 20 | ## String concatenation 21 | The `++=` and `::=` operators can be used to efficiently concatenate a string in-place. 22 | ```sh 23 | {{#include ../../../tests/string_vars.ion:string_concatenation}} 24 | ``` 25 | ```txt 26 | {{#include ../../../tests/string_vars.out:string_concatenation}} 27 | ``` 28 | -------------------------------------------------------------------------------- /tests/string_vars.ion: -------------------------------------------------------------------------------- 1 | echo '# ANCHOR: string_variables' 2 | # The CI can not handle deletions properly. 3 | mkdir -p _tmp _tmp/t1 _tmp/t2 4 | cd _tmp 5 | let filelist = * 6 | echo $filelist 7 | cd .. 8 | echo '# ANCHOR_END: string_variables' 9 | echo '# ANCHOR: string_slicing' 10 | let foo = "Hello, World" 11 | echo $foo[..5] 12 | echo $foo[7..] 13 | echo $foo[2..9] 14 | echo '# ANCHOR_END: string_slicing' 15 | echo '# ANCHOR: string_concatenation' 16 | let string = world 17 | echo $string 18 | let string ++= ! 19 | echo $string 20 | let string ::= "Hello, " 21 | echo $string 22 | echo '# ANCHOR_END: string_concatenation' 23 | echo '# ANCHOR: string_operations' 24 | let string1 = Doctor 25 | let string2 = order 26 | echo $string1 27 | echo $string2 28 | let string1 ++= "'s" 29 | let string2 ++= "s" 30 | echo $string1 31 | echo $string2 32 | let string1 ++= " " 33 | let string2 ::= $string1 34 | echo $string2 35 | echo '# ANCHOR_END: string_operations' 36 | -------------------------------------------------------------------------------- /benches/terminator.rs: -------------------------------------------------------------------------------- 1 | use criterion::*; 2 | use ion_shell::parser::Terminator; 3 | 4 | const TEXT: &str = include_str!("test.ion"); 5 | const EOF: &str = include_str!("herestring.ion"); 6 | 7 | fn criterion_benchmark(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("terminator-Throughput"); 9 | for script in &[TEXT, EOF] { 10 | group.throughput(Throughput::Bytes(script.len() as u64)); 11 | 12 | group.bench_with_input( 13 | BenchmarkId::new("terminator", script.len()), 14 | &script, 15 | |b, script| { 16 | b.iter(|| { 17 | let mut bytes = script.bytes().peekable(); 18 | while bytes.peek().is_some() { 19 | let _ = Terminator::new(&mut bytes).terminate(); 20 | } 21 | }) 22 | }, 23 | ); 24 | } 25 | } 26 | 27 | criterion_group!(benches, criterion_benchmark); 28 | criterion_main!(benches); 29 | -------------------------------------------------------------------------------- /src/lib/builtins/functions.rs: -------------------------------------------------------------------------------- 1 | use super::Status; 2 | use crate as ion_shell; 3 | use crate::{types, Shell}; 4 | use builtins_proc::builtin; 5 | use std::io::{self, Write}; 6 | 7 | #[builtin( 8 | names = "fn", 9 | desc = "print a short description of every defined function", 10 | man = " 11 | SYNOPSIS 12 | fn [ -h | --help ] 13 | 14 | DESCRIPTION 15 | Prints all the defined functions along with their help, if provided" 16 | )] 17 | pub fn fn_(args: &[types::Str], shell: &mut Shell<'_>) -> Status { 18 | let stdout = io::stdout(); 19 | let stdout = &mut stdout.lock(); 20 | let _ = writeln!(stdout, "# Functions"); 21 | for (fn_name, function) in shell.variables().functions() { 22 | if let Some(description) = function.description() { 23 | let _ = writeln!(stdout, " {} -- {}", fn_name, description); 24 | } else { 25 | let _ = writeln!(stdout, " {}", fn_name); 26 | } 27 | } 28 | Status::SUCCESS 29 | } 30 | -------------------------------------------------------------------------------- /tests/return.ion: -------------------------------------------------------------------------------- 1 | fn ex 2 | let a = 1 3 | if true 4 | if true 5 | if true 6 | while true 7 | return $a 8 | echo should not be printed 9 | end 10 | echo should not be printed 11 | end 12 | echo should not be printed 13 | end 14 | echo should not be printed 15 | end 16 | echo should not be printed 17 | end 18 | 19 | fn test_for 20 | while true 21 | return 22 | echo should not be printed 23 | end 24 | echo should not be printed 25 | end 26 | 27 | fn ex_wrapper 28 | if ex 29 | echo should not be printed 30 | else 31 | echo should be printed 32 | end 33 | if test_for 34 | echo should be printed 35 | else 36 | echo should not be printed 37 | end 38 | end 39 | 40 | ex_wrapper 41 | echo should$(if true; return; else printf ' not'; end) be printed 42 | echo should be printed 43 | -------------------------------------------------------------------------------- /manual/src/expansions/02-process.md: -------------------------------------------------------------------------------- 1 | # Process Expansions 2 | 3 | Ion supports two forms of process expansions: string-based process expansions (**$()**) that are 4 | commonly found in POSIX shells, and array-based process expansions (**@()**), a concept borrowed 5 | from the Oil shell. Where a string-based process expansion will execute a command and return a 6 | string of that command's standard output, an array-based process expansion will split the output 7 | into an array delimited by whitespaces. 8 | 9 | ```sh 10 | let string = $(cmd args...) 11 | let array = [ @(cmd args...) ] 12 | ``` 13 | **NOTES:** 14 | - To split outputs by line, see [@lines($(cmd))](https://doc.redox-os.org/ion-manual/html/expansions/05-method.html#lines). 15 | - `@(cmd)` is equivalent to [@split($(cmd))](https://doc.redox-os.org/ion-manual/html/expansions/05-method.html#split). 16 | ```sh 17 | {{#include ../../../tests/process_exp.ion:process_expansion}} 18 | ``` 19 | ```txt 20 | {{#include ../../../tests/process_exp.out:process_expansion}} 21 | ``` 22 | -------------------------------------------------------------------------------- /tests/array_test.out: -------------------------------------------------------------------------------- 1 | # ANCHOR: standardArrays 2 | 3 | 1 4 | 1 5 | 2 6 | 3 7 | 4 8 | 5 9 | 6 10 | 1 11 | 2 12 | 3 13 | 4 14 | 5 15 | 6 16 | # ANCHOR_END: standardArrays 17 | # ANCHOR: command_substitution 18 | a 19 | b 20 | c 21 | d 22 | e 23 | a 24 | b 25 | c 26 | d 27 | e 28 | # ANCHOR_END: command_substitution 29 | # ANCHOR: concatenation 30 | 1 31 | 2 32 | 3 33 | 4 34 | 5 35 | 6 36 | a 37 | b 38 | c 39 | d 40 | e 41 | # ANCHOR_END: concatenation 42 | # ANCHOR: array_as_command_args 43 | 1 2 3 4 5 6 44 | a b c d e 45 | 1 2 3 4 5 6 a b c d e 46 | # ANCHOR_END: array_as_command_args 47 | # ANCHOR: slice_by_id 48 | 1 49 | 2 50 | 3 51 | 1 52 | 2 53 | 3 54 | 1 55 | 2 56 | 3 57 | # ANCHOR_END: slice_by_id 58 | # ANCHOR: slice_by_range 59 | 1 60 | 1 2 61 | 1 2 3 62 | 4 5 63 | 1 2 3 4 5 64 | # ANCHOR_END: slice_by_range 65 | # ANCHOR: slice_with_vars 66 | 😉 67 | 😉 68 | 😉 69 | # ANCHOR_END: slice_with_vars 70 | # ANCHOR: convert_to_string 71 | 1 2 3 4 5 72 | 1 2 8 9 10 73 | 3 4 6 7 8 9 10 74 | 6 7 8 9 10 4 3 75 | 6 7 8 2 3 10 3 76 | # ANCHOR_END: convert_to_string 77 | -------------------------------------------------------------------------------- /tests/space_parenthese_start_end.ion: -------------------------------------------------------------------------------- 1 | let word = "hello" 2 | let list = [list hello] 3 | 4 | # exepcted: 5 5 | let length = $len( $word ); 6 | echo $length 7 | 8 | # exepcted: 5 9 | let length = $len( $word); 10 | echo $length 11 | 12 | # exepcted: 5 13 | let length = $len($word ); 14 | echo $length 15 | 16 | # exepcted: 5 17 | let length = $len( ${word} ); 18 | echo $length 19 | 20 | # exepcted: 5 21 | let length = $len( ${word}); 22 | echo $length 23 | 24 | # exepcted: 25 | # h 26 | # llo 27 | for char in @split( ${word} 'e' ); 28 | echo $char 29 | end 30 | 31 | # exepcted: 5 32 | let length = $len(${word} ); 33 | echo $length 34 | 35 | # exepcted: 2 36 | let length = $len( @{list} ); 37 | echo $length 38 | 39 | # exepcted: 2 40 | let length = $len( @list ); 41 | echo $length 42 | 43 | # exepcted: 3 44 | let length = $len( "xxx"); 45 | echo $length 46 | 47 | # exepcted: 6 48 | let length = $len( [2 4 5 89 789 3245] ); 49 | echo $length 50 | 51 | # exepcted: 6 52 | let length = $len([2 4 5 89 789 3245] ); 53 | echo $length 54 | 55 | -------------------------------------------------------------------------------- /src/binary/huponexit.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::Cell, rc::Rc}; 2 | 3 | use builtins_proc::builtin_interactive; 4 | use ion_shell::{ 5 | builtins::{man_pages, Status}, 6 | types, Shell, 7 | }; 8 | 9 | #[builtin_interactive( 10 | desc = "Toggle if it hangups the shell's background jobs on exit", 11 | man = " 12 | SYNOPSIS 13 | huponexit [false|off] 14 | 15 | DESCRIPTION 16 | If activated, it hangups the shell's background jobs on exit. 17 | If no arguments are provided then huponexit is activated. Can be deactivated 18 | again with providing false or off. 19 | 20 | OPTIONS: 21 | false or off: deactivates this behaviour" 22 | )] 23 | pub fn huponexit( 24 | huponexit_state: Rc>, 25 | ) -> impl Fn(&[types::Str], &mut Shell<'_>) -> Status { 26 | move |args: &[types::Str], _shell: &mut Shell<'_>| { 27 | if man_pages::check_help(args, HELP_PAGE) { 28 | return Status::SUCCESS; 29 | } 30 | huponexit_state.set(!matches!(args.get(1).map(AsRef::as_ref), Some("false") | Some("off"))); 31 | Status::SUCCESS 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/lib/builtins/source.rs: -------------------------------------------------------------------------------- 1 | use super::Status; 2 | use crate as ion_shell; 3 | use crate::{shell::Shell, types}; 4 | use builtins_proc::builtin; 5 | use std::fs::File; 6 | 7 | #[builtin( 8 | desc = "evaluates given file", 9 | man = " 10 | SYNOPSIS 11 | source FILEPATH 12 | 13 | DESCRIPTION 14 | Evaluates the commands in a specified file in the current shell. All changes in shell 15 | variables will affect the current shell because of this." 16 | )] 17 | pub fn source(args: &[types::Str], shell: &mut Shell<'_>) -> Status { 18 | match args.get(1) { 19 | Some(argument) => { 20 | if let Ok(file) = File::open(argument.as_str()) { 21 | if let Err(why) = shell.execute_command(file) { 22 | Status::error(format!("ion: {}", why)) 23 | } else { 24 | Status::SUCCESS 25 | } 26 | } else { 27 | Status::error(format!("ion: failed to open {}\n", argument)) 28 | } 29 | } 30 | None => Status::error("an argument is required for source"), 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /manual/src/migrating.md: -------------------------------------------------------------------------------- 1 | # Migrating from POSIX Shells 2 | 3 | ## Notable changes 4 | - Arrays are full-class citizens, using the @ sigil. That means emails and git urls must be single quoted 5 | - The shell has proper scopes (variables get unset after the end of the definition scope), and functions are closures 6 | - The shell has an internal variable store. That means environment variables must be explicitly exported to be available to commands. 7 | - For now, per-command environment variables are not supported (ex: `LANG=it_CH.utf8 man man`) 8 | - The testing builtin (`[[ .. ]]`) was replaced with `test`, `exists`, and/or other commands 9 | - The control flow have been revisited, see the relevant part of the manual 10 | 11 | ## Customizing your prompt 12 | - Define the PROMPT function to be called whenever the prompt needs to be drawn. Simply print the prompt to stdout in the function (printf or git branch directly) 13 | - Variables are defined with all the colors (see the namespaces manual page for all details). This means you don't have to deal with all the escape codes directly. No more `\x033[33;m`, instead it's `${color::yellow}`. 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Redox OS Developers 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. 22 | 23 | -------------------------------------------------------------------------------- /manual/src/pipelines.md: -------------------------------------------------------------------------------- 1 | # Pipelines 2 | 3 | ## Redirection 4 | 5 | Redirection will write the output of a command to a file. 6 | 7 | ### Redirect Stdout 8 | 9 | ```sh 10 | command > stdout 11 | ``` 12 | 13 | ### Redirect Stderr 14 | 15 | ```sh 16 | command ^> stderr 17 | ``` 18 | 19 | ### Redirect Both 20 | 21 | ```sh 22 | command &> combined 23 | ``` 24 | 25 | ### Multiple Redirection 26 | 27 | ```sh 28 | command > stdout ^> stderr &> combined 29 | ``` 30 | 31 | ### Concatenating Redirect 32 | 33 | Instead of truncating and writing a new file with `>`, the file can be appended to with `>>`. 34 | 35 | ```sh 36 | command > stdout 37 | command >> stdout 38 | ``` 39 | 40 | ## Pipe 41 | 42 | ### Pipe Stdout 43 | 44 | ```sh 45 | command | command 46 | ``` 47 | 48 | ### Pipe Stderr 49 | 50 | ```sh 51 | command ^| command 52 | ``` 53 | 54 | ### Pipe Both 55 | 56 | ```sh 57 | command &| command 58 | ``` 59 | 60 | ## Combined 61 | 62 | ```sh 63 | command | command > stdout 64 | ``` 65 | 66 | ## Detaching processes 67 | 68 | ### Send to background 69 | 70 | ```sh 71 | command & 72 | ``` 73 | 74 | ### Disown (detach from shell) 75 | 76 | ```sh 77 | command &! 78 | ``` 79 | -------------------------------------------------------------------------------- /manual/src/variables/04-arithmetic.md: -------------------------------------------------------------------------------- 1 | # Let Arithmetic 2 | 3 | Ion supports applying some basic arithmetic, one operation at a time, to string variables. To 4 | specify to `let` to perform some arithmetic, designate the operation immediately before **=**. 5 | Operators currently supported are: 6 | 7 | - [x] Add (**+**) 8 | - [x] Subtract (**-**) 9 | - [x] Multiply (**\***) 10 | - [x] Divide (**/**) 11 | - [x] Integer Divide (**//**) 12 | - [ ] Modulus (**%**) 13 | - [x] Powers (**\*\***) 14 | 15 | ## Individual Assignments 16 | The following examples are a demonstration of applying a mathematical operation to an individual 17 | variable. 18 | ```sh 19 | {{#include ../../../tests/arithmetic_vars.ion:individual_assignments}} 20 | ``` 21 | ```txt 22 | {{#include ../../../tests/arithmetic_vars.out:individual_assignments}} 23 | ``` 24 | 25 | ## Multiple Assignments 26 | It's also possible to perform a mathematical operation to multiple variables. Each variable will be 27 | designated with a paired value. 28 | ```sh 29 | {{#include ../../../tests/arithmetic_vars.ion:multiple_assignments}} 30 | ``` 31 | ```txt 32 | {{#include ../../../tests/arithmetic_vars.out:multiple_assignments}} 33 | ``` 34 | -------------------------------------------------------------------------------- /sh-interrupt/hash-error.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | test -d foo1 || mkdir foo1 4 | test -d foo2 || mkdir foo2 5 | test -d foo2 || mkdir foo3 6 | echo 'echo :one' > foo1/run 7 | echo 'echo :two' > foo2/run 8 | echo 'echo :three' > foo2/run3 9 | chmod a+x */run* 10 | 11 | hash -r 12 | PATH=./foo3:./foo1:./foo2:./foo5 13 | 14 | echo Expect one: 15 | PATH=./foo3:./foo3:./foo1 run 16 | echo $PATH 17 | echo ERROR: run should be in in foo1, but is in two in old sh: 18 | hash -v 19 | echo ERROR: should give one, but does two in old sh: 20 | run 21 | 22 | hash -r 23 | echo 24 | echo Expect two: 25 | PATH=./foo3:./foo4:./foo3:./foo2:./foo5 26 | run 27 | hash -v 28 | echo ERROR: Expect one, does not find run on old sh: 29 | PATH=./foo3:./foo3:./foo1 run 30 | 31 | echo 32 | hash -r 33 | PATH=./foo3:./foo1:./foo4:./foo5 34 | 35 | echo Expect one, error preparation: 36 | PATH=./foo3:./foo4:./foo1 run 37 | echo Should show run in the wrong place: 38 | hash -v 39 | echo ERROR: Will not find run in old sh, should give one: 40 | run 41 | 42 | echo 43 | echo expect one 44 | PATH=./foo1:./foo2 45 | run 46 | echo expect three... 47 | PATH=./foo3:./foo4:./foo2 run3 48 | echo ERROR ... and now a coredump 49 | hash -v 50 | -------------------------------------------------------------------------------- /manual/src/scripts/02-init_file.md: -------------------------------------------------------------------------------- 1 | ## Executing commands at start of interactive ion session 2 | 3 | Commands can be executed at the start of an interactive ion session. 4 | This can be useful to set up environmental variables specific to ion, 5 | set aliases, set vim keybindings, etc. 6 | 7 | Ion reads those initial commands from a file called "initrc". 8 | Ion looks for this file in its configuration folder 9 | according to the [xdg standard](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html). 10 | 11 | This typically results in the configuration path ~/.config/ion by default. 12 | So ion searches for the file initrc in the folder ~/.config/ion normally. 13 | 14 | For example the following content of the initrc file 15 | causes ion to start with vim bindings enabled. 16 | 17 | ```sh 18 | keybindings vi 19 | ``` 20 | 21 | You can change the location where ion looks for its configuration folder 22 | by setting the environmental variable **$XDG_CONFIG_HOME** to a different path. 23 | 24 | ```sh 25 | export XDG_CONFIG_HOME='/home/some_user/myconfig' 26 | ``` 27 | 28 | In this example ion will look at the path /home/some_user/myconfig/ion 29 | for the initrc file. 30 | -------------------------------------------------------------------------------- /src/lib/expansion/loops.rs: -------------------------------------------------------------------------------- 1 | use super::{Expander, Result}; 2 | use crate::{ranges, types}; 3 | 4 | /// The expression given to a for loop as the value to iterate upon. 5 | pub enum ForValueExpression { 6 | /// A set of values 7 | Multiple(Vec), 8 | /// A single value 9 | Normal(types::Str), 10 | /// A range of numbers 11 | Range(Box + 'static>), 12 | } 13 | 14 | impl ForValueExpression { 15 | /// Parse the arguments for the for loop 16 | pub fn new( 17 | expression: &[types::Str], 18 | expanders: &mut E, 19 | ) -> Result { 20 | let mut output = Vec::new(); 21 | for exp in expression { 22 | output.extend(expanders.expand_string(exp)?); 23 | } 24 | 25 | Ok(if output.is_empty() { 26 | Self::Multiple(output) 27 | } else if let (Some(range), true) = (ranges::parse_range(&output[0]), output.len() == 1) { 28 | Self::Range(range) 29 | } else if output.len() > 1 { 30 | Self::Multiple(output) 31 | } else { 32 | Self::Normal(output[0].clone()) 33 | }) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/help.out: -------------------------------------------------------------------------------- 1 | ion 1.0.0-alpha 2 | The fast, safe, modern rust shell. Ion is a commandline shell created to be a faster and easier to use alternative to 3 | the currently available shells. It is not POSIX compliant. 4 | 5 | USAGE: 6 | ion [FLAGS] [OPTIONS] [args]... 7 | 8 | FLAGS: 9 | -f, --fake-interactive Use a fake interactive mode, where errors don't exit the shell 10 | -h, --help Prints help information 11 | -i, --interactive Force interactive mode 12 | -l, --login No-op for backwards compatability for systems that expect a "login-shell" 13 | -n, --no-execute Do not execute any commands, perform only syntax checking 14 | -x Print commands before execution 15 | -v, --version Print the version, platform and revision of Ion then exit 16 | 17 | OPTIONS: 18 | -c Evaluate given commands instead of reading from the commandline 19 | -o Shortcut layout. Valid options: "vi", "emacs" 20 | 21 | ARGS: 22 | ... Script arguments (@args). If the -c option is not specified, the first parameter is taken as a 23 | filename to execute 24 | -------------------------------------------------------------------------------- /tests/run_benches: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ion 2 | 3 | let PROJECT_DIR = $parent(@split($(cargo locate-project), '"')[3]) 4 | let CARGO = $PROJECT_DIR/target/release/ion 5 | 6 | fn check_timing script:str chars:int 7 | let utime:float = 0.0 8 | for _ in 0..10 9 | let temp = @(time ${global::CARGO} $script >/dev/null)[1] 10 | let utime:float += $temp[..-1] 11 | end 12 | 13 | let utime /= 10 14 | let utime = $utime[..11] 15 | 16 | let script = $filename(script) 17 | let script_len utime_len = $len(script) $len(utime) 18 | 19 | echo $repeat(' ', $(( chars - script_len )))$script \ 20 | $utime$repeat('0', $(( 11 - utime_len ))) 21 | end 22 | 23 | fn script_width scripts[] 24 | let max_chars:int = 0 25 | for script in @scripts 26 | let chars:int = $len($filename(script)) 27 | if test $max_chars -lt $chars 28 | let max_chars:int = $chars 29 | end 30 | end 31 | echo $max_chars 32 | end 33 | 34 | fn bench_values scripts[] 35 | let max_chars = $(script_width [@scripts]) 36 | for file in @scripts 37 | check_timing $file $max_chars 38 | end 39 | end 40 | 41 | cd $PROJECT_DIR/examples 42 | if test $len(@args) -eq 1 43 | bench_values [ ./*.ion ] 44 | else 45 | bench_values [ @args[1..] ] 46 | end 47 | -------------------------------------------------------------------------------- /manual/src/scripts/00-scripts.md: -------------------------------------------------------------------------------- 1 | # Script Executions 2 | 3 | Scripts can be created by designating Ion as the interpreter in the shebang line. 4 | 5 | ```sh 6 | #!/usr/bin/env ion 7 | ``` 8 | 9 | Then writing the script as you would write it in the prompt. When finished, you can execute the 10 | shell by providing the path of the script to Ion as the argument, along with any additional 11 | arguments that the script may want to parse. Arguments can be accessed from the **@args** array, 12 | where the first element in the array is the name of the script being executed. 13 | 14 | ```sh 15 | #!/usr/bin/env ion 16 | 17 | if test $len(@args) -eq 1 18 | echo "Script didn't receive enough arguments" 19 | exit 20 | end 21 | 22 | echo Arguments: @args[1..] 23 | 24 | ``` 25 | 26 | An example of calling a script with positional arguments 27 | 28 | ```sh 29 | ion some_scirpt first second third 30 | ``` 31 | 32 | Content of `some_scipt`. 33 | 34 | ```sh 35 | for next in @args 36 | # do something with the given arguments 37 | echo $next 38 | end 39 | ``` 40 | 41 | `Output:` of example 42 | 43 | **Note:** The zeroth argument is the name of the executed script. 44 | 45 | ```txt 46 | some_scipt 47 | first 48 | second 49 | third 50 | ``` 51 | -------------------------------------------------------------------------------- /src/lib/types.rs: -------------------------------------------------------------------------------- 1 | use smallvec::SmallVec; 2 | pub use types_rs::{array, types::*}; 3 | 4 | pub use crate::shell::flow_control::Function; 5 | /// A owned version of a set of arguments for spawning a command 6 | pub type Args = SmallVec<[small::String; 4]>; 7 | /// Construct a new Array containing the given arguments 8 | /// 9 | /// `array!` acts like the standard library's `vec!` macro, and can be thought 10 | /// of as a shorthand for: 11 | /// ```ignore,rust 12 | /// Array::from_vec(vec![...]) 13 | /// ``` 14 | /// Additionally it will call `Into::into` on each of its members so that one 15 | /// can pass in any type with some `To` implementation; they will 16 | /// automatically be converted to owned `SmallString`s. 17 | /// ```ignore,rust 18 | /// let verbose = Array::from_vec(vec![ 19 | /// "foo".into(), 20 | /// "bar".into(), 21 | /// "baz".into(), 22 | /// "zar".into(), 23 | /// "doz".into(), 24 | /// ]); 25 | /// let concise = array!["foo", "bar", "baz", "zar", "doz"]; 26 | /// assert_eq!(verbose, concise); 27 | /// ``` 28 | #[macro_export] 29 | macro_rules! args [ 30 | ( $($x:expr), *) => ({ 31 | let mut _arr = $crate::types::Args::new(); 32 | $(_arr.push($x.into());)* 33 | _arr 34 | }) 35 | ]; 36 | -------------------------------------------------------------------------------- /src/lib/builtins/man_pages.rs: -------------------------------------------------------------------------------- 1 | use crate::types; 2 | 3 | /// Print the given help if the -h or --help argument are found 4 | pub fn check_help(args: &[types::Str], man_page: &'static str) -> bool { 5 | for arg in args { 6 | if arg == "-h" || arg == "--help" { 7 | println!("{}", man_page); 8 | return true; 9 | } 10 | } 11 | false 12 | } 13 | 14 | // pub const MAN_FN: &str = r#"NAME 15 | // fn - print a list of all functions or create a function 16 | // 17 | // SYNOPSIS 18 | // fn 19 | // 20 | // fn example arg:int 21 | // echo $arg 22 | // end 23 | // 24 | // DESCRIPTION 25 | // fn prints a list of all functions that exist in the shell or creates a 26 | // function when combined with the 'end' keyword. Functions can have type 27 | // hints, to tell ion to check the type of a functions arguments. An error will 28 | // occur if an argument supplied to a function is of the wrong type. 29 | // The supported types in ion are, [], bool, bool[], float, float[], int, 30 | // int[], str, str[]. 31 | // 32 | // Functions are called by typing the function name and then the function 33 | // arguments, separated by a space. 34 | // 35 | // fn example arg0:int arg1:int 36 | // echo $arg 37 | // end 38 | // 39 | // example 1 40 | //"#; 41 | -------------------------------------------------------------------------------- /manual/src/expansions/04-arithmetic.md: -------------------------------------------------------------------------------- 1 | # Arithmetic Expansions 2 | 3 | We've exported our arithmetic logic into a separate crate 4 | [calculate](https://crates.io/crates/calculate). We use this library for both our `calc` builtin, 5 | and for parsing arithmetic expansions. Use `math` if you want a REPL for arithmetic, else use 6 | arithmetic expansions (`$((a + b))`) if you want the result inlined. Variables may be passed into 7 | arithmetic expansions without the **$** sigil, as it is automatically inferred that text references 8 | string variables. Supported operators are as below: 9 | 10 | - Add (`$((a + b))`) 11 | - Subtract(`$((a - b))`) 12 | - Divide(`$((a / b))`) 13 | - Multiply(`$((a * b))`) 14 | - Powers(`$((a ** b))`) 15 | - Square(`$((a²))`) 16 | - Cube(`$((a³))`) 17 | - Modulus(`$((a % b))`) 18 | - Bitwise XOR(`$((a ^ b))`) 19 | - Bitwise AND(`$((a & b))`) 20 | - Bitwise OR(`$((a | b)))`) 21 | - Bitwise NOT(`$((a ~ b))`) 22 | - Left Shift(`$((a << b))`) 23 | - Right Shift(`$((a >> b))`) 24 | - Parenthesis(`$((4 * (pi * r²)))`) 25 | 26 | Take note, however, that these expressions are evaluated to adhere to order of operation rules. 27 | Therefore, expressions are not guaranteed to evaluate left to right, and parenthesis should be 28 | used when you are unsure about the order of applied operations. 29 | -------------------------------------------------------------------------------- /benches/herestring.ion: -------------------------------------------------------------------------------- 1 | cat <<< ' 2 | aueaue 3 | uaei 4 | ui 5 | e 6 | aui 7 | eauieaue 8 | au 9 | ei 10 | auieaui 11 | e 12 | aue 13 | aui 14 | eauie 15 | iaue' 16 | cat <<< ' 17 | aueaue 18 | uaei 19 | ui 20 | e 21 | aui 22 | eauieaue 23 | au 24 | ei 25 | auieaui 26 | e 27 | aue 28 | aui 29 | eauie 30 | iaue' 31 | cat <<< ' 32 | aueaue 33 | uaei 34 | ui 35 | e 36 | aui 37 | eauieaue 38 | au 39 | ei 40 | auieaui 41 | e 42 | aue 43 | aui 44 | eauie 45 | iaue' 46 | cat <<< ' 47 | aueaue 48 | uaei 49 | ui 50 | e 51 | aui 52 | eauieaue 53 | au 54 | ei 55 | auieaui 56 | e 57 | aue 58 | aui 59 | eauie 60 | iaue' 61 | cat <<< ' 62 | aueaue 63 | uaei 64 | ui 65 | e 66 | aui 67 | eauieaue 68 | au 69 | ei 70 | auieaui 71 | e 72 | aue 73 | aui 74 | eauie 75 | iaue' 76 | cat <<< ' 77 | aueaue 78 | uaei 79 | ui 80 | e 81 | aui 82 | eauieaue 83 | au 84 | ei 85 | auieaui 86 | e 87 | aue 88 | aui 89 | eauie 90 | iaue' 91 | cat <<< ' 92 | aueaue 93 | uaei 94 | ui 95 | e 96 | aui 97 | eauieaue 98 | au 99 | ei 100 | auieaui 101 | e 102 | aue 103 | aui 104 | eauie 105 | iaue' 106 | cat <<< ' 107 | aueaue 108 | uaei 109 | ui 110 | e 111 | aui 112 | eauieaue 113 | au 114 | ei 115 | auieaui 116 | e 117 | aue 118 | aui 119 | eauie 120 | iaue' 121 | -------------------------------------------------------------------------------- /tests/script_exec/arrays.ion: -------------------------------------------------------------------------------- 1 | # Standard Arrays 2 | 3 | let array = [1 [2 3] 4 [5 6]] 4 | for i in @array 5 | echo $i 6 | end 7 | 8 | for i in [1 [2 3] 4 [5 6]] 9 | echo $i 10 | end 11 | 12 | # Array Command Substitution 13 | 14 | let array_process = [ @(echo a b c d e) ] 15 | for i in @array_process 16 | echo $i 17 | end 18 | 19 | for i in @(echo a b c d e) 20 | echo $i 21 | end 22 | 23 | # Array Concatenation 24 | 25 | let new_array = [@array @array_process] 26 | for i in @new_array 27 | echo $i 28 | end 29 | 30 | # As Command Arguments 31 | 32 | echo @array 33 | echo @array_process 34 | echo @new_array 35 | 36 | # Slice by ID 37 | 38 | let array = [ 1 2 3 ] 39 | echo @array[0] 40 | echo @array[1] 41 | echo @array[2] 42 | 43 | echo [ 1 2 3 ][0] 44 | echo [ 1 2 3 ][1] 45 | echo [ 1 2 3 ][2] 46 | 47 | echo @(echo 1 2 3)[0] 48 | echo @(echo 1 2 3)[1] 49 | echo @(echo 1 2 3)[2] 50 | 51 | # Slice by Range 52 | 53 | let array = [ 1 2 3 4 5 ] 54 | echo @array[0..1] 55 | echo @array[0...1] 56 | echo @array[..3] 57 | echo @array[3..] 58 | echo @array[..] 59 | 60 | # Slice Syntax w/ variables 61 | for index in 0..3 62 | echo $(echo 😉😉😉)[$index] 63 | end 64 | 65 | # Convert Array to String 66 | 67 | let array = [ 1 2 3 4 5 ] 68 | let as_string = @array 69 | echo $as_string 70 | -------------------------------------------------------------------------------- /tests/string_methods.out: -------------------------------------------------------------------------------- 1 | # ANCHOR: basename 2 | filename.ext 3 | # ANCHOR_END: basename 4 | # ANCHOR: extension 5 | ext 6 | # ANCHOR_END: extension 7 | # ANCHOR: filename 8 | filename 9 | # ANCHOR_END: filename 10 | # ANCHOR: join 11 | 1 2 3 4 5 12 | 1, 2, 3, 4, 5 13 | # ANCHOR_END: join 14 | # ANCHOR: find 15 | 2 16 | -1 17 | # ANCHOR_END: find 18 | # ANCHOR: len 19 | 6 20 | 1 21 | 4 22 | # ANCHOR_END: len 23 | # ANCHOR: len_bytes 24 | 6 25 | 6 26 | # ANCHOR_END: len_bytes 27 | # ANCHOR: parent 28 | /root/parent 29 | # ANCHOR_END: parent 30 | # ANCHOR: repeat 31 | abc, abc, abc, 32 | # ANCHOR_END: repeat 33 | # ANCHOR: replace 34 | 1 two 1 two 35 | 1 2 1 2 36 | # ANCHOR_END: replace 37 | # ANCHOR: replacen 38 | three two one two 39 | one three one three 40 | # ANCHOR_END: replacen 41 | # ANCHOR: regex_replace 42 | Bob 43 | boB 44 | Four Three Two One 45 | # ANCHOR_END: regex_replace 46 | # ANCHOR: reverse 47 | raboof 48 | # ANCHOR_END: reverse 49 | # ANCHOR: to_lowercase 50 | foobar 51 | # ANCHOR_END: to_lowercase 52 | # ANCHOR: to_uppercase 53 | FOOBAR 54 | # ANCHOR_END: to_uppercase 55 | # ANCHOR: escape 56 | Mary had\\ta little \\n\\t lamb\\t 57 | # ANCHOR_END: escape 58 | # ANCHOR: unescape 59 | Mary had a little 60 | lamb 61 | # ANCHOR_END: unescape 62 | # ANCHOR: or 63 | Fallback 64 | 42 65 | # ANCHOR_END: or 66 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | ion-shell (1:1.0.0-alpha2.3) disco; urgency=medium 2 | 3 | * New release 4 | 5 | -- Michael Aaron Murphy Wed, 29 May 2019 17:26:48 -0600 6 | 7 | ion-shell (1:1.0.0-alpha2.2) cosmic; urgency=medium 8 | 9 | * More prompt fixes, optimizations, and string concat support 10 | 11 | -- Michael Aaron Murphy Wed, 27 Mar 2019 15:59:22 -0600 12 | 13 | ion-shell (1:1.0.0-alpha2.1) cosmic; urgency=medium 14 | 15 | * Fix for prompt not displaying when using Ctrl + C in the prompt 16 | 17 | -- Michael Aaron Murphy Tue, 19 Mar 2019 13:12:02 -0600 18 | 19 | ion-shell (1:1.0.0-alpha2~cosmic2) cosmic; urgency=medium 20 | 21 | * Fix for broken export assignments 22 | 23 | -- Michael Aaron Murphy Mon, 18 Mar 2019 10:33:34 -0600 24 | 25 | ion-shell (1:1.0.0-alpha2~cosmic1) cosmic; urgency=medium 26 | 27 | * Upstream updates 28 | 29 | -- Michael Aaron Murphy Sat, 16 Mar 2019 22:31:21 -0600 30 | 31 | ion-shell (1:1.0.0-alpha1~cosmic0) cosmic; urgency=medium 32 | 33 | * Upstream updates 34 | 35 | -- Michael Aaron Murphy Wed, 23 Jan 2019 22:54:59 -0700 36 | 37 | ion-shell (1.0.0-alpha1~cosmic0) cosmic; urgency=medium 38 | 39 | * Add postinst script for setting the shell. 40 | 41 | -- Michael Aaron Murphy Thu, 01 Nov 2018 16:44:15 -0600 42 | 43 | -------------------------------------------------------------------------------- /sh-interrupt/README: -------------------------------------------------------------------------------- 1 | 1) Link or copy the shell to test to "./testshell", then run the tests 2 | like this: `./test01.sh` 3 | 4 | 2) Please keep in mind that the sigint catcher program remaps SIGQUIT 5 | to C-g. When a script fails, it is possible that your terminal is left 6 | with these changes. Either you have to be careful and reset the 7 | terminal when it is needed or keep in mind that you might have to use 8 | C-g insted of C-\ for SIGQUIT. 9 | 10 | 3) Some background jobs are started and are supposed to be killed by a 11 | working shell. You wouldn't read this if you weren't concerned about 12 | the quality of your shell, so you probably have some runaway jobs when 13 | done with the tests. Make sure you `ps` for all jobs attached to your 14 | terminal when you're done. I just discovered a `wc -c /dev/zero` 15 | running on my machine since almost a week ago :-) 16 | 17 | 4) The program ./hardguy lasts 20 seconds it is not catched by the 18 | surrounding shell, no matter what happens. Patience, no need to kill 19 | it. 20 | 21 | 5) Some examples need the -T switch I just committed to 22 | FreeBSD-current's /bin/sh (immediate execution of traps). 23 | 24 | mkdep.sh tests mkdep, which is an example of a program that is 25 | implemented as shell script and need to kill itself in a trap 26 | handler. It should just be interruptable by the first SIGINT. 27 | 28 | test??[ab].sh are subshell neede for other tests, but cann be called 29 | directly as well. 30 | -------------------------------------------------------------------------------- /sh-interrupt/makestuff/README: -------------------------------------------------------------------------------- 1 | To run these tests, you will have to replace /bin/sh with the shell to 2 | test, the symlink trick doesn't work since we want the shell that make 3 | uses to be the testshell as well. 4 | 5 | You may 6 | TESTMAKE=gmake ./test01.sh 7 | to test various makes. 8 | 9 | Currently, FreeBSD's make depends on the /bin/sh to exit on SIGINT even 10 | when a foreground job is run to behave right. GNU make works right 11 | with fixed FreeBSD shells. 12 | 13 | The following fix makes FreeBSD behave right (IMHO) for the non-compat 14 | case. Note that if you don't pass a `-j` parameter to make, it will be 15 | in compat mode even if you don't pass -B. test it like this 16 | export TESTMAKE=/foo/bar/make -j 1 17 | ./test01.sh 18 | 19 | diff -c make.original/job.c make.work/job.c 20 | *** make.original/job.c Tue Aug 26 12:06:38 1997 21 | --- make.work/job.c Wed Mar 11 12:49:52 1998 22 | *************** 23 | *** 2904,2910 **** 24 | } 25 | } 26 | (void) eunlink(tfile); 27 | ! exit(signo); 28 | } 29 | 30 | /* 31 | --- 2904,2918 ---- 32 | } 33 | } 34 | (void) eunlink(tfile); 35 | ! 36 | ! /* 37 | ! * For some signals, we don't want a direct exit, but to 38 | ! * let them resent to ourself, which is done by the calling 39 | ! * Routine. 40 | ! */ 41 | ! 42 | ! if (signo != SIGINT && signo != SIGTERM && signo != SIGHUP) 43 | ! exit(signo); 44 | } 45 | 46 | /* 47 | -------------------------------------------------------------------------------- /manual/src/control/03-matches.md: -------------------------------------------------------------------------------- 1 | # Matches 2 | 3 | Matches will evaluate each case branch, and execute the first branch which succeeds. 4 | A case which is `_` will execute if all other cases have failed. 5 | 6 | ```sh 7 | match $string 8 | case "this" 9 | echo "do that" 10 | case "that" 11 | echo "else this" 12 | case _; echo "not found" 13 | end 14 | ``` 15 | 16 | ## Matching string input with array cases 17 | 18 | If the input is a string, and a case is an array, then a match will succeed if at 19 | least one item in the array is a match. 20 | 21 | ```sh 22 | match five 23 | case [ one two three ]; echo "one of these matched" 24 | case [ four five six ]; echo "or one of these matched" 25 | case _; echo "no match found" 26 | end 27 | ``` 28 | 29 | ## Matching array input with string cases 30 | 31 | The opposite is true when the input is an array, and a case is a string. 32 | 33 | ```sh 34 | match [ five foo bar ] 35 | case "one"; echo "this" 36 | case "two"; echo "that" 37 | case "five"; echo "found five" 38 | case _; echo "no match found" 39 | end 40 | ``` 41 | 42 | ## Match guards 43 | 44 | Match guards can be added to a match to employ an additional test 45 | 46 | ```sh 47 | let foo = bar 48 | match $string 49 | case "this" if eq $foo bar 50 | echo "this and foo = bar" 51 | case "this" 52 | echo "this and foo != bar" 53 | case _; echo "no match found" 54 | end 55 | ``` 56 | -------------------------------------------------------------------------------- /src/binary/keybindings.rs: -------------------------------------------------------------------------------- 1 | use builtins_proc::builtin_interactive; 2 | use ion_shell::{ 3 | builtins::{man_pages, Status}, 4 | types, Shell, 5 | }; 6 | 7 | use liner::{Context, KeyBindings}; 8 | use std::{cell::RefCell, rc::Rc}; 9 | #[builtin_interactive( 10 | desc = "changes key bindings", 11 | man = " 12 | NAME 13 | keybindings - changes the key shortcuts to a different preset 14 | 15 | SYNOPSIS 16 | keybindings [vi|emacs] 17 | 18 | DESCRIPTION 19 | Set key bindings to a preset, vi or emacs. 20 | 21 | OPTIONS: 22 | vi: vim keybindings 23 | emacs: emacs key bindings" 24 | )] 25 | pub fn keybindings( 26 | context_bis: Rc>, 27 | ) -> impl Fn(&[types::Str], &mut Shell<'_>) -> Status { 28 | move |args: &[types::Str], _shell: &mut Shell<'_>| -> Status { 29 | if man_pages::check_help(args, HELP_PAGE) { 30 | return Status::SUCCESS; 31 | } 32 | match args.get(1).map(|s| s.as_str()) { 33 | Some("vi") => { 34 | context_bis.borrow_mut().key_bindings = KeyBindings::Vi; 35 | Status::SUCCESS 36 | } 37 | Some("emacs") => { 38 | context_bis.borrow_mut().key_bindings = KeyBindings::Emacs; 39 | Status::SUCCESS 40 | } 41 | Some(_) => Status::error("Invalid keybindings. Choices are vi and emacs"), 42 | None => Status::error("keybindings need an argument"), 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/methods.ion: -------------------------------------------------------------------------------- 1 | let array = [ one two three four ] 2 | let space_string = $join(array) 3 | let comma_string = $join(array ', ') 4 | echo $space_string 5 | echo $comma_string 6 | echo @split(space_string) 7 | echo @split(comma_string ', ') 8 | 9 | let array = ["one two" "three four" "five six" "seven eight" "nine ten"] 10 | echo $len(@array) 11 | for element in 0..$len(@array) 12 | echo @array[$element] 13 | end 14 | 15 | let string = "one 😉😉😉 two 😉😉😉 three 😉😉😉 four 😉😉😉 five" 16 | echo $len(string) $len_bytes(string) 17 | for grapheme in 0..$len(string) 18 | echo $string[$grapheme] 19 | end 20 | 21 | echo $replace("one two one two" one 1) 22 | echo $replacen("one one one one" one 1 3) 23 | echo $repeat("one " 5) 24 | 25 | echo $join([one two three] "\n") 26 | echo $join([one two three] "\t") 27 | echo $join([one two three] '\\n') 28 | echo $join([one two three] "\\\\n") 29 | echo $replace($join([one two three] "\n") "\n" "\t") 30 | let a = "applesauce" 31 | let pos = $find(a "s") 32 | let array = [@split_at(a $pos)] 33 | echo $join(array "\n") 34 | 35 | let a = [1 2 3 4 5] 36 | let b = "1 2 3 4 5" 37 | echo $join(@split(b " ") $join(a " ")) 38 | 39 | echo $regex_replace("one two onemy anemy town" "\ o|\ a" "\ e") 40 | 41 | echo $unescape('one two\"\tone') 42 | echo $escape("'one two'") 43 | 44 | let spacey = " Spacey " 45 | echo $trim(" So Space ")! 46 | echo $trim($spacey)! 47 | echo $trim_end(" So Space ")! 48 | echo $trim_end($spacey)! 49 | echo $trim_start(" So Space ")! 50 | echo $trim_start($spacey)! 51 | -------------------------------------------------------------------------------- /src/lib/shell/pipe_exec/streams.rs: -------------------------------------------------------------------------------- 1 | use crate::PipelineError; 2 | use nix::unistd; 3 | use std::{ 4 | fs::File, 5 | io, 6 | os::unix::io::{AsRawFd, FromRawFd}, 7 | }; 8 | 9 | /// Use dup2 to replace `old` with `new` using `old`s file descriptor ID 10 | fn redir(old: &Option, new: &F) -> Result<(), PipelineError> { 11 | if let Some(old) = old.as_ref().map(AsRawFd::as_raw_fd) { 12 | unistd::dup2(old, new.as_raw_fd()).map_err(PipelineError::CloneFdFailed)?; 13 | } 14 | Ok(()) 15 | } 16 | 17 | /// Duplicates STDIN, STDOUT, and STDERR; in that order; and returns them as `File`s. 18 | /// Why, you ask? A simple safety mechanism to ensure that the duplicated FDs are closed 19 | /// when dropped. 20 | pub fn duplicate() -> nix::Result<(Option, File, File)> { 21 | // STDIN may have been closed for a background shell, so it is ok if it cannot be duplicated. 22 | let stdin = 23 | unistd::dup(io::stdin().as_raw_fd()).ok().map(|fd| unsafe { File::from_raw_fd(fd) }); 24 | 25 | let stdout = unsafe { File::from_raw_fd(unistd::dup(io::stdout().as_raw_fd())?) }; 26 | let stderr = unsafe { File::from_raw_fd(unistd::dup(io::stderr().as_raw_fd())?) }; 27 | // And then meld stderr alongside stdin and stdout 28 | Ok((stdin, stdout, stderr)) 29 | } 30 | 31 | #[inline] 32 | pub fn redirect( 33 | inp: &Option, 34 | out: &Option, 35 | err: &Option, 36 | ) -> Result<(), PipelineError> { 37 | redir(inp, &io::stdin())?; 38 | redir(out, &io::stdout())?; 39 | redir(err, &io::stderr()) 40 | } 41 | -------------------------------------------------------------------------------- /src/lib/builtins/conditionals.rs: -------------------------------------------------------------------------------- 1 | use super::Status; 2 | use crate as ion_shell; 3 | use builtins_proc::builtin; 4 | 5 | macro_rules! string_function { 6 | (#[$outer:meta], $method:tt) => { 7 | #[$outer] 8 | pub fn $method(args: &[small::String], _shell: &mut crate::Shell<'_>) -> Status { 9 | if args.len() <= 2 { 10 | return Status::bad_argument(concat!( 11 | "ion: ", 12 | stringify!($method), 13 | ": two arguments must be supplied", 14 | )); 15 | } 16 | args[2..].iter().any(|arg| args[1].$method(arg.as_str())).into() 17 | } 18 | }; 19 | } 20 | 21 | string_function!( 22 | #[builtin( 23 | desc = "check if a given string starts with another one", 24 | man = " 25 | SYNOPSIS 26 | starts-with tests... 27 | 28 | DESCRIPTION 29 | Returns 0 if the first argument starts with any other argument, else returns 0" 30 | )], starts_with); 31 | string_function!( 32 | #[builtin( 33 | desc = "check if a given string ends with another one", 34 | man = " 35 | SYNOPSIS 36 | ends-with tests... 37 | 38 | DESCRIPTION 39 | Returns 0 if the first argument ends with any other argument, else returns 0" 40 | )], ends_with); 41 | string_function!( 42 | #[builtin( 43 | desc = "check if a given string contains another one", 44 | man = " 45 | SYNOPSIS 46 | contains tests... 47 | 48 | DESCRIPTION 49 | Returns 0 if the first argument contains any other argument, else returns 0" 50 | )], contains); 51 | -------------------------------------------------------------------------------- /tests/array_methods.ion: -------------------------------------------------------------------------------- 1 | echo '# ANCHOR: lines' 2 | echo @lines($unescape("firstline\nsecondline")) 3 | for line in @lines($unescape("third\nfourth\nfifth")) 4 | echo $line 5 | end 6 | echo '# ANCHOR_END: lines' 7 | echo '# ANCHOR: split' 8 | echo @split("onetwoone" "two") 9 | for data in @split("person, age, some data" ", ") 10 | echo $data 11 | end 12 | for data in @split("person age data") 13 | echo $data 14 | end 15 | echo '# ANCHOR_END: split' 16 | echo '# ANCHOR: split_at' 17 | echo @split_at("onetwoone" "3") 18 | echo @split_at("FOOBAR" "3") 19 | #echo @split_at("FOOBAR") #ion: expansion error: split_at: requires an argument 20 | #echo @split_at("FOOBAR" "-1") #ion: expansion error: split_at: requires a valid number as an argument 21 | #echo @split_at("FOOBAR" "8") #ion: expansion error: split_at: value is out of bounds 22 | echo '# ANCHOR_END: split_at' 23 | echo '# ANCHOR: bytes' 24 | echo @bytes("onetwo") 25 | echo @bytes("abc") 26 | echo '# ANCHOR_END: bytes' 27 | echo '# ANCHOR: chars' 28 | echo @chars("onetwo") 29 | for char in @chars("foobar") 30 | echo $char 31 | end 32 | echo '# ANCHOR_END: chars' 33 | echo '# ANCHOR: graphemes' 34 | echo @graphemes("onetwo" "3") 35 | for grapheme in @graphemes("foobar") 36 | echo $grapheme 37 | end 38 | echo '# ANCHOR_END: graphemes' 39 | echo '# ANCHOR: reverse' 40 | echo @reverse([1 2 3]) 41 | echo @reverse(["a"]) 42 | let foo = [1 2 3] 43 | echo @reverse(@foo) 44 | echo '# ANCHOR_END: reverse' 45 | echo '# ANCHOR: subst' 46 | let empty = [] 47 | for number in @subst(@empty [1 2 3]) 48 | echo $number 49 | end 50 | echo '# ANCHOR_END: subst' 51 | -------------------------------------------------------------------------------- /tests/variables.ion: -------------------------------------------------------------------------------- 1 | let alpha_numeric_name0 = hello 2 | let _name_with_1_leading_underscore = leading 3 | let __2 = underscores 4 | let ___ = ! 5 | echo $alpha_numeric_name0 $_name_with_1_leading_underscore $__2 $___ 6 | echo -e "\n\n$alpha_numeric_name0" 7 | echo "variables:" 8 | # ANCHOR: variables 9 | let string_variable = "hello string" 10 | let array_variable = [ hello array ] 11 | echo $string_variable 12 | echo @array_variable 13 | # ANCHOR_END: variables 14 | echo "multiple_assignment:" 15 | # ANCHOR: multiple_assignment 16 | let a b = one two 17 | echo $a 18 | echo $b 19 | 20 | let a b = one [two three four] 21 | echo $a 22 | echo @b 23 | # ANCHOR_END: multiple_assignment 24 | echo "type_checked_assignment:" 25 | # ANCHOR: type_checked_assignment 26 | let a:bool = 1 27 | let b:bool = true 28 | let c:bool = n 29 | echo $a $b $c 30 | let fail:bool = "" 31 | 32 | let a:str b:[str] c:int d:[float] = one [two three] 4 [5.1 6.2 7.3] 33 | echo $a 34 | echo @b 35 | echo $c 36 | echo @d 37 | # ANCHOR_END: type_checked_assignment 38 | echo "dropping_variables:" 39 | # ANCHOR: dropping_variables 40 | let string = "hello" 41 | drop string 42 | let array = [ hello world ] 43 | drop array 44 | # ANCHOR_END: dropping_variables 45 | echo "command_local_variables:" 46 | # ANCHOR: command_local_variables 47 | export TEST_VAR=other_value 48 | TEST_VAR=test_var_value echo $TEST_VAR 49 | 50 | TZ=America/New_York date --date="2025-10-01T09:00:00+00:00" +'%Y-%m-%d %R:%S' 51 | 52 | TEST_VAR_ONE=one TEST_VAR_TWO=two TEST_VAR_THREE=three echo $TEST_VAR_THREE $TEST_VAR_TWO $TEST_VAR_ONE 53 | # ANCHOR_END: command_local_variables 54 | -------------------------------------------------------------------------------- /manual/src/variables/06-scopes.md: -------------------------------------------------------------------------------- 1 | # Scopes 2 | 3 | A scope is a batch of commands, often ended by `end`. 4 | Things like `if`, `while`, etc all take a scope to execute. 5 | 6 | In ion, just like most other languages, all variables are destroyed once the scope they were defined in is gone. 7 | Similarly, variables from other scopes can still be overriden. 8 | However, ion has no dedicated keyword for updating an existing variable currently, 9 | so the first invokation of `let` gets to "own" the variable. 10 | 11 | *This is an early implementation and will be improved upon with time* 12 | 13 | ```sh 14 | let x = 5 # defines x 15 | 16 | # This will always execute. 17 | # Only reason for this check is to show how 18 | # variables defined inside it are destroyed. 19 | if test 1 == 1 20 | let x = 2 # updates existing x 21 | let y = 3 # defines y 22 | 23 | # end of scope, y is deleted since it's owned by it 24 | end 25 | 26 | echo $x # prints 2 27 | echo $y # prints nothing, y is deleted already 28 | ``` 29 | 30 | ## Functions 31 | 32 | Functions have the scope they were defined in. 33 | This ensures they don't use any unintended local variables that only work in some cases. 34 | Once again, this matches the behavior of most other languages, apart from perhaps LOLCODE. 35 | 36 | ```sh 37 | let x = 5 # defines x 38 | 39 | fn print_vars 40 | echo $x # prints 2 because it was updated before the function was called 41 | echo $y # prints nothing, y is owned by another scope 42 | end 43 | 44 | if test 1 == 1 45 | let x = 2 # updates existing x 46 | let y = 3 # defines y 47 | print_vars 48 | end 49 | ``` 50 | -------------------------------------------------------------------------------- /tests/map_vars.ion: -------------------------------------------------------------------------------- 1 | echo '# ANCHOR: testing_maps' 2 | let map:hmap[str] = [key1=one key3=three] 3 | let map[key2] = two 4 | echo @map[key1] 5 | echo @map[key2] 6 | echo @map[key3] 7 | 8 | let map:hmap[int] = [uno=1 dos=2 tres=3] 9 | echo @map[uno] 10 | echo @map[tres] 11 | echo @map[dos] 12 | 13 | let map:hmap[[int]] = [key1=[1 2 3 4 5] key2=[6 7 8]] 14 | echo @map[key1] 15 | echo @map[key2] 16 | 17 | let map:bmap[str] = [key1=one key3=three] 18 | echo @map 19 | let map[key2] = two 20 | echo @map 21 | echo @map[key1] 22 | echo @map[key2] 23 | echo @map[key3] 24 | 25 | let map:bmap[float] = [ichi=1.0 ni=2.0 san=3.0] 26 | echo @map 27 | 28 | let map[ichi] = foo 29 | echo @map 30 | let map[ni] = bar 31 | echo @map 32 | echo '# ANCHOR_END: testing_maps' 33 | echo '# ANCHOR: hashmap' 34 | let hashmap:hmap[str] = [ blue=pc27 red=pc2 green=pc15 ] 35 | let x = blue 36 | echo @hashmap[$x] @hashmap[red] # fetch values 37 | let hashmap[orange] = pc22 # add new key with value 38 | #echo @keys(hashmap) #get keys 39 | #echo @values(hashmap) #get values 40 | #echo @hashmap #get keys and values 41 | #for key value in @hashmap #use keys and values 42 | # echo $key: $value 43 | #end 44 | echo '# ANCHOR_END: hashmap' 45 | echo '# ANCHOR: btreemap' 46 | let btreemap:bmap[str] = [ pc2=red pc15=green pc27=blue ] 47 | let x = pc2 48 | echo @btreemap[$x] @btreemap[pc15] # fetch values 49 | let btreemap[orange] = pc22 # add new key with value 50 | echo @keys(btreemap) #get keys 51 | echo @values(btreemap) #get values 52 | echo @btreemap #get keys and values 53 | for key value in @btreemap #use keys and values 54 | echo $key: $value 55 | end 56 | echo '# ANCHOR_END: btreemap' 57 | -------------------------------------------------------------------------------- /tests/script_exec.out: -------------------------------------------------------------------------------- 1 | one one 2 | one twoone 3 | o n e t w o 4 | 111 110 101 116 119 111 5 | o n e t w o 6 | firstline secondline 7 | 3 2 1 8 | a 9 | 3 2 1 10 | 1 11 | 2 12 | 3 13 | 4 14 | 5 15 | 6 16 | 1 17 | 2 18 | 3 19 | 4 20 | 5 21 | 6 22 | a 23 | b 24 | c 25 | d 26 | e 27 | a 28 | b 29 | c 30 | d 31 | e 32 | 1 33 | 2 34 | 3 35 | 4 36 | 5 37 | 6 38 | a 39 | b 40 | c 41 | d 42 | e 43 | 1 2 3 4 5 6 44 | a b c d e 45 | 1 2 3 4 5 6 a b c d e 46 | 1 47 | 2 48 | 3 49 | 1 50 | 2 51 | 3 52 | 1 53 | 2 54 | 3 55 | 1 56 | 1 2 57 | 1 2 3 58 | 4 5 59 | 1 2 3 4 5 60 | 😉 61 | 😉 62 | 😉 63 | 1 2 3 4 5 64 | pass 65 | 1A1 1A2 1B1 1B2 66 | 0abc2def5g 0abc2def5h 0abc2def5i 0abc2def6g 0abc2def6h 0abc2def6i 0abc2def7g 0abc2def7h 0abc2def7i 0abc3def5g 0abc3def5h 0abc3def5i 0abc3def6g 0abc3def6h 0abc3def6i 0abc3def7g 0abc3def7h 0abc3def7i 0abc4def5g 0abc4def5h 0abc4def5i 0abc4def6g 0abc4def6h 0abc4def6i 0abc4def7g 0abc4def7h 0abc4def7i 1abc2def5g 1abc2def5h 1abc2def5i 1abc2def6g 1abc2def6h 1abc2def6i 1abc2def7g 1abc2def7h 1abc2def7i 1abc3def5g 1abc3def5h 1abc3def5i 1abc3def6g 1abc3def6h 1abc3def6i 1abc3def7g 1abc3def7h 1abc3def7i 1abc4def5g 1abc4def5h 1abc4def5i 1abc4def6g 1abc4def6h 1abc4def6i 1abc4def7g 1abc4def7h 1abc4def7i 67 | -1 0 1 68 | 2 1 0 69 | a b c 70 | d c b 71 | A B C 72 | D C B 73 | -1 0 1 74 | 2 1 0 75 | a b c 76 | d c b 77 | A B C 78 | D C B 79 | 0 2 4 80 | a c e 81 | A C E 82 | 0 -2 -4 83 | e c a 84 | E C A 85 | 0 2 4 86 | a c e 87 | A C E 88 | 0 -2 -4 89 | e c 90 | E C 91 | 1 92 | 2 93 | 3 94 | 4 95 | 5 96 | 1 97 | 2 98 | 3 99 | 4 100 | 5 101 | true 102 | false 103 | foo 104 | bar 105 | -------------------------------------------------------------------------------- /members/ranges/src/index.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | /// Index into a vector-like object 4 | #[derive(Debug, PartialEq, Copy, Clone)] 5 | pub enum Index { 6 | /// Index starting from the beginning of the vector, where `Forward(0)` 7 | /// is the first element 8 | Forward(usize), 9 | /// Index starting from the end of the vector, where `Backward(0)` is the 10 | /// last element. ` 11 | Backward(usize), 12 | } 13 | 14 | impl Display for Index { 15 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 16 | match self { 17 | Index::Forward(index) => write!(f, "{}", index), 18 | Index::Backward(actual_index) => { 19 | let minus_index = actual_index + 1; 20 | write!(f, "-{}", minus_index) 21 | } 22 | } 23 | } 24 | } 25 | 26 | impl Index { 27 | pub fn resolve(&self, vector_length: usize) -> Option { 28 | match *self { 29 | Index::Forward(n) => Some(n), 30 | Index::Backward(n) if n >= vector_length => None, 31 | Index::Backward(n) => Some(vector_length - (n + 1)), 32 | } 33 | } 34 | 35 | /// Construct an index using the following convetions: 36 | /// - A positive value `n` represents `Forward(n)` 37 | /// - A negative value `-n` reprents `Backwards(n - 1)` such that: 38 | /// ```ignore,rust 39 | /// assert_eq!(Index::new(-1), Index::Backward(0)) 40 | /// ``` 41 | pub fn new(input: isize) -> Index { 42 | if input < 0 { 43 | Index::Backward((input.abs() as usize) - 1) 44 | } else { 45 | Index::Forward(input.abs() as usize) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /manual/src/jobs.md: -------------------------------------------------------------------------------- 1 | # Job Control 2 | 3 | ## Disowning Processes 4 | 5 | Ion features a `disown` command which supports the following flags: 6 | 7 | - **-r**: Remove all running jobs from the background process list. 8 | - **-h**: Specifies that each job supplied will not receive the `SIGHUP` signal when the shell 9 | receives a `SIGHUP`. 10 | - **-a**: If no job IDs were supplied, remove all jobs from the background process list. 11 | 12 | Unlike Bash, job arguments are their specified job IDs. 13 | 14 | ## Foreground & Background Tasks 15 | 16 | When a foreground task is stopped with the **Ctrl+Z** signal, that process will be added to the 17 | background process list as a stopped job. When a supplied command ends with the **&** operator, 18 | this will specify to run the task the background as a running job. To resume a stopped job, 19 | executing the `bg ` command will send a `SIGCONT` to the specified job ID, hence resuming 20 | the job. The `fg` command will similarly do the same, but also set that task as the foreground 21 | process. If no argument is given to either `bg` or `fg`, then the previous job will be used 22 | as the input. 23 | 24 | ## Exiting the Shell 25 | 26 | The `exit` command will exit the shell, sending a `SIGTERM` to any background tasks that are 27 | still active. If no value is supplied to `exit`, then the last status that the shell received 28 | will be used as the exit status. Otherwise, if a numeric value is given to the command, then 29 | that value will be used as the exit status. 30 | 31 | ## Suspending the Shell 32 | 33 | While the shell ignores `SIGTSTP` signals, you can forcefully suspend the shell by executing the 34 | `suspend` command, which forcefully stops the shell via a `SIGSTOP` signal. 35 | -------------------------------------------------------------------------------- /members/types-rs/src/modification.rs: -------------------------------------------------------------------------------- 1 | use super::Value; 2 | 3 | pub trait Modifications { 4 | fn append(&mut self, val: Self) -> bool; 5 | fn prepend(&mut self, val: Self) -> bool; 6 | } 7 | 8 | impl Modifications for Value { 9 | fn append(&mut self, val: Self) -> bool { 10 | match self { 11 | Value::Array(ref mut lhs) => match val { 12 | Value::Array(rhs) => { 13 | lhs.extend(rhs); 14 | true 15 | } 16 | Value::Str(_) => { 17 | lhs.push(val); 18 | true 19 | } 20 | _ => false, 21 | }, 22 | Value::Str(ref mut lhs) => match val { 23 | Value::Str(rhs) => { 24 | lhs.push_str(rhs.as_str()); 25 | true 26 | } 27 | _ => false, 28 | }, 29 | _ => false, 30 | } 31 | } 32 | 33 | fn prepend(&mut self, val: Self) -> bool { 34 | match self { 35 | Value::Array(ref mut lhs) => match val { 36 | Value::Array(rhs) => { 37 | lhs.splice(..0, rhs); 38 | true 39 | } 40 | Value::Str(_) => { 41 | lhs.insert(0, val); 42 | true 43 | } 44 | _ => false, 45 | }, 46 | Value::Str(ref mut lhs) => match val { 47 | Value::Str(rhs) => { 48 | *lhs = format!("{}{}", rhs, lhs).into(); 49 | true 50 | } 51 | _ => false, 52 | }, 53 | _ => false, 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /manual/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Table of Contents 2 | 3 | - [Introduction](introduction.md) 4 | 5 | - [Migrating from POSIX shells](migrating.md) 6 | 7 | - [General rules](general.md) 8 | 9 | - [Read–eval–print loop](repl.md) 10 | 11 | - [Variables](variables/00-variables.md) 12 | 13 | - [String Variables](variables/01-strings.md) 14 | - [Array Variables](variables/02-arrays.md) 15 | - [Map Variables](variables/03-maps.md) 16 | - [Arithmetic Variables](variables/04-arithmetic.md) 17 | - [Exporting Variables](variables/05-exporting.md) 18 | - [Scopes](variables/06-scopes.md) 19 | - [Namespaces (colors, scopes and env vars)](variables/07-namespaces.md) 20 | 21 | - [Expansions](expansions/00-expansions.md) 22 | 23 | - [Variable Expansions](expansions/01-variable.md) 24 | - [Process Expansions](expansions/02-process.md) 25 | - [Brace Expansions](expansions/03-brace.md) 26 | - [Arithmetic Expansions](expansions/04-arithmetic.md) 27 | - [Method Expansions](expansions/05-method.md) 28 | - [String Methods](expansions/06-stringmethods.md) 29 | - [Array Methods](expansions/07-arraymethods.md) 30 | - [Redox OS](expansions/08-redox-os.md) 31 | 32 | - [Slicing Syntax](slicing.md) 33 | 34 | - [Control Flow](control/00-flow.md) 35 | 36 | - [Conditionals](control/01-conditionals.md) 37 | - [Loops](control/02-loops.md) 38 | - [Matches](control/03-matches.md) 39 | 40 | - [Pipelines & Redirection](pipelines.md) 41 | 42 | - [Functions](functions.md) 43 | 44 | - [Script Executions](scripts/00-scripts.md) 45 | - [Sourcing](scripts/01-sourcing_another_file.md) 46 | - [Initialzing File](scripts/02-init_file.md) 47 | 48 | - [Signal Handling](signals.md) 49 | 50 | - [Job Control](jobs.md) 51 | 52 | - [Builtin Commands](builtins.md) 53 | 54 | - [Command History](history.md) 55 | -------------------------------------------------------------------------------- /tests/exists.ion: -------------------------------------------------------------------------------- 1 | # Same tests as in src/builtins/exists.rs:test_evaluate_arguments() 2 | # TODO: find a better way than to write "echo $?" between each test cases 3 | exists 4 | echo $? 5 | exists foo bar 6 | echo $? 7 | 8 | exists --help 9 | echo $? 10 | exists --help unused params 11 | echo $? 12 | 13 | 14 | exists "" 15 | echo $? 16 | exists string 17 | echo $? 18 | exists "string with spaces" 19 | echo $? 20 | exists "-startswithdash" 21 | echo $? 22 | 23 | 24 | exists -a 25 | echo $? 26 | let emptyarray = [] 27 | echo @emptyarray 28 | exists -a emptyarray 29 | echo $? 30 | let array = [ "element" ] 31 | echo @array 32 | exists -a array 33 | echo $? 34 | exists -a array 35 | echo $? 36 | 37 | 38 | exists -b 39 | echo $? 40 | let OLDPATH = $PATH 41 | let PATH = testing/ 42 | echo "PATH = $PATH" 43 | ls -1 $PATH 44 | exists -b executable_file 45 | echo $? 46 | exists -b empty_file 47 | echo $? 48 | exists -b file_does_not_exist 49 | echo $? 50 | let PATH = $OLDPATH 51 | echo "Reset PATH to old path" 52 | 53 | exists -d 54 | echo $? 55 | exists -d testing/ 56 | echo $? 57 | exists -d testing/empty_file 58 | echo $? 59 | exists -d does/not/exist 60 | echo $? 61 | 62 | 63 | exists -f 64 | echo $? 65 | exists -f testing/ 66 | echo $? 67 | exists -f testing/empty_file 68 | echo $? 69 | exists -f does-not-exist 70 | echo $? 71 | 72 | fn testFunc a 73 | echo $a 74 | end 75 | exists --fn testFunc 76 | echo $? 77 | 78 | exists -s 79 | echo $? 80 | let emptyvar = "" 81 | echo "emptyvar = $emptyvar" 82 | exists -s emptyvar 83 | echo $? 84 | let testvar = "foobar" 85 | echo "testvar = $testvar" 86 | exists -s testvar 87 | echo $? 88 | drop testvar 89 | 90 | exists --foo 91 | echo $? 92 | exists -x 93 | echo $? 94 | 95 | echo "testvar = $testvar" 96 | echo This line should not be reached. Accessing an undefined variable is a programming error 97 | -------------------------------------------------------------------------------- /manual/src/repl.md: -------------------------------------------------------------------------------- 1 | # Read-eval-print loop 2 | 3 | ## Implicit `cd` 4 | Like the [Friendly Interactive Shell](https://fishshell.com/), Ion also supports 5 | executing the `cd` command automatically 6 | when given a path. Paths are denoted by beginning with `.`/`/`/`~`, or ending with `/`. 7 | ```sh 8 | ~/Documents # cd ~/Documents 9 | .. # cd .. 10 | .config # cd .config 11 | examples/ # cd examples/ 12 | ``` 13 | 14 | ## Multi-line Arguments 15 | If a line in your script becomes too long, appending `\` will make Ion ignore newlines 16 | and continue reading the next line. 17 | ```sh 18 | command arg arg2 \ 19 | arg3 arg4 \ 20 | arg 5 21 | ``` 22 | 23 | ## Multi-line Strings 24 | If a string needs to contain newlines, you use an open quote. Ion will only 25 | begin parsing supplied commands that are terminated. Either double or single quotes can be used. 26 | ```sh 27 | echo "This is the first line 28 | this is the second line 29 | this is the third line" 30 | ``` 31 | 32 | ## Prompt Function 33 | The prompt may optionally be generated from a function, instead of a string. Due to the need to 34 | perform a fork an capture of its output as prompt, prompts generated from functions aren't as 35 | efficient. Below the requirement to use the function with name **PROMPT**: 36 | ```sh 37 | fn PROMPT 38 | echo -n "${PWD}# " 39 | end 40 | ``` 41 | 42 | ## Key Bindings 43 | There are two pre-set key maps available: **Emacs (default)** and **Vi**. 44 | You can switch between them with the `keybindings` built-in command. 45 | ```sh 46 | keybindings vi 47 | keybindings emacs 48 | ``` 49 | **Vi keybinding**: You can define the displayed indicator for normal and insert modes 50 | with the following variables: 51 | ```sh 52 | $ export VI_NORMAL = "[=] " 53 | $ export VI_INSERT = "[+] " 54 | $ keybindings vi 55 | [+] $ 56 | ``` 57 | -------------------------------------------------------------------------------- /tests/array_test.ion: -------------------------------------------------------------------------------- 1 | echo '# ANCHOR: standardArrays' 2 | let array = [] 3 | echo @array 4 | let array = [1] 5 | for i in @array 6 | echo $i 7 | end 8 | let array = [1 [2 3] 4 [5 6]] 9 | for i in @array 10 | echo $i 11 | end 12 | for i in [1 [2 3] 4 [5 6]] 13 | echo $i 14 | end 15 | echo '# ANCHOR_END: standardArrays' 16 | echo '# ANCHOR: command_substitution' 17 | let array_process = [ @(echo a b c d e) ] 18 | for i in @array_process 19 | echo $i 20 | end 21 | for i in @(echo a b c d e) 22 | echo $i 23 | end 24 | echo '# ANCHOR_END: command_substitution' 25 | echo '# ANCHOR: concatenation' 26 | let new_array = [@array @array_process] 27 | for i in @new_array 28 | echo $i 29 | end 30 | echo '# ANCHOR_END: concatenation' 31 | echo '# ANCHOR: array_as_command_args' 32 | echo @array 33 | echo @array_process 34 | echo @new_array 35 | echo '# ANCHOR_END: array_as_command_args' 36 | echo '# ANCHOR: slice_by_id' 37 | let array = [ 1 2 3 ] 38 | echo @array[0] 39 | echo @array[1] 40 | echo @array[2] 41 | echo [ 1 2 3 ][0] 42 | echo [ 1 2 3 ][1] 43 | echo [ 1 2 3 ][2] 44 | echo @(echo 1 2 3)[0] 45 | echo @(echo 1 2 3)[1] 46 | echo @(echo 1 2 3)[2] 47 | echo '# ANCHOR_END: slice_by_id' 48 | echo '# ANCHOR: slice_by_range' 49 | let array = [ 1 2 3 4 5 ] 50 | echo @array[0..1] 51 | echo @array[0...1] 52 | echo @array[..3] 53 | echo @array[3..] 54 | echo @array[..] 55 | echo '# ANCHOR_END: slice_by_range' 56 | echo '# ANCHOR: slice_with_vars' 57 | for index in 0..3 58 | echo $(echo 😉😉😉)[$index] 59 | end 60 | echo '# ANCHOR_END: slice_with_vars' 61 | echo '# ANCHOR: convert_to_string' 62 | let array = [ 1 2 3 4 5 ] 63 | let as_string = @array 64 | echo $as_string 65 | let array = [1 2 3 4 5 6 7 8 9 10] 66 | echo @array[0 1 7..] 67 | echo @array[2 3 5..] 68 | echo @array[5.. 3 2] 69 | echo @array[5..8 1..3 9 2] 70 | echo '# ANCHOR_END: convert_to_string' 71 | -------------------------------------------------------------------------------- /benches/calls.txt: -------------------------------------------------------------------------------- 1 | qaonukpt 2 | aeqpqwxv 3 | suspend 4 | qbikkjoy 5 | help 6 | true 7 | llempzef 8 | alias 9 | hagppque 10 | olbhijpd 11 | lhgmydgq 12 | fn 13 | xuirqppr 14 | bg 15 | rdpngwtr 16 | popd 17 | qjonappg 18 | isatty 19 | fjxnjdcv 20 | xvvqgbda 21 | dtowcqel 22 | tuhlmnwv 23 | uxfdueiz 24 | rcrrmexe 25 | pgnuuyzu 26 | kbxlargm 27 | tkrzcapt 28 | gnrdqkfm 29 | contains 30 | starts-with 31 | type 32 | gwvisqpo 33 | bdinggto 34 | xwlztjsx 35 | iwepsrfz 36 | lqssdiem 37 | tbohximc 38 | ends-with 39 | zrgzwwvx 40 | cezvmwvc 41 | random 42 | pijczhyk 43 | history 44 | ohosyvvr 45 | matches 46 | xnoqyzqg 47 | exit 48 | test 49 | zawqmsmj 50 | read 51 | lswqkzoh 52 | ifszbrtt 53 | eosrvadi 54 | ajcyiqqh 55 | is 56 | djcfrkoe 57 | kptesdea 58 | jlacsdip 59 | cd 60 | qzafunhq 61 | hcylaiir 62 | jknabygk 63 | ysapelqf 64 | lxlkcxzz 65 | wgcqkjyh 66 | thrjmpea 67 | cpxqmfom 68 | pushd 69 | fxuxznrd 70 | lueaxesf 71 | status 72 | gaqbnmei 73 | ubbokbat 74 | ufwjnpxb 75 | bool 76 | tmnaokwm 77 | fyiuysrz 78 | nfzxwkwz 79 | ughirptq 80 | source 81 | yaqacdcx 82 | lfjdhamz 83 | bslydqaq 84 | dhekmipk 85 | uekytygj 86 | obupdgtg 87 | fg 88 | ssvomqow 89 | disown 90 | mvmvbwgy 91 | unalias 92 | rtvvibsz 93 | dikuzfes 94 | grjqxefn 95 | eval 96 | hnabnohc 97 | akozolzn 98 | vgecngwb 99 | ymbbjyqm 100 | drop 101 | ecaqmokz 102 | exec 103 | vsinblzi 104 | which 105 | olqrykru 106 | rtrfrhqj 107 | eq 108 | wcufcnow 109 | olpkjaec 110 | wqysdkmo 111 | fwruleaq 112 | exists 113 | echo 114 | krqrpkbe 115 | sdvphfxz 116 | calc 117 | bumnoytn 118 | voimtbwi 119 | dynqtlbb 120 | jqfzilwp 121 | wpvhibrq 122 | ihnrmqmq 123 | azhuqdvb 124 | dsalzuhx 125 | ialmytde 126 | enswgpme 127 | jvwvqubj 128 | false 129 | dirs 130 | wait 131 | ayfgyopw 132 | jobs 133 | zocvwdzj 134 | sgtdofpj 135 | xnsbzczc 136 | yhjreitd 137 | tanglida 138 | stkzavte 139 | gfjeqxkw 140 | set 141 | -------------------------------------------------------------------------------- /manual/src/expansions/03-brace.md: -------------------------------------------------------------------------------- 1 | # Brace Expansions 2 | 3 | Sometimes you may want to generate permutations of strings, which is typically used to shorten 4 | the amount of characters you need to type when specifying multiple arguments. This can be achieved 5 | through the use of braces, where braced tokens are comma-delimited and used as infixes. Any 6 | non-whitespace characters connected to brace expansions will also be included within the brace 7 | permutations. 8 | 9 | **NOTE:** Brace expansions will not work within double quotes. 10 | ```sh 11 | {{#include ../../../tests/brace_exp.ion:single_brace_expansion}} 12 | ``` 13 | ```txt 14 | {{#include ../../../tests/brace_exp.out:single_brace_expansion}} 15 | ``` 16 | 17 | Multiple brace tokens may occur within a braced collection, where each token expands the 18 | possible permutation variants. 19 | ```sh 20 | {{#include ../../../tests/brace_exp.ion:multi_brace_expansion}} 21 | ``` 22 | ```txt 23 | {{#include ../../../tests/brace_exp.out:multi_brace_expansion}} 24 | ``` 25 | Brace tokens may even contain brace tokens of their own, as each brace element will also be 26 | expanded. 27 | ```sh 28 | {{#include ../../../tests/brace_exp.ion:nested_brace_expansion}} 29 | ``` 30 | ```txt 31 | {{#include ../../../tests/brace_exp.out:nested_brace_expansion}} 32 | ``` 33 | Braces elements may also be designated as ranges, which may be either inclusive or exclusive, 34 | descending or ascending, numbers or latin alphabet characters. 35 | ```sh 36 | {{#include ../../../tests/brace_exp.ion:range_brace_expansion}} 37 | ``` 38 | ```txt 39 | {{#include ../../../tests/brace_exp.out:range_brace_expansion}} 40 | ``` 41 | It's also important to note that, as range brace expansions return arrays, they may be used in for loops. 42 | ```sh 43 | {{#include ../../../tests/brace_exp.ion:range_brace_expansion_as_array}} 44 | ``` 45 | ```txt 46 | {{#include ../../../tests/brace_exp.out:range_brace_expansion_as_array}} 47 | ``` 48 | -------------------------------------------------------------------------------- /sh-interrupt/mkdep.sh: -------------------------------------------------------------------------------- 1 | #! ./testshell 2 | 3 | ./testshell -c './testshell `which mkdep` catcher.c ; echo $?' 4 | ./testshell -c './testshell `which mkdep` catcher.c ; echo $?' 5 | ./testshell -c './testshell `which mkdep` catcher.c ; echo $?' 6 | ./testshell -c './testshell `which mkdep` catcher.c ; echo $?' 7 | ./testshell -c './testshell `which mkdep` catcher.c ; echo $?' 8 | ./testshell -c './testshell `which mkdep` catcher.c ; echo $?' 9 | ./testshell -c './testshell `which mkdep` catcher.c ; echo $?' 10 | ./testshell -c './testshell `which mkdep` catcher.c ; echo $?' 11 | ./testshell -c './testshell `which mkdep` catcher.c ; echo $?' 12 | ./testshell -c './testshell `which mkdep` catcher.c ; echo $?' 13 | ./testshell -c './testshell `which mkdep` catcher.c ; echo $?' 14 | ./testshell -c './testshell `which mkdep` catcher.c ; echo $?' 15 | ./testshell -c './testshell `which mkdep` catcher.c ; echo $?' 16 | ./testshell -c './testshell `which mkdep` catcher.c ; echo $?' 17 | ./testshell -c './testshell `which mkdep` catcher.c ; echo $?' 18 | ./testshell -c './testshell `which mkdep` catcher.c ; echo $?' 19 | ./testshell -c './testshell `which mkdep` catcher.c ; echo $?' 20 | ./testshell -c './testshell `which mkdep` catcher.c ; echo $?' 21 | ./testshell -c './testshell `which mkdep` catcher.c ; echo $?' 22 | ./testshell -c './testshell `which mkdep` catcher.c ; echo $?' 23 | ./testshell -c './testshell `which mkdep` catcher.c ; echo $?' 24 | ./testshell -c './testshell `which mkdep` catcher.c ; echo $?' 25 | ./testshell -c './testshell `which mkdep` catcher.c ; echo $?' 26 | ./testshell -c './testshell `which mkdep` catcher.c ; echo $?' 27 | ./testshell -c './testshell `which mkdep` catcher.c ; echo $?' 28 | ./testshell -c './testshell `which mkdep` catcher.c ; echo $?' 29 | ./testshell -c './testshell `which mkdep` catcher.c ; echo $?' 30 | ./testshell -c './testshell `which mkdep` catcher.c ; echo $?' 31 | ./testshell -c './testshell `which mkdep` catcher.c ; echo $?' 32 | -------------------------------------------------------------------------------- /members/types-rs/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod math; 2 | mod modification; 3 | pub mod types; 4 | 5 | pub use self::{ 6 | math::{EuclDiv, OpError, Pow}, 7 | modification::Modifications, 8 | }; 9 | use itertools::Itertools; 10 | use std::fmt; 11 | 12 | #[derive(Clone, Debug, PartialEq)] 13 | pub enum Value { 14 | Str(types::Str), 15 | Alias(types::Alias), 16 | Array(types::Array), 17 | HashMap(types::HashMap), 18 | BTreeMap(types::BTreeMap), 19 | Function(T), 20 | None, 21 | } 22 | 23 | impl Eq for Value {} 24 | 25 | // this one’s only special because of the lifetime parameter 26 | impl<'a, T> From<&'a str> for Value { 27 | fn from(string: &'a str) -> Self { Value::Str(string.into()) } 28 | } 29 | 30 | macro_rules! value_from_type { 31 | ($arg:ident: $from:ty => $variant:ident($inner:expr)) => { 32 | impl From<$from> for Value { 33 | fn from($arg: $from) -> Self { Value::$variant($inner) } 34 | } 35 | }; 36 | } 37 | 38 | value_from_type!(string: types::Str => Str(string)); 39 | value_from_type!(string: String => Str(string.into())); 40 | value_from_type!(alias: types::Alias => Alias(alias)); 41 | value_from_type!(array: types::Array => Array(array)); 42 | value_from_type!(hmap: types::HashMap => HashMap(hmap)); 43 | value_from_type!(bmap: types::BTreeMap => BTreeMap(bmap)); 44 | 45 | impl fmt::Display for Value { 46 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 47 | match *self { 48 | Value::Str(ref str_) => write!(f, "{}", str_), 49 | Value::Alias(ref alias) => write!(f, "{}", **alias), 50 | Value::Array(ref array) => write!(f, "{}", array.iter().format(" ")), 51 | Value::HashMap(ref map) => write!(f, "{}", map.values().format(" ")), 52 | Value::BTreeMap(ref map) => write!(f, "{}", map.values().format(" ")), 53 | _ => write!(f, ""), 54 | } 55 | } 56 | } 57 | 58 | #[cfg(test)] 59 | mod trait_test; 60 | -------------------------------------------------------------------------------- /src/binary/readln.rs: -------------------------------------------------------------------------------- 1 | use super::{completer::IonCompleter, InteractiveShell}; 2 | use ion_shell::Shell; 3 | use nix::fcntl::{fcntl, FcntlArg, OFlag}; 4 | use std::io::ErrorKind; 5 | 6 | impl<'a> InteractiveShell<'a> { 7 | /// Make sure to reset the fd to blocking mode 8 | fn change_blocking(fd: std::os::unix::io::RawFd) { 9 | fcntl(fd, FcntlArg::F_SETFL(OFlag::O_RDWR)).unwrap(); 10 | } 11 | 12 | /// Ion's interface to Liner's `read_line` method, which handles everything related to 13 | /// rendering, controlling, and getting input from the prompt. 14 | pub fn readln)>(&self, prep_for_exit: &T) -> Option { 15 | Self::change_blocking(0); 16 | Self::change_blocking(1); 17 | Self::change_blocking(2); 18 | let prompt = self.prompt(); 19 | let line = self.context.borrow_mut().read_line( 20 | prompt, 21 | None, 22 | &mut IonCompleter::new(&self.shell.borrow()), 23 | ); 24 | 25 | match line { 26 | Ok(line) => { 27 | if line.bytes().next() != Some(b'#') 28 | && line.bytes().any(|c| !c.is_ascii_whitespace()) 29 | { 30 | self.terminated.set(false); 31 | } 32 | Some(line) 33 | } 34 | // Handles Ctrl + C 35 | Err(ref err) if err.kind() == ErrorKind::Interrupted => None, 36 | // Handles Ctrl + D 37 | Err(ref err) if err.kind() == ErrorKind::UnexpectedEof => { 38 | let mut shell = self.shell.borrow_mut(); 39 | if self.terminated.get() && shell.exit_block().is_err() { 40 | prep_for_exit(&mut shell); 41 | std::process::exit(shell.previous_status().as_os_code()) 42 | } 43 | None 44 | } 45 | Err(err) => { 46 | eprintln!("ion: liner: {}", err); 47 | None 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/array_vars.ion: -------------------------------------------------------------------------------- 1 | echo '# ANCHOR: array_assignments' 2 | let array = [1 2 3] 3 | let array[0] = 4 4 | echo @array 5 | let value = 1 6 | let array[$value] = 5 7 | echo @array 8 | let array[2]:int = 6 9 | echo @array 10 | echo '# ANCHOR_END: array_assignments' 11 | echo '# ANCHOR: array_ops' 12 | let array = [ 4 4 5 5 5 6 6 6 6 ] 13 | echo @array 14 | let array ++= [ 1 2 3 ] 15 | echo @array 16 | let array ::= [ 1 2 3 ] 17 | echo @array 18 | let array \\= [ 4 5 6 ] 19 | echo @array 20 | let array = [ 1 2 2 3 3 3 ] 21 | echo @array 22 | let array ++= 4 23 | echo @array 24 | let array ::= 0 25 | echo @array 26 | let array \\= 3 27 | echo @array 28 | echo '# ANCHOR_END: array_ops' 29 | echo '# ANCHOR: create_array' 30 | let array = [ one two 'three four' ] 31 | echo @array 32 | echo '# ANCHOR_END: create_array' 33 | echo '# ANCHOR: index_array' 34 | let array = [ 1 2 3 4 5 ] 35 | echo @array[0] 36 | echo @array[2..=4] 37 | echo '# ANCHOR_END: index_array' 38 | echo '# ANCHOR: array_copy' 39 | let array = [ 1 2 3 ] 40 | let array_copy = [ @array ] 41 | echo @array_copy 42 | echo '# ANCHOR_END: array_copy' 43 | echo '# ANCHOR: array_join' 44 | let array = [ hello world ] 45 | let other_array = [ this is the ion ] 46 | let array = [ @array @other_array shell ] 47 | let as_string = @array 48 | echo @array 49 | echo $as_string 50 | echo '# ANCHOR_END: array_join' 51 | echo '# ANCHOR: array_concat_var_strip' 52 | let array = [2 3] 53 | let array ++= [4 5] # append 54 | let array ::= [0 1] # append before beginning [0 1] 55 | let array \\= [2 3] # remove variables 2 and 3 56 | echo @array 57 | let array ++= 6 # same with single variables 58 | let array ::= -1 59 | let array \\= 0 60 | echo @array 61 | echo '# ANCHOR_END: array_concat_var_strip' 62 | echo '# ANCHOR: practical_array' 63 | mkdir -p _tmp _tmp/t1 _tmp/t2 64 | cd _tmp 65 | let args = [-a --file-type] 66 | ls @args # use args as arguments for command ls 67 | let res = [ @(ls) ] # get result of ls as array res 68 | echo @res # output the array res 69 | cd .. 70 | rm -fr _tmp 71 | echo '# ANCHOR_END: practical_array' 72 | -------------------------------------------------------------------------------- /members/ranges/src/range.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | use super::Index; 4 | 5 | /// A range of values in a vector-like object 6 | #[derive(Debug, PartialEq, Copy, Clone)] 7 | pub struct Range { 8 | /// Starting index 9 | start: Index, 10 | /// Ending index 11 | end: Index, 12 | /// Is this range inclusive? If false, this object represents a half-open 13 | /// range of [start, end), otherwise [start, end] 14 | inclusive: bool, 15 | } 16 | 17 | impl Display for Range { 18 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 19 | write!(f, "({},{})", self.start, self.end) 20 | } 21 | } 22 | 23 | impl Range { 24 | /// Returns the bounds of this range as a tuple containing: 25 | /// - The starting point of the range 26 | /// - The length of the range 27 | /// ```ignore,rust 28 | /// let vec = vec![0, 1, 2, 3, 4, 5, 6, 7, 8]; 29 | /// let range = Range::exclusive(Index::new(1), Index::new(5)); 30 | /// let (start, size) = range.bounds(vec.len()).unwrap(); 31 | /// let expected = vec![1, 2, 3, 4]; 32 | /// let selection = vec.iter().skip(start).take(size).collect::>(); 33 | /// assert_eq!(expected, selection); 34 | /// ``` 35 | pub fn bounds(&self, vector_length: usize) -> Option<(usize, usize)> { 36 | let start = self.start.resolve(vector_length)?; 37 | let end = self.end.resolve(vector_length)?; 38 | if end < start { 39 | None 40 | } else if self.inclusive { 41 | Some((start, end - start + 1)) 42 | } else { 43 | Some((start, end - start)) 44 | } 45 | } 46 | 47 | pub fn exclusive(start: Index, end: Index) -> Range { Range { start, end, inclusive: false } } 48 | 49 | pub fn inclusive(start: Index, end: Index) -> Range { Range { start, end, inclusive: true } } 50 | 51 | pub fn from(start: Index) -> Range { Range { start, end: Index::new(-1), inclusive: true } } 52 | 53 | pub fn to(end: Index) -> Range { Range { start: Index::new(0), end, inclusive: false } } 54 | } 55 | -------------------------------------------------------------------------------- /members/ranges/src/select.rs: -------------------------------------------------------------------------------- 1 | use super::{parse_index_range, Index, Range}; 2 | use std::{ 3 | iter::{empty, FromIterator}, 4 | str::FromStr, 5 | }; 6 | 7 | /// Represents a filter on a vector-like object 8 | #[derive(Debug, PartialEq, Clone)] 9 | pub enum Select { 10 | /// Select all elements 11 | All, 12 | /// Select a single element based on its index 13 | Index(Index), 14 | /// Select a range of elements 15 | Range(Range), 16 | /// Select an element by mapped key 17 | Key(K), 18 | } 19 | 20 | pub trait SelectWithSize { 21 | type Item; 22 | fn select(&mut self, selection: &Select, len: usize) -> O 23 | where 24 | O: FromIterator; 25 | } 26 | 27 | impl SelectWithSize for I 28 | where 29 | I: DoubleEndedIterator, 30 | { 31 | type Item = T; 32 | 33 | fn select(&mut self, s: &Select, size: usize) -> O 34 | where 35 | O: FromIterator, 36 | { 37 | match s { 38 | Select::Key(_) => empty().collect(), 39 | Select::All => self.collect(), 40 | Select::Index(Index::Forward(idx)) => self.nth(*idx).into_iter().collect(), 41 | Select::Index(Index::Backward(idx)) => self.rev().nth(*idx).into_iter().collect(), 42 | Select::Range(range) => range 43 | .bounds(size) 44 | .map(|(start, length)| self.skip(start).take(length).collect()) 45 | .unwrap_or_else(|| empty().collect()), 46 | } 47 | } 48 | } 49 | 50 | impl FromStr for Select { 51 | type Err = (); 52 | 53 | fn from_str(data: &str) -> Result { 54 | if data == ".." { 55 | Ok(Select::All) 56 | } else if let Ok(index) = data.parse::() { 57 | Ok(Select::Index(Index::new(index))) 58 | } else if let Some(range) = parse_index_range(data) { 59 | Ok(Select::Range(range)) 60 | } else { 61 | Ok(Select::Key(K::from_str(data).map_err(|_| ())?)) 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: "redoxos/redoxer" 2 | 3 | stages: 4 | - lint 5 | - test 6 | - deploy 7 | 8 | variables: 9 | CARGO_HOME: $CI_PROJECT_DIR/cargo 10 | before_script: 11 | - apt-get update -qq 12 | - apt-get install -qq libssl-dev pkg-config build-essential curl git 13 | 14 | format: 15 | stage: lint 16 | script: 17 | - rustup component add rustfmt 18 | - cargo fmt --all -- --check 19 | 20 | linux: 21 | stage: test 22 | cache: 23 | key: linux 24 | paths: 25 | - cargo/ 26 | - target/ 27 | script: 28 | - cargo check --features=piston 29 | - FULL=1 make tests 30 | 31 | redox: 32 | stage: test 33 | cache: 34 | key: redox 35 | paths: 36 | - cargo/ 37 | - target/ 38 | before_script: 39 | - apt-get update -qq 40 | - apt-get install -qq build-essential curl git 41 | script: 42 | - redoxer build # TODO: do test when it does not hang 43 | 44 | link-check: 45 | stage: lint 46 | rules: 47 | - if: '$CI_COMMIT_BRANCH == "master"' 48 | script: 49 | - PATH=$PATH:$CARGO_HOME/bin 50 | - curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash 51 | - cargo binstall mdbook mdbook-linkcheck 52 | - make manual 53 | - mdbook build manual 54 | 55 | pages: 56 | stage: deploy 57 | rules: 58 | - if: $CI_MERGE_REQUEST_IID 59 | when: never 60 | script: 61 | - PATH=$PATH:$CARGO_HOME/bin 62 | - curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash 63 | - cargo binstall mdbook 64 | - make manual 65 | - mdbook build manual 66 | - mv manual/book public 67 | artifacts: 68 | paths: 69 | - public 70 | 71 | compare-benchmarks: 72 | image: rustlang/rust:nightly 73 | stage: test 74 | when: manual 75 | allow_failure: true 76 | except: [master] 77 | script: 78 | - apt-get install -y libboost-dev jq bc 79 | - sh ./ci/run_benchmark.sh 80 | artifacts: 81 | reports: 82 | junit: target/report.xml 83 | paths: [target/criterion] 84 | -------------------------------------------------------------------------------- /members/types-rs/src/types.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::from_over_into)] 2 | use super::Value; 3 | use std::{ 4 | collections::{BTreeMap as StdBTreeMap, HashMap as StdHashMap}, 5 | iter::FromIterator, 6 | ops::{Deref, DerefMut}, 7 | }; 8 | 9 | pub type Array = Vec>; 10 | pub type HashMap = StdHashMap>; 11 | pub type BTreeMap = StdBTreeMap>; 12 | pub type Str = small::String; 13 | 14 | #[derive(Clone, Debug, PartialEq, Hash, Eq, Default)] 15 | pub struct Alias(pub Str); 16 | 17 | impl Alias { 18 | pub fn empty() -> Self { Alias(Str::with_capacity(1)) } 19 | } 20 | 21 | impl Deref for Alias { 22 | type Target = Str; 23 | 24 | fn deref(&self) -> &Self::Target { &self.0 } 25 | } 26 | 27 | impl DerefMut for Alias { 28 | fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } 29 | } 30 | 31 | impl Into for Alias { 32 | fn into(self) -> Str { self.0 } 33 | } 34 | 35 | impl FromIterator> for Value { 36 | fn from_iter>>(items: I) -> Self { 37 | Value::Array(items.into_iter().collect()) 38 | } 39 | } 40 | 41 | /// Construct a new Array containing the given arguments 42 | /// 43 | /// `array!` acts like the standard library's `vec!` macro, and can be thought 44 | /// of as a shorthand for: 45 | /// ```ignore,rust 46 | /// Array::from_vec(vec![...]) 47 | /// ``` 48 | /// Additionally it will call `Into::into` on each of its members so that one 49 | /// can pass in any type with some `To` implementation; they will 50 | /// automatically be converted to owned SmallStrings. 51 | /// ```ignore,rust 52 | /// let verbose = Array::from_vec(vec![ 53 | /// "foo".into(), 54 | /// "bar".into(), 55 | /// "baz".into(), 56 | /// "zar".into(), 57 | /// "doz".into(), 58 | /// ]); 59 | /// let concise = array!["foo", "bar", "baz", "zar", "doz"]; 60 | /// assert_eq!(verbose, concise); 61 | /// ``` 62 | #[macro_export] 63 | macro_rules! array [ 64 | ( $($x:expr), *) => ({ 65 | let mut _arr = $crate::types::Array::new(); 66 | $(_arr.push($x.into());)* 67 | _arr 68 | }) 69 | ]; 70 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | prefix ?= usr/local 2 | BINARY = $(prefix)/bin/ion 3 | RELEASE = debug 4 | TOOLCHAIN ?= 1.76.0 5 | 6 | GIT_REVISION=git_revision.txt 7 | SRC=Cargo.toml Cargo.lock $(shell find src members -type f -wholename '*src/*.rs') 8 | VENDOR=.cargo/config vendor.tar.xz 9 | 10 | DEBUG ?= 0 11 | ifeq ($(DEBUG),0) 12 | ARGS += --release 13 | RELEASE = release 14 | endif 15 | 16 | VENDORED ?= 0 17 | ifeq ($(VENDORED),1) 18 | ARGSV += --frozen 19 | endif 20 | 21 | REDOX ?= 0 22 | ifeq ($(REDOX),1) 23 | undefine ARGSV 24 | ARGS += --target x86_64-unknown-redox 25 | TOOLCHAIN = nightly 26 | endif 27 | 28 | RUSTUP ?= 1 29 | ifeq ($(RUSTUP),1) 30 | TOOLCHAIN_ARG = +$(TOOLCHAIN) 31 | endif 32 | 33 | .PHONY: tests all clean distclean install uninstall manual 34 | 35 | all: $(SRC) $(GIT_REVISION) 36 | ifeq ($(REDOX),1) 37 | mkdir -p .cargo 38 | grep redox .cargo/config || cat redox_linker >> .cargo/config 39 | endif 40 | ifeq ($(VENDORED),1) 41 | tar pxf vendor.tar.xz 42 | endif 43 | cargo $(TOOLCHAIN_ARG) build $(ARGS) $(ARGSV) 44 | 45 | manual: 46 | rm -rf manual/builtins 47 | mkdir manual/builtins 48 | cargo build --features man 49 | echo -n "# Builtin commands" > manual/src/builtins.md 50 | for man in manual/builtins/*; do \ 51 | echo "" >> manual/src/builtins.md; \ 52 | echo -n "## " >> manual/src/builtins.md; \ 53 | cat $$man >> manual/src/builtins.md; \ 54 | done 55 | 56 | tests: 57 | cargo $(TOOLCHAIN_ARG) test $(ARGSV) 58 | TOOLCHAIN=$(TOOLCHAIN) bash tests/run_examples.sh 59 | for crate in members/*; do \ 60 | cargo $(TOOLCHAIN_ARG) test $(ARGSV) --manifest-path $$crate/Cargo.toml || exit 1; \ 61 | done 62 | 63 | test.%: 64 | TOOLCHAIN=$(TOOLCHAIN) bash tests/run_examples.sh $@ 65 | 66 | vendor: $(VENDOR) 67 | 68 | $(VENDOR): 69 | rm -rf .cargo vendor vendor.tar.xz 70 | mkdir -p .cargo 71 | cargo vendor | head -n -1 > .cargo/config 72 | echo 'directory = "vendor"' >> .cargo/config 73 | tar pcfJ vendor.tar.xz vendor 74 | rm -rf vendor 75 | 76 | update-shells: 77 | if ! grep ion /etc/shells >/dev/null; then \ 78 | echo $(BINARY) >> /etc/shells; \ 79 | else \ 80 | shell=$(shell grep ion /etc/shells); \ 81 | if [ $$shell != $(BINARY) ]; then \ 82 | sed -i -e "s#$$shell#$(BINARY)#g" /etc/shells; \ 83 | fi \ 84 | fi 85 | 86 | -------------------------------------------------------------------------------- /tests/match.ion: -------------------------------------------------------------------------------- 1 | for i in [1 2 3 4 5 6] 2 | match $((i % 2)) 3 | case 0; echo "Even!" 4 | case 1; echo "Odd!" 5 | end 6 | end 7 | 8 | let out1 = "/foo/bar/baz.tar.gz: application/x-gzip"; 9 | let out2 = "/foo/bar/baz.quxx: application/quxx-archive"; 10 | 11 | fn analyze output 12 | match @split(output)[1] 13 | case application/x-gzip; echo "Use tar -xzf" 14 | case _ ; echo "Unknown file type" 15 | end 16 | end 17 | 18 | analyze $out1 19 | analyze $out2 20 | 21 | fn analyze_regex output 22 | match output 23 | case ".*application/x-gzip"; echo "Use tar -xzf" 24 | case _ ; echo "Unknown file type" 25 | end 26 | end 27 | 28 | analyze $out1 29 | analyze $out2 30 | 31 | fn wildcard input 32 | match $input 33 | case _; echo "WILDCARD!" 34 | case huh; echo "U N R E A C H A B L E" 35 | end 36 | end 37 | 38 | wildcard "FOOOOO" 39 | wildcard "huh" 40 | 41 | fn report usage 42 | match $usage 43 | case @(seq 0 25); echo "Plenty of space my guy" 44 | case @(seq 26 50); echo "Almost half full (or half empty)" 45 | case @(seq 51 75); echo "Getting close to full :O" 46 | case @(seq 76 99); echo "Time for spring cleaning, almost full!" 47 | case _; echo "How did you even do this..." 48 | end 49 | end 50 | 51 | report 37 52 | report 55 53 | report 98 54 | report FOOOO 55 | 56 | fn animated filetype 57 | match $filetype 58 | case ["image/jpeg" "image/png"] 59 | echo "Static :(" 60 | case "image/gif" 61 | echo "Animated :D" 62 | end 63 | end 64 | 65 | animated "image/jpeg" 66 | animated "image/png" 67 | animated "image/gif" 68 | 69 | fn in_range min max val 70 | if test $min -gt $max 71 | let min max = $max $min 72 | end 73 | match $val 74 | case @(seq $min $max); 75 | if test $val -eq $min 76 | echo "$val at minimum" 77 | else if test $val -eq $max 78 | echo "$val at maximum" 79 | else 80 | echo "$val is between $min and $max" 81 | end 82 | case _ 83 | if test $val -lt $min 84 | echo "$val is less than min=$min" 85 | else 86 | echo "$val is more than max=$max" 87 | end 88 | end 89 | end 90 | 91 | in_range 0 10 5 92 | in_range 0 10 0 93 | in_range 0 10 10 94 | in_range 1 10 0 95 | in_range 0 9 10 96 | -------------------------------------------------------------------------------- /src/lib/shell/pipe_exec/foreground.rs: -------------------------------------------------------------------------------- 1 | //! Contains the logic for enabling foreground management. 2 | 3 | use nix::unistd::Pid; 4 | 5 | // use std::sync::atomic::{AtomicU32, AtomicU8, Ordering}; 6 | use std::sync::atomic::{AtomicUsize, Ordering}; 7 | 8 | #[derive(Debug)] 9 | pub enum BackgroundResult { 10 | Errored, 11 | Status(i32), 12 | } 13 | 14 | const REPLIED: u8 = 1; 15 | const ERRORED: u8 = 2; 16 | 17 | #[derive(Debug)] 18 | /// An atomic structure that can safely be shared across threads, which serves to provide 19 | /// communication between the shell and background threads. The `fg` command uses this 20 | /// structure to notify a background thread that it needs to wait for and return 21 | /// the exit status back to the `fg` function. 22 | pub struct Signals { 23 | grab: AtomicUsize, // AtomicU32, 24 | status: AtomicUsize, // AtomicU8, 25 | reply: AtomicUsize, // AtomicU8, 26 | } 27 | 28 | impl Signals { 29 | pub fn was_grabbed(&self, pid: Pid) -> bool { 30 | self.grab.load(Ordering::SeqCst) == pid.as_raw() as usize 31 | } 32 | 33 | pub fn was_processed(&self) -> Option { 34 | let reply = self.reply.load(Ordering::SeqCst) as u8; 35 | self.reply.store(0, Ordering::SeqCst); 36 | if reply == ERRORED { 37 | Some(BackgroundResult::Errored) 38 | } else if reply == REPLIED { 39 | Some(BackgroundResult::Status(self.status.load(Ordering::SeqCst) as i32)) 40 | } else { 41 | None 42 | } 43 | } 44 | 45 | pub fn errored(&self) { 46 | self.grab.store(0, Ordering::SeqCst); 47 | self.reply.store(ERRORED as usize, Ordering::SeqCst); 48 | } 49 | 50 | pub fn reply_with(&self, status: i32) { 51 | self.grab.store(0, Ordering::SeqCst); 52 | self.status.store(status as usize, Ordering::SeqCst); 53 | self.reply.store(REPLIED as usize, Ordering::SeqCst); 54 | } 55 | 56 | pub fn signal_to_grab(&self, pid: Pid) { 57 | self.grab.store(pid.as_raw() as usize, Ordering::SeqCst); 58 | } 59 | 60 | pub const fn new() -> Self { 61 | Self { 62 | grab: AtomicUsize::new(0), 63 | status: AtomicUsize::new(0), 64 | reply: AtomicUsize::new(0), 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/binary/designators.rs: -------------------------------------------------------------------------------- 1 | use super::lexer::{DesignatorLexer, DesignatorToken}; 2 | use ion_shell::parser::lexers::ArgumentSplitter; 3 | use liner::Context; 4 | use std::{borrow::Cow, str}; 5 | 6 | pub fn expand_designators<'a>(context: &Context, cmd: &'a str) -> Cow<'a, str> { 7 | if let Some(buffer) = context.history.buffers.back() { 8 | let buffer = buffer.as_bytes(); 9 | let buffer = unsafe { str::from_utf8_unchecked(&buffer) }; 10 | let mut output = String::with_capacity(cmd.len()); 11 | for token in DesignatorLexer::new(cmd.as_bytes()) { 12 | match token { 13 | DesignatorToken::Text(text) => output.push_str(text), 14 | DesignatorToken::Designator(text) => match text { 15 | "!!" => output.push_str(buffer), 16 | "!$" => output.push_str(last_arg(buffer)), 17 | "!0" => output.push_str(command(buffer)), 18 | "!^" => output.push_str(first_arg(buffer)), 19 | "!*" => output.push_str(&args(buffer)), 20 | _ => output.push_str(text), 21 | }, 22 | } 23 | } 24 | Cow::Owned(output) 25 | } else { 26 | Cow::Borrowed(cmd) 27 | } 28 | } 29 | 30 | fn command(text: &str) -> &str { ArgumentSplitter::new(text).next().unwrap_or(text) } 31 | 32 | fn args(text: &str) -> &str { 33 | let bytes = text.as_bytes(); 34 | bytes 35 | .iter() 36 | // Obtain position of the first space character, 37 | .position(|&x| x == b' ') 38 | // and then obtain the arguments to the command. 39 | .and_then(|fp| { 40 | bytes[fp + 1..] 41 | .iter() 42 | // Find the position of the first character in the first argument. 43 | .position(|&x| x != b' ') 44 | // Then slice the argument string from the original command. 45 | .map(|sp| &text[fp + sp + 1..]) 46 | }) 47 | // Unwrap the arguments string if it exists, else return the original string. 48 | .unwrap_or(text) 49 | } 50 | 51 | fn first_arg(text: &str) -> &str { ArgumentSplitter::new(text).nth(1).unwrap_or(text) } 52 | 53 | fn last_arg(text: &str) -> &str { ArgumentSplitter::new(text).last().unwrap_or(text) } 54 | -------------------------------------------------------------------------------- /src/lib/builtins/command_info.rs: -------------------------------------------------------------------------------- 1 | use super::Status; 2 | use crate as ion_shell; 3 | use crate::{ 4 | shell::{Shell, Value}, 5 | types, 6 | }; 7 | use builtins_proc::builtin; 8 | 9 | use std::{borrow::Cow, env}; 10 | 11 | #[builtin( 12 | names = "which, type", 13 | desc = "locate a program file in the current user's path", 14 | man = " 15 | SYNOPSIS 16 | which PROGRAM 17 | 18 | DESCRIPTION 19 | The which utility takes a list of command names and searches for the 20 | alias/builtin/function/executable that would be executed if you ran that command." 21 | )] 22 | pub fn which(args: &[types::Str], shell: &mut Shell<'_>) -> Status { 23 | if args.len() == 1 { 24 | return Status::bad_argument("which: Expected at least 1 args, got only 0"); 25 | } 26 | 27 | let mut result = Status::SUCCESS; 28 | for command in &args[1..] { 29 | match get_command_info(command, shell) { 30 | Ok(c_type) => match c_type.as_ref() { 31 | "alias" => { 32 | if let Some(Value::Alias(ref alias)) = shell.variables().get(&**command) { 33 | println!("{}: alias to {}", command, &**alias); 34 | } 35 | } 36 | "function" => println!("{}: function", command), 37 | "builtin" => println!("{}: built-in shell command", command), 38 | path => println!("{}", path), 39 | }, 40 | Err(_) => result = Status::from_exit_code(1), 41 | } 42 | } 43 | result 44 | } 45 | 46 | fn get_command_info<'a>(command: &str, shell: &mut Shell<'_>) -> Result, ()> { 47 | match shell.variables().get(command) { 48 | Some(Value::Alias(_)) => Ok("alias".into()), 49 | Some(Value::Function(_)) => Ok("function".into()), 50 | _ if shell.builtins().contains(command) => Ok("builtin".into()), 51 | _ => { 52 | let paths = env::var_os("PATH").unwrap_or_else(|| "/bin".into()); 53 | for path in env::split_paths(&paths) { 54 | let executable = path.join(command); 55 | if executable.is_file() { 56 | return Ok(executable.display().to_string().into()); 57 | } 58 | } 59 | Err(()) 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tests/string_methods.ion: -------------------------------------------------------------------------------- 1 | echo '# ANCHOR: basename' 2 | echo $basename("/parent/filename.ext") 3 | echo '# ANCHOR_END: basename' 4 | echo '# ANCHOR: extension' 5 | echo $extension("/parent/filename.ext") 6 | echo '# ANCHOR_END: extension' 7 | echo '# ANCHOR: filename' 8 | echo $filename("/parent/filename.ext") 9 | echo '# ANCHOR_END: filename' 10 | echo '# ANCHOR: join' 11 | let array = [1 2 3 4 5] 12 | echo $join(array) 13 | echo $join(array ", ") 14 | echo '# ANCHOR_END: join' 15 | echo '# ANCHOR: find' 16 | echo $find("FOOBAR" "OB") 17 | echo $find("FOOBAR" "ob") 18 | echo '# ANCHOR_END: find' 19 | echo '# ANCHOR: len' 20 | echo $len("foobar") 21 | echo $len("❤️") 22 | echo $len([one two three four]) 23 | echo '# ANCHOR_END: len' 24 | echo '# ANCHOR: len_bytes' 25 | echo $len_bytes("foobar") 26 | echo $len_bytes("❤️") 27 | echo '# ANCHOR_END: len_bytes' 28 | echo '# ANCHOR: parent' 29 | echo $parent("/root/parent/filename.ext") 30 | echo '# ANCHOR_END: parent' 31 | echo '# ANCHOR: repeat' 32 | echo $repeat("abc, " 3) 33 | echo '# ANCHOR_END: repeat' 34 | echo '# ANCHOR: replace' 35 | let input = "one two one two" 36 | echo $replace(input one 1) 37 | echo $replace($replace(input one 1) two 2) 38 | echo '# ANCHOR_END: replace' 39 | echo '# ANCHOR: replacen' 40 | let input = "one two one two" 41 | echo $replacen(input "one" "three" 1) 42 | echo $replacen(input "two" "three" 2) 43 | echo '# ANCHOR_END: replacen' 44 | echo '# ANCHOR: regex_replace' 45 | echo $regex_replace("bob" "^b" "B") 46 | echo $regex_replace("bob" 'b$' "B") 47 | echo $regex_replace("One Two Three Four" "\(One\) \(Two\) \(Three\) \(Four\)" '$4 $3 $2 $1') 48 | echo '# ANCHOR_END: regex_replace' 49 | echo '# ANCHOR: reverse' 50 | echo $reverse("foobar") 51 | echo '# ANCHOR_END: reverse' 52 | echo '# ANCHOR: to_lowercase' 53 | echo $to_lowercase("FOOBAR") 54 | echo '# ANCHOR_END: to_lowercase' 55 | echo '# ANCHOR: to_uppercase' 56 | echo $to_uppercase("foobar") 57 | echo '# ANCHOR_END: to_uppercase' 58 | echo '# ANCHOR: escape' 59 | let line = " Mary had\ta little \n\t lamb\t" 60 | echo $escape($line) 61 | echo '# ANCHOR_END: escape' 62 | echo '# ANCHOR: unescape' 63 | let line = " Mary had\ta little \n\t lamb\t" 64 | echo $unescape($line) 65 | echo '# ANCHOR_END: unescape' 66 | echo '# ANCHOR: or' 67 | echo $or($unknown_variable "Fallback") 68 | let var = 42 69 | echo $or($var "Not displayed") 70 | echo '# ANCHOR_END: or' 71 | -------------------------------------------------------------------------------- /src/lib/shell/pipe_exec/pipes.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | super::job::{RefinedJob, TeeItem}, 3 | PipelineError, 4 | }; 5 | 6 | #[cfg(any(target_os = "ios", target_os = "macos"))] 7 | use nix::fcntl::{fcntl, FcntlArg}; 8 | use nix::{fcntl::OFlag, unistd}; 9 | use std::{fs::File, os::unix::io::FromRawFd}; 10 | 11 | #[cfg(not(any(target_os = "ios", target_os = "macos")))] 12 | pub fn create_pipe() -> Result<(File, File), PipelineError> { 13 | let (reader, writer) = 14 | unistd::pipe2(OFlag::O_CLOEXEC).map_err(PipelineError::CreatePipeError)?; 15 | Ok(unsafe { (File::from_raw_fd(reader), File::from_raw_fd(writer)) }) 16 | } 17 | #[cfg(any(target_os = "ios", target_os = "macos"))] 18 | pub fn create_pipe() -> Result<(File, File), PipelineError> { 19 | let (reader, writer) = unistd::pipe().map_err(PipelineError::CreatePipeError)?; 20 | fcntl(reader, FcntlArg::F_SETFL(OFlag::O_CLOEXEC)).map_err(PipelineError::CreatePipeError)?; 21 | fcntl(writer, FcntlArg::F_SETFL(OFlag::O_CLOEXEC)).map_err(PipelineError::CreatePipeError)?; 22 | Ok(unsafe { (File::from_raw_fd(reader), File::from_raw_fd(writer)) }) 23 | } 24 | 25 | pub struct TeePipe<'a, 'b> { 26 | parent: &'a mut RefinedJob<'b>, 27 | ext_stdio_pipes: &'a mut Option>, 28 | is_external: bool, 29 | } 30 | 31 | impl<'a, 'b> TeePipe<'a, 'b> { 32 | pub fn new( 33 | parent: &'a mut RefinedJob<'b>, 34 | ext_stdio_pipes: &'a mut Option>, 35 | is_external: bool, 36 | ) -> Self { 37 | TeePipe { parent, ext_stdio_pipes, is_external } 38 | } 39 | 40 | fn inner_connect(&mut self, tee: &mut TeeItem, mut action: F) -> Result<(), PipelineError> 41 | where 42 | F: FnMut(&mut RefinedJob<'b>, File), 43 | { 44 | let (reader, writer) = create_pipe()?; 45 | (*tee).source = Some(reader); 46 | if self.is_external { 47 | self.ext_stdio_pipes 48 | .get_or_insert_with(|| Vec::with_capacity(4)) 49 | .push(writer.try_clone().map_err(PipelineError::ClonePipeFailed)?); 50 | } 51 | action(self.parent, writer); 52 | Ok(()) 53 | } 54 | 55 | pub fn connect(&mut self, out: &mut TeeItem, err: &mut TeeItem) -> Result<(), PipelineError> { 56 | self.inner_connect(out, RefinedJob::stdout)?; 57 | self.inner_connect(err, RefinedJob::stderr) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /ci/run_benchmark.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | git remote add upstream https://gitlab.redox-os.org/redox-os/ion.git 4 | git fetch upstream 5 | git checkout upstream/master 6 | cargo bench 7 | cargo build --release 8 | PREV_SIZE=$(ls -al target/release/ion | cut -d' ' -f5) 9 | 10 | git stash 11 | git checkout - 12 | cargo bench 13 | cargo build --release 14 | SIZE=$(ls -al target/release/ion | cut -d' ' -f5) 15 | 16 | # if lower_bound*upper_bound > 0, then we consider the benchmark "changed" 17 | NOISE=0.05 18 | JQ_FILTER="if .Median.confidence_interval.lower_bound > $NOISE or .Median.confidence_interval.upper_bound < -$NOISE then .Median.point_estimate else \"\" end" 19 | 20 | total=0 21 | total_worse=0 22 | result="" 23 | 24 | for suite in ./target/criterion/*; do 25 | name=$(echo $suite | cut -d'/' -f 4) 26 | worse=0 27 | tests=0 28 | 29 | testcases="" 30 | 31 | for test in $suite/*/*/change/estimates.json; do 32 | estimate=$(cat "$test" | jq -r "$JQ_FILTER" -c) 33 | if echo "$estimate" | grep -Eq '^[0-9]+\.?[0-9]*$'; then 34 | inner="\ 35 | Performance regressed by $estimate in $test\ 36 | " 37 | worse=$((worse+1)) 38 | fi 39 | testcases="$testcases$inner" 40 | tests=$((tests+1)) 41 | done 42 | 43 | result="$result$testcases" 44 | 45 | total_worse=$((total_worse + worse)) 46 | total=$((total + tests)) 47 | done 48 | 49 | binary=$(test $(echo "$PREV_SIZE * 105 / 100" | bc) -ge $SIZE; echo $?) 50 | result="$result\ 51 | \ 52 | " 53 | 54 | total=$((total + 1)) 55 | if [ ! "$binary" -eq "0" ]; then 56 | result="$result\ 57 | \ 58 | Binary size increased from $PREV_SIZE to $SIZE.\ 59 | " 60 | total_worse=$((total_worse + 1)) 61 | fi 62 | 63 | result="$result" 64 | 65 | result=" 66 | 67 | $result 68 | " 69 | 70 | echo $result > target/report.xml 71 | 72 | test "$total_worse" -eq "0" 73 | -------------------------------------------------------------------------------- /src/lib/builtins/random.rs: -------------------------------------------------------------------------------- 1 | use crate::types; 2 | use itertools::Itertools; 3 | use rand::{thread_rng, Rng}; 4 | 5 | const INVALID: &str = "Invalid argument for random"; 6 | 7 | fn rand_list(args: &[types::Str]) -> Result<(), types::Str> { 8 | let num_random = args[0].parse::().map_err::(|_| INVALID.into())?; 9 | let mut output = Vec::with_capacity(num_random); 10 | while output.len() < num_random { 11 | for _ in 0..(num_random - output.len()) { 12 | let rand_num = thread_rng().gen_range(1, args.len()); 13 | output.push(&*args[rand_num]); 14 | } 15 | output.dedup(); 16 | } 17 | println!("{}", output.iter().format(" ")); 18 | Ok(()) 19 | } 20 | 21 | pub fn random(args: &[types::Str]) -> Result<(), types::Str> { 22 | match args.len() { 23 | 0 => { 24 | let rand_num = thread_rng().gen_range(0, 32767); 25 | println!("{}", rand_num); 26 | } 27 | 1 => { 28 | eprintln!("Ion Shell does not currently support changing the seed"); 29 | } 30 | 2 => { 31 | let start: u64 = args[0].parse().map_err::(|_| INVALID.into())?; 32 | let end: u64 = args[1].parse().map_err::(|_| INVALID.into())?; 33 | if end <= start { 34 | return Err("END must be greater than START".into()); 35 | } 36 | let rand_num = thread_rng().gen_range(start, end); 37 | println!("{}", rand_num); 38 | } 39 | 3 => { 40 | let start: u64 = args[0].parse().map_err::(|_| INVALID.into())?; 41 | let step = match args[1].parse::() { 42 | Ok(v) => v, 43 | Err(_) => return rand_list(args), 44 | }; 45 | match args[2].parse::() { 46 | Ok(end) => { 47 | if step <= start { 48 | return Err("END must be greater than START".into()); 49 | } 50 | let mut end = end / step + 1; 51 | if start / step >= end { 52 | end += 1; 53 | } 54 | let rand_num = thread_rng().gen_range(start / step, end); 55 | println!("{}", rand_num * step); 56 | } 57 | Err(_) => return rand_list(args), 58 | }; 59 | } 60 | _ => return rand_list(args), 61 | } 62 | 63 | Ok(()) 64 | } 65 | -------------------------------------------------------------------------------- /manual/src/variables/00-variables.md: -------------------------------------------------------------------------------- 1 | # Variables 2 | The `let` builtin is used to create local variables within the shell, and apply basic arithmetic 3 | to variables. The `export` keyword may be used to do the same for the creation of external 4 | variables. Variables cannot be created the POSIX way, as the POSIX way is awkard to read/write 5 | and parse. 6 | ```sh 7 | {{#include ../../../tests/variables.ion:variables}} 8 | ``` 9 | ```txt 10 | {{#include ../../../tests/variables.out:6:7}} 11 | ``` 12 | 13 | ## Multiple Assignments 14 | Ion also supports setting multiple values at the same time 15 | ```sh 16 | {{#include ../../../tests/variables.ion:multiple_assignment}} 17 | ``` 18 | ```txt 19 | {{#include ../../../tests/variables.out:9:12}} 20 | ``` 21 | 22 | ## Type-Checked Assignments 23 | It's also possible to designate the type that a variable is allowed to be initialized with. 24 | Boolean type assignments will also normalize inputs into either `true` or `false`. When an 25 | invalid value is supplied, the assignment operation will fail and an error message will be 26 | printed. All assignments after the failed assignment will be ignored. 27 | ```sh 28 | {{#include ../../../tests/variables.ion:type_checked_assignment}} 29 | ``` 30 | ```txt 31 | {{#include ../../../tests/variables.out:14:19}} 32 | ``` 33 | 34 | ## Dropping Variables 35 | 36 | Variables may be dropped from a scope with the `drop` keyword. Considering that a variable 37 | can only be assigned to one type at a time, this will drop whichever value is assigned to 38 | that type. 39 | ```sh 40 | {{#include ../../../tests/variables.ion:dropping_variables}} 41 | ``` 42 | 43 | ## Command-line Temporary Variables 44 | 45 | A command starting with a variable assignment uses that variable for the 46 | duration of the command. 47 | 48 | ```sh 49 | {{#include ../../../tests/variables.ion:command_local_variables}} 50 | ``` 51 | ```txt 52 | {{#include ../../../tests/variables.out:22:24}} 53 | ``` 54 | 55 | ## Supported Primitive Types 56 | 57 | - `str`: A string, the essential primitive of a shell. 58 | - `bool`: A value which is either `true` or `false`. 59 | - `int`: An integer is any whole number. 60 | - `float`: A float is a rational number (fractions represented as a decimal). 61 | 62 | ## Arrays 63 | 64 | The `[T]` type, where `T` is a primitive, is an array of that primitive type. 65 | 66 | ## Maps 67 | 68 | Likewise, `hmap[T]` and `bmap[T]` work in a similar fashion, but are a collection 69 | of key/value pairs, where the key is always a `str`, and the value is defined by the 70 | `T`. 71 | -------------------------------------------------------------------------------- /src/lib/parser/lexers/assignments/primitive.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Display, Formatter}; 2 | 3 | /// A primitive defines the type that a requested value should satisfy. 4 | #[derive(Debug, PartialEq, Eq, Hash, Clone)] 5 | pub enum Primitive { 6 | /// A plain string (ex: `"a string"`) 7 | Str, 8 | /// A true-false value 9 | Boolean, 10 | /// An integer numeric type 11 | Integer, 12 | /// A floating-point value 13 | Float, 14 | /// Arrays 15 | Array(Box), 16 | /// A hash map 17 | HashMap(Box), 18 | /// A btreemap 19 | BTreeMap(Box), 20 | /// An index variable (ex: `$array[0]`) 21 | Indexed(String, Box), 22 | } 23 | 24 | impl Primitive { 25 | pub(crate) fn parse(data: &str) -> Option { 26 | match data { 27 | "str" => Some(Self::Str), 28 | "bool" => Some(Self::Boolean), 29 | "int" => Some(Self::Integer), 30 | "float" => Some(Self::Float), 31 | _ => { 32 | let open_bracket = data.find('[')?; 33 | let close_bracket = data.rfind(']')?; 34 | let kind = &data[..open_bracket]; 35 | let inner = &data[open_bracket + 1..close_bracket]; 36 | 37 | if kind == "hmap" { 38 | Some(Self::HashMap(Box::new(Self::parse(inner)?))) 39 | } else if kind == "bmap" { 40 | Some(Self::BTreeMap(Box::new(Self::parse(inner)?))) 41 | } else { 42 | // It's an array 43 | Some(Self::Array(Box::new(Self::parse(inner)?))) 44 | } 45 | } 46 | } 47 | } 48 | } 49 | 50 | impl Display for Primitive { 51 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 52 | match *self { 53 | Self::Str => write!(f, "str"), 54 | Self::Boolean => write!(f, "bool"), 55 | Self::Float => write!(f, "float"), 56 | Self::Integer => write!(f, "int"), 57 | Self::Array(ref kind) => write!(f, "[{}]", kind), 58 | Self::HashMap(ref kind) => match **kind { 59 | Self::Str => write!(f, "hmap[]"), 60 | ref kind => write!(f, "hmap[{}]", kind), 61 | }, 62 | Self::BTreeMap(ref kind) => match **kind { 63 | Self::Str => write!(f, "bmap[]"), 64 | ref kind => write!(f, "bmap[{}]", kind), 65 | }, 66 | Self::Indexed(_, ref kind) => write!(f, "{}", kind), 67 | } 68 | } 69 | } 70 | --------------------------------------------------------------------------------