Import Upstream version 20180207
[hcoop/debian/mlton.git] / mlyacc / src / look.sml
CommitLineData
7f918cf1
CE
1(* ML-Yacc Parser Generator (c) 1989 Andrew W. Appel, David R. Tarditi *)
2
3functor mkLook (structure IntGrammar : INTGRAMMAR) : LOOK =
4 struct
5 val sub = Array.sub
6 infix 9 sub
7 structure Grammar = IntGrammar.Grammar
8 structure IntGrammar = IntGrammar
9 open Grammar IntGrammar
10
11 structure TermSet = ListOrdSet
12 (struct
13 type elem = term
14 val eq = eqTerm
15 val gt = gtTerm
16 end)
17
18 val union = TermSet.union
19 val make_set = TermSet.make_set
20
21 val prLook = fn (termToString,print) =>
22 let val printTerm = print o termToString
23 fun f nil = print " "
24 | f (a :: b) = (printTerm a; print " "; f b)
25 in f
26 end
27
28 structure NontermSet = ListOrdSet
29 (struct
30 type elem = nonterm
31 val eq = eqNonterm
32 val gt = gtNonterm
33 end)
34
35 val mkFuncs = fn {rules : rule list, nonterms : int,
36 produces : nonterm -> rule list} =>
37
38 let
39
40 (* nullable: create a function which tells if a nonterminal is nullable
41 or not.
42
43 Method: Keep an array of booleans. The nth entry is true if
44 NT i is nullable. If is false if we don't know whether NT i
45 is nullable.
46
47 Keep a list of rules whose remaining rhs we must prove to be
48 null. First, scan the list of rules and remove those rules
49 whose rhs contains a terminal. These rules are not nullable.
50
51 Now iterate through the rules that were left:
52 (1) if there is no remaining rhs we have proved that
53 the rule is nullable, mark the nonterminal for the
54 rule as nullable
55 (2) if the first element of the remaining rhs is
56 nullable, place the rule back on the list with
57 the rest of the rhs
58 (3) if we don't know whether the nonterminal is nullable,
59 place it back on the list
60 (4) repeat until the list does not change.
61
62 We have found all the possible nullable rules.
63 *)
64
65 val nullable = let
66 fun add_rule (RULE { lhs, rhs, ... }, r) = let
67 fun addNT (TERM _, _) = NONE
68 | addNT (_, NONE) = NONE
69 | addNT (NONTERM (NT i), SOME ntlist) = SOME (i :: ntlist)
70 in
71 case foldr addNT (SOME []) rhs of
72 NONE => r
73 | SOME ntlist => (lhs, ntlist) :: r
74 end
75 val items = List.foldr add_rule [] rules
76 val nullable = Array.array(nonterms,false)
77 fun f ((NT i,nil),(l,_)) = (Array.update(nullable,i,true);
78 (l,true))
79 | f (a as (lhs,(h::t)),(l,change)) =
80 (case (nullable sub h) of
81 false => (a::l,change)
82 | true => ((lhs,t)::l,true))
83 fun prove(l,true) = prove(List.foldr f (nil,false) l)
84 | prove(_,false) = ()
85 in (prove(items,true); fn (NT i) => nullable sub i)
86 end
87
88 (* scanRhs : look at a list of symbols, scanning past nullable
89 nonterminals, applying addSymbol to the symbols scanned *)
90
91 fun scanRhs addSymbol =
92 let fun f (nil,result) = result
93 | f ((sym as NONTERM nt) :: rest,result) =
94 if nullable nt then f (rest,addSymbol(sym,result))
95 else addSymbol(sym,result)
96 | f ((sym as TERM _) :: _,result) = addSymbol(sym,result)
97 in f
98 end
99
100 (* accumulate: look at the start of the right-hand-sides of rules,
101 looking past nullable nonterminals, applying addObj to the visible
102 symbols. *)
103
104 fun accumulate(rules, empty, addObj) =
105 List.foldr (fn (RULE {rhs,...},r) =>(scanRhs addObj) (rhs,r)) empty rules
106
107 val nontermMemo = fn f =>
108 let val lookup = Array.array(nonterms,nil)
109 fun g i = if i=nonterms then ()
110 else (Array.update(lookup,i,f (NT i)); g (i+1))
111 in (g 0; fn (NT j) => lookup sub j)
112 end
113
114 (* first1: the FIRST set of a nonterminal in the grammar. Only looks
115 at other terminals, but it is clever enough to move past nullable
116 nonterminals at the start of a production. *)
117
118 fun first1 nt = accumulate(produces nt, TermSet.empty,
119 fn (TERM t, set) => TermSet.insert (t,set)
120 | (_, set) => set)
121
122 val first1 = nontermMemo(first1)
123
124 (* starters1: given a nonterminal "nt", return the set of nonterminals
125 which can start its productions. Looks past nullables, but doesn't
126 recurse *)
127
128 fun starters1 nt = accumulate(produces nt, nil,
129 fn (NONTERM nt, set) =>
130 NontermSet.insert(nt,set)
131 | (_, set) => set)
132
133 val starters1 = nontermMemo(starters1)
134
135 (* first: maps a nonterminal to its first-set. Get all the starters of
136 the nonterminal, get the first1 terminal set of each of these,
137 union the whole lot together *)
138
139 fun first nt =
140 List.foldr (fn (a,r) => TermSet.union(r,first1 a))
141 [] (NontermSet.closure (NontermSet.singleton nt, starters1))
142
143 val first = nontermMemo(first)
144
145 (* prefix: all possible terminals starting a symbol list *)
146
147 fun prefix symbols =
148 scanRhs (fn (TERM t,r) => TermSet.insert(t,r)
149 | (NONTERM nt,r) => TermSet.union(first nt,r))
150 (symbols,nil)
151
152 fun nullable_string ((TERM t) :: r) = false
153 | nullable_string ((NONTERM nt) :: r) =
154 (case (nullable nt)
155 of true => nullable_string r
156 | f => f)
157 | nullable_string nil = true
158
159 in {nullable = nullable, first = prefix}
160 end
161end;