kyacclib/src/YaccParse.m3


 Copyright (c) 2000 California Institute of Technology 
 All rights reserved. See the file COPYRIGHT for a full description. 
 $Id: YaccParse.m3,v 1.2 2001-09-19 15:31:35 wagner Exp $ 

MODULE YaccParse;
IMPORT Pragma, PragmaRead;
IMPORT FileRdErr;
IMPORT Prec;
IMPORT CharCodes;
IMPORT CharRange;
IMPORT Sym;
IMPORT SymList;
IMPORT SymListParse;
IMPORT Rule;
IMPORT RuleList;
IMPORT Text;
IMPORT TextWr;
IMPORT TextSubs;
IMPORT TextReader;
IMPORT TextPrecTbl;
IMPORT TextBooleanTbl;
IMPORT TextIntTbl;
IMPORT TokSpec;
IMPORT Rd, Thread, Process;
IMPORT Wr, Fmt;
FROM Stdio IMPORT stderr;
<*FATAL Rd.EndOfFile, Rd.Failure, Wr.Failure, Thread.Alerted *>

REVEAL
  T = Public BRANDED OBJECT
    tok: TokSpec.T;
    rd: Rd.T;
    rules: RuleList.T := NIL;
    prec: TextPrecTbl.T;
    (* rulename -> prec OR
       symname -> prec OR
       @char -> prec
    *)
    start: TextBooleanTbl.T; (* symname -> isStart (no entry for nonstart) *)
    codes: TextIntTbl.T;     (* symname -> code *)
    lastPrec := 0;
    yaccName: TEXT;
    isToken: TextBooleanTbl.T := NIL;
  OVERRIDES
    init := Init;
    fmtRules := FmtRules;
    fmtTypes := FmtTypes;
    getRules := GetRules;
    getCodes := GetCodes;
  END;

TYPE
  SelfPragma = Pragma.T OBJECT
    self: T;
    precKind: Prec.Kind;
    returnSym: Sym.T := NIL;
    lastRuleNo: INTEGER := 0;
  END;

PROCEDURE Warn(message: TEXT; fatal: BOOLEAN := FALSE) =
  BEGIN
    Wr.PutText(stderr, "Warning: " & message & "\n");
    IF fatal THEN
      Process.Exit(1);
    END;
  END Warn;

PROCEDURE ParseStart(p: SelfPragma; rd: Rd.T) =
  VAR
    tr := NEW(TextReader.T).init(Rd.GetLine(rd));
    cur := tr.shatter("\t ","",TRUE);
  BEGIN
    IF cur = NIL THEN
      FileRdErr.E(rd, "expected start symbol");
    END;
    WHILE cur # NIL DO
      IF p.self.start.put(cur.head, TRUE) THEN
        FileRdErr.E(rd,"\""& cur.head&"\" already declared a start symbol");
      END;
      cur := cur.tail;
    END;
  END ParseStart;

PROCEDURE ParsePrec(p: SelfPragma; rd: Rd.T) =
  VAR
    cur: SymList.T;
    key: TEXT;
    val: Prec.T;
pos := Rd.Index(rd); peekLine := Rd.GetLine(rd);
  BEGIN
Term.WrLn(PeekPrec: & peekLine); Rd.Seek(rd, pos);
    cur := SymListParse.Parse(rd, p.self.tok.charTokens);
    INC(p.self.lastPrec);
    WHILE cur # NIL DO
      key := Sym.GetName(cur.head);
      val := NEW(Prec.T, kind := p.precKind, val := p.self.lastPrec);
      EVAL p.self.prec.put(key, val);
Term.WrLn(Putting prec: & key);
      cur := cur.tail;
    END;
  END ParsePrec;

PROCEDURE ParseRule(p: SelfPragma; rd: Rd.T) =
  VAR
    self := p.self;
    pos := Rd.Index(rd);
    peekLine := Rd.GetLine(rd);
    i := Text.FindChar(peekLine, ':');
  BEGIN
    IF i = -1 OR NOT Text.GetChar(peekLine, i-1) IN CharRange.AlphaNum THEN
      IF p.returnSym = NIL THEN
        FileRdErr.E(rd, "Missing return symbol");
      END;
      Rd.Seek(rd, pos);
      INC(p.lastRuleNo);
      self.rules := RuleList.Cons(Rule.FromRd(rd, p.returnSym,
                                              self.tok.charTokens,
                                              p.lastRuleNo),
                                  self.rules);
    ELSE
      p.returnSym := Sym.FromText(Text.Sub(peekLine, 0, i));
    END;
  END ParseRule;

PROCEDURE ParseText(self: T) =
  VAR
    prag := NEW(PragmaRead.T).init();
    parseStart := NEW(SelfPragma, self := self, do := ParseStart);
    parseRule := NEW(SelfPragma, self := self, do := ParseRule);
  PROCEDURE PrecType(kind: Prec.Kind; pragName: TEXT) =
    VAR
      parsePrec := NEW(SelfPragma, self := self,
                       precKind := kind,  do := ParsePrec);
    BEGIN
      prag.add(parsePrec, pragName);
    END PrecType;
  BEGIN
    prag.add(parseStart, "%start");
    prag.add(parseRule, "%rule");
    prag.add(parseRule, "");
    PrecType(Prec.Kind.Left, "%left");
    PrecType(Prec.Kind.Right, "%right");
    PrecType(Prec.Kind.None, "%nonassoc");
    prag.apply(self.rd);
    self.rules := RuleList.ReverseD(self.rules);
  END ParseText;

PROCEDURE LookupSyms(self: T) =
  VAR
    cur := self.rules;
    lastCode: INTEGER := self.tok.lastConstCode;
    iter := self.tok.constTokens.iterate();
    constName: TEXT;
    constCode: INTEGER;
  BEGIN
    WHILE iter.next(constName, constCode) DO
      EVAL self.codes.put(constName, constCode);
    END;
    WHILE cur # NIL DO
      Rule.LookupSyms(cur.head, self.prec, self.start, self.codes,
                      self.tok.constTokens, lastCode);
      cur := cur.tail;
    END;
  END LookupSyms;

PROCEDURE CheckPrecs(self: T) =
  VAR
    iter := self.prec.iterate();
    key: TEXT;
    val: Prec.T;
  BEGIN
    WHILE iter.next(key, val) DO
      IF NOT val.used THEN
        Warn("precedence not used: " & key);
      END;
    END;
  END CheckPrecs;

PROCEDURE CheckToks(self: T) =
  VAR
    cur := self.tok.tokens;
    val: INTEGER;
  BEGIN
    WHILE cur # NIL DO
      IF NOT self.codes.get(cur.head, val) THEN
        Warn("token not used: " & cur.head);
      END;
      cur := cur.tail;
    END;
  END CheckToks;

PROCEDURE FmtRules(self: T; form: TEXT): TEXT =
  VAR
    cur := self.rules;
    wr := TextWr.New();
  BEGIN
    WHILE cur # NIL DO
      Wr.PutText(wr, Rule.Format(cur.head, form, cur.tail = NIL));
      cur := cur.tail;
    END;
    RETURN TextWr.ToText(wr);
  END FmtRules;

PROCEDURE CheckSyms(self: T) =
  VAR
    cur := self.tok.tokens;
    curRule := self.rules;
    definedToken := NEW(TextBooleanTbl.Default).init();
    iter := self.codes.iterate();
    iterStart := self.start.iterate();
    key: TEXT;
    val: INTEGER;
    bool: BOOLEAN;
    hasStart := FALSE;
  BEGIN
    WHILE cur # NIL DO
      EVAL definedToken.put(cur.head, TRUE);
      cur := cur.tail;
    END;
    WHILE curRule # NIL DO
      IF curRule.head.length = 1 THEN
        IF Sym.GetCode(curRule.head.return) =
          Sym.GetCode(curRule.head.syms.head) THEN
          Warn(curRule.head.name & " might loop for a while");
        END;
      END;
      hasStart := hasStart OR Sym.IsStart(curRule.head.return);
      key := Sym.GetName(curRule.head.return);
      IF definedToken.get(key, bool) AND bool THEN
        Warn(CharCodes.Q(key) & " is a token");
      END;
      EVAL definedToken.put(key, FALSE);
      curRule := curRule.tail;
    END;
    IF NOT hasStart THEN
      Warn("No start symbols!!", TRUE);
    END;
    WHILE iter.next(key, val) DO
      IF val >= 256 THEN
        IF NOT definedToken.get(key, bool) THEN
          Warn("Grammar symbol not defined: " & CharCodes.Q(key));
        END;
      END;
    END;
    WHILE iterStart.next(key, bool) DO
      IF NOT definedToken.get(key, bool) THEN
        Warn("Start symbol not defined: " & CharCodes.Q(key));
      ELSIF bool THEN
        Warn("Token used as start symbol: " & CharCodes.Q(key));
      END;
    END;
    self.isToken := definedToken;
  END CheckSyms;

PROCEDURE FmtTypes(self: T; form: TEXT; tokenTypes: BOOLEAN): TEXT =
  VAR
    iter := self.codes.iterate();
    key, sup: TEXT;
    val, dummy: INTEGER;
    bool: BOOLEAN;
    wr := TextWr.New();
    subs := NEW(TextSubs.T).init();
  BEGIN
    <* ASSERT self.isToken # NIL *> (* call CheckSyms first *)
    WHILE iter.next(key, val) DO
      bool := FALSE;
      EVAL self.isToken.get(key, bool);
      bool := bool = tokenTypes;
      IF bool AND NOT self.tok.constTokens.get(key, dummy) THEN
        IF self.start.get(key, bool) THEN
          <* ASSERT bool *>
          sup := "StartType";
        ELSE
          sup := "OtherType";
        END;
        subs.add("%name", key);
        subs.add("%sup", sup);
        subs.add("%yacc", self.yaccName);
        subs.add("%code", Fmt.Int(val));
        Wr.PutText(wr, subs.apply(form));
      END;
    END;
    RETURN TextWr.ToText(wr);
  END FmtTypes;

PROCEDURE Init(self: T; rd: Rd.T; tok: TokSpec.T; name: TEXT): T =
  BEGIN
    self.yaccName := name;
    self.rd := rd;
    self.tok := tok;
    self.prec := NEW(TextPrecTbl.Default).init();
    self.start := NEW(TextBooleanTbl.Default).init();
    self.codes := NEW(TextIntTbl.Default).init();
    ParseText(self);
    LookupSyms(self);
    CheckSyms(self);
    CheckPrecs(self);
    CheckToks(self);
    RETURN self;
  END Init;

PROCEDURE GetRules(self: T): RuleList.T =
  BEGIN RETURN self.rules; END GetRules;

PROCEDURE GetCodes(self: T): TextIntTbl.T =
  BEGIN RETURN self.codes; END GetCodes;

BEGIN
END YaccParse.