Slope Language Documentation

The Slope programming language is similar to the scheme programming language, but in many ways simpler. It lacks many of the metaprogramming features, but adds some standard library options and sensible constructs for dealing with input/output. If you have written scheme or lisp code Slope will feel fairly familiar to you. It is a fun little language to work with and has enough batteries included to get a beginner going for many programming tasks, even some basic gui programming. Plus: it is fun!

Syntax

Slope syntax, like most scheme/lisp oriented languages, is very regular. Most things are enclosed in nested groups of parenthesis. The usual form things take is (procedure-name argument argument etc) where a procedure name is the first thing in the parenthesis. This is the procedure that is being executed and any values that come after it are arguments to that procedure. Arguments can include calls to other procedures.


; Here is a tested call to the plus and minus procedures
(+ 5 3 (- 8 7)) ; => 9
            

In the above example plus looks at its arguments and sees that one is not a value, but is instead a procedure call. As such, it does that first. So eight minus seven is one. One replaces the call to (- 8 7) and then + runs with its three arguments, producing the result: 9. Note that since we are not doing anything to the nine you would not see it under normal circumstances. As a convenience the Slope REPL will show the result of evaluating the given code, so you would see 9 in the REPL.

In addition to the calling form discussed above, there are a few special forms that code can take. Lists can be created by a procedure called, you guessed it, list. However, there is some syntactic sugar to allow you to write a list litteral by surrounding the values in a list with square brackets.

At first it might seem like defining variables has a special format, as it does something special and necessary. But really it is mostly the same as calling any other procedure. It has a name, define and it takes two arguments: a name (which should be given as a symbol, not a string) and the value that name will be set to, which can be anything. lambda is similar in that it feels special purpose, which in some ways it is. It is the way to create procedures in Slope. It takes a special format where its first argument is a set of paramater names, as symbols, surrounded by parenthesis (rather than being an actual list with square brakcets, Slope knows to process this as a special case litteral).


; Defining a list with square brackets
(define my-list [1 2 3])

; Defining a list w/ `list`
(define my-list2 (list 1 2 3))

; Defining a lambda with 0 paramaters
(define my-proc (lambda ()
                        (display "Hi")))

; Defining a lambda with 1 paramater
(define my-proc2 (lambda (word)
                         (display word)))
            

Types

Slope is a dynamically typed language. Values are easily coerced into other types and you do not need to declare what types you are using when creating variables or procedures. However, that does not mean you do not need to pay attention to or know about what types are being used at various points in your programs. There are a wide array of type checking procedures available and it is often wise to make sure your procedures have actually received what they are looking for.

Slope has 9 types: bool, exception, io-handle, list, macro, number, procedure, string, and symbol. An additional three types are available if the gui module is built at interpreter compile time: gui-root, gui-widget, and gui-container. The gui modules are completely optional and cannot be guaranteed on a given system. Check the slope version for the gui label: slope -v to verify presence of gui, or try to use a gui function and see if it is available.

bool

  • Literals: #t and #f
  • All values cast as #t except #f
  • The ~bool procedure allows for looser casting to bool (empty string/list is #f, etc)

exception

  • There is no exception literal, but an exception can be created with the ! procedure
  • Slope offers two exception modes that can be toggled between. See: exception-mode-panic and exception-mode-pass. The system defaults to doing a runtime panic when an exception is reached, but can be made to pass the exception as a value isntead

io-handle

  • These are the only pass by reference items in the core of Slope (the gui extension adds some more)
  • There are five types of io-handle representing the available input/output capabilities of the Slope system:
    • file (read-only)
    • file (write-only)
    • string-buf
    • net-conn
    • net-conn (tls)
    All of these can be written to and read from via the same set of write and read procedures. See the Input/Output Oriented Procedures section
  • io-handles can be open or closed. A closed io-handle cannot be written to or read from

list

  • Lists can be created with the list procedure or as a literal: [1 2 3]
  • Lists are the only true built in data structure in Slope
  • Association lists are made up of a list of lists where the inner lists serve as key/value pairs: [["age" 39]["sign" "gemini"]]. The assoc procedure enables this structure to be used similar to a map structure in other languages

macro

  • Macros are created with the macro procedure
  • Macros are callable, similar to procedures: (my-macro)
  • Arguments passed to macros are not evaluated, unless the body of the macro evaluates them. This allows the manipulation of code, rather than values

number

  • All numbers are implemented as float64 under the hood
  • Hexidecimal numbers can be written with a leading 0x: 0xFFF
  • Octal numbers can be written with a leading 0: 072 (which is 58 in decimal). The interpreter knows the difference between a zero used this way and a zero before a decimal point
  • In strings characters can be accessed via escape sequences for decimal, octal, or hex: "27 three ways: \27 \033 \0x1B"

procedure

  • Procedures are created with the lambda procedure
  • Procedures are callable, similar to macros: (my-proc)
  • Unlike with macros, arguments passed to procedures are always evaluated

string

  • A string litteral is anything between a set of double-quotes: "I am a string"
  • Quotes can be escaped inside a string: "I am a \"string\""
  • Other standard escape sequences are recognized for chars by number, newline, tab, etc \n\t\033[1mHi!\0x1B[0m

symbol

  • In Slope these are mostly used to represent variables
  • Symbols can be created with the quote procedure or as a literal, which starts with a single quote: 'my-symbol
  • Symbols used for variable names should not include the characters ()[]' or the pair of characters ::, which all have special meaning and may trigger parsing errors. It is possible to use these characters when using quote, but they will eventually be evaluated and may not always end up as you expect

gui-root

  • Only exists if the gui module is built at compile time
  • The root type for a gui project

gui-widget

  • Only exists if the gui module is built at compile time
  • Represents a gui component, usually an interactive one

gui-container

  • Only exists if the gui module is built at compile time
  • Represents a gui layout container

Conditionals

There are three types of conditional or branch procedures in Slope. if, cond, and case.

if

Slope's lisp/scheme heritage means that if behaves more or less like any other procedure. It is a special form within the interpreter, but for the user its shape should eb familiar. It is a procedure that takes at least two arguments, with an optional third argument. The first argument is the conditional test: something that will be treated as either #t or #f. Remember that in Slope, the only falsy value is #f itself (see ~bool, for looser conditional casting). If the conditional test/first argument evaluates to #t the next argument will be evaluated. This could be a value, like 23 or code to be evaluated, like (+ 10 13). In any case the final evaluated value will be returned from the call to if. If the conditional test/first argument evaluated to #f the third argument will be evaluated, if it is present. If there is no third argument for a falsy conditional the empty list () will be returned. Note that there is no implicit begin or begin0 procedure within either branch of the if procedure like there is with lambda, so you will need to call them if you need multiple non-nested evaluation steps to an if branch.


(define my-list [0 1 2])
(if (> (length my-list) 3)
  "Longer than three"
  "Three or less")

; => "Three or less"
            

cond

cond is similar to if. It involves a logic test, but can involve any number of tests. It most closely resembles an if else structure in C style languages. It takes pairs of logic tests and code to be evaluated in the event of a truthy test. It executes the tests one after the other from top to bottom until it finds a truthy test, at which point it will evaluate the code associated with the test and return the result. cond can accept a special test: else which should always be the last test case and will always evaluate the associated expression. If no truthy value is found and there is no else test the empty list will be returned.


(define my-list [0 1 2])
(cond
  ((equal? (length my-list) 1) (car my-list))
  ((equal? (length my-list) 2) (car (cdr my-list)))
  (else (! "The given list does is not either one or two items long")))

; This call to cond will raise the error associated with the else condition

(set! my-list [0 1])
(cond
  ((equal? (length my-list) 1) (car my-list))
  ((equal? (length my-list) 2) (car (cdr my-list)))
  (else (! "The given list does is not either one or two items long")))

; This call to cond will return the number 1
            

case

case is similar to a switch statement in C based languages. Instead of testing for truthiness of an expression it takes a target value and tried to match it against other values. When a match is found the associated expression(s) will be executed. case, like cond, can accept a special match case: else which should always be the last match test case and will always evaluate the associated expression. If no truthy value is found and there is no else test the empty list will be returned.


(case 10
  (0 "Zero")
  (5 "Five")
  (10 "Ten")
  (else "?")) ; => "Ten"

(case 100
  (0 "Zero")
  (5 "Five")
  (10 "Ten")
  (else "?")) ; => "?"

(case 100
  (0 "Zero")
  (5 "Five")
  (10 "Ten") ; => ()
            

Procedures

Procedures in Slope look a lot like lists when you call them, and have a special form of procedure called lambda to create them. If you wrap a lambda in an extra set of parenthesis and that include the arguments to the lambda (after the lambda's closing parenthesis but inside the extra set) it will call the lambda with the given arguments. This is called an "anonymous procedure". Lambda's can also be stored and referenced as variables via the define procedure. Variadic procedures, procedures that take a variable number of arguments, are possible via the special paramater name args-list; see lambda for more information.


; Defining a procedure named add2
(define add2 (lambda (num) (+ num 2)))

; Calling add2
(add2 5) ; => 7
            

Reading Procedure Signatures

The procedure signatures use a particular syntax to show what arguments a procedure accepts:


(example [argument-1: string] [[argument-2: number...]])
            

In the above example you can see the procedure name is example. The procedure name is followed by zero or more paramaters. If a paramater is in a single set of square brackets it is a mandatory paramater. In the example, [argument-1: string] is a mandatory paramater. Optional paramaters are surrounded by two sets of square brackets. In the example, [[argument-2: number]] is an optional paramater. An argument is displayed as argument-name: value-type, where argument-name: is optional and is there to provide clarity to the argument by giving it a name. A value-type should always be given. A value-type is the name of a type in slope, such as string, number, bool, etc. You can list multiple value type by separating them with a | character: [argument-1: string|number]. If the procedure is variadic (can accept a variable number of arguments) this can be denoted with .... So in our original example [[argument-2: number...]] actually can represent any number of values. As such, you might call the procedure like so: (example "hi" 1 2 3 4) or simple (example "hi").

Macros

Macros in Slope look a lot like lists when you call them, and have a special form of procedure called macro to create them. If you wrap a macro in an extra set of parenthesis and include the arguments to the macro (after the macro's closing parenthesis but inside the extra set) it will call the macro with the given arguments. This is called an "anonymous macro". Macros can also be stored and referenced as variables via the define procedure. Variadic macros, macros that take a variable number of arguments, are possible via the special paramater names args-list or ...; see macro for more information.

Use a macro instead of a procedure when you need to manipulate actual slope code, rather than values. Anything given to a macro will not be evaluated. This allows you to restructure code to create new control structures that would not be possible without macros.


; Defining a while loop as a macro
(define while
  (macro (test ...)
    (define el
      (eval ['lambda [] ['if test (list-join ['begin] ... [['el]])]]))
    (el)))

; Calling add2
(define x 0)
(while (< x 5) (display x " ") (set! x (+ x 1)))
; => 1 2 3 4 5
            

Full Procedure/API Listing

The built-in procedures have been organized by rough catagories based on what they do/return and are displayed in a consistent format. First you will see the procedure name, this is followed by a code block showing the procedure signature and return value, after that will be a textual description with useful information about using the given procedure. In general if something returns #t or #f it will be in the list of conditional procedures, if it evaluates to a list or operates on a list it will likely be in the list procedures, etc. The Slope System procedures revolve around manipulating the slope system, errors, and special forms such as if, lambda, begin, or cond (and many more).

Constants / Runtime Defined Values

sys-args is populated with the arguments run at the command line, similar to C's argv.

stdin, stdout, stderr, and devnull are set at runtime to an open io-handle representing each file. This makes it easy to use them the same as you would any other file (by passing the io-handle to the reader/writer).

E


E ; => number
                

This value is case sensitive and represents the numerical constant (representation here is truncated for display): 2.7182818284590452353602874713.

PHI


PHI ; => number
                

This value is case sensitive and represents the numerical constant (representation here is truncated for display): 1.6180339887498948482045868343;.

PI


PI ; => number
                

This value is case sensitive and represents the numerical constant (representation here is truncated for display): 3.1415926535897932384626433832;.

sys-args


sys-args ; => list
                

sys-args is the list of arguments that were invoked to run the currently running slope program. The first argument in the list is always the name of the program that was invoked, which will not include the slope interpreter itself (if invoked directly it will be removed from the list). The list is defined at runtime and is mutable if you set! it, so be careful... be weird if that is your thing. Either way.

stderr


stderr ; => io-handle (file: write-only)
                

io-handle for stderr. This value is mutable, so you may redefine it, but it is recommended to store the original value elsewhere if you find the need to redefine it.

stdin


stdin ; => io-handle(file: read-only)
                

io-handle for stdin. This value is mutable, so you may redefine it, but it is recommended to store the original value elsewhere if you find the need to redefine it.

stdout


stdout ; => io-handle (file: write-only)
                

io-handle for stdout

devnull


devnull ; => io-handle (file: write-only)
                

io-handle for devnull. This value is mutable, so you may redefine it, but it is recommended to store the original value elsewhere if you find the need to redefine it.

Number Oriented Procedures

+


(+ [number...]) ; => number
                

Adds the given numbers together, left to right, and returns the sum. If only one number is provided it is simply returned.

-


(- [number...]) ; => number
                

Subtracts the given numbers from each other, left to right, and returns the result. If only one number is provided its sign is inversed and it is returned.

*


(* [number...]) ; => number
                

Multiplys the given numbers and returns the result, If only one number is provided then 1 is divided by it and the result is returned.

/


(/ [number...]) ; => number
                

Divides the given numbers, left to right, and returns the result. If only one number is provided it is simply returned.

%


(% [number] [number]) ; => number
                

Returns the modulus of the two given numbers. If an invalid pair of numbers is given, the result will be zero (since slope does not contain an undefined or NaN value). This has the potential to cause problems, be aware.

abs


(abs [number]) ; => number
                

Returns the absolute value of a given number.

atan


(atan [number] [[number]]) ; => number
                

Returns the atan value of a given number. If two numbers are given returns the atan2 value of the given numbers.

ceil


(ceil [number]) ; => number
                

Return the ceiling value of the given number.


(ceil 1.23) ; => 2
(ceil 1)    ; => 1
                  

cos


(cos [number]) ; => number
                

Returns the cos value of a given number.

floor


(floor [number]) ; => number
                

Returns the floor value of the given number. For example:


(floor 2.78)     ; => 2
(floor 2)        ; => 2
(floor 2.123455) ; => 2
                  

log


(log [number]) ; => number
                

Returns the natural log value of a given number.

max


(max [number...]) ; => number
                

Returns the largest of the given numbers.

min


(min [number...]) ; => number
                

Returns the smallest of the given numbers.

number->string


(number->string [number] [[base: number]]) ; => string
                

The 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. All other bases will be parsed as integers.

positive?


(positive? [number]) ; => bool
                

Checks if the given number is greater than zero

rune->string


(rune->string [number]) ; => string
                

Will floor any value passed to it and attempt to convert it to a single character string represented by the given number.

rand


(rand [[max]] [[min]]) ; => number
                

If 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

round


(round [number] [[decimals-to-round-to: number]]) ; => number
                

Rounds the given number to the nearest whole number. If decimals-to-round-to is provided round will round to that number of decimal places

sin


(sin [number]) ; => number
                

Returns the sin value of a given number.

sqrt


(sqrt [number]) ; => number
                

Returns the sqrt value of a given number.

tan


(tan [number]) ; => number
                

Returns the tan value of a given number.

zero?


(zero? [number]) ; => bool
                

Checks if the given number is zero.

Conditional Oriented Procedures

>


(> [number] [number]) ; => bool
                

Checks of the first number is greater than the second number.

>=


(>= [number] [number]) ; => bool
                

Checks if the first number is greater than or equal to the second number.

<


(< [number] [number]) ; => bool
                

Checks if the first number is less than the second number.

<=


(<= [number] [number]) ; => bool
                

Checks if the first number is less than or equal to the second number.

~bool


(~bool [value]) ; => bool
                

~bool will convert a given value to a boolean value using a looser set of rules than if would normally use: 0, 0.0, (), "", and a closed io-handle will be considered falsy (in addition to #f).

and


(and [expression...]) ; => bool
                

Checks the truthiness of all expressions passed to it. If any expression is falsy then false is returned and no further expressions are checked; otherwise returns #t.

assoc?


(assoc? [value]) ; => bool
                

Determines whether or not the given value is an association list. An empty list will return #t, as an empty association is no different than an empty list.

atom?


(atom? [value]) ; => bool
                

Determines whether a given value is atomic (in practice this mostly works out to: not a list).

bool?


(bool? [value]) ; => bool
                

Checks whether the given value is a boolean: either the value #t or the value #f.

equal?


(equal? [value...]) ; => bool
                

Checks if the given values are equal. If only one value is given iequal? will return #t.

exception?


(exception? [value: any]) ; => bool
                

Checks if the given value is an exception.

exception-mode-panic?


(exception-mode-panic?) ; => bool
                

Checks if the current exception mode is panic.

exception-mode-pass?


(exception-mode-pass?) ; => bool
                

Determines whether the current exception mode is pass.

exists?


(exists? [quoted-symbol...]) ; => bool
                

Determines whether the given symbol(s) exist in the current environment. If multiple values are passed #t will only be returned if all of the symbols are present. All symbols given to exists? should be quoted: (exists? '+) ; => #t. Strings representing symbols may also be passed: (exists? "+") ; => #t.

io-handle?


(io-handle? [value]) ; => bool
                

Checks if the given value is an io-handle.

list?


(list? [value]) ; => bool
                

Checks whether the given value is a list, empty or filled.

macro?


(macro? [value]) ; => bool
                

Checks if the given value is a macro.

member?


(member? [list] [value]) ; => bool
                

Checks whether the given value is in the given list.

negative?


(negative? [number]) ; => bool
                

Checks if the given number is less than zero.

not


(not [value]) ; => bool
                

not will invert the truthiness of the value it is given such that a truthy falue returns #f and #f returns #t.

number?


(number? [value]) ; => bool
                

Checks if the given value is a number.

null?


(null? [value]) ; => bool
                

Checks if the given value is an empty list.

open?


(open? [io-handle]) ; => bool
                

Checks whether or not the given io-handle is currently open.

or


(or [expression...]) ; => bool
                

Evaluates each expression in turn. If any expression evaluates to a truthy value #t is returned and no further arguments are evaluated, otherwise #f is returned.

pair?


(pair? [value]) ; => bool
                

Checks if the given value is a non-empty list.

path-exists?


(path-exists? [filepath: string]) ; => bool
                

Checkes whether the given filepath exists.

path-is-dir?


(path-is-dir? [filepath: string]) ; => bool
                

Checks whether or not the given filepath represents a directory

procedure?


(procedure? [value]) ; => bool
                

Checks if the given value is a procedure (a lambda).

regex-match?


(regex-match? [pattern: string] [string]) ; => bool
                

Checks if the regex pattern is found at least once in the given string. Slope uses golang's regular expression format.

string?


(string? [value]) ; => bool
                

Checks whether the given value is a string.

string-buf?


(string-buf? [value]) ; => bool
                

Checks whether the given value is an io-handle of type string buffer.

symbol?


(symbol? [value]) ; => bool
                

Checks if the given value is a symbol.

List Oriented Procedures

append


(append [list|value] [[value...]]) ; => list|string
                

If a list is the only argument, it will be returned. If a list is followed by other values they will be appended to the list and the list will be returned. If a single non-list value is given it will be coerced to string and returned. If multiple non-list values are given they will be coerced to string and concatenated with the resulting string being returned.

assoc


(assoc [association-list] [key: value] [[value]]) ; => value|list
                

If two arguments are given then assoc retrieves the value of the key. If a third argument is given assoc will set the value for the given key to the given value, returning the updated list.

car


(car [list]) ; => value|list
                

Returns the first value in the given list, which may be an atomic value or another list.

cdr


(cdr [list]) ; => list
                

Returns the given list without its first value. Can be thought of as the remainder of car.

cons


(cons [value] [list]) ; => list
                

Prepends the given value to the given list, and returns the list. See also: append, which can add an item to the end of a list.

filter


(filter [test: procedure] [list]) ; => list
                

The procedure supplied to filter should take one value, the list item being passed to it, and will have its return value treated as a bool. Any value in the list that returns truthy when fed to the test procedure will be included in the list that filter returns.

for-each


(for-each [procedure] [list...]) ; => ()
                

Works via the same rules as map, but is used specifically for side effects rather than return values. Will run the procedure for each list item in each list, if more than one list is passed. For example, if the procedure takes two arguments and two lists were given the procedure will be called with the first item in the first list as its first argument and the first item in the second list as its second argument. It will then procede to the second item in each list as arguments, and so on.

length


(length [list|string|exception|io-handle_string-buf]) ; => number
                

Returns the length of the given value. Length is determined by number of code points for strings and number of items for lists. Exceptions and io-handles for string buffers are converted to their string value for purposes of getting their length.

list


(list [value...]) ; => list
                

Returns a list containing the given values. Syntactic sugar exists to make list creation shorter:


;; This:
(list 1 2 3)

;; Is equal to this:
[1 2 3]
                  

list->string


(list->string [list] [[join-on: string]]) ; => string
                

Converts the given list to a string, inserting the optional join-on string between each list item. Any non-string items in the list will be converted to strings automatically before joining.

ref


(ref [list|string] [index: number] [[set: value]]) ; => value|list|string
                

Gets or sets an item in a given list|string by its index position. Indexing begins at 0. When the optional set argument is provided the list|string will be returned with the value at the given index replaced by the set value that was given. If a string is being operated on and an empty string is given as the set value the index will be removed.


(ref [1 2 3 4] 2)       ; => 3
(ref [1 2 3 4] 2 54)    ; => [1 3 54 4]
(ref "Hello" 2)         ; => "l"
(ref "Hello" 2 "y")     ; => "Heylo"
(ref "Hello" 2 "yyy")   ; => "Heyyylo"
(ref "Hello" 2 5)       ; => "He5lo"
(ref "Hello" 2 "")      ; => "Helo"
                  

list-seed


(list-seed [length: number] [value]) ; => list
                

Using list seed to create lists filled with a certain element is more efficient than recursion for large lists and will not overflow the stack. As such list-seed will return a lift of the given length filled with the given value at each index. length should be a positive number larger than zero. If a floating point number is given, it will be floored.

list-sort


(list-sort [list] [[sub-list-index: number]]) ; => list
                

Sorts a list alphabetically first followed by numerals. Any non-number non-string value will be sorted by its stringified value. If list is a list of lists, sorting can be done by the index value of a sublist (a single level deep) if desired. list-sort always sorts the list in ascending order, but can be reversed with reverse.

map


(map [procedure] [list...]) ; => list
                

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.

range


(range [[count: number]] [[start: number]] [[step: number]]) ; => list
                

If no arguments are given an empty list will be returned. count should be a positive integer and represents the number of list items that will be produced by range. If no arguments other than count are given then a list from 0 to count will be produced. To start at a number other than zero start can be given. To step by a value other than one, step can be given.


(range)            ; => ()
(range 5)          ; => (0 1 2 3 4)
(range 5 10)       ; => (10 11 12 13 14)
(range 5 10 2)     ; => (10 12 14 16 18)
                  

reduce


(reduce [procedure] [accumulator: value] [list]); => list
                

reduce will pass the values of each list item and the current accumulator value as arguments to the given prcedure and will return the state of the accumulator after all list items have been given, in order, to the procedure. The procedure should return the accumulator value that will be given, along with the next list item, to the procedure. Thus, the accumulator will change over the course of running reduce and its initial state is just a starting point.

reverse


(reverse [list|string]) ; => list|string
                

reverse will reverse a list or a string and return the same type of value it was given, but with the contents in the reverse order.

slice


(slice [list|string] [start-index: number] [[end-index: number]]) ; => list|string
                

slice is used to get a subsection of an indexed item (string or list). end-index is exclusive (should be one higher than the last character you want included in the slice). If end-index is not given then the slice will go from the start position until the end of the list|string. If a start-index larger than the largest available index is given an empty list|string will be returned.

String Oriented Procedures

length


(length [list|string|exception|io-handle_string-buf]) ; => number
                

Returns the length of the given value. Length is determined by number of code points for strings and number of items for lists. Exceptions and io-handles for string buffers are converted to their string value for purposes of getting their length.

regex-find


(regex-find [pattern: string] [string]) ; => list
                

Returns list of all matches to the given regex pattern found in the given string. Slope uses golang's regular expression format.

regex-replace


(regex-replace [pattern: string] [input: string] [replacement: string]) ; => string
                

Replaces all instances of the given pattern in the given input string with the replacement string. Slope uses golang's regular expression format.

reverse


(reverse [list|string]) ; => list|string
                

reverse will reverse a list or a string and return the same type of value it was given, but with the contents in the reverse order.

string->list


(string->list [string] [[split-on: value]] [[count: number]]) ; => list
                

Splits the string at each instance of split-on. If split-on is not a string it will be cast to string for the purposes of splitting the string into a list. If a count is provided the string will be split into no more than _count_ list elements.

string->md5


(string->md5 [string]) ; => string
                

Produces an md5 hash string from the given string.

string->rune


(string->rune [string]) ; => rune
                

Converts the first character in the given string to a rune number. Any other characters in the string will be ignored. If the string is empty string->rune will return 0. The resulting number can be converted back to a string with rune->string.

string->sha256


(string->sha256 [string]) ; => string
                

Produces a sha256 hash string from the given string.

string->number


(string->number [string] [[base: number]]) ; => number/#f
                

If a non-decimal base is supplied, the input string will be parsed as an integer and any flaoting point value will be floored. A valid base passed with a string that does not parse to a number will return #f

append


(append [list|value] [[value...]]) ; => list|string
                

If a list is the only argument, it will be returned. If a list is followed by other values they will be appended to the list and the list will be returned. If a single non-list value is given it will be coerced to string and returned. If multiple non-list values are given they will be coerced to string and concatenated with the resulting string being returned.

string-buf-clear


(string-buf-clear [io-handle: string-buf]) ; => ()
                

Resets/clears the content from the given string buffer io-handle, resulting in the buffer being empty.

string-fields


(string-fields [string]) ; => list
                

Splits a string around each instance of one or more consecutive white space characters.

string-format


(string-format [template: string] [value...]) ; => string
                

Replaces values designated by %v in the template string with successive values (value...).

Width can be set by adding an integer between the % and the v: %10v, or left aligned: %-10v

string-index-of


(string-index-of [string] [search-value: string]) ; => number
                

Returns -1 if search-value does not appear in string, otherwise returns the string index at which search-value begins.

string-lower


(string-lower [string]) ; => string
                

Converts all characters in the given string to their lowercase form and returns the new lowercase string.

string-make-buf


(string-make-buf) ; => io-handle: string-buf
                

Returns a new empty string buffer io-handle.

ref


(ref [list|string] [index: number] [[set: value]]) ; => value|list|string
                

Gets or sets an item in a given list|string by its index position. Indexing begins at 0. When the optional set argument is provided the list|string will be returned with the value at the given index replaced by the set value that was given. If a string is being operated on and an empty string is given as the set value the index will be removed.


(ref [1 2 3 4] 2)       ; => 3
(ref [1 2 3 4] 2 54)    ; => [1 3 54 4]
(ref "Hello" 2)         ; => "l"
(ref "Hello" 2 "y")     ; => "Heylo"
(ref "Hello" 2 "yyy")   ; => "Heyyylo"
(ref "Hello" 2 5)       ; => "He5lo"
(ref "Hello" 2 "")      ; => "Helo"
                  

string-trim-space


(string-trim-space [string]) ; => string
                

Removes any leading or trailing white-space from string.

string-upper


(string-upper [string]) ; => string
                

Converts all characters in the given string to their uppercase form and returns the new uppercase string.

slice


(slice [list|string] [start-index: number] [[end-index: number]]) ; => list|string
                

slice is used to get a subsection of an indexed item (string or list). end-index is exclusive (should be one higher than the last character you want included in the slice). If end-index is not given then the slice will go from the start position until the end of the list|string. If a start-index larger than the largest available index is given an empty list|string will be returned.

Input/Output Oriented Procedures

close


(close [io-handle]) ; => ()
                

Closes the given io-handle. Can be called on an already closed io-handle without an exception. Will return an exception if an open io-handle cannot be closed.

display


(display [value...]) ; => ()
                

Print the given values to stdout. Similar to: (write "..."), but can take multiple arguments and can only write to stdout. Display will stringify (make into a string) any value it is given before printing it.

display-lines


(display-lines [value...]) ; => ()
                

Prints the given values to stdout while adding a terminating newline to each value.

file-append-to


(file-append-to [filepath: string] [string...]) ; => ()
                

Appends the given strings to the given file. If the file does not exist, it is created. Appending to the file is an atomic opperation and should not lock the file. A file is not opened and thus there is no need to call close. An empty list is returned.

file-create


(file-create [filepath: string]) ; => io-handle
                

Creates a file at the given filepath. If a file already exists, the file is truncated. Returns an open io-handle representing the file.

file-create-temp


(file-create-temp [filename-pattern: string]) ; => io-handle
                

Creates a temporary file in the system default location for temporary files. The filename is generated by taking the filename-pattern and adding a random string to the end. If the filename-pattern includes a * the random string replaces the last *. Returns an open io-handle representing the file. To get the file name see: file-name.

file-name


(file-name [io-handle]) ; => string
                

Returns a string representing the full path of the file associated with the given io-handle.

file-open-read


(file-open-read [filepath: string]) ; => io-handle
                

Returns an open io-handle for the file represented by the given filepath. The file is opened in read mode.

file-open-write


(file-open-write [filepath: string]) ; => io-handle
                

Returns an open io-handle for the file represented by the given filepath. The file is opened in write mode.

file-stat


(file-stat [filepath: string]) ; => assoc-list|#f
                

Gets information about the file represented by filepath. The returned associative list will have the following keys:

  • name (string)
  • size (bytes: number)
  • mode (number)
  • mod-time (number)
  • is-dir? (bool)
  • is-symlink? (bool)
  • path (string)

#f will be returned if the file does not exist; all other file access errors will raise an exception. mode will default to decimal formatting, as all numbers do in slope, but it can be cast to an octal string with number->string. path will be an absolute path after following a symlink, if present, or the path as provided to stat in the absense of a symlink.

newline


(newline) ; => ()
                

Prints a newline character to stdout.

read-all


(read-all [[io-handle]]) ; => string
                

Reads all data the io-handle has available (either a whole file, whole buffer, until ^D is sent to stdin, or until a net connection is closed) and returns the content as a string.

read-all-lines


(read-all-lines [[io-handle]]) ; => list
                

Reads all data the io-handle has available (either a whole file, whole buffer, until ^D is sent to stdin, or until a net connection is closed) and splits it into a list of lines, split on \n.

read-char


(read-char [[io-handle]]) ; => string|#f
                

Reads a single character from the given io-handle. If no io-handle is provided then stdin will be used. read-char cannot read a character from a string buffer. If there is no more to be read (eother the end of the file has been reached or the connection has been closed) #f will be returned.

read-line


(read-line [[io-handle]]) ; => string|#f
                

Reads a line from the given io-handle (with the exception of a string buffer, which does not support partial reads) and returns it as a string. If there is no more to be read (either the end of the file has been reached or the connection has been closed) #f will be returned. If no io-handle is passed stdin will be used and will read until a newline is encountered.

write


(write [string] [[io-handle]]) ; => ()|io-handle
                

Writes the given string to stdout. If an optional io-handle has been provided the string will be written to the given io-handle. If the io-handle is closed an exception will occur. Returns an empty list if no io-handle is given, or returns the io-handle if one was given

write-raw


(write-raw [string] [[io-handle]]) ; => ()|io-handle
                

Writes the given string to stdout, or an optionally provided io-handle. Does not process any escape values or special characters within the given string. Returns an empty list if no io-handle is given, or returns the given io-handle

Slope System Oriented Procedures

!


(! [exception-text: string]) ; => exception
                

Return an error with the given text. Depending on the error mode this may result in a runtime panic or simply in the exception value being passed to the calling lambda.

apply


(apply [procedure] [arguments: list]) ; => value
                

Spreads the list values as arguments to the given procedure. This results in behavior where the following two examples are equivalent:


(+ 1 2 3)          ; => 6
(apply + [1 2 3])  ; => 6
                  

begin


(begin [expression...]) ; => value
                

Evaluates a series of expressions and returns the value of the last expression that is evaluated.

begin0


(begin [expression...]) ; => value
                

Evaluates a series of expressions and returns the value of the first expression that is evaluated.

case


(case [target-match: value] [([match-value: expression] [#t-branch: expression])...]) ; => value
                

Will check if each the given target-value matches each match-value in turn and evaluate to the #t-branch of the first such match that is equal. If no match is found an empty list will be returned. The match-value `else` is a sepcial case, the #t-branch of this match list will be returned if no other match is found to be truthy.

cond


(cond [([condition: expression] [#t-branch: expression])...]) ; => value
                

Will 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.

define


(define [var-name: symbol] [value|expression|procedure]) ; => value|expression|procedure
                

Returns the value that var-name was set to, in addition to creating the var symbol in the current scope 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). var-name should be a symbol that represents the name of the new variable.

eval


(eval [expression|value] [[treat-string-as-code: bool]]) ; => value
                

Eval will exaluate the given value/expression. If treat-string-as-code is set to #t a string representation of slope code passed as a value will be evaluated as code. Otherwise, a string would evaluate to itself (a string).

exception-mode-panic


(exception-mode-panic) ; => ()
                

Sets the exception mode such that if an exception is encountered a runtime panic will be triggered. This is the slope default behavior.

exception-mode-pass


(exception-mode-pass) ; => ()
                

Sets the exception mode such that if an exception is encountered it is returned to the calling scope as a value.

if


(if [condition: expression] [#t-branch: expression] [[#f-branch: expression]]) ; => value
                

The expression given to if as its condition will be evaluated and its value will be taken as truthy or falsy. Any value other than #f is considered truthy for the purposes of the condition. If the condition evaluates to a truthy value then the #t-branch will be evaluated, otherwise it will be ignore and the #f-branch will be evaluated if one was given.

lambda


(lambda [([[argument: symbol...]])] [expression...]) ; => procedure
                

Lambda is how you make reusable code (functions, procedures, etc) in slope. The special values args-list or ... can be given as an argument name. If this argument name is used it will always be a list and will contain any number of arguments given starting at its position in the argument list. There is an implicit (ie. created for you) begin block that will surround expression.... As such you do not need to add your own. 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), thus creating a closure. Examples:


((lambda (num1 num2)
         (display num1 "+" num2)
         (+ num1 num2))  5 7)

; Prints: 5+7
; => 12

((lambda ()
         (display "Hi!")))
; Prints: Hi!

((lambda (sys-args)
         (apply * sys-args)) [2 3 4])
; => 24
                  

license


(license) ; => ()
                

Prints the slope interpreter's software license (for the interpreter itself, not for slope code run with the interpreter).

load


(load [filepath: string...]) ; => symbol
                

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. As such it is recommended to avoid scope confusion by always calling load from your code's top level scope. Unlike load-mod, load will evaluate all code within the file, not solely calls define, so be sure you really want to load the given file and know what it does. load is variadic and can accept any number of filepaths as strings. They will be loaded one after the other, left to right.

load-mod


(load-mod [module-name: string|symbol] [alias: string|symbol]) ; => symbol
                

load-mod will search the local module path followed by the gloval module path for the given module. If found, locally or globally, 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. This prevents some level of arbitrary code execution, but it is still possible to embed malicious code in lambda expressions that get defined. Be sure you know what a module does or that it is from a trusted source. The code is available for each module on the system for you to look at and you are encouraged to do so. Modules loaded via load-mod can have their symbols referenced in the following manner: module-name::module-symbol. For example, a hypothetical JSON module might have the following procedure: json::parse. Or maybe it was loaded with the alias 'j': (load-mod json j) which would allow it to call the same procedure as j::parse.

load-mod-file


(load-mod-file [filepath: string]) ; => symbol
                

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 (only define, load-mod, and load-mod-file calls will be evaluated). A quirk of load-mod-file is that it can be used outside of modules similar to load, but with the given load restrictions. So if you want to load the definitions in a module and not evaluate globally scoped non-definition code load-mod-file can accomplish that.

macro


(macro [([[argument: symbol...]])] [expression...]) ; => macro
                

Macro is how you extend the control structures of slope. The special values args-list or ... can be given as an argument name. If this argument name is used it will always be a list and will contain any number of arguments given starting at its position in the argument list. There is an implicit (ie. created for you) begin block that will surround expression.... As such you do not need to add your own. macro creates its own scope and any define statements within the macro will be scoped to the macro (available to it and any child scopes), thus creating a closure. The difference between lambda and macro is that arguments to lambda are evaluated and arguments to macro are not. As such, code is manipulated rather than value, or a mix of the two occurs. This enables behaviors not possible with lambda. Example:


(define let
  (macro (args ...)
    (define in (map (lambda (inargs) (car inargs)) args))
    (define out (map (lambda (outargs) (eval (car (cdr outargs)))) args))
    (define l (eval ['lambda in (cons 'begin ...)]) )
    (apply l out)))

(let ((a 12)(b (+ 5 3))) (+ a b)) ; => 20
                  

mod-path


(mod-path) ; => string
                

Returns the current module path. The module path is calculated each time mod-path is called in order to detect changes. As such, this is more expensive than a runtime constant.

quote


(quote [expression...]) ; => bool
                

repl-flush


(repl-flush) ; => #t
                

Forces a write to the repl history file, truncating the file, and writing the contents of the repl sesssion to the file.

set!


(set! [var-name: symbol] [value|expression|procedure]) ; => value | expression | procedure
                

Returns the value that var-name was set to, in addition to updating the variable represented by var-name 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.

type


(type [value: any]) ; => string
                

Returns a string describing the type of anything passed to it.

usage


(usage [[module-or-built-in-name: string|symbol]]) ; => ()
                

If not passed a value, usage will display a list of all known built-in procedures as well a list of loaded modules and any aliases they may have. When given a value equal to a built-in procedure a description of that value will be output to stdout. When passed the name of a loaded module, including the colons (my-module::), a list of symbols that the module supplies usage information for will be printed to stdout. When passed the name of a module symbol reference (my-module::some-symbol) the usage information will be printed to stdout. Any time any unknown/unavailable information is requested a notice of that fact will be printed to stdout.

Operating System Oriented Procedures

chdir


(chdir [filepath: string]) ; => ()
                

Changes the current working directory to the given filepath. Will return an exception if it was unable to change to the given filepath.

chmod


(chmod [filepath: string] [file-mode: number]) ; => ()
                

Changes the filepermissions for the given file to the given file-mode number. Will return an exception if it was not possible to apply the given file-mode to the given filepath. The file-mode can be any format of number (octal, decimal, hex) so long as it represents a valid file-mode for the operating system.

env


(env [[env-key: string]] [[env-value: string]]) ; => association-list|string|()
                

When passed with no arguments will return an association list of the current environment variables. When passed with an env-key, will return an env-value as a string, or an empty string if the key could not be found. If passed an env-key and an env-value env will set the given key to the given value in the current env and return an empty list.

For example:


(env "SHELL")            ; => /bin/bash
(env "SHELL" "/bin/tcsh"); => ()
(env "SHELL")            ; => /bin/tcsh
                  

exit


(exit [[exit-code: number]]) ; => ()
                

Causes the program to exit with an exit code of 0. If an optional exit-code is given it will be used instead of the default, 0.

hostname


(hostname) ; => string
                

Returns the host name reported by the kernel as a string.

mkdir


(mkdir [path: string] [permissions: number] [[make-all: bool]]) ; => ()
                

Creates the directory at the given path. Permissions for the directory must be given, usually as an octal number (but any numerical representation that is valid is fine). If the optional argument make-all is #t any parent directories in the given path that do not exist will also be created with the given file permissions.

mv


(mv [from-path: string] [to-path: string]) ; => ()
                

Moves the file represented by from-path to to-path.

path-abs


(path-abs [filepath: string]) ; => string
                

Creates an absolute path from the given filepath by resolving all relative references and expanding ~.

path-base


(path-base [filepath: string]) ; => string
                

Returns the last element in the given filepath, removing a trailing slash if one is present.


(path-base "/home/someuser/.vimrc") ; => ".vimrc"
(path-base "/home/someuser/.vim/")  ; => ".vim"
                  

path-dir


(path-dir [filepath: string]) ; => string
                

Returns all of the path minus the element after the last path separator, removing a trailing path separator from the result (if one is present).


(path-dir "/home/someuser/.vim")  ; => "/home/someuser"
(path-dir "/home/someuser/.vim/") ; => "/home/someuser/.vim"
                  

path-extension


(path-extension [filepath: string] [[replacement-extension: string]]) ; => string
                

Returns the file extension for the file the given filepath points to, as defined as everything from the last ., i.e. .txt. If there is no extension an empty string is returned. If a replacement-extension is given it will replace the original extension and the full path, including the new extension, will be returned. The replacement-extension should include a . if it is wanted in the new path as the prior . will be removed as part of the replacement procedure.

path-glob


(path-glob [filepath: string]) ; => list
                

Returns a list of filepaths that match the glob pattern provided as filepath. A glob is created by inserting a * character. For example:


(path-glob "/home/*/.bashrc") ; => ["/home/someone/.bashrc" "/home/otherperson/.bashrc"]
(path-glob "/home/myuser/.*") ; => ["/home/myuser/.bashrc" "/home/myuser/.profile" "/home/myuser/.vimrc"]
                  

path-join


(path-join [[path-segment: string...]]) ; => string
                

Joins the given path-segments by the path separator appropriate for the given system. If no path-segments are passed the root direcotry is returned.

pwd


(pwd) ; => string
                

Returns the current working directory as a string.

rm


(rm [path: string] [[rm-all: bool]]) ; => ()
                

Removes the file pointed to by path from the filesystem. If rm-all is set to #t all children of the path will be removed as well (a recursive removal).

signal-catch-sigint


(signal-catch-sigint [procedure]) ; => bool
                

The procedure passed to signal-catch-sigint should take no arguments and will be run upon sigint being received. This will override all default behavior: if you still want the program to exit you will need to call (exit) from within the procedure. This procedure (signal-catch-sigint) should be called at the root level of your program as it will only have access to the global environment.

sleep


(sleep [[milliseconds: number]]) ; => ()
                

Halt further program execution until the given number of milliseconds has passed.

subprocess


(subprocess [list] [[output-redirection: io-handle|#f]] [[error-redirection: io-handle|#f]] [[input-redirection: io-handle|#f]]) ; => number
                

subprocess takes a list representing a command line argument. THe first item in the list should be the program to execute and all following items should be arguments to that program. Passing #f to any of the redirections causes them to use the std version of said redirection (stdout, stderr, or stdin).

term-char-mode


(term-char-mode) ; => ()
                

Changes the terminal to char mode.

term-cooked-mode


(term-cooked-mode) ; => ()
                

Changes the terminal to cooked mode.

term-raw-mode


(term-raw-mode) ; => ()
                

Changes the terminal to raw mode.

term-restore


(term-restore) ; => ()
                

Restores the terminal to how it was before any mode changes were instituted.

term-sane-mode


(term-sane-mode) ; => ()
                

Changes the terminal to sane mode.

term-size


(term-size) ; => list
                

Returns a list containing two numbers that represent the width and height of the terminal.

Net Oriented Procedures

net-conn


(net-conn [host: string] [port: string|number] [[use-tls: bool]] [[timeout-seconds: number]]) ; => io-handle|#f
                

Returns an open io-handle or #f if the connection failed or timed out (an exception if the arguments were not valid). The returned io-handle represents a TCP connection to the given host at the given port. If the optional value use-tls is set to #t then TLS will be used. If the optional value timeout-seconds is set to a positive number #f will be returned and the connection will time out if the connection does not occur within the given number of seconds. If timeout-seconds is not given or is less than 1 there is no timeout and an invalid connection attempt will hang indefinitely. In general it is a good idea to use a timeout for most use cases.

url-host


(url-host [url: string] [[new-host: string]]) ; => string
                

Returns a string representing the host value of the given url. If a new-host value is provided the whole url will be returned but with the original host replaced by the new-host value.

url-path


(url-path [url: string] [[new-path: string]]) ; => string
                

Returns a string representing the path portion of the given url, or an empty string if no path is present. If a new-path value is provided the whole url will be returned but with the original path replaced by the new-path value.

url-port


(url-port [url: string] [[new-port: string]]) ; => string
                

Returns a string representing the port portion of the given url, or an empty string if no port is present. If a new-port value is proviuded the whole url will be returned but with the original port replaced by the new-port value.

url-query


(url-query [url: string] [[new-query: string]]) ; => string
                

Returns a string representing the query portion of the url, or an empty string if no query segment is present. If a new-query value is provided the whole url will be returned but with the original query segment replaced by the new-query value.

url-scheme


(url-scheme [url: string] [[new-scheme: string]]) ; => string
                

Returns a string representing the scheme portion of the url. If a new-scheme value is provided the whole url will be returned but with the original scheme replaced by the new-scheme value.

Date/Time Oriented Procedures

date


(date [[format: string]]) ; => string
                

Returns the current date/time, optionally formatted by a format string argument.

date-format


(date-format [start-format: string] [date: string] [end-format: string]) ; => string
                

Converts a date rendered in the format given by start-format to a date rendered in the format given by end-format and returns that date string.

Date format/pattern substitutions:

%a
12 hour segment, lowercase: pm
%A
12 hour segment, uppercase: PM
%d
Day without leading zero: 4
%D
Day with leading zero: 04
%e
Day without leading zero, but with space padding:  4
%f
Month name, short: Jan
%F
Month name, long: January
%g or %G
Hour, 24 hour format: 15
%h
Hour, 12 hour format without leading zero: 2
%H
Hour, 12 hour format with leading zero: 02
%i
Minutes without leading zero: 4
%I
Minutes with leading zero: 04
%m
Month number, without leading zero: 6
%M
Month number, with leading zero: 06
%o
Timezone offset, without minutes: -07
%O
Timezone offset, with minutes: -0700
%s
Seconds, without leading zero: 5
%S
Seconds, with leading zero: 05
%w
Short weekeday: Wed
%W
Long weekeday: Wednesday
%y
Two digit year: 21
%Y
Four digit year: 2021
%Z
Time zone, as three chars: UTC, PST, etc.
%%
Will yield a literal percent sign: %
Anything else
A percent followed by an unrecognized char will yield a ?, as will a hanging % as the last char in the string

So the string: "F %e, %Y %h:%I%a would be equivalend to something like: August 8, 2021 4:03pm

When a string is converted to a date and no time zone information is present, slope will assume the user's local time.

date->timestamp


(date->timestamp [date: string] [date-format: string]) ; => number
                

Converts a date string that matches the given date-format to a unix timestamp.

Date format/pattern substitutions:

%a
12 hour segment, lowercase: pm
%A
12 hour segment, uppercase: PM
%d
Day without leading zero: 4
%D
Day with leading zero: 04
%e
Day without leading zero, but with space padding:  4
%f
Month name, short: Jan
%F
Month name, long: January
%g or %G
Hour, 24 hour format: 15
%h
Hour, 12 hour format without leading zero: 2
%H
Hour, 12 hour format with leading zero: 02
%i
Minutes without leading zero: 4
%I
Minutes with leading zero: 04
%m
Month number, without leading zero: 6
%M
Month number, with leading zero: 06
%o
Timezone offset, without minutes: -07
%O
Timezone offset, with minutes: -0700
%s
Seconds, without leading zero: 5
%S
Seconds, with leading zero: 05
%w
Short weekeday: Wed
%W
Long weekeday: Wednesday
%y
Two digit year: 21
%Y
Four digit year: 2021
%Z
Time zone, as three chars: UTC, PST, etc.
%%
Will yield a literal percent sign: %
Anything else
A percent followed by an unrecognized char will yield a ?, as will a hanging % as the last char in the string

So the string: "F %e, %Y %h:%I%a would be equivalend to something like: August 8, 2021 4:03pm

When a string is converted to a date and no time zone information is present, slope will assume the user's local time.

date-default-format


(date-default-format) ; => string
                

Returns a format string for the default format produced by date.

timestamp


(timestamp) ; => number
                

Returns a number representing the current date/time in the unix timestamp format.

timestamp->date


(timestamp->date [timestamp: string|number] [[format: string]]) ; => string
                

Converts a timestamp string to a date string that matches the given date-format.

Date format/pattern substitutions:

%a
12 hour segment, lowercase: pm
%A
12 hour segment, uppercase: PM
%d
Day without leading zero: 4
%D
Day with leading zero: 04
%e
Day without leading zero, but with space padding:  4
%f
Month name, short: Jan
%F
Month name, long: January
%g or %G
Hour, 24 hour format: 15
%h
Hour, 12 hour format without leading zero: 2
%H
Hour, 12 hour format with leading zero: 02
%i
Minutes without leading zero: 4
%I
Minutes with leading zero: 04
%m
Month number, without leading zero: 6
%M
Month number, with leading zero: 06
%o
Timezone offset, without minutes: -07
%O
Timezone offset, with minutes: -0700
%s
Seconds, without leading zero: 5
%S
Seconds, with leading zero: 05
%w
Short weekeday: Wed
%W
Long weekeday: Wednesday
%y
Two digit year: 21
%Y
Four digit year: 2021
%Z
Time zone, as three chars: UTC, PST, etc.
%%
Will yield a literal percent sign: %
Anything else
A percent followed by an unrecognized char will yield a ?, as will a hanging % as the last char in the string

So the string: "F %e, %Y %h:%I%a would be equivalend to something like: August 8, 2021 4:03pm

When a string is converted to a date and no time zone information is present, slope will assume the user's local time.

GUI Oriented Procedures (Optional Module)

The GUI module is built at compile time as an optional build feature. To know if your build includes the GUI module run slope -v look for (gui) after the build hash.

dialog-error


(dialog-error [gui-root] [window-name: string] [error: string]) => ()
                

Shows an error message. The dialog that is opened is not a system native dialog and will be constrained by the window size.

dialog-info


(dialog-info [gui-root] [window-name: string] [title: string] [message: string]) => ()
                

Shows an information window with the given title and message. The dialog that is opened is not a system native dialog and will be constrained by the window size.

dialog-open-file


(dialog-open-file [app: gui-root] [window-name: string] [callback: lambda]) => ()
                

Creates an open file dialog. The given lambda should take one argument, a string representing the selected file path. The dialog that is opened is not a system native dialog and will be constrained by the window size.

dialog-save-file


(dialog-save-file [app: gui-root] [window-name: string] [callback: lambda]) => ()
                

Creates an save file dialog. The given lambda should take one argument, a string representing the selected file path. The dialog that is opened is not a system native dialog and will be constrained by the window size.

container


(container [layout: string] [[columns: number]] [contents: gui-widget|container...])
                

container is the basic layout unit of the slope gui system. It can hold any number of other widgets or containers. The columns value is shown as optional, despite having a mandatory value after it, because it is only available for the grid layout. Do not pass columns for any other layout. The available layout strings are:

none
No layout
hbox
Arranges items in a horizontal row. Every element will have the same height (the height of the tallest item in the container) and objects will be left-aligned at their minimum width
vbox
Arranges items in a vertical column. Every element will have the same width (the width of the widest item in the container) and objects will be top-aligned at their minimum height
form
Arranges items in pairs where the first column is at minimum width. This is normally useful for labelling elements in a form, where the label is in the first column and the item it describes is in the second. You should always add an even number of elements to a form layout
grid
Positions elements into the given number of columns from left to right top to bottom. All grid elements will be evenly sized
max
Positions all container elements to fill the available space. The objects will all be full-sized and drawn in the order they were added to the container (last-most is on top)
padded
Positions all container elements to fill the available space but with a small padding around the outside. The size of the padding is theme specific. The objects will all be drawn in the order they were added to the container (last-most is on top)
border
Creates a four element border around any further elements, which will be stretched to fill the center space. At least four elements must be passed to fulfill the following: top, bottom, left, right (in that order). Any further objects will fill the center space and will stretch to fit. If you do not wish to have a top, bottom, left, or right you may pass #f for one of these values

container-add-to


(container-add-to [parent: gui-container] [child: gui-widget|gui-container]) => #t
                

Adds the given child widget/container to the given parent container.

container-scroll


(container-scroll [gui-container]) => gui-container
                

Takes a single, unlike the other containers, container (which can in turn contain widgets or other containers). The minimum size of the container it is passed will be ignored and scroll bars will be added as needed. Note that since container-scroll does not accept more than one gui-container a call to container-add-to targeting a container generated by container-scroll will result in a runtime panic.

container-size


(container-size [gui-container]) => list
                

Returns a two item list containing the given gui-container's width and height (in that order).

gui-add-shortcut


(gui-add-shortcut [gui-root] [window-name: string] [callback: lambda] [key: string] [modifier: string]) => #t
                

Adds a shortcut to the window. Any time the two given keys are pressed the lambda will be called with no arguments. The valid modifiers are:

  • alt
  • ctrl, control, ctl, strg
  • shift, sh
  • super, sup, windows, win, command, com

The available key strings are given in CamelCase with the first character always capitalized. Some examples are: F1, Left, Down, Tab, Q, 4, BackSpace, Home. Note that single characters are always uppercase for purposes of providing a key to this procedure.

gui-add-window


(gui-add-window [app: gui-root] [name: string])
                

Adds a window to the given gui-root as returned by gui-create. Name will be used to refer to the newly created window in other procedures. A string is prefered and called for in the procedure signature, but any other value can be used and will be stringified.

gui-create


(gui-create) => gui-root
                

Creates the application root for a gui application. See: gui-add-window for information about adding content to a gui-root. In general you will always want to use define when creating a gui-root as everything is done by passing it as a reference later on in the development process for GUI applications.

gui-list-windows


(gui-list-windows [app: gui-root]) => list
                

Returns a list containing the name of each window associated with the given gui-root.

gui-set-char-handlers


(gui-set-char-handlers [gui-root] [window-name: string] [callback: lambda]) => #t
                

Sets up all single key keypress handlers for the given window. This is distinct from shortcuts, which are made by a key chord (Ctrl/Shift/Alt/Mod+Something). The lambda should accept a string, which will always be a single character representing the character that was input. All kepresses that are not used by a widget (for example, an entry widget accepts text and will not further pass on the keypresses) will have the callback called with the character that was input as the input to the procedure. Note that it is the character and not the key. As such k and K are different. The character based nature also means that non-printable items (arrows, enter, backspace, etc) are not caught by this procedure. It is suggested that you use an if or cond statement inside of the lambda to sort them into the effects you want to achieve for any given character.

gui-use-light-theme


(gui-use-light-theme [[bool]]) => bool
                

If no arguments are given gui-use-light-theme will check is a light theme is being used and return #t if so, #f if not. Any value passed to gui-use-light-theme will be coerced to a bool. If the given value is #t then the gui will be set to use the light them, otherwise the dark theme will be used.

widget-add-to-size


(widget-add-to-size [gui-widget] [width-change: number] [height-change: number]) => gui-widget
                

Updates the width and height of the given widget by adding the width-change value to the current width and the height-change value to the current height. Returns the updated widget.

widget-disable


(widget-disable [gui-widget] [[set-disabled: bool]]) => bool
                

When called with only a valid gui-widget the current disabled state will be returned (#t means it is disabled). When called with the optional bool set-disabled the widget will be disabled if #t is given and will be enabled if #f is given. Any gui-widget passed to widget-disable that cannot be disabled will return #f for a call to either form of the procedure.

widget-get-text


(widget-get-text [gui-widget]) => string
                

Retrieves the text of the given widget.

widget-hide


(widget-hide [gui-widget]) => #t
                

Hides the given gui-widget. Calling on an already hidden widget will have no effect.

widget-make-button


(widget-make-button [text: string] [callback: lambda] [[alignment: number]] [[importance: number]]) => gui-widget
                

Creates a button widget with the given text. The callback should accept no arguments and should only create side effects (the return value will be discarded). If alignment is given it should be one of:

-1
Align Leading
0
Align Center
1
Align Trailing

If importance is given the buttons styling will change between three modes: least important (importance < 1), highly important (importance > 1), or the default: medium importance (importance = 1).

widget-make-checkbox


(widget-make-checkbox [text: string] [callback: lambda]) => gui-widget
                

Creates a checkbox widget with the given text. The callback should accept one argument representing the boolean state of the checkbox and should only create side effects (the return value will be discarded).

widget-make-entry


(widget-make-entry [placeholder-text: string] [[wrap-text: bool]] [[validation-callback: lambda]]) => gui-widget
                

Creates an entry widget with the given placeholder value. For accessibility purposes it is always advised to also have a label widget describing this widget. If a validation lambda is given it should take a string value as its argument and should validate based on that string. If it successfully validates #t should be returned, otherwise a string explaining the error should be returned.


(widget-make-hyperlink [text: string] [url: string]) => gui-widget
                

Creates a hyperlink widget with the given text linking to the given URL. If the URL cannot be parsed as a valid URL an exception will be returned.

widget-make-image


(widget-make-image [source: string] [location: string] [[mode: string]] [[min-width: number]] [[min-height: number]]) => gui-widget
                

Creates an image widget from the given source. Available sources are: path and url, the later of which will actually load any available URI. The location string represents the address or path of the image. The optional mode argument defaults to fit which will keep the original aspect ratio of the image but size it to fit the container (latching to the smallest container dimension). original may be passed to over-ride the default behavior and use the original image size, expanding the container if need be. Passing a min-width or min-height will cause the resulting image widget to size to this dimension as a minimum value. The way the size minimums work may be surprising as they interact with attempts to keep the aspect ratio and size to the container, so a 50x10 image set to 10x100 will not work, since aspect ratio is being kept. One value will be chosen as the one to use in sizing, this may depend on the type of container.

widget-make-label


(widget-make-label [text: string] [[alignment: number]] [[wrap-text: bool]]) => gui-widget
                

Creates a label widget with the given text. If alignment is given it should be one of:

-1
Align Leading
0
Align Center
1
Align Trailing

widget-make-markdown


(widget-make-markdown [markdown-text: string] [[wrap-text: bool]]) => gui-widget
                

Creates a markdown text block. The markdown-text string should be markdown, which will be parsed and rendered. wrap-text defaults to #f. If set to #t the text will be wrapped at word boundaries using the minimum size of the container.

widget-make-multiline


(widget-make-multiline [placeholder-text: string] [[wrap-text: bool]] [[validation-callback: lambda]]) => gui-widget
                

Creates a multiline text entry widget with the given placeholder value. For accessibility purposes it is always advised to also have a label widget describing this widget. If a validation lambda is given it should take a string value as its argument and should validate based on that string. If it successfully validates #t should be returned, otherwise a string explaining the error should be returned.

widget-make-password


(widget-make-password [placeholder-text: string] [[wrap-text: bool]] [[validation-callback: lambda]]) => gui-widget
                

Creates an password widget with the given placeholder value. For accessibility purposes it is always advised to also have a label widget describing this widget. If a validation lambda is given it should take a string value as its argument and should validate based on that string. If it successfully validates #t should be returned, otherwise a string explaining the error should be returned.

widget-make-select


(widget-make-select [options: list] [callback: lambda] [[alignment: number]]) => gui-widget
                

Creates a select widget with the given options. Any non-string options will be converted to string. The callback should accept a string argument representing the selected element and should only create side effects (the return value will be discarded). If alignment is given it should be one of:

-1
Align Leading
0
Align Center
1
Align Trailing

widget-make-separator


(widget-make-separator) => gui-widget
                

Returns a separator widget, which takes the form of a separator line that will go horizontal or vertical automatically depending on the container/layout.

widget-make-spacer


(widget-make-spacer) => gui-widget
                

Returns a spacer widget which will fill up the available space in the container, pushing other widgets away from it in a direction decided based on the container/layou.

widget-set-text


(widget-set-text [gui-widget] [string]) => string
                

Sets the text of the given widget to the given string and returns the string.

widget-resize


(widget-resize [gui-widget] [width: number] [height: number]) => gui-widget
                

In general the size of widgets is defined by the container/layout. However, in cases where it is not, widget-resize can be used. If used in a situation where it cannot be used, there will be no effect. Returns the widget with the new size.

widget-show


(widget-show [gui-widget]) => #t
                

Shows the given gui-widget. The widget will only become visible if it is in a visible grouping (window, container, layout, etc). Calling on an already visible widget will have no effect.

widget-size


(widget-size [gui-widget]) => list
                

Returns the given widget's current width and height, in that order, in a two item list.

window-allow-resize


(window-allow-resize [app: gui-root] [window-name: string] [bool]) => #t
                

Sets whether or not to allow the user to resize the given window.

window-center


(window-center [app: gui-root] [window-name: string]) => #t
                

Centers the given window on the screen.

window-close


(window-close [app: gui-root] [window-name: string]) => #t
                

Closes the given window. When called on an already closed window the behavior is undefined.

window-hide


(window-hide [app: gui-root] [window-name: string]) => #t
                

Hides the given window. There is no effect when called on an already hidden window.

window-resize


(window-resize [app: gui-root] [window-name: string] [width: number] [height: number]) => #t
                

Resizes the given app's given window to the width and height that were given, if possible.

window-set-content


(window-set-content [app: gui-root] [window-name: string] [content: gui-widget|gui-container]) => #t
                

Sets the content for the given window. This is usually a container, but could be a widget. #t or an exception will be returned.

window-set-fullscreen


(window-set-fullscreen [app: gui-root] [window-name: string] [set-to-full-screen: bool]) => #t
                

Sets the given window to full screen if #t is given and removes it from full screen if #f is given.

window-set-title


(window-set-title [app: gui-root] [window-name: string] [title: string]) => #t
                

Sets the title of the window to the given title string. If a non-string value is passed as title a stringified version of the value will be used.

window-show


(window-show [app: gui-root] [window-name: string]) => #t
                

Shows the given window. There is no effect when called on an already visible window.

window-show-and-run


(window-show-and-run [app: gui-root] [window-name: string]) => #t
                

Runs the gui with the given window visible. This is the main procedure for starting the gui and will block once evaluated until the window is closed.

Modules

Slope has a 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). A module can contain any slope code but only define, load-mod, and load-mod-file calls will be evaluated. Modules have a specific structure that will be gone over later. You may also alias a module by passing a second argument to load-mod that will allow you to reference symbols within a module via that alias. Modules are loaded into separate namespaced and are thus sandboxed from other code. You cannot use define or set! on a module's symbols. A module author can provide procedures that set module variables though, as a module can modify itself. To reference a symbol within a module you use the module name (or alias), two colons, and then the symbol you want to reference: my-module::some-procedure.

Module Path

Calling load-mod will load the first module(s) it finds that matches the name(s) passed to it. It will search the following locations and use the first non-empty string value as the local search path for modules:

  1. $SLOPE_MOD_PATH
  2. $XDG_DATA_HOME/slope/modules
  3. ~/.local/share/slope/modules

After determining the local path it will look for the module that was requested. If the module does not exist on the local module path it will search the global module path: /usr/local/lib/slope/modules. A local module will always be used over a global module if both exist.

How Modules Are Processed

When load-mod is called the module will be located and it's main.slo file will be parsed. All expressions are ignored except calls to define, load-mod, and load-mod-file. This limits but does not at all eliminate the danger of arbitrary code execution. If you define something like the following, there will still be arbitrary code execution: (define a (display "HI!")). The string "HI!" still gets printed. So be careful when you load modules. Know what you are loading and use vetted trusted sources where possible.

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.

On

Module Files

main.slo

Taking as an example a module named test, the bare minimum that the folder (or any other valid local folder path ending in the directory test) ~/.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. This file is the entry point to loading the definitions included in the module as well as calling the loading of other files.

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.

Module Documentation

Modules should contain a value named _USAGE in their main.slo file. This value should be an association list detailing each procedure/value exposed by the module. It should take the form of:


; This is for a module named: "mymodule"

(define _USAGE [
  ["my-proc"         "(my-proc [name: string]) => bool\nFurther info can go here. Provide what would be useful"]
  ["my-other-proc"   "(my-other-proc [name: string] [count: number]) => string"]
])
            

This value is special and will be parsed so that the information can be viewed via calls to usage, most commonly at the slope REPL:


; to get a list of values within the given module
(usage mymodule::)

; to get info on a single procedure
(usage mymodule::my-proc)
            

The Interpreter

There are a number of tools available for working with Slope code that are not in the scope of the language documentation. For information on running Slope code with the slope interpreter please see the interpreter documentation.