Task

Create a function that takes a Roman numeral as its argument and returns its value as a numeric decimal integer.

You don't need to validate the form of the Roman numeral.

Modern Roman numerals are written by expressing each decimal digit of the number to be encoded separately,

starting with the leftmost decimal digit and skipping any 0s   (zeroes).

1990 is rendered as   MCMXC     (1000 = M,   900 = CM,   90 = XC)     and

2008 is rendered as   MMVIII       (2000 = MM,   8 = VIII).

The Roman numeral for 1666,   MDCLXVI,   uses each letter in descending order.




:- module test_roman.
 
:- interface.
 
:- import_module io.
 
:- pred main(io::di, io::uo) is det.
 
:- implementation.
 
:- import_module char.
:- import_module exception.
:- import_module int.
:- import_module list.
:- import_module string.
 
:- type conversion_error --->  not_a_roman_number.
 
:- func build_int(list(char), int, int) = int.
:- func from_roman(string) = int.
:- pred roman_to_int(char::in, int::out) is semidet.
 
from_roman(Roman) = Decimal :-
  List = reverse(to_char_list(Roman)),
  Decimal = build_int(List, 0, 0).
 
build_int([], LastValue, Accumulator) = LastValue + Accumulator.
build_int([Digit|Rest], LastValue, Accumulator) = Sum :-
  ( roman_to_int(Digit, Value) ->
      ( Value < LastValue ->
          Sum = build_int(Rest, Value, Accumulator - LastValue)
      ;   Sum = build_int(Rest, Value, Accumulator + LastValue) )
  ;   throw(not_a_roman_number) ).
 
roman_to_int('I', 1).
roman_to_int('V', 5).
roman_to_int('X', 10).
roman_to_int('L', 50).
roman_to_int('C', 100).
roman_to_int('D', 500).
roman_to_int('M', 1000).
 
main(!IO) :-
    command_line_arguments(Args, !IO),
    foldl((pred(Arg::in, !.IO::di, !:IO::uo) is det :-
              format("%s => %d\n", [s(Arg), i(from_roman(Arg))], !IO)),
          Args, !IO).
 
:- end_module test_roman.