PSALM - psalm.bac
Not logged in
/*------------------------------------------------------------------------------------------------------------------------

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