HerculesAlgebraImpl.mesa

Last Edited by: Stolfi, February 28, 1984 2:40:47 am PST

Was (mostly) JunoAlgebraImplB
Written July, 1982 by Donna M. Auguste and Greg Nelson
Last Edited by: Gnelson, October 11, 1983 9:50 pm

Evaluator/interpreter for symbolic expressions. This module defines the semantics of Juno/Hercules language (and, implicitly, its syntax)

To do:
Procedure for expanding an IF statement or a simple statement and adding its contents to the current image. (February 15, 1984 1:21 am)

DIRECTORY

HerculesStorage,
HerculesAlgebra,
HerculesImage,
HerculesSolver,
HerculesGraphics,
Graphics USING[black, white, Color, StrokeEnds],
Real USING [RoundLI, RoundI, FRem, SqRt],
Rope,
Convert USING [RopeFromInt, RopeFromReal],
Atom;

HerculesAlgebraImpl: PROGRAM

IMPORTS
HerculesGraphics, HerculesImage, HerculesStorage, HerculesSolver, Atom, Real, Convert
EXPORTS
HerculesAlgebra

=

BEGIN

OPEN
Stor: HerculesStorage,
Gr: HerculesGraphics,
Im: HerculesImage,
Solv: HerculesSolver,
HerculesAlgebra;

- - - - RESERVED ATOMS
and: PUBLIC ATOM ← $and;
approx: PUBLIC ATOM ← Atom.MakeAtom["=="];
assign: PUBLIC ATOM ← Atom.MakeAtom[":="];
at: PUBLIC ATOM ← $at;
blow: PUBLIC ATOM ← Atom.MakeAtom["!"];
ccw: PUBLIC ATOM ← $ccw;
colon: PUBLIC ATOM ← Atom.MakeAtom[":"];
comma: PUBLIC ATOM ← Atom.MakeAtom[","];
cong: PUBLIC ATOM ← $cong;
div: PUBLIC ATOM ← $div;
do: PUBLIC ATOM ← $do;
draw: PUBLIC ATOM ← $draw;
ends: PUBLIC ATOM ← $ends;
equals: PUBLIC ATOM ← Atom.MakeAtom["="];
fill: PUBLIC ATOM ← $fill;
font: PUBLIC ATOM ← $font;
gtr: PUBLIC ATOM ← Atom.MakeAtom[">"];
hor: PUBLIC ATOM ← $hor;
if: PUBLIC ATOM ← $if;
is: PUBLIC ATOM ← $is;
leftBrack: PUBLIC ATOM ← Atom.MakeAtom["["];
leftPren: PUBLIC ATOM ← Atom.MakeAtom["("];
lss: PUBLIC ATOM ← Atom.MakeAtom["<"];
minus: PUBLIC ATOM ← Atom.MakeAtom["-"];
mod: PUBLIC ATOM ← $mod;
obox: PUBLIC ATOM ← Atom.MakeAtom["//"];
paint: PUBLIC ATOM ← $paint;
para: PUBLIC ATOM ← $para;
perp: PUBLIC ATOM ← $perp;
plus: PUBLIC ATOM ← Atom.MakeAtom["+"];
print: PUBLIC ATOM ← $print;
rel: PUBLIC ATOM ← $rel;
rightArrow: PUBLIC ATOM ← Atom.MakeAtom["->"];
rightBrack: PUBLIC ATOM ← Atom.MakeAtom["]"];
semicolon: PUBLIC ATOM ← Atom.MakeAtom[";"];
size: PUBLIC ATOM ← $size;
skip: PUBLIC ATOM ← $skip;
slash: PUBLIC ATOM ← Atom.MakeAtom["/"];
stroke: PUBLIC ATOM ← $stroke;
style: PUBLIC ATOM ← $style;
suchThat: PUBLIC ATOM ← Atom.MakeAtom["|"];
times: PUBLIC ATOM ← Atom.MakeAtom["*"];
true: PUBLIC ATOM ← $T;
ver: PUBLIC ATOM ← $ver;
width: PUBLIC ATOM ← $width;

- - - - GLOBAL ALIST

basicGlobals: PUBLIC Alist ← InitGlobals[]; -- (name value name value ... )

InitGlobals
: PROC RETURNS [Alist] =

BEGIN
N000: NumPtr = NEW [NumCell ← [val: 000, const: TRUE, int: TRUE]];
N064: NumPtr = NEW [NumCell ← [val: 064, const: TRUE, int: TRUE]];
N128: NumPtr = NEW [NumCell ← [val: 128, const: TRUE, int: TRUE]];
N192: NumPtr = NEW [NumCell ← [val: 192, const: TRUE, int: TRUE]];
N224: NumPtr = NEW [NumCell ← [val: 224, const: TRUE, int: TRUE]];
N255: NumPtr = NEW [NumCell ← [val: 255, const: TRUE, int: TRUE]];

RETURN [LIST[

-- color values
$black,   LIST [N000, N000, N000],
$white,   LIST [N255, N255, N255],
$grey,    LIST [N128, N128, N128],
$red,    LIST [N255, N000, N000],
$blue,    LIST [N000, N000, N255],
$green,   LIST [N000, N255, N000],
$darkred,   LIST [N192, N064, N000],
$darkblue,  LIST [N000, N064, N192],
$darkgreen,  LIST [N000, N192, N064],
$lightred,   LIST [N255, N128, N128],
$lightblue,  LIST [N128, N192, N255],
$lightgreen,  LIST [N192, N255, N128],
$yellow,   LIST [N255, N255, N000],
$cyan,    LIST [N000, N255, N255],
$magenta,   LIST [N255, N000, N255],
$darkyellow,  LIST [N224, N192, N000],
$darkcyan,  LIST [N000, N192, N192],
$darkmagenta, LIST [N192, N000, N192],
$lightyellow,  LIST [N255, N255, N128],
$lightcyan,  LIST [N128, N255, N255],
$lightmagenta, LIST [N255, N128, N255],

-- styles and fonts
$TimesRoman, "TimesRoman",
$Helvetica, "Helvetica",
$Gacha, "Gacha",
$roman, NEW[NumCell ← [val:0]],
$italic, NEW[NumCell ← [val:1]],
$bold, NEW[NumCell ← [val:2]],
$boldItalic, NEW[NumCell ← [val:3]]

-- stroke end types
$butt, $butt,
$square, $square,
$round, $round,

-- basic operators
comma, comma,
semicolon, semicolon,
plus, plus,
minus, minus,
times, times,
slash, slash,
div, div,
mod, mod,
paint, paint,
draw, draw,
fill, fill,
print, print,
leftPren, leftPren,
leftBrack, leftBrack,
assign, assign,
quote, quote,
blow, blow,
skip, skip,
size, size,
font, font,
style, style,
colon, colon,
if, if,
do, do,
list, list]]
END;

globals: PUBLIC Alist ← basicGlobals; -- (name value name value ... )

AddGlobalDef
: PUBLIC PROC [name: ATOM, value: Value] =

BEGIN
p: Alist ← globals;
WHILE p # NIL DO
IF p.first = name THEN
{p.rest.first ← value; RETURN};
p ← p.rest.rest
ENDLOOP;
globals ← InsertDef[name, value, globals]
END;

- - - - EVAL

EvApError: PUBLIC ERROR [why: ROPE] = CODE;

Eval: PUBLIC PROC [e: Se, alist: Alist] RETURNS [results: Results] =
-- NOTE: assumes/guarantees that each call to Eval allocates fresh cells for the
-- topmost level of results.

BEGIN

IF e = NIL THEN -- what should it be?
{ERROR}
ELSE WITH e SELECT FROM

ee: FunPtr =>

{results ← LIST[ee]};

ee: NumPtr =>

{results ← LIST[ee]};

ee: RopePtr =>

{results ← LIST[ee]};

ee: ATOM =>

{results ← EvalAtom[ee, alist, mode]};

ee: LIST OF Se =>

BEGIN

len: INT = List.Length[ee];
funcs: Results = Eval[ee.first, alist]; -- usually with only one element
IF funcs = NIL OR funcs.rest # NIL THEN
{ERROR EvApError};

SELECT len FROM

1 => -- zerofix operator ([] and () only)

BEGIN
SELECT TRUE FROM

func.first = leftPren =>

{results ← NIL};

func.first = leftBrack =>

{results ← LIST[NIL]};

ENDCASE => ERROR EvApError;

END;

2 => -- matchfix or prefix (or postfix) operator

BEGIN
SELECT TRUE FROM

func.first = if =>

{results ← EvalIfList[Unnest[ee.rest.first, obox, NIL], alist]};

func.first = do =>

{results ← EvalDoList[Unnest[ee.rest.first, obox, NIL], alist]};

func.first = quote =>

{results ← LIST[ee.rest.first]};

ENDCASE =>

{-- Other prefix/postfix operators.
-- Evaluate function and arguments and call apply.
args: Results ← Eval[ee.rest.first, alist];
result ← Apply [funcs.first, args]};

END;

3 => -- infix operator

BEGIN
SELECT TRUE FROM

func.first = colon =>

{results ← LIST [NEW[FunRec ←
[body: ee.rest.rest.first,
parms: Unnest[ee.rest.first, comma, NIL],
alist: alist]]]};

List.Memb[func.first, pushStateOps] =>

{-- Must push the graphics context before evaluating
-- the right argument, and pop it afterwards
largs: Results ← Eval[ee.rest.first, alist, value];
IF largs = NIL OR largs.rest # NIL THEN ERROR EvApError;
PushGraphicsParm[NARROW[func.first], largs.first];
results ← Eval [ee.rest.rest.first, alist, mode];
PopGraphicsParm[NARROW[func.first]]};

ENDCASE =>

{-- Other infix operators
largs: Results ← Eval [ee.rest.first, alist];
rargs: Results ← Eval [ee.rest.rest.first, alist];
results ← ApplyBinary [funcs.first, largs, rargs]};

END;

ENDCASE => ERROR EvApError;

END;

ENDCASE => ERROR EvApError

END;

EvalAtom
: PUBLIC PROC [atom: ATOM, alist: Alist] RETURNS [results: Results] =

BEGIN
-- Atoms normally evaluate to a single value, obtained from alist or globals.
-- However, the primitive atom skip evaluates to no value.
val: Se = GetDef[atom, alist, globals];
IF val = skip THEN results ← NIL
ELSE results ← LIST[val]
END;

EvalIfList
: PROC [elist: LIST OF Se, alist: Alist] RETURNS [results: Results] =

BEGIN
-- Each element of elist is of the form (then (st <locals> <constaints>) <command>)
alts: LIST OF Se ← elist;
success, impossible: BOOLFALSE;
solution: Alist;
stNode: Se ← NIL; -- (st <locals> <constaints>)
choice: Se ← NIL; -- last alternative that wasn't success or impossible
choiceSol: Alist; -- solution corresponding to choice
WHILE alts # NIL AND NOT success DO
stNode ← Cadr[alts.first];
[success, impossible, solution] ← TryToSolve [stNode, alist];
IF NOT impossible THEN
{choice ← alts.first; choiceSol ← solution};
IF success OR alts.rest = NIL THEN -- about to finish
{IF choice = NIL THEN -- all alternatives returned impossible
{Gr.Blink["IF statement aborted!"];
results ← NIL}
ELSE
{results ← Eval [Caddr[choice], choiceSol]}};
alts ← alts.rest;
ENDLOOP
END;

EvalDoList
: PROC [elist: LIST OF Se, alist: Alist] RETURNS [results: Results] =

BEGIN
-- Each element of f is of the form (then (st <locals> <constaints>) <command>)
alts: LIST OF Se;
success, impossible: BOOLFALSE;
solution: Alist;
stNode: Se ← NIL; -- (st <locals> <constaints>)
results ← NIL;
DO
alts ← elist;
UNTIL success OR alts = NIL DO
stNode ← Cadr[alts.first];
[success, impossible, solution] ← TryToSolve [stNode, alist];
IF success THEN
{results ← List.NConc [results, Eval [Caddr[alts.first], solution]]};
alts ← alts.rest
ENDLOOP;
IF NOT success THEN RETURN
ENDLOOP
END;

TryToSolve
: PROC [stNode: Se, alist: LIST OF Se]
RETURNS [success, impossible: BOOLFALSE, newAlist: Alist] =

BEGIN
-- stNode is of the form (st <locals> <constraints>) or just <constraints>
localDecls, constrs: LIST OF Se ← NIL;
newCells: LIST OF NumPtr; -- variables (& point coords) to be adjusted by the solver
IF IsBinAppl[suchThat].fits THEN
{localDecls: LIST OF Se ← Unnest[Cadr[stNode], comma, NIL];
-- Create all local points (at hinted positions) and put them in front of the alist
[newCells, newAlist] ← CreateLocals [locals, alist];
constrs ← Unnest[Caddr[stNode], and, true]}
ELSE
{newAlist ← alist;
newCells ← NIL;
constrs Unnest[stNode, and, true]};

-- Solve the constraints for the local points
[success, impossible] ← Solv.Solve[newCells, constrs];
-- return
RETURN[success, impossible, newAlist]
END;

CreateLocals: PROC [localDecls: LIST OF Se, alist: Alist]
RETURNS [newCells: LIST OF NumPtr, newAlist: Alist] =
-- Creates local variables (adding them to the alist) and makes a list of all NumCells
-- occuring in thir defined value.
-- Those are the NumCells whose val fields the solver will try to adjust.

BEGIN
-- Each element of locals has the forms
-- (<approx> <name> <value>)
-- where <value> is some expression.
-- Acceptable <value>s are: NumPtr, RopePtr, atom, or list of acceptable values
-- (in other words, no FunPtrs!).
-- Now allows hints to be relative to previous hints. May Greg forgive me for this... [JS]
decls: LIST OF Se ← localDecls;
var, hint, hintVal: Se;
fits: BOOL;
newAlist ← alist;
newCells ← NIL;
UNTIL decls = NIL DO
[var, hint, fits] ← IsBinAppl[decls.first, approx];
IF NOT fits THEN ERROR;
[hintVal, newCells] ← CreateVariable[Eval[hint, newAlist], newCells];
newAlist ← CONS[var, CONS[hintVal, newAlist]];
decls ← decls.rest
ENDLOOP
END;

CreateVariable: PROC [val: Se, prevCells: LIST OF NumPtr]
RETURNS [cval: Se, newCells: LIST OF NumPtr] =
-- Creates a copy of val, copying all NumCells contained in it.
-- Prepends to prevCells a list of all NumCells created during the copy.

BEGIN
newCells ← prevCells;
IF val = NIL THEN
{cval ← NIL}
ELSE WITH val SELECT FROM
nn: NumPtr =>
{cval ← NEW [NumCell ← [val: nn.val, int: FALSE, const: FALSE]];
newCells ← CONS [cval, newCells]};
rr: RopePtr =>
{cval ← NEW [RopeCell ← [val: rr.val, const: FALSE]]};
aa: ATOM =>
{cval ← aa};
ll: LIST OF Se =>
{rval: LIST OF Se;
fval: Se;
[fval, newCells] ← CreateVariable[ll.first, newCells];
[rval, newCells] ← CreateVariable[ll.rest, newCells];
cval ← IF fval # ll.first OR rval#ll.rest THEN CONS [fval, rval] ELSE val};
ENDCASE => Gr.Error["invalid hint for local variable"]
END;

PushGraphicsParm: PROC [parm: ATOM, value: Se] =

BEGIN
-- Sets a graphics parameter to a new value, saving the old one.

SELECT parm FROM

font => Gr.PushFont[ToRope[value]];

size => Gr.PushSize[ToNum[value]];

style => Gr.PushStyle[ToStyle[value]];

ends => Gr.PushEnds[ToStrokeEnds[value]];

paint => Gr.PushColor[ToColor[value]];

width => Gr.PushWidth[ToWidth[value]];

ENDCASE => ERROR EvApError

END;

PopGraphicsParm: PROC [parm: ATOM] =

BEGIN
-- Restores a graphics parameter to its old value.

SELECT parm FROM

font => Gr.PopFont[];

size => Gr.PopSize[];

style => Gr.PopStyle[];

ends => Gr.PopEnds[];

paint => Gr.PopColor[];

width => Gr.PopWidth[];

ENDCASE => ERROR EvApError

END;

ToStyle: PROC[s: Se] RETURNS [style: Gr.Style] =

BEGIN
ss: INT = ToInt[s];
style.italic ← (ss MOD 2 # 0);
style.bold ← ((ss DIV 2) MOD 2 # 0);
END;

ToWidth
: PROC[w: Se] RETURNS [REAL] =

BEGIN
WITH w SELECT FROM
fr: NumPtr => RETURN [fr.val];
ff: LIST OF Value =>
{p, q: Coords;
CheckForm[w, pointPairMold];
p ← ToCoords [ff.first];
q ← ToCoords [ff.rest.first];
RETURN [Real.SqRt[(p.x - q.x) * (p.x - q.x) + (p.y - q.y) * (p.y - q.y)]]};
ENDCASE => ERROR
END;

ToColor
: PROC[c: Se] RETURNS [Graphics.Color] =

BEGIN
ToByte: PROC[r: REF ANY] RETURNS [[0..256)] =
{RETURN[Real.RoundI[MAX[0.0, MIN[255.0, ToReal[r]]]]]};

triple: LIST OF Se ← ToList[c, 3, 3]];
RETURN
[[r: ToByte[triple.first],
g: ToByte[triple.rest.first],
b: ToByte[triple.rest.rest.first] ]]
END;

ToStrokeEnds
: PROC[r: Se] RETURNS [Graphics.StrokeEnds] =

BEGIN
SELECT TRUE FROM
r = $butt => RETURN[butt];
r = $square => RETURN[square];
r = $round => RETURN[round]
ENDCASE => ERROR
END;

- - - - APPLY

Apply
: PUBLIC PROC [function: Se, args: Results] RETURNS [results: Results] =

BEGIN
WITH function SELECT FROM
ff: ATOM =>
{results ← ApplyAtom[ff, args]};
ff: NumPtr =>
{<require length[args] = 1, n=ff.val to be integer;
fetch Cad^{n-1}r[args.first] or Cd^{-n}r[args.first], as appropriate>}
ff: FunPtr =>
{results ← ApplyFunVal [ff, args]};
ENDCASE =>
{Gr.Error["Invalid function"]; result ← NIL}
END;

ApplyBinary
: PUBLIC PROC [function: Se, largs, rargs: Results] RETURNS [results: Results] =

BEGIN
WITH function SELECT FROM
ff: ATOM =>
{results ← ApplyBinaryAtom[ff, args]};
ff: FunPtr =>
{results ← ApplyFunVal [ff, largs];
IF results = NIL OR results.rest # NIL OR NOT ISTYPE[results.first, FunPtr]
THEN ERROR;
results 𡤊pplyFunVal [NARROW[results.first], rargs]};
ENDCASE =>
{Gr.Error["Invalid function"]; result ← NIL}
END;

ApplyAtom
: PUBLIC PROC [function: ATOM, args: Results] RETURNS [results: Results] =

BEGIN
SELECT function FROM

blow =>

{-- args should be a LIST[<a single list L>]
IF args = NIL OR args.rest # NIL THEN ERROR EvApError;
results ← List.Copy[NARROEW[args.first]};

minus =>

{-- arg should be a NumPtr or list thereof
MinusIt: LeafMapProc =
{RETURN[WITH e SELECT FROM
nn: NumPtr => NEW[NumCell ← [val: -nn.val, int: nn.int, const: TRUE]],
ENDCASE => e]};

results ← MapLeaves[args, MinusIt]};

print =>

{-- arg should be a list (text, pt)
IF List.Length[args] # 2 THEN ERROR ParmNumberError;
r: Rope.ROPE ← ToRope[g.first];
p: Coords ← ToNumPair[g.rest.first];
Gr.DrawString[p.x, p.y, r];
results ← NIL};

draw, stroke, fill =>

{-- arg should be a list of lists of points
patch: LIST OF Se;
DrawTwo: PROC [p, q: Coords] =
{IF function = draw THEN
{Gr.DrawEdge[p.x, p.y, q.x, q.y]}
ELSE
{Gr.EdgeStroke[p.x, p.y, q.x, q.y]}};
DrawFour: PROC [p, r, s, q: Coords] =
{IF function = draw THEN
{Gr.DrawArc[p.x, p.y, r.x, r.y, s.x, s.y, q.x, q.y]}
ELSE
{Gr.ArcStroke[p.x, p.y, r.x, r.y, s.x, s.y, q.x, q.y]}};
results ← NIL;
IF function = stroke OR function = fill THEN
{Gr.BeginStroke[]};
WHILE args # NIL DO
patch ← ToList[args.first, 2, 4];
IF patch.rest.rest = NIL THEN
{DrawTwo[ToCoords[patch.first], ToCoords[patch.rest.first]]}
ELSE IF patch.rest.rest.rest = NIL THEN Gr.Error ["draw: wrong number of args"]
ELSE IF patch.rest.rest.rest.rest = NIL THEN
{DrawFour
[ToCoords [patch.first], ToCoords [patch.rest.first],
ToCoords [patch.rest.rest.first], ToCoords [patch.rest.rest.rest.first]]}
ELSE Gr.Error ["draw: wrong number of args"];
args ← args.rest
ENDLOOP;
IF function = stroke THEN
{Gr.DrawStroke[]}
ELSE IF function = fill THEN
{Gr.DrawArea}};

leftPren =>

{results ← args};

leftBrack, list =>

{results ← LIST[args]};

ENDCASE =>

{Gr.Error["Unknown function: ", Atom.GetPName[function]]}

END;

ApplyBinaryAtom
: PROC [op: ATOM, largs, rargs: Results] RETURNS [results: Results] =

BEGIN
SELECT op FROM

assign =>

{DoAssign: PROC [p, q: Se] =
BEGIN
WITH p SELECT FROM
pp: NumPtr =>
{WITH q SELECT FROM
qq: NumPtr =>
{IF NOT qq.const THEN
{qq.val ← pp.val;
IF qq.int AND pp.val < ???
THEN qq.val ← Real.RoundLI[qq.val]}};
ENDCASE => ERROR ???};
pp: RopePtr =>
{WITH q SELECT FROM
qq: RopePtr =>
{IF NOT qq.const THEN
{qq.val ← pp.val}};
ENDCASE => ERROR ???};
pp: LIST OF Se =>
{WITH q SELECT FROM
qq: LIST OF Se =>
{WHILE pp # NIL AND qq # NIL DO
DoAssign[pp.first, qq.first];
pp ← pp.rest; qq ← qq.rest
ENDLOOP;
IF pp# NIL OR qq # NIL THEN
{Gr.Error["Assignment length mismatch"]}};
ENDCASE => ERROR ???};
ENDCASE => ERROR ??? -- Oh wonderful Cedar...
END;
results ← NIL;
DoAssign[largs, rargs]};

plus, times, minus, slash, div, mod =>

{<do this for each component>
a: REAL ← ToReal[largs];
b: REAL ← ToReal[rargs];
c: REALSELECT op FROM
plus => a+b,
minus => a-b,
times => a*b,
slash => a/b,
div => (a - Real.FRem[a, b])/b,
mod => Real.FRem[a, b],
ENDCASE => ERROR;
ValueOnly[mode];
result ← MakeNumber
[c, ISTYPE [largs, REF INT] AND ISTYPE[rargs, REF INT] AND op # slash]};

rel =>

{<check that largs is a single argument>
-- in actions or hints (not in constraints).
-- Transforms number pair to specified frame.

coords: Coords = ToCoords[largs];
frame: Frame ← ToFrame[rargs];
newc: Coords;
mFrame ← GetFrameMatrix[frame, mFrame];
newc ← TransformPoint [coords, mFrame];
results ← ~~~};

comma =>

{results ← DConc[largs, rargs]};

semicolon =>

{results ← rargs};

ENDCASE =>

{Gr.Error["Unknown infix operator: ", Atom.GetPName[op]]}

END;

ApplyFunVal
: PUBLIC PROC[funVal: FunPtr, args: Results] RETURNS [results: Results] =

BEGIN
newAlist: Alist = BindArgs !!! CATCH ERROR
[funVal.parms, args, funVal.alist];
results ← Eval[funVal.body, newAlist]
END;

listOfPointMold: Mold = NEW [ListMoldRec ← [min: 1, eMold: $PointPtr]];

mFrame: REF Matrix ← NEW[Matrix]; -- temp for frame conversion

drawParmsMold: Mold = NEW [ListMoldRec ← [min: 1, eMold: patchMold]];

patchMold: Mold = NEW [UnionRec ←
[alts: LIST
[LIST [$PointPtr, $PointPtr],
LIST [$PointPtr, $PointPtr, $PointPtr, $PointPtr]]]];

pointPairMold: Mold = LIST [$PointPtr, $PointPtr];

widthSpecMold: Mold = NEW [UnionRec ← [alts: LIST[$REFNUM, pointPairMold]]];

numTripletMold: Mold ← LIST [$REFNUM, $REFNUM, $REFNUM];

- - - - SOME USEFUL MOLDS AND PREDICATES

pointMold: PUBLIC Mold ← LIST [$NUM, $NUM];

frameMold: PUBLIC Mold ← NEW [VectorMoldRec ← [min: 1, max: 3, elm: pointMold]];

edgeMold: PUBLIC Mold ← NEW [VectorMoldRec ← [min: 2, max: 2, elm: pointMold]];

arcMold: PUBLIC Mold ← NEW [VectorMoldRec ← [min: 4, max: 4, elm: pointMold]];

pathMold: PUBLIC Mold ← NEW [VectorMoldRec ← [min: 1, elm: pointMold]];

ToNumPair
: PUBLIC PROC [e: Se] RETURNS [p: NumPair] =

{ee: LIST OF Se ← ToList[e, 2, 2];
IF ee.first = NIL OR NOT ISTYPE [ee.first, NumPtr] OR
ee.rest.first = NIL OR NOT ISTYPE [ee.rest.first, NumPtr] THEN
ERROR InvalidSe[e];
p.x ← NARROW[ee.first];
p.y ← NARROW[ee.rest.first]};

nullFrame: PUBLIC Frame ← [0, [NIL, NIL], [NIL, NIL], [NIL, NIL]];

ToFrame
: PUBLIC PROC [e: Se] RETURNS [frame: Frame] =

BEGIN
list: LIST OF Se ← ToList [e, 1, 3];
frame ← nullFrame;
frame.np ← 1;
frame.org ← ToNumPair[list.first];
list ← list.rest;
IF list # NIL THEN
{frame.np ← 2;
frame.hor ← NARROW[list.first];
list ← list.rest;
IF list # NIL THEN
{frame.np ← 3;
frame.ver ← NARROW[list.first]}}
END;

ToReal
: PUBLIC PROC [e: Se] RETURNS [r: REAL] =

{WITH e SELECT FROM
en: NumPtr => RETURN[en.val]
ENDCASE => ERROR InvalidSe[e]};

ToCoords
: PUBLIC PROC [e: Se] RETURNS [c: Coords] =

{p: NumPair ← ToNumPair[e];
RETURN[p.x.val, p.y.val]};

- - - - COORDINATE TRANSFORMATION MATRICES

m1: REF Matrix ← NEW[Matrix]; -- Work matrices

mInv: REF Matrix ← NEW[Matrix];

GetFrameMatrix
: PUBLIC PROC [frame: Frame, m: REF Matrix ← NIL]
RETURNS [mr: REF Matrix] =
-- Returns the coordinate transform matrix for the frame determined
-- by the points
frame.org, frame.xP, frame.yP (the last one or two may be NIL).
-- The resulting matrix satisfies
m*[0,0,1]^T= org, m*[1,0,1]^T= xP, m*[1,0,1]^T= yP.
-- The optional matrix m^ is used if not NIL, otherwise a new one is allocated.

BEGIN
IF m = NIL THEN m ← NEW [Matrix];
m^[1][3] ← frame.org.x;
m^[2][3] ← frame.org.y;
m^[3][3] ← 1;

IF frame.xP=NIL THEN
{m^[1][1] ← 300;
m^[2][1] ← 0}
ELSE
{m^[1][1] ← frame.xP.x-frame.org.x;
m^[2][1] ← frame.xP.y-frame.org.y};
m^[3][1] ← 0;
IF frame.yP=NIL THEN
{m^[1][2] ← -m^[2][1];
m^[2][2] ← m^[1][1]}
ELSE
{m^[1][2] ← frame.yP.x-frame.org.x;
m^[2][2] ← frame.yP.y-frame.org.y};
m^[3][2] ← 0;
RETURN[m]
END;

InvertMatrix
: PUBLIC PROC [m: REF Matrix, work: REF Matrix ← NIL]
RETURNS [mInv: REF Matrix, singular: BOOL] =

BEGIN
-- Inverts m^ into mInv^ by pivoting three times; or sets "singular" flag.
-- Uses work if not NIL, otherwise allocates new matrix for result.
i, j, k, l: INTEGER;
c: ARRAY [1..3] OF INTEGER;
pivoted: ARRAY [1..3] OF BOOLEAN ← [FALSE, FALSE, FALSE];
p: REAL;
-- k is the row in which we are pivoting.
-- l is the column in which we are pivoting.
-- i and j are miscellaneous row and column indices respectively
-- c[i] is the column of the pivot in the ith row.
-- p is the reciprocal of the pivot element; also used as temp for swapping.
IF work = NIL THEN work ← NEW[Matrix];
FOR k IN [1..3] DO
-- set l so m^[k,l] is largest of m^[k,1], m^[k, 2], m^[k, 3], excluding
-- columns in which we have already pivoted.
p ← 0;
FOR j IN [1 .. 3]
DO IF ABS[m^[k][j]] >= p AND NOT pivoted[j] THEN {l ← j; p ← ABS[m^[k][l]]} ENDLOOP;
-- We will pivot at m^[k,l], if it is not too small:
IF ABS[m^[k][l]] < .0001 THEN RETURN[NIL, TRUE];
c[k] ← l; pivoted[l] ← TRUE;
p ← 1.0 / m^[k][l]; m^[k][l] ← 1.0;
-- divide everything in pivot row by the pivot element:
FOR j IN [1..3] DO m^[k][j] ← m^[k][j] * p ENDLOOP;
FOR i IN [1..3] DO
IF i # k THEN
FOR j IN [1..3] DO
IF j # l THEN -- for each m^[i,j] outside the pivot row and column
  m^[i][j] ← m^[i][j] - m^[i][l] * m^[k][j]; -- note that m^[k,j] was already * p.
ENDLOOP ENDLOOP;
-- Finally process pivot column:
FOR i IN [1..3] DO IF i # k THEN m^[i][l] ← -m^[i][l] * p ENDLOOP;
ENDLOOP;
-- Now we permute rows and columns:
FOR i IN [1..3] DO FOR j IN [1..3] DO work^[c[i]][j] ← m^[i][c[j]] ENDLOOP ENDLOOP;
RETURN[work, FALSE]
END;

MultiplyMatrix
: PUBLIC PROC [ma, mb: REF Matrix, mc: REF Matrix ← NIL]
RETURNS [mr: REF Matrix] =
-- Multiply ma^ * mb^.
-- Matrix
mc is used if not NIL, otherwise allocates new one.

BEGIN
i, j, k: INTEGER;
sum: REAL;
IF mc = NIL THEN mc ← NEW [Matrix];
FOR i IN [1..3] DO FOR j IN [1..3] DO
sum ← 0.0;
FOR k IN [1..3] DO sum ← sum + ma^[i][k] * mb^[k][j] ENDLOOP;
mc^[i][j] ← sum
ENDLOOP ENDLOOP;
RETURN[mc]
END;

ComputeTransform
: PUBLIC PROC
[src, dest: Frame, mm: REF Matrix ← NIL]
RETURNS [mat: REF Matrix, singular: BOOL] =

BEGIN
-- We want
--
mat * [ src.org, src.xP, src.yP ] = [ dest.org, dest.xP, dest.yP ],
-- where the points are viewed as
-- column vectors with third component 1. Hence we compute the inverse
-- of src and multipy on the left by dest. But the pairs
-- (dest.xP, src.xP), (dest.yP, src.yP) may be missing, in which case they
-- are filled in by default to make the transformation a
-- translation (if both are missing) or a Euclidean motion (if just (c, sc) is missing).
-- The matrix
mm is optional, and is used to store the result: if NIL, a new one is allocated.
-- Uses
m1, mInv as work areas.
sing: BOOL;
mm ← GetFrameMatrix[src, mm];
[mInv, sing] ← InvertMatrix[mm, mInv];
IF sing THEN RETURN [NIL, sing];
m1 ← GetFrameMatrix[dest, m1];
mm ← MultiplyMatrix[m1, mInv, mm];
RETURN [mm, sing]
END;

ComputeSomeTransform
: PUBLIC PROC
[src, dest: Frame, mm: REF Matrix ← NIL]
RETURNS [mat: REF Matrix, singular: BOOL] =

BEGIN
-- Similar to the above, except that tries simpler transforms (by dropping
-- last one or two pairs) if the given frames specify a aingular transformation.

[mat, singular] ← ComputeTransform [src, dest, mm];
IF singular THEN [mat, singular] ← ComputeTransform
[src: [src.org, src.xP, NIL], dest: [dest.org, dest.xP, NIL], mm: mat];
IF singular THEN [mat, singular] ← ComputeTransform
[src: [src.org, NIL, NIL], dest: [dest.org, NIL, NIL], mm: mat]
END;

TransformPoint
: PUBLIC PROC [x, y: REAL, mat: REF Matrix] RETURNS [xT, yT: REAL] =

BEGIN
-- Transforms x, y by the given matrix
xT ← x * mat^[1][1] + y * mat^[1][2] + mat^[1][3];
yT ← x * mat^[2][1] + y * mat^[2][2] + mat^[2][3]
END;

frameMold: Mold = NEW [ListMoldRec ← [min: 1, max: 3, eMold: $PointPtr]];

END.