Adds command completion to the repl and updates the readme

This commit is contained in:
sloum 2022-04-05 16:05:55 -07:00
parent 31405a6f2e
commit 781ff8efeb
4 changed files with 132 additions and 42 deletions

7
LICENSE Normal file
View File

@ -0,0 +1,7 @@
Copyright © 2021 sloum <sloum@rawtext.club>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

138
README.md
View File

@ -1,18 +1,55 @@
# 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.
slope is an interpreted s-expression based programming language. The interpreter is implemented in the [go programming language](https://go.dev). Those familiar with scheme or lisp will likely find themselves on reasonably familiar ground. Given its roots in golang, it wraps a lot of the go standard library in convenient ways for interpreted/script based programming. The [REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop) comes with command completion, readline-style line editing, and procedure help/definitions (via the `usage` procedure).
## 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).
**Table of Contents**
1. [Why another programming language?](#why-another-programming-language?)
2. [Acknowledgements](#acknowledgements)
3. [The Language](#the-language)
1. [Types](#types)
2. [Special Forms](#special-forms)
3. [Library](#library)
1. [Constant Values](#values)
2. [Numbers](#number-related)
3. [Conditionals](#conditional)
4. [Lists](#list-related)
5. [Strings](#string-related)
6. [IO](#io-related)
7. [System](#system-related)
8. [OS](#os-related)
9. [Net](#net-related)
10. [Date/Time](#date-time-related)
4. [Preload](#preload)
5. [Modules](#modules)
1. [Package Management](#package-management)
2. [How Modules Are Processed](#how-modules-are-processed)
3. [Module Documentation](#module-documentation)
4. [Module Files](#module-files)
1. [main.slo](#main.slo)
2. [Additional Source Files](#additional-source-files)
3. [module.json](#module.json)
6. [Building From Source](#building-from-source)
7. [Running](#running)
1. [The REPL](#the-repl)
2. [One Liners](#one-liners)
3. [Slope Files](#slope-files)
4. [Other Options](#other-options)
8. [License](#license)
## What is implemented?
## Why another programming language?
Like many many projects on the internet: to learn and have fun. 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 ended up with a much more realistically usable language (though your mileage may vary).
## Acknowledgements
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).
## The language
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
@ -416,7 +453,7 @@ _Note_: For the time being, `net-conn` can be used to make most requests. At the
</ul>
</details>
#### Date/Time Related
#### Date Time Related
Implemented:
@ -470,7 +507,6 @@ Implemented:
---
@ -482,7 +518,7 @@ slope allows for files in a specific directory to be automatically loaded. For t
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.
Unlike modules, arbitrary code execution _can_ 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
@ -501,9 +537,9 @@ So, a hypethetical module named `test` would be found at `~/.local/share/slope/m
Note that the fourth item, above, is the global (system-wide) module directory. `slp` can install modules to this directory by passing the `-g` or `--global` flag while acting as a user that has access to writing files in the `/usr/local` heirarchy. You may, alternately, clone modules directly to this path. A local module will always be used before a global module.
### slp
### Package Management
slope has a package manager available at [https://git.rawtext.club/slope-lang/slp](https://git.rawtext.club/slope-lang/slp). Once installed you will be able to search/browse packages, install, remove, and update things, generate new module skeletons, read docs, etc. The slp repository has information on how to get modules added to the package registry. This is, at present, the best way to deal with modules and is highly recommended but by no means required. The same thing can be done by finding a repository with a module and cloning it to your module path. So long as it is a valid module it will be loadable with `load-mod`.
slope has a package manager, `slp`, available at [https://git.rawtext.club/slope-lang/slp](https://git.rawtext.club/slope-lang/slp). Once installed you will be able to search/browse packages, install, remove, and update modules. You can also use `slp` to generate new module skeletons, read docs, etc. The `slp` repository has information on how to get modules added to the package registry. This is, at present, the best way to deal with modules and is highly recommended but by no means required. The same thing can be done by finding a repository with a module and cloning it to your module path. So long as it is a valid module it will be loadable with `load-mod`.
### How Modules are Processed
@ -514,24 +550,7 @@ A `define` expression _will still be fully evaluated_. As such, something like:
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.json
Modules found in the package registry will include this file. It contains metadata related to the package including things like description, version, repository url, homepage, author, and, most importantly the dependency list (so that a modules dependencies can also be installed).
If you are making a module and do not know what should be here you can look at an existing module to get a sense of it. Or, with `slp` installed, you can just run `slp gen` and it will build out your module skeleton for you.
#### Module Documentation
### Module Documentation
Modules may (one could argue _should_) contain a value named `_USAGE` in their `main.slo` file. This value should be an association list detailing each procedure. It should take the form of:
@ -556,38 +575,76 @@ This value is special and will be parsed so that the information can be retrieve
It is good form to always include the procedure signature as the first item in the usage instructions. Arguments are included in the format: `[name: type]`. Optional arguments use double square brackets: `[[name: type]]`. The output should be denoted after the close of the function signature via `=> type`. If multiple types can be used or returned use a `|`: `(my-func [input: number|string]) => procedure|#f`. If a specific value can be returned, you can include that, as `#f` was used in the previous example. Many procedures return some value _or_ false (`#f`) to indicate a failure.
### 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.
#### Additional 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.json
Modules found in the package registry will include this file. It contains metadata related to the package including things like description, version, repository url, homepage, author, and, most importantly the dependency list (so that a modules dependencies can also be installed).
If you are making a module and do not know what should be here you can look at an existing module to get a sense of it. Or, with `slp` installed, you can just run `slp gen` and it will build out your module skeleton for you.
## Building
## Building from source
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 slope is on your `$PATH`:
All of the shell commands in this section assume slope is on your `$PATH`.
To run the repl:
### The REPL
To run the slope REPL environment execute the following at your terminal prompt:
``` sh
slope
```
From the repl you can execute `(license)` to view the license.
You can, at your option, use the `-L` flag to preload the environment with your [preload](#preload) content.
From the REPL you can execute `(license)` to view the license. To exit, you execute `(exit)`.
To run a one liner:
While in the REPL readline compatible key commands are available. For a list of compatible key commands please see [this list](https://github.com/peterh/liner#line-editing).
In addition to key commands, the repl supports command completion for any identifier that has `usage` information available. Since `usage` supports getting usage information from _loaded_ (via `load-mod`) modules, any usage information provided by said modules will be included in completion. To complete a known identifier press the `Tab` key. The first found completion will appear. Subsequent presses of the `Tab` key will cycle through any other completion candidates that were found.
### One Liners
To run a one liner execute the following at your terminal prompt:
``` sh
slope -run "(display (+ 1 3))"
```
To run a file:
You may, of course, replace the contents between the quotes with your own one liner. In general all features of slope are available from a one liner, including loading modules.
You can, at your option, use the `-L` flag to preload the environment with your [preload](#preload) content.
### Slope Files
To run a file execute slope with the file path (an example path has been provided below):
``` sh
slope ./myfile.slo
```
You can, at your option, use the `-L` flag to preload the environment with your [preload](#preload) content.
### Other Options
To see command line options:
``` sh
@ -600,10 +657,7 @@ To see version information:
slope -v
```
To install a module from a url to a git repo:
``` sh
slope -install https://git.rawtext.club/sloum/ansi.git
```
## License
slope is available under the terms of the MIT license. See either the `LICENSE` file in this repository or run `(license)` from slope (via file, `-run`, or REPL).

View File

@ -574,3 +574,18 @@ func variadic(l expression) int {
}
return 0
}
func completeFromMap(m map[string]string, input string, index int) (c []string) {
for k, _ := range m {
if index < 0 && strings.HasPrefix(string(k), input) {
c = append(c, string(k))
} else if len(input) > index+1 && strings.HasPrefix(string(k), input[index+1:]) {
start := len(input) - index - 1
if start < 0 {
start = 0
}
c = append(c, input+string(k)[start:])
}
}
return
}

14
main.go
View File

@ -120,6 +120,20 @@ func Repl() {
f.Close()
}
// Set up completion
line.SetTabCompletionStyle(ln.TabCircular)
line.SetCompleter(func(l string) (c []string) {
if len(l) == 0 {
return
}
lastIndex := strings.LastIndexAny(l, "( \n")
c = append(c, completeFromMap(usageStrings, l, lastIndex)...)
for _, v := range moduleUsageStrings {
c = append(c, completeFromMap(v, l, lastIndex)...)
}
return
})
var text strings.Builder
var cont bool
for {