Array operators
ArrayCreate: 
PUBLIC 
PROC [self: Root, size: 
INT] 
RETURNS [Array] ~ {
IF size<0 THEN ERROR Error[rangecheck];
IF size 
IN ArrayIndex 
THEN {
length: ArrayIndex ~ size;
ref: ArrayRef ~ self.zone.NEW[ArrayRep[length]];
ref.level ← self.level;
FOR i: ArrayIndex IN[0..length) DO ref[i] ← null ENDLOOP;
RETURN[[
val: [executable: FALSE, variant: array[access: unlimited, start: 0, length: length]], 
ref: ref
]];
 
}
 
ELSE ERROR Error[limitcheck];
};
 
ArrayGet: 
PUBLIC 
PROC [array: Array, index: 
INT] 
RETURNS [Any] ~ {
IF index NOT IN [0..array.val.length) THEN ERROR Error[rangecheck];
RETURN [array.ref[array.val.start+index]];
};
 
ArraySave: 
PROC [self: Root, ref: ArrayRef] ~ {
top: Level ~ self.level-1;
copy: ArrayRef ~ self.zone.NEW[ArrayRep[ref.size]];
copy.level ← ref.level;
FOR i: ArrayIndex IN[0..copy.size) DO copy[i] ← ref[i] ENDLOOP;
self.restore[top] ← NEW [RestoreItemRep.array ← [self.restore[top], array[ref, copy]]];
ref.level ← self.level;
};
 
ArrayPut: 
PUBLIC 
PROC [self: Root, array: Array, index: 
INT, x: Any] ~ {
IF index NOT IN[0..array.val.length) THEN ERROR Error[rangecheck];
IF array.ref.level<self.level THEN ArraySave[self, array.ref];
array.ref[array.val.start+index] ← x;
};
 
ArrayGetInterval: 
PUBLIC 
PROC [array: Array, index, count: 
INT] 
RETURNS [Array] ~ {
IF index NOT IN [0..array.val.length] THEN ERROR Error[rangecheck];
IF count NOT IN [0..(array.val.length-index)] THEN ERROR Error[rangecheck];
RETURN[[
val: [executable: array.val.executable, variant: array[
access: array.val.access, start: array.val.start+index, length: count]], 
ref: array.ref
]];
 
};
 
ArrayPutInterval: 
PUBLIC 
PROC [self: Root, array: Array, index: 
INT, interval: Array] ~ {
subarray: Array ~ ArrayGetInterval[array, index, ArrayLength[interval]];
IF subarray.ref.level<self.level THEN ArraySave[self, subarray.ref];
FOR i: 
INT 
IN[0..ArrayLength[subarray]) 
DO
ArrayPut[self, subarray, i, ArrayGet[interval, i]];
ENDLOOP;
 
};
 
ArrayCopy: 
PUBLIC 
PROC [self: Root, array: Array, from: Array] 
RETURNS [Array] ~ {
subarray: Array ~ ArrayGetInterval[array, 0, ArrayLength[from]];
ArrayPutInterval[self, subarray, 0, from];
RETURN [subarray];
};
 
ArrayForAll: 
PUBLIC 
PROC [array: Array, action: 
PROC [Any]] ~ {
FOR index: 
INT 
IN [0..ArrayLength[array]) 
DO
action[ArrayGet[array, index]];
ENDLOOP;
 
};
 
ALoad: 
PUBLIC 
PROC [self: Root, array: Array] ~ {
length: INT ~ ArrayLength[array];
FOR index: 
INT 
IN [0..length) 
DO
PushAny[self, ArrayGet[array, index]];
ENDLOOP;
 
};
 
AStore: 
PUBLIC 
PROC [self: Root, array: Array] ~ {
length: INT ~ ArrayLength[array];
IF Count[self]<length THEN ERROR Error[stackunderflow];
FOR index: 
INT 
DECREASING 
IN [0..length) 
DO
ArrayPut[self, array, index, PopAny[self]];
ENDLOOP;
 
};
 
 
String operators
StringCreate: 
PUBLIC 
PROC [self: Root, size: 
INT] 
RETURNS [String] ~ {
IF size<0 THEN ERROR Error[rangecheck];
IF size 
IN StringIndex 
THEN {
length: StringIndex ~ size;
ref: StringRef ~ self.zone.NEW[StringRep[length]];
ref.level ← self.level;
FOR i: StringIndex IN[0..length) DO ref[i] ← VAL[0] ENDLOOP;
RETURN[[
val: [executable: FALSE, variant: string[access: unlimited, start: 0, length: length]], 
ref: ref
]];
 
}
 
ELSE ERROR Error[limitcheck];
};
 
StringCreateFromText: 
PUBLIC 
PROC [self: Root, text: Text] 
RETURNS [String] ~ {
RETURN [StringFromText[self, StringCreate[self, text.length], text]];
};
 
StringCreateFromString: 
PUBLIC 
PROC [self: Root, string: String] 
RETURNS [String] ~ {
RETURN [StringCopy[self, StringCreate[self, string.val.length], string]];
};
 
StringGet: 
PUBLIC 
PROC [string: String, index: 
INT] 
RETURNS [
CHAR] ~ {
IF index NOT IN [0..string.val.length) THEN ERROR Error[rangecheck];
RETURN [string.ref[string.val.start+index]];
};
 
StringSave: 
PROC [self: Root, ref: StringRef] ~ {
top: Level ~ self.level-1;
copy: StringRef ~ self.zone.NEW[StringRep[ref.size]];
copy.level ← ref.level;
FOR i: StringIndex IN[0..copy.size) DO copy[i] ← ref[i] ENDLOOP;
self.restore[top] ← NEW [RestoreItemRep.string ← [self.restore[top], string[ref, copy]]];
ref.level ← self.level;
};
 
StringPut: 
PUBLIC 
PROC [self: Root, string: String, index: 
INT, x: 
CHAR] ~ {
IF index NOT IN[0..string.val.length) THEN ERROR Error[rangecheck];
IF string.ref.level<self.level THEN StringSave[self, string.ref];
string.ref[string.val.start+index] ← x;
};
 
StringGetInterval: 
PUBLIC 
PROC [string: String, index, count: 
INT] 
RETURNS [String] ~ {
IF index NOT IN [0..string.val.length] THEN ERROR Error[rangecheck];
IF count NOT IN [0..(string.val.length-index)] THEN ERROR Error[rangecheck];
RETURN[[
val: [executable: string.val.executable, variant: string[
access: string.val.access, start: string.val.start+index, length: count]], 
ref: string.ref
]];
 
};
 
StringPutInterval: 
PUBLIC 
PROC [self: Root, string: String, index: 
INT, interval: String] ~ {
target: String ~ StringGetInterval[string, index, StringLength[interval]];
IF target.ref.level<self.level THEN StringSave[self, target.ref];
FOR i: StringIndex 
IN[0..target.val.length) 
DO
target.ref[target.val.start+i] ← interval.ref[interval.val.start+i];
ENDLOOP;
 
};
 
StringCopy: 
PUBLIC 
PROC [self: Root, string: String, from: String] 
RETURNS [String] ~ {
substring: String ~ StringGetInterval[string, 0, StringLength[from]];
StringPutInterval[self, substring, 0, from];
RETURN [substring];
};
 
StringForAll: 
PUBLIC 
PROC [string: String, action: 
PROC [
CHAR]] ~ {
FOR index: 
INT 
IN [0..StringLength[string]) 
DO
action[StringGet[string, index]];
ENDLOOP;
 
};
 
StringEq: 
PUBLIC 
PROC [string1: String, string2: String] 
RETURNS [
BOOL] ~ {
IF StringLength[string1]#StringLength[string2] THEN RETURN [FALSE];
RETURN [StringCompare[string1, string2]=equal];
};
 
StringEqText: 
PUBLIC 
PROC [string: String, text: Text] 
RETURNS [
BOOL] ~ {
length: StringIndex ~ StringLength[string];
IF length#text.length THEN RETURN [FALSE];
FOR index: StringIndex 
IN[0..length) 
DO
char1: CHAR ~ string.ref[string.val.start+index];
char2: CHAR ~ text[index];
IF char1#char2 THEN RETURN [FALSE];
ENDLOOP;
 
RETURN [TRUE];
};
 
StringCompare: 
PUBLIC 
PROC [string1, string2: String] 
RETURNS [Comparison] ~ {
length1: StringIndex ~ string1.val.length;
length2: StringIndex ~ string2.val.length;
FOR index: StringIndex 
IN[0..
MIN[length1, length2]) 
DO
char1: CHAR ~ string1.ref[string1.val.start+index];
char2: CHAR ~ string2.ref[string2.val.start+index];
IF char1=char2 THEN NULL
ELSE RETURN [IF char1<char2 THEN less ELSE greater];
ENDLOOP;
 
IF length1=length1 THEN RETURN [equal]
ELSE RETURN [IF length1<length2 THEN less ELSE greater];
};
 
Search: 
PUBLIC 
PROC [string, seek: String, anchor: 
BOOL] 
RETURNS [found: 
BOOL ← 
FALSE, index: 
INT ← 0] ~ {
stringLength: INT ~ StringLength[string];
seekLength: INT ~ StringLength[seek];
WHILE seekLength<=(stringLength-index) 
DO
FOR i: 
INT 
IN[0..seekLength) 
DO
char1: CHAR ~ StringGet[string, index+i];
char2: CHAR ~ StringGet[seek, i];
IF char1#char2 THEN EXIT;
REPEAT FINISHED => found ← TRUE;
ENDLOOP;
 
IF found OR anchor THEN EXIT ELSE index ← index+1;
ENDLOOP;
 
};
 
 
Dictionary operators
DictCreate: 
PUBLIC 
PROC [self: Root, size: 
INT] 
RETURNS [Dict] ~ {
IF size<0 THEN ERROR Error[rangecheck];
IF size 
IN DictIndex 
THEN {
maxlength: DictIndex ~ size;
mod: DictIndex ~ maxlength+MIN[DictIndex.LAST-maxlength, 1+maxlength/10];
ref: DictRef ~ self.zone.NEW[DictRep[mod]];
ref.level ← self.level;
ref.access ← unlimited;
ref.length ← 0;
ref.maxlength ← maxlength;
FOR i: DictIndex IN[0..mod) DO ref[i] ← nullTuple ENDLOOP;
RETURN[[val: [executable: FALSE, variant: dict[]], ref: ref]];
}
 
ELSE ERROR Error[limitcheck];
};
 
TextHash: 
PROC [text: Text] 
RETURNS [hash: 
CARDINAL ← 0] ~ {
start: NAT ~ 0;
length: NAT ~ text.length;
FOR i: 
NAT 
IN[0..
MIN[16, length]) 
DO
hash ← hash*2+ORD[text[start+i]];
ENDLOOP;
 
};
 
StringHash: 
PROC [string: String] 
RETURNS [hash: 
CARDINAL ← 0] ~ {
text: StringRef ~ string.ref;
start: StringIndex ~ string.val.start;
length: StringIndex ~ string.val.length;
FOR i: 
NAT 
IN[0..
MIN[16, length]) 
DO
hash ← hash*2+ORD[text[start+i]];
ENDLOOP;
 
};
 
Hash: 
PROC [x: Any] 
RETURNS [hash: 
CARDINAL ← 42] ~ {
Munch: PROC [CARD32] RETURNS [CARD16] ~ TRUSTED MACHINE CODE { PrincOps.zXOR };
WITH val: x.val 
SELECT 
FROM
name => 
WITH x.ref 
SELECT 
FROM
ref: NameRef => hash ← ref.hash;
ENDCASE => ERROR Bug;
string => 
WITH x.ref 
SELECT 
FROM
ref: StringRef => hash ← StringHash[[val, ref]];
ENDCASE => ERROR Bug;
integer => hash ← Munch[LOOPHOLE[val.int]];
boolean => hash ← ORD[val.bool];
operator, array, dict, file, font => hash ← Munch[LOOPHOLE[x.ref]];
ENDCASE;
 
};
 
InlineEq: 
PROC [x1, x2: Any] 
RETURNS [
BOOL] ~ 
INLINE {
RETURN [IF x1.val.type=name AND x2.val.type=name THEN x1.ref=x2.ref ELSE Eq[x1, x2]];
};
 
EqText: 
PROC [x1: Any, text: Text] 
RETURNS [
BOOL] ~ {
WITH v1: x1.val 
SELECT 
FROM
name => 
WITH x1.ref 
SELECT 
FROM
r1: NameRef => RETURN [StringEqText[r1.string, text]];
ENDCASE => ERROR Bug;
string => 
WITH x1.ref 
SELECT 
FROM
r1: StringRef => RETURN [StringEqText[[v1, r1], text]];
ENDCASE => ERROR Bug;
ENDCASE;
 
RETURN [FALSE];
};
 
DictFetch: 
PROC [dict: Dict, key: Any] 
RETURNS [found: 
BOOL, val: Any] ~ {
mod: DictIndex ~ dict.ref.mod;
index: DictIndex ← Hash[key] MOD mod;
THROUGH [0..mod) 
DO
SELECT 
TRUE 
FROM
dict.ref[index].key.val.type=null => RETURN [FALSE, null];
InlineEq[dict.ref[index].key, key] => RETURN [TRUE, dict.ref[index].val];
ENDCASE => index ← (index+1) MOD mod;
 
ENDLOOP;
 
ERROR Bug;
};
 
DictFetchText: 
PROC [dict: Dict, text: Text] 
RETURNS [found: 
BOOL, val: Any] ~ {
mod: DictIndex ~ dict.ref.mod;
index: DictIndex ← TextHash[text] MOD mod;
THROUGH [0..mod) 
DO
SELECT 
TRUE 
FROM
dict.ref[index].key.val.type=null => RETURN [FALSE, null];
EqText[dict.ref[index].key, text] => RETURN [TRUE, dict.ref[index].val];
ENDCASE => index ← (index+1) MOD mod;
 
ENDLOOP;
 
ERROR Bug;
};
 
DictGet: 
PUBLIC 
PROC [dict: Dict, key: Any] 
RETURNS [Any] ~ {
found: BOOL; val: Any;
[found, val] ← DictFetch[dict, key];
IF found THEN RETURN [val]
ELSE ERROR Error[undefined];
};
 
DictGetText: 
PUBLIC 
PROC [dict: Dict, text: Text] 
RETURNS [Any] ~ {
found: BOOL; val: Any;
[found, val] ← DictFetchText[dict, text];
IF found THEN RETURN [val]
ELSE ERROR Error[undefined];
};
 
DictSave: 
PROC [self: Root, ref: DictRef] ~ {
top: Level ~ self.level-1;
copy: DictRef ~ self.zone.NEW[DictRep[ref.mod]];
copy.level ← ref.level;
copy.access ← ref.access;
copy.length ← ref.length;
copy.maxlength ← ref.maxlength;
FOR i: DictIndex IN[0..copy.mod) DO copy[i] ← ref[i] ENDLOOP;
self.restore[top] ← NEW [RestoreItemRep.dict ← [self.restore[top], dict[ref, copy]]];
ref.level ← self.level;
};
 
DictPut: 
PUBLIC 
PROC [self: Root, dict: Dict, key: Any, val: Any, name: 
BOOL ← 
FALSE] ~ {
mod: DictIndex ~ dict.ref.mod;
index: DictIndex ← Hash[key] MOD mod;
IF dict.ref.level<self.level THEN DictSave[self, dict.ref];
THROUGH [0..mod) 
DO
SELECT 
TRUE 
FROM
dict.ref[index].key.val.type=null => {
IF NOT dict.ref.length<dict.ref.maxlength THEN ERROR Error[dictfull];
IF Type[key]=string 
AND 
NOT name 
THEN {
string: String ~ StringFromAny[key, readOnly];
name: Name ~ NameFromString[self, string];
key ← AnyFromName[name];
};
 
dict.ref[index] ← [key, val];
dict.ref.length ← dict.ref.length+1;
RETURN;
};
InlineEq[dict.ref[index].key, key] => { dict.ref[index].val ← val; RETURN };
ENDCASE => index ← (index+1) MOD mod;
 
ENDLOOP;
 
ERROR Bug;
};
 
DictForAll: 
PUBLIC 
PROC [dict: Dict, action: 
PROC [key: Any, val: Any]] ~ {
FOR index: DictIndex 
IN [0..dict.ref.mod) 
DO
IF dict.ref[index].key.val.type#null 
THEN {
tuple: Tuple ~ dict.ref[index];
action[tuple.key, tuple.val];
};
 
ENDLOOP;
 
};
 
Known: 
PUBLIC 
PROC [dict: Dict, key: Any] 
RETURNS [
BOOL] ~ {
RETURN [DictFetch[dict, key].found];
};
 
DictCopy: 
PUBLIC 
PROC [self: Root, dict: Dict, from: Dict] 
RETURNS [Dict] ~ {
put: PROC [key: Any, val: Any] ~ { DictPut[self, dict, key, val] };
IF DictLength[dict]#0 THEN ERROR Error[rangecheck];
IF DictMaxLength[dict]<DictLength[from] THEN Error[rangecheck];
DictForAll[from, put];
RETURN [DictSetAccess[self, dict, DictAccess[from]]];
};
 
Begin: 
PUBLIC 
PROC [self: Root, dict: Dict] ~ {
IF NOT self.dcount<self.dsize THEN ERROR Error[dictstackoverflow];
self.dstack[self.dcount] ← dict;
self.dcount ← self.dcount+1;
};
 
End: 
PUBLIC 
PROC [self: Root] ~ {
IF NOT self.dcount>2 THEN ERROR Error[dictstackunderflow];
self.dcount ← self.dcount-1;
};
 
CurrentDict: 
PUBLIC 
PROC [self: Root] 
RETURNS [Dict] ~ {
IF NOT self.dcount>0 THEN ERROR Bug; -- should always be >=2
RETURN [self.dstack[self.dcount-1]];
};
 
CountDictStack: 
PUBLIC 
PROC [self: Root] 
RETURNS [StackIndex] ~ {
RETURN [self.dcount];
};
 
DictStack: 
PUBLIC 
PROC [self: Root, array: Array] 
RETURNS [Array] ~ {
subarray: Array ~ ArrayGetInterval[array, 0, self.dcount];
FOR i: StackIndex 
IN [0..self.dcount) 
DO
dict: Dict ~ self.dstack[i];
ArrayPut[self, subarray, i, AnyFromDict[dict]];
ENDLOOP;
 
RETURN [subarray];
};
 
Def: 
PUBLIC 
PROC [self: Root, key: Any, val: Any] ~ {
DictPut[self, CurrentDict[self], key, val];
};
 
nullDict: Dict ~ [val: [executable: 
FALSE, variant: dict[]], ref: 
NIL];
Where: 
PUBLIC 
PROC [self: Root, key: Any] 
RETURNS [found: 
BOOL, where: Dict] ~ {
FOR i: StackIndex 
DECREASING 
IN [0..self.dcount) 
DO
dict: Dict ~ self.dstack[i];
IF DictFetch[dict, key].found THEN RETURN [TRUE, dict];
ENDLOOP;
 
RETURN [FALSE, nullDict];
};
 
Load: 
PUBLIC 
PROC [self: Root, key: Any] 
RETURNS [Any] ~ {
FOR i: StackIndex 
DECREASING 
IN [0..self.dcount) 
DO
dict: Dict ~ self.dstack[i];
found: BOOL; val: Any;
[found, val] ← DictFetch[dict, key];
IF found THEN RETURN [val];
ENDLOOP;
 
ERROR Error[undefined];
};
 
Store: 
PUBLIC 
PROC [self: Root, key: Any, val: Any] ~ {
found: BOOL; dict: Dict;
[found, dict] ← Where[self, key];
IF NOT found THEN dict ← CurrentDict[self];
DictPut[self, dict, key, val];
};
 
 
Name operators
NameCreate: 
PROC [self: Root, string: String] 
RETURNS [Name] ~ {
hash: CARDINAL ~ StringHash[string];
ref: NameRef ~ self.zone.NEW[NameRep ← [level: self.level, string: string, hash: hash]];
name: Name ~ [val: [executable: TRUE, variant: name[]], ref: ref];
DictPut[self, self.nameDict, AnyFromString[string], AnyFromName[name], TRUE];
RETURN [name];
};
 
NameLength: 
PUBLIC 
PROC [name: Name] 
RETURNS [
INT] ~ {
RETURN [StringLength[name.ref.string]];
};
 
NameFromText: 
PUBLIC 
PROC [self: Root, text: Text] 
RETURNS [Name] ~ {
found: BOOL; val: Any;
[found, val] ← DictFetchText[self.nameDict, text];
IF found THEN RETURN [NameFromAny[val]]
ELSE RETURN [NameCreate[self, StringCreateFromText[self, text]]];
};
 
NameFromString: 
PUBLIC 
PROC [self: Root, string: String] 
RETURNS [Name] ~ {
found: BOOL; val: Any;
[found, val] ← DictFetch[self.nameDict, AnyFromString[string]];
IF found THEN RETURN [NameFromAny[val]]
ELSE RETURN [NameCreate[self, StringCreateFromString[self, string]]];
};
 
 
File operators
EndOfFile: 
PUBLIC 
ERROR ~ 
CODE;
 
RopeFromString: 
PROC [string: String] 
RETURNS [
ROPE] ~ {
text: Rope.Text ~ Rope.NewText[string.val.length];
FOR i: NAT IN[0..string.val.length) DO text[i] ← string.ref[string.val.start+i] ENDLOOP;
RETURN [text];
};
 
FileCreate: 
PUBLIC 
PROC [self: Root, string: String, accessMode: FileAccessMode] 
RETURNS [File] ~ {
fileName: ROPE ~ RopeFromString[string];
stream: STREAM ← NIL;
fsError: FS.ErrorDesc;
SELECT 
TRUE 
FROM
Rope.Equal[fileName, "%statementedit"] => {
line: ROPE ~ IO.GetLineRope[self.stdin.ref.stream];
RETURN [FileFromStream[self, IO.RIS[line]]];
};
Rope.Equal[fileName, "%stdin"] => xxx;
Rope.Equal[fileName, "%stdout"] => xxx;
Rope.Equal[fileName, "%stderr"] => xxx;
ENDCASE;
 
stream ← 
FS.StreamOpen[fileName: fileName, accessOptions: accessMode !
FS.Error => { fsError ← error; CONTINUE };
];
IF stream=
NIL 
THEN 
SELECT fsError.group 
FROM
user => ERROR Error[undefinedfilename];
ENDCASE => ERROR Error[ioerror];
 
RETURN [FileFromStream[self, stream]];
};
 
FileFromStream: 
PUBLIC 
PROC [self: Root, stream: 
STREAM] 
RETURNS [File] ~ {
ref: FileRef ~ self.zone.NEW[FileRep ← [stream: stream]];
RETURN [[val: [executable: FALSE, variant: file[access: unlimited]], ref: ref]];
};
 
CloseFile: 
PUBLIC 
PROC [file: File] ~ {
stream: STREAM ~ file.ref.stream;
IO.Close[stream];
};
 
Read: 
PUBLIC 
PROC [file: File] 
RETURNS [
CHAR] ~ {
ENABLE {
IO.EndOfStream => GOTO End;
IO.Error => IF ec=StreamClosed THEN GOTO Closed ELSE GOTO IOError;
};
 
stream: STREAM ~ file.ref.stream;
RETURN[IO.GetChar[stream]];
EXITS
End, Closed => ERROR EndOfFile;
IOError => ERROR Error[ioerror];
 
};
 
Write: 
PUBLIC 
PROC [file: File, char: 
CHAR] ~ {
ENABLE {
IO.Error => GOTO IOError;
};
 
stream: STREAM ~ file.ref.stream;
IO.PutChar[stream, char];
EXITS
IOError => ERROR Error[ioerror];
 
};
 
HexDigit: 
TYPE ~ [0..16);
ReadHexString: 
PUBLIC 
PROC [self: Root, file: File, string: String] 
RETURNS [String, 
BOOL] ~ {
state: {hex0, hex1} ← hex0;
d0, d1: HexDigit ← 0;
length: INT ~ StringLength[string];
index: INT ← 0;
eof: BOOL ← FALSE;
WHILE index<length 
DO
char: CHAR ~ Read[file ! EndOfFile => { eof ← TRUE; EXIT }];
SELECT char 
FROM
IN ['0..'9] => d1 ← char-'0;
IN ['A..'F] => d1 ← 10+(char-'A);
IN ['a..'f] => d1 ← 10+(char-'a);
ENDCASE => LOOP;
 
SELECT state 
FROM
hex0 => { d0 ← d1; state ← hex1 };
hex1 => { StringPut[self, string, index, VAL[d0*16+d1]]; index ← index+1; state ← hex0 };
ENDCASE => ERROR Bug;
 
ENDLOOP;
 
IF state=hex1 THEN { StringPut[self, string, index, VAL[d0*16]]; index ← index+1 };
RETURN [StringGetInterval[string, 0, index], NOT eof];
};
 
HexArray: TYPE ~ PACKED ARRAY HexDigit OF CHAR;
hex: 
REF HexArray ~ 
NEW[HexArray ← ['0, '1, '2, '3, '4, '5, '6, '7, '8, '9, 'a, 'b, 'c, 'd, 'e, 'f]];
WriteHexString: 
PUBLIC 
PROC [file: File, string: String] ~ {
length: INT ~ StringLength[string];
FOR index: 
INT 
IN [0..length) 
DO
char: CHAR ~ StringGet[string, index];
d0: HexDigit ~ ORD[char]/16;
d1: HexDigit ~ ORD[char] MOD 16;
Write[file, hex[d0]];
Write[file, hex[d1]];
ENDLOOP;
 
};
 
ReadString: 
PUBLIC 
PROC [self: Root, file: File, string: String] 
RETURNS [String, 
BOOL] ~ {
length: INT ~ StringLength[string];
index: INT ← 0;
eof: BOOL ← FALSE;
WHILE index<length 
DO
char: CHAR ~ Read[file ! EndOfFile => { eof ← TRUE; EXIT }];
StringPut[self, string, index, char];
index ← index+1;
ENDLOOP;
 
RETURN [StringGetInterval[string, 0, index], NOT eof];
};
 
WriteString: 
PUBLIC 
PROC [file: File, string: String] ~ {
length: INT ~ StringLength[string];
FOR index: 
INT 
IN [0..length) 
DO
char: CHAR ~ StringGet[string, index];
Write[file, char];
ENDLOOP;
 
};
 
ReadLine: 
PUBLIC 
PROC [self: Root, file: File, string: String] 
RETURNS [String, 
BOOL] ~ {
index: INT ← 0;
eof: BOOL ← FALSE;
DO
char: CHAR ~ Read[file ! EndOfFile => { eof ← TRUE; EXIT }];
SELECT char FROM Ascii.CR, Ascii.LF => EXIT ENDCASE;
StringPut[self, string, index, char];
index ← index+1;
ENDLOOP;
 
RETURN [StringGetInterval[string, 0, index], NOT eof];
};
 
BytesAvailable: 
PUBLIC 
PROC [file: File] 
RETURNS [
INT] ~ {
ENABLE {
IO.Error => IF ec=StreamClosed THEN GOTO Closed ELSE GOTO IOError;
};
 
stream: STREAM ~ file.ref.stream;
IF IO.EndOf[stream] THEN RETURN [-1];
RETURN [IO.CharsAvail[stream]];
EXITS
Closed => RETURN [-1];
IOError => ERROR Error[ioerror];
 
};
 
FlushFile: 
PUBLIC 
PROC [file: File] ~ {
ENABLE {
IO.Error => IF ec=StreamClosed THEN GOTO Closed ELSE GOTO IOError;
};
 
stream: STREAM ~ file.ref.stream;
variety: IO.StreamVariety ~ IO.GetInfo[stream].variety;
SELECT variety 
FROM
input, inputOutput => UNTIL IO.EndOf[stream] DO [] ← IO.GetChar[stream] ENDLOOP;
ENDCASE;
 
SELECT variety 
FROM
output, inputOutput => IO.Flush[stream];
ENDCASE;
 
EXITS
Closed => NULL;
IOError => ERROR Error[ioerror];
 
};
 
ResetFile: 
PUBLIC 
PROC [file: File] ~ {
ENABLE {
IO.Error => IF ec=StreamClosed THEN GOTO Closed ELSE GOTO IOError;
};
 
stream: STREAM ~ file.ref.stream;
IO.Reset[stream];
EXITS
Closed => NULL;
IOError => ERROR Error[ioerror];
 
};
 
Status: 
PUBLIC 
PROC [file: File] 
RETURNS [
BOOL] ~ {
ENABLE {
IO.Error => GOTO IOError;
};
 
stream: STREAM ~ file.ref.stream;
RETURN [IO.GetInfo[stream].class#$Closed];
EXITS
IOError => RETURN [FALSE];
 
};
 
Echo: 
PUBLIC 
PROC [self: Root, echo: 
BOOL] ~ {
};