nendo 0.5.4 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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