review 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/COPYING +515 -0
  2. data/ChangeLog +1278 -0
  3. data/README.rdoc +21 -0
  4. data/Rakefile +42 -0
  5. data/VERSION +1 -0
  6. data/bin/review-check +190 -0
  7. data/bin/review-checkdep +63 -0
  8. data/bin/review-compile +165 -0
  9. data/bin/review-epubmaker +525 -0
  10. data/bin/review-index +108 -0
  11. data/bin/review-preproc +140 -0
  12. data/bin/review-validate +51 -0
  13. data/bin/review-vol +106 -0
  14. data/doc/format.re +486 -0
  15. data/doc/format.txt +434 -0
  16. data/doc/format_idg.txt +194 -0
  17. data/doc/format_sjis.txt +313 -0
  18. data/doc/sample.css +91 -0
  19. data/doc/sample.yaml +46 -0
  20. data/lib/lineinput.rb +155 -0
  21. data/lib/review/book.rb +580 -0
  22. data/lib/review/builder.rb +274 -0
  23. data/lib/review/compat.rb +22 -0
  24. data/lib/review/compiler.rb +483 -0
  25. data/lib/review/epubbuilder.rb +692 -0
  26. data/lib/review/ewbbuilder.rb +382 -0
  27. data/lib/review/exception.rb +21 -0
  28. data/lib/review/htmlbuilder.rb +370 -0
  29. data/lib/review/htmllayout.rb +19 -0
  30. data/lib/review/htmlutils.rb +27 -0
  31. data/lib/review/idgxmlbuilder.rb +1078 -0
  32. data/lib/review/index.rb +224 -0
  33. data/lib/review/latexbuilder.rb +420 -0
  34. data/lib/review/latexindex.rb +35 -0
  35. data/lib/review/latexutils.rb +52 -0
  36. data/lib/review/preprocessor.rb +520 -0
  37. data/lib/review/textutils.rb +19 -0
  38. data/lib/review/tocparser.rb +333 -0
  39. data/lib/review/tocprinter.rb +220 -0
  40. data/lib/review/topbuilder.rb +572 -0
  41. data/lib/review/unfold.rb +138 -0
  42. data/lib/review/volume.rb +66 -0
  43. data/lib/review.rb +4 -0
  44. data/review.gemspec +93 -0
  45. data/setup.rb +1587 -0
  46. data/test/test_epubbuilder.rb +73 -0
  47. data/test/test_helper.rb +2 -0
  48. data/test/test_htmlbuilder.rb +42 -0
  49. data/test/test_latexbuilder.rb +74 -0
  50. metadata +122 -0
@@ -0,0 +1,274 @@
1
+ #
2
+ #
3
+ # Copyright (c) 2002-2009 Minero Aoki
4
+ #
5
+ # This program is free software.
6
+ # You can distribute or modify this program under the terms of
7
+ # the GNU LGPL, Lesser General Public License version 2.1.
8
+ #
9
+
10
+ require 'review/index'
11
+ require 'review/exception'
12
+ require 'stringio'
13
+ require 'nkf'
14
+
15
+ module ReVIEW
16
+
17
+ class Builder
18
+
19
+ def initialize(strict = false, *args)
20
+ @strict = strict
21
+ builder_init(*args)
22
+ end
23
+
24
+ def builder_init(*args)
25
+ end
26
+ private :builder_init
27
+
28
+ def setParameter(param)
29
+ @param = param
30
+ end
31
+
32
+ def bind(compiler, chapter, location)
33
+ @compiler = compiler
34
+ @chapter = chapter
35
+ @location = location
36
+ @output = StringIO.new
37
+ @book = ReVIEW.book
38
+ builder_init_file
39
+ end
40
+
41
+ def builder_init_file
42
+ end
43
+ private :builder_init_file
44
+
45
+ def result
46
+ @output.string
47
+ end
48
+
49
+ def print(*s)
50
+ if @param["outencoding"] =~ /^EUC$/i
51
+ @output.print(NKF.nkf("-W, -e", *s))
52
+ elsif @param["outencoding"] =~ /^SJIS$/i
53
+ @output.print(NKF.nkf("-W, -s", *s))
54
+ elsif @param["outencoding"] =~ /^JIS$/i
55
+ @output.print(NKF.nkf("-W, -j", *s))
56
+ else
57
+ @output.print(*s)
58
+ end
59
+ end
60
+
61
+ def puts(*s)
62
+ if @param["outencoding"] =~ /^EUC$/i
63
+ @output.puts(NKF.nkf("-W, -e", *s))
64
+ elsif @param["outencoding"] =~ /^SJIS$/i
65
+ @output.puts(NKF.nkf("-W, -s", *s))
66
+ elsif @param["outencoding"] =~ /^JIS$/i
67
+ @output.puts(NKF.nkf("-W, -j", *s))
68
+ else
69
+ @output.puts(*s)
70
+ end
71
+ end
72
+
73
+ def list(lines, id, caption)
74
+ begin
75
+ list_header id, caption
76
+ rescue KeyError
77
+ error "no such list: #{id}"
78
+ end
79
+ list_body lines
80
+ end
81
+
82
+ def listnum(lines, id, caption)
83
+ begin
84
+ list_header id, caption
85
+ rescue KeyError
86
+ error "no such list: #{id}"
87
+ end
88
+ listnum_body lines
89
+ end
90
+
91
+ def source(lines, caption)
92
+ source_header caption
93
+ source_body lines
94
+ end
95
+
96
+ def image(lines, id, caption_or_metric, caption = nil)
97
+ if caption
98
+ metric = caption_or_metric
99
+ else
100
+ metric = nil
101
+ caption = caption_or_metric
102
+ end
103
+ if @chapter.image(id).bound?
104
+ image_image id, metric, caption
105
+ else
106
+ warn "image not bound: #{id}" if @strict
107
+ image_dummy id, caption, lines
108
+ end
109
+ end
110
+
111
+ def table(lines, id = nil, caption = nil)
112
+ rows = []
113
+ sepidx = nil
114
+ lines.each_with_index do |line, idx|
115
+ if /\A[\=\-]{12}/ =~ line
116
+ # just ignore
117
+ #error "too many table separator" if sepidx
118
+ sepidx ||= idx
119
+ next
120
+ end
121
+ rows.push line.strip.split(/\t+/).map {|s| s.sub(/\A\./, '') }
122
+ end
123
+ rows = adjust_n_cols(rows)
124
+
125
+ begin
126
+ table_header id, caption unless caption.nil?
127
+ rescue KeyError => err
128
+ error "no such table: #{id}"
129
+ end
130
+ return if rows.empty?
131
+ table_begin rows.first.size
132
+ if sepidx
133
+ sepidx.times do
134
+ tr rows.shift.map {|s| th(compile_inline(s)) }
135
+ end
136
+ rows.each do |cols|
137
+ tr cols.map {|s| td(compile_inline(s)) }
138
+ end
139
+ else
140
+ rows.each do |cols|
141
+ h, *cs = *cols
142
+ tr [th(compile_inline(h))] + cs.map {|s| td(compile_inline(s)) }
143
+ end
144
+ end
145
+ table_end
146
+ end
147
+
148
+ def adjust_n_cols(rows)
149
+ rows.each do |cols|
150
+ while cols.last and cols.last.strip.empty?
151
+ cols.pop
152
+ end
153
+ end
154
+ n_maxcols = rows.map {|cols| cols.size }.max
155
+ rows.each do |cols|
156
+ cols.concat [''] * (n_maxcols - cols.size)
157
+ end
158
+ rows
159
+ end
160
+ private :adjust_n_cols
161
+
162
+ #def footnote(id, str)
163
+ # @footnotes.push [id, str]
164
+ #end
165
+ #
166
+ #def flush_footnote
167
+ # footnote_begin
168
+ # @footnotes.each do |id, str|
169
+ # footnote_item(id, str)
170
+ # end
171
+ # footnote_end
172
+ #end
173
+
174
+ def compile_inline(s)
175
+ @compiler.text(s)
176
+ end
177
+
178
+ def inline_chapref(id)
179
+ @chapter.env.chapter_index.display_string(id)
180
+ rescue KeyError
181
+ error "unknown chapter: #{id}"
182
+ nofunc_text("[UnknownChapter:#{id}]")
183
+ end
184
+
185
+ def inline_chap(id)
186
+ @chapter.env.chapter_index.number(id)
187
+ rescue KeyError
188
+ error "unknown chapter: #{id}"
189
+ nofunc_text("[UnknownChapter:#{id}]")
190
+ end
191
+
192
+ def inline_title(id)
193
+ @chapter.env.chapter_index.title(id)
194
+ rescue KeyError
195
+ error "unknown chapter: #{id}"
196
+ nofunc_text("[UnknownChapter:#{id}]")
197
+ end
198
+
199
+ def inline_list(id)
200
+ "リスト#{@chapter.list(id).number}"
201
+ rescue KeyError
202
+ error "unknown list: #{id}"
203
+ nofunc_text("[UnknownList:#{id}]")
204
+ end
205
+
206
+ def inline_img(id)
207
+ "図#{@chapter.image(id).number}"
208
+ rescue KeyError
209
+ error "unknown image: #{id}"
210
+ nofunc_text("[UnknownImage:#{id}]")
211
+ end
212
+
213
+ def inline_table(id)
214
+ "表#{@chapter.table(id).number}"
215
+ rescue KeyError
216
+ error "unknown table: #{id}"
217
+ nofunc_text("[UnknownTable:#{id}]")
218
+ end
219
+
220
+ def inline_fn(id)
221
+ @chapter.footnote(id).content
222
+ rescue KeyError
223
+ error "unknown footnote: #{id}"
224
+ nofunc_text("[UnknownFootnote:#{id}]")
225
+ end
226
+
227
+ def inline_bou(str)
228
+ text(str)
229
+ end
230
+
231
+ def inline_ruby(arg)
232
+ base, ruby = *arg.split(',', 2)
233
+ compile_ruby(base, ruby)
234
+ end
235
+
236
+ def inline_kw(arg)
237
+ word, alt = *arg.split(',', 2)
238
+ compile_kw(word, alt)
239
+ end
240
+
241
+ def inline_href(arg)
242
+ url, label = *arg.scan(/(?:(?:(?:\\\\)*\\,)|[^,\\]+)+/).map(&:lstrip)
243
+ url = url.gsub(/\\,/, ",").strip
244
+ compile_href(url, label)
245
+ end
246
+
247
+ def text(str)
248
+ str
249
+ end
250
+
251
+ def bibpaper(lines, id, caption)
252
+ puts "<div>"
253
+ bibpaper_header id, caption
254
+ unless lines.empty?
255
+ bibpaper_bibpaper id, caption, lines
256
+ end
257
+ puts "</div>"
258
+ end
259
+
260
+ def raw(str)
261
+ print str.gsub("\\n", "\n")
262
+ end
263
+
264
+ def warn(msg)
265
+ $stderr.puts "#{@location}: warning: #{msg}"
266
+ end
267
+
268
+ def error(msg)
269
+ raise ApplicationError, "#{@location}: error: #{msg}"
270
+ end
271
+
272
+ end
273
+
274
+ end # module ReVIEW
@@ -0,0 +1,22 @@
1
+ unless String.method_defined?(:lines)
2
+ # Ruby 1.8
3
+ class String
4
+ alias lines to_a
5
+ end
6
+ end
7
+
8
+ if String.method_defined?(:bytesize)
9
+ # Ruby 1.9
10
+ class String
11
+ alias charsize size
12
+ end
13
+ else
14
+ # Ruby 1.8
15
+ class String
16
+ alias bytesize size
17
+
18
+ def charsize
19
+ split(//).size
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,483 @@
1
+ #
2
+ # Copyright (c) 2002-2007 Minero Aoki
3
+ # Copyright (c) 2009-2010 Minero Aoki, Kenshi Muto
4
+ #
5
+ # This program is free software.
6
+ # You can distribute or modify this program under the terms of
7
+ # the GNU LGPL, Lesser General Public License version 2.1.
8
+ #
9
+
10
+ require 'review/compat'
11
+ require 'review/preprocessor'
12
+ require 'review/exception'
13
+ require 'lineinput'
14
+
15
+ module ReVIEW
16
+
17
+ class Location
18
+ def initialize(filename, f)
19
+ @filename = filename
20
+ @f = f
21
+ end
22
+
23
+ attr_reader :filename
24
+
25
+ def lineno
26
+ @f.lineno
27
+ end
28
+
29
+ def string
30
+ "#{@filename}:#{@f.lineno}"
31
+ end
32
+
33
+ alias to_s string
34
+ end
35
+
36
+
37
+ class Compiler
38
+
39
+ def initialize(strategy)
40
+ @strategy = strategy
41
+ end
42
+
43
+ attr_reader :strategy
44
+
45
+ def setParameter(param)
46
+ @param = param
47
+ @strategy.setParameter(@param)
48
+ end
49
+
50
+ def compile(chap)
51
+ @chapter = chap
52
+ @chapter.setParameter(@param)
53
+ do_compile
54
+ @strategy.result
55
+ end
56
+
57
+ class SyntaxElement
58
+ def initialize(name, type, argc, &block)
59
+ @name = name
60
+ @type = type
61
+ @argc_spec = argc
62
+ @checker = block
63
+ end
64
+
65
+ attr_reader :name
66
+
67
+ def check_args(args)
68
+ unless @argc_spec === args.size
69
+ raise CompileError, "wrong # of parameters (block command //#{@name}, expect #{@argc_spec} but #{args.size})"
70
+ end
71
+ @checker.call(*args) if @checker
72
+ end
73
+
74
+ def min_argc
75
+ case @argc_spec
76
+ when Range then @argc_spec.begin
77
+ when Integer then @argc_spec
78
+ else
79
+ raise TypeError, "argc_spec is not Range/Integer: #{inspect()}"
80
+ end
81
+ end
82
+
83
+ def block_required?
84
+ @type == :block
85
+ end
86
+
87
+ def block_allowed?
88
+ @type == :block or @type == :optional
89
+ end
90
+ end
91
+
92
+ SYNTAX = {}
93
+
94
+ def Compiler.defblock(name, argc, optional = false, &block)
95
+ defsyntax name, (optional ? :optional : :block), argc, &block
96
+ end
97
+
98
+ def Compiler.defsingle(name, argc, &block)
99
+ defsyntax name, :line, argc, &block
100
+ end
101
+
102
+ def Compiler.defsyntax(name, type, argc, &block)
103
+ SYNTAX[name] = SyntaxElement.new(name, type, argc, &block)
104
+ end
105
+
106
+ def syntax_defined?(name)
107
+ SYNTAX.key?(name.to_sym)
108
+ end
109
+
110
+ def syntax_descriptor(name)
111
+ SYNTAX[name.to_sym]
112
+ end
113
+
114
+ class InlineSyntaxElement
115
+ def initialize(name)
116
+ @name = name
117
+ end
118
+
119
+ attr_reader :name
120
+ end
121
+
122
+ INLINE = {}
123
+
124
+ def Compiler.definline(name)
125
+ INLINE[name] = InlineSyntaxElement.new(name)
126
+ end
127
+
128
+ def inline_defined?(name)
129
+ INLINE.key?(name.to_sym)
130
+ end
131
+
132
+ defblock :read, 0
133
+ defblock :lead, 0
134
+ defblock :list, 2
135
+ defblock :emlist, 0..1
136
+ defblock :cmd, 0..1
137
+ defblock :table, 0..2
138
+ defblock :quote, 0
139
+ defblock :image, 2..3, true
140
+ defblock :source, 1
141
+ defblock :listnum, 2
142
+ defblock :emlistnum, 0..1
143
+ defblock :bibpaper, 2..3, true
144
+ defblock :address, 0
145
+ defblock :blockquote, 0
146
+ defblock :bpo, 0
147
+ defblock :flushright, 0
148
+ defblock :note, 0..1
149
+
150
+ defsingle :footnote, 2
151
+ defsingle :comment, 1
152
+ defsingle :noindent, 0
153
+ defsingle :linebreak, 0
154
+ defsingle :pagebreak, 0
155
+ defsingle :hr, 0
156
+ defsingle :parasep, 0
157
+ defsingle :label, 1
158
+ defsingle :raw, 1
159
+ defsingle :tsize, 1
160
+
161
+ definline :chapref
162
+ definline :chap
163
+ definline :title
164
+ definline :img
165
+ definline :list
166
+ definline :table
167
+ definline :fn
168
+ definline :kw
169
+ definline :ruby
170
+ definline :bou
171
+ definline :ami
172
+ definline :b
173
+ definline :dtp
174
+ definline :code
175
+ definline :bib
176
+ definline :href
177
+ definline :recipe
178
+
179
+ definline :abbr
180
+ definline :acronym
181
+ definline :cite
182
+ definline :dfn
183
+ definline :em
184
+ definline :kbd
185
+ definline :q
186
+ definline :samp
187
+ definline :strong
188
+ definline :var
189
+ definline :big
190
+ definline :small
191
+ definline :del
192
+ definline :ins
193
+ definline :sup
194
+ definline :sub
195
+ definline :tt
196
+ definline :i
197
+
198
+ definline :raw
199
+
200
+ private
201
+
202
+ def do_compile
203
+ f = LineInput.new(Preprocessor::Strip.new(StringIO.new(@chapter.content)))
204
+ @strategy.bind self, @chapter, Location.new(@chapter.basename, f)
205
+ tagged_section_init
206
+ while f.next?
207
+ case f.peek
208
+ when /\A=+[\[\s\{]/
209
+ compile_headline f.gets
210
+ when %r<\A\s+\*>
211
+ compile_ulist f
212
+ when %r<\A\s+□>
213
+ compile_multichoice f
214
+ when %r<\A\s+○>
215
+ compile_singlechoice f
216
+ when %r<\A\s+\d+\.>
217
+ compile_olist f
218
+ when %r<\A:\s>
219
+ compile_dlist f
220
+ when %r<\A//\}>
221
+ error 'block end seen but not opened'
222
+ f.gets
223
+ when %r<\A//[a-z]+>
224
+ name, args, lines = read_command(f)
225
+ syntax = syntax_descriptor(name)
226
+ unless syntax
227
+ error "unknown command: //#{name}"
228
+ compile_unknown_command args, lines
229
+ next
230
+ end
231
+ compile_command syntax, args, lines
232
+ when %r<\A//>
233
+ line = f.gets
234
+ warn "`//' seen but is not valid command: #{line.strip.inspect}"
235
+ if block_open?(line)
236
+ warn "skipping block..."
237
+ read_block(f)
238
+ end
239
+ else
240
+ if f.peek.strip.empty?
241
+ f.gets
242
+ next
243
+ end
244
+ compile_paragraph f
245
+ end
246
+ end
247
+ close_all_tagged_section
248
+ end
249
+
250
+ def compile_headline(line)
251
+ m = /\A(=+)(?:\[(.+?)\])?(?:\{(.+?)\})?(.*)/.match(line)
252
+ level = m[1].size
253
+ tag = m[2]
254
+ label = m[3]
255
+ caption = m[4].strip
256
+ while @tagged_section.last and @tagged_section.last[1] >= level
257
+ close_tagged_section(* @tagged_section.pop)
258
+ end
259
+ if tag
260
+ open_tagged_section tag, level, label, caption
261
+ else
262
+ @strategy.headline level, label, @strategy.text(caption)
263
+ end
264
+ end
265
+
266
+ def headline(level, label, caption)
267
+ @strategy.headline level, label, @strategy.text(caption)
268
+ end
269
+
270
+ def tagged_section_init
271
+ @tagged_section = []
272
+ end
273
+
274
+ def open_tagged_section(tag, level, label, caption)
275
+ mid = "#{tag}_begin"
276
+ unless @strategy.respond_to?(mid)
277
+ error "strategy does not support tagged section: #{tag}"
278
+ headline level, label, caption
279
+ return
280
+ end
281
+ @tagged_section.push [tag, level]
282
+ @strategy.__send__ mid, level, label, @strategy.text(caption)
283
+ end
284
+
285
+ def close_tagged_section(tag, level)
286
+ mid = "#{tag}_end"
287
+ if @strategy.respond_to?(mid)
288
+ @strategy.__send__ mid, level
289
+ else
290
+ error "strategy does not support block op: #{mid}"
291
+ end
292
+ end
293
+
294
+ def close_all_tagged_section
295
+ until @tagged_section.empty?
296
+ close_tagged_section(* @tagged_section.pop)
297
+ end
298
+ end
299
+
300
+ def compile_ulist(f)
301
+ @strategy.ul_begin
302
+ f.while_match(/\A\s+\*/) do |line|
303
+ buf = [text(line.sub(/\*/, '').strip)]
304
+ f.while_match(/\A\s+(?!\*)\S/) do |cont|
305
+ buf.push text(cont.strip)
306
+ end
307
+ @strategy.ul_item buf
308
+ end
309
+ @strategy.ul_end
310
+ end
311
+
312
+ def compile_multichoice(f)
313
+ @strategy.choice_multi_begin
314
+ f.while_match(/\A\s+□/) do |line|
315
+ buf = [text(line.sub(/□/, '').strip)]
316
+ f.while_match(/\A\s+(?!□)\S/) do |cont|
317
+ buf.push text(cont.strip)
318
+ end
319
+ @strategy.ul_item buf
320
+ end
321
+ @strategy.choice_multi_end
322
+ end
323
+
324
+ def compile_singlechoice(f)
325
+ @strategy.choice_single_begin
326
+ f.while_match(/\A\s+○/) do |line|
327
+ buf = [text(line.sub(/○/, '').strip)]
328
+ f.while_match(/\A\s+(?!○)\S/) do |cont|
329
+ buf.push text(cont.strip)
330
+ end
331
+ @strategy.ul_item buf
332
+ end
333
+ @strategy.choice_single_end
334
+ end
335
+
336
+ def compile_olist(f)
337
+ @strategy.ol_begin
338
+ f.while_match(/\A\s+\d+\./) do |line|
339
+ num = line.match(/(\d+)\./)[1]
340
+ buf = [text(line.sub(/\d+\./, '').strip)]
341
+ f.while_match(/\A\s+(?!\d+\.)\S/) do |cont|
342
+ buf.push text(cont.strip)
343
+ end
344
+ @strategy.ol_item buf, num
345
+ end
346
+ @strategy.ol_end
347
+ end
348
+
349
+ def compile_dlist(f)
350
+ @strategy.dl_begin
351
+ while /\A:/ =~ f.peek
352
+ @strategy.dt text(f.gets.sub(/:/, '').strip)
353
+ @strategy.dd f.break(/\A\S/).map {|line| text(line.strip) }
354
+ f.skip_blank_lines
355
+ end
356
+ @strategy.dl_end
357
+ end
358
+
359
+ def compile_paragraph(f)
360
+ buf = []
361
+ f.until_match(%r<\A//>) do |line|
362
+ break if line.strip.empty?
363
+ buf.push text(line.sub(/^(\t+)\s*/) {|m| "<!ESCAPETAB!>" * m.size}.strip.gsub(/<!ESCAPETAB!>/, "\t"))
364
+ end
365
+ @strategy.paragraph buf
366
+ end
367
+
368
+ def read_command(f)
369
+ line = f.gets
370
+ name = line.slice(/[a-z]+/).intern
371
+ args = parse_args(line.sub(%r<\A//[a-z]+>, '').rstrip.chomp('{'))
372
+ lines = block_open?(line) ? read_block(f) : nil
373
+ return name, args, lines
374
+ end
375
+
376
+ def block_open?(line)
377
+ line.rstrip[-1,1] == '{'
378
+ end
379
+
380
+ def read_block(f)
381
+ head = f.lineno
382
+ buf = []
383
+ f.until_match(%r<\A//\}>) do |line|
384
+ buf.push text(line.rstrip)
385
+ end
386
+ unless %r<\A//\}> =~ f.peek
387
+ error "unexpected EOF (block begins at: #{head})"
388
+ return buf
389
+ end
390
+ f.gets # discard terminator
391
+ buf
392
+ end
393
+
394
+ def parse_args(str)
395
+ return [] if str.empty?
396
+ unless str[0,1] == '[' and str[-1,1] == ']'
397
+ error "argument syntax error: #{str.inspect}"
398
+ return []
399
+ end
400
+ str[1..-2].split('][', -1)
401
+ end
402
+
403
+ def compile_command(syntax, args, lines)
404
+ unless @strategy.respond_to?(syntax.name)
405
+ error "strategy does not support command: //#{syntax.name}"
406
+ compile_unknown_command args, lines
407
+ return
408
+ end
409
+ begin
410
+ syntax.check_args args
411
+ rescue CompileError => err
412
+ error err.message
413
+ args = ['(NoArgument)'] * SYNTAX[name].min_argc
414
+ end
415
+ if syntax.block_allowed?
416
+ compile_block syntax, args, lines
417
+ else
418
+ if lines
419
+ error "block is not allowed for command //#{syntax.name}; ignore"
420
+ end
421
+ compile_single syntax, args
422
+ end
423
+ end
424
+
425
+ def compile_unknown_command(args, lines)
426
+ @strategy.unknown_command args, lines
427
+ end
428
+
429
+ def compile_block(syntax, args, lines)
430
+ @strategy.__send__(syntax.name, (lines || default_block(syntax)), *args)
431
+ end
432
+
433
+ def default_block(syntax)
434
+ if syntax.block_required?
435
+ error "block is required for //#{syntax.name}; use empty block"
436
+ end
437
+ []
438
+ end
439
+
440
+ def compile_single(syntax, args)
441
+ @strategy.__send__(syntax.name, *args)
442
+ end
443
+
444
+ def text(str)
445
+ return '' if str.empty?
446
+ words = str.split(/(@<\w+>\{(?:[^\}\\]+|\\.)*\})/, -1)
447
+ words.each do |w|
448
+ error "`@<xxx>' seen but is not valid inline op: #{w}" if w.scan(/@<\w+>/).size > 1 && !/\A@<raw>/.match(w)
449
+ end
450
+ result = @strategy.nofunc_text(words.shift)
451
+ until words.empty?
452
+ result << compile_inline(words.shift.gsub(/\\\}/, '}'))
453
+ result << @strategy.nofunc_text(words.shift)
454
+ end
455
+ result
456
+ end
457
+ public :text # called from strategy
458
+
459
+ def compile_inline(str)
460
+ op, arg = /\A@<(\w+)>\{(.*?)\}\z/.match(str).captures
461
+ unless inline_defined?(op)
462
+ raise CompileError, "no such inline op: #{op}"
463
+ end
464
+ unless @strategy.respond_to?("inline_#{op}")
465
+ raise "strategy does not support inline op: @<#{op}>"
466
+ end
467
+ @strategy.__send__("inline_#{op}", arg)
468
+ rescue => err
469
+ error err.message
470
+ @strategy.nofunc_text(str)
471
+ end
472
+
473
+ def warn(msg)
474
+ @strategy.warn msg
475
+ end
476
+
477
+ def error(msg)
478
+ @strategy.error msg
479
+ end
480
+
481
+ end
482
+
483
+ end # module ReVIEW