nendo 0.5.4 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,585 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- encoding: utf-8 -*-
3
+ #
4
+ # reader.rb - "sexp reader for nendo"
5
+ #
6
+ # Copyright (c) 2009-2010 Kiyoka Nishiyama <kiyoka@sumibi.org>
7
+ #
8
+ # Redistribution and use in source and binary forms, with or without
9
+ # modification, are permitted provided that the following conditions
10
+ # are met:
11
+ #
12
+ # 1. Redistributions of source code must retain the above copyright
13
+ # notice, this list of conditions and the following disclaimer.
14
+ #
15
+ # 2. Redistributions in binary form must reproduce the above copyright
16
+ # notice, this list of conditions and the following disclaimer in the
17
+ # documentation and/or other materials provided with the distribution.
18
+ #
19
+ # 3. Neither the name of the authors nor the names of its contributors
20
+ # may be used to endorse or promote products derived from this
21
+ # software without specific prior written permission.
22
+ #
23
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
26
+ # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
27
+ # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
29
+ # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
30
+ # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31
+ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
32
+ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
33
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34
+ #
35
+ module Nendo
36
+
37
+ class CharReader
38
+ def initialize( inport, sourcefile )
39
+ @inport = inport
40
+ @sourcefile = sourcefile
41
+ self.reset
42
+ end
43
+
44
+ def reset
45
+ @lineno = 1
46
+ @column = 1
47
+ end
48
+
49
+ def getc
50
+ @undo_lineno = @lineno
51
+ @undo_column = @column
52
+ ch = @inport.getc
53
+ if nil != ch
54
+ if ch.chr.match( /[\r\n]/ )
55
+ @lineno += 1
56
+ end
57
+ @column += 1
58
+ end
59
+ ch
60
+ end
61
+
62
+ def ungetc( ch )
63
+ @lineno = @undo_lineno
64
+ @column = @undo_column
65
+ @inport.ungetc( ch )
66
+ end
67
+
68
+ def sourcefile
69
+ @sourcefile
70
+ end
71
+
72
+ def lineno
73
+ @lineno
74
+ end
75
+
76
+ def column
77
+ @column
78
+ end
79
+ end
80
+
81
+ class Reader
82
+ ## tokens
83
+ T_EOF = :t_eof
84
+ T_LPAREN = :t_lparen
85
+ T_RPAREN = :t_rparen
86
+ T_LVECTOR = :t_lvector
87
+ T_SYMBOL = :t_symbol
88
+ T_KEYWORD = :t_keyword
89
+ T_NUM = :t_num
90
+ T_STRING = :t_string
91
+ T_QUOTE = :t_quote
92
+ T_QUASIQUOTE = :t_quasiquote
93
+ T_UNQUOTE = :t_unquote
94
+ T_UNQUOTE_SPLICING = :t_unquote_splicing
95
+ T_FEEDTO = :t_feedto
96
+ T_DOT = :t_dot
97
+ T_LINEFEED = :t_linefeed
98
+ T_COMMENT = :t_comment
99
+ T_DEBUG_PRINT = :t_debug_print
100
+ T_MACRO_DEBUG_PRINT = :t_macro_debug_print
101
+ T_REGEXP = :t_regexp
102
+
103
+ # inport is IO class
104
+ def initialize( inport, sourcefile, debug = false )
105
+ @inport = inport
106
+ @sourcefile = sourcefile
107
+ @chReader = nil
108
+ @curtoken = nil
109
+ @debug = debug
110
+ end
111
+
112
+ def reset
113
+ @chReader.reset if @chReader
114
+ end
115
+
116
+ def sourcefile
117
+ @sourcefile
118
+ end
119
+
120
+ def lineno
121
+ if @chReader
122
+ @chReader.lineno
123
+ else
124
+ 1
125
+ end
126
+ end
127
+
128
+ def skipspace
129
+ begin
130
+ ch = @chReader.getc
131
+ break if nil == ch # non eof?
132
+ #printf( " skipspace: [%02x]\n", ch ) if @debug
133
+ end while ch.chr.match( /[ \t]/ )
134
+ @chReader.ungetc( ch ) if nil != ch
135
+ end
136
+
137
+ def readwhile( exp, oneshot = false )
138
+ ret = ""
139
+ while true
140
+ ch = @chReader.getc
141
+ #printf( " readwhile: [%02x]\n", ch ) if @debug
142
+ if !ch # eof?
143
+ break
144
+ end
145
+ if ch.chr.match( exp )
146
+ ret += ch.chr
147
+ else
148
+ @chReader.ungetc( ch )
149
+ break
150
+ end
151
+ if oneshot then break end
152
+ end
153
+ ret
154
+ end
155
+
156
+ def peekchar( exp )
157
+ ch = @chReader.getc
158
+ #printf( " peekchar: [%02x]\n", ch ) if @debug
159
+ if !ch # eof?
160
+ return nil
161
+ end
162
+ if ch.chr.match( exp )
163
+ ch.chr
164
+ else
165
+ @chReader.ungetc( ch )
166
+ nil
167
+ end
168
+ end
169
+
170
+ def readstring()
171
+ ret = ""
172
+ while true
173
+ ch = @chReader.getc
174
+ #printf( " readstring: [%s]\n", ch )
175
+ if !ch # eof?
176
+ break
177
+ end
178
+ if ch.chr == "\\"
179
+ ch2 = @chReader.getc
180
+ ret += case ch2.chr
181
+ when '"' # \" reduce to "
182
+ '"'
183
+ when '\\' # \\ reduce to \
184
+ "\\"
185
+ when 'n'
186
+ "\n"
187
+ when 'r'
188
+ "\r"
189
+ when 't'
190
+ "\t"
191
+ else
192
+ ""
193
+ end
194
+ elsif ch.chr != '"'
195
+ ret += ch.chr
196
+ else
197
+ @chReader.ungetc( ch )
198
+ break
199
+ end
200
+ end
201
+ ret
202
+ end
203
+
204
+ def readRegexp()
205
+ ret = ""
206
+ while true
207
+ ch = @chReader.getc
208
+ #printf( " readRegexp1: [%s]\n", ch )
209
+ if !ch # eof?
210
+ break
211
+ end
212
+ if ch.chr == "\\" #escape
213
+ ch2 = @chReader.getc
214
+ #printf( " readRegexp2: [%s]\n", ch2 )
215
+ ret += "\\" + ch2.chr
216
+ elsif ch.chr == '/'
217
+ break
218
+ else
219
+ ret += ch.chr
220
+ end
221
+ end
222
+ ret
223
+ end
224
+
225
+ def tokenWithComment
226
+ skipspace
227
+ ch = @chReader.getc
228
+ if nil == ch # eof?
229
+ @curtoken = Token.new( T_EOF, "", @chReader.sourcefile, @chReader.lineno, @chReader.column )
230
+ else
231
+ str = ch.chr
232
+ kind =
233
+ case str
234
+ when /[\']/
235
+ T_QUOTE
236
+ when /[\`]/
237
+ T_QUASIQUOTE
238
+ when /[,]/
239
+ str += readwhile( /[@]/, true )
240
+ if 1 == str.length
241
+ T_UNQUOTE
242
+ else
243
+ T_UNQUOTE_SPLICING
244
+ end
245
+ when '(', '['
246
+ T_LPAREN
247
+ when ')', ']'
248
+ T_RPAREN
249
+ when '.'
250
+ str += readwhile( /[_a-zA-Z0-9!?.]/ )
251
+ if 1 == str.length
252
+ T_DOT
253
+ else
254
+ T_SYMBOL
255
+ end
256
+ when /[\r\n]/
257
+ T_LINEFEED
258
+ when /;/
259
+ readwhile( /[^\r\n]/ )
260
+ str = ""
261
+ T_COMMENT
262
+ when /[#]/
263
+ nextch = peekchar( /[?!tfbodx(\/]/ )
264
+ case nextch
265
+ when "?"
266
+ if peekchar( /[=]/ )
267
+ str = ""
268
+ T_DEBUG_PRINT
269
+ elsif peekchar( /[.]/ )
270
+ str = ""
271
+ T_MACRO_DEBUG_PRINT
272
+ else
273
+ str += readwhile( /[^ \t\r\n]/ )
274
+ raise NameError, sprintf( "Error: unknown #xxxx syntax for Nendo %s", str )
275
+ end
276
+ when "!"
277
+ readwhile( /[^\r\n]/ )
278
+ str = ""
279
+ T_COMMENT
280
+ when "("
281
+ str = ""
282
+ T_LVECTOR
283
+ when "t"
284
+ str = "true"
285
+ T_SYMBOL
286
+ when "f"
287
+ str = "false"
288
+ T_SYMBOL
289
+ when "b","o","d","x"
290
+ str = readwhile( /[0-9a-zA-Z]/ )
291
+ case nextch
292
+ when "b"
293
+ if str.match( /^[0-1]+$/ )
294
+ str = "0b" + str
295
+ else
296
+ raise RuntimeError, sprintf( "Error: illegal #b number for Nendo #b%s", str )
297
+ end
298
+ when "o"
299
+ if str.match( /^[0-7]+$/ )
300
+ str = "0o" + str
301
+ else
302
+ raise RuntimeError, sprintf( "Error: illegal #o number for Nendo #o%s", str )
303
+ end
304
+ when "d"
305
+ if str.match( /^[0-9]+$/ )
306
+ str = "0d" + str
307
+ else
308
+ raise RuntimeError, sprintf( "Error: illegal #d number for Nendo #d%s", str )
309
+ end
310
+ when "x"
311
+ if str.match( /^[0-9a-fA-F]+$/ )
312
+ str = "0x" + str
313
+ else
314
+ raise RuntimeError, sprintf( "Error: illegal #x number for Nendo #x%s", str )
315
+ end
316
+ end
317
+ str = Integer( str ).to_s
318
+ T_NUM
319
+ when "/" # T_REGEXP's str takes "iXXXXX"(igreno case) or " XXXXXX"(case sensitive) value.
320
+ readwhile( /[\/]/ ) # consume
321
+ str = readRegexp()
322
+ str = ((0 < readwhile( /[i]/ ).size) ? "i" : " ") + str
323
+ T_REGEXP
324
+ else
325
+ str += readwhile( /[^ \t\r\n]/ )
326
+ raise NameError, sprintf( "Error: unknown #xxxx syntax for Nendo %s", str )
327
+ end
328
+ when /[_a-zA-Z!$%&*+\/:<=>?@^~-]/ # symbol
329
+ str += readwhile( /[0-9._a-zA-Z!$%&*+\/:<=>?@^~-]/ )
330
+ if str.match( /^[=][>]$/ )
331
+ T_FEEDTO
332
+ elsif str.match( /^[+-][0-9.]+$/ )
333
+ T_NUM
334
+ elsif str.match( /^[:]/ )
335
+ str = str[1..-1]
336
+ T_KEYWORD
337
+ else
338
+ T_SYMBOL
339
+ end
340
+ when /[0-9]/ # Numeric
341
+ str += readwhile( /[0-9.]/ )
342
+ T_NUM
343
+ when /["]/ # String
344
+ str = LispString.new( readstring() )
345
+ readwhile( /["]/ )
346
+ T_STRING
347
+ else
348
+ str += readwhile( /[^ \t\r\n]/ )
349
+ raise NameError, sprintf( "Error: unknown token for Nendo [%s]", str )
350
+ end
351
+ printf( " token: [%s] : %s (%s:L%d:C%d)\n", str, kind.to_s, @chReader.sourcefile, @chReader.lineno, @chReader.column ) if @debug
352
+ @curtoken = Token.new( kind, str, @chReader.sourcefile, @chReader.lineno, @chReader.column )
353
+ end
354
+ end
355
+
356
+ def token
357
+ begin
358
+ tokenWithComment
359
+ end while T_COMMENT == curtoken.kind
360
+ curtoken
361
+ end
362
+
363
+ def curtoken
364
+ if !@curtoken
365
+ self.token
366
+ end
367
+ @curtoken
368
+ end
369
+
370
+ def atom
371
+ cur = curtoken
372
+ printf( " NonT: [%s] : [%s]\n", "atom", cur.str ) if @debug
373
+ token
374
+ case cur.kind
375
+ when T_SYMBOL
376
+ sym = cur.str.intern
377
+ sym.setLispToken( cur )
378
+ case sym
379
+ when :true
380
+ true
381
+ when :false
382
+ false
383
+ when :nil
384
+ nil
385
+ else
386
+ sym
387
+ end
388
+ when T_NUM
389
+ if cur.str.match( /[.]/ ) # floating point
390
+ cur.str.to_f
391
+ else
392
+ cur.str.to_i
393
+ end
394
+ when T_STRING
395
+ cur.str
396
+ when T_REGEXP
397
+ LispRegexp.new( cur.str )
398
+ when T_QUOTE
399
+ :quote
400
+ when T_QUASIQUOTE
401
+ :quasiquote
402
+ when T_UNQUOTE
403
+ :unquote
404
+ when T_UNQUOTE_SPLICING
405
+ :"unquote-splicing"
406
+ when T_DOT
407
+ :"dot-operator"
408
+ when T_FEEDTO
409
+ :feedto
410
+ when T_DEBUG_PRINT
411
+ "debug-print".intern
412
+ when T_MACRO_DEBUG_PRINT
413
+ LispString.new( sprintf( "%s:%d", cur.sourcefile, cur.lineno ))
414
+ when T_KEYWORD
415
+ LispKeyword.new( cur.str )
416
+ else
417
+ raise "Error: Unknown token in atom()"
418
+ end
419
+ end
420
+
421
+ # vector := sexp
422
+ # | atom ... atom
423
+ def vector
424
+ printf( " NonT: [%s]\n", "vector" ) if @debug
425
+ arr = []
426
+ while true
427
+ case curtoken.kind
428
+ when T_LINEFEED
429
+ token # skipEnter
430
+ when T_EOF
431
+ begin
432
+ raise RuntimeError, "Error: unbalanced vector's paren(4)"
433
+ rescue => e
434
+ e.set_backtrace( [sprintf( "%s:%d", curtoken.sourcefile, curtoken.lineno )] + e.backtrace )
435
+ raise e
436
+ end
437
+ when T_LPAREN, T_LVECTOR
438
+ arr << sexp()
439
+ when T_RPAREN
440
+ break
441
+ when T_QUOTE , T_QUASIQUOTE , T_UNQUOTE , T_UNQUOTE_SPLICING, T_DEBUG_PRINT
442
+ arr << sexp()
443
+ when T_DOT
444
+ raise RuntimeError, "Error: illegal list."
445
+ else
446
+ arr << atom()
447
+ end
448
+ end
449
+ arr
450
+ end
451
+
452
+ # list := sexp
453
+ # | atom ... atom
454
+ # | atom ... . atom
455
+ def list
456
+ printf( " NonT: [%s]\n", "list" ) if @debug
457
+ dotted = false
458
+ cells = []
459
+ lastAtom = Nil.new
460
+ while true
461
+ case curtoken.kind
462
+ when T_LINEFEED
463
+ token # skipEnter
464
+ when T_EOF
465
+ begin
466
+ raise RuntimeError, "Error: unbalanced paren(1)"
467
+ rescue => e
468
+ e.set_backtrace( [sprintf( "%s:%d", curtoken.sourcefile, curtoken.lineno )] + e.backtrace )
469
+ raise e
470
+ end
471
+ when T_LPAREN, T_LVECTOR
472
+ cells << Cell.new( sexp() )
473
+ when T_RPAREN
474
+ break
475
+ when T_DOT
476
+ if 0 == cells.length
477
+ # (. symbol1 symbol2 ... ) form
478
+ cells << Cell.new( atom() )
479
+ else
480
+ # ( symbol1 ... symbol2 . symbol3 ) form
481
+ token
482
+ lastAtom = sexp()
483
+ if lastAtom.is_a? Cell and lastAtom.isNull
484
+ lastAtom = Nil.new # the null list "()" could not be a lastAtom.
485
+ end
486
+ end
487
+ when T_QUOTE , T_QUASIQUOTE , T_UNQUOTE , T_UNQUOTE_SPLICING, T_DEBUG_PRINT
488
+ cells << Cell.new( sexp() )
489
+ else
490
+ if not lastAtom.is_a? Nil
491
+ raise "Error : illegal dotted pair syntax."
492
+ else
493
+ cells << Cell.new( atom() )
494
+ end
495
+ end
496
+ end
497
+ ## setup list
498
+ if 0 == cells.size
499
+ Cell.new() # null list
500
+ elsif 1 == cells.size
501
+ cells.first.cdr = lastAtom
502
+ cells.first
503
+ elsif 1 < cells.size
504
+ ptr = cells.pop
505
+ ptr.cdr = lastAtom
506
+ cells.reverse.each { |x|
507
+ x.cdr = ptr
508
+ ptr = x
509
+ }
510
+ cells.first
511
+ end
512
+ end
513
+
514
+ def skipEnter
515
+ while T_LINEFEED == curtoken.kind
516
+ token
517
+ end
518
+ end
519
+
520
+ # sexp := ( list ) | | #( vector ) | 'sexp | `sexp | atom
521
+ def sexp
522
+ printf( " NonT: [%s]\n", "sexp" ) if @debug
523
+ case curtoken.kind
524
+ when T_LINEFEED
525
+ token
526
+ sexp()
527
+ when T_EOF
528
+ begin
529
+ raise RuntimeError, "Error: unbalanced paren(2)"
530
+ rescue => e
531
+ e.set_backtrace( [sprintf( "%s:%d", curtoken.sourcefile, curtoken.lineno )] + e.backtrace )
532
+ raise e
533
+ end
534
+ when T_LPAREN
535
+ skipEnter
536
+ token # consume '('
537
+ ret = list()
538
+ skipEnter
539
+ token # consume ')'
540
+ ret
541
+ when T_RPAREN
542
+ token # consume ')'
543
+ begin
544
+ raise RuntimeError, "Error: unbalanced vector's paren(3)"
545
+ rescue => e
546
+ e.set_backtrace( [sprintf( "%s:%d", curtoken.sourcefile, curtoken.lineno )] + e.backtrace )
547
+ raise e
548
+ end
549
+ when T_LVECTOR
550
+ skipEnter
551
+ token # consume '#('
552
+ ret = vector()
553
+ skipEnter
554
+ token # consume ')'
555
+ ret
556
+ when T_QUOTE , T_QUASIQUOTE , T_UNQUOTE , T_UNQUOTE_SPLICING
557
+ _atom = atom() ## "quote" symbol
558
+ Cell.new( _atom, Cell.new( sexp() ))
559
+ when T_DEBUG_PRINT
560
+ file = curtoken.sourcefile
561
+ lineno = curtoken.lineno
562
+ _atom = atom() ## "debug-print" symbol
563
+ child = sexp()
564
+ [_atom, child, LispString.new( file ), lineno, Cell.new( :quote, Cell.new( child )) ].to_list
565
+ else
566
+ atom()
567
+ end
568
+ end
569
+
570
+ # return value is [ S-expression-tree, eof-flag, valid-sexp-flag ]
571
+ def _read
572
+ @chReader = CharReader.new( @inport, @sourcefile ) unless @chReader
573
+ case curtoken.kind
574
+ when T_EOF
575
+ [ Nil.new, true, false ]
576
+ when T_LINEFEED
577
+ token
578
+ [ Nil.new, false, false ]
579
+ else
580
+ [ sexp(), false, true ]
581
+ end
582
+ end
583
+ end
584
+
585
+ end