IoTSS

v1.0.1 by wu5@hekr.me 8/7/2015 6:44:10 PM

Hangzhou District Nine Technology Co., Ltd. © 2015. All Rights Reserved.

Overview

What is IoTSS

IoTSS is short for Internet of Things Scheme System, it is an implementation of Scheme Programming Language designed especially for embedded system with the purpose of memory consumption optimization and portability.

Traditional Embedded System Development

Apart from the Internet of Things, all other embedded development tasks meet the same problems as below:

Challenge

The challenge of IoT and other embedded development is to solve the problem mentioned in the previous section without increasing too much cost.

Virtual Machine

Many years ago, we met the same problem on PC and Mobile Devices. Java Virtual Machine has tried to solve the problem by constructing a virtual machine.

The virtual machine is an abstract computer which doesn't have a corresponded real hardware, instead, it is designed for easier to be transported to most platforms.

Scheme

Lisp

Scheme is dialect of the Lisp Programming Language which follows a minimalist design philosophy that specifies a small standard core. Scheme supports multi-paradigm programming including functional and procedural programming which means you are freely to choose the way of expression the logic of the program. It includes the following features:

Standard

The IoTSS Project trying to provide a strict subset of the R5RS Standard as core part to make Scheme users get started as quickly as possible.

On embedded platforms, the problem's got a bit complex due to the resource limit of the devices, we could not have the same experience as what we code on a high-end machine. Some of the features of Scheme consumes too much resource that we have better to abandon:

Benefits

Since we introduced a high-level programming language, every benefits of high-level programming languages are also been introduced into the embedded development including Internet of Things development, some highlights are listed below:

Scheme Programming Language References

  1. The Scheme Programming Language, 4th Edition
  2. MIT-Scheme Documentation
  3. Revised5Report on the Algorithmic Language Scheme

Progress

The project is currently in very early period. The interface of core part might be changed and the functions provided by the standard library could be changed massively and a large amount of TODOs are going to be done.

License

The license which this software is distributed has NOT been decided.

Acknowledgments

We acknowledge no one.

Get Started

The easiest way to get familiar with IoTSS is to build it and trying to use it. Instead of putting the project on the target platform, building it on PC to confirm if it works is a much easier way for beginners.

Build & Installing

GNU/Linux

The build system according to the system should be installed before you build it.

On Debian/Ubuntu:

apt-get install build-essential

On Fedora/CentOS/RHEL:

dnf install gcc

Windows

MinGW (Minimalist GNU for Windows) is recommended as the replacement of the development suite in GNU/Linux.

OSX

Currently missing.

Customization

IoTSS provides many options for customizing the whole things. The first thing should care almost contained in the following 4 files:

Platform

IoTSS has been designed with high portability that could run on different platforms, the first thing we have to do to port the project on a new platform to set an identifier via a platform macro in the file iotss_platform.h.

Configuration

In the file iotss_config.h, there are many options that provided as macros.

Virtual Machine Options

The virtual machine provides an interface to set the options at the time of the virtual machine been created. The possible options are listed below:

Optimization Options

For the purpose of reducing the consume of RAM or accelerating the running of program, some optimizations are provided.

Currently the available optimizations are listed below:

Resource

The word resource could be used to represents many kinds of resource, but we separate others from memory, because memory allocation and releasing are the most fundamental operating.

The two operating of memory allocation and releasing are been defined in iotss_res.c. Invoking your own copy of malloc and free then enabled by the macro passed in or defined in iotss_platform.h.

File System Interface

Some functions of IoTSS relies on file system such as loading an external file with require or reading some data from a file.

To enhance the portability, IoTSS standardized the interface of file system and provides the following interfaces:

The prototypes of the file systems are listed as following:

void *(*cb_fopen)(const char *path, iotss_os_file_mode_t mode);
int (*cb_fclose)(void *handle);
iotss_size_t (*cb_fread)(void *ptr, iotss_size_t size, iotss_size_t nmemb, void *handle);
iotss_size_t (*cb_fwrite)(const void *ptr, iotss_size_t size, iotss_size_t nmemb, void *handle);
int (*cb_fflush)(void *handle);
iotss_bool (*cb_available)(void *handle);
int (*cb_fseek)(void *handle, long offset, int whence);
int (*cb_ftell)(void *handle);
iotss_bool (*cb_is_file_exists)(const char *path);
int (*cb_file_read)(const char *path, char *buffer, iotss_size_t buffer_size);
int (*cb_file_write)(const char *path, char *buffer, iotss_size_t content_size);
iotss_size_t (*cb_file_length)(const char *path);
char *(*cb_fullname)(const char *path, char *resolved_path);

One thing should be noticed that the term 'file' doesn't been strict to the concept of 'file', 'files' could be anything that has action and features of a file, such as FTP or HTTP.

Use the following function to set the file system interface:

int iotss_vm_register_os_interface(iotss_vm_t *vm, \
        iotss_os_interface_t interface, void *callback)

Library Path

Library paths are the default paths to put common libraries and frequent used snippets of code. The require form will first look up the required library you are trying to load in the library paths.

To add a library path, use the following function:

int iotss_vm_append_lib_dir(iotss_vm_t *vm, \
        char *pathname);

Programming Language

Standard

IoTSS implements a subset of R5RS, some functions that consumes too much resource will not been implemented.

Data Types

The following data types original appeared in Scheme are supported:

Most of these data types are the same as the original Scheme except Integer. Unlike the 'classic' Scheme which has many refined type of numbers such as integer, fractal, real number, IoTSS only supports 32-bit integer. For reducing the consume of RAM, unlimited precise integer is unsupported.

Nil

Nil is a type which contains only one value, #nil. It is an equality thing as the NULL, None or null in other programming languages to represent the meaning of nothing, not found, or not exists.

Void

Void is a type which contains only one value #void. Similar to Nil, Void can be used to represent the same meaning of nothing or something like this, but it differs from Nil slightly that as the evaluating result, it won't be displayed.

Error

Error is a data type to represent the error message of the evaluation.

A value in type error usually contains some of two parts, an symbol and a message. The symbol is a brief summary of the category of the error such as 'File not found' or 'Divide by zero', by categorizing different kinds of errors, we can easily handle the selected ones from them by simply checking the symbol. The message contains the detail of the error to make user figure out what happened exactly when the error value's been produced such as an 'File not found' error value can contains the information of "which file was not found exactly".

Integer

Currently the only numeric data type in Scheme is integer,

For saving the consume of memory usage, unlimited precise integers are not supported and the actual width (the actual range of integer can be represent) of integers in IoTSS are depends on the specific machine.

In some situations, there are requirements to represent the fractal numbers that could not fit into integers directly. The workaround to solve this is to use 'fixpoint fractal' to emulate the effect of float-point numbers. For example, we want to represent the value of price and the basic unit, if we use the integer 1 to represent one dollar, then there will be no way to represent

Boolean

Values in Boolean type are for representing true or false, and written as #t f or true and #f for false.

Symbol

Symbols in Scheme are a data type as well. Unlike other common programming languages which symbols are normally identifiers, in Scheme, symbols could be used as values and assigned to variables.

The literal of a symbol usually use to reference the values of variables, to prevent the action performed by default, we can use a special quote:

(quote something)

Or use the following form for short:

'something

String

The reducing the usage of memory, strings are stored as an array of 8-bit unsigned integers.

On most UTF-8 based operating systems, storing and displaying strings with chars out of the range of 0 to 127 are OK, but the procedures directly operating strings won't be able to operate the contents such as reference a char by index, the measuring of string length is also impossible.

Pair

Pairs are the most fundamental data structure to construct more complex data structure, including list, stack, queue, tree and so on.

Each pair is consisted with two part, the car part and cdr part. For example, to construct a list, we use cdr parts of pairs to represent the connection node of each node of the list, and car points to the actual value of each node.

Environment

Environments are the execute environments which store the variables and attributes, they also reference to the 'previous' layout of environment that makes users to access the 'outer layers' of scopes.

Procedure

Procedures are almost equal to the concept of functions in common programming languages but are slightly different from those functions, procedures save the environment at the time they been created which cause particular behaviors.

Generally, the procedures could be divided in two main categories, the native procedures and non-native procedures.

Native procedures are written in low-level programming languages and bound to the environment of Scheme, the execution of them don't require the usage of virtual machine.

Non-native procedures are written in Scheme source code.

EOF

An EOF is a value to represent the end of file. While reading from the port which doesn't produce any more date (e.g. reading at the end of file), an eof will be returned.

Port

Ports are the IO interface in Scheme.

Vector

Vector is the most frequent used composite data structure, unlike lists that consisted with pairs and the values of each node, a vector is a complete data structure with a fixed number of continuous elements, thus provides the ability of random access by index.

A vector procedure is used to produce a vector data structure, but a faster way is to construct via # at the phase of parsing. For example, we want to construct a vector with 3 elements of 1, 2 and 3, we use the following code:

#(1 2 3)

ByteVector

Bytevectors are vector like data structures, but only stores 8-bit integers, all elements are bytes which is the most common used data format in embedded environment.

IoTSS implements the string data structure and stores chars that also the same width of bytes, but strings are not recommended, because implemented with bytes as internal elements are not guaranteed, strings can be handled by string procedures only which doesn't provide flexible enough interface to users.

Additional Data Types

IoTSS implements the following additional data types:

Raw data type stores nothing but a simple pointer which points to the data structure defined by user. Users can stores anything with this data type, garbage collector applies to raw data types when the marker and sweeper functions are provided at the time of creating the value in specified raw data type.

Customized Type is a generic data type that contains refined data type with extra standardized interfaces. The extra interfaces like display, eq?, eqv?, equal? that make the value in the data type you customized works with the standard procedures seamlessly.

Encoding

The current implementation only support source code which use UTF-8 encoding. For reducing the consume of RAM, strings stores as arrays of 8-bit char which means the strings contains the characters out of the range of ASCII can be displayed correctly

Extending C with Scheme

Setup

For extending C programs with Scheme code, confirm the iotss has been built correctly to either static library or dynamic library. Directly set the source code of iotss as a sub module of your own project is also a possible. Set the header and library path correctly to make your compiler find where IoTSS locates.

The following code is for demonstrating how to set the arguments of include path and library path and organize a Makefile:

CC = gcc
CFLAGS = -Wall -Wextra -g
INCLUDES = -I/usr/include/iotss/
LDFLAGS = -L/usr/lib/ -liotss
RM = rm -rf
SOURCES = $(wildcard *.c)
TARGET = iotss
CP = cp
RM = rm

default:
    $(CC) $(CFLAGS) $(INCLUDES) $(SOURCES) -o $(TARGET) $(LDFLAGS) 

clean:
    $(RM) $(TARGET)

Initialization

To make the declarations of IoTSS visible to you own C programs, use #include preprocess instrument as the code listed below:

#include <iotss.h>
#include <iotss_vm.h>

A virtual machine maintains a context of the running status, the bindings and other essentials to accept Scheme source code and execute them. Creating an instance of virtual machine before using is required.

The type of a virtual machine is iotss_vm_t, use the following code to define a variable points to the structure iotss_vm_t

iotss_vm_t *vm = NULL;

Use the iotss_vm_new function to create a new virtual machine.

vm = iotss_vm_new();

Options

Some options could be set to enable some functions of virtual machine. For example, to enable the optimization, we use the following statement:

IOTSS_OPTIONS_SET(vm->options, IOTSS_VM_OPTIONS_OPTZ);

Cancel the option is possible as well:

IOTSS_OPTIONS_CLR(vm->options, IOTSS_VM_OPTIONS_OPTZ);

Standard Bindings

The virtual machine created is blank that with no procedures bound, we have to bind fundamental and essential procedures before doing something with the virtual machine.

IoTSS provides the following functions for binding procedures to the initial environment:

Read the library part of reference manual for more detail.

Execute the code

There are some functions the provided to users for evaluating the Scheme source code.

iotss_obj_t *iotss_vm_eval_io( \
        iotss_vm_t *vm, \
        iotss_obj_t *env, \
        struct iotss_io *io);
iotss_obj_t *iotss_vm_eval_len(
        iotss_vm_t *vm, \
        iotss_obj_t *env, \
        const char *str, const iotss_size_t len);
iotss_obj_t *iotss_vm_eval( \
        iotss_vm_t *vm, \
        iotss_obj_t *env, \
        const char *str);
void iotss_vm_eval_noret( \
        iotss_vm_t *vm, \
        iotss_obj_t *env, \
        const char *str);

These functions are almost the same except the form of source code passed in as arguments, iotss_vm_eval_io accepts io data structure which stores a customized source, others accepts strings.

env parameter could be passed with the environment you specified that achieve different effect you want, such as the environment which contains the limited functioned bound procedure only to make the virtual machine execute the code in a sandbox like thing. If NULL has been passed as argument, the evaluation will be performed in the initial environment that has been created automatically.

Extending Scheme with C

Native Procedure Bindings

The most directly way to extend the Scheme language with C (or other low-level languages) is to write native procedure.

Overview

The process of binding and calling a native procedure is described as following:

  1. Write a native procedure in C or other low-level programming languages.
  2. Bind the native procedure to the environment (either initial one or customized) of virtual machine.
  3. Evaluate a code that calls the native procedure by name.
  4. Virtual machine looks up from the environment and locate to the native procedure.
  5. The number and types of evaluated arguments been checked and converted to the data type for easy processing by the native procedure.
  6. Native procedure retrieve the arguments, do some processing and return some value;
  7. Virtual Machine convert the returned value to the data type of the virtual machine uses.

Prototype of Native Procedure

Every native procedure should have the same signature as following:

void (*)(iotss_native_proc_args_t *args);

The C code written by users and IoTSS communicates via the args parameter which is in type iotss_native_proc_args_t.

Get Arguments

When the arguments have not been checked or the procedure accepts arity arguments, the get the specific number of the arguments is possible with the following function:

iotss_size_t iotss_native_proc_args_count(iotss_native_proc_args_t *args);

Another function is used to check if the number of arguments is zero and will be useful while processing all arguments one by one:

iotss_bool iotss_native_proc_args_empty(iotss_native_proc_args_t *args);

The following two functions retrieve one argument for all arguments:

iotss_native_proc_arg_t *iotss_native_proc_args_get_arg(iotss_native_proc_args_t *args);
iotss_native_proc_arg_t *iotss_native_proc_args_get_arg_from_tail(iotss_native_proc_args_t *args);

The difference between them is the argument been picked either from the head or from tail.

Another common requirement of retrieving arguments is to access randomly by index. Before accessing, an index should be built with the following function:

int iotss_native_proc_args_build_index(iotss_native_proc_args_t *args);

Arguments Checking

The number and types of the arguments are going to be checked before the native procedure been called. This checking relies on three parameters:

The minimum and maximum number of arguments are easy to understand, a special constant that represents skips the checking is IOTSS_NATIVE_PROC_ARGS_INFINITE.

A type signature is a string which contains a series of chars that represent types of acceptable arguments one by one.

The usable chars and what type they represent are listed below:

A . accepts an argument in any type. An 'o' keeps the argument as a virtual machine object.

Used Argument

The retrieved argument should be released manually with the following function:

void iotss_native_proc_arg_destroy(iotss_native_proc_arg_t *arg);

Return Value

The execution of a procedure should returns a value, before returning it, we should create such a value and the following functions are interface of creating the value to return:

iotss_native_proc_arg_t *iotss_native_proc_arg_new_nil(void);
iotss_native_proc_arg_t *iotss_native_proc_arg_new_void(void);
iotss_native_proc_arg_t *iotss_native_proc_arg_new_int(const int value);
iotss_native_proc_arg_t *iotss_native_proc_arg_new_bool(const iotss_bool value);
iotss_native_proc_arg_t *iotss_native_proc_arg_new_str(const char *str, const iotss_size_t len);
iotss_native_proc_arg_t *iotss_native_proc_arg_new_sym(const char *str, const iotss_size_t len);
iotss_native_proc_arg_t *iotss_native_proc_arg_new_raw(void *ptr_raw, \
        void (*mark)(struct iotss_vm *vm, void *ptr), \
        void (*sweep)(void *ptr));
iotss_native_proc_arg_t *iotss_native_proc_arg_new_err(char *str);
iotss_native_proc_arg_t *iotss_native_proc_arg_new_obj(struct iotss_obj *obj);

Most functions to deal with primitive data types are easy to figure out how they work from the name and the parameter list.

The values in raw data type could contain other values which require to be marked while doing garbage collection and inform the garbage collector that how to release the resource allocated, so the mark and sweep should be provided.

To return the created value directly set the member of args structure as the code below:

args->ret = the_return_value;

Bind to Environment

The following function is used to bind the native procedure to the initial environment:

int iotss_vm_bind_native_proc(iotss_vm_t *vm, \
        const char *str, \
        void (*callback)(iotss_native_proc_args_t *args), \
        iotss_u8 args_min, iotss_u8 args_max, \
        char *type_sig);

str parameter refers to the 'variable name' of the binding.

callback parameter refers to the callback function which has been described before.

args_min and args_max refers to the minimum and maximum number of arguments. type_sig refers to the type signature.

Static Bindings

The normal binding provide the most simple way to bind native procedures, The problem is each procedures will be assigned with a name and the name will stay in RAM which is the shortage resource in embedded systems.

Static binding is a new way of binding that solves the problem. Instead of bind procedures one by one, we bind only one variable which reference to an array of a series of procedures. While reference to the name of the procedure, it searches the procedures in the static binding one by one.

The most significant benefit of static binding is saving RAM usage, because the table which stores the names the address of procedures and the arguments checking can be been stored in Flash or some other places that outside of RAM.

Because static bindings are not completely equal to normal bindings, some common operatings apply to the normal bound procedures do not work on static bindings.

To create a static binding with specified functions, we define an array in type iotss_static_bindings_item_t[], and we use the macro IOTSS_STATIC_BINDINGS_ITEM which takes 5 arguments.

name: the name of the item of binding.

callback: the callback function of native procedure which has the prototype of static void *(iotss_native_proc_args_t *).

minumum: the minumum number of arguments.

maximum: the maximum number of arguments.

typesig: the type signature of the arguments, use the same rule of the type signature of normal native bindings.

The final item should followed with the macro IOTSS_STATIC_BINDINGS_ITEM_FINAL to tell system that the definitions of items are stopped at the point.

After creating the array which contains items of bindings, we need to bind the native bindings to the system with the following function:

int iotss_vm_bind_static_bindings_from_items(iotss_vm_t *vm, \
        const char *name, \
        iotss_static_bindings_item_t *items);

Lazy Bindings

To delay the loading of native bindings, there is another possible way besides the static bindings, the lazy bindings.

The main idea of lazy bindings is to put the binding information into a file and do binding when required.

The very first thing to do lazy bindings is to extract the information of native procedures, the most common way to do this is to use the binary utilities shipped with the development kit of the target platform (like objdump) to lists all symbols in the binary objects as well as the address of the procedures.

The lazy bindings is currently an experimental feature, and should do more testing before using in production environment.

Remote Procedure

The remote procedures provide a universal way to locate to the code you want, all it asks for is a URL (Universal Resource Locator)-like address and the code will be load while the bound procedure been called.

The remote procedure doesn't mean absolute 'remote' like the code located on another machine, it just means the experience of universal procedure access, like what users usually do while accessing a remote webpage with the URL.

The first step to bind a remote procedure is to use the procedure in standard library make-remote-procedure in the following form:

(make-remote-procedure <url>)

Different from normal procedures which stores the whole abstract syntax tree, the remote procedures store only the URLs which have no complex data structures and loaded when been called.

Library

Library is the most common way of using remote procedure. A library is considered as a normal code file consists with some procedures, the entire file with code is the library, and each procedure lies on the top-level of the library is the procedure or say provided interface of the library. The following given code is the content of the file demolib.ss:

;; demolib.ss library
(define foo
  (lambda ()
    (displayln "foo")))
(define add2
  (lambda (a b)
    (+ a b)))

The library has the filename demolib.ss, the name of library is demolib and it provides 2 procedures, foo and add2, so we got two URLs for the two procedures as following:

lib://demolib/foo
lib://demolib/add2

To make them the real remore procedures, we use the following code:

(make-remote-procedure "lib://demolib/foo")
(make-remote-procedure "lib://demolib/add2")

The made remote procedures could be use immediately:

((make-remote-procedure "lib://demolib/foo")) ;; -> "foo"
((make-remote-procedure "lib://demolib/add2") 1 2) ;; -> 3

We could also assign them to the some variables and use them in 'natural' way:

(define foo (make-remote-procedure "lib://demolib/foo"))
(define add2 (make-remote-procedure "lib://demolib/add2"))
(foo) ;; -> "foo"
(add2 1 2) ;; -> 3

Customizable Data Type

The system has some built-in data types, but the sometimes the requirements of using some customizable data types are met.

The following function is for creating a new customized type which returns a value in type iotss_obj_customized_type_t:

iotss_customized_type_t *iotss_customized_type_new( \
        const char *name, const iotss_size_t len);

The parameters name and len are the name of the customized type to be created. and for doing further configuration, the fields of the returned value in struct of iotss_customized_type_t should be set according to the requirements;

The data structure of iotss_customized_type_t is listed below:

struct iotss_customized_type
{
    char *name;
    iotss_size_t len;
    iotss_u8 checksum;
    /* Callbacks : Mark & Sweep */
    void (*cb_mark)(void *data, struct iotss_vm *vm);
    void (*cb_sweep)(void *data, struct iotss_vm *vm);
    /* Callbacks : Display as External representation */
    void (*cb_print_normal)(struct iotss_port *port, void *data);
    /* Callbacks : Display as Expression that evaluates to */
    void (*cb_print_code)(struct iotss_port *port, void *data);
    /* Callbacks : Equality */
    iotss_bool (*cb_eqvp)(struct iotss_vm *vm, \
        void *data1, void *data2);
    iotss_bool (*cb_equalp)(struct iotss_vm *vm, \
        void *data1, void *data2);
        struct iotss_customized_type *next;
};

The value of name, len, and checksum have been automatically calculated at the time of the data type been created, the selected part of the other fields are things we have to take care of.

cb_mark and cb_sweep are the interface been invoked by Garbage Collector that clean the objects and other resources in the values in this data type.

cb_print_normal and cb_print_code are similar, they output the value in some kind of forms to the port, the differences between them are the presentation. cb_print_normal prints the value in 'natural' way but the cb_print_code prints in the form of code which makes the output can directly used as the code input to the system again.

cb_eqvp and cb_equalp are the interfaces of comparing values. Generally, cb_eqvl affects the 'single' values but the cb_equalp affects 'composite' values such while using the universal equality comparing interface of Scheme eqv? and equal? as 'lists' or 'vectors'.

Because all customized type are referenced type, eq? will simply returns false when compare two same values in customized type if they are not reference to the same value.

Providing both the universal equality comparing interfaces and particular comparing interfaces are possible as well.

When the new customized data type's been created and configured, we should have it registered to make it known by the system:

void iotss_customized_type_list_append( \
        iotss_customized_type_list_t *list, \
        iotss_customized_type_t *new_customized_type);

Port

Ports are objects that represent the interface of Input/Output. A port should be created or bound before using it and passed as a argument of the procedures that process the IO.

The first thing to operate ports is to create a new port with the function below:

iotss_port_t *iotss_port_new( \
        char *name)

The function returns a value in type iotss_port_t *.

From the perspective of the data flow direction, ports could be divided into two categories, Input Ports and Output Ports. From the perspective of the type of data, ports could be divided into two categories, Textual Ports and Binary Ports.

In IoTSS a port could have both of the contrary attributes, like a port could be not only an input port but also an output port or used to read textual data and to write binary data if the related interface are ready.

The mode of ports which specifies the abilities could be set after the port been created:

#define IOTSS_PORT_MODE_INPUT (1<<0)
#define IOTSS_PORT_MODE_OUTPUT (1<<1)
#define IOTSS_PORT_MODE_BINARY (1<<2)
#define IOTSS_PORT_MODE_TEXTUAL (1<<3)

Every port has the following primitive operations:

The higher level of functions such as reading as specified width of integers are based on these primitive operations.

The full operations of a port isn't mandatory required, provide the essential ones will be enough.

Form Plugin

Forms are the fundamental part of syntax in Scheme, they have been used to extend the syntax of higher level parts of Scheme and could not easily assembled with higher parts of programming language.

The steps of creating and applying a form plugin are listed below:

Each form plugin contains a name and a callback function that process the abstract syntax tree.

The callback function has the following prototype:

typedef struct iotss_obj *(*iotss_plugin_form_callback_t)( \
        struct iotss_vm *vm, \
        struct iotss_obj *env, \
        struct iotss_obj *obj);

To create and register a new form plugin, use the following function:

int iotss_vm_register_plugin_form(iotss_vm_t *vm, \
        const char *name, const iotss_size_t len, \
        iotss_plugin_form_callback_t cb);

Literal Parser Plugin

In Scheme, constructing a value in specified data structure with related procedure is widely used, for example, the procedure list is used to construct a list, and the following code is used to construct a list which contains 3 elements:

(list 1 2 3)

Scheme also provides a fast way to create a list which looks like:

'(1 2 3)

The quote symbol has been used for constructing lists, so another symbol which is # (pronounced as 'sharp') has been provided as common symbol to construct customized data structures. Presume we want to construct a value in data type bytevector which contains an array of 8-bit integers, the name fast way we provide to construct the bytevector is vu8 then the code may looks like as following:

#vu8(1 2 3)

Constructing the value in customized data type with the literal doesn't require the process of constructing the list then apply the procedure to the list first, it constructs from the source code and produce the final value directly.

The very first thing is to write the callback of a literal parser plugin which should have the following prototype:

struct iotss_obj (*)( \
        struct iotss_vm *vm, \
        struct iotss_io *io, \
        iotss_u8 opt)

vm refers to the virtual machine. io refers to the io data structure which contains the reading status. opt refers the options of the currently parsing status, leave it 0 will be OK.

The we should process the source code from the io interface and produce the final value.

Finally we should acknowledge the system about the callback we finished.

FAQ

Q: Should I implement all operations of the operating system interface?

A: The operations are only used when necessary, for example, the file reading interface won't be used until you read require a library or open a file port. But for the consideration of functional completeness, implementing all interfaces required are recommended.

Q: What can I do if the system reports the memory allocation failed frequently?

A: That was because of the lack of the physical RAM, follow the following steps to reduce the RAM consume:

  1. Check if the related sections has been put in the right section depends on the chip and development kit.
  2. Reduce the size of store.
  3. Turn on some optimization options when necessary.
  4. Rewrite some procedures in Low level language.
  5. Delay the loading by using lazy bindings and static bindings.
  6. Check if there is unexpected error such as memory leak.

Q: The evaluation returns an error without any further detail.

A: Comment the IOTSS_DISABLE_ERR_MSG macro in iotss_config.h.

Q: I wrote some code which runs in other implementation of Scheme without problem, but returns some error.

A: IoTSS has not implement all features of Scheme, write the code with the supported syntax and procedures.

Q: Do I have to bind all standard libraries?

A: No, only bind the ones you need.