Applying Mappings
ApplyMappings:
PUBLIC
PROC [toMap:
REF, palette: Profiles.Profile, mapData: MapData, mapOnly:
ROPE ←
NIL, subpaletteList:
LIST
OF
ROPE ←
NIL]
RETURNS [mappedRope:
ROPE ←
NIL, mappedList:
LIST
OF
ROPE ←
NIL] ~ {
IF toMap#
NIL
THEN
WITH toMap
SELECT
FROM
toMap:
ROPE => {
--single keyName to be mapped.
IF mapOnly=
NIL
THEN {
--the normal case. Search the exception palettes, don't calculate
SELECT
TRUE
FROM
mapData=NIL OR (mapData.exceptionsPalette=NIL AND mapData.requestedMappings=NIL) => RETURN;
mapData.exceptionsPalette#NIL => IF (mappedRope ← Profiles.Line[profile: mapData.exceptionsPalette, key: toMap])#NIL THEN RETURN; --first look in the exceptions palette
ENDCASE => NULL;
FOR each:
LIST
OF ColorMapping ← mapData.requestedMappings, each.rest
UNTIL each=
NIL
DO
IF (mappedRope ← Profiles.Line[profile: palette, key: Rope.Concat[each.first.name, toMap]])#NIL THEN RETURN; --then try regular palette with exceptions prefixes
ENDLOOP;
}
ELSE {
--apply a single mapping (mapOnly) and protect it with braces. Used in exceptions like c13: ForceToSpring. Braces prevent later mappings.
tokenList: LIST OF ROPE; --first, look up toMap in palette
levelsExceeded: BOOL;
colorMapping: ColorMapping;
IF mapData#NIL THEN [colorMapping, mapData.installedMappings] ← IsAvailable[mapOnly, mapData.installedMappings, palette] ELSE RETURN;
[tokenList, levelsExceeded] ← GetRecursiveValue[key: toMap, palette: palette, subpaletteList: CONS[mapOnly, subpaletteList], mapData: NIL, noMappings: TRUE]; --get the actual color numbers for toMap
SELECT
TRUE
FROM
--then check various errors
tokenList=NIL => RETURN;
levelsExceeded =>
{
SIGNAL ColorizeViewPoint.Warning[class: $MalformedPaletteEntry, explanation: IO.PutFR[format: "%g is part of a recursive color definition beyond allowable levels; ignoring it.", v1: [rope[toMap]]]];
RETURN;
};
colorMapping=
NIL =>
{
--tokenList was found but mapping wasn't; so "mapping" is not a true mapping but IS a sub-palette. Return the tokenList
mappedList ← tokenList;
};
colorMapping.bad => RETURN; --error already sent when bad was set
ENDCASE =>
--finally, apply mapping
mappedList ← colorMapping.mappingProc[valueIn: tokenList, name: mapOnly, palette: palette, data: colorMapping.data ! BadFormula =>
{SIGNAL ColorizeViewPoint.Warning[$MalformedPaletteEntry, IO.PutFR[format: "Ignoring mapping \"%g\" because: \n %g", v1: [rope[colorMapping.name]], v2: [rope[msg]]]]; colorMapping.bad ← TRUE; GOTO Bad}];
IF mappedList#NIL --possible with NilMapper-- THEN RETURN [mappedList: CONS["{", JoinLists[mappedList, LIST["}"]]]]; --add braces to protect further mapping
};
};
toMap:
LIST
OF
ROPE => {
--use calculations to produce a mapped LIST OF ROPE
IF mapData=NIL OR (mapOnly=NIL AND mapData.requestedMappings=NIL) THEN RETURN [mappedList: StripBraces[toMap]];
mappedList ← toMap;
IF mapOnly=
NIL
THEN
FOR each:
LIST
OF ColorMapping ← mapData.requestedMappings, each.rest
UNTIL each=
NIL
DO
IF ~each.first.bad
THEN mappedList ← each.first.mappingProc[mappedList, each.first.name, palette, each.first.data
! BadFormula => {SIGNAL ColorizeViewPoint.Warning[$MalformedPaletteEntry, IO.PutFR[format: "Ignoring mapping \"%g\" because: \n %g", v1: [rope[each.first.name]], v2: [rope[msg]]]]; each.first.bad ← TRUE; LOOP}];
ENDLOOP
ELSE {
--just do a single mapping
colorMapping: ColorMapping;
[colorMapping, mapData.installedMappings] ← IsAvailable[mapOnly, mapData.installedMappings, palette];
IF colorMapping#
NIL
AND ~colorMapping.bad
THEN mappedList ← colorMapping.mappingProc[mappedList, mapOnly, palette, colorMapping.data
! BadFormula => {SIGNAL ColorizeViewPoint.Warning[$MalformedPaletteEntry, IO.PutFR[format: "Ignoring mapping \"%g\" because: \n %g", v1: [rope[colorMapping.name]], v2: [rope[msg]]]]; colorMapping.bad ← TRUE; GOTO Bad}]
ELSE RETURN;
};
};
ENDCASE => ERROR --System error!
EXITS Bad => RETURN;
};
GetMappings:
PUBLIC PROC [palette: Profiles.Profile]
RETURNS [mapData: MapData] ~ {
ENABLE {
BadFormula => {
SIGNAL ColorizeViewPoint.Warning[$MalformedPaletteEntry, IO.PutFR[format: "Can't do any mapping because: \n %g", v1: [rope[msg]]]];
GOTO Bad}
};
installedMappings, requestedMappings: LIST OF ColorMapping;
[installedMappings, requestedMappings] ← GetRequested[palette, GetStandardMappings[palette]];
mapData ←
NEW[ MapDataRep ← [
installedMappings: installedMappings,
requestedMappings: requestedMappings,
exceptionsPalette: GetExceptionsPalette[palette]]];
EXITS Bad => RETURN [NIL];
};
GetRequested:
PROC [palette: Profiles.Profile, installed:
LIST
OF ColorMapping]
RETURNS [installedMappings, requestedMappings:
LIST
OF ColorMapping ←
NIL] ~ {
dummyHead: LIST OF ColorMapping ← LIST[NIL]; --to add elements to end of a list
tail: LIST OF ColorMapping ← dummyHead;
namesRequested: LIST OF ROPE ← Profiles.ListOfTokens[profile: palette, key: "Palette"]; --this will include subpalettes and mappings, so sort them out.
installedMappings ← installed;
FOR each:
LIST
OF
ROPE ← namesRequested, each.rest
UNTIL each=
NIL
DO
mapping: ColorMapping;
[mapping, installedMappings] ← IsAvailable[each.first, installedMappings, palette];
IF mapping#NIL THEN tail ← (tail.rest ← LIST[mapping]);
ENDLOOP;
requestedMappings ← dummyHead.rest;
while we're at it, now's the time to set the appropriate palette entries TRUE
FOR each:
LIST
OF ColorMapping ← requestedMappings, each.rest
UNTIL each=
NIL
DO
FOR every:
LIST
OF
ROPE ← each.first.setTrue, every.rest
UNTIL every=
NIL
DO
SetProfileBoolean[profile: palette, key: every.first, val: TRUE]; --for each requested Mapping, set every "setTrue" listing TRUE
ENDLOOP;
ENDLOOP;
};
IsAvailable:
PROC [name:
ROPE, installed:
LIST
OF ColorMapping, palette: Profiles.Profile]
RETURNS [mapping: ColorMapping ←
NIL, installedMappings:
LIST
OF ColorMapping] ~ {
ENABLE {
BadFormula => {
SIGNAL ColorizeViewPoint.Warning[$MalformedPaletteEntry, IO.PutFR[format: "Ignoring mapping \"%g\" because: \n %g", v1: [rope[name]], v2: [rope[msg]]]];
GOTO Bad}
};
DefinedFormulas: Profiles.EnumProc = {
--check defined "Formula" mappings
[key: ROPE] RETURNS [quit: BOOL ← FALSE]
prefix: ROPE ← key.Substr[0, key.Find[s2: "Formula", case: FALSE]];
IF prefix.Equal[s2: name, case:
FALSE]
THEN {
mapping ←
NEW [ColorMappingRep ← [
name: prefix,
mappingProc: FormulaMapper,
setTrue: CONS["AmbushAllYESColors", Profiles.ListOfTokens[profile: palette, key: prefix.Concat["SetTrue"]]], --eg, "GreySetTrue: IP2"
data: Profiles.Line[profile: palette, key: key] ]];
installedMappings ← CONS[mapping, installedMappings];
found ← TRUE;
RETURN [quit: TRUE];
};
};
DefinedSnapMaps: Profiles.EnumProc = {
--check defined "SnapList" mappings
[key: ROPE] RETURNS [quit: BOOL ← FALSE]
prefix: ROPE ← key.Substr[0, key.Find[s2: "SnapList", case: FALSE]];
IF prefix.Equal[s2: name, case:
FALSE]
THEN {
mapping ←
NEW [ColorMappingRep ← [
name: prefix,
mappingProc: SnapMapper,
setTrue: CONS["AmbushAllYESColors", Profiles.ListOfTokens[profile: palette, key: prefix.Concat["SetTrue"]]], --eg, "SpringSetTrue: foo"
data: GetSnapList[key, palette] ]];
installedMappings ← CONS[mapping, installedMappings];
found ← TRUE;
RETURN [quit: TRUE];
};
};
found: BOOL ← FALSE;
data: LIST OF ROPE ← NIL;
FOR each:
LIST
OF ColorMapping ← installed, each.rest
UNTIL each=
NIL
DO
--check if currently installed
IF name.Equal[s2: each.first.name, case: FALSE] THEN RETURN [each.first, installed];
ENDLOOP;
installedMappings ← installed;
Profiles.EnumerateKeys[profile: palette, pattern: "*Formula", proc: DefinedFormulas]; --check the defined "Formula" mappings, like "GreyFormula"
IF ~found THEN Profiles.EnumerateKeys[profile: palette, pattern: "*SnapList", proc: DefinedSnapMaps]; --check the defined "SnapList" mappings, like "SpringSnapList"
IF ~found
AND (data ← GetRecursiveValue[key: name, palette: palette, subpaletteList: SubpaletteSearchList[prefixesIn:
NIL, profile: palette], mapData:
NIL, noMappings:
TRUE].value)#
NIL
--name is in profile--
THEN {
mapping ←
NEW [ColorMappingRep ← [
--This allows users to say "ForceTolightblue" or "ForceToC15" and list a whole bunch of colors. This mapping simply substitutes "lightblue" or "C15" for those colors
name: name,
mappingProc: Substituter,
setTrue: NIL,
data: data ]];
installedMappings ← CONS[mapping, installedMappings];
};
EXITS Bad => RETURN [mapping: NIL, installedMappings: installed];
};
Color: TYPE ~ RECORD [vals: ARRAY [1..4] OF REAL ← ALL[0.0], size: [0..4] ← 0];
RGBColor: TYPE ~ Color; --size will always be 3
nullColor: Color;
nullRGB: RGBColor;
GetSnapList:
PROC [name:
ROPE, palette: Profiles.Profile]
RETURNS [snapList: SnapList ←
NIL] ~ {
ConvertColors: ApplyMappingProc ~ {
[toMap: Color, tokenAfter: ROPE] RETURNS [LIST OF ROPE]
IF Rope.Match[pattern: "Sweep*", object: tokenAfter, case:
FALSE]
THEN
SIGNAL ColorizeViewPoint.Warning[$MalformedPaletteEntry, IO.PutFR[format: "Can't snap to a sweep color--will take each color in Sweep def as a constant color"]];
IF toMap.size#0 THEN tail ← (tail.rest ← LIST[ConvertToRGB[toConvert: toMap, palette: palette]]);
RETURN [NIL]; --don't care what we return
};
dummyHead: LIST OF RGBColor ← LIST[nullRGB]; --to add elements to end of a list
tail: LIST OF RGBColor ← dummyHead;
subpaletteList: LIST OF ROPE ← SubpaletteSearchList[prefixesIn: NIL, profile: palette];
snapColors: LIST OF ROPE ← GetRecursiveValue[key: name, palette: palette, subpaletteList: subpaletteList, mapData: NIL, noMappings: TRUE].value;
IF snapColors=NIL THEN RETURN;
[] ← MapValues[valueIn: snapColors, mapper: ConvertColors];
snapList ← dummyHead.rest;
};
GetExceptionsPalette:
PROC [palette: Profiles.Profile]
RETURNS [exceptionsPalette: Profiles.Profile ←
NIL]~ {
--finds all the exceptions to the mappings specified by the user, such as "ForceToBlack: c1, c2", which says map c1 & c2 to black no matter what the normal mapping would be.
exceptionList: LIST OF ROPE ← ExpandExceptionCommands[palette];
IF exceptionList#NIL THEN exceptionsPalette ← Profiles.CreateFromRope[slices: exceptionList];
};
ExpandExceptionCommands:
PROC [palette: Profiles.Profile]
RETURNS [outList:
LIST
OF
ROPE ←
NIL] ~ {
ExpandEach: Profiles.EnumProc ~ {
--like ForceToSpring, ForceToGrey, .... Changes "ForceToSpring: c1, c2" to "c1: ForceToSpring\n c2: ForceToSpring\n"
[key: ROPE] RETURNS [quit: BOOL ← FALSE]
temp: ROPE;
SELECT
TRUE
FROM
--black and white are treated specially; need to have a 1 color black/white to get thru an IP2 printer
key.Equal[s2: "ForceToBlack", case: FALSE] => IF (temp ← ExpandCommand[cmd: "ForceToBlack", value: IF palette.Boolean[key: "IP2"] THEN "1.0" --1 SetGray-- ELSE "ForceToBlack", palette: palette])#NIL THEN outList ← CONS[temp, outList]; --eg, "c5: 1.0" for IP2 printers else "c5: ForceToBlack"
key.Equal[s2: "ForceToWhite", case: FALSE] => IF (temp ← ExpandCommand[cmd: "ForceToWhite", value: IF palette.Boolean[key: "IP2"] THEN "0.0" --0 SetGray-- ELSE "ForceToWhite", palette: palette])#NIL THEN outList ← CONS[temp, outList];
ENDCASE => {
IF (temp ← ExpandCommand[cmd: key, value: key, palette: palette])#NIL THEN outList ← CONS[temp, outList]; --eg, "c1: ForceToSpring"
};
};
Profiles.EnumerateKeys[profile: palette, pattern: "ForceTo*", proc: ExpandEach];
};
ExpandCommand:
PROC [cmd, value:
ROPE, palette: Profiles.Profile]
RETURNS [out:
ROPE ←
NIL] ~ {
--looks in palette for cmd; expands that cmd line. Command lines should be of the form "cmd: color1, color2, ...\n", and are expanded to "color1: value\n color2: value\n ..." Eg, "ForceToRed: c1, c2" => "c1: ForceToRed\n, c2: ForceToRed\n..", which causes c1 & c2 to be forced to red. A separator other than space must be used btwn colors since named colors may have spaces.
valueLine: ROPE ← Rope.Cat[": ", value, " \n"];
tokens: LIST OF ROPE ← GetRecursiveValue[key: cmd, palette: palette, subpaletteList: LIST[""], levelsAllowed: 0--don't recurse!--, mapData: NIL, noMappings: TRUE].value; --this automatically changes bad tokens like c04 to c4, etc.
IF tokens=NIL THEN RETURN;
FOR each:
LIST
OF
ROPE ← tokens, each.rest
UNTIL each=
NIL
DO
SELECT each.first.Fetch
FROM
',, ';, '/ --separators-- => LOOP;
ENDCASE => out ← out.Cat[each.first, valueLine]; --eg "c1: 1.0 \n"
ENDLOOP;
};
StripBraces:
PROC [tokens:
LIST
OF
ROPE]
RETURNS [
LIST
OF
ROPE] ~ {
--w/o "{" or "}"
dummyHead: LIST OF ROPE ← LIST[NIL];
tail: LIST OF ROPE ← dummyHead;
FOR each:
LIST
OF
ROPE ← tokens, each.rest
UNTIL each=
NIL
DO
SELECT TRUE FROM
each.first.Equal["{"], each.first.Equal["}"] => NULL;
ENDCASE => tail ← (tail.rest ← LIST[each.first]);
ENDLOOP;
RETURN [dummyHead.rest];
};
MappingProcs
SnapList: TYPE ~ LIST OF RGBColor;
SnapMapper: MappingProc ~ {
--snaps to nearest color in a list of colors
PROC [valueIn: LIST OF ROPE, name: ROPE, palette: Profiles.Profile, data: REF ← NIL] RETURNS [mapped: LIST OF ROPE]
ApplySnapMap: ApplyMappingProc ~ {
[toMap: Color, tokenAfter: ROPE] RETURNS [mapped: LIST OF ROPE, quit: BOOL ← FALSE]
dummyHead: LIST OF ROPE ← LIST[NIL]; --mechanism to add elements to end of a list
tail: LIST OF ROPE ← dummyHead;
toMapRGB, mappedColor: RGBColor;
IF toMap.size=0 THEN RETURN [mapped: LIST[tokenAfter]];
toMapRGB ← ConvertToRGB[toMap, palette]; --deal uniformly w RBG
mappedColor ← FindNearest[toMapRGB, snapList, palette];
FOR i:
INT
IN [1..3]
DO
tail ← (tail.rest ← LIST[Convert.FtoRope[r: mappedColor.vals[i], afterDot: 2]]);
ENDLOOP;
tail ← (tail.rest ← LIST[tokenAfter]); --add the tokenAfter to end
RETURN [mapped: dummyHead.rest];
};
snapList: SnapList ← NARROW[data];
IF snapList=
NIL
THEN {
SIGNAL ColorizeViewPoint.Warning[$MalformedPaletteEntry, IO.PutFR[format: "No colors found to define mapping \"%g\"", v1: [rope[name]]]];
RETURN [valueIn];
};
mapped ← MapValues[valueIn: valueIn, mapper: ApplySnapMap];
};
FormulaMapper: MappingProc ~ {
--applies a formula from palette to color
PROC [valueIn: LIST OF ROPE, name: ROPE, palette: Profiles.Profile, data: REF ← NIL] RETURNS [mapped: LIST OF ROPE]
ApplyFormula: ApplyMappingProc ~ {
[toMap: Color, tokenAfter: ROPE] RETURNS [mapped: LIST OF ROPE, quit: BOOL ← FALSE]
TokenVal: GetValueProc ~ {
--used by ParseFormula
PROC [r: ROPE, s: Stack] RETURNS [v: REAL ← Real.LargestNumber, pop: NAT ← 0]
RETURN [v: (IF Rope.Match[pattern: r.Concat["*"], object: tokenAfter, case: FALSE] THEN 1.0 ELSE 0.0)]};
dummyHead: LIST OF ROPE ← LIST[NIL]; --mechanism to add elements to end of a list
tail: LIST OF ROPE ← dummyHead;
mappedColor: Color;
[mappedColor, quit] ← ParseFormula[toMap, formula, palette, TokenVal];
FOR i:
INT
IN [1..mappedColor.size]
DO
tail ← (tail.rest ← LIST[Convert.FtoRope[r: mappedColor.vals[i], afterDot: 2]]);
ENDLOOP;
IF ~quit THEN tail ← (tail.rest ← LIST[tokenAfter]); --add the tokenAfter to end
RETURN [mapped: dummyHead.rest, quit: quit];
};
formula: ROPE ← NARROW[data];
IF formula=
NIL
THEN {
SIGNAL ColorizeViewPoint.Warning[$MalformedPaletteEntry, IO.PutFR[format: "No formula found to define mapping \"%g\"", v1: [rope[name]]]];
RETURN [valueIn];
};
mapped ← MapValues[valueIn: valueIn, mapper: ApplyFormula];
};
Substituter: MappingProc ~ {
--substitutes data for valueIn
[valueIn: LIST OF ROPE, name: ROPE, palette: Profiles.Profile, data: REF ← NIL] RETURNS [mapped: LIST OF ROPE]
RETURN [NARROW[data]];
};
NoMapper: MappingProc ~ {
--returns input with no mapping
[valueIn: LIST OF ROPE, name: ROPE, palette: Profiles.Profile, data: REF ← NIL] RETURNS [mapped: LIST OF ROPE]
RETURN [valueIn];
};
NilMapper: MappingProc ~ {
--returns NIL, causing b&w pattern to stay uncolorized
[valueIn: LIST OF ROPE, name: ROPE, palette: Profiles.Profile, data: REF ← NIL] RETURNS [mapped: LIST OF ROPE]
RETURN [NIL];
};
BWMapper: MappingProc ~ {
--map from RGB or CMYK to BW
PROC [valueIn: LIST OF ROPE, name: ROPE, palette: Profiles.Profile, data: REF ← NIL] RETURNS [mapped: LIST OF ROPE]
ApplyBWMap: ApplyMappingProc ~ {
[toMap: Color, tokenAfter: ROPE] RETURNS [mapped: LIST OF ROPE, quit: BOOL ← FALSE]
RGBEq: PROC [r: REAL] RETURNS [BOOL] ~ INLINE {FOR i: INT IN [1..3] DO IF toMap.vals[i]#r THEN RETURN [FALSE]; ENDLOOP; RETURN [TRUE]};
IF Rope.Match[pattern: "Sweep*", object: tokenAfter, case: FALSE] THEN RETURN [mapped: LIST["0.0"], quit: TRUE]; --all sweeps go to white in BW
SELECT toMap.size
FROM
0 --nothing in toMap-- => RETURN [mapped: LIST[tokenAfter]];
1 --SetGray-- => RETURN [mapped: IF toMap.vals[1]>0.8 THEN LIST["1.0", tokenAfter] ELSE LIST["0.0", tokenAfter]]; --dark grays to black, rest to white
3 --rgb-- => RETURN [mapped: LIST[IF RGBEq[0.0--black--] THEN "1.0" ELSE "0.0", tokenAfter]];
ENDCASE --4=cmyk-- => RETURN [mapped: LIST[IF (RGBEq[1.0--black--] OR toMap.vals[4]=1.0) THEN "1.0" ELSE "0.0", tokenAfter]];
};
mapped ← MapValues[valueIn: valueIn, mapper: ApplyBWMap];
};
GreyMapper: MappingProc ~ {
--map from RGB or CMYK to Grey
PROC [valueIn: LIST OF ROPE, name: ROPE, palette: Profiles.Profile, data: REF ← NIL] RETURNS [mapped: LIST OF ROPE]
ApplyGreyMap: ApplyMappingProc ~ {
[toMap: Color, tokenAfter: ROPE] RETURNS [mapped: LIST OF ROPE, quit: BOOL ← FALSE]
SELECT toMap.size
FROM
0 --nothing in toMap-- => RETURN [mapped: LIST[tokenAfter]];
1 --SetGray-- => RETURN [mapped: LIST[Convert.FtoRope[r: MIN[1.0, toMap.vals[1]], afterDot: 2], tokenAfter]];
3 --rgb-- => RETURN [mapped: LIST[Convert.FtoRope[r:--1.0-(.253r+.684g+.063b)-- MAX[0.0, 1.0 - (.253*toMap.vals[1]+.684*toMap.vals[2]+.063*toMap.vals[3])], afterDot: 2], tokenAfter]];
ENDCASE --4=cmyk-- => RETURN [mapped: LIST[Convert.FtoRope[r:--.253c+.684m+.063y+k-- MIN[1.0, (.253*toMap.vals[1]+.684*toMap.vals[2]+.063*toMap.vals[3]+toMap.vals[4])], afterDot: 2], tokenAfter]];
};
mapped ← MapValues[valueIn: valueIn, mapper: ApplyGreyMap];
BrightMapper: MappingProc ~ {
--map from RGB or CMYK to nearest saturated color
PROC [valueIn: LIST OF ROPE, name: ROPE, palette: Profiles.Profile, data: REF ← NIL] RETURNS [mapped: LIST OF ROPE]
ApplyBrightMap: ApplyMappingProc ~ {
[toMap: Color, tokenAfter: ROPE] RETURNS [mapped: LIST OF ROPE, quit: BOOL ← FALSE]
dummyHead: LIST OF ROPE ← LIST[NIL]; --mechanism to add elements to end of a list
tail: LIST OF ROPE ← dummyHead;
IF toMap.size=0 THEN RETURN [mapped: LIST[tokenAfter]];
FOR i:
INT
IN [1..toMap.size]
DO
tail ← (tail.rest ← LIST[Convert.RopeFromInt[ColorizeViewPointBackdoor.AltRound[toMap.vals[i]]]]); --each value will map to 0 or 1 (eg, light green .4 .9 .3 => 0 1 0).
ENDLOOP;
tail ← (tail.rest ← LIST[tokenAfter]); --add the tokenAfter to end
RETURN [mapped: dummyHead.rest];
};
mapped ← MapValues[valueIn: valueIn, mapper: ApplyBrightMap];
Mapping Utilities
ApplyMappingProc: TYPE ~ PROC [toMap: Color, tokenAfter: ROPE] RETURNS [mapped: LIST OF ROPE, quit: BOOL ← FALSE]; --each MappingProc supplies one of these to MapValues. toMap contains "size" numbers found, tokenAfter is the first non-number token found after numbers
MapValues:
PROC [valueIn:
LIST
OF
ROPE, mapper: ApplyMappingProc]
RETURNS [mapped:
LIST
OF
ROPE] ~ {
--by convention, tokens in braces are not mapped; braces removed. Tokens in parens are not mapped; parens retained. (This allows dropShadow info to be contained in parens.)
AddToEnd:
PROC [list:
LIST
OF
ROPE] ~ {
--adds a list of rope to end, one element at a time
FOR each:
LIST
OF
ROPE ← list, each.rest
UNTIL each=
NIL
DO
IF each.first#NIL THEN tail ← (tail.rest ← LIST[each.first]);
ENDLOOP;
};
color: Color ← nullColor;
dummyHead: LIST OF ROPE ← LIST[NIL]; --mechanism to add elements to end of a list
tail: LIST OF ROPE ← dummyHead;
braceCount: INT ← 0;
temp: LIST OF ROPE;
quit: BOOL;
FOR each:
LIST
OF
ROPE ← valueIn, each.rest
UNTIL each=
NIL
DO
token: ROPE ← each.first;
SELECT token.Fetch
FROM
IN ['0..'9] , '. => {
IF braceCount=0
THEN {
IF color.size=4
THEN {
SIGNAL ColorizeViewPoint.Warning[$MalformedPaletteEntry, IO.PutFR[format: "Too many numbers in color definition %g. Ignoring all values beyond fourth number.", v1: [rope[RopeFromList[valueIn]]] ]];
LOOP;
}
ELSE color.size ← color.size+1;
color.vals[color.size] ← Convert.RealFromRope[r: token ! Convert.Error => {
SIGNAL ColorizeViewPoint.Warning[$MalformedPaletteEntry, IO.PutFR[format: "Difficulty converting token [%g]. Grey value set to 0.0.", v1: [rope[token]]]];
color.vals[color.size] ← 0.0;
CONTINUE }
];
}
ELSE AddToEnd[LIST[token]]; --without mapping
};
'{ => braceCount ← braceCount+1; -- discard '{
'( => {braceCount ← braceCount+1; AddToEnd[LIST[token]]}; -- don't discard '(
'} => braceCount ← MAX[0, braceCount-1]; -- discard '}
') => {braceCount ← MAX[0, braceCount-1]; AddToEnd[LIST[token]]}; -- don't discard ')
ENDCASE =>
IF braceCount=0
THEN {
IF color.size=2 THEN GOTO Barf;
[temp, quit] ← mapper[color, token];
AddToEnd[temp];
IF quit THEN RETURN [dummyHead.rest];
color.size ← 0;
}
ELSE AddToEnd[LIST[token]]; --without mapping
ENDLOOP;
IF color.size#2 THEN AddToEnd[mapper[color, NIL].mapped] ELSE GOTO Barf;
mapped ← dummyHead.rest;
EXITS
Barf => {
SIGNAL ColorizeViewPoint.Warning[$MalformedPaletteEntry, IO.PutFR[format: "Only 2 numbers in color definition %g. Being mapped to white.", v1: [rope[RopeFromList[valueIn]]]]];
RETURN [LIST["0.0"]];
};
};
ConvertToRGB:
PROC [toConvert: Color, palette: Profiles.Profile]
RETURNS [rgbVal: RGBColor ← nullRGB] ~ {
BuiltInConvert:
PROC []
RETURNS [rgbVal: RGBColor ← nullRGB] ~ {
if no formula or bad formula in palette, use this
SELECT toConvert.size
FROM
0 --not a color-- => RETURN;
1
--transform gray to RGB-- =>
--.9 SetGray => .1 .1 .1 RGB
rgbVal.vals[3] ← rgbVal.vals[2]← (rgbVal.vals[1] ← 1.0 - toConvert.vals[1]);
3 --no transform-- => rgbVal ← toConvert;
4
--transform CMYK to RGB-- => {
addBlack: REAL ← toConvert.vals[4]; --distr. the black component among CMY
FOR i:
INT
IN [1..3]
DO
rgbVal.vals[i] ← 1.0 - MIN[1.0, toConvert.vals[i]+addBlack]; -- Red=1.0 - [Cyan + Black]
ENDLOOP;
};
ENDCASE => NULL;
rgbVal.size ← 3;
};
formula: ROPE ← Profiles.Line[profile: palette, key: "ConvertToRGBFormula"];
IF formula#
NIL
THEN
rgbVal ← ParseFormula[toMap: toConvert, formula: formula, palette: palette
! BadFormula => {
SIGNAL ColorizeViewPoint.Warning[$MalformedPaletteEntry, IO.PutFR[format: "Can't use ConvertToRGBFormula because: \n\t %g \n Using built-in convert formula instead.", v1: [rope[msg]]]];
rgbVal ← BuiltInConvert[];
};
].mappedColor
ELSE RETURN [BuiltInConvert[]];
};
FindNearest:
PROC [target: RGBColor, snapList:
SnapList, palette: Profiles.Profile]
RETURNS [nearest: RGBColor] ~ {
BuiltInNearest:
PROC []
RETURNS [nearest: RGBColor] ~ {
if no formula or bad formula in palette, use this
FOR each:
LIST
OF RGBColor ← snapList, each.rest
UNTIL each=
NIL
DO
a: REAL ← each.first.vals[1]-target.vals[1]; b: REAL ← each.first.vals[2]-target.vals[2]; c: REAL ← each.first.vals[3]-target.vals[3];
distance: REAL ← a*a + b*b + c*c; --SqRt not nec since only used for comparison
IF distance < minDistance THEN {minDistance ← distance; nearest ← each.first};
ENDLOOP;
};
minDistance: REAL ← Real.LargestNumber;
formula: ROPE ← Profiles.Line[profile: palette, key: "NearestFormula"];
IF formula#
NIL
THEN
FOR each:
LIST
OF RGBColor ← snapList, each.rest
UNTIL each=
NIL
DO
EachVal: GetValueProc ~ {RETURN [v: (SELECT TRUE FROM r.Equal[s2: "e1", case: FALSE] => each.first.vals[1], r.Equal[s2: "e2", case: FALSE] => each.first.vals[2], r.Equal[s2: "e3", case: FALSE] => each.first.vals[3], ENDCASE => Real.LargestNumber)]}; --formula must use tokens "e*" to access each.first
distance:
REAL ← ParseFormula[toMap: target, formula: formula, palette: palette, getValue: EachVal
! BadFormula => {
SIGNAL ColorizeViewPoint.Warning[$MalformedPaletteEntry, IO.PutFR[format: "Can't use ConvertToRGBFormula because: \n\t %g \n Using built-in convert formula instead.", v1: [rope[msg]]]];
GOTO Bad
};
].mappedColor.vals[1]; --formula must store dist in [1]
IF distance < minDistance THEN {minDistance ← distance; nearest ← each.first};
REPEAT
Bad => RETURN [BuiltInNearest[]];
ENDLOOP
ELSE RETURN [BuiltInNearest[]]
};
stackSize: NAT ~ 30;
Stack: TYPE ~ REF StackRep;
StackRep:
TYPE ~
RECORD [
stack: ARRAY [1..stackSize] OF REAL ← ALL[0.0],
top: INT ← 0,
curEntry: ROPE ← NIL, --used in error reporting
levelsExceeded: BOOL ← FALSE --used in error reporting
];
GetValueProc: TYPE ~ PROC [r: ROPE, s: Stack] RETURNS [v: REAL ← Real.LargestNumber, pop: NAT ← 0]; --client of ParseFormula can choose to map any ROPE into a REAL
maxLevels: NAT ← 4; --levels of recursion allowed in subroutines
BadFormula: ERROR [msg: ROPE] ~ CODE; --never seen outside
ParseFormula:
PROC [toMap: Color, formula:
ROPE, palette: Profiles.Profile, getValue: GetValueProc ←
NIL, level:
NAT ← 0, s: Stack ←
NEW[StackRep]]
RETURNS [mappedColor: Color, quit:
BOOL ←
FALSE] ~ {
--formula expected in Rev Polish; spaces separating each element. quit reports that the word "quit" was found in formula; client handles it
ENABLE {
RuntimeError.BoundsFault => ERROR BadFormula[msg: IO.PutFR[format: "Stack or array overflow or underflow at \"%g\" in formula \"%g\"", v1: [rope[s.curEntry]], v2: [rope[formula]]]];
Convert.Error => ERROR BadFormula[msg: IO.PutFR[format: "Problem converting number \"%g\" in formula \"%g\"", v1: [rope[s.curEntry]], v2: [rope[formula]]]];
};
ProcessToken:
PROC []
RETURNS [done:
BOOL ← FALSE] ~ {
--this stuff embedded in proc to catch BoundsFault in DCedar
Push: PROC [r: REAL] ~ INLINE {s.top ← s.top+1; s.stack[s.top] ← r};
Pop: PROC [i: NAT] ~ INLINE {s.top ← s.top-i};
Store: PROC [at: [1..4]] ~ INLINE {mappedColor.vals[at] ← s.stack[s.top]; mappedColor.size ← MAX[mappedColor.size, at]; Pop[1]};
StoreToMap: PROC [at: [1..4]] ~ INLINE {toMap.vals[at] ← s.stack[s.top]; toMap.size ← MAX[toMap.size, at]; Pop[1]};
CheckForSkip: PROC [] RETURNS [BOOL] ~ INLINE {match: BOOL ← s.stack[s.top]=op.toMatch; Pop[1]; RETURN [match=op.matchMeansSkip]};
GetValue:
PROC [] ~ {temp:
REAL; pop:
NAT;
IF getValue=NIL OR ([temp, pop] ← getValue[tok, s]).v=Real.LargestNumber THEN ReportProblem[unrecognized] ELSE {Pop[pop]; Push[temp]}; --pop whatever amt client says, then push the value returned by client--};
IsSwitchToken:
PROC []
RETURNS [found:
BOOL ←
TRUE] ~ {
SELECT
TRUE
FROM
--tokens used for switch stmts
skipToken => {skipToken ← FALSE; RETURN};
tok.Equal[s2: "Switch", case:
FALSE] =>
--Switch statement, ended with ;
IF skipToEndStmt OR skipToEndBranch THEN --just count-- op.stmtCount ← op.stmtCount+1
ELSE {
--begin processing
new: Op ← NEW [OpRep ← [toMatch: s.stack[s.top], matchMeansSkip: FALSE]];
Pop[1]; --pop toMatch
opList ← CONS[new, opList]
};
tok.Equal[s2: "If", case:
FALSE] =>
--If statement, ended with ;
IF skipToEndStmt OR skipToEndBranch THEN --just count-- op.stmtCount ← op.stmtCount+1
ELSE {
--begin processing
new: Op ← NEW [OpRep ← [toMatch: s.stack[s.top]]];
Pop[1]; --pop toMatch
Push[0.0]; --thus, anything#0.0 is true (noMatch=do it)
opList ← CONS[new, opList]};
tok.Equal[s2: "Loop", case:
FALSE] => {
--Loop statement, ended with "EndLoop ;"
IF skipToEndStmt OR skipToEndBranch THEN --just count-- {op.stmtCount ← op.stmtCount+1; RETURN};
IF op#NIL AND op.loopCount=100 THEN ReportProblem[tooManyLoops];
IF op=
NIL
OR op.loopCount=Real.LargestNumber
THEN {
--set up loop first time thru. Loop is reset by EndLoop; loops until loopcount=toMatch, then sets skipToEndStmt where it finds the ; after EndLoop
new: Op ← NEW [OpRep ← [toMatch: s.stack[s.top]+1--+1 needed for correct number of loops before skipping--, parensMatter: FALSE, stmtStart: curIndex, loopCount: 1.0]];
Pop[1]; --pop toMatch
skipToEndStmt ← new.toMatch<=1.0; --first time, don't do loop if loop value <=0
opList ← CONS[new, opList];
};
};
c='; =>
IF op#
NIL
AND (op.stmtCount ← op.stmtCount-1)=0
THEN
--we've reached the end of a statement; pop the statement and reset.
{opList ← opList.rest --pop op--; skipToEndStmt ← skipToEndBranch ← FALSE};
skipToEndStmt => RETURN;
c='( , c='[ => {
--count up. Then, if currently skipping, skip on. Otherwise, compare stack top with op.toMatch. If they match, set skipToEndBranch according to op.matchMeansSkip. Pop stack top.
IF op=NIL OR ~op.parensMatter THEN RETURN; --parens allowed but no meaning
op.parenCount ← op.parenCount+1;
IF ~skipToEndBranch --not skipping-- THEN skipToEndBranch ← CheckForSkip[];
};
c=') , c='] => {
--when parenCount is 0, done skipping or processing that branch. If you were skipping, stop skipping. If you were processing, skip all other branches to the end bracket
IF op=NIL OR ~op.parensMatter THEN RETURN; --parens allowed but no meaning
IF (op.parenCount ← op.parenCount-1)=0
THEN {
IF skipToEndBranch THEN skipToEndBranch ← FALSE ELSE skipToEndStmt ← TRUE};--because the match was found and branch has been processed
};
skipToEndBranch => RETURN;
tok.Equal[s2: "Else", case: FALSE] => IF op#NIL THEN {Push[op.toMatch]; op.matchMeansSkip ← FALSE}; --the Else branch will always match, which is ok since it's skipped over if prev match found already
ENDCASE => found ← FALSE;
};
IsNumber:
PROC []
RETURNS [found:
BOOL ←
TRUE] ~ {
SELECT c
FROM
--numbers
IN ['0..'9], '. => Push[Convert.RealFromRope[tok]];
ENDCASE => found ← FALSE;
};
IsShortTok:
PROC []
RETURNS [found:
BOOL ←
TRUE] ~ {
IF length=1
THEN
SELECT c
FROM
--most common toks
'R, 'r, 'C, 'c => Push[toMap.vals[1]];
'G, 'g, 'M, 'm => Push[toMap.vals[2]];
'B, 'b, 'Y, 'y => Push[toMap.vals[3]];
'K, 'k => Push[toMap.vals[4]];
'I, 'i => IF op#NIL THEN Push[op.loopCount];
'+ => {Pop[1]; s.stack[s.top] ← s.stack[s.top]+s.stack[s.top+1]};
'- => {Pop[1]; s.stack[s.top] ← s.stack[s.top]-s.stack[s.top+1]};
'* => {Pop[1]; s.stack[s.top] ← s.stack[s.top]*s.stack[s.top+1]};
'/ => {Pop[1]; s.stack[s.top] ← s.stack[s.top]/s.stack[s.top+1]};
'< => {Pop[1]; s.stack[s.top] ← IF s.stack[s.top]<s.stack[s.top+1] THEN 1.0 ELSE 0.0};
'> => {Pop[1]; s.stack[s.top] ← IF s.stack[s.top]>s.stack[s.top+1] THEN 1.0 ELSE 0.0};
'= => {Pop[1]; s.stack[s.top] ← IF s.stack[s.top]=s.stack[s.top+1] THEN 1.0 ELSE 0.0};
'# => {Pop[1]; s.stack[s.top] ← IF s.stack[s.top]#s.stack[s.top+1] THEN 1.0 ELSE 0.0};
ENDCASE => ReportProblem[unrecognized]
ELSE found ← FALSE;
};
IsLongTok1:
PROC []
RETURNS [found:
BOOL ←
TRUE] ~ {
SELECT
TRUE
FROM
--less common tokens
tok.Equal[s2: "R2", case: FALSE], tok.Equal[s2: "C2", case: FALSE] => Push[mappedColor.vals[1]];
tok.Equal[s2: "G2", case: FALSE], tok.Equal[s2: "M2", case: FALSE] => Push[mappedColor.vals[2]];
tok.Equal[s2: "B2", case: FALSE], tok.Equal[s2: "Y2", case: FALSE] => Push[mappedColor.vals[3]];
tok.Equal[s2: "K2", case: FALSE] => Push[mappedColor.vals[4]];
tok.Equal[s2: "V1", case: FALSE] => {Pop[1]; Push[toMap.vals[ColorizeViewPointBackdoor.AltRound[s.stack[s.top+1]]]]};
tok.Equal[s2: "V2", case: FALSE] => {Pop[1]; Push[mappedColor.vals[ColorizeViewPointBackdoor.AltRound[s.stack[s.top+1]]]]};
tok.Equal[s2: "Store", case: FALSE] => Store[mappedColor.size+1];
Rope.Match[pattern: "*Formula", object: tok, case:
FALSE]
--SubRout-- =>
{
newFormula: ROPE ← Profiles.Line[profile: palette, key: tok];
IF newFormula#NIL THEN [mappedColor, quit] ← ParseFormula[toMap, newFormula, palette, getValue, level+1, s]
ELSE ReportProblem[unrecognized];
IF s.levelsExceeded THEN ReportProblem[tooDeep];
};
tok.Equal[s2: "ConvertToRGB", case: FALSE] --oft used formula-- => mappedColor ← ConvertToRGB[toConvert: toMap, palette: palette];
tok.Equal[s2: "End", case: FALSE] => done ← TRUE; --normal end
tok.Equal[s2: "EndLoop", case:
FALSE] =>
IF op#
NIL
AND op.loopCount#Real.LargestNumber
THEN {
Push[(op.loopCount ← op.loopCount+1)];
skipToEndStmt ← CheckForSkip[];
IF ~skipToEndStmt THEN {IO.SetIndex[self: in, index: op.stmtStart]; resetLoop ← TRUE};--resets to beginning of loop
};
ENDCASE => found ← FALSE;
};
IsLongTok2:
PROC []
RETURNS [found:
BOOL ←
TRUE] ~ {
SELECT
TRUE
FROM
--less common tokens
tok.Equal[s2: "Quit", case: FALSE] => {quit ← TRUE; done ← TRUE}; --eg, BWMapper quits all future parsing once a "Sweep" tag is found
tok.Equal[s2: "Nil", case: FALSE] => RETURN;
tok.Equal[s2: "Error", case: FALSE] => ReportProblem[errorBranch];
tok.Equal[s2: "Round", case: FALSE] => s.stack[s.top] ← ColorizeViewPointBackdoor.AltRound[s.stack[s.top]];
tok.Equal[s2: "Size", case: FALSE] => Push[toMap.size];
tok.Equal[s2: "Size2", case: FALSE] => Push[mappedColor.size];
tok.Equal[s2: "Random", case: FALSE] => Push[Random.ChooseInt[min: 0, max:100]/100.0]; --returns random real btwn 0.00 & 1.00
tok.Equal[s2: "<=", case: FALSE] => {Pop[1]; s.stack[s.top] ← IF s.stack[s.top]<=s.stack[s.top+1] THEN 1.0 ELSE 0.0};
tok.Equal[s2: ">=", case: FALSE] => {Pop[1]; s.stack[s.top] ← IF s.stack[s.top]>=s.stack[s.top+1] THEN 1.0 ELSE 0.0};
tok.Equal[s2: "Min", case: FALSE] => {Pop[1]; s.stack[s.top] ← MIN[s.stack[s.top], s.stack[s.top+1]]};
tok.Equal[s2: "Max", case: FALSE] => {Pop[1]; s.stack[s.top] ← MAX[s.stack[s.top], s.stack[s.top+1]]};
ENDCASE => found ← FALSE;
};
IsLongTok3:
PROC []
RETURNS [found:
BOOL ←
TRUE] ~ {
--most uncommon toks
SELECT
TRUE
FROM
--less common tokens
tok.Equal[s2: "SqRt", case: FALSE] => s.stack[s.top] ← RealFns.SqRt[s.stack[s.top]];
tok.Equal[s2: "Ln", case: FALSE] => s.stack[s.top] ← RealFns.Ln[s.stack[s.top]];
tok.Equal[s2: "Sin", case: FALSE] => s.stack[s.top] ← RealFns.Sin[s.stack[s.top]];
tok.Equal[s2: "Cos", case: FALSE] => s.stack[s.top] ← RealFns.Cos[s.stack[s.top]];
tok.Equal[s2: "Tan", case: FALSE] => s.stack[s.top] ← RealFns.Tan[s.stack[s.top]];
tok.Equal[s2: "Not", case: FALSE] => {s.stack[s.top] ← IF s.stack[s.top]=0.0 THEN 1.0 ELSE 0.0};
tok.Equal[s2: "Dup", case: FALSE] => Push[s.stack[s.top]];
tok.Equal[s2: "Exch", case: FALSE] => {tmp: REAL ← s.stack[s.top]; s.stack[s.top] ← s.stack[s.top-1]; s.stack[s.top-1] ← tmp};
tok.Equal[s2: "C1ToC2", case: FALSE] => {FOR i: INT IN [1..toMap.size] DO mappedColor.vals[i] ← toMap.vals[i] ENDLOOP; mappedColor.size ← toMap.size; toMap ← nullColor};
tok.Equal[s2: "C1ToTemp", case: FALSE] => {FOR i: INT IN [1..toMap.size] DO tempColor.vals[i] ← toMap.vals[i] ENDLOOP; tempColor.size ← toMap.size; toMap ← nullColor};
tok.Equal[s2: "C2ToC1", case: FALSE] => {FOR i: INT IN [1..mappedColor.size] DO toMap.vals[i] ← mappedColor.vals[i] ENDLOOP; toMap.size ← mappedColor.size; mappedColor ← nullColor};
tok.Equal[s2: "C2ToTemp", case: FALSE] => {FOR i: INT IN [1..mappedColor.size] DO tempColor.vals[i] ← mappedColor.vals[i] ENDLOOP; tempColor.size ← mappedColor.size; mappedColor ← nullColor};
tok.Equal[s2: "C1ExchC2", case:
FALSE] => {temp: Color;
FOR i: INT IN [1..mappedColor.size] DO temp.vals[i] ← mappedColor.vals[i] ENDLOOP; temp.size ← mappedColor.size;
FOR i: INT IN [1..toMap.size] DO mappedColor.vals[i] ← toMap.vals[i] ENDLOOP; mappedColor.size ← toMap.size;
FOR i: INT IN [1..temp.size] DO toMap.vals[i] ← temp.vals[i] ENDLOOP; toMap.size ← temp.size;
};
tok.Equal[s2: "ZeroC1", case: FALSE] => toMap ← nullColor;
tok.Equal[s2: "ZeroC2", case: FALSE] => mappedColor ← nullColor;
ENDCASE => found ← FALSE;
};
ErrType: TYPE ~ {unrecognized, tooDeep, tooManyLoops, errorBranch};
ReportProblem:
PROC [errType: ErrType] ~ {
SELECT errType
FROM
unrecognized => ERROR BadFormula[msg: IO.PutFR[format: "Unrecognized object \"%g\" in formula \"%g\"", v1: [rope[tok]], v2: [rope[formula]]]];
tooDeep => ERROR BadFormula[msg: IO.PutFR[format: "Recursion beyond %g levels at \"%g\" in formula \"%g\"", v1: [cardinal[maxLevels]], v2: [rope[tok]], v3: [rope[formula]]]];
tooManyLoops => ERROR BadFormula[msg: IO.PutFR[format: "Looped more than 100 times; probably no EndLoop in formula \"%g\"", v1: [rope[formula]]]];
errorBranch => ERROR BadFormula[msg: IO.PutFR[format: "Error branch reached in formula \"%g\"", v1: [rope[formula]]]];
ENDCASE => ERROR; --system error!
};
length: INT ← tok.Length;
c: CHAR ← Rope.Fetch[tok];
op: Op ← IF opList#NIL THEN opList.first ELSE NIL;
SELECT
TRUE
FROM
IsSwitchToken[], IsNumber[], IsShortTok[], IsLongTok1[], IsLongTok2[], IsLongTok3[] => NULL;
ENDCASE => GetValue[];
};
GetTokens:
PROC [] ~ {
TokenBreak:
IO.BreakProc = {
[char: CHAR] RETURNS [IO.CharClass]
RETURN [SELECT char FROM IN [IO.NUL..IO.SP], '\t => sepr, '*, '+, '/, '-, '{, '}, '(, '), '[, '], '#, '; => break, ENDCASE => other]};
IF tok=
NIL
--first time--
OR resetLoop
OR skipToken
THEN {
curIndex ← IO.GetIndex[in];
tok ← IO.GetTokenRope[stream: in, breakProc: TokenBreak ! IO.EndOfStream => {tok ← NIL; GOTO End}].token;
nextIndex ← IO.GetIndex[in];
nextTok ← IO.GetTokenRope[stream: in, breakProc: TokenBreak ! IO.EndOfStream => {nextTok ← NIL; GOTO End}].token;
resetLoop ← skipToken ← FALSE;
}
ELSE {
tok ← nextTok; curIndex ← nextIndex;
nextIndex ← IO.GetIndex[in];
nextTok ← IO.GetTokenRope[stream: in, breakProc: TokenBreak ! IO.EndOfStream => {nextTok ← NIL; GOTO End}].token;
};
EXITS End => RETURN;
};
Op: TYPE ~ REF OpRep;
OpRep: TYPE ~ RECORD [parenCount: INT ← 0, stmtCount: INT ← 1, toMatch, loopCount: REAL ← Real.LargestNumber, matchMeansSkip, parensMatter: BOOL ← TRUE, stmtStart: INT ← 0--set by each stmt--];
opList: LIST OF Op ← NIL; --keeps "stack" of current switching operator status
tempColor: Color;
skipToken, resetLoop, skipToEndStmt, skipToEndBranch: BOOL ← FALSE;
in: IO.STREAM ← IO.RIS[formula];
tok, nextTok: ROPE ← NIL;
curIndex, nextIndex: INT ← 0;
IF level>maxLevels THEN {s.levelsExceeded ← TRUE; RETURN};
DO
GetTokens[];
IF (s.curEntry ← tok)=NIL OR ProcessToken[]--returns done if forced done-- THEN EXIT;
ENDLOOP;
};
RopeFromList: PROC [list: LIST OF ROPE] RETURNS [rope: ROPE ← NIL] ~ {FOR each: LIST OF ROPE ← list, each.rest UNTIL each=NIL DO rope ← rope.Cat[" ", each.first]; ENDLOOP};
JoinLists:
PROC [start:
LIST
OF
ROPE, addAtEnd:
LIST
OF
ROPE]
RETURNS [joined:
LIST
OF
ROPE] ~ {
RETURN [
IF start=
NIL
THEN addAtEnd
ELSE
CONS[start.first, JoinLists[start.rest, addAtEnd]]]};