5 ms·
Could someone explain this gem in the C version? https://github.com/kanaka/mal/blob/master/c/types.c#L170 https://github.com/kanaka/mal/blob/master/c/types.c#L
by yosyp 9y ago
Could someone explain this gem in the C version?
https://github.com/kanaka/mal/blob/master/c/types.c#L170 https://github.com/kanaka/mal/blob/master/c/types.c#L170
- gbacon 9y agoIn struct MalVal[0], the values f0 through f20, where each holds a pointer-to-functions of the corresponding arity, live in a big (multifacted?) union. Using arg_cnt, the switch assigns func to the appropriate value in mv->val using the appropriate cast. For example, when arg_cnt is 2, mv->val.f2 receives func converted to void * (* )(void* , void* ), that is, pointer to a function that accepts two parameters of type pointer-to-void and returns pointer-to-void. [Weird spacing to effectively escape the asterisks that would otherwise be treated as italicizing markup.] C permits conversion between pointer-to-function of one type to pointer-to-function of another type. The inverse is also defined, and the resulting pointer will compare equal to the original. However, calling a function through a pointer-to-function where the types do not match invokes undefined behavior. [0]: https://github.com/kanaka/mal/blob/master/c/types.h#L85 https://github.com/kanaka/mal/blob/master/c/types.h#L85
- copx 9y agoThis could be made massively less ugly and more readable by typedef'ing the function pointer types by the way. Then one could just write: case 13: mv->val.f13 = (F13)func; break; As a C programmer I got to say people in general don't use typedef for function pointer types often enough. The types are so ugly and verbose, they should be typedef'ed by default.
- kanaka 9y agoI look forwards to your pull request! One challenge you'll run into is that (as I recall) the Boehm GC linkage no longer works with newer versions of Boehm.
- gizmo385 9y agoI think the primary argument that I've heard against typedefing function pointers is you can end up obfuscating what is happening and end up making the code less readable/harder to maintain in the future.
- deleted 9y ago[deleted]
- kazinator 9y agoThis code could take advantage of the fact that the type is a union. One can bend the rules of prim-and-proper well-defined ISO C just a little bit and assign to the simplest function pointer, taking advantage of all function pointers having the same representation on every machine known to mortal hacker, and all being overlaid by the union. Thus all the cases collapse down to this: mv->val.u.f0 = (void (*)(void)) func; done. Now if this is 13 arguments, then strictly speaking, accessing mv->val.uf13 is not well-defined behavior. That's only going to be problem when it actually doesn't work, though. A big problem, then. :)
- kazinator 9y agoIn TXR Lisp, I did the grunt work and wrote separate constructor functions for different function signatures: http://www.kylheku.com/cgit/txr/tree/lib.c#n5525 http://www.kylheku.com/cgit/txr/tree/lib.c#n5525 These functions are only used from within C code; they are not the basis for any intrinsic functions. The call to each of these functions knows exactly the type of function it wants to make. There is a notation in the naming. Firstly, the leading 'f' denotes functions with an environment value, whereas 'n' denotes non-environment functions. Then there is a number indicating the number of arguments. Then an optional letter which is 'o' if the function has optional arguments, or 'v' if it is variadic. Both can be present in which case we have 'ov. Thus for example func_n2ov(some_c_function, 1) will create a variadic function with 2 fixed arguments, of which 1 is required (so one optional). some_c_function has to have the type signature val (* )(val, val, varg). No casting is required because func_n2ov just assigns this to the correct union member. If we pass a function with an incorrect signature, we get a C compile error. The func_n2ov constructor is used when registering the unique function in eval.c: reg_fun(intern(lit("unique"), user_package), func_n2ov(unique, 1)); We intern the string "unique" in the user package ("usr"), and use that symbol to register a function createdy by hoisting the C function unique into the Lisp domain with func_n2ov. I wrote TXR in a way that is easy to understand and maintain. It is only implemented in C, though. If you want to study how to make a Lisp interpreter in C. Yet, it is production code for real work.