Skip site navigation (1)Skip section navigation (2)

  
 
  

home | help
KLELTUT(1)			     libklel			      KLELTUT(1)

INTRODUCTION
     This manual page provides a tutorial for the creation of a simple applica-
     tion that embeds the KL-EL compiler and interpreter.

BASIC EXAMPLE
     Here 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 pro-
     gram 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 ex-
     cept getting error messages.  Assuming compilation succeeded, we then pro-
     ceed 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 com-
     pile expressions and run them simultaneously (though a single context
     shouldn't be shared across threads without proper handling and serializa-
     tion).

     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 ex-
     ample shows the basic workflow followed by any application that embeds
     KL-EL.

EXPORTING FUNCTIONS AND VARIABLES EXAMPLE
     Here 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 environ-
     ment 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 re-
     turns 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, Get-
     Value:

	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 KlelCreate-
     Unknown would signal to KL-EL that the variable is known but an error oc-
     curred.  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 ex-
     ported 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 pp-
     sArgs, though they aren't all necessarily filled in.  The zeroeth entry is
     the function's first argument, and arguments are specified in ascending or-
     der.

     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 COMMANDS
   What 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 ex-
     pression 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' vari-
     able is '/etc/passwd', then execute the '/bin/rm' command with the follow-
     ing 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 pro-
     vided 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:

     The guard expression must be boolean
     The first two arguments to the eval function must be literal strings with
     no interpolations and shorter than 255 bytes.
     Exit codes must be literal integers.

   Compiling and Executing Guarded Commands
     If you just use KlelCompile and KlelExecute and don't run the guarded com-
     mand 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:

     KlelGetInterpreter
	 returns the interpreter argument to the eval function

     KlelGetProgram
	 returns the program argument to the eval function

     KlelIsSuccessReturnCode
	 returns true or false if the code passed in is a successful code ac-
	 cording to the expression.

     None of the functions above will cause evaluation of the remaining argu-
     ments to the eval function, so they can be called even before your call-
     backs are ready to be executed.  This allows you to make sure these argu-
     ments 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 inter-
     preters: "echo" and "system".  For the "echo" interpreter, it simply echos
     the contents of ppcArgumentVector.  The "system" interpreter takes the con-
     tents of pcProgram and passes it to the standard system(3) function.

SEE ALSO
     klel-expr(1), klelapi(3), klellang(3), klelstdlib(3)

1.1.0				   2026-08-27			      KLELTUT(1)

Want to link to this manual page? Use this URL:
<https://man.freebsd.org/cgi/man.cgi?query=kleltut&sektion=3&manpath=FreeBSD+Ports+15.1.quarterly>

home | help