Add some runtime type-checking.
Adjust simple-ops structure.
This commit is contained in:
parent
e22e022cd8
commit
66a07e395c
|
@ -393,8 +393,8 @@ struct Environment defaultEnv()
|
||||||
{"/", &dvi},
|
{"/", &dvi},
|
||||||
{"%", &mod},
|
{"%", &mod},
|
||||||
{"=", &equ},
|
{"=", &equ},
|
||||||
{">", >h},
|
{">", &greaterThan},
|
||||||
{"<", <h},
|
{"<", &lessThan},
|
||||||
{"&", &and},
|
{"&", &and},
|
||||||
{"|", &or},
|
{"|", &or},
|
||||||
pf("cat", catObjects),
|
pf("cat", catObjects),
|
||||||
|
|
25
src/object.c
25
src/object.c
|
@ -178,7 +178,7 @@ static const char* errorText[] = {"MISMATCHED_PARENS",
|
||||||
"LAMBDA_ARGS_NOT_LIST",
|
"LAMBDA_ARGS_NOT_LIST",
|
||||||
"DID_NOT_FIND_SYMBOL",
|
"DID_NOT_FIND_SYMBOL",
|
||||||
"BAD_TYPE",
|
"BAD_TYPE",
|
||||||
"LISTS_NOT_SAME_SIZE",
|
"BAD_PARAMS_ON",
|
||||||
"BAD_NUMBER",
|
"BAD_NUMBER",
|
||||||
"UNSUPPORTED_NUMBER_TYPE",
|
"UNSUPPORTED_NUMBER_TYPE",
|
||||||
"NOT_ENOUGH_ARGUMENTS",
|
"NOT_ENOUGH_ARGUMENTS",
|
||||||
|
@ -299,7 +299,7 @@ int stringNObj(char* dest, const Object* obj, const size_t len)
|
||||||
dest += stringf(dest, len, "E[%d]", (int)code);
|
dest += stringf(dest, len, "E[%d]", (int)code);
|
||||||
#else
|
#else
|
||||||
if (obj->error->context && obj->error->context[0] != '\0') {
|
if (obj->error->context && obj->error->context[0] != '\0') {
|
||||||
dest += stringf(dest, len, "'%s': %s", errorText[code],
|
dest += stringf(dest, len, "%s: %s", errorText[code],
|
||||||
obj->error->context);
|
obj->error->context);
|
||||||
} else if (code >= 0 && code <= INDEX_PAST_END) {
|
} else if (code >= 0 && code <= INDEX_PAST_END) {
|
||||||
dest += stringf(dest, len, "%s", errorText[code]);
|
dest += stringf(dest, len, "%s", errorText[code]);
|
||||||
|
@ -602,6 +602,11 @@ inline Object startList(const Object start)
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline int isNumber(const Object test)
|
||||||
|
{
|
||||||
|
return test.type == TYPE_NUMBER;
|
||||||
|
}
|
||||||
|
|
||||||
inline int isListy(const Object test)
|
inline int isListy(const Object test)
|
||||||
{
|
{
|
||||||
return test.type == TYPE_LIST || test.type == TYPE_SLIST;
|
return test.type == TYPE_LIST || test.type == TYPE_SLIST;
|
||||||
|
@ -612,6 +617,16 @@ inline int isStringy(const Object test)
|
||||||
return test.type == TYPE_STRING || test.type == TYPE_SYMBOL;
|
return test.type == TYPE_STRING || test.type == TYPE_SYMBOL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline int isBool(const Object test)
|
||||||
|
{
|
||||||
|
return test.type == TYPE_BOOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int isFuncy(const Object test)
|
||||||
|
{
|
||||||
|
return test.type == TYPE_LAMBDA || test.type == TYPE_FUNC;
|
||||||
|
}
|
||||||
|
|
||||||
inline int isValidType(const Object test)
|
inline int isValidType(const Object test)
|
||||||
{
|
{
|
||||||
switch (test.type) {
|
switch (test.type) {
|
||||||
|
@ -823,7 +838,11 @@ inline enum errorCode getErrorCode(const Object obj)
|
||||||
inline void errorAddContext(Object* o, const char* context, int lineNo, const char* fileName)
|
inline void errorAddContext(Object* o, const char* context, int lineNo, const char* fileName)
|
||||||
{
|
{
|
||||||
o->error->context = calloc(sizeof(char), RESULT_LENGTH);
|
o->error->context = calloc(sizeof(char), RESULT_LENGTH);
|
||||||
sprintf(o->error->context, "%s [33m%s:%d[0m", context, fileName, lineNo);
|
char* cursor = o->error->context;
|
||||||
|
cursor += sprintf(cursor, "%s", context);
|
||||||
|
if (fileName) {
|
||||||
|
sprintf(cursor, " [33m%s:%d[0m", fileName, lineNo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Object errorWithContextLineNo(enum errorCode code, const char* context, int lineNo, const char* fileName)
|
inline Object errorWithContextLineNo(enum errorCode code, const char* context, int lineNo, const char* fileName)
|
||||||
|
|
15
src/object.h
15
src/object.h
|
@ -25,13 +25,6 @@
|
||||||
_element = _element->forward)
|
_element = _element->forward)
|
||||||
#define POINTER _element
|
#define POINTER _element
|
||||||
|
|
||||||
#define FOR_POINTERS_IN_LISTS(_list, _list2) \
|
|
||||||
for(Object *_element = (_list)->list, *_element2 = (_list2)->list; \
|
|
||||||
_element != NULL && _element2 != NULL; \
|
|
||||||
_element = _element->forward, _element2 = _element2->forward)
|
|
||||||
#define P1 POINTER
|
|
||||||
#define P2 _element2
|
|
||||||
|
|
||||||
#ifdef PBL_PLATFORM_APLITE
|
#ifdef PBL_PLATFORM_APLITE
|
||||||
#define LOW_MEM
|
#define LOW_MEM
|
||||||
#endif
|
#endif
|
||||||
|
@ -50,7 +43,7 @@ enum errorCode {
|
||||||
LAMBDA_ARGS_NOT_LIST,
|
LAMBDA_ARGS_NOT_LIST,
|
||||||
DID_NOT_FIND_SYMBOL,
|
DID_NOT_FIND_SYMBOL,
|
||||||
BAD_TYPE,
|
BAD_TYPE,
|
||||||
LISTS_NOT_SAME_SIZE,
|
BAD_PARAMS_ON,
|
||||||
BAD_NUMBER,
|
BAD_NUMBER,
|
||||||
UNSUPPORTED_NUMBER_TYPE,
|
UNSUPPORTED_NUMBER_TYPE,
|
||||||
NOT_ENOUGH_ARGUMENTS,
|
NOT_ENOUGH_ARGUMENTS,
|
||||||
|
@ -169,10 +162,16 @@ void allocObject(Object** spot, Object src);
|
||||||
|
|
||||||
void appendList(Object* dest, const Object* src);
|
void appendList(Object* dest, const Object* src);
|
||||||
|
|
||||||
|
int isNumber(Object test);
|
||||||
|
|
||||||
int isListy(Object test);
|
int isListy(Object test);
|
||||||
|
|
||||||
int isStringy(Object test);
|
int isStringy(Object test);
|
||||||
|
|
||||||
|
int isBool(Object test);
|
||||||
|
|
||||||
|
int isFuncy(Object test);
|
||||||
|
|
||||||
int isValidType(Object test);
|
int isValidType(Object test);
|
||||||
|
|
||||||
int isError(Object obj, enum errorCode err);
|
int isError(Object obj, enum errorCode err);
|
||||||
|
|
|
@ -714,8 +714,8 @@ int main(int argc, const char* argv[])
|
||||||
}
|
}
|
||||||
deleteEnv(&env);
|
deleteEnv(&env);
|
||||||
shredDictionary();
|
shredDictionary();
|
||||||
// fprintf(stderr, "TOTAL ALLOCATIONS: %d\n", getAllocations());
|
// fprintf(stderr, "\nHEAP-ALLOCATED OBJECTS: %d\n", getAllocations());
|
||||||
// fprintf(stderr, "TOTAL BYTES: %zu\n", getBytes());
|
// fprintf(stderr, "TOTAL OBJECT.C ALLOC: %zu\n", getBytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -18,8 +18,9 @@ static const char * const _name ## Tests[] = {__VA_ARGS__}; \
|
||||||
static_assert(array_length(_name ## Tests) % 2 == 0, "Array of test strings must have exactly one expected result for each test."); \
|
static_assert(array_length(_name ## Tests) % 2 == 0, "Array of test strings must have exactly one expected result for each test."); \
|
||||||
Object _name(Object* params, int length, struct Environment* env)
|
Object _name(Object* params, int length, struct Environment* env)
|
||||||
|
|
||||||
|
// GCC warns without the attribute, even when typeChecks are used
|
||||||
#define tfn(_name, _type, _docs, ...) \
|
#define tfn(_name, _type, _docs, ...) \
|
||||||
static const Type _name ## Type[] = UNPACK _type; \
|
__attribute__((unused)) static int (*_name ## TypeChecks[])(Object) = UNPACK _type; \
|
||||||
fn(_name, _docs, __VA_ARGS__)
|
fn(_name, _docs, __VA_ARGS__)
|
||||||
|
|
||||||
#define fnn(_name, _symbol, _docs, ...) \
|
#define fnn(_name, _symbol, _docs, ...) \
|
||||||
|
|
231
src/plfunc.c
231
src/plfunc.c
|
@ -3,8 +3,33 @@
|
||||||
|
|
||||||
#include "plfunc.h"
|
#include "plfunc.h"
|
||||||
|
|
||||||
|
Object typeCheck(const char* funcName, Object* params, int length, int (*typeChecks[])(Object), int typeLength, int* failed)
|
||||||
|
{
|
||||||
|
*failed = 1;
|
||||||
|
if ((typeLength - 1) > length ) {
|
||||||
|
return errorObject(NOT_ENOUGH_ARGUMENTS);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < typeLength - 1; i++) {
|
||||||
|
if (typeChecks[i] && !typeChecks[i](params[i])) { // TODO: Use pl func name instead of C function name.
|
||||||
|
return /*errorObject(BAD_TYPE); */ errorWithContextLineNo(BAD_PARAMS_ON, funcName, 0, NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*failed = 0;
|
||||||
|
return numberObject(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef DISABLE_TYPE_CHECKS
|
||||||
|
#define checkTypes(FUNC) int FAILED; Object ERROR = typeCheck(#FUNC, params, length, FUNC ## TypeChecks, array_length(FUNC ## TypeChecks), &FAILED); \
|
||||||
|
if (FAILED) { \
|
||||||
|
return ERROR; \
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
#define checkTypes(FUNC) ;
|
||||||
|
#endif
|
||||||
|
|
||||||
Object reduce(Object* params, int length, struct Environment* env)
|
Object reduce(Object* params, int length, struct Environment* env)
|
||||||
{
|
{
|
||||||
|
checkTypes(reduce)
|
||||||
Object list = params[0];
|
Object list = params[0];
|
||||||
const Object func = params[1];
|
const Object func = params[1];
|
||||||
Object total = params[2];
|
Object total = params[2];
|
||||||
|
@ -22,6 +47,7 @@ Object reduce(Object* params, int length, struct Environment* env)
|
||||||
|
|
||||||
Object charAt(Object* params, int length, struct Environment* env)
|
Object charAt(Object* params, int length, struct Environment* env)
|
||||||
{
|
{
|
||||||
|
checkTypes(charAt)
|
||||||
Object string = params[0];
|
Object string = params[0];
|
||||||
Object at = params[1];
|
Object at = params[1];
|
||||||
|
|
||||||
|
@ -41,6 +67,7 @@ Object charAt(Object* params, int length, struct Environment* env)
|
||||||
|
|
||||||
Object filter(Object* params, int length, struct Environment* env)
|
Object filter(Object* params, int length, struct Environment* env)
|
||||||
{
|
{
|
||||||
|
checkTypes(filter)
|
||||||
Object condition = params[0];
|
Object condition = params[0];
|
||||||
Object list = params[1];
|
Object list = params[1];
|
||||||
|
|
||||||
|
@ -61,6 +88,7 @@ Object filter(Object* params, int length, struct Environment* env)
|
||||||
|
|
||||||
Object append(Object* params, int length, struct Environment* env)
|
Object append(Object* params, int length, struct Environment* env)
|
||||||
{
|
{
|
||||||
|
checkTypes(append)
|
||||||
Object list = params[0];
|
Object list = params[0];
|
||||||
Object newElement = params[1];
|
Object newElement = params[1];
|
||||||
|
|
||||||
|
@ -71,6 +99,7 @@ Object append(Object* params, int length, struct Environment* env)
|
||||||
|
|
||||||
Object prepend(Object* params, int length, struct Environment* env)
|
Object prepend(Object* params, int length, struct Environment* env)
|
||||||
{
|
{
|
||||||
|
checkTypes(prepend)
|
||||||
Object list = params[0];
|
Object list = params[0];
|
||||||
Object newElement = params[1];
|
Object newElement = params[1];
|
||||||
|
|
||||||
|
@ -82,6 +111,7 @@ Object prepend(Object* params, int length, struct Environment* env)
|
||||||
|
|
||||||
Object at(Object* params, int length, struct Environment* env)
|
Object at(Object* params, int length, struct Environment* env)
|
||||||
{
|
{
|
||||||
|
checkTypes(at)
|
||||||
Object index = params[0];
|
Object index = params[0];
|
||||||
Object list = params[1];
|
Object list = params[1];
|
||||||
|
|
||||||
|
@ -95,6 +125,7 @@ Object at(Object* params, int length, struct Environment* env)
|
||||||
|
|
||||||
Object rest(Object* params, int length, struct Environment* env)
|
Object rest(Object* params, int length, struct Environment* env)
|
||||||
{
|
{
|
||||||
|
checkTypes(rest)
|
||||||
Object list = params[0];
|
Object list = params[0];
|
||||||
if (!isListy(list)) {
|
if (!isListy(list)) {
|
||||||
return errorObject(NOT_A_LIST);
|
return errorObject(NOT_A_LIST);
|
||||||
|
@ -113,11 +144,9 @@ Object rest(Object* params, int length, struct Environment* env)
|
||||||
|
|
||||||
Object reverse(Object* params, int length, struct Environment* ignore2)
|
Object reverse(Object* params, int length, struct Environment* ignore2)
|
||||||
{
|
{
|
||||||
|
checkTypes(reverse)
|
||||||
Object _list = params[0];
|
Object _list = params[0];
|
||||||
|
|
||||||
if (!isListy(_list)) {
|
|
||||||
return errorObject(NOT_A_LIST);
|
|
||||||
}
|
|
||||||
const Object* list = &_list;
|
const Object* list = &_list;
|
||||||
Object rev = listObject();
|
Object rev = listObject();
|
||||||
|
|
||||||
|
@ -136,6 +165,7 @@ Object reverse(Object* params, int length, struct Environment* ignore2)
|
||||||
|
|
||||||
Object isNum(Object* params, int length, struct Environment* env)
|
Object isNum(Object* params, int length, struct Environment* env)
|
||||||
{
|
{
|
||||||
|
checkTypes(isNum)
|
||||||
Object test = params[0];
|
Object test = params[0];
|
||||||
|
|
||||||
return test.type == TYPE_NUMBER ? boolObject(1) : boolObject(0);
|
return test.type == TYPE_NUMBER ? boolObject(1) : boolObject(0);
|
||||||
|
@ -143,6 +173,7 @@ Object isNum(Object* params, int length, struct Environment* env)
|
||||||
|
|
||||||
Object isList(Object* params, int length, struct Environment* env)
|
Object isList(Object* params, int length, struct Environment* env)
|
||||||
{
|
{
|
||||||
|
checkTypes(isList)
|
||||||
Object test = params[0];
|
Object test = params[0];
|
||||||
|
|
||||||
return test.type == TYPE_LIST ? boolObject(1) : boolObject(0);
|
return test.type == TYPE_LIST ? boolObject(1) : boolObject(0);
|
||||||
|
@ -150,6 +181,7 @@ Object isList(Object* params, int length, struct Environment* env)
|
||||||
|
|
||||||
Object isString(Object* params, int length, struct Environment* env)
|
Object isString(Object* params, int length, struct Environment* env)
|
||||||
{
|
{
|
||||||
|
checkTypes(isString)
|
||||||
Object test = params[0];
|
Object test = params[0];
|
||||||
|
|
||||||
return test.type == TYPE_STRING ? boolObject(1) : boolObject(0);
|
return test.type == TYPE_STRING ? boolObject(1) : boolObject(0);
|
||||||
|
@ -157,6 +189,7 @@ Object isString(Object* params, int length, struct Environment* env)
|
||||||
|
|
||||||
Object charVal(Object* params, int length, struct Environment* env)
|
Object charVal(Object* params, int length, struct Environment* env)
|
||||||
{
|
{
|
||||||
|
checkTypes(charVal)
|
||||||
Object test = params[0];
|
Object test = params[0];
|
||||||
|
|
||||||
return numberObject(test.string[0]);
|
return numberObject(test.string[0]);
|
||||||
|
@ -164,6 +197,7 @@ Object charVal(Object* params, int length, struct Environment* env)
|
||||||
|
|
||||||
Object isErr(Object* params, int length, struct Environment* env)
|
Object isErr(Object* params, int length, struct Environment* env)
|
||||||
{
|
{
|
||||||
|
checkTypes(isErr)
|
||||||
Object test = params[0];
|
Object test = params[0];
|
||||||
|
|
||||||
return test.type == TYPE_ERROR ? boolObject(1) : boolObject(0);
|
return test.type == TYPE_ERROR ? boolObject(1) : boolObject(0);
|
||||||
|
@ -189,17 +223,11 @@ Object parseEvalO(Object* params, int length, struct Environment* env)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Object listEquality(const Object* list1, const Object* list2)
|
#ifdef STANDALONE
|
||||||
{
|
|
||||||
FOR_POINTERS_IN_LISTS(list1, list2) {
|
|
||||||
if (P1->type != P2->type || P1->number != P2->number) {
|
|
||||||
return boolObject(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return boolObject(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#define CAT_MAX 1024
|
#define CAT_MAX 1024
|
||||||
|
#else
|
||||||
|
#define CAT_MAX 64
|
||||||
|
#endif
|
||||||
|
|
||||||
Object _catObjects(Object obj1, Object obj2, struct Environment* env)
|
Object _catObjects(Object obj1, Object obj2, struct Environment* env)
|
||||||
{
|
{
|
||||||
|
@ -222,6 +250,8 @@ Object _catObjects(Object obj1, Object obj2, struct Environment* env)
|
||||||
|
|
||||||
Object catObjects(Object* params, int length, struct Environment* env)
|
Object catObjects(Object* params, int length, struct Environment* env)
|
||||||
{
|
{
|
||||||
|
checkTypes(catObjects)
|
||||||
|
|
||||||
Object string = stringFromSlice("", 0);
|
Object string = stringFromSlice("", 0);
|
||||||
if (length == 0) {
|
if (length == 0) {
|
||||||
return string;
|
return string;
|
||||||
|
@ -234,89 +264,12 @@ Object catObjects(Object* params, int length, struct Environment* env)
|
||||||
return string;
|
return string;
|
||||||
}
|
}
|
||||||
|
|
||||||
Object _basicOp(const Object* obj1, const Object* obj2, const char op,
|
|
||||||
struct Environment* env)
|
|
||||||
{
|
|
||||||
const int n1 = obj1->number;
|
|
||||||
const int n2 = obj2->number;
|
|
||||||
|
|
||||||
switch (op) {
|
|
||||||
case '&':
|
|
||||||
return boolObject(n1 != 0 && n2 != 0);
|
|
||||||
case '|':
|
|
||||||
return boolObject(n1 != 0 || n2 != 0);
|
|
||||||
|
|
||||||
case '=':
|
|
||||||
if (bothAre(TYPE_STRING, obj1, obj2)) {
|
|
||||||
return boolObject(!strcmp(obj1->string, obj2->string));
|
|
||||||
}
|
|
||||||
if (bothAre(TYPE_LIST, obj1, obj2)) {
|
|
||||||
return listEquality(obj1, obj2);
|
|
||||||
}
|
|
||||||
return boolObject(n1 == n2 && areSameType(obj1, obj2));
|
|
||||||
case '>':
|
|
||||||
return boolObject(n1 > n2);
|
|
||||||
case '<':
|
|
||||||
return boolObject(n1 < n2);
|
|
||||||
default:
|
|
||||||
return *obj1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Object basicOp(const Object* obj1, const Object* obj2, const char op,
|
|
||||||
struct Environment* env)
|
|
||||||
{
|
|
||||||
if (isError(*obj2, NOT_ENOUGH_ARGUMENTS)) {
|
|
||||||
return *obj2;
|
|
||||||
}
|
|
||||||
|
|
||||||
int lists = (obj1->type == TYPE_LIST) + (obj2->type == TYPE_LIST);
|
|
||||||
if (lists == 0) {
|
|
||||||
return _basicOp(obj1, obj2, op, env);
|
|
||||||
|
|
||||||
} else if (lists == 1) { // Single operand is applied to each element in list
|
|
||||||
const Object* listObj = (obj1->type == TYPE_LIST) ? obj1 : obj2;
|
|
||||||
const Object* singleObj = (obj1->type == TYPE_LIST) ? obj2 : obj1;
|
|
||||||
|
|
||||||
Object newList = listObject();
|
|
||||||
FOR_POINTER_IN_LIST(listObj) {
|
|
||||||
Object adding = eval(POINTER, env);
|
|
||||||
nf_addToList(&newList, _basicOp(&adding, singleObj, op, env));
|
|
||||||
}
|
|
||||||
return newList;
|
|
||||||
|
|
||||||
} else { // 2 lists with the op applied to matching indices of both lists
|
|
||||||
if (listLength(obj1) == listLength(obj2)) {
|
|
||||||
Object newList = listObject();
|
|
||||||
FOR_POINTERS_IN_LISTS(obj1, obj2) {
|
|
||||||
const Object ev1 = eval(P1, env);
|
|
||||||
const Object ev2 = eval(P2, env);
|
|
||||||
nf_addToList(&newList, _basicOp(&ev1, &ev2, op, env));
|
|
||||||
}
|
|
||||||
return newList;
|
|
||||||
} else {
|
|
||||||
return errorObject(LISTS_NOT_SAME_SIZE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Object len(Object* params, int length, struct Environment* env)
|
Object len(Object* params, int length, struct Environment* env)
|
||||||
{
|
{
|
||||||
Object obj1 = params[0];
|
checkTypes(len)
|
||||||
|
|
||||||
if (!isListy(obj1)) {
|
return numberObject(listLength(¶ms[0]));
|
||||||
return errorObject(NOT_A_LIST);
|
|
||||||
}
|
|
||||||
Object o = numberObject(listLength(&obj1));
|
|
||||||
return o;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define BASIC_OP(_name, _char) \
|
|
||||||
Object _name(Object* params, int length, struct Environment* env) \
|
|
||||||
{ \
|
|
||||||
Object obj1 = params[0]; \
|
|
||||||
Object obj2 = params[1]; \
|
|
||||||
return basicOp(&obj1, &obj2, _char, env); \
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#define BASIC_MATH(_name, _op) \
|
#define BASIC_MATH(_name, _op) \
|
||||||
|
@ -342,17 +295,86 @@ BASIC_MATH(dvi, /=)
|
||||||
|
|
||||||
BASIC_MATH(mod, %=)
|
BASIC_MATH(mod, %=)
|
||||||
|
|
||||||
BASIC_OP(equ, '=')
|
int areEqual(const Object* obj1, const Object* obj2);
|
||||||
|
|
||||||
BASIC_OP(gth, '>')
|
int listEquality(const Object* list1, const Object* list2)
|
||||||
|
{
|
||||||
|
Object* element1, *element2;
|
||||||
|
for (element1 = (list1)->list, element2 = (list2)->list;
|
||||||
|
element1 != ((void*) 0) && element2 != ((void*) 0);
|
||||||
|
element1 = element1->forward, element2 = element2->forward) {
|
||||||
|
if (!areEqual(element1, element2)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (element1 == NULL && element2 == NULL);
|
||||||
|
}
|
||||||
|
|
||||||
BASIC_OP(lth, '<')
|
int areEqual(const Object* obj1, const Object* obj2)
|
||||||
|
{
|
||||||
|
const int n1 = obj1->number;
|
||||||
|
const int n2 = obj2->number;
|
||||||
|
|
||||||
BASIC_OP(and, '&')
|
if (bothAre(TYPE_STRING, obj1, obj2)) {
|
||||||
|
return !strcmp(obj1->string, obj2->string);
|
||||||
|
}
|
||||||
|
if (bothAre(TYPE_LIST, obj1, obj2)) {
|
||||||
|
return listEquality(obj1, obj2);
|
||||||
|
}
|
||||||
|
return n1 == n2 && areSameType(obj1, obj2);
|
||||||
|
}
|
||||||
|
|
||||||
BASIC_OP(or, '|')
|
Object equ(Object* params, int length, struct Environment* env)
|
||||||
|
{
|
||||||
|
if (length < 2) {
|
||||||
|
return errorObject(NOT_ENOUGH_ARGUMENTS);
|
||||||
|
}
|
||||||
|
int bool = 1;
|
||||||
|
for (int i = 0; i < length - 1; i++) {
|
||||||
|
if (!areEqual(¶ms[i], ¶ms[i + 1])) {
|
||||||
|
bool = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return boolObject(bool);
|
||||||
|
}
|
||||||
|
|
||||||
#undef BASIC_OP
|
Object or(Object* params, int length, struct Environment* env)
|
||||||
|
{
|
||||||
|
if (length < 2) {
|
||||||
|
return errorObject(NOT_ENOUGH_ARGUMENTS);
|
||||||
|
}
|
||||||
|
int bool = 0;
|
||||||
|
for (int i = 0; i < length - 1; i++) {
|
||||||
|
if (params[i].number || params[i + 1].number) {
|
||||||
|
bool = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return boolObject(bool);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define BASIC_COMPARISON(NAME, OP)\
|
||||||
|
Object NAME(Object* params, int length, struct Environment* env) \
|
||||||
|
{ \
|
||||||
|
if (length < 2) { \
|
||||||
|
return errorObject(NOT_ENOUGH_ARGUMENTS); \
|
||||||
|
} \
|
||||||
|
int bool = 1; \
|
||||||
|
for (int i = 0; i < length - 1; i++) { \
|
||||||
|
if (!(params[i].number OP params[i + 1].number)) { \
|
||||||
|
bool = 0; \
|
||||||
|
break; \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
return boolObject(bool); \
|
||||||
|
}
|
||||||
|
|
||||||
|
BASIC_COMPARISON(greaterThan, >)
|
||||||
|
|
||||||
|
BASIC_COMPARISON(lessThan, <)
|
||||||
|
|
||||||
|
BASIC_COMPARISON(and, &&)
|
||||||
|
|
||||||
#ifdef STANDALONE
|
#ifdef STANDALONE
|
||||||
|
|
||||||
|
@ -366,6 +388,7 @@ Object print(Object* params, int length, struct Environment* env)
|
||||||
|
|
||||||
Object numToChar(Object* params, int length, struct Environment* env)
|
Object numToChar(Object* params, int length, struct Environment* env)
|
||||||
{
|
{
|
||||||
|
checkTypes(numToChar)
|
||||||
Object c = params[0];
|
Object c = params[0];
|
||||||
|
|
||||||
if (c.type != TYPE_NUMBER) {
|
if (c.type != TYPE_NUMBER) {
|
||||||
|
@ -396,6 +419,7 @@ Object takeInput(Object* params, int length, struct Environment* env)
|
||||||
|
|
||||||
Object loadFile(Object* params, int length, struct Environment* env)
|
Object loadFile(Object* params, int length, struct Environment* env)
|
||||||
{
|
{
|
||||||
|
checkTypes(loadFile)
|
||||||
Object filename = params[0];
|
Object filename = params[0];
|
||||||
|
|
||||||
if (isStringy(filename)) {
|
if (isStringy(filename)) {
|
||||||
|
@ -407,6 +431,7 @@ Object loadFile(Object* params, int length, struct Environment* env)
|
||||||
|
|
||||||
Object systemCall(Object* params, int length, struct Environment* env)
|
Object systemCall(Object* params, int length, struct Environment* env)
|
||||||
{
|
{
|
||||||
|
checkTypes(systemCall)
|
||||||
Object process = params[0];
|
Object process = params[0];
|
||||||
|
|
||||||
if (isStringy(process)) {
|
if (isStringy(process)) {
|
||||||
|
@ -430,8 +455,9 @@ char* readFileToString(FILE* input)
|
||||||
size_t capacity = 128;
|
size_t capacity = 128;
|
||||||
char* string = malloc(sizeof(char) * capacity);
|
char* string = malloc(sizeof(char) * capacity);
|
||||||
int c;
|
int c;
|
||||||
int i = 0;
|
int i = 1; // Skip refCount
|
||||||
|
|
||||||
|
string[0] = 1; // Set refCount
|
||||||
while ((c = fgetc(input)) != EOF) {
|
while ((c = fgetc(input)) != EOF) {
|
||||||
string[i] = c;
|
string[i] = c;
|
||||||
i++;
|
i++;
|
||||||
|
@ -439,16 +465,19 @@ char* readFileToString(FILE* input)
|
||||||
char* prev = string;
|
char* prev = string;
|
||||||
capacity *= 2;
|
capacity *= 2;
|
||||||
string = malloc(sizeof(char) * capacity);
|
string = malloc(sizeof(char) * capacity);
|
||||||
memcpy(string, prev, sizeof(char) * capacity / 2);
|
string += 1;
|
||||||
|
memcpy(string, prev, sizeof(char) * (capacity / 2));
|
||||||
free(prev);
|
free(prev);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
string[i] = '\0';
|
||||||
|
|
||||||
return string;
|
return string + 1; // Offset past refCount
|
||||||
}
|
}
|
||||||
|
|
||||||
Object readFileToObject(Object* params, int length, struct Environment* env)
|
Object readFileToObject(Object* params, int length, struct Environment* env)
|
||||||
{
|
{
|
||||||
|
checkTypes(readFileToObject)
|
||||||
Object filename = params[0];
|
Object filename = params[0];
|
||||||
|
|
||||||
if (filename.type != TYPE_STRING) {
|
if (filename.type != TYPE_STRING) {
|
||||||
|
|
204
src/plfunc.h
204
src/plfunc.h
|
@ -18,9 +18,9 @@ BASIC_OP(mod);
|
||||||
|
|
||||||
BASIC_OP(equ);
|
BASIC_OP(equ);
|
||||||
|
|
||||||
BASIC_OP(gth);
|
BASIC_OP(greaterThan);
|
||||||
|
|
||||||
BASIC_OP(lth);
|
BASIC_OP(lessThan);
|
||||||
|
|
||||||
BASIC_OP(and);
|
BASIC_OP(and);
|
||||||
|
|
||||||
|
@ -28,119 +28,119 @@ BASIC_OP(or);
|
||||||
|
|
||||||
#undef BASIC_OP
|
#undef BASIC_OP
|
||||||
|
|
||||||
/// ANY => STRING
|
tfn(catObjects,
|
||||||
fn(catObjects,
|
({ NULL, isStringy }),
|
||||||
"Concatenate string versions of the given objects.",
|
"Concatenate string versions of the given objects.",
|
||||||
"(cat \"Stuff: \" (1 2 3))", "Stuff: ( 1 2 3 )",
|
"(cat \"Stuff: \" (1 2 3))", "Stuff: ( 1 2 3 )",
|
||||||
);
|
);
|
||||||
|
|
||||||
/// FUNCY, LIST => LIST
|
tfn(filter,
|
||||||
fn(filter,
|
({ isFuncy, isListy, isListy }),
|
||||||
"Filter a list based on the given condition.",
|
"Filter a list based on the given condition.",
|
||||||
"(fil (fn (a) (< 50 a)) (25 60 100))", "( 60 100 )",
|
"(fil (fn (a) (< 50 a)) (25 60 100))", "( 60 100 )",
|
||||||
"(fil (fn (a) (< 0 (len a))) ( () (1) (1 2) () ))", "( ( 1 ) ( 1 2 ) )",
|
"(fil (fn (a) (< 0 (len a))) ( () (1) (1 2) () ))", "( ( 1 ) ( 1 2 ) )",
|
||||||
);
|
);
|
||||||
|
|
||||||
/// LIST, ANY => LIST
|
tfn(append,
|
||||||
fn(append,
|
({ isListy, NULL, isListy }),
|
||||||
"Append the given element. Creates a new list.",
|
"Append the given element. Creates a new list.",
|
||||||
"(ap (1 2) 3)", "( 1 2 3 )",
|
"(ap (1 2) 3)", "( 1 2 3 )",
|
||||||
);
|
);
|
||||||
|
|
||||||
/// LIST, ANY => LIST
|
tfn(prepend,
|
||||||
fn(prepend,
|
({ isListy, NULL, isListy }),
|
||||||
"Prepend the given element. Creates a new list",
|
"Prepend the given element. Creates a new list",
|
||||||
"(pre (2 3) 1)", "( 1 2 3 )",
|
"(pre (2 3) 1)", "( 1 2 3 )",
|
||||||
);
|
);
|
||||||
|
|
||||||
tfn(len,
|
tfn(len,
|
||||||
({ TYPE_LIST, TYPE_NUMBER }),
|
({ isListy, isNumber }),
|
||||||
"Returns the length of the given list, or a NOT_A_LIST error if the expression is not a list.",
|
"Returns the length of the given list, or a NOT_A_LIST error if the expression is not a list.",
|
||||||
"(len (2 3))", "2",
|
"(len (2 3))", "2",
|
||||||
"(len ())", "0",
|
"(len ())", "0",
|
||||||
"(len \"string\")", "NOT_A_LIST",
|
"(len \"string\")", "BAD_PARAMS_ON: len",
|
||||||
);
|
);
|
||||||
|
|
||||||
/// LIST, FUNCY, ANY => ANY
|
tfn(reduce,
|
||||||
fn(reduce,
|
({ NULL, isFuncy, NULL, NULL }),
|
||||||
"Performs a simple reduction. Does not currently work with lambdas.\n"
|
"Performs a simple reduction. Does not currently work with lambdas.\n"
|
||||||
"Takes three arguments:\n"
|
"Takes three arguments:\n"
|
||||||
" - Values\n"
|
" - Values\n"
|
||||||
" - A function to apply to each value\n"
|
" - A function to apply to each value\n"
|
||||||
" - An initial value",
|
" - An initial value",
|
||||||
"(reduce 5 + 6)", "11",
|
"(reduce 5 + 6)", "11",
|
||||||
"(reduce (1 2 3) + 0)", "6",
|
"(reduce (1 2 3) + 0)", "6",
|
||||||
);
|
);
|
||||||
|
|
||||||
/// NUMBER, LIST => ANY
|
tfn(at,
|
||||||
fn(at,
|
({ isNumber, isListy, NULL }),
|
||||||
"Get item at the given index in the given list.",
|
"Get item at the given index in the given list.",
|
||||||
"(at 1 (1 2 3))", "2",
|
"(at 1 (1 2 3))", "2",
|
||||||
"(at 99 (1 2 3))", "INDEX_PAST_END",
|
"(at 99 (1 2 3))", "INDEX_PAST_END",
|
||||||
"(at 99 \"string\")", "INDEX_PAST_END",
|
"(at 99 \"string\")", "BAD_PARAMS_ON: at",
|
||||||
);
|
);
|
||||||
|
|
||||||
/// LIST => LIST
|
tfn(rest,
|
||||||
fn(rest,
|
({ isListy, isListy }),
|
||||||
"Get the tail of a list. All but the first element.",
|
"Get the tail of a list. All but the first element.",
|
||||||
"(rest (1 2 3))", "( 2 3 )",
|
"(rest (1 2 3))", "( 2 3 )",
|
||||||
"(rest ())", "( )",
|
"(rest ())", "( )",
|
||||||
"(rest \"string\")", "NOT_A_LIST",
|
"(rest \"string\")", "BAD_PARAMS_ON: rest",
|
||||||
);
|
);
|
||||||
|
|
||||||
/// LIST => LIST
|
tfn(reverse,
|
||||||
fn(reverse,
|
({ isListy, isListy }),
|
||||||
"Reverse a list.",
|
"Reverse a list.",
|
||||||
"(rev (1 2 3))", "( 3 2 1 )",
|
"(rev (1 2 3))", "( 3 2 1 )",
|
||||||
"(rev \"string\")", "NOT_A_LIST",
|
"(rev \"string\")", "BAD_PARAMS_ON: reverse",
|
||||||
);
|
);
|
||||||
|
|
||||||
/// ANY => BOOL
|
tfn(isNum,
|
||||||
fn(isNum,
|
({ NULL, isBool }),
|
||||||
"Returns `T` only if the argument evaluates to a number.",
|
"Returns `T` only if the argument evaluates to a number.",
|
||||||
"(isnum 1)", "T",
|
"(isnum 1)", "T",
|
||||||
"(isnum (+ 5 5))", "T",
|
"(isnum (+ 5 5))", "T",
|
||||||
"(isnum '(+ 5 5))", "F",
|
"(isnum '(+ 5 5))", "F",
|
||||||
"(isnum \"Hello\")", "F",
|
"(isnum \"Hello\")", "F",
|
||||||
);
|
);
|
||||||
|
|
||||||
/// ANY => BOOL
|
tfn(isList,
|
||||||
fn(isList,
|
({ NULL, isBool }),
|
||||||
"Returns `T` only if the argument is a list.",
|
"Returns `T` only if the argument is a list.",
|
||||||
"(islist (1 2 3))", "T",
|
"(islist (1 2 3))", "T",
|
||||||
"(islist ())", "T",
|
"(islist ())", "T",
|
||||||
"(islist \"Stringy\")", "F",
|
"(islist \"Stringy\")", "F",
|
||||||
);
|
);
|
||||||
|
|
||||||
/// ANY => BOOL
|
tfn(isString,
|
||||||
fn(isString,
|
({ NULL, isBool }),
|
||||||
"Returns `T` only if the argument is a string.",
|
"Returns `T` only if the argument is a string.",
|
||||||
"(isstr \"Heyo\")", "T",
|
"(isstr \"Heyo\")", "T",
|
||||||
"(isstr \"\")", "T",
|
"(isstr \"\")", "T",
|
||||||
"(isstr (cat 5 5))", "T",
|
"(isstr (cat 5 5))", "T",
|
||||||
"(isstr 10)", "F",
|
"(isstr 10)", "F",
|
||||||
);
|
);
|
||||||
|
|
||||||
/// ANY => BOOL
|
tfn(isErr,
|
||||||
fn(isErr,
|
({ NULL, isBool }),
|
||||||
"Check if the argument is an error.",
|
"Check if the argument is an error.",
|
||||||
"(iserr (at 10 ()))", "T",
|
"(iserr (at 10 ()))", "T",
|
||||||
"(iserr 5)", "F",
|
"(iserr 5)", "F",
|
||||||
);
|
);
|
||||||
|
|
||||||
/// STRING => STRING
|
tfn(charAt,
|
||||||
fn(charAt,
|
({isStringy, isNumber, isStringy}),
|
||||||
"Get the char in the given string at the given index.",
|
"Get the char in the given string at the given index.",
|
||||||
"(chat \"Hello\" 1)", "e",
|
"(chat \"Hello\" 1)", "e",
|
||||||
"(chat \"Hello\" 10)", "",
|
"(chat \"Hello\" 10)", "",
|
||||||
);
|
);
|
||||||
|
|
||||||
/// STRING => NUMBER
|
tfn(charVal,
|
||||||
fn(charVal,
|
({ isStringy, isNumber }),
|
||||||
"Get the ascii integer representaton of the given character.",
|
"Get the ascii integer representaton of the given character.",
|
||||||
"(char \"h\")", "104",
|
"(char \"h\")", "104",
|
||||||
"(char \"hello\")", "104",
|
"(char \"hello\")", "104",
|
||||||
"(char \"\")", "0",
|
"(char \"\")", "0",
|
||||||
);
|
);
|
||||||
|
|
||||||
/// STRING/SLIST => ANY
|
/// STRING/SLIST => ANY
|
||||||
|
@ -161,27 +161,29 @@ fn(possessive,
|
||||||
|
|
||||||
fn(print, "Prints the string representation of the given object to stdout.");
|
fn(print, "Prints the string representation of the given object to stdout.");
|
||||||
|
|
||||||
fn(numToChar,
|
tfn(numToChar,
|
||||||
"Gets a string containing the ascii character for the given number value.",
|
({ isNumber, isStringy }),
|
||||||
"(ch 107)", "k",
|
"Gets a string containing the ascii character for the given number value.",
|
||||||
"(ch 0x21)", "!",
|
"(ch 107)", "k",
|
||||||
|
"(ch 0x21)", "!",
|
||||||
);
|
);
|
||||||
|
|
||||||
fn(printEnvO, "Prints out the current scoped environment.");
|
fn(printEnvO, "Prints out the current scoped environment.");
|
||||||
|
|
||||||
fn(systemCall,
|
tfn(systemCall,
|
||||||
"Opens a shell and runs the given command, returning 0 if successful.\n"
|
({ isStringy, isNumber }),
|
||||||
"If the argument is not a string, returns 255.\n",
|
"Opens a shell and runs the given command, returning 0 if successful.\n"
|
||||||
"(sys \"echo yee > /dev/null\")", "0",
|
"If the argument is not a string, returns 255.\n",
|
||||||
"(sys 5)", "255",
|
"(sys \"echo yee > /dev/null\")", "0",
|
||||||
);
|
);
|
||||||
|
|
||||||
fn(loadFile,
|
tfn(loadFile,
|
||||||
"Loads and parses the given file.\n"
|
({ isStringy, NULL }),
|
||||||
"Returns 0 if the file was loaded and parsed successfully. Otherwise 1.\n"
|
"Loads and parses the given file.\n"
|
||||||
"(loadfile \"printdate.pl\")\n"
|
"Returns 0 if the file was loaded and parsed successfully. Otherwise 1.\n"
|
||||||
"Mon 21 Mar 2022 10:35:03 AM EDT\n"
|
"(loadfile \"printdate.pl\")\n"
|
||||||
"=> 0"
|
"Mon 21 Mar 2022 10:35:03 AM EDT\n"
|
||||||
|
"=> 0"
|
||||||
);
|
);
|
||||||
|
|
||||||
/// @code
|
/// @code
|
||||||
|
@ -201,9 +203,9 @@ fn(help,
|
||||||
"(? \"+\") => \"(+ 1 2) => 3\""
|
"(? \"+\") => \"(+ 1 2) => 3\""
|
||||||
);
|
);
|
||||||
|
|
||||||
/// STRING => STRING
|
tfn(readFileToObject,
|
||||||
fn(readFileToObject,
|
({ isStringy, isStringy }),
|
||||||
"Read a file into a string object."
|
"Read a file into a string object."
|
||||||
);
|
);
|
||||||
|
|
||||||
#endif // STANDALONE
|
#endif // STANDALONE
|
||||||
|
|
15
src/tests.sh
15
src/tests.sh
|
@ -107,6 +107,7 @@ check "ChainDiv" "(/ 1493856 741 96 7)" "3"
|
||||||
|
|
||||||
title "Comparison"
|
title "Comparison"
|
||||||
check "GratrThn" "(> 23847123 19375933)" "T"
|
check "GratrThn" "(> 23847123 19375933)" "T"
|
||||||
|
check "GratrThnMulti" "(> 9999 55 1 0)" "T"
|
||||||
check "LessThan" "(< 23847123 19375933)" "F"
|
check "LessThan" "(< 23847123 19375933)" "F"
|
||||||
check "Equality" "(= 987654321 987654321 )" "T"
|
check "Equality" "(= 987654321 987654321 )" "T"
|
||||||
check "StringEquality" '(= "Bean" "Bean" )' "T"
|
check "StringEquality" '(= "Bean" "Bean" )' "T"
|
||||||
|
@ -215,18 +216,18 @@ check "FuncReturningAFunc" "(def plusser (fn (outer) (fn (inner) (+ outer inner)
|
||||||
(plusFive 10)" "15"
|
(plusFive 10)" "15"
|
||||||
|
|
||||||
title "ShouldError"
|
title "ShouldError"
|
||||||
check "LenOfNotList" "(len 5)" "NOT_A_LIST"
|
check "LenOfNotList" "(len 5)" regex "BAD_PARAMS_ON.*"
|
||||||
check "NoMapList" "(map sq)" "( )"
|
check "NoMapList" "(map sq)" "( )"
|
||||||
check "BadNumber" "(5df)" regex "BAD_NUMBER.*"
|
check "BadNumber" "(5df)" regex "BAD_NUMBER.*"
|
||||||
check "BadHex" "(0x0zf)" regex "BAD_NUMBER.*"
|
check "BadHex" "(0x0zf)" regex "BAD_NUMBER.*"
|
||||||
check "BadBinary" "(0b01120)" regex "BAD_NUMBER.*"
|
check "BadBinary" "(0b01120)" regex "BAD_NUMBER.*"
|
||||||
check "UnsupportedNumber" "(00000)" regex "UNSUPPORTED_NUMBER.*"
|
check "UnsupportedNumber" "(00000)" regex "UNSUPPORTED_NUMBER.*"
|
||||||
check "BadParens1" "(hey()" regex "'MISMATCHED_PARENS.*"
|
check "BadParens1" "(hey()" regex "MISMATCHED_PARENS.*"
|
||||||
check "BadParens2" "(hey)(" regex "'MISMATCHED_PARENS.*"
|
check "BadParens2" "(hey)(" regex "MISMATCHED_PARENS.*"
|
||||||
check "BadParens3" "((hey(" regex "'MISMATCHED_PARENS.*"
|
check "BadParens3" "((hey(" regex "MISMATCHED_PARENS.*"
|
||||||
check "BadParens4" ")))hey" regex "'MISMATCHED_PARENS.*"
|
check "BadParens4" ")))hey" regex "MISMATCHED_PARENS.*"
|
||||||
check "BadParens5" "hey))(" regex "'MISMATCHED_PARENS.*"
|
check "BadParens5" "hey))(" regex "MISMATCHED_PARENS.*"
|
||||||
check "BadParens6" '(ey")"' regex "'MISMATCHED_PARENS.*"
|
check "BadParens6" '(ey")"' regex "MISMATCHED_PARENS.*"
|
||||||
|
|
||||||
title "ListArithmetic" disabled
|
title "ListArithmetic" disabled
|
||||||
check "UnevenLists" "(+ (1 2) (1 2 3))" "LISTS_NOT_SAME_SIZE"
|
check "UnevenLists" "(+ (1 2) (1 2 3))" "LISTS_NOT_SAME_SIZE"
|
||||||
|
|
Loading…
Reference in New Issue