This directory contains a standalone utility which can be used to experiment with the evalfilter
package/library.
If you have this repository cloned locally you can install it via:
cd cmd/evalfilter
go install .
Otherwise you can fetch the source and install it via the standard golang approach:
go get github.com/skx/evalfilter/v2/cmd/evalfilter
The utility uses a number of subcommands for various purposes, you can see an overview by invoking it with no arguments:
$ evalfilter
Usage: evalfilter <flags> <subcommand> <subcommand args>
Subcommands:
bytecode Show the bytecode for a script.
help describe subcommands and their syntax
lex Show our lexer output.
parse Show our parser output.
run Run a script file, against a JSON object.
The bytecode sub-command allows you to see the instructions into which a script is compiled. This can be useful if you suspect there is a bug in the bytecode-generation, or if you're curious to see how a stack-based virtual machine might work.
If you're interested in the bytecode you should read the top-level BYTECODE.md file which contains more details.
Sample input:
// sample.in
if ( 1 + 2 * 3 == 7 ) { print( "OK\n" ); }
return true;
Sample usage:
$ evalfilter bytecode t.in
Bytecode:
0000 OpConstant 0 // push constant onto stack: "OK\n"
0003 OpConstant 1 // push constant onto stack: "print"
0006 OpCall 1 // call function with 1 arg(s)
0009 OpTrue
0010 OpReturn
Constant Pool:
0000 Type:STRING Value:"OK\n"
0001 Type:STRING Value:"print"
Here you'll notice that the generated bytecode is quite different from the input script. That is because the optimizer has worked its magic over a series of iterations.
Specifically the if
condition was changed over a series of steps:
if ( 1 + 2 * 3 == 7 ) { print( "OK\n" ); } return true;
if ( 1 + 6 == 7 ) { print( "OK\n" ); } return true;
if ( 7 == 7 ) { print( "OK\n" ); } return true;
if ( true ) { print( "OK\n" ); } return true;
print( "OK\n" ); return true;
If you add the -no-optimizer
flag to the bytecode sub-command you can see the bytecode which was generated before the optimizer updated it:
$ evalfilter bytecode -no-optimizer sample.in
Bytecode:
0000 OpPush 1 // Push 1 to stack
0003 OpPush 2 // Push 2 to stack
0006 OpPush 3 // Push 3 to stack
0009 OpMul
0010 OpAdd
0011 OpPush 7 // Push 7 to stack
0014 OpEqual
0015 OpJumpIfFalse 27
0018 OpConstant 0 // push constant onto stack: "OK\n"
0021 OpConstant 1 // push constant onto stack: "print"
0024 OpCall 1 // call function with 1 arg(s)
0027 OpTrue
0028 OpReturn
Constant Pool:
0000 Type:STRING Value:"OK\n"
0001 Type:STRING Value:"print"
The lexer sub-command allows you to see how a given input-script would be lexed. Lexing is the process of splitting a source file into a series of tokens.
Most users won't care about this command, but it was helpful when updating the language to allow new operators.
Sample input:
// sample.in
if ( 1 + 2 * 3 == 7 ) { print( "OK\n" ); }
return true;
Sample usage:
$ evalfilter lex sample.in
{IF if}
{( (}
{INT 1}
{+ +}
{INT 2}
{* *}
{INT 3}
{== ==}
{INT 7}
{) )}
{{ {}
{IDENT print}
{( (}
{STRING OK
...
The parse
sub-command allows you to see how a given input-script would be parsed. Parsing is the process of turning the series of tokens produced by the lexer into an abstract-syntax-tree. (Once the AST exists our compiler generates our bytecode.)
Most users won't care about this command, but it was helpful when updating the language to allow new operators.
Sample input:
// sample.in
if ( 1 + 2 * 3 == 7 ) { print( "OK\n" ); }
return true;
Sample usage:
$ evalfilter parse sample.in
if((1 + (2 * 3)) == 7)
{
print("OK\n");
}
return true;
The main reason for having the evalfilter
command is to let users experiment with actually running scripts before they've embedded it into their own application(s).
The run-command allows you to run a script:
- A script may be executed in a standalone fashion.
- Or you may pass an object as input to the script, which better reflects how the library is designed to be used.
- The object is passed in as a JSON file.
Sample input:
// sample.in
if ( 1 + 2 * 3 == 7 ) { print( "OK\n" ); }
return true;
Sample usage:
$ evalfilter run sample.in
OK
Script gave result type:BOOLEAN value:true - which is 'true'.
This example merely displayed the text OK
, but you could also specify a more complex script and pass in an object to test it with:
Sample JSON:
$ cat sample.json
{"Forename": "Steve", "Surname": "Kemp", "Link": "https://steve.fi/" }
Now we'll use a more complex script:
// sample.in
print( "Person is ", Forename, " ", Surname, "\n" );
if ( Link ~= /^https/ ) { print( "Link uses SSL\n" ); }
return true;
And the output:
$ evalfilter run -json sample.json sample.in
Person is Steve Kemp
Link uses SSL
Script gave result type:BOOLEAN value:true - which is 'true'.
As with the bytecode
sub-command you can disable the optimizer if you suspect you're seeing bogus output:
$ evalfilter run -json sample.json -no-optimizer sample.in
You can also see the opcodes being executed, as well as a view of the stack via the -debug
flag:
$ evalfilter run -json sample.json -no-optimizer -debug sample.in