juno-app/src/Source.m3


 Copyright (C) 1992, Digital Equipment Corporation                         
 All rights reserved.                                                      
 See the file COPYRIGHT for a full description.                            
                                                                           
 Last modified on Sun Oct 26 14:01:00 PST 1997 by heydon                   
      modified on Wed Aug  2 16:17:14 PST 1995 by gnelson                  
      modified on Fri Aug  7 21:51:58 PDT 1992 by myers                    
<* PRAGMA LL *>

MODULE Source;

IMPORT CurrCmd, JunoBuild, View, JunoError, Editor, Drawing, JunoConfig;
FROM JunoHandleLexErr IMPORT HandleLexErr;
IMPORT JunoAST, JunoCompileErr, JunoToken, JunoLex, JunoParse, JunoUnparse;
IMPORT TextPort, TextEditVBT;
IMPORT VBT, Axis, Rect;
IMPORT Text, Rd, Wr, Thread, TextRd, TextWr, Stdio;

REVEAL
  T = Public BRANDED "Source.T" OBJECT
    port: Port
  OVERRIDES
    init := Init;
    update := Update
  END;

TYPE
  Port = TextPort.T BRANDED "Source.Port" OBJECT
    src: T;				 (* the Source.T around this port *)
    width := -1;
    ignoreModifies := FALSE;
  OVERRIDES
    <* LL.sup = VBT.mu *>
    key      := Key;
    <* LL.sup = VBT.mu.SELF *>
    reshape  := Reshape;
    shape    := Shape;
    <* LL.sup < VBT.mu *>
    modified := Modified
  END;
A Source.T's filter child is a TextEditVBT.T with a port of type Port. The width of a Port is the most recent width it was unparsed to, or -1 if it was unparsed to a window with an empty domain.

PROCEDURE Init(self: T; root: View.Root): T =
  VAR
    port := NEW(Port, src := self).init(
      wrap := FALSE, font := JunoConfig.codeFont);
    child := NEW(TextEditVBT.T, tp := port).init();
  BEGIN
    self.root := root;
    self.port := port;
    RETURN View.T.init(self, child);
  END Init;

PROCEDURE Update(s: T) =
  <* LL.sup <= VBT.mu *>
  VAR port := s.port; BEGIN
    IF Rect.IsEmpty(VBT.Domain(port))
      THEN port.width := -1
      ELSE UpdatePort(port, Editor.Width(port))
    END
  END Update;

PROCEDURE UpdatePort(port: Port; width: INTEGER) =
  <* LL.sup <= VBT.mu *>
  VAR v := port.src; BEGIN
    <* ASSERT v.root.astTrue *>
    IF (NOT v.root.sTrue) OR width # port.width THEN
      VAR
        wr := TextWr.New();
        cpos := TextPort.Index(port);
        ast := CurrCmd.GetAST(v.root.ccmd);
        <* FATAL Thread.Alerted, Wr.Failure *>
      BEGIN
        JunoUnparse.Cmd(wr, ast, LAST(CARDINAL),
          width := width, prec := JunoConfig.realPrec);
        SetTextPort(port, TextWr.ToText(wr));
        TextPort.Normalize(port, cpos);
        Wr.Close(wr);
        v.root.sTrue := TRUE;
        port.width := width
      END
    END
  END UpdatePort;

PROCEDURE SetTextPort(port: Port; t: TEXT) =
  BEGIN
    port.ignoreModifies := TRUE;
    TextPort.SetText(port, t);
    port.ignoreModifies := FALSE
  END SetTextPort;
If continuous unparsing is turned off, then it is possible that the AST is up-to-date, but the source does not reflect changes made through the drawing view. In this case, the user should not be able to type in the source window until it has been updated from the AST.

PROCEDURE Key(self: Port; READONLY cd: VBT.KeyRec) =
  <* LL.sup = VBT.mu *>
  BEGIN
    WITH root = self.src.root DO
      IF root.astTrue AND NOT root.sTrue
    	THEN JunoError.Display(self, "Oops! You forgot to click Run.")
    	ELSE TextPort.T.key(self, cd)
      END
    END
  END Key;

PROCEDURE Reshape(port: Port; READONLY cd: VBT.ReshapeRec) =
Reshape reformats its contents according to the new width (if any), then reshapes the textport normally.
  <* LL.sup = VBT.mu.port *>
  BEGIN
    IF Rect.IsEmpty(cd.new) THEN
      port.width := -1
    ELSE
      IF port.src.root.astTrue THEN
        UpdatePort(port, Editor.Width(port))
      END
    END;
    TextPort.T.reshape(port, cd)
  END Reshape;

PROCEDURE Shape(port: Port; ax: Axis.T; n: CARDINAL): VBT.SizeRange =
  <* LL.sup = VBT.mu.port *>
  BEGIN
    IF ax = Axis.T.Hor THEN
      RETURN VBT.DefaultShape
    ELSE
      VAR res := TextPort.T.shape(port, ax, n); BEGIN
        res.lo := 0;
        res.hi := VBT.DefaultShape.hi;
        RETURN res
      END
    END
  END Shape;

PROCEDURE Modified(port: Port) =
This procedure is called by the underlying TextPort when its text is modified.
  <* LL.sup < VBT.mu *>
  BEGIN
    TextPort.T.modified(port);
    IF port.ignoreModifies
      THEN TextPort.SetModified(port, FALSE)
      ELSE port.src.modified(how := View.ModKind.Explicit)
    END
  END Modified;

PROCEDURE ShowError(
    s: T; ast: JunoAST.T;
    READONLY err: JunoCompileErr.ErrVal;
    ts: VBT.TimeStamp) =
  <* FATAL Wr.Failure, Thread.Alerted *>
  VAR txt: TEXT; start, finish: INTEGER; BEGIN
    VAR wr := TextWr.New(); BEGIN
      JunoUnparse.P(wr, ast, width := Editor.Width(s.port),
        prec := JunoConfig.realPrec, errast := err.ast);
      txt := TextWr.ToText(wr);
      Wr.Close(wr)
    END;
    start := Text.FindChar(txt, '\001');
    finish := Text.FindChar(txt, '\002');
    IF start >= 0 AND finish > start THEN
      txt := Text.Sub(txt, 0, start)
        & Text.Sub(txt, start + 1, finish - start - 1)
        & Text.Sub(txt, finish + 1);
      SetTextPort(s.port, txt);
      JunoError.P(s.port, err.msg, start, finish - 1, ts)
    ELSE
      Wr.PutText(Stdio.stderr, err.msg & "\n");
      Wr.PutText(Stdio.stderr, "Error AST:\n");
      JunoUnparse.Debug(err.ast);
      Wr.PutText(Stdio.stderr, "Original AST:\n");
      JunoUnparse.Debug(ast)
    END
  END ShowError;

PROCEDURE Parse(s: T; time: VBT.TimeStamp): JunoAST.Cmd =
  BEGIN
    (* use cached version if it is valid *)
    IF s.root.astTrue THEN RETURN CurrCmd.GetAST(s.root.ccmd) END;

    (* otherwise, parse the contents of the textport *)
    VAR res := ParseFromPort(s.port, time); BEGIN
      IF res # NIL THEN CurrCmd.ChangeAST(s.root.ccmd, res) END;
      RETURN res
    END
  END Parse;

PROCEDURE ParseFromPort(port: Port; time: VBT.TimeStamp): JunoAST.Cmd =
Returns the parsed current command from port, or NIL if there was a lex or parse error. In the event of either error, the error is underlined using timestamp time, and an error window is displayed.
  <* FATAL Rd.Failure, Thread.Alerted, Wr.Failure *>
  VAR
    res: JunoAST.Cmd;
    rd := TextRd.New(TextPort.GetText(port));
    tokens: CARDINAL;
  BEGIN
    TRY JunoParse.Command(rd, res, tokens) EXCEPT
      JunoLex.Error (err) =>
        VAR wr := TextWr.New(); start, finish: INTEGER; BEGIN
  	  JunoUnparse.Cmd(wr, res, tokens,
            width := Editor.Width(port), prec := JunoConfig.realPrec);
  	  HandleLexErr(err, rd, wr, start, finish);
  	  SetTextPort(port, TextWr.ToText(wr));
          Wr.Close(wr);
  	  JunoError.P(port, JunoLex.ErrorText(err.kind), start, finish, time)
        END;
        res := NIL
    | JunoParse.Error (err) =>
        IF tokens = 0 AND err.found.kind = JunoToken.Kind.EndMarker THEN
          res := JunoAST.SkipVal
        ELSE
          VAR wr := TextWr.New(); start, finish: CARDINAL; BEGIN
            JunoUnparse.Cmd(wr, res, tokens,
              width := Editor.Width(port), prec := JunoConfig.realPrec);
            Wr.PutChar(wr, '\n');
            start := Wr.Index(wr);
            Wr.PutText(wr, JunoToken.ToText(err.found));
            finish := Wr.Index(wr);
            Wr.PutChar(wr, ' ');
            Wr.PutText(wr, err.additional);
            Wr.PutText(wr, Rd.GetText(rd, LAST(CARDINAL)));
            SetTextPort(port, TextWr.ToText(wr));
            Wr.Close(wr);
            JunoError.P(port, "Parse error", start, finish, time)
          END;
          res := NIL
        END
    END;
    Rd.Close(rd);
    RETURN res
  END ParseFromPort;

PROCEDURE Compile(s: T; time: VBT.TimeStamp; skipify: BOOLEAN): BOOLEAN =
  BEGIN
    IF s.root.astTrue AND s.root.ccmd.codeValid
      AND s.root.ccmd.skipify = skipify THEN
      RETURN TRUE
    END;
    RETURN Compile2(s, time, skipify)
  END Compile;

PROCEDURE Compile2(s: T; time: VBT.TimeStamp; skipify: BOOLEAN): BOOLEAN =
  VAR port: Port := s.port; ast: JunoAST.Cmd := Parse(s, time); BEGIN
    (* check for parse error *)
    IF ast = NIL THEN RETURN FALSE END;
    (* recompile and update drawing if necessary *)
    WITH cc = s.root.ccmd DO
      IF NOT cc.codeValid OR skipify # cc.skipify THEN
        TRY
          VAR astp := ast; BEGIN
            IF skipify THEN astp := CurrCmd.Skipify(ast) END;
            cc.slot := JunoBuild.CurrCmd(astp,
              CurrCmd.GetScope(cc), checkTotal := TRUE);
            cc.codeValid := TRUE;
            cc.skipify := skipify
          END
        EXCEPT
          JunoCompileErr.Error (err) =>
            ShowError(s, ast, err, time);
            RETURN FALSE
        END;
        TextPort.SetModified(port, FALSE);
        s.root.astTrue := TRUE
      END
    END;
    RETURN TRUE
  END Compile2;

PROCEDURE Make(s: T; time: VBT.TimeStamp; skipify: BOOLEAN): BOOLEAN =
  BEGIN
    IF NOT Compile(s, time, skipify) THEN RETURN FALSE END;
    Drawing.Make(s.root.drawing, skipify);
    s.update();
    RETURN TRUE
  END Make;

PROCEDURE GetText(s: T): TEXT =
  BEGIN
    RETURN TextPort.GetText(s.port)
  END GetText;

PROCEDURE SetText(s:T; txt: TEXT) =
  BEGIN
    TextPort.SetText(s.port, txt)
  END SetText;

BEGIN
END Source.

interface Source is in:


interface View is in:


interface Editor is in: