rly 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,52 @@
1
+ require "rly/parse/lr_table"
2
+
3
+ module Rly
4
+
5
+ class PlyDump
6
+ attr_reader :backlog
7
+
8
+ def initialize(grammar)
9
+ @grammar = grammar
10
+ @backlog = ""
11
+ if grammar
12
+ @t = Rly::LRTable.new(grammar)
13
+ @t.parse_table(self)
14
+ end
15
+ end
16
+
17
+ def to_s
18
+ fn = File.join(File.dirname(__FILE__), '..', '..', '..', 'assets', 'ply_dump.erb')
19
+ e = ERB.new(open(fn).read)
20
+ e.result(TinyContext.new(g: @grammar, backlog: @backlog).get_binding)
21
+ end
22
+
23
+ def self.stub
24
+ PlyDump.new(nil)
25
+ end
26
+
27
+ def info(*args)
28
+ s = sprintf(*args)
29
+ @backlog += s + "\n"
30
+ end
31
+
32
+ def debug(*args)
33
+ s = sprintf(*args)
34
+ @backlog += s + "\n"
35
+ end
36
+
37
+ class TinyContext
38
+ def initialize(ctx)
39
+ @ctx = ctx
40
+ end
41
+
42
+ def get_binding
43
+ binding()
44
+ end
45
+
46
+ def method_missing(m)
47
+ @ctx[m]
48
+ end
49
+ end
50
+ end
51
+
52
+ end
@@ -0,0 +1,38 @@
1
+ module Rly
2
+ class Production
3
+ attr_reader :index, :name, :prod, :precedence, :block, :usyms, :line
4
+ attr_accessor :lr_items, :lr_next, :lr0_added, :reduced
5
+
6
+ # FIXME line!!!
7
+ def initialize(index, name, prod, precedence=[:right, 0], block=nil)
8
+ @index = index
9
+ @name = name
10
+ @prod = prod
11
+ @precedence = precedence
12
+ @block = block
13
+
14
+ @usyms = []
15
+ prod.each { |sym| @usyms << sym unless @usyms.include?(sym) }
16
+ end
17
+
18
+ def to_s
19
+ "#{name} -> #{@prod.map { |s| s.to_s }.join(' ')}"
20
+ end
21
+
22
+ def inspect
23
+ "#<Production #{to_s}>"
24
+ end
25
+
26
+ def length
27
+ @prod.length
28
+ end
29
+
30
+ def lr0_added
31
+ @lr0_added ||= 0
32
+ end
33
+
34
+ def reduced
35
+ @reduced ||= 0
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,68 @@
1
+ require "rly/lex"
2
+ require "rly/parse/grammar"
3
+ require "rly/parse/lr_table"
4
+
5
+ module Rly
6
+ class RuleParser < Yacc
7
+ def self.lexer_class
8
+ return @lexer_class if @lexer_class
9
+
10
+ @lexer_class = Class.new(Lex) do
11
+ token :ID, /[a-zA-Z_][a-zA-Z_0-9]*/
12
+ token :LITERAL, /"."|'.'/ do |t|
13
+ t.value = t.value[1]
14
+ t
15
+ end
16
+ literals ":|"
17
+ ignore " \t\n"
18
+ end
19
+
20
+ @lexer_class
21
+ end
22
+
23
+ def grammar
24
+ return @grammar if @grammar
25
+
26
+ @grammar = Grammar.new(self.class.lexer_class.terminals)
27
+
28
+ @grammar.add_production(:grammar, [:ID, ':', :rules]) do |g, pname, _, r|
29
+ productions = []
30
+ r.value.each do |p|
31
+ productions << [pname.value.to_sym, p]
32
+ end
33
+ g.value = productions
34
+ end
35
+ @grammar.add_production(:rules, [:rule, '|', :rules]) do |rls, r, _, rl|
36
+ rls.value = [r.value] + rl.value
37
+ end
38
+ @grammar.add_production(:rules, [:rule]) do |rl, r|
39
+ rl.value = [r.value]
40
+ end
41
+ @grammar.add_production(:rule, [:tokens]) do |r, tok|
42
+ r.value = tok.value
43
+ end
44
+ @grammar.add_production(:tokens, [:ID, :tokens]) do |t, i, toks|
45
+ t.value = [i.value.to_sym] + toks.value
46
+ end
47
+ @grammar.add_production(:tokens, [:LITERAL, :tokens]) do |t, l, toks|
48
+ t.value = [l.value] + toks.value
49
+ end
50
+ @grammar.add_production(:tokens, [:ID]) do |t, i|
51
+ t.value = [i.value.to_sym]
52
+ end
53
+ @grammar.add_production(:tokens, [:LITERAL]) do |t, l|
54
+ t.value = [l.value]
55
+ end
56
+
57
+ @grammar.set_start
58
+
59
+ @grammar.build_lritems
60
+
61
+ @lr_table = LRTable.new(@grammar)
62
+
63
+ @lr_table.parse_table
64
+
65
+ @grammar
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,11 @@
1
+ module Rly
2
+ class YaccProduction
3
+ attr_accessor :lexer, :parser, :stack, :slice
4
+
5
+ def initialize(slice, stack=nil)
6
+ @slice = slice
7
+ @stack = stack
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,6 @@
1
+ module Rly
2
+ class YaccSymbol
3
+ attr_accessor :type, :value, :lineno, :endlineno, :lexpos, :endlexpos
4
+
5
+ end
6
+ end
@@ -1,3 +1,4 @@
1
1
  module Rly
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
+ PLY_BASE_VERSION = "3.4"
3
4
  end
@@ -0,0 +1,355 @@
1
+ require "rly/lex"
2
+ require "rly/parse/grammar"
3
+ require "rly/parse/yacc_production"
4
+ require "rly/parse/yacc_symbol"
5
+
6
+ module Rly
7
+ class YaccError < RuntimeError; end
8
+
9
+ class Yacc
10
+ attr_reader :lex, :grammar, :lr_table
11
+
12
+ def initialize(lex=nil)
13
+ raise ArgumentError.new("No lexer available") if lex == nil && self.class.lexer_class == nil
14
+ @lex = lex || self.class.lexer_class.new
15
+
16
+ @grammar = grammar
17
+ end
18
+
19
+ def parse(input=nil)
20
+ lookahead = nil
21
+ lookaheadstack = []
22
+ actions = @lr_table.lr_action
23
+ goto = @lr_table.lr_goto
24
+ prod = @lr_table.lr_productions
25
+ pslice = YaccProduction.new(nil)
26
+ errorcount = 0
27
+
28
+ # Set up the lexer and parser objects on pslice
29
+ pslice.lexer = @lex
30
+ pslice.parser = self
31
+
32
+ # If input was supplied, pass to lexer
33
+ @lex.input(input) if input
34
+
35
+ # Set up the state and symbol stacks
36
+ @statestack = []
37
+ @symstack = []
38
+
39
+ pslice.stack = @symstack
40
+ errtoken = nil
41
+
42
+ # The start state is assumed to be (0,$end)
43
+ @statestack.push(0)
44
+ sym = YaccSymbol.new
45
+ sym.type = :"$end"
46
+ @symstack.push(sym)
47
+ state = 0
48
+
49
+ while true
50
+ # Get the next symbol on the input. If a lookahead symbol
51
+ # is already set, we just use that. Otherwise, we'll pull
52
+ # the next token off of the lookaheadstack or from the lexer
53
+
54
+ # DBG # puts "State: #{state}"
55
+
56
+ unless lookahead
57
+ if lookaheadstack.empty?
58
+ lookahead = @lex.next
59
+ else
60
+ lookahead = lookaheadstack.pop
61
+ end
62
+ unless lookahead
63
+ lookahead = YaccSymbol.new()
64
+ lookahead.type = :"$end"
65
+ end
66
+ end
67
+
68
+ # Check the action table
69
+ ltype = lookahead.type
70
+ t = actions[state][ltype]
71
+
72
+ if t
73
+ if t > 0
74
+ # shift a symbol on the stack
75
+ @statestack.push(t)
76
+ state = t
77
+
78
+ # DBG # puts "Action : Shift and goto state #{t}"
79
+
80
+ @symstack.push(lookahead)
81
+ lookahead = nil
82
+
83
+ # Decrease error count on successful shift
84
+ errorcount -= 1 if errorcount > 0
85
+ next
86
+ end
87
+
88
+ if t < 0
89
+ # reduce a symbol on the stack, emit a production
90
+ p = prod[-t]
91
+ pname = p.name
92
+ plen = p.length
93
+
94
+ # Get production function
95
+ sym = YaccSymbol.new()
96
+ sym.type = pname
97
+ sym.value = nil
98
+
99
+ # DBG # if plen
100
+ # DBG # puts "Action : Reduce rule [#{p}] with [#{@symstack[-plen..@symstack.length].map{|s|s.value}.join(', ')}] and goto state #{-t}"
101
+ # DBG # else
102
+ # DBG # puts "Action : Reduce rule [#{p}] with [] and goto state #{-t}"
103
+ # DBG # end
104
+
105
+ if plen
106
+ targ = @symstack.pop(plen)
107
+ targ.insert(0, sym)
108
+
109
+ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
110
+ # The code enclosed in this section is duplicated
111
+ # below as a performance optimization. Make sure
112
+ # changes get made in both locations.
113
+
114
+ pslice.slice = targ
115
+
116
+ begin
117
+ # Call the grammar rule with our special slice object
118
+ @statestack.pop(plen)
119
+ instance_exec(*targ, &p.block)
120
+
121
+ # DBG # puts "Result : #{targ[0].value}"
122
+
123
+ @symstack.push(sym)
124
+ state = goto[@statestack[-1]][pname]
125
+ @statestack.push(state)
126
+ rescue YaccError
127
+ # If an error was set. Enter error recovery state
128
+ lookaheadstack.push(lookahead)
129
+ @symstack.pop # FIXME: this is definitely broken
130
+ @statestack.pop
131
+ state = @statestack[-1]
132
+ sym.type = :error
133
+ lookahead = sym
134
+ errorcount = self.class.error_count
135
+ @errorok = false
136
+ end
137
+ next
138
+ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
139
+ else
140
+ targ = [ sym ]
141
+
142
+ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
143
+ # The code enclosed in this section is duplicated
144
+ # below as a performance optimization. Make sure
145
+ # changes get made in both locations.
146
+
147
+ pslice.slice = targ
148
+
149
+ begin
150
+ # Call the grammar rule with our special slice object
151
+ @statestack.pop(plen)
152
+ pslice[0] = instance_exec(*pslice, &p.block)
153
+
154
+ # DBG # puts "Result : #{targ[0].value}"
155
+
156
+ @symstack.push(sym)
157
+ state = goto[@statestack[-1]][pname]
158
+ @statestack.push(state)
159
+ rescue
160
+ # If an error was set. Enter error recovery state
161
+ lookaheadstack.push(lookahead)
162
+ @symstack.pop # FIXME: this is definitely broken
163
+ @statestack.pop
164
+ state = @statestack[-1]
165
+ sym.type = :error
166
+ lookahead = sym
167
+ errorcount = error_count
168
+ @errorok = false
169
+ end
170
+ next
171
+ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
172
+ end
173
+ end
174
+
175
+ if t == 0
176
+ n = @symstack[-1]
177
+ result = n.value
178
+
179
+ # DBG # puts "Done : Returning #{result}"
180
+
181
+ return result
182
+ end
183
+ end
184
+
185
+ if t == nil
186
+ # We have some kind of parsing error here. To handle
187
+ # this, we are going to push the current token onto
188
+ # the tokenstack and replace it with an 'error' token.
189
+ # If there are any synchronization rules, they may
190
+ # catch it.
191
+ #
192
+ # In addition to pushing the error token, we call call
193
+ # the user defined p_error() function if this is the
194
+ # first syntax error. This function is only called if
195
+ # errorcount == 0.
196
+ if errorcount == 0 || @errorok == true
197
+ errorcount = error_count
198
+ @errorok = false
199
+ errtoken = lookahead
200
+ errtoken = nil if errtoken.type == :"$end"
201
+
202
+ if self.class.error_handler
203
+ errok = @errok
204
+ token = @lex.next
205
+ restart = @restart
206
+ errtoken.lex = @lex if errtoken
207
+
208
+ tok = self.class.error_handler.call(errtoken)
209
+
210
+ if @errorok
211
+ # User must have done some kind of panic
212
+ # mode recovery on their own. The
213
+ # returned token is the next lookahead
214
+ lookahead = tok
215
+ errtoken = nil
216
+ next
217
+ end
218
+ else
219
+ if errtoken
220
+ # if hasattr(errtoken,"lineno"): lineno = lookahead.lineno
221
+ # else: lineno = 0
222
+ #if lineno:
223
+ # sys.stderr.write("yacc: Syntax error at line %d, token=%s\n" % (lineno, errtoken.type))
224
+ #else:
225
+ # sys.stderr.write("yacc: Syntax error, token=%s" % errtoken.type)
226
+ else
227
+ # sys.stderr.write("yacc: Parse error in input. EOF\n")
228
+ return nil
229
+ end
230
+ end
231
+ else
232
+ errorcount = self.class.error_count
233
+ end
234
+
235
+ # case 1: the @statestack only has 1 entry on it. If we're in this state, the
236
+ # entire parse has been rolled back and we're completely hosed. The token is
237
+ # discarded and we just keep going.
238
+
239
+ if @statestack.length <= 1 and lookahead.type != :"$end"
240
+ lookahead = nil
241
+ errtoken = nil
242
+ state = 0
243
+ # Nuke the pushback stack
244
+ lookaheadstack = []
245
+ next
246
+ end
247
+
248
+ # case 2: the @statestack has a couple of entries on it, but we're
249
+ # at the end of the file. nuke the top entry and generate an error token
250
+
251
+ # Start nuking entries on the stack
252
+ if lookahead.type == :"$end"
253
+ # Whoa. We're really hosed here. Bail out
254
+ return nil
255
+ end
256
+
257
+ if lookahead.type != :error
258
+ sym = @symstack[-1]
259
+ if sym.type == :error
260
+ # Hmmm. Error is on top of stack, we'll just nuke input
261
+ # symbol and continue
262
+ lookahead = nil
263
+ next
264
+ end
265
+ t = YaccSymbol.new
266
+ t.type = :error
267
+ # if hasattr(lookahead,"lineno"):
268
+ # t.lineno = lookahead.lineno
269
+ t.value = lookahead
270
+ lookaheadstack.push(lookahead)
271
+ lookahead = t
272
+ else
273
+ @symstack.pop
274
+ @statestack.pop
275
+ state = @statestack[-1] # Potential bug fix
276
+ end
277
+
278
+ next
279
+ end
280
+
281
+ # Call an error function here
282
+ raise RuntimeError.new("yacc: internal parser error!!!")
283
+ end
284
+ end
285
+
286
+ protected
287
+ def grammar
288
+ return @grammar if @grammar
289
+
290
+ @grammar = Grammar.new(@lex.class.terminals)
291
+
292
+ self.class.prec_rules.each do |assoc, terms|
293
+ terms.each_with_index do |term, i|
294
+ @grammar.set_precedence(term, assoc, i)
295
+ end
296
+ end
297
+
298
+ self.class.parsed_rules.each do |prod, block|
299
+ @grammar.add_production(*prod, &block)
300
+ end
301
+
302
+ @grammar.set_start
303
+
304
+ @grammar.build_lritems
305
+
306
+ @lr_table = LRTable.new(@grammar)
307
+
308
+ @lr_table.parse_table
309
+
310
+ @grammar
311
+ end
312
+
313
+ class << self
314
+ attr_accessor :rules, :grammar, :lexer_class, :prec_rules
315
+
316
+ def rule(desc, &block)
317
+ self.rules << [desc, block]
318
+ end
319
+
320
+ def lexer(&block)
321
+ @lexer_class = Class.new(Lex, &block)
322
+ end
323
+
324
+ def rules
325
+ @rules ||= []
326
+ end
327
+
328
+ def precedence(*prec)
329
+ assoc = prec.shift
330
+ self.prec_rules << [assoc, prec.reverse]
331
+ end
332
+
333
+ def prec_rules
334
+ @prec_rules ||= []
335
+ end
336
+
337
+ def error_count
338
+ 3
339
+ end
340
+
341
+ def parsed_rules
342
+ @parsed_rules if @parsed_rules
343
+
344
+ @parsed_rules = []
345
+ rp = RuleParser.new
346
+ self.rules.each do |d, b|
347
+ rp.parse(d).each do |prod|
348
+ @parsed_rules << [prod, b]
349
+ end
350
+ end
351
+ @parsed_rules
352
+ end
353
+ end
354
+ end
355
+ end