A format for expressing an ordered list of integers is to use a comma separated list of either

 individual integers
 Or a range of integers denoted by the starting integer separated from the end integer in the range by a dash, '-'. (The range includes all integers in the interval including both endpoints)
 The range syntax is to be used only for, and for every range that expands to more than two values.
Example

The list of integers:

 -6, -3, -2, -1, 0, 1, 3, 4, 5, 7, 8, 9, 10, 11, 14, 15, 17, 18, 19, 20
Is accurately expressed by the range expression:

 -6,-3-1,3-5,7-11,14,15,17-20
(And vice-versa).



Task

Expand the range description:

  -6,-3--1,3-5,7-11,14,15,17-20 

Note that the second element above,
is the range from minus 3 to minus 1. 



Related task

   Range extraction


MODE YIELDINT = PROC(INT)VOID;
 
MODE RANGE = STRUCT(INT lwb, upb);
MODE RANGEINT = UNION(RANGE, INT);
 
OP SIZEOF = ([]RANGEINT list)INT: (
# determine the length of the output array #
  INT upb := LWB list - 1;
  FOR key FROM LWB list TO UPB list DO
    CASE list[key] IN
      (RANGE value): upb +:= upb OF value - lwb OF value + 1,
      (INT): upb +:= 1
    ESAC
  OD;
  upb
);
 
PROC gen range expand = ([]RANGEINT list, YIELDINT yield)VOID:
  FOR key FROM LWB list TO UPB list DO
    CASE list[key] IN
      (RANGE range): FOR value FROM lwb OF range TO upb OF range DO yield(value) OD,
      (INT int): yield(int)
    ESAC
  OD;
 
PROC range expand = ([]RANGEINT list)[]INT: (
  [LWB list: LWB list + SIZEOF list - 1]INT out;
  INT upb := LWB out - 1;
# FOR INT value IN # gen range expand(list, # ) DO #
##   (INT value)VOID:
    out[upb +:= 1] := value
# OD #);
  out
);
 
#
test:(
  []RANGEINT list = (-6, RANGE(-3, -1), RANGE(3, 5),  RANGE(7, 11), 14, 15, RANGE(17, 20));
  print((range expand(list), new line))
)
#
 
 
# converts string containing a comma-separated list of ranges and values to a []RANGEINT #
OP TORANGE = ( STRING s )[]RANGEINT:
BEGIN
 
    # counts the number of elements - one more than the number of commas #
    # and so assumes there is always at least one element                #
    PROC count elements = INT:
    BEGIN
 
        INT    elements := 1;
 
        FOR pos FROM LWB s TO UPB s
        DO
            IF s[ pos ] = ","
            THEN
                elements +:= 1
            FI
        OD;
 
    # RESULT #
        elements
    END; # count elements #
 
    REF[]RANGEINT result = HEAP [ 1 : count elements ]RANGEINT;
 
    # does the actual parsing - assumes the string is syntatically valid and doesn't check for errors #
    # - in particular, a string with no elements will cause problems, as will space characters in the string #
    PROC parse range string = []RANGEINT:
    BEGIN
 
        INT  element := 0;
        INT  str pos := 1;
 
        PROC next       = VOID: str pos +:= 1;
        PROC curr char  = CHAR: IF str pos > UPB s THEN "?" ELSE s[ str pos ] FI;
        PROC have minus = BOOL: curr char = "-";
        PROC have digit = BOOL: curr char >= "0" AND curr char <= "9";
 
 
        # parses a number out of the string #
        # the number must be a sequence of digits with an optional leading minus sign #
        PROC get number = INT:
        BEGIN
 
            INT number := 0;
 
            INT sign multiplier = IF have minus
                                  THEN
                                      # negaive number #
                                      # skip the sign #
                                      next;
                                      -1
                                  ELSE
                                      # positive number #
                                      1
                                  FI;
 
            WHILE curr char >= "0" AND curr char <= "9"
            DO
                number *:= 10;
                number +:= ( ABS curr char - ABS "0" );
                next
            OD;
 
        # RESULT #
            number * sign multiplier
        END; # get number #
 
 
        # main parsing #
        WHILE str pos <= UPB s
        DO
            CHAR c = curr char;
 
            IF have minus OR have digit
            THEN
                # have the start of a number #
                INT from value = get number;
                element +:= 1;
                IF NOT have minus
                THEN
                    # not a range #
                    result[ element ] := from value
                ELSE
                    # have a range #
                    next;
                    INT to value = get number;
                    result[ element ] := RANGE( from value, to value )
                FI
            ELSE
                # should be a comma #
                next
            FI
        OD;
 
    # RESULT #
        result
    END; # parse range string #
 
 
# RESULT #
    parse range string
END; # TORANGE #
 
 
# converts a []INT to a comma separated string of the elements #
OP TOSTRING = ( []INT values )STRING:
BEGIN
 
    STRING result    := "";
    STRING separator := "";
 
    FOR pos FROM LWB values TO UPB values
    DO
        result +:= ( separator + whole( values[ pos ], 0 ) );
        separator := ","
    OD;
 
# RESULT #
    result
END; # TOSTRING #
 
 
test:(
    print( ( TOSTRING range expand( TORANGE "-6,-3--1,3-5,7-11,14,15,17-20" ), newline ) )
)
 