NodePropsImpl.mesa
Copyright © 1985, 1986 by Xerox Corporation. All rights reserved.
written by Paxton. January 1981
Michael Plass, May 28, 1986 3:38:28 pm PDT
Rick Beach, March 28, 1985 9:57:47 am PST
Russ Atkinson (RRA) August 19, 1985 4:17:41 pm PDT
Doug Wyatt, September 12, 1986 7:08:21 pm PDT
DIRECTORY
Atom USING [DottedPairNode, GetPName, MakeAtom, PropList],
Basics USING [BoundsCheckHighHalf, DoubleShiftLeft, NonNegative],
IO USING [atom, int, PutFR],
List USING [Length, Sort],
MessageWindow USING [Append, Blink],
RefTab USING [Create, Fetch, Ref, Store],
Rope USING [Concat, Equal, Fetch, FromChar, FromRefText, IsEmpty, ROPE, Size, Substr],
Rosary USING [FromProcProc, MapRuns, ROSARY, Size],
Tioga USING [MapPropsAction, Node, PropCopier, PropReader, Props, PropsBody, PropWriter];
~
BEGIN
ROSARY: TYPE = Rosary.ROSARY;
ROPE: TYPE = Rope.ROPE;
Node: TYPE = Tioga.Node;
Prop, Props: TYPE = Tioga.Props;
PropsBody: TYPE = Tioga.PropsBody;
MapPropsAction: TYPE = Tioga.MapPropsAction;
PropReader: TYPE = Tioga.PropReader;
PropWriter: TYPE = Tioga.PropWriter;
PropCopier: TYPE = Tioga.PropCopier;
commentAtom: ATOM = $Comment;
formatAtom: ATOM = $Format;
styleDefAtom: ATOM = $StyleDef;
prefixAtom: ATOM = $Prefix;
postfixAtom: ATOM = $Postfix;
charpropsAtom: ATOM = $CharProps;
charsetsAtom: ATOM = $CharSets;
artworkAtom: ATOM = $Artwork;
refBool: ARRAY BOOL OF REF BOOL ← [FALSE: NEW[BOOL ← FALSE], TRUE: NEW[BOOL ← TRUE]];
RefBool: PUBLIC PROC [b: BOOL] RETURNS [REF BOOL] ~ { RETURN[refBool[b]] };
ReadComment: PropReader ~ {
comment: BOOL ~ Rope.Equal[specs, "TRUE", FALSE];
RETURN [refBool[comment]];
};
WriteComment: PropWriter ~ {
comment: BOOL ← FALSE;
WITH value
SELECT
FROM
x: REF BOOL => comment ← x^;
ENDCASE => NULL;
RETURN [IF comment THEN "TRUE" ELSE "FALSE"];
};
ReadFormat: PropReader ~ {
RETURN[IF Rope.IsEmpty[specs] THEN NIL ELSE Atom.MakeAtom[specs]];
};
WriteFormat: PropWriter ~ {
IF value=NIL THEN RETURN[""];
WITH value
SELECT
FROM
atom: ATOM => RETURN[Atom.GetPName[atom]];
ENDCASE => RETURN[NIL];
};
ReadCharSets: PropReader ~ {
Raises a bounds fault if invalid specs are provided.
Encoding is (<repeatCount><charSet>)*, where <repeatCount> is encoded with the variable-length integer encoding scheme described in FileOps, and <charSet> is just encoded as a byte.
p:
PROC [q:
PROC [
REF,
INT]] ~ {
i: INT ← 0;
size: INT ← Rope.Size[specs];
GetByte: PROC RETURNS [b: [0..256)] ~ {b ← Rope.Fetch[specs, i]-'\000; i ← i + 1};
Combine:
PROC [a:
INT, b: [0..128)]
RETURNS [
INT] ~ {
bnd: NAT ~ LAST[NAT]/128+1;
RETURN [Basics.DoubleShiftLeft[[li[Basics.BoundsCheckHighHalf[a, bnd]]], 7].li + b];
};
GetInt:
PROC
RETURNS [int:
INT ← 0] ~ {
b: [0..256) ← GetByte[];
WHILE b > 127
DO
int ← Combine[int, b-128];
b ← GetByte[];
ENDLOOP;
int ← Combine[int, b];
};
UNTIL i = size
DO
repeat: INT ~ GetInt[];
charSet: [0..256) ~ GetByte[];
IF charSet=0 THEN q[NIL, repeat] ELSE q[NEW[[0..256) ← charSet], repeat];
ENDLOOP;
};
RETURN [Rosary.FromProcProc[p]];
};
WriteCharSets: PropWriter ~ {
text: REF TEXT ~ NEW[TEXT[48]];
Fold: PROC ~ {specs ← Rope.Concat[specs, Rope.FromRefText[text]]; text.length ← 0};
PutByte:
PROC [b: [0..256)] ~ {
IF text.length = text.maxLength THEN Fold[];
text[text.length] ← VAL[b];
text.length ← text.length + 1;
};
PutInt:
PROC [int:
INT, more:
BOOLEAN ←
FALSE] ~ {
IF int > 127 THEN {PutInt[int/128, TRUE]; int ← int MOD 128};
IF more THEN int ← int + 128;
PutByte[int];
};
charSet: [0..256) ← 0;
charSetRepeat: INT ← 0;
FlushRun:
PROC ~ {
IF charSetRepeat > 0 THEN {PutInt[charSetRepeat]; PutByte[charSet]; charSetRepeat ← 0};
};
Action:
PROC [item:
REF, repeat:
INT]
RETURNS [quit:
BOOL ←
FALSE] ~ {
new: [0..256) ←
WITH item
SELECT
FROM
r: REF [0..256) => r^,
ENDCASE => 0;
IF charSetRepeat > 0 AND new#charSet THEN FlushRun[];
charSet ← new;
charSetRepeat ← charSetRepeat + repeat;
};
specs ← NIL;
[] ← Rosary.MapRuns[[NARROW[value]], Action];
FlushRun[];
Fold[];
IF specs#NIL AND Rope.Size[specs]=0 THEN specs ← NIL;
};
ReadCharProps: PropReader ~ {
Raises a bounds fault if invalid specs are provided.
Encoding is
(<repeatCount><propsCount>(<atomLen><atomBytes><valueLen><valueBytes>)*)*,
where the counts and lengths are encoded with the variable-length integer encoding scheme described in FileOps.
p:
PROC [q:
PROC [
REF,
INT]] ~ {
i: INT ← 0;
size: INT ← Rope.Size[specs];
GetByte: PROC RETURNS [b: [0..256)] ~ {b ← Rope.Fetch[specs, i]-'\000; i ← i + 1};
Combine:
PROC [a:
INT, b: [0..128)]
RETURNS [
INT] ~ {
bnd: NAT ~ LAST[NAT]/128+1;
RETURN [Basics.DoubleShiftLeft[[li[Basics.BoundsCheckHighHalf[a, bnd]]], 7].li + b];
};
GetInt:
PROC
RETURNS [int:
INT ← 0] ~ {
b: [0..256) ← GetByte[];
WHILE b > 127
DO
int ← Combine[int, b-128];
b ← GetByte[];
ENDLOOP;
int ← Combine[int, b];
};
GetRope:
PROC
RETURNS [rope:
ROPE ←
NIL] ~ {
len: INT ~ GetInt[];
rope ← Rope.Substr[specs, i, len];
i ← i + len;
[] ← Basics.NonNegative[size-i];
};
UNTIL i = size
DO
repeatCount: INT ~ GetInt[];
propsCount: INT ~ GetInt[];
propList: Atom.PropList ← NIL;
THROUGH [0..propsCount)
DO
atom: ATOM ~ Atom.MakeAtom[GetRope[]];
valueSpecs: ROPE ~ GetRope[];
value: REF ~ ReadProp[atom, valueSpecs];
propList ← CONS[NEW[Atom.DottedPairNode ← [key: atom, val: value]], propList];
ENDLOOP;
q[propList, repeatCount];
ENDLOOP;
};
RETURN [Rosary.FromProcProc[p]];
};
WriteCharProps: PropWriter ~ {
curRope: ROPE ← NIL;
Grab: PROC RETURNS [rope: ROPE] ~ {rope ← curRope; curRope ← NIL};
PutByte: PROC [b: [0..256)] ~ {curRope ← Rope.Concat[curRope, Rope.FromChar[VAL[b]]]};
PutRope: PROC [rope: ROPE] ~ {curRope ← Rope.Concat[curRope, rope]};
PutInt:
PROC [int:
INT, more:
BOOLEAN ←
FALSE] ~ {
IF int > 127 THEN {PutInt[int/128, TRUE]; int ← int MOD 128};
IF more THEN int ← int + 128;
PutByte[int];
};
rept: INT ← 0;
itemSpecs: ROPE ← NIL;
FlushRun: PROC ~ { IF rept > 0 THEN {PutInt[rept]; PutRope[itemSpecs]; rept ← 0} };
PutItem:
PROC [newItemSpecs:
ROPE, repeat:
INT] ~ {
IF NOT Rope.Equal[newItemSpecs, itemSpecs] THEN FlushRun[];
itemSpecs ← newItemSpecs;
rept ← rept + repeat;
};
Action:
PROC [item:
REF, repeat:
INT]
RETURNS [quit:
BOOL ←
FALSE] ~ {
save: ROPE ~ Grab[];
SpecsList:
PROC [propList: Atom.PropList]
RETURNS [s:
LIST
OF
REF ←
NIL] ~ {
FOR p: Atom.PropList ← propList, p.rest
UNTIL p =
NIL
DO
atom: ATOM ~ NARROW[p.first.key];
v: ROPE ~ WriteProp[atom, p.first.val];
IF v #
NIL
THEN {
a: ROPE ~ Atom.GetPName[atom];
PutInt[Rope.Size[a]]; PutRope[a]; PutInt[Rope.Size[v]]; PutRope[v];
s ← CONS[Grab[], s];
};
ENDLOOP;
};
specList: LIST OF REF ← List.Sort[SpecsList[NARROW[item]]];
newItemSpecs: ROPE;
PutInt[List.Length[specList]];
UNTIL specList =
NIL
DO
t: LIST OF REF ← specList;
PutRope[NARROW[t.first]];
specList ← t.rest;
t.rest ← NIL;
ENDLOOP;
newItemSpecs ← Grab[];
curRope ← save;
PutItem[newItemSpecs, repeat];
};
[] ← Rosary.MapRuns[[NARROW[value]], Action];
FlushRun[];
specs ← Grab[];
IF specs#NIL AND Rope.Size[specs]=0 THEN specs ← NIL;
};
FindName:
PROC [n: Node, name:
ATOM, remove:
BOOL]
RETURNS [prop: Prop] ~ {
lst, prev: Props;
IF n=NIL THEN RETURN [NIL];
lst ← n.props;
prop ← NIL; prev ← NIL;
WHILE lst#
NIL
DO
IF lst.name = name THEN { prop ← lst; EXIT };
prev ← lst; lst ← lst.next;
ENDLOOP;
IF prop#
NIL
AND remove
THEN {
IF prev#NIL THEN prev.next ← prop.next
ELSE n.props ← prop.next;
};
};
PutProp:
PUBLIC
PROC [n: Node, name:
ATOM, value:
REF] ~ {
prop: Prop ← FindName[n, name, FALSE];
IF name = charpropsAtom
OR name = charsetsAtom
THEN {
value ← CheckRosarySize[NARROW[value], n, name];
};
IF value =
NIL
THEN {
IF prop#NIL THEN prop.value ← NIL;
SELECT name
FROM
styleDefAtom => n.hasstyledef ← FALSE;
prefixAtom => n.hasprefix ← FALSE;
postfixAtom => n.haspostfix ← FALSE;
formatAtom => n.formatName ← NIL;
commentAtom => n.comment ← FALSE;
charpropsAtom => n.hascharprops ← FALSE;
charsetsAtom => n.hascharsets ← FALSE;
artworkAtom => n.hasartwork ← FALSE;
ENDCASE;
RETURN
};
SELECT name
FROM
styleDefAtom => n.hasstyledef ← TRUE;
prefixAtom => n.hasprefix ← TRUE;
postfixAtom => n.haspostfix ← TRUE;
formatAtom => {
n.formatName ← NARROW[value];
RETURN;
};
commentAtom => {
-- simply set the bit in the node
n.comment ← NARROW[value, REF BOOL]^;
RETURN;
};
charpropsAtom => n.hascharprops ← TRUE;
charsetsAtom => n.hascharsets ← TRUE;
artworkAtom => n.hasartwork ← TRUE;
ENDCASE;
IF prop#NIL THEN prop.value ← value
ELSE n.props ← NEW[PropsBody ← [name: name, value: value, next: n.props]];
};
CheckRosarySize:
PROC [rosary:
ROSARY, n: Node, what:
ATOM]
RETURNS [
ROSARY] ~ {
IF rosary #
NIL
THEN {
rosarySize: INT ~ Rosary.Size[rosary];
ropeSize: INT ~ Rope.Size[n.rope];
IF rosarySize # ropeSize
THEN {
msg: ROPE ~ IO.PutFR["NodePropsImpl: %g property discarded because its size (%g) differed from rope size (%g).", IO.atom[what], IO.int[rosarySize], IO.int[ropeSize]];
MessageWindow.Append[msg, TRUE];
MessageWindow.Blink[];
RETURN [NIL];
};
};
RETURN [rosary];
};
GetProp:
PUBLIC
PROC [node: Node, name:
ATOM]
RETURNS [value:
REF ←
NIL] ~ {
IF node#
NIL
THEN {
prop: Prop ~ FindName[node, name, FALSE];
IF prop#NIL THEN value ← prop.value
ELSE
SELECT name
FROM
commentAtom => value ← refBool[node.comment];
formatAtom => IF node.formatName#NIL THEN value ← node.formatName;
ENDCASE;
};
};
RemProp:
PUBLIC
PROC [n: Node, name:
ATOM] ~ {
[] ← FindName[n, name, TRUE]; -- removes the value
SELECT name
FROM
styleDefAtom => n.hasstyledef ← FALSE;
prefixAtom => n.hasprefix ← FALSE;
postfixAtom => n.haspostfix ← FALSE;
formatAtom => n.formatName ← NIL;
commentAtom => n.comment ← FALSE;
charpropsAtom => n.hascharprops ← FALSE;
charsetsAtom => n.hascharsets ← FALSE;
artworkAtom => n.hasartwork ← FALSE;
ENDCASE;
};
MapProps:
PUBLIC
PROC [node: Node, action: MapPropsAction,
formatFlag, commentFlag:
BOOL ←
TRUE]
RETURNS [quit:
BOOL] ~ {
apply the action to each name & value pair for the node
returns true if&when an action returns true
IF node#
NIL
THEN {
props: Props ← node.props;
IF formatFlag AND node.formatName#NIL AND action[formatAtom, node.formatName] THEN RETURN [TRUE];
IF commentFlag AND action[commentAtom, refBool[node.comment]] THEN RETURN [TRUE];
UNTIL props=
NIL
DO
next: Props ~ props.next; -- get it now in case action deletes current prop
IF props.value#NIL AND action[props.name, props.value] THEN RETURN [TRUE];
props ← next;
ENDLOOP;
};
RETURN [FALSE]
};
Read, Write, Copy Props
PropProcs: TYPE ~ REF PropProcsRep;
PropProcsRep:
TYPE ~
RECORD [reader: PropReader, writer: PropWriter, copier: PropCopier];
propProcsTable: RefTab.Ref ← RefTab.Create[];
RegisterProp:
PUBLIC
PROC [name:
ATOM,
reader: PropReader, writer: PropWriter, copier: PropCopier] ~ {
registers these procs for this variety of node
they will be called by DoSpecs, GetSpecs, and CopyInfo
IF name#
NIL
THEN {
procs: PropProcs ~ NEW[PropProcsRep ← [reader: reader, writer: writer, copier: copier]];
[] ← RefTab.Store[propProcsTable, name, procs];
};
};
NullRead: PUBLIC PropReader ~ { RETURN [NIL] };
NullWrite: PUBLIC PropWriter ~ { RETURN [NIL] };
NullCopy: PUBLIC PropCopier ~ { RETURN [NIL] };
ReadProp:
PUBLIC PropReader ~ {
used when reading files
calls the registered reader for this property name
returns specs if no reader is registered
reader: PropReader ← NIL;
WITH RefTab.Fetch[propProcsTable, name].val
SELECT
FROM
procs: PropProcs => reader ← procs.reader;
ENDCASE;
IF reader#NIL THEN RETURN[reader[name, specs]]
ELSE RETURN[specs];
};
WriteProp:
PUBLIC PropWriter ~ {
used when writing files
calls the registered writer for this property name
if no writer is registered, returns value if it is a rope, NIL otherwise
writer: PropWriter ← NIL;
WITH RefTab.Fetch[propProcsTable, name].val
SELECT
FROM
procs: PropProcs => writer ← procs.writer;
ENDCASE;
IF writer#NIL THEN RETURN[writer[name, value]]
ELSE WITH value SELECT FROM rope: ROPE => RETURN[rope]; ENDCASE => RETURN[NIL];
};
CopyProp:
PUBLIC PropCopier ~ {
used when copying nodes
calls the registered copier for this property name
if no copier is registered, returns value
copier: PropCopier ← NIL;
WITH RefTab.Fetch[propProcsTable, name].val
SELECT
FROM
procs: PropProcs => copier ← procs.copier;
ENDCASE;
IF copier#NIL THEN RETURN[copier[name, value]]
ELSE RETURN[value];
};