jejune 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/ruby
2
+ # encoding: utf-8
3
+ #
4
+ # author: Kyle Yetter
5
+ #
6
+
7
+ module Jejune
8
+ class Macro
9
+ include Constants
10
+
11
+ attr_accessor :manager, :name, :parameters, :blueprint
12
+
13
+ def initialize( manager, tree )
14
+ @manager = manager
15
+ #@tree = tree
16
+ @name = tree[ 0 ].text
17
+
18
+ if node = tree[ 1 ] and node.type == PARAMS
19
+ @parameters = ParameterSet.study( node )
20
+ else
21
+ @parameters = ParameterSet.new
22
+ end
23
+
24
+ body_tokens = tree.last.tokens
25
+
26
+ body_tokens.shift while t = body_tokens.first and t.type != LBRACE
27
+ body_tokens.shift
28
+
29
+ body_tokens.pop while t = body_tokens.last and t.type != RBRACE
30
+ body_tokens.pop
31
+
32
+ body_tokens.map! do | token |
33
+ if token.type == ID and index = @parameters.include?( token.text )
34
+ index
35
+ else
36
+ token.text
37
+ end
38
+ end
39
+
40
+ @blueprint = body_tokens
41
+ end
42
+
43
+ def self._load( serialized )
44
+ macro = allocate
45
+ macro.name, macro.parameters, macro.blueprint = Marshal.load( serialized )
46
+ return macro
47
+ end
48
+
49
+ def _dump( depth )
50
+ Marshal.dump( [ @name, @parameters, @blueprint ] )
51
+ end
52
+
53
+ def apply( *params )
54
+ params =
55
+ params.map do | i |
56
+ if i.respond_to?( :tokens ) then i.tokens
57
+ elsif TokenData::Token === i then [ i ]
58
+ else i
59
+ end
60
+ end
61
+
62
+ tokens = []
63
+ for item in @blueprint
64
+ case item
65
+ when Fixnum
66
+ tokens.concat( params[ item ] )
67
+ else
68
+ tokens << item
69
+ end
70
+ end
71
+
72
+ @manager.translate( tokens.join( '' ) )
73
+ end
74
+
75
+ alias expand apply
76
+
77
+ end
78
+ end
@@ -0,0 +1,289 @@
1
+ #!/usr/bin/ruby
2
+ # encoding: utf-8
3
+ #--
4
+ # Copyright (c) 2010-2011 Kyle C. Yetter
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining
7
+ # a copy of this software and associated documentation files (the
8
+ # "Software"), to deal in the Software without restriction, including
9
+ # without limitation the rights to use, copy, modify, merge, publish,
10
+ # distribute, sublicense, and/or sell copies of the Software, and to
11
+ # permit persons to whom the Software is furnished to do so, subject to
12
+ # the following conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be
15
+ # included in all copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
+ #++
25
+
26
+ require 'optparse'
27
+ require 'jejune'
28
+
29
+ module Jejune
30
+
31
+ module Options
32
+ # the input encoding type; defaults to +nil+ (currently, not used)
33
+ attr_accessor :encoding
34
+ # the input stream used by the Main script; defaults to <tt>$stdin</tt>
35
+ attr_accessor :input
36
+ # a boolean flag indicating whether or not to run the Main
37
+ # script in interactive mode; defaults to +false+
38
+ attr_accessor :interactive
39
+ attr_accessor :no_output
40
+ attr_accessor :profile
41
+ attr_accessor :debug_socket
42
+ attr_accessor :ruby_prof
43
+
44
+ def initialize( options = {} )
45
+ @no_output = options.fetch( :no_output, false )
46
+ @profile = options.fetch( :profile, false )
47
+ @debug_socket = options.fetch( :debug_socket, false )
48
+ @ruby_prof = options.fetch( :ruby_prof, false )
49
+ @encoding = options.fetch( :encoding, nil )
50
+ @interactive = options.fetch( :interactive, false )
51
+ @input = options.fetch( :input, $stdin )
52
+ end
53
+
54
+ # constructs an OptionParser and parses the argument list provided by +argv+
55
+ def parse_options( argv = ARGV )
56
+ oparser = OptionParser.new do | o |
57
+ o.separator 'Input Options:'
58
+
59
+ o.on( '-i', '--input "text to process"', doc( <<-END ) ) { |val| @input = val }
60
+ | a string to use as direct input to the recognizer
61
+ END
62
+
63
+ o.on( '-I', '--interactive', doc( <<-END ) ) { @interactive = true }
64
+ | run an interactive session with the recognizer
65
+ END
66
+ end
67
+
68
+ setup_options( oparser )
69
+ return oparser.parse( argv )
70
+ end
71
+
72
+ private
73
+
74
+ def setup_options( oparser )
75
+ # overridable hook to modify / append options
76
+ end
77
+
78
+ def doc( description_string )
79
+ description_string.chomp!
80
+ description_string.gsub!( /^ *\| ?/, '' )
81
+ description_string.gsub!( /\s+/, ' ' )
82
+ return description_string
83
+ end
84
+
85
+ end
86
+
87
+ =begin rdoc ANTLR3::Main::Main
88
+
89
+ The base-class for the three primary Main script-runner classes.
90
+ It defines the skeletal structure shared by all main
91
+ scripts, but isn't particularly useful on its own.
92
+
93
+ =end
94
+
95
+ class Main
96
+ include Options
97
+ include Util
98
+ attr_accessor :output, :error
99
+
100
+ def initialize( options = {} )
101
+ @input = options.fetch( :input, $stdin )
102
+ @output = options.fetch( :output, $stdout )
103
+ @error = options.fetch( :error, $stderr )
104
+ @name = options.fetch( :name, File.basename( $0, '.rb' ) )
105
+ super
106
+ block_given? and yield( self )
107
+ end
108
+
109
+
110
+ # runs the script
111
+ def execute( argv = ARGV )
112
+ args = parse_options( argv )
113
+ setup
114
+
115
+ @interactive and return execute_interactive
116
+
117
+ in_stream =
118
+ case
119
+ when @input.is_a?( ::String ) then StringStream.new( @input )
120
+ when args.length == 1 && args.first != '-'
121
+ ANTLR3::FileStream.new( args[ 0 ] )
122
+ else ANTLR3::FileStream.new( @input )
123
+ end
124
+ case
125
+ when @ruby_prof
126
+ load_ruby_prof
127
+ profile = RubyProf.profile do
128
+ recognize( in_stream )
129
+ end
130
+ printer = RubyProf::FlatPrinter.new( profile )
131
+ printer.print( @output )
132
+ when @profile
133
+ require 'profiler'
134
+ Profiler__.start_profile
135
+ recognize( in_stream )
136
+ Profiler__.print_profile
137
+ else
138
+ recognize( in_stream )
139
+ end
140
+ end
141
+
142
+ private
143
+
144
+ def recognize( *args )
145
+ # overriden by subclasses
146
+ end
147
+
148
+ def execute_interactive
149
+ @output.puts( tidy( <<-END ) )
150
+ | ===================================================================
151
+ | Ruby ANTLR Console for #{ $0 }
152
+ | ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
153
+ | * Enter source code lines
154
+ | * Enter EOF to finish up and exit
155
+ | (control+D on Mac/Linux/Unix or control+Z on Windows)
156
+ | ===================================================================
157
+ |
158
+ END
159
+
160
+ read_method =
161
+ begin
162
+ require 'readline'
163
+ line_number = 0
164
+ lambda do
165
+ begin
166
+ if line = Readline.readline( "#@name:#{ line_number += 1 }> ", true )
167
+ line << $/
168
+ else
169
+ @output.print( "\n" ) # ensures result output is on a new line after EOF is entered
170
+ nil
171
+ end
172
+ rescue Interrupt, EOFError
173
+ retry
174
+ end
175
+ line << "\n" if line
176
+ end
177
+
178
+ rescue LoadError
179
+ lambda do
180
+ begin
181
+ printf( "%s:%i> ", @name, @input.lineno )
182
+ flush
183
+ line = @input.gets or
184
+ @output.print( "\n" ) # ensures result output is on a new line after EOF is entered
185
+ line
186
+ rescue Interrupt, EOFError
187
+ retry
188
+ end
189
+ line
190
+ end
191
+ end
192
+
193
+ stream = InteractiveStringStream.new( :name => @name, &read_method )
194
+ recognize( stream )
195
+ end
196
+
197
+ def screen_width
198
+ ( ENV[ 'COLUMNS' ] || 80 ).to_i
199
+ end
200
+
201
+ def attempt( lib, message = nil, exit_status = nil )
202
+ yield
203
+ rescue LoadError => error
204
+ message or raise
205
+ @error.puts( message )
206
+ report_error( error )
207
+ report_load_path
208
+ exit( exit_status ) if exit_status
209
+ rescue => error
210
+ @error.puts( "received an error while attempting to load %p" % lib )
211
+ report_error( error )
212
+ exit( exit_status ) if exit_status
213
+ end
214
+
215
+ def report_error( error )
216
+ puts!( "~ error details:" )
217
+ puts!( ' [ %s ]' % error.class.name )
218
+ message = error.to_s.gsub( /\n/, "\n " )
219
+ puts!( ' -> ' << message )
220
+ for call in error.backtrace
221
+ puts!( ' ' << call )
222
+ end
223
+ end
224
+
225
+ def report_load_path
226
+ puts!( "~ content of $LOAD_PATH: " )
227
+ for dir in $LOAD_PATH
228
+ puts!( " - #{ dir }" )
229
+ end
230
+ end
231
+
232
+ def setup
233
+ # hook
234
+ end
235
+
236
+ def fetch_class( name )
237
+ name.nil? || name.empty? and return( nil )
238
+ unless constant_exists?( name )
239
+ try_to_load( name )
240
+ constant_exists?( name ) or return( nil )
241
+ end
242
+
243
+ name.split( /::/ ).inject( Object ) do |mod, name|
244
+ # ::SomeModule splits to ['', 'SomeModule'] - so ignore empty strings
245
+ name.empty? and next( mod )
246
+ mod.const_get( name )
247
+ end
248
+ end
249
+
250
+ def constant_exists?( name )
251
+ eval( "defined?(#{ name })" ) == 'constant'
252
+ end
253
+
254
+ def try_to_load( name )
255
+ if name =~ /(\w+)::(Lexer|Parser|TreeParser)$/
256
+ retry_ok = true
257
+ module_name, recognizer_type = $1, $2
258
+ script = name.gsub( /::/, '' )
259
+ begin
260
+ return( require( script ) )
261
+ rescue LoadError
262
+ if retry_ok
263
+ script, retry_ok = module_name, false
264
+ retry
265
+ else
266
+ return( nil )
267
+ end
268
+ end
269
+ end
270
+ end
271
+
272
+ %w(puts print printf flush).each do |method|
273
+ class_eval( <<-END, __FILE__, __LINE__ )
274
+ private
275
+
276
+ def #{ method }(*args)
277
+ @output.#{ method }(*args) unless @no_output
278
+ end
279
+
280
+ def #{ method }!( *args )
281
+ @error.#{ method }(*args) unless @no_output
282
+ end
283
+ END
284
+ end
285
+ end
286
+
287
+
288
+
289
+ end
@@ -0,0 +1,333 @@
1
+ #!/usr/bin/ruby
2
+ # encoding: utf-8
3
+ #--
4
+ # Copyright (c) 2010-2011 Kyle C. Yetter
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining
7
+ # a copy of this software and associated documentation files (the
8
+ # "Software"), to deal in the Software without restriction, including
9
+ # without limitation the rights to use, copy, modify, merge, publish,
10
+ # distribute, sublicense, and/or sell copies of the Software, and to
11
+ # permit persons to whom the Software is furnished to do so, subject to
12
+ # the following conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be
15
+ # included in all copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
+ #++
25
+
26
+ module Jejune
27
+ class Manager
28
+ include Constants
29
+ include Utils
30
+ include JString
31
+
32
+ attr_reader :load_path, :input, :translator, :data_directory, :macros, :dependency_map
33
+ attr_accessor :browser, :location_markers, :verbose, :root_input
34
+
35
+ alias location_markers? location_markers
36
+ private :location_markers
37
+
38
+ def initialize( *args )
39
+ options = {}
40
+ args.last.is_a?( Hash ) and options.update( args.pop )
41
+ @root_input = args.shift
42
+
43
+ @data_directory = File.expand_path( '~/.jejune' )
44
+ test( ?d, @data_directory ) or Dir.mkdir( @data_directory )
45
+ @cache_directory = File.join( @data_directory, 'cache' )
46
+ test( ?d, @cache_directory ) or Dir.mkdir( @cache_directory )
47
+
48
+ @input = nil
49
+ @input_stack = []
50
+
51
+ @load_path = options.fetch( :load_path, [] )
52
+ @load_path = [ @load_path ].flatten
53
+ @load_path.compact!
54
+
55
+ @loaded_files = {}
56
+ @translator = Translator.new( self )
57
+ @browser = options[ :browser ]
58
+ @location_markers = options.fetch( :location_markers, true )
59
+ @macros = options.fetch( :macros, {} )
60
+ @verbose = options.fetch( :verbose, false )
61
+ @dependency_map = {}
62
+ end
63
+
64
+
65
+ def define_macro( node )
66
+ macro = Macro.new( self, node )
67
+ @macros[ macro.name ] = macro
68
+ @input and @input.macros[ macro.name ] = macro
69
+ return macro
70
+ end
71
+
72
+ def add_to_path( *directories )
73
+ directories = [ directories ].flatten!.map! { | i | i.to_s }
74
+ @load_path.unshift( *directories )
75
+ end
76
+
77
+ def set_root_file( file, options = {} )
78
+ options[ :manager ] = self
79
+ @root_input = JJSFile.new( file, options )
80
+ end
81
+
82
+ def set_root_source( source, options = {} )
83
+ options[ :manager ] = self
84
+ @root_input = JJSSource.new( source, options )
85
+ end
86
+
87
+ def find_path( name, exts = DEFAULT_EXT )
88
+ find_in_path_list( @load_path, name, exts )
89
+ end
90
+
91
+ def find_resource( name, ext, system = false )
92
+ unless path = system ? find_path( name, ext ) : find_relative( name, ext )
93
+ raise( "cannot locate file `#{ name }' (exts = #{ ext })" )
94
+ end
95
+ return path
96
+ end
97
+
98
+ def find_relative( name, exts = DEFAULT_EXT )
99
+ find_in_directory( @input ? @input.directory : File.expand_path( '.' ), name, exts )
100
+ end
101
+
102
+ def find_library( name, system = false )
103
+ unless path = system ? find_path( name ) : find_relative( name )
104
+ raise( "cannot locate file `#{ name }'" )
105
+ end
106
+ return path
107
+ end
108
+
109
+
110
+ def cache_file( path )
111
+ id = MD5.hexdigest( File.expand_path( path ) << "\0" << @browser.to_s )
112
+ File.join( @cache_directory, id )
113
+ end
114
+
115
+ def loaded?( path )
116
+ @loaded_files.has_key?( File.expand_path( path ) )
117
+ end
118
+
119
+ def load_file( path )
120
+ #$stderr.puts( "- loading #{ path } #{ File.basename( cache_file( File.expand_path( path ) ) ) }" )
121
+ log( "cache check - #{ path }", 34 )
122
+ full_path = File.expand_path( path )
123
+ add_dependency( full_path )
124
+
125
+ @loaded_files.fetch( full_path ) do
126
+ cache = cache_file( full_path )
127
+ output = nil
128
+
129
+ begin
130
+ if test( ?f, cache ) and test( ?>, cache, path )
131
+ data = Marshal.load( File.read( cache ) )
132
+ for depend in data[ :dependencies ]
133
+ unless test( ?f, depend ) and test( ?>, cache, depend )
134
+ #$stderr.puts( " old < #{ depend }" )
135
+ raise "no cache"
136
+ end
137
+ end
138
+
139
+ data[ :macros ].each_value { | macro | macro.manager = self }
140
+ @macros.update( data[ :macros ] )
141
+
142
+ return( @loaded_files[ full_path ] = data[ :output ] )
143
+ end
144
+ rescue => e
145
+ #$stderr.puts( e.inspect )
146
+ # ignore
147
+ end
148
+
149
+ @loaded_files[ full_path ] = JJSFile.new( path, :manager => self, :cache_file => cache )
150
+ end
151
+ end
152
+
153
+ def require!( lib, system = lib !~ /^[\.\/\~]/ )
154
+ path = find_library( lib, system )
155
+ @input and @input.dependencies.add( File.expand_path( path ) )
156
+ log( "require! #{ path.inspect }" )
157
+ jejune_file?( path ) ? loaded?( path ) ? nil : load_file( path ) : File.read( path )
158
+ end
159
+
160
+ def include!( lib )
161
+ require!( lib, false )
162
+ end
163
+
164
+ def load!( lib, system = false )
165
+ path = find_library( lib, system )
166
+ @input and @input.dependencies.add( File.expand_path( path ) )
167
+ log( "load! #{ path.inspect }" )
168
+ jejune_file?( path ) ? load_file( path ) : File.read( path )
169
+ end
170
+
171
+ def expand_macro( name, *parameters )
172
+ macro = @macros[ name.to_s ] or raise MacroNotFound.new( name.to_s )
173
+ macro.expand( *parameters )
174
+ end
175
+
176
+ def translate( input = @root_input, options = nil )
177
+ case input
178
+ when RewriteTree
179
+ @translator.translate( input )
180
+ when JJSInput
181
+ log( "translating #{ input.path }" )
182
+ with_input( input ) do
183
+ @translator.translate( input.tree )
184
+ end
185
+ when IO, ARGF
186
+ options ||= {}
187
+ options[ :manager ] = self
188
+ input = JJSFile.new( input, options )
189
+ translate( input )
190
+ when String
191
+ options ||= {}
192
+ options[ :manager ] = self
193
+ input = JJSSource.new( input, options )
194
+ translate( input )
195
+ end
196
+ end
197
+
198
+ def compress( source, type = 'js' )
199
+ command = %W( yui-compressor --type #{ type } ).join( ' ' )
200
+ Open3.popen3( command ) do | inp, out, err |
201
+ inp.write source
202
+ inp.close
203
+ return out.read
204
+ end
205
+ end
206
+
207
+ def parse( tokens, *args )
208
+ options = args.last.is_a?( Hash ) ? args.pop : {}
209
+ rule = args.pop || options[ :rule ] || :program
210
+
211
+ stream = RewriteStream.new( tokens, options )
212
+ adaptor = RewriteAdaptor.new( stream )
213
+ parser = Parser.new( stream, :adaptor => adaptor )
214
+ parser.send( rule ).tree
215
+ end
216
+
217
+ def commit_dependencies( file, deps )
218
+ @dependency_map[ file ] = deps.to_a
219
+ end
220
+
221
+ def with_input( input )
222
+ @input_stack.push( @input ) if @input
223
+ @input = input
224
+ yield self
225
+ ensure
226
+ @input = @input_stack.pop
227
+ end
228
+
229
+ def add_dependency( path )
230
+ full_path = File.expand_path( path )
231
+ @input and @input.dependencies.add( full_path )
232
+ @input_stack.each { | f | f.dependencies.add( full_path ) }
233
+ end
234
+
235
+ def interpolate( blob, outdent = false )
236
+ segments = []
237
+
238
+ s = SourceScanner.new(
239
+ blob.data, :file => blob.file, :line => blob.line, :column => blob.data_column
240
+ )
241
+ indent = nil
242
+ margin = true
243
+ nlines = 0
244
+
245
+ until s.eos?
246
+ case
247
+ when s.scan( /#(?:(\{)|([@%\$][\$\w]*))/ )
248
+ if s[ 1 ]
249
+ tks = s.lexer { | lx | lx.tokenize_until_balanced( LBRACE, RBRACE ) }
250
+ tree = parse( tks, :source_name => blob.file )
251
+ segments << [ ?c, translate( tree ) ]
252
+ elsif variable = s[ 2 ]
253
+ case variable[ 0 ]
254
+ when ?$
255
+ segments << [ ?c, variable ]
256
+ when ?@
257
+ variable.length > 1 or raise( "syntax error -- expected variable name: `#{ s.matched }'" )
258
+ segments << [ ?c, 'this.' << variable[ 1, variable.length ] ]
259
+ when ?%
260
+ variable.length > 1 or raise( "syntax error -- expected variable name: `#{ s.matched }'" )
261
+ segments << [ ?c, variable[ 1, variable.length ] ]
262
+ else
263
+ raise( "BUG: this shouldn't happen" )
264
+ end
265
+ else
266
+ raise( "BUG: this shouldn't happen" )
267
+ end
268
+ when s.scan( /\r?\n(?:\s*\r?\n)* / )
269
+ nl = s.matched
270
+ nlines += nl.count( "\n" )
271
+
272
+ if last = segments.last and last[ 0 ] == ?s
273
+ last[ 1 ] << nl
274
+ else
275
+ segments << [ ?s, nl ]
276
+ end
277
+
278
+ if leading_space = s.check( /^\s*(?=(\S))/ )
279
+ margin &&= ( s[ 1 ] == '|' )
280
+ n = expand_tabs( leading_space ).length
281
+ indent = indent ? n < indent ? n : indent : n
282
+ end
283
+ when s.scan( %r<(?: [^\\\r\n\#]+ | (?: \\ \\ )+ | \\ \r? \n | \\ . | \# (?! [\{\$@%] ))+>mx )
284
+ if last = segments.last and last[ 0 ] == ?s
285
+ last[ 1 ] << s.matched
286
+ else
287
+ segments << [ ?s, s.matched ]
288
+ end
289
+ else
290
+ msg = "BUG: this shouldn't happen!\n"
291
+ msg << ( '= ' * 30 ) << "\n"
292
+ msg << blob.inspect << "\n"
293
+ msg << ( '= ' * 30 ) << "\n"
294
+ msg << s.inspect
295
+ raise( msg )
296
+ end
297
+ end
298
+
299
+ if outdent and nlines > 0
300
+ if first = segments.first and first[ 0 ] == ?s
301
+ first[ 1 ].sub!( /\A[ \t]*\r?\n/, '' )
302
+ first.empty? and segments.shift
303
+ end
304
+ if last = segments.last and last[ 0 ] == ?s
305
+ last[ 1 ].sub!( /\r?\n[ \t]*\z/, '' )
306
+ last.empty? and segments.pop
307
+ end
308
+
309
+ for type, text in segments
310
+ if type == ?s
311
+ margin ? text.gsub!( /(\r?\n)[ \t]*\|[ \t]?/, '\1' ) :
312
+ text.gsub!( /(\r?\n)([ \t]*)/ ) { $1 << $2[ indent, $2.length ].to_s }
313
+ end
314
+ end
315
+ end
316
+
317
+ case segments.length
318
+ when 0 then %q("")
319
+ when 1
320
+ type, text = segments.first
321
+ type == ?s ? jstring( text ) : "(#{ text }).toString()"
322
+ else
323
+ segments.map! { | type, text | type == ?s ? jstring( text ) : text.strip }
324
+ %([#{ segments.join( ',' ) }].join(''))
325
+ end
326
+ end
327
+
328
+ def log( message, color = 33 )
329
+ @verbose and $stderr.puts( "\e[#{ color }m#{ message }\e[0m" )
330
+ end
331
+
332
+ end
333
+ end