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.