RosettaCodeData/Task/Range-expansion/ALGOL-68/range-expansion.alg

166 lines
4.4 KiB
Plaintext

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
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 ) )