relisp 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+