5 ms·
>Looking for prior implementations of this idea I’ve found magma (2014), where it is called doto. It is a macro for the cmacro[0] pre-processor Hey, that's me!
by zetalyrae 5y ago
>Looking for prior implementations of this idea I’ve found magma (2014), where it is called doto. It is a macro for the cmacro[0] pre-processor
Hey, that's me! I wrote cmacro. I'm glad someone has built a newer and better alternative (cmacro is a big mess inside and has some significant parser errors :P).
Related to the backstitch operator, I've wondered about implementing a transform to turn code like:
type_t value = allocate();
operate(value);
operate(value);
destroy(value);
Into:
type_t value = allocate();
type_t tmp1 = operate(value);
type_t tmp2 = operate(tmp1);
destroy(tmp2);
To implement something like linear types[1] in C, such that each instance of type_t must be used once and only once. But I imagine this would require too much integration with the type system and code analysis.
I wish binary inclusion was provided by more languages or build systems. For small files it's a very suitable alternative to the difficulties of packaging resources when distributing an application or library.
Great project! C is a very useful language and this type of source-to-source compilers are huge force multipliers.
[0]: https://github.com/eudoxia0/cmacro https://github.com/eudoxia0/cmacro
[1]: https://en.wikipedia.org/wiki/Substructural_type_system#Linear_type_systems https://en.wikipedia.org/wiki/Substructural_type_system#Line...
- jll29 5y ago> I wish binary inclusion was provided by more languages or build systems. For small files it's a very suitable alternative to the difficulties of packaging resources when distributing an application or library. When it occasionally comes up as a need, I use the bintosrc command available at https://github.com/jochenleidner/ltools https://github.com/jochenleidner/ltools
- AlbertoGP 5y agoNice to meet you! Thanks for the nice words, my program is much more modest than cmacro :-) Yes, I think that the transform you describe would require the processor to know about types, which is something I avoided here. I experimented early with writing a function that would track the types of things and enable smarter macros, but it became obvious that it was going to be a choice between writing a full compiler frontend and a hacky thing that worked only in trivial cases, so I discarded it. The decision was to keep it as a naïve macro processor, just with a minimum understanding of C syntax, and leave out anything that requires taking into account the semantics. I have other macros that do things like expanding «obj[a..b]» to «(Slice){.start_p=&obj[a], .end_p=&obj[b] }» but they are not worth it, because to work smoothly they would need to know the type of «obj». If absolutely needed, I think that «_Generic» could be used here but it starts complicating things beyond the point of diminishing returns.
- zetalyrae 5y agoYou can get quite far with a preprocessor that's completely unaware of the semantics of the code. Or that trusts the programmer to use the macros on constructions of the right type. Even parsing C "correctly" is a huge problem, since the syntax is ambiguous and the ambiguity can only be resolved by keeping a symbol table that tracks what kind of thing a symbol is. So there's a huge pragmatic force to keep a strict separation between a type-unaware preprocessor and a full-blown compiler frontend. (I considered using clang to dump the C AST when building cmacro, but concluded it would be too unwieldy.)
- garganzol 5y agoFormally this is called a pipeline operator and can be used like this: type_t value = allocate(); operate(value) |> operate |> destroy; Functional languages like OCaml, F#, and Haskell have it built in.
- AlbertoGP 5y agoYou are right, and I list some of those in the “Related work“ section: https://sentido-labs.com/en/library/cedro/202106171400/#backstitch-operator-related-work https://sentido-labs.com/en/library/cedro/202106171400/#back...
- thom 5y agoI am a very weak C programmer, but one thing I find myself doing a fair bit is wrapping the intermediate data from sequences of operations like this into a single structure representing the whole task or unit of work or whatever. That structure then has a single 'valid' and/or 'error' field that each step checks to see if it should actually proceed, and if so, it will fill its field in the structure, and the next step will pick up from there. That way you get to write something like: typedef struct cookie_batch { bool valid; bool baked; unsigned short ingredient_count; char **ingredients; } cookie_batch; . . . void bake(cookie_batch *batch) { if(!batch->valid) return; batch->baked = true; } . . . cookie_batch batch = { .valid = true }; add_ingredients(&batch); bake(&batch); serve(&batch); cleanup(&batch); So you don't need any complex error handling in client code.
- AlbertoGP 5y agoYes, that’s a very clean and robust approach. In the “Deferred resource release → Related work” section, one of the links is to a video from the ACCU 2021 conference by Luca Sas titled “Modern C and What We Can Learn From It” that explains modern C techniques, and your one is mentioned at 23′15″: https://youtu.be/QpAhX-gsHMs?t=23m15s https://youtu.be/QpAhX-gsHMs?t=23m15s If you haven’t watched it, I can recommend it.
- hzhou321 5y agoNot sure about the example semantics, but with MyDef (https://github.com/hzhou/MyDef https://github.com/hzhou/MyDef), it is trivial -- $call value_ops, val, operate, operate subcode: value_ops(v, @ops) type_t $(v) = allocate(); $(for:op in $(ops)) type_t tmp$(_i) = operate($(v)); $(set-1:v=tmp$(_i)) destroy($(v)) MyDef is a general purpose preprocessor. EDIT: maybe this is more fit what was intended type_t var; $(for:op in allocate, operate, operate, destroy) var = $(op)(var);