relisp 0.9.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.
- data/CHANGELOG +4 -0
- data/COPYING +674 -0
- data/Manifest +18 -0
- data/README +171 -0
- data/Rakefile +50 -0
- data/examples/elisp_master/elisp_master.el +54 -0
- data/examples/elisp_master/ruby_slave +19 -0
- data/examples/ruby_master/ruby_master_example +27 -0
- data/lib/relisp/editing_types.rb +86 -0
- data/lib/relisp/programming_types.rb +293 -0
- data/lib/relisp/slaves.rb +365 -0
- data/lib/relisp.rb +30 -0
- data/setup.rb +1599 -0
- data/src/relisp.el +264 -0
- data/src/relisp.elc +0 -0
- data/tasks/ann.rake +80 -0
- data/tasks/bones.rake +20 -0
- data/tasks/gem.rake +201 -0
- data/tasks/git.rake +40 -0
- data/tasks/manifest.rake +48 -0
- data/tasks/notes.rake +27 -0
- data/tasks/post_load.rake +39 -0
- data/tasks/rdoc.rake +50 -0
- data/tasks/rubyforge.rake +55 -0
- data/tasks/setup.rb +279 -0
- data/tasks/spec.rake +54 -0
- data/tasks/svn.rake +47 -0
- data/tasks/test.rake +40 -0
- data/test/test_editing_types.rb +43 -0
- data/test/test_programming_types.rb +275 -0
- data/test/test_slaves.rb +84 -0
- metadata +110 -0
@@ -0,0 +1,293 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C) 2009 <don@ohspite.net>
|
3
|
+
#
|
4
|
+
# This file is part of Relisp.
|
5
|
+
#
|
6
|
+
# Relisp is free software: you can redistribute it and/or modify it
|
7
|
+
# under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# Relisp is distributed in the hope that it will be useful, but
|
12
|
+
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
14
|
+
# General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with this program. If not, see
|
18
|
+
# <http://www.gnu.org/licenses/>.
|
19
|
+
#++
|
20
|
+
#
|
21
|
+
# Straight from the elisp info manual:
|
22
|
+
#
|
23
|
+
# Programming Types
|
24
|
+
#
|
25
|
+
# Integer Type:: Numbers without fractional parts.
|
26
|
+
# Floating Point Type:: Numbers with fractional parts and with a large range.
|
27
|
+
# Symbol Type:: A multi-use object that refers to a function,
|
28
|
+
# variable, or property list, and has a unique identity.
|
29
|
+
# (Sequence Type):: Both lists and arrays are classified as sequences.
|
30
|
+
# Cons Cell Type:: Cons cells, and lists (which are made from cons cells).
|
31
|
+
# (Array Type):: Arrays include strings and vectors.
|
32
|
+
# String Type:: An (efficient) array of characters.
|
33
|
+
# Vector Type:: One-dimensional arrays.
|
34
|
+
# Hash Table Type:: Super-fast lookup tables.
|
35
|
+
#
|
36
|
+
# These are also elisp programming types, but Relisp doesn't
|
37
|
+
# explicitly deal with them. Characters are basically just integers,
|
38
|
+
# and functions and macros are cons.
|
39
|
+
#
|
40
|
+
# Character Type:: The representation of letters, numbers and
|
41
|
+
# control characters.
|
42
|
+
# Function Type:: A piece of executable code you can call from elsewhere.
|
43
|
+
# Macro Type:: A method of expanding an expression into another
|
44
|
+
# expression, more fundamental but less pretty.
|
45
|
+
#
|
46
|
+
# And then these elisp types are ignored completely.
|
47
|
+
#
|
48
|
+
# Char-Table Type:: One-dimensional sparse arrays indexed by characters.
|
49
|
+
# Bool-Vector Type:: One-dimensional arrays of `t' or `nil'.
|
50
|
+
# Primitive Function Type:: A function written in C, callable from Lisp.
|
51
|
+
# Byte-Code Type:: A function written in Lisp, then compiled.
|
52
|
+
# Autoload Type:: A type used for automatically loading seldom-used
|
53
|
+
# functions.
|
54
|
+
#
|
55
|
+
# Every type in the first group obviously matches a ruby class except
|
56
|
+
# for 'Cons' and 'Vector'. The types that have an analogous ruby
|
57
|
+
# class are mapped directly to that class. 'Cons' and 'Vector' are
|
58
|
+
# mapped to a corresponding subclass of Array; the only difference
|
59
|
+
# between the two is how they are translated back to elisp.
|
60
|
+
#
|
61
|
+
# Every ruby class that corresponds to a elisp data type is duplicated
|
62
|
+
# inside the Relisp module with the rubyized version of the elisp
|
63
|
+
# name; for example, Relisp::Integer is exactly the same as Fixnum and
|
64
|
+
# Relisp::HashTable is the same as Hash.
|
65
|
+
#
|
66
|
+
# Every class needs to have a +from_elisp+ method that accepts a hash
|
67
|
+
# with the object's string representation, variable name (in the elisp
|
68
|
+
# process) and the Slave instance that created the object. The method
|
69
|
+
# returns a ruby object analogous to the elisp value.
|
70
|
+
#
|
71
|
+
# Every object needs a +to_elisp+ method that creates a string which
|
72
|
+
# results in a elisp value equivalent to the ruby object when read and
|
73
|
+
# evaluated in elisp (i.e. <tt>elisp_eval "(eval (read
|
74
|
+
# #{obj.to_elisp}))"</tt>). For most objects this basically amounts
|
75
|
+
# to a variation on the +to_s+ method. However, for objects that
|
76
|
+
# don't have a string representation (hashes, buffers, frames, ...)
|
77
|
+
# the +to_elisp+ method actually results in elisp code that, when run,
|
78
|
+
# returns the appropriate object.
|
79
|
+
|
80
|
+
module Relisp
|
81
|
+
|
82
|
+
Integer = 1.class
|
83
|
+
class Integer
|
84
|
+
def self.from_elisp(object)
|
85
|
+
object[:string].to_i
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
Float = (3.14).class
|
91
|
+
class Float
|
92
|
+
def self.from_elisp(object)
|
93
|
+
object[:string].to_f
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
Symbol = :flag.class
|
99
|
+
class Symbol
|
100
|
+
def self.from_elisp(object)
|
101
|
+
if object[:string] == 'nil'
|
102
|
+
nil
|
103
|
+
elsif object[:string] == 't'
|
104
|
+
true
|
105
|
+
else
|
106
|
+
object[:string].to_sym
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def to_elisp
|
111
|
+
"'" + self.to_s
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
class Cons < Array
|
117
|
+
def self.from_elisp(object)
|
118
|
+
new(super(object))
|
119
|
+
end
|
120
|
+
|
121
|
+
def to_elisp
|
122
|
+
print_string = '(list '
|
123
|
+
each do |elt|
|
124
|
+
print_string << elt.to_elisp << ' '
|
125
|
+
end
|
126
|
+
print_string << ')'
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
String = "words, words, words".class
|
132
|
+
class String
|
133
|
+
def self.from_elisp(object)
|
134
|
+
new(eval(object[:string]))
|
135
|
+
end
|
136
|
+
|
137
|
+
def to_elisp
|
138
|
+
self.dump
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
|
143
|
+
class Vector < Array
|
144
|
+
def self.from_elisp(object)
|
145
|
+
new(super(object))
|
146
|
+
end
|
147
|
+
|
148
|
+
def to_elisp
|
149
|
+
print_string = '[ '
|
150
|
+
each do |elt|
|
151
|
+
print_string << elt.to_elisp << ' '
|
152
|
+
end
|
153
|
+
print_string << ']'
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
|
158
|
+
HashTable = {:money => "power"}.class
|
159
|
+
class HashTable
|
160
|
+
def self.from_elisp(object)
|
161
|
+
slave = object[:slave]
|
162
|
+
object_variable = slave.get_permanent_variable(object[:variable])
|
163
|
+
|
164
|
+
unless slave.elisp_eval("(type-of #{object_variable})") == "hash-table".to_sym
|
165
|
+
raise ArgumentError, "#{object_variable} isn't a hash-table"
|
166
|
+
end
|
167
|
+
keys_var = slave.new_elisp_variable
|
168
|
+
vals_var = slave.new_elisp_variable
|
169
|
+
slave.elisp_execute( "(setq #{keys_var} nil)" )
|
170
|
+
slave.elisp_execute( "(setq #{vals_var} nil)" )
|
171
|
+
slave.elisp_execute( "(maphash (lambda (key val)
|
172
|
+
(setq #{keys_var} (append #{keys_var} (list key)))
|
173
|
+
(setq #{vals_var} (append #{vals_var} (list val)))) #{object_variable})" )
|
174
|
+
keys = slave.elisp_eval( keys_var )
|
175
|
+
vals = slave.elisp_eval( vals_var )
|
176
|
+
keys ||= []
|
177
|
+
hash = Hash.new
|
178
|
+
keys.each_index do |i|
|
179
|
+
hash[keys[i]] = vals[i]
|
180
|
+
end
|
181
|
+
|
182
|
+
slave.makunbound(object_variable)
|
183
|
+
slave.makunbound(keys_var)
|
184
|
+
slave.makunbound(vals_var)
|
185
|
+
|
186
|
+
return hash
|
187
|
+
end
|
188
|
+
|
189
|
+
def to_elisp
|
190
|
+
lisp = "(progn \n"
|
191
|
+
lisp << " (let ((--temp--relisp--variable (make-hash-table))) \n"
|
192
|
+
each_pair do |key, val|
|
193
|
+
lisp << " (puthash #{key.to_elisp} #{val.to_elisp} --temp--relisp--variable) \n"
|
194
|
+
end
|
195
|
+
lisp << "--temp--relisp--variable))"
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
end
|
200
|
+
|
201
|
+
|
202
|
+
# Every object is given a default +to_elisp+ method, and classes get a
|
203
|
+
# dummy +from_elisp+ method.
|
204
|
+
#
|
205
|
+
class Object
|
206
|
+
def to_elisp
|
207
|
+
self.to_s
|
208
|
+
end
|
209
|
+
|
210
|
+
def self.from_elisp(*args)
|
211
|
+
nil
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
class Class
|
216
|
+
# Convert classes to the symbol form of their name
|
217
|
+
def to_elisp
|
218
|
+
self.to_s.to_sym.to_elisp
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
class NilClass
|
223
|
+
def to_elisp
|
224
|
+
"nil"
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
|
229
|
+
class TrueClass
|
230
|
+
def to_elisp
|
231
|
+
"t"
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
|
236
|
+
class FalseClass
|
237
|
+
# Falseness in elisp is represented by 'nil'.
|
238
|
+
def to_elisp
|
239
|
+
nil.to_elisp
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
|
244
|
+
# The normal Array class is modified so that an array can be converted
|
245
|
+
# to either Relisp::Cons or Relisp::Vector.
|
246
|
+
#
|
247
|
+
class Array
|
248
|
+
@@default_elisp_type = Relisp::Cons
|
249
|
+
|
250
|
+
# Converts either a 'cons' or 'vector' to a ruby array.
|
251
|
+
def self.from_elisp(object)
|
252
|
+
object_variable = object[:slave].get_permanent_variable(object[:variable])
|
253
|
+
size = object[:slave].elisp_eval( "(length #{object_variable})" )
|
254
|
+
object_array = new
|
255
|
+
size.times do |i|
|
256
|
+
object_array << object[:slave].elisp_eval( "(elt #{object_variable} #{i.to_elisp})" )
|
257
|
+
end
|
258
|
+
|
259
|
+
object[:slave].elisp_execute( "(makunbound #{object_variable.to_elisp})" )
|
260
|
+
return object_array
|
261
|
+
end
|
262
|
+
|
263
|
+
# Set the elisp type that ruby arrays are converted to by default.
|
264
|
+
def self.default_elisp_type=(type)
|
265
|
+
unless type.ancestors.include?(Array)
|
266
|
+
raise ArgumentError, "#{type} is not a kind of Array"
|
267
|
+
end
|
268
|
+
|
269
|
+
@@default_elisp_type = type
|
270
|
+
end
|
271
|
+
|
272
|
+
def self.default_elisp_type
|
273
|
+
@@default_elisp_type
|
274
|
+
end
|
275
|
+
|
276
|
+
# The elisp type that this array will be converted to by to_elisp.
|
277
|
+
def elisp_type
|
278
|
+
@elisp_type = nil unless defined?(@elisp_type) #to avoid uninitialized warning
|
279
|
+
@elisp_type || @@default_elisp_type
|
280
|
+
end
|
281
|
+
|
282
|
+
def elisp_type=(type)
|
283
|
+
unless type.ancestors.include?(Array)
|
284
|
+
raise ArgumentError, "#{type} is not a kind of Array"
|
285
|
+
end
|
286
|
+
|
287
|
+
@elisp_type = type
|
288
|
+
end
|
289
|
+
|
290
|
+
def to_elisp
|
291
|
+
elisp_type.new(self).to_elisp
|
292
|
+
end
|
293
|
+
end
|
@@ -0,0 +1,365 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C) 2009 <don@ohspite.net>
|
3
|
+
#
|
4
|
+
# This file is part of Relisp.
|
5
|
+
#
|
6
|
+
# Relisp is free software: you can redistribute it and/or modify it
|
7
|
+
# under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# Relisp is distributed in the hope that it will be useful, but
|
12
|
+
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
14
|
+
# General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with this program. If not, see
|
18
|
+
# <http://www.gnu.org/licenses/>.
|
19
|
+
#++
|
20
|
+
#
|
21
|
+
# TODO:
|
22
|
+
# * maybe catch Errno::EPIPE to see if slave died
|
23
|
+
|
24
|
+
module Relisp
|
25
|
+
|
26
|
+
@default_slave = nil
|
27
|
+
|
28
|
+
def self.default_slave
|
29
|
+
@default_slave
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.default_slave=(slave)
|
33
|
+
unless slave.kind_of?(Slave)
|
34
|
+
raise ArgumentError, "#{slave} is not a Slave"
|
35
|
+
end
|
36
|
+
@default_slave = slave
|
37
|
+
end
|
38
|
+
|
39
|
+
# This is the base class for RubySlave and ElispSlave--the Slave
|
40
|
+
# class isn't meant to be used itself.
|
41
|
+
#
|
42
|
+
class Slave
|
43
|
+
|
44
|
+
# Ruby and elisp use these strings to terminate messages sent to
|
45
|
+
# each other. This ruby library and the elisp code are tied to
|
46
|
+
# one another so closely that I don't know if it matters, but it
|
47
|
+
# still seemed like a bad idea to hard code the constants in both
|
48
|
+
# places.
|
49
|
+
ANSWER_CODE = '___FORTYTWO___'
|
50
|
+
QUESTION_CODE = '___TOBEORNOTTOBE___'
|
51
|
+
ERROR_CODE = '___NO_THATSNOTTRUE_THATSIMPOSSIBLE___'
|
52
|
+
ENDOFMESSAGE_REGEXP = Regexp.new(ANSWER_CODE + '|' + QUESTION_CODE + '|' + ERROR_CODE)
|
53
|
+
# Every time ruby asks elisp to evaluate an expression, the result
|
54
|
+
# is saved in this variable so ruby can access it if necessary.
|
55
|
+
PREVIOUS_ELISP_RESULT = :"--relisp--previous--result"
|
56
|
+
# A prefix for elisp variables created by ruby.
|
57
|
+
VARIABLE_PREFIX = '--relisp--variable--'
|
58
|
+
|
59
|
+
def initialize
|
60
|
+
# Whenever ruby calls 'eval' on some code at the request of
|
61
|
+
# elisp it is in a context where any new variables set will drop
|
62
|
+
# out of scope immediately. The @local_binding is a way of
|
63
|
+
# allowing these variables to persist through multiple calls.
|
64
|
+
@local_binding = nil
|
65
|
+
@current_elisp_variable_num = '0'
|
66
|
+
@debug = nil
|
67
|
+
Relisp.default_slave = self
|
68
|
+
end
|
69
|
+
|
70
|
+
# Return a symbol which corresponds to an unused variable in
|
71
|
+
# elisp.
|
72
|
+
#
|
73
|
+
def new_elisp_variable
|
74
|
+
(VARIABLE_PREFIX + @current_elisp_variable_num.succ!).to_sym
|
75
|
+
end
|
76
|
+
|
77
|
+
# Return a symbol corresponding to a new elisp variable which hold
|
78
|
+
# the same information as _old_variable_. Intended primarily for
|
79
|
+
# use in the +from_elisp+ method in various classes.
|
80
|
+
#
|
81
|
+
def get_permanent_variable(old_variable)
|
82
|
+
permanent_variable = new_elisp_variable
|
83
|
+
elisp_execute( "(setq #{permanent_variable} #{old_variable})" )
|
84
|
+
return permanent_variable
|
85
|
+
end
|
86
|
+
|
87
|
+
# Run _code_ in the elisp process.
|
88
|
+
#
|
89
|
+
def elisp_execute(code)
|
90
|
+
code = code.to_s # maybe code is a symbol or something
|
91
|
+
write_to_emacs code
|
92
|
+
write_to_emacs QUESTION_CODE
|
93
|
+
|
94
|
+
output = ''
|
95
|
+
output_line = read_from_emacs
|
96
|
+
until output_line.strip == ANSWER_CODE
|
97
|
+
if output_line.strip == QUESTION_CODE
|
98
|
+
write_to_emacs((eval(output, @local_binding)).to_elisp)
|
99
|
+
write_to_emacs ANSWER_CODE
|
100
|
+
output = ''
|
101
|
+
elsif output_line.strip == ERROR_CODE
|
102
|
+
raise Relisp::ElispError, (eval output)
|
103
|
+
else
|
104
|
+
output << output_line
|
105
|
+
end
|
106
|
+
output_line = read_from_emacs
|
107
|
+
end
|
108
|
+
|
109
|
+
output.gsub!(/\n\z/, '')
|
110
|
+
return output
|
111
|
+
end
|
112
|
+
|
113
|
+
# Run _code_ in the elisp process and return the result as the
|
114
|
+
# corresponding ruby object. If the ruby object is not going to
|
115
|
+
# be used, use elisp_execute instead.
|
116
|
+
#
|
117
|
+
def elisp_eval(code)
|
118
|
+
result_string = elisp_execute(code)
|
119
|
+
to_ruby(result_string)
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
# Pass an elisp evaluation result to the appropriate Relisp class
|
125
|
+
# for translation. The first line of _result_string_ is the
|
126
|
+
# 'type-of' the elisp object. The line(s) after that are the text
|
127
|
+
# version of the object. In case the string representation isn't
|
128
|
+
# enough information to translate the object, the result needs to
|
129
|
+
# be kept (in emacs) in the variable +PREVIOUS_ELISP_RESULT+.
|
130
|
+
#
|
131
|
+
def to_ruby(result_string)
|
132
|
+
result_string = result_string.split("\n")
|
133
|
+
elisp_type = result_string.reverse!.pop
|
134
|
+
object_string = result_string.reverse!.join("\n")
|
135
|
+
|
136
|
+
object_info = {
|
137
|
+
:string => object_string,
|
138
|
+
:variable => PREVIOUS_ELISP_RESULT,
|
139
|
+
:slave => self,
|
140
|
+
}
|
141
|
+
|
142
|
+
# Just one more reason to love Ruby. Call the Relisp class
|
143
|
+
# formed by rubyizing the 'type-of' the result (i.e., hash-table
|
144
|
+
# becomes HashTable).
|
145
|
+
ruby_type = (eval elisp_type.split("-").map { |a| a.capitalize }.join)
|
146
|
+
unless ruby_type.kind_of? Class
|
147
|
+
raise "#{ruby_type} not implemented"
|
148
|
+
end
|
149
|
+
ruby_type.from_elisp(object_info)
|
150
|
+
end
|
151
|
+
|
152
|
+
# Send the constants that ruby and elisp need to share.
|
153
|
+
#
|
154
|
+
def pass_constants
|
155
|
+
[ANSWER_CODE, QUESTION_CODE, ERROR_CODE, PREVIOUS_ELISP_RESULT].each do |constant|
|
156
|
+
read_from_emacs
|
157
|
+
write_to_emacs constant
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# Forward any missing method to elisp, writing the function and
|
162
|
+
# arguments in prefix notation (calling the +to_elisp+ method of
|
163
|
+
# each of the _args_).
|
164
|
+
#
|
165
|
+
# This automatically allows access to a large portion of elisp
|
166
|
+
# functions a rubyish way.
|
167
|
+
#
|
168
|
+
def method_missing(function, *args) # :doc:
|
169
|
+
function = function.to_s.gsub('_', '-')
|
170
|
+
unless elisp_eval("(functionp '#{function})")
|
171
|
+
raise NameError, "#{function} is not an elisp function"
|
172
|
+
end
|
173
|
+
|
174
|
+
elisp_eval('(' +
|
175
|
+
function + ' ' +
|
176
|
+
args.map{|a| a.to_elisp}.join(' ') +
|
177
|
+
')')
|
178
|
+
end
|
179
|
+
|
180
|
+
public
|
181
|
+
|
182
|
+
# Creates a method in the slave that is a reference to the
|
183
|
+
# variable given by _symbol_ in the scope of _binding_. This is
|
184
|
+
# probably only useful when calling elisp in ruby where the elisp
|
185
|
+
# code itself calls ruby again. When the elisp process calls
|
186
|
+
# +ruby_eval+ the code is executed inside the loop of the slave
|
187
|
+
# object, so the variables in the scope of the original call to
|
188
|
+
# elisp aren't normally available.
|
189
|
+
#
|
190
|
+
# emacs = Relisp::ElispSlave.new
|
191
|
+
# number = 5
|
192
|
+
# emacs.elisp_eval('(ruby-eval "number")') # => [error]
|
193
|
+
#
|
194
|
+
# emacs = Relisp::ElispSlave.new
|
195
|
+
# number = 5
|
196
|
+
# local_binding = binding
|
197
|
+
# emacs.provide(:number, local_binding)
|
198
|
+
# emacs.elisp_eval('(ruby-eval "number")') # => 5
|
199
|
+
#
|
200
|
+
def provide(symbol, binding)
|
201
|
+
eval "@__#{symbol.to_s}__binding = binding"
|
202
|
+
|
203
|
+
instance_eval <<-endstr
|
204
|
+
def #{symbol.to_s}
|
205
|
+
eval("#{symbol.to_s}", @__#{symbol.to_s}__binding)
|
206
|
+
end
|
207
|
+
endstr
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
|
212
|
+
# This class dedicates the ruby process to responding to queries
|
213
|
+
# from the emacs process that started it. See Relisp::Slave.
|
214
|
+
#
|
215
|
+
class RubySlave < Slave
|
216
|
+
|
217
|
+
# Can be provided with a block, in which case the block is run in
|
218
|
+
# the context of the slave and then the slave is automatically
|
219
|
+
# started. This makes slave methods available to the block
|
220
|
+
# without specifying an explicit receiver, and variables and
|
221
|
+
# functions defined in the block are in scope when requests from
|
222
|
+
# elisp are evaluated.
|
223
|
+
#
|
224
|
+
def initialize
|
225
|
+
super
|
226
|
+
pass_constants
|
227
|
+
|
228
|
+
if block_given?
|
229
|
+
yield self
|
230
|
+
start
|
231
|
+
end
|
232
|
+
|
233
|
+
end
|
234
|
+
|
235
|
+
# Begin the main listening loop.
|
236
|
+
#
|
237
|
+
def start
|
238
|
+
begin
|
239
|
+
@local_binding = binding
|
240
|
+
|
241
|
+
loop do
|
242
|
+
code = ''
|
243
|
+
input = read_from_emacs
|
244
|
+
until input.strip == QUESTION_CODE
|
245
|
+
code << input
|
246
|
+
input = read_from_emacs
|
247
|
+
end
|
248
|
+
code.gsub!(/\n\z/, '')
|
249
|
+
|
250
|
+
write_to_emacs((eval code, @local_binding).to_elisp)
|
251
|
+
write_to_emacs ANSWER_CODE
|
252
|
+
end
|
253
|
+
rescue => dag_yo
|
254
|
+
write_to_emacs dag_yo
|
255
|
+
write_to_emacs ERROR_CODE
|
256
|
+
retry
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
private
|
261
|
+
|
262
|
+
# Emacs sends ruby's stdout to the filter function designated for
|
263
|
+
# the ruby process.
|
264
|
+
#
|
265
|
+
def write_to_emacs(code)
|
266
|
+
puts code
|
267
|
+
end
|
268
|
+
|
269
|
+
# Messages appear on ruby's stdin after emacs sends them to ruby
|
270
|
+
# process.
|
271
|
+
#
|
272
|
+
def read_from_emacs
|
273
|
+
gets
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
# Provides an interface to an instance of emacs started as an IO
|
278
|
+
# object. See Relisp::Slave.
|
279
|
+
#
|
280
|
+
class ElispSlave < Slave
|
281
|
+
alias do elisp_eval
|
282
|
+
|
283
|
+
# Start an emacs process, load the relisp library, and force the
|
284
|
+
# process to become a slave to ruby's bidding. The string
|
285
|
+
# _cli_options_ specifies arguments to pass to emacs on the
|
286
|
+
# command line, and _load_files_ is array of files to load (with
|
287
|
+
# the '-l' command line option) after the relisp.el library.
|
288
|
+
#
|
289
|
+
def initialize(cli_options = "--no-site-file --no-init-file", load_files = [])
|
290
|
+
super()
|
291
|
+
# load relisp.elc if available
|
292
|
+
elisp_path = File.expand_path(File.join(File.dirname(__FILE__), '../../src/relisp'))
|
293
|
+
|
294
|
+
@local_binding = binding
|
295
|
+
|
296
|
+
emacs_command = if RUBY_PLATFORM.downcase.include?('mswin')
|
297
|
+
"start emacs --batch "
|
298
|
+
else
|
299
|
+
"emacs --batch "
|
300
|
+
end
|
301
|
+
emacs_command << cli_options
|
302
|
+
emacs_command << " -l #{elisp_path}"
|
303
|
+
load_files.each do |file|
|
304
|
+
emacs_command << " -l #{file}"
|
305
|
+
end
|
306
|
+
emacs_command << " --eval '(relisp-become-slave)'"
|
307
|
+
# In batch mode, emacs sends its normal output to stderr for
|
308
|
+
# some reason. I'm sure it's a good one...
|
309
|
+
emacs_command << " 2>&1"
|
310
|
+
@emacs_pipe = IO.popen(emacs_command, "w+")
|
311
|
+
|
312
|
+
# gobble whatever output until emacs reports for duty
|
313
|
+
until read_from_emacs.strip == "SEND CONSTANTS"; end
|
314
|
+
pass_constants
|
315
|
+
end
|
316
|
+
|
317
|
+
attr_accessor :debug
|
318
|
+
|
319
|
+
# When given a block, runs the block with debugging turned on and
|
320
|
+
# then restores the former status of debug messages. Otherwise,
|
321
|
+
# toggles the status of debug messages.
|
322
|
+
#
|
323
|
+
def debugging
|
324
|
+
if block_given?
|
325
|
+
debug_original_val = @debug
|
326
|
+
begin
|
327
|
+
@debug = true
|
328
|
+
puts
|
329
|
+
puts "-----------------"
|
330
|
+
yield
|
331
|
+
ensure
|
332
|
+
@debug = debug_original_val
|
333
|
+
puts "-----------------"
|
334
|
+
end
|
335
|
+
else
|
336
|
+
@debug = ! @debug
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
private
|
341
|
+
|
342
|
+
# Emacs reads from stdin and makes the input available to the
|
343
|
+
# mini-buffer.
|
344
|
+
#
|
345
|
+
def write_to_emacs(code)
|
346
|
+
if @debug
|
347
|
+
puts "ruby> " + code.to_s unless code =~ ENDOFMESSAGE_REGEXP
|
348
|
+
end
|
349
|
+
@emacs_pipe.puts code
|
350
|
+
end
|
351
|
+
|
352
|
+
# Emacs sends whatever it outputs by way of 'message' to stderr,
|
353
|
+
# which is redirected to stdout when the emacs process is started
|
354
|
+
# in initialize.
|
355
|
+
#
|
356
|
+
def read_from_emacs
|
357
|
+
output = @emacs_pipe.gets
|
358
|
+
if @debug
|
359
|
+
puts "lisp> " + output unless output =~ ENDOFMESSAGE_REGEXP
|
360
|
+
end
|
361
|
+
return output
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
end
|
data/lib/relisp.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C) 2009 <don@ohspite.net>
|
3
|
+
#
|
4
|
+
# This file is part of Relisp.
|
5
|
+
#
|
6
|
+
# Relisp is free software: you can redistribute it and/or modify it
|
7
|
+
# under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# Relisp is distributed in the hope that it will be useful, but
|
12
|
+
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
14
|
+
# General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with this program. If not, see
|
18
|
+
# <http://www.gnu.org/licenses/>.
|
19
|
+
#++
|
20
|
+
|
21
|
+
module Relisp
|
22
|
+
VERSION = '0.9.0'
|
23
|
+
|
24
|
+
class ElispError < RuntimeError; end
|
25
|
+
end
|
26
|
+
|
27
|
+
require 'relisp/slaves'
|
28
|
+
require 'relisp/programming_types'
|
29
|
+
require 'relisp/editing_types'
|
30
|
+
|