I'm writing common lisp code for a coding challenge, it's an rpg-esque puzzle where you need to calculate the total overkill damage dealt by the warrior. as I'm very new to common lisp, my code is likely pretty bad. Please refrain from posting general common lisp coding tips, unless they are relevant to the error. I plan on posting this code, after the error gets fixed, to codereview
The code runs fine until inside tick (at the bottom) the condition when (> overkill-damage 0) is true. I'm using GNU Clisp 2.49 to run this code.
(defun timer (initialization-time interval)
(list :init initialization-time :interval interval :ready nil :time-passed 0))
(defun tick-timer (timer)
(let ((newtime (1+ (getf timer :time-passed))))
(when (and (not (getf timer :ready)) (>= newtime (getf timer :init)))
(setf (getf timer :ready) t))
(setf (getf timer :time-passed) newtime)))
(defun timer-ready? (timer)
(and
(getf timer :ready)
(= 0 (mod (getf timer :time-passed) (getf timer :interval)))))
(defun weapon (damage timer)
(list :damage damage :timer timer))
(defun weapon-attack (weapon)
(tick-timer (getf weapon :timer))
(if (timer-ready? (getf weapon :timer))
(getf weapon :damage)
0))
(defun attack (character)
(reduce #'(lambda (total weapon) (+ (weapon-attack weapon) total)) (getf character :weapons) :initial-value 0))
(defun attack-monster (monster damage)
(- monster damage))
(defun calculate-overkill-damage (health)
(if (> health 0)
0
(abs health)))
(defparameter *warrior* `(:weapons ,(list (weapon 35 (timer 0 4)))))
(defparameter *mage* `(:weapons ,(list (weapon 80 (timer 2 8)))))
(defparameter *rogue* `(:weapons ,(list (weapon 20 (timer 0 3))
(weapon 30 (timer 0 4)))))
(defparameter *monsters* '(300 600 850 900 1100 3500))
(defparameter *current-monster* 0)
(defparameter *overkill* 0)
(defparameter *game-over* nil)
; I assume, for now, that when a monster dies, they will miss the rest of their attacks
(defun tick ()
(sleep .1)
(let* ((monster (nth *current-monster* *monsters*))
(new-health (attack-monster monster (attack *warrior*)))
(overkill-damage (calculate-overkill-damage new-health)))
(format t "Attacking~%-------~%Attacking monster ~a, which has ~a health." *current-monster* monster)
(format t "~%Dealt ~a overkill damage!" overkill-damage)
(when (> overkill-damage 0)
(do (format t "Dealt ~a overkill damage!" overkill-damage)
(setf *overkill* (+ *overkill* overkill-damage))
(format t "Total overkill damage is now ~a" *overkill*)
(setf *current-monster* (1+ *current-monster*))
(format t "Moving to next monster, ~a" *current-monster*)
(when (= *current-monster* (1- (length *monsters*)))
(setf *game-over* t))))
(let* ((new-health (attack-monster monster (attack *mage*)))
(new-health (attack-monster monster (attack *rogue*))))
(setf (nth *current-monster* *monsters*) new-health)
(format t "~%Monster is now at ~a health~%" (nth *current-monster* *monsters*)))))
(loop for x from 1 until (equal *game-over* t)
do (tick))
The most important part is at the bottom of the code, the tick function. When this code gets run, I get the error *** - LET: T is a constant, may not be used as a variable.
This is what gets printed at execution:
TRUNCATED LOTS OF POINTLESS MESSAGES...
-------
Attacking monster 0, which has 10 health.
Dealt 0 overkill damage!
Monster is now at 10 health
Attacking
-------
Attacking monster 0, which has 10 health.
Dealt 25 overkill damage!
*** - LET: T is a constant, may not be used as a variable
The following restarts are available:
USE-VALUE :R1 Input a value to be used instead.
ABORT :R2 Abort main loop
Break 1 [18]> :w
<1/172> #<SPECIAL-OPERATOR LET>
[170] EVAL frame for form
(LET (FORMAT T "Dealt ~a overkill damage!" OVERKILL-DAMAGE)
(TAGBODY #:LOOP-5923 (IF SETF (GO #:END-5924))
(FORMAT T "Total overkill damage is now ~a" *OVERKILL*)
(SETQ *CURRENT-MONSTER* (1+ *CURRENT-MONSTER*))
(FORMAT T "Moving to next monster, ~a" *CURRENT-MONSTER*)
(WHEN (= *CURRENT-MONSTER* (1- (LENGTH *MONSTERS*))) (SETQ *GAME-OVER* T))
(PSETQ) (GO #:LOOP-5923) #:END-5924
(RETURN-FROM NIL (PROGN *OVERKILL* (+ *OVERKILL* OVERKILL-DAMAGE)))))
Break 1 [18]>
That :w command shows code that isn't even there, I really don't understand what's going on there.
Even if I call macroexpand on tick, the code (LET (FORMAT T "Dealt ~a overkill damage!" OVERKILL-DAMAGE)...... doesn't show up anywhere.
Does anyone know what's going on? Alternatively, if you have any CLISP debugging tips to help me pinpoint the error, please let me know!
Well, I don't understand what the code is supposed to do, but your error comes from DO: http://www.lispworks.com/documentation/HyperSpec/Body/m_do_do.htm
As the documentation says, this is a loop whose first argument is a list of variables:
(do (format t "Dealt ~a overkill damage!" overkill-damage)
This tries to use format, t, "Dealt ~a overkill damage!", and overkill-damage as variables.
If you just want to use multiple forms in the body of when, you don't have to do anything special. when supports this out of the box:
(when (> overkill-damage 0)
(format t "Dealt ~a overkill damage!" overkill-damage)
(setf *overkill* (+ *overkill* overkill-damage))
(format t "Total overkill damage is now ~a" *overkill*)
...)
DO is a macro in Common Lisp. It is one of the older control structures in Lisp, like DOLIST and DOTIMES.
Since it is a macro, it might be difficult to debug. Especially when the DO macro does not do any syntax checking on its own.
For debugging in Lisp we can use the compiler and the interpreter. First let's use the compiler:
[1]> (defun test () (do (format t "hello world") (read)))
TEST
[2]> (compile 'test)
** - Continuable Error
in TEST : Illegal syntax in LET/LET*: "hello world"
The compiler gives an error message about illegal syntax. So there is a syntax error, but it does not make it clear where it is coming from. Since there is no LETor LET* in the code, it must come from some syntax transformation -> macro. DEFUN and DO are macros.
(macro-function 'do) -> #<COMPILED-FUNCTION DO>
The next step is to look at the macro expansion of the DO form:
[4]> (macroexpand-1 '(do (format t "hello world") (read)))
(BLOCK NIL
(LET (FORMAT T "hello world")
(TAGBODY #:LOOP-3239 (IF READ (GO #:END-3240)) (PSETQ) (GO #:LOOP-3239) #:END-3240
(RETURN-FROM NIL (PROGN))))) ;
T
In above form we see the LET and we can see that the binding is wrong. So the DO form is probably wrong.
This is now a perfect time to check the syntax of do: see the HyperSpec entry for DO. Usually that should be clear enough to find the syntax error.
We can also use the CLISP interpreter and step through the example:
[5]> (step (test))
step 1 --> (TEST)
Step 1 [6]> step
step 2 --> (BLOCK NIL (LET (FORMAT T "hello world") (TAGBODY #:LOOP-3210 # # ...)))
Step 2 [7]> step
step 3 --> (LET (FORMAT T "hello world") (TAGBODY #:LOOP-3210 (IF READ #) (PSETQ) ...))
Step 3 [8]> step
*** - LET: T is a constant, may not be used as a variable
Basically we see the code transformation done in each step.
Since Common Lisp has many implementations, there are some with better error messages. For example SBCL:
* (defun test () (do (format t "hello world") (read)))
; in: DEFUN TEST
; (DO (FORMAT
; T
; "hello world")
; (READ))
;
; caught ERROR:
; during macroexpansion of
; (DO (FORMAT
; T
; "hello world")
; (READ)).
; Use *BREAK-ON-SIGNALS* to intercept.
;
; "hello world" is an illegal form for a DO varlist.
;
; compilation unit finished
; caught 1 ERROR condition
That's better.
The first argument to do defines the local variables to the loop, as a let does; you are using it as the start of the body.
Related
Does call/cc in Scheme the same thing with yield in Python and JavaScript?
I am not clear about generators. In my opinion, yield gives a language the ability to generate iterators without pain. But I am not sure whether I am right.
Does call/cc in Scheme has something related to yield in other languages? If so, are they the same thing, or what is the difference?
Thanks!
call/cc is a much more general language feature than generators. Thus you can make generators with call/cc, but you cannot make call/cc with generators.
If you have a program that computes values and use those values in other places its basically a step machine.. One might think of it as a program that have one function for each step and a continuation for the rest of the steps. Thus:
(+ (* 3 4) (* 5 6))
Can be interpreted as:
((lambda (k)
(k* 3 4 (lambda (v34)
(k* 5 6 (lambda (v56)
(k+ v34 v56 k)))))
halt)
The k-prefix just indicate that it's a CPS-version of the primitives. Thus they call the last argument as a function with the result. Notice also that the order of evaluation, which is not defined in Scheme, is in fact chosen in this rewrite. In this beautiful language call/cc is just this:
(define (kcall/cc kfn k)
(kfn (lambda (value ignored-continuation)
(k value))
k))
So when you do:
(+ (* 3 4) (call/cc (lambda (exit) (* 5 (exit 6)))))
; ==> 18
Under the hood this happens:
((lambda (k)
(k* 3 4 (lambda (v34)
(kcall/cc (lambda (exit k)
(exit 6 (lambda (v6)
(k* 5 v6 k)))
k))))
halt)
By using the substitution we can prove that this actually does exactly as intended. Since the exit function is invoked the original continuation is never called and thus the computation cancelled. In contrast to call/cc giving us this continuation that doesn't seem obvious it's no magic in CPS. Thus much of the magic of call/cc is in the compiler stage.
(define (make-generator procedure)
(define last-return values)
(define last-value #f)
(define (last-continuation _)
(let ((result (procedure yield)))
(last-return result)))
(define (yield value)
(call/cc (lambda (continuation)
(set! last-continuation continuation)
(set! last-value value)
(last-return value))))
(lambda args
(call/cc (lambda (return)
(set! last-return return)
(if (null? args)
(last-continuation last-value)
(apply last-continuation args))))))
(define test
(make-generator
(lambda (collect)
(collect 1)
(collect 5)
(collect 10)
#f)))
(test) ; ==> 1
(test) ; ==> 5
(test) ; ==> 10
(test) ; ==> #f (procedure finished)
One might make a macro to make the syntax more similar, but it's just sugar on top of this.
For more examples I love Matt Mights page with lots of examples on how to use continuations.
Here's code to define a generator:
(define-syntax define-generator
(lambda (x)
(syntax-case x (lambda)
((stx name (lambda formals e0 e1 ...))
(with-syntax ((yield (datum->syntax (syntax stx) 'yield)))
(syntax (define name
(lambda formals
(let ((resume #f) (return #f))
(define yield
(lambda args
(call-with-current-continuation
(lambda (cont)
(set! resume cont)
(apply return args)))))
(lambda ()
(call-with-current-continuation
(lambda (cont)
(set! return cont)
(cond (resume (resume))
(else (let () e0 e1 ...)
(error 'name "unexpected return"))))))))))))
((stx (name . formals) e0 e1 ...)
(syntax (stx name (lambda formals e0 e1 ...)))))))
There are examples of the use of generators at my blog. Generators use call-with-current-continuation, in a manner similar to yield in Python, but are much more general.
You can implement generators with call/cc. Here is an example:
coroutines.ss
They work in a similar way to python and ECMAScript generators.
MIT Scheme has string->input-port, Racket has open-input-string. How to implement this in pure Scheme (no Racket, Chicken, Gambit, or any implementation-specific extension).
According to Chis' answer we have a new Scheme standard, R7RS. It has open-input-string provided.
For the older R6RS, it's trivial to implement the same with make-custom-textual-input-port from (rnrs io ports (6)) library. Here is something I put together:
#!r6rs
(import (rnrs base (6))
(rnrs io ports (6))
(rnrs mutable-strings (6))
(rnrs io simple (6)))
(define (open-input-string str)
;; might not be so important to have a different indentifier
;; but this will make debugging easier if implementations use the
;; id provided
(define get-id
(let ((n 0))
(lambda (str)
(set! n (+ n 1))
(string->symbol
(string-append "string-port" str "-"
(number->string n))))))
(let ((len (string-length str))
(pos 0))
(make-custom-textual-input-port
(get-id str)
(lambda (string start count)
(let loop ((cur-dst start)
(cur-src pos)
(n 0))
(cond ((or (>= cur-src len)
(>= n count))
(set! pos cur-src)
n)
(else
(string-set! string cur-dst (string-ref str cur-src))
(loop (+ cur-dst 1)
(+ cur-src 1)
(+ n 1))))))
(lambda () pos)
(lambda (new-pos) (set! pos new-pos))
#f)))
(define test (open-input-string "(1 2 3 4)(5 6 7 8)"))
(define str (read test)) ; str == (1 2 3 4)
(define str2 (read test)) ; str2 == (5 6 7 8)
With R5RS there is not way to do this except using a file.
In the recently-ratified R7RS, open-input-string is provided directly. (Thanks to Sylwester for reminding me to look beyond R5RS. :-))
In R5RS, a pure Scheme implementation of string ports is not trivial, as it requires you to redefine all the standard I/O functions. See SRFI 6 for a reference implementation.
It really is better if your implementation supports string ports directly.
Write the string to a (temporary) file, then return an input port to read it back in. Like this:
(define (open-input-string string)
(let ((file "/tmp/foo"))
(call-with-output-file file
(lambda (port)
(display string port)))
(open-input-file file)))
> (define ps (open-input-string "This is a test; it is only a test"))
> ps
#<input-port (textual) "/tmp/foo">
> (read-line ps)
"This is a test; it is only a test"
Note, you'll need to be more sophisticated with use of file. For example, the above code only works once; it will fail with 'file exists' on a second call. But the above is a simple answer to your question.
I am writing a program that needs to recursively analyze a list of "commands" and "programs" (it is an interpreter of some "robotic language" invented by our professor for a robot living in a maze). Because my initial implementation was too slow, I decided to use call-with-current-continuation.
I know how call/cc works, I have read this and this as an explanation.
My call/cc is based on this part of tutorial:
Often we want to use call-with-current-continuation to call some
procedure that takes arguments other than an escape procedure. For
example, we might have a procedure that takes two arguments besides
the escape procedure, thus:
(define (foo x y escape) ... (if (= x 0)
(escape 'ERROR)) ...)) We can fix this by currying the procedure, making it a procedure of one argument.
[ An earlier chapter should have a discussion of currying! ]
Suppose we want to pass 0 and 1 as the values of x and y, as well as
handing foo the escape procedure. Rather than saying
(call-with-current-continuation foo) which doesn't pass enough
arguments to the call to foo, we say
(call-with-current-continuation (lambda (escape) (foo 0 1 escape)))
The lambda expression creates a closure that does exactly what we
want. It will call foo with arguments 0, 1, and the escape procedure
created by call-with-current-continuation.
However, for some reason it doesn't work and throws this exception:
call-with-current-continuation: contract violation
expected: (any/c . -> . any)
given: #<procedure:...mazesimulator.ss:301:34>
I would like you to help me find my mistake and explain why it occurs...
Here is the part of code relevant to this question:
; main program
(define (simulate state expression-list program limit)
; read the input and set global variables
(set! current-orientation (list-ref state 2))
(set! current-coordinates (list-ref state 1))
(set! current-maze (list-ref state 0))
; call the inner function
(call-with-current-continuation (lambda (exit)
(command state expression-list program limit exit)))
; this is the output
(list list-of-executed-commands (list current-maze current-coordinates current-orientation))
)
;; main recursive function
;; analyses expression-list parameter
;; evaluates its elements
;; and calls itself on the cdr of the espression-list
(define (command state expression-list program limit exit)
(if (and (not (null? expression-list))(equal? stop-command #f))
; recursion end condition, the whole procedure will be done only
; if the list is still not empty
(if (atom? expression-list) ;if the list consists of only one command
(if (equal? stop-command #f) ;positive branch - if there were no erros before
(atomic-command state expression-list program limit exit) ;call atomic-command on this element
;when flag is set to #t
(exit))
; here comes a problem with "inner ifs"
(if (atom? (car expression-list)) ;negative branch - if the first element is "if"
(if (equal? (car expression-list) 'if) ;if the list consisits only of if-clause, no other commands ((if ...))
(if ((name->function (list-ref expression-list 1))) ;evaluate the boolean - wall? north? and choose corresponding branch
(command state (list-ref expression-list 2) program limit exit)
(command state (list-ref expression-list 3) program limit exit))
(evaluate-first-and-call-command-on-rest expression-list program limit exit))
(if (equal? (car(car expression-list)) 'if) ;if the if-clause is not the only element in list - "inner if" ((if ...) turn-left put-mark)
(begin ;not only evaluate if-clause,
(if ((name->function (list-ref (car expression-list) 1)))
(command state (list-ref (car expression-list) 2) program limit exit)
(command state (list-ref (car expression-list) 3) program limit exit))
(command state (cdr expression-list) program limit exit)) ;but also call command on cdr!
(evaluate-first-and-call-command-on-rest expression-list program limit exit))))
;when limit is exceeded or when the flag is set to #t
(exit) ))
New follow-up: So at this point I am puzzled by the poster's question. GoZoner has pointed out that the continuation passed by call/cc may require an actual parameter when invoked, but this is not generally true in Racket (which, based on the poster's error message, is the language implementation that I assume is under discussion).
Furthermore, the code snippet that the original poster put in the question is incomplete, so one cannot directly execute the code in an attempt to replicate the problem. (My informal analysis hasn't revealed an obvious bug in the use of call-with-current-continuation that was presented by the original poster.) It would be useful if the original poster could derive a minimal (or at least smaller) test case that exposes the same issue.
It could be that one of the specific languages or language levels within Racket uses a more restrictive form of call/cc, but I have not found evidence of such a language level. I will pose the question to the original poster.
Edit: Chris-Jester Young has pointed out that my commentary may not apply properly here (see comments on this answer). I need to more carefully investigate the Racket's handling of continuations in imperative code; the notes below may be leading the asker down an incorrect path. (I plan to delete this answer if I can confirm that my notes below are bogus.)
Follow-up edit: It appears that Racket's handling of call/cc does indeed pass along a continuation that will accept zero values when it is invoked from a context that discards the value. For example:
(define global 7)
(define (goner exit)
(set! global 11)
(exit)
(set! global 13)
(* 2 3))
(define (hi)
(define x global)
(call-with-current-continuation goner)
(* 5 x global))
(* 5 7 11)
(hi)
The above prints 385 (twice); once for (* 5 7 11), and once for (hi).
(Original commentary follows)
The fact that you are invoking (exit) with zero arguments leads me to think that you do not completely understand how call/cc works (as in its observable behavior), despite your claim to the contrary.
I recommend you play with some small examples, completely independently of your professor's robot maze infrastructure, before you try to incorporate call/cc into your solution.
For example, I can readily reproduce your error message this way:
(define (goner)
(* 2 3))
(define (hi)
(let ((x (call-with-current-continuation goner)))
(* 5 x)))
(hi)
From the above, I get:
call-with-current-continuation: contract violation
expected: (any/c . -> . any)
given: #<procedure:goner>
which is quite similar to your error message, no? (Though to be honest, that might just be a coincidence).
Compare the output from the run above with the outputs from runs of:
(define (goner exit)
(* 2 3))
(define (hi)
(let ((x (call-with-current-continuation goner)))
(* 5 x)))
(hi)
and:
(define (goner exit)
(* 2 (exit)))
(define (hi)
(let ((x (call-with-current-continuation goner)))
(* 5 x)))
(hi)
and:
(define (goner exit)
(* 2 (exit 3)))
(define (hi)
(let ((x (call-with-current-continuation goner)))
(* 5 x)))
(hi)
and:
(define (goner exit)
(* (exit 2) 3))
(define (hi)
(let ((x (call-with-current-continuation goner)))
(* 5 x)))
(hi)
Give some careful thought to what each is illustrating, and think about how it might matter in the case of your program.
The call/cc 'escape procedure' expects a single argument. You are calling it as (exit) without the required argument. Different Scheme implementations will handle this differently. Here is one that complains:
1 ]=> (define (doit exit)
(display "DOIT2\n")
(exit)) ;; no argument, expect error
;Value: doit
1 ]=> (define (try)
(call-with-current-continuation
(lambda (exit) (display "TRY\n") (doit exit)))
'done)
;Value: try
1 ]=> (try)
TRY
DOIT2
;The procedure #[continuation 13] has been called with 0 arguments; it requires exactly 1 argument.
The reason that the 'escape procedure' expects a value is that call/cc, like all Scheme functions, needs to produce a value. The argument that is provided is the return value of call/cc when the escape procedure is invoked.
I'm having some difficulty understanding how for loops work in scheme. In particular this code runs but I don't know why
(define (bubblesort alist)
;; this is straightforward
(define (swap-pass alist)
(if (eq? (length alist) 1)
alist
(let ((fst (car alist)) (scnd (cadr alist)) (rest (cddr alist)))
(if (> fst scnd)
(cons scnd (swap-pass (cons fst rest)))
(cons fst (swap-pass (cons scnd rest)))))))
; this is mysterious--what does the 'for' in the next line do?
(let for ((times (length alist))
(val alist))
(if (> times 1)
(for (- times 1) (swap-pass val))
(swap-pass val))))
I can't figure out what the (let for (( is supposed to do here, and the for expression in the second to last line is also a bit off putting--I've had the interpreter complain that for only takes a single argument, but here it appears to take two.
Any thoughts on what's going on here?
That's not a for loop, that's a named let. What it does is create a function called for, then call that; the "looping" behavior is caused by recursion in the function. Calling the function loop is more idiomatic, btw. E.g.
(let loop ((times 10))
(if (= times 0)
(display "stopped")
(begin (display "still looping...")
(loop (- times 1)))))
gets expanded to something like
(letrec ((loop (lambda (times)
(if (= times 0)
(display "stopped")
(begin (display "still looping...")
(loop (- times 1)))))))
(loop 10))
This isn't actually using a for language feature but just using a variation of let that allows you to easily write recursive functions. See this documentation on let (it's the second form on there).
What's going on is that this let form binds the name it's passed (in this case for) to a procedure with the given argument list (times and val) and calls it with the initial values. Uses of the bound name in the body are recursive calls.
Bottom line: the for isn't significant here. It's just a name. You could rename it to foo and it would still work. Racket does have actual for loops that you can read about here.
I often find myself converting code like this:
before do
:something
end
to
before { :something }
Is there a way to automate this task in emacs? I use ruby-mode and rinary, but they're not too helpful here.
ruby-mode in Emacs 24.3 and newer has the command ruby-toggle-block.
The default binding is C-c {.
I am sure it can be made shorter and better, but for now I've got the following:
(defun ruby-get-containing-block ()
(let ((pos (point))
(block nil))
(save-match-data
(save-excursion
(catch 'break
;; If in the middle of or at end of do, go back until at start
(while (and (not (looking-at "do"))
(string-equal (word-at-point) "do"))
(backward-char 1))
;; Keep searching for the containing block (i.e. the block that begins
;; before our point, and ends after it)
(while (not block)
(if (looking-at "do\\|{")
(let ((start (point)))
(ruby-forward-sexp)
(if (> (point) pos)
(setq block (cons start (point)))
(goto-char start))))
(if (not (search-backward-regexp "do\\|{" (point-min) t))
(throw 'break nil))))))
block))
(defun ruby-goto-containing-block-start ()
(interactive)
(let ((block (ruby-get-containing-block)))
(if block
(goto-char (car block)))))
(defun ruby-flip-containing-block-type ()
(interactive)
(save-excursion
(let ((block (ruby-get-containing-block)))
(goto-char (car block))
(save-match-data
(let ((strings (if (looking-at "do")
(cons
(if (= 3 (count-lines (car block) (cdr block)))
"do\\( *|[^|]+|\\)? *\n *\\(.*?\\) *\n *end"
"do\\( *|[^|]+|\\)? *\\(\\(.*\n?\\)+\\) *end")
"{\\1 \\2 }")
(cons
"{\\( *|[^|]+|\\)? *\\(\\(.*\n?\\)+\\) *}"
(if (= 1 (count-lines (car block) (cdr block)))
"do\\1\n\\2\nend"
"do\\1\\2end")))))
(when (re-search-forward (car strings) (cdr block) t)
(replace-match (cdr strings) t)
(delete-trailing-whitespace (match-beginning 0) (match-end 0))
(indent-region (match-beginning 0) (match-end 0))))))))
There are two functions to be bound to keys: ruby-goto-containing-block-start and ruby-flip-containing-block-type.
Either command works anywhere inside a block, and hopefully they can skip blocks that should be skipped - although that shouldn't be an issue if you are converting to a short block format.
The ruby-flip-containing-block-type collapses three line do .. end blocks to single line {} and vice versa. If the blocks are not exactly 3 lines and 1 line long, it should leave them alone.
I am using this on my ruby setup now, so I would appreciate improvements.
You could use a regular expression that crosses newlines.
/do(C-q C-j\?)*(.*)(C-q C-j\?)*end/
and replace with
{\2 }
Something like that could work. You could then customize it until it does exactly what you need and bind it to a macro so that you can whip it out and impress your friends anytime!
I tested the above regexes in vi (my editor of choice) and they worked. So something similar should work for you.
For more information, make sure to checkout the emacs wiki!
Here is a function. I am an elisp beginner. It only goes one way; from do to {. let me know if it works for you.