Crumb is a high level, functional, interpreted, dynamically typed, general-purpose programming language, with a terse syntax, and a verbose standard library.
It features:
- Strictly no side effects* to help you write functional code
- The ability to localize the effects of imported Crumb files.
- Dynamic typing and garbage collection.
- 0 keywords, everything is a function.
*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 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"
"\\"
"\""
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
For a quick start, you can use Replit, by forking this repl.
First, clone this repo,
git clone https://github.com/liam-ilan/crumb.git
Then compile with gcc,
gcc src/*.c -Wall -lm -o crumb
Or with clang,
clang src/*.c -Wall -lm -o crumb
From there, run a quick example,
./crumb examples/car.crumb
Or run your own code,
./crumb YOURCODE.crumb
The Crumb interpreter is built for POSIX compliant systems, and utilizes
ioctl.h
andunistd.h
. To use Crumb on windows, either use WSL, or use a Linux container.
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:
string
integer
float
function
/native function
list
void
We 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:
- Function applications are dynamically scoped.
- Functions cannot create side effects.
- Like in JavaScript and Python, functions can be passed into other functions as arguments.
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")
} {
(if (is (remainder i 3) 0) {
(print "fizz\n")
} {
(if (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
- A list command line arguments, like argv in C.
-
(print arg1 arg2 arg3 ...)
- Prints all arguments to stdout, returns nothing.
-
(input)
- Gets a line of input from stdin.
-
(rows)
- Returns the number of rows in the terminal.
-
(columns)
- Returns the number of columns in the terminal.
-
(read_file path)
- Returns the contents of the file designated by
path
, in a string path
:string
- Returns the contents of the file designated by
-
(write_file path contents)
- Writes the string
contents
into the file designated bypath
, returns nothing. path
:string
contents
:string
- Writes the string
-
(event)
- Returns the ANSI string corresponding with the current event. This may block for up to 0.1 seconds.
-
(use path1 path2 path3 ... fn)
- Crumb's code splitting method. Runs code in file paths, in order, on a new scope. Then uses said scope to apply
fn
. path1
,path2
,path3
, ...:string
fn
:function
- Crumb's code splitting method. Runs code in file paths, in order, on a new scope. Then uses said scope to apply
-
(is a b)
- Checks if
a
andb
are equal, returns1
if so, else returns0
. Ifa
andb
are lists, a deep comparison is made.
- Checks if
-
(less_than a b)
- Checks if
a
is less thanb
, returns1
if so, else returns0
. a
:integer
orfloat
b
:integer
orfloat
- Checks if
-
(greater_than a b)
- Checks if
a
is greater thanb
, returns1
if so, else returns0
. a
:integer
orfloat
b
:integer
orfloat
- Checks if
-
(not a)
- Returns
0
ifa
is1
, and1
ifa
is0
. a
:integer
, which is1
or0
- Returns
-
(and a b)
- Returns
1
ifa
andb
are both1
, else returns0
a
:integer
, which is1
or0
b
:integer
, which is1
or0
- Returns
-
(or a b)
- Returns
1
ifa
orb
are1
, else returns0
a
:integer
, which is1
or0
b
:integer
, which is1
or0
- Returns
-
(add arg1 arg2 arg3 ...)
- Returns
arg1
+arg2
+arg3
+ ... - Requires a minimum of two args
arg1
,arg2
,arg3
, ...:integer
orfloat
- Returns
-
(subtract arg1 arg2 arg3 ...)
- Returns
arg1
-arg2
-arg3
- ... - Requires a minimum of two args
arg1
,arg2
,arg3
, ...:integer
orfloat
- Returns
-
(divide arg1 arg2 arg3 ...)
- Returns
arg1
/arg2
/arg3
/ ... - Requires a minimum of two args
arg1
,arg2
,arg3
, ...:integer
orfloat
- Returns
-
(multiply arg1 arg2 arg3 ...)
- Returns
arg1
*arg2
*arg3
* ... - Requires a minimum of two args
arg1
,arg2
,arg3
, ...:integer
orfloat
- Returns
-
(remainder a b)
- Returns the remainder of
a
andb
. a
:integer
orfloat
b
:integer
orfloat
- Returns the remainder of
-
(power a b)
- Returns
a
to the power ofb
. a
:integer
orfloat
b
:integer
orfloat
- Returns
-
(random)
- Returns a random number from 0 to 1.
-
(loop count fn)
- Applies
fn
,count
times. Iffn
returns, the loop breaks, andloop
returns whateverfn
returned, else repeats until loop is completed. count
:integer
, which is greater than or equal to0
fn
:function
, which is in the form{n -> ...}
, where n is the current loop index (starting at0
).
- Applies
-
(until stop fn initial_state)
or(until stop fn)
- Applies
fn
, and repeats untilfn
returnsstop
.until
returns whateverfn
returned, beforestop
. - The return value of every past iteration is passed on to the next. The initial iteration uses
initial_state
if supplied, or returnsvoid
if not. fn
:function
, which is in the form{state n -> ...}
, where n is the current loop index (starting at0
), andstate
is the current state.
- Applies
-
(if condition fn1)
or(if condition fn1 fn2)
- If
condition
is1
, appliesfn1
. (like the "then" part in an if statement). - If
condition
is0
, andfn2
was supplied, applyfn2
. (the "else" part in an if statement). - Returns whatever
fn1
orfn2
return condition
:integer
, which is1
or0
fn1
:function
, which takes no argumentsfn2
:function
, which takes no arguments
- If
-
(wait time)
- Blocks execution for
time
amount of seconds. time
:integer
orfloat
.
- Blocks execution for
-
void
- A value of type
void
- A value of type
-
(integer a)
- Returns
a
as aninteger
. a
:string
,float
, orinteger
.
- Returns
-
(string a)
- Returns
a
as astring
. a
:string
,float
, orinteger
.
- Returns
-
(float a)
- Returns
a
as afloat
. a
:string
,float
, orinteger
.
- Returns
-
(type a)
- Returns the type of
a
as astring
.
- Returns the type of
-
(list arg1 arg2 arg3 ...)
- Returns a
list
, with the arguments as it's contents.
- Returns a
-
(length x)
- Returns the length of
x
x
:string
orlist
.
- Returns the length of
-
(join arg1 arg2 arg3 ...)
- Returns all args joined together.
- All args must be of the same type.
arg1
,arg2
,arg3
, ...:string
orlist
.
-
(get x index1)
or(get x index1 index2)
- Returns the item in
x
atindex1
. If x is astring
, this is a single char. - If
index2
is supplied, returns a sub-array or substring fromindex1
toindex2
, not includingindex2
. x
:string
orlist
.index1
:int
.index2
:int
.
- Returns the item in
-
(insert x item)
or(insert x item index)
- Returns a
list
orstring
, in whichitem
was inserted intox
atindex
. Does not overwrite any data. - If
index
not supplied,item
is assumed to be put at the end ofx
. x
:string
orlist
.item
:string
ifx
isstring
, else anyindex
:int
.
- Returns a
-
(set x item index)
- Returns a
list
orstring
, in which the item located atindex
inx
, was replaced byitem
.Overwrites data. x
:string
orlist
.item
:string
ifx
isstring
, else anyindex
:int
.
- Returns a
-
(delete x index1)
or(delete x index1 index2)
- Returns a
string
orlist
, whereindex1
was removed fromx
. - If
index2
is supplied, all items fromindex1
toindex2
are removed, not includingindex2
. x
:string
orlist
.index1
:int
.index2
:int
.
- Returns a
-
(map arr fn)
- Returns a list created by calling
fn
on every item ofarr
, and using the values returned byfn
to populate the returned array. arr
:list
fn
:function
, which is in the form{item i -> ...}
, whereitem
is the current item, andi
is the current index.
- Returns a list created by calling
-
(reduce arr fn initial_acc)
or(reduce arr fn)
- Returns a value, computed via running
fn
on every item inarr
. With every iteration, the last return fromfn
is passed to the next application offn
. The final returned value fromfn
is the value returned fromreduce
. arr
:list
.fn
:function
, which is in the form{acc item i -> ...}
, whereitem
is the current item,acc
is the accumulator (the result offn
from the last item), andi
is the current index.acc
isinitial_acc
if supplied, orvoid
if not.
- Returns a value, computed via running
-
(range n)
- Returns a list with the integers from
0
ton
, not includingn
. n
:integer
, which is greater than or equal to 0.
- Returns a list with the integers from
-
(find x item)
- Returns the index of
item
inx
. Returnsvoid
if not found. x
:string
orlist
item
:string
ifx
isstring
, else any
- Returns the index of
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
To obtain debug information about how your code is interpreted (Tokens, AST, etc.), add the -d
flag.
./crumb -d YOURCODE.crumb
- Built by Liam Ilan