msh has a fixed number of built in data types

The source code for all these types is in a single file at: mshell/MShellObject.go.

Literal § Back to top

Since brevity is a strong design goal, like most shells, many basic tokens can be written as-is. But unlike in most shells, we limit where the number of places literal tokens can be used. They can only be used in list literals, because that's how commands are input. Outside of lists, it will result in a error.

Bool § Back to top

Bools are represented by true and false.

Quotation § Back to top

Quotations are blocks of code that can be executed. They are created by wrapping code in parentheses. They are often used in places where a lambda function (or anonymous function) would be used in other languages. For example, a quotation is used as an argument to the map function.

(1 2 +)

List § Back to top

Lists are created by wrapping elements in square brackets. No commas are required between elements.

[1 2 3]

Lists can be added together with the + operator. The result is a new list object.

[1 2] [3 4] + # [1 2 3 4]

String § Back to top

msh has double quotes and single quotes. Double quoted strings have the following escape sequences:

If you have a backslash, and it isn't followed by one of the escape characters above, msh will throw an error.

No escaping is done within single quoted strings or paths.

Path § Back to top

Since paths are such a common object to deal with in shell scripts, mshell has a dedicated type.

Paths are created by wrapping the path in backticks. No escaping is done. If you need to escape or use string interpolation, then use double quotes and toPath.

`path/to/file`
$"my/path/is/built/from/{@variable}" toPath

Because paths are fundamentally different than strings, many functions change their behavior on whether the input is a path or a string. For example, for a function like parseJson, if the input is a path, it will read the file at that path and parse the contents as JSON. If the input is binary data, it first decodes the bytes as UTF-8. Otherwise, it will parse the string as JSON directly.

The other place this is important is in command input redirection. You often want to pipe input into a command, but sometimes it's from a file, and sometimes it's from a string. In msh, this choice can be made by using the appropriate type.

[wc -l] `myfile.txt` < ; # Counts the number of lines in myfile.txt
[wc -l] "line 1\nline 2\n" < ; # Counts the number of lines in the string "line 1\nline 2\n" (2)

Dictionary § Back to top

mshell has the concept of dictionaries or associative arrays, but only with string keys (like awk).

Dictionaries can be instantiated using dictionary literals, like:

{ "key": 1 }

Be careful with some of the lexing around the colon, as it's used with indexing. Best practice is a space between the colon and the value.

{ "key":1 } # Bad because ':1' is treated as index

Date/Time § Back to top

Date/times can be entered using a literal syntax, in ISO-8601 format.

# All are valid
2023-10-01
2023-10-01T13
2023-10-01T13:01
2023-10-01T13:01:30

Dates can be subtracted from each other, and the result is a float number of days.

2023-10-02 2023-10-01 - # 1.0

Float and Integer § Back to top

mshell makes a distinction between integers and floats. Any literal number with a decimal will be lexed as a float, otherwise it will be an integer.

1   # This is a integer literal
1.0 # This is a float literal

Division for integers will result in an integer without the remainder.

5 2 / # Integer 2, not float 2.5

Division is only supported for similar types, it is a runtime error to divide with a float and an integer. For the other operations, if the types are mixed, the result is a float.

2 2.5 * # 5.0 float
3 2.5 - # 0.5 float
3 2.5 + # 5.5 float

Maybe § Back to top

The Maybe type is a way to represent a value that may or may not exist. It's like a better, strongly typed null value. It's a value that can either be none or a just[type] value.

They can be instantiated using:

none # None value
"my string" just # Just value of a string

There are several functions with a Maybe type, but perhaps the most important is to just unwrap the value and fail entirely if it is none. This is done with the ? operator.

"1" toFloat? # This will result in 1.0 being on the stack (Not wrapped)
"bad string" toFloat? # This will result in failure and execution will stop.

Pipe § Back to top

A pipe is really a special list (and this type may get consumed by list in the future).

It's created by the | operator.

[
  [ myCommand ]
  [ wc -l ]
] | ;  # Counting the number of lines of output from myCommand

Binary § Back to top

Many times in shell programs you need to deal directly with binary data. This type represents an array of bytes. Example functions that takes binary data as input and output are utf8Str, utf8Bytes, base64encode, and base64decode.