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

231
README.md
View File

@ -1,93 +1,132 @@
# PebbLisp
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.
## 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 `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` 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 be involve a comparison operator. For example:
The condition will typically involve a comparison operator. For example:
```
(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` 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 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)))
```
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))
```
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 ...)
```
For example, combining numbers and strings:
For example, combining numbers and strings is quite simple:
```
(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` 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))
@ -96,19 +135,24 @@ A `cat` operation is applied implicitly when using `+` with strings, but this ma
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` 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))
```
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))
@ -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)`.
# 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
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
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` 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))
@ -145,9 +189,11 @@ would return 16, if called at 5:16
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:
@ -155,7 +201,8 @@ Basic Window and TextLayer manipulations are enabled in PebbLisp.
(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:
@ -163,7 +210,8 @@ Creates and pushes to the screen a blank white window. Note that windows can be
(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:
@ -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.
## 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)
@ -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.
# 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

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

View File

@ -174,6 +174,14 @@
(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) (
(matches text (cat pattern "*"))
)))

View File

@ -6,11 +6,14 @@
" " " " " "
))
(def is-empty (fn (tile) (= " " tile)))
(def print-board (fn (board) (
(sys "clear")
(def '(a b c
d e f
g h i) board)
(prnl)
(prnl (cat " " a " | " b " | " c))
(prnl " -----------")
(prnl (cat " " d " | " e " | " f))
@ -18,36 +21,50 @@
(prnl (cat " " g " | " h " | " i))
)))
(def do-move (fn (board player) (
(def input (inp))
(def do-move (fn (board player warn) (
(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 i 0)
(if (& (is-empty (at (- spot 1) board)) (> spot 0) (< spot 10))
(map (fn (piece) (
(set i (+ 1 i))
(if (= i spot) player piece)
)) 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)
(& (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) (
(sys "clear")
(def '(a b c
d e f
g h i) board)
(if (winning-row (a b c)) a
(if (winning-row (d e f)) d
(if (winning-row (g h i)) g
(if (winning-row (a d g)) a
(if (winning-row (b e h)) b
(if (winning-row (c f i)) c
(if (winning-row (a e i)) a
(if (winning-row (c e g)) c
(if (is-winning-row (a b c)) a
(if (is-winning-row (d e f)) d
(if (is-winning-row (g h i)) g
(if (is-winning-row (a d g)) a
(if (is-winning-row (b e h)) b
(if (is-winning-row (c f i)) c
(if (is-winning-row (a e i)) a
(if (is-winning-row (c e g)) c
(if (is-full-board board) "Nobody"
" "
))))))))
)))))))))
)))
(def next-player (fn (current) (
@ -55,15 +72,15 @@
)))
(def game (fn (board player) (
(if (= " " (get-winner 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))
) (
(print-board board)
(prnl "")
(prnl "Game over! " (next-player player) " wins!")
(prnl)
(prnl "Game over! " winner " 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)
{
checkTypes(addToHashTable)
checkTypes(addToHashTable);
Object name = params[1];
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)
{
checkTypes(getFromHashTable)
checkTypes(getFromHashTable);
struct ObjectTable* table = &params[0].table->table;
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",
"LAMBDA_ARGS_NOT_LIST",
"DID_NOT_FIND_SYMBOL",
"BAD_SYMBOL",
"BAD_TYPE",
"BAD_PARAMS",
"BAD_NUMBER",
@ -242,10 +243,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 +277,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)
@ -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)
{
if (!params || !body) {
return errorObject(NULL_LAMBDA_LIST);
throw(NULL_LAMBDA_LIST, "fn params and body cannot be null");
}
if (params->type != TYPE_LIST) {

View File

@ -22,7 +22,13 @@
#ifdef RELEASE
#define assert(...) do { } while (0)
#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
#define MAX_TOK_CNT 2048
@ -94,6 +100,7 @@ enum errorCode {
NULL_MAP_ARGS,
LAMBDA_ARGS_NOT_LIST,
DID_NOT_FIND_SYMBOL,
BAD_SYMBOL,
BAD_TYPE,
BAD_PARAMS,
BAD_NUMBER,
@ -315,8 +322,11 @@ Object errorWithAllocatedContextLineNo(enum errorCode code, char* context, int l
#define errorAddContext(x, y, z, a) ;
#define throw(_code, ...) return errorObject(_code)
#else
#define throw(_code, ...) do { char* ERROR_CONTEXT = malloc(sizeof(char) * ERR_LEN); sprintf(ERROR_CONTEXT, __VA_ARGS__); \
return errorWithAllocatedContextLineNo(_code, ERROR_CONTEXT, __LINE__, __FILE__); } while (0)
#define throw(_code, ...) do { \
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__)
#endif

View File

@ -1,4 +1,4 @@
#define _GNU_SOURCE
#define _GNU_SOURCE // For segfault handling
#include "pebblisp.h"
@ -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;
}
@ -116,7 +120,7 @@ void repl(struct Environment* env)
free(oldBuf);
}
Object o = parseEval(buf, env);
Object o = parseEval(buf, "REPL", env);
if (isFuncy(o) || isError(o, DID_NOT_FIND_SYMBOL)) {
system(buf);
} else {
@ -246,9 +250,9 @@ int main(int argc, const char* argv[])
readFile(SCRIPTDIR "/lib.pbl", &env);
}
Object o = parseEval("(def prompt %%)", &env);
Object o = parseEval("(def prompt %%)", "[INTERNAL]", &env);
cleanObject(&o);
o = parseEval("(def preprocess (fn (text) (text)))", &env);
o = parseEval("(def preprocess (fn (text) (text)))", "[INTERNAL]", &env);
cleanObject(&o);
if (!settings.ignoreConfig) {
@ -268,10 +272,10 @@ int main(int argc, const char* argv[])
if (file) {
// Execute a file
loadArgsIntoEnv(argc, argv, &env);
_readFile(file, &env);
_readFile(file, argv[argc - 1], &env);
} else {
// Run arguments directly as pl code
Object r = parseEval(argv[argc - 1], &env);
Object r = parseEval(argv[argc - 1], "[PL ARGUMENTS]", &env);
printAndClean(&r);
}
} 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
#include "object.h"
#include "plfunc/pebbleobject.h"
#include "calc.h"
#include "pebblemain.h"
#define RESULT_LENGTH 128
@ -379,7 +379,7 @@ static Object run_script(Object* params, int length,
free(code);
return o;
}
return errorObject(SCRIPT_NOT_FOUND);
throw(SCRIPT_NOT_FOUND, "");
}
static void code_window_unload(Window* window)
@ -527,7 +527,7 @@ static void inbox_received_callback(DictionaryIterator* iter, void* context)
}
/** 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);
o.func = func;
@ -538,15 +538,15 @@ static struct Environment pebbleEnv()
{
struct Environment e = defaultEnv();
setGlobal(&e);
af("window", &add_window, &e);
af("sc", &run_script, &e);
af("cw", &createWindow, &e);
af("pw", &pushWindow, &e);
af("rw", &deleteWindow, &e);
af("atl", &addTextLayer, &e);
af("utl", &updateTextLayer, &e);
af("vibe", &doVibe, &e);
af("sub", &subscribe, &e);
addFunction("window", &add_window, &e);
addFunction("sc", &run_script, &e);
addFunction("cw", &createWindow, &e);
addFunction("pw", &pushWindow, &e);
addFunction("rw", &deleteWindow, &e);
addFunction("atl", &addTextLayer, &e);
addFunction("utl", &updateTextLayer, &e);
addFunction("vibe", &doVibe, &e);
addFunction("sub", &subscribe, &e);
return e;
}

View File

@ -1,5 +1,5 @@
#ifndef CALC_H
#define CALC_H
#ifndef PEBBLE_MAIN_H
#define PEBBLE_MAIN_H
#include <pebble.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
* @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])) {
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);
}
}
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)
{
checkTypes(structAccess)
checkTypes(structAccess);
Object structo = params[0];
Object field = params[1];
@ -565,13 +567,13 @@ struct Slice* getLastOpen()
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 Slice* tokens = nf_tokenize(input, &err);
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);
return o;
}
@ -681,21 +683,21 @@ char* readFileToString(FILE* input)
}
/// 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) {
return 1;
}
_readFile(input, env);
_readFile(input, fileName, env);
return 0;
}
int _readFile(FILE* input, struct Environment* env)
int _readFile(FILE* input, const char* fileName, struct Environment* env)
{
char* fileText = readFileToString(input);
fclose(input);
Object r = parseEval(fileText, env);
Object r = parseEval(fileText, fileName, env);
cleanObject(&r);
free(fileText - 1);

View File

@ -27,7 +27,7 @@ Result readSeq(struct Slice* slices);
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);
@ -46,20 +46,24 @@ Object typeCheck(const char* funcName, Object* params, int length,
#ifndef DISABLE_TYPE_CHECKS
#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) { \
return ERROR; \
}
#else
#define checkTypes(FUNC) ;
#define checkTypes(FUNC) do { } while (0)
#endif
#ifdef STANDALONE
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();

View File

@ -6,7 +6,7 @@
Object reduce(Object* params, unused int length, struct Environment* env)
{
checkTypes(reduce)
checkTypes(reduce);
Object list = params[0];
Object func = params[1];
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)
{
checkTypes(filter)
checkTypes(filter);
Object condition = params[0];
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)
{
checkTypes(append)
checkTypes(append);
Object list = params[0];
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)
{
checkTypes(prepend)
checkTypes(prepend);
Object list = cloneObject(params[0]);
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)
{
checkTypes(at)
checkTypes(at);
Object index = params[0];
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)
{
checkTypes(rest)
checkTypes(rest);
Object list = params[0];
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)
{
checkTypes(reverse)
checkTypes(reverse);
const Object* list = &params[0];
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)
{
checkTypes(isNum)
checkTypes(isNum);
Object test = params[0];
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)
{
checkTypes(isList)
checkTypes(isList);
Object test = params[0];
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)
{
checkTypes(isString)
checkTypes(isString);
Object test = params[0];
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)
{
checkTypes(isErr)
checkTypes(isErr);
Object test = params[0];
return boolObject(test.type == TYPE_ERROR);
@ -176,14 +176,14 @@ Object parseEvalO(Object* params, unused int length, struct Environment* env)
switch (text.type) {
case TYPE_SYMBOL: {
Object string = eval(&text, env);
Object parsed = parseEval(string.string, env);
Object parsed = parseEval(string.string, "(eval)", env);
cleanObject(&string);
return parsed;
}
case TYPE_SLIST:
return evalList(&text, env);
case TYPE_STRING:
return parseEval(text.string, env);
return parseEval(text.string, "(eval)", env);
default:
throw(CAN_ONLY_EVAL_STRINGS, "Tried to (eval) a %s, instead of a string or symbol list",
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)
{
checkTypes(catObjects)
checkTypes(catObjects);
if (length == 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)
{
checkTypes(len)
checkTypes(len);
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)
{
if (length < 2) {
return errorObject(NOT_ENOUGH_ARGUMENTS);
throw(NOT_ENOUGH_ARGUMENTS, "expected at least 2");
}
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)
{
if (timeStructDefinition == -1) {
parseEval("(struct Time (minute hour sec))", env);
parseEval("(struct Time (minute hour sec))", "[INTERNAL]", env);
timeStructDefinition = getStructIndex("Time");
}
time_t t = time(NULL);

View File

@ -12,9 +12,15 @@ Object print(Object* params, int length, unused struct Environment* env)
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)
{
checkTypes(numToChar)
checkTypes(numToChar);
Object c = params[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)
{
checkTypes(cd)
checkTypes(cd);
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)
{
checkTypes(systemCall)
checkTypes(systemCall);
Object process = params[0];
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)
{
checkTypes(readFileToObject)
checkTypes(readFileToObject);
Object filename = params[0];
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)
{
checkTypes(getEnvVar)
checkTypes(getEnvVar);
const char* envVar = getenv(params[0].string);
if (envVar) {
return nullTerminated(envVar);
}
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

View File

@ -7,6 +7,14 @@
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",
({ expect(isNumber), returns(isStringy) }),
"Gets a string containing the ascii character for the given number value.",
@ -60,6 +68,12 @@ tfn(getEnvVar, "env",
"(env HOME) => /home/sagevaillancourt"
);
tfn(randomO, "rand",
({ returns(isNumber) }),
"Returns a semi-random integer\n"
"(rand) => 2394568"
);
#endif //PEBBLISP_PC_H
#endif // STANDALONE

View File

@ -1,6 +1,6 @@
#include "pebbleobject.h"
#include "../calc.h"
#include "../pebblemain.h"
#include "../object.h"
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 text = params[1];
if (getPebbleType(window) != WINDOW) {
return errorObject(0);
throw(BAD_TYPE, "Expected a pebble window, but received %s", getTypeName(&window));
}
Layer* window_layer =
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});
return trueObject();
} 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)
{
checkTypes(charVal)
checkTypes(charVal);
Object test = params[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)
{
checkTypes(chars)
checkTypes(chars);
char c[2];
c[1] = '\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)
{
checkTypes(matches)
checkTypes(matches);
return boolObject(stringComp(params[0].string, params[1].string));
}
Object slen(Object* params, int length, unused struct Environment* env)
{
checkTypes(slen)
checkTypes(slen);
return numberObject(strlen(params[0].string));
}
Object substring(Object* params, int length, unused struct Environment* env)
{
checkTypes(substring)
checkTypes(substring);
Object start = params[0]; // First char to include
Object end = params[1]; // First char to exclude
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)
{
checkTypes(charAt)
checkTypes(charAt);
Object string = params[0];
Object at = params[1];

View File

@ -40,7 +40,7 @@ void cleanPromise(struct Promise* promise)
Object await(Object* params, int length, struct Environment* env)
{
checkTypes(await)
checkTypes(await);
struct Promise* promise = params[0].promise;
if (!promise->done) { // TODO: Does `done` need a mutex or other lock?
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 parsed;
if (isDigit(value[0])) {
parsed = parseEval(value, NULL);
parsed = parseEval(value, "[INTERNAL]", NULL);
} else {
parsed = stringFromSlice(value, strlen(value));
}
@ -127,8 +127,7 @@ void initialize()
if (!initialized) {
initialized = 1;
}
eprintf("Initializing...\n");
Object o = parseEval("(struct Request (queryParams username password))", global());
Object o = parseEval("(struct Request (queryParams username password))", "[INTERNAL]", global());
cleanObject(&o);
requestDefinition = getStructIndex("Request");
}

View File

@ -75,7 +75,9 @@ check() {
fi
local expected="$3"
if [ "$3" == "$regex" ]; then
expected="$4"
if [[ "$output" =~ ^$4$ ]]; then
pass "$1" $exit_code
return
@ -86,7 +88,7 @@ check() {
fi
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::"

View File

@ -4,17 +4,55 @@
/*
* Grammar:
* token
* expr
* (op expr expr)
* (list expr expr ... )
* Number:
* Hex:
* 0x[0-9a-f]+
* 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?
static const char singleTokens[] = "()'?";
int isSingle(const char *c)
{
static const char singleTokens[] = "()'?";
int i = 0;
while (singleTokens[i] != '\0') {
if (singleTokens[i] == c[0]) {
@ -39,18 +77,18 @@ int isWhitespace(const char c)
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->code = code;
err->plContext = slice;
int start = i > ERR_LEN ? i - ERR_LEN : 0;
strncpy(err->context, &input[start], ERR_LEN);
}
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,
int* length);
void processString(const char* input, struct Error* err, struct Slice* slice, int* i, int* lineNumber, int* length);
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* slice = &slices[0];
//int token_count = MAX_TOK_CNT;
// do {
// slices = malloc(sizeof(struct Slice) * token_count);
@ -67,7 +106,6 @@ struct Slice* nf_tokenize(const char* input, struct Error* err)
// } while (slices == NULL);
int i = 0;
int slice = 0;
int lineNumber = 1;
int parens = 0;
@ -86,14 +124,14 @@ struct Slice* nf_tokenize(const char* input, struct Error* err)
} else if (input[i] == ')') {
parens--;
if (parens < 0) {
buildErrFromInput(err, MISMATCHED_PARENS, i, input);
buildErrFromInput(err, MISMATCHED_PARENS, i, input, slice);
//free(slices);
return NULL;
}
}
slices[slice].text = &input[i];
slices[slice].lineNumber = lineNumber;
slice->text = &input[i];
slice->lineNumber = lineNumber;
if (isSingle(&input[i])) {
i++;
@ -103,28 +141,31 @@ struct Slice* nf_tokenize(const char* input, struct Error* err)
}
continue;
} else if (input[i] == '"') {
processString(input, err, slices, slice, &i, &lineNumber, &length);
} else {
collectSymbol(input, &i, &length);
}
processString(input, err, slice, &i, &lineNumber, &length);
if (err->context != 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++;
}
if (parens != 0) {
buildErrFromInput(err, MISMATCHED_PARENS, i, input);
buildErrFromInput(err, MISMATCHED_PARENS, i, input, slice);
//free(slices);
return NULL;
}
slices[slice].text = NULL;
slices[slice].length = 0;
size_t size = sizeof(struct Slice) * (slice + 1);
slice->text = NULL;
slice->length = 0;
size_t size = sizeof(struct Slice) * ((slice - &slices[0]) + 1);
struct Slice* allocated = malloc(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,
int* length);
void processString(const char* input, struct Error* err, struct Slice* slices, int slice, int* i, int* lineNumber,
int* length)
void processString(const char* input, struct Error* err, struct Slice* slice, int* i, int* lineNumber, int* length)
{
if (input[(*i) + 1] == '"' && input[(*i) + 2] == '"') {
tripleQuotedString(input, err, &slices[slice], i, lineNumber, length);
tripleQuotedString(input, err, slice, i, lineNumber, length);
} else {
singleQuotedString(input, i, lineNumber, length);
}
(*i)++;
}
int validSymbolChar(const char* c)
{
return !isWhitespace(*c)
&& !isSingle(c)
&& *c != '"'
&& *c != '\0';
}
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)++;
}
}
@ -171,7 +220,7 @@ void tripleQuotedString(const char* input, struct Error* err, struct Slice* slic
}
(*length)++;
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;
}
}
@ -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)
{
while (input[++(*i)] != '\0') {
if (input[(*i)] == '"') {
const int c = *i;
if (input[c] == '"') {
int backslashes = 0;
while (input[((*i) - 1) - backslashes] == '\\') {
while (input[(c - 1) - backslashes] == '\\') {
backslashes++;
}
// \\\" => 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) {
break;
}
} else if (input[(*i)] == '\n') {
} else if (input[c] == '\n') {
(*lineNumber)++;
}
(*length)++;