Citrine programming language



one language to rule them all

Citrine is simple and easy to learn general purpose programming language for UNIX®-like operating systems. Citrine is inspired by the Kayian School of Object Orientation. Everyone can learn Citrine in a couple of minutes, try it yourself! Citrine runs on OpenBSD (preferred platform), Linux, FreeBSD (confirmed by community) and Mac OS X (needs improvement).

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

Travis-CI Test Report

discuss on Google Groups discuss on reddit

Also interesting reads: WHY?, Reference Manual, Roadmap and Progress Blog.

What does it look like?

A slightly over-engineered 'hello world' program:

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

Run the program:

$ ctr hello.ctr

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.

  • Minimalist syntax
  • Everything is an object
  • Message-based with interspersed arguments (the 'Smalltalky' way)
  • Classless, simple prototypical inheritance
  • Magic methods for DSL/Fluid API creation
  • Malleable Objects
  • UTF-8 compliant, 💩 (pile of poo) is a valid object name
  • Small core, extendible using plugins
  • Garbage Collection
  • Dynamic Scoping
  • Uses := for assignment, finally back to sanity!

Citrine is a next generation programming language, not tied to a perticular ecosystem or technology, aiming to provide a better textual programming interface between humans and machines.

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:

  • Nil
  • Booleans: True False
  • Numbers: 1 2 3.5 100 etc...
  • Strings: 'Hello'
  • Blocks of code: { ...parameters... ...code here... }

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

This is what a comment looks like:

# this a comment
# it starts with #.

Messages

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.

Note that spaces matter, you have to write 3 + 7 not 3+7.

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: 'A', write: 'B'.

Control Flow

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: 'dream on!'.
}.

Break and Continue

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

5 times: { :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!

Creating your own objects

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 
	var b := 'coffee'.
	(forPerson = 'Picard') ifTrue: { 
		b := 'Earl Grey'. 
	}.
	^ b.
}.

Note that each parameter of a block starts with a color (:).

Properties (Me and My)

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

animal := Object new.
animal on: 'name:' do: { :n
 my name := n.
}.

Since properties are private, other objects can't access it. If you want to make a property (like name) available, you have to add a method that returns it, like this:

animal on: 'name' do: {
 ^ my name.
}.

If you want to send a message to the current instance you use the me keyword. So, for instance, say we have a method called 'sleep' and we wish to make an alias for it called takeNap, we could write something like:

animal on: 'takeNap' do: {
 me sleep.
}.

Here, on receiving the message takeNap, the animal will call its internal method sleep (sending a message to itself using the me keyword).

The keywords me and my always refer to the objects to which they belong. If you invoke a block of code by sending the run or applyTo: message, me and my will refer to the block itself.

Prototypes

To reuse code, Citrine uses prototypical inheritance. Let's create a cat from the animal object:

cat := animal new.
cat name: 'Diva'. 

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 (`).

cat on: 'name' do: {
 ^ 'your royal highness '
  + (me `name).
}.

If you now ask the cat for its name, it will point out you should address her with 'your royal highness Diva.' instead of just 'Diva'.

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. }.

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!. #ho!ho!

Functions & Scoping

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

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.

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

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: { :i
   mapping applyTo: i.
 }.
	
}.
a := Array < 1; 2; 3.
mapper applyTo: a and: (addition applyTo: 100). #101 102 103

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

The == symbol

Because Citrine uses := for assignment, it makes sense that = is used for comparisons. The == symbol has no meaning in Citrine.

Template Syntax

Citrine supports PHP-like template syntax using <? and ?>. 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 clean and small we use plugins (thus adhering to the UNIX philosophy of doing only one thing and doing it well). To load a plugin just begin to send messages to the object provided by the plugin. As soon as you start 'talking' to the object, Citrine will attempt to load it from your plugin folder. Take a look at the HTTP plugin below for an example.

HTTP Plugin

Citrine ships with a HTTP plugin that allows you to deal with GET and POST requests. There's no need to explicitly load the plugin, just start talking to an object and it gets loaded automatically. Plugin path format:

mods/{plugin}/libctr{plugin}.so

for instance the HTTP Request plugin provides a Request object, so the path is:

mods/request/libctrrequest.so

you can use the Request object like this:

#blog.ctr?search=eastereggs
s := Request get: 'search'. #autoloads Request plugin
Pen write: ('searching for: '+s). #searching for eastereggs

More examples:

list := Request getArray: 'orders[]'. #GET an array of items
post := Request post: 'message'. #get a POST var
list := Request postArray: 'orders[]'. #array from POST
file := Request file: 'avatar'. #returns Array (0 = temp file, 1 = name)

Storm Server

Storm Server is a CCGILib based SCGI server for Citrine for hi-traffic web applications, to start an instance of Storm we put the following code in storm.ctr:

#Citrine Storm Server Demo storm.ctr
#Put your Citrine scripts in /var/www/htdocs/...
#To kill this server
#kill $(cat /var/run/storm.pid)
Pen write: 'Start Citrine Storm Server v1', brk.
Request host:'localhost' listen:4000 pid:'/var/run/storm.pid' callback: {
	#Now set the content type and any other headers
        Pen write: 'Content-type: text/html\n\n'.
	#Load the script file
	var fname := Command env: 'DOCUMENT_URI'.
        var script := File new: '/var/www/htdocs' + fname.
        #Run it!
	script include.
}.

Nginx configuration

Start Storm like this:

ctr storm.ctr

You'll now see a welcome message like this:

Start Citrine Storm Server v1

Configure nginx to use Storm Server, open nginx.conf:

vi /etc/nginx/nginx.conf

(note: the path of the nginx configuration file can be different depending on your OS)

and add:

location ~ \.ctr$ {
	try_files $uri $uri/ =404;
        scgi_pass 127.0.0.1:4000;
        include   scgi_params; 
}

Now restart nginx and you're done! You can now add your Citrine pages (using .ctr extension) in /var/www/htdocs/ or whatever folder you specified in storm.ctr. Have fun!



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



Served by Citrine

BACK TO TOP