Pokology - a community-driven site around GNU poke
_____
---' __\_______
______) Introduction to libpoke library
__)
__)
---._______)
Table of Contents
_________________
Introduction
Initialization
Error handling
Compilation and execution of Poke code
Dealing with Poke values
Dealing with Poke types
Dealing with IO spaces
Introduction
============
GNU poke project has two major components: libpoke library and poke
editor program. libpoke provides the compiler and execution
environment for the Poke programming language, and poke is a REPL
program to edit binary data.
Here we'll talk about libpoke (GNU poke 4.0+).
*NOTE* Identifiers with _p suffix in libpoke API represent predicates.
The type of a predicate is int. Zero represents false and non-zero
represents true.
Initialization
==============
To get a new Poke environment (compiler and execution environment),
you have to call one of the following functions:
,----
| pk_compiler pk_compiler_new (struct pk_term_if *terminal_interface);
|
| pk_compiler pk_compiler_new_with_flags (struct pk_term_if *terminal_interface,
| uint32_t flags);
`----
The terminal_interface argument abstracts the output handling. This is
an example implementation of the interface to write everything in
stdout.
,----
| static void
| tif_flush(pk_compiler pkc)
| {
| (void)pkc;
| fflush(stdout);
| }
| static void
| tif_puts(pk_compiler pkc, const char* s)
| {
| (void)pkc;
| printf("%s", s);
| }
| static void
| tif_printf(pk_compiler pkc, const char* fmt, ...)
| {
| va_list ap;
|
| (void)pkc;
|
| va_start(ap, fmt);
| vprintf(fmt, ap);
| va_end(ap);
| }
| static void
| tif_indent(pk_compiler pkc, unsigned int level, unsigned int step)
| {
| (void)pkc;
|
| putchar('\n');
| for (unsigned int i = 0; i < step * level; ++i)
| putchar(' ');
| }
| static void
| tif_class(pk_compiler pkc, const char* name)
| {
| (void)pkc;
| (void)name;
| }
| static int
| tif_class_end(pk_compiler pkc, const char* name)
| {
| (void)pkc;
| (void)name;
| return 1;
| }
| static void
| tif_hlink(pk_compiler pkc, const char* name, const char* id)
| {
| (void)pkc;
| (void)name;
| (void)id;
| }
| static int
| tif_hlink_end(pk_compiler pkc)
| {
| (void)pkc;
| return 1;
| }
| static struct pk_color
| tif_color(pk_compiler pkc)
| {
| static struct pk_color c = {
| .red = 255,
| .green = 255,
| .blue = 255,
| };
| (void)pkc;
| return c;
| }
| static struct pk_color
| tif_bgcolor(pk_compiler pkc)
| {
| static struct pk_color c = {
| .red = 0,
| .green = 0,
| .blue = 0,
| };
| (void)pkc;
| return c;
| }
| static void
| tif_color_set(pk_compiler pkc, struct pk_color c)
| {
| (void)pkc;
| (void)c;
| }
| static void
| tif_bgcolor_set(pk_compiler pkc, struct pk_color c)
| {
| (void)pkc;
| (void)c;
| }
|
| static struct pk_term_if tif = {
| .flush_fn = tif_flush,
| .puts_fn = tif_puts,
| .printf_fn = tif_printf,
| .indent_fn = tif_indent,
| .class_fn = tif_class,
| .end_class_fn = tif_class_end,
| .hyperlink_fn = tif_hlink,
| .end_hyperlink_fn = tif_hlink_end,
| .get_color_fn = tif_color,
| .get_bgcolor_fn = tif_bgcolor,
| .set_color_fn = tif_color_set,
| .set_bgcolor_fn = tif_bgcolor_set,
| };
`----
All of the terminal interface callbacks get the current instance of
Poke compiler (pk_compiler).
One can use pk_set_user_data function to associate an opaque pointer
to the current compiler instance. That pointer can be retrieved later
(e.g., inside the terminal interface callbacks) using pk_get_user_data
function.
,----
| void pk_set_user_data (pk_compiler pkc, void *user_data);
| void *pk_get_user_data (pk_compiler pkc);
`----
The pk_compiler can be destroyed by calling pk_compiler_free function.
*NOTE* libpoke, currently, uses global variables to store some
internal data, so you cannot have more than one compiler instance per
process. This issue will be fixed in near future.
Error handling
==============
Error code of failed invocation of API functions can be retrieved
using pk_errno function.
,----
| #define PK_OK 0
| #define PK_ERROR 1
| #define PK_ENOMEM 2
| #define PK_EEOF 3
| #define PK_EINVAL 4
|
| int pk_errno (pk_compiler pkc);
`----
Compilation and execution of Poke code
======================================
There are four functions to compile and run Poke code:
,----
| /* Compile and run code from file `filepath` and report the status */
| int pk_compile_file (pk_compiler pkc, const char *filepath,
| pvm_val *exit_exception);
|
| /* Compile and run code from C string `buffer` */
| int pk_compile_buffer (pk_compiler pkc, const char *buffer,
| const char **end, pvm_val *exit_exception);
|
| /* Compile and run the statement in C string `buffer` and report the value */
| int pk_compile_statement (pk_compiler pkc, const char *buffer,
| const char **end, pk_val *val,
| pvm_val *exit_exception);
|
| /* Compile and run the expression in C string `buffer` and report the value */
| int pk_compile_expression (pk_compiler pkc, const char *buffer,
| const char **end, pk_val *val,
| pvm_val *exit_exception);
|
| /* Similar to pk_compile_buffer but also gets location information. */
| int pk_compile_buffer_with_loc (pk_compiler pkc, const char *buffer,
| const char *source,
| uint32_t line, uint32_t column,
| const char **end, pk_val *exit_exception);
|
| /* Similar to pk_compile_statement but also gets location information. */
| int pk_compile_statement_with_loc (pk_compiler pkc, const char *buffer,
| const char *source,
| uint32_t line, uint32_t column,
| const char **end, pk_val *val,
| pk_val *exit_exception);
|
| /* Similar to pk_compile_expression but also gets location information. */
| int pk_compile_expression_with_loc (pk_compiler pkc, const char *buffer,
| const char *source,
| uint32_t line, uint32_t column,
| const char **end, pk_val *val,
| pk_val *exit_exception);
`----
Despite the fact that these functions have only compile in their
names, they actually do more than compiling the code! They *compile*
and *run* the provided Poke code. These functions are more like eval
function in some dynamic languages (like Lisp, Python, Perl,
JavaScript, ...).
Dealing with Poke values
========================
Declarations in Poke are either a variable, or a function, or a type.
,----
| #define PK_DECL_KIND_VAR 0
| #define PK_DECL_KIND_FUNC 1
| #define PK_DECL_KIND_TYPE 2
`----
You can check for the existence of an identifier using pk_decl_p
function.
,----
| int pk_decl_p (pk_compiler pkc, const char *name, int kind);
`----
E.g., pk_decl_p (pkc, "x", PK_DECL_KIND_VAR) returns 1 if the variable
x is already defined in current Poke environment.
All Poke entities are accessible through a handle of type pk_val.
E.g., pk_decl_val (pkc, "x") returns a handle of type pk_val to Poke
variable x. If the x is not declared, it returns PK_NULL.
,----
| pk_val pk_decl_val (pk_compiler pkc, const char *name);
`----
You can declare a new variable using pk_defvar function. All variables
need an initial value.
,----
| int pk_defvar (pk_compiler pkc, const char *varname, pk_val val);
`----
Changing the value of a variable is possible using pk_decl_set_val
function.
,----
| void pk_decl_set_val (pk_compiler pkc, const char *name, pk_val val);
`----
You can define signed/unsigned integers using the following functions:
,----
| pk_val pk_make_int (pk_compiler pkc, int64_t value, int size);
| pk_val pk_make_uint (pk_compiler pkc, uint64_t value, int size);
`----
The value and size of integers are accessible using the following
functions:
,----
| int64_t pk_int_value (pk_val val);
| uint64_t pk_uint_value (pk_val val);
|
| int pk_int_size (pk_val val);
| int pk_uint_size (pk_val val);
`----
Functions to deal with strings:
,----
| pk_val pk_make_string (pk_compiler pkc, const char *str);
| const char *pk_string_str (pk_val val);
`----
Offsets:
,----
| pk_val pk_make_offset (pk_compiler pkc, pk_val magnitude, pk_val unit);
| pk_val pk_offset_magnitude (pk_val val);
| pk_val pk_offset_unit (pk_val val);
`----
If the value is a callable (a function or a lambda), you can call it
using pk_call:
,----
| int pk_call (pk_compiler pkc, pk_val cls,
| pk_val *ret, pk_val *exit_exception,
| int narg, ...);
`----
Return value will be reported in ret and unhandled exception in
exit_exception.
E.g., to call a Poke function foo with signature (int<32> i,
string s) int<64>:
,----
| pk_val ret;
| pk_val exception;
| pk_val i = pk_make_int (pkc, /*value*/ 1, /*width*/ 32);
| pk_val s = pk_make_string (pkc, "Hi");
|
| /* PK_NULL should be the last item in arguments list */
| pk_call (pkc, pk_decl_val (pkc, "foo"), &ret, &exception, 2, i, s);
`----
Dealing with Poke types
=======================
Simplest types are string and any:
,----
| pk_val pk_make_string_type (pk_compiler pkc);
| pk_val pk_make_any_type (pk_compiler pkc);
`----
To create integral types like uint<3> or int<32>, you can
use pk_make_integral_type. size is an uint<64> value represents
the width of the integral type in bits. signed_p is an int<32>.
,----
| pk_val pk_make_integral_type (pk_compiler pkc,
| pk_val /*uint<64>*/ size,
| pk_val /*int<32>*/ signed_p);
`----
E.g., to create uint<3>:
,----
| pk_make_integral_type (pkc,
| pk_make_uint (pkc, 3, 64),
| pk_make_int (pkc, 0, 32));
`----
To inspect the type:
,----
| pk_val pk_integral_type_size (pk_val type);
| pk_val pk_integral_type_signed_p (pk_val type);
`----
To create and inspect offset types (e.g.,
offset<int<32>,8>):
,----
| pk_val pk_make_offset_type (pk_compiler pkc,
| pk_val base_type, pk_val /*uint<64>*/ unit);
| pk_val pk_offset_type_base_type (pk_val type);
| pk_val pk_offset_type_unit (pk_val type);
`----
E.g., to create offset<int<32>,8>:
,----
| pk_make_offset_type (
| pkc,
| /*base_type*/ pk_make_integral_type (pkc,
| /*size*/ pk_make_uint (32, 64),
| /*signed_p*/ pk_make_int (1, 32)),
| /*unit*/ pk_make_uint (pkc, 8, 64));
`----
To create a struct type:
,----
| pk_val pk_make_struct_type (pk_compiler pkc, pk_val nfields, pk_val name,
| pk_val *fnames, pk_val *ftypes);
`----
Let's see an example:
,----
| /* Poke code:
| *
| * type S = struct
| * {
| * int<32> i;
| * string s;
| * };
| */
|
| #define NFIELDS 2
| pk_val nfields = pk_make_uint (pkc, NFIELDS, /*size*/ 64);
| pk_val fnames[NFIELDS] = {
| pk_make_string (pkc, "i"),
| pk_make_string (pkc, "j"),
| };
| pk_val ftypes[NFIELDS] = {
| pk_make_integral_type (pkc,
| /*size*/ pk_make_uint (pkc, 32, 64),
| /*signed_p*/ pk_make_int (pkc, 1, 32)),
| pk_make_string_type (pkc),
| };
| pk_val S = pk_make_struct_type (pkc, NFIELDS, fnames, ftypes);
`----
Functions to make/inspect an array type (e.g, string[10]):
,----
| pk_val pk_make_array_type (pk_compiler pkc,
| pk_val element_type, pk_val bound);
| pk_val pk_array_type_etype (pk_val type);
| pk_val pk_array_type_bound (pk_val type);
`----
Examples:
,----
| /* string[10] */
| pk_make_array_type (pkc,
| /*etype*/ pk_make_string_type (pkc),
| /*bound*/ pk_make_uint (pkc, 10, 64));
|
| /* uint<32>[8#B] */
| pk_make_array_type (
| pkc,
| pk_make_integral_type (/*size*/ pk_make_uint (pkc, 32, 64),
| /*signed_p*/ pk_make_int (pkc, 0, 32)),
| pk_make_offset_type (
| /*base_type*/ pk_make_integral_type (pkc,
| /*size*/ pk_make_uint (32, 64),
| /*signed_p*/ pk_make_int (1, 32)),
| /*unit*/ pk_make_uint (8, 64)));
`----
Dealing with IO spaces
======================
/* WIP */