Citrine started in 1993 under the name Basic-NL. At that time, I had used a hack with the KCS Power Cartridge to translate the BASIC tokenizer of the Commodore 64 into Dutch. In 2009, the project was restarted. In 2020, I added support for 110 languages. Since 2023, Citrine has been using the SDL2 library, which also allows for the development of games and apps.
Can I use Citrine to make commercial apps?
Yes. Citrine is free and open source (BSD-3). You can do whatever you want with it.
Will you support SDL3?
Yes, SDL3 is on the roadmap! However, it might take some time, as I already have a lot on my plate.
What export options are available?
Currently: Windows (.exe), Linux and Android (.apk) and MacOS (.dmg). Soon: iOS and Web. Later: Steam Deck.
What is the current roadmap (2025)?
In 2025, I plan to introduce export options for macOS, iOS, the web, and Steam Deck. Additionally, I aim to add support for graphical user interfaces (GUIs) to enable the creation of phone apps using LVGL. Later I will add support for SDL3.
Is Citrine suitable for professional programmers?
Yes, Citrine has no artificial limitations. While some features are still in development, it is capable of creating functional and complete products.
Is Citrine suitable for schools?
Yes, Citrine is an excellent choice for learning programming. Starting with Citrine allows you to explore coding in a purely object-oriented programming language, giving you a solid foundation for a future in the industry. Since Citrine can be used in your native language, it also enables earlier access to programming education, as proficiency in English is not required.
Why use Citrine?
I created Citrine because I wanted to write software in my native language. I thought others might feel the same, so I decided to create multiple language editions. My native language is Dutch, and I’m also fluent in English, but my language skills end there. As a result, I primarily focus on these two languages and rely on the community to help with the rest.
Is there no Citrine binary my language/OS?
If your language and platform combination is not listed in the download section, it means there are currently no supporters for it. By joining the Citrine community, you can request a custom language and platform binary of Citrine. Alternatively, if you prefer not to become a member, you can compile the code yourself.
Is there no Citrine edition for my language?
If there is no Citrine edition in your language please notify me. You help us to create a language edition for your language by offering a translation.
What are the benefits of membership?
As a member of the Citrine community, you gain access to the export server, allowing you to export your programs to any platform of your choice. Additionally, members can request custom binaries for language-platform combinations that aren’t currently available. You’ll also receive exclusive bonus content. The easiest way to join is through Patreon.
How can I pay for membership?
The easiest way for you to join Citrine is to become a 'patron' and support us via Patreon. If that's not possible you can also pay by invoice or use XMR. Contact me for more details.
Why the low quality of some language editions?
Citrine aims to support all human languages. Currently, we have human translators for only a few languages, and for the rest, we rely on machine translations. While machine translations may not always be perfect, they provide a basic starting point, offering a glimpse of what programming in your native language could look like. From there, we can work together to refine and improve the translations. If you notice any mistakes in a specific language version, we encourage you to submit your corrections and suggestions.
How many images/sounds can I use?
The Media plugin has hard limits for resources. You can use a maximum of 100 images, 50 sounds or tunes, 20 fonts and 100 timers. You may recycle resources by changing the source of a music object or an image object. You can also reuse timers of course. To switch off a timer, set it to -1.
How to export your program?
Name the main code file __1__. Then, select all the files that belong to your program and drag them to the 'export' icon/program. This will generate a datapak - a bundle that contains all the resources of your program, including the code. Once your datapak is ready, simply drag it to the website and choose your target platform. That’s it! Your program will be converted and ready to use on the selected platform.
How will my app look on Android?
Your app will automatically be scaled to fit the screen size of the phone. Your app will run in landscape mode (default), unless you select Android export portrait (coming soon).
What are the limitations of exports?
Exports to Android will not have FFI available, although I am planning to create a JNI/FFI bridge so you can access native Android code through the JNI bridge (coming soon). On iOS a similar bridge will be constructed (coming soon). At the moment you can only have a main code file in an export, so all the code needs to be in one file. This will soon be changed because it will be possible to load code from a datapak. @todo
MacOS says my export is damaged?
Apple tells you this for all apps you download from unknown sources. Just copy the app to your Application folder like you normally do. Then do:
xattr -c /Applications/CitrineXX.appReplace XX with the ISO code of your language (i.e. nl/en etc). This removes the quarantine bit from the application.
How do I control the player on Android/iOS?
If you export to Android/iOS your phone will function as a gamepad. On the left 10% (bottom 10%) you can press to go LEFT-RIGHT, on the right 90% bottom 20% you can press to go UP/DOWN or jump. This is done automatically. To visualize the controls you can simply display an overlay image.
What is self-bootable export?
Exporting to self-bootable image will result in an ISO-image that you can put on a USB drive or disc. You can then boot your PC from that drive and it will directly start your app/game (coming soon).
How to translate code?
To translate a Citrine Program file:
ctr -t en2nl.dict myprogram.ctrDictionaries (.dict) have the following format:
t "SOURCE" "TARGET"s "SOURCE" "TARGET"
d "SOURCE" "TARGET"
x "SOURCE" "TARGET"
Use t for tokens and s for texts (strings). Use d for decimal separators and x for thousands separators. To generate a dict automatically from 2 headers:
ctr -g /nl/dictionary.h /en/dictionary.hHow to create a 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):
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_MBCITRINE_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?
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.ctrXX = your language edition
The output looks like:
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:
Code | Type of AST-node |
51 | Assignment |
52 | Message expression |
53 | Unary Message |
54 | Binary message |
55 | Keyword Message |
56 | String Literal |
57 | Object Reference |
58 | Numeric Literal |
59 | Code Block Literal { } |
60 | Return Statement ↲ |
76 | Parameters |
77 | Instructions in a Code Block |
78 | Parameter (in the parameters) |
79 | End of Program |
80 | Nested Expression between (...) |
Modifiers:
Code | Meaning |
0 | Variable: x |
1 | Property: own x |
2 | Declaration: >> 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