MimosaDebug.mesa
Copyright Ó 1985, 1986, 1987, 1988, 1991 by Xerox Corporation. All rights reserved.
Satterthwaite, June 18, 1986 12:26:15 pm PDT
Russ Atkinson (RRA) October 31, 1988 9:37:35 pm PST
JKF August 8, 1988 4:45:55 pm PDT
DIRECTORY
Alloc USING [AddNotify, Base, DropNotify, Handle, Index, Notifier, Top],
MobDefs USING [Link, VersionStamp],
CompilerUtil USING [AcquireStream, ReleaseStream],
ConvertUnsafe USING [SubString, ToRope],
IO USING [Put1, PutChar, PutF1, PutRope, STREAM],
Literals USING [Base, LitDescriptor, LitClass, LTIndex, ltType],
LiteralOps USING [DescriptorValue, IsShort, MasterString, StringValue, Value, ValueBits, ValueInt, ValueReal],
MessageTab USING [ModePrintName, NodePrintName, TypePrintName],
MimData USING [wordAlignment],
Rope USING [ROPE],
Symbols USING [Base, BitAddress, bodyType, BTIndex, CSEIndex, CTXFirst, CTXIndex, CTXNull, CTXRecord, ctxType, ISEIndex, lG, lZ, mdType, Name, nullName, RootBti, SEIndex, SENull, SERecord, seType, TransferMode, TypeClass, typeTYPE],
SymbolOps USING [CtxLevel, DecodeBitAddr, DecodeCard, DecodeInt, DecodeLink, DecodeType, EnumerateBodies, FindExtension, NameForSe, NextSe, own, SubStringForName, TypeLink, XferMode],
Table USING [IndexRep],
Target: TYPE MachineParms USING [bitOrder, bitsPerByte, bitsPerLongWord],
Tree USING [Base, Index, Link, NodeName, Scan, nullIndex, nullInfo, treeType],
TreeOps USING [GetNode, GetTag, ScanSons];
MimosaDebug: PROGRAM
IMPORTS Alloc, CompilerUtil, ConvertUnsafe, IO, LiteralOps, MessageTab, MimData, SymbolOps, TreeOps
EXPORTS CompilerUtil = {
OPEN Symbols;
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;
tb: Tree.Base;
seb: Symbols.Base;
ctxb: Symbols.Base;
mdb: Symbols.Base;
bb: Symbols.Base;
ltb: Literals.Base;
definitionsOnly: BOOL ¬ FALSE;
DebugNotify: Alloc.Notifier = {
tb ¬ base[Tree.treeType];
seb ¬ base[seType];
ctxb ¬ base[ctxType];
mdb ¬ base[mdType];
bb ¬ base[bodyType];
ltb ¬ base[Literals.ltType];
};
SubString: TYPE = ConvertUnsafe.SubString;
bytesPerLongWord: NAT = Target.bitsPerLongWord/Target.bitsPerByte;
basic io
errorStream: STREAM ¬ NIL;
oldStream: STREAM ¬ NIL;
logStream: STREAM ¬ NIL;
WriteBool: PROC [prefix: ROPE, b: BOOL] = {
IF prefix # NIL THEN IO.PutRope[errorStream, prefix];
IO.PutRope[errorStream, IF b THEN "TRUE" ELSE "FALSE"];
};
WriteChar: PROC [c: CHAR] = INLINE {
IO.PutChar[errorStream, c];
};
WriteRope: PROC [r: ROPE] = INLINE {
IO.PutRope[errorStream, r];
};
WriteRopeInt: PROC [r: ROPE, n: INT] = INLINE {
IO.PutF1[errorStream, r, [integer[n]] ];
};
WriteRopeCard: PROC [r: ROPE, c: CARD] = INLINE {
IO.PutF1[errorStream, r, [cardinal[c]] ];
};
WriteInt: PROC [n: INT] = INLINE {
IO.Put1[errorStream, [integer[n]]];
};
NewLine: PROC [n: NAT ¬ 1] = INLINE {
IF n > 2 THEN THROUGH [2..n) DO WriteChar['\n]; ENDLOOP;
IF n > 1 THEN WriteChar['\n];
WriteChar['\n];
};
Indent: PROC [n: CARDINAL] = {
NewLine[];
THROUGH [1..n/8] DO WriteRope[" "] ENDLOOP;
THROUGH [1..n MOD 8] DO WriteChar[' ] ENDLOOP;
};
errorStream is set by Enter
Enter: PROC [table: Alloc.Handle, st: STREAM ¬ NIL] = {
table.AddNotify[DebugNotify];
IF st # NIL
THEN {
oldStream ¬ errorStream;
errorStream ¬ st;
logStream ¬ NIL;
}
ELSE {
oldStream ¬ NIL;
logStream ¬ errorStream ¬ CompilerUtil.AcquireStream[log];
};
definitionsOnly ¬ FALSE;
};
Exit: PROC [table: Alloc.Handle] = {
definitionsOnly ¬ FALSE;
IF logStream # NIL THEN CompilerUtil.ReleaseStream[log];
errorStream ¬ oldStream;
oldStream ¬ NIL;
logStream ¬ NIL;
table.DropNotify[DebugNotify];
};
tree printing
PrintLiteral: PROC [t: Tree.Link.literal] = {
lti: Literals.LTIndex = t.index;
IF LiteralOps.IsShort[lti]
THEN {
class: Literals.LitClass ¬ LiteralOps.Value[lti].class;
SELECT class FROM
signed, either => WriteInt[LiteralOps.ValueInt[lti]];
real => IO.PutF1[errorStream, "%g", [real[LiteralOps.ValueReal[lti]]]];
ENDCASE => IO.PutF1[errorStream, "%bB", [cardinal[LiteralOps.ValueBits[lti]]]];
}
ELSE {
desc: Literals.LitDescriptor = LiteralOps.DescriptorValue[t.index];
WriteChar['[];
FOR i: CARDINAL IN [0 .. desc.words) DO
IF i # 0 THEN WriteChar[',];
IO.PutF1[errorStream, "%b", [cardinal[SymbolOps.DecodeCard[ltb[desc.offset][i]]]]];
ENDLOOP;
WriteChar[']];
};
};
PrintString: PROC [t: Tree.Link.string] = {
s: LONG STRING = LiteralOps.StringValue[t.index];
WriteChar['"];
FOR i: CARDINAL IN [0..s.length) DO WriteChar[s[i]] ENDLOOP;
WriteChar['"];
IF t.index # LiteralOps.MasterString[t.index] THEN WriteChar['L];
};
WriteNodeName: PROC [n: Tree.NodeName] = INLINE {
IO.PutRope[errorStream, MessageTab.NodePrintName[n]];
};
PrintSubTree: PROC [t: Tree.Link, nBlanks: CARDINAL] = {
OPEN Tree;
Printer: Tree.Scan = {
Indent[nBlanks];
WITH s: t SELECT TreeOps.GetTag[t] FROM
hash => PrintName[NIL, s.index];
symbol => {PrintSei[NIL, s.index]; PrintIndex["[", s.index]; WriteChar[']]};
literal => PrintLiteral[s];
string => PrintString[s];
subtree => {
node: Tree.Index = s.index;
IF node = Tree.nullIndex
THEN WriteRope["<empty>"]
ELSE {
OPEN tb[node];
WriteNodeName[name];
PrintIndex["[", node]; WriteRope["] "];
IF info # Tree.nullInfo THEN PrintIndex[" info=", LOOPHOLE[info]];
IF attr1 OR attr2 OR attr3 THEN {
IF info = Tree.nullInfo THEN WriteChar[' ];
WriteChar['(];
IF attr1 THEN WriteChar['1];
IF attr2 THEN WriteChar['2];
IF attr3 THEN WriteChar['3];
WriteChar[')];
};
IF tb[node].subInfo # 0 THEN
WriteRopeInt[" <<%g>>", tb[node].subInfo];
nBlanks ¬ nBlanks + 2;
IF name # thread
THEN TreeOps.ScanSons[s, Printer]
ELSE {
PrintIndex[" link=", TreeOps.GetNode[son[2]]];
Printer[son[1]];
};
nBlanks ¬ nBlanks - 2;
};
};
ENDCASE;
};
Printer[t];
};
PrintTree: PUBLIC PROC [table: Alloc.Handle, root: Tree.Link] = {
Enter[table];
PrintSubTree[root, 0];
NewLine[2];
Exit[table];
};
PrintBodies: PUBLIC PROC [table: Alloc.Handle] = {
Enter[table];
[] ¬ SymbolOps.EnumerateBodies[SymbolOps.own, RootBti, PrintBody];
NewLine[];
Exit[table];
};
PrintBody: PROC [bti: BTIndex] RETURNS [BOOL] = {
OPEN body: bb[bti];
WriteRope["Body: "];
WITH b: body SELECT FROM
Callable => {
PrintSei[NIL, b.id];
IF b.inline
THEN WriteRope[" [inline]"]
ELSE {
SELECT b.kind FROM
Outer => WriteRope[" (Outer)"];
Inner => WriteRope[" (Inner)"];
Catch => WriteRope[" (Catch)"];
ENDCASE => WriteRope[" (Other)"];
WriteRopeInt[", ep: %g", b.entryIndex];
WriteRopeInt[", frame addr: %g", b.frameOffset];
};
WriteRope[", attrs: "];
WriteChar[IF ~b.noXfers THEN 'x ELSE '-];
WriteChar[IF b.hints.safe THEN 's ELSE '-];
WriteChar[IF b.hints.nameSafe THEN 'n ELSE '-];
IF ~b.hints.noStrings THEN {Indent[2]; WriteRope["string literals"]};
};
ENDCASE => WriteRope["(anon)"];
Indent[2];
PrintIndex["context: ", body.localCtx];
WriteRopeInt[", level: %g", body.level];
WITH body.info SELECT FROM
Internal => {
WriteRopeInt[", frame size: %g", frameSize];
IF body.kind = Callable
THEN PrintSubTree[[subtree[index: bodyTree]], 0]
ELSE PrintIndex[", tree root: ", bodyTree];
};
ENDCASE;
NewLine[2];
RETURN [FALSE];
};
PrintSymbols: PUBLIC PROC [table: Alloc.Handle, definitions: BOOL] = {
ctx: CTXIndex;
limit: CTXIndex;
Enter[table];
definitionsOnly ¬ definitions; -- must be after Enter
limit ¬ table.Top[Symbols.ctxType];
ctx ¬ CTXFirst + CTXRecord.nil.SIZE;
UNTIL ctx = limit DO
PrintContext[ctx];
NewLine[2];
ctx ¬ ctx + (WITH ctxb[ctx] SELECT FROM
included => CTXRecord.included.SIZE,
imported => CTXRecord.imported.SIZE,
ENDCASE => CTXRecord.simple.SIZE);
ENDLOOP;
NewLine[];
Exit[table];
};
PrintContext: PROC [ctx: CTXIndex] = {
sei, root: ISEIndex;
PrintIndex["Context: ", ctx];
IF SymbolOps.CtxLevel[SymbolOps.own, ctx] # lZ THEN
WriteRopeInt[", level: %g", SymbolOps.CtxLevel[SymbolOps.own, ctx]];
WITH c: ctxb[ctx] SELECT FROM
included => {
PrintName[", copied from: ", mdb[c.module].moduleId];
PrintName[" [", mdb[c.module].fileId];
PrintVersion[", ", mdb[c.module].stamp];
PrintIndex["], context: ", c.map];
};
imported =>
PrintName[", imported from: ", mdb[ctxb[c.includeLink].module].moduleId];
ENDCASE;
root ¬ sei ¬ ctxb[ctx].seList;
DO
IF sei = SENull THEN EXIT;
PrintSE[sei, 2];
IF (sei ¬ SymbolOps.NextSe[SymbolOps.own, sei]) = root THEN EXIT;
ENDLOOP
};
PrintSE: PROC [sei: ISEIndex, nBlanks: CARDINAL] = {
sep: LONG POINTER TO SERecord.id = @seb[sei];
typeSei: SEIndex;
Indent[nBlanks];
PrintSei[NIL, sei];
PrintIndex[" [", sei]; WriteChar[']];
IF sep.public THEN WriteRope[" [public]"];
IF sep.mark3 THEN {
IF sep.idType = typeTYPE
THEN {
typeSei ¬ SymbolOps.DecodeType[sep.idInfo];
PrintType[", type = TYPE, equated to: ", typeSei];
IF SymbolOps.CtxLevel[SymbolOps.own, sep.idCtx] = lZ
AND SymbolOps.TypeLink[SymbolOps.own, sei] # SENull THEN
WriteRopeInt[", tag code: %g", SymbolOps.DecodeInt[sep.idValue]];
}
ELSE {
typeSei ¬ sep.idType;
PrintType[", type = ", typeSei];
SELECT TRUE FROM
sep.constant => WriteRope[" [const]"];
sep.immutable => WriteRope[" [init only]"];
ENDCASE;
IF ~sep.mark4
THEN
WriteRopeInt[", # refs: %g", SymbolOps.DecodeInt[sep.idInfo]]
ELSE
SELECT TRUE FROM
sep.constant =>
IF NOT sep.extended AND seb[typeSei].mark3 THEN {
WriteRope[", value: "];
SELECT SymbolOps.XferMode[SymbolOps.own, typeSei] FROM
proc, program, signal, error =>
PrintLink[SymbolOps.DecodeLink[sep.idValue]];
ENDCASE => {
val: CARD = SymbolOps.DecodeCard[sep.idValue];
IF val < 1000
THEN WriteInt[val]
ELSE IO.PutF1[errorStream, "%bB", [cardinal[val]]];
};
};
(definitionsOnly AND SymbolOps.CtxLevel[SymbolOps.own, sep.idCtx] = lG) =>
WriteRopeInt[", index: %g", SymbolOps.DecodeInt[sep.idValue]];
sep.linkSpace => {
WriteRope[", "];
PrintLink[SymbolOps.DecodeLink[sep.idValue]];
};
ENDCASE => {
addr: BitAddress = SymbolOps.DecodeBitAddr[sep.idValue];
WriteRopeInt[", address: %g[", addr];
WriteRopeInt["%g]", SymbolOps.DecodeInt[sep.idInfo]];
};
};
PrintTypeInfo[typeSei, nBlanks+2];
IF sep.extended THEN
PrintSubTree[SymbolOps.FindExtension[SymbolOps.own, sei].tree, nBlanks+4];
};
};
PrintName: PROC [prefix: ROPE, name: Name] = {
IF prefix # NIL THEN WriteRope[prefix];
IF name = nullName
THEN WriteRope["(anon)"]
ELSE {
s: SubString ¬ SymbolOps.SubStringForName[SymbolOps.own, name];
FOR i: CARDINAL IN [s.offset..s.offset+s.length) DO
WriteChar[s.base[i]];
ENDLOOP;
};
};
PrintSei: PROC [prefix: ROPE, sei: ISEIndex] = {
PrintName[prefix, SymbolOps.NameForSe[SymbolOps.own, sei]];
};
WriteTypeName: PROC [n: TypeClass] = INLINE {
IO.PutRope[errorStream, MessageTab.TypePrintName[n]];
};
WriteModeName: PROC [n: TransferMode] = INLINE {
IO.PutRope[errorStream, MessageTab.ModePrintName[n]];
};
PrintType: PROC [prefix: STRING, sei: SEIndex] = {
tSei: SEIndex;
IF prefix # NIL THEN WriteRope[ConvertUnsafe.ToRope[prefix]];
IF sei = SENull
THEN WriteChar['?]
ELSE
WITH t: seb[sei] SELECT FROM
cons =>
WITH t SELECT FROM
transfer => WriteModeName[mode];
ENDCASE => WriteTypeName[t.typeTag];
id =>
FOR tSei ¬ sei, SymbolOps.TypeLink[SymbolOps.own, tSei] UNTIL tSei = SENull DO
WITH seb[tSei] SELECT FROM
id => {
IF sei # tSei THEN WriteChar[' ];
PrintSei[NIL, LOOPHOLE[tSei, ISEIndex]];
IF ~mark3 OR SymbolOps.CtxLevel[SymbolOps.own, idCtx] # lZ THEN EXIT;
};
ENDCASE;
ENDLOOP;
ENDCASE;
PrintIndex[" [", sei];
WriteChar[']];
};
PrintTypeInfo: PROC [sei: SEIndex, nBlanks: CARDINAL] = {
IF sei # SENull THEN {
sep: LONG POINTER TO Symbols.SERecord = @seb[sei];
WITH s: sep­ SELECT FROM
id => {
PrintSE[LOOPHOLE[sei, ISEIndex], nBlanks];
};
cons => {
option: BOOL ¬ FALSE;
PutOption: PROC [name: ROPE] = {
IF option THEN WriteRope[", "] ELSE {WriteRope[" ("]; option ¬ TRUE};
WriteRope[name];
};
EndOption: PROC = {
IF option THEN {WriteRope[")"]; option ¬ FALSE};
};
Indent[nBlanks];
PrintIndex["[", sei]; WriteRope["] "];
WITH s SELECT FROM
transfer => WriteModeName[mode];
ENDCASE => WriteTypeName[s.typeTag];
WITH t: s SELECT FROM
basic => {
WriteRopeInt[" (code: %g", t.code];
WriteRopeInt[", bits: %g", t.length];
IF t.ordered THEN WriteBool[", ordered: ", t.ordered];
WriteChar[')];
};
enumerated => {
IF t.machineDep THEN PutOption["md"];
IF ~t.painted THEN PutOption["~painted"];
IF t.sparse THEN PutOption["sparse"];
IF t.ordered THEN PutOption["ordered"];
IF t.empty THEN PutOption["empty"];
EndOption[];
PrintIndex[", value ctx: ", t.valueCtx];
};
record => {
IF t.packed THEN PutOption["packed"];
IF t.list THEN PutOption["list"];
IF t.machineDep THEN {
PutOption["md["];
WriteRope[IF t.bitOrder = msBit THEN "msBit" ELSE "lsBit"];
WriteRopeInt[": %g]", t.grain];
};
IF t.monitored THEN PutOption["monitored"];
IF t.hints.variant THEN PutOption["variant"];
IF t.align # MimData.wordAlignment THEN
SELECT t.align FROM
none => PutOption["noAlign"];
oneAU => PutOption["oneAU"];
twoAU => PutOption["twoAU"];
fourAU => PutOption["fourAU"];
eightAU => PutOption["eightAU"];
ENDCASE => PutOption["align?"];
EndOption[];
OutCtx[", field", t.fieldCtx];
WITH ctxb[t.fieldCtx] SELECT FROM
included => IF ~complete THEN WriteRope[" [partial]"];
imported => WriteRope[" [partial]"];
ENDCASE;
WITH t SELECT FROM
linked => PrintType[", link: ", linkType];
ENDCASE;
};
ref => {
IF t.counted THEN PutOption["counted"];
IF t.var THEN PutOption["var"];
IF t.ordered THEN PutOption["ordered"];
IF t.basing THEN PutOption["base"];
IF t.list THEN PutOption["list"];
EndOption[];
PrintType[", to: ", t.refType];
IF t.readOnly THEN WriteRope[" (readonly)"];
PrintTypeInfo[t.refType, nBlanks+2];
};
array => {
IF t.packed THEN PutOption["packed"];
SELECT t.bitOrder FROM
msBit => IF Target.bitOrder # msBit THEN PutOption["msBit"];
lsBit => IF Target.bitOrder # lsBit THEN PutOption["lsBit"];
ENDCASE;
EndOption[];
PrintType[", index type: ", t.indexType];
PrintType[", component type: ", t.componentType];
PrintTypeInfo[t.indexType, nBlanks+2];
PrintTypeInfo[t.componentType, nBlanks+2];
};
arraydesc => {
PrintType[", described type: ", t.describedType];
IF t.readOnly THEN PutOption["readonly"];
SELECT t.bitOrder FROM
msBit => IF Target.bitOrder # msBit THEN PutOption["msBit"];
lsBit => IF Target.bitOrder # lsBit THEN PutOption["lsBit"];
ENDCASE;
EndOption[];
PrintTypeInfo[t.describedType, nBlanks+2];
};
transfer => {
IF t.safe THEN PutOption["safe"];
EndOption[];
OutArgType[", input", t.typeIn];
OutArgType[", output", t.typeOut];
};
definition => {
PrintIndex[", ctx: ", t.defCtx];
WriteRopeInt[", slots: %g", t.slots];
};
union => {
IF t.overlaid THEN PutOption["overlaid"];
IF t.machineDep THEN PutOption["md"];
EndOption[];
IF t.controlled THEN PrintSei[", tag: ", t.tagSei];
PrintType[", tag type: ", seb[t.tagSei].idType];
PrintIndex[", case ctx: ", t.caseCtx];
IF t.controlled THEN PrintSE[t.tagSei, nBlanks+2];
};
sequence => {
IF t.packed THEN PutOption["packed"];
IF t.machineDep THEN PutOption["md"];
EndOption[];
PrintType[", parent: ", t.parentType];
IF t.controlled
THEN PrintSei[", tag: ", t.tagSei]
ELSE PrintType[", index type: ", seb[t.tagSei].idType];
PrintType[", component type: ", t.componentType];
IF t.controlled
THEN PrintSE[t.tagSei, nBlanks+2]
ELSE PrintTypeInfo[seb[t.tagSei].idType, nBlanks+2];
PrintTypeInfo[t.componentType, nBlanks+2];
};
relative => {
PrintType[", base type: ", t.baseType];
PrintType[", offset type: ", t.offsetType];
PrintTypeInfo[t.baseType, nBlanks+2];
PrintTypeInfo[t.offsetType, nBlanks+2];
};
opaque => {
PrintSei[", id: ", t.id];
IF t.lengthKnown THEN WriteRopeInt[", length: %g", t.length];
};
zone => {
IF t.counted THEN PutOption["counted"];
IF t.mds THEN PutOption["mds"];
EndOption[];
};
subrange => {
IF NOT t.filled THEN PutOption["~filled"];
IF t.origin < 0 AND NOT t.biased THEN PutOption["unbiased"];
IF t.empty THEN PutOption["empty"];
EndOption[];
PrintType[" of: ", t.rangeType];
IF t.filled THEN {
WriteRopeInt[" origin: %g", t.origin];
WriteRopeCard[", range: %g", t.range];
};
PrintTypeInfo[t.rangeType, nBlanks+2];
};
real =>
WriteRopeInt[" (bits: %g)", t.length];
signed =>
WriteRopeInt[" (bits: %g)", t.length];
unsigned =>
WriteRopeInt[" (bits: %g)", t.length];
ENDCASE;
};
ENDCASE;
};
};
OutCtx: PROC [prefix: ROPE, ctx: CTXIndex] = {
WriteRope[prefix];
IF ctx = CTXNull THEN WriteRope[" ctx: NIL"] ELSE PrintIndex[" ctx: ", ctx];
};
OutArgType: PROC [prefix: ROPE, sei: CSEIndex] = {
WriteRope[prefix];
IF sei = SENull
THEN WriteRope[": NIL"]
ELSE
WITH t: seb[sei] SELECT FROM
record => OutCtx[NIL, t.fieldCtx];
any => WriteRope[": ANY"];
ENDCASE;
};
PrintIndex: PROC [prefix: ROPE, v: Alloc.Index] = {
tagged: Table.IndexRep ¬ LOOPHOLE[v];
int: INT ¬ LOOPHOLE[tagged];
WriteRope[prefix];
SELECT tagged.tag FROM
0, 255 => {
A small positive or negative number (or index)
};
ENDCASE => {
WriteRopeInt["%g:", tagged.tag];
tagged.tag ¬ 0;
int ¬ LOOPHOLE[tagged];
};
WriteInt[int];
};
PrintLink: PROC [link: MobDefs.Link] = {
SELECT link.tag FROM
var => WriteRope["var["];
proc => WriteRope["proc["];
type => WriteRope["type["];
other => WriteRope["other["];
ENDCASE => ERROR;
WriteRopeInt["mod: %g", link.modIndex];
WriteRopeInt[", offset: %g]", link.offset];
};
<<
PrintVersion: PROC [prefix: ROPE, stamp: MobDefs.VersionStamp] = {
stampWords: CARDINAL = MobDefs.VersionStamp.SIZE;
str: PACKED ARRAY [0..4*stampWords) OF [0..16) = LOOPHOLE[stamp];
digit: STRING = "0123456789abcdef"L;
WriteRope[prefix];
FOR i: NAT IN [0..4*stampWords) DO WriteChar[digit[str[i]]] ENDLOOP;
};>>
PrintVersion: PROC [prefix: Rope.ROPE, stamp: MobDefs.VersionStamp] = {
stampBytes: CARDINAL = BYTES[MobDefs.VersionStamp];
str: PACKED ARRAY [0..2*stampBytes) OF [0..16) = LOOPHOLE[stamp];
digit: STRING = "0123456789abcdef"L;
WriteRope[prefix];
FOR i: NAT IN [0..2*stampBytes) DO WriteChar[digit[str[i]]] ENDLOOP;
};
}.