vx_api.h - The definitions of the parser for Vandalix
#include "vx_api.h"
#include <stdarg.h>
#include <stdio.h>
static void dumper(vx_talk_t * t, const char * fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vprintf(fmt, ap);
va_end(ap);
}
int main(void)
{
vx_env_t * env = vx_env_new();
obj_lock_t * code;
vx_talk_t talk;
char emsg[1024];
vx_state_t state;
env = vx_env_new();
register_parsers(env);
vx_talk_init(&talk, dumper);
env->txt = "my program";
env->len = strlen(env->txt);
env->off = 0;
env->msg = emsg;
env->msg_len = sizeof(emsg);
vx_env_space_handlers(env, vx_get_id(env, VX_NAME("comment")));
vx_env_expressions(env, vx_get_id(env, VX_NAME("expression")));
vx_env_space_is_ignored(env, 1);
// report the syntax
vx_talker(vx_env_object(env), &talk);
if(!vx_parse(env, &code))
{
fprintf(stderr, "%s\n", emsg);
obj_unlock(vx_env_object(env));
exit(-1);
}
// No more parsing is needed, so release parser
obj_unlock(vx_env_object(env));
vx_exec(code, &state);
if(state.code > 0)
{
printf("Run-time error %d\n", state.code);
if(state.arg != NULL)
{
printf("Description: %.*s\n",
(int)vx_str_length(state.arg), vx_str_start(state.arg));
obj_unlock(state.arg);
}
}
vx_talker(code, &talk);
obj_unlock(code);
return 0;
}
In general, Vandalix should not be considered a "programming language". It is intended to be a framework allowing to create an interpreter for some language. The syntax of that language is not fixed. Plus it should be very easy to embed this interpreter into other programs.
The ideas implemented in Vandalix were taken from the work of Crenshaw "Let's build a compiler".
The Vandalix framework forces few rules, otherwise it wouldn't be a "frame", but hopefully these rules won't get in the way of defining the syntax too much. In a nut shell, Vandalix expects the user to provide text parsers that generate opcodes after processing portion of the input. It is not that complex as may sound since the "divide and concur" approach can be used. For example, the syntax of an expression that outputs given strings to stdout may be defined as
<output-expression> ::= say <string> (',' <string>)*
This notation suggests that the parser for "output-expression" should first find word "say" in the input. After that it can simply let a parser for "string" to somehow find string value in the input and produce opcode for it. Then the parser for "output-expression" should see if the input contains coma (,) and if yes, then it should again let the parser for "string" to work. The parsing of "output-expression" is finished when there are no more comas in the input. The opcode for "output-expression" shall internally store list of opcodes produced by parsers for "string" and at run-time activate them to obtain the strings for the output.
As one can see, this approach does not need any explicit stack management. Anyway the interpreter itself already has stack, so the opcodes can simply rely on it.
Vandalix assumes a program to be sequence of expressions. So there must be set of parsers that produce opcodes for expressions. These parsers are called in the reverse order of registration untill one of them produces opcode. After that the process is repeated untill the input is exhausted. The parsers may indicate syntax error, in which case the parsing is stopped and the user is notified.
The produced opcodes for expressions are chained together. To execute the code, all of opcodes are activated in turn. Opcodes may request jump to different opcode or termination of the execution. Of course, the code may be executed multiple times.
Expression opcodes can be arranged into blocks so that execution of one expression may in fact be executing another block of expressions. This corresponds to BNF rule
<block> ::= <expression> *
<expression> ::= <block>
The parser maintains stack of currently parsed blocks. Any expression parser may add new block to this stack. See vx_push_block for details.
It is possible to jump from an inneren block to the outer block, but not vice-versa.
The parsers are grouped by category. Each category of parsers produces opcodes with common interface. For example, all parsers from category "expression" produce opcodes that communicate to the caller only jump information.
The category names have to be defined by the user. This way, the user can avoid name clashes. The parser internally maintains index of available categories as a convinience. It never accesses it during parsing, but when the parser is released it also releases all of the opcode producers. The only category name reserved by this header is "vx-block". This name is available as macro VX_BLOCK. The name is reserved so that opcode parsers don't have to search for this name. This category contains only one parser and this parser is provided by Vandalix.
The parsers are directly associated with the category name. So the parser can produce opcodes of one category only. All registered parsers are called one after another until one of them produces the opcode (returns non-zero). The parsers are called in the reverse order of registration.
Important. Whitespace handling. If the syntax allows ignoring of whitespaces, then it is recommended for each parser to skip whitespaces that follow the contstruct it has recognized. This way each subsequent parser may expect meaningful input right from the start. Actually, Vandalix allows to define category containing parsers for "space". This way one may process comments.
Since a "program" is human readable text, few words should be said about support for international characters. Vandalix assumes, that the text of programs is encoded using UTF-8 encoding. For simplicity, the same encoding should be used for any text data passed between opcodes. The opcodes that output text to external world or receive text input should rely on the current locale to see which encoding should be or was used. The function iconv can be used to convert from/to UTF-8.
Another note is about threads. The parser is not thread-safe. So it should not be used from 2 different threads simulataneously. The generated opcodes might be thread-safe. This already depends on the parsers. The protocols for category "expression" are thread safe. since these opcodes don't produce any values. The rest comes from parsers outside of Vandalix and this might require synchronization.
This is very grob view of the frame-work. The details are given below.
These are functions and structures important for using Vandalix. The functions needed for extending Vandalix are listed further down.
This structure is used to provide the text to be parsed and track the parsed so far data. The parsing is non-destructive. After some parser has recognized a construct it should simply adjust the current offset to point to the first byte after the recognized construct.
So, the fields that are visible to the user are
Pointer to the text (const char *)
Length of the text (size_t)
Offset where the parsing should continue (size_t)
Pointer to buffer for storing syntax errors (char *)
Size of the buffer for storing syntax errors (size_t)
No other fields are accessible to the user
This is the constructor for the vx_env_t. It returns pointer to the environment. The user has to add to this environment all of the parsers that are desired/needed. After the modules are added to the environment, it is ready to process input. Just set the pointer to the text and the parsing can be started. The same parser can be used to parse multiple programs. Though only one parsing can be active at a time.
This function is used to access object implementing interface vx_talker_mt. This object can also be used to lock/unlock the parser. When the lock count drops to 0, the parser is released.
This function does actual parsing of the code. Essentially, it calls vx_block_parser to produce block of code but on top of it, it makes sure that pasing has used up all text of the program. Additionally this function arranges catching of parsing errors (see vx_missing).
The function returns non-zero if parsing was successful. In this case, the pointer to generated code is stored in provided obj_lock_t** slot. (See, objects.h for definition of this object).
In case of error, 0 is returned and the field msg of the vx_env_t shall contain the syntax error that caused the abort of parsing. The field off shall indicate position where the parsing has failed. The message shall contain the address in lines and characters from the beginning of line.
The rest of vx_env_t is returned to the state before parsing was started.
Note, don't forget to call vx_env_expressions (and may be vx_env_space_handlers) before call to this function. Otherwise you'll get syntax error "missing expression".
The code shall be executed using vx_exec function.
The returned object implements vx_talker_mt interface.
Vandalix allows to recreate the text of the program from the resulting opcodes. This structure provides a way for outputing the text. The structure contains only one interesting field output. This field has type "void (*)(vx_talk_t *, const char *, ...)". The field should point to function that processes text as it comes. The value for this field shall be provided by the user. This way the user may catch the output into some buffer, or pass it to stdout.
The other field shift specifies number of spaces that should be placed at the beginning of the line. This field is used to control the code alignment. Assuming, that each expression takes up single line, this field should be initiated to 0.
The convinience function vx_talk_init can be used to initiate the structure. The function takes pointer to vx_talk_t and pointer to the output handler as arguments.
Both, parser and produced code implement this interface. This interface allows to obtain syntax supported by the parser, or the textual form of the parsed code.
The structure has only 2 fields. The first field has type obj_lock_mt and name l_api. The second field has type void (*)(const obj_lock_t *, vx_talk_t *). This field has name talk. Use macro VX_TALKER_INIT to initiate statically allocated structures of this type. It takes 2 pointers to functions. First argument is pointer to function for lockin/unlocking the object.
The function vx_talker_init can be used to associate vx_talker_mt with some obj_lock_t.
The function vx_talker can be used to activate function stored in field talk. The function takes the same arguments as the function in that field.
This function walks through all currently registered parsers and lets them describe the syntax of the input that they parse. This way you get definition of the supported syntax.
This structure is used to communicate information from/to opcode. It has following fields
This field has type obj_lock_t *. It is used for data exchange between opcodes. See definition for obj_lock_t in objects.h.
The opcode that sets this field must lock the object. The opcode that reads this field must unlock the object when it is not needed it anymore.
This integer field shall be used to communicate run-time error conditions or jumps.
Zero value indicates that the operation was performed successfully and the field arg contains value suitable for the caller.
To indicate the run-time error, some positive non-zero value shall be stored here. The meaning of the code can be specific to the opcode that sets it.
If necessary, the opcode may provide textual description of the error using the field arg. The methods for accessing the text are defined in structure vx_str_mt. Otherwise, the field arg must be set to NULL. If the execution has finished with this field containing positive value, don't forget to unlock the data if arg is not NULL!
If the jump is requested, this field shall be set to negative value. If the jump is done to some opcode, then the field arg shall point to the object providing methods in vx_jump_mt structure. Otherwise the arg must be set to NULL. In which case the execution is simply stopped.
Note, when executing the code, the user shall check only for run-time error indication. The Vandalix code shall never return any value to the user in arg field of the vx_state_t!.
This function executes specified code. After execution check the code field of vx_state_t to see the execution status. If the code is positive, then field arg may contain object describing the run-time exception.
This structure shall be used to provide methods for accessing textual data. The user of Vandalix may get description of run-time error in objects providing this interface.
It has following fields
This is obj_lock_mt structure. The pointer to it shall be stored in methods field of obj_lock_t.
Type is const char *(*)(obj_lock_t *). This function returns pointer to the beginning of the textual (or maybe binary) data. Convenience function vx_str_start can be used to activate this method.
Type is size_t (*)(obj_lock_t *). This function provides the length of the data. Convenience function vx_str_length can be used to activate this method.
The macro VX_STR_INIT can be used to initiate statically allocated structure containing methods for string access. The macro takes 3 arguments: function for locking, function for start address and function for the length.
Opcodes in Vandalix store their data in structures wrapped around obj_hold_t and provide methods for working with that data in structure vx_op_mt.
The structure vx_op_mt has following fields
This field has type obj_hold_mt. Pointer to this field is stored in obj_hold_t structure.
This field has type void (*)(const obj_hold_t *, vx_talk_t *). The function pointed by this field is called to restore the text from which this opcode was generated. The function vx_op_talk can be used to activate function pointed by this field.
This field has type void (*)(obj_hold_t *, vx_state_t *). The function pointed by this field is called at run time. The function performs the actions of the opcode. The second argument to this function shall be used to communicate status of the execution and the input/output. See description of vx_state_t.
The function vx_op_exec can be used to activate function pointed by this field.
If this structure is statically allocated, then macro VX_OP_INIT can be used to initiate it. This macro takes 3 arguments. First argument is pointer to function that shall release the object, the second argument is pointer to function for field talk and third argument is pointer for function for field exec.
To associate obj_hold_t with appropriate methods, function vx_op_init can be used. This function takes pointer to obj_hold_t and pointer to vx_op_mt as argument. =cut
*/
static inline void vx_op_init(obj_hold_t * op, const vx_op_mt * type) { obj_hold_init(op, &type->h_api); }
#define VX_OP_INIT(_d,_t,_e) { .h_api = OBJ_HOLD_INIT(_d), .talk = _t, .exec = _e }
static inline void vx_op_talk(const obj_hold_t * op, vx_talk_t * t) { vx_op_mt * m = (vx_op_mt *)op->methods; m->talk(op, t); }
static inline void vx_op_exec(obj_hold_t * op, vx_state_t * state) { vx_op_mt * m = (vx_op_mt *)op->methods; m->exec(op, state); }
/*
Structure vx_jump_mt, as already said, is used to define address of an expression to which jump has to be done. The Vandalix code is separated into blocks of expressions. So there are 2 additional functions in vx_jump_mt structure.
Type of this field is obj_lock_mt. The pointer to this field is stored in obj_lock_t structure.
Type is obj_hold_t *(*)(obj_lock_t *). This function returns address of the block to which the jump is done. Remember, the jumps are possible only to the enclosing blocks, not to enclosed ones. At parsing time, the address of a block is obtained using vx_block_code function. One can use convenience function vx_jump_block(obj) to activate this method.
Type is obj_hold_t *(*)(obj_lock_t *). This field provides the address of the expression within block to which the jump shall be done. When set to NULL, it simply requests that specified block ends. One can use convenience function vx_jump_exp(obj) to activate this method.
The macro VX_JUMP_INIT can be used to initiate statically allocated structure containing methods for returning addresses. The macro takes 3 arguments: function for locking, function for block address and function for expression address.
The function vx_jump_init can be used to attach vx_jump_mt to obj_lock_t. The function takes pointers to obj_lock_t and vx_jump_mt as arguments.
The function vx_str_copy can be used to allocate object that contains copy of specified text. The function vx_str_ptr can be used to create object that references specified STATIC string, in other words it just contains the pointer to the string. Both functions take pointer to text and length of text as arguments. They return locked object, so that next unlock would release the space.
Another function that might be convenient is vx_str_space. It does the same thing as vx_str_copy, except that it takes only one argument: the length of string. The user shall obtain pointer to allocated space using vx_str_space_ptr and copy there the string. After that the user may trim the length of the string using vx_str_space_trim function, which takes new length as second argument.
To initiate an object wich impelements string interface, the function vx_str_init can be used. This function takes pointer to obj_lock_t and pointer to vx_str_mt as arguments.
All parsers are wrapped around obj_el_t structure. See its description in objects.h. The parser must implement methods defined in structure vx_parser_mt.
The structure vx_parser_mt has following fields.
This field has type obj_el_mt. The pointer to this field is stored in obj_el_t.
This field has type void (*)(const obj_el_t *, vx_talk_t *, const char *). The function pointed by this field is called when the syntax handled by this parser shall be reported. The last argument is pointer to 0-terminated category name. The output produced by the function shall be something like
<category-name> ::= name <arg-category>
In general, the BNF notation should be used, of course with modifications. Since it is easier to provide alternatives for some non-terminal by just repeating the declaration of non-terminal, this approach is used instead of using '|'. So
<non-terminal> ::= expression1
<non-terminal> ::= expression2
is the same as
<non-terminal> ::= expression1 | expression2
The non-terminals shall appear within '<' and '>'. There are special characters that are used in writing rules. Here's the list of them.
'(' and ')' are used to group things together.
'[' and ']' can be used to specify list of valid characters.
'|' can be used to introduce alternatives
'*' can be used to indicate any number of preceeding items (groups).
'+' can be used to inicate 1 or more of preceeding items (groups).
'?' can be used to indicate optional item (group).
A pair of '/' can be used to define local non-terminal that is valid only till the declaration of next global non-terminal.
The character '%' followed by 2 hex digits shall be used to introduce binary values for characters.
The '#' starts comment which continues till the end of the current line
A pair of single quotes (') or double quote (") shall be used to quote these characters so that they loose their special meaning.
The rest of characters are interpreted as terminals. The spaces are only separators between terminal. It is assumed, that where spaces appear in the rules, the spaces or comments may appear in the code.
Note, the field shift of vx_talk_t can be used to align all of the comments.
This field has type int (*)(obj_el_t *, vx_env_t *, obj_hold_t **). The function pointed by this field is the one that does actual input processing. It shall return non-zero if it could produce opcode. Zero indicates that opcode was not produced and Vandalix should try another parser. The last argument is the slot where produced opcode must be anchored. Since any of the parser may call vx_missing function which performs long jump, it is important to store pointers to allocated opcodes in the provided slot BEFORE letting other parsers to look at the input.
The statically allocated structures of type vx_parser_mt can be initiated using macro VX_PARSER_INIT. This macro takes 3 arguments: pointers to functions for field e_api.drop, talk and parse in this order.
Function vx_parser_init can be used to initiate obj_el_t. The function has prototype void (*)(obj_el_t *, const vx_parser_mt *).
Function void vx_add_parser(vx_category_t *, obj_el_t *) shall be used to register parsers of this type with the parsing environment. The first argument is pointer to the slot, where all parsers of this category are stored. To obtain this pointer function vx_category_t * vx_get_id(vx_env_t *, const char *, size_t) shall be used. The last 2 arguments of this function are the pointer to category name and the length of that name. Note, in case if the category name is a constant string, the macro VX_NAME can be used. This macro takes the constant string as the argument and produces 2 arguments for the function call. Ie.
vx_get_id(env, VX_NAME("my_category"));
Basically it expands as ARG, sizeof(ARG) - 1
To find out the name associated with some vx_category_t use function vx_category_name. The function takes vx_category_t * and returns const char *.
Vandalix parser defines following events
The handlers of this event are activated just before the parsing begins. At this time there are still no blocks in the blocks stack. The handlers are activated only once per parsing. One can add handlers for this event at any time. The handlers are executed in the order of addition and are not removed from the list. The handlers for this event are registered using function vx_at_parsing_start.
The handlers are dropped from the list at the destruction of parser.
The handlers are objects wrapped around obj_el_t and implementing methods defined in vx_action_mt structure.
The handlers of this event are activated right after new block was added to the stack of blocks. The parsing of the block hasn't started yet. One can add handlers for this event at any time. The handlers are executed in the order of addition and are not removed from the list. The handlers for this event are registered using function vx_at_new_block.
The handlers are dropped from the list at the destruction of parser
The handlers are objects wrapped around obj_el_t and implementing methods defined in vx_action_mt structure.
The handlers of this event are activated after parsing of the block has been finished without any error. The block is still on the stack, but shall be removed soon after the handlers have finished their work. The handlers are block specific. Before activation they are removed from the list. They can be added only to the top-level block, so this type of handlers may be added only when there are blocks in the stack.
The handlers are executed in the reverse order of addition. The handlers for this event are registered using function vx_at_block_end.
Note, the handlers are not executed in case of syntax error, so they have to be removed by some other handler.
The handlers are objects wrapped around vx_pclean_t. They don't need to implement any special methods.
The handlers for this event are activated in case of parsing abortion via vx_missing function. Just like End of block handlers, these ones are called in the reverse order of registration and are removed from the list before activation. This happens BEFORE the blocks are released and the handlers associated with End of Block are activated.
The handlers can be added only during parsing, so one should use Parsing Start handlers or other ways to add this type of handling. The handlers are registered using function vx_at_syntax_error
Note, in case of success these handlers are not dropped from the list, so other event handlers must remove them!
The handlers are objects wrapped around vx_pclean_t. They don't need to implement any special methods.
The handlers for this event are activated only when the parser is released. The handlers shall be wrapped around obj_el_t and don't need to implement any other methods, but the drop method. Use vx_at_release function to register such handlers.
Here are the fields that are present in vx_action_mt structure.
Type obj_el_mt. Pointer to this field is stored in obj_el_t. It contains the function which is called when the object is detached from the list.
Type void (*)(obj_el_t *, vx_env_t *). This is pointer to the function that performs action. The function is called when event has happened. The function vx_action can be used to activate this field. It takes the same arguments.
Macro VX_ACTION_INIT can be used to initiate static structures of type vx_action_mt. It takes 2 arguments. First argument is the pointer to function called when the object is dropped from the list. Second element is the pointer to action function.
Function vx_action_init can be used to associate the structure with obj_el_t. It has type void (*)(obj_el_t *, const vx_action_mt *)
Here are the fields that are present in vx_pclean_t structure.
This field has type llist_t. Using this field the object is attached to the chain of elements and it can be removed at any time.
This field has type void (*)(vx_pclean_t *, vx_env_t *). This field must point to function that shall be called when the event happens. The element is removed from the list before the function is called.
The function vx_pclean_init can be used to initiate vx_pclean_t structure. It takes pointer to the structure and pointer to function.
Quite often, at run-time certain actions have to be performed that are not part of program flow, but rather responsible for returning everything to original conditions. Such cleaners shall be wrapped around the structure obj_hold_t. They must implement methods defined in vx_cleaner_mt structure.
The structure has following fields
Type obj_hold_mt. Pointer to this field is stored in obj_hold_t.
Type void (*)(obj_hold_t *). This is pointer to function that shall perform the work. The function is called when the block to which it is attached has finished its work. The function vx_cleaner can be used to activate function from this field.
Macro VX_CLEANER_INIT can be used to initiate static structure vx_cleaner_mt. It takes 2 arguments. First is function called when the structure is dropped from the list, the second one if function that is called at the end of block execution.
Function vx_cleaner_init can be used to initiate the obj_hold_t. It has type void (*)(obj_hold_t *, const vx_cleaner_mt *)
Function void vx_block_cleaner(vx_env_t *, obj_hold_t *) shall be used to attach specified cleaner to the currently parsed block. So the cleaner shall be executed at the end of block execution at run-time (if the block was executed at all). It is an error to call this function outside of parsing.
The cleaner is dropped from the chain when the opcodes are released.
Since the naming policy for variables may be different, Vandalix does not define it. Instead it defines simple API for handling names. The user has to provide an object that implements this API. The parsers shall assume that such object exists and simply call appropriate methods.
It is assumed, that the parsers shall perform following actions with named objects.
The parser may need to know if the input contains name at current position and that name is associated with some object.
The parser may need to add new named object, and it needs to know if this operation is allowed.
At run time, the objects associated with some name have to be reset at the end of its life-time. Normally, for block specific names this should happen when the block execution is finished.
So, the object managing names shall implement methods defined in vx_namer_mt structure. This structure has following fields.
This field contains pointer to function with type size_t (*)(obj_lock_t *, const vx_env_t *) The function shall be called to see if the input contains something that looks like name. The function returns length of name if it is present. 0 indicates that no name is available. The function vx_namer_is_at_name may be used to activate this method. The function takes the same arguments.
This field contains pointer to function with type size_t (*)(obj_lock_t *, const vx_env_t *, const char *, const char *) The function shall be called to see if the input starts with specified text. The function makes sure, that nothing that can be part of the name follows specified text. The function allows use of "namespace" prefix passed as first argument. The first and second strings are expected to be one after another. The function returns 0 if the text is not found in the input. Otherwise it returns the number of bytes that both names take up in the input. The convinience function vx_namer_match_txt may be used to activate this method. It takes the same arguments
This field contains pointer to function with type int (*)(obj_lock_t *, const char * , size_t). The function shall be called to see if specified name can be added to the tracker. The function returns non-zero if reserving of space was successfull. Zero is returned if such name can not be added. The convinience function is called vx_namer_reserve.
This field contains pointer to function with type void (*)(obj_lock_t *, obj_hold_t *). The function is used to actually attach object to previously reserved name. This function must be called right after the "reserve". The convinience function is called vx_namer_add.
This field contains pointer to function with type obj_hold_t * (*)(obj_lock_t *, const char *, size_t). The function is used to see if there is object associated with given name. The function returns appropriate pointer, or NULL, if the name is unknown.
If the function is called with last argument set to 0 it shall return pointer to the first of currently visible names. One can follow the chain via "chain" field of each of the structures. The chain ends when the field is set to NULL.
The convinience function is called vx_namer_find.
The function void vx_env_namer(vx_env_t *, obj_lock_t *) shall be used to specify the names controller for the parser. The parser shall unlock the object during parser destruction.
The function obj_lock_t * vx_env_names(vx_env_t *) shall be used to get pointer to the names controller. Note, the object is not locked!
To initiate statically allocated structure containing all of the methods, the macro VX_NAMER_INIT can be used. The macro takes 6 arguments: locker function, functions for is_at_name, match_txt, reserve, add and find fields.
To associate vx_namer_mt with obj_lock_t one may use vx_namer_init function. This function takes 2 arguments, pointer to obj_lock_t and pointer to vx_namer_mt.
The named object has to implement methods defined in vx_name_mt structure. The object itself shall be wrapped around obj_hold_t. The named objects shall be chained together both at parsing and at run time. They shall be removed from the chain only when opcodes are released.
The vx_name_mt structure shall have following fields.
This field contains pointer to function with type const char * (*)(const obj_hold_t *). It shall return the name that is associated with the object. It is needed mostly for restoring the code, but it might be used also during parsing to index all names. The convinience function for activating this method is called vx_name.
This field contains pointer to function with type vx_method_t (*)(const obj_hold_t *, const vx_category_t *). The function shall be used to see if the named object supports calling conventions defined by specified category. The function must return NULL if such convention is not possible. Otherwise it shall return pointer to function that has type void (*vx_method_t)(obj_hold_t *, vx_state_t *). The convenience function for activating this method is vx_name_api.
This field contains pointer to function with type void (*)(obj_hold_t *). It is called at run-time to indicate, that the execution has been finished. The convenience function is called vx_name_reset.
The macro VX_NAME_INIT can be used to initiate static structures of type vx_name_mt. The macro takes 4 arguments. First comes the function called when the object is dropped from the chain, then come functions for fields name, api and reset in this order.
To associate structure vx_name_mt with obj_hold_t object one should use vx_name_init function. It takes 2 arguments. Pointer to obj_hold_t and pointer to vx_name_mt.
This is the list of functions that are necessary or convinient in different situations during parsing.
This function should be used to parse input and produce opcode with type corresponding to specified category ID. Basically, this function just lets all parsers attached to specified category to look at the input. The function stops as soon as some parser returns non-zero. The parsers are checked in the reverse order of registration.
The function returns non-zero if opcode was produced. Zero is returned if none of the parsers could produce the opcode. The opcode shall be placed into specified slot.
This function can be used to include at current position code from another location. The code is passed in the last 2 arguments. The code is expected to produce block of expressions, so after successfull parsing the slot shall contain pointer to the opcode with chain of expressions. This is the same as for opcode returned by vx_block_code.
If the parsing has failed, then the function does not return. In this case the error shall have text " included at line X, offset Y" appended to the message about syntax error.
This function returns 1 when called inside of vx_parse. It might be usefull.
This function can be used by modules to abort parsing. This function does not return, so before calling it make sure that you have released the memory, or placed it into some slot provided by the caller.
The second argument shall be the description of what was not found in the input. This description is taken as printf style format for subsequent arguments.
The function copies into space defined with fields "msg" and "msg_len" the text describing problem. First it places text "Missing ", then whatever was passed to it as arguments. After that it adds text " at line X, offset Y" defining position where the object was expected. The field "len" of the vx_env_t is updated to contain length of the error text. Note, the lines counting starts with 1, not with 0. This is how it is done in vim editor :)
Of course, if field "msg_len" is 0, then no message will be produced.
This function shall be used to skip any number of spaces at the current position. The spaces are everything that function isspace considers to be a space.
The function uses parsers from category set via vx_env_space_handlers to skip any comments as well.
The function should be used to specify set of parsers that handle comments in the text The parsers are activated inside of vx_skip_space. The parsers must not produce any opcodes, so when they are called, their obj_hold_t** argument is NULL!
The function should be used to specify set of parsers that shall be called to produce expressions. Without this set no parsing is possible.
Use this function to tell the parser that it should strip any spaces from the beginning of the input (non-zero second argument). When called with zero, then parser won't try to skip the space. By default, the parser shall strip spaces at the beginning of the input before attempting to find a block.
Use this function to tell the parser that at the end of the block parsing, the vx_block_was_limited must return TRUE. This shall be used when some value is expected from the code. In this case, some parser shall be used to return the value and indicate that the block was terminated.
This function reports the value last set by vx_env_block_must_be_limited.
This function becomes necessary if you want to handle some sequence of expressions as single block visible only to you. This functions adds new block to the stack of blocks. Then you shall call function vx_block_parser and then obtain pointer to the opcode for the block using vx_block_code. After that you should call vx_pop_block with flag VX_KEEP_CODE. Make sure that your syntax allows termination of an internal block!
To execute the expressions in this block simply call function pointed by field "exec" in vx_op_mt.
The function vx_push_block shall be used to initiate parsing of new block of expressions. This function must be used after the parsing of this new block is finished. This function removes the block from the top of the stack of blocks.
The second argument specifies if the generated opcodes shall be kept. Pass VX_DEL_CODE if you want the opcodes to be released. Otherwise pass VX_KEEP_CODE. Don't forget to obtain pointer to the opcodes using vx_block_code function.
This function looks similar to vx_parse, but it shall be used only when parsing some internal block of code. The parser just produces chain of expressions untill no more expressions are found in the input. At this point, the parsing is considered to be finished and the actions registered with vx_at_block_end are activated.
This function can be used inside of an expression parser to request, that the parsing of the current block stops after this expression. Can be used to make sure that there are no expressions present after the one that jumps away from block.
This function shall return non-zero if the parsing of current block was stopped as result of call to vx_block_last_exp.
This function returns pointer to the opcode for the currently parsed block.
Andrei A. Voropaev