Compare commits

..

10 Commits

Author SHA1 Message Date
Sage Vaillancourt 8b9a351be1 Use throw() instead of returning errorObject() results directly
Add (type-of) for getting the string representation of an object's type.
2023-03-03 14:58:54 -05:00
Sage Vaillancourt c37a12e244 Rename main() files.
Tweak checkTypes() to expect a closing semicolon.
2023-03-03 10:57:04 -05:00
Sage Vaillancourt 6176e9eb4b Improve lambda param handling.
Add (rand)
Clean up testing output.
tic-tac-toe.pbl QOL changes
2022-10-25 14:39:20 -04:00
Sage Vaillancourt 9b6b80f204 Add missing BAD_SYMBOL error 2022-10-25 14:39:16 -04:00
Sage Vaillancourt a145b436c9 Merge remote-tracking branch 'origin/master' 2022-10-25 14:39:02 -04:00
Sage Vaillancourt 672cdb692a Display parseEval source in error messages. 2022-10-25 13:21:04 -04:00
Sage Vaillancourt 6503bd7dcc Extend README to cover some non-Pebble functions.
Replace native function's pointer representation with "NATIVE_FUNC"
2022-10-11 22:27:25 -04:00
Sage Vaillancourt aae322e7fc Add basic table of contents to README 2022-10-12 01:04:14 +00:00
Sage Vaillancourt cba73e45b7 Add explicit fallback prompt. 2022-10-11 20:44:03 -04:00
Sage Vaillancourt e05b5648ae Correct README typo. 2022-10-12 00:14:08 +00:00
24 changed files with 485 additions and 199 deletions

235
README.md
View File

@ -1,93 +1,132 @@
# PebbLisp # PebbLisp
A very basic LISP implementation with an interpreter and editor written for the Pebble. A very basic LISP implementation with an interpreter and editor written for the Pebble.
[Visit the Rebble page](https://apps.rebble.io/en_US/application/5ec1f90f3dd31081fc98fce0) to download the full, compiled version! [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. [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. PebbLisp includes several built-ins functionalities.
## Def ## Def
`def` stores a given object into the environment with the given symbol. The general form is: `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
`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` 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 condition expr1 expr2)
``` ```
The condition will typically be involve a comparison operator. For example: The condition will typically involve a comparison operator. For example:
``` ```
(if (< 5 10) "Yee" "haw") (if (< 5 10) "Yee" "haw")
``` ```
Would return `"Yee"`, as `(< 5 10)` aka `5 < 10` evaluates to true. This return `"Yee"`, as `(< 5 10)` (commonly written `5 < 10`) evaluates to true.
## Fn ## Fn
`fn` creates a lambda with a (theoretically) arbitrary number of arguments to be evaluated on call. The general form is: `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: Lambdas may also have no arguments:
``` ```
(fn () 9001) (fn () (uppercase (input)))
``` ```
## Cat ## Cat
`cat` returns its arguments concatenated as strings. It has the same general form as arithmetic operators `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
`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` 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 lambda (input-list))
@ -96,19 +135,24 @@ A `cat` operation is applied implicitly when using `+` with strings, but this ma
For example, using a `sq` lambda: 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
`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` 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 (partial-condition) (candidate-list))
``` ```
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: 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)) (fil (< 100) (20 150 30 200))
@ -117,26 +161,26 @@ Each element in the candidate list is compared against the partial condition. If
would return `( 150 200 )`, as no other elements fit the condition `(< 100 n)`. would return `( 150 200 )`, as no other elements fit the condition `(< 100 n)`.
# Pebble-Specific Functions # Pebble-Specific Functions
There are several functions to access features of the Pebble itself. 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 ## 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:
``` - `sec` the current seconds (0-59)
(mnt) - `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 ## 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` 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)) (vibe (100 200 200 400 200 800))
@ -145,9 +189,11 @@ would return 16, if called at 5:16
would cause a sort of *Bz. Bzz. Bzzzz. Bzzzzzzzz.* pattern. would cause a sort of *Bz. Bzz. Bzzzz. Bzzzzzzzz.* pattern.
## Window Manipulation ## Window Manipulation
Basic Window and TextLayer manipulations are enabled in PebbLisp. 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:
@ -155,7 +201,8 @@ Basic Window and TextLayer manipulations are enabled in PebbLisp.
(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:
@ -163,7 +210,8 @@ Creates and pushes to the screen a blank white window. Note that windows can be
(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:
@ -174,11 +222,17 @@ Adds a TextLayer to `ww` with the text "Hello", where `ww` is a Window created w
changes the text in `tl` to "Good-bye", where `tl` is an existing TextLayer. changes the text in `tl` to "Good-bye", where `tl` is an existing TextLayer.
## Subscribing ## 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.
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) (sub upwin 1)
@ -186,7 +240,102 @@ Subscribing currently has little use outside of window manipulation, as it's eff
would request that `upwin` be run every second, where `upwin` is a lambda that does not rely on arguments. 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 # TODO
- Better script calling - Better script calling
- Even more pebble functions - Wrap more Pebble SDK functions
- Maybe hard-code more built-in functions to ease up on memory use - Maybe hard-code additional built-in functions to ease up on memory use

View File

@ -1,6 +1,6 @@
exe = pl exe = pl
base_files = main.c pebblisp.c tokens.c object.c env.c hash.c base_files = pcmain.c pebblisp.c tokens.c object.c env.c hash.c
func_files = plfunc/web.c plfunc/general.c plfunc/threads.c plfunc/plstring.c plfunc/pc.c func_files = plfunc/web.c plfunc/general.c plfunc/threads.c plfunc/plstring.c plfunc/pc.c
files:= $(base_files) $(func_files) files:= $(base_files) $(func_files)

View File

@ -131,7 +131,7 @@ struct Environment envForLambda(const Object* params, const Object* arguments, i
// Evaluate the `argument` list // Evaluate the `argument` list
const Object* param = params->list; const Object* param = params->list;
const Object* argument = arguments; const Object* argument = arguments;
for (int i = 0; i < paramCount && param; i++) { for (; param; param = param->forward) {
const char* paramName = param->string; const char* paramName = param->string;
if (paramName[0] == '.' && paramName[1] == '.' && paramName[2] == '.' && paramName[3] != '\0') { if (paramName[0] == '.' && paramName[1] == '.' && paramName[2] == '.' && paramName[3] != '\0') {
paramName = &paramName[3]; paramName = &paramName[3];
@ -144,6 +144,9 @@ struct Environment envForLambda(const Object* params, const Object* arguments, i
cleanObject(&varargs); cleanObject(&varargs);
break; break;
} }
if (!argument) {
break;
}
Object newEnvObj = eval(argument, outer); Object newEnvObj = eval(argument, outer);
@ -151,8 +154,7 @@ struct Environment envForLambda(const Object* params, const Object* arguments, i
cleanObject(&newEnvObj); cleanObject(&newEnvObj);
argument = argument ? argument->forward : NULL; argument = argument->forward;
param = param->forward;
} }
env.outer = outer; env.outer = outer;
@ -240,6 +242,7 @@ struct Environment defaultEnv()
#endif #endif
#ifdef STANDALONE #ifdef STANDALONE
pf(segfault), pf(segfault),
pf(typeOf),
pf(print), pf(print),
pf(numToChar), pf(numToChar),
pf(printEnvO), pf(printEnvO),
@ -254,7 +257,8 @@ struct Environment defaultEnv()
pf(help), pf(help),
pf(buildHashTable), pf(buildHashTable),
pf(addToHashTable), pf(addToHashTable),
pf(getFromHashTable) pf(getFromHashTable),
pf(randomO)
#endif #endif
}; };
@ -476,7 +480,7 @@ Object help(Object* params, int length, struct Environment* env)
int runTest(const char* test, const char* expected, int detailed) int runTest(const char* test, const char* expected, int detailed)
{ {
struct Environment env = defaultEnv(); struct Environment env = defaultEnv();
Object o = parseEval(test, &env); Object o = parseEval(test, "testCode", &env);
size_t length; size_t length;
char* result = stringObj(&o, &length); char* result = stringObj(&o, &length);
cleanObject(&o); cleanObject(&o);

View File

@ -174,6 +174,14 @@
(eval (rf file-name)) (eval (rf file-name))
))) )))
(def any-in (fn (list f)
(> (len (fil f list)) 0)
))
(def none-in (fn (list f)
(not (any-in list f))
))
(def startsWith (fn (text pattern) ( (def startsWith (fn (text pattern) (
(matches text (cat pattern "*")) (matches text (cat pattern "*"))
))) )))

View File

@ -6,48 +6,65 @@
" " " " " " " " " " " "
)) ))
(def is-empty (fn (tile) (= " " tile)))
(def print-board (fn (board) ( (def print-board (fn (board) (
(sys "clear") (sys "clear")
(def '(a b c (def '(a b c
d e f d e f
g h i) board) g h i) board)
(prnl)
(prnl (cat " " a " | " b " | " c)) (prnl (cat " " a " | " b " | " c))
(prnl "-----------") (prnl " -----------")
(prnl (cat " " d " | " e " | " f)) (prnl (cat " " d " | " e " | " f))
(prnl "-----------") (prnl " -----------")
(prnl (cat " " g " | " h " | " i)) (prnl (cat " " g " | " h " | " i))
))) )))
(def do-move (fn (board player) ( (def do-move (fn (board player warn) (
(def input (inp)) (prnl)
(if warn (prnl "You can't go there!") ())
(prnl "Your turn, " player "!")
(prnl "Where do you want to play?")
(def input (inp "1-9: "))
(def spot (eval input)) (def spot (eval input))
(def i 0) (def i 0)
(if (& (is-empty (at (- spot 1) board)) (> spot 0) (< spot 10))
(map (fn (piece) ( (map (fn (piece) (
(set i (+ 1 i)) (set i (+ 1 i))
(if (= i spot) player piece) (if (= i spot) player piece)
)) board) )) board)
(
(print-board board)
(do-move board player T)
)
)
))) )))
(def winning-row (fn (row) ( (def is-winning-row (fn (row) (
(def '(a b c) row) (def '(a b c) row)
(& (not (= " " a)) (= a b c)) (& (not (is-empty a)) (= a b c))
))) )))
(def is-full-board (fn (board)
(none-in board is-empty)
))
(def get-winner (fn (board) ( (def get-winner (fn (board) (
(sys "clear")
(def '(a b c (def '(a b c
d e f d e f
g h i) board) g h i) board)
(if (winning-row (a b c)) a (if (is-winning-row (a b c)) a
(if (winning-row (d e f)) d (if (is-winning-row (d e f)) d
(if (winning-row (g h i)) g (if (is-winning-row (g h i)) g
(if (winning-row (a d g)) a (if (is-winning-row (a d g)) a
(if (winning-row (b e h)) b (if (is-winning-row (b e h)) b
(if (winning-row (c f i)) c (if (is-winning-row (c f i)) c
(if (winning-row (a e i)) a (if (is-winning-row (a e i)) a
(if (winning-row (c e g)) c (if (is-winning-row (c e g)) c
(if (is-full-board board) "Nobody"
" " " "
)))))))) )))))))))
))) )))
(def next-player (fn (current) ( (def next-player (fn (current) (
@ -55,15 +72,15 @@
))) )))
(def game (fn (board player) ( (def game (fn (board player) (
(if (= " " (get-winner board)) (
(print-board board) (print-board board)
(def b (do-move board player)) (def winner (get-winner board))
(if (is-empty winner) (
(def b (do-move board player F))
(game b (next-player player)) (game b (next-player player))
) ( ) (
(print-board board) (prnl)
(prnl "") (prnl "Game over! " winner " wins!")
(prnl "Game over! " (next-player player) " wins!")
)) ))
))) )))
(game empty-board "X") (game empty-board (if (= 0 (rand 2)) "X" "O"))

View File

@ -185,7 +185,7 @@ Object buildHashTable(Object* params, int length, struct Environment* env)
Object addToHashTable(Object* params, int length, struct Environment* env) Object addToHashTable(Object* params, int length, struct Environment* env)
{ {
checkTypes(addToHashTable) checkTypes(addToHashTable);
Object name = params[1]; Object name = params[1];
Object add = params[2]; Object add = params[2];
@ -197,7 +197,7 @@ Object addToHashTable(Object* params, int length, struct Environment* env)
Object getFromHashTable(Object* params, int length, struct Environment* env) Object getFromHashTable(Object* params, int length, struct Environment* env)
{ {
checkTypes(getFromHashTable) checkTypes(getFromHashTable);
struct ObjectTable* table = &params[0].table->table; struct ObjectTable* table = &params[0].table->table;
struct StrippedObject* fetched = getFromTable(table, params[1].string); struct StrippedObject* fetched = getFromTable(table, params[1].string);

View File

@ -1,4 +0,0 @@
#ifndef PEBBLISP_MAIN_H
#define PEBBLISP_MAIN_H
#endif // PEBBLISP_MAIN_H

View File

@ -122,6 +122,7 @@ static const char* errorText[] = {
"NULL_MAP_ARGS", "NULL_MAP_ARGS",
"LAMBDA_ARGS_NOT_LIST", "LAMBDA_ARGS_NOT_LIST",
"DID_NOT_FIND_SYMBOL", "DID_NOT_FIND_SYMBOL",
"BAD_SYMBOL",
"BAD_TYPE", "BAD_TYPE",
"BAD_PARAMS", "BAD_PARAMS",
"BAD_NUMBER", "BAD_NUMBER",
@ -242,10 +243,10 @@ int stringNObj(struct string* s, const Object* obj)
break; break;
} }
case TYPE_STATIC_FUNC: case TYPE_STATIC_FUNC:
appendf(s, "STATIC FUNC"); appendf(s, "STATIC_FUNC");
break; break;
case TYPE_HASH_TABLE: case TYPE_HASH_TABLE:
appendf(s, "HASH TABLE"); appendf(s, "HASH_TABLE");
break; break;
case TYPE_STRUCT: case TYPE_STRUCT:
stringStruct(s, obj); stringStruct(s, obj);
@ -276,7 +277,7 @@ int stringNObj(struct string* s, const Object* obj)
break; break;
} }
case TYPE_FUNC: case TYPE_FUNC:
appendf(s, "F%ld", obj->number); appendf(s, "NATIVE_FUNC");
break; break;
case TYPE_LAMBDA: { case TYPE_LAMBDA: {
#if defined(STANDALONE) && !defined(DEBUG) #if defined(STANDALONE) && !defined(DEBUG)
@ -733,7 +734,7 @@ inline Object withLen(size_t len, enum Type type)
inline Object constructLambda(const Object* params, const Object* body, struct Environment* env) inline Object constructLambda(const Object* params, const Object* body, struct Environment* env)
{ {
if (!params || !body) { if (!params || !body) {
return errorObject(NULL_LAMBDA_LIST); throw(NULL_LAMBDA_LIST, "fn params and body cannot be null");
} }
if (params->type != TYPE_LIST) { if (params->type != TYPE_LIST) {

View File

@ -22,7 +22,13 @@
#ifdef RELEASE #ifdef RELEASE
#define assert(...) do { } while (0) #define assert(...) do { } while (0)
#else #else
#define assert(...) do {if (!(__VA_ARGS__)) {eprintf("\nassertion '" # __VA_ARGS__ "' at %s:%d failed!\nBailing!\n", __FILE__, __LINE__); int* X = NULL; unused int Y = *X;}} while (0) #define assert(...) do { \
if (!(__VA_ARGS__)) { \
eprintf("\nassertion '" # __VA_ARGS__ "' at %s:%d failed!\nBailing!\n", __FILE__, __LINE__); \
int* X = NULL; \
unused int Y = *X; \
}\
} while (0)
#endif #endif
#define MAX_TOK_CNT 2048 #define MAX_TOK_CNT 2048
@ -94,6 +100,7 @@ enum errorCode {
NULL_MAP_ARGS, NULL_MAP_ARGS,
LAMBDA_ARGS_NOT_LIST, LAMBDA_ARGS_NOT_LIST,
DID_NOT_FIND_SYMBOL, DID_NOT_FIND_SYMBOL,
BAD_SYMBOL,
BAD_TYPE, BAD_TYPE,
BAD_PARAMS, BAD_PARAMS,
BAD_NUMBER, BAD_NUMBER,
@ -315,8 +322,11 @@ Object errorWithAllocatedContextLineNo(enum errorCode code, char* context, int l
#define errorAddContext(x, y, z, a) ; #define errorAddContext(x, y, z, a) ;
#define throw(_code, ...) return errorObject(_code) #define throw(_code, ...) return errorObject(_code)
#else #else
#define throw(_code, ...) do { char* ERROR_CONTEXT = malloc(sizeof(char) * ERR_LEN); sprintf(ERROR_CONTEXT, __VA_ARGS__); \ #define throw(_code, ...) do { \
return errorWithAllocatedContextLineNo(_code, ERROR_CONTEXT, __LINE__, __FILE__); } while (0) char* ERROR_CONTEXT = malloc(sizeof(char) * ERR_LEN); \
sprintf(ERROR_CONTEXT, __VA_ARGS__); \
return errorWithAllocatedContextLineNo(_code, ERROR_CONTEXT, __LINE__, __FILE__); \
} while (0)
#define errorWithContext(_code, _context) errorWithContextLineNo(_code, _context, __LINE__, __FILE__) #define errorWithContext(_code, _context) errorWithContextLineNo(_code, _context, __LINE__, __FILE__)
#endif #endif

View File

@ -1,4 +1,4 @@
#define _GNU_SOURCE #define _GNU_SOURCE // For segfault handling
#include "pebblisp.h" #include "pebblisp.h"
@ -21,7 +21,11 @@ struct Settings {
Object getPrompt(struct Environment* env) Object getPrompt(struct Environment* env)
{ {
Object prompt = fetchFromEnvironment("prompt", env); Object prompt = fetchFromEnvironment("prompt", env);
if (!isError(prompt, DID_NOT_FIND_SYMBOL)) {
prompt = cloneObject(prompt); prompt = cloneObject(prompt);
} else {
prompt = stringFromSlice("pl ~> ", 7);
}
if (prompt.type == TYPE_STRING) { if (prompt.type == TYPE_STRING) {
return prompt; return prompt;
} }
@ -116,7 +120,7 @@ void repl(struct Environment* env)
free(oldBuf); free(oldBuf);
} }
Object o = parseEval(buf, env); Object o = parseEval(buf, "REPL", env);
if (isFuncy(o) || isError(o, DID_NOT_FIND_SYMBOL)) { if (isFuncy(o) || isError(o, DID_NOT_FIND_SYMBOL)) {
system(buf); system(buf);
} else { } else {
@ -246,9 +250,9 @@ int main(int argc, const char* argv[])
readFile(SCRIPTDIR "/lib.pbl", &env); readFile(SCRIPTDIR "/lib.pbl", &env);
} }
Object o = parseEval("(def prompt %%)", &env); Object o = parseEval("(def prompt %%)", "[INTERNAL]", &env);
cleanObject(&o); cleanObject(&o);
o = parseEval("(def preprocess (fn (text) (text)))", &env); o = parseEval("(def preprocess (fn (text) (text)))", "[INTERNAL]", &env);
cleanObject(&o); cleanObject(&o);
if (!settings.ignoreConfig) { if (!settings.ignoreConfig) {
@ -268,10 +272,10 @@ int main(int argc, const char* argv[])
if (file) { if (file) {
// Execute a file // Execute a file
loadArgsIntoEnv(argc, argv, &env); loadArgsIntoEnv(argc, argv, &env);
_readFile(file, &env); _readFile(file, argv[argc - 1], &env);
} else { } else {
// Run arguments directly as pl code // Run arguments directly as pl code
Object r = parseEval(argv[argc - 1], &env); Object r = parseEval(argv[argc - 1], "[PL ARGUMENTS]", &env);
printAndClean(&r); printAndClean(&r);
} }
} else { } else {

4
src/pcmain.h Normal file
View File

@ -0,0 +1,4 @@
#ifndef PC_MAIN_H
#define PC_MAIN_H
#endif

View File

@ -4,7 +4,7 @@
#define HASHLESS_ENV #define HASHLESS_ENV
#include "object.h" #include "object.h"
#include "plfunc/pebbleobject.h" #include "plfunc/pebbleobject.h"
#include "calc.h" #include "pebblemain.h"
#define RESULT_LENGTH 128 #define RESULT_LENGTH 128
@ -379,7 +379,7 @@ static Object run_script(Object* params, int length,
free(code); free(code);
return o; return o;
} }
return errorObject(SCRIPT_NOT_FOUND); throw(SCRIPT_NOT_FOUND, "");
} }
static void code_window_unload(Window* window) static void code_window_unload(Window* window)
@ -527,7 +527,7 @@ static void inbox_received_callback(DictionaryIterator* iter, void* context)
} }
/** General **/ /** General **/
void af(const char* name, Object (* func)(Object*, int, struct Environment*), struct Environment* env) void addFunction(const char* name, Object (* func)(Object*, int, struct Environment*), struct Environment* env)
{ {
Object o = newObject(TYPE_FUNC); Object o = newObject(TYPE_FUNC);
o.func = func; o.func = func;
@ -538,15 +538,15 @@ static struct Environment pebbleEnv()
{ {
struct Environment e = defaultEnv(); struct Environment e = defaultEnv();
setGlobal(&e); setGlobal(&e);
af("window", &add_window, &e); addFunction("window", &add_window, &e);
af("sc", &run_script, &e); addFunction("sc", &run_script, &e);
af("cw", &createWindow, &e); addFunction("cw", &createWindow, &e);
af("pw", &pushWindow, &e); addFunction("pw", &pushWindow, &e);
af("rw", &deleteWindow, &e); addFunction("rw", &deleteWindow, &e);
af("atl", &addTextLayer, &e); addFunction("atl", &addTextLayer, &e);
af("utl", &updateTextLayer, &e); addFunction("utl", &updateTextLayer, &e);
af("vibe", &doVibe, &e); addFunction("vibe", &doVibe, &e);
af("sub", &subscribe, &e); addFunction("sub", &subscribe, &e);
return e; return e;
} }

View File

@ -1,5 +1,5 @@
#ifndef CALC_H #ifndef PEBBLE_MAIN_H
#define CALC_H #define PEBBLE_MAIN_H
#include <pebble.h> #include <pebble.h>
#include "pebblisp.h" #include "pebblisp.h"

View File

@ -54,15 +54,17 @@ Object listDef(Object* nameList, Object* valueList, struct Environment* env)
* @param env The environment to add the new definition to * @param env The environment to add the new definition to
* @return The symbol(s) defined * @return The symbol(s) defined
*/ */
Object def(Object* params, unused int length, struct Environment* env) Object def(Object* params, int length, struct Environment* env)
{ {
if (length == 2) {
if (isStringy(params[0])) { if (isStringy(params[0])) {
return singleDef(params[0].string, &params[1], env); return singleDef(params[0].string, &params[1], env);
} }
if (length == 2 && isListy(params[0]) && isListy(params[1])) { if (isListy(params[0]) && isListy(params[1])) {
return listDef(&params[0], &params[1], env); return listDef(&params[0], &params[1], env);
} }
}
throw(BAD_TYPE, "Poorly constructed (def)"); throw(BAD_TYPE, "Poorly constructed (def)");
} }
@ -361,7 +363,7 @@ Object eval(const Object* obj, struct Environment* env)
Object structAccess(Object* params, unused int length, unused struct Environment* env) Object structAccess(Object* params, unused int length, unused struct Environment* env)
{ {
checkTypes(structAccess) checkTypes(structAccess);
Object structo = params[0]; Object structo = params[0];
Object field = params[1]; Object field = params[1];
@ -565,13 +567,13 @@ struct Slice* getLastOpen()
return lastOpen; return lastOpen;
} }
Object parseEval(const char* input, struct Environment* env) Object parseEval(const char* input, const char* codeSource, struct Environment* env)
{ {
struct Error err = noError(); struct Error err = noError();
struct Slice* tokens = nf_tokenize(input, &err); struct Slice* tokens = nf_tokenize(input, &err);
if (err.context != NULL) { if (err.context != NULL) {
Object o = errorWithContext(err.code, err.context); Object o = errorWithContextLineNo(err.code, err.context, err.plContext->lineNumber, codeSource);
free(err.context); free(err.context);
return o; return o;
} }
@ -681,21 +683,21 @@ char* readFileToString(FILE* input)
} }
/// Returns 1 if the file could not be opened. Otherwise, 0 /// Returns 1 if the file could not be opened. Otherwise, 0
int readFile(const char* filename, struct Environment* env) int readFile(const char* fileName, struct Environment* env)
{ {
FILE* input = fopen(filename, "r"); FILE* input = fopen(fileName, "r");
if (!input) { if (!input) {
return 1; return 1;
} }
_readFile(input, env); _readFile(input, fileName, env);
return 0; return 0;
} }
int _readFile(FILE* input, struct Environment* env) int _readFile(FILE* input, const char* fileName, struct Environment* env)
{ {
char* fileText = readFileToString(input); char* fileText = readFileToString(input);
fclose(input); fclose(input);
Object r = parseEval(fileText, env); Object r = parseEval(fileText, fileName, env);
cleanObject(&r); cleanObject(&r);
free(fileText - 1); free(fileText - 1);

View File

@ -27,7 +27,7 @@ Result readSeq(struct Slice* slices);
Result parseAtom(struct Slice* slice); Result parseAtom(struct Slice* slice);
Object parseEval(const char* input, struct Environment* env); Object parseEval(const char* input, const char* fileName, struct Environment* env);
Object evalList(const Object* obj, struct Environment* env); Object evalList(const Object* obj, struct Environment* env);
@ -46,20 +46,24 @@ Object typeCheck(const char* funcName, Object* params, int length,
#ifndef DISABLE_TYPE_CHECKS #ifndef DISABLE_TYPE_CHECKS
#define checkTypes(FUNC) int FAILED; Object ERROR = typeCheck(FUNC ## Symbol, params, length, FUNC ## TypeChecks, array_length(FUNC ## TypeChecks), &FAILED); \ #define checkTypes(FUNC) int FAILED; Object ERROR = typeCheck(FUNC ## Symbol, params, length, FUNC ## TypeChecks, array_length(FUNC ## TypeChecks), &FAILED); \
if (FAILED) { \
return ERROR; \
} else do { } while (0)
#define verifyTypes(FUNC, TYPE_CHECKS) int FAILED; Object ERROR = typeCheck(FUNC ## Symbol, params, length, TYPE_CHECKS, length, &FAILED); \
if (FAILED) { \ if (FAILED) { \
return ERROR; \ return ERROR; \
} }
#else #else
#define checkTypes(FUNC) ; #define checkTypes(FUNC) do { } while (0)
#endif #endif
#ifdef STANDALONE #ifdef STANDALONE
char* readFileToString(FILE* input); char* readFileToString(FILE* input);
int _readFile(FILE* input, struct Environment* env); int _readFile(FILE* input, const char* fileName, struct Environment* env);
int readFile(const char* filename, struct Environment* env); int readFile(const char* fileName, struct Environment* env);
struct Slice* getLastOpen(); struct Slice* getLastOpen();

View File

@ -6,7 +6,7 @@
Object reduce(Object* params, unused int length, struct Environment* env) Object reduce(Object* params, unused int length, struct Environment* env)
{ {
checkTypes(reduce) checkTypes(reduce);
Object list = params[0]; Object list = params[0];
Object func = params[1]; Object func = params[1];
Object total = params[2]; Object total = params[2];
@ -33,7 +33,7 @@ Object reduce(Object* params, unused int length, struct Environment* env)
Object filter(Object* params, unused int length, struct Environment* env) Object filter(Object* params, unused int length, struct Environment* env)
{ {
checkTypes(filter) checkTypes(filter);
Object condition = params[0]; Object condition = params[0];
Object list = params[1]; Object list = params[1];
@ -54,7 +54,7 @@ Object filter(Object* params, unused int length, struct Environment* env)
Object append(Object* params, unused int length, unused struct Environment* env) Object append(Object* params, unused int length, unused struct Environment* env)
{ {
checkTypes(append) checkTypes(append);
Object list = params[0]; Object list = params[0];
Object newElement = params[1]; Object newElement = params[1];
@ -65,7 +65,7 @@ Object append(Object* params, unused int length, unused struct Environment* env)
Object prepend(Object* params, unused int length, unused struct Environment* env) Object prepend(Object* params, unused int length, unused struct Environment* env)
{ {
checkTypes(prepend) checkTypes(prepend);
Object list = cloneObject(params[0]); Object list = cloneObject(params[0]);
Object newElement = cloneObject(params[1]); Object newElement = cloneObject(params[1]);
@ -77,7 +77,7 @@ Object prepend(Object* params, unused int length, unused struct Environment* env
Object at(Object* params, unused int length, unused struct Environment* env) Object at(Object* params, unused int length, unused struct Environment* env)
{ {
checkTypes(at) checkTypes(at);
Object index = params[0]; Object index = params[0];
Object list = params[1]; Object list = params[1];
@ -93,7 +93,7 @@ Object at(Object* params, unused int length, unused struct Environment* env)
Object rest(Object* params, unused int length, unused struct Environment* env) Object rest(Object* params, unused int length, unused struct Environment* env)
{ {
checkTypes(rest) checkTypes(rest);
Object list = params[0]; Object list = params[0];
Object BuildListNamed(ret); Object BuildListNamed(ret);
@ -108,7 +108,7 @@ Object rest(Object* params, unused int length, unused struct Environment* env)
Object reverse(Object* params, unused int length, unused struct Environment* ignore2) Object reverse(Object* params, unused int length, unused struct Environment* ignore2)
{ {
checkTypes(reverse) checkTypes(reverse);
const Object* list = &params[0]; const Object* list = &params[0];
Object rev = listObject(); Object rev = listObject();
@ -128,7 +128,7 @@ Object reverse(Object* params, unused int length, unused struct Environment* ign
Object isNum(Object* params, unused int length, unused struct Environment* env) Object isNum(Object* params, unused int length, unused struct Environment* env)
{ {
checkTypes(isNum) checkTypes(isNum);
Object test = params[0]; Object test = params[0];
return boolObject(test.type == TYPE_NUMBER); return boolObject(test.type == TYPE_NUMBER);
@ -136,7 +136,7 @@ Object isNum(Object* params, unused int length, unused struct Environment* env)
Object isList(Object* params, unused int length, unused struct Environment* env) Object isList(Object* params, unused int length, unused struct Environment* env)
{ {
checkTypes(isList) checkTypes(isList);
Object test = params[0]; Object test = params[0];
return boolObject(test.type == TYPE_LIST); return boolObject(test.type == TYPE_LIST);
@ -144,7 +144,7 @@ Object isList(Object* params, unused int length, unused struct Environment* env)
Object isString(Object* params, unused int length, unused struct Environment* env) Object isString(Object* params, unused int length, unused struct Environment* env)
{ {
checkTypes(isString) checkTypes(isString);
Object test = params[0]; Object test = params[0];
return boolObject(test.type == TYPE_STRING); return boolObject(test.type == TYPE_STRING);
@ -152,7 +152,7 @@ Object isString(Object* params, unused int length, unused struct Environment* en
Object isErr(Object* params, unused int length, unused struct Environment* env) Object isErr(Object* params, unused int length, unused struct Environment* env)
{ {
checkTypes(isErr) checkTypes(isErr);
Object test = params[0]; Object test = params[0];
return boolObject(test.type == TYPE_ERROR); return boolObject(test.type == TYPE_ERROR);
@ -176,14 +176,14 @@ Object parseEvalO(Object* params, unused int length, struct Environment* env)
switch (text.type) { switch (text.type) {
case TYPE_SYMBOL: { case TYPE_SYMBOL: {
Object string = eval(&text, env); Object string = eval(&text, env);
Object parsed = parseEval(string.string, env); Object parsed = parseEval(string.string, "(eval)", env);
cleanObject(&string); cleanObject(&string);
return parsed; return parsed;
} }
case TYPE_SLIST: case TYPE_SLIST:
return evalList(&text, env); return evalList(&text, env);
case TYPE_STRING: case TYPE_STRING:
return parseEval(text.string, env); return parseEval(text.string, "(eval)", env);
default: default:
throw(CAN_ONLY_EVAL_STRINGS, "Tried to (eval) a %s, instead of a string or symbol list", throw(CAN_ONLY_EVAL_STRINGS, "Tried to (eval) a %s, instead of a string or symbol list",
getTypeName(&text)); getTypeName(&text));
@ -192,7 +192,7 @@ Object parseEvalO(Object* params, unused int length, struct Environment* env)
Object catObjects(Object* params, int length, unused struct Environment* env) Object catObjects(Object* params, int length, unused struct Environment* env)
{ {
checkTypes(catObjects) checkTypes(catObjects);
if (length == 0) { if (length == 0) {
return stringFromSlice("", 0); return stringFromSlice("", 0);
@ -217,7 +217,7 @@ Object catObjects(Object* params, int length, unused struct Environment* env)
Object len(Object* params, unused int length, unused struct Environment* env) Object len(Object* params, unused int length, unused struct Environment* env)
{ {
checkTypes(len) checkTypes(len);
return numberObject(listLength(&params[0])); return numberObject(listLength(&params[0]));
} }
@ -277,7 +277,7 @@ int areEqual(const Object* obj1, const Object* obj2)
Object equ(Object* params, int length, unused struct Environment* env) Object equ(Object* params, int length, unused struct Environment* env)
{ {
if (length < 2) { if (length < 2) {
return errorObject(NOT_ENOUGH_ARGUMENTS); throw(NOT_ENOUGH_ARGUMENTS, "expected at least 2");
} }
for (int i = 0; i < length - 1; i++) { for (int i = 0; i < length - 1; i++) {
@ -329,7 +329,7 @@ int timeStructDefinition = -1;
Object getTime(unused Object* params, unused int length, struct Environment* env) Object getTime(unused Object* params, unused int length, struct Environment* env)
{ {
if (timeStructDefinition == -1) { if (timeStructDefinition == -1) {
parseEval("(struct Time (minute hour sec))", env); parseEval("(struct Time (minute hour sec))", "[INTERNAL]", env);
timeStructDefinition = getStructIndex("Time"); timeStructDefinition = getStructIndex("Time");
} }
time_t t = time(NULL); time_t t = time(NULL);

View File

@ -12,9 +12,15 @@ Object print(Object* params, int length, unused struct Environment* env)
return numberObject(0); return numberObject(0);
} }
Object typeOf(Object* params, unused int length, unused struct Environment* env)
{
const char* typeString = getTypeName(&(params[0]));
return nullTerminated(typeString);
}
Object numToChar(Object* params, unused int length, unused struct Environment* env) Object numToChar(Object* params, unused int length, unused struct Environment* env)
{ {
checkTypes(numToChar) checkTypes(numToChar);
Object c = params[0]; Object c = params[0];
if (c.number > 255 || c.number < 0) { if (c.number > 255 || c.number < 0) {
@ -39,7 +45,7 @@ Object takeInput(Object* params, int length, unused struct Environment* env)
Object cd(Object* params, unused int length, unused struct Environment* env) Object cd(Object* params, unused int length, unused struct Environment* env)
{ {
checkTypes(cd) checkTypes(cd);
return numberObject(chdir(params[0].string)); return numberObject(chdir(params[0].string));
} }
@ -52,7 +58,7 @@ Object cwd(unused Object* params, unused int length, unused struct Environment*
Object systemCall(Object* params, unused int length, unused struct Environment* env) Object systemCall(Object* params, unused int length, unused struct Environment* env)
{ {
checkTypes(systemCall) checkTypes(systemCall);
Object process = params[0]; Object process = params[0];
if (isStringy(process)) { if (isStringy(process)) {
@ -63,7 +69,7 @@ Object systemCall(Object* params, unused int length, unused struct Environment*
Object readFileToObject(Object* params, unused int length, unused struct Environment* env) Object readFileToObject(Object* params, unused int length, unused struct Environment* env)
{ {
checkTypes(readFileToObject) checkTypes(readFileToObject);
Object filename = params[0]; Object filename = params[0];
FILE* file = fopen(filename.string, "r"); FILE* file = fopen(filename.string, "r");
@ -79,11 +85,27 @@ Object readFileToObject(Object* params, unused int length, unused struct Environ
Object getEnvVar(Object* params, unused int length, unused struct Environment* env) Object getEnvVar(Object* params, unused int length, unused struct Environment* env)
{ {
checkTypes(getEnvVar) checkTypes(getEnvVar);
const char* envVar = getenv(params[0].string); const char* envVar = getenv(params[0].string);
if (envVar) { if (envVar) {
return nullTerminated(envVar); return nullTerminated(envVar);
} }
return stringFromSlice("", 0); return stringFromSlice("", 0);
} }
#include <time.h>
int initializedRand = 0;
Object randomO(Object* params, unused int length, unused struct Environment* env)
{
if (!initializedRand) {
srand(time(0));
initializedRand = 1;
}
int num = rand();
if (length > 0 && params[0].type == TYPE_NUMBER) {
num = num % params[0].number;
}
return numberObject(num);
}
#endif // STANDALONE #endif // STANDALONE

View File

@ -7,6 +7,14 @@
fn(print, "prn", "Prints the string representation of the given object to stdout."); fn(print, "prn", "Prints the string representation of the given object to stdout.");
tfn(typeOf, "type-of",
({ anyType, returns(isStringy) }),
"Gets a string indicating the type of the given object",
"(type-of 10)", "TYPE_NUMBER",
"(type-of \"string\")", "TYPE_STRING",
"(type-of (fn () ()))", "TYPE_LAMBDA",
);
tfn(numToChar, "ch", tfn(numToChar, "ch",
({ expect(isNumber), returns(isStringy) }), ({ expect(isNumber), returns(isStringy) }),
"Gets a string containing the ascii character for the given number value.", "Gets a string containing the ascii character for the given number value.",
@ -60,6 +68,12 @@ tfn(getEnvVar, "env",
"(env HOME) => /home/sagevaillancourt" "(env HOME) => /home/sagevaillancourt"
); );
tfn(randomO, "rand",
({ returns(isNumber) }),
"Returns a semi-random integer\n"
"(rand) => 2394568"
);
#endif //PEBBLISP_PC_H #endif //PEBBLISP_PC_H
#endif // STANDALONE #endif // STANDALONE

View File

@ -1,6 +1,6 @@
#include "pebbleobject.h" #include "pebbleobject.h"
#include "../calc.h" #include "../pebblemain.h"
#include "../object.h" #include "../object.h"
Object pebbleOther(enum PebbleType type, void* data, void (* cleanup)(Object*), Object pebbleOther(enum PebbleType type, void* data, void (* cleanup)(Object*),
@ -94,7 +94,7 @@ Object addTextLayer(Object* params, int length, struct Environment* env)
Object window = params[0]; Object window = params[0];
Object text = params[1]; Object text = params[1];
if (getPebbleType(window) != WINDOW) { if (getPebbleType(window) != WINDOW) {
return errorObject(0); throw(BAD_TYPE, "Expected a pebble window, but received %s", getTypeName(&window));
} }
Layer* window_layer = Layer* window_layer =
window_get_root_layer(accessPebbleObject(window)->window); window_get_root_layer(accessPebbleObject(window)->window);
@ -179,6 +179,6 @@ Object doVibe(Object* params, int length, struct Environment* env)
(VibePattern) {.durations = pattern, .num_segments = l}); (VibePattern) {.durations = pattern, .num_segments = l});
return trueObject(); return trueObject();
} else { } else {
return errorObject(NOT_A_LIST); throw(NOT_A_LIST, "(vibe) requires a non-empty list!");
} }
} }

View File

@ -4,7 +4,7 @@
Object charVal(Object* params, unused int length, unused struct Environment* env) Object charVal(Object* params, unused int length, unused struct Environment* env)
{ {
checkTypes(charVal) checkTypes(charVal);
Object test = params[0]; Object test = params[0];
return numberObject(test.string[0]); return numberObject(test.string[0]);
@ -12,7 +12,7 @@ Object charVal(Object* params, unused int length, unused struct Environment* env
Object chars(Object* params, unused int length, unused struct Environment* env) Object chars(Object* params, unused int length, unused struct Environment* env)
{ {
checkTypes(chars) checkTypes(chars);
char c[2]; char c[2];
c[1] = '\0'; c[1] = '\0';
int i = 0; int i = 0;
@ -62,19 +62,19 @@ int stringComp(const char* string, const char* pattern)
Object matches(Object* params, int length, unused struct Environment* env) Object matches(Object* params, int length, unused struct Environment* env)
{ {
checkTypes(matches) checkTypes(matches);
return boolObject(stringComp(params[0].string, params[1].string)); return boolObject(stringComp(params[0].string, params[1].string));
} }
Object slen(Object* params, int length, unused struct Environment* env) Object slen(Object* params, int length, unused struct Environment* env)
{ {
checkTypes(slen) checkTypes(slen);
return numberObject(strlen(params[0].string)); return numberObject(strlen(params[0].string));
} }
Object substring(Object* params, int length, unused struct Environment* env) Object substring(Object* params, int length, unused struct Environment* env)
{ {
checkTypes(substring) checkTypes(substring);
Object start = params[0]; // First char to include Object start = params[0]; // First char to include
Object end = params[1]; // First char to exclude Object end = params[1]; // First char to exclude
Object string = params[2]; Object string = params[2];
@ -99,7 +99,7 @@ Object substring(Object* params, int length, unused struct Environment* env)
Object charAt(Object* params, unused int length, unused struct Environment* env) Object charAt(Object* params, unused int length, unused struct Environment* env)
{ {
checkTypes(charAt) checkTypes(charAt);
Object string = params[0]; Object string = params[0];
Object at = params[1]; Object at = params[1];

View File

@ -40,7 +40,7 @@ void cleanPromise(struct Promise* promise)
Object await(Object* params, int length, struct Environment* env) Object await(Object* params, int length, struct Environment* env)
{ {
checkTypes(await) checkTypes(await);
struct Promise* promise = params[0].promise; struct Promise* promise = params[0].promise;
if (!promise->done) { // TODO: Does `done` need a mutex or other lock? if (!promise->done) { // TODO: Does `done` need a mutex or other lock?
pthread_join(promise->thread, NULL); pthread_join(promise->thread, NULL);

View File

@ -59,7 +59,7 @@ add_query_param(void* queryParamsV, enum MHD_ValueKind kind, const char* key,
Object pair = startList(nullTerminated(key)); Object pair = startList(nullTerminated(key));
Object parsed; Object parsed;
if (isDigit(value[0])) { if (isDigit(value[0])) {
parsed = parseEval(value, NULL); parsed = parseEval(value, "[INTERNAL]", NULL);
} else { } else {
parsed = stringFromSlice(value, strlen(value)); parsed = stringFromSlice(value, strlen(value));
} }
@ -127,8 +127,7 @@ void initialize()
if (!initialized) { if (!initialized) {
initialized = 1; initialized = 1;
} }
eprintf("Initializing...\n"); Object o = parseEval("(struct Request (queryParams username password))", "[INTERNAL]", global());
Object o = parseEval("(struct Request (queryParams username password))", global());
cleanObject(&o); cleanObject(&o);
requestDefinition = getStructIndex("Request"); requestDefinition = getStructIndex("Request");
} }

View File

@ -75,7 +75,9 @@ check() {
fi fi
local expected="$3"
if [ "$3" == "$regex" ]; then if [ "$3" == "$regex" ]; then
expected="$4"
if [[ "$output" =~ ^$4$ ]]; then if [[ "$output" =~ ^$4$ ]]; then
pass "$1" $exit_code pass "$1" $exit_code
return return
@ -86,7 +88,7 @@ check() {
fi fi
fail "$1" "$2" fail "$1" "$2"
FAIL_OUTPUT="${FAIL_OUTPUT}\n  expected '$3' but received '$output'\n" FAIL_OUTPUT="${FAIL_OUTPUT}\n  expected '$expected' but received '$output'\n"
} }
echo "::SHELL TESTS::" echo "::SHELL TESTS::"

View File

@ -4,17 +4,55 @@
/* /*
* Grammar: * Grammar:
* token * Number:
* expr * Hex:
* (op expr expr) * 0x[0-9a-f]+
* (list expr expr ... ) * Bin:
* 0b[01]+
* Dec:
* -?[0-9]+
*
* String:
* "([^\n"])|(\\")*"
* """.*"""
*
* Boolean:
* T
* F
*
* List:
* (...Expression)
*
* SList:
* '(...Expression)
*
* Symbol:
* [a-z][a-z0-9]*
*
* Primitive:
* Symbol
* Number
* String
* Boolean
* List
*
* Function Call:
* (Symbol ...Expression)
*
* Struct Access:
* Symbol.Symbol
*
* Expression:
* Primitive
* Function Call
* Struct Access
*/ */
// Is the char a standalone token? // Is the char a standalone token?
static const char singleTokens[] = "()'?";
int isSingle(const char *c) int isSingle(const char *c)
{ {
static const char singleTokens[] = "()'?";
int i = 0; int i = 0;
while (singleTokens[i] != '\0') { while (singleTokens[i] != '\0') {
if (singleTokens[i] == c[0]) { if (singleTokens[i] == c[0]) {
@ -39,18 +77,18 @@ int isWhitespace(const char c)
return c == ' ' || c == '\t' || c == '\n'; return c == ' ' || c == '\t' || c == '\n';
} }
void buildErrFromInput(struct Error* err, enum errorCode code, int i, const char* input) void buildErrFromInput(struct Error* err, enum errorCode code, int i, const char* input, struct Slice* slice)
{ {
err->context = malloc(sizeof(char) * ERR_LEN + 1); err->context = malloc(sizeof(char) * ERR_LEN + 1);
err->code = code; err->code = code;
err->plContext = slice;
int start = i > ERR_LEN ? i - ERR_LEN : 0; int start = i > ERR_LEN ? i - ERR_LEN : 0;
strncpy(err->context, &input[start], ERR_LEN); strncpy(err->context, &input[start], ERR_LEN);
} }
void collectSymbol(const char* input, int* i, int* length); void collectSymbol(const char* input, int* i, int* length);
void processString(const char* input, struct Error* err, struct Slice* slices, int slice, int* i, int* lineNumber, void processString(const char* input, struct Error* err, struct Slice* slice, int* i, int* lineNumber, int* length);
int* length);
struct Slice* nf_tokenize(const char* input, struct Error* err) struct Slice* nf_tokenize(const char* input, struct Error* err)
{ {
@ -60,6 +98,7 @@ struct Slice* nf_tokenize(const char* input, struct Error* err)
} }
struct Slice slices[MAX_TOK_CNT]; struct Slice slices[MAX_TOK_CNT];
struct Slice* slice = &slices[0];
//int token_count = MAX_TOK_CNT; //int token_count = MAX_TOK_CNT;
// do { // do {
// slices = malloc(sizeof(struct Slice) * token_count); // slices = malloc(sizeof(struct Slice) * token_count);
@ -67,7 +106,6 @@ struct Slice* nf_tokenize(const char* input, struct Error* err)
// } while (slices == NULL); // } while (slices == NULL);
int i = 0; int i = 0;
int slice = 0;
int lineNumber = 1; int lineNumber = 1;
int parens = 0; int parens = 0;
@ -86,14 +124,14 @@ struct Slice* nf_tokenize(const char* input, struct Error* err)
} else if (input[i] == ')') { } else if (input[i] == ')') {
parens--; parens--;
if (parens < 0) { if (parens < 0) {
buildErrFromInput(err, MISMATCHED_PARENS, i, input); buildErrFromInput(err, MISMATCHED_PARENS, i, input, slice);
//free(slices); //free(slices);
return NULL; return NULL;
} }
} }
slices[slice].text = &input[i]; slice->text = &input[i];
slices[slice].lineNumber = lineNumber; slice->lineNumber = lineNumber;
if (isSingle(&input[i])) { if (isSingle(&input[i])) {
i++; i++;
@ -103,28 +141,31 @@ struct Slice* nf_tokenize(const char* input, struct Error* err)
} }
continue; continue;
} else if (input[i] == '"') { } else if (input[i] == '"') {
processString(input, err, slices, slice, &i, &lineNumber, &length); processString(input, err, slice, &i, &lineNumber, &length);
} else {
collectSymbol(input, &i, &length);
}
if (err->context != NULL) { if (err->context != NULL) {
return NULL; return NULL;
} }
} else {
collectSymbol(input, &i, &length);
if (length == 0) {
buildErrFromInput(err, BAD_SYMBOL, i, input, slice);
return NULL;
}
}
slices[slice].length = length; slice->length = length;
slice++; slice++;
} }
if (parens != 0) { if (parens != 0) {
buildErrFromInput(err, MISMATCHED_PARENS, i, input); buildErrFromInput(err, MISMATCHED_PARENS, i, input, slice);
//free(slices); //free(slices);
return NULL; return NULL;
} }
slices[slice].text = NULL; slice->text = NULL;
slices[slice].length = 0; slice->length = 0;
size_t size = sizeof(struct Slice) * (slice + 1); size_t size = sizeof(struct Slice) * ((slice - &slices[0]) + 1);
struct Slice* allocated = malloc(size); struct Slice* allocated = malloc(size);
memcpy(allocated, slices, size); memcpy(allocated, slices, size);
@ -136,20 +177,28 @@ void singleQuotedString(const char* input, int* i, int* lineNumber, int* length)
void tripleQuotedString(const char* input, struct Error* err, struct Slice* slice, int* i, int* lineNumber, void tripleQuotedString(const char* input, struct Error* err, struct Slice* slice, int* i, int* lineNumber,
int* length); int* length);
void processString(const char* input, struct Error* err, struct Slice* slices, int slice, int* i, int* lineNumber, void processString(const char* input, struct Error* err, struct Slice* slice, int* i, int* lineNumber, int* length)
int* length)
{ {
if (input[(*i) + 1] == '"' && input[(*i) + 2] == '"') { if (input[(*i) + 1] == '"' && input[(*i) + 2] == '"') {
tripleQuotedString(input, err, &slices[slice], i, lineNumber, length); tripleQuotedString(input, err, slice, i, lineNumber, length);
} else { } else {
singleQuotedString(input, i, lineNumber, length); singleQuotedString(input, i, lineNumber, length);
} }
(*i)++; (*i)++;
} }
int validSymbolChar(const char* c)
{
return !isWhitespace(*c)
&& !isSingle(c)
&& *c != '"'
&& *c != '\0';
}
void collectSymbol(const char* input, int* i, int* length) void collectSymbol(const char* input, int* i, int* length)
{ {
while (!isWhitespace(input[++(*i)]) && !isSingle(&input[(*i)]) && input[(*i)] != '"' && input[(*i)] != '\0') { // TODO: Error if length is 0?
while (validSymbolChar(&input[++(*i)])) {
(*length)++; (*length)++;
} }
} }
@ -171,7 +220,7 @@ void tripleQuotedString(const char* input, struct Error* err, struct Slice* slic
} }
(*length)++; (*length)++;
if (input[c] == '\0' || input[c + 1] == '\0' || input[c + 2] == '\0') { if (input[c] == '\0' || input[c + 1] == '\0' || input[c + 2] == '\0') {
buildErrFromInput(err, UNEXPECTED_EOF, c, input); buildErrFromInput(err, UNEXPECTED_EOF, c, input, slice);
return; return;
} }
} }
@ -180,9 +229,10 @@ void tripleQuotedString(const char* input, struct Error* err, struct Slice* slic
void singleQuotedString(const char* input, int* i, int* lineNumber, int* length) void singleQuotedString(const char* input, int* i, int* lineNumber, int* length)
{ {
while (input[++(*i)] != '\0') { while (input[++(*i)] != '\0') {
if (input[(*i)] == '"') { const int c = *i;
if (input[c] == '"') {
int backslashes = 0; int backslashes = 0;
while (input[((*i) - 1) - backslashes] == '\\') { while (input[(c - 1) - backslashes] == '\\') {
backslashes++; backslashes++;
} }
// \\\" => Odd number of backslashes, quote IS escaped // \\\" => Odd number of backslashes, quote IS escaped
@ -190,7 +240,7 @@ void singleQuotedString(const char* input, int* i, int* lineNumber, int* length)
if (backslashes % 2 == 0) { if (backslashes % 2 == 0) {
break; break;
} }
} else if (input[(*i)] == '\n') { } else if (input[c] == '\n') {
(*lineNumber)++; (*lineNumber)++;
} }
(*length)++; (*length)++;