C & C++ (Updated Answer)
As observed in a comment, my original solution had two problems:
- Optional parameters are only available in C99 and later standards of the language family.
- Trailing comma in enum definition is also specific to C99 and later.
Since I wanted my code to be as generic as possible to work on older platforms, I decided to take another stab at it. It is longer than it was before, but it works on compilers and preprocessors set to C89/C90 compatibility mode. All macros are passed an appropriate number of arguments in the source code, though sometimes those macros "expand" into nothing.
Visual C++ 2013 (aka version 12) emits warnings about missing parameters, but neither mcpp (an open source preprocessor that claims high compliance with the standard) nor gcc 4.8.1 (with -std=iso9899:1990 -pedantic-errors switches) emit warnings or errors for those macro invocations with an effectively empty argument list.
After reviewing the relevant standard (ANSI/ISO 9899-1990, 6.8.3, Macro Replacement), I think there is sufficient ambiguity that this should not be considered non-standard. "The number of arguments in an invocation of a function-like macro shall agree with the number of parameters in the macro definition...". It does not seem to preclude an empty argument list as long as the needed parentheses (and commas in the case of multiple parameters) are in place to invoke the macro
As for the trailing comma problem, that is resolved by adding an extra identifier to the enumeration (in my case, MMMM which seems as reasonable as anything for the identifier to follow 3999 even if it doesn't obey the accepted rules of Roman numeral sequencing exactly).
A slightly cleaner solution would involve moving the enum and supporting macros to a separate header file as was implied in a comment elsewhere, and using undef of the macro names immediately after they were used so as to avoid polluting the namespace. Better macro names should undoubtedly be chosen as well, but this is adequate for the task at hand.
My updated solution, followed by my original solution:
#define _0(i,v,x)
#define _1(i,v,x) i
#define _2(i,v,x) i##i
#define _3(i,v,x) i##i##i
#define _4(i,v,x) i##v
#define _5(i,v,x) v
#define _6(i,v,x) v##i
#define _7(i,v,x) v##i##i
#define _8(i,v,x) v##i##i##i
#define _9(i,v,x) i##x
#define k(p,s) p##s,
#define j(p,s) k(p,s)
#define i(p) j(p,_0(I,V,X)) j(p,_1(I,V,X)) j(p,_2(I,V,X)) j(p,_3(I,V,X)) j(p,_4(I,V,X)) j(p,_5(I,V,X)) j(p,_6(I,V,X)) j(p,_7(I,V,X)) j(p,_8(I,V,X)) j(p,_9(I,V,X))
#define h(p,s) i(p##s)
#define g(p,s) h(p,s)
#define f(p) g(p,_0(X,L,C)) g(p,_1(X,L,C)) g(p,_2(X,L,C)) g(p,_3(X,L,C)) g(p,_4(X,L,C)) g(p,_5(X,L,C)) g(p,_6(X,L,C)) g(p,_7(X,L,C)) g(p,_8(X,L,C)) g(p,_9(X,L,C))
#define e(p,s) f(p##s)
#define d(p,s) e(p,s)
#define c(p) d(p,_0(C,D,M)) d(p,_1(C,D,M)) d(p,_2(C,D,M)) d(p,_3(C,D,M)) d(p,_4(C,D,M)) d(p,_5(C,D,M)) d(p,_6(C,D,M)) d(p,_7(C,D,M)) d(p,_8(C,D,M)) d(p,_9(C,D,M))
#define b(p) c(p)
#define a() b(_0(M,N,O)) b(_1(M,N,O)) b(_2(M,N,O)) b(_3(M,N,O))
enum { _ a() MMMM };
#include <stdio.h>
int main(int argc, char** argv)
{
printf("%d", MMMCMXCIX * MMMCMXCIX);
return 0;
}
The original answer (which received the first six upvotes, so if no one ever upvotes this again, you shouldn't think my updated solution got the upvotes):
In the same spirit as an earlier answer, but done in a way that should be portable using only defined behavior (though different environments don't always agree on some aspects of the preprocessor). Treats some parameters as optional, ignores others, it should work on preprocessors that don't support the __VA_ARGS__
macro, including C++, it uses indirect macros to ensure parameters are expanded before token pasting, and finally it is shorter and I think easier to read (though it is still tricky and probably not easy to read, just easier):
#define g(_,__) _, _##I, _##II, _##III, _##IV, _##V, _##VI, _##VII, _##VIII, _##IX,
#define f(_,__) g(_,)
#define e(_,__) f(_,) f(_##X,) f(_##XX,) f(_##XXX,) f(_##XL,) f(_##L,) f(_##LX,) f(_##LXX,) f(_##LXXX,) f(_##XC,)
#define d(_,__) e(_,)
#define c(_,__) d(_,) d(_##C,) d(_##CC,) d(_##CCC,) d(_##CD,) d(_##D,) d(_##DC,) d(_##DCC,) d(_##DCCC,) d(_##CM,)
#define b(_,__) c(_,)
#define a b(,) b(M,) b(MM,) b(MMM,)
enum { _ a };
My language has a function that does exactly this but to input. (The language was created after this challenge). Can I use this e.g take input and convert? – caird coinheringaahing – 2017-03-15T21:25:45.617
@ValyrioAccount I'm fine with a newer language as long as you state it in the answer. The problem to me is that it sounds like you wouldn't be answering the question properly -- This is not really about the actual conversion from Roman numerals, but rather about finding a way to alter the language itself in a way so that it accepts Roman numerals as code. If I'm wrong then go ahead :) – daniero – 2017-03-17T08:31:18.890
1So, we need to write an extension to the language, thereby creating a new one? – Kendall Frey – 2014-02-13T15:48:45.347
@KendallFrey That depends on the language you choose I guess. But I wouldn't recommend building a new Java compiler. – daniero – 2014-02-13T15:51:41.283
I don't know if there is an alternative for Java (and most other languages). – Kendall Frey – 2014-02-13T16:04:35.040
I used Java as an example for the hell of it ;) and @histocrat just delivered an awesome Ruby example, so no more complaining! – daniero – 2014-02-13T16:05:51.870
4I'll complain if I want to, because the languages I use are not extensible like that, so I can't even participate. – Kendall Frey – 2014-02-13T16:15:21.053
Sure you can't tap into the runtime somehow..? – daniero – 2014-02-13T16:18:48.590
At compile-time? Unlikely. :P – Kendall Frey – 2014-02-13T16:24:42.093
3
@KendallFrey The source would have to compile and run. For Java, you could write a "compiler" that edits the source, then programmatically compiles. One way of such compiling is through running a
– Justin – 2014-02-13T19:04:12.737Process
@Quincunx I too thought of something like this, but as the poster of the question I didn't want to give to anything away. The post you refer to shouldn't be very far away from a valid answer here. I am also thinking about relaxing the requirements a little though, so that the roman numerals wouldn't need to behave exactly like other numbers, but for example could be used like
Numeral n = new XLII()
- as long as the numeral itself in not contained in a string or other literal . That could probably be helpful in some languages. – daniero – 2014-02-13T19:17:38.007@daniero That would be weird; it would require hard-coding the classes for every roman numeral (not possible, there are infinite roman numerals). Something like
Numeral n = new Numeral("XLII");
makes more sense. I don't think you should change your question though. – Justin – 2014-02-13T19:19:59.280@Quincunx You're right. I was thinking that since we already were into compiled languages and toying with reflection, one could do some static analysis to figure out just which numerals one would need to make a class for. But it doesn't actually make much sense since you could rather use the analysis to figure out what constants to create.
new Numeral("XLII")
would be against the rules either way. – daniero – 2014-02-13T19:28:52.0831Seems boring in most languages. For example in python I would simply write a script that uses
ast
to parse the source. Insert at the top of the AST the definition of the roman numerals from 1 to 3999. Compile the whole thing and run it. It's just boring to write the code to handle the process. – Bakuriu – 2014-02-13T21:12:14.2472@Bakuriu and your comment is boring too. This is a popularity contest, so you should try to come up with something fun. I think there are some nice answers here that are more imaginative (than compiling a scripting language). – daniero – 2014-02-13T21:19:15.500
1
@Quincunx an old blog post about someone doing that... but rather closer to the compiler than a separate thing. http://www.iam.unibe.ch/~akuhn/blog/2008/roman-numerals-in-your-java/
– None – 2014-02-16T01:25:45.197