5

I am working on a custom programming language called BPML and I want to try and update the output function called say().

Recent version in C:

void say(char *text) {
    printf("%s", text);
}

Recent version in Python:

def say(text):
    print(text, end = '')

In C, I want it to act like Python's print() function by just typing in the variable to the function, but it's not the case in C, because you still have to use the appropriate kind of variable.

Which is why I got an error in a program I made:

#include <stdio.h>

void say(char *text);

int main() {
    int number = 21;
    say(number);
}

void say(char *text) {
    // Output function
}

Error:

P1.c:7:9: error: incompatible integer to pointer conversion passing 'int' to parameter of type 'char *' [-Werror,-Wint-conversion]
    say(number);
        ^~~~~~

I still have to use the itoa() function in <stdlib.h>, which I didn't have in mind, since it wasn't available in the version of C I use.

So is it possible to make some kind of output function that is similar to the way Python's function acts?

BMPL
  • 35
  • 16
  • 2
    Perhaps we must look towards how Python implemented this, which is likely through run-time type-detection (slow, but allows such generalised functions) – A P Jo Sep 24 '20 at 10:19
  • 5
    It sounds like you want a polymorphic function. C doesn't have those. You could instead create your own universal data type wrapper, which includes type info, then pass instances of those to `say`. – Tom Karzes Sep 24 '20 at 10:23
  • 3
    Are you trying to write a function in *C*, or in your custom *BPML* language? It sounds like what you really have is a language design issue, and you need to figure out how your language design is going to handle this. Maybe you need your language to have function overloading, or dynamic typing or something. – user2357112 Sep 24 '20 at 10:50
  • @user2357112supportsMonica I haven't started making the compiler, the language files, the other stuff just yet. I might need assistance in that. I'm just starting off with base by making the functions in different languages first. – BMPL Sep 24 '20 at 14:40
  • 2
    @BPML: The `_Generic` answer you've accepted is almost certainly going to be 100% useless for the language you're planning to write, though. It's extremely specific to C. – user2357112 Sep 24 '20 at 14:49

4 Answers4

5

C does not have overloads, nor does it have function templates. But you can a macro and generic selection in C11 and above to choose an appropriate format:

#include <stdio.h>

#define say(X) printf(_Generic((X),    \
                        double: "%f ", \
                        float:  "%f ", \
                        char *: "%s ", \
                        int:    "%d "  \
                ), (X));

int main(void) {
    say(21);
    say(21.5);
    say(21.5f);
    say("Hello world");
}

This can be coupled with X-macros to have really powerful constructs easily:

#include <stdio.h>

// you can easily add new supported types here
#define SAY_FORMATS(X) \
    X(double, "%f")    \
    X(float,  "%f")    \
    X(char *, "%s")    \
    X(int,    "%d")
    
// add space after each item. Leading comma so that we do not
// need to have a dummy entry in the end. Unfortunately C 
// does not like trailing commas in _Generic. Thanks to 
// user694733 for the idea
#define GENERIC_ENTRY(Type, Format) \
    , Type: Format " "

#define say(X)                      \
     printf(_Generic((X)            \
         SAY_FORMATS(GENERIC_ENTRY) \
     ), (X))

int main(void) {
    say(21);
    say(21.5);
    say(21.5f);
    say("Hello world");
}
2

Whether you're doing this in C, or another language you implement in C, you'll probably need to use something other than C primitives to store your data. In principle, a union has the capability to store multiple data types, but it's probably not flexible enough for what you need.

Compiler implementers typically use structures of this general form to store data of types not known at compile time:

struct GenericData
  {
  int i;
  char *str;
  double d;
  // ... other types
  enum Type t;
  };

Here, the member t indicates which of the various fields contains valid data at any given time -- whether it's representing an integer, or a string, or a floating-point number, or whatever. So you can define your say function like this:

void say (const struct GenericData *data)
  {
  ...
  }

and you'd use it something like this:

GenericData *v = // some function to intialize it
say (v);

Of course, you'd need functions to initialize your values with data of the right type, do math on them, tidy them up after use, etc.

Just as an illustration, the GLib library (used by GTK and Gnome) has built-in support for "variant" data types, using a structure called GVariant:

https://developer.gnome.org/glib/stable/glib-GVariant.html

I imagine that anybody who's ever written a compiler or interpreter will have gone through the process of handling variant types, and will be aware how much work is involved. Don't underestimate the amount of work, or the difficulty.

Kevin Boone
  • 4,092
  • 1
  • 11
  • 15
1

It's not possible. C does not support type checking in runtime.

Here's the source of Python's print(), although I doubt it will be of much help at all: link

Take a look at the parameters it takes – not a single primitive.

static PyObject *
builtin_print(PyObject *self, PyObject *args, PyObject *kwds)
Harvastum
  • 61
  • 10
0

I decided to expand Antti Haapala's answer to resemble every single format in the printf() function:

#define say(X) printf(_Generic((X),                     \
                      double: "%f", float: "%f",        \
                      char *: "%s", char: "%c",         \
                         int: "%d", unsigned int: "%u", \
                  signed int: "%i", void *: "%p"
               ), (X));

I don't know exactly how to take the formats of %e, %g, %x, %o and %a, as they all take either a floating-point value (float or double) or an unsigned int.