RPCL - Reverse Polish Configuration Language
 
 

RPCL - Reverse Polish Configuration Language

March 22, 2025
development
C, RPCL
This post will be eventually superseded by the RPCL project website which is currently just redirected to here.

What is this? #

RPCL is a minimalistic configuration and control language, especially designed to act as a CLI for daemon-like background processes. RPCL is an acronym for Reverse Polish Configuration (or Control) Language.

It is working stack-oriented in reverse polish notation and therefore borrows some control words from FORTH.

RPCL offers some control structures and has the ability to define compound words - however, the scope of RPCL remains to configure and control something (and not to program).

Overview #

One single Datatype #

RPCL is built around a single datatype, a classical char* with a maximum length of RPCLWORDLEN. Pushes and pops and all other operations are always ‘by value’.

typedef uint8_t RPCLWORD[RPCLWORDLEN];

The Dictionary #

The RPCL dictionary is implemented as a skip list, where each entry holds a key, a value and an optional function pointer. Key and value are of size RPCLWORDLEN and always guaranteed to be 0 terminated.

UTF-8 / Unicode within the value field is supported, variable names are restricted to ASCII printable only, without double quotes, backslash and any whitespace. The variable name is resticted to a maximum length of 32 characters. The variable names are case sensitive.

The Stacks #

The Data Stack #

struct rpcl_stack {
  char stack[RPCLSTACKDEPTH][RPCLWORDLEN];
  int sp;
};

The Execution Stack #

The execution stack is represented by the underlying CPU stack, the interpreter keeps track of nested calls and signals an error when a threshold overflows.

The RPCL Datatype #

A RPCL datatype holds argc and argv as specified by RPCL* RPCLCreate(int argc, char** argv), its dictionary skip list and its stack.

typedef struct rpcl_struct {
  int argc;
  char** argv;
  SL* dictionary;
  struct rpcl_stack s;
} RPCL;

Comments #

Comments within a RPCL line are started with a #.

Example:

$ rpcl
ok 1 2 + . # just adding 1 and 2
3
ok "# This is not a comment" string ! # but this is
ok .d
string                           # This is not a comment
ok

Double Quotes #

Example:

$ rpcl
ok "This is a string containing also \\ and \"" .
This is a string containing also \ and "
ok "#" "#" strcat .
##
ok

Built-in Functions #

ok .i
!                                stores a variable ( value name - )
!!                               define compound word ( line name - )
*                                product of two values ( n1 n2 - result )
+                                sum of two values ( n1 n2 - sum )
-                                difference of two values ( n1 n2 - result )
.                                print element ( s - )
.clear                           empties the stack ( - )
.d                               prints dictionary variables ( - )
.exec-sp                         returns the execution stack pointer ( - execsp )
.i                               prints built-in functions ( - )
.m                               prints internal memory allocations ( - )
.s                               prints stack ( - )
/                                quotient of two values ( n1 n2 - result )
2drop                            drops two elements ( s1 s2 - )
;                                synonym for ! ( value name - )
@                                fetch a variable ( name - value )
@ne                              fetch a variable, "" if unknown ( name - value )
argc                             returns argc ( - n )
argv                             returns argv ( n - result )
delete                           delete variable from dictionary ( name - )
drop                             drops one element ( s - )
dup                              duplicates top of stack ( s1 - s1 s1 )
error                            signal an error ( message - )
eval                             evaluate a line ( line - ? )
evaln                            evaluates a line n times ( line n - )
getenv                           returns getenv() result ( name - value )
geteuid                          returns the effective user ID ( - euid )
getpid                           returns the process ID (PID) ( - pid )
getuid                           returns the real user ID ( - uid )
load                             load a RPCL file ( filename - )
load-if-present                  load a RPCL file if accessible ( filename - )
macaddr-check                    raise error if TOS is not a MAC address ( s - s )
macaddr?                         returns false or the MAC address ( s - result )
manuf                            returns the MAC address manufacturer ( macaddr - manuf )
sleep                            sleep n seconds ( n - )
stop                             EXIT immediately with all threads ( - )
strcat                           concatenates two strings ( s1 s2 - result )
strlen                           returns length of string ( s - n )
swap                             swap two elements ( s1 s2 - s2 s1)
system                           execute command with system() ( command - )
uptime                           return interpreter uptime in seconds ( - s )
uptime-hr                        return uptime as d/h/m/s ( - result )
ok 

The RPCL API #

RPCL is easily embeddable with a small set of C functions.

Managing the Interpreter #

RPCL* RPCLCreate(int argc, char** argv);
void RPCLDestroy(RPCL * rpcl);
void RPCLMainLoop(RPCL * rpcl);

RPCL_OK and RPCL_ERROR #

#define RPCL_OK 0
#define RPCL_ERROR -1

Registering a Function #

void RPCLRegister(RPCL * rpcl, char* key_string, 
                  char* value_string, int (*function)(RPCL * rpcl));

Evaluating #

int RPCLEvalLine(RPCL * rpcl, char* line);
int RPCLEvalToken(RPCL * rpcl, char* token);

Converting a 0-Terminated char* to a RPCLWORD #

void RPCLPrepareWord(char* string, RPCLWORD result);

Push and Pop #

Both functions return either RPCL_OK or RPCL_ERROR. A function registered with RPCLRegister() should also immediately return RPCL_ERROR in case that RPCLPush() or RPCLPop() are failing.

int RPCLPush(RPCL * rpcl, RPCLWORD word);
int RPCLPop(RPCL * rpcl, RPCLWORD result);

A Basic RPCL Shell #

This implements a basic RPCL shell that reads RPCL lines from its standard input and evaluates them. As expected, any result is printed to standard output, error messages to standard error.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <rpcl.h>

int main(int argc, char** argv) {

  RPCL* rpcl;

  rpcl = RPCLCreate(argc, argv);
  RPCLMainLoop(rpcl);
  RPCLDestroy(rpcl);

  exit(EXIT_SUCCESS);
}