jejune 1.1.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.
- checksums.yaml +15 -0
- data/.gemtest +0 -0
- data/History.txt +12 -0
- data/Manifest.txt +39 -0
- data/README.txt +55 -0
- data/Rakefile +75 -0
- data/bin/jjs +173 -0
- data/lib/jejune.rb +78 -0
- data/lib/jejune/boot.rb +107 -0
- data/lib/jejune/constants.rb +105 -0
- data/lib/jejune/data-extension.rb +144 -0
- data/lib/jejune/dependency-scanner.rb +69 -0
- data/lib/jejune/ejjs.rb +178 -0
- data/lib/jejune/errors.rb +53 -0
- data/lib/jejune/grammar.rb +32 -0
- data/lib/jejune/grammar/JavaScript.g +668 -0
- data/lib/jejune/grammar/Jejune.g +1029 -0
- data/lib/jejune/grammar/Jejune.tokens +241 -0
- data/lib/jejune/grammar/lexer.rb +6504 -0
- data/lib/jejune/grammar/parser.rb +17378 -0
- data/lib/jejune/grammar/rakefile +29 -0
- data/lib/jejune/grammar/tree.rb +6737 -0
- data/lib/jejune/input.rb +124 -0
- data/lib/jejune/jstring.rb +163 -0
- data/lib/jejune/lo-fi-lexer.rb +633 -0
- data/lib/jejune/macro.rb +78 -0
- data/lib/jejune/main.rb +289 -0
- data/lib/jejune/manager.rb +333 -0
- data/lib/jejune/node-test.rb +71 -0
- data/lib/jejune/parameters.rb +83 -0
- data/lib/jejune/rewrite-debug.rb +61 -0
- data/lib/jejune/rewrite.rb +125 -0
- data/lib/jejune/scanner.rb +201 -0
- data/lib/jejune/translator.rb +710 -0
- data/lib/jejune/tree-walker.rb +81 -0
- data/lib/jejune/utils.rb +81 -0
- data/lib/jejune/version.rb +38 -0
- data/spec/samples.txt +51 -0
- data/spec/translation.rb +69 -0
- data/spec/utils.rb +63 -0
- data/tools/env.fish +2 -0
- metadata +147 -0
data/lib/jejune/macro.rb
ADDED
@@ -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
|
data/lib/jejune/main.rb
ADDED
@@ -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
|