CIFParserImpl.mesa
Copyright Ó 1987 by Xerox Corporation. All rights reserved.
written by Ch. Le Cocq, September 10, 1987 10:46:27 am PDT
Christian Le Cocq September 16, 1987 2:37:09 pm PDT
Parser of the CIF (Caltech Intermediate Form) format stream.
Meant to be half of the CIF to ChipNDale converter.
DIRECTORY
CIFParser USING [Point, Path, Transformation, Registration, TMatrix],
Convert USING [IntFromRope, CardFromRope],
IO USING [STREAM, GetTokenRope, CharClass],
Real USING [Round, SqRt],
Rope USING [ROPE, Cat, Concat, Length, Fetch, FromChar];
CIFParserImpl: CEDAR PROGRAM
IMPORTS Convert, IO, Real, Rope
EXPORTS CIFParser
~ BEGIN OPEN CIFParser;
Implementation note:
the declaration of the variables is specified separately from their first use, as opposed to declaration-initialization statements. This is done on purpose, as it is highly probable to have errors or signals going thru these statements and I don't like too much the way the debugger reports error in initializations.
defaultInt: INT=LAST[INT];
defaultCard: CARD=LAST[CARD];
noPoint: Point = [defaultInt, defaultInt];
Types
external types
ROPE: TYPE ~ Rope.ROPE;
local types
CmdType: TYPE ~ {emptyCmd, endCmd, defDeleteCmd, defStartCmd, defFinishCmd, primCmd};
PrimCmdType: TYPE ~ {polygonCmd, boxCmd, roundFlashCmd, wireCmd, layerCmd, callCmd, userExtensionCmd, commentCmd, error};
Cmd: TYPE ~ RECORD[
type: CmdType ← primCmd,
token: ROPE ← NIL
];
PrimCmd: TYPE ~ RECORD[
type: PrimCmdType ← error,
token: ROPE ← NIL
];
State: TYPE ~ REF StateRec;
StateRec: TYPE ~ RECORD[
layer: ROPE ← NIL,
inSymbol: BOOLEANFALSE
];
Stream: TYPE ~ REF StreamRec;
StreamRec: TYPE ~ RECORD[lastToken: ROPE, s: IO.STREAM];
TokenKind: TYPE ~ {upperChar, digit, semicolon, leftPar, rightPar, blank};
global variables
emptyCmdNotOK: BOOLEANFALSE; --it looks as if it was forbidden AND used...
Stream Parser
Error: PUBLIC ERROR [msg: ROPE, cifFile: IO.STREAM] ~ CODE;
ClientError: PUBLIC ERROR [clientMsg: ROPE] ~ CODE;
Parse: PUBLIC PROC [cifStream: IO.STREAM, reg: Registration] ~ {
cifFile = {{blank} [command] semi} endCmd {blank}.
command = primCmd | defDeleteCmd | defStartCmd semi {{blank} [primCmd] semi} defFinishCmd
ENABLE ClientError => Error[Rope.Concat["client error: ", clientMsg], cifStream];
cifFile: Stream ← NEW[StreamRec ← [NIL, cifStream]];
cmd: Cmd;
state: BOOLEANFALSE;
DO
cmd ← GetCmd[cifFile];
SELECT cmd.type FROM
endCmd   => {CheckEnd[cifFile, state]; RETURN};
defDeleteCmd  => stateDefDelete[cifFile, reg, state];
defStartCmd   => stateDefStart[cifFile, reg, state];
defFinishCmd  => stateDefFinish[cifFile, reg, state];
primCmd   => ParsePrim[cmd.token, cifFile, reg];
emptyCmd  => IF emptyCmdNotOK THEN Error["empty cmd", cifFile.s];
ENDCASE => ERROR; -- CmdType def is not in sync with Parse
GoToNextCmd[cifFile];
ENDLOOP;
};
GetCmd: PROC [cifFile: Stream] RETURNS [cmd: Cmd] ~ {
c: CHAR;
c ← GetChar[cifFile];
cmd.token ← Rope.FromChar[c];
SELECT c FROM
'E  => cmd.type ← endCmd;
';  => cmd.type ← emptyCmd;
'D  => {
c ← GetChar[cifFile];
cmd.token ← Rope.Concat[cmd.token, Rope.FromChar[c]];
SELECT c FROM
'D  => cmd.type ← defDeleteCmd;
'S  => cmd.type ← defStartCmd;
'F  => cmd.type ← defFinishCmd;
ENDCASE => Error[Rope.Concat[cmd.token, " : invalid cmd "], cifFile.s];
};
ENDCASE => cmd.type ← primCmd;
};
GoToNextCmd: PROC [cifFile: Stream] ~ {
IF GetTokenKind[cifFile.lastToken]#semicolon THEN {
cifFile.lastToken ← GetToken[cifFile];
IF GetTokenKind[cifFile.lastToken]#semicolon THEN Error["wrong cmd syntax", cifFile.s];
};
};
Cmd Procs
CheckEnd: PROC[cifFile: Stream, state: BOOLEAN] ~ {
endCommand = "E".
IF state THEN Error["E inside symbol def ", cifFile.s];
};
DefDelete: PROC[cifFile: Stream, reg: Registration, state: BOOLEAN] RETURNS[newState: BOOLEAN ← FALSE] ~ {
defDeleteCmd = "D" {blank} "D" integer.
index: CARD;
IF state THEN Error["DD inside symbol def ", cifFile.s];
index ← GetCard[cifFile];
reg.defDelete[index, reg.data];
};
DefStart: PROC[cifFile: Stream, reg: Registration, state: BOOLEAN] RETURNS[newState: BOOLEAN ← TRUE] ~ {
defStartCmd = "D" {blank} "S" integer [sep integer sep integer].
index, a, b: CARD;
IF state THEN Error["DS inside symbol def ", cifFile.s];
index ← GetCard[cifFile];
a ← GetCardAfterSep[cifFile, TRUE];
b ← GetCardAfterSep[cifFile, TRUE];
reg.defStart[index, a, b, reg.data];
};
DefFinish: PROC[cifFile: Stream, reg: Registration, state: BOOLEAN] RETURNS[newState: BOOLEAN ← FALSE] ~ {
defFinishCmd = "D" {blank} "F".
IF NOT state THEN Error["DF outside symbol def ", cifFile.s];
reg.defFinish[reg.data];
};
ParsePrim: PROC [token: ROPE, cifFile: Stream, reg: Registration] ~ {
primCmd = polygonCmd | boxCmd | roundFlashCmd | wireCmd | layerCmd | callCmd | userExtensionCmd | commentCmd.
type: PrimCmdType ← GetPrimType[token];
SELECT type FROM
polygonCmd => Polygon[cifFile, reg];
boxCmd  => Box[cifFile, reg];
roundFlashCmd => RoundFlash[cifFile, reg];
wireCmd   => Wire[cifFile, reg];
layerCmd   => Layer[cifFile, reg];
callCmd   => Call[cifFile, reg];
userExtensionCmd  => UserExtension[token, cifFile, reg];
commentCmd  => Comment[token, cifFile, reg];
error   => Error[Rope.Cat[token," is not a CIF cmd"], cifFile.s];
ENDCASE => ERROR; -- PrimCmdType def is not in sync with ParsePrim
};
GetPrimType: PROC [token: ROPE] RETURNS [type: PrimCmdType] ~ {
type ← SELECT Rope.Fetch[token] FROM
'P  => polygonCmd,
'B  => boxCmd,
'R  => roundFlashCmd,
'W  => wireCmd,
'L  => layerCmd,
'C  => callCmd,
IN ['0..'9]  => userExtensionCmd,
'(  => commentCmd,
ENDCASE => error;
};
PrimCmd Procs
Polygon: PROC[cifFile: Stream, reg: Registration] ~ {
polygonCmd = "P" path.
path: Path;
path ← GetPath[cifFile];
reg.polygon[path, reg.data]
};
Box: PROC[cifFile: Stream, reg: Registration] ~ {
boxCmd = "B" integer sep integer sep point [sep point]..
length, width: CARD;
center, direction: Point;
length ← GetCard[cifFile];
width ← GetCardAfterSep[cifFile];
center ← GetPointAfterSep[cifFile];
direction ← GetPointAfterSep[cifFile, TRUE];
IF direction=noPoint THEN direction ← [1, 0];
reg.box[length, width, center, direction, reg.data]
};
RoundFlash: PROC[cifFile: Stream, reg: Registration] ~ {
roundFlashCmd = "R" integer sep point.
diameter: CARD;
center: Point;
diameter ← GetCard[cifFile];
center ← GetPointAfterSep[cifFile];
reg.roundFlash[diameter, center, reg.data]
};
Wire: PROC[cifFile: Stream, reg: Registration] ~ {
wireCmd = "W" integer sep path.
width: CARD;
path: Path;
width ← GetCard[cifFile];
path ← GetPathAfterSep[cifFile];
reg.wire[width, path, reg.data]
};
Layer: PROC[cifFile: Stream, reg: Registration] ~ {
layerCmd = "L" {blank} shortname.
shortname: ROPE;
shortname ← GetShortName[cifFile];
reg.layer[shortname, reg.data]
};
Call: PROC[cifFile: Stream, reg: Registration] ~ {
callCmd = "C" integer transformation.
symbol: CARD;
transformation: Transformation;
symbol ← GetCard[cifFile];
transformation ← GetTransformation[cifFile];
reg.call[symbol, transformation, reg.data]
};
UserExtension: PROC[token: ROPE, cifFile: Stream, reg: Registration] ~ {
userExtensionCmd = digit userText.
digit: CARD;
userText: ROPE;
digit ← Convert.IntFromRope[token];
userText ← GetUserText[cifFile];
reg.userExtension[digit, userText, reg.data]
};
Comment: PROC[token: ROPE, cifFile: Stream, reg: Registration] ~ {
commentCmd = "(" commentText ")".
comment: ROPE;
comment ← Rope.Cat[token, GetCommentText[cifFile]];
reg.comment[comment, reg.data]
};
Typed Get Functions
GetTransformation: PROC [cifFile: Stream] RETURNS [t: Transformation] ~ {
transformation = {{blank} ("T" point | "M" {blank} "X" | "M" {blank} "Y" | "R" point)}.
transf: Transformation;
t ← transf ← LIST[[translation, [0, 0]]];
DO
SELECT GetChar[cifFile] FROM
';  => RETURN[IF t.rest=NIL THEN t ELSE t.rest];
'T  => transf.rest ← LIST[[translation, GetPoint[cifFile]]];
'R => transf.rest ← LIST[[rotation, GetPoint[cifFile]]];
'M  => SELECT GetChar[cifFile] FROM
'X  => transf.rest ← LIST[[xSym, noPoint]];
'Y => transf.rest ← LIST[[ySym, noPoint]];
ENDCASE => Error["incorrect Mirror Transformation", cifFile.s];
ENDCASE => Error["incorrect Transformation", cifFile.s];
transf ← transf.rest;
ENDLOOP;
};
GetPath: PROC [cifFile: Stream] RETURNS [path: Path] ~ {
path = point {sep point}.
p: Path;
p ← path ← LIST[GetPoint[cifFile]];
DO
point: Point;
point ← GetPointAfterSep[cifFile, TRUE];
IF point=noPoint THEN RETURN;
p.rest ← LIST[point];
p ← p.rest;
ENDLOOP;
};
GetPathAfterSep: PROC [cifFile: Stream] RETURNS [path: Path] ~ {
path = point {sep point}.
p: Path;
p ← path ← LIST[GetPointAfterSep[cifFile]];
DO
point: Point;
point ← GetPointAfterSep[cifFile, TRUE];
IF point=noPoint THEN RETURN;
p.rest ← LIST[point];
p ← p.rest;
ENDLOOP;
};
GetPoint: PROC [cifFile: Stream, mayBeDefaulted: BOOLEANFALSE] RETURNS [point: Point] ~ {
point = sInteger sep sInteger
point.x ← GetInt[cifFile, mayBeDefaulted];
IF point.x=defaultInt THEN RETURN[noPoint];
point.y ← GetIntAfterSep[cifFile];
};
GetPointAfterSep: PROC [cifFile: Stream, mayBeDefaulted: BOOLEANFALSE] RETURNS [point: Point] ~ {
point.x ← GetIntAfterSep[cifFile, mayBeDefaulted];
IF point.x=defaultInt THEN RETURN[noPoint];
point.y ← GetIntAfterSep[cifFile];
};
GetInt: PROC [cifFile: Stream, mayBeDefaulted: BOOLEANFALSE] RETURNS [i: INT ← defaultInt] ~ {
sInteger = {sep} ["-"] integerD.
integerD = digit {digit}.
token: ROPE;
tokenKind: TokenKind ← GetTokenKind[cifFile.lastToken];
IF tokenKind=semicolon THEN IF mayBeDefaulted THEN RETURN ELSE Error["missing INT", cifFile.s];
token ← GetToken[cifFile];
tokenKind ← GetTokenKind[token];
SELECT tokenKind FROM
digit  => i ← Convert.IntFromRope[token];
upperChar  => i ← Convert.IntFromRope[GetToken[cifFile]];
semicolon  => IF mayBeDefaulted THEN RETURN ELSE Error["missing INT", cifFile.s];
ENDCASE => Error["wrong INT value", cifFile.s];
};
GetIntAfterSep: PROC [cifFile: Stream, mayBeDefaulted: BOOLEANFALSE] RETURNS [i: INT ← defaultInt] ~ {
sInteger = {sep} ["-"] integerD.
integerD = digit {digit}.
token: ROPE;
tokenKind: TokenKind ← GetTokenKind[cifFile.lastToken];
IF tokenKind=semicolon THEN IF mayBeDefaulted THEN RETURN ELSE Error["missing INT", cifFile.s];
token ← GetToken[cifFile];
tokenKind ← GetTokenKind[token];
SELECT tokenKind FROM
digit  => i ← Convert.IntFromRope[token];
upperChar  => i ← GetInt[cifFile, mayBeDefaulted];
semicolon  => IF mayBeDefaulted THEN RETURN ELSE Error["missing INT", cifFile.s];
ENDCASE => Error["wrong INT value", cifFile.s];
};
GetCard: PROC [cifFile: Stream, mayBeDefaulted: BOOLEANFALSE] RETURNS [c: CARD ← defaultCard] ~ {
integer = {sep} integerD.
integerD = digit {digit}.
token: ROPE;
tokenKind: TokenKind ← GetTokenKind[cifFile.lastToken];
IF tokenKind=semicolon THEN IF mayBeDefaulted THEN RETURN ELSE Error["missing CARD", cifFile.s];
token ← GetToken[cifFile];
tokenKind ← GetTokenKind[token];
SELECT tokenKind FROM
digit  => c ← Convert.CardFromRope[token];
upperChar  => c ← Convert.CardFromRope[GetToken[cifFile]];
semicolon  => IF mayBeDefaulted THEN RETURN ELSE Error["missing CARD", cifFile.s];
ENDCASE => Error["wrong CARD value", cifFile.s];
};
GetCardAfterSep: PROC [cifFile: Stream, mayBeDefaulted: BOOLEANFALSE] RETURNS [c: CARD ← defaultCard] ~ {
integer = {sep} integerD.
integerD = digit {digit}.
token: ROPE;
tokenKind: TokenKind ← GetTokenKind[cifFile.lastToken];
IF tokenKind=semicolon THEN IF mayBeDefaulted THEN RETURN ELSE Error["missing CARD", cifFile.s];
token ← GetToken[cifFile];
tokenKind ← GetTokenKind[token];
SELECT tokenKind FROM
digit  => c ← Convert.CardFromRope[token];
upperChar  => c ← GetCard[cifFile, mayBeDefaulted];
semicolon  => IF mayBeDefaulted THEN RETURN ELSE Error["missing CARD", cifFile.s];
ENDCASE => Error["wrong CARD value", cifFile.s];
};
GetShortName: PROC [cifFile: Stream] RETURNS [shortname: ROPE] ~ {
shortname = c[c][c][c].
c = digit | upperChar
shortname ← GetToken[cifFile];
IF Rope.Length[shortname]>4 THEN Error[Rope.Cat[shortname, "is a too long name"], cifFile.s];
};
GetUserText: PROC [cifFile: Stream] RETURNS [userText: ROPE] ~ {
userText = {userChar}.
userChar = any ASCII char except ";".
userText ← IO.GetTokenRope[cifFile.s, UserTextBreakProc].token;
cifFile.lastToken ← userText;
};
GetCommentText: PROC [cifFile: Stream] RETURNS [userText: ROPE ← NIL] ~ {
commentText = {commentChar} | commentText "(" commentText ")" commentText.
commentChar = any ASCII char except "(", or ")".
token: ROPE;
tokenKind: TokenKind ← blank;
level: NAT ← 1;
token ← IO.GetTokenRope[cifFile.s, CommentTextBreakProc].token;
tokenKind ← GetTokenKind[token];
UNTIL level=0 AND tokenKind=semicolon DO
userText ← Rope.Concat[userText, token];
IF tokenKind=leftPar THEN level ← level+1;
IF tokenKind=rightPar THEN level ← level-1;
token ← IO.GetTokenRope[cifFile.s, CommentTextBreakProc].token;
tokenKind ← GetTokenKind[token];
ENDLOOP;
cifFile.lastToken ← token;
};
Cedar IOs level
GetTokenKind: PROC [token: ROPE] RETURNS [tokenKind: TokenKind] ~ {
Select a type from the first char of the token
tokenKind ← SELECT Rope.Fetch[token] FROM
IN ['A..'Z]  => upperChar,
IN ['0..'9], '- => digit,
';  => semicolon,
'(  => leftPar,
')  => rightPar,
ENDCASE => blank;
};
GetChar: PROC [cifFile: Stream] RETURNS [c: CHAR] ~ {
Description of the procedure.
token: ROPE;
token ← IO.GetTokenRope[cifFile.s, CharBreakProc].token;
c ← Rope.Fetch[token];
cifFile.lastToken ← token;
};
GetToken: PROC [cifFile: Stream] RETURNS [token: ROPE] ~ {
Description of the procedure.
token ← IO.GetTokenRope[cifFile.s, BreakProc].token;
cifFile.lastToken ← token;
};
BreakProc: PROC [char: CHAR] RETURNS [IO.CharClass] ~ {
Description of the procedure.
RETURN[SELECT char FROM
IN ['A..'Z], IN ['0..'9], '- => other, -- "upperChar" or "digit"
'(, '), ';  => break, -- comments, minus or cmd separator
ENDCASE  => sepr -- discard everything else (including low case)
]
};
UserTextBreakProc: PROC [char: CHAR] RETURNS [IO.CharClass] ~ {
Description of the procedure.
RETURN[IF char='; THEN break ELSE other]
};
CommentTextBreakProc: PROC [char: CHAR] RETURNS [IO.CharClass] ~ {
Description of the procedure.
RETURN[SELECT char FROM
'(, '), ';  => break, -- comments or cmd separator
ENDCASE  => other -- keep everything else
]
};
CharBreakProc: PROC [char: CHAR] RETURNS [IO.CharClass] ~ {
Description of the procedure.
RETURN[SELECT char FROM
IN ['A..'Z], IN ['0..'9], '-,'(, '), '; => break, -- any meaningful char
ENDCASE => sepr -- discard everything else
]
};
Transformation Utilities
IsOrthogonalTransform: PUBLIC PROC [m: TMatrix] RETURNS [BOOL] = {
checks transformation to make sure any rotation is a multiple of 90 deg
RETURN[(m.a11 = 0 AND m.a22 = 0) OR (m.a21 = 0 AND m.a12 = 0)];
};
MatMult: PUBLIC PROC [m1, m2: TMatrix] RETURNS [mProd: TMatrix] ~ {
mProd.a11 ← m1.a11*m2.a11 +m1.a12*m2.a21;
mProd.a21 ← m1.a21*m2.a11 +m1.a22*m2.a21;
mProd.a31 ← m1.a31*m2.a11 +m1.a32*m2.a21 +m1.a33*m2.a31;
mProd.a12 ← m1.a11*m2.a12 +m1.a12*m2.a22;
mProd.a22 ← m1.a21*m2.a12 +m1.a22*m2.a22;
mProd.a32 ← m1.a31*m2.a12 +m1.a32*m2.a22 +m1.a33*m2.a32;
a31
a32
mProd.a33 ←       m1.a33*m2.a33;
};
TransformPt: PUBLIC PROC [m: TMatrix, p: Point] RETURNS [newP: Point] ~ {
newP.x ← Real.Round[m.a11*p.x+m.a21*p.y+m.a31];
newP.y ← Real.Round[m.a21*p.x+m.a22*p.y+m.a32];
};
TransfToMatrix: PUBLIC PROC [t: Transformation] RETURNS [m: TMatrix] ~ {
m ← GetTranslation[[0, 0]];
UNTIL t=NIL DO
SELECT t.first.t FROM
translation  => IF t.first.p#[0, 0] THEN m ← MatMult[m, GetTranslation[t.first.p]];
rotation  => IF t.first.p#[0, 0] THEN m ← MatMult[m, GetRotation[t.first.p]];
xSym  => m ← MatMult[m, GetMirrorX[]];
ySym  => m ← MatMult[m, GetMirrorY[]];
ENDCASE => ERROR;
t ← t.rest;
ENDLOOP;
};
GetTranslation: PROC [p: Point] RETURNS [m: TMatrix] ~ {
m.a11 ← 1.0;  m.a12 ← 0.0;
m.a21 ← 0.0;  m.a22 ← 1.0;
m.a31 ← p.x;  m.a32 ← p.y;  m.a33 ← 1.0;
};
GetMirrorX: PROC RETURNS [m: TMatrix] ~ {
m.a11 ← -1.0;  m.a12 ← 0.0;
m.a21 ← 0.0;  m.a22 ← 1.0;
m.a31 ← 0.0;  m.a32 ← 0.0;  m.a33 ← 1.0;
};
GetMirrorY: PROC RETURNS [m: TMatrix] ~ {
m.a11 ← 1.0;  m.a12 ← 0.0;
m.a21 ← 0.0;  m.a22 ← -1.0;
m.a31 ← 0.0;  m.a32 ← 0.0;  m.a33 ← 1.0;
};
GetRotation: PROC [p: Point] RETURNS [m: TMatrix] ~ {
c: REAL ← Real.SqRt[p.x*p.x+p.y*p.y];
m.a11 ← p.x/c;  m.a12 ← p.y/c;
m.a21 ← -m.a12;  m.a22 ← m.a11;
m.a31 ← 0.0;  m.a32 ← 0.0;  m.a33 ← 1.0;
};
END.