ppr 0.0.2 → 0.0.3
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.
- checksums.yaml +4 -4
- data/lib/ppr/keyword_searcher.rb +15 -14
- data/lib/ppr/ppr_core.rb +96 -86
- data/lib/ppr/safer_generator.rb +14 -14
- data/lib/ppr/version.rb +1 -1
- data/lib/ppr.rb +3 -2
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9c406b99cf38bd9244827b268c16a331b41f752d
|
4
|
+
data.tar.gz: 213074c919132f292e4edff0e0389c537f3b5349
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4e908bea3122b499442c72844fa5675a0cf58626dc15d2b2437d574f7536e2f6c0861c89d242de5242a97076e4c0d37bf86b2f21a8f2573820b8ea48a1c0479a
|
7
|
+
data.tar.gz: 412ca44eee6324788cd342ff83b75502bb133bb10f933814e403e3933137bb12fd0939a9fe9cbfb340b02811ccb61f9b3a7c6b2c8ab5c99e4eca4de1e15aacd7
|
data/lib/ppr/keyword_searcher.rb
CHANGED
@@ -2,11 +2,12 @@
|
|
2
2
|
## Map class for searching keywords in a text. ##
|
3
3
|
######################################################################
|
4
4
|
|
5
|
-
|
5
|
+
##
|
6
|
+
# Tool for looking for keywords within a string.
|
6
7
|
class KeywordSearcher
|
7
8
|
|
8
|
-
|
9
|
-
#
|
9
|
+
# Creates a new keyword searcher, where words are between +seperators+
|
10
|
+
# regular expressions.
|
10
11
|
def initialize(separator = "")
|
11
12
|
# Checks and set the separator.
|
12
13
|
@separator = Regexp.new(separator).to_s
|
@@ -25,7 +26,7 @@ class KeywordSearcher
|
|
25
26
|
return self
|
26
27
|
end
|
27
28
|
|
28
|
-
|
29
|
+
# Adds a +keyword+ to the searcher associated with an +object+.
|
29
30
|
def []=(keyword,object)
|
30
31
|
# Ensure the keyword is a valid string.
|
31
32
|
keyword = keyword.to_str
|
@@ -41,17 +42,17 @@ class KeywordSearcher
|
|
41
42
|
@keyword_extract = Regexp.new(@keywords.join("|"))
|
42
43
|
end
|
43
44
|
|
44
|
-
|
45
|
+
# Get the object corresponding to +keyword+.
|
45
46
|
def [](keyword)
|
46
47
|
return @map[keyword.to_s]
|
47
48
|
end
|
48
49
|
|
49
|
-
|
50
|
-
#
|
50
|
+
# Search a keyword inside a +text+ and return the corresponding object
|
51
|
+
# if found with the range in the string where it has been found.
|
51
52
|
#
|
52
|
-
#
|
53
|
+
# If a keyword is in +skip+ it s ignored.
|
53
54
|
#
|
54
|
-
#
|
55
|
+
# NOTE: the first found object is returned.
|
55
56
|
def find(text,skip = [])
|
56
57
|
# print "skip=#{skip} @keywords=#{@keywords}\n"
|
57
58
|
# Compute the regular expression for finding the keywords.
|
@@ -78,13 +79,13 @@ class KeywordSearcher
|
|
78
79
|
end
|
79
80
|
end
|
80
81
|
|
81
|
-
|
82
|
-
#
|
83
|
-
#
|
82
|
+
# Search each keyword inside a +text+ and apply the block on the
|
83
|
+
# corresponding objects if found with the range in the string where it
|
84
|
+
# has been found.
|
84
85
|
#
|
85
|
-
#
|
86
|
+
# Returns an enumerator if no block is given.
|
86
87
|
#
|
87
|
-
#
|
88
|
+
# NOTE: keywords included into a longer one are ignored.
|
88
89
|
def each_in(text)
|
89
90
|
return to_enum(:each_in,text) unless block_given?
|
90
91
|
# Check and clone the text to avoid side effects.
|
data/lib/ppr/ppr_core.rb
CHANGED
@@ -8,38 +8,42 @@ require "ppr/keyword_searcher.rb"
|
|
8
8
|
module Ppr
|
9
9
|
|
10
10
|
|
11
|
-
##
|
11
|
+
##
|
12
|
+
# Converts a +name+ to an attribute symbol.
|
12
13
|
def Ppr.to_attribute(name)
|
13
14
|
name = name.to_s
|
14
15
|
(name.start_with?("@") ? name : "@" + name).to_sym
|
15
16
|
end
|
16
17
|
|
17
|
-
##
|
18
|
+
##
|
19
|
+
# Describes a storage for line number
|
18
20
|
class LineNumber < SimpleDelegator
|
19
|
-
|
21
|
+
# Creates a new storage for line number +num+.
|
20
22
|
def initialize(num)
|
21
23
|
super(num.to_i)
|
22
24
|
end
|
23
25
|
|
24
|
-
|
26
|
+
# Sets the line number to +num+.
|
25
27
|
def set(num)
|
26
28
|
__setobj__(num.to_i)
|
27
29
|
end
|
28
30
|
end
|
29
31
|
|
30
32
|
|
31
|
-
##
|
33
|
+
##
|
34
|
+
# Describes a macro of the ruby preprocessor.
|
32
35
|
class Macro
|
33
36
|
|
34
|
-
|
37
|
+
# The name of the macro.
|
35
38
|
attr_reader :name
|
36
39
|
|
37
|
-
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
40
|
+
# Creates a new macro named +name+, starting at line number +num+,
|
41
|
+
# generated from preprocessor +ppr+ and with possible +variables+.
|
42
|
+
#
|
43
|
+
# Other parameters:
|
44
|
+
# +expand+:: used to redefine the expand operator
|
45
|
+
# +final+:: indicates that the result of the macro expansion
|
46
|
+
# shall not be preprocessed again.
|
43
47
|
def initialize(name, num, ppr, *variables, expand: ":<", final: true)
|
44
48
|
# Check and set the name.
|
45
49
|
@name = name.to_str
|
@@ -82,13 +86,13 @@ class Macro
|
|
82
86
|
#}}} This comment is just to avoid confusing the text editor.
|
83
87
|
end
|
84
88
|
|
85
|
-
|
86
|
-
#
|
89
|
+
# Tells if the maco expansion result is final (i.e., it is not preprocessed
|
90
|
+
# again) or not.
|
87
91
|
def final?
|
88
92
|
return @final
|
89
93
|
end
|
90
94
|
|
91
|
-
|
95
|
+
# Adds a +line+ to the macro.
|
92
96
|
def add(line)
|
93
97
|
# Process the line.
|
94
98
|
# Remove the ending newline if any.
|
@@ -96,13 +100,13 @@ class Macro
|
|
96
100
|
end
|
97
101
|
alias << add
|
98
102
|
|
99
|
-
|
103
|
+
# Checks if the macro is empty (no code line yet).
|
100
104
|
def empty?
|
101
105
|
return @lines.empty?
|
102
106
|
end
|
103
107
|
|
104
|
-
|
105
|
-
#
|
108
|
+
# Generates the code of the macro invoked at line number +i_number+
|
109
|
+
# using +values+ for the variables.
|
106
110
|
def generate(i_number,*values)
|
107
111
|
# First generate a variable for the resulting text.
|
108
112
|
result = "result_"
|
@@ -140,13 +144,13 @@ class Macro
|
|
140
144
|
|
141
145
|
# Methods used by apply for handling exception messages.
|
142
146
|
|
143
|
-
|
144
|
-
#
|
147
|
+
# Regular expression for identifying a line number inside an exception
|
148
|
+
# message.
|
145
149
|
E_NUMBER = /:[1-9][0-9]*:/
|
146
|
-
|
150
|
+
# Type of exception which correspond to a macro execution.
|
147
151
|
E_TYPE = /\(eval\)\s*/
|
148
152
|
|
149
|
-
|
153
|
+
# Tells if an exception +message+ includes a line number.
|
150
154
|
def e_number(message)
|
151
155
|
found = E_NUMBER.match(message)
|
152
156
|
if found then
|
@@ -158,12 +162,12 @@ class Macro
|
|
158
162
|
end
|
159
163
|
end
|
160
164
|
|
161
|
-
|
165
|
+
# Tells if an exception message is of a given +type+.
|
162
166
|
def e_type?(message,type)
|
163
167
|
return message =~ Regexp.new(type)
|
164
168
|
end
|
165
169
|
|
166
|
-
|
170
|
+
# Shifts the line number inside an exception +message+ by +sh+.
|
167
171
|
def e_shift_number(message,sh)
|
168
172
|
# Edit the message to fix the line number and raise then.
|
169
173
|
return message.gsub(E_NUMBER) { |str|
|
@@ -181,8 +185,8 @@ class Macro
|
|
181
185
|
}
|
182
186
|
end
|
183
187
|
|
184
|
-
|
185
|
-
#
|
188
|
+
# Update an exception +message+ to refer macro +name+ invoked at line
|
189
|
+
# number +i_number+ and adds a possible macro line +number+.
|
186
190
|
def Macro.e_message(name, message, i_number, number = nil)
|
187
191
|
result = "Ppr error (#{name}:#{i_number})"
|
188
192
|
result << ":#{number}: " if number
|
@@ -190,13 +194,13 @@ class Macro
|
|
190
194
|
return result
|
191
195
|
end
|
192
196
|
|
193
|
-
|
194
|
-
#
|
197
|
+
# Update an exception +message+ to refer the macro invoked at line number
|
198
|
+
# +i_number+ and adds a possible macro line +number+.
|
195
199
|
def e_message(message, i_number, number = nil)
|
196
200
|
Macro.e_message(@name,message,i_number,number)
|
197
201
|
end
|
198
202
|
|
199
|
-
|
203
|
+
# Applies the macro invoked at line number +i_number+ with +arguments+.
|
200
204
|
def apply(i_number,*arguments)
|
201
205
|
# Generate the code of the macro.
|
202
206
|
code = self.generate(i_number,*arguments)
|
@@ -261,20 +265,22 @@ class Macro
|
|
261
265
|
end
|
262
266
|
|
263
267
|
|
264
|
-
##
|
268
|
+
##
|
269
|
+
# Describes an assignment macro of the ruby preprocessor.
|
265
270
|
class Assign < Macro
|
266
|
-
|
267
|
-
#
|
268
|
-
#
|
269
|
-
#
|
271
|
+
# Creates a new assignment macro whose assigned variable is +var+,
|
272
|
+
# starting at line number +num+ generated from preprocessor +ppr+.
|
273
|
+
#
|
274
|
+
# Other parameters:
|
275
|
+
# +expand+:: redefines the expand operator string.
|
270
276
|
def initialize(name, num, ppr, expand: ":<")
|
271
277
|
super(name,num,ppr,expand: expand)
|
272
278
|
# Creates the attribute which will be assigned.
|
273
279
|
@var_sym = Ppr.to_attribute(name)
|
274
280
|
end
|
275
281
|
|
276
|
-
|
277
|
-
#
|
282
|
+
# Applies the macro invoked at line number +i_number+,
|
283
|
+
# its result in assigned to the class variable.
|
278
284
|
def apply(i_number)
|
279
285
|
# Expands the macro.
|
280
286
|
line = super(i_number)
|
@@ -285,17 +291,18 @@ class Assign < Macro
|
|
285
291
|
end
|
286
292
|
end
|
287
293
|
|
288
|
-
##
|
294
|
+
##
|
295
|
+
# Descibes an abstract class for loading or requiring files.
|
289
296
|
class LoadRequire < Macro
|
290
|
-
|
291
|
-
#
|
292
|
-
#
|
293
|
-
#
|
297
|
+
# Creates a new load or require macro starting at line number +num+
|
298
|
+
# generated from preprocessor +ppr+.
|
299
|
+
#
|
300
|
+
# The +expand+ strings be redefined through keyword arguments.
|
294
301
|
def initialize(num, ppr, expand: ":<")
|
295
302
|
super("",num,ppr,expand: expand)
|
296
303
|
end
|
297
304
|
|
298
|
-
|
305
|
+
# Loads and preprocess file +name+.
|
299
306
|
def loadm(name)
|
300
307
|
output = StringIO.new("")
|
301
308
|
# print "name=#{name}\n"
|
@@ -306,10 +313,11 @@ class LoadRequire < Macro
|
|
306
313
|
end
|
307
314
|
end
|
308
315
|
|
309
|
-
##
|
316
|
+
##
|
317
|
+
# Describes a macro loading and pasting a file into the current one.
|
310
318
|
class Load < LoadRequire
|
311
|
-
|
312
|
-
#
|
319
|
+
# Applies the macro invoked at line number +i_number+,
|
320
|
+
# its result is the name of the file to be loaded.
|
313
321
|
def apply(i_number)
|
314
322
|
# Expand the macro, its result is the name of the file to load.
|
315
323
|
name = super(i_number)
|
@@ -320,13 +328,13 @@ class Load < LoadRequire
|
|
320
328
|
end
|
321
329
|
end
|
322
330
|
|
323
|
-
|
324
|
-
#
|
331
|
+
# Describes a macro loading and pasting a file into the current one
|
332
|
+
# only if it has not already been loaded before.
|
325
333
|
class Require < LoadRequire
|
326
334
|
@@required = [] # The already required files.
|
327
335
|
|
328
|
-
|
329
|
-
#
|
336
|
+
# Applies the macro invoked at line number +i_number+,
|
337
|
+
# its result is the name of the file to be loaded if not already loaded.
|
330
338
|
def apply(i_number)
|
331
339
|
# Expand the macro, its result is the name of the file to load.
|
332
340
|
name = super(i_number)
|
@@ -343,36 +351,38 @@ class Require < LoadRequire
|
|
343
351
|
end
|
344
352
|
end
|
345
353
|
|
346
|
-
##
|
354
|
+
##
|
355
|
+
# Describes a conditional macro.
|
347
356
|
class If < Macro
|
348
|
-
|
349
|
-
#
|
350
|
-
#
|
351
|
-
#
|
357
|
+
# Creates a new load or require macro starting at line number +num+
|
358
|
+
# generated from preprocessor +ppr+.
|
359
|
+
#
|
360
|
+
# The +expand+ strings be redefined through keyword arguments.
|
352
361
|
def initialize(num, ppr, expand: ":<")
|
353
362
|
super("",num,ppr,expand: expand)
|
354
363
|
end
|
355
364
|
end
|
356
365
|
|
357
366
|
|
358
|
-
##
|
367
|
+
##
|
368
|
+
# Describes the ruby preprocessor.
|
359
369
|
#
|
360
|
-
#
|
370
|
+
# Usage:
|
361
371
|
# ppr = Ppr::Preprocessor.new(<some options>)
|
362
372
|
# ppr.preprocess(<some input stream>, <some output stream>)
|
363
373
|
class Preprocessor
|
364
374
|
|
365
|
-
|
366
|
-
#
|
367
|
-
#
|
368
|
-
#
|
369
|
-
#
|
370
|
-
#
|
371
|
-
#
|
372
|
-
#
|
375
|
+
# Creates a new preprocessor, where +apply+, +applyR+, +define+, +defineR+,
|
376
|
+
# +assign+, +loadm+, +requirem+, +ifm+, +elsem+ and +endm+ are the
|
377
|
+
# keywords defining the beginings and end of a macro definitions,
|
378
|
+
# and where +separator+ is the regular expression used for
|
379
|
+
# separating macro references to the remaining of the code, +expand+ is
|
380
|
+
# the string representing the expansion operator of the macro, +glue+ is
|
381
|
+
# string used for glueing a macro expension to the text,
|
382
|
+
# +escape+ is the escape character.
|
373
383
|
#
|
374
|
-
#
|
375
|
-
#
|
384
|
+
# Assigned parameters can be added through +param+ to be used within
|
385
|
+
# the macros of the preprocessed text.
|
376
386
|
def initialize(params = {},
|
377
387
|
apply: ".do", applyR: ".doR",
|
378
388
|
define: ".def", defineR: ".defR",
|
@@ -447,27 +457,27 @@ class Preprocessor
|
|
447
457
|
|
448
458
|
# Methods for handling the execution context of the macros.
|
449
459
|
|
450
|
-
|
460
|
+
# Executes a macro in a safe context.
|
451
461
|
def run(&proc)
|
452
462
|
@generator.run do |__stream__|
|
453
463
|
@context.instance_exec(__stream__,&proc)
|
454
464
|
end
|
455
465
|
end
|
456
466
|
|
457
|
-
|
467
|
+
# Sets parameter +param+ to +value+.
|
458
468
|
def parameter_set(param,value)
|
459
469
|
# print "Setting #{Ppr.to_attribute(param)} with #{value.to_s}\n"
|
460
470
|
@context.instance_variable_set(Ppr.to_attribute(param),value.to_s)
|
461
471
|
end
|
462
472
|
|
463
|
-
|
473
|
+
# Gets the value of parameter +param.
|
464
474
|
def parameter_get(param)
|
465
475
|
return @context.instance_variable_get(Ppr.to_attribute(param))
|
466
476
|
end
|
467
477
|
|
468
478
|
# Methods for parsing the lines.
|
469
479
|
|
470
|
-
|
480
|
+
# Restores a +string+ whose begining may have been glued.
|
471
481
|
def unglue_front(string)
|
472
482
|
if string.start_with?(@glue) then
|
473
483
|
# There is a glue, so remove it.
|
@@ -479,7 +489,7 @@ class Preprocessor
|
|
479
489
|
return string
|
480
490
|
end
|
481
491
|
|
482
|
-
|
492
|
+
# Restores a +string+ whose ending may have been glued.
|
483
493
|
def unglue_back(string)
|
484
494
|
if string.end_with?(@glue) then
|
485
495
|
# There is a glue, so remove it.
|
@@ -491,8 +501,7 @@ class Preprocessor
|
|
491
501
|
return string
|
492
502
|
end
|
493
503
|
|
494
|
-
|
495
|
-
# from offset +start+ of +line+.
|
504
|
+
# Gets the range of an argument starting at offset +start+ in +line+.
|
496
505
|
def get_argument_range(line, start)
|
497
506
|
if start >= line.size then
|
498
507
|
raise "incomplete arguments in macro call."
|
@@ -501,8 +510,9 @@ class Preprocessor
|
|
501
510
|
return (range[0]+start)..(range[1]+start-1)
|
502
511
|
end
|
503
512
|
|
504
|
-
|
505
|
-
#
|
513
|
+
# Iterates over the range each argument of a +line+ from offset +start+.
|
514
|
+
#
|
515
|
+
# NOTE: keywords included into a longer one are ignored.
|
506
516
|
def each_argument_range(line,start)
|
507
517
|
return to_enum(:each_argument_range,line,start) unless block_given?
|
508
518
|
begin
|
@@ -519,22 +529,22 @@ class Preprocessor
|
|
519
529
|
end
|
520
530
|
|
521
531
|
|
522
|
-
|
532
|
+
# Tells if a line corresponds to an end keyword.
|
523
533
|
def is_endm?(line)
|
524
534
|
@endm.match(line)
|
525
535
|
end
|
526
536
|
|
527
|
-
|
537
|
+
# Tells if a line corresponds to an else keyword.
|
528
538
|
def is_elsem?(line)
|
529
539
|
@elsem.match(line)
|
530
540
|
end
|
531
541
|
|
532
|
-
|
542
|
+
# Tells if a line corresponds to an endif keyword.
|
533
543
|
def is_endifm?(line)
|
534
544
|
@endifm.match(line)
|
535
545
|
end
|
536
546
|
|
537
|
-
|
547
|
+
# Extract a macro definition from a +line+ if there is one.
|
538
548
|
def get_macro_def(line)
|
539
549
|
line = line.strip
|
540
550
|
# Locate and identify the macro keyword.
|
@@ -613,9 +623,9 @@ class Preprocessor
|
|
613
623
|
end
|
614
624
|
|
615
625
|
|
616
|
-
|
626
|
+
# Applies recursively each element of +macros+ to +line+.
|
617
627
|
#
|
618
|
-
#
|
628
|
+
# NOTE: a same macro is apply only once in a portion of the line.
|
619
629
|
def apply_macros(line)
|
620
630
|
# print "apply_macros on line=#{line}\n"
|
621
631
|
|
@@ -677,10 +687,10 @@ class Preprocessor
|
|
677
687
|
return expanded
|
678
688
|
end
|
679
689
|
|
680
|
-
|
681
|
-
#
|
690
|
+
# Close a +macro+ being input registering it if named or applying it
|
691
|
+
# otherwise.
|
682
692
|
#
|
683
|
-
#
|
693
|
+
# NOTE: for internal use only.
|
684
694
|
def close_macro(macro)
|
685
695
|
# Is the macro named?
|
686
696
|
unless macro.name.empty? or macro.is_a?(Assign) then
|
@@ -717,8 +727,8 @@ class Preprocessor
|
|
717
727
|
end
|
718
728
|
private :close_macro
|
719
729
|
|
720
|
-
|
721
|
-
#
|
730
|
+
# Preprocess an +input+ stream and write the result to an +output+
|
731
|
+
# stream.
|
722
732
|
def preprocess(input, output)
|
723
733
|
# # The current list of macros.
|
724
734
|
# @macros = KeywordSearcher.new(@separator)
|
data/lib/ppr/safer_generator.rb
CHANGED
@@ -5,24 +5,24 @@
|
|
5
5
|
######################################################################
|
6
6
|
|
7
7
|
|
8
|
-
|
9
|
-
#
|
8
|
+
# Tool for executing in a safer sandbox a proc that generates a string into a
|
9
|
+
# stream.
|
10
10
|
class SaferGenerator
|
11
11
|
|
12
|
-
|
12
|
+
# The exception raised when the safer processed failed.
|
13
13
|
class SaferException < RuntimeError
|
14
14
|
end
|
15
15
|
|
16
|
-
|
16
|
+
# The list of dangerous constants of Object.
|
17
17
|
DANGER_CONSTANTS = [ :File, :IO, :Dir ]
|
18
18
|
|
19
|
-
|
19
|
+
# The list of dangerous methods of Kernel
|
20
20
|
DANGER_METHODS = [ :system, :`, :open ]
|
21
21
|
|
22
22
|
|
23
|
-
|
24
|
-
#
|
25
|
-
#
|
23
|
+
# Creates a new safe context with while removing Kernel methods and
|
24
|
+
# constants from +black_list+ in addition to the default dangerous
|
25
|
+
# ones.
|
26
26
|
def initialize(*black_list)
|
27
27
|
# Set the black list of methods.
|
28
28
|
@black_methods = black_list.select do |symbol|
|
@@ -34,10 +34,10 @@ class SaferGenerator
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
-
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
37
|
+
# Strips all the Kernel methods and constants appart from the
|
38
|
+
# elements of the white list.
|
39
|
+
# Also strip Object from dangerous methods and constants apart
|
40
|
+
# from the elements of the white list.
|
41
41
|
def secure
|
42
42
|
# Gather the methods to strip.
|
43
43
|
methods = DANGER_METHODS + @black_methods
|
@@ -54,9 +54,9 @@ class SaferGenerator
|
|
54
54
|
end
|
55
55
|
|
56
56
|
|
57
|
-
|
57
|
+
# Executes +block+ in a safe context for generating text into a +stream+.
|
58
58
|
#
|
59
|
-
#
|
59
|
+
# If no stream is given, returns the result as a string instead.
|
60
60
|
def run(stream = nil, &block)
|
61
61
|
unless stream
|
62
62
|
# No stream given
|
data/lib/ppr/version.rb
CHANGED
data/lib/ppr.rb
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
require "ppr/version"
|
2
2
|
require "ppr/ppr_core"
|
3
3
|
|
4
|
-
##
|
4
|
+
##
|
5
|
+
# Module including the classes implementing the preprocessor in Ruby.
|
5
6
|
#
|
6
|
-
#
|
7
|
+
# Usage:
|
7
8
|
# ppr = Ppr::Preprocessor.new(<some options>)
|
8
9
|
# ppr.preprocess(<input stream to preprocess>,
|
9
10
|
# <output stream where to write the preprocessing result>)}
|