1# This file is interpreted by $(BASERUBY) and miniruby.
2# $(BASERUBY) is used for miniprelude.c.
3# miniruby is used for prelude.c.
4# Since $(BASERUBY) may be older than Ruby 1.9,
5# Ruby 1.9 feature should not be used.
6
7require 'erb'
8
9class Prelude
10  SRCDIR = File.dirname(File.dirname(__FILE__))
11  $:.unshift(SRCDIR)
12
13  C_ESC = {
14    "\\" => "\\\\",
15    '"' => '\"',
16    "\n" => '\n',
17  }
18
19  0x00.upto(0x1f) {|ch| C_ESC[[ch].pack("C")] ||= "\\%03o" % ch }
20  0x7f.upto(0xff) {|ch| C_ESC[[ch].pack("C")] = "\\%03o" % ch }
21  C_ESC_PAT = Regexp.union(*C_ESC.keys)
22
23  def c_esc(str)
24    '"' + str.gsub(C_ESC_PAT) { C_ESC[$&] } + '"'
25  end
26  def prelude_base(filename)
27    filename[/\A#{Regexp.quote(SRCDIR)}\/(.*?)(\.rb)?\z/om, 1]
28  end
29  def prelude_name(filename)
30    "<internal:" + prelude_base(filename) + ">"
31  end
32
33  def initialize(preludes)
34    @mkconf = nil
35    @have_sublib = false
36    @need_ruby_prefix = false
37    @preludes = {}
38    @mains = preludes.map {|filename| translate(filename)[0]}
39    @preludes.delete_if {|_, (_, _, lines, sub)| !sub && lines.empty?}
40  end
41
42  def translate(filename, sub = false)
43    idx = @preludes[filename]
44    return idx if idx
45    lines = []
46    @preludes[filename] = result = [@preludes.size, filename, lines, sub]
47    File.readlines(filename).each do |line|
48      line.gsub!(/RbConfig::CONFIG\["(\w+)"\]/) {
49        key = $1
50        unless @mkconf
51          require './rbconfig'
52          @mkconf = RbConfig::MAKEFILE_CONFIG.merge('prefix'=>'#{TMP_RUBY_PREFIX}')
53        end
54        if RbConfig::MAKEFILE_CONFIG.has_key? key
55          val = RbConfig.expand("$(#{key})", @mkconf)
56          @need_ruby_prefix ||= /\A\#\{TMP_RUBY_PREFIX\}/ =~ val
57          c_esc(val)
58        else
59          "nil"
60        end
61      }
62      line.sub!(/require\s*\(?\s*(["'])(.*?)\1\)?/) do
63        orig, path = $&, $2
64        path = File.join(SRCDIR, path)
65        if File.exist?(path)
66          @have_sublib = true
67          "TMP_RUBY_PREFIX.require(#{translate(path, true)[0]})"
68        else
69          orig
70        end
71      end
72      lines << c_esc(line)
73    end
74    result
75  end
76
77  def emit(outfile)
78    @init_name = outfile[/\w+(?=_prelude.c\b)/] || 'prelude'
79    erb = ERB.new(<<'EOS', nil, '%')
80/* -*-c-*-
81 THIS FILE WAS AUTOGENERATED BY tool/compile_prelude.rb. DO NOT EDIT.
82
83 sources: <%= @preludes.map {|n,*| prelude_base(n)}.join(', ') %>
84*/
85#include "ruby/ruby.h"
86#include "internal.h"
87#include "vm_core.h"
88
89% preludes = @preludes.values.sort
90% preludes.each {|i, prelude, lines, sub|
91
92static const char prelude_name<%=i%>[] = <%=c_esc(prelude_name(*prelude))%>;
93static const char prelude_code<%=i%>[] =
94%   lines.each {|line|
95<%=line%>
96%   }
97;
98% }
99
100#define PRELUDE_COUNT <%=@have_sublib ? preludes.size : 0%>
101
102% if @have_sublib or @need_ruby_prefix
103struct prelude_env {
104    volatile VALUE prefix_path;
105#if PRELUDE_COUNT > 0
106    char loaded[PRELUDE_COUNT];
107#endif
108};
109
110static VALUE
111prelude_prefix_path(VALUE self)
112{
113    struct prelude_env *ptr = DATA_PTR(self);
114    return ptr->prefix_path;
115}
116% end
117
118% unless preludes.empty?
119static void
120prelude_eval(VALUE code, VALUE name, VALUE line)
121{
122    rb_iseq_eval(rb_iseq_compile_with_option(code, name, Qnil, line, 0, Qtrue));
123}
124% end
125
126% if @have_sublib
127static VALUE
128prelude_require(VALUE self, VALUE nth)
129{
130    struct prelude_env *ptr = DATA_PTR(self);
131    VALUE code, name;
132    int n = FIX2INT(nth);
133
134    if (n > PRELUDE_COUNT) return Qfalse;
135    if (ptr->loaded[n]) return Qfalse;
136    ptr->loaded[n] = 1;
137    switch (n) {
138%   @preludes.each_value do |i, prelude, lines, sub|
139%     if sub
140      case <%=i%>:
141	code = rb_usascii_str_new(prelude_code<%=i%>, sizeof(prelude_code<%=i%>) - 1);
142        name = rb_usascii_str_new(prelude_name<%=i%>, sizeof(prelude_name<%=i%>) - 1);
143	break;
144%     end
145%   end
146      default:
147	return Qfalse;
148    }
149    prelude_eval(code, name, INT2FIX(1));
150    return Qtrue;
151}
152
153% end
154void
155Init_<%=@init_name%>(void)
156{
157% if @have_sublib or @need_ruby_prefix
158    struct prelude_env memo;
159    ID name = rb_intern("TMP_RUBY_PREFIX");
160    VALUE prelude = Data_Wrap_Struct(rb_cData, 0, 0, &memo);
161
162    memo.prefix_path = rb_const_remove(rb_cObject, name);
163    rb_const_set(rb_cObject, name, prelude);
164    rb_define_singleton_method(prelude, "to_s", prelude_prefix_path, 0);
165% end
166% if @have_sublib
167    memset(memo.loaded, 0, sizeof(memo.loaded));
168    rb_define_singleton_method(prelude, "require", prelude_require, 1);
169% end
170% preludes.each do |i, prelude, lines, sub|
171%   next if sub
172    prelude_eval(
173      rb_usascii_str_new(prelude_code<%=i%>, sizeof(prelude_code<%=i%>) - 1),
174      rb_usascii_str_new(prelude_name<%=i%>, sizeof(prelude_name<%=i%>) - 1),
175      INT2FIX(1));
176% end
177% if @have_sublib or @need_ruby_prefix
178    rb_gc_force_recycle(prelude);
179% end
180
181#if 0
182% preludes.length.times {|i|
183    puts(prelude_code<%=i%>);
184% }
185#endif
186}
187EOS
188    tmp = erb.result(binding)
189    open(outfile, 'w'){|f|
190      f << tmp
191    }
192  end
193end
194
195preludes = ARGV.dup
196outfile = preludes.pop
197Prelude.new(preludes).emit(outfile)
198
199