/*------------------------------------------------------------------------------------------------------------------------ This is PSALM, a library gateway based solely on the 'dlopen' API which comes default with any C installation. Supports both function and argument typing. Only C types and the propriety type 'address' are supported. Pointers are only possible for char and void, e.g. char* and void*. An API function cannot be invoked with more than 8 arguments. The communcation *only* is possible by stdin/stdout. Use a coprocess, or a tool like 'socat' or 'dpipe' or a named pipe to connect Psalm to a script. example 1: socat EXEC:./psalm EXEC:./script.bash example 2: dpipe ./psalm = ./script.bash example 3: pipexec [ A ./psalm ] [ B ./script.bash ] "{A:1>B:0}" "{B:1>A:0}" example 4 (linux only): : | { ./psalm | ./script.bash; } > /dev/fd/0 There are 4 functions in psalm: - define or def: define the prototype of the API call in the format "library type function argtype1 [argtype2 ...etc...]" example to define the 'cosine' function in libm: DEF libm.so double cos double - execute or exe: execute a previous defined function example to execute the 'cosine' function: EXE cos 0.8 - callback or cb: retrieve a value sent to the internal callback function. Use the type 'address' to define a callback address. This can either point to a previous defined function or to the internal callback function called 'callback'. example to retrieve the first argument from the internal callback function: cb 1 - exit, quit: exit psalm Psalm should be executed without any arguments. Two optional command line arguments are available: -about: plain message about Psalm -log=/path/to/logfile.txt: send incoming requests to logfile (c) Peter van Eerten - Nov 2022/May 2024 - MIT License - Release version 1.4. 1.1:-better logging -allow functions with 0 arguments -allow void* as regular pointer in arguments -make sure NULL works as NULL -made EXE command optional 1.2:-corrected logging message for wrong argument types -end when argument type is not recognized 1.3:-reset argument definition so DEF can redefine functions as well 1.4:-added on-the-fly abbreviations for argument type ------------------------------------------------------------------------------------------------------------------------*/ PRAGMA LDFLAGS -export-dynamic dl OPTION COLLAPSE TRUE PROTO dlopen ALIAS OPENLIB PROTO dlsym ALIAS GETFUNC DECLARE last_cb$ TYPE STRING DECLARE type$, args$ ASSOC STRING DECLARE library, funcptr ASSOC void* SIGNAL SIG_IGN, SIGPIPE last_cb$ = "0 0 0 0 0 0 0 0" '----------------------------------------------------------------------------------- ' Generic callback function ('address' type in function arguments) FUNC callback(void* ptr0, void* ptr1, void* ptr2, void* ptr3, void* ptr4, void* ptr5, void* ptr6, void* ptr7) TYPE void* last_cb$ = STR$((long)ptr0) & " " & STR$((long)ptr1) & " " & STR$((long)ptr2) & " " & STR$((long)ptr3) last_cb$ = last_cb$ & " " & STR$((long)ptr4) & " " & STR$((long)ptr5) & " " & STR$((long)ptr6) & " " & STR$((long)ptr7) RETURN ptr0 ENDFUNC '----------------------------------------------------------------------------------- ' Function to return genuine float FUNC tofloat(char* nr) TYPE float RETURN (float)strtof(nr, NULL) ENDFUNC '----------------------------------------------------------------------------------- ' Function to return same void pointer FUNC vaddr(pointer$) TYPE void* IF pointer$ = "NULL" THEN RETURN NULL RETURN (void*)(long)VAL(pointer$) ENDFUNC '----------------------------------------------------------------------------------- ' Function to return same string pointer, trim double quotes while we're at it FUNC saddr(char* pointer) TYPE char* RETURN CHOP$(pointer, CHR$(34)) ENDFUNC '----------------------------------------------------------------------------------- SUB Exec_Voidp_Type(name$, void* cast[], char* arg$[]) LOCAL (*exe)() TYPE void* LOCAL (*ptr[8])() = { NULL } TYPE void* LOCAL result TYPE void* exe = funcptr(name$) COPY cast TO ptr SIZE 8*SIZEOF(void*) result = (*exe)( (*ptr[0])(arg$[0]), (*ptr[1])(arg$[1]), (*ptr[2])(arg$[2]), (*ptr[3])(arg$[3]), (*ptr[4])(arg$[4]), (*ptr[5])(arg$[5]), (*ptr[6])(arg$[6]), (*ptr[7])(arg$[7]) ) PRINT STR$((long)result) ENDSUB '----------------------------------------------------------------------------------- SUB Exec_Charp_Type(name$, void* cast[], char* arg$[]) LOCAL (*exe)() TYPE char* LOCAL (*ptr[8])() = { NULL } TYPE void* LOCAL result TYPE char* exe = funcptr(name$) COPY cast TO ptr SIZE 8*SIZEOF(void*) result = (*exe)( (*ptr[0])(arg$[0]), (*ptr[1])(arg$[1]), (*ptr[2])(arg$[2]), (*ptr[3])(arg$[3]), (*ptr[4])(arg$[4]), (*ptr[5])(arg$[5]), (*ptr[6])(arg$[6]), (*ptr[7])(arg$[7]) ) PRINT result ENDSUB '----------------------------------------------------------------------------------- SUB Exec_Double_Type(name$, void* cast[], char* arg$[]) LOCAL (*exe)() TYPE double LOCAL (*ptr[8])() = { NULL } TYPE void* LOCAL result TYPE double exe = funcptr(name$) COPY cast TO ptr SIZE 8*SIZEOF(void*) result = (*exe)( (*ptr[0])(arg$[0]), (*ptr[1])(arg$[1]), (*ptr[2])(arg$[2]), (*ptr[3])(arg$[3]), (*ptr[4])(arg$[4]), (*ptr[5])(arg$[5]), (*ptr[6])(arg$[6]), (*ptr[7])(arg$[7]) ) PRINT STR$(result) ENDSUB '----------------------------------------------------------------------------------- SUB Exec_Float_Type(name$, void* cast[], char* arg$[]) LOCAL (*exe)() TYPE float LOCAL (*ptr[8])() = { NULL } TYPE void* LOCAL result TYPE float exe = funcptr(name$) COPY cast TO ptr SIZE 8*SIZEOF(void*) result = (*exe)( (*ptr[0])(arg$[0]), (*ptr[1])(arg$[1]), (*ptr[2])(arg$[2]), (*ptr[3])(arg$[3]), (*ptr[4])(arg$[4]), (*ptr[5])(arg$[5]), (*ptr[6])(arg$[6]), (*ptr[7])(arg$[7]) ) PRINT STR$(result) ENDSUB '----------------------------------------------------------------------------------- SUB Exec_Long_Type(name$, void* cast[], char* arg$[]) LOCAL (*exe)() TYPE long LOCAL (*ptr[8])() = { NULL } TYPE void* LOCAL result TYPE long exe = funcptr(name$) COPY cast TO ptr SIZE 8*SIZEOF(void*) result = (*exe)( (*ptr[0])(arg$[0]), (*ptr[1])(arg$[1]), (*ptr[2])(arg$[2]), (*ptr[3])(arg$[3]), (*ptr[4])(arg$[4]), (*ptr[5])(arg$[5]), (*ptr[6])(arg$[6]), (*ptr[7])(arg$[7]) ) PRINT STR$(result) ENDSUB '----------------------------------------------------------------------------------- SUB Exec_Int_Type(name$, void* cast[], char* arg$[]) LOCAL (*exe)() TYPE int LOCAL (*ptr[8])() = { NULL } TYPE void* LOCAL result TYPE int exe = funcptr(name$) COPY cast TO ptr SIZE 8*SIZEOF(void*) result = (*exe)( (*ptr[0])(arg$[0]), (*ptr[1])(arg$[1]), (*ptr[2])(arg$[2]), (*ptr[3])(arg$[3]), (*ptr[4])(arg$[4]), (*ptr[5])(arg$[5]), (*ptr[6])(arg$[6]), (*ptr[7])(arg$[7]) ) PRINT STR$(result) ENDSUB '----------------------------------------------------------------------------------- SUB Exec_Short_Type(name$, void* cast[], char* arg$[]) LOCAL (*exe)() TYPE short LOCAL (*ptr[8])() = { NULL } TYPE void* LOCAL result TYPE short exe = funcptr(name$) COPY cast TO ptr SIZE 8*SIZEOF(void*) result = (*exe)( (*ptr[0])(arg$[0]), (*ptr[1])(arg$[1]), (*ptr[2])(arg$[2]), (*ptr[3])(arg$[3]), (*ptr[4])(arg$[4]), (*ptr[5])(arg$[5]), (*ptr[6])(arg$[6]), (*ptr[7])(arg$[7]) ) PRINT STR$(result) ENDSUB '----------------------------------------------------------------------------------- SUB Exec_Char_Type(name$, void* cast[], char* arg$[]) LOCAL (*exe)() TYPE char LOCAL (*ptr[8])() = { NULL } TYPE void* LOCAL result TYPE char exe = funcptr(name$) COPY cast TO ptr SIZE 8*SIZEOF(void*) result = (*exe)( (*ptr[0])(arg$[0]), (*ptr[1])(arg$[1]), (*ptr[2])(arg$[2]), (*ptr[3])(arg$[3]), (*ptr[4])(arg$[4]), (*ptr[5])(arg$[5]), (*ptr[6])(arg$[6]), (*ptr[7])(arg$[7]) ) PRINT STR$(result) ENDSUB '----------------------------------------------------------------------------------- SUB Exec_Void_Type(name$, void* cast[], char* arg$[]) LOCAL (*exe)() TYPE void LOCAL (*ptr[8])() = { NULL } TYPE void* exe = funcptr(name$) COPY cast TO ptr SIZE 8*SIZEOF(void*) CALL (*exe)( (*ptr[0])(arg$[0]), (*ptr[1])(arg$[1]), (*ptr[2])(arg$[2]), (*ptr[3])(arg$[3]), (*ptr[4])(arg$[4]), (*ptr[5])(arg$[5]), (*ptr[6])(arg$[6]), (*ptr[7])(arg$[7]) ) PRINT "ok" ENDSUB '----------------------------------------------------------------------------------- SUB Execute_Cmd(cmd$) LOCAL cb = NULL TYPE void* LOCAL cast[8] = { NULL } TYPE void* LOCAL arg$[] = { "0", "0", "0", "0", "0", "0", "0", "0" } LOCAL name$ LOCAL t name$ = TOKEN$(cmd$, 1) IF funcptr(name$) = NULL THEN EPRINT "ERROR: undefined symbol '", name$, "'" END ELIF AMOUNT(cmd$) > 9 THEN EPRINT "ERROR: too many arguments specified in '", cmd$, "'" END ELSE ' Obtain arguments if there are any IF AMOUNT(cmd$) > 1 THEN FOR t = 2 TO AMOUNT(cmd$) IF REGEX(TOKEN$(cmd$, t), "^[vcsilfdspa]:|^v[*]:|^c[*]:") THEN args$(name$) = CHANGE$(args$(name$), t-1, TOKEN$(TOKEN$(cmd$, t), 1, ":")) arg$[t-2] = TOKEN$(TOKEN$(cmd$, t), 2, ":") ELSE arg$[t-2] = TOKEN$(cmd$, t) ENDIF NEXT ENDIF ' Always reset remaining arguments to void FOR t = AMOUNT(cmd$)+1 TO 9 args$(name$) = CHANGE$(args$(name$), t-1, "void") NEXT ' Runtime conversion based on dlsym FOR t = 0 TO 7 SELECT TOKEN$(args$(name$), t+1) CASE "void", "v" cast[t] = FP(atoi) CASE "char", "c" cast[t] = FP(atoi) CASE "short", "s" cast[t] = FP(atoi) CASE "int", "i" cast[t] = FP(atoi) CASE "long", "l" cast[t] = FP(atol) CASE "float", "f" cast[t] = FP(tofloat) CASE "double", "d" cast[t] = FP(atof) CASE "char*", "c*" cast[t] = FP(saddr) CASE "void*", "v*" cast[t] = FP(vaddr) CASE "address", "a" cb = GETFUNC(library(arg$[t]), arg$[t]) IF cb = NULL THEN cb = FP(arg$[t]) arg$[t] = STR$((long)cb) cast[t] = FP(vaddr) DEFAULT EPRINT "ERROR: unknown argument type '", TOKEN$(args$(name$), t+1), "' in '", name$, "'" END ENDSELECT NEXT ' Invoke function based on return type SELECT type$(TOKEN$(cmd$, 1)) CASE "void" Exec_Void_Type(name$, cast, arg$) CASE "char" Exec_Char_Type(name$, cast, arg$) CASE "short" Exec_Short_Type(name$, cast, arg$) CASE "int" Exec_Int_Type(name$, cast, arg$) CASE "long" Exec_Long_Type(name$, cast, arg$) CASE "float" Exec_Float_Type(name$, cast, arg$) CASE "double" Exec_Double_Type(name$, cast, arg$) CASE "char*" Exec_Charp_Type(name$, cast, arg$) CASE "void*" Exec_Voidp_Type(name$, cast, arg$) ENDSELECT ENDIF ENDSUB '----------------------------------------------------------------------------------- SUB Define_Cmd(cmd$) LOCAL ptr TYPE void* LOCAL result$ LOCAL i IF AMOUNT(cmd$) < 3 THEN EPRINT "ERROR: the define keyword should contain at least 4 members separated by space: '", cmd$, "'" END ELSE type$(TOKEN$(cmd$, 3)) = TOKEN$(cmd$, 2) IF LEFT$(TOKEN$(cmd$, 1), 7) = "libm.so" OR LEFT$(TOKEN$(cmd$, 1), 7) = "libc.so" THEN ptr = OPENLIB(NULL, RTLD_LAZY) ELSE ptr = OPENLIB(TOKEN$(cmd$, 1), RTLD_LAZY) ENDIF IF ptr = NULL THEN EPRINT "ERROR: cannot open library '", TOKEN$(cmd$, 1), "' in '", cmd$, "'" END ELSE library(TOKEN$(cmd$, 3)) = ptr funcptr(TOKEN$(cmd$, 3)) = GETFUNC(ptr, TOKEN$(cmd$, 3)) IF funcptr(TOKEN$(cmd$, 3)) = NULL THEN EPRINT "ERROR: cannot find symbol '", TOKEN$(cmd$, 3), "' in '", cmd$, "'" END ELSE IF AMOUNT(cmd$) BETWEEN 3 AND 11 THEN FOR i = 4 TO AMOUNT(cmd$) args$(TOKEN$(cmd$, 3)) = APPEND$(args$(TOKEN$(cmd$, 3)), 0, TOKEN$(cmd$, i)) NEXT IF AMOUNT(cmd$) < 11 THEN FOR i = AMOUNT(cmd$)+1 TO 11 args$(TOKEN$(cmd$, 3)) = APPEND$(args$(TOKEN$(cmd$, 3)), 0, "void") NEXT ENDIF PRINT "ok" ELSE EPRINT "ERROR: too many arguments specified: '", cmd$, "'" END ENDIF ENDIF ENDIF ENDIF ENDSUB '----------------------------------------------------------------------------------- SUB Parse_Input(data$) SELECT LCASE$(TOKEN$(data$, 1)) CASE "define", "def" Define_Cmd(LAST$(data$, 1)) CASE "execute", "exe" Execute_Cmd(LAST$(data$, 1)) CASE "callback", "cb" IF AMOUNT(data$) BETWEEN 2 AND 9 THEN PRINT CHOP$(COIL$(AMOUNT(data$)-1, TOKEN$(last_cb$, VAL(TOKEN$(data$, _+1))))) last_cb$ = "0 0 0 0 0 0 0 0" ELSE EPRINT "ERROR: cb needs 1 to max 8 arguments" END ENDIF CASE "exit", "quit" PRINT "Goodbye!" END DEFAULT Execute_Cmd(data$) ENDSELECT ENDSUB '----------------------------------------------------------------------------------- FOR arg$ IN ARGUMENT$ SELECT TOKEN$(TOKEN$(ARGUMENT$, 2), 1, "=") CASE "-about" PRINT "This is PSALM version 1.4, a generic minimalistic library gateway implemented in BaCon." END CASE "-log" logfile$ = TOKEN$(TOKEN$(ARGUMENT$, 2), 2, "=") ENDSELECT NEXT IF FILEEXISTS(logfile$) THEN DELETE FILE logfile$ ' Keep filedescriptors open in case shell client uses named pipes OPEN "/dev/stdin" FOR DEVICE AS infile OPEN "/dev/stdout" FOR DEVICE AS outfile WHILE TRUE INPUT communication$ IF LEN(logfile$) THEN APPEND communication$ & NL$ TO logfile$ IF LEN(communication$) THEN Parse_Input(CHOP$(communication$)) WEND CLOSE DEVICE infile, outfile '-----------------------------------------------------------------------------------
Return to PSALM