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

FreeBSD Manual Pages

  
 
  

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

INTRODUCTION
       This manual page	provides a tutorial for	the creation of	a simple
       application 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 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 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
       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	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
       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:

       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
       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:

       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
	   according to	the 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 ALSO
       klel-expr(1), klelapi(3), klellang(3), klelstdlib(3)

1.1.0				  2021-02-28			    KLELTUT(1)

INTRODUCTION | BASIC EXAMPLE | EXPORTING FUNCTIONS AND VARIABLES EXAMPLE | GUARDED COMMANDS | SEE ALSO

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

home | help