-- ArpaMTEvalImpl.pgs
-- file ArpaMTEvalImpl.mesa rewritten by PGS, 14-Jun-83 15:33
-- last edit by Schmidt, June 11, 1982 3:12 pm
-- last modified by Satterthwaite, June 2, 1983 12:45 pm
-- Last Edited by: Nichols, July 15, 1983 5:35 pm
-- Last Edited by: HGM, March 3, 1984 10:46:50 pm PST
-- John Larson, October 10, 1987 5:12:05 pm PDT
-- pgs [defs: ArpaMTParseTable, bcd: ArpaMTParseData, grammar: ArpaMT] ← ArpaMTEvalImpl.mesa;
-- output: new version of ArpaMTEvalImpl.mesa, tables in ArpaMTParseData.bcd
-- interface on ArpaMTParseTable.mesa
-- log on PGS.Log, grammar on ArpaMT.Grammar,
-- errors on ArpaMTParseData.errlog
DIRECTORY
List: TYPE USING [Nconc1, NthElement],
ArpaMTMiscOps: TYPE USING [AToI, IToA, Substitute],
ArpaMTP1: TYPE --P1-- USING [ActionStack, AddrSpecNode, DomainNode, DotWordsNode,
InternalNode, LinkStack, MailboxNode, RouteAddrNode, RouteNode, TerminalNode, TValue,
ValueStack],
ArpaMTParseTable: TYPE ParseTable USING [ProdDataRef],
Rope: TYPE USING [Cat, Equal, IsEmpty, Length, ROPE, SkipOver, Substr];
ArpaMTEvalImpl: CEDAR PROGRAM
IMPORTS List, ArpaMTMiscOps, Rope
EXPORTS ArpaMTP1 ~ {
-- parse tree building
OPEN P1~~ArpaMTP1, ArpaMTParseTable;
-- stores parse tree when done with parse
parseTree: REF ANY;
-- Set if the reduction routines had trouble and we should declare an invalid parse.
badSemantics: BOOLEAN;
-- local data base (supplied by parser)
v: P1.ValueStack;
l: P1.LinkStack;
q: P1.ActionStack;
prodData: ProdDataRef;
-- initialization/termination
AssignDescriptors: PUBLIC PROC[
qd: P1.ActionStack,
vd: P1.ValueStack, ld: P1.LinkStack,
pp: ProdDataRef] ~ {
q ← qd; v ← vd; l ← ld; prodData ← pp;};
EvalInit: PUBLIC PROC ~ {
-- This routine should be called before a parse begins. The parser will do this for you.
parseTree ← NIL;
badSemantics ← FALSE;
};
-- Random
digits: Rope.ROPE ~ "0123456789";
-- Tree manipulation
CollectInternals: PROC [top, number: NAT] ~ {
kids: P1.InternalNode; -- LIST OF REF ANY
kids ← LIST[v[top+number-1].t];
FOR i: NAT DECREASING IN [top..top+number-1) DO
kids ← CONS[v[i].t, kids];
ENDLOOP;
v[top].t ← kids;
};
-- This procedure builds the parse tree. It is called by the parser for each reduction to be
-- made. In addition to building the tree, it does a few minor corrections for the extra
-- productions in the grammar. In particular, a missing ";" is added to a group list, and extra
-- commas are removed from mailbox and address lists.
-- For dates, it does something else (more later).
-- The grammar here is really a pair of grammars, one for name lists and one for dates.
-- We put them together like this because it is pretty inconvenient to have two grammars
-- with pgs. The name-dummy and date-dummy tokens are the first tokens returned by
-- the lexer in each case and are used to force the parser to parse a name list or a date.
ProcessQueue: PUBLIC PROC[qI, top: CARDINAL] ~ {
FOR i: CARDINAL IN [0..qI) DO
GetRule: PROC[n: CARDINAL] RETURNS [CARDINAL] ~ TRUSTED {
RETURN [prodData[n].rule]};
UnixProc1: PROC ~ {
-- date-time ::= day month atom hour atom
-- Unix date format. Rearrange everything and fix up the year.
time, date: LIST OF REF ANY;
year: REF P1.TerminalNode ← NARROW[v[top+4].t];
year.text ← year.text.Substr[year.text.Length[]-2, 2];
time ← LIST[v[top+3].t];
date ← LIST[v[top+2].t, v[top+1].t, year];
v[top].t ← LIST[v[top].t, NEW[P1.TerminalNode ← [text~","]], date, time];
};
UnixProc2: PROC ~ {
-- date-time ::= day month atom hour zone atom
-- Unix date format. Rearrange everything and fix up the year.
time, date: LIST OF REF ANY;
year: REF P1.TerminalNode ← NARROW[v[top+5].t];
year.text ← year.text.Substr[year.text.Length[]-2, 2];
time ← LIST[v[top+3].t, v[top+4].t];
date ← LIST[v[top+2].t, v[top+1].t, year];
v[top].t ← LIST[v[top].t, NEW[P1.TerminalNode ← [text~","]], date, time];
};
top ← top-q[i].tag.pLength+1;
SELECT GetRule[q[i].transition] FROM
0 => -- TABLE: ArpaMTParseData TYPE: ParseTable EXPORTS: SELF
-- GOAL: goal
-- TERMINALS:
-- name-dummy
-- atom
-- quoted-string
-- domain-lit
-- . , : ;
-- @ < >
-- eol
-- error
-- date-dummy
-- ampm day month zone
-- ALIASES:
-- name-dummy nameDummy
-- atom atomTok
-- quoted-string qStringTok
-- domain-lit dLitTok
-- . dotTok
-- , commaTok
-- : colonTok
-- ; semiTok
-- @ atSignTok
-- < lBracketTok
-- > rBracketTok
-- error errorTok
-- eol EOLTok
-- date-dummy dateDummy
-- ampm ampmTok
-- day dayTok
-- month monthTok
-- zone zoneTok
-- PRODUCTIONS:
-- goal ::= name-dummy addr-list eol
BEGIN
CollectInternals[top+1, 2]; -- save the EOL, too.
parseTree ← v[top+1].t;
END;
1 => -- goal ::= date-dummy date-time eol
BEGIN
CollectInternals[top+1, 2]; -- save the EOL, too.
parseTree ← v[top+1].t;
END;
2 => -- address ::=
v[top].t ← NIL;
3 => -- address ::= mailbox
NULL;
4 => -- address ::= group
NULL;
5 => -- addr-list ::= address
NULL;
6 => -- addr-list ::= addr-list , address
IF v[top+2].t # NIL THEN -- only collect new address if non-empty
CollectInternals[top, 3];
7 => -- addr-spec ::= dot-words
v[top].t ← NEW[P1.AddrSpecNode ← [localPart~v[top].t]];
8 => -- addr-spec ::= addr-spec @ domain
BEGIN
t: REF P1.TerminalNode ← NARROW[v[top+1].t];
d: REF P1.DomainNode ← NARROW[v[top+2].t];
t.text ← "@"; -- make sure that we don't have "at"
-- also zap white space from around the @
t.whiteSpace ← NIL;
WHILE d.domain # NIL DO
d ← d.domain;
ENDLOOP;
d.subDomain.whiteSpace ← NIL;
v[top].t ← NEW[P1.AddrSpecNode ← [localPart~v[top].t,
atSign~t, domain~NARROW[v[top+2].t]]];
END;
9 => -- domain ::= sub-domain
v[top].t ← NEW[P1.DomainNode ← [subDomain~NARROW[v[top].t]]];
10 => -- domain ::= domain . sub-domain
v[top].t ← NEW[P1.DomainNode ← [domain~NARROW[v[top].t],
dot~NARROW[v[top+1].t], subDomain~NARROW[v[top+2].t]]];
11 => -- dot-words ::= word
v[top].t ← NEW[P1.DotWordsNode ← [word~NARROW[v[top].t]]];
12 => -- dot-words ::= dot-words . word
v[top].t ← NEW[P1.DotWordsNode ← [dotWords~NARROW[v[top].t],
dot~NARROW[v[top+1].t], word~NARROW[v[top+2].t]]];
13 => -- group ::= phrase : mailbox-list ;
CollectInternals[top, 4];
14 => -- group ::= phrase : mailbox-list
BEGIN
CollectInternals[top, 3];
-- Add semicolon.
v[top].t ← List.Nconc1[NARROW[v[top].t, LIST OF REF ANY],
NEW[P1.TerminalNode ← [text~";"]]];
END;
15 => -- mailbox ::= addr-spec
v[top].t ← NEW[P1.MailboxNode ← [address~v[top].t]];
16 => -- mailbox ::= route-addr
v[top].t ← NEW[P1.MailboxNode ← [address~v[top].t]];
17 => -- mailbox ::= phrase route-addr
v[top].t ← NEW[P1.MailboxNode ← [phrase~v[top].t, address~v[top+1].t]];
18 => -- mailbox ::= @ dot-words
BEGIN
CollectInternals[top, 2];
v[top].t ← NEW[P1.MailboxNode ← [address~v[top].t]];
-- TODO: should hide structure of dl somehow.
END;
19 => -- mailbox-list ::=
v[top].t ← NIL;
20 => -- mailbox-list ::= mailbox
NULL;
21 => -- mailbox-list ::= mailbox-list ,
NULL; -- discard comma
22 => -- mailbox-list ::= mailbox-list , mailbox
IF v[top+2].t # NIL THEN -- only collect new mailbox if non-empty
CollectInternals[top, 3];
23 => -- phrase ::= dot-words
NULL;
24 => -- phrase ::= phrase dot-words
CollectInternals[top, 2];
25 => -- route ::= @ domain
v[top].t ← NEW[P1.RouteNode ← [atSign~NARROW[v[top].t],
domain~NARROW[v[top+1].t]]];
26 => -- route ::= route , @ domain
v[top].t ← NEW[P1.RouteNode ← [route~NARROW[v[top].t],
comma~NARROW[v[top+1].t], atSign~NARROW[v[top+2].t],
domain~NARROW[v[top+3].t]]];
27 => -- route-addr ::= < addr-spec >
v[top].t ← NEW[P1.RouteAddrNode ← [lBrack~NARROW[v[top].t],
addrSpec~NARROW[v[top+1].t], rBrack~NARROW[v[top+2].t]]];
28 => -- route-addr ::= < route : addr-spec >
v[top].t ← NEW[P1.RouteAddrNode ← [lBrack~NARROW[v[top].t],
route~NARROW[v[top+1].t], colon~NARROW[v[top+2].t],
addrSpec~NARROW[v[top+3].t], rBrack~NARROW[v[top+4].t]]];
29 => -- sub-domain ::= atom
NULL;
30 => -- sub-domain ::= domain-lit
NULL;
31 => -- word ::= atom
NULL;
32 => -- word ::= quoted-string
NULL;
33 => -- date-time ::= date time
CollectInternals[top, 2];
34 => -- date-time ::= day date time
BEGIN
-- Add comma after day and treat like date-time ::= day , date time
t: REF P1.TerminalNode ← NARROW[v[top].t];
t.text ← t.text.Substr[0, 3];
v[top].t ← LIST[v[top].t, NEW[P1.TerminalNode ← [text~","]], v[top+1].t,
v[top+2].t];
END;
35 => -- date-time ::= day , date time
BEGIN
-- Day is supposed to be in 3-letter abbreviated form, so truncate it.
t: REF P1.TerminalNode ← NARROW[v[top].t];
t.text ← t.text.Substr[0, 3];
CollectInternals[top, 4];
END;
36 => -- date-time ::= day , date , time
BEGIN
-- Remove comma after date and treat like date-time ::= day , date time
t: REF P1.TerminalNode ← NARROW[v[top].t];
t.text ← t.text.Substr[0, 3];
-- Cheat! Rather than chase down the tree to tack on the white space, we just
-- leave the comma node in.
t ← NARROW[v[top+3].t];
t.whiteSpace ← " ";
t.text ← NIL;
CollectInternals[top, 5];
END;
37 => -- date-time ::= day month atom hour atom
UnixProc1[]; --COMPILER--
38 => -- date-time ::= day month atom hour zone atom
UnixProc2[]; --COMPILER--
39 => -- date ::= atom month atom
BEGIN
-- Month should be truncated to three letters. Atom at end should be year
-- in two digit form.
t: REF P1.TerminalNode ← NARROW[v[top+1].t];
t.text ← t.text.Substr[0, 3];
t ← NARROW[v[top+2].t];
IF t.text.Length[] < 2 OR t.text.SkipOver[skip: digits] # t.text.Length[] THEN
badSemantics ← TRUE
ELSE
t.text ← t.text.Substr[t.text.Length[]-2, 2];
CollectInternals[top, 3];
END;
40 => -- date ::= atom month . atom
BEGIN
-- Discard dot and handle as above.
t: REF P1.TerminalNode ← NARROW[v[top+1].t];
t.text ← t.text.Substr[0, 3];
t ← NARROW[v[top+3].t];
IF t.whiteSpace.IsEmpty THEN t.whiteSpace ← " ";
IF t.text.Length[] < 2 OR t.text.SkipOver[skip: digits] # t.text.Length[] THEN
badSemantics ← TRUE
ELSE
t.text ← t.text.Substr[t.text.Length[]-2, 2];
v[top].t ← LIST[v[top].t, v[top+1].t, v[top+3].t];
END;
41 => -- date ::= atom
BEGIN
-- Most likely, we have a date of the form dd-mmm-yy, so replace the hyphens
-- with spaces.
t: REF P1.TerminalNode ← NARROW[v[top].t];
t.text ← ArpaMTMiscOps.Substitute[t.text, '-, ' ];
END;
42 => -- time ::= hour zone
CollectInternals[top, 2];
43 => -- time ::= hour ampm zone
BEGIN
-- Flush ampm and adjust hour.
t: REF P1.TerminalNode ←
NARROW[NARROW[v[top].t, LIST OF REF ANY].first];
ampm: REF P1.TerminalNode ← NARROW[v[top+1].t];
hour: INT ← ArpaMTMiscOps.AToI[t.text];
-- To quote Taft: "If hour is not 12 then AM leaves it alone and PM adds 12.
-- But if hour is 12 then AM sets it to zero and PM leaves it alone. The
-- following code actually sets it to zero and then adds 12 in the 12 PM case."
IF hour = 12 THEN
hour ← 0;
IF ampm.text.Equal["pm", FALSE] THEN
hour ← hour + 12;
t.text ← ArpaMTMiscOps.IToA[hour];
IF t.text.Length < 2 THEN
-- pad it
t.text ← Rope.Cat["0", t.text];
v[top].t ← LIST[v[top].t, v[top+2].t];
END;
44 => -- time ::= hour
BEGIN
-- This comes from Tenex/Twenex format times, in which the zone is separated
-- from the hour by a hyphen. Replace hyphens with spaces.
t: REF P1.TerminalNode ← NARROW[List.NthElement[NARROW[v[top].t], -1]];
t.text ← ArpaMTMiscOps.Substitute[t.text, '-, ' ];
END;
45 => -- hour ::= atom : atom
BEGIN
-- Make sure we have numbers, but don't force last atom to, as it may have
-- zone mixed in.
t: REF P1.TerminalNode ← NARROW[v[top].t];
t2: REF P1.TerminalNode ← NARROW[v[top+2].t];
IF t.text.SkipOver[skip: digits] # t.text.Length[] THEN
badSemantics ← TRUE;
CollectInternals[top, 3];
END;
46 => -- hour ::= atom : atom : atom
BEGIN
-- Make sure we have numbers.
t: REF P1.TerminalNode ← NARROW[v[top].t];
t2: REF P1.TerminalNode ← NARROW[v[top+2].t];
t3: REF P1.TerminalNode ← NARROW[v[top+4].t];
IF t.text.SkipOver[skip: digits] # t.text.Length[] OR
t2.text.SkipOver[skip: digits] # t2.text.Length[] THEN
badSemantics ← TRUE;
CollectInternals[top, 5];
END;
47 => -- hour ::= atom
BEGIN
-- Time is in 4-digit form: hhmm. Convert to hh:mm. There may be junk
-- after the 4 digits (e.g. -zone) which is preserved.
t: REF P1.TerminalNode ← NARROW[v[top].t];
r: Rope.ROPE ← t.text;
IF r.SkipOver[skip: digits] = 4 AND ArpaMTMiscOps.AToI[r.Substr[0,2]] <= 23 AND
ArpaMTMiscOps.AToI[r.Substr[2,2]] <= 59 THEN
v[top].t ← LIST[NEW[P1.TerminalNode ← [whiteSpace~t.whiteSpace,
text~r.Substr[0, 2]]],
NEW[P1.TerminalNode ← [text~ ":"]],
NEW[P1.TerminalNode ← [text~ r.Substr[start: 2]]]]
ELSE {
badSemantics ← TRUE;
v[top].t ← LIST[v[top].t];
};
END;
ENDCASE => ERROR;
ENDLOOP};
ProcessError: PUBLIC PROC [top: CARDINAL, inputValue: P1.TValue] = {
CollectInternals[1, top];
parseTree ← LIST[v[1].t, inputValue];
};
GetEvalResult: PUBLIC PROC RETURNS [tree: REF ANY, badSemantics: BOOL] = {
RETURN [parseTree, ArpaMTEvalImpl.badSemantics];
};
}.