Crumb is a high level, functional, interpreted, dynamically typed, general-purpose programming language, with a terse syntax, and a verbose standard library.
It features:
*With the exception of IO
Click here to Get Started.
table = (map (range 10) {_ y ->
<- (map (range 10) {item x ->
<- (multiply (add x 1) (add y 1))
})
})
From examples/mult-table.crumb
From examples/game-of-life.crumb
Find more examples under the examples directory.
Crumb can also build standalone binaries through Loaf.
You do not need to clone this repo. Instead, follow the instructions in this template repo.
If you are on VSCode, you can install the Crumb syntax highlighter extension. The source for the extension can be found here.
All function calls are done with s-expressions (think lisp). For example,
(print "hello world")
In this case, the function print is applied with the string "hello world" as an argument.
All data in crumb is one of 6 different types:
stringintegerfloatfunction / native functionlistvoidWe can store this data in variables, for example,
a = 5
b = "hello"
We can combine data together to form lists,
magic_list = (list 123 "hello" 42.0)
Lists are always passed by value.
We can encapsulate code in functions using curly braces,
f = {
(print "Funky!")
}
(f) // prints "Funky"
Functions can get arguments, denoted using the "->" symbol. For example,
add_two_things = {a b ->
(print (add a b))
}
(add_two_things 3 5) // prints 8
They can also return values using the "<-" symbol,
geometric_mean = {a b ->
<- (power (multiply a b) 0.5)
}
(print (geometric_mean 3 5) "\n") // prints 3.87...
Functions operate in a few important ways:
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,
(loop 100 {i ->
i = (add i 1)
(if (is (remainder i 15) 0) {
(print "fizzbuzz\n")
} (is (remainder i 3) 0) {
(print "fizz\n")
} (is (remainder i 5) 0) {
(print "buzz\n")
} {(print i "\n")}
)
})
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.
arguments
(print arg1 arg2 arg3 ...)
(input)
(rows)
(columns)
(read_file path)
path, in a string. If the file cannot be read, returns void.path: string(write_file path contents)
contents into the file designated by path, returns nothing.path: stringcontents: string(event time) or (event)
time seconds, rounded up to the nearest 100 ms. If no time is supplied, the function will not return before receiving an event.time: integer or float(use path1 path2 path3 ... fn)
fn.path1, path2, path3, ...: stringfn: function(shell command)
command as an sh program in a seperate process, and returns stdout of the process as a string.command: string(is a b)
a and b are equal, returns 1 if so, else returns 0. If a and b are lists, a deep comparison is made.(less_than a b)
a is less than b, returns 1 if so, else returns 0.a: integer or floatb: integer or float(greater_than a b)
a is greater than b, returns 1 if so, else returns 0.a: integer or floatb: integer or float(not a)
0 if a is 1, and 1 if a is 0.a: integer, which is 1 or 0(and arg1 arg2 arg3 ...)
1 if all arguments are 1, else returns 0arg1, arg2, arg3, ...: integer, which is 1 or 0(or arg1 arg2 arg3 ...)
1 if at least one argument is 1, else returns 0arg1, arg2, arg3, ...: integer, which is 1 or 0(add arg1 arg2 arg3 ...)
arg1 + arg2 + arg3 + ...arg1, arg2, arg3, ...: integer or float(subtract arg1 arg2 arg3 ...)
arg1 - arg2 - arg3 - ...arg1, arg2, arg3, ...: integer or float(divide arg1 arg2 arg3 ...)
arg1 / arg2 / arg3 / ...arg1, arg2, arg3, ...: integer or float(multiply arg1 arg2 arg3 ...)
arg1 * arg2 * arg3 * ...arg1, arg2, arg3, ...: integer or float(remainder a b)
a and b.a: integer or floatb: integer or float(power a b)
a to the power of b.a: integer or floatb: integer or float(random)
(loop count fn)
fn, count times. If fn returns, the loop breaks, and loop returns whatever fn returned, else repeats until loop is completed.count: integer, which is greater than or equal to 0fn: function, which is in the form {n -> ...}, where n is the current loop index (starting at 0).(until stop fn initial_state) or (until stop fn)
fn, and repeats until fn returns stop. until returns whatever fn returned, before stop.initial_state if supplied, or returns void if not.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.(if condition1 fn1 condtion2 fn2 condtion3 fn3 ... fn_else)
condition1 is 1, applies fn1.condition2 is 1, applies fn2, else if ...1, applies fn_else.fn1, fn2, fn3, ..., or fn_else was.condition1, condition2, condition3, ...: integer, which is 1 or 0fn1, fn2, fn3, ..., fn_else: function, which takes no arguments(wait time)
time amount of seconds.time: integer or float.void
void(integer a)
a as an integer.a: string, float, or integer.(string a)
a as a string.a: string, float, or integer.(float a)
a as a float.a: string, float, or integer.(type a)
a as a string.(list arg1 arg2 arg3 ...)
list, with the arguments as it's contents.(length x)
xx: string or list.(join arg1 arg2 arg3 ...)
arg1, arg2, arg3, ...: string or list.(get x index1) or (get x index1 index2)
x at index1. If x is a string, this is a single char.index2 is supplied, returns a sub-array or substring from index1 to index2, not including index2.x: string or list.index1: int.index2: int.(insert x item) or (insert x item index)
list or string, in which item was inserted into x at index. Does not overwrite any data.index not supplied, item is assumed to be put at the end of x.x: string or list.item: string if x is string, else anyindex: int.(set x item index)
list or string, in which the item located at index in x, was replaced by item.x: string or list.item: string if x is string, else anyindex: int.(delete x index1) or (delete x index1 index2)
string or list, where index1 was removed from x.index2 is supplied, all items from index1 to index2 are removed, not including index2.x: string or list.index1: int.index2: int.(map arr fn)
fn on every item of arr, and using the values returned by fn to populate the returned array.arr: listfn: function, which is in the form {item i -> ...}, where item is the current item, and i is the current index.(reduce arr fn initial_acc) or (reduce arr fn)
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.arr: list.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.(range n)
0 to n, not including n.n: integer, which is greater than or equal to 0.(find x item)
item in x. Returns void if not found.x: string or listitem: string if x is string, else anyCrumb 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.
program = start, statement, end;
statement = {return | assignment | value};
return = "<-", value;
assignment = identifier, "=", value;
value = application | function | int | float | string | identifier;
application = "(", {value}, ")";
function = "{", [{identifier}, "->"], statement, "}";

Crumb syntax diagram, generated with DrawGrammar.
"="
"("
")"
"{"
"}"
"->"
"<-"
identifier
int
float
string
start
end
Strings are characters surrounded by quotes, for example:
"hello world"
"this is\nsplit between new lines"
"\e[31mthis text is in red\e[0m"
Escape codes in Crumb are equivalent to their respective C escape codes. The list of supported escape codes is:
"\a"
"\b"
"\f"
"\n"
"\r"
"\t"
"\v"
"\e"
"\\"
"\""
"\x4d" // for arbitrary ascii chars
Integers are groups of number characters, that may be preceded by - for example:
1234
-14
345
Floats are like integers, but have a decimal in them, for example:
13.45
-2.3
745.0
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:
hello
x₂
symbol1
+
Comments start with "//", and end with the end of a line, for example:
// this is a program that prints hi
(print "hi") // this prints hi
To identify the current interpreter version, use the -v flag.
./crumb -v
When debugging the interpreter, it may be useful to compile with the -g flag.
gcc src/*.c -g -Wall -lm -o crumb
This will allow Valgrind to provide extra information,
valgrind --leak-check=full -s ./crumb -d YOURCODE.crumb
On mac, you can use leaks,
leaks -atExit -- ./crumb YOURCODE.crumb
To obtain debug information about how your code is interpreted (Tokens, AST, etc.), add the -d flag.
./crumb -d YOURCODE.crumb
You can also pipe code straight into crumb (passed files always take priority over piped code).
echo '(print (add 1 2) "\\n")' | ./crumb
Note that piping and using the event function is undefined/unsupported.
Loaf derives it's templating from loaf-template/template.crumb. For Loaf to function correctly, this template must have no dependencies, and expose the following function:
(template_main entry used_files)
main.c to substitute with src/main.c in standalone builds.entry: string, the entry point path of the Crumb program.used_files: list of string, a list of paths to Crumb files in the project.138 commits
C
100.0%
Crumb is a high level, functional, interpreted, dynamically typed, general-purpose programming language, with a terse syntax, and a verbose standard library.
It features:
*With the exception of IO
Click here to Get Started.
table = (map (range 10) {_ y ->
<- (map (range 10) {item x ->
<- (multiply (add x 1) (add y 1))
})
})
From examples/mult-table.crumb
From examples/game-of-life.crumb
Find more examples under the examples directory.
Crumb can also build standalone binaries through Loaf.
You do not need to clone this repo. Instead, follow the instructions in this template repo.
If you are on VSCode, you can install the Crumb syntax highlighter extension. The source for the extension can be found here.
All function calls are done with s-expressions (think lisp). For example,
(print "hello world")
In this case, the function print is applied with the string "hello world" as an argument.
All data in crumb is one of 6 different types:
stringintegerfloatfunction / native functionlistvoidWe can store this data in variables, for example,
a = 5
b = "hello"
We can combine data together to form lists,
magic_list = (list 123 "hello" 42.0)
Lists are always passed by value.
We can encapsulate code in functions using curly braces,
f = {
(print "Funky!")
}
(f) // prints "Funky"
Functions can get arguments, denoted using the "->" symbol. For example,
add_two_things = {a b ->
(print (add a b))
}
(add_two_things 3 5) // prints 8
They can also return values using the "<-" symbol,
geometric_mean = {a b ->
<- (power (multiply a b) 0.5)
}
(print (geometric_mean 3 5) "\n") // prints 3.87...
Functions operate in a few important ways:
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,
(loop 100 {i ->
i = (add i 1)
(if (is (remainder i 15) 0) {
(print "fizzbuzz\n")
} (is (remainder i 3) 0) {
(print "fizz\n")
} (is (remainder i 5) 0) {
(print "buzz\n")
} {(print i "\n")}
)
})
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.
arguments
(print arg1 arg2 arg3 ...)
(input)
(rows)
(columns)
(read_file path)
path, in a string. If the file cannot be read, returns void.path: string(write_file path contents)
contents into the file designated by path, returns nothing.path: stringcontents: string(event time) or (event)
time seconds, rounded up to the nearest 100 ms. If no time is supplied, the function will not return before receiving an event.time: integer or float(use path1 path2 path3 ... fn)
fn.path1, path2, path3, ...: stringfn: function(shell command)
command as an sh program in a seperate process, and returns stdout of the process as a string.command: string(is a b)
a and b are equal, returns 1 if so, else returns 0. If a and b are lists, a deep comparison is made.(less_than a b)
a is less than b, returns 1 if so, else returns 0.a: integer or floatb: integer or float(greater_than a b)
a is greater than b, returns 1 if so, else returns 0.a: integer or floatb: integer or float(not a)
0 if a is 1, and 1 if a is 0.a: integer, which is 1 or 0(and arg1 arg2 arg3 ...)
1 if all arguments are 1, else returns 0arg1, arg2, arg3, ...: integer, which is 1 or 0(or arg1 arg2 arg3 ...)
1 if at least one argument is 1, else returns 0arg1, arg2, arg3, ...: integer, which is 1 or 0(add arg1 arg2 arg3 ...)
arg1 + arg2 + arg3 + ...arg1, arg2, arg3, ...: integer or float(subtract arg1 arg2 arg3 ...)
arg1 - arg2 - arg3 - ...arg1, arg2, arg3, ...: integer or float(divide arg1 arg2 arg3 ...)
arg1 / arg2 / arg3 / ...arg1, arg2, arg3, ...: integer or float(multiply arg1 arg2 arg3 ...)
arg1 * arg2 * arg3 * ...arg1, arg2, arg3, ...: integer or float(remainder a b)
a and b.a: integer or floatb: integer or float(power a b)
a to the power of b.a: integer or floatb: integer or float(random)
(loop count fn)
fn, count times. If fn returns, the loop breaks, and loop returns whatever fn returned, else repeats until loop is completed.count: integer, which is greater than or equal to 0fn: function, which is in the form {n -> ...}, where n is the current loop index (starting at 0).(until stop fn initial_state) or (until stop fn)
fn, and repeats until fn returns stop. until returns whatever fn returned, before stop.initial_state if supplied, or returns void if not.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.(if condition1 fn1 condtion2 fn2 condtion3 fn3 ... fn_else)
condition1 is 1, applies fn1.condition2 is 1, applies fn2, else if ...1, applies fn_else.fn1, fn2, fn3, ..., or fn_else was.condition1, condition2, condition3, ...: integer, which is 1 or 0fn1, fn2, fn3, ..., fn_else: function, which takes no arguments(wait time)
time amount of seconds.time: integer or float.void
void(integer a)
a as an integer.a: string, float, or integer.(string a)
a as a string.a: string, float, or integer.(float a)
a as a float.a: string, float, or integer.(type a)
a as a string.(list arg1 arg2 arg3 ...)
list, with the arguments as it's contents.(length x)
xx: string or list.(join arg1 arg2 arg3 ...)
arg1, arg2, arg3, ...: string or list.(get x index1) or (get x index1 index2)
x at index1. If x is a string, this is a single char.index2 is supplied, returns a sub-array or substring from index1 to index2, not including index2.x: string or list.index1: int.index2: int.(insert x item) or (insert x item index)
list or string, in which item was inserted into x at index. Does not overwrite any data.index not supplied, item is assumed to be put at the end of x.x: string or list.item: string if x is string, else anyindex: int.(set x item index)
list or string, in which the item located at index in x, was replaced by item.x: string or list.item: string if x is string, else anyindex: int.(delete x index1) or (delete x index1 index2)
string or list, where index1 was removed from x.index2 is supplied, all items from index1 to index2 are removed, not including index2.x: string or list.index1: int.index2: int.(map arr fn)
fn on every item of arr, and using the values returned by fn to populate the returned array.arr: listfn: function, which is in the form {item i -> ...}, where item is the current item, and i is the current index.(reduce arr fn initial_acc) or (reduce arr fn)
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.arr: list.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.(range n)
0 to n, not including n.n: integer, which is greater than or equal to 0.(find x item)
item in x. Returns void if not found.x: string or listitem: string if x is string, else anyCrumb 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.
program = start, statement, end;
statement = {return | assignment | value};
return = "<-", value;
assignment = identifier, "=", value;
value = application | function | int | float | string | identifier;
application = "(", {value}, ")";
function = "{", [{identifier}, "->"], statement, "}";

Crumb syntax diagram, generated with DrawGrammar.
"="
"("
")"
"{"
"}"
"->"
"<-"
identifier
int
float
string
start
end
Strings are characters surrounded by quotes, for example:
"hello world"
"this is\nsplit between new lines"
"\e[31mthis text is in red\e[0m"
Escape codes in Crumb are equivalent to their respective C escape codes. The list of supported escape codes is:
"\a"
"\b"
"\f"
"\n"
"\r"
"\t"
"\v"
"\e"
"\\"
"\""
"\x4d" // for arbitrary ascii chars
Integers are groups of number characters, that may be preceded by - for example:
1234
-14
345
Floats are like integers, but have a decimal in them, for example:
13.45
-2.3
745.0
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:
hello
x₂
symbol1
+
Comments start with "//", and end with the end of a line, for example:
// this is a program that prints hi
(print "hi") // this prints hi
To identify the current interpreter version, use the -v flag.
./crumb -v
When debugging the interpreter, it may be useful to compile with the -g flag.
gcc src/*.c -g -Wall -lm -o crumb
This will allow Valgrind to provide extra information,
valgrind --leak-check=full -s ./crumb -d YOURCODE.crumb
On mac, you can use leaks,
leaks -atExit -- ./crumb YOURCODE.crumb
To obtain debug information about how your code is interpreted (Tokens, AST, etc.), add the -d flag.
./crumb -d YOURCODE.crumb
You can also pipe code straight into crumb (passed files always take priority over piped code).
echo '(print (add 1 2) "\\n")' | ./crumb
Note that piping and using the event function is undefined/unsupported.
Loaf derives it's templating from loaf-template/template.crumb. For Loaf to function correctly, this template must have no dependencies, and expose the following function:
(template_main entry used_files)
main.c to substitute with src/main.c in standalone builds.entry: string, the entry point path of the Crumb program.used_files: list of string, a list of paths to Crumb files in the project.138 commits
C
100.0%