0

I am developing a c++ interpreter and I don't really have the best way to approach making an inbuilt library. i have added functions (for the most part) however the functions are useless if I can't print to console, take user input etc. here is what I thought of first:

#include <iostream>
#include <string>
#include <any>
#include <vector>
#include <sstream>
#include <functional>
#include <map>

#define VOIDFC void* (*)

using std::string;


namespace marine {
    namespace inb {

        namespace console {
            template <typename string>
            void log(std::string s) {
                std::cout << s << std::endl;
            }
            template<typename int>
            void log(int i) {
                std::cout << i << std::endl;
            }
            template<typename float>
            void log(float i) {
                std::cout << i << std::endl;
            }
        };
        template<typename Type, typename ...Args>
        struct LibHolder;

        struct FuncInfo {
            const char* name;
            int param_c;
            std::vector<Base::Decl> paramTypes;
        };
        template<typename Type, typename ...Args>
        struct LibHolder {
            using __t = Type (*)(Args...);
            std::map<FuncInfo, __t> inb_functions{};

            void add(FuncInfo f, __t t) {
                inb_functions.push_back(f, t);
            }

        };

        LibHolder<void, void*> void_1arg;
        LibHolder<void, void*, void*> void_2arg;
        LibHolder<void, void*, void*, void*> void_3arg;
        LibHolder<void, void*, void*, void*, void*> void_4arg;
        LibHolder<void, void*, void*, void*, void*, void*> void_5arg;
        LibHolder<void, void*, void*, void*, void*, void*, void*> void_6arg;


        static void init() {
            /*console*/
            {
                void_1arg.add({ "log", 1, {Base::Decl::INT} }, (void (*) (void*))console::log<int>);
                void_1arg.add({ "log", 1, {Base::Decl::FLOAT} }, (void (*) (void*))console::log<float>);
                void_1arg.add({ "log", 1, {Base::Decl::STRING} }, (void (*) (void*))console::log<string>);
            }
        }
        using ret_st = void* (*);
        template<int param_c = 1,typename ...Params>
        static auto getINBMethod(std::vector<Base::Decl> v, std::string name) {
            switch (param_c) {
            case 0:
                throw marine::errors::IndexError("A function cannot take 0 arguments.");
            case 1:
                for (auto& x : void_1arg.inb_functions) {
                    if (x.first.name == name) return &x.second;
                }
            case 2:
                for (auto& x : void_2arg.inb_functions) {
                    if (x.first.name == name) return &x.second;
                }
            case 3:
                for (auto& x : void_3arg.inb_functions) {
                    if (x.first.name == name) return &x.second;
                }
            case 4:
                for (auto& x : void_4arg.inb_functions) {
                    if (x.first.name == name) return &x.second;
                }
            case 5:
                for (auto& x : void_5arg.inb_functions) {
                    if (x.first.name == name) return &x.second;
                }
            case 6:
                for (auto& x : void_6arg.inb_functions) {
                    if (x.first.name == name) return &x.second;
                }

            }

            return nullptr;
        }
    }
}

I am kind of new to function pointers and I can tell that this is far from a good solution. basically my interpreter would find a function call ex 'print("hello world!")' and look for the inbuilt function print below using getINBMethod(). I'm not sure if there is a better way to do this, as the way I do function in my language is also very dodgy, I basically skip parsing the function until it is called. not the best I know...

i want to be able to scale this, so i could have different modules, like networking, and others that you would import in your script and then the functions would be in a vector or something waiting to be executed. of course i need the function name as i would need to check it when we call it.

any help would be appreciated!

Lajos Arpad
  • 64,414
  • 37
  • 100
  • 175
TBCM
  • 58
  • 6
  • 1
    `using __t = ...` -- Usage of double underscores is reserved for the compiler implementation. – PaulMcKenzie Jul 01 '22 at 13:41
  • 2
    Not to dash your hopes, but full C++ is too complex to be interpreted :) Try making an interpreter for something simpler first (e.g. +-*/ calculator) About your templates you can reduce the amount of code a lot with variadic templates. – Pepijn Kramer Jul 01 '22 at 13:41
  • @OP [Read this question, and the high-rated answers](https://stackoverflow.com/questions/69539/have-you-used-any-of-the-c-interpreters-not-compilers). So decide if it's worth the effort to attempt to create a C++ interpreter, given the hurdles others have had to overcome (if those hurdles have been overcome). – PaulMcKenzie Jul 01 '22 at 13:47
  • 2
    @PepijnKramer Somewhat decent interpreters do exist, see [Cling](https://github.com/root-project/cling). But I agree that it's too much for a newbie to make by themselves. – HolyBlackCat Jul 01 '22 at 13:54
  • @HolyBlackCat I was under the impression that that was actually more of a JIT compiler and that real interpreters do not exist. Anyway 25 years of experience with C++ here and I wouldn't even dare to get started on an interpreter ;) – Pepijn Kramer Jul 01 '22 at 14:02
  • 1
    A C++ interpreter is a massive undertaking; you need lots of expertise, and it will take years. Start with something more reasonable, like Lisp (you can do a basic Lisp, including garbage collection, in a very short time) or possibly a subset of C or Pascal. (But do make an interpreter or two, because it's also a massive amount of fun.) – molbdnilo Jul 01 '22 at 14:03
  • @PepijnKramer i have already created the sytnax lexer, that parses everything and calculates. it doesnt do everything, but it works. – TBCM Jul 01 '22 at 17:37
  • im pretty sure there has to be a proper way to do this, im just stuck on how to approach it – TBCM Jul 01 '22 at 17:55

1 Answers1

0

There are several problems to tackle with and I will focus on ideas rather than exact code in this answer.

Finding a function by name

You can create a Map between function names and function pointers, so you will have a direct link between function names and function pointers.

Numbers of parameters

You could also store the number of parameters by function pointer/function name, so you will have an easy time calling those functions. Your interpreter would take a single string and split it by some separator.

Main idea

If you have a map between the name of the functions and the functions themselves, then you can do something like this:

myMap["add"](2, 3);
Lajos Arpad
  • 64,414
  • 37
  • 100
  • 175
  • this works but a problem arises when i want to say, add 2 different types. normally i would create an overload for the function, but then we have 2 same map keys which is illegal. – TBCM Jul 01 '22 at 17:36
  • @TBCM this is a solvable issue if you create another level of abstraction. You can define `add` (to continue with the example) as a function that takes an array of parameters, whose size is not known in advance and the types of the elements could be abstractized into `void` pointers. This "abstract" `add` would be part of your `Map` and it would check for the number of parameter and the type of each individual parameter. And then it can call the corresponding implementation. So, you would have n + 1 overloads for the same method, among which you would have – Lajos Arpad Jul 02 '22 at 14:04
  • @TBCM a very abstract implementation that calls the correct overload. You still have a single match for the key and that match would call the reasonable overload. – Lajos Arpad Jul 02 '22 at 14:05