FAQ

Translate to using: Google Yandex

Close all

Project

Why use Citrine?

I originally created Citrine because I wanted to write code in Dutch. I realized that others might feel the same about coding in their native languages, so I made Citrine open-source and available for everyone to use. While I’m not a language expert, I used machine translations to give you a sense of how Citrine works in different languages. Since then, the community has been amazing at helping improve the translations, but you might still spot some funny or unexpected translations in certain versions of Citrine. That's totally okay! Perfection isn't the goal—creating a tool where everyone can code in their own language is. Citrine is a community-driven project, so if you'd like to improve translations or add new ones, we'd love to have your help! The world of languages is vast, and Citrine is all about embracing that variety. So, while we may never reach "perfect" translations, together we can make Citrine a tool that truly belongs to everyone.


Why is Citrine suitable for localization?

Citrine keeps things simple with only a few keywords and a very basic grammar. This makes it much easier to adapt to different languages. Instead of having separate syntax for things like for-loops, while-loops, or if-statements, Citrine treats everything as messages to objects. This flexible design allows you to shape the language to fit your own native tongue. For example, you can create a supplement (a library written in Citrine) to make the phrasing of code feel more natural. If your language uses cases or inflections, you can easily build those into the structure. You can even add supplements to support gender variations in objects or messages if that’s something your language needs.


Why not use AI instead?

AI is a hot topic right now, and it’s exciting to see where it will take us. AI could actually free us from focusing so much on the English language and help us concentrate more on the logic of programming, no matter which language we’re using. So, I believe AI might give Citrine a big boost in the future, making it even easier for people to code in their own languages.


Why is Citrine apolitical?

Because Citrine is a localized language that aims to support every human language, it may naturally attract submissions that are politically motivated. To keep the project focused we therefore focus on technology only and leave politics for what they are.


Why is Citrine written in C?

C is the programming language I feel most at home with. It’s great because it offers excellent compatibility with system components and libraries, making it a solid choice for integration.


Why is the project not democratic?

I am the BDFL (Benevolent Dictator for Life) of Citrine, which means that while I value and listen to suggestions from the community, the final decisions rest with me - even if they don’t always align with the majority’s opinions. I know this might be uncomfortable for some, especially if they have strong views that differ from mine, but I believe a single, unified vision leads to the most coherent and consistent results for the project. You can always create a fork of course.


Why do I have to pay for the build service?

The build service is a premium option available for members. It lets you easily export your game or app to different devices by uploading it to the build server. Since maintaining and developing the software to keep this service running requires ongoing effort, it’s available only to paying members. That said, the export scripts you need to export your game or app are completely open-source. This means you can technically handle the export process yourself for free. However, since this can be quite complex, I created the build service to make things easier. So, while everything is open and free to use, you’re simply paying for the convenience of avoiding platform-specific configurations and build tools.


Are the games family-friendly?

Yes, all the games on the Citrine website, including demo games and others, are designed to be family-friendly.


Can I use Citrine as a teacher?

Yes, you can absolutely use Citrine to teach programming in schools or universities! I’m happy to assist you in creating a curriculum or exercise materials if needed. We can also work together to integrate Citrine with your school’s IT infrastructure or ensure it runs smoothly on your students’ laptops and computers.


Is Citrine safe for my kids?

It depends. Citrine is a fully functional programming language, so unlike toy languages, it’s possible (though unlikely) that your child could accidentally cause issues on the device. If you’re concerned about this, a good solution is to install Citrine on a dedicated device just for programming. Second-hand Linux computers are often affordable, and since Citrine has modest system requirements, an older computer that’s no longer ideal for everyday tasks would work well for your child’s programming projects.


Why are some translations in Citrine machine-generated, and how can they be improved?

Citrine aims to support multiple languages, and while some translations are community-driven, others currently rely on machine translation. This approach helps provide an initial idea of how Citrine looks in various languages and encourages native speakers to contribute by improving any inaccuracies. For example, languages like Polish and Russian have some machine-generated translations that may contain errors, especially in technical contexts. Citrine is transparent about using machine translations, and (progress ratings on the website reflect the quality of translations. Users are encouraged to submit improvements, either through suggestions or by opening a pull request. The ultimate goal is to refine translations with community contributions. If you notice issues in the translation for your language, feel free to contribute and help improve the accuracy!


Technology

How to translate code?

To translate a Citrine program from one language into another:

ctr -t en2nl.dict myprogram.ctr

Dictionaries have the following format:

t "SOURCE" "TARGET"
s "SOURCE" "TARGET"
d "SOURCE" "TARGET"
x "SOURCE" "TARGET"

Use t for tokens and s for strings. There are 2 special translation pairs: the decimal separator (d) and the thousands separator (x). To generate a dictionary from 2 versions of Citrine in different languages:

ctr -g /nl/dictionary.h /en/dictionary.h

How to write my own plugin?

To write a plugin for Citrine you have to generate a shared library in the /mod folder that can be dynamically loaded. For a complete example see the Percolator plugin.

First, create a .c file that contains a begin() function. In this function you can add objects to the world of Citrine, i.e. add them as public properties to the World object (CtrStdWorld):

void begin(){
ctr_internal_object_add_property(
	CtrStdWorld,
	ctr_build_string_from_cstring( "Thing" ),
	thingObject,
	CTR_CATEGORY_PUBLIC_PROPERTY);
}

Note that Citrine does not have classes, so you can only add objects to the world. Property names are also objects (Text Objects), to wrap a char* into a Text object you can use ctr_build_from_cstring( char* ... ), the inner value can be accessed through o->value.svalue->value (char*) and o->value.svalue->vlen (ctr_size). All strings in Citrine must have a length (vlen), they are NOT NUL-terminated. Create your object like this:

ctr_object* thingObject = ctr_thing_new(CtrStdObject, NULL);

always override the prototype link (necessary to find parent methods), otherwise you get an infinite loop:

thingObject->link = CtrStdObject;

Your constructor function (ctr_thing_new) may look like this:

ctr_object* ctr_thing_new(
	ctr_object* myself, ctr_argument* argumentList) {

	ctr_object* instance = 
		ctr_internal_create_object(CTR_OBJECT_TYPE_OTOBJECT);
	instance->link = myself;
	return instance;
}

To add a method to your object use:

ctr_internal_create_func(
	thingObject,
	ctr_build_string_from_cstring( "new" ),
	&ctr_thing_new
);

All method functions are invoked with a reference to the object itself (myself) and an argumentList object. To obtain the first object in the list use:

argumentList->object

To obtain the second object:

argumentList->next->object

and so on... To add a property to your object:

ctr_internal_object_property(
	myself, 
	ctr_build_string_from_cstring( "size" ),
	ctr_build_number_from_float(123)
);

To get a property from an object:

ctr_internal_object_property(
	myself, 
	ctr_build_string_from_cstring( "size" ),
	NULL
);

As you can see you can use ctr_build_number_from_float() to wrap a double in a Number object (access the inner value with: o->value.nvalue). Booleans are always references, so you don't need to wrap them, simply assign CtrStdBoolTrue or CtrStdBoolFalse. The same applies to Nil: CtrStdNil. Custom resources like file handles can be wrapped in objects of type OTEX (ctr_internal_create_object(CTR_OBJECT_TYPE_OTEX)), put the custom data in o->value.rvalue.

To trigger an exception use ctr_error( message, code ); where code can be errno or 0 for custom errors. To cast an object use: ctr_internal_cast2bool( ... ); ctr_internal_cast2number( ... ); ctr_internal_cast2string( ... ); For strings and numbers, to enforce the return of a copy (otherwise if the type already matches you get the same object back): ctr_internal_copy2string( ctr_object* o ); ctr_internal_copy2number( ctr_object* o );

To allocate memory use: ctr_heap_allocate(size), to free: ctr_heap_free(). All memory need to be freed upon the end of a Citrine program, otherwise you will get a warning message about a memory leak. Use ctr_heap_allocate_cstring( object ) to copy the contents of a text object to char* memory (automatically allocated). To send a message to another object:

ctr_argument* argumentList = 
	ctr_heap_allocate( sizeof( ctr_argument ) );
argumentList->object = someObject;
ctr_object* result = 
ctr_send_message(
	myObject,
	"work:",
	strlen("work:"),
	argumentList
);

To run user code (Task object), use ctr_block_run(task, argumentList, me). The last parameter is the object that acts as "myself". It is important to make sure that the garbage collector does not remove any objects that are not reachable from within the world object, if you created some temporary objects with ctr_heap_allocate, and they are not part of the world yet, set the sticky flag: object->info.sticky = 1; until the user code is done. During execution of user code the GC becomes active.

All functions you need are defined in the citrine.h header file (which you need to include). Compile and link your plugin with (before copying to /mod):

cc -I . -c thing.c -Wall -Werror -fPIC -o thing.o
cc -o libctrthing.so thing.o

To activate a plugin named "thing", just send a message to it:

>> :=Thing new.

If the reference does not exist, Citrine will automatically try to load it from the mods folder.


How is Citrine tested?

The Citrine code base is tested using the Test-o-Mat framework which ships with Citrine. The Test-o-Mat system is very simple to understand and work with and can also be used to test other software. The basic idea is that you have test definitions and expectation files (.exp). After loading the test framework, override the next-message to generate a new test specification object and override the process-message of the Test Suite to describe how the test should be processed. For a detailed example you can look at test no. 365 in the test folder (tests/test0365.ctr). The Test-o-Mat framework is only available in the English language at the moment.


What are the X3/4 languages?

The special languages X3 and X4 are English and Dutch but with pictograms instead of ASCII symbols. Before version 1.0 Citrine used to use UTF-8 symbols because they are more readable. However, they are difficult to produce on a normal keyboard and the macro package that used to support them conflicted with several international keyboard layouts. So in 2024 I switched to ASCII to make writing Citrine programs easier. The X3/4 editions are maintained for backward compatibility only. The special language XX is for testing purposes. In practice the Citrine editions Citrine/XX, Citrine/X3 and Citrine/X4 should never be used for normal use.


How to write webapps (outdated)

Citrine ships with a CCGILIB-based HTTP plugin (mods/request/libctrrequest.so) that allows you to deal with GET and POST requests. you can use the Request object like this:

get  := Request get: ['search'] .
list := Request getArray: ['orders[]'] .
post := Request post: ['message'] .
list := Request postArray: ['orders[]'] .
file := Request file: ['avatar'] .

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:

Request 
host: ['localhost'] 
listen: 4000
pid: ['/var/run/storm.pid']  callback: {
	Out write: ['Content-type: text/html\n\n'] .
	>> fname := Program setting: ['DOCUMENT_URI'] .
	>> script := File new: ['/var/www/htdocs'] + fname.
	Program use: script.
}.

NGINX example configuration /etc/nginx/nginx.conf:

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

How to start the core unit tests?

As of july, Citrine will feature a new unit test system to test the core of the language. Over the next few years, we expect to extend this test suite with all sorts of useful tests to improve the quality of the Citrine code. The unit test runner can be invoked simply from the language itself, using:

Program test.

This will start the internal unit test runner. After running all the unit tests the process will be stopped automatically. There is no way to continue a Citrine program after invoking the test runner. If a single test fails, the test suite will report the error and exit. The output from the unit test runner might look like this (although this will change from release to release):


Running Internal Tests
[1][2][3][4][5][6][7][8][9][10][11][12][13][14][15][16][17][18][19][20][21][22][23][24][25][26][27]...

The unit test system is included in the regular test suite and currently resides in test file 0356. The unit test system is used to test parts of Citrine that are difficult to test from the outside like the inner workings of the Lexer, Parser, Walker, as well of specific system utilities for memory management and encoding.


What memory modes are available?

You can use the following environment variables:

CITRINE_MEMORY_LIMIT_MB
CITRINE_MEMORY_MODE
CITRINE_MEMORY_POOL_SHARE

CITRINE_MEMORY_LIMIT_MB sets the memory limit in megabytes upfront. CITRINE_MEMORY_MODE selects the garbage collector setting (see Program tidiness). CITRINE_MEMORY_POOL_SHARE sets the share of the pool (2 means that you want 50% of all allocated memory to be devoted to the pool).


What are special effects?

You can tweak / tune the Citrine Media Plugin with so-called special effects. Special effects are more than just visual gimmicks, they can also change the way the underlying engine works. To apply a special effect (in English) use: Media effect: [Number] options: [Sequence].

FX-code Explanation
0 For testing puposes only, prints test message to stdout.
1 Remaps all gamepad/joystick buttons to UP


Why is my text rendered incorrectly?

For some languages like Arab or Hindi you need special text shaping. Thanks to the wonderful SDL and Harfbuzz libraries this is very simple to do. Simply write (for example for Arabic text):

font style: ['Arab'] direction: 1.

For more details see: Harfbuzz codes


Where can I find Harfbuzz codes?

To render text correctly in certain languages you need to provide a Harfbuzz code to configure the shaping of the language. You can find the Harfbuzz codes here.


How to transpile Citrine to C or Java?

AST Citrine allows you to export the AST representation of any Citrine program so you can transpile or compile it to any other target language, including for example: machine code, Assembler, C, C++, Java, Lua, SQL, PHP, Perl or JavaScript.


To generate an AST-export:

ctrXX -x program.ctr

XX = your language edition

The output looks like:

52;0;0;;[57;0;3;Out;;55;0;6;write:;[56;0;5;Hello;;];];52;0;0;;[57;0;3;Out;;55;0;6;write:;[56;0;6;World!;;];];79;0;0;;;

The original program:

Out write: ['Hello'].
Out write: ['World!'].

The format for the AST-export is as follows, each node in the tree is represented as:

{TYPE} ; {MODIFIER} ; {LENGTH OF VALUE BUFFER} ; {VALUE BUFFER} ; {NESTED NODES}

Nested nodes are between [ and ].

Opcodes for nodes:

CodeType of AST-node
51Assignment
52Message expression
53Unary Message
54Binary message
55Keyword Message
56String Literal
57Object Reference
58Numeric Literal
59Code Block Literal { }
60Return Statement ↲
76Parameters
77Instructions in a Code Block
78Parameter (in the parameters)
79End of Program
80Nested Expression between (...)

Modifiers:

CodeMeaning
0Variable: x
1Property: own x
2Declaration: >> x :=

To test the AST-export you can pipe it through the example program 'info' (available in the source tree in misc/tools):

ctrXX -x program.ctr | info Message Object Reference(Out) Keyword Message(write:) String Literal(Hello) Message Object Reference(Out) Keyword Message(write:) String Literal(World!) End