A toy language built for the Pebble smartwatch
Go to file
Sage Vaillancourt e97746ce07 More JSON-y struct string representation 2023-12-22 14:31:06 -05:00
resources Smaller white logo 2023-12-05 19:56:45 -05:00
src More JSON-y struct string representation 2023-12-22 14:31:06 -05:00
.gitignore Start work on forbble2. 2022-04-18 15:37:34 -04:00
README.md Correct `fil` typo 2023-12-17 12:16:26 -05:00
package.json Version bump to 0.4 2021-07-21 20:52:09 +01:00
wscript Initial Commit 2020-05-06 04:33:24 +01:00

README.md

PebbLisp

A very basic LISP implementation with an interpreter and editor written for the Pebble.

Visit the Rebble page to download the full, compiled version!

Download the Android app to easily type up and send scripts to your Pebble.

Live REPL:

Only functional on git.sagev.space

Table of contents

Coding 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)

As an example, to store a long greeting message into the short symbol greeting:

(def greeting "Hello, my dear friend! How are you today?")

greeting will then evaluate to "Hello, my dear friend! How are you today?".

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)

The condition will typically involve a comparison operator. For example:

(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))

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)))

Calling it is as simple as (sq 5), which returns 25.

Lambdas can also be applied anonymously. 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))

This is particularly valuable on a low-memory device like the Pebble, where it may be wise to avoid storing the named lambda object in the environment.

Lambdas may also have no arguments:

(fn () (uppercase (input)))

Cat

cat returns its arguments concatenated as strings. It has the same general form as arithmetic operators

(cat expr1 expr2 ...)

For example, combining numbers and strings is quite simple:

(cat "There are " 5 " cats")

And this evaluates to "There are 5 cats".

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))

For example, using a sq lambda:

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

Fil

fil returns a filtered list, based on a given list and a given condition. The general form of a fil expression is

(fil (condition) (candidate-list))

Each element in the candidate list is compared against the condition. If the comparison returns true, it is added to the returned list. For example:

(fil (fn (a) (> a 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 the following functions expect to receive two arguments, regardless of how many they actually use.

Checking the Time

There are several functions for fetching individual 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: (mnt) would return 16, if called at 5:16

Vibrating

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.

pw is the function responsible for pushing a window onto the stack. For example:

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

atl adds a text layer to the given window, and displays the given object as text. For example:

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

utl changes the text in a given TextLayer. For example:

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

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) lists all useful objects that are currently available in a pl session, and (?) 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
  • Wrap more Pebble SDK functions
  • Maybe hard-code additional built-in functions to ease up on memory use