|
INTRODUCTIONThis manual page provides a tutorial for the creation of a simple application that embeds the KL-EL compiler and interpreter.BASIC EXAMPLEHere is a simple application that uses KL-EL, in its entirety. Note that the line numbers are for explanatory purposes.1 #include <klel.h> 2 #include <stdio.h> 3 #include <stdlib.h> 4 5 int 6 main(int iArgumentCount, char **ppcArgumentVector) 7 { 8 KLEL_CONTEXT *psContext = NULL; 9 KLEL_VALUE *psResult = NULL; 10 char *pcExpression = (iArgumentCount >= 2) ? ppcArgumentVector[1] : ""; 11 char *pcMessage = NULL; 12 size_t szLength = 0; 13 psContext = KlelCompile(pcExpression, 0, NULL, NULL, NULL); 14 if (KlelIsValid(psContext)) 15 { 16 psResult = KlelExecute(psContext); 17 if (psResult != NULL) 18 { 19 pcMessage = KlelValueToString(psResult, &szLength); 20 fprintf(stdout, "result: %s\n", pcMessage); 21 KlelFreeResult(psResult); 22 free(pcMessage); 23 } 24 else 25 { 26 fprintf(stderr, "error: %s\n", KlelGetError(psContext)); 27 } 28 } 29 else 30 { 31 fprintf(stderr, "error: %s\n", KlelGetError(psContext)); 32 } 33 KlelFreeContext(psContext); 34 35 return 0; 36 } This example includes basic error handling and shows how simple embedding KL-EL can be. When compiled and linked with the KL-EL library, this program will take the first argument (which represents a KL-EL expression) and compile/execute it. Then, the result of that execution is converted to a string value and printed to stdout. Let's analyze what's going on here. The first really interesting call is this: 13 psContext = KlelCompile(pcExpression, 0, NULL, NULL, NULL); This compiles the expression (or an empty string if the user didn't provide an argument) and returns a KLEL_CONTEXT structure. The 0 means that we aren't passing any special flags to the compiler. The next two NULLs mean that we're not exporting any variables into the KL-EL envrionment, and the final NULL means that we have no user-defined data to pass around. Next we check to make sure the compile was successful: 14 if (KlelIsValid(psContext)) If KlelIsValid returns zero (false), it means that an error occurred during compilation and the resulting context, if any, is useless for anything except getting error messages. Assuming compilation succeeded, we then proceed to execute the expression: 16 psResult = KlelExecute(psContext); KlelExecute will return NULL if execution fails with any generated error messages being stored in the provided context. If execution doesn't fail, we proceed to print out the result of the execution and free the result since we're done using it: 19 pcMessage = KlelValueToString(psResult, &szLength); 20 fprintf(stdout, "result: %s\n", pcMessage); 21 KlelFreeResult(psResult); 22 free(pcMessage); Most of the rest of the example is simply error handling that is called if either compilation or execution fails. The final thing we do is free the context for the expression: 33 KlelFreeContext(psContext); And that's it! Note that the context could have been executed again or as many times as you wish. There's no global state in KL-EL, so different threads can compile expressions and run them simultaneously (though a single context shouldn't be shared across threads without proper handling and serialization). Assuming the source file from above is named "klel-basic.c" and you have an appropriate build environment (including gcc, libklel, and libpcre), you should be able to compile the code as follows: $ gcc -o klel-basic klel-basic.c -lklel -lpcre Try out a few simple expressions: $ klel-basic '2 + 2' result: 4 $ klel-basic 'pi / e' result: 1.15573 $ klel-basic '"0x" . hex_of_int(65536)' result: 0x10000 This tutorial includes a few more complicated examples below, but this example shows the basic workflow followed by any application that embeds KL-EL. EXPORTING FUNCTIONS AND VARIABLES EXAMPLEHere is some source code that we can add to the example program above that demonstrates how to export variables and functions into the KL-EL environment where they can be used in KL-EL expressions. We export an integer variable, "arg_count", that holds the number of command line arguments passed to the application. We also export a function, "get_arg", that returns a string representation of the numbered command line argument.The code below should be inserted into the example above starting at line 4. 1 #include <string.h> 2 3 int giArgumentCount = 0; 4 char **gppcArgumentVector = NULL; 5 6 KLEL_VALUE * 7 GetArg(KLEL_VALUE **ppsArgs, void *pvContext) 8 { 9 int64_t i64Arg = ppsArgs[0]->llInteger; 10 11 if (i64Arg < 0 || i64Arg >= giArgumentCount) 12 { 13 KlelReportError((KLEL_CONTEXT *)pvContext, "get_arg: invalid argument", NULL); 14 return NULL; 15 } 16 17 return KlelCreateString(strlen(gppcArgumentVector[i64Arg]), gppcArgumentVector[i64Arg]); 18 } 19 20 KLEL_EXPR_TYPE 21 GetType(const char *pcName, void *pvContext) 22 { 23 if (strcmp(pcName, "arg_count") == 0) 24 { 25 return KLEL_TYPE_INT64; 26 } 27 else if (strcmp(pcName, "get_arg") == 0) 28 { 29 return KLEL_TYPE_STRING_FUNCTION1(KLEL_TYPE_INT64); 30 } 31 32 return KLEL_TYPE_UNKNOWN; 33 } 34 35 KLEL_VALUE * 36 GetValue(const char *pcName, void *pvContext) 37 { 38 if (strcmp(pcName, "arg_count") == 0) 39 { 40 return KlelCreateInteger(giArgumentCount); 41 } 42 else if (strcmp(pcName, "get_arg") == 0) 43 { 44 return KlelCreateFunction(KLEL_TYPE_STRING_FUNCTION1(KLEL_TYPE_INT64), "get_arg", GetArg); 45 } 46 47 return KlelCreateUnknown(); 48 } After inserting this code into the basic example above, change the basic example's compile expression from this 13 psContext = KlelCompile(pcExpression, 0, NULL, NULL, NULL); to this 13 psContext = KlelCompile(pcExpression, 0, GetType, GetValue, NULL); 14 giArgumentCount = iArgumentCount; 15 gppcArgumentVector = ppcArgumentVector; By doing this, KL-EL expressions now have access to a new variable, "arg_count" and a new function "get_arg". Let's examine the new code in depth. KL-EL allows you to export variables and functions by defining two callback functions. One function is called with a variable name and returns the type of that variable. The other function is called with a variable name and returns the value of that variable. The type of a variable can never change, but a variable's value can change at any time. We export the GetArg function as "get_arg" and the giArgumentCount variable as "arg_count". Let's start by looking at the type callback, GetType: 20 KLEL_EXPR_TYPE 21 GetType(const char *pcName, void *pvContext) 22 { 23 if (strcmp(pcName, "arg_count") == 0) 24 { 25 return KLEL_TYPE_INT64; 26 } 27 else if (strcmp(pcName, "get_arg") == 0) 28 { 29 return KLEL_TYPE_STRING_FUNCTION1(KLEL_TYPE_INT64); 30 } 31 32 return KLEL_TYPE_UNKNOWN; 33 } This function simply checks the name of the variable it's supposed to look up and returns the appropriate type: KLEL_TYPE_INT64 for "arg_count" and a slightly more complicated type descriptor for "get_arg" that says that it is a function that returns a string and takes one argument, an integer. If it's passed a variable name it doesn't know, it returns KLEL_TYPE_UNKNOWN, which causes KL-EL to search the standard library to see if it can be found there. In this simple example we just use a cascading if-then-else to figure out which variable name was passed in. In larger applications, a hash function might be used to hash the variable name and turn it into a table lookup. Now let's look at the function that returns those variables' values, GetValue: 35 KLEL_VALUE * 36 GetValue(const char *pcName, void *pvContext) 37 { 38 if (strcmp(pcName, "arg_count") == 0) 39 { 40 return KlelCreateInteger(giArgumentCount); 41 } 42 else if (strcmp(pcName, "get_arg") == 0) 43 { 44 return KlelCreateFunction(KLEL_TYPE_STRING_FUNCTION1(KLEL_TYPE_INT64), "get_arg", GetArg); 45 } 46 47 return KlelCreateUnknown(); 48 } The basic structure of this function is similar to GetType: it determines which variable is being requested, and returns the appropriate value. It uses the KlelCreateInteger function to create the integer value for "arg_count", and KlelCreateFunction to create the function value for "get_arg". By returning the result of KlelCreateUnknown if it doesn't know the value of the variable, it causes KL-EL to search the standard library. If the values of the exported variables were calculated dynamically (which they certainly can be), returning NULL instead of the result of KlelCreateUnknown would signal to KL-EL that the variable is known but an error occurred. In that case, KL-EL won't search the standard library. Both GetType and GetValue take a second argument, a void pointer to the context. This is the same value (cast as a void pointer) that was returned by KlelCompile. The value returned for "arg_count" isn't that interesting -- it's just an integer. The value returned for "get_arg" is much more interesting -- it's a function that will get invoked by the library. Let's look at the exported function, GetArg: 6 KLEL_VALUE * 7 GetArg(KLEL_VALUE **ppsArgs, void *pvContext) 8 { 9 int64_t i64Arg = ppsArgs[0]->llInteger; 10 11 if (i64Arg < 0 || i64Arg >= giArgumentCount) 12 { 13 KlelReportError((KLEL_CONTEXT *)pvContext, "get_arg: invalid argument", NULL); 14 return NULL; 15 } 16 17 return KlelCreateString(strlen(gppcArgumentVector[i64Arg]), gppcArgumentVector[i64Arg]); 18 } Just like GetValue, exported functions must return a pointer to a KLEL_VALUE. Exported functions take two arguments, an array of pointers to KLEL_VALUE representing the the function's arguments, and a void pointer for the current context. There are always exactly thirteen entries in ppsArgs, though they aren't all necessarily filled in. The zeroeth entry is the function's first argument, and arguments are specified in ascending order. GetArg is pretty simple -- it just takes the value of its first argument (an integer) and either returns the corresponding command line argument as a string, or it reports an error and returns NULL. The library's internal type checker guaranteed that this function would be called with one integer argument, so it's technically okay that this example doesn't explicitly check -- but there's no harm in double checking if you want. Finally, the last change needed to export our variable and function was to pass the two callback functions to KlelCompile. Once that is done, your new variable and function are ready for use in KL-EL expressions. GUARDED COMMANDSWhat Are Guarded Commands?Guarded commands are a kind of two-part expression. The first part of the expression is called the "guard", and the second part is the "command". The guard must be a boolean expression, and the command must be a call to the special function "eval".Guarded commands allow for extra information to be passed to an application from the expression. The extra information consists of two strings, known as the "interpreter" and the "program", a collection of 256 integers called "exit codes", and up to twelve additional strings. Conventionally, guarded commands are used to pass operating system commands to the application to be executed, but KL-EL assigns no semantics to them other than that they are guarded commands with a boolean expression for a guard. KL-EL has been used as a "control language" for some applications that embed multiple programming language interpreters (Python, Perl, Lua, etc) to determine which interpreter should be run on a given set of input by using guarded commands. Guarded commands look like this: if (filename == "/etc/passwd") then eval("exec", "/bin/rm", "-f", filename) pass [0, 255] The part in parentheses after the "if" is the guard. The rest of the expression after the "then" is the command. In this example, "exec" is the interpreter and "/bin/rm" is the program. One might read this example as saying "if the value of the 'filename' variable is '/etc/passwd', then execute the '/bin/rm' command with the following arguments and assume the operation was successful if the exit code is 0 or 255. While you could read it that way, KL-EL doesn't enforce any of that -- it's up to your application to decide what to do with the success criteria provided in this expression. KL-EL provides functions to extract each of the various parts of the guarded command. There are some type checking and syntactic limits enforced by KL-EL on guarded commands:
Compiling and Executing Guarded CommandsIf you just use KlelCompile and KlelExecute and don't run the guarded command in the specified interpreter, then KL-EL will simply treat a guarded command as a boolean expression. More specifically, it will act as though the guard is the whole expression.The function KlelIsGuardedCommand will test to see if a compiled expression is a guarded command. If you want to force the user to only provide guarded commands, the KLEL_MUST_BE_GUARDED_COMMAND flag should be passed to KlelCompile. If this flag is set, KlelCompile will return a compilation error for any requests to compile a non-guarded command. If a guarded command is supplied, then you can use the following functions on the compiled expression:
None of the functions above will cause evaluation of the remaining arguments to the eval function, so they can be called even before your callbacks are ready to be executed. This allows you to make sure these arguments are valid and set up any external interpreters you may need. Once you're ready to evaluate the remaining arguments to the eval function, you can call the KlelGetCommand function. Presumably you'd do this if the result of KlelExecute was true, but it's up to you. The KlelGetCommand function returns a pointer to a KLEL_COMMAND structure. This structure contains all of the information about the guarded command and is defined as follows: typedef struct _KLEL_COMMAND { char pcInterpreter[KLEL_MAX_NAME + 1]; char pcProgram[KLEL_MAX_NAME + 1]; size_t szArgumentCount; char *ppcArgumentVector[KLEL_MAX_FUNC_ARGS + 1]; int aiCodes[256]; } KLEL_COMMAND; The pcInterpreter and pcProgram arguments simply contain the associated string. The aiCodes array contains a nonzero value in every position that corresponds to a successful exit code. Note that if the expression does not contain any exit codes (they are optional), then 0 is considered the only successful exit code. Most interesting, however, is the ppcArgumentVector array. This contains the values of the remaining arguments to the eval function converted to strings. As a convenience, the first entry in this array is identical to the contents of the pcProgram member; this makes it easy to interface with the execv(3) family of functions. Once you're done with a KLEL_COMMAND structure, it needs to be freed using KlelFreeCommand. The klel-expr program included in the KL-EL source distribution accepts normal expressions and guarded commands. It evaluates normal expressions and prints their results, but for guarded commands, it defines two interpreters: "echo" and "system". For the "echo" interpreter, it simply echos the contents of ppcArgumentVector. The "system" interpreter takes the contents of pcProgram and passes it to the standard system(3) function. SEE ALSOklel-expr(1), klelapi(3), klellang(3), klelstdlib(3)
Visit the GSP FreeBSD Man Page Interface. |