Skip to the content.

How nushell evaluates user input (new version)

There is a redirection overhaul pr recently, it changes how lite block parsing and block parsing works, so previous blog is out of date.

I’m always curious if we type something like ^ls -alh, how does nushell parse my input and execute the command.

This blog will note how nushell evaluates user input.

High level insight

At high level, the data flow will be something like this:

                           lex parse            lite parse                parse
user input command(string) ----------> Tokens ------------->  LiteBlocks ------------> Blocks

In detail:

  1. Tokens are components of user input command, which mainly separate by space, or pipe, or something else
  2. Lite parsing converts a stream of tokens to a syntax element structure(LiteBlock) that can be parsed.
  3. parsing from LiteBlock to AST Block.

Here are some examples of how nushell parses user input commands:

Example 1: ^ls -alh out> here11

It’s different to previous version, in previous version, redirection is treated as a single command, along with redirection target.

After analysing tokens, nushell knows that it’s going to be 1 command, ls -alh, with redirection attribute out> here11. It’s a big improvement that we don’t need to check next command to know if there is a redirection.

It contains more information about our commands, our pipeline contains 1 element(different to previous version), with pipe, expr, and redirection attributes.

Example 2: ^ls -alh | save --raw a.txt

It still contains two commands, one is ^ls -alh, the other one is save --raw a.txt.

It contains two elements:

  1. first one is nushell expression, it contains external call with name “ls” and arguments “-alh”
  2. second one is another nushell expression, it contains internal command, the command have declaration id 197(which is “save” command in our case), and a named argument called “raw”, a positional argument with value “a.txt”

Example 3: ^ls -alh e>| save --raw a.txt

As we can see, our lex contains 6 tokens, they are separated by space and pipe.

It still contains two commands, one is ^ls -alh, the other one is save --raw a.txt.

Note that the redirection attribute of first LiteCommand is different to previous one, which value is None, here the value is Some(Single{source: Stderr, ...}).

It contains two elements:

  1. first one is nushell expression, it contains external call with name “ls” and arguments “-alh”
  2. second one is another nushell expression, it contains internal command, the command have declaration id 197(which is “save” command in our case), and a named argument called “raw”, a positional argument with value “a.txt”

Note that the redirection attribute of first PipelineElemet is different to previous example, which value is None, here the value is Some(Single{source: Stderr, ...}). It’s important, when we run external command ls, we can set stderr attribute of the process to piped() directly.

Example 3: ^ls -alh o+e>| save --raw a.txt

As we can see, our lex contains 6 tokens, they are separated by space and pipe.

It still contains two commands, one is ^ls -alh, the other one is save --raw a.txt.

Note that the redirection attribute of first LiteCommand is different to previous one, which value is None, here the value is Some(Single{source: Stderr, ...}).

It contains two elements:

  1. first one is nushell expression, it contains external call with name “ls” and arguments “-alh”
  2. second one is another nushell expression, it contains internal command, the command have declaration id 207(which is “save” command in our case), and a named argument called “raw”, a positional argument with value “a.txt”

Note that the redirection attribute of first PipelineElemet is different to previous example, which value is None, here the value is Some(Single{source: StdoutAndStderr, ...}). It’s important, when we run external command ls, we can set stderr attribute of the process to piped() directly.

Reference source code:

  1. eval_source function, it’s the main entrypoint in repl.
  2. parse function, as we look into the function body, we can see there are two function calls lex and parse_block.
  3. parse_block function accepts lex tokens, and invoke lite_parse to parse from tokens to LiteBlock, then convert from LiteBlock to nushell Block.
  4. Finally nushell call eval_block to evaluate Block.