ruby-perl 0.99.15j
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +1 -0
- data/.rspec +0 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +49 -0
- data/README.md +123 -0
- data/Rakefile +5 -0
- data/autotest/discover.rb +1 -0
- data/bin/rperl +23 -0
- data/examples/hello.pl +1 -0
- data/examples/hello.rb +9 -0
- data/examples/hello_block.rb +5 -0
- data/examples/hello_here.rb +5 -0
- data/examples/passenger/config.ru +3 -0
- data/examples/passenger/log/.keep +0 -0
- data/examples/passenger/public/.keep +0 -0
- data/examples/passenger/tmp/.keep +0 -0
- data/examples/passenger/webapp.psgi +16 -0
- data/examples/perl.ru +4 -0
- data/examples/webapp.psgi +16 -0
- data/lib/perl.rb +43 -0
- data/lib/perl/common.rb +64 -0
- data/lib/perl/ext/hash.rb +10 -0
- data/lib/perl/ext/object.rb +30 -0
- data/lib/perl/ext/string.rb +10 -0
- data/lib/perl/ffi_lib.rb +65 -0
- data/lib/perl/internal.rb +73 -0
- data/lib/perl/interpreter.rb +89 -0
- data/lib/perl/rack.rb +45 -0
- data/lib/perl/shell.rb +28 -0
- data/lib/perl/stack.rb +99 -0
- data/lib/perl/stack/function.rb +67 -0
- data/lib/perl/value.rb +4 -0
- data/lib/perl/value/array.rb +54 -0
- data/lib/perl/value/hash.rb +73 -0
- data/lib/perl/value/scalar.rb +130 -0
- data/spec/ext/hash_spec.rb +24 -0
- data/spec/ext/object_spec.rb +40 -0
- data/spec/ext/string_spec.rb +37 -0
- data/spec/hash_value_spec.rb +47 -0
- data/spec/interpreter_spec.rb +220 -0
- data/spec/scalar_value_spec.rb +82 -0
- data/spec/spec_helper.rb +39 -0
- data/spec/support/perl_value_helpers.rb +13 -0
- metadata +109 -0
data/lib/perl/ffi_lib.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
module Perl
|
2
|
+
module FFILib
|
3
|
+
def self.included(klass)
|
4
|
+
klass.instance_eval do
|
5
|
+
require 'perl/internal'
|
6
|
+
extend FFI::Library
|
7
|
+
|
8
|
+
klass.ffi_lib '/System/Library/Perl/5.10.0/darwin-thread-multi-2level/CORE/libperl.dylib'
|
9
|
+
|
10
|
+
# PERL_SYS_INIT3()
|
11
|
+
attach_function 'Perl_sys_init3', [:int, :pointer, :pointer], :void
|
12
|
+
# PERL_SYS_TERM()
|
13
|
+
attach_function 'Perl_sys_term', [], :void
|
14
|
+
|
15
|
+
klass.attach_function 'perl_alloc', [], :pointer
|
16
|
+
attach_function 'perl_construct', [:pointer], :void
|
17
|
+
# PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
|
18
|
+
attach_function 'perl_parse', [:pointer, :pointer, :int, :pointer, :pointer], :void
|
19
|
+
attach_function 'perl_run', [:pointer], :void
|
20
|
+
attach_function 'perl_destruct', [:pointer], :void
|
21
|
+
attach_function 'perl_free', [:pointer], :void
|
22
|
+
|
23
|
+
# eval_pv()
|
24
|
+
attach_function 'Perl_eval_pv', [:pointer, :string, :int], :pointer
|
25
|
+
# call_pv()
|
26
|
+
attach_function 'Perl_call_pv', [:pointer, :string, :int], :int
|
27
|
+
# call_sv()
|
28
|
+
attach_function 'Perl_call_sv', [:pointer, :pointer, :int], :int
|
29
|
+
|
30
|
+
# ENTER()
|
31
|
+
attach_function 'Perl_push_scope', [:pointer], :void
|
32
|
+
# SAVETMPS()
|
33
|
+
attach_function 'Perl_save_int', [:pointer, :pointer], :void
|
34
|
+
# FREETMPS()
|
35
|
+
attach_function 'Perl_free_tmps', [:pointer], :void
|
36
|
+
# LEAVE()
|
37
|
+
attach_function 'Perl_pop_scope', [:pointer], :void
|
38
|
+
# PUSHMARK()
|
39
|
+
attach_function 'Perl_markstack_grow', [:pointer], :void
|
40
|
+
|
41
|
+
# newSV()
|
42
|
+
attach_function 'Perl_newSVpv', [:pointer, :string, :int], :pointer
|
43
|
+
|
44
|
+
attach_function 'Perl_newRV_noinc', [:pointer, :pointer], :pointer
|
45
|
+
attach_function 'Perl_sv_2mortal', [:pointer, :pointer], :pointer
|
46
|
+
|
47
|
+
attach_function 'Perl_newHV', [:pointer], :pointer
|
48
|
+
# hv_store()
|
49
|
+
attach_function 'Perl_hv_common_key_len', [:pointer, :pointer, :string, :int32, :int, :pointer, :uint32], :pointer
|
50
|
+
|
51
|
+
attach_variable 'PL_curinterp', :pointer
|
52
|
+
|
53
|
+
#
|
54
|
+
# Returns a reference to internal information on the current
|
55
|
+
# interpreter. Much of the public Perl API is in fact a thin
|
56
|
+
# wrapper over data contained in here. A lot of damage can be
|
57
|
+
# done my manipulating it incorrectly, so be careful.
|
58
|
+
#
|
59
|
+
def curinterp
|
60
|
+
Perl::Internal.new(Perl.PL_curinterp)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Perl
|
2
|
+
class Internal < FFI::Struct
|
3
|
+
class Stat < FFI::Struct
|
4
|
+
size = 144
|
5
|
+
layout :a, :pointer
|
6
|
+
end
|
7
|
+
|
8
|
+
class Tms < FFI::Struct
|
9
|
+
size = 32
|
10
|
+
layout :a, :pointer
|
11
|
+
end
|
12
|
+
|
13
|
+
class Jmpenv < FFI::Struct
|
14
|
+
size = 168
|
15
|
+
layout :a, :pointer
|
16
|
+
end
|
17
|
+
|
18
|
+
layout :Istack_sp, :pointer,
|
19
|
+
:Iopsave, :pointer,
|
20
|
+
:Icurpad, :pointer,
|
21
|
+
:Istack_base, :pointer,
|
22
|
+
:Istack_max, :pointer,
|
23
|
+
:Iscopestack, :pointer,
|
24
|
+
:Iscopestack_ix, :int32,
|
25
|
+
:Iscopestack_max, :int32,
|
26
|
+
:Isavestack, :pointer,
|
27
|
+
:Isavestack_ix, :int32,
|
28
|
+
:Isavestack_max, :int32,
|
29
|
+
:Itmps_stack, :pointer,
|
30
|
+
:Itmps_ix, :int32,
|
31
|
+
:Itmps_floor, :int32,
|
32
|
+
:Itmps_max, :int32,
|
33
|
+
:Imodcount, :int32,
|
34
|
+
:Imarkstack, :pointer,
|
35
|
+
:Imarkstack_ptr, :pointer,
|
36
|
+
:Imarkstack_max, :pointer,
|
37
|
+
:ISv, :pointer,
|
38
|
+
:IXpv, :pointer,
|
39
|
+
:Ina, :int,
|
40
|
+
:Istatbuf, Stat, # FIXME that's just wrong
|
41
|
+
:Istatcache, Stat, 288, # FIXME that's just wrong
|
42
|
+
:Istatgv, :pointer, 432,
|
43
|
+
:Istatname, :pointer,
|
44
|
+
:Itimesbuf, Tms, # FIXME that's just wrong
|
45
|
+
:Icurpm, :pointer, 480,
|
46
|
+
:Irs, :pointer,
|
47
|
+
:Ilast_in_gv, :pointer,
|
48
|
+
:Iofs_sv, :pointer,
|
49
|
+
:Idefoutgv, :pointer,
|
50
|
+
:Ichopset, :string,
|
51
|
+
:Iformtarget, :pointer,
|
52
|
+
:Ibodytarget, :pointer,
|
53
|
+
:Itoptarget, :pointer,
|
54
|
+
:Idefstash, :pointer,
|
55
|
+
:Icurstash, :pointer,
|
56
|
+
:Irestartop, :pointer,
|
57
|
+
:Icurcop, :pointer,
|
58
|
+
:Icurstack, :pointer,
|
59
|
+
:Icurstackinfo, :pointer,
|
60
|
+
:Imainstack, :pointer,
|
61
|
+
:Itop_env, :pointer,
|
62
|
+
:Istart_env, Jmpenv, # FIXME that's just wrong
|
63
|
+
:Ierrors, :pointer, 784,
|
64
|
+
:Ihv_fetch_ent_mh, :pointer,
|
65
|
+
:Ilastgotoprobe, :pointer,
|
66
|
+
:Isortcop, :pointer,
|
67
|
+
:Isortstash, :pointer,
|
68
|
+
:Ifirstgv, :pointer,
|
69
|
+
:Isecondgv, :pointer,
|
70
|
+
# TODO more stuff here
|
71
|
+
:Iexit_flags, :uint8, 1245
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'perl'
|
2
|
+
require 'perl/common'
|
3
|
+
require 'perl/stack'
|
4
|
+
|
5
|
+
module Perl
|
6
|
+
class Interpreter
|
7
|
+
include Perl::Common
|
8
|
+
|
9
|
+
G_SCALAR = 0
|
10
|
+
G_ARRAY = 1
|
11
|
+
G_DISCARD = 2
|
12
|
+
G_EVAL = 4
|
13
|
+
G_NOARGS = 8
|
14
|
+
G_KEEPERR = 16
|
15
|
+
G_NODEBUG = 32
|
16
|
+
G_METHOD = 64
|
17
|
+
G_VOID = 128
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
start
|
21
|
+
end
|
22
|
+
|
23
|
+
def eval(str=nil, &block)
|
24
|
+
return Perl.Perl_eval_pv(Perl.PL_curinterp, str, 1) if str
|
25
|
+
|
26
|
+
if block_given?
|
27
|
+
yield(Statement.new)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
alias_method :run, :eval
|
31
|
+
|
32
|
+
def load(filename)
|
33
|
+
file = File.read(filename)
|
34
|
+
eval(file)
|
35
|
+
end
|
36
|
+
|
37
|
+
def call(method_name, args, return_type)
|
38
|
+
Perl::Stack.function_stack(args) do |stack|
|
39
|
+
rc = do_call(method_name, options_for_call(args, return_type))
|
40
|
+
ret = handle_return(rc, return_type, stack)
|
41
|
+
|
42
|
+
if return_type == :void
|
43
|
+
return nil
|
44
|
+
else
|
45
|
+
if block_given?
|
46
|
+
return yield(ret)
|
47
|
+
else
|
48
|
+
ret.freeze!
|
49
|
+
return ret
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
protected
|
56
|
+
def do_call(method_name, options)
|
57
|
+
method = method_name.is_a?(String) ? :Perl_call_pv : :Perl_call_sv
|
58
|
+
Perl.send(method, Perl.PL_curinterp, method_name, options)
|
59
|
+
end
|
60
|
+
|
61
|
+
# XXX should we have G_DISCARD ?
|
62
|
+
def options_for_call(args, return_type)
|
63
|
+
options = G_EVAL
|
64
|
+
options |= G_NOARGS if args.empty?
|
65
|
+
|
66
|
+
case return_type
|
67
|
+
when :list
|
68
|
+
options |= G_ARRAY
|
69
|
+
when :scalar
|
70
|
+
options |= G_SCALAR
|
71
|
+
when :void
|
72
|
+
options |= G_VOID
|
73
|
+
else
|
74
|
+
raise "Unknown return type #{return_type}"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def handle_return(ret, return_type, stack)
|
79
|
+
case return_type
|
80
|
+
when :list
|
81
|
+
when :scalar
|
82
|
+
raise "Unexpected ret=#{ret}" unless ret == 1
|
83
|
+
Perl::Value::Scalar.new(stack.pop_scalar)
|
84
|
+
when :void
|
85
|
+
raise "Unexpected ret=#{ret}" unless ret == 0
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/lib/perl/rack.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'perl'
|
2
|
+
require 'perl/interpreter'
|
3
|
+
|
4
|
+
module Perl
|
5
|
+
class Rack
|
6
|
+
def initialize(filename)
|
7
|
+
@interpreter = Perl::Interpreter.new
|
8
|
+
@app = @interpreter.load(filename)
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(env)
|
12
|
+
@interpreter.call(@app, {:ref => clean_env(env)}, :scalar) do |ret|
|
13
|
+
value = ret.deref.value # Array
|
14
|
+
|
15
|
+
status = value[0].value
|
16
|
+
|
17
|
+
v1 = value[1].deref.value
|
18
|
+
headers = Hash[*v1.map { |v| v.value }]
|
19
|
+
|
20
|
+
body = value[2].deref.value.map { |v| v.value }
|
21
|
+
|
22
|
+
[status, headers, body]
|
23
|
+
end
|
24
|
+
rescue => e
|
25
|
+
puts "e: #{e.inspect}\n#{e.backtrace.join("\n")}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def clean_env(hash)
|
29
|
+
ret = hash.dup
|
30
|
+
if defined?(PhusionPassenger)
|
31
|
+
ret = ret.reject do |k,v|
|
32
|
+
v.is_a?(PhusionPassenger::Utils::RewindableInput)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
ret.tap do |h|
|
36
|
+
["async.close"].each do |k|
|
37
|
+
if h.has_key?(k)
|
38
|
+
puts "Cannot handle env['#{k}'] (#{k} => #{h[k].inspect}), skipping"
|
39
|
+
h.delete("async.close")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/perl/shell.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'perl'
|
2
|
+
require 'perl/common'
|
3
|
+
|
4
|
+
module Perl
|
5
|
+
class Shell
|
6
|
+
include Perl::Common
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def run
|
10
|
+
new.run
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
Perl.setup
|
16
|
+
|
17
|
+
@my_perl = Perl.perl_alloc
|
18
|
+
Perl.perl_construct(@my_perl)
|
19
|
+
end
|
20
|
+
|
21
|
+
def run
|
22
|
+
argc, argv = argv_to_ffi
|
23
|
+
|
24
|
+
Perl.perl_parse(@my_perl, nil, argc, argv, nil)
|
25
|
+
Perl.perl_run(@my_perl)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/perl/stack.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
module Perl; class Stack; end; end
|
2
|
+
|
3
|
+
require 'perl/stack/function'
|
4
|
+
|
5
|
+
module Perl
|
6
|
+
class Stack
|
7
|
+
class << self
|
8
|
+
def function_stack(args, &block)
|
9
|
+
Function.new(self.new, args, &block)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@sp = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def dSP
|
18
|
+
@sp = Perl.curinterp[:Istack_sp].tap { |sp| trace("dSP: @sp=#{sp}") }
|
19
|
+
end
|
20
|
+
alias_method :spagain, :dSP
|
21
|
+
|
22
|
+
def enter
|
23
|
+
Perl.Perl_push_scope(Perl.PL_curinterp)
|
24
|
+
end
|
25
|
+
|
26
|
+
def savetmps
|
27
|
+
curinterp = Perl.curinterp
|
28
|
+
trace("savetmps: curinterp=#{curinterp.to_ptr.inspect}")
|
29
|
+
|
30
|
+
trace("savetmps: tmps_floor=#{curinterp[:Itmps_floor].inspect}")
|
31
|
+
addr = Perl.PL_curinterp + curinterp.offset_of(:Itmps_floor)
|
32
|
+
trace("addr=#{addr.inspect}")
|
33
|
+
|
34
|
+
Perl.Perl_save_int(Perl.PL_curinterp, addr)
|
35
|
+
trace("savetmps: tmps_floor now #{curinterp[:Itmps_floor].inspect}")
|
36
|
+
|
37
|
+
trace("curinterp[:Itmps_ix] was #{curinterp[:Itmps_ix].inspect}")
|
38
|
+
curinterp[:Itmps_floor] = curinterp[:Itmps_ix]
|
39
|
+
trace("savetmps: tmps_floor now #{curinterp[:Itmps_floor].inspect}")
|
40
|
+
end
|
41
|
+
|
42
|
+
def pushmark
|
43
|
+
trace("pushmark: @sp=#{@sp}")
|
44
|
+
|
45
|
+
curinterp = Perl.curinterp
|
46
|
+
trace("curinterp[:Imarkstack_ptr] was #{curinterp[:Imarkstack_ptr].inspect} == #{curinterp[:Imarkstack_max].inspect}")
|
47
|
+
curinterp[:Imarkstack_ptr] += FFI.type_size(:int32)
|
48
|
+
trace("curinterp[:Imarkstack_ptr] now #{curinterp[:Imarkstack_ptr].inspect}")
|
49
|
+
if curinterp[:Imarkstack_ptr] == curinterp[:Imarkstack_max]
|
50
|
+
Perl.Perl_markstack_grow(Perl.PL_curinterp)
|
51
|
+
end
|
52
|
+
trace("curinterp[:Imarkstack_ptr] <= #{@sp.address - curinterp[:Istack_base].address}")
|
53
|
+
curinterp[:Imarkstack_ptr].put_pointer(0, @sp.address - curinterp[:Istack_base].address)
|
54
|
+
trace("curinterp[:Imarkstack_ptr] now #{curinterp[:Imarkstack_ptr].inspect}")
|
55
|
+
end
|
56
|
+
|
57
|
+
def push(sv)
|
58
|
+
trace("push: @sp=#{@sp}")
|
59
|
+
@sp += FFI.type_size(:pointer)
|
60
|
+
@sp.put_pointer(0, sv)
|
61
|
+
trace("push: is now @sp=#{@sp}")
|
62
|
+
end
|
63
|
+
|
64
|
+
def putback
|
65
|
+
trace("putback: @sp=#{@sp}")
|
66
|
+
|
67
|
+
curinterp = Perl.curinterp
|
68
|
+
curinterp[:Istack_sp] = @sp
|
69
|
+
end
|
70
|
+
|
71
|
+
def pops
|
72
|
+
trace("pops: @sp=#{@sp}")
|
73
|
+
|
74
|
+
ret = @sp.get_pointer(0)
|
75
|
+
@sp = FFI::Pointer.new(@sp.address - FFI.type_size(:pointer))
|
76
|
+
trace("pops: now @sp=#{@sp}")
|
77
|
+
ret
|
78
|
+
end
|
79
|
+
|
80
|
+
def freetmps
|
81
|
+
curinterp = Perl.curinterp
|
82
|
+
trace("#{curinterp[:Itmps_ix].inspect} > #{curinterp[:Itmps_floor].inspect}")
|
83
|
+
|
84
|
+
if curinterp[:Itmps_ix] > curinterp[:Itmps_floor]
|
85
|
+
Perl.Perl_free_tmps(Perl.PL_curinterp)
|
86
|
+
trace("#{curinterp[:Itmps_ix].inspect} > #{curinterp[:Itmps_floor].inspect}")
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def leave
|
91
|
+
Perl.Perl_pop_scope(Perl.PL_curinterp)
|
92
|
+
end
|
93
|
+
|
94
|
+
protected
|
95
|
+
def trace(msg)
|
96
|
+
$stderr.puts(msg) if false
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
class Perl::Stack::Function
|
2
|
+
def initialize(stack, args, &block)
|
3
|
+
@stack = stack
|
4
|
+
@stack.dSP
|
5
|
+
@stack.enter
|
6
|
+
@stack.savetmps
|
7
|
+
|
8
|
+
case args
|
9
|
+
when nil
|
10
|
+
# nothing
|
11
|
+
when Hash
|
12
|
+
init_with_hash(args)
|
13
|
+
else
|
14
|
+
init_with_array(Array(args))
|
15
|
+
end
|
16
|
+
|
17
|
+
yield(self)
|
18
|
+
ensure
|
19
|
+
if @stack
|
20
|
+
@stack.freetmps
|
21
|
+
@stack.leave
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def push(value)
|
26
|
+
value = Perl.Perl_sv_2mortal(Perl.PL_curinterp, value)
|
27
|
+
@stack.push(value)
|
28
|
+
end
|
29
|
+
|
30
|
+
def pop_scalar
|
31
|
+
@stack.spagain
|
32
|
+
return @stack.pops
|
33
|
+
ensure
|
34
|
+
@stack.putback
|
35
|
+
end
|
36
|
+
|
37
|
+
protected
|
38
|
+
def init_with_hash(args)
|
39
|
+
return unless args.any?
|
40
|
+
|
41
|
+
begin
|
42
|
+
@stack.pushmark
|
43
|
+
args.each_pair do |type, arg|
|
44
|
+
value = arg.to_perl
|
45
|
+
value = Perl.Perl_newRV_noinc(Perl.PL_curinterp, value) if type == :ref
|
46
|
+
push(value)
|
47
|
+
end
|
48
|
+
ensure
|
49
|
+
@stack.putback
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def init_with_array(args)
|
54
|
+
return unless args.any?
|
55
|
+
|
56
|
+
args = Array(args)
|
57
|
+
begin
|
58
|
+
@stack.pushmark
|
59
|
+
args.each do |arg|
|
60
|
+
value = arg.to_perl
|
61
|
+
push(value)
|
62
|
+
end
|
63
|
+
ensure
|
64
|
+
@stack.putback
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|