Roman Numeral Fun

In my waiting-for-people-to-leave-so-I-can-get-into-classrooms time at work I wrote a roman numeral translator in Factor. It’s a bit different from your normal implementation as Factor’s parser actually does almost all the work:

USING: strings parser kernel words sequences math ;

: NUMERAL: CREATE dup reset-generic dup t "parsing" set-word-prop parse-definition  parsed add define-compound ; parsing

NUMERAL: I 1 ;
NUMERAL: IV 4 ;
NUMERAL: V 5 ;
NUMERAL: IX 9 ;
NUMERAL: X 10 ;
NUMERAL: XL 40 ;
NUMERAL: L 50 ;
NUMERAL: XC 90 ;
NUMERAL: C 100 ;
NUMERAL: CD 400 ;
NUMERAL: D 500 ;
NUMERAL: CM 900 ;
NUMERAL: M 1000 ;

: separate ( str -- str )
    "" swap [ " " append ] [ add ] interleave ;

: join-special ( str str -- str )
    dup >r split1 [ 1 r> remove-nth swap 3append ] [ r> drop ] if* ;

: merge-specials ( str -- str )
    [ "I V" "I X" "X L" "X C" "C D" "C M" ] [ join-special ] each ;

: convert-numerals ( string -- arr )
    separate merge-specials parse ;

: all-numerals? ( str -- ? )
    [ "IVXLCDM" member? ] all? ;

: roman>number ( roman -- number )
    >upper dup all-numerals? [ convert-numerals sum ] [ drop "Not a roman numeral" ] if ;

Instead of grabbing characters and keeping a running tally, I defined a bunch of parsing words using NUMERAL: to hold the values. I then took the string and split it into individual characters (“XIV” becomes “X I V”). The 4’s and 9’s are then rejoined (so we get “X IV”). I then simply parse the string, which gives a list of numbers and sum that up. It’s not perfect as it allows any pattern of numerals (“IVIVIVIV” parsed to 22), but good enough.