├── examples ├── hello-world.crumb ├── française.crumb ├── quine.crumb ├── translation.crumb ├── 10-print.crumb ├── factorial.crumb ├── fizzbuzz.crumb ├── geometric-mean.crumb ├── gcd.crumb ├── fibonacci.crumb ├── armstrong.crumb ├── mult-table.crumb ├── collatz.crumb ├── split.crumb ├── pig-latin.crumb ├── car.crumb ├── game-of-life.crumb ├── fish.crumb └── cube.crumb ├── media ├── banner.png ├── icons.fig ├── game-of-life.gif ├── game-of-life.mp4 ├── syntax-diagram.png ├── color-rounded-text.png ├── color-square-text.png ├── banner.svg ├── color-square-text.svg └── color-rounded-text.svg ├── .gitignore ├── src ├── string.h ├── lex.h ├── run.h ├── eval.h ├── stdlib.h ├── events.h ├── parse.h ├── list.h ├── file.h ├── generic.h ├── tokens.h ├── scope.h ├── ast.h ├── string.c ├── tokens.c ├── run.c ├── main.c ├── ast.c ├── events.c ├── scope.c ├── generic.c ├── list.c ├── lex.c ├── file.c ├── eval.c ├── parse.c └── stdlib.c ├── loaf-template └── template.crumb └── README.md /examples/hello-world.crumb: -------------------------------------------------------------------------------- 1 | (print "hello world") -------------------------------------------------------------------------------- /examples/française.crumb: -------------------------------------------------------------------------------- 1 | afficher = print 2 | répéter = loop -------------------------------------------------------------------------------- /examples/quine.crumb: -------------------------------------------------------------------------------- 1 | (print (read_file (get arguments 0)) "\n") -------------------------------------------------------------------------------- /media/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liam-ilan/crumb/HEAD/media/banner.png -------------------------------------------------------------------------------- /media/icons.fig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liam-ilan/crumb/HEAD/media/icons.fig -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | _ignore 3 | _test.crumb 4 | crumb 5 | crumb.dSYM 6 | .DS_Store -------------------------------------------------------------------------------- /media/game-of-life.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liam-ilan/crumb/HEAD/media/game-of-life.gif -------------------------------------------------------------------------------- /media/game-of-life.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liam-ilan/crumb/HEAD/media/game-of-life.mp4 -------------------------------------------------------------------------------- /media/syntax-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liam-ilan/crumb/HEAD/media/syntax-diagram.png -------------------------------------------------------------------------------- /media/color-rounded-text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liam-ilan/crumb/HEAD/media/color-rounded-text.png -------------------------------------------------------------------------------- /media/color-square-text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liam-ilan/crumb/HEAD/media/color-square-text.png -------------------------------------------------------------------------------- /src/string.h: -------------------------------------------------------------------------------- 1 | #ifndef STRING_H 2 | #define STRING_H 3 | 4 | // prototype 5 | char *parseString(char *); 6 | 7 | #endif -------------------------------------------------------------------------------- /examples/translation.crumb: -------------------------------------------------------------------------------- 1 | (use "française.crumb" { 2 | (répéter 10 {_ -> 3 | (afficher "Bonjour monde!\n") 4 | }) 5 | }) -------------------------------------------------------------------------------- /src/lex.h: -------------------------------------------------------------------------------- 1 | #ifndef LEX_H 2 | #define LEX_H 3 | #include "tokens.h" 4 | 5 | // prototype 6 | int lex(Token *, char *, int); 7 | 8 | #endif -------------------------------------------------------------------------------- /src/run.h: -------------------------------------------------------------------------------- 1 | #ifndef RUN_H 2 | #define RUN_H 3 | #include 4 | 5 | // prototypes 6 | int run(char *code, long length, int argc, char *argv[], bool debug); 7 | 8 | #endif -------------------------------------------------------------------------------- /src/eval.h: -------------------------------------------------------------------------------- 1 | #ifndef EVAL_H 2 | #define EVAL_H 3 | #include "generic.h" 4 | #include "ast.h" 5 | #include "scope.h" 6 | 7 | // prototype 8 | Generic *eval(AstNode *p_head, Scope *p_scope, int); 9 | 10 | #endif -------------------------------------------------------------------------------- /examples/10-print.crumb: -------------------------------------------------------------------------------- 1 | // loop forever 2 | (until "stop" {state n -> 3 | 4 | // print random slash 5 | (print (if (integer (add (random) 0.5)) {<- "╱"} {<- "╲"})) 6 | 7 | // wait to slow down animation 8 | (wait 0.001) 9 | }) -------------------------------------------------------------------------------- /examples/factorial.crumb: -------------------------------------------------------------------------------- 1 | factorial = {n -> 2 | <- (if (is n 0) {<- 1} { 3 | <- (multiply n (factorial (subtract n 1))) 4 | }) 5 | } 6 | 7 | (print "Calculate the factorial for: ") 8 | (print (factorial (integer (input))) "\n") -------------------------------------------------------------------------------- /src/stdlib.h: -------------------------------------------------------------------------------- 1 | #ifndef STDLIB_H 2 | #define STDLIB_H 3 | #include "generic.h" 4 | #include "scope.h" 5 | 6 | // prototypes 7 | Scope *newGlobal(int argc, char *argv[]); 8 | Generic *applyFunc(Generic *, Scope *, Generic *[], int, int); 9 | 10 | #endif -------------------------------------------------------------------------------- /examples/fizzbuzz.crumb: -------------------------------------------------------------------------------- 1 | (loop 100 {i -> 2 | i = (add i 1) 3 | 4 | (if (is (remainder i 15) 0) { 5 | (print "fizzbuzz\n") 6 | } (is (remainder i 3) 0) { 7 | (print "fizz\n") 8 | } (is (remainder i 5) 0) { 9 | (print "buzz\n") 10 | } {(print i "\n")} 11 | ) 12 | }) -------------------------------------------------------------------------------- /examples/geometric-mean.crumb: -------------------------------------------------------------------------------- 1 | geometric_mean = {a b -> 2 | <- (power (multiply a b) 0.5) 3 | } 4 | 5 | (print "Input two numbers to find the geometric mean of:\n") 6 | (print "Number 1: ") 7 | a = (float (input)) 8 | (print "Number 2: ") 9 | b = (float (input)) 10 | 11 | (print (geometric_mean a b) "\n") -------------------------------------------------------------------------------- /examples/gcd.crumb: -------------------------------------------------------------------------------- 1 | gcd = {a b -> 2 | <- (if (is a 0) {<- b} { 3 | <- (gcd (remainder b a) a) 4 | }) 5 | } 6 | 7 | (print "Input two numbers to find the gcd.\n") 8 | 9 | (print "Number 1: ") 10 | a = (integer (input)) 11 | 12 | (print "Number 2: ") 13 | b = (integer (input)) 14 | 15 | (print "The result is:" (gcd a b) "\n") -------------------------------------------------------------------------------- /examples/fibonacci.crumb: -------------------------------------------------------------------------------- 1 | // use a simple recursive function to calculate the nth fibonacci number 2 | fibonacci = {n -> 3 | <- (if 4 | (is n 0) {<- 0} 5 | (is n 1) {<- 1} 6 | {<- (add (fibonacci (subtract n 1)) (fibonacci (subtract n 2)))} 7 | ) 8 | } 9 | 10 | (until "stop" {state n -> 11 | (print (add n 1) "-" (fibonacci (add n 1)) "\n") 12 | }) 13 | -------------------------------------------------------------------------------- /examples/armstrong.crumb: -------------------------------------------------------------------------------- 1 | is_🧑‍🚀 = {n -> 2 | digits = (map (range (length (string n))) {_ i -> 3 | <- (integer (get (string n) i)) 4 | }) 5 | 6 | new_n = (reduce (map digits {x _ -> 7 | <- (power x 3) 8 | }) {a b _ -> 9 | <- (add a b) 10 | } 0) 11 | 12 | <- (is n new_n) 13 | } 14 | 15 | (loop 1000 {n -> 16 | (if (is_🧑‍🚀 n) {(print n "\n")}) 17 | }) -------------------------------------------------------------------------------- /examples/mult-table.crumb: -------------------------------------------------------------------------------- 1 | table = (map (range 10) {_ y -> 2 | <- (map (range 10) {item x -> 3 | <- (multiply (add x 1) (add y 1)) 4 | }) 5 | }) 6 | 7 | print_table = {table -> 8 | (map table {row y -> 9 | (print (reduce (get row 1 (length row)) {acc item i -> 10 | <- (join (string acc) " " (string item)) 11 | } (get row 0)) "\n") 12 | }) 13 | } 14 | 15 | (print_table table) -------------------------------------------------------------------------------- /src/events.h: -------------------------------------------------------------------------------- 1 | #ifndef EVENTS_H 2 | #define EVENTS_H 3 | #include 4 | 5 | // prototypes 6 | void initEvents(); 7 | void exitEvents(); 8 | char *event(); 9 | char readChar(); 10 | void disableRaw(); 11 | void enableRaw(); 12 | 13 | // stores original terminal settings 14 | extern struct termios orig_termios; 15 | 16 | // stores terminal settings for while the program is running (echo off) 17 | extern struct termios run_termios; 18 | 19 | 20 | #endif -------------------------------------------------------------------------------- /src/parse.h: -------------------------------------------------------------------------------- 1 | #ifndef PARSE_H 2 | #define PARSE_H 3 | #include "tokens.h" 4 | #include "ast.h" 5 | 6 | // prototypes 7 | void skipClosure(int *, Token **, enum TokenType, enum TokenType, int); 8 | 9 | AstNode *parseValue(Token *, int); 10 | AstNode *parseAssignment(Token *, int); 11 | AstNode *parseStatement(Token *, int); 12 | AstNode *parseApplication(Token *, int); 13 | AstNode *parseReturn(Token *, int); 14 | AstNode *parseFunction(Token *, int); 15 | AstNode *parseProgram(Token *, int); 16 | 17 | #endif -------------------------------------------------------------------------------- /src/list.h: -------------------------------------------------------------------------------- 1 | #ifndef LIST_H 2 | #define LIST_H 3 | #include "generic.h" 4 | 5 | // list container 6 | typedef struct List { 7 | Generic **vals; 8 | int len; 9 | } List; 10 | 11 | // prototypes 12 | void List_print(List *); 13 | List *List_new(Generic **, int); 14 | List *List_copy(List *); 15 | Generic *List_get(List *, int); 16 | List *List_insert(List *, Generic *, int); 17 | List *List_delete(List *, int); 18 | void List_free(List *); 19 | List *List_join(List **, int); 20 | List *List_sublist(List *, int, int); 21 | int List_length(List *); 22 | List *List_set(List *, Generic *, int); 23 | List *List_deleteMultiple(List *, int, int); 24 | int List_compare(List *, List *); 25 | 26 | #endif -------------------------------------------------------------------------------- /src/file.h: -------------------------------------------------------------------------------- 1 | #ifndef FILE_H 2 | #define FILE_H 3 | #define FILE_CACHE_SIZE 1024 4 | #include 5 | 6 | // an individual cached file, both path and contents are heap allocated 7 | typedef struct CachedFile { 8 | char *path; 9 | char *contents; 10 | long fileLength; 11 | } CachedFile; 12 | 13 | // cache of files that were read 14 | typedef struct FileCache { 15 | int index; 16 | bool frozen; 17 | CachedFile cache[FILE_CACHE_SIZE]; 18 | } FileCache; 19 | 20 | // prototypes 21 | char *readFile(char *, bool); 22 | void writeFile(char *, char *, int); 23 | void FileCache_free(); 24 | void FileCache_write(char *path, char *contents, long fileLength); 25 | void FileCache_freeze(); 26 | 27 | #endif -------------------------------------------------------------------------------- /src/generic.h: -------------------------------------------------------------------------------- 1 | #ifndef GENERIC_H 2 | #define GENERIC_H 3 | 4 | // types for generic 5 | enum Type { 6 | TYPE_INT, 7 | TYPE_FLOAT, 8 | TYPE_STRING, 9 | TYPE_VOID, 10 | TYPE_FUNCTION, 11 | TYPE_NATIVEFUNCTION, 12 | TYPE_LIST 13 | }; 14 | 15 | // generic struct 16 | // p_val: a void pointer to the value 17 | // type: the type of *p_val 18 | typedef struct Generic { 19 | enum Type type; 20 | void *p_val; 21 | int refCount; 22 | } Generic; 23 | 24 | // prototypes 25 | char* getTypeString(enum Type); 26 | void Generic_print(Generic *); 27 | Generic *Generic_new(enum Type, void *, int refCount); 28 | void Generic_free(Generic *); 29 | Generic *Generic_copy(Generic *); 30 | int Generic_is(Generic *, Generic *); 31 | #endif -------------------------------------------------------------------------------- /examples/collatz.crumb: -------------------------------------------------------------------------------- 1 | // prints the collatz path for a given input 2 | (print "Input a number to print the collatz sequence for: ") 3 | in = (integer (input)) 4 | 5 | // recursively applys the collatz conjecture function 6 | // returns the stopping time 7 | collatz = {n -> 8 | 9 | // divides 2 if divisible by 2 10 | // multiplies by 3 and adds 1 if not 11 | // applys f again on the new number 12 | res = (if (is (remainder n 2) 0) { 13 | <- (integer (divide n 2)) 14 | } { 15 | <- (add (multiply n 3) 1) 16 | }) 17 | 18 | <- (if (is res 1) { 19 | (print 1 "\n") 20 | <- 1 21 | } { 22 | // recursively apply collatz 23 | (print res "\n") 24 | <- (add (collatz res) 1) 25 | }) 26 | } 27 | 28 | (print "stopping time:" (collatz in) "\n") -------------------------------------------------------------------------------- /examples/split.crumb: -------------------------------------------------------------------------------- 1 | // split a string on a single char separator 2 | split = {in sep -> 3 | // convert to list of chars 4 | arr = (map (range (length in)) {index _ -> <- (get in index)}) 5 | 6 | // reduce list of chars to list of words 7 | <- (if (is sep "") {<- arr} { 8 | <- (reduce arr {acc char _ -> 9 | last_index = (subtract (length acc) 1) 10 | 11 | <- (if (is char sep) { 12 | // add new item to result if we come across a separator 13 | <- (insert acc "") 14 | } { 15 | // else, add char to the last item 16 | <- (set acc (insert (get acc last_index) char) last_index) 17 | }) 18 | } (list "")) 19 | }) 20 | } 21 | 22 | (print "Insert an input to be split on commas: ") 23 | (print (split (input) ",") "\n") 24 | -------------------------------------------------------------------------------- /src/tokens.h: -------------------------------------------------------------------------------- 1 | #ifndef TOKENS_H 2 | #define TOKENS_H 3 | 4 | enum TokenType { 5 | TOK_ASSIGNMENT, 6 | TOK_APPLYOPEN, 7 | TOK_APPLYCLOSE, 8 | TOK_FUNCOPEN, 9 | TOK_FUNCCLOSE, 10 | TOK_COMMA, 11 | TOK_ARROW, 12 | TOK_RETURN, 13 | TOK_IDENTIFIER, 14 | TOK_INT, 15 | TOK_FLOAT, 16 | TOK_STRING, 17 | TOK_END, 18 | TOK_START 19 | }; 20 | 21 | // token 22 | // linked list element containing pointer to next token, type of token, and value 23 | typedef struct Token { 24 | char *val; 25 | enum TokenType type; 26 | struct Token *p_next; 27 | int lineNumber; 28 | } Token; 29 | 30 | // prototypes 31 | char* getTokenTypeString(enum TokenType); 32 | void Token_print(Token *, int); 33 | void Token_push(Token *, char *, enum TokenType, int); 34 | void Token_free(Token *); 35 | 36 | #endif -------------------------------------------------------------------------------- /examples/pig-latin.crumb: -------------------------------------------------------------------------------- 1 | vowels = (list "a" "e" "i" "o" "u") 2 | 3 | // checks if a letter is a vowel 4 | is_vowel = {letter -> 5 | <- (reduce (map vowels {vowel i -> 6 | <- (is letter vowel) 7 | }) {acc item i -> 8 | <- (or acc item) 9 | } 0) 10 | } 11 | 12 | // translate a word to pig latin 13 | translate = {word -> 14 | <- (if (is_vowel (get word 0)) { 15 | // if a word starts with a vowel, simply add "way" 16 | <- (insert word "way") 17 | } { 18 | 19 | // find first vowel 20 | first_vowel = (loop (length word) {i -> 21 | <- (if (is_vowel (get word i)) {<- i}) 22 | }) 23 | 24 | // return translated name 25 | <- (join (delete word 0 first_vowel) (get word 0 first_vowel) "ay") 26 | }) 27 | } 28 | 29 | (print "Input a word to translate: ") 30 | (print (translate (input)) "\n") -------------------------------------------------------------------------------- /src/scope.h: -------------------------------------------------------------------------------- 1 | #ifndef SCOPE_H 2 | #define SCOPE_H 3 | #include "generic.h" 4 | 5 | // a key value pair held in Scope (linked list) 6 | typedef struct ScopeItem { 7 | char* key; 8 | Generic *p_val; 9 | struct ScopeItem *p_next; 10 | } ScopeItem; 11 | 12 | // scope (assigned to every statement) 13 | // every scope knows its parent, so that if a var is not in local scope, parent scope can be accesed 14 | // a scope contains a linked map of var names and values 15 | typedef struct Scope { 16 | struct Scope *p_parent; 17 | ScopeItem *p_head; 18 | } Scope; 19 | 20 | // prototypes 21 | ScopeItem *ScopeItem_new(char*, Generic *); 22 | Scope *Scope_new(Scope *); 23 | void Scope_print(Scope *); 24 | void Scope_set(Scope *, char *, Generic *); 25 | Generic *Scope_get(Scope *, char *, int); 26 | void Scope_free(Scope *); 27 | 28 | #endif -------------------------------------------------------------------------------- /src/ast.h: -------------------------------------------------------------------------------- 1 | #ifndef AST_H 2 | #define AST_H 3 | // types for ast nodes (opcodes) 4 | enum Opcodes { 5 | OP_INT, 6 | OP_FLOAT, 7 | OP_STRING, 8 | OP_IDENTIFIER, 9 | OP_ASSIGNMENT, 10 | OP_RETURN, 11 | OP_STATEMENT, 12 | OP_APPLICATION, 13 | OP_FUNCTION 14 | }; 15 | 16 | // node in ast 17 | // each node is an element in a linked list of it's siblings 18 | // additionally, each node contains a pointer to a "head" child node (may be null) 19 | // each node has an opCode to designate an opperation when the tree is traversed afterwards (string) 20 | // each node has a val (string) 21 | typedef struct AstNode { 22 | struct AstNode *p_headChild; 23 | struct AstNode *p_next; 24 | enum Opcodes opcode; 25 | char *val; 26 | int lineNumber; 27 | } AstNode; 28 | 29 | // prototypes 30 | void AstNode_free(AstNode *); 31 | char* getOpcodeString(enum Opcodes); 32 | void AstNode_print(AstNode *, int); 33 | void AstNode_appendChild(AstNode *, AstNode **, AstNode *); 34 | AstNode* AstNode_new(char*, enum Opcodes, int); 35 | AstNode* AstNode_copy(AstNode *, int); 36 | 37 | #endif -------------------------------------------------------------------------------- /examples/car.crumb: -------------------------------------------------------------------------------- 1 | // returns a single frame of the animation 2 | frame = {n -> 3 | 4 | // message to display 5 | message = " HELLO WORLD " 6 | 7 | // the visible part of the message, as a list 8 | message = (map (range n) {item i -> <- (get message i)}) 9 | 10 | // convert back to string 11 | message = (reduce message {acc item i -> <- (join acc item)} "") 12 | 13 | // add car 14 | <- (join message "ō͡≡o") 15 | } 16 | 17 | // init 18 | // \e[H goes to top left 19 | // \e[2J clears screen 20 | (print "\e[H\e[2J") 21 | 22 | // render loop 23 | (until "" {state n -> 24 | 25 | // calculate frame number 26 | frame_number = (add (remainder n 40) 1) 27 | 28 | // clear when animation loop 29 | (if (is frame_number 1) {(print "\e[H\e[J")}) 30 | 31 | // \e[H goes to top left 32 | (print (join "\e[H" "Frame: " (string frame_number) "\n\n")) 33 | 34 | // get color escape code 35 | color_code = (join "\e[" (string (add 31 (remainder frame_number 7))) "m") 36 | 37 | // print frame 38 | // \e[0m resets color 39 | (print (join color_code (frame frame_number) "\e[0m\n")) 40 | 41 | // print floor 42 | (print "‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n") 43 | 44 | // wait to slow down animation 45 | (wait 0.1) 46 | }) -------------------------------------------------------------------------------- /examples/game-of-life.crumb: -------------------------------------------------------------------------------- 1 | w = (integer (divide (columns) 2)) 2 | h = (subtract (rows) 1) 3 | 4 | world = (map (range h) {_ y -> 5 | <- (map (range w) {item x -> 6 | <- (integer (add (random) 0.5)) 7 | }) 8 | }) 9 | 10 | get_block = {n -> 11 | <- (if (is n 1) {<- "██"} {<- " "}) 12 | } 13 | 14 | render = {world n -> 15 | <- (join "\e[H" (reduce world {acc row y -> 16 | <- (join acc 17 | (reduce row {acc item x -> <- (join acc (get_block item))} "") 18 | "\n") 19 | } "") "Frame: " (string n)) 20 | } 21 | 22 | get_cell = {world x y -> 23 | out_of_bounds = (or 24 | (greater_than x (subtract w 1)) 25 | (less_than x 0) 26 | (greater_than y (subtract h 1)) 27 | (less_than y 0) 28 | ) 29 | 30 | <- (if out_of_bounds {<- 0} {<- (get (get world y) x)}) 31 | } 32 | 33 | count_neighbors = {world x y -> 34 | <- (add 35 | (get_cell world (add x 1) (add y 1)) 36 | (get_cell world (add x 1) y) 37 | (get_cell world (add x 1) (subtract y 1)) 38 | 39 | (get_cell world x (add y 1)) 40 | (get_cell world x (subtract y 1)) 41 | 42 | (get_cell world (subtract x 1) (add y 1)) 43 | (get_cell world (subtract x 1) y) 44 | (get_cell world (subtract x 1) (subtract y 1)) 45 | ) 46 | } 47 | 48 | update_cell = {world x y -> 49 | neighbors = (count_neighbors world x y) 50 | state = (get_cell world x y) 51 | 52 | <- (if (is neighbors 3) {<- 1} 53 | (is neighbors 2) {<- state} 54 | {<- 0} 55 | ) 56 | } 57 | 58 | update_world = {world -> 59 | <- (map world {row y -> 60 | <- (map row {item x -> 61 | <- (update_cell world x y) 62 | }) 63 | }) 64 | } 65 | 66 | (until "stop" {curr_world n -> 67 | (print (render curr_world n)) 68 | res = (update_world curr_world) 69 | <- res 70 | } world) 71 | -------------------------------------------------------------------------------- /src/string.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "string.h" 5 | 6 | // handle escape codes 7 | char *parseString(char *in) { 8 | char *res = (char *) malloc(sizeof(char) * (strlen(in) + 1)); 9 | int length = strlen(in); 10 | 11 | // the number of charechters lost due to escape codes 12 | int lost = 0; 13 | 14 | for (int i = 0; i < length; i++) { 15 | if (in[i] == '\\') { 16 | if (in[i + 1] == 'a') res[i - lost] = '\a'; 17 | else if (in[i + 1] == 'b') res[i - lost] = '\b'; 18 | else if (in[i + 1] == 'f') res[i - lost] = '\f'; 19 | else if (in[i + 1] == 'n') res[i - lost] = '\n'; 20 | else if (in[i + 1] == 'r') res[i - lost] = '\r'; 21 | else if (in[i + 1] == 't') res[i - lost] = '\t'; 22 | else if (in[i + 1] == 'v') res[i - lost] = '\v'; 23 | else if (in[i + 1] == 'e') res[i - lost] = '\e'; 24 | else if (in[i + 1] == '\\') res[i - lost] = '\\'; 25 | else if (in[i + 1] == '\"') res[i - lost] = '\"'; 26 | else if (in[i + 1] == 'x') { 27 | 28 | // if this escape codes leaks out of bounds, treat \x as just x 29 | if (i + 3 > length - 1) res[i - lost] = 'x'; 30 | else { 31 | // get char 32 | char hexStr[3] = {in[i + 2], in[i + 3], '\0'}; 33 | char c = (char) strtol(hexStr, NULL, 16); 34 | 35 | // invalid inputs return 0 (the null char) 36 | // we do not allow null chars in crumb, if we find one, simply ignore the full escape code 37 | if (c != '\0') { 38 | res[i - lost] = c; 39 | } else {lost += 1;} 40 | 41 | // hexadecimal escape codes are two chars longer than other escape codes 42 | lost += 2; 43 | i += 2; 44 | } 45 | } else res[i - lost] = in[i + 1]; 46 | 47 | lost++; 48 | i++; 49 | } else res[i - lost] = in[i]; 50 | } 51 | 52 | res = (char *) realloc(res, strlen(in) + 1 - lost); 53 | res[strlen(in) - lost] = '\0'; 54 | 55 | return res; 56 | } -------------------------------------------------------------------------------- /src/tokens.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "tokens.h" 5 | 6 | // get token type as string, given enum 7 | char* getTokenTypeString(enum TokenType type) { 8 | switch (type) { 9 | case TOK_ASSIGNMENT: return "assignment"; 10 | case TOK_APPLYOPEN: return "applyopen"; 11 | case TOK_APPLYCLOSE: return "applyclose"; 12 | case TOK_FUNCOPEN: return "funcopen"; 13 | case TOK_FUNCCLOSE: return "funcclose"; 14 | case TOK_ARROW: return "arrow"; 15 | case TOK_RETURN: return "return"; 16 | case TOK_IDENTIFIER: return "identifier"; 17 | case TOK_INT: return "int"; 18 | case TOK_FLOAT: return "float"; 19 | case TOK_STRING: return "string"; 20 | case TOK_START: return "start"; 21 | case TOK_END: return "end"; 22 | default: return "unknown"; 23 | } 24 | } 25 | 26 | // print token list 27 | void Token_print(Token *p_head, int length) { 28 | Token *p_curr = p_head; 29 | int i = 0; 30 | while (p_curr != NULL && i < length) { 31 | if (p_curr->val == NULL) printf("%i| %s\n", p_curr->lineNumber, getTokenTypeString(p_curr->type)); 32 | else printf("%i| %s: %s\n", p_curr->lineNumber, getTokenTypeString(p_curr->type), p_curr->val); 33 | 34 | i++; 35 | p_curr = p_curr->p_next; 36 | } 37 | } 38 | 39 | // add token to list 40 | void Token_push(Token *p_head, char *val, enum TokenType type, int lineNumber) { 41 | // loop to end of list 42 | Token *p_curr = p_head; 43 | while (p_curr->p_next != NULL) { 44 | p_curr = p_curr->p_next; 45 | } 46 | 47 | // allocate memory for new token 48 | Token *p_newToken = (Token *)(malloc(sizeof(Token))); 49 | 50 | // add new node to end of list 51 | p_curr->p_next = p_newToken; 52 | 53 | // write data 54 | p_curr->p_next->val = val; 55 | p_curr->p_next->type = type; 56 | p_curr->p_next->p_next = NULL; 57 | p_curr->p_next->lineNumber = lineNumber; 58 | } 59 | 60 | // frees all tokens, and their values 61 | void Token_free(Token *p_head) { 62 | Token *p_curr = p_head; 63 | Token *p_tmp = p_curr; 64 | 65 | while (p_curr != NULL) { 66 | p_tmp = p_curr; 67 | p_curr = p_curr->p_next; 68 | 69 | free(p_tmp->val); 70 | free(p_tmp); 71 | } 72 | } -------------------------------------------------------------------------------- /src/run.c: -------------------------------------------------------------------------------- 1 | #include "run.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "tokens.h" 10 | #include "lex.h" 11 | #include "ast.h" 12 | #include "parse.h" 13 | #include "events.h" 14 | #include "stdlib.h" 15 | #include "eval.h" 16 | #include "file.h" 17 | #include "scope.h" 18 | 19 | void exitHandler() { 20 | exit(0); 21 | } 22 | 23 | int run(char *code, long length, int argc, char *argv[], bool debug) { 24 | if (debug) { 25 | printf("\nTOKENS\n"); 26 | } 27 | 28 | /* lex */ 29 | // create initial token 30 | Token *p_headToken = (Token *) malloc(sizeof(Token)); 31 | p_headToken->lineNumber = 1; 32 | p_headToken->type = TOK_START; 33 | p_headToken->val = NULL; 34 | p_headToken->p_next = NULL; 35 | 36 | int tokenCount = lex(p_headToken, code, length); 37 | 38 | if (debug) { 39 | // print tokens 40 | Token_print(p_headToken, tokenCount); 41 | printf("Token Count: %i\n", tokenCount); 42 | printf("\nAST\n"); 43 | } 44 | 45 | /* parse */ 46 | AstNode *p_headAstNode = parseProgram(p_headToken, tokenCount); 47 | 48 | if (debug) { 49 | // print AST 50 | AstNode_print(p_headAstNode, 0); 51 | printf("\nEVAL\n"); 52 | }; 53 | 54 | /* evaluate */ 55 | initEvents(); 56 | 57 | // cleanly handle exit events 58 | signal(SIGTERM, exitHandler); 59 | signal(SIGINT, exitHandler); 60 | 61 | Scope *p_global = newGlobal(argc, argv); 62 | Generic *res = eval(p_headAstNode, p_global, 0); 63 | 64 | // get exit code 65 | int exitCode = 0; 66 | if (res->type == TYPE_INT) { 67 | exitCode = *((int *) res->p_val); 68 | } 69 | res->refCount = 0; 70 | 71 | /* free */ 72 | if (debug) printf("\nFREE\n"); 73 | 74 | // free tokens 75 | Token_free(p_headToken); 76 | p_headToken = NULL; 77 | if (debug) printf("Tokens Freed\n"); 78 | 79 | // free ast 80 | AstNode_free(p_headAstNode); 81 | p_headAstNode = NULL; 82 | if (debug) printf("AST Freed\n"); 83 | 84 | // free global scope 85 | Scope_free(p_global); 86 | p_global = NULL; 87 | if (debug) printf("Global Scope Freed\n"); 88 | 89 | // free file cache. 90 | FileCache_free(); 91 | if (debug) printf("File Cache Freed\n"); 92 | 93 | // free return code. 94 | Generic_free(res); 95 | res = NULL; 96 | if (debug) printf("Return Code Freed\n"); 97 | 98 | return exitCode; 99 | } -------------------------------------------------------------------------------- /examples/fish.crumb: -------------------------------------------------------------------------------- 1 | // init 2 | // \e[H goes to top left 3 | // \e[2J clears screen 4 | (print "\e[H\e[2J") 5 | 6 | // render loop 7 | // state is a list containing: 1. the coordinates, 2. the color 8 | (until "stop" {state n -> 9 | 10 | // get current event 11 | curr = (event) 12 | 13 | // if event is a mouse event, get coords, else use old coords 14 | coords = (if (greater_than (length curr) 6) { 15 | <- (if (is (get curr 0 5) "\e[<35") { 16 | 17 | // get coords part of ansi code 18 | coords = (get curr 6 (subtract (length curr) 1)) 19 | 20 | // get index of seperator 21 | split_index = (find coords ";") 22 | 23 | // split coords into list 24 | coords = (list 25 | (get coords 0 split_index) 26 | (get coords (add 1 split_index) (length coords)) 27 | ) 28 | 29 | <- coords 30 | } {<- (get state 0)}) 31 | } {<- (get state 0)}) 32 | 33 | // handle color 34 | color = (if (is curr " ") { 35 | <- (add 1 (get state 1)) 36 | } {<- (get state 1)}) 37 | 38 | // construct new state 39 | newState = (list coords color) 40 | 41 | // if there was a change, rerender 42 | (if (not (is state newState)) { 43 | 44 | // clear and print information 45 | (print "\e[2;0H\e[0J\e[H") 46 | instructions = "The Fish will follow the mouse, press space to change the fish." 47 | 48 | // make blank buffer at end of instructions 49 | blank = (range (subtract (multiply (rows) (columns)) (length instructions))) 50 | blank = (reduce 51 | (map blank {item i -> <- " "}) 52 | {acc item i -> <- (join acc item)} 53 | "") 54 | 55 | // print instructions 56 | (print (join instructions blank)) 57 | 58 | // get escape code to move cursor to coordinate 59 | coords_code = (join "\e[" (get coords 1) ";" (get coords 0) "H") 60 | 61 | // get color code 62 | color_code = (join 63 | "\e[" 64 | (string (add 31 (remainder (get newState 1) 7))) 65 | "m" 66 | ) 67 | 68 | // get fish emoji 69 | fish_list = (list "🐠" "🐟" "🐡" "🐕") 70 | fish = (get fish_list (remainder (get newState 1) (length fish_list))) 71 | 72 | // print fish 73 | (print (join coords_code color_code " " fish " NOM NOM NOM" " \e[0m")) 74 | }) 75 | 76 | <- newState 77 | } (list (list "1" "1") 1)) -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "tokens.h" 7 | #include "lex.h" 8 | #include "ast.h" 9 | #include "parse.h" 10 | #include "generic.h" 11 | #include "scope.h" 12 | #include "eval.h" 13 | #include "stdlib.h" 14 | #include "events.h" 15 | #include "file.h" 16 | #include "run.h" 17 | 18 | #define CRUMB_VERSION ("v0.0.4") 19 | 20 | int main(int argc, char *argv[]) { 21 | 22 | // parse version flag 23 | if (argc >= 2 && strcmp(argv[1], "-v") == 0) { 24 | printf("%s\n", CRUMB_VERSION); 25 | return 0; 26 | } 27 | 28 | // parse debug flag 29 | bool debug = argc >= 2 && strcmp(argv[1], "-d") == 0; 30 | 31 | /* read file */ 32 | if (debug) printf("\nCODE\n"); 33 | char *code = NULL; 34 | long fileLength = 0; 35 | bool pipedInput = false; 36 | 37 | // Check if stdin is empty. 38 | fseek(stdin, 0, SEEK_END); 39 | bool stdinEmpty = ftell(stdin) == 0; 40 | rewind(stdin); 41 | 42 | 43 | if ((debug && argc >= 3) || (!debug && argc >= 2)) { 44 | 45 | // if a path was supplied 46 | char *codePath = debug ? argv[2] : argv[1]; 47 | 48 | // if code is passed through an file argument 49 | FILE *p_file = fopen(codePath, "r"); 50 | if (p_file == NULL) { 51 | printf("Error: Could not read file %s.\n", codePath); 52 | return 0; 53 | } 54 | 55 | // go to end, and record position (this will be the length of the file) 56 | fseek(p_file, 0, SEEK_END); 57 | fileLength = ftell(p_file); 58 | 59 | // rewind to start 60 | rewind(p_file); 61 | 62 | // allocate memory (+1 for 0 terminated string) 63 | code = malloc(fileLength + 1); 64 | 65 | // read file and close 66 | fread(code, fileLength, 1, p_file); 67 | fclose(p_file); 68 | 69 | // set terminator to 0 70 | code[fileLength] = '\0'; 71 | 72 | } else if (!stdinEmpty) { 73 | 74 | // if stdin is empty, read it (code was passed in as a pipe) 75 | code = malloc(0); 76 | 77 | // finalIndex tracks the last index populated with a non '\0' char 78 | int finalIndex = 0; 79 | char c = EOF; 80 | 81 | // loop through stdin. 82 | for (finalIndex = 0; (c = getchar()) != EOF; finalIndex += 1) { 83 | code = realloc(code, finalIndex + 1); 84 | code[finalIndex] = c; 85 | } 86 | 87 | // create space for null terminator 88 | code = realloc(code, finalIndex + 2); 89 | fileLength = finalIndex + 1; 90 | code[fileLength] = '\0'; 91 | 92 | pipedInput = true; 93 | } else { 94 | printf("Error: Program not supplied through pipe or argument.\n"); 95 | return 0; 96 | } 97 | 98 | if (debug) { 99 | printf("%s\n", code); 100 | } 101 | 102 | // Calculate the number of arguments to skip (ie. name of executable, file passed). 103 | int argsToSkip = 1 + (pipedInput ? 0 : 1) + (debug ? 1 : 0); 104 | int exitCode = run(code, fileLength, argc - argsToSkip, &argv[argsToSkip], debug); 105 | 106 | // free code 107 | free(code); 108 | code = NULL; 109 | if (debug) printf("Code Freed\n"); 110 | 111 | return exitCode; 112 | } -------------------------------------------------------------------------------- /src/ast.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "ast.h" 5 | 6 | // frees ast 7 | void AstNode_free(AstNode *p_head) { 8 | // for each child, free memory 9 | AstNode *p_curr = p_head->p_headChild; 10 | AstNode *p_tmp = NULL; 11 | 12 | while (p_curr != NULL) { 13 | p_tmp = p_curr; 14 | p_curr = p_curr->p_next; 15 | AstNode_free(p_tmp); 16 | } 17 | 18 | // free self 19 | free(p_head->val); 20 | free(p_head); 21 | } 22 | 23 | // converts opcode to string for printing 24 | char* getOpcodeString(enum Opcodes code) { 25 | switch (code) { 26 | case OP_INT: return "int"; 27 | case OP_FLOAT: return "float"; 28 | case OP_STRING: return "string"; 29 | case OP_IDENTIFIER: return "identifier"; 30 | case OP_ASSIGNMENT: return "assignment"; 31 | case OP_RETURN: return "return"; 32 | case OP_STATEMENT: return "statement"; 33 | case OP_APPLICATION: return "application"; 34 | case OP_FUNCTION: return "function"; 35 | default: return "unknown"; 36 | } 37 | } 38 | 39 | // print ast nicely 40 | void AstNode_print(AstNode *p_head, int depth) { 41 | 42 | // print current node 43 | if (p_head->val == NULL) printf("%i| %s\n", p_head->lineNumber, getOpcodeString(p_head->opcode)); 44 | else printf("%i| %s: %s\n", p_head->lineNumber, getOpcodeString(p_head->opcode), p_head->val); 45 | 46 | // for each child 47 | AstNode *p_curr = p_head->p_headChild; 48 | while (p_curr != NULL) { 49 | 50 | // create appropriate whitespace and dash 51 | for (int x = 0; x < depth + 1; x++) printf(" "); 52 | 53 | // print child 54 | AstNode_print(p_curr, depth + 1); 55 | 56 | p_curr = p_curr->p_next; 57 | } 58 | } 59 | 60 | // appened an item as child to p_head, given a pointer to p_lastChild 61 | // if p_lastChild is null, there are no current children 62 | void AstNode_appendChild(AstNode *p_head, AstNode **p_p_lastChild, AstNode *p_child) { 63 | if (*p_p_lastChild == NULL) { 64 | p_head->p_headChild = p_child; 65 | *p_p_lastChild = p_head->p_headChild; 66 | } else { 67 | (*p_p_lastChild)->p_next = p_child; 68 | *p_p_lastChild = (*p_p_lastChild)->p_next; 69 | } 70 | } 71 | 72 | // allocates memory for a new ast node and populates it 73 | AstNode* AstNode_new(char* val, enum Opcodes opcode, int lineNumber) { 74 | AstNode *res = (AstNode *) malloc(sizeof(AstNode)); 75 | res->opcode = opcode; 76 | res->p_headChild = NULL; 77 | res->p_next = NULL; 78 | res->val = NULL; 79 | 80 | if (val != NULL) { 81 | res->val = (char *) malloc(sizeof(char) * (strlen(val) + 1)); 82 | strcpy(res->val, val); 83 | } 84 | 85 | 86 | res->lineNumber = lineNumber; 87 | return res; 88 | } 89 | 90 | // creats a copy of the astnode, recursively traversing through its children/siblings, and returns it 91 | // depth is the current depth of the copy (start at 0), if 0, do not copy siblings 92 | AstNode* AstNode_copy(AstNode *p_head, int depth) { 93 | if (p_head == NULL) return NULL; 94 | AstNode* p_res = AstNode_new(p_head->val, p_head->opcode, p_head->lineNumber); 95 | p_res->p_headChild = AstNode_copy(p_head->p_headChild, depth + 1); 96 | 97 | if (depth != 0) p_res->p_next = AstNode_copy(p_head->p_next, depth + 1); 98 | else p_res->p_next = NULL; 99 | 100 | return p_res; 101 | } -------------------------------------------------------------------------------- /src/events.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "events.h" 8 | 9 | // stores original terminal settings 10 | struct termios orig_termios; 11 | 12 | // stores terminal settings for while the program is running (echo off) 13 | struct termios run_termios; 14 | 15 | void disableRaw() { 16 | // reset 17 | printf("\e[?1000l"); 18 | fflush(stdout); 19 | 20 | tcsetattr(STDIN_FILENO, TCSAFLUSH, &run_termios); 21 | } 22 | 23 | void enableRaw() { 24 | 25 | // get standard settings 26 | struct termios raw = orig_termios; 27 | 28 | // set flags 29 | raw.c_iflag &= ~(IXON); 30 | raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); 31 | raw.c_oflag &= ~(OPOST); 32 | raw.c_cc[VMIN] = 0; 33 | raw.c_cc[VTIME] = 1; 34 | 35 | // write settings back into terminal 36 | tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw); 37 | 38 | // enable mouse 39 | printf("\e[?1003h\e[?1006h"); 40 | 41 | // flush stdout 42 | fflush(stdout); 43 | } 44 | 45 | // reads a single char from stdin, assuming that raw is enabled 46 | char readChar() { 47 | char c = '\0'; 48 | read(STDIN_FILENO, &c, 1); 49 | 50 | // printf("%c\r\n", c); 51 | // handle exit 52 | if (iscntrl(c) && (c == 26 || c == 3)) exit(0); 53 | return c; 54 | } 55 | 56 | // returns the current event as a string, where the result is malloc 57 | char *event() { 58 | // enable mouse events 59 | enableRaw(); 60 | 61 | // string to return 62 | char *res = malloc(sizeof(char) * 1); 63 | res[0] = '\0'; 64 | 65 | // read first char 66 | char c = readChar(); 67 | 68 | if (c == '\e') { 69 | // escape code case 70 | // increase memory, and add \e 71 | res = realloc(res, sizeof(char) * (strlen(res) + 1 + 2)); 72 | strncat(res, &c, 1); 73 | 74 | // read char again and add to result 75 | c = readChar(); 76 | strncat(res, &c, 1); 77 | 78 | if (c == '[') { 79 | // control sequence introducer case 80 | // read more at https://en.wikipedia.org/wiki/ANSI_escape_code#CSIsection 81 | 82 | // all escape sequences are terminated by a char in the range of 0x40 – 0x7E (64 - 126) 83 | // we loop until we find a terminator, and append to res 84 | do { 85 | res = realloc(res, sizeof(char) * (strlen(res) + 1 + 1)); 86 | c = readChar(); 87 | strncat(res, &c, 1); 88 | } while (!(64 <= c && c <= 126)); 89 | 90 | } else if (c == 'O') { 91 | // \e O is used under certain cases to access function keys 92 | // this escape code accepts the next char, thus this case is needed 93 | res = realloc(res, sizeof(char) * (strlen(res) + 1 + 1)); 94 | c = readChar(); 95 | strncat(res, &c, 1); 96 | } 97 | 98 | } else if (c != '\0') { 99 | // case where just a key was pressed, add memory, insert, and return 100 | res = realloc(res, sizeof(char) * (strlen(res) + 1 + 1)); 101 | strncat(res, &c, 1); 102 | } 103 | 104 | // disable mouse events 105 | disableRaw(); 106 | return res; 107 | } 108 | 109 | void exitEvents() { 110 | tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios); 111 | // \e[?1000l disables mouse events 112 | printf("\e[?1000l\e[?25h"); 113 | } 114 | 115 | void initEvents() { 116 | tcgetattr(STDIN_FILENO, &orig_termios); 117 | run_termios = orig_termios; 118 | run_termios.c_lflag &= ~(ECHO); 119 | atexit(exitEvents); 120 | } -------------------------------------------------------------------------------- /examples/cube.crumb: -------------------------------------------------------------------------------- 1 | // utilities 2 | pi = 3.1415926 3 | 4 | clear = {(print "\e[2J")} 5 | 6 | absolute = {x -> <- (if (less_than x 0) {<- (multiply x -1)} {<- x})} 7 | max = {a b -> <- (if (greater_than a b) {<- a} {<- b})} 8 | 9 | sine = {x -> 10 | in = (subtract pi (remainder x (multiply pi 2))) 11 | <- (add 12 | in 13 | (divide (power in 3) -6) 14 | (divide (power in 5) 120) 15 | (divide (power in 7) -5040) 16 | (divide (power in 7) 362880) 17 | ) 18 | } 19 | cosine = {x -> <- (sine (add x (divide pi 2)))} 20 | 21 | // DDA algorithim 22 | line = {x1 y1 x2 y2 fill color -> 23 | dx = (subtract (float x2) (float x1)) 24 | dy = (subtract (float y2) (float y1)) 25 | 26 | step = (max (absolute dx) (absolute dy)) 27 | 28 | dx = (divide dx step) 29 | dy = (divide dy step) 30 | 31 | (map (range (add (integer step) 1)) {_ i -> 32 | x = (integer (add (multiply i dx) x1)) 33 | y = (integer (add (multiply i dy) y1)) 34 | 35 | move_code = (join "\e[" (string y) ";" (string x) "H") 36 | color_start_code = (join "\e[0;" (string (add color 30)) ";40m") 37 | color_reset_code = "\e[0;0m" 38 | 39 | (print (join move_code color_start_code fill color_reset_code)) 40 | }) 41 | } 42 | 43 | // draw cube frame 44 | cube = {theta char -> 45 | 46 | // center of cube 47 | center_x = (divide (columns) 2) 48 | center_y = (divide (rows) 2) 49 | 50 | // squish factor of base / top 51 | squish = 4 52 | 53 | // distance from base to ceiling 54 | height = (divide (rows) 2.5) 55 | 56 | // "radius" (half of the diagonal of the base) 57 | radius = (multiply height 1.41) 58 | 59 | // find all base coordinates 60 | base = (map (range 4) {_ i -> 61 | <- (list 62 | (add 63 | (multiply (cosine (add theta (multiply i (divide pi 2)))) radius) 64 | center_x 65 | ) 66 | 67 | (add 68 | (multiply (sine (add theta (multiply i (divide pi 2)))) (divide radius squish)) 69 | center_y 70 | (divide height 2) 71 | ) 72 | ) 73 | }) 74 | 75 | // transalate upwards to find all ceiling coordinates 76 | roof = (map base {coords _ -> <- (list (get coords 0) (subtract (get coords 1) height))}) 77 | 78 | // draw base 79 | (map base {coords i -> 80 | (line 81 | (get coords 0) 82 | (get coords 1) 83 | (get (get base (remainder (add i 1) 4)) 0) 84 | (get (get base (remainder (add i 1) 4)) 1) 85 | char (add i 1) 86 | ) 87 | }) 88 | 89 | // draw roof 90 | (map roof {coords i -> 91 | (line 92 | (get coords 0) 93 | (get coords 1) 94 | (get (get roof (remainder (add i 1) 4)) 0) 95 | (get (get roof (remainder (add i 1) 4)) 1) 96 | char (add i 1) 97 | ) 98 | }) 99 | 100 | // connect base and roof 101 | (map base {coords i -> 102 | (line 103 | (get coords 0) 104 | (get coords 1) 105 | (get (get roof i) 0) 106 | (get (get roof i) 1) 107 | char (add i 1) 108 | ) 109 | }) 110 | 111 | } 112 | 113 | // clears a given cube 114 | clear_cube = {theta -> (cube theta "\e[0;0m ")} 115 | 116 | // animation loop 117 | (clear) 118 | (until "stop" {state i -> 119 | 120 | // calculate angle 121 | theta = (divide i 10) 122 | 123 | // draw cube, and then clear 124 | (cube theta "█") 125 | (wait 0.05) 126 | (clear_cube theta) 127 | 128 | // clear screen if width / height changed 129 | <- (if (or (not (is (columns) (get state 0))) (not (is (rows) (get state 1)))) { 130 | (clear) 131 | <- (list (columns) (rows)) 132 | } {<- state}) 133 | 134 | } (list (columns) (rows))) 135 | -------------------------------------------------------------------------------- /media/banner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /media/color-square-text.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /media/color-rounded-text.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/scope.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "scope.h" 5 | #include "generic.h" 6 | 7 | // creates a new scope item, allocates memory, returns pointer 8 | ScopeItem *ScopeItem_new(char* key, Generic *p_val) { 9 | ScopeItem *res = (ScopeItem *) malloc(sizeof(ScopeItem)); 10 | res->key = key; 11 | res->p_val = p_val; 12 | res->p_next = NULL; 13 | 14 | return res; 15 | } 16 | 17 | // creates a new empty scope, allocates memory, and returns a pointer 18 | Scope *Scope_new(Scope *p_parent) { 19 | Scope *res = (Scope *) malloc(sizeof(Scope)); 20 | res->p_parent = p_parent; 21 | res->p_head = NULL; 22 | 23 | return res; 24 | } 25 | 26 | // nicely prints scope, given pointer 27 | void Scope_print(Scope *p_in) { 28 | if (p_in->p_parent == NULL) printf("Global Scope:\n"); 29 | else printf("Local Scope:\n"); 30 | 31 | // for each pair, print 32 | ScopeItem *p_curr = p_in->p_head; 33 | while (p_curr != NULL) { 34 | printf("%s = ", p_curr->key); 35 | Generic_print(p_curr->p_val); 36 | printf("\n"); 37 | p_curr = p_curr->p_next; 38 | } 39 | } 40 | 41 | // gets a pointer to the scope 42 | // sets a key in the scope to val 43 | // if the key does not exist, creates a new scope item to house it 44 | void Scope_set(Scope *p_target, char *key, Generic *p_val) { 45 | p_val->refCount++; 46 | 47 | char *keyCopy = (char *) malloc(sizeof(char) * (strlen(key) + 1)); 48 | strcpy(keyCopy, key); 49 | 50 | // set p_p_curr to the ScopeItem with the correct key, or NULL if not found 51 | ScopeItem **p_p_curr = &(p_target->p_head); 52 | while (*(p_p_curr) != NULL && strcmp((*p_p_curr)->key, keyCopy) != 0) p_p_curr = &((*p_p_curr)->p_next); 53 | 54 | if (*p_p_curr == NULL) { 55 | // case where variable was previously undefined, create new item 56 | *p_p_curr = ScopeItem_new(keyCopy, p_val); 57 | } else { 58 | 59 | // case where variable was previosuly defined, simply overwrite value, and decrease ref count of old value (if 0, free) 60 | (*p_p_curr)->p_val->refCount--; 61 | if ((*p_p_curr)->p_val->refCount == 0) { 62 | Generic_free((*p_p_curr)->p_val); 63 | } 64 | 65 | free((*p_p_curr)->key); 66 | 67 | (*p_p_curr)->p_val = p_val; 68 | (*p_p_curr)->key = keyCopy; 69 | } 70 | } 71 | 72 | // returns the generic in the requested key of the target scope 73 | // if the generic cannot be found, attempts to search parent recursively 74 | Generic *Scope_get(Scope *p_target, char *key, int lineNumber) { 75 | 76 | // set p_curr to the item with correct key, or NULL 77 | ScopeItem *p_curr = p_target->p_head; 78 | while (p_curr != NULL && strcmp(p_curr->key, key) != 0) p_curr = p_curr->p_next; 79 | 80 | if (p_curr == NULL) { 81 | // key does not exist in current scope 82 | // if parent does not exist, we are in the global scope, and can throw an error, else elevate 83 | if (p_target->p_parent == NULL) { 84 | // Error handling 85 | printf( 86 | "Runtime Error @ Line %i: %s is not defined.\n", 87 | lineNumber, key 88 | ); 89 | exit(0); 90 | } else { 91 | return Scope_get(p_target->p_parent, key, lineNumber); 92 | } 93 | } else { 94 | // if key exists, return generic 95 | return p_curr->p_val; 96 | } 97 | } 98 | 99 | // free Scope and ScopeItems from memory 100 | // returns the generic in the requested key of the target scope 101 | // if the generic cannot be found, attempts to search parent recursively 102 | void Scope_free(Scope *p_target) { 103 | // set p_curr to the item with correct key, or NULL 104 | ScopeItem *p_curr = p_target->p_head; 105 | 106 | while (p_curr != NULL) { 107 | ScopeItem *p_tmp = p_curr; 108 | p_curr = p_curr->p_next; 109 | 110 | p_tmp->p_val->refCount--; 111 | if (p_tmp->p_val->refCount == 0) Generic_free(p_tmp->p_val); 112 | 113 | free(p_tmp->key); 114 | free(p_tmp); 115 | } 116 | 117 | free(p_target); 118 | } -------------------------------------------------------------------------------- /loaf-template/template.crumb: -------------------------------------------------------------------------------- 1 | // This crumb file must: 2 | // - Have 0 dependencies. 3 | // - Expose template_main, a function that gets an entry point and list of usables, and returns a templated main.c. 4 | string_to_list = { str -> <- (map (range (length str)) {index _ -> <- (get str index)}) } 5 | list_to_string = { arr -> <- (reduce arr {accum item _ -> <- (join accum (string item))} "")} 6 | 7 | ascii_table = (list 8 | "\x00" "\x01" "\x02" "\x03" "\x04" "\x05" "\x06" "\x07" "\x08" "\x09" "\x0a" "\x0b" "\x0c" "\x0d" "\x0e" "\x0f" 9 | "\x10" "\x11" "\x12" "\x13" "\x14" "\x15" "\x16" "\x17" "\x18" "\x19" "\x1a" "\x1b" "\x1c" "\x1d" "\x1e" "\x1f" 10 | "\x20" "\x21" "\x22" "\x23" "\x24" "\x25" "\x26" "\x27" "\x28" "\x29" "\x2a" "\x2b" "\x2c" "\x2d" "\x2e" "\x2f" 11 | "\x30" "\x31" "\x32" "\x33" "\x34" "\x35" "\x36" "\x37" "\x38" "\x39" "\x3a" "\x3b" "\x3c" "\x3d" "\x3e" "\x3f" 12 | "\x40" "\x41" "\x42" "\x43" "\x44" "\x45" "\x46" "\x47" "\x48" "\x49" "\x4a" "\x4b" "\x4c" "\x4d" "\x4e" "\x4f" 13 | "\x50" "\x51" "\x52" "\x53" "\x54" "\x55" "\x56" "\x57" "\x58" "\x59" "\x5a" "\x5b" "\x5c" "\x5d" "\x5e" "\x5f" 14 | "\x60" "\x61" "\x62" "\x63" "\x64" "\x65" "\x66" "\x67" "\x68" "\x69" "\x6a" "\x6b" "\x6c" "\x6d" "\x6e" "\x6f" 15 | "\x70" "\x71" "\x72" "\x73" "\x74" "\x75" "\x76" "\x77" "\x78" "\x79" "\x7a" "\x7b" "\x7c" "\x7d" "\x7e" "\x7f" 16 | "\x80" "\x81" "\x82" "\x83" "\x84" "\x85" "\x86" "\x87" "\x88" "\x89" "\x8a" "\x8b" "\x8c" "\x8d" "\x8e" "\x8f" 17 | "\x90" "\x91" "\x92" "\x93" "\x94" "\x95" "\x96" "\x97" "\x98" "\x99" "\x9a" "\x9b" "\x9c" "\x9d" "\x9e" "\x9f" 18 | "\xa0" "\xa1" "\xa2" "\xa3" "\xa4" "\xa5" "\xa6" "\xa7" "\xa8" "\xa9" "\xaa" "\xab" "\xac" "\xad" "\xae" "\xaf" 19 | "\xb0" "\xb1" "\xb2" "\xb3" "\xb4" "\xb5" "\xb6" "\xb7" "\xb8" "\xb9" "\xba" "\xbb" "\xbc" "\xbd" "\xbe" "\xbf" 20 | "\xc0" "\xc1" "\xc2" "\xc3" "\xc4" "\xc5" "\xc6" "\xc7" "\xc8" "\xc9" "\xca" "\xcb" "\xcc" "\xcd" "\xce" "\xcf" 21 | "\xd0" "\xd1" "\xd2" "\xd3" "\xd4" "\xd5" "\xd6" "\xd7" "\xd8" "\xd9" "\xda" "\xdb" "\xdc" "\xdd" "\xde" "\xdf" 22 | "\xe0" "\xe1" "\xe2" "\xe3" "\xe4" "\xe5" "\xe6" "\xe7" "\xe8" "\xe9" "\xea" "\xeb" "\xec" "\xed" "\xee" "\xef" 23 | "\xf0" "\xf1" "\xf2" "\xf3" "\xf4" "\xf5" "\xf6" "\xf7" "\xf8" "\xf9" "\xfa" "\xfb" "\xfc" "\xfd" "\xfe" "\xff" 24 | ) 25 | 26 | string_reverse = { str -> 27 | <- (reduce (string_to_list str) { accum item index -> 28 | <- (join item accum) 29 | } "") 30 | } 31 | 32 | dec_to_hex_char = { num -> 33 | <- (get (list "0" "1" "2" "3" "4" "5" "6" "7" "8" "9" "a" "b" "c" "d" "e" "f") num) 34 | } 35 | 36 | base_10_to_other = { val base -> 37 | convert = { val res -> 38 | <- (if (greater_than val (subtract base 1)) { 39 | res = (join res (dec_to_hex_char(remainder val base))) 40 | <- (convert (integer (divide val base)) res) 41 | } { 42 | res = (join res (dec_to_hex_char val) ) 43 | <- res 44 | }) 45 | } 46 | 47 | <- (string_reverse (convert val "")) 48 | } 49 | 50 | base_10_to_16 = { val -> 51 | <- (base_10_to_other val 16) 52 | } 53 | 54 | // Convert char to corresponding \x escape code. 55 | encode_char = {char -> 56 | <- (join "\\x" (base_10_to_16 (find ascii_table char))) 57 | } 58 | 59 | // Convert code to sequence of \x__ escape codes. 60 | encode_str = {str -> 61 | <- (list_to_string (map (string_to_list str) {char _ -> <- (encode_char char)})) 62 | } 63 | 64 | // Split a string on a single char separator. 65 | split = {in sep -> 66 | arr = (string_to_list in) 67 | 68 | // Reduce list of chars to list of words. 69 | <- (if (is sep "") {<- arr} { 70 | <- (reduce arr {acc char _ -> 71 | last_index = (subtract (length acc) 1) 72 | 73 | <- (if (is char sep) { 74 | // Add new item to result if we come across a separator. 75 | <- (insert acc "") 76 | } { 77 | // Else, add char to the last item. 78 | <- (set acc (insert (get acc last_index) char) last_index) 79 | }) 80 | } (list "")) 81 | }) 82 | } 83 | 84 | // Generates main.c for packaged program. 85 | // This function must be exposed. 86 | template_main = {entry used_files -> 87 | code = (read_file entry) 88 | 89 | // Generate a single FileCache_write line for a given file. 90 | template_file_cache_write = {path -> 91 | contents = (read_file path) 92 | <- (join " FileCache_write(\"" path "\", \"" (encode_str contents) "\", " (string (length contents)) ");\n") 93 | } 94 | 95 | file_cache_writes = (list_to_string (map used_files {path _ -> <- (template_file_cache_write path)})) 96 | 97 | // Main function. 98 | <- (join 99 | "#include \"run.h\"\n" 100 | "#include \"file.h\"\n" 101 | "int main(int argc, char *argv[]) {\n" 102 | file_cache_writes 103 | " FileCache_freeze();\n" 104 | " return run(\"" (encode_str code) "\", " (string (length code)) ", argc - 1, &argv[1], false);\n" 105 | "}" 106 | ) 107 | } -------------------------------------------------------------------------------- /src/generic.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "generic.h" 5 | #include "ast.h" 6 | #include "list.h" 7 | 8 | // print generic nicely 9 | void Generic_print(Generic *in) { 10 | if (in->type == TYPE_INT) { 11 | printf("%i", *((int *) in->p_val)); 12 | } else if (in->type == TYPE_FLOAT) { 13 | printf("%f", *((double *) in->p_val)); 14 | } else if (in->type == TYPE_STRING) { 15 | printf("%s", *((char **) in->p_val)); 16 | } else if (in->type == TYPE_VOID) { 17 | printf("[Void]"); 18 | } else if (in->type == TYPE_FUNCTION) { 19 | printf("[Function]"); 20 | } else if (in->type == TYPE_NATIVEFUNCTION) { 21 | printf("[Native Function]"); 22 | } else if (in->type == TYPE_LIST) { 23 | List_print((List *) (in->p_val)); 24 | } 25 | fflush(stdout); 26 | } 27 | 28 | // create a new generic and return 29 | Generic* Generic_new(enum Type type, void *p_val, int refCount) { 30 | Generic *res = malloc(sizeof(Generic)); 31 | res->type = type; 32 | res->p_val = p_val; 33 | res->refCount = refCount; 34 | return res; 35 | } 36 | 37 | // frees p_val of generic 38 | void Generic_free(Generic *target) { 39 | // if string, free contents as well 40 | if (target->type == TYPE_STRING) free(*((char **) target->p_val)); 41 | 42 | if (target->type == TYPE_LIST) { 43 | List_free((List *) (target->p_val)); // use list's own free function 44 | } else if (target->type == TYPE_FUNCTION) { 45 | AstNode_free(target->p_val); // functions are in reality ast nodes, so free them with the appropriate function 46 | } else if (target->type != TYPE_NATIVEFUNCTION) { 47 | // dont free native functions, as their void pointers are not allocated to heap 48 | free(target->p_val); 49 | } 50 | 51 | target->p_val = NULL; 52 | 53 | // free generic itself 54 | free(target); 55 | } 56 | 57 | // returns type as a string given enum 58 | char *getTypeString(enum Type type) { 59 | switch (type) { 60 | case TYPE_FLOAT: return "float"; 61 | case TYPE_INT: return "integer"; 62 | case TYPE_FUNCTION: return "function"; 63 | case TYPE_STRING: return "string"; 64 | case TYPE_VOID: return "void"; 65 | case TYPE_NATIVEFUNCTION: return "native function"; 66 | case TYPE_LIST: return "list"; 67 | default: return "unknown"; 68 | } 69 | } 70 | 71 | Generic *Generic_copy(Generic *target) { 72 | Generic *res = (Generic *) malloc(sizeof(Generic)); 73 | res->type = target->type; 74 | res->refCount = 0; 75 | 76 | if (res->type == TYPE_STRING) { 77 | res->p_val = (char **) malloc(sizeof(char *)); 78 | *((char **) res->p_val) = malloc(sizeof(char) * (strlen(*((char **) target->p_val)) + 1)); 79 | strcpy(*((char **) res->p_val), *((char **) target->p_val)); 80 | } else if (res->type == TYPE_FUNCTION) { 81 | res->p_val = AstNode_copy(target->p_val, 0); 82 | } else if (res->type == TYPE_NATIVEFUNCTION) { 83 | res->p_val = target->p_val; 84 | } else if (res->type == TYPE_VOID) { 85 | res->p_val = NULL; 86 | } else if (res->type == TYPE_INT) { 87 | res->p_val = (int *) malloc(sizeof(int)); 88 | *((int *) res->p_val) = *((int *) target->p_val); 89 | } else if (res->type == TYPE_FLOAT) { 90 | res->p_val = (double *) malloc(sizeof(double)); 91 | *((double *) res->p_val) = *((double *) target->p_val); 92 | } else if (res->type == TYPE_LIST) { 93 | res->p_val = List_copy((List *) target->p_val); 94 | } 95 | 96 | return res; 97 | } 98 | 99 | // returns 1 if a and b are the same, else returns 0 100 | int Generic_is(Generic *a, Generic *b) { 101 | int res = 0; 102 | 103 | // case where we want numerical equality (is 1.0 1) -> 1 104 | if ( 105 | (a->type == TYPE_INT || a->type == TYPE_FLOAT) 106 | && (b->type == TYPE_INT || b->type == TYPE_FLOAT) 107 | ) { 108 | return ( 109 | (a->type == TYPE_FLOAT ? *((double *) a->p_val) : *((int *) a->p_val)) 110 | == (b->type == TYPE_FLOAT ? *((double *) b->p_val) : *((int *) b->p_val)) 111 | ); 112 | } 113 | 114 | // else check types 115 | if (a->type == b->type) { 116 | 117 | // do type conversions and check data 118 | switch (a->type) { 119 | case TYPE_FLOAT: 120 | if (*((double *) a->p_val) == *((double *) b->p_val)) res = 1; 121 | break; 122 | case TYPE_INT: 123 | if (*((int *) a->p_val) == *((int *) b->p_val)) res = 1; 124 | break; 125 | case TYPE_STRING: 126 | if (strcmp(*((char **) a->p_val), *((char **) b->p_val)) == 0) res = 1; 127 | break; 128 | case TYPE_VOID: 129 | res = 1; 130 | break; 131 | case TYPE_NATIVEFUNCTION: 132 | if (a->p_val == b->p_val) res = 1; 133 | break; 134 | case TYPE_FUNCTION: 135 | if (a->p_val == b->p_val) res = 1; 136 | break; 137 | case TYPE_LIST: 138 | res = List_compare((List *) a->p_val, (List *) b->p_val); 139 | } 140 | } 141 | 142 | return res; 143 | } -------------------------------------------------------------------------------- /src/list.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "list.h" 4 | #include "generic.h" 5 | 6 | void List_print(List *p_target) { 7 | printf("[List: "); 8 | 9 | for (int i = 0; i < p_target->len; i += 1) { 10 | Generic_print(p_target->vals[i]); 11 | if (i != p_target->len - 1) printf(", "); 12 | } 13 | 14 | printf("]"); 15 | } 16 | 17 | // copy a given list 18 | List *List_copy(List *p_target) { 19 | return List_new(p_target->vals, p_target->len); 20 | } 21 | 22 | // make a new list struct, given a list of generics 23 | List *List_new(Generic **items, int length) { 24 | List *res = (List *) malloc(sizeof(List)); 25 | res->vals = (Generic **) malloc(sizeof(Generic *) * length); 26 | res->len = length; 27 | 28 | for (int i = 0; i < res->len; i += 1) { 29 | res->vals[i] = Generic_copy(items[i]); 30 | } 31 | 32 | return res; 33 | } 34 | 35 | // get item from list 36 | Generic *List_get(List *p_target, int index) { 37 | // return copy of generic 38 | return Generic_copy(p_target->vals[index]); 39 | } 40 | 41 | // insert item at index 42 | List *List_insert(List *p_target, Generic *p_val, int index) { 43 | List *res = (List *) malloc(sizeof(List)); 44 | res->vals = (Generic **) malloc(sizeof(Generic *) * (p_target->len + 1)); 45 | res->len = p_target->len + 1; 46 | 47 | // resIndex is the index in the resulting list 48 | // targetIndex is the index in the provided list 49 | int targetIndex = 0; 50 | for (int resIndex = 0; resIndex < res->len; resIndex += 1) { 51 | if (resIndex == index) { 52 | res->vals[index] = Generic_copy(p_val); 53 | continue; 54 | } 55 | 56 | res->vals[resIndex] = Generic_copy(p_target->vals[targetIndex]); 57 | targetIndex += 1; 58 | } 59 | 60 | return res; 61 | } 62 | 63 | // delete item from list 64 | List *List_delete(List *p_target, int index) { 65 | List *res = (List *) malloc(sizeof(List)); 66 | res->vals = (Generic **) malloc(sizeof(Generic *) * (p_target->len - 1)); 67 | res->len = p_target->len - 1; 68 | 69 | // resIndex is the index in the resulting list 70 | // targetIndex is the index in the provided list 71 | int resIndex = 0; 72 | for (int targetIndex = 0; targetIndex < p_target->len; targetIndex += 1) { 73 | if (targetIndex == index) continue; 74 | res->vals[resIndex] = Generic_copy(p_target->vals[targetIndex]); 75 | resIndex += 1; 76 | } 77 | 78 | return res; 79 | } 80 | 81 | // free list 82 | void List_free(List *p_target) { 83 | for (int i = 0; i < p_target->len; i += 1) { 84 | Generic_free(p_target->vals[i]); 85 | } 86 | 87 | free(p_target->vals); 88 | free(p_target); 89 | } 90 | 91 | // joins all lists into a single one, and returns 92 | List *List_join(List *lists[], int count) { 93 | List *res = (List *) malloc(sizeof(List)); 94 | res->len = 0; 95 | 96 | for (int i = 0; i < count; i += 1) { 97 | res->len += lists[i]->len; 98 | } 99 | 100 | res->vals = (Generic **) malloc(sizeof(Generic *) * res->len); 101 | 102 | int i = 0; 103 | for (int listIndex = 0; listIndex < count; listIndex += 1) { 104 | for (int itemIndex = 0; itemIndex < lists[listIndex]->len; itemIndex += 1) { 105 | res->vals[i] = Generic_copy(lists[listIndex]->vals[itemIndex]); 106 | i += 1; 107 | } 108 | } 109 | 110 | return res; 111 | } 112 | 113 | // returns the sublist from index1 to index2 114 | List *List_sublist(List *p_target, int index1, int index2) { 115 | List *res = (List *) malloc(sizeof(List)); 116 | res->vals = (Generic **) malloc(sizeof(Generic *) * (index2 - index1)); 117 | res->len = index2 - index1; 118 | 119 | for (int i = index1; i < index2; i += 1) { 120 | res->vals[i - index1] = Generic_copy(p_target->vals[i]); 121 | } 122 | 123 | return res; 124 | } 125 | 126 | // set item in list 127 | List *List_set(List *p_target, Generic *p_val, int index) { 128 | List *res = (List *) malloc(sizeof(List)); 129 | res->vals = (Generic **) malloc(sizeof(Generic *) * p_target->len); 130 | res->len = p_target->len; 131 | 132 | for (int i = 0; i < res->len; i += 1) { 133 | if (i != index) { 134 | res->vals[i] = Generic_copy(p_target->vals[i]); 135 | } else { 136 | res->vals[i] = Generic_copy(p_val); 137 | } 138 | } 139 | 140 | return res; 141 | } 142 | 143 | // get length of list 144 | int List_length(List *p_target) { 145 | return p_target->len; 146 | } 147 | 148 | // delete multiple items from list from index1 to index2 149 | List *List_deleteMultiple(List *p_target, int index1, int index2) { 150 | List *res = (List *) malloc(sizeof(List)); 151 | res->len = p_target->len - index2 + index1; 152 | res->vals = (Generic **) malloc(sizeof(Generic *) * (res->len)); 153 | 154 | for (int i = 0; i < index1; i += 1) { 155 | res->vals[i] = Generic_copy(p_target->vals[i]); 156 | } 157 | 158 | for (int i = index2; i < p_target->len; i += 1) { 159 | res->vals[i - index2 + index1] = Generic_copy(p_target->vals[i]); 160 | } 161 | 162 | return res; 163 | } 164 | 165 | int List_compare(List *p_target1, List *p_target2) { 166 | 167 | // Early check on length. 168 | if (p_target1->len != p_target2->len) return 0; 169 | 170 | for(int i = 0; i < p_target1->len; i += 1) { 171 | if (!Generic_is(p_target1->vals[i], p_target2->vals[i])) return 0; 172 | } 173 | 174 | return 1; 175 | } -------------------------------------------------------------------------------- /src/lex.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "lex.h" 7 | #include "tokens.h" 8 | #include "string.h" 9 | 10 | // handles errors while scanning chars in a string 11 | void handleStringError(char c, int lineNumber) { 12 | if (c == '\n') { 13 | printf("Syntax Error @ Line %i: Unexpected new line before string closed.\n", lineNumber); 14 | exit(0); 15 | } 16 | 17 | if (c == '\0') { 18 | printf("Syntax Error @ Line %i: Unexpected end of file before string closed.\n", lineNumber); 19 | exit(0); 20 | } 21 | } 22 | 23 | // lex code with length fileLength into tokens 24 | int lex(Token *p_headToken, char *code, int fileLength) { 25 | 26 | // token count 27 | int tokenCount = 1; 28 | 29 | // line number 30 | int lineNumber = 1; 31 | 32 | // for each char (including terminator, helps us not need to push number tokens if they are last) 33 | int i = 0; 34 | while (i < fileLength) { 35 | char c = code[i]; 36 | 37 | if (c == '\n') lineNumber++; 38 | 39 | if (c == '/' && code[i + 1] == '/') { 40 | // comments 41 | while (code[i] != '\n' && i < fileLength) i++; 42 | lineNumber++; 43 | } else if (c == '(') { 44 | Token_push(p_headToken, NULL, TOK_APPLYOPEN, lineNumber); 45 | tokenCount++; 46 | } else if (c == ')') { 47 | Token_push(p_headToken, NULL, TOK_APPLYCLOSE, lineNumber); 48 | tokenCount++; 49 | } else if (c == '=') { 50 | Token_push(p_headToken, NULL, TOK_ASSIGNMENT, lineNumber); 51 | tokenCount++; 52 | } else if (c == '{') { 53 | Token_push(p_headToken, NULL, TOK_FUNCOPEN, lineNumber); 54 | tokenCount++; 55 | } else if (c == '}') { 56 | Token_push(p_headToken, NULL, TOK_FUNCCLOSE, lineNumber); 57 | tokenCount++; 58 | } else if (c == '-' && code[i + 1] == '>') { 59 | Token_push(p_headToken, NULL, TOK_ARROW, lineNumber); 60 | i++; 61 | tokenCount++; 62 | } else if (c == '<' && code[i + 1] == '-') { 63 | Token_push(p_headToken, NULL, TOK_RETURN, lineNumber); 64 | i++; 65 | tokenCount++; 66 | } else if (c == '"') { 67 | 68 | // record first char in string 69 | int stringStart = i + 1; 70 | 71 | // go to first char after quotes 72 | i++; 73 | 74 | // count to last char in string (last quote) 75 | while (code[i] != '"') { 76 | 77 | // error handling 78 | handleStringError(code[i], lineNumber); 79 | 80 | // skip escape codes 81 | if (code[i] == '\\') { 82 | i++; 83 | handleStringError(code[i], lineNumber); 84 | } 85 | 86 | i++; 87 | } 88 | 89 | // get substring and add token 90 | char *val = malloc(i - stringStart + 1); 91 | strncpy(val, &code[stringStart], i - stringStart); 92 | val[i - stringStart] = '\0'; 93 | 94 | Token_push(p_headToken, parseString(val), TOK_STRING, lineNumber); 95 | 96 | free(val); 97 | tokenCount++; 98 | 99 | } else if (isdigit((unsigned char) c) > 0 || (c == '-' && isdigit((unsigned char) code[i + 1]) > 0)) { 100 | 101 | // record first char in int 102 | int numStart = i; 103 | 104 | // float flag 105 | bool isFloat = false; 106 | 107 | // increment 108 | i++; 109 | 110 | // increment until char is not a valid number char 111 | while (isdigit((unsigned char) code[i]) > 0 || code[i] == '.') { 112 | 113 | // handle float flag and protect against multiple points 114 | if (code[i] == '.') { 115 | if (isFloat) { 116 | // case were we saw a point before 117 | printf("Syntax Error @ Line %i: Multiple decimal points in single number.\n", lineNumber); 118 | exit(0); 119 | } else isFloat = true; 120 | } 121 | 122 | i++; 123 | } 124 | 125 | // get substring and add token 126 | char *val = malloc(i - numStart + 1); 127 | strncpy(val, &code[numStart], i - numStart); 128 | val[i - numStart] = '\0'; 129 | 130 | if (isFloat) Token_push(p_headToken, val, TOK_FLOAT, lineNumber); 131 | else Token_push(p_headToken, val, TOK_INT, lineNumber); 132 | tokenCount++; 133 | 134 | // make sure to go back to last char of number 135 | i--; 136 | 137 | } else if ( 138 | strchr(" \n\r\t\f\v{}()\"=", c) == NULL 139 | && !(c == '-' && code[i + 1] == '>') 140 | && !(c == '<' && code[i + 1] == '-') 141 | && !(c == '/' && code[i + 1] == '/') 142 | ) { 143 | // case of identifier 144 | int identifierStart = i; 145 | 146 | // while valid identifier char 147 | while ( 148 | strchr(" \n\r\t\f\v{}()\"=", code[i]) == NULL 149 | && !(code[i] == '-' && code[i + 1] == '>') 150 | && !(code[i] == '<' && code[i + 1] == '-') 151 | && !(code[i] == '/' && code[i + 1] == '/') 152 | ) i++; 153 | 154 | // get substring and add token 155 | char *val = malloc(i - identifierStart + 1); 156 | strncpy(val, &code[identifierStart], i - identifierStart); 157 | val[i - identifierStart] = '\0'; 158 | 159 | Token_push(p_headToken, val, TOK_IDENTIFIER, lineNumber); 160 | 161 | tokenCount++; 162 | 163 | // make sure to go back to last char of identifier 164 | i--; 165 | } else if (strchr(" \n\r\t\f\v", code[i]) == NULL) { 166 | // handle unexpected char 167 | printf("Syntax Error @ Line %i: Unexpected char \"%c\".\n", lineNumber, c); 168 | exit(0); 169 | } 170 | 171 | i++; 172 | } 173 | 174 | Token_push(p_headToken, NULL, TOK_END, lineNumber); 175 | tokenCount++; 176 | 177 | return tokenCount; 178 | } -------------------------------------------------------------------------------- /src/file.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "file.h" 6 | 7 | // Normalize a path to be as simple as possible, relative to the current working dir. 8 | // Makes similar files have the same path. 9 | // ie. ./abc/def/../useable.crumb and abc/useable.crumb both become ./abc/useable.crumb. 10 | // Allocates new memory. 11 | char *normalizePath(char *path) { 12 | // Only normalize relative paths 13 | if (path[0] == '~' || path[0] == '/') { 14 | printf( 15 | "Error: Attempted to write %s to the use cache, " 16 | "paths in invocations to the use function must be relative, " 17 | "and the path cannot step out of the working directory.\n", path 18 | ); 19 | exit(0); 20 | } 21 | 22 | // Copy that path for strtok_r. 23 | char *pathCopy = malloc(strlen(path) + 1); 24 | strcpy(pathCopy, path); 25 | 26 | // Keep track of the start for freeing at the end. 27 | char *pathCopyStart = pathCopy; 28 | 29 | // Allocate memory for the result. 30 | // The size of the result will at most be the size of the original path, 31 | // + 2 for the "./" prefix. 32 | char *res = malloc(strlen(path) + 1 + 2); 33 | 34 | // res without the prefix ".". 35 | char *unprefixedRes = &res[1]; 36 | res[0] = '.'; 37 | res[1] = '/'; 38 | 39 | // Index to write to next in unprefixedRes. 40 | int writeIndex = 0; 41 | char* token; 42 | 43 | // Split on "/". 44 | while ((token = strtok_r(pathCopy, "/", &pathCopy))) { 45 | if (strcmp(token, "..") == 0) { 46 | // If "..", backtrack to last "/". 47 | while (unprefixedRes[writeIndex] != '/') { 48 | writeIndex -= 1; 49 | 50 | // If we go too far back, fail. 51 | if (writeIndex < 0) { 52 | printf( 53 | "Error: Attempted to write %s to the use cache, " 54 | "paths in invocations to the use function must be relative, " 55 | "and the path cannot step out of the working directory.\n", path 56 | ); 57 | exit(0); 58 | } 59 | } 60 | 61 | unprefixedRes[writeIndex] = '\0'; 62 | } else if (!(strcmp(token, "") == 0 || strcmp(token, ".") == 0)){ 63 | // Don't act on empty tokens or ".". 64 | // Add "/token". 65 | strcpy(&unprefixedRes[writeIndex], "/"); 66 | writeIndex += 1; 67 | strcpy(&unprefixedRes[writeIndex], token); 68 | writeIndex += strlen(token); 69 | } 70 | } 71 | 72 | free(pathCopyStart); 73 | return res; 74 | } 75 | 76 | 77 | static FileCache fileCache = { 78 | .index = 0, 79 | .frozen = false 80 | }; 81 | 82 | void FileCache_freeze() { 83 | fileCache.frozen = true; 84 | } 85 | 86 | CachedFile *FileCache_read(char *path) { 87 | char *normalizedPath = normalizePath(path); 88 | 89 | for (int i = 0; i < FILE_CACHE_SIZE; i++) { 90 | bool pathExists = fileCache.cache[i].path != NULL; 91 | if (pathExists && strcmp(fileCache.cache[i].path, normalizedPath) == 0) { 92 | free(normalizedPath); 93 | return &(fileCache.cache[i]); 94 | } 95 | } 96 | 97 | free(normalizedPath); 98 | return NULL; 99 | } 100 | 101 | void FileCache_write(char *path, char *contents, long fileLength) { 102 | // free item to write to 103 | if (fileCache.cache[fileCache.index].path != NULL) { 104 | free(fileCache.cache[fileCache.index].path); 105 | } 106 | if (fileCache.cache[fileCache.index].contents != NULL) { 107 | free(fileCache.cache[fileCache.index].contents); 108 | } 109 | 110 | // allocate new content/path to heap 111 | char *newContents = malloc(fileLength + 1); 112 | memcpy(newContents, contents, fileLength + 1); 113 | 114 | char *newPath = normalizePath(path); 115 | 116 | fileCache.cache[fileCache.index].path = newPath; 117 | fileCache.cache[fileCache.index].contents = newContents; 118 | fileCache.cache[fileCache.index].fileLength = fileLength; 119 | 120 | // progress the write index 121 | fileCache.index += 1; 122 | fileCache.index %= FILE_CACHE_SIZE; 123 | } 124 | 125 | void FileCache_free() { 126 | for (int i = 0; i < FILE_CACHE_SIZE; i++) { 127 | if (fileCache.cache[i].path != NULL) { 128 | free(fileCache.cache[i].path); 129 | } 130 | if (fileCache.cache[i].contents != NULL) { 131 | free(fileCache.cache[i].contents); 132 | } 133 | 134 | fileCache.cache[i].path = NULL; 135 | fileCache.cache[i].contents = NULL; 136 | fileCache.cache[i].fileLength = 0; 137 | } 138 | 139 | fileCache.index = 0; 140 | } 141 | 142 | char *readFile(char *path, bool cache) { 143 | if (cache) { 144 | CachedFile *p_cachedFile = FileCache_read(path); 145 | if (p_cachedFile != NULL) { 146 | char *res = malloc(p_cachedFile->fileLength + 1); 147 | memcpy(res, p_cachedFile->contents, p_cachedFile->fileLength + 1); 148 | return res; 149 | } 150 | } 151 | 152 | FILE *p_file = fopen(path, "r"); 153 | 154 | // error handling 155 | if (p_file == NULL) { 156 | return NULL; 157 | } 158 | 159 | // go to end, and record position (this will be the length of the file) 160 | int fseekRes = fseek(p_file, 0, SEEK_END); 161 | if (fseekRes != 0) { 162 | // handle fseek error 163 | return NULL; 164 | } 165 | 166 | long fileLength = ftell(p_file); 167 | 168 | // rewind to start 169 | rewind(p_file); 170 | 171 | // allocate memory (+1 for 0 terminated string) 172 | char *res = malloc(fileLength + 1); 173 | 174 | // read file and close 175 | fread(res, fileLength, 1, p_file); 176 | fclose(p_file); 177 | 178 | // set terminator to 0 and return 179 | res[fileLength] = 0; 180 | 181 | // write to the cache so we do not need to open a new reader next time 182 | if (cache && !fileCache.frozen) { 183 | FileCache_write(path, res, fileLength); 184 | } 185 | 186 | return res; 187 | } 188 | 189 | void writeFile(char *path, char* contents, int lineNumber) { 190 | FILE *p_file = fopen(path, "w+"); 191 | 192 | // check the file succesfully opened 193 | if (p_file == NULL) { 194 | printf( 195 | "Runtime Error @ Line %i: Cannot write file \"%s\".\n", 196 | lineNumber, path 197 | ); 198 | exit(0); 199 | } 200 | 201 | fprintf(p_file, "%s", contents); 202 | fclose(p_file); 203 | } -------------------------------------------------------------------------------- /src/eval.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "ast.h" 5 | #include "generic.h" 6 | #include "scope.h" 7 | 8 | // evaluates an ast in a given scope 9 | Generic *eval(AstNode *p_head, Scope *p_scope, int depth) { 10 | // protect against infinite recursion 11 | if (depth > 20000) { 12 | printf( 13 | "Runtime Error @ Line %i: Exceeded recursion limit.\n", 14 | p_head->lineNumber 15 | ); 16 | exit(0); 17 | } 18 | 19 | if (p_head->opcode == OP_INT) { 20 | // int case 21 | int *p_val = (int *) malloc(sizeof(int)); 22 | *p_val = atoi(p_head->val); 23 | return Generic_new(TYPE_INT, p_val, 0); 24 | 25 | } else if (p_head->opcode == OP_FLOAT) { 26 | // float case 27 | double *p_val = (double *) malloc(sizeof(double)); 28 | *p_val = atof(p_head->val); 29 | return Generic_new(TYPE_FLOAT, p_val, 0); 30 | 31 | } else if (p_head->opcode == OP_STRING) { 32 | // string case 33 | char **p_val = (char **) malloc(sizeof(char *)); 34 | 35 | // create string and copy from ast 36 | char *val = (char *) malloc(sizeof(char) * (strlen(p_head->val) + 1)); 37 | strcpy(val, p_head->val); 38 | *p_val = val; 39 | 40 | return Generic_new(TYPE_STRING, p_val, 0); 41 | 42 | } else if (p_head->opcode == OP_RETURN) { 43 | // simply return the value 44 | Generic *res = eval(p_head->p_headChild, p_scope, depth + 1); 45 | Generic *copy = Generic_copy(res); 46 | if (res->refCount == 0) Generic_free(res); 47 | return copy; 48 | 49 | } else if (p_head->opcode == OP_STATEMENT) { 50 | // statement case 51 | // for each child 52 | AstNode *p_curr = p_head->p_headChild; 53 | while (p_curr != NULL) { 54 | // if return found, return value out of statement, else just eval 55 | if (p_curr->opcode == OP_RETURN) return eval(p_curr, p_scope, depth + 1); 56 | else { 57 | Generic *res = eval(p_curr, p_scope, depth + 1); 58 | if (res->refCount == 0) Generic_free(res); 59 | } 60 | 61 | p_curr = p_curr->p_next; 62 | } 63 | 64 | // if no return found, return void generic 65 | return Generic_new(TYPE_VOID, NULL, 0); 66 | } else if (p_head->opcode == OP_IDENTIFIER) { 67 | // identifier case 68 | // return approriate identifier from scope 69 | return Scope_get(p_scope, p_head->val, p_head->lineNumber); 70 | 71 | } else if (p_head->opcode == OP_ASSIGNMENT) { 72 | // assignment case 73 | Generic *p_val = eval(p_head->p_headChild->p_next, p_scope, depth + 1); 74 | 75 | // set scope and return void 76 | Scope_set(p_scope, p_head->p_headChild->val, p_val); 77 | return Generic_new(TYPE_VOID, NULL, 0); 78 | 79 | } else if (p_head->opcode == OP_FUNCTION) { 80 | // function case 81 | // returns a function generic, whose value is a pointer to the functions ast node 82 | return Generic_new(TYPE_FUNCTION, AstNode_copy(p_head, 0), 0); 83 | 84 | } else if (p_head->opcode == OP_APPLICATION) { 85 | // throw error for empty application 86 | if (p_head->p_headChild == NULL) { 87 | printf( 88 | "Runtime Error @ Line %i: Empty Application.\n", 89 | p_head->lineNumber 90 | ); 91 | 92 | exit(0); 93 | } 94 | 95 | // application case 96 | // get function 97 | Generic *func = eval(p_head->p_headChild, p_scope, depth + 1); 98 | 99 | if (func->type == TYPE_FUNCTION) { 100 | // if function found, create new scope, with current scope as parent 101 | Scope *p_local = Scope_new(p_scope); 102 | 103 | // loop over arguments 104 | AstNode *p_currApplyArg = p_head->p_headChild->p_next; 105 | AstNode *p_currFuncArg = ((AstNode *) func->p_val)->p_headChild; 106 | 107 | // set vars in local scope 108 | while (p_currApplyArg != NULL && p_currFuncArg->opcode != OP_STATEMENT) { 109 | Generic *p_val = eval(p_currApplyArg, p_scope, depth + 1); 110 | Scope_set(p_local, p_currFuncArg->val, p_val); 111 | p_currApplyArg = p_currApplyArg->p_next; 112 | p_currFuncArg = p_currFuncArg->p_next; 113 | } 114 | 115 | // error handling 116 | if (p_currApplyArg != NULL) { 117 | // supplied too many args, throw error 118 | printf( 119 | "Runtime Error @ Line %i: Supplied more arguments than required to function.\n", 120 | p_currApplyArg->lineNumber 121 | ); 122 | exit(0); 123 | } else if (p_currFuncArg->opcode != OP_STATEMENT) { 124 | // supplied too little args, throw error 125 | printf( 126 | "Runtime Error @ Line %i: Supplied less arguments than required to function.\n", 127 | p_head->lineNumber 128 | ); 129 | exit(0); 130 | } 131 | 132 | // now p_currFuncArg points to the statement, so we eval it on the local scope, and return the result 133 | Generic *res = eval(p_currFuncArg, p_local, depth + 1); 134 | 135 | // free local scope 136 | Scope_free(p_local); 137 | 138 | p_local = NULL; 139 | 140 | // free function if no references 141 | if (func->refCount == 0) { 142 | Generic_free(func); 143 | } 144 | 145 | // return 146 | return res; 147 | } else if (func->type == TYPE_NATIVEFUNCTION) { 148 | // native functions contain pointers to c functions 149 | // get function 150 | Generic *(*cb)(Scope *, Generic *[], int, int) = func->p_val; 151 | 152 | Scope *p_local = Scope_new(p_scope); 153 | 154 | // collect arguments 155 | // first round collects length 156 | int count = 0; 157 | AstNode *p_curr = p_head->p_headChild->p_next; 158 | 159 | while (p_curr != NULL) { 160 | count++; 161 | p_curr = p_curr->p_next; 162 | } 163 | 164 | // second round, append to list 165 | Generic *args[count]; 166 | 167 | int i = 0; 168 | p_curr = p_head->p_headChild->p_next; 169 | 170 | while (i < count) { 171 | args[i] = eval(p_curr, p_scope, depth + 1); 172 | args[i]->refCount++; 173 | 174 | i++; 175 | p_curr = p_curr->p_next; 176 | } 177 | 178 | // call and return 179 | Generic *res = (*cb)(p_local, args, count, p_head->lineNumber); 180 | 181 | // drop ref count for args, and free if refCount is 0 182 | for (int i = 0; i < count; i++) { 183 | args[i]->refCount--; 184 | if (args[i]->refCount == 0) Generic_free(args[i]); 185 | } 186 | 187 | // free scope 188 | Scope_free(p_local); 189 | p_local = NULL; 190 | 191 | if (func->refCount == 0) Generic_free(func); 192 | 193 | return res; 194 | 195 | } else { 196 | // if func is not a function type, throw error 197 | printf( 198 | "Runtime Error @ Line %i: Attempted to call %s instead of function.\n", 199 | p_head->lineNumber, getTypeString(func->type) 200 | ); 201 | exit(0); 202 | } 203 | } 204 | 205 | return NULL; 206 | } -------------------------------------------------------------------------------- /src/parse.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "parse.h" 4 | #include "ast.h" 5 | #include "tokens.h" 6 | 7 | // skips closure, such as function or application 8 | // particulary useful for parsing statement 9 | // p_p_curr is the pointer to the pointer to the token containing the open of the closure ("(" or "{") 10 | // open and close are the respective token types designating the open and close types 11 | // p_index is a pointer to the loop itterating over the tokens 12 | // length is the length of the parent expression 13 | void skipClosure(int *p_index, Token **p_p_curr, enum TokenType open, enum TokenType close, int length) { 14 | int depth = 1; 15 | 16 | // until depth == 0 (we are out of the application), loop 17 | while (depth != 0 && *p_index < length) { 18 | *p_p_curr = (*p_p_curr)->p_next; 19 | (*p_index)++; 20 | 21 | // throw bug if we reach the end of a file 22 | if ((*p_p_curr)->type == TOK_END) { 23 | printf( 24 | "Syntax Error @ Line %i: Unexpected %s token.\n", 25 | (*p_p_curr)->lineNumber, getTokenTypeString((*p_p_curr)->type) 26 | ); 27 | exit(0); 28 | } 29 | 30 | if ((*p_p_curr)->type == close) depth--; 31 | else if ((*p_p_curr)->type == open) depth++; 32 | } 33 | } 34 | 35 | // parse application 36 | // ebnf: application = "(", {value}, ")"; 37 | AstNode *parseApplication(Token *p_head, int length) { 38 | if (p_head->type != TOK_APPLYOPEN) { 39 | // error handling for invalid first token 40 | printf( 41 | "Syntax Error @ Line %i: Unexpected %s token.\n", 42 | p_head->lineNumber, getTokenTypeString(p_head->type) 43 | ); 44 | exit(0); 45 | } 46 | 47 | AstNode *res = AstNode_new(NULL, OP_APPLICATION, p_head->lineNumber); 48 | AstNode *p_lastChild = NULL; 49 | 50 | // go to first token of first item item in application 51 | Token *p_curr = p_head->p_next; 52 | int i = 1; 53 | 54 | while(p_curr->type != TOK_APPLYCLOSE && i < length) { 55 | 56 | if (p_curr->type == TOK_APPLYOPEN || p_curr->type == TOK_FUNCOPEN) { 57 | // case of value which is closure 58 | // record first token 59 | Token *p_start = p_curr; 60 | int startIndex = i; 61 | 62 | // skip closure 63 | skipClosure(&i, &p_curr, p_curr->type, p_curr->type + 1, length); 64 | 65 | // add child 66 | AstNode_appendChild(res, &p_lastChild, parseValue(p_start, i - startIndex + 1)); 67 | } else { 68 | // case of value not in closure 69 | AstNode_appendChild(res, &p_lastChild, parseValue(p_curr, 1)); 70 | } 71 | 72 | p_curr = p_curr->p_next; 73 | i++; 74 | } 75 | 76 | if (p_curr->type != TOK_APPLYCLOSE) { 77 | // error handling for invalid closing token 78 | printf( 79 | "Syntax Error @ Line %i: Application not closed.\n", 80 | p_curr->lineNumber 81 | ); 82 | exit(0); 83 | } 84 | 85 | return res; 86 | } 87 | 88 | // parse value 89 | // ebnf: value = application | function | int | float | string | identifier; 90 | AstNode *parseValue(Token *p_head, int length) { 91 | if (length == 1) { 92 | // int, float, or string 93 | if (p_head->type == TOK_STRING) { 94 | return AstNode_new(p_head->val, OP_STRING, p_head->lineNumber); 95 | } else if (p_head->type == TOK_FLOAT) { 96 | return AstNode_new(p_head->val, OP_FLOAT, p_head->lineNumber); 97 | } else if (p_head->type == TOK_INT) { 98 | return AstNode_new(p_head->val, OP_INT, p_head->lineNumber); 99 | } else if (p_head->type == TOK_IDENTIFIER) { 100 | return AstNode_new(p_head->val, OP_IDENTIFIER, p_head->lineNumber); 101 | } else { 102 | printf( 103 | "Syntax Error @ Line %i: Unexpected %s token.\n", 104 | p_head->lineNumber, getTokenTypeString(p_head->type) 105 | ); 106 | exit(0); 107 | } 108 | } else if (length > 1) { 109 | // application and function 110 | if (p_head->type == TOK_APPLYOPEN) { 111 | return parseApplication(p_head, length); 112 | } else if (p_head->type == TOK_FUNCOPEN) { 113 | return parseFunction(p_head, length); 114 | } else { 115 | printf( 116 | "Syntax Error @ Line %i: Unexpected %s token.\n", 117 | p_head->lineNumber, getTokenTypeString(p_head->type) 118 | ); 119 | exit(0); 120 | } 121 | } 122 | 123 | return NULL; 124 | } 125 | 126 | // parse assignment 127 | // ebnf: assignment = identifier, "=", value; 128 | AstNode *parseAssignment(Token *p_head, int length) { 129 | if (length < 3) { 130 | printf( 131 | "Syntax Error @ Line %i: Incomplete assignment.\n", 132 | p_head->lineNumber 133 | ); 134 | exit(0); 135 | 136 | } else if (p_head->type != TOK_IDENTIFIER) { 137 | // error handling if first token not identifier 138 | printf( 139 | "Syntax Error @ Line %i: Unexpected %s token.\n", 140 | p_head->lineNumber, getTokenTypeString(p_head->type) 141 | ); 142 | exit(0); 143 | 144 | } else if (p_head->p_next->type != TOK_ASSIGNMENT) { 145 | // error handling if second token not assignment 146 | printf( 147 | "Syntax Error @ Line %i: Unexpected %s token.\n", 148 | p_head->p_next->lineNumber, getTokenTypeString(p_head->p_next->type) 149 | ); 150 | exit(0); 151 | 152 | } else { 153 | // create node and return 154 | AstNode *res = AstNode_new(NULL, OP_ASSIGNMENT, p_head->lineNumber); 155 | res->p_headChild = AstNode_new(p_head->val, OP_IDENTIFIER, p_head->lineNumber); 156 | res->p_headChild->p_next = parseValue(p_head->p_next->p_next, length - 2); 157 | 158 | return res; 159 | } 160 | } 161 | 162 | // parse return 163 | // ebnf: return = "<-", value; 164 | AstNode *parseReturn(Token *p_head, int length) { 165 | if (length < 2) { 166 | printf( 167 | "Syntax Error @ Line %i: Incomplete return.\n", 168 | p_head->lineNumber 169 | ); 170 | exit(0); 171 | 172 | } else if (p_head->type != TOK_RETURN) { 173 | printf( 174 | "Syntax Error @ Line %i: Unexpected %s token.\n", 175 | p_head->lineNumber, getTokenTypeString(p_head->type) 176 | ); 177 | exit(0); 178 | 179 | } else { 180 | AstNode *res = AstNode_new(NULL, OP_RETURN, p_head->lineNumber); 181 | res->p_headChild = parseValue(p_head->p_next, length - 1); 182 | return res; 183 | } 184 | } 185 | 186 | // parse statement 187 | // ebnf: statement = {return | assignment | value}; 188 | // precedence: return, assignment, value 189 | AstNode *parseStatement(Token *p_head, int length) { 190 | 191 | // create ast node for statement 192 | AstNode *res = AstNode_new(NULL, OP_STATEMENT, p_head->lineNumber); 193 | AstNode *p_lastChild = res->p_headChild; 194 | 195 | // for each token 196 | Token *p_curr = p_head; 197 | int i = 0; 198 | 199 | while (p_curr != NULL && i < length) { 200 | if (p_curr->type == TOK_RETURN) { 201 | // return case 202 | // first token of return 203 | Token *p_returnStart = p_curr; 204 | int returnIndex = i; 205 | 206 | if (p_curr->p_next->type == TOK_APPLYOPEN || p_curr->p_next->type == TOK_FUNCOPEN) { 207 | 208 | // go to open apply token 209 | p_curr = p_curr->p_next; 210 | i++; 211 | 212 | // skip closure 213 | // we pass the type + 1 as the close type, as closing types are 1 + the respective opening type 214 | skipClosure(&i, &p_curr, p_curr->type, p_curr->type + 1, length); 215 | 216 | // add parsed return expression to result 217 | AstNode_appendChild(res, &p_lastChild, parseReturn(p_returnStart, i - returnIndex + 1)); 218 | 219 | } else { 220 | // parse return with next token 221 | AstNode_appendChild(res, &p_lastChild, parseReturn(p_returnStart, 2)); 222 | 223 | // increment 224 | i++; 225 | p_curr = p_curr->p_next; 226 | } 227 | } else if (p_curr->type == TOK_IDENTIFIER && p_curr->p_next->type == TOK_ASSIGNMENT) { 228 | // assignment case 229 | // first token of assignment 230 | Token *p_assignmentStart = p_curr; 231 | int assignmentIndex = i; 232 | 233 | // increment step by 1 234 | p_curr = p_curr->p_next; 235 | i++; 236 | 237 | // if closure 238 | if (p_curr->p_next->type == TOK_APPLYOPEN || p_curr->p_next->type == TOK_FUNCOPEN) { 239 | 240 | // go to open apply token 241 | p_curr = p_curr->p_next; 242 | i++; 243 | 244 | // skip closure 245 | // we pass the type + 1 as the close type, as closing types are 1 + the respective opening type 246 | skipClosure(&i, &p_curr, p_curr->type, p_curr->type + 1, length); 247 | 248 | // add parsed return expression to result 249 | AstNode_appendChild(res, &p_lastChild, parseAssignment(p_assignmentStart, i - assignmentIndex + 1)); 250 | 251 | } else { 252 | // do assignment with next token 253 | AstNode_appendChild(res, &p_lastChild, parseAssignment(p_assignmentStart, 3)); 254 | 255 | // increment 256 | i++; 257 | p_curr = p_curr->p_next; 258 | } 259 | } else if (p_curr->type == TOK_APPLYOPEN || p_curr->type == TOK_FUNCOPEN) { 260 | // case of value which is closure 261 | // record first token 262 | Token *p_start = p_curr; 263 | int startIndex = i; 264 | 265 | // skip closure 266 | skipClosure(&i, &p_curr, p_curr->type, p_curr->type + 1, length); 267 | 268 | // add child 269 | AstNode_appendChild(res, &p_lastChild, parseValue(p_start, i - startIndex + 1)); 270 | } else { 271 | // case of value not in closure 272 | AstNode_appendChild(res, &p_lastChild, parseValue(p_curr, 1)); 273 | } 274 | 275 | i++; 276 | p_curr = p_curr->p_next; 277 | } 278 | 279 | return res; 280 | } 281 | 282 | // parse function 283 | // ebnf: function = "{", [{identifier, ","}, identifier, "->"], statement, "}"; 284 | AstNode *parseFunction(Token *p_head, int length) { 285 | 286 | // if first token is not {, return error 287 | if (p_head->type != TOK_FUNCOPEN) { 288 | printf( 289 | "Syntax Error @ Line %i: Unexpected %s token.\n", 290 | p_head->lineNumber, getTokenTypeString(p_head->type) 291 | ); 292 | 293 | exit(0); 294 | } 295 | 296 | // create res 297 | AstNode *res = AstNode_new(NULL, OP_FUNCTION, p_head->lineNumber); 298 | AstNode *p_lastChild = NULL; 299 | 300 | // skip to end of function or arrow 301 | Token *p_curr = p_head; 302 | int i = 0; 303 | 304 | while (p_curr->type != TOK_ARROW && i < length - 1) { 305 | p_curr = p_curr->p_next; 306 | if (p_curr->type == TOK_FUNCOPEN) skipClosure(&i, &p_curr, TOK_FUNCOPEN, TOK_FUNCCLOSE, length); 307 | i++; 308 | } 309 | 310 | if (p_curr->type == TOK_FUNCCLOSE) { 311 | // case where there are no arguments 312 | res->p_headChild = parseStatement(p_head->p_next, length - 2); 313 | } else if (p_curr->type == TOK_ARROW) { 314 | // case where there are arguments 315 | // go to first argument 316 | Token *p_curr = p_head->p_next; 317 | 318 | while (p_curr->type != TOK_ARROW) { 319 | if (p_curr->type != TOK_IDENTIFIER) { 320 | // error handling in case identifier not found 321 | printf( 322 | "Syntax Error @ Line %i: Unexpected %s token.\n", 323 | p_curr->lineNumber, getTokenTypeString(p_curr->type) 324 | ); 325 | 326 | exit(0); 327 | } 328 | 329 | // add identifier 330 | AstNode_appendChild(res, &p_lastChild, AstNode_new(p_curr->val, OP_IDENTIFIER, p_curr->lineNumber)); 331 | p_curr = p_curr->p_next; 332 | } 333 | 334 | // add statement 335 | AstNode_appendChild(res, &p_lastChild, parseStatement(p_curr->p_next, length - i - 2)); 336 | 337 | } else { 338 | printf( 339 | "Syntax Error @ Line %i: Unexpected %s token.\n", 340 | p_curr->lineNumber, getTokenTypeString(p_curr->type) 341 | ); 342 | 343 | exit(0); 344 | } 345 | 346 | return res; 347 | } 348 | 349 | // parse program 350 | // ebnf: program = start, statement, end; 351 | AstNode *parseProgram(Token *p_head, int length) { 352 | if (p_head->type != TOK_START) { 353 | printf("Syntax Error @ Line 1: Missing start token.\n"); 354 | exit(0); 355 | } 356 | 357 | Token *p_curr = p_head; 358 | for (int i = 0; i < length - 1; i++) p_curr = p_curr->p_next; 359 | 360 | if(p_curr->type != TOK_END) { 361 | printf("Syntax Error @ Line %i: Missing end token.\n", p_curr->lineNumber); 362 | exit(0); 363 | } 364 | 365 | return parseStatement(p_head->p_next, length - 2); 366 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

crumb icon

2 | 3 | # The Crumb Programming Language 4 | Crumb is a high level, functional, interpreted, dynamically typed, general-purpose programming language, with a terse syntax, and a verbose standard library. 5 | 6 | It features: 7 | - Strictly __no side effects__* to help you write functional code 8 | - The ability to __localize the effects of imported Crumb files__. 9 | - __Dynamic typing__ and __garbage collection__. 10 | - 0 keywords, __everything is a function__. 11 | > *With the exception of IO 12 | 13 | Click here to [Get Started](#getting-started). 14 | 15 | --- 16 | 17 | ``` 18 | table = (map (range 10) {_ y -> 19 | <- (map (range 10) {item x -> 20 | <- (multiply (add x 1) (add y 1)) 21 | }) 22 | }) 23 | ``` 24 | *From [`examples/mult-table.crumb`](./examples/mult-table.crumb)* 25 | 26 | --- 27 | 28 | ![Game of Life in Crumb](./media/game-of-life.gif) 29 | *From [`examples/game-of-life.crumb`](./examples/game-of-life.crumb)* 30 | 31 | --- 32 | 33 | Find more examples under the [`examples`](./examples/) directory. 34 | 35 | Crumb can also build standalone binaries through [Loaf](https://github.com/liam-ilan/loaf). 36 | 37 | ## Getting Started 38 | ### Install 39 | **You do not need to clone this repo.** Instead, follow the instructions in [this template repo](https://github.com/liam-ilan/crumb-template). 40 | 41 | If you are on VSCode, you can install the [Crumb syntax highlighter extension](https://marketplace.visualstudio.com/items?itemName=liamilan.crumb). The source for the extension can be found [here](https://github.com/liam-ilan/crumb-vscode). 42 | 43 | ### Basics 44 | All function calls are done with s-expressions (think lisp). For example, 45 | ``` 46 | (print "hello world") 47 | ``` 48 | 49 | In this case, the function `print` is applied with the `string` `"hello world"` as an argument. 50 | 51 | All data in crumb is one of 6 different types: 52 | 1. `string` 53 | 2. `integer` 54 | 3. `float` 55 | 4. `function` / `native function` 56 | 5. `list` 57 | 6. `void` 58 | 59 | We can store this data in variables, for example, 60 | ``` 61 | a = 5 62 | b = "hello" 63 | ``` 64 | 65 | We can combine data together to form lists, 66 | ``` 67 | magic_list = (list 123 "hello" 42.0) 68 | ``` 69 | Lists are always passed by value. 70 | 71 | We can encapsulate code in functions using curly braces, 72 | ``` 73 | f = { 74 | (print "Funky!") 75 | } 76 | 77 | (f) // prints "Funky" 78 | ``` 79 | 80 | Functions can get arguments, denoted using the "->" symbol. For example, 81 | ``` 82 | add_two_things = {a b -> 83 | (print (add a b)) 84 | } 85 | 86 | (add_two_things 3 5) // prints 8 87 | ``` 88 | 89 | They can also return values using the "<-" symbol, 90 | ``` 91 | geometric_mean = {a b -> 92 | <- (power (multiply a b) 0.5) 93 | } 94 | 95 | (print (geometric_mean 3 5) "\n") // prints 3.87... 96 | ``` 97 | 98 | Functions operate in a few important ways: 99 | 1. Function applications are *dynamically scoped*. 100 | 2. Functions *cannot create side effects*. 101 | 3. Like in JavaScript and Python, *all functions are first-class*. 102 | 103 | Most of the features you may expect in a programming language are implemented in the form of functions. For example, here is a Fizzbuzz program using the `add`, `loop`, `if`, `remainder`, `is`, and `print` functions, 104 | 105 | ``` 106 | (loop 100 {i -> 107 | i = (add i 1) 108 | 109 | (if (is (remainder i 15) 0) { 110 | (print "fizzbuzz\n") 111 | } (is (remainder i 3) 0) { 112 | (print "fizz\n") 113 | } (is (remainder i 5) 0) { 114 | (print "buzz\n") 115 | } {(print i "\n")} 116 | ) 117 | }) 118 | ``` 119 | *From [`examples/fizzbuzz.crumb`](./examples/fizzbuzz.crumb)* 120 | 121 | You should now be ready to write your own Crumb programs! More info on how to build applications with events, files, code-splitting, and more, is found in the standard library documentation below. 122 | 123 | ## Standard Library 124 | ### IO 125 | - `arguments` 126 | - A list command line arguments, like argv in C. 127 | - Will skip all arguments up to and including the path to the crumb program. 128 | 129 | - `(print arg1 arg2 arg3 ...)` 130 | - Prints all arguments to stdout, returns nothing. 131 | 132 | - `(input)` 133 | - Gets a line of input from stdin. 134 | 135 | - `(rows)` 136 | - Returns the number of rows in the terminal. 137 | 138 | - `(columns)` 139 | - Returns the number of columns in the terminal. 140 | 141 | - `(read_file path)` 142 | - Returns the contents of the file designated by `path`, in a string. If the file cannot be read, returns void. 143 | - `path`: `string` 144 | 145 | - `(write_file path contents)` 146 | - Writes the string `contents` into the file designated by `path`, returns nothing. 147 | - `path`: `string` 148 | - `contents`: `string` 149 | 150 | - `(event time)` or `(event)` 151 | - Returns the ANSI string corresponding with the current event. This may block for up to `time` seconds, rounded up to the nearest 100 ms. If no `time` is supplied, the function will not return before receiving an event. 152 | - `time`: `integer` or `float` 153 | 154 | - `(use path1 path2 path3 ... fn)` 155 | - Crumb's code splitting method. Runs code in file paths, in order, on a new scope. Then uses said scope to apply `fn`. 156 | - `path1`, `path2`, `path3`, ...: `string` 157 | - `fn`: `function` 158 | 159 | - `(shell command)` 160 | - Runs `command` as an sh program in a seperate process, and returns stdout of the process as a `string`. 161 | - `command`: `string` 162 | 163 | ### Comparisons 164 | - `(is a b)` 165 | - Checks if `a` and `b` are equal, returns `1` if so, else returns `0`. If `a` and `b` are lists, a deep comparison is made. 166 | 167 | - `(less_than a b)` 168 | - Checks if `a` is less than `b`, returns `1` if so, else returns `0`. 169 | - `a`: `integer` or `float` 170 | - `b`: `integer` or `float` 171 | 172 | - `(greater_than a b)` 173 | - Checks if `a` is greater than `b`, returns `1` if so, else returns `0`. 174 | - `a`: `integer` or `float` 175 | - `b`: `integer` or `float` 176 | 177 | ### Logical Operators 178 | - `(not a)` 179 | - Returns `0` if `a` is `1`, and `1` if `a` is `0`. 180 | - `a`: `integer`, which is `1` or `0` 181 | 182 | - `(and arg1 arg2 arg3 ...)` 183 | - Returns `1` if all arguments are `1`, else returns `0` 184 | - `arg1`, `arg2`, `arg3`, ...: `integer`, which is `1` or `0` 185 | 186 | - `(or arg1 arg2 arg3 ...)` 187 | - Returns `1` if at least one argument is `1`, else returns `0` 188 | - `arg1`, `arg2`, `arg3`, ...: `integer`, which is `1` or `0` 189 | 190 | ### Arithmetic 191 | - `(add arg1 arg2 arg3 ...)` 192 | - Returns `arg1` + `arg2` + `arg3` + ... 193 | - Requires a minimum of two args 194 | - `arg1`, `arg2`, `arg3`, ...: `integer` or `float` 195 | 196 | - `(subtract arg1 arg2 arg3 ...)` 197 | - Returns `arg1` - `arg2` - `arg3` - ... 198 | - Requires a minimum of two args 199 | - `arg1`, `arg2`, `arg3`, ...: `integer` or `float` 200 | 201 | - `(divide arg1 arg2 arg3 ...)` 202 | - Returns `arg1` / `arg2` / `arg3` / ... 203 | - Requires a minimum of two args 204 | - `arg1`, `arg2`, `arg3`, ...: `integer` or `float` 205 | 206 | - `(multiply arg1 arg2 arg3 ...)` 207 | - Returns `arg1` * `arg2` * `arg3` * ... 208 | - Requires a minimum of two args 209 | - `arg1`, `arg2`, `arg3`, ...: `integer` or `float` 210 | 211 | - `(remainder a b)` 212 | - Returns the remainder of `a` and `b`. 213 | - `a`: `integer` or `float` 214 | - `b`: `integer` or `float` 215 | 216 | - `(power a b)` 217 | - Returns `a` to the power of `b`. 218 | - `a`: `integer` or `float` 219 | - `b`: `integer` or `float` 220 | 221 | - `(random)` 222 | - Returns a random number from 0 to 1. 223 | 224 | ### Control 225 | - `(loop count fn)` 226 | - Applies `fn`, `count` times. If `fn` returns, the loop breaks, and `loop` returns whatever `fn` returned, else repeats until loop is completed. 227 | - `count`: `integer`, which is greater than or equal to `0` 228 | - `fn`: `function`, which is in the form `{n -> ...}`, where n is the current loop index (starting at `0`). 229 | 230 | - `(until stop fn initial_state)` or `(until stop fn)` 231 | - Applies `fn`, and repeats until `fn` returns `stop`. `until` returns whatever `fn` returned, before `stop`. 232 | - The return value of every past iteration is passed on to the next. The initial iteration uses `initial_state` if supplied, or returns `void` if not. 233 | - `fn`: `function`, which is in the form `{state n -> ...}`, where n is the current loop index (starting at `0`), and `state` is the current state. 234 | 235 | - `(if condition1 fn1 condtion2 fn2 condtion3 fn3 ... fn_else)` 236 | - If `condition1` is `1`, applies `fn1`. 237 | - Else if `condition2` is `1`, applies `fn2`, else if ... 238 | - If no condtions were `1`, applies `fn_else`. 239 | - Return whatever the result of `fn1`, `fn2`, `fn3`, ..., or `fn_else` was. 240 | - `condition1`, `condition2`, `condition3`, ...: `integer`, which is `1` or `0` 241 | - `fn1`, `fn2`, `fn3`, ..., `fn_else`: `function`, which takes no arguments 242 | 243 | - `(wait time)` 244 | - Blocks execution for `time` amount of seconds. 245 | - `time`: `integer` or `float`. 246 | 247 | ### Types 248 | - `void` 249 | - A value of type `void` 250 | 251 | - `(integer a)` 252 | - Returns `a` as an `integer`. 253 | - `a`: `string`, `float`, or `integer`. 254 | 255 | - `(string a)` 256 | - Returns `a` as a `string`. 257 | - `a`: `string`, `float`, or `integer`. 258 | 259 | - `(float a)` 260 | - Returns `a` as a `float`. 261 | - `a`: `string`, `float`, or `integer`. 262 | 263 | - `(type a)` 264 | - Returns the type of `a` as a `string`. 265 | 266 | ### List and String 267 | - `(list arg1 arg2 arg3 ...)` 268 | - Returns a `list`, with the arguments as it's contents. 269 | 270 | - `(length x)` 271 | - Returns the length of `x` 272 | - `x`: `string` or `list`. 273 | 274 | - `(join arg1 arg2 arg3 ...)` 275 | - Returns all args joined together. 276 | - All args must be of the same type. 277 | - `arg1`, `arg2`, `arg3`, ...: `string` or `list`. 278 | 279 | - `(get x index1)` or `(get x index1 index2)` 280 | - Returns the item in `x` at `index1`. If x is a `string`, this is a single char. 281 | - If `index2` is supplied, returns a sub-array or substring from `index1` to `index2`, not including `index2`. 282 | - `x`: `string` or `list`. 283 | - `index1`: `int`. 284 | - `index2`: `int`. 285 | 286 | - `(insert x item)` or `(insert x item index)` 287 | - Returns a `list` or `string`, in which `item` was inserted into `x` at `index`. Does not overwrite any data. 288 | - If `index` not supplied, `item` is assumed to be put at the end of `x`. 289 | - `x`: `string` or `list`. 290 | - `item`: `string` if `x` is `string`, else any 291 | - `index`: `int`. 292 | 293 | - `(set x item index)` 294 | - Returns a `list` or `string`, in which the item located at `index` in `x`, was replaced by `item`. 295 | - `x`: `string` or `list`. 296 | - `item`: `string` if `x` is `string`, else any 297 | - `index`: `int`. 298 | 299 | - `(delete x index1)` or `(delete x index1 index2)` 300 | - Returns a `string` or `list`, where `index1` was removed from `x`. 301 | - If `index2` is supplied, all items from `index1` to `index2` are removed, not including `index2`. 302 | - `x`: `string` or `list`. 303 | - `index1`: `int`. 304 | - `index2`: `int`. 305 | 306 | - `(map arr fn)` 307 | - Returns a list created by calling `fn` on every item of `arr`, and using the values returned by `fn` to populate the returned array. 308 | - `arr`: `list` 309 | - `fn`: `function`, which is in the form `{item i -> ...}`, where `item` is the current item, and `i` is the current index. 310 | 311 | - `(reduce arr fn initial_acc)` or `(reduce arr fn)` 312 | - Returns a value, computed via running `fn` on every item in `arr`. With every iteration, the last return from `fn` is passed to the next application of `fn`. The final returned value from `fn` is the value returned from `reduce`. 313 | - `arr`: `list`. 314 | - `fn`: `function`, which is in the form `{acc item i -> ...}`, where `item` is the current item, `acc` is the accumulator (the result of `fn` from the last item), and `i` is the current index. `acc` is `initial_acc` if supplied, or `void` if not. 315 | 316 | - `(range n)` 317 | - Returns a list with the integers from `0` to `n`, not including `n`. 318 | - `n`: `integer`, which is greater than or equal to 0. 319 | 320 | - `(find x item)` 321 | - Returns the index of `item` in `x`. Returns `void` if not found. 322 | - `x`: `string` or `list` 323 | - `item`: `string` if `x` is `string`, else any 324 | 325 | ## Syntax 326 | Crumb utilizes a notably terse syntax definition. The whole syntax can described in 6 lines of EBNF. Additionally, there are no reserved words, and only 7 reserved symbols. 327 | 328 | ### EBNF 329 | ```ebnf 330 | program = start, statement, end; 331 | statement = {return | assignment | value}; 332 | return = "<-", value; 333 | assignment = identifier, "=", value; 334 | value = application | function | int | float | string | identifier; 335 | application = "(", {value}, ")"; 336 | function = "{", [{identifier}, "->"], statement, "}"; 337 | ``` 338 | 339 | ![Syntax Diagram](./media/syntax-diagram.png) 340 | 341 | *Crumb syntax diagram, generated with [DrawGrammar](https://jacquev6.github.io/DrawGrammar/).* 342 | 343 | ### Tokens 344 | ``` 345 | "=" 346 | "(" 347 | ")" 348 | "{" 349 | "}" 350 | "->" 351 | "<-" 352 | identifier 353 | int 354 | float 355 | string 356 | start 357 | end 358 | ``` 359 | 360 | ### Specifics 361 | Strings are characters surrounded by quotes, for example: 362 | ``` 363 | "hello world" 364 | "this is\nsplit between new lines" 365 | "\e[31mthis text is in red\e[0m" 366 | ``` 367 | 368 | Escape codes in Crumb are equivalent to their respective C escape codes. The list of supported escape codes is: 369 | ``` 370 | "\a" 371 | "\b" 372 | "\f" 373 | "\n" 374 | "\r" 375 | "\t" 376 | "\v" 377 | "\e" 378 | "\\" 379 | "\"" 380 | "\x4d" // for arbitrary ascii chars 381 | ``` 382 | 383 | Integers are groups of number characters, that may be preceded by `-` for example: 384 | ``` 385 | 1234 386 | -14 387 | 345 388 | ``` 389 | 390 | Floats are like integers, but have a decimal in them, for example: 391 | ``` 392 | 13.45 393 | -2.3 394 | 745.0 395 | ``` 396 | 397 | Identifiers are any collection of characters, that are not separated by whitespace, don't begin with quotes or numbers, and are not any reserved symbols, for example: 398 | ``` 399 | hello 400 | x₂ 401 | symbol1 402 | + 403 | ``` 404 | 405 | Comments start with "//", and end with the end of a line, for example: 406 | ``` 407 | // this is a program that prints hi 408 | (print "hi") // this prints hi 409 | ``` 410 | 411 | ## Development 412 | To identify the current interpreter version, use the `-v` flag. 413 | ```bash 414 | ./crumb -v 415 | ``` 416 | 417 | When debugging the interpreter, it may be useful to compile with the `-g` flag. 418 | ```bash 419 | gcc src/*.c -g -Wall -lm -o crumb 420 | ``` 421 | 422 | This will allow Valgrind to provide extra information, 423 | ```bash 424 | valgrind --leak-check=full -s ./crumb -d YOURCODE.crumb 425 | ``` 426 | 427 | On mac, you can use `leaks`, 428 | ```bash 429 | leaks -atExit -- ./crumb YOURCODE.crumb 430 | ``` 431 | 432 | To obtain debug information about how your code is interpreted (Tokens, AST, etc.), add the `-d` flag. 433 | ```bash 434 | ./crumb -d YOURCODE.crumb 435 | ``` 436 | 437 | You can also pipe code straight into crumb (passed files always take priority over piped code). 438 | ```bash 439 | echo '(print (add 1 2) "\\n")' | ./crumb 440 | ``` 441 | 442 | Note that piping and using the `event` function is undefined/unsupported. 443 | 444 | Loaf derives it's templating from [`loaf-template/template.crumb`](./loaf-template/template.crumb). For Loaf to function correctly, this template must have no dependencies, and expose the following function: 445 | - `(template_main entry used_files)` 446 | - Returns a formatted `main.c` to substitute with [`src/main.c`](./src/main.c) in standalone builds. 447 | - `entry`: `string`, the entry point path of the Crumb program. 448 | - `used_files`: `list` of `string`, a list of paths to Crumb files in the project. 449 | 450 | ## Credit 451 | - Built by [Liam Ilan](https://www.liamilan.com/) -------------------------------------------------------------------------------- /src/stdlib.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "stdlib.h" 11 | #include "generic.h" 12 | #include "ast.h" 13 | #include "scope.h" 14 | #include "eval.h" 15 | #include "list.h" 16 | #include "events.h" 17 | #include "file.h" 18 | #include "lex.h" 19 | #include "tokens.h" 20 | #include "parse.h" 21 | 22 | /* tools, used later in stdlib */ 23 | // validate number of arguments 24 | void validateArgCount(int min, int max, int length, int lineNumber) { 25 | if (length > max) { 26 | // supplied too many args, throw error 27 | printf( 28 | "Runtime Error @ Line %i: Supplied more arguments than required to function.\n", 29 | lineNumber 30 | ); 31 | exit(0); 32 | } else if (length < min) { 33 | // supplied too little args, throw error 34 | printf( 35 | "Runtime Error @ Line %i: Supplied less arguments than required to function.\n", 36 | lineNumber 37 | ); 38 | exit(0); 39 | } 40 | } 41 | 42 | // verify that a bare minimum amount of args are passed 43 | void validateMinArgCount(int min, int length, int lineNumber) { 44 | if (length < min) { 45 | // supplied too little args, throw error 46 | printf( 47 | "Runtime Error @ Line %i: Supplied less arguments than required to function.\n", 48 | lineNumber 49 | ); 50 | exit(0); 51 | } 52 | } 53 | 54 | // validate type of argument 55 | void validateType(enum Type allowedTypes[], int typeCount, enum Type type, int argNum, int lineNumber, char* funcName) { 56 | bool valid = false; 57 | 58 | // check if type is valid 59 | for (int i = 0; i < typeCount; i++) { 60 | if (type == allowedTypes[i]) { 61 | valid = true; 62 | break; 63 | } 64 | } 65 | 66 | if (!valid) { 67 | // print error msg 68 | printf("Runtime Error @ Line %i: %s function requires ", lineNumber, funcName); 69 | 70 | for (int i = 0; i < typeCount - 1; i++) { 71 | printf("%s type or ", getTypeString(allowedTypes[i])); 72 | } 73 | 74 | printf("%s type", getTypeString(allowedTypes[typeCount - 1])); 75 | printf(" for argument #%i, %s type supplied instead.\n", argNum, getTypeString(type)); 76 | 77 | exit(0); 78 | } 79 | } 80 | 81 | // validate that argument is binary 82 | void validateBinary(int *p_val, int argNum, int lineNumber, char* funcName) { 83 | if (*(p_val) != 0 && *(p_val) != 1) { 84 | printf( 85 | "Runtime Error @ Line %i: %s function expected 0 or 1 for argument #%i, %i supplied instead.\n", 86 | lineNumber, funcName, argNum, *p_val 87 | ); 88 | exit(0); 89 | } 90 | } 91 | 92 | // validate that argument is within a range 93 | void validateRange(int *p_val, int min, int max, int argNum, int lineNumber, char* funcName) { 94 | if (*p_val > max || *p_val < min) { 95 | printf( 96 | "Runtime Error @ Line %i: %s function expected a value in the range [%i, %i] for argument #%i, %i supplied instead.\n", 97 | lineNumber, funcName, min, max, argNum, *p_val 98 | ); 99 | 100 | exit(0); 101 | } 102 | } 103 | 104 | // validate that value is at least a minimum 105 | void validateMin(int *p_val, int min, int argNum, int lineNumber, char* funcName) { 106 | if (*p_val < min) { 107 | printf( 108 | "Runtime Error @ Line %i: %s function expected a minimum value of %i for argument #%i, %i supplied instead.\n", 109 | lineNumber, funcName, min, argNum, *p_val 110 | ); 111 | 112 | exit(0); 113 | } 114 | } 115 | 116 | // applys a func, given arguments 117 | // used for callbacks from the standard library 118 | Generic *applyFunc(Generic *func, Scope *p_scope, Generic *args[], int length, int lineNumber) { 119 | if(func->type == TYPE_NATIVEFUNCTION) { 120 | // native func case, simply obtain cb and run 121 | Generic *(*cb)(Scope *, Generic *[], int, int) = func->p_val; 122 | 123 | // increase ref count 124 | for (int i = 0; i < length; i++) { 125 | args[i]->refCount++; 126 | } 127 | 128 | Generic *res = cb(p_scope, args, length, lineNumber); 129 | 130 | // drop ref count, and free if count is 0 131 | for (int i = 0; i < length; i++) { 132 | args[i]->refCount--; 133 | if (args[i]->refCount == 0) Generic_free(args[i]); 134 | } 135 | 136 | if (func->refCount == 0) Generic_free(func); 137 | 138 | return res; 139 | 140 | } else if (func->type == TYPE_FUNCTION) { 141 | // non-native func case 142 | // get ast node at head of function 143 | AstNode *p_head = ((AstNode *) func->p_val)->p_headChild; 144 | 145 | // create local scope 146 | Scope *p_local = Scope_new(p_scope); 147 | 148 | // loop until statement found, and populate local scope 149 | AstNode *p_curr = p_head; 150 | int i = 0; 151 | 152 | while (p_curr->opcode != OP_STATEMENT && i < length) { 153 | Scope_set(p_local, p_curr->val, args[i]); 154 | p_curr = p_curr->p_next; 155 | i++; 156 | } 157 | 158 | // error handling 159 | if (i != length) { 160 | // supplied too many args, throw error 161 | printf( 162 | "Runtime Error @ Line %i: Supplied more arguments than required to function.\n", 163 | p_curr->lineNumber 164 | ); 165 | exit(0); 166 | } else if (p_curr->opcode != OP_STATEMENT) { 167 | // supplied too little args, throw error 168 | printf( 169 | "Runtime Error @ Line %i: Supplied less arguments than required to function.\n", 170 | p_head->lineNumber 171 | ); 172 | exit(0); 173 | } 174 | 175 | // run statement with local scope 176 | Generic *res = eval(p_curr, p_local, 0); 177 | 178 | // free scope 179 | Scope_free(p_local); 180 | 181 | // free function if no references 182 | if (func->refCount == 0) Generic_free(func); 183 | 184 | return res; 185 | 186 | } else { 187 | printf( 188 | "Runtime Error @ Line %i: Attempted to call %s instead of function.\n", 189 | lineNumber, getTypeString(func->type) 190 | ); 191 | exit(0); 192 | } 193 | } 194 | 195 | /* IO */ 196 | // (print args...) 197 | // prints given arguments 198 | Generic *StdLib_print(Scope *p_scope, Generic *args[], int length, int lineNumber) { 199 | for (int i = 0; i < length; i++) { 200 | Generic_print(args[i]); 201 | if (i < length - 1) printf(" "); 202 | } 203 | 204 | return Generic_new(TYPE_VOID, NULL, 0); 205 | } 206 | 207 | // (input) 208 | // gets input from stdin 209 | Generic *StdLib_input(Scope *p_scope, Generic *args[], int length, int lineNumber) { 210 | validateArgCount(0, 0, length, lineNumber); 211 | 212 | // eat through anything in the input buffer 213 | enableRaw(); 214 | char eat = readChar(); 215 | while (eat != '\0') eat = readChar(); 216 | disableRaw(); 217 | 218 | // enable echo 219 | tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios); 220 | 221 | // initial allocation of empty string 222 | char *res = (char *) malloc(sizeof(char)); 223 | res[0] = '\0'; 224 | 225 | // loop through every char 226 | char c = getchar(); 227 | while (c != '\n') { 228 | // reallocate and add char 229 | res = realloc(res, sizeof(char) * (strlen(res) + 1 + 1)); 230 | strncat(res, &c, 1); 231 | 232 | c = getchar(); 233 | } 234 | 235 | // enable events again (turn off echo) 236 | tcsetattr(STDIN_FILENO, TCSAFLUSH, &run_termios); 237 | 238 | // create pointer 239 | char **p_res = (char **) malloc(sizeof(char *)); 240 | *p_res = res; 241 | 242 | return Generic_new(TYPE_STRING, p_res, 0); 243 | } 244 | 245 | // (columns) 246 | // returns number of columns in terminal 247 | Generic *StdLib_columns(Scope *p_scope, Generic *args[], int length, int lineNumber) { 248 | // get current dimensions 249 | struct winsize w; 250 | ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); 251 | 252 | // create pointer to rows 253 | int *rows = (int *) malloc(sizeof(int)); 254 | *rows = w.ws_col; 255 | 256 | // return generic 257 | return Generic_new(TYPE_INT, rows, 0); 258 | } 259 | 260 | // (rows) 261 | // returns number of rows in terminal 262 | Generic *StdLib_rows(Scope *p_scope, Generic *args[], int length, int lineNumber) { 263 | // get current dimensions 264 | struct winsize w; 265 | ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); 266 | 267 | // create pointer to rows 268 | int *rows = (int *) malloc(sizeof(int)); 269 | *rows = w.ws_row; 270 | 271 | // return generic 272 | return Generic_new(TYPE_INT, rows, 0); 273 | } 274 | 275 | // (read_file filepath) 276 | // reads text at filepath and returns 277 | Generic *StdLib_read_file(Scope *p_scope, Generic *args[], int length, int lineNumber) { 278 | validateArgCount(1, 1, length, lineNumber); 279 | 280 | enum Type allowedTypes[] = {TYPE_STRING}; 281 | validateType(allowedTypes, 1, args[0]->type, 1, lineNumber, "read_file"); 282 | 283 | // read file 284 | char *res = readFile(*((char **) args[0]->p_val), false); 285 | 286 | // if file couldn't be read, return void 287 | if (res == NULL) return Generic_new(TYPE_VOID, NULL, 0); 288 | 289 | // malloc pointer to string 290 | char **p_res = (char **) malloc(sizeof(char *)); 291 | *p_res = res; 292 | 293 | // return 294 | return Generic_new(TYPE_STRING, p_res, 0); 295 | } 296 | 297 | // (write_file filepath string) 298 | // writes string to filepath 299 | Generic *StdLib_write_file(Scope *p_scope, Generic *args[], int length, int lineNumber) { 300 | validateArgCount(2, 2, length, lineNumber); 301 | 302 | // check that type of args is string 303 | enum Type allowedTypes[] = {TYPE_STRING}; 304 | validateType(allowedTypes, 1, args[0]->type, 1, lineNumber, "write_file"); 305 | validateType(allowedTypes, 1, args[1]->type, 2, lineNumber, "write_file"); 306 | 307 | writeFile(*((char **) args[0]->p_val), *((char **) args[1]->p_val), lineNumber); 308 | return Generic_new(TYPE_VOID, NULL, 0); 309 | } 310 | 311 | // (event time) or (event) 312 | // returns a string containing the current event, blocking up to time seconds 313 | Generic *StdLib_event(Scope *p_scope, Generic *args[], int length, int lineNumber) { 314 | validateArgCount(0, 1, length, lineNumber); 315 | 316 | // find the number of deciseconds to block 317 | int decisecondsBlock; 318 | if (length == 1) { 319 | enum Type allowedTypes[] = {TYPE_FLOAT, TYPE_INT}; 320 | validateType(allowedTypes, 2, args[0]->type, 1, lineNumber, "event"); 321 | 322 | if (args[0]->type == TYPE_INT) { 323 | decisecondsBlock = *((int *) args[0]->p_val) * 10; 324 | } 325 | if (args[0]->type == TYPE_FLOAT) { 326 | decisecondsBlock = (int) ceilf(*((double *) args[0]->p_val) * 10); 327 | } 328 | } 329 | 330 | char **p_res = (char **) malloc(sizeof(char *)); 331 | 332 | int i = 0; 333 | while (length == 0 || i < decisecondsBlock) { 334 | if (i != 0) { 335 | free(*p_res); 336 | } 337 | *p_res = event(); 338 | 339 | // if an event is received 340 | if (strcmp(*p_res, "") != 0) { 341 | return Generic_new(TYPE_STRING, p_res, 0); 342 | } 343 | 344 | i += 1; 345 | } 346 | 347 | return Generic_new(TYPE_STRING, p_res, 0); 348 | } 349 | 350 | // (use path1 path2 path3 ... fn) 351 | // creates a new scope, evaluates the code in path1, path2, and path3, and then uses said scope to evaluate fn 352 | Generic *StdLib_use(Scope *p_scope, Generic *args[], int length, int lineNumber) { 353 | validateMinArgCount(2, length, lineNumber); 354 | 355 | // validate that all arguments with exception of last one are strings 356 | for (int i = 0; i < length - 1; i++) { 357 | enum Type allowedTypes[] = {TYPE_STRING}; 358 | validateType(allowedTypes, 1, args[i]->type, i + 1, lineNumber, "use"); 359 | } 360 | 361 | // validate that last argument is a function 362 | enum Type allowedTypes[] = {TYPE_FUNCTION, TYPE_NATIVEFUNCTION}; 363 | validateType(allowedTypes, 2, args[length - 1]->type, length, lineNumber, "use"); 364 | 365 | // create a new scope for all paths and fn to run in 366 | Scope *p_newScope = Scope_new(p_scope); 367 | 368 | // for each path 369 | for (int i = 0; i < length - 1; i++) { 370 | 371 | // read file 372 | char *code = readFile(*((char **) args[i]->p_val), true); 373 | 374 | // throw error if file could not be read 375 | if (code == NULL) { 376 | printf( 377 | "Runtime Error @ Line %i: Cannot read file \"%s\".\n", 378 | lineNumber, *((char **) args[i]->p_val) 379 | ); 380 | exit(0); 381 | } 382 | 383 | // create initial token 384 | Token *p_headToken = (Token *) malloc(sizeof(Token)); 385 | p_headToken->lineNumber = 1; 386 | p_headToken->type = TOK_START; 387 | p_headToken->val = NULL; 388 | p_headToken->p_next = NULL; 389 | 390 | // lex 391 | int tokenCount = lex(p_headToken, code, strlen(code)); 392 | 393 | // parse 394 | AstNode *p_headAstNode = parseProgram(p_headToken, tokenCount); 395 | 396 | // eval and free 397 | Generic_free(eval(p_headAstNode, p_newScope, 0)); 398 | 399 | // free memory 400 | // code 401 | free(code); 402 | code = NULL; 403 | 404 | // tokens 405 | Token_free(p_headToken); 406 | p_headToken = NULL; 407 | 408 | // ast 409 | AstNode_free(p_headAstNode); 410 | p_headAstNode = NULL; 411 | } 412 | 413 | // apply callback with new scope 414 | Generic *res = applyFunc(args[length - 1], p_newScope, NULL, 0, lineNumber); 415 | 416 | // free new scope and return 417 | Scope_free(p_newScope); 418 | return res; 419 | } 420 | 421 | // (shell command) 422 | // runs command with popen, returns stdout 423 | Generic *StdLib_shell(Scope *p_scope, Generic *args[], int length, int lineNumber) { 424 | 425 | // validation 426 | validateArgCount(1, 1, length, lineNumber); 427 | 428 | enum Type allowedTypes[] = {TYPE_STRING}; 429 | validateType(allowedTypes, 1, args[0]->type, 1, lineNumber, "shell"); 430 | 431 | // open process to run file 432 | FILE *p_out = popen(*((char **) args[0]->p_val), "r"); 433 | 434 | // ensure process returned 435 | if (p_out == NULL) { 436 | printf( 437 | "Runtime Error @ Line %i: Failed to run shell command.\n", 438 | lineNumber 439 | ); 440 | exit(0); 441 | } 442 | 443 | char *res = malloc(sizeof(char)); 444 | res[0] = '\0'; 445 | 446 | // get stdout 447 | char c; 448 | while (true) { 449 | // add char to res 450 | res = realloc(res, sizeof(char) * (strlen(res) + 1 + 1)); 451 | c = fgetc(p_out); 452 | if (feof(p_out)) break; // end of stdout 453 | strncat(res, &c, 1); 454 | } 455 | 456 | // close process 457 | pclose(p_out); 458 | 459 | // create pointer for generic to return 460 | char **p_res = malloc(sizeof(char *)); 461 | *p_res = res; 462 | 463 | return Generic_new(TYPE_STRING, p_res, 0); 464 | } 465 | 466 | /* comparissions */ 467 | // (is a b) 468 | // checks for equality between a and b 469 | // returns 1 for true, 0 for false 470 | Generic *StdLib_is(Scope *p_scope, Generic *args[], int length, int lineNumber) { 471 | validateArgCount(2, 2, length, lineNumber); 472 | 473 | // return 474 | int *res = (int *) malloc(sizeof(int)); 475 | *res = Generic_is(args[0], args[1]); 476 | return Generic_new(TYPE_INT, res, 0); 477 | } 478 | 479 | // (less_than a b) 480 | // checks if a is less than b 481 | Generic *StdLib_less_than(Scope *p_scope, Generic *args[], int length, int lineNumber) { 482 | validateArgCount(2, 2, length, lineNumber); 483 | 484 | enum Type allowedTypes[] = {TYPE_FLOAT, TYPE_INT}; 485 | 486 | validateType(allowedTypes, 2, args[0]->type, 1, lineNumber, "less_than"); 487 | validateType(allowedTypes, 2, args[1]->type, 2, lineNumber, "less_than"); 488 | 489 | // do comparision and return 490 | Generic *a = args[0]; 491 | Generic *b = args[1]; 492 | int *p_res = (int *) malloc(sizeof(int)); 493 | *p_res = ( 494 | (a->type == TYPE_FLOAT ? *((double *) a->p_val) : *((int *) a->p_val)) 495 | < (b->type == TYPE_FLOAT ? *((double *) b->p_val) : *((int *) b->p_val)) 496 | ); 497 | 498 | return Generic_new(TYPE_INT, p_res, 0); 499 | } 500 | 501 | // (greater_than a b) 502 | // checks if a is greater than b 503 | Generic *StdLib_greater_than(Scope *p_scope, Generic *args[], int length, int lineNumber) { 504 | validateArgCount(2, 2, length, lineNumber); 505 | 506 | enum Type allowedTypes[] = {TYPE_FLOAT, TYPE_INT}; 507 | 508 | validateType(allowedTypes, 2, args[0]->type, 1, lineNumber, "greater_than"); 509 | validateType(allowedTypes, 2, args[1]->type, 2, lineNumber, "greater_than"); 510 | 511 | // do comparision and return 512 | Generic *a = args[0]; 513 | Generic *b = args[1]; 514 | int *p_res = (int *) malloc(sizeof(int)); 515 | *p_res = ( 516 | (a->type == TYPE_FLOAT ? *((double *) a->p_val) : *((int *) a->p_val)) 517 | > (b->type == TYPE_FLOAT ? *((double *) b->p_val) : *((int *) b->p_val)) 518 | ); 519 | 520 | return Generic_new(TYPE_INT, p_res, 0); 521 | } 522 | 523 | /* logical operators */ 524 | // (not a) 525 | // returns 1 if a = 0, 0 if a = 1 526 | Generic *StdLib_not(Scope *p_scope, Generic *args[], int length, int lineNumber) { 527 | validateArgCount(1, 1, length, lineNumber); 528 | 529 | enum Type allowedTypes[] = {TYPE_INT}; 530 | validateType(allowedTypes, 1, args[0]->type, 1, lineNumber, "not"); 531 | 532 | validateBinary(args[0]->p_val, 1, lineNumber, "not"); 533 | 534 | int *p_res = (int *) malloc(sizeof(int)); 535 | *p_res = 1 - *((int *) args[0]->p_val); 536 | 537 | return Generic_new(TYPE_INT, p_res, 0); 538 | } 539 | 540 | // (and a b) 541 | // returns a and b 542 | Generic *StdLib_and(Scope *p_scope, Generic *args[], int length, int lineNumber) { 543 | validateMinArgCount(2, length, lineNumber); 544 | 545 | // type check 546 | enum Type allowedTypes[] = {TYPE_INT}; 547 | for (int i = 0; i < length; i++) { 548 | validateType(allowedTypes, 1, args[i]->type, i + 1, lineNumber, "and"); 549 | validateBinary(args[i]->p_val, i + 1, lineNumber, "and"); 550 | }; 551 | 552 | // set up result 553 | int *p_res = (int *) malloc(sizeof(int)); 554 | *p_res = 1; 555 | 556 | // add each arg to *p_res 557 | for (int i = 0; i < length; i++) { 558 | *p_res = *p_res && *((int *) args[i]->p_val); 559 | } 560 | 561 | return Generic_new(TYPE_INT, p_res, 0); 562 | } 563 | 564 | // (or a b ...) 565 | // returns a or b 566 | Generic *StdLib_or(Scope *p_scope, Generic *args[], int length, int lineNumber) { 567 | validateMinArgCount(2, length, lineNumber); 568 | 569 | // type check 570 | enum Type allowedTypes[] = {TYPE_INT}; 571 | for (int i = 0; i < length; i++) { 572 | validateType(allowedTypes, 1, args[i]->type, i + 1, lineNumber, "or"); 573 | validateBinary(args[i]->p_val, i + 1, lineNumber, "or"); 574 | }; 575 | 576 | // set up result 577 | int *p_res = (int *) malloc(sizeof(int)); 578 | *p_res = 0; 579 | 580 | // add each arg to *p_res 581 | for (int i = 0; i < length; i++) { 582 | *p_res = *p_res || *((int *) args[i]->p_val); 583 | } 584 | 585 | return Generic_new(TYPE_INT, p_res, 0); 586 | } 587 | 588 | /* arithmetic */ 589 | // (add arg1 arg2 arg3 ...) 590 | // sums all args 591 | Generic *StdLib_add(Scope *p_scope, Generic *args[], int length, int lineNumber) { 592 | validateMinArgCount(2, length, lineNumber); 593 | 594 | // flag if we can return integer, or if we must return float 595 | bool resIsInt = true; 596 | 597 | // type check 598 | enum Type allowedTypes[] = {TYPE_INT, TYPE_FLOAT}; 599 | for (int i = 0; i < length; i++) { 600 | resIsInt = args[i]->type == TYPE_INT && resIsInt; 601 | validateType(allowedTypes, 2, args[i]->type, i + 1, lineNumber, "add"); 602 | }; 603 | 604 | if (resIsInt) { 605 | // case where we can return int 606 | int *p_res = (int *) malloc(sizeof(int)); 607 | *p_res = 0; 608 | 609 | // add each arg to *p_res 610 | for (int i = 0; i < length; i++) { 611 | *p_res += *((int *) args[i]->p_val); 612 | } 613 | 614 | return Generic_new(TYPE_INT, p_res, 0); 615 | 616 | } else { 617 | // case where we must return float 618 | double *p_res = (double *) malloc(sizeof(double)); 619 | *p_res = 0; 620 | 621 | // add each arg to *p_res 622 | for (int i = 0; i < length; i++) { 623 | *p_res += args[i]->type == TYPE_FLOAT 624 | ? *((double *) args[i]->p_val) 625 | : *((int *) args[i]->p_val); 626 | } 627 | 628 | return Generic_new(TYPE_FLOAT, p_res, 0); 629 | } 630 | } 631 | 632 | // (subtract arg1 arg2 arg3 ...) 633 | // returns arg1 - arg2 - arg3 - ... 634 | Generic *StdLib_subtract(Scope *p_scope, Generic *args[], int length, int lineNumber) { 635 | validateMinArgCount(2, length, lineNumber); 636 | 637 | // flag if we can return integer, or if we must return float 638 | bool resIsInt = true; 639 | 640 | // type check 641 | enum Type allowedTypes[] = {TYPE_INT, TYPE_FLOAT}; 642 | for (int i = 0; i < length; i++) { 643 | resIsInt = args[i]->type == TYPE_INT && resIsInt; 644 | validateType(allowedTypes, 2, args[i]->type, i + 1, lineNumber, "subtract"); 645 | }; 646 | 647 | if (resIsInt) { 648 | // case where we can return int 649 | int *p_res = (int *) malloc(sizeof(int)); 650 | *p_res = *((int *) args[0]->p_val); 651 | 652 | // subtract each arg from *p_res 653 | for (int i = 1; i < length; i++) { 654 | *p_res -= *((int *) args[i]->p_val); 655 | } 656 | 657 | return Generic_new(TYPE_INT, p_res, 0); 658 | 659 | } else { 660 | // case where we must return float 661 | double *p_res = (double *) malloc(sizeof(double)); 662 | *p_res = args[0]->type == TYPE_FLOAT 663 | ? *((double *) args[0]->p_val) 664 | : *((int *) args[0]->p_val);; 665 | 666 | // subtract each arg from *p_res 667 | for (int i = 1; i < length; i++) { 668 | *p_res -= args[i]->type == TYPE_FLOAT 669 | ? *((double *) args[i]->p_val) 670 | : *((int *) args[i]->p_val); 671 | } 672 | 673 | return Generic_new(TYPE_FLOAT, p_res, 0); 674 | } 675 | } 676 | 677 | // (divide arg1 arg2 arg3 ...) 678 | // returns arg1 / arg2 / arg3 / ... 679 | Generic *StdLib_divide(Scope *p_scope, Generic *args[], int length, int lineNumber) { 680 | validateMinArgCount(2, length, lineNumber); 681 | 682 | // type check 683 | enum Type allowedTypes[] = {TYPE_INT, TYPE_FLOAT}; 684 | for (int i = 0; i < length; i++) { 685 | validateType(allowedTypes, 2, args[i]->type, i + 1, lineNumber, "divide"); 686 | }; 687 | 688 | // initial value 689 | double *p_res = (double *) malloc(sizeof(double)); 690 | *p_res = args[0]->type == TYPE_FLOAT 691 | ? *((double *) args[0]->p_val) 692 | : *((int *) args[0]->p_val);; 693 | 694 | // divide each arg from *p_res 695 | for (int i = 1; i < length; i++) { 696 | double val = args[i]->type == TYPE_FLOAT 697 | ? *((double *) args[i]->p_val) 698 | : *((int *) args[i]->p_val); 699 | 700 | if (val == 0) { 701 | // throw error for division by 0 702 | printf( 703 | "Runtime Error @ Line %i: Division by 0.\n", 704 | lineNumber 705 | ); 706 | exit(0); 707 | }; 708 | 709 | *p_res /= val; 710 | } 711 | 712 | return Generic_new(TYPE_FLOAT, p_res, 0); 713 | } 714 | 715 | // (multiply arg1 arg2 arg3 ...) 716 | // returns arg1 * arg2 * arg3 * ... 717 | Generic *StdLib_multiply(Scope *p_scope, Generic *args[], int length, int lineNumber) { 718 | validateMinArgCount(2, length, lineNumber); 719 | 720 | // flag if we can return integer, or if we must return float 721 | bool resIsInt = true; 722 | 723 | // type check 724 | enum Type allowedTypes[] = {TYPE_INT, TYPE_FLOAT}; 725 | for (int i = 0; i < length; i++) { 726 | resIsInt = args[i]->type == TYPE_INT && resIsInt; 727 | validateType(allowedTypes, 2, args[i]->type, i + 1, lineNumber, "multiply"); 728 | }; 729 | 730 | if (resIsInt) { 731 | // case where we can return int 732 | int *p_res = (int *) malloc(sizeof(int)); 733 | *p_res = *((int *) args[0]->p_val);; 734 | 735 | // multiply each arg to *p_res 736 | for (int i = 1; i < length; i++) { 737 | *p_res *= *((int *) args[i]->p_val); 738 | } 739 | 740 | return Generic_new(TYPE_INT, p_res, 0); 741 | 742 | } else { 743 | // case where we must return float 744 | double *p_res = (double *) malloc(sizeof(double)); 745 | *p_res = args[0]->type == TYPE_FLOAT 746 | ? *((double *) args[0]->p_val) 747 | : *((int *) args[0]->p_val); 748 | 749 | // multiply each arg to *p_res 750 | for (int i = 1; i < length; i++) { 751 | *p_res *= args[i]->type == TYPE_FLOAT 752 | ? *((double *) args[i]->p_val) 753 | : *((int *) args[i]->p_val); 754 | } 755 | 756 | return Generic_new(TYPE_FLOAT, p_res, 0); 757 | } 758 | } 759 | 760 | // (remainder a b) 761 | // returns the a remainder b 762 | Generic *StdLib_remainder(Scope *p_scope, Generic *args[], int length, int lineNumber) { 763 | validateArgCount(2, 2, length, lineNumber); 764 | 765 | enum Type allowedTypes[] = {TYPE_INT, TYPE_FLOAT}; 766 | validateType(allowedTypes, 2, args[0]->type, 1, lineNumber, "remainder"); 767 | validateType(allowedTypes, 2, args[1]->type, 2, lineNumber, "remainder"); 768 | 769 | if ((args[1]->type == TYPE_FLOAT ? *((double *) args[1]->p_val) : *((int *) args[1]->p_val)) == 0) { 770 | // throw error for division by 0 771 | printf( 772 | "Runtime Error @ Line %i: Remainder of division by 0.\n", 773 | lineNumber 774 | ); 775 | exit(0); 776 | }; 777 | 778 | if (args[0]->type == TYPE_INT && args[1]->type == TYPE_INT) { 779 | // if we can return integer 780 | int *p_res = (int *) malloc(sizeof(int)); 781 | *p_res = *((int *) args[0]->p_val) % *((int *) args[1]->p_val); 782 | return Generic_new(TYPE_INT, p_res, 0); 783 | } else { 784 | // if we must return float 785 | double *p_res = (double *) malloc(sizeof(double)); 786 | 787 | *p_res = fmod( 788 | (args[0]->type == TYPE_FLOAT ? *((double *) args[0]->p_val) : *((int *) args[0]->p_val)), 789 | (args[1]->type == TYPE_FLOAT ? *((double *) args[1]->p_val) : *((int *) args[1]->p_val)) 790 | ); 791 | 792 | return Generic_new(TYPE_FLOAT, p_res, 0); 793 | } 794 | } 795 | 796 | // (power a b) 797 | // returns the a to the power of b 798 | Generic *StdLib_power(Scope *p_scope, Generic *args[], int length, int lineNumber) { 799 | validateArgCount(2, 2, length, lineNumber); 800 | 801 | enum Type allowedTypes[] = {TYPE_INT, TYPE_FLOAT}; 802 | validateType(allowedTypes, 2, args[0]->type, 1, lineNumber, "power"); 803 | validateType(allowedTypes, 2, args[1]->type, 2, lineNumber, "power"); 804 | 805 | if (args[0]->type == TYPE_INT && args[1]->type == TYPE_INT) { 806 | // if we can return integer 807 | int *p_res = (int *) malloc(sizeof(int)); 808 | 809 | *p_res = pow( 810 | *((int *) args[0]->p_val), 811 | *((int *) args[1]->p_val) 812 | ); 813 | 814 | return Generic_new(TYPE_INT, p_res, 0); 815 | } else { 816 | // if we must return float 817 | double *p_res = (double *) malloc(sizeof(double)); 818 | 819 | *p_res = pow( 820 | (args[0]->type == TYPE_FLOAT ? *((double *) args[0]->p_val) : *((int *) args[0]->p_val)), 821 | (args[1]->type == TYPE_FLOAT ? *((double *) args[1]->p_val) : *((int *) args[1]->p_val)) 822 | ); 823 | 824 | return Generic_new(TYPE_FLOAT, p_res, 0); 825 | } 826 | } 827 | 828 | // (random) 829 | // returns random number from 0 to 1 830 | Generic *StdLib_random(Scope *p_scope, Generic *args[], int length, int lineNumber) { 831 | validateArgCount(0, 0, length, lineNumber); 832 | 833 | double *p_res = (double *) malloc(sizeof(double)); 834 | *p_res = (double) rand() / (double) RAND_MAX; 835 | 836 | return Generic_new(TYPE_FLOAT, p_res, 0); 837 | } 838 | 839 | /* control */ 840 | // (loop n f) 841 | // applys f, n times, passing the current index to f 842 | // returns whatever f returns if not void 843 | // will go to completion and return void if void is all f returns 844 | Generic *StdLib_loop(Scope *p_scope, Generic *args[], int length, int lineNumber) { 845 | validateArgCount(2, 2, length, lineNumber); 846 | 847 | enum Type allowedTypes1[] = {TYPE_INT}; 848 | enum Type allowedTypes2[] = {TYPE_NATIVEFUNCTION, TYPE_FUNCTION}; 849 | 850 | validateType(allowedTypes1, 1, args[0]->type, 1, lineNumber, "loop"); 851 | validateType(allowedTypes2, 2, args[1]->type, 2, lineNumber, "loop"); 852 | 853 | validateMin(args[0]->p_val, 0, 1, lineNumber, "loop"); 854 | 855 | // loop 856 | for (int i = 0; i < *((int *) args[0]->p_val); i++) { 857 | 858 | // get arg to pass to cb 859 | int *p_arg = (int *) malloc(sizeof(int)); 860 | *p_arg = i; 861 | Generic *newArgs[1] = {Generic_new(TYPE_INT, p_arg, 0)}; 862 | 863 | // call cb 864 | Generic *res = applyFunc(args[1], p_scope, newArgs, 1, lineNumber); 865 | 866 | // if void type returned, free 867 | if (res->type != TYPE_VOID) return res; 868 | else Generic_free(res); 869 | } 870 | 871 | return Generic_new(TYPE_VOID, NULL, 0); 872 | } 873 | 874 | // (until stop f initial) 875 | // runs until f returns stop 876 | // passes the last returned value to f, as well as the current index 877 | // intial acts like the first "state", unless another state supplied 878 | Generic *StdLib_until(Scope *p_scope, Generic *args[], int length, int lineNumber) { 879 | validateArgCount(2, 3, length, lineNumber); 880 | 881 | // verify 2nd arg is a function 882 | enum Type allowedTypes[] = {TYPE_NATIVEFUNCTION, TYPE_FUNCTION}; 883 | validateType(allowedTypes, 2, args[1]->type, 2, lineNumber, "until"); 884 | 885 | // set up state 886 | Generic *state; 887 | 888 | if (length == 3) { 889 | state = Generic_copy(args[2]); 890 | } else { 891 | state = Generic_new(TYPE_VOID, NULL, 0); 892 | } 893 | 894 | // flags and index 895 | int i = 0; 896 | 897 | // loop 898 | while (true) { 899 | 900 | // get index 901 | int *p_i = (int *) malloc(sizeof(int)); 902 | *p_i = i; 903 | Generic *newArgs[2] = {Generic_copy(state), Generic_new(TYPE_INT, p_i, 0)}; 904 | 905 | // callback 906 | Generic *res = applyFunc(args[1], p_scope, newArgs, 2, lineNumber); 907 | 908 | // handle state 909 | int comp = Generic_is(res, args[0]); 910 | if (comp) { 911 | Generic_free(res); 912 | return state; 913 | } else { 914 | Generic_free(state); 915 | state = res; 916 | } 917 | 918 | i++; 919 | } 920 | } 921 | 922 | // (if c1 f1 c2 f2 c3 f3 ... else) 923 | // applys f1 if c1 == 1, etc. 924 | // otherwise run else 925 | Generic *StdLib_if(Scope *p_scope, Generic *args[], int length, int lineNumber) { 926 | validateMinArgCount(2, length, lineNumber); 927 | 928 | enum Type allowedTypesCond[] = {TYPE_INT}; 929 | enum Type allowedTypesCb[] = {TYPE_NATIVEFUNCTION, TYPE_FUNCTION}; 930 | 931 | bool conditionPassed = false; 932 | 933 | for (int i = 0; i < length; i++) { 934 | if (i % 2 == 0) { 935 | if (i == length - 1) { 936 | // else case 937 | validateType(allowedTypesCb, 2, args[i]->type, i + 1, lineNumber, "if"); 938 | return applyFunc(args[i], p_scope, NULL, 0, lineNumber); 939 | 940 | } else { 941 | // condition case 942 | validateType(allowedTypesCond, 1, args[i]->type, i + 1, lineNumber, "if"); 943 | validateBinary(args[i]->p_val, i + 1, lineNumber, "if"); 944 | 945 | // find if condition is true 946 | conditionPassed = *((int *) args[i]->p_val) == 1; 947 | } 948 | } else { 949 | // callback case 950 | validateType(allowedTypesCb, 2, args[i]->type, i + 1, lineNumber, "if"); 951 | 952 | // case where callback can be evaluated 953 | if (conditionPassed) { 954 | return applyFunc(args[i], p_scope, NULL, 0, lineNumber); 955 | } 956 | } 957 | } 958 | 959 | return Generic_new(TYPE_VOID, NULL, 0); 960 | } 961 | 962 | // (wait t) 963 | // waits t seconds 964 | Generic *StdLib_wait(Scope *p_scope, Generic *args[], int length, int lineNumber) { 965 | validateArgCount(1, 1, length, lineNumber); 966 | enum Type allowedTypes[] = {TYPE_INT, TYPE_FLOAT}; 967 | validateType(allowedTypes, 2, args[0]->type, 1, lineNumber, "wait"); 968 | int initialTime = clock(); 969 | while ( 970 | (((double) clock()) - ((double) initialTime)) / ((double) CLOCKS_PER_SEC) 971 | < (args[0]->type == TYPE_INT ? *((int *) args[0]->p_val) : *((double *) args[0]->p_val)) 972 | ) {} 973 | 974 | return Generic_new(TYPE_VOID, NULL, 0); 975 | } 976 | 977 | /* types */ 978 | // (int x) 979 | // returns x as an integer, if string, float, or int passed 980 | Generic *StdLib_integer(Scope *p_scope, Generic *args[], int length, int lineNumber) { 981 | validateArgCount(1, 1, length, lineNumber); 982 | 983 | enum Type allowedTypes[] = {TYPE_STRING, TYPE_INT, TYPE_FLOAT}; 984 | validateType(allowedTypes, 3, args[0]->type, 1, lineNumber, "integer"); 985 | 986 | int *p_res = malloc(sizeof(int)); 987 | 988 | if (args[0]->type == TYPE_STRING) { 989 | char *str = *((char **) args[0]->p_val); 990 | *p_res = atoi(str); 991 | } else if (args[0]->type == TYPE_FLOAT) { 992 | double f = *((double *) args[0]->p_val); 993 | *p_res = (int) f; 994 | } else { 995 | int i = *((int *) args[0]->p_val); 996 | *p_res = i; 997 | } 998 | 999 | return Generic_new(TYPE_INT, p_res, 0); 1000 | } 1001 | 1002 | // (string x) 1003 | // returns x as a string, if string, float, or int passed 1004 | Generic *StdLib_string(Scope *p_scope, Generic *args[], int length, int lineNumber) { 1005 | validateArgCount(1, 1, length, lineNumber); 1006 | 1007 | enum Type allowedTypes[] = {TYPE_STRING, TYPE_INT, TYPE_FLOAT}; 1008 | validateType(allowedTypes, 3, args[0]->type, 1, lineNumber, "string"); 1009 | 1010 | char *res; 1011 | 1012 | if (args[0]->type == TYPE_FLOAT) { 1013 | int length = snprintf(NULL, 0, "%f", *((double *) args[0]->p_val)); // get length 1014 | res = malloc(sizeof(char) * (length + 1)); // allocate memory 1015 | snprintf(res, length + 1, "%f", *((double *) args[0]->p_val)); // populate memory 1016 | } else if (args[0]->type == TYPE_INT) { 1017 | int length = snprintf(NULL, 0, "%i", *((int *) args[0]->p_val)); 1018 | res = malloc(sizeof(char) * (length + 1)); 1019 | snprintf(res, length + 1, "%i", *((int *) args[0]->p_val)); 1020 | } else { 1021 | int length = snprintf(NULL, 0, "%s", *((char **) args[0]->p_val)); 1022 | res = malloc(sizeof(char) * (length + 1)); 1023 | snprintf(res, length + 1, "%s", *((char **) args[0]->p_val)); 1024 | } 1025 | 1026 | char **p_res = (char **) malloc(sizeof(char *)); 1027 | *p_res = res; 1028 | 1029 | return Generic_new(TYPE_STRING, p_res, 0); 1030 | } 1031 | 1032 | // (float x) 1033 | // returns x as an float, if string, float, or int passed 1034 | Generic *StdLib_float(Scope *p_scope, Generic *args[], int length, int lineNumber) { 1035 | validateArgCount(1, 1, length, lineNumber); 1036 | 1037 | enum Type allowedTypes[] = {TYPE_STRING, TYPE_INT, TYPE_FLOAT}; 1038 | validateType(allowedTypes, 3, args[0]->type, 1, lineNumber, "float"); 1039 | 1040 | double *p_res = malloc(sizeof(double)); 1041 | 1042 | if (args[0]->type == TYPE_STRING) { 1043 | char *str = *((char **) args[0]->p_val); 1044 | *p_res = atof(str); 1045 | } else if (args[0]->type == TYPE_FLOAT) { 1046 | double f = *((double *) args[0]->p_val); 1047 | *p_res = f; 1048 | } else { 1049 | int i = *((int *) args[0]->p_val); 1050 | *p_res = (double) i; 1051 | } 1052 | 1053 | return Generic_new(TYPE_FLOAT, p_res, 0); 1054 | } 1055 | 1056 | // (type arg) 1057 | // returns a string with the type of arg 1058 | Generic *StdLib_type(Scope *p_scope, Generic *args[], int length, int lineNumber) { 1059 | validateArgCount(1, 1, length, lineNumber); 1060 | 1061 | // get type 1062 | char *type = getTypeString(args[0]->type); 1063 | 1064 | // malloc memory for result and write 1065 | char *res = malloc(sizeof(char) * (strlen(type) + 1)); 1066 | strcpy(res, type); 1067 | res[strlen(type)] = '\0'; 1068 | 1069 | // pointer to string 1070 | char **p_res = (char **) malloc(sizeof(char *)); 1071 | *p_res = res; 1072 | 1073 | // return new generic 1074 | return Generic_new(TYPE_STRING, p_res, 0); 1075 | } 1076 | 1077 | /* list and string */ 1078 | // (list args...) 1079 | // returns a new list with args as values 1080 | Generic *StdLib_list(Scope *p_scope, Generic *args[], int length, int lineNumber) { 1081 | return Generic_new(TYPE_LIST, List_new(args, length), 0); 1082 | } 1083 | 1084 | // (length list) 1085 | // returns list length 1086 | Generic *StdLib_length(Scope *p_scope, Generic *args[], int length, int lineNumber) { 1087 | validateArgCount(1, 1, length, lineNumber); 1088 | 1089 | enum Type allowedTypes[] = {TYPE_LIST, TYPE_STRING}; 1090 | validateType(allowedTypes, 2, args[0]->type, 1, lineNumber, "length"); 1091 | 1092 | // create int 1093 | int *p_res = (int *) malloc(sizeof(int)); 1094 | 1095 | // get length and return 1096 | if (args[0]->type == TYPE_LIST) *p_res = List_length((List *) args[0]->p_val); 1097 | else *p_res = strlen(*((char **) args[0]->p_val)); 1098 | return Generic_new(TYPE_INT, p_res, 0); 1099 | } 1100 | 1101 | // (join arg1 arg2 arg3 ...) 1102 | // returns args joined together 1103 | Generic *StdLib_join(Scope *p_scope, Generic *args[], int length, int lineNumber) { 1104 | validateMinArgCount(2, length, lineNumber); 1105 | 1106 | // validate first type 1107 | enum Type allowedTypes[] = {TYPE_LIST, TYPE_STRING}; 1108 | validateType(allowedTypes, 2, args[0]->type, 1, lineNumber, "join"); 1109 | 1110 | // validate rest of types 1111 | enum Type selectedType[] = {args[0]->type}; 1112 | for (int i = 1; i < length; i++) { 1113 | validateType(selectedType, 1, args[i]->type, i + 1, lineNumber, "join"); 1114 | } 1115 | 1116 | if (args[0]->type == TYPE_STRING) { 1117 | // string case 1118 | // calculate size (starts at 1 for \0) 1119 | int stringSize = 1; 1120 | for (int i = 0; i < length; i++) stringSize += strlen(*((char **) args[i]->p_val)); 1121 | 1122 | // malloc memory 1123 | char *res = (char *) malloc(sizeof(char) * stringSize); 1124 | 1125 | // copy / concat 1126 | strcpy(res, *((char **) args[0]->p_val)); 1127 | for (int i = 1; i < length; i++) strcat(res, *((char **) args[i]->p_val)); 1128 | 1129 | // malloc pointer 1130 | char **p_res = (char **) malloc(sizeof(char *)); 1131 | *p_res = res; 1132 | 1133 | return Generic_new(TYPE_STRING, p_res, 0); 1134 | } else { 1135 | // list case 1136 | // create array of lists 1137 | List *lists[length]; 1138 | 1139 | // populate 1140 | for (int i = 0; i < length; i++) lists[i] = ((List *) args[i]->p_val); 1141 | 1142 | // return new generic using List_join 1143 | return Generic_new(TYPE_LIST, List_join(lists, length), 0); 1144 | } 1145 | } 1146 | 1147 | // (get list index1) or (get list index1 index2) 1148 | // returns item from list/string, or sublist/substring 1149 | Generic *StdLib_get(Scope *p_scope, Generic *args[], int length, int lineNumber) { 1150 | validateArgCount(2, 3, length, lineNumber); 1151 | 1152 | enum Type allowedTypes1[] = {TYPE_LIST, TYPE_STRING}; 1153 | validateType(allowedTypes1, 2, args[0]->type, 1, lineNumber, "get"); 1154 | 1155 | enum Type allowedTypes2[] = {TYPE_INT}; 1156 | validateType(allowedTypes2, 1, args[1]->type, 2, lineNumber, "get"); 1157 | if (length == 3) validateType(allowedTypes2, 1, args[2]->type, 3, lineNumber, "get"); 1158 | 1159 | if (args[0]->type == TYPE_LIST) { 1160 | int inputLength = List_length((List *) args[0]->p_val); 1161 | validateRange(args[1]->p_val, 0, inputLength - 1, 2, lineNumber, "get"); 1162 | 1163 | // single item from list 1164 | if (length == 2) return List_get((List *) (args[0]->p_val), *((int *) args[1]->p_val)); 1165 | 1166 | // multiple items from list 1167 | else if (length == 3) { 1168 | validateRange(args[2]->p_val, *((int *) args[1]->p_val) + 1, inputLength, 3, lineNumber, "get"); 1169 | 1170 | return Generic_new(TYPE_LIST, List_sublist( 1171 | (List *) (args[0]->p_val), 1172 | *((int *) args[1]->p_val), 1173 | *((int *) args[2]->p_val) 1174 | ), 0); 1175 | } 1176 | 1177 | } else if (args[0]->type == TYPE_STRING) { 1178 | int inputLength = strlen(*((char **) args[0]->p_val)); 1179 | validateRange(args[1]->p_val, 0, inputLength - 1, 2, lineNumber, "get"); 1180 | 1181 | if (length == 2) { 1182 | // single item from string 1183 | char *res = malloc(sizeof(char) * 2); 1184 | res[0] = (*((char **) args[0]->p_val))[*((int *) args[1]->p_val)]; 1185 | res[1] = '\0'; 1186 | 1187 | char **p_res = (char **) malloc(sizeof(char *)); 1188 | *p_res = res; 1189 | 1190 | return Generic_new(TYPE_STRING, p_res, 0); 1191 | } else if (length == 3) { 1192 | // mutliple items from string 1193 | validateRange(args[2]->p_val, *((int *) args[1]->p_val) + 1, inputLength, 3, lineNumber, "get"); 1194 | 1195 | // create substring 1196 | int start = *((int *) args[1]->p_val); 1197 | int end = *((int *) args[2]->p_val); 1198 | 1199 | char *res = malloc(sizeof(char) * (end - start + 1)); 1200 | strncpy( 1201 | res, 1202 | &(*((char **) args[0]->p_val))[start], 1203 | end - start 1204 | ); 1205 | 1206 | res[end - start] = 0; 1207 | 1208 | // create pointer 1209 | char **p_res = (char **) malloc(sizeof(char *)); 1210 | *p_res = res; 1211 | 1212 | // return 1213 | return Generic_new(TYPE_STRING, p_res, 0); 1214 | } 1215 | } 1216 | 1217 | return Generic_new(TYPE_VOID, NULL, 0); 1218 | } 1219 | 1220 | // (insert list item index) or (insert list item) 1221 | // returns list or string, modified with item at index, if no index supplied, end is assumed 1222 | Generic *StdLib_insert(Scope *p_scope, Generic *args[], int length, int lineNumber) { 1223 | 1224 | // input validation 1225 | validateArgCount(2, 3, length, lineNumber); 1226 | 1227 | enum Type allowedTypes1[] = {TYPE_LIST, TYPE_STRING}; 1228 | validateType(allowedTypes1, 2, args[0]->type, 1, lineNumber, "insert"); 1229 | 1230 | enum Type allowedTypes2[] = {TYPE_STRING}; 1231 | if (args[0]->type == TYPE_STRING) 1232 | validateType(allowedTypes2, 1, args[1]->type, 2, lineNumber, "insert"); 1233 | 1234 | if (length == 3) { 1235 | enum Type allowedTypes3[] = {TYPE_INT}; 1236 | validateType(allowedTypes3, 1, args[2]->type, 3, lineNumber, "insert"); 1237 | 1238 | int inputLength = args[0]->type == TYPE_LIST 1239 | ? List_length((List *) args[0]->p_val) 1240 | : strlen(*((char **) args[0]->p_val)); 1241 | validateRange(args[2]->p_val, 0, inputLength, 3, lineNumber, "insert"); 1242 | } 1243 | 1244 | if (args[0]->type == TYPE_LIST) { 1245 | 1246 | // list case 1247 | if (length == 2) { 1248 | return Generic_new(TYPE_LIST, List_insert( 1249 | (List *) (args[0]->p_val), args[1], List_length((List *) (args[0]->p_val)) 1250 | ), 0); 1251 | } else if (length == 3) { 1252 | return Generic_new(TYPE_LIST, List_insert( 1253 | (List *) (args[0]->p_val), args[1], *((int *) args[2]->p_val) 1254 | ), 0); 1255 | } 1256 | } else if (args[0]->type == TYPE_STRING) { 1257 | 1258 | // string case 1259 | if (length == 2) { 1260 | 1261 | // when an index is not supplied, put simply acts like join 1262 | return StdLib_join(p_scope, args, length, lineNumber); 1263 | } else if (length == 3) { 1264 | int stringSize = strlen(*((char **) args[0]->p_val)) + strlen(*((char **) args[1]->p_val)) + 1; 1265 | 1266 | // malloc memory 1267 | char *res = (char *) malloc(sizeof(char) * stringSize); 1268 | 1269 | 1270 | // copy / concat 1271 | strncpy(res, *((char **) args[0]->p_val), *((int *) args[2]->p_val)); 1272 | res[*((int *) args[2]->p_val)] = '\0'; 1273 | 1274 | strcat(res, *((char **) args[1]->p_val)); 1275 | strcat(res, &((*((char **) args[0]->p_val))[*((int *) args[2]->p_val)])); 1276 | 1277 | // pointer 1278 | char **p_res = (char **) malloc(sizeof(char *)); 1279 | *p_res = res; 1280 | 1281 | // return 1282 | return Generic_new(TYPE_STRING, p_res, 0); 1283 | } 1284 | } 1285 | 1286 | return Generic_new(TYPE_VOID, NULL, 0); 1287 | } 1288 | 1289 | // (set list item index) 1290 | // sets index to item 1291 | Generic *StdLib_set(Scope *p_scope, Generic *args[], int length, int lineNumber) { 1292 | 1293 | // input validation 1294 | validateArgCount(3, 3, length, lineNumber); 1295 | 1296 | enum Type allowedTypes1[] = {TYPE_LIST, TYPE_STRING}; 1297 | validateType(allowedTypes1, 2, args[0]->type, 1, lineNumber, "set"); 1298 | 1299 | enum Type allowedTypes2[] = {TYPE_STRING}; 1300 | if (args[0]->type == TYPE_STRING) 1301 | validateType(allowedTypes2, 1, args[1]->type, 2, lineNumber, "set"); 1302 | 1303 | enum Type allowedTypes3[] = {TYPE_INT}; 1304 | validateType(allowedTypes3, 1, args[2]->type, 3, lineNumber, "set"); 1305 | 1306 | int inputLength = args[0]->type == TYPE_LIST 1307 | ? List_length((List *) args[0]->p_val) 1308 | : strlen(*((char **) args[0]->p_val)); 1309 | validateRange(args[2]->p_val, 0, inputLength - 1, 3, lineNumber, "set"); 1310 | 1311 | if (args[0]->type == TYPE_LIST) { 1312 | // list case 1313 | return Generic_new(TYPE_LIST, List_set( 1314 | (List *) (args[0]->p_val), args[1], *((int *) args[2]->p_val) 1315 | ), 0); 1316 | 1317 | } else if (args[0]->type == TYPE_STRING) { 1318 | char *target = *((char **) args[0]->p_val); 1319 | char *item = *((char **) args[1]->p_val); 1320 | int index = *((int *) args[2]->p_val); 1321 | 1322 | // string case 1323 | // length of result 1324 | int stringSize = index + (strlen(item) > strlen(target) - index ? strlen(item) : strlen(target) - index) + 1; 1325 | 1326 | // malloc memory 1327 | char *res = (char *) malloc(sizeof(char) * stringSize); 1328 | 1329 | // copy / concat 1330 | strncpy(res, target, index); 1331 | res[index] = '\0'; 1332 | 1333 | strcat(res, item); 1334 | 1335 | strncat(res, &(target[index]), stringSize - strlen(res) - 1); 1336 | 1337 | // pointer 1338 | char **p_res = (char **) malloc(sizeof(char *)); 1339 | *p_res = res; 1340 | 1341 | // return 1342 | return Generic_new(TYPE_STRING, p_res, 0); 1343 | } 1344 | 1345 | return Generic_new(TYPE_VOID, NULL, 0); 1346 | } 1347 | 1348 | // (delete list index) 1349 | // deletes item from list and returns 1350 | Generic *StdLib_delete(Scope *p_scope, Generic *args[], int length, int lineNumber) { 1351 | validateArgCount(2, 3, length, lineNumber); 1352 | 1353 | enum Type allowedTypes1[] = {TYPE_LIST, TYPE_STRING}; 1354 | validateType(allowedTypes1, 2, args[0]->type, 1, lineNumber, "delete"); 1355 | 1356 | enum Type allowedTypes2[] = {TYPE_INT}; 1357 | validateType(allowedTypes2, 1, args[1]->type, 2, lineNumber, "delete"); 1358 | if (length == 3) validateType(allowedTypes2, 1, args[2]->type, 3, lineNumber, "delete"); 1359 | 1360 | if (args[0]->type == TYPE_LIST) { 1361 | int inputLength = List_length((List *) args[0]->p_val); 1362 | validateRange(args[1]->p_val, 0, inputLength - 1, 2, lineNumber, "delete"); 1363 | 1364 | // list case 1365 | if (length == 2) { 1366 | return Generic_new(TYPE_LIST, List_delete((List *) (args[0]->p_val), *((int *) args[1]->p_val)), 0); 1367 | } else if (length == 3) { 1368 | validateRange(args[2]->p_val, *((int *) args[1]->p_val) + 1, inputLength, 3, lineNumber, "delete"); 1369 | 1370 | // case where we must delete multiple items 1371 | return Generic_new(TYPE_LIST, List_deleteMultiple( 1372 | (List *) (args[0]->p_val), *((int *) args[1]->p_val), *((int *) args[2]->p_val) 1373 | ), 0); 1374 | } 1375 | 1376 | } else { 1377 | // string case 1378 | int inputLength = strlen(*((char **) args[0]->p_val)); 1379 | validateRange(args[1]->p_val, 0, inputLength - 1, 2, lineNumber, "delete"); 1380 | 1381 | char *target = *((char **) args[0]->p_val); 1382 | int index1 = *((int *) args[1]->p_val); 1383 | 1384 | if (length == 2) { 1385 | 1386 | // length of result (no +1, as we are removing a charechter) 1387 | int stringSize = strlen(target); 1388 | 1389 | // malloc memory 1390 | char *res = (char *) malloc(sizeof(char) * stringSize); 1391 | 1392 | // copy / concat 1393 | strncpy(res, target, index1); 1394 | res[index1] = '\0'; 1395 | strncat(res, &(target[index1 + 1]), stringSize - strlen(res) - 1); 1396 | 1397 | // pointer 1398 | char **p_res = (char **) malloc(sizeof(char *)); 1399 | *p_res = res; 1400 | 1401 | // return 1402 | return Generic_new(TYPE_STRING, p_res, 0); 1403 | } else if (length == 3) { 1404 | // mutliple items from string 1405 | validateRange(args[2]->p_val, *((int *) args[1]->p_val) + 1, inputLength, 3, lineNumber, "delete"); 1406 | 1407 | int index2 = *((int *) args[2]->p_val); 1408 | 1409 | // length of result 1410 | int stringSize = strlen(target) + 1 - (index2 - index1); 1411 | 1412 | // malloc memory 1413 | char *res = (char *) malloc(sizeof(char) * stringSize); 1414 | 1415 | // copy / concat 1416 | strncpy(res, target, index1); 1417 | res[index1] = '\0'; 1418 | strncat(res, &(target[index2]), stringSize - strlen(res) - 1); 1419 | 1420 | // pointer 1421 | char **p_res = (char **) malloc(sizeof(char *)); 1422 | *p_res = res; 1423 | 1424 | // return 1425 | return Generic_new(TYPE_STRING, p_res, 0); 1426 | } 1427 | } 1428 | 1429 | return Generic_new(TYPE_VOID, NULL, 0); 1430 | } 1431 | 1432 | // (map list fn) 1433 | // applys fn to every item in list, returns list with results 1434 | Generic *StdLib_map(Scope *p_scope, Generic *args[], int length, int lineNumber) { 1435 | validateArgCount(2, 2, length, lineNumber); 1436 | 1437 | enum Type allowedTypes1[] = {TYPE_LIST}; 1438 | validateType(allowedTypes1, 1, args[0]->type, 1, lineNumber, "map"); 1439 | 1440 | enum Type allowedTypes2[] = {TYPE_FUNCTION, TYPE_NATIVEFUNCTION}; 1441 | validateType(allowedTypes2, 2, args[1]->type, 2, lineNumber, "map"); 1442 | 1443 | // create copy of list 1444 | Generic *res = Generic_copy(args[0]); 1445 | List *p_list = (List *) (res->p_val); 1446 | 1447 | for (int i = 0; i < p_list->len; i += 1) { 1448 | int *p_i = (int *) malloc(sizeof(int)); 1449 | *p_i = i; 1450 | 1451 | Generic *newArgs[] = {p_list->vals[i], Generic_new(TYPE_INT, p_i, 0)}; 1452 | p_list->vals[i] = applyFunc(args[1], p_scope, newArgs, 2, lineNumber); 1453 | } 1454 | 1455 | return res; 1456 | } 1457 | 1458 | // (reduce list fn acc) 1459 | // applys fn to every item in list, returns single reduced item 1460 | // acc is initial accumulator (assumed to be void if not supplied) 1461 | Generic *StdLib_reduce(Scope *p_scope, Generic *args[], int length, int lineNumber) { 1462 | validateArgCount(2, 3, length, lineNumber); 1463 | 1464 | enum Type allowedTypes1[] = {TYPE_LIST}; 1465 | validateType(allowedTypes1, 1, args[0]->type, 1, lineNumber, "reduce"); 1466 | 1467 | enum Type allowedTypes2[] = {TYPE_FUNCTION, TYPE_NATIVEFUNCTION}; 1468 | validateType(allowedTypes2, 2, args[1]->type, 2, lineNumber, "reduce"); 1469 | 1470 | // TODO: check empty list case 1471 | 1472 | // create accumulator 1473 | Generic *p_acc; 1474 | if (length == 3) { 1475 | p_acc = Generic_copy(args[2]); 1476 | } else { 1477 | p_acc = Generic_new(TYPE_VOID, NULL, 0); 1478 | } 1479 | 1480 | // Get list 1481 | List *p_list = (List *) (args[0]->p_val); 1482 | 1483 | // loop on every item 1484 | for (int i = 0; i < p_list->len; i += 1) { 1485 | // create pointer for index, to create generic for args 1486 | int *p_i = (int *) malloc(sizeof(int)); 1487 | *p_i = i; 1488 | 1489 | // apply function 1490 | Generic *newArgs[] = {p_acc, Generic_copy(p_list->vals[i]), Generic_new(TYPE_INT, p_i, 0)}; 1491 | p_acc = applyFunc(args[1], p_scope, newArgs, 3, lineNumber); 1492 | } 1493 | 1494 | return p_acc; 1495 | } 1496 | 1497 | // (range n) 1498 | // returns list with range from 0 -> n - 1 1499 | Generic *StdLib_range(Scope *p_scope, Generic *args[], int length, int lineNumber) { 1500 | validateArgCount(1, 1, length, lineNumber); 1501 | 1502 | enum Type allowedTypes[] = {TYPE_INT}; 1503 | validateType(allowedTypes, 1, args[0]->type, 1, lineNumber, "range"); 1504 | validateMin(args[0]->p_val, 0, 1, lineNumber, "range"); 1505 | 1506 | // make list of arguments to pass to List_new 1507 | int count = *((int *) args[0]->p_val); 1508 | Generic *argList[count]; 1509 | 1510 | for (int i = 0; i < count; i++) { 1511 | int *p_i = (int *) malloc(sizeof(int)); 1512 | *p_i = i; 1513 | argList[i] = Generic_new(TYPE_INT, p_i, 0); 1514 | } 1515 | 1516 | Generic *res = Generic_new(TYPE_LIST, List_new(argList, count), 0); 1517 | 1518 | // free 1519 | for (int i = 0; i < count; i++) { 1520 | Generic_free(argList[i]); 1521 | } 1522 | 1523 | return res; 1524 | } 1525 | 1526 | // (find x item) 1527 | // returns index of item 1528 | Generic *StdLib_find(Scope *p_scope, Generic *args[], int length, int lineNumber) { 1529 | validateArgCount(2, 2, length, lineNumber); 1530 | 1531 | enum Type allowedTypes1[] = {TYPE_STRING, TYPE_LIST}; 1532 | validateType(allowedTypes1, 2, args[0]->type, 1, lineNumber, "find"); 1533 | 1534 | if (args[0]->type == TYPE_STRING) { 1535 | 1536 | // string case 1537 | enum Type allowedTypes2[] = {TYPE_STRING}; 1538 | validateType(allowedTypes2, 1, args[1]->type, 2, lineNumber, "find"); 1539 | 1540 | // find pointer to substring 1541 | char *p_sub = strstr(*((char **) args[0]->p_val), *((char **) args[1]->p_val)); 1542 | 1543 | // return void if not found 1544 | if (p_sub == NULL) return Generic_new(TYPE_VOID, NULL, 0); 1545 | 1546 | // get index and return 1547 | int *p_index = malloc(sizeof(int)); 1548 | *p_index = p_sub - *((char **) args[0]->p_val); 1549 | 1550 | return Generic_new(TYPE_INT, p_index, 0); 1551 | } else { 1552 | // list case 1553 | List *p_list = ((List *) args[0]->p_val); 1554 | 1555 | // for each item 1556 | for (int i = 0; i < p_list->len; i += 1) { 1557 | 1558 | // if found, return index 1559 | if (Generic_is(p_list->vals[i], args[1])) { 1560 | int *p_index = (int *) malloc(sizeof(int)); 1561 | *p_index = i; 1562 | return Generic_new(TYPE_INT, p_index, 0); 1563 | } 1564 | } 1565 | 1566 | // if not found return void 1567 | return Generic_new(TYPE_VOID, NULL, 0); 1568 | } 1569 | } 1570 | 1571 | // creates a new global scope 1572 | Scope *newGlobal(int argc, char *argv[]) { 1573 | 1574 | // initialize random number generator for (random) 1575 | srand(time(NULL) + clock()); 1576 | 1577 | // create global scope 1578 | Scope *p_global = Scope_new(NULL); 1579 | Generic *args[argc]; 1580 | 1581 | // for each arg 1582 | for (int i = 0; i < argc; i++) { 1583 | char **p_val = (char **) malloc(sizeof(char *)); 1584 | 1585 | // create string 1586 | char *val = (char *) malloc(sizeof(char) * (strlen(argv[i]) + 1)); 1587 | strcpy(val, argv[i]); 1588 | *p_val = val; 1589 | 1590 | // add to args 1591 | args[i] = Generic_new(TYPE_STRING, p_val, 0); 1592 | } 1593 | 1594 | // add arguments and arguments count 1595 | Scope_set(p_global, "arguments", Generic_new(TYPE_LIST, List_new(args, argc), 0)); 1596 | 1597 | // free arguments memory (as List_new does a copy) 1598 | for (int i = 0; i < argc; i++) { 1599 | Generic_free(args[i]); 1600 | args[i] = NULL; 1601 | } 1602 | 1603 | // add void 1604 | Scope_set(p_global, "void", Generic_new(TYPE_VOID, NULL, 0)); 1605 | 1606 | // populate global scope with stdlib functions 1607 | /* IO */ 1608 | Scope_set(p_global, "print", Generic_new(TYPE_NATIVEFUNCTION, &StdLib_print, 0)); 1609 | Scope_set(p_global, "input", Generic_new(TYPE_NATIVEFUNCTION, &StdLib_input, 0)); 1610 | Scope_set(p_global, "rows", Generic_new(TYPE_NATIVEFUNCTION, &StdLib_rows, 0)); 1611 | Scope_set(p_global, "columns", Generic_new(TYPE_NATIVEFUNCTION, &StdLib_columns, 0)); 1612 | Scope_set(p_global, "read_file", Generic_new(TYPE_NATIVEFUNCTION, &StdLib_read_file, 0)); 1613 | Scope_set(p_global, "write_file", Generic_new(TYPE_NATIVEFUNCTION, &StdLib_write_file, 0)); 1614 | Scope_set(p_global, "event", Generic_new(TYPE_NATIVEFUNCTION, &StdLib_event, 0)); 1615 | Scope_set(p_global, "use", Generic_new(TYPE_NATIVEFUNCTION, &StdLib_use, 0)); 1616 | Scope_set(p_global, "shell", Generic_new(TYPE_NATIVEFUNCTION, &StdLib_shell, 0)); 1617 | 1618 | /* comparisions */ 1619 | Scope_set(p_global, "is", Generic_new(TYPE_NATIVEFUNCTION, &StdLib_is, 0)); 1620 | Scope_set(p_global, "less_than", Generic_new(TYPE_NATIVEFUNCTION, &StdLib_less_than, 0)); 1621 | Scope_set(p_global, "greater_than", Generic_new(TYPE_NATIVEFUNCTION, &StdLib_greater_than, 0)); 1622 | 1623 | /* logical operators */ 1624 | Scope_set(p_global, "not", Generic_new(TYPE_NATIVEFUNCTION, &StdLib_not, 0)); 1625 | Scope_set(p_global, "and", Generic_new(TYPE_NATIVEFUNCTION, &StdLib_and, 0)); 1626 | Scope_set(p_global, "or", Generic_new(TYPE_NATIVEFUNCTION, &StdLib_or, 0)); 1627 | 1628 | /* arithmetic */ 1629 | Scope_set(p_global, "add", Generic_new(TYPE_NATIVEFUNCTION, &StdLib_add, 0)); 1630 | Scope_set(p_global, "subtract", Generic_new(TYPE_NATIVEFUNCTION, &StdLib_subtract, 0)); 1631 | Scope_set(p_global, "divide", Generic_new(TYPE_NATIVEFUNCTION, &StdLib_divide, 0)); 1632 | Scope_set(p_global, "multiply", Generic_new(TYPE_NATIVEFUNCTION, &StdLib_multiply, 0)); 1633 | Scope_set(p_global, "remainder", Generic_new(TYPE_NATIVEFUNCTION, &StdLib_remainder, 0)); 1634 | Scope_set(p_global, "power", Generic_new(TYPE_NATIVEFUNCTION, &StdLib_power, 0)); 1635 | Scope_set(p_global, "random", Generic_new(TYPE_NATIVEFUNCTION, &StdLib_random, 0)); 1636 | 1637 | /* control */ 1638 | Scope_set(p_global, "loop", Generic_new(TYPE_NATIVEFUNCTION, &StdLib_loop, 0)); 1639 | Scope_set(p_global, "until", Generic_new(TYPE_NATIVEFUNCTION, &StdLib_until, 0)); 1640 | Scope_set(p_global, "if", Generic_new(TYPE_NATIVEFUNCTION, &StdLib_if, 0)); 1641 | Scope_set(p_global, "wait", Generic_new(TYPE_NATIVEFUNCTION, &StdLib_wait, 0)); 1642 | 1643 | /* types */ 1644 | Scope_set(p_global, "integer", Generic_new(TYPE_NATIVEFUNCTION, &StdLib_integer, 0)); 1645 | Scope_set(p_global, "string", Generic_new(TYPE_NATIVEFUNCTION, &StdLib_string, 0)); 1646 | Scope_set(p_global, "float", Generic_new(TYPE_NATIVEFUNCTION, &StdLib_float, 0)); 1647 | Scope_set(p_global, "type", Generic_new(TYPE_NATIVEFUNCTION, &StdLib_type, 0)); 1648 | 1649 | /* list and string */ 1650 | Scope_set(p_global, "list", Generic_new(TYPE_NATIVEFUNCTION, &StdLib_list, 0)); 1651 | Scope_set(p_global, "length", Generic_new(TYPE_NATIVEFUNCTION, &StdLib_length, 0)); 1652 | Scope_set(p_global, "join", Generic_new(TYPE_NATIVEFUNCTION, &StdLib_join, 0)); 1653 | Scope_set(p_global, "get", Generic_new(TYPE_NATIVEFUNCTION, &StdLib_get, 0)); 1654 | Scope_set(p_global, "insert", Generic_new(TYPE_NATIVEFUNCTION, &StdLib_insert, 0)); 1655 | Scope_set(p_global, "set", Generic_new(TYPE_NATIVEFUNCTION, &StdLib_set, 0)); 1656 | Scope_set(p_global, "delete", Generic_new(TYPE_NATIVEFUNCTION, &StdLib_delete, 0)); 1657 | Scope_set(p_global, "map", Generic_new(TYPE_NATIVEFUNCTION, &StdLib_map, 0)); 1658 | Scope_set(p_global, "reduce", Generic_new(TYPE_NATIVEFUNCTION, &StdLib_reduce, 0)); 1659 | Scope_set(p_global, "range", Generic_new(TYPE_NATIVEFUNCTION, &StdLib_range, 0)); 1660 | Scope_set(p_global, "find", Generic_new(TYPE_NATIVEFUNCTION, &StdLib_find, 0)); 1661 | 1662 | return p_global; 1663 | } --------------------------------------------------------------------------------