<*PRAGMA LL*> MODULEA; IMPORT JunoBuild, JunoPt; IMPORT JunoAST, JunoASTUtils, JunoChkBNF, JunoCompileErr, JunoScope; IMPORT JunoRT, JunoValue AS Value, JunoRTError; IMPORT Atom, AtomRefTbl, Fmt; CONST InitCacheSize = 30; REVEAL T = TPublic BRANDED "CurrCmd.T" OBJECT ast: JunoAST.Cmd; scp: JunoScope.T; pointCache, oldPointCache: AtomRefTbl.Default; othersCache, oldOthersCache: AtomRefTbl.Default; changedVal: REF ARRAY OF BOOLEAN; END; CurrCmd 
CurrCmd.T contains ast, an AST representing a current command. This
   field should never be NIL.
   pointCache maps names of variables representing points to their
   current positions, represented as Value.T's. The othersCache is
   similar, but maps to all values that are not pairs of real numbers.
   The old... caches are used to tell when one of the cached values has
   changed, to avoid unnecessary compilations of the current command.
   The changedVal array is set by the FillCache procedure, and used by the
   UpdateNearVars procedure. If there are n variables in the current
   command, then changedVal[i] for 0 <= i < n is true iff the current
   value of the ith variable is different from the cached value, or if there
   is no value cached for the ith variable. 
VAR R2DotPlus := JunoASTUtils.QIdFromTexts("R2", "Plus");
 ----------------- Creation / Replacement / Accessors -------------------- 
PROCEDURE--------------------- Scope-Related Operations --------------------------New (ast: JunoAST.Cmd; scp: JunoScope.T): T = VAR res: T; BEGIN <* ASSERT ast # NIL *> res := NEW(T, ast := ast, pointCache := NEW(AtomRefTbl.Default).init(sizeHint := InitCacheSize), oldPointCache := NEW(AtomRefTbl.Default).init(sizeHint := InitCacheSize), othersCache := NEW(AtomRefTbl.Default).init(sizeHint := InitCacheSize), oldOthersCache := NEW(AtomRefTbl.Default).init(sizeHint:=InitCacheSize), changedVal := NEW(REF ARRAY OF BOOLEAN, InitCacheSize)); IF scp = NIL THEN scp := JunoScope.New(NIL) END; res.scp := scp; RETURN res END New; PROCEDUREGetAST (cc: T): JunoAST.Cmd = BEGIN RETURN cc.ast END GetAST; PROCEDUREChangeAST (cc: T; ast: JunoAST.Cmd) = BEGIN <* ASSERT ast # NIL *> cc.ast := ast; cc.codeValid := FALSE; ClearCaches(cc) END ChangeAST; PROCEDUREGetVariables (ast: JunoAST.Cmd): JunoAST.NearVarList = BEGIN TYPECASE ast OF JunoAST.Proj (p) => RETURN p.vars ELSE RETURN JunoAST.EmptyNVList END END GetVariables; PROCEDUREGetVariable (ast: JunoAST.T; name: JunoAST.Id): JunoAST.NearVarLink = VAR vars := GetVariables(ast); BEGIN VAR v: JunoAST.NearVarLink := vars.head; BEGIN WHILE v # NIL DO IF v.id = name THEN RETURN v END; v := v.next END END; RETURN NIL END GetVariable; PROCEDUREGetConstraint (ast: JunoAST.Cmd): JunoAST.Formula = BEGIN TYPECASE ast OF JunoAST.Proj (p) => TYPECASE p.body OF JunoAST.Guard (g) => RETURN g.grd ELSE (* SKIP *) END ELSE (* SKIP *) END; RETURN JunoAST.TrueVal END GetConstraint; PROCEDUREGetCmd (ast: JunoAST.Cmd): JunoAST.Cmd = BEGIN TYPECASE ast OF JunoAST.Proj (p) => TYPECASE p.body OF JunoAST.Guard (g) => RETURN g.body ELSE RETURN p.body END ELSE RETURN ast END END GetCmd;
PROCEDURE--------------------------- Modification --------------------------------GetScope (cc: T): JunoScope.T = BEGIN RETURN cc.scp END GetScope; PROCEDURESetScope (cc: T; scp: JunoScope.T) = BEGIN cc.scp := scp END SetScope; PROCEDURENewDeclName (cc: T; prefix: TEXT; tryEmptySuffix := FALSE): TEXT = VAR res := prefix; i := 0; BEGIN WHILE (i = 0 AND NOT tryEmptySuffix) OR JunoScope.Lookup(cc.scp, Atom.FromText(res), localOnly := TRUE) # NIL DO res := prefix & Fmt.Int(i); INC(i) END; RETURN res END NewDeclName;
PROCEDURESkipify (ast: JunoAST.Cmd): JunoAST.Cmd = BEGIN TYPECASE ast OF JunoAST.Proj (p) => RETURN NEW(JunoAST.Proj, bp := p, vars := p.vars, body := Skipify(p.body)) | JunoAST.Guard (g) => RETURN NEW(JunoAST.Guard, bp := g, grd := g.grd, body := Skipify(g.body)) ELSE RETURN JunoAST.SkipVal END END Skipify; PROCEDUREAddVariable ( cc: T; v: JunoAST.Id; loc: JunoPt.T; near: JunoAST.Expr; frozen := FALSE) = VAR new := NEW(JunoAST.NearVarLink, id := v, hint := near, frozen := frozen); BEGIN <* ASSERT near # NIL *> TYPECASE cc.ast OF <* NOWARN *> JunoAST.Proj (p) => VAR v2 := p.vars.head; BEGIN WHILE v2.next # NIL DO <* ASSERT v2.id # v *> v2 := v2.next END; v2.next := new; p.vars.size := p.vars.size + 1 END | JunoAST.Cmd (c) => cc.ast := NEW(JunoAST.Proj, bp := JunoAST.End, vars := NEW(JunoAST.NearVarList, bp := JunoAST.End, size := 1, head := new), body := c) END; EVAL cc.pointCache.put(v, Value.NewPoint(loc.x, loc.y)); cc.codeValid := FALSE END AddVariable; PROCEDUREAddConstraint (cc: T; con: JunoAST.Constraint) = BEGIN TYPECASE cc.ast OF <* NOWARN *> JunoAST.Proj (p) => AddConstraint1(p.body, con) | JunoAST.Cmd => AddConstraint1(cc.ast, con) END; cc.codeValid := FALSE END AddConstraint; PROCEDUREAddConstraint1 ( VAR (*INOUT*) ast: JunoAST.Cmd; con: JunoAST.Constraint) = BEGIN TYPECASE ast OF <* NOWARN *> JunoAST.Guard (g) => AddConstraint2(g.grd, con) | JunoAST.Cmd (c) => ast := NEW(JunoAST.Guard, bp := JunoAST.End, grd := con, body := c) END END AddConstraint1; PROCEDUREAddConstraint2 ( VAR (*INOUT*) f: JunoAST.Formula; con: JunoAST.Constraint) = BEGIN TYPECASE f OF JunoAST.Or (or) => AddConstraint2(or.f2, con) ELSE f := NEW(JunoAST.And, bp := JunoAST.End, f1 := f, f2 := con) END END AddConstraint2; PROCEDUREAddCommand (cc: T; cmd: JunoAST.Cmd) = BEGIN TYPECASE cc.ast OF <* NOWARN *> JunoAST.Proj (p) => AddCommand1(p.body, cmd) | JunoAST.Cmd => AddCommand2(cc.ast, cmd) END; cc.codeValid := FALSE END AddCommand; PROCEDUREAddCommand1 (VAR (*INOUT*) c1: JunoAST.Cmd; c2: JunoAST.Cmd) = BEGIN TYPECASE c1 OF <* NOWARN *> JunoAST.Guard (g) => AddCommand2(g.body, c2) | JunoAST.Cmd => AddCommand2(c1, c2) END END AddCommand1; PROCEDUREAddCommand2 (VAR (*INOUT*) c1: JunoAST.Cmd; c2: JunoAST.Cmd) = BEGIN TYPECASE c1 OF <* NOWARN *> JunoAST.Skip => c1 := c2 | JunoAST.Cmd => c1 := NEW(JunoAST.Seq, bp := JunoAST.End, c1 := c1, c2 := c2) END END AddCommand2; PROCEDURERemCommand (cc: T): BOOLEAN = VAR changed: BOOLEAN; BEGIN TYPECASE cc.ast OF <* NOWARN *> JunoAST.Skip => changed := FALSE | JunoAST.Proj (p) => changed := RemCommand1(p.body) | JunoAST.Cmd => changed := RemCommand2(cc.ast) END; cc.codeValid := cc.codeValid AND NOT changed; RETURN changed END RemCommand; PROCEDURERemCommand1 (VAR (*INOUT*) cmd: JunoAST.Cmd): BOOLEAN = BEGIN TYPECASE cmd OF <* NOWARN *> JunoAST.Guard (g) => RETURN RemCommand2(g.body) | JunoAST.Cmd => RETURN RemCommand2(cmd) END END RemCommand1; PROCEDURERemCommand2 (VAR (*INOUT*) ast: JunoAST.Cmd): BOOLEAN = VAR res: BOOLEAN; BEGIN TYPECASE ast OF JunoAST.Skip => res := FALSE; | JunoAST.Seq (seq) => res := RemCommand2(seq.c2); IF seq.c2 = JunoAST.SkipVal THEN ast := seq.c1; res := TRUE END | JunoAST.Cmd => ast := JunoAST.SkipVal; res := TRUE END; RETURN res END RemCommand2; PROCEDUREDoRel (cc: T; c, a, b: JunoAST.Id) = VAR v := GetVariable(cc.ast, c); BEGIN VAR aa, bb: JunoAST.Id; BEGIN IF IsRelHint(v.hint, aa, bb) AND a = aa AND b = bb THEN v.hint := PointValue(cc, c); cc.codeValid := FALSE; RETURN END END; (* Make the hint of the form "c ~= (x, y) REL (a, b)" *) VAR ax, ay, bx, by: Real; BEGIN EVAL PointLocation(cc, a, ax, ay); EVAL PointLocation(cc, b, bx, by); v.hint := MkRelHint(cc, c, a, ax, ay, b, bx, by, v.hint); cc.codeValid := FALSE END END DoRel; PROCEDUREIsRelHint (hint: JunoAST.Expr; VAR (*OUT*) a, b: JunoAST.Id): BOOLEAN =
Ifhintis of the form (num, num) REL (p,q), forId'spandq, seta,b := p,qand returnTRUE; else returnFALSE.
  BEGIN
    IF hint = NIL THEN RETURN FALSE END;
    TYPECASE hint OF JunoAST.Rel (rel) =>
      TYPECASE rel.e2 OF JunoAST.Pair (p) =>
        TYPECASE p.e1 OF JunoAST.QId (p1) =>
	  TYPECASE p.e2 OF JunoAST.QId (p2) =>
            IF p1.id0 = JunoAST.NilId AND
               p2.id0 = JunoAST.NilId AND
               IsNumericPoint(rel.e1)
            THEN
              a := p1.id1;
              b := p2.id1;
              RETURN TRUE
            END
	  ELSE (* SKIP *)
	  END
        ELSE (* SKIP *)
        END
      ELSE (* SKIP *)
      END
    ELSE (* SKIP *)
    END;
    RETURN FALSE
  END IsRelHint;
PROCEDURE IsRel1Hint (hint: JunoAST.Expr; VAR (*OUT*) a: JunoAST.Id): BOOLEAN =
Ifhintis of the formR2.Plus(p, (num, num)), forIdp, seta := pand returnTRUE; else returnFALSE.
  BEGIN
    TYPECASE hint OF
      NULL => (*SKIP*)
    | JunoAST.Call (call) =>
      IF call.ins.size = 2 THEN
        TYPECASE call.ins.head.expr OF
          JunoAST.QId (arg0) =>
            IF arg0.id0 = JunoAST.NilId AND
               JunoASTUtils.EqualQIds(call.name, R2DotPlus) AND
               IsNumericPoint(call.ins.head.next.expr)
            THEN
              a := arg0.id1;
              RETURN TRUE
            END
        ELSE (* SKIP *)
        END
      END
    ELSE (* SKIP *)
    END;
    RETURN FALSE
  END IsRel1Hint;
PROCEDURE DoRel1 (cc: T; c, a: JunoAST.Id) =
  VAR v := GetVariable(cc.ast, c); BEGIN
    VAR aa: JunoAST.Id; BEGIN
      IF IsRel1Hint(v.hint, aa) AND a = aa THEN
        v.hint := PointValue(cc, c);
        cc.codeValid := FALSE;
        RETURN
      END
    END;
    (* Make the hint of the form "c ~= R2.Plus(a, (x, y))" *)
    VAR ax, ay: Real; BEGIN
      EVAL PointLocation(cc, a, ax, ay);
      v.hint := MkRel1Hint(cc, c, a, ax, ay, v.hint);
      cc.codeValid := FALSE
    END
  END DoRel1;
PROCEDURE MkRelHint (cc: T; c: JunoAST.Id;
  a: JunoAST.Id; ax, ay: Real;
  b: JunoAST.Id; bx, by: Real;
  else: JunoAST.Expr): JunoAST.Expr =
Returnc's position relative toaandb, or returnelseif this is not possible.
  VAR x, y, cx, cy: Real; BEGIN
    IF NOT PointLocation(cc, c, cx, cy) THEN RETURN else END;
    IF NOT JunoPt.RelVal(cx, cy, ax, ay, bx, by, x, y) THEN RETURN else END;
    RETURN
      NEW(JunoAST.Rel, bp := JunoAST.End,
	e1 := JunoASTUtils.NewPoint(x, y),
	e2 := NEW(JunoAST.Pair, bp := JunoAST.End,
	  e1 := JunoASTUtils.QIdFromIds(JunoAST.NilId, a),
	  e2 := JunoASTUtils.QIdFromIds(JunoAST.NilId, b)))
  END MkRelHint;
PROCEDURE MkRel1Hint (cc: T; c: JunoAST.Id;
  a: JunoAST.Id; ax, ay: Real; else: JunoAST.Expr): JunoAST.Expr =
Returnc's position relative toa.
  VAR x, y, cx, cy: Real; BEGIN
    IF NOT PointLocation(cc, c, cx, cy) THEN RETURN else END;
    x := cx - ax;
    y := cy - ay;
    RETURN
      NEW(JunoAST.Call, bp := JunoAST.End,
	name := R2DotPlus,
	inouts := JunoAST.EmptyExprList,
	ins := NEW(JunoAST.ExprList, bp := JunoAST.End, size := 2, head :=
	  NEW(JunoAST.ExprLink,
	    expr := JunoASTUtils.QIdFromId(a),
	    next := NEW(JunoAST.ExprLink,
              expr := JunoASTUtils.NewPoint(x, y)))))
  END MkRel1Hint;
 ------------------------ Operations on Points --------------------------- 
PROCEDUREPointLocation (cc: T; a: JunoAST.Id; VAR (*OUT*) h, v: Real): BOOLEAN =
Sethandvto the coordinates of the point namedaincc.pointCacheand return TRUE; return FALSE if there is no such point in the cache.
  VAR vl: Value.T; BEGIN
    IF cc.pointCache.get(a, vl) THEN
      TYPECASE vl OF <* NOWARN *>
        REF Value.Pair (p) =>
          h := NARROW(p.car, REF Real)^;
          v := NARROW(p.cdr, REF Real)^;
          RETURN TRUE
      END
    ELSE
      RETURN FALSE
    END
  END PointLocation;
PROCEDURE PointValue (cc: T; a: JunoAST.Id): JunoAST.Pair =
Requires that the point namedais incc.pointCache. Returns an AST corresponding to the point's value in the cache.
  VAR x, y: Real; inCache := PointLocation(cc, a, (*OUT*) x, (*OUT*) y); BEGIN
    <* ASSERT inCache *>
    RETURN JunoASTUtils.NewPoint(x, y)
  END PointValue;
PROCEDURE FreezePoint (cc: T; a: JunoAST.Id) =
  VAR v := GetVariable(cc.ast, a); BEGIN
    <* ASSERT v # NIL *>
    v.frozen := NOT v.frozen;
    cc.codeValid := FALSE
  END FreezePoint;
PROCEDURE IsFrozen (cc: T; a: JunoAST.Id): BOOLEAN =
  VAR v := GetVariable(cc.ast, a); BEGIN
    IF v = NIL
      THEN RETURN FALSE
      ELSE RETURN v.frozen
    END
  END IsFrozen;
PROCEDURE DiffNumericPair (p: JunoAST.Pair; x, y: Real): BOOLEAN =
Return TRUE iffpis a pair of constant numbers, but differs from the pair(x, y).
  VAR px, py: Real; BEGIN
    RETURN IsNumber(p.e1, px) AND IsNumber(p.e2, py) AND (x # px OR y # py)
  END DiffNumericPair;
PROCEDURE IsNumericPoint (e: JunoAST.Expr): BOOLEAN =
 Return TRUE iff e is a pair of real numbers. 
  BEGIN
    TYPECASE e OF
      NULL => (*SKIP*)
    | JunoAST.Pair (p) =>
        VAR dummy: Real; BEGIN
          RETURN IsNumber(p.e1, dummy) AND IsNumber(p.e2, dummy)
        END
    ELSE (*SKIP*)
    END;
    RETURN FALSE
  END IsNumericPoint;
PROCEDURE IsNumber (e: JunoAST.Expr; VAR (*OUT*) v: Real): BOOLEAN =
Return TRUE iffea numeric constant of the form<number>or- <number>, and if so, setvto its value.
  BEGIN
    TYPECASE e OF
      JunoAST.Number (n) =>
        v := n.val; RETURN TRUE
    | JunoAST.UMinus (u) =>
        VAR res := IsNumber(u.e, v); BEGIN
          IF res THEN v := -v END;
          RETURN res
        END
    ELSE (* SKIP *)
    END;
    RETURN FALSE
  END IsNumber;
PROCEDURE MovePoint (cc: T; a: JunoAST.Id; x, y: Real) =
  VAR v: JunoAST.NearVarLink := GetVariable(cc.ast, a); BEGIN
    <* ASSERT v # NIL AND v.hint # JunoAST.NilExpr *>
    VAR newV := Value.NewPoint(x, y); oldV: Value.T; BEGIN
      (* only do work if the point does not already have the given value *)
      IF NOT (cc.pointCache.get(v.id, oldV) AND Value.Equal(newV, oldV)) THEN
        EVAL cc.pointCache.put(v.id, newV);
        EVAL MovePoint1(cc, v, x, y);
        cc.codeValid := FALSE
      END
    END
  END MovePoint;
PROCEDURE MovePoint1 (
  cc: T; v: JunoAST.NearVarLink; x, y: Real; changed := TRUE): BOOLEAN =
Updatev.hintto have the value(x, y), preserving the current structure of the hint. For the current hint to be changed, it must be either a simple pair, a literal value, an expression of the form(x,y) REL (a, b), wherexandyare numbers, or an expression of the formR2.Plus(a, (x,y)). In the latter two cases, the values for the pointsaandbare read fromcc.pointCache, and the pair(x,y)is updated. If none of these cases applies, the hint is unchanged. Returns TRUE iff the hintv.hintwas changed.If
changedisFALSEand the hint is absolute, then the hint is not changed andFALSEis returned.
  VAR res := FALSE; aa, bb: JunoAST.Id; BEGIN
    <* ASSERT v.hint # NIL AND v.hint # JunoAST.NilExpr *>
    TYPECASE v.hint OF JunoAST.Pair (p) =>
      (* The hint is of the form "(x, y)" *)
      IF changed AND DiffNumericPair(p, x, y) THEN
        res := TRUE;
        p.e1 := JunoASTUtils.NewNumber(x);
        p.e2 := JunoASTUtils.NewNumber(y)
      END
    ELSE
      IF IsLiteral(v.hint) THEN
        (* update hint if it is a literal of some sort *)
        res := TRUE;
        v.hint := JunoASTUtils.NewPoint(x, y)
      ELSIF IsRelHint(v.hint, aa, bb) THEN
        (* The hint is of the form "(num, num) REL (aa, bb)" *)
        VAR ax, ay, bx, by, x2, y2: Real; BEGIN
          IF PointLocation(cc, aa, ax, ay) AND
      	     PointLocation(cc, bb, bx, by) AND
      	     JunoPt.RelVal(x, y, ax, ay, bx, by, x2, y2)
      	  THEN
            VAR r: JunoAST.Rel := v.hint; p1: JunoAST.Pair := r.e1; BEGIN
              IF DiffNumericPair(p1, x2, y2) THEN
                res := TRUE;
                r.e1 := JunoASTUtils.NewPoint(x2, y2)
              END
            END
          END
        END
      ELSIF IsRel1Hint(v.hint, aa) THEN
        (* The hint is of the form "R2.Plus(aa, (num, num))" *)
        VAR ax, ay, x2, y2: Real; BEGIN
          IF PointLocation(cc, aa, ax, ay) THEN
            x2 := x - ax; y2 := y - ay;
            VAR
              call: JunoAST.Call := v.hint;
              p1: JunoAST.Pair := call.ins.head.next.expr;
            BEGIN
              IF DiffNumericPair(p1, x2, y2) THEN
                res := TRUE;
                call.ins.head.next.expr := JunoASTUtils.NewPoint(x2, y2)
              END
            END
          END
        END
      END
    END;
    RETURN res
  END MovePoint1;
PROCEDURE IsLiteral (expr: JunoAST.Expr): BOOLEAN =
Return TRUE iffexpris a literal value, namely, a (possibly negated) number, a text, the value JunoNIL, a list of literals, or a parenthesized literal.
  BEGIN
    IF expr = JunoAST.NilExpr THEN RETURN TRUE END;
    TYPECASE expr OF
      JunoAST.Number, JunoAST.Text, JunoAST.Nil => RETURN TRUE
    | JunoAST.UMinus (minus) => RETURN IsLiteral(minus.e)
    | JunoAST.List (l) =>
        VAR res := TRUE; curr := l.elts.head; BEGIN
          WHILE curr # NIL AND res DO
            res := res AND IsLiteral(curr.expr);
            curr := curr.next
          END;
          RETURN res
        END
    | JunoAST.GroupedExpr (g) => RETURN IsLiteral(g.expr)
    ELSE RETURN FALSE
    END
  END IsLiteral;
PROCEDURE ForAllPoints (cc: T; p: PointProc) =
  VAR it := cc.pointCache.iterate(); a: Atom.T; v: Value.T; BEGIN
    WHILE it.next(a, v) DO
      VAR
        pair := NARROW(v, REF Value.Pair);
        x := NARROW(pair.car, REF Real);
        y := NARROW(pair.cdr, REF Real);
      BEGIN
        p(a, JunoPt.T{x^, y^})
      END
    END;
  END ForAllPoints;
 ------------------------ Folding Operations ----------------------------- 
PROCEDUREWrapProcHeader ( name: JunoAST.Id; ins: JunoAST.IdList; body: JunoAST.Cmd) : JunoAST.ProcDecl =
Return a procedure declaration namednamewith IN parametersinsand bodybody. Ifbodyis not total, it is first bracketed byIF...FI.
  BEGIN
    TRY JunoChkBNF.TotalCmd(body) EXCEPT JunoCompileErr.Error =>
      body := NEW(JunoAST.If, body := body, bp := JunoAST.End)
    END;
    RETURN NEW(JunoAST.ProcDecl, bp := JunoAST.End,
      header := NEW(JunoAST.ProcHeader, name := name, ins := ins,
        outs := JunoAST.EmptyIdList, inouts := JunoAST.EmptyIdList),
      body := body)
  END WrapProcHeader;
PROCEDURE FoldNoArgs (cc: T; name: JunoAST.Id): JunoAST.ProcDecl =
  BEGIN
    RETURN WrapProcHeader(name, JunoAST.EmptyIdList, cc.ast)
  END FoldNoArgs;
PROCEDURE GetFoldArgs (cc: T): JunoAST.IdList =
  VAR
    res := NEW(JunoAST.IdList, bp := JunoAST.End);
    vars := JunoASTUtils.NearVarListCopy(GetVariables(cc.ast));
    nv := vars.head;
    last: JunoAST.IdLink := NIL;
  BEGIN
    WHILE nv # NIL DO
      IF nv.hint = JunoAST.NilExpr OR IsNumericPoint(nv.hint) THEN
        (* add to end of "params" list *)
        VAR new := NEW(JunoAST.IdLink, id := nv.id); BEGIN
          IF last = NIL
            THEN res.head := new
            ELSE last.next := new
          END;
          last := new
        END;
        INC(res.size)
      END;
      nv := nv.next
    END;
    RETURN res;
  END GetFoldArgs;
PROCEDURE FoldPred (
    cc: T;
    name: JunoAST.Id;
    params: JunoAST.IdList;
    locals: JunoAST.NearVarList): JunoAST.PredDecl =
Fold the current commandccinto a predicate declaration, and return it.Nameis the predicate's name,Paramsare its arguments, andlocalsare its (existentially quantified) local variables. The body of the predicate is the constraint section of the current commandcc.
  VAR body := GetConstraint(cc.ast); BEGIN
    IF locals.size > 0 THEN
      body := NEW(JunoAST.Exists, bp := JunoAST.End, vars := locals, f := body)
    END;
    RETURN NEW(JunoAST.PredDecl, bp := JunoAST.End,
      header := NEW(JunoAST.PredHeader, name := name, ins := params),
      body := body)
  END FoldPred;
PROCEDURE FoldProc (
    cc: T;
    name: JunoAST.Id;
    params: JunoAST.IdList;
    locals: JunoAST.NearVarList): JunoAST.ProcDecl =
Fold the current commandccinto a procedure declaration, and return it.Nameis the procedure's name,paramsare its (IN) parameters, andlocalsare its (projected) local variables. The procedure's body is formed from the constraint section and command section of the current commandcc.
  VAR grd := GetConstraint(cc.ast); body := GetCmd(cc.ast); BEGIN
    IF grd # JunoAST.TrueVal THEN
      body := NEW(JunoAST.Guard, bp := JunoAST.End,
        grd := grd, body := body)
    END;
    IF locals.size > 0 THEN
      body := NEW(JunoAST.Proj, bp := JunoAST.End,
        vars := locals, body := body)
    END;
    RETURN WrapProcHeader(name, params, body)
  END FoldProc;
TYPE RelToArray = ARRAY [0..1] OF RECORD id: JunoAST.Id; x,y: Real END;
 If rt: RelToArray, then rt holds the names and (point) values of up to
   two parameters relative to which the other variable's hints will be
   computed. 
PROCEDURESetUpRelTo ( cc: T; args: JunoAST.IdList; VAR (*OUT*) rt: RelToArray; VAR (*OUT*) i: INTEGER) =
Find up toNUMBER(rt)elements ofargswhose values are points, store their names and values inrt, and setito the number found.
  VAR l := args.head; BEGIN
    i := 0;
    WHILE i < NUMBER(rt) AND l # NIL DO
      IF PointLocation(cc, l.id, rt[i].x, rt[i].y) THEN
        rt[i].id := l.id;
        INC(i)
      END;
      l := l.next
    END
  END SetUpRelTo;
PROCEDURE AugmentRelTo (
  cc: T;
  nv: JunoAST.NearVarLink;
  ignore: JunoAST.IdList;
  VAR (*INOUT*) rt: RelToArray;
  VAR (*INOUT*) i: INTEGER) =
Ifi=1and there is some point innvnot named in the listignorewhose hint isREL1tort[0], add that point tort.
  BEGIN
    WHILE i = 1 AND nv # NIL DO
      IF NOT JunoASTUtils.MemIdList(nv.id, ignore) THEN
      	VAR aa: JunoAST.Id; BEGIN
      	  IF IsRel1Hint(nv.hint, aa) AND aa = rt[0].id THEN
      	    rt[i].id := nv.id;
      	    EVAL PointLocation(cc, nv.id, rt[i].x, rt[i].y);
      	    INC(i)
      	  END
        END
      END;
      nv := nv.next
    END
  END AugmentRelTo;
PROCEDURE CheckFoldArgs (ids: JunoAST.IdList; locals: JunoAST.NearVarList)
    RAISES {BadFoldArg} =
Check that each variable inidsoccurs inlocals. RaiseBadFoldArgwith the name of the variable if there is one that doesn't appear inlocals. This procedure is implemented by brute-force; it requires time on the order ofids.size * locals.size.
  VAR curr := ids.head; BEGIN
    WHILE curr # NIL DO
      IF JunoASTUtils.MemNearVarList(curr.id, locals) = NIL THEN
        RAISE BadFoldArg(curr.id)
      END;
      curr := curr.next
    END
  END CheckFoldArgs;
PROCEDURE MkLocals (cc: T; args: JunoAST.IdList;
  args2: JunoAST.IdList := NIL): JunoAST.NearVarList
  RAISES {BadFoldArg} =
Return the list of local variables for the procedure or predicate resulting from folding the current commandccwith argumentsargs. The variables in the resulting list will have hints relative to theargsif possible. Any variables named inargs2will not be incorporated into the result either, but these variables are not used as a basis for relativizing the hints of the result variables.
  VAR
    rt: RelToArray;
    rtCount: INTEGER;
    res := NEW(JunoAST.NearVarList, bp := JunoAST.End);
    locals := JunoASTUtils.NearVarListCopy(GetVariables(cc.ast));
    nv: JunoAST.NearVarLink := locals.head;
    last: JunoAST.NearVarLink := NIL;
  BEGIN
    CheckFoldArgs(args, locals);
    IF args2 = NIL
      THEN args2 := JunoAST.EmptyIdList
      ELSE CheckFoldArgs(args2, locals)
    END;
    SetUpRelTo(cc, args, (*OUT*) rt, (*OUT*) rtCount);
    AugmentRelTo(cc, nv, args2, (*INOUT*) rt, (*INOUT*) rtCount);
    WHILE nv # NIL DO
      IF NOT JunoASTUtils.MemIdList(nv.id, args) AND
         NOT JunoASTUtils.MemIdList(nv.id, args2) THEN
        IF IsNumericPoint(nv.hint) THEN
          (* make nv's hint relative to rt *)
          CASE rtCount OF <* NOWARN *>
            0 => (* SKIP *)
          | 1 => nv.hint :=
              MkRel1Hint(cc, nv.id, rt[0].id, rt[0].x, rt[0].y, nv.hint)
          | 2 => nv.hint :=
              MkRelHint(cc, nv.id, rt[0].id, rt[0].x,
                rt[0].y, rt[1].id, rt[1].x, rt[1].y, nv.hint)
          END
        END;
        (* add nv to the end of res *)
        IF last = NIL
          THEN res.head := nv
          ELSE last.next := nv
        END;
        last := nv;
        INC(res.size)
      END;
      nv := nv.next
    END;
    IF last # NIL THEN last.next := NIL END;
    RETURN res
  END MkLocals;
PROCEDURE FoldByHeader (cc: T; hdr: JunoAST.PredHeader; kind: FoldKind):
  JunoAST.Decl RAISES {BadFoldArg} =
  BEGIN
    IF kind = FoldKind.ProcNoArgs THEN
      RETURN FoldNoArgs(cc, hdr.name)
    END;
    <* ASSERT hdr.ins # NIL *>
    VAR locals := MkLocals(cc, hdr.ins); BEGIN
      CASE kind OF <* NOWARN *>
    	FoldKind.Pred => RETURN FoldPred(cc, hdr.name, hdr.ins, locals)
      | FoldKind.Proc => RETURN FoldProc(cc, hdr.name, hdr.ins, locals)
      END
    END
  END FoldByHeader;
PROCEDURE FoldByName (cc: T; name: JunoAST.Id; kind := FoldKind.Proc):
  JunoAST.Decl =
  BEGIN
    CASE kind OF
    | FoldKind.ProcNoArgs => RETURN FoldNoArgs(cc, name)
    | FoldKind.Pred, FoldKind.Proc =>
        VAR
          hdr := NEW(JunoAST.PredHeader, bp := JunoAST.End,
            name := name, ins := GetFoldArgs(cc));
        <* FATAL BadFoldArg *> BEGIN
          RETURN FoldByHeader(cc, hdr, kind)
        END
    END
  END FoldByName;
PROCEDURE FoldAnim (cc: T; hdr: JunoAST.PredHeader;
    sliderPts: JunoAST.IdList): JunoAST.ProcDecl
    RAISES {BadFoldArg} =
  VAR
    newHdr := NEW(JunoAST.PredHeader, bp := hdr, name := hdr.name,
      ins := JunoASTUtils.ConcatIdLists(hdr.ins, sliderPts));
    locals := MkLocals(cc, hdr.ins, sliderPts);
  BEGIN
    RETURN FoldProc(cc, newHdr.name, newHdr.ins, locals)
  END FoldAnim;
PROCEDURE AppendSuffix (cc: T; id: JunoAST.Id; suffix: TEXT): JunoAST.Id =
Return the name resulting from concatenatingidwithsuffix. If this name is already defined in the current scope, append additional digits as necessary to produce an unused name.
  VAR resTxt := Atom.ToText(id) & suffix; BEGIN
    resTxt := NewDeclName(cc, resTxt, tryEmptySuffix := TRUE);
    RETURN Atom.FromText(resTxt);
  END AppendSuffix;
VAR (* READONLY after init *)
  DurId := Atom.FromText("dur");
  TId := Atom.FromText("t");
  DurQId := JunoASTUtils.QIdFromId(DurId);
  TQId := JunoASTUtils.QIdFromId(TId);
  DurIdList := JunoASTUtils.NewIdList(DurId);
  TIdList := JunoASTUtils.NewIdList(TId);
  ZeroVal := NEW(JunoAST.Number, bp := JunoAST.End, val := 0.0);
  OneVal := NEW(JunoAST.Number, bp := JunoAST.End, val := 1.0);
PROCEDURE FoldAnimFrame (cc: T; hdr: JunoAST.PredHeader;
  sliderPts: JunoAST.IdList): JunoAST.ProcDecl =
Construct a declaration of the form:
PROC <hdr.name>Frame(<hdr.ins>, dur, t) IS IF VAR <pt[0].nm> = <pt[0].value>, <pt[2].nm> = <pt[2].value>, <pt[1].nm> = (t/dur, 0) REL (<pt[0].nm>, <pt[2].nm>) IN <hdr.name>(<hdr.ins>, <sliderPts>) END FIwhere <pt[i]> denotes the ith variable in thesliderPtsargument, thenmfield is the name of the point, and thevaluefield is the value of that variable in the current command point cache.
  VAR
    pHeader := NEW(JunoAST.ProcHeader, bp := JunoAST.End,
      name := AppendSuffix(cc, hdr.name, "Frame"),
      ins := JunoASTUtils.ConcatIdLists(hdr.ins,
        JunoASTUtils.ConcatIdLists(DurIdList, TIdList)),
      outs := JunoAST.EmptyIdList, inouts := JunoAST.EmptyIdList);
    s0Name := sliderPts.head.id;
    s0QName := JunoASTUtils.QIdFromId(s0Name);
    s1Name := sliderPts.head.next.id;
    s2Name := sliderPts.head.next.next.id;
    s2QName := JunoASTUtils.QIdFromId(s2Name);
    (* initialize local variables in reverse order *)
    div := NEW(JunoAST.Divide, bp := JunoAST.End, e1 := TQId, e2 := DurQId);
    s1Hint := NEW(JunoAST.Rel, bp := JunoAST.End,
      e2 := NEW(JunoAST.Pair, bp := JunoAST.End, e1 := s0QName, e2 := s2QName),
      e1 := NEW(JunoAST.Pair, bp := JunoAST.End, e1 := div, e2 := ZeroVal));
    s1Var := NEW(JunoAST.NearVarLink, id := s1Name,
      frozen := TRUE, hint := s1Hint);
    s2Var := NEW(JunoAST.NearVarLink, id := s2Name,
      frozen := TRUE, hint := PointValue(cc, s2Name), next := s1Var);
    s0Var := NEW(JunoAST.NearVarLink, id := s0Name,
      frozen := TRUE, hint := PointValue(cc, s0Name), next := s2Var);
    nearVars := NEW(JunoAST.NearVarList, bp := JunoAST.End,
      size := 3, head := s0Var);
    call := NEW(JunoAST.ProcCall, bp := JunoAST.End,
      name := JunoASTUtils.QIdFromId(hdr.name),
      ins := JunoASTUtils.IdListToQIdList(
        JunoASTUtils.ConcatIdLists(hdr.ins, sliderPts)),
      outs := JunoAST.EmptyQIdList, inouts := JunoAST.EmptyExprList);
    proj := NEW(JunoAST.Proj, bp := JunoAST.End,
      vars := nearVars, body := call);
    ifCmd := NEW(JunoAST.If, bp := JunoAST.End, body := proj);
  BEGIN
    RETURN NEW(JunoAST.ProcDecl, bp := JunoAST.End,
      header := pHeader, body := ifCmd)
  END FoldAnimFrame;
PROCEDURE FoldAnimCreator (cc: T; hdr: JunoAST.PredHeader;
  frameNm: JunoAST.Id): JunoAST.ProcDecl =
Return a procedure declaration of the form:
PROC an := <hdr.name>Anim(<hdr.ins>, dur) IS an := (CLOSE(<frameNm>, <hdr.ins>, dur), dur) END
  VAR
    anId := Atom.FromText("an");
    inArgs := JunoASTUtils.ConcatIdLists(hdr.ins, DurIdList);
    pHeader := NEW(JunoAST.ProcHeader, bp := JunoAST.End,
      name := AppendSuffix(cc, hdr.name, "Anim"), ins := inArgs,
      outs := JunoASTUtils.NewIdList(anId), inouts := JunoAST.EmptyIdList);
    closeArgs := JunoASTUtils.IdListToQIdList(
      JunoASTUtils.ConcatIdLists(JunoASTUtils.NewIdList(frameNm), inArgs));
    close := NEW(JunoAST.Call, bp := JunoAST.End,
      name := JunoASTUtils.QIdFromText("CLOSE"),
      ins := closeArgs, inouts := JunoAST.EmptyExprList);
    animPair := NEW(JunoAST.Pair, bp := JunoAST.End,
      e1 := close, e2 := DurQId);
    assign := NEW(JunoAST.Assign, bp := JunoAST.End,
      vars := JunoASTUtils.NewQIdList(JunoASTUtils.QIdFromId(anId)),
      exprs := JunoASTUtils.NewExprList(animPair, bp := JunoAST.End));
  BEGIN
    RETURN NEW(JunoAST.ProcDecl, bp := JunoAST.End,
      header := pHeader, body := assign)
  END FoldAnimCreator;
PROCEDURE SelectVar (cc: T; nm: JunoAST.Id): JunoAST.NearVarLink =
Return a newNearVarLinkfor the variable namednmwhose hint is determined from the current value ofnmincc's point cache. The hint is frozen or not according to whether it is frozen or hinted incc. The fields other thanid,frozen, andhintof the result are defaulted.
  BEGIN
    RETURN NEW(JunoAST.NearVarLink, id := nm,
      frozen := IsFrozen(cc, nm), hint := PointValue(cc, nm))
  END SelectVar;
PROCEDURE SelectVars (cc: T; vars: JunoAST.IdList): JunoAST.NearVarList =
Return aNearVarListcontaining the variablesvars(in the same order), hinted to values determined from the current values of those variables incc's point cache, and with the hint being frozen or not according to whether it is frozen or hinted in the current commandcc.
  VAR head, last: JunoAST.NearVarLink := NIL; curr := vars.head; BEGIN
    WHILE curr # NIL DO
      VAR new := SelectVar(cc, curr.id); BEGIN
      	IF head = NIL
      	  THEN head := new
      	  ELSE last.next := new
      	END;
        last := new
      END;
      curr := curr.next
    END;
    RETURN NEW(JunoAST.NearVarList, bp := JunoAST.End,
      size := vars.size, head := head)
  END SelectVars;
PROCEDURE PrependVar (id: JunoAST.Id; hint: JunoAST.Expr;
  l: JunoAST.NearVarList; frozen := TRUE): JunoAST.NearVarList =
Destructively prepend aNearVarLinkof the form<id> ~= <hint>to theNearVarListl. Thefrozenparameter determines whether~or=is used.
  VAR link := NEW(JunoAST.NearVarLink, id := id, frozen := frozen,
    hint := hint, next := l.head);
  BEGIN
    INC(l.size);
    l.head := link;
    RETURN l
  END PrependVar;
PROCEDURE FoldAnimCmd (cc: T; args: JunoAST.IdList; animProcNm: JunoAST.Id):
    JunoAST.Cmd =
Produce an animation command of the form:VAR dur = 1, <near-vars> IN Slider.SetVisibility(Slider.Invisible); Anim.Play(<animProcNm>(<args>, dur)) ENDThe <near-vars> are formed from bothccandargs.
  VAR
    nearVars := PrependVar(DurId, OneVal, SelectVars(cc, args));
    animCall := NEW(JunoAST.Call, bp := JunoAST.End,
      name := JunoASTUtils.QIdFromId(animProcNm),
      ins := JunoASTUtils.IdListToQIdList(
        JunoASTUtils.ConcatIdLists(args, DurIdList)),
      inouts := JunoAST.EmptyExprList);
    sliderCall := NEW(JunoAST.ProcCall, bp := JunoAST.End,
      name := JunoASTUtils.QIdFromTexts("Slider", "SetVisibility"),
      ins := JunoASTUtils.NewExprList(
        JunoASTUtils.QIdFromTexts("Slider", "Invisible")),
      outs := JunoAST.EmptyQIdList, inouts := JunoAST.EmptyExprList);
    playCall := NEW(JunoAST.ProcCall, bp := JunoAST.End,
      name := JunoASTUtils.QIdFromTexts("Anim", "Play"),
      ins := JunoASTUtils.NewExprList(animCall),
      outs := JunoAST.EmptyQIdList, inouts := JunoAST.EmptyExprList);
    semi := NEW(JunoAST.Seq, bp := JunoAST.End,
      c1 := sliderCall, c2 := playCall);
  BEGIN
    RETURN NEW(JunoAST.Proj, bp := JunoAST.End,
      vars := nearVars, body := semi)
  END FoldAnimCmd;
 ------------------------- Running / Updating ---------------------------- 
PROCEDURERun (cc: T; skipify: BOOLEAN): BOOLEAN RAISES {CompileError, RuntimeError} = BEGIN (* compile the current command if necessary *) TRY IF NOT cc.codeValid OR skipify # cc.skipify THEN VAR ast := cc.ast; BEGIN IF skipify THEN ast := Skipify(ast) END; cc.slot := JunoBuild.CurrCmd(ast, cc.scp); cc.codeValid := TRUE; cc.skipify := skipify END END EXCEPT JunoCompileErr.Error (err) => RAISE CompileError(err.msg) END; (* run it *) RETURN Run2(cc) END Run; PROCEDURERun2 (cc: T): BOOLEAN RAISES {RuntimeError} = VAR res: BOOLEAN; runRes := JunoRT.ExecFromSlot(cc.slot, reset := TRUE); BEGIN (* We expect the command to halt *) IF runRes.trapCode = JunoRT.TrapCode.Error THEN IF runRes.errorCode # JunoRTError.Code.Halt THEN JunoRT.ResetMachine(); RAISE RuntimeError(RTError{JunoRT.TrapMessage(runRes), runRes}) END ELSE RAISE RuntimeError(RTError{JunoRT.TrapMessage(runRes), runRes}) END; (* update hints from stack frame *) res := UpdateHints(cc); (* Finish running the command until completion (this will be a no-op *) runRes := JunoRT.Exec(); <* ASSERT runRes.trapCode = JunoRT.TrapCode.NormalHalt *> RETURN res END Run2; PROCEDUREClearCaches (cc: T) = BEGIN EVAL cc.pointCache.init(sizeHint := InitCacheSize); EVAL cc.othersCache.init(sizeHint := InitCacheSize) END ClearCaches; PROCEDUREUpdateHints (cc: T): BOOLEAN = PROCEDURE SwapTables(VAR t1, t2: AtomRefTbl.Default) = VAR t := t1; BEGIN t1 := t2; t2 := t END SwapTables; VAR res := FALSE; BEGIN SwapTables(cc.oldPointCache, cc.pointCache); SwapTables(cc.oldOthersCache, cc.othersCache); ClearCaches(cc); TYPECASE cc.ast OF NULL => (* SKIP *) | JunoAST.Proj (proj) => FillCache(cc, proj.vars); res := UpdateNearVars(cc, proj.vars) ELSE (* SKIP *) END; RETURN res END UpdateHints; PROCEDUREFillCache (cc: T; vars: JunoAST.NearVarList) =
Read the values for the points declared invarsfrom the run-time stack, and store those values that are points incc.pointCacheand those that are not incc.othersCache. ThechangedValarray is set to indicate which values have changed. Ifcc.astis not a projection, then both caches are made empty, andchangedValis unchanged.
  BEGIN
    <* ASSERT cc.ast # NIL AND ISTYPE(cc.ast, JunoAST.Proj) *>
    VAR nv := vars.head; frame := JunoRT.BaseFrame().up(); i := 0; BEGIN
      IF vars.size > NUMBER(cc.changedVal^) THEN
        cc.changedVal := NEW(REF ARRAY OF BOOLEAN,
          MAX(vars.size, 2 * NUMBER(cc.changedVal^)))
      END;
      WHILE nv # NIL DO
        <* ASSERT nv.index > 0 *>
        VAR v := frame.getLocal(nv.index); oldV: Value.T; BEGIN
          WITH changed = cc.changedVal[i] DO
            IF v # NIL THEN
              (* The frame contains a value for "nv" *)
              IF IsPointValue(v) THEN
            	changed := NOT (cc.oldPointCache.get(nv.id, oldV)
            	  AND Value.Equal(v, oldV));
            	EVAL cc.pointCache.put(nv.id, v)
              ELSE
            	changed := NOT (cc.oldOthersCache.get(nv.id, oldV)
            	  AND Value.Equal(v, oldV));
            	EVAL cc.othersCache.put(nv.id, v)
              END
            ELSE
              (* There is no value in the frame, so the value is being changed
                 only if it exists in one of the old caches. *)
              IF cc.oldPointCache.get(nv.id, oldV)
                 OR cc.oldOthersCache.get(nv.id, oldV) THEN
                changed := TRUE
              END
            END
          END
        END;
        nv := nv.next;
        INC(i)
      END
    END
  END FillCache;
PROCEDURE IsPointValue (v: Value.T): BOOLEAN =
 Return TRUE iff v is the value of a 2D point, that is, a pair of two
   numbers. 
  BEGIN
    TYPECASE v OF REF Value.Pair (p) =>
      TYPECASE p.car OF REF Value.Real =>
        TYPECASE p.cdr OF REF Value.Real =>
          RETURN TRUE
        ELSE (* SKIP *)
        END
      ELSE (* SKIP *)
      END
    ELSE (* SKIP *)
    END;
    RETURN FALSE
  END IsPointValue;
PROCEDURE UpdateNearVars (cc: T; vars: JunoAST.NearVarList): BOOLEAN =
Update the hints invarsfromcc.pointCacheandcc.othersCache, and mark the compiled code as being out-of-date iff some hint changed. ReturnsTRUEiff some hint changed.Requires that all values
vincc.pointCachesatisfyIsPointValue(v), and thatcc.changedValaccurately reflects which values need updating.
  VAR nv := vars.head; v: Value.T; changedHint := FALSE; i := 0; BEGIN
    <* ASSERT NUMBER(cc.changedVal^) >= vars.size *>
    WHILE nv # NIL DO
      IF cc.pointCache.get(nv.id, v) THEN
        TYPECASE v OF <* NOWARN *> REF Value.Pair (p) =>
          VAR
            x := NARROW(p.car, REF Real);
            y := NARROW(p.cdr, REF Real);
          BEGIN
            IF nv.hint = JunoAST.NilExpr THEN
              (* the value didn't previously have a hint; now it does *)
              changedHint := TRUE;
              nv.hint := JunoASTUtils.NewPoint(x^, y^)
            ELSE
              (* change the existing hint for the point *)
              changedHint := MovePoint1(cc, nv, x^, y^,
                changed := cc.changedVal[i]) OR changedHint
            END;
          END
        END
      ELSIF cc.changedVal[i] THEN
        changedHint := TRUE;
        IF cc.othersCache.get(nv.id, v) THEN
          (* only update the hint for a non-point if it already has
             a literal hint *)
          IF nv.hint # JunoAST.NilExpr AND IsLiteral(nv.hint) THEN
            nv.hint := JunoASTUtils.NewASTFromValue(v)
          END
        ELSE
          (* If not in either cache, then what has changed is that the
             variable no longer has a hint. *)
          nv.hint := JunoAST.NilExpr
        END
      END;
      INC(i);
      nv := nv.next
    END;
    cc.codeValid := cc.codeValid AND NOT changedHint;
    RETURN changedHint
  END UpdateNearVars;
BEGIN
END CurrCmd.