]> git.lizzy.rs Git - metalua.git/blob - src/lib/springs.lua
rewrote the build and install system, tested on linux and windows
[metalua.git] / src / lib / springs.lua
1 ----------------------------------------------------------------------
2 -- Springs -- Serialization with Pluto for Rings
3 ----------------------------------------------------------------------
4 --
5 -- Copyright (c) 2008, Fabien Fleutot <metalua@gmail.com>.
6 --
7 -- This software is released under the MIT Licence, see licence.txt
8 -- for details.
9 --
10 ----------------------------------------------------------------------
11 --
12 -- This module is an improved version of
13 -- Lua Rings <http://www.keplerproject.org/rings/>:
14 -- Lua Rings lets users create independant Lua states, and provides
15 -- limited communication means (sending commands as strings,
16 -- receiving as results strings, numbers, booleans).
17 -- Springs uses Pluto <http://luaforge.net/projects/pluto/> to
18 -- let both states communicate arbitrary data, by serializing them
19 -- as strings.
20 --
21 -- This module is designed to avoid touching Rings and Pluto, in order
22 -- to ease maintenance. This requires a bit of monkey-patching here
23 -- and there.
24 --
25 -- API
26 -- ---
27 --
28 -- * new states are created with 'springs.new()' as before
29 --
30 -- * method :dostring() still works as usual
31 --
32 -- * method :pcall(f, arg1, ..., argn) works as standard function
33 --   pcall(), except that execution occurs in the sub-state.
34 --   Moreover, 'f' can also be a string, rather tahn a function.
35 --   If it's a string, this string must eval to a function in 
36 --   the substate's context. This allows to pass standard functions
37 --   easily. For instance:
38 --   > r:pcall('table.concat', {'a', 'b', 'c'}, ',')
39 --
40 -- * method :call() is similar to :pcall(), except that in case of
41 --   error, it actually throws the error in the sender's context.
42 --   Therefore, it doesn't return a success status as does pcall().
43 --   For instance:
44 --   > assert('xxx' == r:call('string.rep', 'x', 3))
45 --
46 -- Springs requires modules Rings and Pluto to be accessible to
47 -- require() in order to work.
48 --
49 ----------------------------------------------------------------------
50
51 require 'pluto'
52 require 'rings'
53
54 ----------------------------------------------------------------------
55 -- Make the name match the module, grab state __index metamethod.
56 -- We need to use the debug() API, since there is a __metatable
57 -- metamethod to prevent metatable retrieval.
58 -- Change the __NAME__ if you want to rename the module!
59 ----------------------------------------------------------------------
60 local __NAME__      = 'springs'
61 local rings         = rings
62 local ring_index    = debug.getregistry()['state metatable'].__index
63 getfenv()[__NAME__] = rings
64
65 ----------------------------------------------------------------------
66 -- Permanent tables for Pluto's persist() and unpersist() functions.
67 -- Unused for now.
68 ----------------------------------------------------------------------
69 rings.p_perms = { }
70 rings.u_perms = { }
71
72 ----------------------------------------------------------------------
73 -- For springs to work, the newly created state must load springs, so
74 -- that it has the 'pcall_receive' function needed to answer :call()
75 -- and :pcall().
76 ----------------------------------------------------------------------
77 local original_rings_new = rings.new
78 function rings.new ()
79    local r = original_rings_new ()
80    r:dostring (string.format ("require %q", __NAME__))
81    return r
82 end
83
84 ----------------------------------------------------------------------
85 -- Serialize, send the request to the child state, 
86 -- deserialize and return the result
87 ----------------------------------------------------------------------
88 function ring_index:pcall (f, ...)
89
90    local type_f = type(f) 
91    if type_f ~= 'string' and type_f ~= 'function' then 
92       error "Springs can only call functions and strings"
93    end
94
95    -------------------------------------------------------------------
96    -- pack and send msg, get response msg
97    -------------------------------------------------------------------
98    local  data = { f, ... }
99    local  msg_snd = pluto.persist (rings.p_perms, data)
100    local  st, msg_rcv = 
101       self:dostring (string.format ("return rings.pcall_receive %q", msg_snd))
102    
103    -------------------------------------------------------------------
104    -- Upon success, unpack and return results.
105    -- Upon failure, msg_rcv is an error message
106    -------------------------------------------------------------------
107    if st then return unpack(pluto.unpersist (rings.u_perms, msg_rcv))
108    else return st, msg_rcv end
109 end
110
111 ----------------------------------------------------------------------
112 -- Similar to pcall(), but if the result is an error, this error
113 -- is actually thrown *in the sender's context*.
114 ----------------------------------------------------------------------
115 function ring_index:call (f, ...)
116    local results = { self:pcall(f, ...) }
117    if results[1] then return select(2, unpack(results))
118    else error(results[2]) end
119 end
120
121 ----------------------------------------------------------------------
122 -- Receive a request from the master state, deserialize it, do it,
123 -- serialize the result.
124 -- Format of the message, once unpersisted:
125 -- * either a function and its arguments;
126 -- * or a string, which must eval to a function, and its arguments
127 ----------------------------------------------------------------------
128 function rings.pcall_receive (rcv_msg)
129    local result
130    local data = pluto.unpersist (rings.u_perms, rcv_msg)
131    assert (type(data)=='table', "illegal springs message")
132
133    -------------------------------------------------------------------
134    -- If the function is a string, the string is evaluated in
135    -- the context of the sub-state. This is an easy way to
136    -- pass reference to global functions.
137    -------------------------------------------------------------------
138    if type(data[1]) == 'string' then 
139       local f, msg = loadstring ('return '..data[1])
140       if not f then result = { false, msg } else 
141          local status
142          status, f = pcall(f)
143          if f then data[1] = f else result = { false, f } end
144       end
145    end
146
147    -------------------------------------------------------------------
148    -- result might already have been set by a failure to load or eval
149    -- a string passed as first argument.
150    -------------------------------------------------------------------
151    if not result then result = { pcall (unpack (data)) } end
152
153    -------------------------------------------------------------------
154    -- Format of the result: a table, with as first element a boolean
155    -- indicating the success status (true ==> the evaluation went
156    -- successfully), then all the results of the evaluation.
157    -------------------------------------------------------------------
158    return pluto.persist (rings.p_perms, result)
159 end
160