Starts adding usage information in order to build up an internal help system

master
sloum 2 years ago
parent c3307e4375
commit 5b5e496f06

@ -76,6 +76,8 @@ There are a number of special forms in the language that will allow, for example
- Loads a module. Module _name_ should be used
- `load-mod-file`
- Used within modules to load additional files
- `usage`
- Used to display the procedure signature and any additional information about the procedure, mostly used interactively
<details>
@ -97,6 +99,7 @@ There are a number of special forms in the language that will allow, for example
<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>
<li><code>(usage [symbol|string]): <code>()</code> Will display usage information for the given procedure or runtime value</li>
</ul>
</details>
@ -140,7 +143,7 @@ Not implemented, but on my radar:
<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] [[base: number]])</code>: <code>string</code></li>
<li><code>(number->string [number] [[base: number]])</code>: <code>string</code></li>
<li><code>(rand [[max]] [[min]])</code>: <code>number</code>
</ul>
</details>
@ -226,7 +229,7 @@ Implemented:
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`, `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`, `string->md5`, `string->sha256`
- `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`
@ -244,8 +247,10 @@ Implemented:
<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->number [string] [[base: number]])</code>: <code>number</code> If a non-decimal base is supplied, the input string will be parsed as an integer and any flaoting point value will be floored</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->md5 [string])</code>: <code>string</code> If a non-decimal base is supplied, the input string will be parsed as an integer and any flaoting point value will be floored</li>
<li><code>(string->sha256 [string])</code>: <code>string</code> If a non-decimal base is supplied, the input string will be parsed as an integer and any flaoting point value will be floored</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>

@ -472,9 +472,9 @@ func percentToDate(c rune) string {
case 'Z':
return "MST" // Time zone as three chars
case '%':
return "%"
return "%" // Literal percent
default:
return "?"
return "?" // Unknown escape sequence
}
}
@ -500,3 +500,4 @@ func createTimeFormatString(s string) string {
}
return out.String()
}

@ -105,7 +105,10 @@ func eval(exp expression, en *env) (value expression) {
if len(e) < 3 {
return exception("Invalid 'set!' syntax - too few arguments")
}
v := e[1].(symbol)
v, ok := e[1].(symbol)
if !ok {
return exception("'set!' expected a symbol as its first argument, a non-symbol was provided")
}
val := eval(e[2], en)
en.Find(v).vars[v] = val
value = val
@ -138,6 +141,22 @@ func eval(exp expression, en *env) (value expression) {
eval(i, en)
}
}
case "usage":
proc, ok := e[1].(string)
if !ok {
p, ok2 := e[1].(symbol)
if !ok2 {
return exception("'usage' expected a string or symbol as its first argument, a non-string non-symbol value was given")
}
proc = string(p)
}
v, ok := usageStrings[proc]
if !ok {
fmt.Printf("%q does not have a usage definition\n", proc)
} else {
fmt.Println(v)
}
value = make([]expression, 0)
case "load":
if en.outer != nil {
return expression("'load' is only callable from the global/top-level")

@ -2,6 +2,8 @@ package main
import (
"bufio"
"crypto/md5"
"crypto/sha256"
"crypto/tls"
"fmt"
"io"
@ -23,8 +25,6 @@ import (
"git.rawtext.club/sloum/slope/termios"
)
// TODO add proc to manually force/throw a panic
var stdLibrary = vars{
"stdin": &IOHandle{os.Stdin, true},
"stdout": &IOHandle{os.Stdout, true},
@ -1256,16 +1256,35 @@ var stdLibrary = vars{
return out.String()
},
"string->number": func(a ...expression) expression {
// (string->number [string] [[base: number]])
if len(a) == 0 {
return exception("insufficient number of arguments given to 'string->number'")
}
if s, ok := a[0].(string); ok {
s, ok := a[0].(string)
if !ok {
return exception("'string->number' expects a string as its first argument, a non-string value was given")
}
base := 10
if len(a) > 1 {
b, ok2 := a[1].(number)
if !ok2 {
return exception("'string->number' expects a number representing the base being used for number parsing as its second argument, a non-number value was given")
}
base = int(b)
}
if len(a) == 1 || base == 10 {
f, err := strconv.ParseFloat(s, 64)
if err == nil {
return number(f)
}
}
return false
i, err := strconv.ParseInt(s, base, 64)
if err != nil {
return exception("'string->number' was unable to parse the given string into a valid number")
}
return number(i)
},
"string-fields": func(a ...expression) expression {
if len(a) == 0 {
@ -1374,7 +1393,9 @@ var stdLibrary = vars{
if !ok2 {
return exception("'number->string' expected a number as its second argument, a non-number value was given")
}
return strconv.FormatInt(int64(n), int(base))
if base != 10 {
return strconv.FormatInt(int64(n), int(base))
}
}
return strconv.FormatFloat(float64(n), 'f', -1, 64)
},
@ -1479,9 +1500,7 @@ var stdLibrary = vars{
return exception("'file-open-read' expects a filepath as a string, a non-string value was given")
},
"file-open-write": func(a ...expression) expression {
//
// (file-open-write _filepath_ [_truncate bool_])
//
// (file-open-write [filepath: string] [[truncate: bool]])
if len(a) == 0 {
return exception("'file-open-write' expects a filepath, no filepath was given")
}
@ -1503,9 +1522,7 @@ var stdLibrary = vars{
return exception("'file-write' expects a filepath as a string, a non-string value was given")
},
"file-append-to": func(a ...expression) expression {
//
// (file-append-to "filepath" "string"...)
//
// (file-append-to [filepath: string] [[string...]])
if len(a) < 2 {
return exception("'file-append-to' requires a filepath and at least one string")
}
@ -2283,7 +2300,26 @@ var stdLibrary = vars{
return exception("'rand' expected a number as its second argument, a non-number value was given")
}
return number(rand.Float64() * (float64(max) - float64(min)) + float64(min))
},
"string->md5": func(a ...expression) expression {
if len(a) == 0 {
return exception("'string->md5' expects a string, no value was given")
}
s, ok := a[0].(string)
if !ok {
return exception("'string->md5' expected a string as its first argument, a non-string argument was given")
}
return fmt.Sprintf("%x", md5.Sum([]byte(s)))
},
"string->sha256": func(a ...expression) expression {
if len(a) == 0 {
return exception("'string->sha256' expects a string, no value was given")
}
s, ok := a[0].(string)
if !ok {
return exception("'string->sha256' expected a string as its first argument, a non-string argument was given")
}
return fmt.Sprintf("%x", sha256.Sum256([]byte(s)))
},
}

@ -11,3 +11,190 @@ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR I
`
const historyFilename string = "slope-repl.history"
var usageStrings = map[string]string{
"quote": "(quote [expression...]) => bool",
"if": "(if [condition: expression] [#t-branch: expression] [[#f-branch: expression]]) => value\n\nAny value other than #f is considered true for the purposes of the `condition`, any expression given to `if` as a `condition` will be evaluated and its value will be taken as truthy or falsy by the above truthiness rule",
"and": "(and [expression...]) => bool",
"or": "(or [expression...]) => bool",
"cond": "(cond [([condition: expression] [#t-branch: expression])...]) => value\n\nWill evaluate each condition in turn and evaluate to the #t-branch of the first condition that is truthy. If no condition is truthy then an empty list will be returned. The value `else` is a sepcial case allowed as the final condition expression, the #t-branch of this condition list will be returned if no other condition is found to be truthy",
"set!": "(set! [var-name: symbol] [value|expression|procedure]) => value | expression | procedure\n\nReturns the value that var-name was set to, in addition to creating the var symbol and setting it to the given value. Only an existing variable can be set with `set!`, trying to set a non-existant variable will result in an exception. `set!` will always set the value for the variable in the nearest scope",
"define": "(define [var-name] [value|expression|procedure]) => value | expression | procedure\n\nReturns the value that var-name was set to, in addition to creating the var symbol and setting it to the given value. Define is lexically scoped (using define within a lambda will create a variable that is only visible to that lambda and its child scopes, but not to any parent scopes)",
"lambda": "(lambda [([[argument: sumbol...]])] [expression...]) => procedure\n\n`lambda` creates its own scope and any define statements within the lambda will be scoped to the lambda (available to it and any child scopes)",
"begin": "(begin [expression...]) => value\n\nEvaluates a series of expressions and returns the value of the last expression that is evaluated",
"begin0": "(begin [expression...]) => value\n\nEvaluates a series of expressions and returns the value of the first expression that is evaluated",
"filter": "(filter [test: procedure] [list]) => list\n\nThe test procedure supplied to filter should take one value and will have its return value treated as a bool. Any value in the list that returns a true when fed to the test procedure will be added to the list that filter returns",
"load": "(load [filepath: string...]) => symbol\n\n`load` will open the file represented by filepath and evaluate its contents as slope code. It will run any code within the file. `load` can accept a relative reference, which will be treated as relative to the current working directory. Any `define` statements within the laoded file will result in the symbol getting added to the global environment",
"load-mod": "(load-mod [module-name: string...]) => symbol\n\n`load-mod` will search the module path for the given module. If found, the file 'main.slo' within the module folder will be parsed. Arbitrary code will not be executed, only `define`, `load-mod`, and `load-mod-file` statements will be evaluated",
"load-mod-file": "(load-mod-file [filepath: string]) => symbol\n\n`load-mod-file` is used within modules to do a relative load of files that are a part of the module, but are not 'main.slo'. The same evaluation rules as `load-mod` apply to `load-mod-file`",
"positive?": "(positive? [number]) => bool",
"negative?": "(negative? [number]) => bool",
"zero?": "(zero? [number]) => bool",
"abs": "(abs [number]) => number",
"floor": "(floor [number]) => number",
"ceil": "(ceil [number]) => number",
"round": "(round [number] [[decimals-to-round-to: number]]) => number",
"+": "(+ [number...]) => number",
"-": "(- [number...]) => number",
"*": "(* [number...]) => number",
"/": "(/ [number...]) => number",
"min": "(min [number...]) => number",
"max": "(max [number...]) => number",
"number->string": "(number->string [number] [[base: number]]) => string\n\nThe value for `base` defaults to 10 (decimal). If a value other than 10 is provided then the number will be parsed as an integer and output in the given base. If the value for `base` is 10 or is not given, the number will be parsed as a floating point number",
"rand": "(rand [[max]] [[min]]) => number\n\nIf no arguments are given `rand` will produce a floating point number between 0 and 1. If only `max` is given, `rand` will produce a floating point number between 0 and `max`. If `max` and `min` are both given, `rand` will produce a floating point number between `min` and `max`. `max` is always exclusive (the value produced will always be less than `max`), while `min` is inclusize. To obtain integers use `floor`, `ceil`, or `round` on the result or `rand`",
">": "(> [number] [number]) => bool",
">=": "(>= [number] [number]) => bool",
"<": "(< [number] [number]) => bool",
"<=": "(<= [number] [number]) => bool",
"not": "(not [value]) => bool\n\n`not` will invert the truthiness of the value it is given",
"equal?": "(equal? [value] [value]) => bool",
"number?": "(number? [value]) => bool",
"string?": "(string? [value]) => bool",
"bool?": "(bool? [value]) => bool",
"symbol?": "(symbol? [value]) => bool",
"pair?": "(pair? [value]) => bool",
"list?": "(list? [value]) => bool",
"null?": "(null? [value]) => bool",
"procedure?": "(procedure? [value]) => bool",
"atom?": "(atom? [value]) => bool",
"assoc?": "(assoc? [value]) => bool",
"io-handle?": "(io-handle? [value]) => bool",
"string-buf?": "(string-buf? [value]) => bool",
"~bool": "(~bool [value]) => bool\n\n`~bool` will convert a given value to a boolean value using a looser set of rules than `if` would normally use: 0, 0.0, the empty list, an empty string, and a closed io-handle will be considered falsy (in addition to #f)",
"length": "(length [list|string|exception|IOHandle>string-buf]) => number",
"list": "(list [value...]) => list",
"cons": "(cons [value] [list]) => list",
"car": "(car [list]) => value|list",
"cdr": "(cdr [list]) => list",
"append": "(append [list] [value...]) => list",
"map": "(map [procedure] [list...]) => list\n\n`map` will pass the values of each list as arguments to the given prcedure and a new list will be created from the results. If multiple lists are given, they should be the same length and their values will be given as arguments such that the first argument of each list will be provided to the given procedure (as two arguments in the case of two lists, three in the case of three etc), then the second argument, and so on",
"for-each": "(for-each [procedure] [list...]) => empty-list\n\nWorks via the same rules as `map`, but is used specifically for its side effects, rather than return values. See `map` for further details",
}
/*
(list->string [list])</code>: <code>string</code></li>
(list-ref [list] [number])</code>: <code>value</code></li>
(list-sort [list])</code>: <code>list</code></li>
(reverse [list|string])</code>: <code>list</code></li>
(assoc-get [assoc-list] [value])</code>: <code>value</code></li>
(assoc-set [assoc-list] [value] [value])</code>: <code>value</code></li>
(member? [list] [value])</code>: <code>bool</code></li>
(length [list|string|exception|IOHandle:string-buf])</code>: <code>number</code></li>
(list [value...])</code>: <code>list</code></li>
(substring [string] [number] [number])</code>: <code>string</code></li>
(string-append [string...])</code>: <code>string</code></li>
(string-format [format: string] [value...])</code>: <code>string</code> Replaces `%v`'s in string with successive values. Width can be set by adding an integer between the % and v: `%10v`, or to left align: `%-10v`</li>
(string->number [string] [[base: number]])</code>: <code>number</code> If a non-decimal base is supplied, the input string will be parsed as an integer and any flaoting point value will be floored</li>
(string->list [string] [[value]])</code>: <code>list</code> (splits the first string at each instance of value, cast as a string)</li>
(string->md5 [string])</code>: <code>string</code> If a non-decimal base is supplied, the input string will be parsed as an integer and any flaoting point value will be floored</li>
(string->sha256 [string])</code>: <code>string</code> If a non-decimal base is supplied, the input string will be parsed as an integer and any flaoting point value will be floored</li>
(string-fields [string])</code>: <code>list</code></li>
(string-trim-space [string])</code>: <code>string</code></li>
(string-index-of [string] [string])</code>: <code>number</code> (`-1` if the string could not be found)</li>
(string-ref [string] [number])</code>: <code>string</code></li>
(string-upper [string])</code>: <code>string</code></li>
(string-lower [string])</code>: <code>string</code></li>
(string-make-buf)</code>: <code>IOHandle:string-buf</code></li>
(string-buf-clear [IOHandle:string-buf])</code>: <code>()</code></li>
(regex-match? [string (pattern)] [string])</code>: <code>bool</code></li>
(regex-find [string (pattern)] [string])</code>: <code>list</code></li>
(regex-replace [string (pattern)] [string])</code>: <code>string</code></li>
(newline)</code>: <code>()</code></li>
(display [value...])</code>: <code>()</code></li>
(display-lines [value...])</code>: <code>()</code></li>
(write [string] [[IOHandle]])</code>: <code>()|IOHandle</code></li>
(write-raw [string] [[IOHandle]])</code>: <code>()|IOHandle</code></li>
(read-all [[IOHandle]])</code>: <code>string</code></li>
(read-line [[IOHandle]])</code>: <code>string|symbol:EOF</code></li>
(read-char [[IOHandle]])</code>: <code>string</code></li>
(read-all-lines [[IOHandle]])</code>: <code>list</code></li>
(close [IOHandle])</code>: <code>()</code></li>
(file-create [filepath: string])</code>: <code>IOHandle</code></li>
(file-create-temp [filename-pattern: string])</code>: <code>IOHandle</code></li>
(file-open-read [filepath: string])</code>: <code>IOHandle</code></li>
(file-open-write [filepath: string])</code>: <code>IOHandle</code></li>
(file-append-to [filepath: string] [string...])</code>: <code>()</code></li>
(file-name [IOHandle: file])</code>: <code>string</code></li>
(file-stat [filepath: string])</code>: <code>assoc list</code>
ssociative list will have the following keys: </p>
<code>name</code></li>
<code>size</code></li>
<code>mode</code></li>
<code>mod-time</code></li>
<code>is-dir?</code></li>
<code>is-symlink?</code></li>
<code>path</code></li>
>mode</code> will be in decimal as a number and can be converted to an octal string with <code>number->string</code>. <code>size</code> is in bytes. <code>path</code> will be an absolute path after following a symlink, or the path as provided to stat if the file is not a symlink.</p>
(exit [[number]])</code>: <code>()</code></li>
(license)</code>: <code>()</code></li>
(apply [procedure] [arguments: list])</code>: <code>value</code></li>
(! [exception-text: string])</code>: <code>exception</code></li>
(exception-mode-panic)</code>: <code>()</code></li>
(exception-mode-pass)</code>: <code>()</code></li>
(term-size)</code>: <code>list</code></li>
(term-restore)</code>: <code>()</code></li>
(term-char-mode)</code>: <code>()</code></li>
(term-raw-mode)</code>: <code>()</code></li>
(term-cooked-mode)</code>: <code>()</code></li>
(term-sane-mode)</code>: <code>()</code></li>
(path-exists? [filepath: string])</code>: <code>bool</code></li>
(path-is-dir? [filepath: string])</code>: <code>bool</code></li>
(path-abs [filepath: string])</code>: <code>string</code></li>
(path-join [string])</code>: <code>string</code></li>
(path-extension [filepath: string] [[replacement-extension: string]])</code>: <code>string</code></li>
(path-base [filepath: string])</code>: <code>string</code></li>
(path-dir [filepath: string])</code>: <code>string</code></li>
(path-glob [filepath: string])</code>: <code>list</code></li>
(chmod [filepath: string] [file-mode: number])</code>: <code>()</code></li>
(chdir [filepath: string])</code>: <code>()</code></li>
(env [[env-key: string]] [[env-value: string]])</code>: <code>list|string|()</code></li>
(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>
(mkdir [path: string] [permissions: number] [[make-all: bool]])</code>: <code>()</code></li>
(rm [path: string] [[make-all: bool]])</code>: <code>()</code></li>
(mv [from-path: string] [to-path: string])</code>: <code>()</code></li>
(pwd)</code>: <code>path: string</code></li>
(net-conn [host: string] [port: string|number] [[use-tls: bool]] [[timeout-seconds: number]])</code>: <code>IOHandle</code></li>
(url-scheme [url: string] [[new-scheme: string]])</code>: <code>string</code></li>
(url-host [url: string] [[new-host: string]])</code>: <code>string</code></li>
(url-port [url: string] [[new-port: string]])</code>: <code>string</code></li>
(url-path [url: string] [[new-path: string]])</code>: <code>string</code></li>
(url-query [url: string] [[new-query: string]])</code>: <code>string</code></li>
(hostname)</code>: <code>string</code></li>
(timestamp)</code>: <code>timestamp: number</code></li>
(date)</code>: <code>date: string</code></li>
(timestamp->date [timestamp: string|number] [[format: string]])</code>: <code>string</code></li>
(date->timestamp [date: string] [format: string])</code>: <code>timestamp: number</code></li>
(date-format [start-format: string] [date: string] [end-format: string])</code>: <code>string</code></li>
(sleep [[milliseconds: number]])</code>: <code>()</code></li>
(date-default-format)</code>: <code>string</code> (returns a format string for the default format produced by <code>date</code>)</li>
<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>
<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>
*/

Loading…
Cancel
Save