Skip to content

Variables & scope

In value positions, bare words are text: 123, true, false, and nil have no special literal meaning. Double-quoted strings interpolate $name and $(...), and support \", \\, \n, \r, \t, and \$. Single-quoted strings preserve their content without interpolation or escapes. ${name} remains literal text. Triple quotes preserve newlines and indentation: """...""" supports the same interpolation and escapes, while '''...''' preserves all content literally. Outside strings, variable references use $name. Binding and assignment targets use bare names, including function parameters, loop bindings, and environment settings. Names start with a letter or _, followed by letters, digits, or _. Binding targets must be bare names, not quoted strings or expressions.

Typed scalars use #1, #1.0, #true, #false, and #nil. Runtime text conversion uses int, float, and bool; see syntax and conversions.

Values use copy semantics. Reading a variable, assigning a value, or passing a function argument copies it, including nested lists and maps. Collection updates return new values; use set to replace an existing binding.

(let name "a friend")
(echo hello $name)
(set name world)
(echo "hello, $name")
(export EDITOR vim)

Every parenthesized expression creates a fresh lexical scope. let is a declaration form: it binds in the scope surrounding the current parenthesized expression. A duplicate name in that same scope is an error, checked before the right-hand side is evaluated; nested scopes may shadow outer bindings. A top-level (let name value) therefore binds in the session scope, while let inside do, a function, or a loop stays in that enclosing body scope. let and set both return nil after a successful update. set first checks for the nearest existing binding or environment entry; an undefined name errors before the right-hand side is evaluated. It then evaluates the right-hand side and updates the binding. Lookups fall back to the inherited environment. export sets the environment for subsequent children and updates an existing local binding of the same name. Environment and working directory changes belong to this parsh session.

get takes exactly one text variable name, which can itself be an expression, and returns the original value using the same lookup rules as $name:

(get name)
(get (str na me))
(echo "hello, $(get name)_friend")

Names must follow the variable naming rules above. Undefined names are errors. get preserves lists, maps, numbers, booleans, and nil; conversion to text is attempted only when used inside a string. get is a reserved built-in command name. For expression interpolation and literal dollars, see strings.

(let files (list "a file.rs" other.rs))
(printf "<%s>\n" $files)
(for file (glob *.rs)
(echo $file))

Lists expand by one level into external command arguments. Text stays a single argument, including spaces and embedded newlines. list constructs lists; str concatenates scalar values, converting numbers with the same format used for command arguments and environment values. glob is explicit, returns sorted paths, and returns an empty list when nothing matches. Ordinary words, strings, and variable values never undergo implicit globbing, word splitting, or tilde expansion.

cd accepts a path, or defaults to $HOME; it updates $PWD and $OLDPWD. Relative commands, glob patterns, and redirections use the current directory.

map accepts alternating text keys and values. (map) creates an empty map. Keys may be bare words, strings, text variables, or expressions returning text; other key types are errors. Values retain their types and may contain lists or maps. Keys and values evaluate once from left to right. An odd argument count is rejected before evaluating any arguments; a duplicate key is rejected at its second occurrence, before evaluating that key’s value.

(let config (map name parsh version #1 nested (map enabled #true)))
(let updated (map/set $config name new-name))
(map/get $config name)
(map/get $updated name)
(map/get $config missing fallback)
(map/keys $config)
(set config (map/del $updated version))

Every m command takes a map value as its first argument: $config, a map literal, or any expression returning a map. A bare word such as config is text and produces a type error. In the example, updated contains the new name while config keeps its old value until the final set.

Command Result
(map/get $config key) Original value; a missing key is an error
(map/get $config key default) Always evaluate default; return it if the key is missing
(map/set $config key value) New map with the value inserted or replaced
(map/del $config key) New map without the key; a missing key is an error
(map/has $config key) Boolean indicating whether the key exists
(map/keys $config) Text keys in insertion order, as a list
(map/vals $config) Values in the same order, as a list
(map/len $config) Integer entry count

An existing key with a nil value is still present and returns nil; an explicit default is still evaluated. The default does not handle undefined variables or non-map arguments. After checking argument count, arguments evaluate once from left to right. map/get evaluates all explicit arguments before checking the map and choosing a result. Other map operations check the map before evaluating keys and values. The operation uses that independent map value, even if later expressions change the source variable. In map construction and map/set, invalid keys stop evaluation before the value expression.

Operations compose directly, without binding intermediate maps:

(map/set {name parsh} version v1)
(map/get (map/set $config name other) name)
(map/set (map/set $config name other) version v2)

Updating a key keeps its position. New keys append; deleting preserves the order of remaining keys, and reinserting appends. Maps display in insertion order as (map "name" "parsh"); eq compares contents regardless of that order.

map/set and map/del return new maps without modifying their inputs. For example, after (let copy $config), (set config (map/set $config name changed)) updates only config; copy retains its previous value, including nested data. Functions can use set to update captured outer bindings, while a parameter with the same name is a separate binding. A function returning an updated map affects its caller’s variable only when the caller explicitly assigns that result. map/get and map/vals also return independent values. Maps cannot implicitly become text: str, string interpolation, external command arguments, and environment values reject maps. Use map/keys or map/vals explicitly when a list is needed for iteration or command arguments.

The read-only special variable $? (also (get ?)) holds the previous complete CallResult, initially nil. Use (exit-code $?) or (signal $?) to inspect termination, or (let saved $?) to keep a snapshot. It is not an environment variable and cannot be assigned. See execution results.