Commit | Line | Data |
---|---|---|
7f918cf1 CE |
1 | (* ML-Yacc Parser Generator (c) 1989 Andrew W. Appel, David R. Tarditi *) |
2 | ||
3 | functor 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 | |
161 | end; |