GraphReadAscii.mesa, Copyright © 1985 by Xerox Corporation. All rights reserved.
Last Edited by:
Sweetsun Chen, November 20, 1985 4:29:05 pm PST
DIRECTORY
Ascii USING [CR, FF, LF, NUL, SP, TAB],
Atom USING [GetPropFromList],
Convert USING [Error, RealFromRope],
Graph USING [CaretIndex, CaretSpec, ColorIndex, Entity, FontIndex, GraphHandle, NtNan, ROPE, TargetSpec, ValueList, XY],
GraphOps USING [AddCurve, NewGraph, SetXValues],
GraphPrivate USING [DataError, RopeList, SyntaxError],
GraphUtil USING [BlinkMsg, LengthOfVL],
Imager USING [Box],
IO USING [Backup, EndOfStream, Error, GetCedarToken, GetChar, GetID, int, PeekChar, PutFR, SetIndex, STREAM, TokenError, TokenKind],
List USING [Kill],
Real USING [Fix, LargestNumber],
RefText USING [Equal, ObtainScratch, ReleaseScratch, TrustTextAsRope],
Rope USING [Equal, FromRefText, Substr];
GraphReadAscii: CEDAR PROGRAM
IMPORTS Atom, Convert, GraphOps, GraphPrivate, IO, GraphUtil, List, Real, RefText, Rope
EXPORTS GraphPrivate = {
OPEN Graph, GraphOps, GraphPrivate, GraphUtil;
TextDataKeys: TYPE = {varsKey, nVarsKey, valuesKey, noneKey};
LVL: TYPE = LIST OF ValueList;
ReadAsciiFile: PUBLIC PROC [handle: GraphHandle, s: IO.STREAM] RETURNS [msg: ROPENIL] = {
ok: BOOLTRUE;
key: ROPE ← s.GetID[ ! IO.EndOfStream, IO.Error => ok ← FALSE];
IF ok THEN ok ← key.Equal["graph", FALSE];
IF ok THEN msg ← ReadAsciiComplex[handle, s]
ELSE {
s.SetIndex[0];
msg ← ReadAsciiSimple[handle, s];
};
}; -- ReadAsciiFile
ReadAsciiComplex: PROC [handle: GraphHandle, s: IO.STREAM] RETURNS [msg: ROPENIL] = {
read more complicated graph file in text format.
msg ← "Not implemented yet."
}; -- ReadAsciiComplex
If values have been read, then vars key or nVars key will end previous group, and start a new group of data.
ReadAsciiSimple: PROC [handle: GraphHandle, s: IO.STREAM] RETURNS [msg: ROPENIL] = {
buffer: REF TEXT ← RefText.ObtainScratch[512];
tokenKind: IO.TokenKind ← tokenCOMMENT;
token: REF TEXT;
charsSkipped: INT;
error: IO.TokenError;
get: BOOLTRUE;
endOfFile: BOOLFALSE;
key: TextDataKeys ← noneKey;
nVars, specifiedN, guessedN, finalN, iRow, iCol: INT;
-- iRow, iCol: last row/col processed so far.
varsSpecified, nVarsSpecified, valuesSpecified, nVarsGuessed, nToBeGuessed, endOfGroup: BOOL;
vars, lastVar: RopeList;
xvalues: ValueList;
yvalues, tlvl: LVL;
status: ROPE;
AppendIDtoNames: PROC [] = {
nl: RopeList ← CONS[Rope.FromRefText[token], NIL];
IF lastVar = NIL THEN {vars ← lastVar ← nl}
ELSE {lastVar.rest ← nl; lastVar ← nl};
nVars ← nVars + 1;
varsSpecified ← TRUE;
}; -- AppendIDtoNames
AppendROPEtoNames: PROC [] = { -- othere input/output: vars and lastVar
name: ROPEIF token.length <= 2 THEN "" ELSE Rope.FromRefText[token].Substr[1, token.length - 2];
nl: RopeList ← CONS[name, NIL];
IF lastVar = NIL THEN {vars ← lastVar ← nl}
ELSE {lastVar.rest ← nl; lastVar ← nl};
nVars ← nVars + 1;
varsSpecified ← TRUE;
}; -- AppendROPEtoNames
ProcessTokenID: PROC [] = {
other input: s, token, varsSpecified; other output: key, get, eog. s is possibly affected too.
For nVarsKey and valuesKey, one must make sure the thing they indicate has been specified before calling this.
currKey: TextDataKeys ← key;
nextChar: CHAR;
[nextChar, endOfFile] ← PeekNextNonBlankChar[s];
IF endOfFile THEN SELECT currKey FROM
varsKey, nVarsKey, noneKey => SIGNAL DataError["can't find values"];
valuesKey => endOfGroup ← TRUE;
ENDCASE
ELSE IF nextChar = ': THEN {
[] ← s.GetChar[];
key ← GetKeyWord[token, s];
SELECT key FROM
varsKey => {
IF currKey = varsKey THEN SIGNAL SyntaxError["duplicate vars key"];
IF valuesSpecified THEN endOfGroup ← TRUE
ELSE IF varsSpecified THEN SIGNAL SyntaxError["duplicate vars key"];
else ok.
};
nVarsKey => {
IF currKey = nVarsKey THEN SIGNAL SyntaxError["duplicate nVars key"];
IF valuesSpecified THEN endOfGroup ← TRUE
ELSE IF nVarsSpecified THEN SIGNAL SyntaxError["duplicate nVars key"];
else ok.
};
valuesKey => {
IF currKey = valuesKey THEN endOfGroup ← TRUE; -- values must have been specified.
[finalN, vars, yvalues] ← InitData[nVarsSpecified, specifiedN, nVars, vars];
};
ENDCASE; -- nothing else after GetKeyWord.
}
ELSE
SELECT key FROM
varsKey => AppendIDtoNames[];
valuesKey => { -- values must have been specified.
endOfGroup ← TRUE;
key ← varsKey; get ← FALSE;
};
nVarsKey => { -- nVarsSpecified must have been specified.
IF varsSpecified THEN {
IF valuesSpecified THEN {
endOfGroup ← TRUE;
key ← varsKey; get ← FALSE;
}
ELSE SIGNAL SyntaxError["possibly missing `:' after keyword"];
}
ELSE { -- vars not specified.
key ← varsKey; get ← FALSE;
};
};
ENDCASE => { -- noneKey, so values, vars, and nName are not specified.
key ← varsKey; get ← FALSE;
};
}; -- ProcessTokenID
UNTIL endOfFile DO
xmin, ymin: REAL ← Real.LargestNumber;
xmax, ymax: REAL ← -xmin;
nVars ← specifiedN ← guessedN ← iRow ← iCol ← 0;
finalN ← -1;
varsSpecified ← nVarsSpecified ← nVarsGuessed ← nToBeGuessed ← valuesSpecified ← endOfGroup ← FALSE;
vars ← lastVar ← NIL;
xvalues ← NIL;
yvalues ← tlvl ← NIL;
status ← NIL;
UNTIL endOfGroup OR endOfFile DO
IF get THEN [tokenKind, token, charsSkipped, error] ← s.GetCedarToken[buffer]
ELSE get ← TRUE;
SELECT tokenKind FROM
tokenCOMMENT => LOOP;
tokenERROR => SIGNAL SyntaxError[];
tokenEOF => {
IF NOT valuesSpecified THEN SIGNAL DataError["can't find values"];
endOfFile ← TRUE;
EXIT;
};
ENDCASE => SELECT key FROM
noneKey => { -- can happen only at first time in the loop.
errorMsg: ROPE ← "key word, rope, or values expected";
SELECT tokenKind FROM
tokenROPE => {key ← varsKey; get ← FALSE};
tokenID => ProcessTokenID[];
tokenREAL, tokenDECIMAL => {
key ← valuesKey; get ← FALSE;
[finalN, vars, yvalues] ← InitData[nVarsSpecified, specifiedN, nVars, vars];
};
tokenSINGLE => SELECT token[0] FROM
'-, '+, '* => {
IO.Backup[s, token[0]];
key ← valuesKey;
[finalN, vars, yvalues] ← InitData[nVarsSpecified, specifiedN, nVars, vars];
};
ENDCASE => SIGNAL SyntaxError[errorMsg];
ENDCASE => SIGNAL SyntaxError[errorMsg];
};
nVarsKey => {
status ← "after nVars key";
SELECT tokenKind FROM
tokenDECIMAL, tokenREAL => {
IF nVarsSpecified THEN {
key ← valuesKey; get ← FALSE;
[finalN, vars, yvalues] ← InitData[nVarsSpecified, specifiedN, nVars, vars];
}
ELSE {
ok: BOOLTRUE;
r: REAL;
IF tokenKind = tokenREAL THEN SIGNAL DataError["nVars should be an integer"];
r ← Convert.RealFromRope[RefText.TrustTextAsRope[token]
! Convert.Error => {ok ← FALSE; CONTINUE}];
IF ok THEN {
IF r > LAST[INT] THEN SIGNAL DataError["nVars too big"];
IF r < 1 THEN SIGNAL DataError["nVars < 1"];
specifiedN ← Real.Fix[r];
nVarsSpecified ← TRUE;
}
ELSE SIGNAL DataError[status];
};
};
ENDCASE => {
IF NOT nVarsSpecified THEN SIGNAL DataError["can't find the number of variables"]; -- currently doesn't allow '+ in front of the number of vars.
SELECT tokenKind FROM
tokenSINGLE => SELECT token[0] FROM
'-, '+, '* => {
IO.Backup[s, token[0]];
key ← valuesKey;
[finalN, vars, yvalues] ← InitData[nVarsSpecified, specifiedN, nVars, vars];
};
ENDCASE => LOOP;
tokenID => ProcessTokenID[];
tokenROPE => {
IF varsSpecified THEN SIGNAL DataError["names have been listed"];
key ← varsKey; get ← FALSE;
};
ENDCASE => SIGNAL SyntaxError[status];
};
};
varsKey => {
status ← "getting vars";
SELECT tokenKind FROM
tokenROPE => AppendROPEtoNames[];
tokenID => ProcessTokenID[];
ENDCASE => {
IF NOT varsSpecified THEN SIGNAL DataError["can't find the names of variables"];
SELECT tokenKind FROM
tokenSINGLE => SELECT token[0] FROM
'-, '+, '* => {
IO.Backup[s, token[0]];
key ← valuesKey;
[finalN, vars, yvalues] ← InitData[nVarsSpecified, specifiedN, nVars, vars];
};
ENDCASE => LOOP;
tokenDECIMAL, tokenREAL => {
key ← valuesKey; get ← FALSE;
[finalN, vars, yvalues] ← InitData[nVarsSpecified, specifiedN, nVars, vars];
};
ENDCASE => SIGNAL SyntaxError[status];
};
}; -- varsKey
valuesKey => {
status ← "parsing values";
SELECT tokenKind FROM
tokenDECIMAL, tokenREAL, tokenSINGLE => {
ok, normal: BOOLTRUE;
negative: BOOLFALSE;
r: REAL;
IF tokenKind # tokenSINGLE THEN
r ← Convert.RealFromRope[RefText.TrustTextAsRope[token]
! Convert.Error => {ok ← FALSE; CONTINUE}]
ELSE SELECT token[0] FROM
'-, '+ => {
negative ← token[0] = '-;
[tokenKind, token, charsSkipped, error] ← s.GetCedarToken[buffer];
SELECT tokenKind FROM
tokenDECIMAL, tokenREAL => r ← Convert.RealFromRope[
RefText.TrustTextAsRope[token]
! Convert.Error => {ok ← FALSE; CONTINUE}];
ENDCASE => ok ← FALSE;
IF negative THEN r ← -r;
};
'* => {r ← NtNan; normal ← FALSE}
ENDCASE => LOOP;
IF NOT ok THEN SIGNAL DataError[status];
IF iCol = finalN OR iCol = 0 THEN {iCol ← 1; iRow ← iRow + 1}
ELSE iCol ← iCol + 1;
IF iCol = 1 THEN {
xvalues ← CONS[r, xvalues];
tlvl ← yvalues;
IF normal THEN {xmin ← MIN[r, xmin]; xmax ← MAX[r, xmax]};
}
ELSE {
IF tlvl = NIL THEN SIGNAL DataError["gee!"]; -- incredible?
tlvl.first ← CONS[r, tlvl.first]; tlvl ← tlvl.rest;
IF normal THEN {ymin ← MIN[r, ymin]; ymax ← MAX[r, ymax]};
};
valuesSpecified ← TRUE;
};
tokenID => IF valuesSpecified THEN ProcessTokenID[]
ELSE SIGNAL DataError["can't find values"];
tokenROPE => {
IF NOT valuesSpecified THEN SIGNAL DataError["can't find values"];
endOfGroup ← TRUE;
key ← varsKey; get ← FALSE;
};
ENDCASE => SIGNAL SyntaxError[status];
}; -- valuesKey
ENDCASE;
ENDLOOP;
IF valuesSpecified THEN handle ← AddNewGraph[oldGraph: handle,
fileName: NARROW[Atom.GetPropFromList[s.propList, $Name]],
vars: vars, xvalues: xvalues, yvalues: yvalues,
bounds: [xmin, ymin, xmax, ymax]
];
ENDLOOP;
RefText.ReleaseScratch[token];
}; -- ReadAsciiSimple
KeyFromToken: PROC [token: REF TEXT] RETURNS [key: TextDataKeys ← noneKey] = {
begin and end keys are not to be specified.
IF token # NIL THEN RETURN [SELECT TRUE FROM
RefText.Equal[token, "nVars", FALSE],
RefText.Equal[token, "nVariables", FALSE] => nVarsKey,
RefText.Equal[token, "values", FALSE] => valuesKey,
RefText.Equal[token, "vars", FALSE],
RefText.Equal[token, "variables", FALSE] => varsKey,
ENDCASE => noneKey];
}; -- KeyFromToken
GetKeyWord: PROC [token: REF TEXT, s: IO.STREAM] RETURNS [key: TextDataKeys] = {
[] ← s.GetChar[]; -- skip the colon.
key ← KeyFromToken[token];
here we must find a legal key.
IF key = noneKey THEN SIGNAL SyntaxError["unknown key word"];
}; -- GetKeyWord
InitData: PROC [nVarsSpecified: BOOL, specifiedN, nVars: INT, names: RopeList] RETURNS [finalN: INT, vars: RopeList, lvl: LVLNIL] = { -- , toGuess: BOOL ← FALSE] = {
finalN ← nVars;
vars ← names;
IF vars = NIL AND NOT nVarsSpecified THEN SIGNAL DataError["number of variables unknown."];
IF vars = NIL AND nVarsSpecified THEN finalN ← specifiedN
ELSE IF vars # NIL AND NOT nVarsSpecified THEN finalN ← nVars
ELSE IF nVarsSpecified AND vars # NIL THEN {
IF specifiedN # nVars THEN BlinkMsg["number of vars listed # specified nVars. The latter is discarded."];
};
ELSE {
finalN ← -1; -- toGuess ← TRUE};
IF NOT toGuess THEN {
IF finalN <= 1 THEN SIGNAL DataError["number of variables <= 1"];
IF vars = NIL THEN FOR i: INT IN [1..finalN] DO
vars ← CONS[NIL, vars];
ENDLOOP;
FOR j: INT IN [2..finalN] DO lvl ← CONS[NIL, lvl]; ENDLOOP;
}; -- InitData
BlankChar: PROC [char: CHAR] RETURNS [BOOL] = { OPEN Ascii;
RETURN [SELECT char FROM
SP, TAB, LF, CR, FF, NUL => TRUE, ENDCASE => FALSE]
}; -- BlankChar
PeekNextNonBlankChar: PROC [s: IO.STREAM] RETURNS [char: CHAR, eof: BOOLFALSE] = {
ENABLE IO.EndOfStream => {eof ← TRUE; CONTINUE};
WHILE BlankChar[char ← s.PeekChar[]] DO [] ← s.GetChar[] ENDLOOP;
}; -- PeekNextNonBlankChar
AddNewGraph: PROC [oldGraph: GraphHandle, fileName: ROPE,
vars: RopeList, xvalues: ValueList, yvalues: LVL, bounds: Imager.Box] RETURNS [newGraph: GraphHandle] = {
tlvl: LVL;
lengthX, lengthY, iCurve, groupId: INT ← 0;
IF oldGraph = NIL OR vars = NIL OR xvalues = NIL OR yvalues = NIL THEN RETURN;
lengthX ← LengthOfVL[xvalues];
FOR tlvl ← yvalues, tlvl.rest UNTIL tlvl = NIL DO
iCurve ← iCurve + 1;
IF (lengthY ← LengthOfVL[tlvl.first]) # lengthX THEN SIGNAL DataError[
IO.PutFR["%g-th variable has %g values but x has %g values",
IO.int[iCurve], IO.int[lengthY], IO.int[lengthX]]];
ENDLOOP;
[newGraph, groupId] ← NewGraph[
fileName: fileName,
comment: fileName,
xName: vars.first,
autoBounds: FALSE,
bounds: bounds,
oldGraph: oldGraph
];
[] ← SetXValues[handle: newGraph, values: xvalues, groupId: groupId]; -- xvalues is reversed, and it will be cleaned up.
tlvl ← yvalues;
FOR tn: RopeList ← vars.rest, tn.rest UNTIL tn = NIL DO
[] ← AddCurve[handle: newGraph, values: tlvl.first, groupId: groupId, name: tn.first];
tlvl ← tlvl.rest;
ENDLOOP;
TRUSTED {
List.Kill[LOOPHOLE[yvalues]];
List.Kill[LOOPHOLE[vars]];
};
}; -- AddNewGraph
}.
LOG.
SChen, November 16, 1985 9:40:08 pm PST, created.
ReadValueWithGuessedN: PROC [] = {
nextChar: CHAR;
IF NOT nVarsGuessed THEN {
vl: ValuesList ← CONS[r, NIL];
ttlvl: LVLCONS[vl, NIL];
IF finalN = -1 THEN finalN ← 1 ELSE finalN ← finalN + 1;
iCol ← finalN;
vars ← CONS[NIL, vars];
IF iCol = 1 THEN xvalues ← vl
ELSE IF tlvl = NIL THEN {yvalues ← tlvl ← ttlvl}
ELSE {tlvl.rest ← ttlvl; tlvl ← ttlvl};
nextChar ← s.PeekChar[];
WHILE BlankChar[(nextChar ← s.PeekChar[])] DO
ENABLE IO.EndOfStream => {endOfFile ← TRUE; GOTO found};
SELECT nextChar FROM
Ascii.CR, Ascii.LF => GOTO guessed;
ENDCASE => s.GetChar[];
nextChar ← s.PeekChar[];
ENDLOOP;
EXITS
found => {
nVarsGuessed ← TRUE;
ttlvl.first ← NIL;
tlvl ← NIL;
};
}
ELSE { -- nVarsGuessed
IF iCol = finalN THEN iCol ← 1 ELSE iCol ← iCol + 1;
};
}; -- ReadValuesWithGuessedN