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 */