Merge remote-tracking branch 'origin/master'

This commit is contained in:
Sage Vaillancourt 2022-10-25 13:21:14 -04:00
commit a145b436c9
3 changed files with 258 additions and 63 deletions

309
README.md
View File

@ -1,150 +1,341 @@
# PebbLisp
A very basic LISP implementation with an interpreter and editor written for the Pebble.
[Download the Android app](https://gitlab.com/sagev9000/pebblispandroid/-/wikis/Downloads) to easily type up and send scripts to your Pebble.
[Visit the Rebble page](https://apps.rebble.io/en_US/application/5ec1f90f3dd31081fc98fce0) to download the full,
compiled version!
[Download the Android app](https://gitlab.com/sagev9000/pebblispandroid/-/wikis/Downloads) to easily type up and send
scripts to your Pebble.
# Table of contents
- [Coding in PebbLisp](#coding-in-pebblisp)
- [Def](#def)
- [If](#if)
- [Fn](#fn)
- [Cat](#cat)
- [Map](#map)
- [Fil](#fil)
- [Pebble-Specific Functions](#pebble-specific-functions)
- [Checking the Time](#checking-the-time)
- [Vibrating](#vibrating)
- [Window Manipulation](#window-manipulation)
- [Subscribing](#subscribing)
- [Non-Pebble Functionality](#non-pebble-functionality)
- [pl](#pl)
- [pebblisp.pbl](#pebblisppbl)
- [The Help Function `(?)`](#the-help-function-)
- [Viewing the `pl` Environment `(penv)`](#viewing-the-pl-environment-penv)
# Coding in PebbLisp
# Writing in PebbLisp
PebbLisp includes several built-ins functionalities.
## Def
`def` stores a given object into the environment with the given symbol. The general form is:
`(def symbol object)`
```
(def symbol object)
```
As an example, to store a long greeting message into the short symbol `g`:
As an example, to store a long greeting message into the short symbol `greeting`:
`(def g "Hello, how are you today?")`
```
(def greeting "Hello, my dear friend! How are you today?")
```
`g` will then evaluate to `"Hello, how are you today"`.
`greeting` will then evaluate to `"Hello, my dear friend! How are you today?"`.
> Remember that defining an object with the same symbol as an object that already exists is possible, but deletes the original object from the environment!
> **Note:**\
> Defining an object with the same symbol as an object that already exists is possible, but deletes the
> original object from the environment!
## If
`if` checks a given condition. If the condition evaluates to true, the first given expression is returned. If false, the second expression is returned. The general format is:
`(if condition expr1 expr2)`
`if` checks a given condition. If the condition evaluates to true, the first given expression is returned. If false, the
second expression is returned. The general format is:
The condition will typically be involve a comparison operator. For example:
```
(if condition expr1 expr2)
```
`(if (< 5 10) "Yee" "haw")`
The condition will typically involve a comparison operator. For example:
Would return `"Yee"`, as `(< 5 10)` aka `5 < 10` evaluates to true.
```
(if (< 5 10) "Yee" "haw")
```
This return `"Yee"`, as `(< 5 10)` (commonly written `5 < 10`) evaluates to true.
## Fn
`fn` creates a lambda with a (theoretically) arbitrary number of arguments to be evaluated on call. The general form is:
`(fn (arg-list) (lambda-body))`
```
(fn (arg-list) (lambda-body))
```
A lambda will commonly be stored under a `def` symbol, for example:
A lambda will frequently be stored under a `def` symbol. For example, to define a simple square function, you might
write:
`(def sq (fn (a) (* a a)))`
```
(def sq (fn (a) (* a a)))
```
Defines a simple lambda to square a given number. Calling it is as simple as `(sq 5)`, which returns `25`.
Calling it is as simple as `(sq 5)`, which returns `25`.
Lambdas can also be applied anonymously, as in `((fn (a) (* a a)) 5)`, which also returns `25`, but this is most useful when using something like `map`. For example:
Lambdas can also be applied anonymously, as in `((fn (a) (* a a)) 5)`, which also returns `25`.
This is most useful when using something like `map`. For example, an anonymous lambda could be used to square each
element in a list:
`(map (fn (a) (* a a)) (1 2 3 5 8 13 21 34))`
```
(map (fn (a) (* a a)) (1 2 3 5 8 13 21 34))
```
Uses an anonymous lambda to square each element in the list. This is particularly useful on a low-memory device like the Pebble, where it may be useful to avoid storing the named lambda object in the environment.
This is particularly useful on a low-memory device like the Pebble, where it may be useful to avoid storing the named
lambda object in the environment.
Lambdas may also have no arguments:
`(fn () 9001)`
```
(fn () (uppercase (input)))
```
## Cat
`cat` returns its arguments concatenated as strings. It has the same general form as arithmetic operators
`(cat expr1 expr2 ...)`
```
(cat expr1 expr2 ...)
```
For example, combining numbers and strings:
For example, combining numbers and strings is quite simple:
`(cat "There are " 5 " cats")`
```
(cat "There are " 5 " cats")
```
Would return `There are 5 cats`.
And this evaluates to `"There are 5 cats"`.
A `cat` operation is applied implicitly when using `+` with strings, but this may result in confusing behavior when combined with number objects, due to the left-associativity of operators. For example, `(+ 5 10 " cats")` results in `15 cats` but `(+ "Cats: " 5 10)` results in `Cats: 510`.
Previously, a `cat` operation was applied implicitly when using `+` with strings, but this resulted in confusing
behavior when combined with number objects (due to the left-associativity of operators, `(+ 1 2 "3")` would result
in `"33"`) and was thus removed.
## Map
`map` applies a given lambda to each element in a given list and returns a list composed of each result. The general form of a `map` expression is
`(map lambda (input-list))`
`map` applies a given lambda to each element in a given list and returns a list composed of each result. The general
form of a `map` expression is
```
(map lambda (input-list))
```
For example, using a `sq` lambda:
`(map sq (1 2 3 4 5 6 7 8 9 10)`
```
(map sq (1 2 3 4 5 6 7 8 9 10))
```
Would return `( 1 4 9 16 25 36 49 64 81 100 )`, with each element being the square of the corresponding element in the input list.
would return `( 1 4 9 16 25 36 49 64 81 100 )`, with each element being the square of the corresponding element in the
input list.
## Fil
`fil` returns a filtered list, based on a given list and a given condition. Partial function support in PebbLisp is nowhere near comprehensive, but `fil` operates on the bare notion that currently exists. The general form of a `fil` expression is
`(fil (partial-condition) (candidate-list))`
`fil` returns a filtered list, based on a given list and a given condition. Partial function support in PebbLisp is
nowhere near comprehensive, but `fil` operates on the bare notion that currently exists. The general form of a `fil`
expression is
Each element in the candidate list is compared against the partial condition. If the comparison returns true, it is added to the returned list. For example
```
(fil (partial-condition) (candidate-list))
```
`(fil (< 100) (20 150 30 200))`
Each element in the candidate list is compared against the partial condition. If the comparison returns true, it is
added to the returned list. For example:
```
(fil (< 100) (20 150 30 200))
```
would return `( 150 200 )`, as no other elements fit the condition `(< 100 n)`.
# Pebble-Specific Functions
There are several functions to access features of the Pebble itself.
Note that due to some over-simplicity of function-handling, all of the following functions expect to receive two arguments, regardless of how many they actually use.
Note that due to some over-simplicity of function-handling, all the following functions expect to receive two
arguments, regardless of how many they actually use.
## Checking the Time
There are several functions for fetching invidual elements of the current time
- `sec` the current seconds (0-59)
- `mnt` the current minutes (0-59)
- `hr` the current hour (0-23)
- `hrt` the current hour (1-12)
For example
There are several functions for fetching individual elements of the current time:
`(mnt)`
- `sec` the current seconds (0-59)
- `mnt` the current minutes (0-59)
- `hr` the current hour (0-23)
- `hrt` the current hour (1-12)
would return 16, if called at 5:16
For example: `(mnt)` would return `16`, if called at 5:16
## Vibrating
`vibe` calls the vibration engine to start, following a given pattern. The pattern should be a list, composed of alternating on/off durations, in milliseconds. For example
`(vibe (100 200 200 400 200 800))`
`vibe` starts the vibration engine with a given pattern. The pattern should be a list, composed of
alternating on/off durations in milliseconds. For example:
```
(vibe (100 200 200 400 200 800))
```
would cause a sort of *Bz. Bzz. Bzzzz. Bzzzzzzzz.* pattern.
## Window Manipulation
Basic Window and TextLayer manipulations are enabled in PebbLisp.
`cw` creates a blank window that can be manipulated by other functions. Note that `cw` does not itself display the window.
`cw` creates a blank window that can be manipulated by other functions. Note that `cw` does not itself display the
window.
`pw` is the function responsible for pushing a window onto the stack. For example
`pw` is the function responsible for pushing a window onto the stack. For example:
`(def win (cw)) (pw win)`
```
(def win (cw)) (pw win)
```
Creates and pushes to the screen a blank white window. Note that windows can be exited by tapping the back button. Getting something useful to display requires the use of a TextLayer.
Creates and pushes to the screen a blank white window. Note that windows can be exited by tapping the back button.
Getting something useful to display requires the use of a TextLayer.
`atl` adds a text layer to the given window, and displays the given object as text. For example
`atl` adds a text layer to the given window, and displays the given object as text. For example:
`(def tl (atl win "Hello"))`
```
(def tl (atl win "Hello"))
```
Adds a TextLayer to `ww` with the text "Hello", where `ww` is a Window created with `cw`. It also stores a reference to the TextLayer in `tl`, for later updates.
Adds a TextLayer to `ww` with the text "Hello", where `ww` is a Window created with `cw`. It also stores a reference to
the TextLayer in `tl`, for later updates.
`utl` changes the text in a given TextLayer. For example
`utl` changes the text in a given TextLayer. For example:
`(utl tl "Good-bye")`
```
(utl tl "Good-bye")
```
changes the text in `tl` to "Good-bye", where `tl` is an existing TextLayer.
## Subscribing
`sub` allows for a given lambda to be repeatedly called at a given time interval. More testing is needed, but it is possible that this lambda needs to be defined beforehand, instead of directly passed to `sub`. The lambda will likely fail if it requires arguments.
The first argument is the lambda to be called, and the second is an optional argument selecting the frequency of the repetition. If the second argument is any number 1-6, it will repeat every second, minute, hour, day, month, or year, respectively. If the argument is anything else, it will default to repeating every minute.
`sub` allows for a given lambda to be repeatedly called at a given time interval. More testing is needed, but it is
possible that this lambda needs to be defined beforehand, instead of directly passed to `sub`. The lambda will likely
fail if it requires arguments.
Subscribing currently has little use outside of window manipulation, as it's effects are hard to view outside of that environment. As an example
The first argument is the lambda to be called, and the second is an optional argument selecting the frequency of the
repetition. If the second argument is any number 1-6, it will repeat every second, minute, hour, day, month, or year,
respectively. If the argument is anything else, it will default to repeating every minute.
`(sub upwin 1)`
Subscribing currently has little use outside of window manipulation, as it's effects are hard to view outside of that
environment. As an example
```
(sub upwin 1)
```
would request that `upwin` be run every second, where `upwin` is a lambda that does not rely on arguments.
# Non-Pebble Functionality
PebbLisp has several features designed for use _outside_ of a Pebble device.\
The first was a simple command-line REPL, to more quickly test the language while developing.\
The number of extensions quickly grew, for the sake of dogfooding the language and purely for sport.
This is currently **not** an exhaustive list. However, calling [`(penv)`](#viewing-the-pl-environment-penv) lists all
useful objects that are
currently available in a `pl` session, and [`(?)`](#the-help-function-) can be used to read the built-in documentation
for available functions.
## pl
`pl` is a hybrid shell designed as both a PebbLisp REPL and a working user shell.
When a line is entered, it will be evaluated as PebbLisp code. If any error occurs, it will instead be evaluated
using stdlib's `system()` function.
> Note: It will also fall back to `system()` if the line evaluates to a function.
> This is because some PebbLisp functions overlap with common commands. E.g. `/bin/time` and `(time)`.\
> A quick way around this is to use `(cat your-fn)`, forcing a string representation.
Furthermore, `pl` offers tab-completion over the names of objects in its current environment, falling back to path
completion when appropriate.
## pebblisp.pbl
A plain pbl file read from `$HOME/.pebblisp.pbl`, by default.
Allows for setting default variables, aliases, and the `pl` prompt.
A simple take on this file can be found in `examples/`
## The Help Function `(?)`
Important for its usefulness in navigating all _other_ functions is `(?)`
`(?)` is used to pull up any available documentation for the given object, typically a function:
```
(? time)
```
Which provides help like:
```
Get a struct of the current time with fields (minute hour sec).
```
Notably, `(?)` can also be used on itself. I.e. `(? ?)`, which returns:
```
Gets a string with help text or a string representation for the object.
Function help:
(? islist) => "(islist (1 2 3)) => T"
Struct fields:
(? Output) => "{ stdout stderr }"
Other objects:
(? "Hello") => "Hello"
```
> **Note:**\
> The `pl` prompt has a hard-coded shorthand for these calls so that parentheses may be omitted.\
> Entering `? sys` at the prompt is equivalent to `(? sys)`
## Viewing the `pl` Environment `(penv)`
`(penv)` is a quick way to examine the objects in the current PebbLisp environment.
Example output:
```
env->capacity = 256
env->count = 92
x: "hello"
isnum: Native
min: Return the smallest of the given values
(min 2 1 6) => 1
( `...values` ) -> ( NATIVE_FUNC `values` `_min` ( `first` `values` ) )>
...
```
Note that the first two lines list the current capacity and number of objects in the environment.
Objects may be displayed a few ways, depending on their type. Seen here are:
* A simple object, `x`, displayed as a string
* A native function, `isnum`, denoted with the word Native (as its storage is opaque pointer data)
* A lambda, `min`, with a small docstring, a simple test, and the basic construction of the lambda.
`(penv)`, combined with `(?)` and tab-completion, makes it relatively easy to browse and test the PebbLisp interpreter.
# TODO
- Better script calling
- Even more pebble functions
- Maybe hard-code more built-in functions to ease up on memory use
- Wrap more Pebble SDK functions
- Maybe hard-code additional built-in functions to ease up on memory use

View File

@ -21,7 +21,11 @@ struct Settings {
Object getPrompt(struct Environment* env)
{
Object prompt = fetchFromEnvironment("prompt", env);
if (!isError(prompt, DID_NOT_FIND_SYMBOL)) {
prompt = cloneObject(prompt);
} else {
prompt = stringFromSlice("pl ~> ", 7);
}
if (prompt.type == TYPE_STRING) {
return prompt;
}

View File

@ -242,10 +242,10 @@ int stringNObj(struct string* s, const Object* obj)
break;
}
case TYPE_STATIC_FUNC:
appendf(s, "STATIC FUNC");
appendf(s, "STATIC_FUNC");
break;
case TYPE_HASH_TABLE:
appendf(s, "HASH TABLE");
appendf(s, "HASH_TABLE");
break;
case TYPE_STRUCT:
stringStruct(s, obj);
@ -276,7 +276,7 @@ int stringNObj(struct string* s, const Object* obj)
break;
}
case TYPE_FUNC:
appendf(s, "F%ld", obj->number);
appendf(s, "NATIVE_FUNC");
break;
case TYPE_LAMBDA: {
#if defined(STANDALONE) && !defined(DEBUG)