Class: Relisp::Slave

Inherits:
Object show all
Defined in:
lib/relisp/slaves.rb,
lib/relisp/elisp_functions.rb

Overview

This is the base class for RubySlave and ElispSlave–the Slave class isn’t meant to be used itself.

Direct Known Subclasses

ElispSlave, RubySlave

Constant Summary collapse

CONSTANTS =

Ruby and elisp use these strings to terminate messages sent to each other. This ruby library and the elisp code are tied to one another so closely that I don’t know if it matters, but it still seemed like a bad idea to hard code the constants in both places. Just make sure that the order in the elisp function relisp-get-constants matches the ruby method send_constants.

Array.new
TRANSMISSION_CODES_REGEXP =
Regexp.new(CONSTANTS.join('|'))
PREVIOUS_ELISP_RESULT =

Every time ruby asks elisp to evaluate an expression, the result is saved in this variable so ruby can access it if necessary.

:"--relisp--previous--result"
VARIABLE_PREFIX =

A prefix for elisp variables created by ruby.

'--relisp--variable--'

Instance Method Summary collapse

Constructor Details

#initializeSlave

Returns a new instance of Slave.



67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/relisp/slaves.rb', line 67

def initialize
  # Whenever ruby calls 'eval' on some code at the request of
  # elisp it is in a context where any new variables set will drop
  # out of scope immediately.  The @local_binding is a way of
  # allowing these variables to persist through multiple calls.
  @local_binding = binding
  @current_elisp_variable_num = '0'
  @debug = nil

  @elisp_function_eh ||= Hash.new # for memoization

  Relisp.default_slave = self
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(function, *args) ⇒ Object (private)

Forward any missing method to elisp.

If a function with that name exists, write the rubyized (swap _ for -) function and arguments in prefix notation (calling the to_elisp method of each of the args).

If a symbol with that name exists, return its value.

If the last char of the missing method is ‘=’, set the symbol formed by the characters before the ‘=’ to the first argument.

For example:

emacs = Relisp::ElispSlave.new
puts emacs.concat "two", "words"
emacs.a= 5
puts emacs.a

This automatically allows access to a large portion of elisp in a rubyish way.



118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/relisp/elisp_functions.rb', line 118

def method_missing(function, *args) # :doc:
  lisp_name = function.to_s.gsub('_', '-')

  if elisp_function? lisp_name
    elisp_eval "(#{lisp_name} #{args.map{|a| a.to_elisp}.join(' ')})"
  elsif elisp_eval("(boundp '#{lisp_name})") && args.empty? # if there are args, it was meant to be a function
    elisp_eval "#{lisp_name}"
  elsif lisp_name[lisp_name.size-1..lisp_name.size-1] == "="
    elisp_eval "(setq #{lisp_name[0..lisp_name.size-2]} #{args[0].to_elisp})"
  else
    raise NameError, "#{function} is undefined variable/method/function in Ruby and Elisp"
  end
#      elisp_eval("(if (functionp '#{function}) (#{function} #{args.map{|a| a.to_elisp}.join(' ')}) #{function})")
end

Instance Method Details

#elisp_eval(code) ⇒ Object

Run code in the elisp process and return the result as the corresponding ruby object. If the ruby object is not going to be used, use elisp_exec instead.



111
112
113
114
115
116
# File 'lib/relisp/slaves.rb', line 111

def elisp_eval(code)
  code = code.to_s # maybe code is a symbol or something
  write_to_emacs code
  write_to_emacs QUESTION_CODE
  to_ruby(receive_answer)
end

#elisp_exec(code) ⇒ Object

Run code in the elisp process.



100
101
102
103
104
105
# File 'lib/relisp/slaves.rb', line 100

def elisp_exec(code)
  code = code.to_s # maybe code is nil or something
  write_to_emacs code
  write_to_emacs COMMAND_CODE
  receive_answer
end

#elisp_function?(name) ⇒ Boolean

TODO: save_selected_window TODO: with_selected_window

Returns:

  • (Boolean)


88
89
90
91
# File 'lib/relisp/elisp_functions.rb', line 88

def elisp_function?(name)
  @elisp_function_eh[name] ||= 
    elisp_eval "(functionp '#{name})"
end

#get_permanent_variable(old_variable) ⇒ Object

Return a symbol corresponding to a new elisp variable which hold the same information as old_variable. Intended primarily for use in the from_elisp method in various classes.



92
93
94
95
96
# File 'lib/relisp/slaves.rb', line 92

def get_permanent_variable(old_variable)
  permanent_variable = new_elisp_variable
  elisp_exec( "(setq #{permanent_variable} #{old_variable})" )
  return permanent_variable
end

#new_elisp_variableObject

Return a symbol which corresponds to an unused variable in elisp.



84
85
86
# File 'lib/relisp/slaves.rb', line 84

def new_elisp_variable
  (VARIABLE_PREFIX + @current_elisp_variable_num.succ!).to_sym
end

#provide(symbol, binding) ⇒ Object

Creates a method in the slave that is a reference to the variable given by symbol in the scope of binding. This is probably only useful when calling elisp in ruby where the elisp code itself calls ruby again. When the elisp process calls ruby_eval the code is executed inside the loop of the slave object, so the variables in the scope of the original call to elisp aren’t normally available.

emacs = Relisp::ElispSlave.new
number = 5
emacs.elisp_eval('(ruby-eval "number")')  #  => [error]

emacs = Relisp::ElispSlave.new
number = 5
local_binding = binding
emacs.provide(:number, local_binding)
emacs.elisp_eval('(ruby-eval "number")')  #  => 5


219
220
221
222
223
224
225
226
227
# File 'lib/relisp/slaves.rb', line 219

def provide(symbol, binding)
  instance_variable_set "@__#{symbol.to_s}__binding".to_sym, binding

  instance_eval <<-endstr
    def #{symbol.to_s}
      eval("#{symbol.to_s}", @__#{symbol.to_s}__binding)
    end
  endstr
end

#save_excursionObject

Save point, mark, and current buffer; execute a block of code; restore those things.

This does not simply call the save-excursion function in elisp, it is a rewrite to accept a ruby block.

Raises:

  • (ArgumentError)


36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/relisp/elisp_functions.rb', line 36

def save_excursion 
  raise ArgumentError unless block_given?
  begin
    start_point  = point()
    start_mark   = mark()
    start_buffer = current_buffer()
    start_active = elisp_eval( "mark-active" )
    yield
  ensure
    set_buffer(start_buffer)
    set(:"mark-active", start_active)
    goto_char(start_point)
    set_mark(start_mark)
  end
end

#with_current_bufferObject

Save the current buffer; execute a block of code; restore the current buffer.

This does not simply call the with-current-buffer function in elisp, it is a rewrite to accept a ruby block.

Raises:

  • (ArgumentError)


58
59
60
61
62
63
64
65
66
# File 'lib/relisp/elisp_functions.rb', line 58

def with_current_buffer
  raise ArgumentError unless block_given?
  begin
    start_buffer = current_buffer()
    yield
  ensure
    set_buffer(start_buffer)
  end
end

#with_temp_bufferObject

Create a temporary buffer, and evaluate a block of code there.

This does not simply call the with-temp-buffer function in elisp, it is a rewrite to accept a ruby block.

Raises:

  • (ArgumentError)


73
74
75
76
77
78
79
80
81
82
83
# File 'lib/relisp/elisp_functions.rb', line 73

def with_temp_buffer
  raise ArgumentError unless block_given?
  begin
    start_buffer = current_buffer()
    temp_buffer = Relisp::Buffer.new("*temp--relisp--buffer*", self)
    yield
  ensure
    set_buffer(start_buffer)
    temp_buffer.kill
  end
end