Define function symbols at their declaration.

This commit is contained in:
Sage Vaillancourt 2022-03-29 19:00:01 -04:00
parent 52c928846d
commit 5779ad5427
4 changed files with 71 additions and 69 deletions

View File

@ -215,8 +215,7 @@ struct helpText {
int currentHelp = 0; int currentHelp = 0;
struct helpText helpTexts[100]; struct helpText helpTexts[100];
#define pfn(_func) buildFuncSym(_func ## Symbol, &(_func), _func ## Doc, NULL) #define pf(_func) buildFuncSym(_func ## Symbol, &(_func), _func ## Doc, _func ## Tests, array_length(_func ## Tests))
#define pf(_name, _func) buildFuncSym(_name, &(_func), _func ## Doc, _func ## Tests, array_length(_func ## Tests))
/// For any instances (e.g. segfault recovery) where defaultEnv() may be called /// For any instances (e.g. segfault recovery) where defaultEnv() may be called
/// multiple times. /// multiple times.
@ -282,11 +281,12 @@ void printColored(const char* code)
} }
} }
fn(help, fn(help, "?",
"Gets a string with help text for the given function.\n" "Gets a string with help text for the given function.\n"
"For example:\n" "For example:\n"
" (? islist) => \"(islist (1 2 3)) => T\"" " (? islist) => \"(islist (1 2 3)) => T\""
) { )
{
const char* symbol; const char* symbol;
if (!length || !params[0].string || params[0].string[0] == '\0') { if (!length || !params[0].string || params[0].string[0] == '\0') {
symbol = "?"; symbol = "?";
@ -316,7 +316,9 @@ fn(help,
return nullTerminated("Help not found!"); return nullTerminated("Help not found!");
} }
fnn(segfault, "seg", "Induces a segfault.") fn(segfault, "seg",
"Induces a segfault."
)
{ {
int* p = NULL; int* p = NULL;
return numberObject(*p); return numberObject(*p);
@ -403,41 +405,40 @@ struct Environment defaultEnv()
{"<", &lessThan}, {"<", &lessThan},
{"&", &and}, {"&", &and},
{"|", &or}, {"|", &or},
pf("cat", catObjects), pf(catObjects),
pf("fil", filter), pf(filter),
pf("len", len), pf(len),
pf("ap", append), pf(append),
pf("pre", prepend), pf(prepend),
pf("reduce", reduce), pf(reduce),
pf("at", at), pf(at),
pf("rest", rest), pf(rest),
pf("chat", charAt), pf(charAt),
#ifndef LOW_MEM #ifndef LOW_MEM
pf("rev", reverse), pf(reverse),
#endif #endif
pf("isnum", isNum), pf(isNum),
pf("islist", isList), pf(isList),
pf("isstr", isString), pf(isString),
pf("iserr", isErr), pf(isErr),
pf("char", charVal), pf(charVal),
pf("eval", parseEvalO), pf(parseEvalO),
pf("poss", possessive), pf(possessive),
#ifdef WEBSERVER #ifdef WEBSERVER
pf("get", addGetRoute), pf(addGetRoute),
pf("post", addPostRoute), pf(addPostRoute),
pf("serve", startServer), pf(startServer),
#endif #endif
#ifdef STANDALONE #ifdef STANDALONE
pf("seg", segfault), pf(segfault),
//pfn(segfault), pf(print),
pf("prn", print), pf(numToChar),
pf("ch", numToChar), pf(printEnvO),
pf("penv", printEnvO), pf(systemCall),
pf("sys", systemCall), pf(loadFile),
pf("loadfile", loadFile), pf(takeInput),
pf("inp", takeInput), pf(readFileToObject),
pf("rf", readFileToObject), pf(help)
pf("?", help)
#endif #endif
}; };

View File

@ -12,20 +12,21 @@
#define UNPACK(...) __VA_ARGS__ #define UNPACK(...) __VA_ARGS__
#define fn(_name, _docs, ...) \ #define fnn(_name, _docs, ...) \
static const char * const _name ## Doc = _docs; \ static const char * const _name ## Doc = _docs; \
static const char * const _name ## Tests[] = {__VA_ARGS__}; \ 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 // GCC warns without the attribute, even when typeChecks are used
#define tfn(_name, _type, _docs, ...) \ #define tfn(_name, _symbol, _type, _docs, ...) \
__attribute__((unused)) static int (*_name ## TypeChecks[])(Object) = UNPACK _type; \ __attribute__((unused)) static int (*_name ## TypeChecks[])(Object) = UNPACK _type; \
fn(_name, _docs, __VA_ARGS__)
#define fnn(_name, _symbol, _docs, ...) \
static const char * const _name ## Symbol = _symbol; \ static const char * const _name ## Symbol = _symbol; \
fn(_name, _docs, __VA_ARGS__) fnn(_name, _docs, __VA_ARGS__)
#define fn(_name, _symbol, _docs, ...) \
static const char * const _name ## Symbol = _symbol; \
fnn(_name, _docs, __VA_ARGS__)
struct Slice { struct Slice {
const char* text; const char* text;

View File

@ -28,32 +28,32 @@ BASIC_OP(or);
#undef BASIC_OP #undef BASIC_OP
tfn(catObjects, tfn(catObjects, "cat",
({ NULL, isStringy }), ({ NULL, isStringy }),
"Concatenate string representations of the given objects.", "Concatenate string representations of the given objects.",
"(cat \"Stuff: \" (1 2 3))", "Stuff: ( 1 2 3 )", "(cat \"Stuff: \" (1 2 3))", "Stuff: ( 1 2 3 )",
); );
tfn(filter, tfn(filter, "fil",
({ isFuncy, isListy, isListy }), ({ 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 ) )",
); );
tfn(append, tfn(append, "ap",
({ isListy, NULL, isListy }), ({ 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 )",
); );
tfn(prepend, tfn(prepend, "pre",
({ isListy, NULL, isListy }), ({ 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, "len",
({ isListy, isNumber }), ({ isListy, isNumber }),
"Returns the length of the given list, or an error if the expression is not a list.", "Returns the length of the given list, or an error if the expression is not a list.",
"(len (2 3))", "2", "(len (2 3))", "2",
@ -61,7 +61,7 @@ tfn(len,
"(len \"string\")", "BAD_PARAMS_ON: len", "(len \"string\")", "BAD_PARAMS_ON: len",
); );
tfn(reduce, tfn(reduce, "reduce",
({ NULL, isFuncy, NULL, NULL }), ({ 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"
@ -72,7 +72,7 @@ tfn(reduce,
"(reduce (1 2 3) + 0)", "6", "(reduce (1 2 3) + 0)", "6",
); );
tfn(at, tfn(at, "at",
({ isNumber, isListy, NULL }), ({ 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",
@ -80,7 +80,7 @@ tfn(at,
"(at 99 \"string\")", "BAD_PARAMS_ON: at", "(at 99 \"string\")", "BAD_PARAMS_ON: at",
); );
tfn(rest, tfn(rest, "rest",
({ isListy, isListy }), ({ 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 )",
@ -88,14 +88,14 @@ tfn(rest,
"(rest \"string\")", "BAD_PARAMS_ON: rest", "(rest \"string\")", "BAD_PARAMS_ON: rest",
); );
tfn(reverse, tfn(reverse, "rev",
({ isListy, isListy }), ({ isListy, isListy }),
"Reverse a list.", "Reverse a list.",
"(rev (1 2 3))", "( 3 2 1 )", "(rev (1 2 3))", "( 3 2 1 )",
"(rev \"string\")", "BAD_PARAMS_ON: reverse", "(rev \"string\")", "BAD_PARAMS_ON: reverse",
); );
tfn(isNum, tfn(isNum, "isnum",
({ NULL, isBool }), ({ 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",
@ -104,7 +104,7 @@ tfn(isNum,
"(isnum \"Hello\")", "F", "(isnum \"Hello\")", "F",
); );
tfn(isList, tfn(isList, "islist",
({ NULL, isBool }), ({ 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",
@ -112,7 +112,7 @@ tfn(isList,
"(islist \"Stringy\")", "F", "(islist \"Stringy\")", "F",
); );
tfn(isString, tfn(isString, "isstr",
({ NULL, isBool }), ({ 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",
@ -121,21 +121,21 @@ tfn(isString,
"(isstr 10)", "F", "(isstr 10)", "F",
); );
tfn(isErr, tfn(isErr, "iserr",
({ NULL, isBool }), ({ 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",
); );
tfn(charAt, tfn(charAt, "chat",
({ isStringy, isNumber, isStringy }), ({ 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)", "",
); );
tfn(charVal, tfn(charVal, "char",
({ isStringy, isNumber }), ({ 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",
@ -144,14 +144,14 @@ tfn(charVal,
); );
/// STRING/SLIST => ANY /// STRING/SLIST => ANY
fn(parseEvalO, fn(parseEvalO, "eval",
"Evaluate the given string or quoted list.", "Evaluate the given string or quoted list.",
"(eval \"(1 2 3)\")", "( 1 2 3 )", "(eval \"(1 2 3)\")", "( 1 2 3 )",
"(eval '(+ 5 5))", "10", "(eval '(+ 5 5))", "10",
); );
/// STRUCT, STRING => ANY /// STRUCT, STRING => ANY
fn(possessive, fn(possessive, "poss",
"(struct Post (title body))\n" "(struct Post (title body))\n"
"(def p (Post \"TI\" \"BO\"))\n" "(def p (Post \"TI\" \"BO\"))\n"
"p's title => TI" "p's title => TI"
@ -159,25 +159,25 @@ fn(possessive,
#ifdef STANDALONE #ifdef STANDALONE
fn(print, "Prints the string representation of the given object to stdout."); fn(print, "prn", "Prints the string representation of the given object to stdout.");
tfn(numToChar, tfn(numToChar, "ch",
({ isNumber, isStringy }), ({ isNumber, 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.",
"(ch 107)", "k", "(ch 107)", "k",
"(ch 0x21)", "!", "(ch 0x21)", "!",
); );
fn(printEnvO, "Prints out the current scoped environment."); fn(printEnvO, "penv", "Prints out the current scoped environment.");
tfn(systemCall, tfn(systemCall, "sys",
({ isStringy, isNumber }), ({ isStringy, isNumber }),
"Opens a shell and runs the given command, returning 0 if successful.\n" "Opens a shell and runs the given command, returning 0 if successful.\n"
"If the argument is not a string, returns 255.\n", "If the argument is not a string, returns 255.\n",
"(sys \"echo yee > /dev/null\")", "0", "(sys \"echo yee > /dev/null\")", "0",
); );
tfn(loadFile, tfn(loadFile, "loadfile",
({ isStringy, NULL }), ({ isStringy, NULL }),
"Loads and parses the given file.\n" "Loads and parses the given file.\n"
"Returns 0 if the file was loaded and parsed successfully. Otherwise 1.\n" "Returns 0 if the file was loaded and parsed successfully. Otherwise 1.\n"
@ -189,13 +189,13 @@ tfn(loadFile,
/// @code /// @code
/// () => STRING /// () => STRING
/// STRING => STRING /// STRING => STRING
fn(takeInput, fn(takeInput, "inp",
"Take console input with an optional prompt. For example:\n" "Take console input with an optional prompt. For example:\n"
"`(def x (input))` will wait for user input with no prompt.\n" "`(def x (input))` will wait for user input with no prompt.\n"
"`(def x (input \">> \"))` wait for input, but prompt the user with '>> '.\n" "`(def x (input \">> \"))` wait for input, but prompt the user with '>> '.\n"
); );
tfn(readFileToObject, tfn(readFileToObject, "rf",
({ isStringy, isStringy }), ({ isStringy, isStringy }),
"Read a file into a string object." "Read a file into a string object."
); );

View File

@ -1,13 +1,13 @@
#include "pebblisp.h" #include "pebblisp.h"
fn(startServer, fn(startServer, "serve",
"(serve) => 0\n" "(serve) => 0\n"
"Starts a simple web server with routes that have been added using (get) and (post).\n" "Starts a simple web server with routes that have been added using (get) and (post).\n"
"Returns 0 if the server was successfully started, otherwise 1.\n" "Returns 0 if the server was successfully started, otherwise 1.\n"
"Note: Not a blocking call! Calling (inp) is a simple way to keep the server open.\n" "Note: Not a blocking call! Calling (inp) is a simple way to keep the server open.\n"
); );
fn(addGetRoute, fn(addGetRoute, "get",
"Adds a GET route at the given path with the given function.\n" "Adds a GET route at the given path with the given function.\n"
" (get \"/\" (fn () (\"Hello, world!\")))\n" " (get \"/\" (fn () (\"Hello, world!\")))\n"
" (get \"/parampath\" (fn (req) (req's queryParams)))\n" " (get \"/parampath\" (fn (req) (req's queryParams)))\n"
@ -15,7 +15,7 @@ fn(addGetRoute,
"Also see: (serve)\n" "Also see: (serve)\n"
); );
fn(addPostRoute, fn(addPostRoute, "post",
"Adds a POST route at the given path with the given function.\n" "Adds a POST route at the given path with the given function.\n"
"Note: Can't do anything with POSTed data yet.\n" "Note: Can't do anything with POSTed data yet.\n"
" (post \"/\" (fn () (\"Hello, world!\")))\n" " (post \"/\" (fn () (\"Hello, world!\")))\n"