-
Notifications
You must be signed in to change notification settings - Fork 14
Programming for Utilities PicoC
PicoC aims to be close enough to C90 so that most programs written with this standard in mind will run without modifications - within the limitations of the Utilities implementation, namely the lack of a proper stdin/stdout/stderr or a POSIX file API.
Unlike C90 proper, PicoC supports C++ style comments (starting with //
).
Other differences from C90 are described in the following wiki page, part of the old Google Code page for PicoC:
https://code.google.com/archive/p/picoc/wikis/DifferencesFromC90.wiki
Unlike normal C code, where any instructions must be inside functions and execution begins in the function "main", PicoC in Utilities takes a different approach. It is configured such that the script executes from top to bottom, and any instructions outside function blocks are immediately executed. This approach is commonly seen in other scripting languages, like Python.
The configuration used in Utilities PicoC is similar to that described in the "Interactive mode" section of the wiki page liked above. The behavior is similar to feeding the whole file to the interpreter at once.
If you prefer to have a "main" function, you can still add it, placing a call to it at the bottom of the file.
The following code:
#include <fxcg/display.h>
Bdisp_AllClr_VRAM();
void Hello() {
PrintXY(1, 1, " Hello", TEXT_MODE_NORMAL, TEXT_COLOR_BLACK);
}
PrintXY(1, 1, " Nope", TEXT_MODE_NORMAL, TEXT_COLOR_BLACK);
Hello();
PrintXY(1, 2, " World", TEXT_MODE_NORMAL, TEXT_COLOR_BLACK);
// keyboard pause omitted for simplicity
...will clear the screen and show "Hello" on the first line of the screen and "World" on the second line. The word "Nope" is obscured as "Hello" gets printed when the function Hello is called.
Like with normal C code, before external functions can be used, their headers must be imported. The PicoC environment on the Prizm offers a set of virtual header files, which allow for using syscalls, Utilities Framework functions, and some of the standard library functions. By "virtual" we mean that the files are not actually present in the file system, and they don't exist in the traditional form. These headers are like dictionaries linking PicoC function names to native code written in C and assembly. If you want to know more, you can see how these headers are built by looking at the src/picoc/platform/library_prizm_cpp.cpp file in the Utilities source code.
For example, in order to use the GetKey syscall traditionally present in the fxcg/keyboard.h
file in the include
folder of libfxcg, in a PicoC script one should use:
#include <fxcg/keyboard.h>
Unlike the proper libfxcg headers, these headers only include the functions themselves and do not include any structs, enums or macros meant for use with them. There are a few exceptions, as we will see later. This frugality is needed so that more of the 128 KiB dedicated to PicoC objects are free for the PicoC programmers to use as they please. In practice, this means that if a certain syscall needs a specific struct, it should be declared in the PicoC script. Note that each import requires memory in order to hold information about the symbols in the imported header.
In the case of Utilities Framework functions, the header file names are similar to those in the src folder of the Utilities source code, except with a different extension. If one were to import the functions in graphicsProvider.hpp
, one would do:
#import <utilities/graphicsProvider.h>
Like with libfxcg functions, these special header files don't contain any structs or enums necessary for using the functions within them. These should be copied from the proper .hpp headers correspondent to the correct version of Utilities, and any syntax specific to C++ (like default field values) should be removed. Extra care needs to be taken with regards to custom type definitions, such as color_t
: if one doesn't feel like bringing all the type definitions into the PicoC script, these can be replaced with their definition (in the case of color_t
, unsigned short
), which also helps save memory.
PicoC doesn't support default values in struct fields, and manually initializing each field can be tedious (and slow). To help with struct initialization, an additional virtual header, utilities/structInit.h
, exists. This header contains initialization functions for all the structs in the Utilities Framework; these functions initialize the respective structs with the default values, as seen in the "full" .hpp headers.
For example, to declare and initialize an instance of the textElement
struct, one should do:
#import <utilities/structInit.h>
typedef struct
{
char* text;
unsigned short color;
char newLine;
char spaceAtEnd;
char lineSpacing;
char minimini;
} textElement;
textElement e;
// Necessary to get the struct filled with default values
initTextElement(&e);
Another important note is that PicoC doesn't support default parameter values in function arguments, as is the case with the C standard it aims to support. This means that regardless of the existence of default values in the C++ headers of the source code, in PicoC one should always include all the parameters:
#import <utilities/keyboard.h>
int key;
// even though the last parameter defaults to 0, it must still be specified:
mGetKey(&key, 0);
For the tradidional standard library headers, just import them as you would do on any other system:
#import <stdio.h>
PicoC does not support double-width integers, which are 8 bytes wide on the Prizm and correspond to long long int
s in C compiled for the Prizm. Utilities makes heavy use of long long int
s in the chronometer functions and in other functions that deal with timestamps (in Utilities, timestamps contain the number of milliseconds since the Unix epoch, much like Java's java.util.Date type).
We worked around this PicoC limitation in two different ways:
- For the
chronoProvider.h
functionsetChrono
, which takes along long int
for the last two parameters, each 8-bytes integer is broken into two 4-bytes integers, so the function takes five arguments instead of three. The last two parameters are reconstructed in the following way:
long long int realDuration = (long long int)parameter2 << 32 | parameter3;
long long int realType = (long long int)parameter4 << 32 | parameter5;
- For the
timeProvider.h
functionsdateTimeToUEBT
,currentUEBT
, andcurrentUTCUEBT
, we opted for not making them available to the PicoC environment. Their functionality can be replicated on PicoC scripts by using syscalls and othertimeProvider
functions.
As indicated above, PicoC scripts have their own 128 KiB region of memory, used for stack and other PicoC objects like symbol names. We also mentioned that, because of this, symbol names could be kept short to leave more memory available for the program to run.
PicoC is a scripting language, but this doesn't mean memory management is done automatically. You should always free malloc'ed memory, even when you are going to exit right after. This is because the heap used is the system heap, and Utilities does not keep track of memory allocated by the scripts. In practice, this means that if you don't free memory allocated inside your scripts, it will stay allocated until Utilities exits (and not until your script exits), leaving less memory available for Utilities and other scripts.