From: Clark C. E. <cc...@cl...> - 2003-09-22 02:54:36
|
Sorry about being so prolific... I'm pondering "C" APIs. Given that most of the API complexity is wrapped up in the actual bytecodes and their definitions; it makes sending bytecodes between components quite easy. There are two methods for sending bytecodes, one instruction at a time, or as a bytecode buffer, which is a chunk of the textual format. Each one of these methods has a push and a pull variant. The method I outlined earlier, was the buffer method: | typedef enum { | YAML_OK = 0, /* proceed */ | YAML_E_MEMORY = 'M', /* could not allocate memory */ .. | YAML_E_PARSE = 'P' /* parse error, check bytecodes */ | } yaml_result_t; | | typedef const yaml_char_t *yaml_buffer_t; /* argument to a code */ | | /* producer pushes a null terminated buffer filled with one or more | * bytecode events to the consumer; if the consumer's result is not | * YAML_OK, then the producer should stop */ | typedef void * yaml_consumer_t; | typedef | yaml_result_t | (*yaml_push_t)( | yaml_consumer_t self, | yaml_buffer_t buff | ); | | /* consumer pulls bytecode events from the producer; in this case | * the buffer is owned by the producer, and will remain valid till | * the pull function is called once again; if the buffer pointer | * is set to NULL, then there are no more results; it is important | * to call the pull function till it returns NULL so that the | * producer can clean up its memory allocations */ | typedef void * yaml_producer_t; | typedef | yaml_result_t | (*yaml_pull_t)( | yaml_producer_t self, | yaml_buffer_t *buff /* to be filled in by the producer */ | ); In the above case, the buffer contains bytecodes together with the various scalar values, etc. The other option, is the one which Oren was proposing to me over the phone (only that he did not use a structure... I think the structure is probably useful for building processing chains rather than using args). typedef struct yaml_instruction_s { yaml_char_t code, const yaml_char_t *start; /* NULL unless bytecode has an argument */ const yaml_char_t *finish; /* length of argument is finish - start */ } *yaml_instruction_t; typedef result_t (*yaml_pullinst_t)( yaml_producer_t self, yaml_instruction_t *inst ); typedef result_t (*yaml_pushinst_t) ( yaml_consumer_t self, yaml_instruction_t inst ); Note it is easy to go from pushbuff_t to pushinst_t beacuse the intermediate converter need only keep a current instruction as its data (and the previous producer in the chain; going the other way probably requires variable length memory allocation. Is there another approach? The goal is to keep the API "RISC" style, generic across all instructions. In the pushinst, the only deviation would be that '<' WHOLE_SCALAR would not have the <<HERE stuff. Best, Clark |