Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Octal, hexadecimal, and binary integer literals via `0o`, `0x`, and `0b`
prefixes (case-insensitive), e.g. `0o644`, `0xFF`, `0b101`. The base is purely
a way of writing the literal; the value is an ordinary integer and prints in
decimal. There are no digit separators.
- Functions
- `toBase` / `fromBase`: format an integer in / parse a string from an
arbitrary base (2–36). `fromBase` returns `Maybe[int]`.
- `toHex` / `toOctal` / `toBin` and `parseHex` / `parseOctal` / `parseBin`:
convenience wrappers over `toBase` / `fromBase` for the common bases.
- Optional fields in dictionary shape types, written `name?: T` (and
`"name"?: T` in `def` signatures). An optional field may be absent from a
value; when present, its value is still type-checked. This lets option-style
Expand Down
28 changes: 28 additions & 0 deletions doc/data-types.inc.html
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,34 @@ <h1 id="data-type-int-and-float">Float and Integer <a class="section-link" href=
<span class="mshellFLOAT">1.0</span> <span class="mshellLINECOMMENT"># This is a float literal</span></code>
</pre>

<p>
Integer literals may also be written in octal, hexadecimal, or binary using a
<code>0o</code>, <code>0x</code>, or <code>0b</code> prefix (the letters are
case-insensitive, so <code>0O</code>/<code>0X</code>/<code>0B</code> work too).
The base is purely a way of writing the literal; the value on the stack is an
ordinary integer with no record of how it was entered, and it prints back in
decimal. There are no digit separators, so <code>0o6_44</code> is not a number.
</p>

<pre>
<code><span class="mshellINTEGER">0o644</span> <span class="mshellLINECOMMENT"># 420 (octal, e.g. a file mode)</span>
<span class="mshellINTEGER">0xFF</span> <span class="mshellLINECOMMENT"># 255 (hexadecimal)</span>
<span class="mshellINTEGER">0b101</span> <span class="mshellLINECOMMENT"># 5 (binary)</span></code>
</pre>

<p>
To go the other way, format an integer to a base-N string with
<code>toBase</code> (or the <code>toHex</code>/<code>toOctal</code>/<code>toBin</code>
convenience words), and parse a string in a given base with <code>fromBase</code>
(or <code>parseHex</code>/<code>parseOctal</code>/<code>parseBin</code>), which
returns a <code>Maybe[int]</code>. Formatted output is bare digits with no prefix.
</p>

<pre>
<code><span class="mshellINTEGER">420</span> <span class="mshellLITERAL">toOctal</span> <span class="mshellLINECOMMENT"># "644"</span>
<span class="mshellSTRING">&#34;644&#34;</span> <span class="mshellLITERAL">parseOctal</span> <span class="mshellLINECOMMENT"># Some 420</span></code>
</pre>

<p>
Division for integers will result in an integer without the remainder.
</p>
Expand Down
10 changes: 9 additions & 1 deletion doc/functions.inc.html
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ <h1 id="functions-built-ins">Built-ins <a class="section-link" href="#functions-
<tr> <td><code>x</code></td> <td>Interpret or execute a quotation.</td> <td><code>(<span class="sig-type sig-type-quote">quote</span> -- )</code></td> </tr>
<tr> <td><code>toFloat</code></td> <td>Convert to a float. A string is parsed and returns a <code>Maybe</code> (<code>none</code> on parse failure); an int or float returns a plain float.</td> <td><code>(<span class="sig-type sig-type-str">str</span> -- <span class="sig-type sig-type-maybe">Maybe</span>[<span class="sig-type sig-type-float">float</span>])</code>, <code>(<span class="sig-type sig-type-numeric">numeric</span> -- <span class="sig-type sig-type-float">float</span>)</code></td> </tr>
<tr> <td><code>toInt</code></td> <td>Convert to an int. A string is parsed and returns a <code>Maybe</code> (<code>none</code> on parse failure); an int or float returns a plain int (floats truncate toward zero).</td> <td><code>(<span class="sig-type sig-type-str">str</span> -- <span class="sig-type sig-type-maybe">Maybe</span>[<span class="sig-type sig-type-int">int</span>])</code>, <code>(<span class="sig-type sig-type-numeric">numeric</span> -- <span class="sig-type sig-type-int">int</span>)</code></td> </tr>
<tr> <td><code>toBase</code></td> <td>Format an integer in the given base (2&ndash;36) as bare digits (no <code>0o</code>/<code>0x</code>/<code>0b</code> prefix). The named wrappers <code>toHex</code>, <code>toOctal</code>, and <code>toBin</code> cover bases 16/8/2.</td> <td><code>(<span class="sig-type sig-type-int">int</span> <span class="sig-type sig-type-int">int</span> -- <span class="sig-type sig-type-str">str</span>)</code></td> </tr>
<tr> <td><code>fromBase</code></td> <td>Parse a string as an integer in the given base (2&ndash;36), returning <code>none</code> on failure. Surrounding whitespace, an optional sign, and a redundant matching <code>0o</code>/<code>0x</code>/<code>0b</code> prefix are all accepted. The named wrappers <code>parseHex</code>, <code>parseOctal</code>, and <code>parseBin</code> cover bases 16/8/2.</td> <td><code>(<span class="sig-type sig-type-str">str</span> <span class="sig-type sig-type-int">int</span> -- <span class="sig-type sig-type-maybe">Maybe</span>[<span class="sig-type sig-type-int">int</span>])</code></td> </tr>
<tr> <td><code>toHex</code></td> <td>Format an integer as a bare hexadecimal string (<code>16 toBase</code>).</td> <td><code>(<span class="sig-type sig-type-int">int</span> -- <span class="sig-type sig-type-str">str</span>)</code></td> </tr>
<tr> <td><code>toOctal</code></td> <td>Format an integer as a bare octal string (<code>8 toBase</code>).</td> <td><code>(<span class="sig-type sig-type-int">int</span> -- <span class="sig-type sig-type-str">str</span>)</code></td> </tr>
<tr> <td><code>toBin</code></td> <td>Format an integer as a bare binary string (<code>2 toBase</code>).</td> <td><code>(<span class="sig-type sig-type-int">int</span> -- <span class="sig-type sig-type-str">str</span>)</code></td> </tr>
<tr> <td><code>parseHex</code></td> <td>Parse a hexadecimal string to <code>Maybe[int]</code> (<code>16 fromBase</code>); accepts an optional <code>0x</code> prefix.</td> <td><code>(<span class="sig-type sig-type-str">str</span> -- <span class="sig-type sig-type-maybe">Maybe</span>[<span class="sig-type sig-type-int">int</span>])</code></td> </tr>
<tr> <td><code>parseOctal</code></td> <td>Parse an octal string to <code>Maybe[int]</code> (<code>8 fromBase</code>); accepts an optional <code>0o</code> prefix.</td> <td><code>(<span class="sig-type sig-type-str">str</span> -- <span class="sig-type sig-type-maybe">Maybe</span>[<span class="sig-type sig-type-int">int</span>])</code></td> </tr>
<tr> <td><code>parseBin</code></td> <td>Parse a binary string to <code>Maybe[int]</code> (<code>2 fromBase</code>); accepts an optional <code>0b</code> prefix.</td> <td><code>(<span class="sig-type sig-type-str">str</span> -- <span class="sig-type sig-type-maybe">Maybe</span>[<span class="sig-type sig-type-int">int</span>])</code></td> </tr>
<tr> <td><code>exit</code></td> <td>Exit the current script with the provided exit code.</td> <td><code>(<span class="sig-type sig-type-int">int</span> -- )</code></td> </tr>
<tr> <td><code>return</code></td> <td>Stop executing the current definition or quotation immediately, leaving the stack as-is for the caller.</td> <td><code>(--)</code></td> </tr>
<tr> <td><code>read</code></td> <td>Read a line from stdin. Leaves the line and a success flag.</td> <td><code>(-- <span class="sig-type sig-type-str">str</span> <span class="sig-type sig-type-bool">bool</span>)</code></td> </tr>
Expand Down Expand Up @@ -113,7 +121,7 @@ <h1 id="functions-file-directory">File and Directory <a class="section-link" hre
<tr> <td><code>mv</code></td> <td>Move a file or directory.</td> <td><code>(<span class="sig-type sig-type-str">str</span>:source <span class="sig-type sig-type-str">str</span>:dest -- )</code></td> </tr>
<tr> <td><code>zipDirInc</code></td> <td>Create/overwrite a <code>.zip</code> from a directory; the archive root contains the directory’s contents (no parent folder).</td> <td><code>(<span class="sig-type sig-type-path">path</span>:sourceDir <span class="sig-type sig-type-path">path</span>:zipPath -- )</code></td> </tr>
<tr> <td><code>zipDirExc</code></td> <td>Create/overwrite a <code>.zip</code> that includes the source directory itself at the archive root (entries are prefixed with the directory name).</td> <td><code>(<span class="sig-type sig-type-path">path</span>:sourceDir <span class="sig-type sig-type-path">path</span>:zipPath -- )</code></td> </tr>
<tr> <td><code>zipPack</code></td> <td>Create/overwrite a <code>.zip</code> by packing a list of dictionaries. Each dictionary must contain <code>path</code> plus optional <code>archivePath</code> (override the in-archive name) and <code>mode</code> (os.FileMode as an int).</td> <td><code>([<span class="sig-type sig-type-dict">dict</span>] <span class="sig-type sig-type-path">path</span>:zipPath -- )</code></td> </tr>
<tr> <td><code>zipPack</code></td> <td>Create/overwrite a <code>.zip</code> by packing a list of dictionaries. Each entry requires <code>path</code> (the file or directory to add); <code>archivePath</code> (override the in-archive name) and <code>mode</code> are optional. <code>mode</code> is a Go <a href="https://pkg.go.dev/io/fs#FileMode" target="_blank" rel="noopener noreferrer"><code>os.FileMode</code></a>; write it with an octal literal, e.g. <code>0o644</code> (<code>rw-r--r--</code>), <code>0o755</code> (<code>rwxr-xr-x</code>), <code>0o600</code>. On Linux/macOS these are the POSIX permission bits restored on extraction; on Windows file permissions are synthesized by Go and largely ignored (the executable bit is still preserved for Unix consumers). If <code>mode</code> is omitted, the entry keeps the source file’s own mode.</td> <td><code>([<span class="sig-type sig-type-dict">dict</span>] <span class="sig-type sig-type-path">path</span>:zipPath -- )</code></td> </tr>
<tr> <td><code>zipList</code></td> <td>List archive entries as dictionaries with columns: <code>name</code> (string, forward-slash paths, directories end with <code>/</code>), <code>compressedSize</code> (int bytes), <code>uncompressedSize</code> (int bytes), <code>isDir</code> (bool), <code>perm</code> (int POSIX permission bits), <code>executable</code> (bool), and <code>modified</code> (datetime from the archive entry).</td> <td><code>(<span class="sig-type sig-type-path">path</span> -- [<span class="sig-type sig-type-dict">dict</span>])</code></td> </tr>
<tr> <td><code>zipExtract</code></td> <td>Extract an entire archive. Options dict is required; defaults: <code>overwrite=false</code>, <code>skipExisting=false</code> (mutually exclusive), <code>stripComponents=0</code>, <code>pattern=""</code> (glob matched before stripping), <code>preservePermissions=true</code>. Destination is created if missing.</td> <td><code>(<span class="sig-type sig-type-path">path</span>:zipPath <span class="sig-type sig-type-path">path</span>:destDir <span class="sig-type sig-type-dict">dict</span>:options -- )</code></td> </tr>
<tr> <td><code>zipExtractEntry</code></td> <td>Extract a single entry (file or directory subtree) to a destination path. Options dict is required; defaults: <code>overwrite=false</code>, <code>skipExisting=false</code> (mutually exclusive), <code>preservePermissions=true</code>, <code>mkdirs=true</code> (create parent directories when needed).</td> <td><code>(<span class="sig-type sig-type-path">path</span>:zipPath <span class="sig-type sig-type-str">str</span>:entry <span class="sig-type sig-type-path">path</span>:dest <span class="sig-type sig-type-dict">dict</span>:options -- )</code></td> </tr>
Expand Down
37 changes: 36 additions & 1 deletion doc/mshell.md
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,28 @@ Dates can be subtracted from each other, and the result is a floating-point numb
2023-10-02 2023-10-01 - # 1.0
```

### Integers and Floats

Any literal number containing a decimal point is a float; otherwise it is an integer.

Integer literals may be written in octal, hexadecimal, or binary with a `0o`, `0x`, or `0b` prefix (case-insensitive).
The base is only a way of writing the literal — the stack value is a plain integer with no record of its base, and it prints in decimal.
There are no digit separators (`0o6_44` is not a number).

```mshell
0o644 # 420 (octal, e.g. a file mode)
0xFF # 255 (hexadecimal)
0b101 # 5 (binary)
```

Format an integer to a base-N string with `toBase` / `toHex` / `toOctal` / `toBin` (bare digits, no prefix),
and parse a string in a given base with `fromBase` / `parseHex` / `parseOctal` / `parseBin` (returns `Maybe[int]`).

```mshell
420 toOctal # "644"
"644" parseOctal # Some 420
```

## Type System

Use `msh --check-types script.msh` to run static type checking before script execution.
Expand Down Expand Up @@ -994,6 +1016,10 @@ end wl # Output: 11
- `x`: Interpret/execute quotation `(quote -- )`
- `toFloat`: Convert to float. A `str` is parsed and returns `Maybe[float]` (`none` on parse failure); an `int`/`float` returns a plain `float`. `(str -- Maybe[float])` / `(numeric -- float)`
- `toInt`: Convert to int. A `str` is parsed and returns `Maybe[int]` (`none` on parse failure); an `int`/`float` returns a plain `int` (floats truncate toward zero). `(str -- Maybe[int])` / `(numeric -- int)`
- `toBase`: Format an integer in the given base (2–36) as bare digits (no `0o`/`0x`/`0b` prefix). `(int int -- str)`
- `fromBase`: Parse a string as an integer in the given base (2–36), returning `none` on failure. Whitespace, an optional sign, and a redundant matching `0o`/`0x`/`0b` prefix are accepted. `(str int -- Maybe[int])`
- `toHex` / `toOctal` / `toBin`: Format an int as a bare hex/octal/binary string (`16`/`8`/`2 toBase`). `(int -- str)`
- `parseHex` / `parseOctal` / `parseBin`: Parse a hex/octal/binary string to `Maybe[int]` (`16`/`8`/`2 fromBase`); an optional matching prefix is accepted. `(str -- Maybe[int])`
- `exit`: Exit the current script with the provided exit code. `(int -- )`
- `read`: Read a line from stdin. Puts a str and bool of whether the read was successful on the stack. `( -- str bool)`
- `prompt`: Write a prompt string to the controlling TTY and read a line from the controlling TTY. Fails if no controlling TTY is available. `(str -- str)`
Expand Down Expand Up @@ -1368,7 +1394,16 @@ See [Regexp.Expand](https://pkg.go.dev/regexp#Regexp.Expand) for replacement syn

- `zipDirInc`: Create/overwrite a `.zip` from a directory; the archive root contains the directory's contents (no parent folder). `(path:sourceDir path:zipPath -- )`
- `zipDirExc`: Create/overwrite a `.zip` that includes the source directory itself at the archive root (entries are prefixed with the directory name). `(path:sourceDir path:zipPath -- )`
- `zipPack`: Create/overwrite a `.zip` by packing a list of dictionaries. Each dictionary must contain `path` plus optional `archivePath` (override the in-archive name) and `mode` (os.FileMode as an int). `([dict] path:zipPath -- )`
- `zipPack`: Create/overwrite a `.zip` by packing a list of dictionaries.
Each entry requires `path` (the file or directory to add);
`archivePath` (override the in-archive name) and `mode` are optional.
`mode` is a Go `os.FileMode`; write it with an octal literal,
e.g. `0o644` (`rw-r--r--`), `0o755` (`rwxr-xr-x`), `0o600`.
On Linux/macOS these are the POSIX permission bits restored on extraction;
on Windows file permissions are synthesized by Go and largely ignored
(the executable bit is still preserved for Unix consumers).
If `mode` is omitted, the entry keeps the source file's own mode.
Type: `([{path: str | path, archivePath?: str, mode?: int}] str | path -- )`
- `zipList`: List archive entries as dictionaries with keys: `name` (string, forward-slash paths, directories end with `/`), `compressedSize` (int bytes), `uncompressedSize` (int bytes), `isDir` (bool), `perm` (int POSIX permission bits), `executable` (bool), and `modified` (datetime from the archive entry). `(path -- [dict])`
- `zipExtract`: Extract an entire archive. Options dict is required; defaults: `overwrite=false`, `skipExisting=false` (mutually exclusive), `stripComponents=0`, `pattern=""` (glob matched before stripping), `preservePermissions=true`. Destination is created if missing. `(path:zipPath path:destDir dict:options -- )`
- `zipExtractEntry`: Extract a single entry (file or directory subtree) to a destination path. Options dict is required; defaults: `overwrite=false`, `skipExisting=false` (mutually exclusive), `preservePermissions=true`, `mkdirs=true`. `(path:zipPath str:entry path:dest dict:options -- )`
Expand Down
12 changes: 12 additions & 0 deletions lib/std.msh
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,18 @@ def 2tuple (a b -- [a | b])
[] rot append swap append
end

# Integer base formatting/parsing. The integer itself never records a base;
# these wrap the generic `toBase`/`fromBase` builtins for the common bases.
# Output is bare digits (no 0o/0x/0b prefix); parsing accepts an optional
# matching prefix, so both "0o644" and "644" work with parseOctal.
def toHex (int -- str) 16 toBase end
def toOctal (int -- str) 8 toBase end
def toBin (int -- str) 2 toBase end

def parseHex (str -- Maybe[int]) 16 fromBase end
def parseOctal (str -- Maybe[int]) 8 fromBase end
def parseBin (str -- Maybe[int]) 2 fromBase end

# COMPLETIONS {{{
# msh {{{
def __mshCompletion { 'complete': ['msh' 'mshell'] } ([str] -- [str])
Expand Down
2 changes: 2 additions & 0 deletions mshell/BuiltInList.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ var BuiltInList = map[string]struct{}{
"intCmp": {},
"dateTimeCmp": {},
"floor": {},
"fromBase": {},
"fromOleDate": {},
"fromUnixTime": {},
"fromUnixTimeMicro": {},
Expand Down Expand Up @@ -183,6 +184,7 @@ var BuiltInList = map[string]struct{}{
"tempFile": {},
"tempFileExt": {},
"title": {},
"toBase": {},
"toDict": {},
"toDt": {},
"toFixed": {},
Expand Down
Loading