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 = {
TextDataKeys: TYPE = {varsKey, nVarsKey, valuesKey, noneKey};
LVL: TYPE = LIST OF ValueList;
ReadAsciiFile:
PUBLIC
PROC [handle: GraphHandle, s:
IO.
STREAM]
RETURNS [msg:
ROPE ←
NIL] = {
ok: BOOL ← TRUE;
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:
ROPE ←
NIL] = {
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:
ROPE ←
NIL] = {
buffer: REF TEXT ← RefText.ObtainScratch[512];
tokenKind: IO.TokenKind ← tokenCOMMENT;
token: REF TEXT;
charsSkipped: INT;
error: IO.TokenError;
get: BOOL ← TRUE;
endOfFile: BOOL ← FALSE;
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: ROPE ← IF 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: BOOL ← TRUE;
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: BOOL ← TRUE;
negative: BOOL ← FALSE;
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:
LVL ←
NIL] = {
-- , 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:
BOOL ←
FALSE] = {
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]];
};
}.