Citrine Programming Language

-- Because someone has to read your code --

Welcome to the Citrine® Programming Language Website. Here you'll find the reference manual page (API), the roadmap and updates about the progress of the language.

Citrine is an easy-to-learn general purpose programming language for UNIX®-like operating systems. The Citrine Programming Language focuses on simplicity and emphasizes maintainability through readability. Simplicity is achieved by syntactical and conceptual minimalism. It's fully open source, licensed BSD:

Citrine 0.3 OpenBSD Binary Distribution AMD64 TARGZ
Citrine Latest Source Code (github).

View Test Reports

discuss on Google Groups discuss on reddit

What does it look like?

A slightly over-engineered 'hello world' program:

Butler := Object new.
Butler on: 'greet:' do: { name |
	Pen write: 'Welcome ' + name.
}.
james := Butler new.
james greet: 'visitor'.

This program will generate the following output:

Welcome visitor

Obviously this code could have been written much simpler, but that would reveal less of the language.

Features

Citrine combines lots of ideas from various programming languages like Ruby, Smalltalk, JavaScript and C.

The basics

In Citrine, everything is an object. You write a program by making these objects talk to each other, by sending messages.

There are 5 literal objects:

Strings begin and end with a single quote (').

This is what a comment looks like:

# Hello this line is a comment, because
# it starts with a #.

There are three kinds of messages. A unary message is a message without any arguments:

3 factorial.

Here we send the message 'factorial' to 3, this expression will return 6 ( 3 * 2 * 1 ). Note that each line of code should end with a period (.) . You can chain messages like this:

3 factorial factorial.

This line will first send the message 'factorial' to the number 3. Then, it will send the message 'factorial' to the result of the previous operation (6), the final result will be 720.

A binary message uses infix notation:

3 + 7.

This code sends the message '+ 7' to 3, the result will be 10. Every message that's only one unicode character long is considered to be a binary message. A binary message always takes one argument: the object that follows after the message.

So, for instance: + - / * < > and = are all binary messages.

Finally, there are keyword messages taking one or more arguments:

3 between: 1 and: 5.

Here we send the message 'between: 1 and: 5' to number 3. This expression will return True. Here is another example:

Pen write: 'Hello world!'.

This will print 'Hello World!' on the screen. We send the message 'write: hello world!' to the Pen object and that object will print the message on the screen.

You can chain multiple keyword messages using a comma:

Pen write: 'Hello', write: world!'.

Citrine does not need a special syntax for loops or conditions. To create a loop, send 'times' to a number:

3 times: {\ Pen write: 'Ho!'. }.

Likewise, to create a condition, send 'ifTrue' (or 'ifFalse') to a Boolean:

( money < price ) ifTrue: {\ Pen write: 'not enough money!'. }.

You can create your own objects by sending the 'new' message to Object.

myObject := Object new.

Once you've created a new object, you can make it respond to messages (adding methods) like this:

myObject on: 'getBeverage' do: {\
	^ 'Coffee'.
}.

As you can see, the second argument of this message is a literal block of code returning a string (^ means return, it's easy to remember because it takes the form of a little arrow pointing upwards!). If you do not return anything within a block of code, a reference to the object itself will be returned (so you can chain messages).

myObject on: 'getBeverageFor:' do: { forPerson |
	(forPerson = 'Picard') ifTrue: {\ 
		^ 'Earl Grey'. 
	}.
	^ 'Coffee'.
}.

Note that a block without parameters (previous example) uses a \ instead of a |. This is also easy to remember: think of the arguments as books supporting the pipe like a bookend, if they are removed the pipe falls over and leans on the curly opening bracket.

Functions

You can treat a block of code like a function, just send the message run to a block and it will execute the code within. If you need parameters use: applyTo instead.

{\ ^9. } run. #yields 9
{ a | ^ a * a. } applyTo: 6. #yields 36
{ a b | ^ (a + b). } applyTo: 3 and: 4. #yields 7
{ a b c | ^ (a + b + c). } applyTo: 3 and: 4 and: 1. #yields 8

Scope

Citrine uses dynamic scoping. To declare a new variable use:

var a := 1.

Here, we assign the value 1 to variable a in the current scope, it will exist as long as the current block of code runs, and it will be visible to all blocks of code called during that time.

{\ var q := 1. {\ Pen write: q. } run. } run. #prints 1.
{\ var q := 1. {\ q := 2. Pen write: q. } run. } run. #prints 2.
{\ var q := 1. {\ x := 2. Pen write: x. } run. } run. #Not allowed x is not defined
f := {\ q := 2. }. {\ var q := 1. f run. Pen write: q. } run. #prints 2

As you might have noticed, only in global scope you're allowed to omit the var keyword.

Closures

To create a closure, bind the in-function variables explicitly to the block object, like this:

multiplier := { m | ^ { x | ^(my f * x). } set: 'f' value: m. }.
double := multiplier applyTo: 2.
q := double applyTo: 9. #yields 18

Higher order functions

Here we create a higher order function mapper that takes an array and a function and applies the function to every element in the array:

addition := { x |
	var f := { y | ^ (my x + y). } set: 'x' value: x. 
	^ f.
}.
mapper := { array func |
	var mapping := { i | my q put: (my f applyTo: (my q at: i)) at: i. } 
	set: 'q' value: array,
	set: 'f' value: func.
	(array count) times: mapping.	
}.
a := Array <- 1; 2; 3.
mapper applyTo: a and: (addition applyTo: 100). #101 102 103

Properties (Me and My)

All properties of an object are private, you can access them using the my keyword.

myObject on: 'setBeverage:' do: { b |
	my beverage := b.
}

On the other hand, if you want to send a message to the current instance you use the me keyword.

myObject on: 'getBeverage' do: {\
	me prepareBeverage. #sends message to self
}.

Prototypes

To reuse code, Citrine uses prototypical inheritance. Suppose you have a dog and a cat, both can respond to the message 'eat', so to avoid duplicate code we put that response in another object called Animal and then create dog by sending the message new to Animal instead of Object.

Animal := Object new.
Animal on: 'eat' do: {\ 
	Pen write: 'munch! munch!'.
}.
dog := Animal new.
dog eat.

The new message will create a new object, setting the prototype link to the original one. To invoke an overridden method, prefix the message with a backtick (`).

dog on: 'eat' do: {\ 
	me `eat. #Animal will respond
}.

Malleable Objects

You can extend existing objects, for instance, to make numbers respond to × :

#let's extend the number object with a unicode times equivalent!
Number on: '×' do: { b |
	me times: b. 
}.
7 × { i | Pen write: i. }.

Break and Continue

To break out of a loop send the 'break' message to a boolean, like this:

5 * { i |
	Pen write: i.
	(i > 2) break. #after 2
}.

To skip the remainder of a block in a loop and proceed to the next iteration use 'continue'.

(i > 2) continue.

Exceptions

To catch an exception, you need to associate a catch block with your code block:

{\ #throw an exception:
	thisBlock error: 'oops!'.
} catch: { err |
	Pen write: err.
}, run.

Don't forget the comma right after the catch block! You want to say 'run' to the first code block, not the catch block!

Generic responses

You can make objects respond to arbitrary messages, in some languages this is known as 'magic methods'.

echo := Object new.
echo on: 'respondTo:' do: { 
	sound |
	2 times: {\ Pen write: sound. }. 
}.
echo ho!. #prints ho!ho!

Unicode Symbols

Citrine frequently uses Unicode symbols: (a ≤ 3). However, since most keyboards lack a '≤' key, the Citrine Lexer will automatically convert : '<=' to '≤'. The Lexer automatically converts the following character sequences:

SEQUENCE OF CHARACTERS    SYMBOL
<=
<=
>=
!=
&&
||
<-

Spaces matter

In Citrine, spaces matter, for instance:

priority + 3

should mean something different (send binary message + with argument 3 to object priority) than:

priority +3

i.e. send unary message +3 to priority. However, to avoid confusion, the following binary messages have 'special priority': + - / * = != > < >= <= <- || and &&. This makes it possible to do (3+3) instead of having to keep track of the spaces: (3 + 3). Unfortunately this also means unary messages are not allowed to begin with one of these characters.

The == symbol

Because Citrine uses := for assignment, it makes sense that = is used for comparisons. However, because there are so many languages out there using ==, Citrine accepts == as an alias for =.

Template Syntax

Citrine supports PHP-like template syntax using <? and ?>. Here is a web page served by Citrine, more will follow: test page To use Template Syntax, begin your document with ?> start a code section with: '<?.'. Mind the dot, it ends the 'template' string. Without the dot you can chain additional Pen commands like 'write'.

?><html>...
	<?. Pen write: 'Citrine starts here!'. ?>	

Plugins

To keep Citrine focused and clean (and adhere to the UNIX philosophy of doing one thing well), many functionalities people take for granted (like regex) will not end up in the core but will be available through the plug-in architecture.



Written by Gabor de Mooij,
📪 mail at gabordemooij ​dot ​com



Served by Citrine

BACK TO TOP