Skip to content

Lexical Structure

lash uses two separate namespaces resolved by syntactic position (Lisp-2 design):

  • Command namespace: built-in methods, user-defined functions, and external binaries from $PATH.
  • Variable namespace: variables declared with let or mut.

A name in the first-word position of a statement resolves in the command namespace. A name in expression context (assignment RHS, conditions, lambda bodies, chain arguments) resolves in the variable namespace.

Command namespace resolution order:

  1. Built-in functional method (e.g., sort, filter, map)
  2. User-defined function
  3. External binary from $PATH

A variable and a command can share the same name without collision:

let grep = "my pattern"
grep "foo" file.txt # command namespace: /usr/bin/grep
let result = grep # variable namespace: the variable
echo "pattern: $grep" # string interpolation: the variable

To use a command in expression context, wrap it in backticks (see Functional Chains).

lash supports three quoting styles:

String with interpolation:

echo "hello $name"
echo "hello ${name}"
echo "result: $((count + 1))"
echo "literal \$sign"

Literal string, no processing:

echo 'no $interpolation'
echo 'no \escapes'

Command execution and functional bridge (not POSIX command substitution):

let files = `ls -la`
`ls -la`.sort().take(5)

Available inside double-quoted strings only:

SequenceMeaning
\nNewline
\tTab
\\Literal backslash
\$Literal dollar sign
\"Literal double quote

In command position, unquoted words are literal arguments. In expression context, unquoted words resolve as variable names.

Backticks are reserved for the functional bridge. Command substitution uses $():

echo "today is $(date)"
let branch = $(git branch --show-current)

Uppercase names before a command set environment variables for that command only:

DFLAGS="-O2" dub build --compiler=ldc2
CC=gcc CFLAGS="-Wall" make

Only names matching [A-Z][A-Z0-9_]* are recognized as env var prefixes. Lowercase names are treated as commands.