{"id":13609048,"url":"https://github.com/liam-ilan/crumb","last_synced_at":"2025-04-12T17:33:44.048Z","repository":{"id":186390492,"uuid":"671683512","full_name":"liam-ilan/crumb","owner":"liam-ilan","description":"The Crumb Programming Language","archived":false,"fork":false,"pushed_at":"2025-02-24T04:48:52.000Z","size":7479,"stargazers_count":421,"open_issues_count":1,"forks_count":8,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-02-24T05:30:17.498Z","etag":null,"topics":["c","crumb","functional-programming","interpreter","language","programming-languages"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/liam-ilan.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-07-27T22:37:59.000Z","updated_at":"2025-02-24T04:46:37.000Z","dependencies_parsed_at":"2023-10-11T09:12:03.424Z","dependency_job_id":"21361edd-fd08-40d9-b738-dc7589894314","html_url":"https://github.com/liam-ilan/crumb","commit_stats":{"total_commits":106,"total_committers":1,"mean_commits":106.0,"dds":0.0,"last_synced_commit":"f2be1a1fa8e7667451aef79fcdb1e5ed3dcb47b6"},"previous_names":["liam-ilan/crumb"],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/liam-ilan%2Fcrumb","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/liam-ilan%2Fcrumb/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/liam-ilan%2Fcrumb/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/liam-ilan%2Fcrumb/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/liam-ilan","download_url":"https://codeload.github.com/liam-ilan/crumb/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248605514,"owners_count":21132186,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["c","crumb","functional-programming","interpreter","language","programming-languages"],"created_at":"2024-08-01T19:01:32.056Z","updated_at":"2025-04-12T17:33:39.037Z","avatar_url":"https://github.com/liam-ilan.png","language":"C","readme":"\u003cp align=center\u003e\u003cimg src=\"./media/color-rounded-text.svg\" alt=\"crumb icon\" height=\"150\"/\u003e\u003c/p\u003e\r\n\r\n# The Crumb Programming Language\r\nCrumb is a high level, functional, interpreted, dynamically typed, general-purpose programming language, with a terse syntax, and a verbose standard library.\r\n\r\nIt features:\r\n- Strictly __no side effects__* to help you write functional code\r\n- The ability to __localize the effects of imported Crumb files__.\r\n- __Dynamic typing__ and __garbage collection__.\r\n- 0 keywords, __everything is a function__.\r\n\u003e *With the exception of IO\r\n\r\nClick here to [Get Started](#getting-started).\r\n\r\n---\r\n\r\n```\r\ntable = (map (range 10) {_ y -\u003e\r\n  \u003c- (map (range 10) {item x -\u003e\r\n    \u003c- (multiply (add x 1) (add y 1))\r\n  })\r\n})\r\n```\r\n*From [`examples/mult-table.crumb`](./examples/mult-table.crumb)*\r\n\r\n---\r\n\r\n![Game of Life in Crumb](./media/game-of-life.gif)\r\n*From [`examples/game-of-life.crumb`](./examples/game-of-life.crumb)*\r\n\r\n---\r\n\r\nFind more examples under the [`examples`](./examples/) directory.\r\n\r\n## Getting Started\r\n### Install\r\n**You do not need to clone this repo.** Instead, follow the instructions in [this template repo](https://github.com/liam-ilan/crumb-template).\r\n\r\nIf 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).\r\n\r\n### Basics\r\nAll function calls are done with s-expressions (think lisp). For example,\r\n```\r\n(print \"hello world\")\r\n```\r\n\r\nIn this case, the function `print` is applied with the `string` `\"hello world\"` as an argument.\r\n\r\nAll data in crumb is one of 6 different types:\r\n1. `string`\r\n2. `integer`\r\n3. `float`\r\n4. `function` / `native function`\r\n5. `list`\r\n6. `void`\r\n\r\nWe can store this data in variables, for example,\r\n```\r\na = 5\r\nb = \"hello\"\r\n```\r\n\r\nWe can combine data together to form lists,\r\n```\r\nmagic_list = (list 123 \"hello\" 42.0)\r\n```\r\nLists are always passed by value.\r\n\r\nWe can encapsulate code in functions using curly braces,\r\n```\r\nf = {\r\n  (print \"Funky!\")\r\n}\r\n\r\n(f) // prints \"Funky\"\r\n```\r\n\r\nFunctions can get arguments, denoted using the \"-\u003e\" symbol. For example,\r\n```\r\nadd_two_things = {a b -\u003e\r\n  (print (add a b))\r\n}\r\n\r\n(add_two_things 3 5) // prints 8\r\n```\r\n\r\nThey can also return values using the \"\u003c-\" symbol,\r\n```\r\ngeometric_mean = {a b -\u003e\r\n  \u003c- (power (multiply a b) 0.5)\r\n}\r\n\r\n(print (geometric_mean 3 5) \"\\n\") // prints 3.87...\r\n```\r\n\r\nFunctions operate in a few important ways:\r\n1. Function applications are *dynamically scoped*.\r\n2. Functions *cannot create side effects*.\r\n3. Like in JavaScript and Python, *all functions are first-class*.\r\n\r\nMost 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,\r\n\r\n```\r\n(loop 100 {i -\u003e\r\n  i = (add i 1)\r\n\r\n  (if (is (remainder i 15) 0) {\r\n      (print \"fizzbuzz\\n\")\r\n    } (is (remainder i 3) 0) {\r\n      (print \"fizz\\n\")\r\n    } (is (remainder i 5) 0) {\r\n      (print \"buzz\\n\")\r\n    } {(print i \"\\n\")}\r\n  )\r\n})\r\n```\r\n*From [`examples/fizzbuzz.crumb`](./examples/fizzbuzz.crumb)*\r\n\r\nYou 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.\r\n\r\n## Standard Library\r\n### IO\r\n- `arguments`\r\n  - A list command line arguments, like argv in C.\r\n\r\n- `(print arg1 arg2 arg3 ...)`\r\n  - Prints all arguments to stdout, returns nothing.\r\n\r\n- `(input)`\r\n  - Gets a line of input from stdin.\r\n\r\n- `(rows)`\r\n  - Returns the number of rows in the terminal.\r\n\r\n- `(columns)`\r\n  - Returns the number of columns in the terminal.\r\n\r\n- `(read_file path)`\r\n  - Returns the contents of the file designated by `path`, in a string. If the file cannot be read, returns void.\r\n  - `path`: `string`\r\n\r\n- `(write_file path contents)`\r\n  - Writes the string `contents` into the file designated by `path`, returns nothing.\r\n  - `path`: `string`\r\n  - `contents`: `string`\r\n\r\n- `(event time)` or `(event)`\r\n  - 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.\r\n  - `time`: `integer` or `float`\r\n\r\n- `(use path1 path2 path3 ... fn)`\r\n  - Crumb's code splitting method. Runs code in file paths, in order, on a new scope. Then uses said scope to apply `fn`.\r\n  - `path1`, `path2`, `path3`, ...: `string`\r\n  - `fn`: `function`\r\n\r\n- `(shell command)`\r\n  - Runs `command` as an sh program in a seperate process, and returns stdout of the process as a `string`.\r\n  - `command`: `string`\r\n  \r\n### Comparisons\r\n- `(is a b)`\r\n  - 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.\r\n\r\n- `(less_than a b)`\r\n  - Checks if `a` is less than `b`, returns `1` if so, else returns `0`.\r\n  - `a`: `integer` or `float`\r\n  - `b`: `integer` or `float`\r\n\r\n- `(greater_than a b)`\r\n  - Checks if `a` is greater than `b`, returns `1` if so, else returns `0`.\r\n  - `a`: `integer` or `float`\r\n  - `b`: `integer` or `float`\r\n\r\n### Logical Operators\r\n- `(not a)`\r\n  - Returns `0` if `a` is `1`, and `1` if `a` is `0`.\r\n  - `a`: `integer`, which is `1` or `0`\r\n\r\n- `(and arg1 arg2 arg3 ...)`\r\n  - Returns `1` if all arguments are `1`, else returns `0`\r\n  - `arg1`, `arg2`, `arg3`, ...: `integer`, which is `1` or `0`\r\n\r\n- `(or arg1 arg2 arg3 ...)`\r\n  - Returns `1` if at least one argument is `1`, else returns `0`\r\n  - `arg1`, `arg2`, `arg3`, ...: `integer`, which is `1` or `0`\r\n\r\n### Arithmetic\r\n- `(add arg1 arg2 arg3 ...)`\r\n  - Returns `arg1` + `arg2` + `arg3` + ...\r\n  - Requires a minimum of two args\r\n  - `arg1`, `arg2`, `arg3`, ...: `integer` or `float`\r\n\r\n- `(subtract arg1 arg2 arg3 ...)`\r\n  - Returns `arg1` - `arg2` - `arg3` - ...\r\n  - Requires a minimum of two args\r\n  - `arg1`, `arg2`, `arg3`, ...: `integer` or `float`\r\n\r\n- `(divide arg1 arg2 arg3 ...)`\r\n  - Returns `arg1` / `arg2` / `arg3` / ...\r\n  - Requires a minimum of two args\r\n  - `arg1`, `arg2`, `arg3`, ...: `integer` or `float`\r\n\r\n- `(multiply arg1 arg2 arg3 ...)`\r\n  - Returns `arg1` * `arg2` * `arg3` * ...\r\n  - Requires a minimum of two args\r\n  - `arg1`, `arg2`, `arg3`, ...: `integer` or `float`\r\n\r\n- `(remainder a b)`\r\n  - Returns the remainder of `a` and `b`.\r\n  - `a`: `integer` or `float`\r\n  - `b`: `integer` or `float`\r\n  \r\n- `(power a b)`\r\n  - Returns `a` to the power of `b`.\r\n  - `a`: `integer` or `float`\r\n  - `b`: `integer` or `float`\r\n\r\n- `(random)`\r\n  - Returns a random number from 0 to 1.\r\n\r\n### Control\r\n- `(loop count fn)`\r\n  - Applies `fn`, `count` times. If `fn` returns, the loop breaks, and `loop` returns whatever `fn` returned, else repeats until loop is completed.\r\n  - `count`: `integer`, which is greater than or equal to `0`\r\n  - `fn`: `function`, which is in the form `{n -\u003e ...}`, where n is the current loop index (starting at `0`).\r\n\r\n- `(until stop fn initial_state)` or `(until stop fn)`\r\n  - Applies `fn`, and repeats until `fn` returns `stop`. `until` returns whatever `fn` returned, before `stop`.\r\n  - 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.\r\n  - `fn`: `function`, which is in the form `{state n -\u003e ...}`, where n is the current loop index (starting at `0`), and `state` is the current state.\r\n\r\n- `(if condition1 fn1 condtion2 fn2 condtion3 fn3 ... fn_else)`\r\n  - If `condition1` is `1`, applies `fn1`.\r\n  - Else if `condition2` is `1`, applies `fn2`, else if ...\r\n  - If no condtions were `1`, applies `fn_else`.\r\n  - Return whatever the result of `fn1`, `fn2`, `fn3`, ..., or `fn_else` was.\r\n  - `condition1`, `condition2`, `condition3`, ...: `integer`, which is `1` or `0`\r\n  - `fn1`, `fn2`, `fn3`, ..., `fn_else`: `function`, which takes no arguments\r\n  \r\n- `(wait time)`\r\n  - Blocks execution for `time` amount of seconds.\r\n  - `time`: `integer` or `float`.\r\n\r\n### Types\r\n- `void`\r\n  - A value of type `void`\r\n\r\n- `(integer a)`\r\n  - Returns `a` as an `integer`.\r\n  - `a`: `string`, `float`, or `integer`.\r\n\r\n- `(string a)`\r\n  - Returns `a` as a `string`.\r\n  - `a`: `string`, `float`, or `integer`.\r\n\r\n- `(float a)`\r\n  - Returns `a` as a `float`.\r\n  - `a`: `string`, `float`, or `integer`.\r\n\r\n- `(type a)`\r\n  - Returns the type of `a` as a `string`.\r\n\r\n### List and String\r\n- `(list arg1 arg2 arg3 ...)`\r\n  - Returns a `list`, with the arguments as it's contents.\r\n\r\n- `(length x)`\r\n  - Returns the length of `x`\r\n  - `x`: `string` or `list`.\r\n\r\n- `(join arg1 arg2 arg3 ...)`\r\n  - Returns all args joined together.\r\n  - All args must be of the same type.\r\n  - `arg1`, `arg2`, `arg3`, ...: `string` or `list`.\r\n\r\n- `(get x index1)` or `(get x index1 index2)`\r\n  - Returns the item in `x` at `index1`. If x is a `string`, this is a single char.\r\n  - If `index2` is supplied, returns a sub-array or substring from `index1` to `index2`, not including `index2`.\r\n  - `x`: `string` or `list`.\r\n  - `index1`: `int`.\r\n  - `index2`: `int`.\r\n\r\n- `(insert x item)` or `(insert x item index)`\r\n  - Returns a `list` or `string`, in which `item` was inserted into `x` at `index`. Does not overwrite any data.\r\n  - If `index` not supplied, `item` is assumed to be put at the end of `x`.\r\n  - `x`: `string` or `list`.\r\n  - `item`: `string` if `x` is `string`, else any\r\n  - `index`: `int`.\r\n  \r\n- `(set x item index)`\r\n  - Returns a `list` or `string`, in which the item located at `index` in `x`, was replaced by `item`.Overwrites data.\r\n  - `x`: `string` or `list`.\r\n  - `item`: `string` if `x` is `string`, else any\r\n  - `index`: `int`.\r\n\r\n- `(delete x index1)` or `(delete x index1 index2)`\r\n  - Returns a `string` or `list`, where `index1` was removed from `x`.\r\n  - If `index2` is supplied, all items from `index1` to `index2` are removed, not including `index2`.\r\n  - `x`: `string` or `list`.\r\n  - `index1`: `int`.\r\n  - `index2`: `int`.\r\n\r\n- `(map arr fn)`\r\n  - Returns a list created by calling `fn` on every item of `arr`, and using the values returned by `fn` to populate the returned array.\r\n  - `arr`: `list`\r\n  - `fn`: `function`, which is in the form `{item i -\u003e ...}`, where `item` is the current item, and `i` is the current index.\r\n\r\n- `(reduce arr fn initial_acc)` or `(reduce arr fn)`\r\n  - 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`.\r\n  - `arr`: `list`.\r\n  - `fn`: `function`, which is in the form `{acc item i -\u003e ...}`, 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.\r\n\r\n- `(range n)`\r\n  - Returns a list with the integers from `0` to `n`, not including `n`.\r\n  - `n`: `integer`, which is greater than or equal to 0.\r\n\r\n- `(find x item)`\r\n  - Returns the index of `item` in `x`. Returns `void` if not found.\r\n  - `x`: `string` or `list`\r\n  - `item`: `string` if `x` is `string`, else any\r\n\r\n## Syntax\r\nCrumb 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.\r\n\r\n### EBNF\r\n```ebnf\r\nprogram = start, statement, end;\r\nstatement = {return | assignment | value};\r\nreturn = \"\u003c-\", value;\r\nassignment = identifier, \"=\", value;\r\nvalue = application | function | int | float | string | identifier;\r\napplication = \"(\", {value}, \")\";\r\nfunction = \"{\", [{identifier}, \"-\u003e\"], statement, \"}\";\r\n```\r\n\r\n![Syntax Diagram](./media/syntax-diagram.png)\r\n\r\n*Crumb syntax diagram, generated with [DrawGrammar](https://jacquev6.github.io/DrawGrammar/).*\r\n\r\n### Tokens\r\n```\r\n\"=\"\r\n\"(\"\r\n\")\"\r\n\"{\"\r\n\"}\"\r\n\"-\u003e\"\r\n\"\u003c-\"\r\nidentifier\r\nint\r\nfloat\r\nstring\r\nstart\r\nend\r\n```\r\n\r\n### Specifics\r\nStrings are characters surrounded by quotes, for example:\r\n```\r\n\"hello world\"\r\n\"this is\\nsplit between new lines\"\r\n\"\\e[31mthis text is in red\\e[0m\"\r\n```\r\n\r\nEscape codes in Crumb are equivalent to their respective C escape codes. The list of supported escape codes is:\r\n```\r\n\"\\a\"\r\n\"\\b\"\r\n\"\\f\"\r\n\"\\n\" \r\n\"\\r\"\r\n\"\\t\"\r\n\"\\v\"\r\n\"\\e\"\r\n\"\\\\\"\r\n\"\\\"\"\r\n\"\\x4d\" // for arbitrary ascii chars\r\n```\r\n\r\nIntegers are groups of number characters, that may be preceded by `-` for example:\r\n```\r\n1234\r\n-14\r\n345\r\n```\r\n\r\nFloats are like integers, but have a decimal in them, for example:\r\n```\r\n13.45\r\n-2.3\r\n745.0\r\n```\r\n\r\nIdentifiers 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:\r\n```\r\nhello\r\nx₂\r\nsymbol1\r\n+\r\n```\r\n\r\nComments start with \"//\", and end with the end of a line, for example:\r\n```\r\n// this is a program that prints hi\r\n(print \"hi\") // this prints hi\r\n```\r\n\r\n## Development\r\nTo identify the current interpreter version, use the `-v` flag.\r\n```bash\r\n./crumb -v\r\n```\r\n\r\nWhen debugging the interpreter, it may be useful to compile with the `-g` flag.\r\n```bash \r\ngcc src/*.c -g -Wall -lm -o crumb\r\n```\r\n\r\nThis will allow Valgrind to provide extra information,\r\n```bash\r\nvalgrind --leak-check=full -s ./crumb -d YOURCODE.crumb\r\n```\r\n\r\nTo obtain debug information about how your code is interpreted (Tokens, AST, etc.), add the `-d` flag.\r\n```bash\r\n./crumb -d YOURCODE.crumb\r\n```\r\n\r\n## Credit\r\n- Built by [Liam Ilan](https://www.liamilan.com/)","funding_links":[],"categories":["Uncategorized"],"sub_categories":["Uncategorized"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fliam-ilan%2Fcrumb","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fliam-ilan%2Fcrumb","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fliam-ilan%2Fcrumb/lists"}