slope/README.md
2021-08-18 20:30:21 -07:00

524 lines
31 KiB
Markdown

# slope
slope is an s-expression based language loosely modeled after scheme R4RS. It does, however, diverge quite a bit in some areas (especially IO and filesystem related constructs). slope also does not include many standard constructs of scheme (such as _let_, _\*let_, _case_, etc.) in favor of using other existing constructs (this often involves slightly longer code that uses a narrower range of generalized constructs).
The interpreter is implemented in golang and many of the library differences between slope and scheme are based around generalizing the go standard library into a usable base system for a dynamically typed language. A great example of this generalization is that in slope you can use the function `write` to easily write to a file (including stdout/err), a tcp connection, or a buffer object. slope knows what you are trying to do based on what you pass it, so separate `file-write`, `tcp-conn-write`, `string-buf-write` functions are not necessary. Even with a runtime panic, slope will also attempt (I will stop short of guarantee) to close any open files or net connections. Other niceties are planned.
## Why?
Like many of my projects: to learn and have fun. This interpreter is based on some [existing code](https://gist.github.com/pkelchte/c2bd76b9f8f9cd603b3c). I rewrote sections of the lexer and parser (areas I enjoy working in), left the apply and environment setup alone, and made a few small changes to the eval function. As such, this could be considered a fork of that code with some added core language constructs as well as a significantly expanded library (another area I enjoy working in). I have definitely learned different things than my [previous language project](https://git.rawtext.club/sloum/nimf) while working on this codebase, and I think have already (early days as I am writing this) ended up with a much more realistically usable language (though that is certainly contextual).
## What is implemented?
As mentioned above, this is not a scheme implementation in any proper sense and it will go its own way when it sees fit.
### Types
slope recognizes a few types, that are generally created and used dynamically:
- `number`
- implemented as `float64` under the hood
- hexidecimal can be written with a leading `0x`, as in `0xFFF`
- octal can be written with a leading `0`, as in `077`
- in strings, values can be escaped to characters as decimal, octal, or hex: `"\27[1mI am bold text\033[0m, I am not bold. I am a hex based char: \0x84"`
- `symbol`
- strings that represent language objects/constructs/vars
- can be created using `(quote my-symbol)` or `'my-symbol `
- `bool`
- as `#t` and `#f`
- anything can be cast to bool with a `#t` value, except `#f` (the only truly falsy value without using `~bool`, see below)
- `string`
- anything in "quotes"
- allows for backslash escapes within the quotes (for chars by number or shortcuts to tab, newline, etc)
- `list`/`pair`
- includes the empty list (null)
- lists are the primary data structure
- `exception`
- an error response
- can be created using `(! "My exception text")`
- can be tested for with `(exception? some-var)`
- two functions exist to set behavior for exceptions:
- `(exception-mode-panic)`, will panic and halt execution when an exception is encountered (_default_)
- `(exception-mode-pass)`, will return the exception like any other value and it can be handled
- the two modes can be switched on and off at various points in the code
There is also the concept/type `IOHandle`. This type cannot be cast to another (except string, but the output will likely not be useful for much beyond debugging), nor can it be entered manually. It is returned by things like `net-conn` and `file-open-read`. It is the general concept of something that can be read from and/or written to.
### Special Forms
There are a number of special forms in the language that will allow, for example, un-initialized symbols to be passed (for example, in `define`), among other things less available from regular lambdas (which are themselves a special form).
- `quote`
- Can be shorthanded so that `'(1 2 3)` is equal to `(quote (1 2 3))`
- `if`
- `and`
- `or`
- `cond`
- `set!`
- `define`
- Lexically scoped, such that its use from within a lambda scopes any defined variables to the scope of that lambda and its child scopes
- `lambda`
- The body of a lambda has an implied `(begin ...)` surrounding it. As such, you may place non-nested procedure calls or values inside of a lambda body and the last will be returned
- Variadic lambdas can be created by using the special param name `args-list` (which will be a list of all of the remaining arguments when accessed rom the lambda body). This param should be the last param in the lambda definition, as it will eat all arguments that come after its position. Any params defined after `args-list` have undefined behavior (they will generally not be created)
- `begin`
- Can be shorthanded so that `{(+ 1 2) (display "Hi")}` is equal to `(begin (+ 1 2) (display "Hi"))`
- `begin0`
- Like begin, but returns the value resulting from the evaluation of the first expression. No shorthand exists for `begin0`
- `filter`
- Takes a procedure that, in turn, takes a single argument. Each value from the second argument, a list, will be passed to the procedure. Anything truthy results are returned in a new list
- `load`
- Loads and executes a slope file. Absolute path to file must be used
- `load-mod`
- Loads a module. Module _name_ should be used
- `load-mod-file`
- Used within modules to load additional files
<details>
<summary>Special Forms API Description</summary>
<p>Anything inside `[]` is a placeholder representing an argument. Anything inside `[[]]` is a placeholder representing an _optional_ argument. `...` indicates a variadic value.</p>
<p>What the procedure returns is indicated, though any procedure can return an exception in addition to the given type.</p>
<ul>
<li><code>(quote [expression...])</code>: <code>bool</code></li>
<li><code>(if [condition check: expression] [true branch: expression] [[false branch: expression]])</code>: <code>value</code></li>
<li><code>(and [expression...])</code>: <code>bool</code></li>
<li><code>(or [expression...])</code>: <code>bool</code></li>
<li><code>(cond [([condition check: expression] [expression])...])</code>: <code>value</code></li>
<li><code>(set! [variable name: symbol] [value|expression])</code>: <code>value/expression</code></li>
<li><code>(define [variable name: symbol] [value|expression|procedure])</code>: <code>value/expression</code></li>
<li><code>(lambda [([[argument: symbol...]])] [[expression...]])</code>: <code>procedure</code></li>
<li><code>(begin [expression...])</code>: <code>value</code></li>
<li><code>(begin0 [expression...])</code>: <code>value</code></li>
<li><code>(filter [test: procedure] [list])</code>: <code>list</code></li>
<li><code>(load [filepath: string...])</code>: <code>symbol</code></li>
<li><code>(load-mod [filepath: string...])</code>: <code>symbol</code></li>
<li><code>(load-mod-file [filepath: string...])</code>: <code>symbol</code></li>
</ul>
</details>
### Library
#### Values
`PI`, `E`
#### Number related
Implemented:
`positive?`, `negative?`, `zero?`, `abs`, `floor`, `ceil`, `round`, `+`, `-`, `*`, `/`, `min`, `max`, `number->string`
Not implemented, but on my radar:
`modulo`, `remainder`, `quotient`, `expt`, `sin`, `cos`, `tan`, `atan`, `sqrt`
<details>
<summary>Number API Description</summary>
<p>Anything inside `[]` is a placeholder representing an argument. Anything inside `[[]]` is a placeholder representing an _optional_ argument. `...` indicates a variadic value.</p>
<p>What the procedure returns is indicated, though any procedure can return an exception in addition to the given type.</p>
<ul>
<li><code>(positive? [number])</code>: <code>bool</code></li>
<li><code>(negative? [number])</code>: <code>bool</code></li>
<li><code>(zero? [number])</code>: <code>bool</code></li>
<li><code>(abs [number])</code>: <code>number</code></li>
<li><code>(floor [number])</code>: <code>number</code></li>
<li><code>(ceil [number])</code>: <code>number</code></li>
<li><code>(round [number] [[number: decimal places to round to]])</code>: <code>number</code></li>
<li><code>(+ [number...])</code>: <code>number</code></li>
<li><code>(- [number...])</code>: <code>number</code></li>
<li><code>(* [number...])</code>: <code>number</code></li>
<li><code>(/ [number...])</code>: <code>number</code></li>
<li><code>(min [number...])</code>: <code>number</code></li>
<li><code>(max [number...])</code>: <code>number</code></li>
<li><code>(number-> string [number])</code>: <code>number</code></li>
</ul>
</details>
#### Conditional
Implemented:
`>`, `>=`, `<`, `<=`, `not`, `equal?`, `number?`, `string?`, `bool?`, `symbol?`, `pair?`, `null?`, `list?`, `procedure?`, `atom?`, `~bool`, `assoc?`, `string-buf?`, `io-handle?`
`~bool` is non-standard and will cast any value as a boolean value using a looser set of rules. Under normal conditional rules in _slope_ anythong other than `#f` is considered truthy (`#t`). When using `~bool` the number `0` is false, the list empty list `()` is false, and empty string `""` is false, and a closed IOHandle is false. Everything else, except the literal value `#f`, is true (`#t`).
So...
``` slope
> (if (~bool 0) "Truthy" "Falsy")
#1=> Falsy
> (if 0 "Truthy" "Falsy")
#2=> Truthy
```
<details>
<summary>Conditional API Description</summary>
<p>Anything inside `[]` is a placeholder representing an argument. Anything inside `[[]]` is a placeholder representing an _optional_ argument. `...` indicates a variadic value.</p>
<p>What the procedure returns is indicated, though any procedure can return an exception in addition to the given type.</p>
<ul>
<li><code>(> [number] [number])</code>: <code>bool</code></li>
<li><code>(>= [number] [number])</code>: <code>bool</code></li>
<li><code>(< [number] [number])</code>: <code>bool</code></li>
<li><code>(<= [number] [number])</code>: <code>bool</code></li>
<li><code>(not [value])</code>: <code>bool</code></li>
<li><code>(equal? [value] [value])</code>: <code>bool</code></li>
<li><code>(number? [value])</code>: <code>bool</code></li>
<li><code>(string? [value])</code>: <code>bool</code></li>
<li><code>(bool? [value])</code>: <code>bool</code></li>
<li><code>(symbol? [value])</code>: <code>bool</code></li>
<li><code>(pair? [value])</code>: <code>bool</code></li>
<li><code>(list? [value])</code>: <code>bool</code></li>
<li><code>(null? [value])</code>: <code>bool</code></li>
<li><code>(procedure? [value])</code>: <code>bool</code></li>
<li><code>(atom? [value])</code>: <code>bool</code></li>
<li><code>(assoc? [value])</code>: <code>bool</code></li>
<li><code>(io-handle? [value])</code>: <code>bool</code></li>
<li><code>(string-buf? [value])</code>: <code>bool</code></li>
<li><code>(~bool [value])</code>: <code>bool</code></li>
</ul>
</details>
#### List related
Implemented:
`length`*, `cons`, `car`, `cdr`, `append`, `list`, `map`, `for-each`, `list->string`, `list-ref`, `list-sort`, `reverse`, `assoc-get`, `assoc-set`, `member?`
- `length` and `reverse` will take a string or a list.
- `list-sort` will sort a list in ascending order by casting all elements within the list as strings and doing a compare. The exception to this is numbers, which will be compared among themselves as numbers. In a mixed list numbers will come after other values. To sort descending, pass the results of `list-sort` to `reverse`
<details>
<summary>List API Description</summary>
<p>Anything inside `[]` is a placeholder representing an argument. Anything inside `[[]]` is a placeholder representing an _optional_ argument. `...` indicates a variadic value.</p>
<p>What the procedure returns is indicated, though any procedure can return an exception in addition to the given type.</p>
<ul>
<li><code>(length [list|string|exception|IOHandle:string-buf])</code>: <code>number</code></li>
<li><code>(list [value...])</code>: <code>list</code></li>
<li><code>(cons [value] [list])</code>: <code>list</code></li>
<li><code>(car [list])</code>: <code>list</code></li>
<li><code>(cdr [list])</code>: <code>list</code></li>
<li><code>(append [list] [value...])</code>: <code>list</code></li>
<li><code>(map [procedure] [list...])</code>: <code>list</code></li>
<li><code>(for-each [procedure] [list...])</code>: <code>list</code></li>
<li><code>(list->string [list])</code>: <code>string</code></li>
<li><code>(list-ref [list] [number])</code>: <code>value</code></li>
<li><code>(list-sort [list])</code>: <code>list</code></li>
<li><code>(reverse [list|string])</code>: <code>list</code></li>
<li><code>(assoc-get [assoc-list] [value])</code>: <code>value</code></li>
<li><code>(assoc-set [assoc-list] [value] [value])</code>: <code>value</code></li>
<li><code>(member? [list] [value])</code>: <code>bool</code></li>
</ul>
</details>
#### String related
Implemented:
`length`*, `substring`, `string-append`, `string->number`, `string-format`, `string->list`, `string-trim-space`, `string-index-of`, `string-ref`, `string-upper`, `string-lower`, `string-make-buf`, `string-buf-clear`, `regex-match?`, `regex-find`, `regex-replace`, `string-fields`
- `length` will take a string, an IOHandle representing a string-buf, or a list. In the case of a string it will return the number of _runes_ as thought of in golang. Loosely equivalent to characters, rather than bytes.
- Standard read and write procedures work with the IOHandle returned from `string-make-buf`
- `length` and `reverse` will take a string or a list.
- `string-format` works like a simplified sprintf. All values are represented by `%v` (for value). You can pad values with spaces on the right by adding the desired field width after the percent: `%10v` with the string `"hello"` would produce `" hello"` and you can left justify the text with a `-` after the percent: `%-10v` for the same variable would produce `"hello "`. There are no other modifiers.
<details>
<summary>String API Description</summary>
<p>Anything inside `[]` is a placeholder representing an argument. Anything inside `[[]]` is a placeholder representing an _optional_ argument. `...` indicates a variadic value.</p>
<p>What the procedure returns is indicated, though any procedure can return an exception in addition to the given type.</p>
<ul>
<li><code>(length [list|string|exception|IOHandle:string-buf])</code>: <code>number</code></li>
<li><code>(list [value...])</code>: <code>list</code></li>
<li><code>(substring [string] [number] [number])</code>: <code>string</code></li>
<li><code>(string-append [string...])</code>: <code>string</code></li>
<li><code>(string-format [format: string] [value...])</code>: <code>string</code> (replaces `{}`'s in string with successive values)</li>
<li><code>(string->number [string])</code>: <code>number</code></li>
<li><code>(string->list [string] [[value]])</code>: <code>list</code> (splits the first string at each instance of value, cast as a string)</li>
<li><code>(string-fields [string])</code>: <code>list</code></li>
<li><code>(string-trim-space [string])</code>: <code>string</code></li>
<li><code>(string-index-of [string] [string])</code>: <code>number</code> (`-1` if the string could not be found)</li>
<li><code>(string-ref [string] [number])</code>: <code>string</code></li>
<li><code>(string-upper [string])</code>: <code>string</code></li>
<li><code>(string-lower [string])</code>: <code>string</code></li>
<li><code>(string-make-buf)</code>: <code>IOHandle:string-buf</code></li>
<li><code>(string-buf-clear [IOHandle:string-buf])</code>: <code>()</code></li>
<li><code>(regex-match? [string (pattern)] [string])</code>: <code>bool</code></li>
<li><code>(regex-find [string (pattern)] [string])</code>: <code>list</code></li>
<li><code>(regex-replace [string (pattern)] [string])</code>: <code>string</code></li>
</ul>
</details>
#### IO Related
Implemented:
`newline`, `display`, `display-lines`, `write`, `write-raw`, `read-line`, `read-char`, `read-all`, `close` `file-create`, `file-open-read`, `file-open-write`, `file-append-to`, `read-all-lines`
Not implemented, but on my radar:
`read-bytes`, `file-seek`
<details>
<summary>IO API Description</summary>
<p>Anything inside `[]` is a placeholder representing an argument. Anything inside `[[]]` is a placeholder representing an _optional_ argument. `...` indicates a variadic value.</p>
<p>What the procedure returns is indicated, though any procedure can return an exception in addition to the given type.</p>
<ul>
<li><code>(newline)</code>: <code>()</code></li>
<li><code>(display [value...])</code>: <code>()</code></li>
<li><code>(display-lines [value...])</code>: <code>()</code></li>
<li><code>(write [string] [[IOHandle]])</code>: <code>()|IOHandle</code></li>
<li><code>(write-raw [string] [[IOHandle]])</code>: <code>()|IOHandle</code></li>
<li><code>(read-all [[IOHandle]])</code>: <code>string</code></li>
<li><code>(read-line [[IOHandle]])</code>: <code>string|symbol:EOF</code></li>
<li><code>(read-char [[IOHandle]])</code>: <code>string</code></li>
<li><code>(read-all-lines [[IOHandle]])</code>: <code>list</code></li>
<li><code>(close [IOHandle])</code>: <code>()</code></li>
<li><code>(file-create [filepath: string])</code>: <code>IOHandle</code></li>
<li><code>(file-open-read [filepath: string])</code>: <code>IOHandle</code></li>
<li><code>(file-open-write [filepath: string])</code>: <code>IOHandle</code></li>
<li><code>(file-append-to [filepath: string] [string...])</code>: <code>()</code></li>
</ul>
</details>
#### System Related
Implemented:
`exit`, `license`, `apply`, `!`, `exception-mode-panic`, `exception-mode-pass`, `sys-args`, `term-size`, `term-restore`, `term-char-mode`, `term-raw-mode`, `term-cooked-mode`, `term-sane-mode`
- `(license)` will print the license terms for slope, this is mostly input as a convenience in the repl.
- The various term modes are an experimental feature at the moment, but should work as described
<details>
<summary>System API Description</summary>
<p>Anything inside `[]` is a placeholder representing an argument. Anything inside `[[]]` is a placeholder representing an _optional_ argument. `...` indicates a variadic value.</p>
<p>What the procedure returns is indicated, though any procedure can return an exception in addition to the given type.</p>
<ul>
<li><code>(exit [[number]])</code>: <code>()</code></li>
<li><code>(license)</code>: <code>()</code></li>
<li><code>(apply [procedure] [arguments: list])</code>: <code>value</code></li>
<li><code>(! [string])</code>: <code>exception</code></li>
<li><code>(exception-mode-panic)</code>: <code>()</code></li>
<li><code>(exception-mode-pass)</code>: <code>()</code></li>
<li><code>(sys-args)</code>: <code>list</code></li>
<li><code>(term-size)</code>: <code>list</code></li>
<li><code>(term-restore)</code>: <code>()</code></li>
<li><code>(term-char-mode)</code>: <code>()</code></li>
<li><code>(term-raw-mode)</code>: <code>()</code></li>
<li><code>(term-cooked-mode)</code>: <code>()</code></li>
<li><code>(term-sane-mode)</code>: <code>()</code></li>
</ul>
</details>
#### OS Related
`path-exists?`, `path-is-dir?`, `path-abs`, `path-join`, `path-ext`, `path-glob`, `chmod`, `chdir`, `env`, `subprocess`
Not implemented, but on my radar:
`path-dir-contents`, `path-dir`, `path-base`, `chown`, `mkdir`
<details>
<summary>OS API Description</summary>
<p>Anything inside `[]` is a placeholder representing an argument. Anything inside `[[]]` is a placeholder representing an _optional_ argument. `...` indicates a variadic value.</p>
<p>What the procedure returns is indicated, though any procedure can return an exception in addition to the given type.</p>
<ul>
<li><code>(path-exists? [filepath: string])</code>: <code>bool</code></li>
<li><code>(path-is-dir? [filepath: string])</code>: <code>bool</code></li>
<li><code>(path-abs [filepath: string])</code>: <code>string</code></li>
<li><code>(path-join [string])</code>: <code>string</code></li>
<li><code>(path-extension [filepath: string] [[replacement-extension: string]])</code>: <code>string</code></li>
<li><code>(path-glob [filepath: string])</code>: <code>list</code></li>
<li><code>(chmod [filepath: string] [file-mode: number])</code>: <code>()</code></li>
<li><code>(chdir [filepath: string])</code>: <code>()</code></li>
<li><code>(env [[env-key: string]] [[env-value: string]])</code>: <code>list|string|()</code></li>
<li><code>(subprocess [list] [[output-redirection: IOHandle|#f]] [[error-redirection: IOHandle|#f]])</code>: <code>number</code> (Passing `#f` to output redirection allows you to do stdout while still redirecting stderr. The `#f` for stderr only exists for symetry)</li>
</ul>
</details>
#### Net Related
Implemented:
`net-conn`
For the time being, `net-conn` can be used to make most requests. At the moment the tls settings are not particularly secure and I am looking into ways to allow for more granular customization of both the timeout and the tls without weighing down the function or introducing more types.
<details>
<summary>Net API Description</summary>
<p>Anything inside `[]` is a placeholder representing an argument. Anything inside `[[]]` is a placeholder representing an _optional_ argument. `...` indicates a variadic value.</p>
<p>What the procedure returns is indicated, though any procedure can return an exception in addition to the given type.</p>
<ul>
<li><code>(net-conn [host: string] [port: string|number] [[use-tls: bool]] [[timeout-seconds: number]])</code>: <code>IOHandle</code></li>
</ul>
</details>
#### Date/Time Related
Implemented:
`timestamp`, `date`, `timestamp->date`, `date->timestamp`, `date-format`, `sleep`, `date-default-format`
<details>
<summary>Date/Time API Description</summary>
<p>Anything inside `[]` is a placeholder representing an argument. Anything inside `[[]]` is a placeholder representing an _optional_ argument. `...` indicates a variadic value.</p>
<p>What the procedure returns is indicated, though any procedure can return an exception in addition to the given type.</p>
<ul>
<li><code>(timestamp)</code>: <code>timestamp: number</code></li>
<li><code>(date)</code>: <code>date: string</code></li>
<li><code>(timestamp->date [timestamp: string|number] [[format: string]])</code>: <code>string</code></li>
<li><code>(date->timestamp [date: string] [format: string])</code>: <code>timestamp: number</code></li>
<li><code>(date-format [start-format: string] [date: string] [end-format: string])</code>: <code>string</code></li>
<li><code>(sleep [[milliseconds: number]])</code>: <code>()</code></li>
<li><code>(date-default-format)</code>: <code>string</code> (returns a format string for the default format produced by <code>date</code>)</li>
</ul>
<p>In the above procedures there are strings listed as "format". The format string is similar to the format string used in PHP, Python, etc.</p>
<p>Certain characters are escaped by <code>%</code> symbols followed by a single character. Here is the list of the conversions:</p>
<dl>
<dt><code>%a</code></dt><dd>12 hour segment, lowercase: <code>pm</code></dd>
<dt><code>%A</code></dt><dd>12 hour segment, uppercase: <code>PM</code></dd>
<dt><code>%d</code></dt><dd><b>Day</b> without leading zero: <code>4</code></dd>
<dt><code>%D</code></dt><dd><b>Day</b> with leading zero: <code>04</code></dd>
<dt><code>%e</code></dt><dd><b>Day</b> without leading zero, but with space padding: <code> 4</code></dd>
<dt><code>%f</code></dt><dd><b>Month</b> name, short: <code>Jan</code></dd>
<dt><code>%F</code></dt><dd><b>Month</b> name, long: <code>January</code></dd>
<dt><code>%g</code> or <code>%G</code></dt><dd><b>Hour</b>, 24 hour format: <code>15</code></dd>
<dt><code>%h</code></dt><dd><b>Hour</b>, 12 hour format without leading zero: <code>2</code></dd>
<dt><code>%H</code></dt><dd><b>Hour</b>, 12 hour format with leading zero: <code>02</code></dd>
<dt><code>%i</code></dt><dd><b>Minutes</b> without leading zero: <code>4</code></dd>
<dt><code>%I</code></dt><dd><b>Minutes</b> with leading zero: <code>04</code></dd>
<dt><code>%m</code></dt><dd><b>Month number</b>, without leading zero: <code>6</code></dd>
<dt><code>%M</code></dt><dd><b>Month number</b>, with leading zero: <code>06</code></dd>
<dt><code>%o</code></dt><dd>Timezone <b>offset</b>, without minutes: <code>-07</code></dd>
<dt><code>%O</code></dt><dd>Timezone <b>offset</b>, with minutes: <code>-0700</code></dd>
<dt><code>%s</code></dt><dd><b>Seconds</b>, without leading zero: <code>5</code></dd>
<dt><code>%S</code></dt><dd><b>Seconds</b>, with leading zero: <code>05</code></dd>
<dt><code>%w</code></dt><dd>Short <b>weekeday</b>: <code>Wed</code></dd>
<dt><code>%W</code></dt><dd>Long <b>weekday</b>: <code>Wednesday</code></dd>
<dt><code>%y</code></dt><ddaTwo digit <b>year</b>: <code>21</code></dd>
<dt><code>%Y</code></dt><dd>Four digit <b>year</b>: <code>2021</code></dd>
<dt><code>%Z</code></dt><dd><b>Time zone</b>, as three chars: <code>UTC</code>, <code>PST</code>, etc.</dd>
<dt><code>%</code></dt><dd>Will yield a literal <code>%</code></dd>
<dt><code>anything else</code></dt><dd>A percent followed by an unrecognized char will yield a <code>?</code>, as will a hanging % as the last char in the string</dd>
</dl>
<p>So the string: <code>"%F %e, %Y %h:%I%a"</code> would be equivalend to something like: <code>"August 8, 2021 4:03pm"</code></p>
<p>When a string is converted to a date and no time zone information is present, slope will assume the user's local time.</p>
</details>
---
\*the general write, read, and close operations in the IO section apply to net objects as well as files and string buffers
## Preload
slope allows for files in a specific directory to be automatically loaded. For this to occur slope should be invoked with the `-L` flag. When that flag is passed slope will do its normal setup routine then preload all files in the preload folder then go on to whatever its main task is (repl, run file, run single line from command).
slope will use the variable `$SLOPE_PRELOAD_DIR` if it is defined and not empty. Otherwise, if `$XDG_DATA_HOME` is defined and not empty it will use `$XDG_DATA_HOME/slope/preload/`. Lastly it will use `~/.local/share/slope/preload/`.
Unlike modules, arbitrary code execution will occur with preloads (not just `define` expressions). The rationalle is that you, the user, have put this code there and as such it is considered safe and in your control. Preloads are a really easy way to make procedures and variables outside of the standard library, but that you regularly use, available at a repl session without having to manually load them.
## Modules
slope has a very basic module system. When you use the `load` procedure you are loading a single file from a given path. Anything in that file will be run when it is loaded. Using `load-mod` allows you to load a module from a designated location on your system by simply passing the module's name (as represented by the directory name it is contained within).
`load-mod` will load modules from the first non-empty-string value it finds out of these options:
1. `$SLOPE_MOD_PATH`
2. `$XDG_DATA_HOME/slope/modules`
3. `~/.local/share/slope/modules`
So, a hypethetical module named `test` would be found at `~/.local/share/slope/modules/test`.
A separate tool is coming soon to help manage acquiring/removing/viewing modules as well as dealing with subdependencies. Until then it is a more manual process.
### How Modules are Processed
When `load-mod` is called the module will be located and `main.slo` will be parsed. All expressions are ignored except `define`, `load-mod`, and `load-mod-file`. Arbitrary code cannot be run (there are no side effects to `load-mod` or `load-mod-file` other than populating the global environment with the defined values from the given module).
A `define` expression _will still be fully evaluated_. As such, something like: `(define twenty (+ 15 5))` is perfectly fine. But something like `(display "I am in a module")` will not be evaluated.
Calling `load-mod` from within a module allows modules to have their own dependencies. Calling `load-mod-file` allows modules to be made up of more files than just `main.slo`. More info about files can be found in the next section.
### Module Files
#### main.slo
Taking our above example of a module named `test`, the bare minimum that the folder `~/.local/share/slope/modules/test` should contain is a file named `main.slo`. When a module is loaded, this is the file that is looked for. If it is not present, loading will fail and a runtime panic will result.
#### Other slope source files
You may, optionally, have other code files present in the module and can load them by calling `load-mod-file`. This procedure should be given a path relative to the module root directory. So within our `main.slo` file we might call something like: `(load-mod-file "./submodules/unit-tests.slo")`. Something to note is that a call to `load-mod-file` cannot jump out of the module's root directory (doing something like `../../../some-other-code.py` or `/etc/something` will result in a runtime panic).
#### module.info
Another special file that is not yet in use, but is planned, is `module.info`. This file will contain details about the module: name, dependencies, repo url, description, version number, commit-hash, license, etc. This will be used by an eventual tool that will manage modules and dependencies.
## Building
A Makefile is available. Running `make` will build in the local directory. Running `sudo make install` will install the software (sudo may or may not be needed depending on your setup, or the functionality may be provided by a different tool such as doas). _slope_ was written with Go 1.16, but will likely build on older versions with limited modifications.
## Running
All of these assume slosh is on your `$PATH`:
To run the repl:
``` sh
slope
```
From the repl you can execute `(license)` to view the license.
To run a one liner:
``` sh
slope -run "(display (+ 1 3))"
```
To run a file:
``` sh
slope ./myfile.slo
```
To see command line options:
``` sh
slope -h
```
To see version information:
``` sh
slope -v
```
## TODO
- Add termios, net-serve, and other procedures
- Maybe add some form of character primitive type (will make reading by either byte or char from a file easier)
- Build cool things with _slope_
- Think out a basic package system with imports