crush
crush copied to clipboard
Commands that contain the '-' char are not parsed correctly.
Example:
crush> nix
*works*
crush> nix-env
Error: Unknown variable nix
and
crush> nix "--help"
*works*
crush> nix-env "--help"
Error: Stray Arguments
That's expected, since - is used for subtraction is Crush. The real question is what one should do about this. Force the user to quote commands with the '-' characters? Change the subtraction operator? Something else?
BTW, the first proposed workaround mentioned above works, so as of today, the way to run a command containing a - is to quote the command name using single quotes, like so:
crush> 'nix-env'
I don't think changing the subtraction operator is a good idea, as you would have to pick something that is not allowed in filenames, which is only the NUL char and '/' on Ext4.
I see a few solutions here.
- Keep the current behaviour and force quotes. That would probably be the easiest solution, but feels very clunky from a usablility perspective and could confuse users coming from other shells.
- Force whitespace around the '-' when doing subtractions. Nicer for the casual user, but making the behaviour of your script dependent on whitespace is one of the things I really dislike about other shells.
- Try subtraction first, and if one of the two variables is not defined, try to run it as a command instead (or the other way around, see if a program with that name exists). This would probably complicate parsing, as parsing then depends on external factors (defined variables or programs in $PATH), and a scripts behaviour could unexpectedly change depending on those factors.
- Only do calculation in blocks like $((2 - 1)) in bash. This, like 2., also makes syntax worse and less intuitive to use.
None of those are really great IMO, but maybe this leads to some better ideas.
Anyway, I appreciate the effort to make this shell!
Edit: Both 2. and 4. would not only solve this, but also #7 (allow * as glob character) and allow using '/' for division.
Another solution would be to add add, sub, mul and div builtins and remove the four operators.
Calculating a number would then be add 1 2, similar to how functions like mod or max work in Haskell.
While the syntax is slightly worse than keeping the operators. I don't think this has any other big downsides.
If you look at the git history of crush, you will see that crush used to use add 1 2 to add two numbers together. I felt that the language at that point was quite clunky and hard to read, which is why I tried this new version. So far, I like the tradeoff of the new solution better.
The idea of becoming extremely whitespace sensitive by differentiating between a-b and a - b is in itself highly unappealing, but there are definitely big advantages. Like you say, it will solve all current issues with operators, and instead create a fleet of different new problems.
In CSS they solved the problem by declaring that subtraction operator must have spaces on both side. Otherwise it’s a part of a token.
So nix-env is one token, nix -env are two tokens and nix - env is subtraction.
Mandating whitespace around - doesn't completely solve the problem either. Many standard *nix utilities (cat, tar, etc.) use - as an argument to designate stdin/stdout. Forcing users to jump through hoops to "un-math" an argument that's documented in hundreds of man pages seems like a huge step backwards in shell usability.
Instead, I think math operations should be encapsulated in a special context, perhaps using the bash-like (( ... )). This should also address #7, adhere to the Principle of Least Surprise, and might even simplify crush's parsing logic.
I'm a lurker here, working on a different pipe-objects-instead-of-strings shell, but I do have some perspective on this issue. Trying to preserve expected syntax on the command line, while trying to ALSO cram in familiar syntax from other languages is somewhere between very difficult and impossible. My shell is bash-like (commands, arguments, short and long options, pipes), but it exposes Python expressions (only, not arbitary Python code) on the command line in places where some logic or computation is required. The rule I use is simple: Python expressions go inside top-level parens. So there is never any confusion about the meaning of *, -, or other tokens that have meaning in both languages.
Parsing is quite simple. When I see a top-level '(', I just scan for a matching ')' (paying attention to quotes and escapes on the way), and then everything inside the parens is turned into a function which the shell command can then invoke.