DIRECTORY
Basics USING [LongMult, BITSHIFT, BITAND, logBitsPerWord, bitsPerWord,
HighHalf, LongNumber, BITOR, BITNOT, BytePair],
ImagerBasic USING [IntPair],
PrincOps USING [DstFunc, SrcFunc, BitAddress, BBTableSpace, BBptr],
PrincOpsUtils USING [AlignedBBTable, BITBLT],
ImagerPixelMaps USING [PixelMap, PixelMapRep, Function, DeviceRectangle,
BoundedWindow],
ImagerPixelMapsExtras USING [SetBit, Set2Bits, Set4Bits, Set8Bits, Set16Bits, ByteSequence];
Pixel operations
replicator: ARRAY [0..4] OF CARDINAL ~ [0FFFFH, 05555H, 01111H, 00101H, 00001H];
FillConstantTrap: PUBLIC PROC [destination: PixelMap, -- trapezoid, constant color
top, bottom, leftTop, leftBot, rightTop, rightBot: NAT,
pxlValue:
CARDINAL, function: Function ← [null, null]] ~
TRUSTED {
lgBitsPerPixel: NAT ~ destination.refRep.lgBitsPerPixel;
bitsPerPixel: NAT ~ Basics.BITSHIFT[1, lgBitsPerPixel];
value: CARDINAL ~ MIN[pxlValue, Basics.BITSHIFT[1, bitsPerPixel] - 1];
rastWds: CARDINAL ~ destination.refRep.rast;
rastBits: CARDINAL ~ rastWds * bitsPerWord;
EdgeBlock: TYPE = RECORD [xPos: LONG CARDINAL, xIncr: LONG INTEGER];
MakeEdge:
UNSAFE
PROC[xTop, xBot, height:
NAT]
RETURNS [edge: EdgeBlock] = {
LOOPHOLE[edge.xPos, Basics.LongNumber].highbits ← xBot;
LOOPHOLE[edge.xPos, Basics.LongNumber].lowbits ← 0;
LOOPHOLE[edge.xIncr, Basics.LongNumber].highbits ← xTop - xBot;
LOOPHOLE[edge.xIncr, Basics.LongNumber].lowbits ← 0;
IF height > 1 THEN edge.xIncr ← edge.xIncr / height;
};
DoScanSeg: UNSAFE PROC[] ~ { PrincOpsUtils.BITBLT[bb]; };
bbspace: PrincOps.BBTableSpace;
bb: PrincOps.BBptr ← PrincOpsUtils.AlignedBBTable[@bbspace];
replicatedPixel: CARDINAL ← Basics.BITAND[value, Basics.BITSHIFT[1, Basics.BITSHIFT[1, lgBitsPerPixel]]-1] * replicator[lgBitsPerPixel]; -- make brick for BitBlt
lEdge: EdgeBlock ← MakeEdge[leftTop, leftBot, top - bottom];
rEdge: EdgeBlock ← MakeEdge[rightTop, rightBot, top - bottom];
bb^ ← [
dst: BitAddr[destination.refRep.pointer + Basics.LongMult[bottom, rastWds],
leftBot, lgBitsPerPixel],
dstBpl: rastBits,
src: [word: @replicatedPixel, bit: 0],
srcDesc: [gray[[yOffset: 0, widthMinusOne: 0, heightMinusOne: 0]]],
height: 1,
width: Basics.BITSHIFT[rightTop - leftTop, lgBitsPerPixel],
flags: [direction: forward, disjoint: TRUE, disjointItems: TRUE, gray: TRUE, srcFunc: function.srcFunc, dstFunc: function.dstFunc]
];
FOR height:
NAT
IN [bottom..top]
DO
bb.dst ← BitAddr[destination.refRep.pointer + Basics.LongMult[height, rastWds],
Basics.HighHalf[lEdge.xPos], lgBitsPerPixel];
bb.width ← Basics.BITSHIFT[ Basics.HighHalf[rEdge.xPos] - Basics.HighHalf[lEdge.xPos] + 1,
lgBitsPerPixel ];
DoScanSeg[];
lEdge.xPos ← lEdge.xPos + lEdge.xIncr;
rEdge.xPos ← rEdge.xPos + rEdge.xIncr;
ENDLOOP;
};
FillSmoothTrap: PUBLIC PROC [destination: PixelMap, -- bilinear interpolated 8-bit color
top, bottom, leftTop, leftBot, rightTop, rightBot: NAT,
leftTopVal, leftBotVal, rightTopVal, rightBotVal:
NAT] ~
TRUSTED {
rastWds: CARDINAL ~ destination.refRep.rast;
rastBits: CARDINAL ~ rastWds * bitsPerWord;
pxlsPerWd: CARDINAL ~ IF destination.refRep.lgBitsPerPixel = 4 THEN 1 ELSE 2;
EdgeBlock: TYPE = RECORD [xPos, value: LONG CARDINAL, xIncr, valIncr: INT];
MakeEdge: UNSAFE PROC[xTop, valTop, xBot, valBot, height: NAT]
RETURNS [edge: EdgeBlock] = {
valBot ← MIN[valBot, 255]; valTop ← MIN[valTop, 255];
LOOPHOLE[edge.xPos, Basics.LongNumber].highbits ← xBot;
LOOPHOLE[edge.xPos, Basics.LongNumber].lowbits ← 0;
LOOPHOLE[edge.xIncr, Basics.LongNumber].highbits ← xTop - xBot;
LOOPHOLE[edge.xIncr, Basics.LongNumber].lowbits ← 0;
LOOPHOLE[edge.value, Basics.LongNumber].highbits ← valBot;
LOOPHOLE[edge.value, Basics.LongNumber].lowbits ← 0;
LOOPHOLE[edge.valIncr, Basics.LongNumber].highbits ← valTop - valBot;
LOOPHOLE[edge.valIncr, Basics.LongNumber].lowbits ← 0;
IF height > 1
THEN { edge.xIncr ← edge.xIncr / height; edge.valIncr ← edge.valIncr / height; };
};
DoScanSeg:
PROC[lx, lVal, rx, rVal:
LONG
CARDINAL] =
TRUSTED {
value, valIncr: INT;
xStart: NAT ← Basics.HighHalf[lx];
xFinish: NAT ← Basics.HighHalf[rx];
length: INTEGER ← xFinish - xStart;
leftByte: BOOLEAN;
valIncr ← INT[rVal] - INT[lVal];
IF ABS[length] >= 1 THEN valIncr ← valIncr / length;
IF xFinish >= xStart
THEN {
value ← lVal;
pxlPtr ← scanPtr + LOOPHOLE[Basics.HighHalf[lx] / pxlsPerWd, CARDINAL];
}
ELSE {
-- twisted or otherwise backwards
xTemp: CARDINAL ← xStart; xStart ← xFinish; xFinish ← xTemp;
value ← rVal;
pxlPtr ← scanPtr + LOOPHOLE[Basics.HighHalf[rx] / pxlsPerWd, CARDINAL];
};
leftByte ← (xStart MOD 2) = 0;
IF pxlsPerWd = 2
THEN FOR i:
NAT
IN [xStart..xFinish]
DO
IF leftByte
THEN pxlPtr^.high ← Basics.HighHalf[value]
ELSE { pxlPtr^.low ← Basics.HighHalf[value]; pxlPtr ← pxlPtr + 1; };
leftByte ← NOT leftByte;
value ← value + valIncr;
ENDLOOP
ELSE
FOR i:
NAT
IN [xStart..xFinish]
DO
pxlPtr^.low ← Basics.HighHalf[value]; pxlPtr ← pxlPtr + 1;
value ← value + valIncr;
ENDLOOP;
};
scanPtr, pxlPtr: LONG POINTER TO Basics.BytePair;
wordsPerLine: NAT ← destination.refRep.rast;
lEdge: EdgeBlock ← MakeEdge[leftTop, leftTopVal, leftBot, leftBotVal, top - bottom];
rEdge: EdgeBlock ← MakeEdge[rightTop, rightTopVal, rightBot, rightBotVal, top - bottom];
IF (destination.refRep.lgBitsPerPixel # 3) AND (destination.refRep.lgBitsPerPixel # 4) THEN ERROR;
TRUSTED { scanPtr ← LOOPHOLE[
destination.refRep.pointer + Basics.LongMult[destination.refRep.rast, bottom],
LONG POINTER TO Basics.BytePair]; };
FOR height:
NAT
IN [bottom..top]
DO
DoScanSeg[lEdge.xPos, lEdge.value, rEdge.xPos, rEdge.value];
lEdge.xPos ← lEdge.xPos + lEdge.xIncr; lEdge.value ← lEdge.value + lEdge.valIncr;
rEdge.xPos ← rEdge.xPos + rEdge.xIncr; rEdge.value ← rEdge.value + rEdge.valIncr;
scanPtr ← scanPtr + wordsPerLine;
ENDLOOP;
};
DrawLine: PUBLIC PROC [destination: PixelMap, p1, p2: IntPair, -- fast line, constant color
pxlValue:
CARDINAL, function: Function ← [null, null]] ~ {
increment, bias, error, sBump, t, shiftDist: INTEGER;
wrdPtr: LONG POINTER TO WORD;
p1s, p1f, p2s, p2f:
INTEGER;
Get necessary constants based on bits per pixel
lgBitsPerPixel: NAT ~ destination.refRep.lgBitsPerPixel;
logPxlsPerWd: NAT ~ Basics.logBitsPerWord - lgBitsPerPixel;
bitsPerPixel: NAT ~ Basics.BITSHIFT[1, lgBitsPerPixel];
maxShift: NAT ~ Basics.bitsPerWord - bitsPerPixel;
maxValue: CARDINAL ~ Basics.BITSHIFT[1, bitsPerPixel] - 1;
value: CARDINAL ~ MIN[pxlValue, maxValue];
p1f ← p1.x; p1s ← p1.y; p2f ← p2.x; p2s ← p2.y;
Make sure of positive-going fast coordinate
IF p1f > p2f
THEN { t ← p1f; p1f ← p2f; p2f ← t; t ← p1s; p1s ← p2s; p2s ← t; };
Get pointer to initial word and bit offset
TRUSTED {
wrdPtr ← destination.refRep.pointer + Basics.LongMult[destination.refRep.rast, p1s]
+ Basics.BITSHIFT[p1f, -logPxlsPerWd];
};
shiftDist ← maxShift - Basics.
BITSHIFT[
Basics.BITAND[ p1f, Basics.BITSHIFT[1, logPxlsPerWd] - 1], -- p1f MOD pixelsPerWord
lgBitsPerPixel
];
IF (p2f - p1f) >
ABS[p2s - p1s]
More horizontal line (moves faster along fast axis)
THEN {
increment ← 2 * (p2s - p1s);
bias ← 2 * (p2f - p1f);
sBump ← SGN[increment] * destination.refRep.rast;
increment ← ABS[increment];
error ← increment - bias/2;
IF lgBitsPerPixel = 3
-- speedup for 8 bits per pixel
THEN
FOR i:
NAT
IN [0..(p2f-p1f)]
DO
TRUSTED {
IF shiftDist = 0
THEN { LOOPHOLE[wrdPtr^, BytePair].low ← value;
wrdPtr ← wrdPtr + 1; shiftDist ← 8; }
ELSE { LOOPHOLE[wrdPtr^, BytePair].high ← value; shiftDist ← 0; };
IF error > 0 THEN TRUSTED { wrdPtr ← wrdPtr + sBump; error ← error - bias; };
error ← error + increment;
};
ENDLOOP
ELSE
FOR i:
NAT
IN [0..(p2f-p1f)]
DO
TRUSTED {
wrdPtr^ ← Basics.
BITOR[
-- deposit pixel bits in word
Basics.BITAND[wrdPtr^, Basics.BITNOT[Basics.BITSHIFT[maxValue, shiftDist]]],
Basics.BITSHIFT[value, shiftDist]
];
};
IF shiftDist = 0 THEN TRUSTED { wrdPtr ← wrdPtr + 1; shiftDist ← maxShift; }
ELSE shiftDist ← shiftDist - bitsPerPixel;
IF error > 0 THEN TRUSTED { wrdPtr ← wrdPtr + sBump; error ← error - bias; };
error ← error + increment;
ENDLOOP;
}
More vertical line (moves faster along slow axis)
ELSE {
j: NAT ← Basics.BITSHIFT[shiftDist, -lgBitsPerPixel];
pixelsPerWd: NAT ~ Basics.BITSHIFT[1, logPxlsPerWd];
mask, values: ARRAY [0..16) OF CARDINAL;
FOR i:
NAT
IN [0..pixelsPerWd)
DO
mask[i] ← Basics.BITNOT[Basics.BITSHIFT[maxValue, Basics.BITSHIFT[i, lgBitsPerPixel]]];
values[i] ← Basics.BITSHIFT[value, Basics.BITSHIFT[i, lgBitsPerPixel]];
ENDLOOP;
increment ← 2 * (p2f - p1f);
bias ← 2 * (p2s - p1s);
sBump ← SGN[bias] * destination.refRep.rast;
bias ← ABS[bias];
error ← increment - bias/2;
IF lgBitsPerPixel = 3
-- speedup for 8 bits per pixel
THEN FOR i:
NAT
IN [0..
ABS[p2s - p1s]]
DO TRUSTED {
IF shiftDist = 0
THEN { LOOPHOLE[wrdPtr^, BytePair].low ← value;
IF error > 0
THEN { wrdPtr ← wrdPtr + 1; shiftDist ← 8; error ← error - bias; };
}
ELSE { LOOPHOLE[wrdPtr^, BytePair].high ← value;
IF error > 0
THEN { shiftDist ← 0; error ← error - bias; };
};
wrdPtr ← wrdPtr + sBump;
error ← error + increment;
};
ENDLOOP
ELSE
FOR i:
NAT
IN [0..
ABS[p2s - p1s]]
DO
TRUSTED {
wrdPtr^ ← Basics.BITOR[ Basics.BITAND[wrdPtr^, mask[j]] , values[j]];
};
TRUSTED { wrdPtr ← wrdPtr + sBump; };
IF error > 0
THEN {
error ← error - bias;
IF j = 0 THEN TRUSTED { wrdPtr ← wrdPtr + 1; j ← pixelsPerWd - 1; }
ELSE j ← j - 1;
};
error ← error + increment;
ENDLOOP;
};
};
DrawBltLine: PUBLIC PROC [destination: PixelMap, -- fast line using BitBLT, constant color
p1, p2: IntPair,
pxlValue:
CARDINAL, function: Function ← [null, null]] ~
TRUSTED {
increment, bias, error, sBump, t: INTEGER;
p1s, p1f, p2s, p2f: INTEGER;
wrdPtr: LONG POINTER;
bbTableSpace: PrincOps.BBTableSpace;
bb: PrincOps.BBptr ← PrincOpsUtils.AlignedBBTable[@bbTableSpace];
Get necessary constants based on bits per pixel
lgBitsPerPixel: NAT ~ destination.refRep.lgBitsPerPixel;
logBitsPerPixel: NAT ~ lgBitsPerPixel;
bitsPerPixel: NAT ~ Basics.BITSHIFT[1, lgBitsPerPixel];
logPxlsPerWd: NAT ~ Basics.logBitsPerWord - logBitsPerPixel;
pxlsPerWordLessOne: NAT ~ Basics.BITSHIFT[1, logPxlsPerWd] - 1;
replicatedPixel:
CARDINAL ← Basics.
BITAND[
value,
Basics.BITSHIFT[1, Basics.BITSHIFT[1, logBitsPerPixel]] - 1 ]
* replicator[logBitsPerPixel]; -- make brick for BitBlt
pxlCnt, leftOverPixels: NAT ← 0; --pixels since last jag in line
value: CARDINAL ~ MIN[pxlValue, Basics.BITSHIFT[1, bitsPerPixel] - 1];
p1f ← p1.x; p1s ← p1.y; p2f ← p2.x; p2s ← p2.y;
Make sure of positive-going fast coordinate
IF p1f > p2f
THEN { t ← p1f; p1f ← p2f; p2f ← t; t ← p1s; p1s ← p2s; p2s ← t; };
Get pointer to initial word and bit offset
wrdPtr ← destination.refRep.pointer + Basics.LongMult[destination.refRep.rast, p1s]
+ Basics.BITSHIFT[p1f, -logPxlsPerWd];
leftOverPixels ← Basics.
BITAND[p1f, pxlsPerWordLessOne];
Set up BITBLT table
bb^ ← [
dst: [word: wrdPtr, bit: Basics.BITSHIFT[leftOverPixels, logBitsPerPixel]],
dstBpl: destination.refRep.rast * Basics.bitsPerWord,
src: [word: @replicatedPixel, bit: 0],
srcDesc: [gray[[yOffset: 0, widthMinusOne: 0, heightMinusOne: 0]]],
height: 1,
width: bitsPerPixel,
flags: [direction: forward, disjoint: TRUE, disjointItems: TRUE, gray: TRUE,
srcFunc: function.srcFunc, dstFunc: function.dstFunc]
];
IF (p2f - p1f) >
ABS[p2s - p1s]
More horizontal line (moves faster along fast axis)
THEN {
increment ← 2 * (p2s - p1s);
bias ← 2 * (p2f - p1f);
sBump ← SGN[increment] * destination.refRep.rast;
increment ← ABS[increment];
error ← increment - bias/2;
FOR i:
NAT
IN [0..(p2f-p1f)]
DO
pxlCnt ← pxlCnt + 1;
IF error > 0
THEN {
-- end of straight segment
bb.dst ← [word: wrdPtr,
bit: Basics.BITSHIFT[leftOverPixels, logBitsPerPixel]
];
bb.width ← Basics.BITSHIFT[pxlCnt, lgBitsPerPixel];
PrincOpsUtils.BITBLT[bb];
pxlCnt ← pxlCnt + leftOverPixels;
wrdPtr ← wrdPtr + sBump + Basics.BITSHIFT[pxlCnt, -logPxlsPerWd];
leftOverPixels ← Basics.BITAND[pxlCnt, pxlsPerWordLessOne];
pxlCnt ← 0;
error ← error - bias
};
error ← error + increment;
ENDLOOP;
bb.dst ← [word: wrdPtr,
-- BLT last segment
bit: Basics.BITSHIFT[leftOverPixels, logBitsPerPixel]
];
bb.width ← Basics.BITSHIFT[pxlCnt, logBitsPerPixel];
PrincOpsUtils.BITBLT[bb];
}
More vertical line (moves faster along slow axis)
ELSE {
lastWrdPtr: LONG POINTER TO CARDINAL ← wrdPtr;
increment ← 2 * (p2f - p1f);
bias ← 2 * (p2s - p1s);
sBump ← SGN[bias] * destination.refRep.rast;
bias ← ABS[bias];
error ← increment - bias/2;
FOR i:
NAT
IN [0..
ABS[p2s - p1s]]
DO
pxlCnt ← pxlCnt + 1;
wrdPtr ← wrdPtr + sBump;
IF error > 0
THEN {
bb.dst ← [ word:
IF sBump < 0
THEN wrdPtr
ELSE lastWrdPtr,
bit: Basics.BITSHIFT[leftOverPixels, logBitsPerPixel]
];
bb.height ← pxlCnt;
PrincOpsUtils.BITBLT[bb];
IF leftOverPixels = pxlsPerWordLessOne
THEN { wrdPtr ← wrdPtr + 1; leftOverPixels ← 0; }
ELSE leftOverPixels ← leftOverPixels + 1;
lastWrdPtr ← wrdPtr;
pxlCnt ← 0;
error ← error - bias;
};
error ← error + increment;
ENDLOOP;
bb.dst ← [word:
IF sBump < 0
THEN wrdPtr
ELSE lastWrdPtr,
-- BLT last segment
bit: Basics.BITSHIFT[leftOverPixels, logBitsPerPixel]
];
bb.height ← pxlCnt;
PrincOpsUtils.BITBLT[bb];
};
};
LoadScanSeg: PUBLIC PROC[destination: PixelMap,
s, f: INTEGER, length: NAT,
segment:
LONG
POINTER, offset:
NAT ← 0] =
TRUSTED{
bbspace: PrincOps.BBTableSpace;
bb: PrincOps.BBptr ← PrincOpsUtils.AlignedBBTable[@bbspace];
pxpWd: NAT ~ Basics.BITSHIFT[1, Basics.logBitsPerWord - destination.refRep.lgBitsPerPixel];
bb^ ← [
dst: [
word:
LOOPHOLE[ destination.refRep.pointer
+ Basics.LongMult[destination.refRep.rast, s] + (f / pxpWd),
LONG POINTER],
bit: Basics.BITSHIFT[f MOD pxpWd, destination.refRep.lgBitsPerPixel]
],
dstBpl: 0,
src: [
word: segment + offset/pxpWd,
bit: Basics.BITSHIFT[offset MOD pxpWd, destination.refRep.lgBitsPerPixel]
],
srcDesc: [srcBpl[0]],
width: Basics.BITSHIFT[length , destination.refRep.lgBitsPerPixel],
height: 1,
flags: [disjoint: TRUE]
];
PrincOpsUtils.BITBLT[bb];
};
StoreScanSeg: PUBLIC PROC [source: PixelMap,
s, f: INTEGER, length: NAT,
segment:
LONG
POINTER, offset:
NAT ← 0] =
TRUSTED{
bbspace: PrincOps.BBTableSpace;
bb: PrincOps.BBptr ← PrincOpsUtils.AlignedBBTable[@bbspace];
pxpWd: NAT ~ Basics.BITSHIFT[1, Basics.logBitsPerWord - source.refRep.lgBitsPerPixel];
bb^ ← [
dst: [
word: segment + offset/pxpWd,
bit: Basics.BITSHIFT[offset MOD pxpWd, source.refRep.lgBitsPerPixel]
],
dstBpl: 0,
src: [
word:
LOOPHOLE[ source.refRep.pointer
+ Basics.LongMult[source.refRep.rast, s] + (f / pxpWd),
LONG POINTER],
bit: Basics.BITSHIFT[f MOD pxpWd, source.refRep.lgBitsPerPixel]
],
srcDesc: [srcBpl[0]],
width: Basics.BITSHIFT[length , source.refRep.lgBitsPerPixel],
height: 1,
flags: [disjoint: TRUE]
];
PrincOpsUtils.BITBLT[bb];
};
MergeMaps:
PUBLIC PROC[dstMap, srcMap: PixelMap, srcWidth, dstOffset:
NAT] ~
TRUSTED {
Merge source pixelMap into destination pixelMap. Take "srcWidth" least significant bits from "srcMap" pixels and load them into "dstMap" pixels offset by "dstOffset" bits.
bbspace: PrincOps.BBTableSpace;
bb: PrincOps.BBptr ← PrincOpsUtils.AlignedBBTable[@bbspace];
size: INT ← Basics.LongMult[dstMap.fSize, dstMap.sSize];
loopCount: NAT ← size / 32000;
remainder: CARDINAL ← size MOD 32000;
dstPixPWd: NAT ← Basics.bitsPerWord / Basics.BITSHIFT[1, dstMap.refRep.lgBitsPerPixel];
srcPixPWd: NAT ← Basics.bitsPerWord / Basics.BITSHIFT[1, srcMap.refRep.lgBitsPerPixel];
FOR i:
NAT
IN [0..loopCount]
DO
dstDelta: INT ← Basics.LongMult[i, 32000] / dstPixPWd;
srcDelta: INT ← Basics.LongMult[i, 32000] / srcPixPWd;
numPixels: CARDINAL ← IF i = loopCount THEN remainder ELSE 32000;
bb^ ← [
height: numPixels,
width: srcWidth,
src: [word: srcMap.refRep.pointer + srcDelta, bit: 0],
dst: [word: dstMap.refRep.pointer + dstDelta, bit: dstOffset],
srcDesc: [srcBpl[Basics.BITSHIFT[1, srcMap.refRep.lgBitsPerPixel]]],
dstBpl: Basics.BITSHIFT[1, dstMap.refRep.lgBitsPerPixel],
flags: [disjoint: TRUE]
];
PrincOpsUtils.BITBLT[bb];
ENDLOOP;
};
SetPixel:
PUBLIC PROC [destination: PixelMap, s, f:
INTEGER, value:
CARDINAL] ~ {
Raises bounds fault if the point is not in the window.
bounds: DeviceRectangle ← ImagerPixelMaps.BoundedWindow[destination];
sCheck: NAT ← bounds.sSize-1-NAT[s-bounds.sMin];
fCheck: NAT ← bounds.fSize-1-NAT[f-bounds.fMin];
SELECT destination.refRep.lgBitsPerPixel
FROM
0 => ImagerPixelMapsExtras.SetBit[destination, s, f, value];
1 => ImagerPixelMapsExtras.Set2Bits[destination, s, f, value];
2 => ImagerPixelMapsExtras.Set4Bits[destination, s, f, value];
3 => ImagerPixelMapsExtras.Set8Bits[destination, s, f, value];
4 => ImagerPixelMapsExtras.Set16Bits[destination, s, f, value];
ENDCASE => ERROR;
};