diff --git a/README.md b/README.md index b000883..8fa150e 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/src/main.c b/src/main.c index 8987840..30fbf36 100644 --- a/src/main.c +++ b/src/main.c @@ -21,7 +21,11 @@ struct Settings { Object getPrompt(struct Environment* env) { Object prompt = fetchFromEnvironment("prompt", env); - prompt = cloneObject(prompt); + if (!isError(prompt, DID_NOT_FIND_SYMBOL)) { + prompt = cloneObject(prompt); + } else { + prompt = stringFromSlice("pl ~> ", 7); + } if (prompt.type == TYPE_STRING) { return prompt; } diff --git a/src/object.c b/src/object.c index 62f3063..128194d 100644 --- a/src/object.c +++ b/src/object.c @@ -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)