-
Notifications
You must be signed in to change notification settings - Fork 11
Home
New to the Crema language? Start here! This wiki contains code samples, reference material, and general Crema topics to help you get started writing your own Crema programs.
Lets start by writing a simple program with Crema. Before proceeding, make sure that you have built the Crema compiler using the instructions on the Crema repo page.
First, create a directory named "programs" to house your Crema code. This directory can be located anywhere, but we will place it inside the Crema base directory for this example. Inside this directory, create a new file named hello.crema. Your directory structure should appear as follows:
- crema
- docs
- programs
- hello.crema
- src
- cremacc
- stdlib
- (etc.)
- tests
- vagrant
Next, open hello.crema with your favorite text editor, type the following, and then save the file:
str_println("Hello, Crema!")
Next, compile your program by navigating to crema/src, and running the command:
./cremacc -f ../programs/hello.crema -o hello
Finally, run your program using the command ./hello
. You should see the message "Hello, Crema!" printed to your command prompt.
You can also pass Crema code directly to cremacc from the command line by omitting the -f option. Try recreating the hello program using the command line only:
-
Run cremacc without the -f option:
./cremacc -o hello
-
Type the following directly into the command prompt: str_println("Hello, Crema!")
-
Hit ENTER
-
Finish passing code to cremacc by typing Ctrl+d
-
Run the generated program:
./hello
After you have successfully run the "Hello, Crema" programs, you should become aquainted with Crema's features by reading on to the section Crema Syntax. Once you are ready to start writing your own Crema programs, check out the section Crema Standard Library to learn about all of the built-in functions that Crema has to offer. If you are more inclined to learn Crema by example, check out the code samples near the end of this wiki.
Crema is designed in the popular procedural paradigm, following a number of C styles while adopting some newer features from more modern languages like Python and Ruby. Crema code is written without the use of semicolons or end-of-line characters. White space is completely ignored by the compiler.
You can make comments in a Crema program using the #
symbol:
# This comment will be ignored by the compiler
Crema uses the following data types for variables and/or return values
Type | Description |
---|---|
int | A 64-bit signed integer value |
double | A 64-bit signed floating-point decimal value |
char | A single character |
string | A string of characters |
bool | A boolean value, "true" or "false" |
struct | A C-like structure |
void | Return type for functions that do not return a value |
Variables can be declared anywhere in a Crema file like this:
int x
Or defined with a value like this:
bool b = true
Crema is a statically typed language, meaning that you are not permitted to change the data type of a variable at run time:
int number = 10
number = 12
number = "String" # <-- Error
There are two data types in Crema that represent numbers: int
and double
int x = 2
double d = 5.0
Crema automatically casts values from an int to a double for you:
double d = 2 # <-- d == 2.0
Crema strings can be defined using double-quotes around a string literal:
string str = "Hello, World"
You can print strings using the standard lib str_print()
and str_println()
functions:
string str1 = "Hello, "
string str2 = "World"
str_print(str1)
str_println(str2)
# Outputs: "Hello, World" (str_print() doesn't trigger a new line)
Characters can be defined with single quotes
char c = 'a'
Currently, Crema does not support escape characters or formatted printing. For example, the following will print the given string literal exactly as it is written:
str_println("escape \n characters \t\t not supported") # <-- This will print literally
Crema uses the lower-case keywords true
and false
to define boolean values
boolean b1 = true
boolean b2 = false
Structures can be defined to build more complex data types:
# Declare a new person structure
struct person{
string name,
int age
}
# Create a person structure
struct person p1
p1.name = John
p1.age = 26
p1.age = p1.age + 10
Crema arrays can be defined for the following data types...
int array1[] = [1, 2, 3, 4, 5]
char array2[] = ['a', 'l', 'l']
double array3[] = [1.0, 1.2, 1.3]
string array4[] = ["bob", "alice"]
bool array5[] = [true, false]
# ...
... and you can access elements of an array like this:
# ...
int x = array1[0] # <-- x == 1
array2[2] = 'e' # <-- array2 == ['a', 'l', 'e']
Crema supports the following mathematical operators for numerical data types:
int x = 2
int y = 3
int result
result = x + y # result == 5
result = x - y # result == -1
result = x * y # result == 6
result = y / x # result == 1
result = y % x # result == 1
result = x | y # result == 3
result = x & y # result == 2
result = x ^ y # result == 1
Crema supports the following logical operators:
bool b1 = true
bool b2 = false
int x = 2
int y = 3
bool result
result = b1 && b2 # result == false
result = b1 || b2 # result == true
result = x == y # result == false
result = x != y # result == true
result = x > y # result == false
result = x < y # result == true
result = x >= y # result == false
result = x <= y # result == true
The following common operators are currently NOT supported by Crema:
x++
x--
~x
x >> 1
x << 1
(x == 5) ? 1 : 10
Crema supports standard if, else if, and else statements for conditional processing
int a = 1
if(a > 0){
str_println("a > 0")
}else if(a == 0){
str_println("a == 0")
}else{
str_println("a < 0")
}
The common switch
expression is currently NOT supported
The only type of loop supported by Crema is the foreach
loop. This loop is used to sequentially iterate over each element of a list. Crema specifically does not support other common loops (such as while or for loops) so that Crema programs are forced to operate in sub-Turing Complete space. In other words, foreach loops always have a defined upper bound on the number of iterations possible during the loop's execution. This makes it impossible to write a loop that could execute for an arbitrary length of time.
char letters[] = ['C', 'r', 'e', 'm', 'a', ' ', 'R', 'o', 'c', 'k', 's', '!']
# print the letters (Crema Rocks!)
foreach(letters as i){
str_print(i)
}
Functions in Crema can be defined using the syntax: def (){}
All functions must have either a return type, or specify void
if no value is returned.
For example:
def int foo(){
return 0;
}
def void bar(){
}
You can pass parameters to functions like this:
def int add(int a, int b){
return a + b
}
def void printString(string str){
str_println(str)
}
# ...
... And call a function like this:
# ...
int x = add(3, 2)
printString("Hello, World")
You can include code from external .crema files into your program with the include
statement. This statement will take the contents of a specified file and insert it directly into a Crema program at the same line. For example, suppose you have two Crema files as follows:
foo.crema
str_print("Foo ")
bar.crema
str_println("Bar")
Then, the following Crema programs are identical, and will both print "Foo Bar":
include foo.crema
include bar.crema
str_print("Foo ")
str_println("Bar")
Variables in Crema follow C-like lexical scope rules. Variables can be defined globally (accessible by all Crema functions) or locally within a function. Variables defined locally within a function take precedence over global variables that have the same name.
int x = 5 # <-- Global Variable
int y
def int foo(){
int x = 10 # <-- Local Variable
return x # <-- Local variable x equals 10, x in global scope is unchanged
}
def int bar(){
return x # <-- Inherits x from global scope
}
y = foo() # <-- y == 10
y = bar() # <-- y == 5
Functions in Crema may only be defined in global scope. Nested functions definitions are not permitted:
def void foo(){ # <-- Good
str_println("This is how to define a function in Crema")
}
def void bar(){
def void foo2(){ # <-- Bad. This is not valid in Crema
str_println("This nested function will cause a complier error")
}
}
Crema supports a small standard library of functions that cover a variety of basic programming tasks. You can call any of the functions from this library in your Crema programs.
Function | Return Type | Description |
---|---|---|
str_print(string s) | void | Prints a string to stdout |
str_println(string s) | void | Prints a string to stdout terminated with a new-line character |
int_print(int i) | void | Prints an int to stdout |
int_println(int i) | void | Prints an int to stdout terminated with a new-line character |
double_print(double d) | void | Prints a double to stdout |
double_println(double val) | void | Prints a double to stdout terminated with a new-line character |
prog_arg_count() | int | Returns the number of command line arguments passed to the running Crema program |
prog_argument(int i) | string | Returns the string representation of a command line argument at a given postion i from the command line arguments list passed to the running Crema program |
Function | Return Type | Description |
---|---|---|
double_to_int(double d) | int | Casts the given double value as an int |
int_to_double(int i) | double | Casts the given int value as an double |
string_to_int(string str) | int | Parses given string to an int value, or returns 0 if the string cannot be parsed as an int |
string_to_double(string str) | double | Parses given string to a double value, or returns 0 if the string cannot be parsed as a double |
Function | Return Type | Description |
---|---|---|
double_floor(double d) | double | Returns the nearest integral value less than the given number |
double_ceiling(double d) | double | Returns the nearest integral value greater than the given number |
double_truncate(double d) | double | Truncates the given double value |
double_round(double d) | double | Rounds a double to the nearest integer value |
double_square(double d) | double | Returns the square of the given double value |
int_square(int i) | int | Returns the square of the given int value |
double_pow(double base, double power) | double | Returns the double value of base raised to the power power |
int_pow(int base, int power) | int | Returns the integer value of base raised to the power power |
double_sqrt(double val) | double | Returns the square root of the given double value |
double_abs(double val) | double | Returns the absolute value of the given double value |
int_abs(int val) | int | Returns the absolute value of the given int value |
double_sin(double d) | double | Returns sin of the given double value |
double_cos(double d) | double | Return cos of the given double value |
double_tan(double d) | double | Returns tan of the given double value |
The following demonstrates the classic "FizBuz" printing function. This program defines a single function called fizBuz that accepts one int argument passed in from the command line. If the argument is divisible by 3, "Fiz" gets printed. If the argument is divisible by 5, "Buz" gets printed. If the argument is divisible by both 3 and 5, "FizBuz" gets printed.
def void fizBuz(int a){
if(a%3 == 0){
str_print("Fiz")
}
if(a%5 == 0){
str_print("Buz")
}
str_println("")
}
int argc = prog_arg_count()
if(argc < 2){
str_println("Usage: Type a number to be fiz'ed and/or buz'ed")
}else{
int input = string_to_int(prog_argument(1))
if(input <= 0){
str_println("Please type a valid number greater than 0")
}else{
fizBuz(input)
}
}
The following demonstrates a Crema implementation of the classic bubble sort algorithm. The program creates an unsorted list of integers, and then prints the list after each iteration of the sorting algorithm completes. Since Crema only allows the use of a foreach loop, note the use of iterator variables in the global scope of this program. With Crema, you must manually track the position of an element in an array during execution of a foreach loop. Also notice the use of extra variables to access elements in an array by an offset. With Crema, you must explicitly access elements of an array with a fixed value, and not with an expression (i.e. array[2] is legal, array[2+1] is not).
NOTE: This program will not work correctly until an open bug related to nested loops is fixed.
int a = 0
int b = 0
int i = 0
int j = 0
int values[] = [6, 3, 8, 7, 2, 1, 4, 9, 0, 5]
int upperBound = 9 # <-- length of values - 1
foreach(values as itteration)
{
str_println("itteration started")
foreach(values as element)
{
if(i < upperBound)
{
j = i + 1
a = values[i]
b = values[j]
if(a > b)
{
values[i] = b
values[j] = a
}
}
int_print(values[i])
i = i + 1
}
str_println("")
upperBound = upperBound - 1
i = 0
}
The following demonstrates a Crema implementation of a binary search algorithm.
def int binarySearch(int values[], int searchTarget){
int upperBound = list_length(values) - 1 # Upper index of seach region
int lowerBound = 0 # Lower index of seach region
int delta = list_length(values) # Distance between upperBound and lowerBound
int middleValueIndex = 0 # Mid-point index between upper and lower bounds
int middleValue = 0 # Value at the mid-point index
int foundIndex = -1 # The index of the target number after finding
foreach(values as value){
# Check middle value to see if it matches target number
middleValueIndex = ((upperBound + lowerBound) / 2)
middleValue = values[middleValueIndex]
if(middleValue == searchTarget){
foundIndex = middleValueIndex
break
}
#Re-adjust the lower and upper bounds for next itteration
if(middleValue >= searchTarget){
upperBound = middleValueIndex - 1
}else{
lowerBound = middleValueIndex + 1
}
delta = upperBound - lowerBound
}
return foundIndex
}
*This research was developed with funding from the Defense Advanced Research Projects Agency (DARPA). Distribution Statement "A" (Approved for Public Release, Distribution Unlimited)