File: tech.doc

package info (click to toggle)
mlton 20100608-2
  • links: PTS
  • area: main
  • in suites: squeeze
  • size: 34,980 kB
  • ctags: 69,089
  • sloc: ansic: 18,421; lisp: 2,879; makefile: 1,570; sh: 1,325; pascal: 256; asm: 97
file content (252 lines) | stat: -rw-r--r-- 10,976 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
A Hacker's guide ML-Yacc itself

The program for computing the LALR(1) table can be divided into 3 separate
parts.  The first part computes the LR(0) graph.  The second part attaches
lookahead to the LR(0) graph to get the LALR(1) graph.  The third part
computes the parse tables from the LALR(1) graph.

Look at the file sigs.sml to see how the modules are layed out.
The file graph.sml contains the Graph functor, which produces a structure
containing a function mkGraph.  mkGraph takes a grammar and returns a
some useful values and functions, including the LR(0) graph.  It renumbers
the rules to an internal form to make the LR(0) graph generation more
efficient.  The LR(0) graph includes only core items in its set of items.

The file look.sml takes some of theses values and produces functions
which tell whether a nonterm is nullable and the first set of a symbol
list.

The functor mkLalr creates a structure with a function that takes an LR(0)
graph and some other values (notably the first and nullable) functions
produced by Look and creates a stripped down version of an LR(0) graph with
lookaheads attached.  Nullable items (which usually aren't core items) are
added and all other items without dots at the end (i.e. non-reduction items)
are removed.

The functor MkTable produces a function with takes the LR(0) graph
produced by the function in mkGraph and the LR(0) graph with lookaheads
produced by Lalr and creates an LALR(1) table from these graphs.


-----------------------------------------------------------------------
An overview of the algorithms used in LR(0) graph generation and
LALR(1) lookahead creation.

LR(0) graph
-----------

The LR(0) graph consists of sets of items.  Each set of items will be
called a core set.  The basic algorithm is:

        let fun add_gotos(graph,f,nil,r) = (graph,r)
              | add_gotos(graph,f,(a,symbol)::b,r)
                        let newgraph = graph + edge from f to a labelled
                            with symbol
                        in if a exists in graph then
                                add_gotos(newgraph,f,b,r)
                           else add_gotos(newgraph,f,b,a::r)
                        end
             fun f(graph,nil) = graph
               | f(graph,a::b) = f(add_gotos(graph,a,gotos of closure a,b))
        in f(empty-graph,[initial core set])
        end

For each core, we compute the new cores which result from doing a shift
or goto, and then add these new cores with the symbol used in the shift
or goto to the graph.  We continue doing this until there are no more cores
to adds to the graph.

We have to take the closure of a core to include those items which are
derived from nonterminals with a dot before them.  If item A -> 'a .B 'c
is in a core, the all productions derived by B must also be in the core.

We want to be able to do the following operations efficently:
        (1) check if a core is in the graph already
        (2) compute the closure of a core
        (3) compute the cores resulting from goto/shift operations.

(1) This can be done efficiently if a complete order exists for the cores. This
can be done by imposing an ordering on items, giving each item a unique
integer and using the place in an item.  This can be  used to order a
set of items.

(2) Much of the computation for the closure can be done ahead of time.
The set of nonterminals to add for a given a nonterminal can be pre-computed
using a transitive closure algorithm (the transitive closure is sparse
in practice).  One can then compute the closure for a core in the following
manner.  First, compute the set of nonterminals with . in front of them.
This can be done in (m ln m) time.   Next, use the results from the
transitive closure to compute the complete set of nonterminals that
should be used.  Finally, for each nonterminal, merge its set of
productions (sort all rules by the nonterminals from which they
are derived before numbering them, then all we have to do is just
prepend the rules while scanning the list in reverse order).

(3) To do this, just scan the core closure, sorting rules by their
symbols into lists.  Then reverse all the lists, and we have the
new core sets.

Lookahead representation
------------------------

The previous part throws away the result of the closure operations.
It is used only to compute new cores for use in the goto operation.
These intermediate results should be saved because they will be useful
here.

Lookaheads are attached to an item when

        (1) an item is the result of a shift/goto.  The item
            must have the same lookahead as the item from which it
            is derived.
        (2) an item is added as the result of a closure.  Note that
            in fact all productions derived from a given nonterminal
            are added here.  This can be used (perhaps) to our
            advantage, as we can represent a closure using just the
            nonterminal.

            This can be divided into two cases:

                (a) A -> 'a .B 'c , where 'c derives epsilon,
                (b) A -> 'a .B 'c , where 'c does not derive epsilon

            In (a), lookahead(items derived from B) includes first('c)
            and lookahead(A -> 'a .B 'c)

            In (b), lookahead(items derived from B) includes only first('c).

            This is an example of back propagation.

        Note that an item is either the result of a closure or the
        result of a shift/goto.  It is never the result of both (that
        would be a contradiction).

        The following representation will be used:

          goto/shift items:
                an ordered list of item * lookahead ref *
                                          lookahead ref for the resulting
                                          shift/goto item in another core.

          closure items:
                for each nonterminal:
                   (1) lookahead ref
                   (2) a list of item * lookahead ref for the
                                        resulting shift/goto item in another
                                        core.

Lookahead algorithms
--------------------

After computing the LR(0) graph, lookaheads must be attached to the items in
the graph.  An item i may receive lookaheads in two ways.  If item i
was the result of a shift or goto from some item j, then lookahead(i) includes
lookahead(j).  If item i is a production of some nonterminal B, and there
exists some item j of the form A -> x .B y, then item i will be added through
closure(j).  This implies that lookahead(i) includes first(y).  If y =>
epsilon, then lookahead(i) includes lookahead(j).

Lookahead must be recorded for completion items, which are items of the
form A -> x., non-closure items of the form A -> y . B z, where z is
not nullable, and closure items of the form A -> epsilon.  (comment:
items of the form A -> .x can appear in the start state as non-closure items.
A must be the start symbol, which should not appear in the right hand side
of any rule.  This implies that lookaheads will never be propagated to
such items)

We chose to omit closure items that do not have the form A -> epsilon.
It is possible to add lookaheads to closure items, but we have not
done so because it would greatly slow down the addition of lookaheads.

Instead we precompute the nonterminals whose productions are
added through the closure operation, the lookaheads for these
nonterminals, and whether the lookahead for these nonterminals
should include first(y) and lookahead(j) for some item j of the
form  A -> x .B y.  This information depends only on the particular
nonterminal whose closure is being taken.

Some notation is necessary to describe what is happening here.  Let
=c=> denote items added in one closure step that are derived from some
nonterminal B in a production A -> x .B y.  Let =c+=> denote items
added in one or more =c=> steps.

Consider the following productions

                B -> S ;
                S -> E
                E -> F * E
                F -> num

in a kernal with the item

                B -> .S

The following derivations are possible:

B -> .S   =c=>   S -> .E        =c+=>   S -> .E, E -> .F * E, F -> .num

The nonterminals that are added through the closure operation
are the nonterminals for some item j = A -> .B x such that j =c+=> .C y.
Lookahead(C) includes first(y).  If y =*=> epsilon then
lookahead (C) includes first (x).  If x=*=> epsilon and y =*=> epsilon
then lookahead(C) includes first(j).

The following algorithm computes the information for each nonterminal:

        (1) nonterminals  such that c =c+=> .C y and y =*=> epsilon

        Let s = the set of nonterminals added through closure = B

        repeat
                for all B which are elements of s,
                        if B -> .C z and z =*=> epsilon then
                        add B to s.
        until s does not change.

        (2) nonterminals added through closure and their lookaheads

        Let s = the set of nonterminals added through closure = B
        where A -> x . B y

        repeat
                for all B which are elements of s,
                        if B -> .C z then add C to s, and
                        add first(z) to lookahead(C)
        until nothing changes.

        Now, for each nonterminal A in s, find the set of nonterminals
        such that A =c+=> .B z, and z =*=> epsilon (i.e. use the results
        from 1).  Add the lookahead for nonterminal A to the lookahead
        for each nonterminal in this set.

These algorithms can be restated as either breadth-first or depth-first search
algorithms.   The loop invariant of the algorithms is that whenever a
nonterminal is added to the set being calculated, all the productions
for the nonterminal are checked.

This algorithm computes the lookahead for each item:

  for each state,
        for each item of the form A -> u .B v in the state, where u may be
        nullable,
           let  first_v = first(v)
                l-ref  = ref for A -> u .B v
                s = the set of nonterminals added through the closure of B.

                for each element X of s,

                  let r = the rules produced by an element X of s
                      l = the lookahead ref cells for each rule, i.e.
                          all items of A -> x. or A -> x .B y, where
                          y =*=> epsilon, and x is not epsilon

                      add the lookahead we have computed for X to the
                      elements of l

                      if B =c+=> X z, where z is nullable, add first(y) to
                      the l.  If y =*=> epsilon, save l with the ref for
                      A -> x .B y in a list.

 Now take the list of (lookahead ref, list of lookahead refs) and propagate
 each lookahead ref cell's contents to the elements of the list of lookahead
 ref cells associated with it.  Iterate until no lookahead set changes.