Skip to content
This repository has been archived by the owner on Sep 6, 2023. It is now read-only.

PicoC information

Gabriel edited this page May 9, 2016 · 2 revisions

PicoC is a scripting language which aims to be as close to C as possible. With PicoC, C code can be interpreted and executed, without the need for compiling. Many of the patterns possible with standard, compiled C are possible with PicoC, including access to arbitrary memory addresses. This is especially interesting in embedded devices, where certain peripherals and CPU features are memory-mapped. PicoC scripts can also call compiled C functions, if special interfaces for them are defined. One notable limitation of the PicoC environment is the lack of support for function pointers.

Starting with version 2.1 of Utilities, PicoC script execution is available in a separate edition of this add-in. This edition has a larger file size and offers a PicoC execution environment tailored to the Prizm platform; it can be identified in the About screen by the presence of a small "c" after the version number. The standard edition does not offer this feature and retains the usual small file size.

In the PicoC edition, additional options appear when seeing the details for a file with .c extension in the file manager. We will talk about these in the Running and Debugging section.

Environment capabilities

The PicoC environment is given 128 KiB of memory, allocated on the stack of the Utilities add-in, used for all the PicoC objects. This includes the stack and the heap used by PicoC scripts, plus internal PicoC objects necessary for script execution, like symbol names. Within this 128 KiB block, the stack grows downwards towards the heap and it's possible for them to collide.

PicoC scripts can call any OS syscalls present in gbl08ma's fork of libfxcg, as long as they don't have function pointers as arguments or return values. Additionally, PicoC scripts can make use of the wide set of functions present in Utilities. These include:

  • Functions to provide common UI elements, like menus, text inputs, and text areas;
  • Functions for building those common UI elements, like sprite drawing and shape drawing functions;
  • Helpers and wrappers for easier usage of OS syscalls, like a wrapper around PrintXY that automatically adds the necessary leading bytes in a safe way, or a GetKey wrapper that takes care of Utilities-specific key combinations, including the one used to open the settings menu;
  • Functions used to provide and control the various features and tools of Utilities, including functions for dealing with date and time, settings, file management, and more.

From now on, we will refer to these functions as the Utilities Framework.

Calls to syscalls or Utilities Framework functions do not use the 128 KiB of memory dedicated to PicoC. They will use their standard stack and heap, with Utilities Framework functions allocating stack above (or below, in terms of memory addresses) the PicoC region. This is why the whole Utilities stack can't be given to PicoC: native functions need their stack to be somewhere.

Performance considerations

PicoC program execution is at least an order of magnitude slower than native code execution. Furthermore, unlike what usually happens with compiled code and certain interpreted languages, there is no optimization whatsoever. PicoC is really not well suited to performance-critical code.

As PicoC is an interpreter, it needs to hold the names of all symbols in memory. This means longer variable, macro and function names use more memory than shorter ones. Comments also slow code execution by a small amount. Of course, the lack of comments and indecipherably short names means the code becomes unmaintainable. Ideally, code would be "minified" for running while keeping development versions properly documented. Obviously, for small scripts, like those one could even write on the calculator itself, this is not a concern.

Finally, to execute scripts PicoC needs to load the whole contents of the files into memory. This means there is an upper limit to the file size of a script: 64 KiB. Again, code minification would help with fitting more program in less script.

Safety considerations

PicoC does is not and does not aim to be a sandboxed environment. Sandboxing scripts would heavily limit the possibilities it offers. This means that PicoC scripts can crash the calculator just like native code, and do even more catastrophic stuff like corrupt the OS or erase the bootloader, yielding a brick.

You should be careful when running scripts you do not understand. When developing, using an emulator can save you many headaches, while also reducing deployment and debugging time and avoiding Flash memory wear on your real Prizm.

Programming for Utilities PicoC

Standard support

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

Control flow

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 and a call to it at the bottom of the file.

Example

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.

Header files

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 and Utilities Framework 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);

Memory management

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.

Running and debugging

To run PicoC scripts one should begin by transferring plain-text C source files, with .c extension, to the calculator. If you prefer a more tedious alternative, you can also write a C program using the editor in Utilities and then saving the file with extension .c.

These files can then be executed by opening the Utilities file manager, then opening the details screen for the C source file, and pressing F4 or EXE, ou simply by pressing EXE twice from the file listing.

Debugging

TBD

Clone this wiki locally