/*
 * Higher-order functions,
 *   object-oriented programming
 *   and (real) function composition
 *   on the fly, at runtime, in C.
 *
 * (c) @dr-Jonas-Birch, 2025
 *
 * Compile: gcc higher.order.c -o higher.order -O0 -lbu
 * Dependency: lib-birchutils from repo.doctorbirch.com
 *
 * Licensed as Open Source under MIT license.
 *
 */

#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <birchutils.h>

#define $1 (int8 *)
#define $c (char *)
#define $i (int)

struct s_tuple {
    void *addr;
    int16 size;
};
typedef struct s_tuple Tuple;

struct s_function {
    int8 machinecode[64];
    int (*call)(int);
};
typedef struct s_function function;

int8 pre[]  = "\x55\x89\xe5\x83\xec\x10";
int8 post[] = "\x89\x45\xfc\x8b\x45\xfc\xc9\xc3";
int8 ebp8[] = "\x90\x90\x89\x45\x08\x90\x90";

#define prelen   (sizeof(pre)-1)
#define postlen  (sizeof(post)-1)
#define overhead (prelen+postlen)
#define ebp8len  (sizeof(ebp8))

function compose(void*,void*);
void *findend(void*);
Tuple extract(void*);
int8 *empty(int16);
int addone(int);
int x2(int);
int main(void);

function compose(void *f1, void *f2) {
    int8 *fun, *p;
    Tuple f1_, f2_;
    function ret;

    fun = empty(64);
    f1_ = extract(f1);
    f2_ = extract(f2);
    p = fun + prelen + 1;
    memcpy($c p, $c f1_.addr, $i f1_.size);
    p += f1_.size;
    memcpy($c p, ebp8, (ebp8len-1));
    p += (ebp8len-1);
    memcpy($c p, $c f2_.addr, $i f2_.size);
    
    memcpy($c &ret, $c fun, 64);

    return ret;
}

Tuple extract(void *fun) {
 void *start, *end;
 int16 size;
 int8 *p;

 start = fun + prelen;
 end = findend(fun) + 1;
 size = end - start;
 p = (int8 *)malloc($i size);
 assert(p);
 zero(p, size);
 memcpy($1 p, $1 start, $i size);

 Tuple ret = {
    .addr = p,
    .size = size
 };

 return ret;
}

int8 *empty(int16 size) {
 int8 *p;

 assert(size >= overhead+1);
 p = (int8 *)malloc(size);
 assert(p);
 zero(p, size);

 memset($c p, 0x90, $i size);
 memcpy(p, pre, prelen);
 memcpy(p + size - postlen, post, postlen);

 return p;
}

int addone(int x) {
 int ret;

 ret = x + 1;
 return ret;
}

int x2(int x) {
 int ret;

 ret = x * 2;
 return ret;
}

void *findend(void *fun) {
 int8 *p;
 void *end;
 int16 n;

 for (p = fun, n=0; n < 1024; p++, n++)
  if ((*p == 0xc3) || (*p == 0xc9)) {
   end = p - 1;
   break;
  }

 return end;
}

int main() {
    void *mem;
    function fun;
    int x;

    fun = compose(&addone, &x2);
    mem = &fun;
    fun.call = mem;

    x = fun.call(5);
    printf("x = %d\n", x);
    exit(0);

    return 0;
}

/*
int main() {
    int (*f)(int);
    int8 *fun;
    int8 *p;
    void *mem;
    Tuple essence;
    int x;

    fun = empty(64);
    essence = extract(&x2);
    p = fun + prelen;
    memcpy($1 p, $1 essence.addr, $i essence.size);
    mem = fun;
    f = mem;

    x = f(5);
    printf("x = %d\n", x);
    exit(0);

    return 0;
}

int main() {
 int8 *p;
 void *mem;
 Tuple foo;
 void (*f)();

 p = empty(64);
 //printhex($1 p, 64, 0);
 mem = p;
 f = mem;


 f();
 foo = extract(&addone);
 printf("Size: %d\nAddr: 0x%x\n\n", $i foo.size, $i foo.addr);
 printhex($1 foo.addr, foo.size, 0);

 exit(0);

 //addone(5);

 return 0;
}
*/

