Skip to content

Instantly share code, notes, and snippets.

@heiko-henrich
Created April 28, 2012 13:37
Show Gist options
  • Save heiko-henrich/2519124 to your computer and use it in GitHub Desktop.
Save heiko-henrich/2519124 to your computer and use it in GitHub Desktop.
breakpoints for Nu
# breakpoints.nu
# Heiko Henrich - 2012 - heiko.henrich@gmail.com
# do you need breakpoints? no?
# i do.
# here's a small library for breakpoints in nu.
# not really extensively tested, and no idea, whether this will work on iphone...
#
# what you'll need is in the macro section:
# - breakable, !!! : exexutes a block in a different thread to enable breakpoints
# - breakpoint, ! and !: to insert breakpoints
# - resume to resume
# - on-break to setup a breakpoint handler
# - on-error to set up an error handler
#
# caveats:
# the breakble code runs in a different thread than main,
# that can cause serious problems, especially with cocoa ui ..
# you can use the !M macro to send something to the main thread.
# also multithreading isn't necessarily a good idea,
# maybe someone can solve these problems
# or has an idea ....
# the breakpoint- and error-handler are running in the main thread,
# so you can setup a nice popup window with a big resume button!
# some globals
(global global-break-handler (do (v i) ()))
(global global-error-handler (do (e) ()))
(global resume (do () ()))
(global empty-breakpoint (do (v i) (v)))
(global breakpoint empty-breakpoint)
# class BreakableThread, the basic functions for breakpoints
(class BreakableThread is NSThread
(- initWithFunction: f arguments: arglist breakHandler: bh errorHandler: eh is
(set @break-thread (super init))
(set @fun f)
(set @start-arguments arglist)
(set @break-handler bh)
(set @error-handler eh)
(set @condition ((NSCondition alloc) init))
(set @return-value nil)
(set @break-value nil)
(set @continue nil)
(set @waiting nil)
@break-thread)
(- callBreakHandler: info is
(if @break-handler
(@break-handler @break-value info))
)
(- callErrorHandler: obj is
(if @error-handler
(@error-handler obj)))
;; callFun starts the breakable code
;; the start-function @fun gets callEd with 2 arguments:
;; (@fun breakpoint-function *start-arguments)
;; 1. breakpoint-function:
;; hands the breakpoint to the function
;; whenever breakpoint-function gets evauated the thread stops
;; the breakpoint-function takes itself 2 arguments:
;; (breakpoint-function value info)
;; the could be the value of the actual expression, which gets returned after resume
;; info is is optional and will be passed to the break-handler
;; 2. start-arguments: optional start-arguments, if fun takes some
(- callFun is
(try
(eval
(cons
@fun
(cons
(do (value info)
(self break: value info: info))
@start-arguments)))
(catch (obj)
(self
performSelectorOnMainThread: "callErrorHandler:"
withObject: obj
waitUntilDone: NO))))
;; here's whrere the magic happens:
;; the thread gets blocked by an NSCondition
;; the method takes a value, which will be returned with resume
;;
(- break: bv info: info is
(set @break-value bv)
(set @waiting 't)
(self
performSelectorOnMainThread: "callBreakHandler:"
withObject: info
waitUntilDone: NO)
(while (not @continue)
(@condition lock)
(@condition wait)
(@condition unlock))
(set @waiting nil)
(set @continue nil)
@return-value)
;; Here is the counterpart, which signals the thread to continue
;; resume: injects a new value to the breakpoint
(- resume: rv is
(if @waiting
(then
(set @return-value rv)
(set @continue 't)
(@condition lock)
(@condition signal)
(@condition unlock)))
rv)
;; passes the old value before the breakpoint
(- resume is
(self resume: @break-value))
(- main is
(self callFun))
)
# breakable-fun: this is the function, that glues everything together
;; breakable-fun takes 3 functions as arguments and returns 1 function
;; start-function = breakable-fun (exec-fun breakpoint-handler error-handler)
;; 1. exec-fun (breakpoint-function *optional-arguments)
;; 2. breakpoint-handler (value info)
;; 3. error-handler (exception)
;; => start-function
;; ths start-funciton takes opional arguments for exec-fun und returns the resume-function
;; resume-function = start-function (*opional-arguments)
;;
;; resume-function (*value)
;; resume-function resumes the last breakpoint with *value or
;; the original value and returns the passed value
;;
;; breakpoint-function takes 2 arguments.
;; it hands the control to the main thread after breakpoint-handler was called
;; breakpoint-function (value info)
;;
;; breakpoint-handler gets called in the main thread after a breakpoint
;; it takes 2 arguments and returns nowhere
;; breakpoint-handler (value info)
;; - value is the value handed to the breakpoint
;; - info additional info
;;
;; error-handler caches every exception
;; error-handler (exception)
;;
(function breakable-fun (exec-fun breakpoint-handler error-handler)
(do (*optional-args)
(set bt ((BreakableThread alloc)
initWithFunction: exec-fun
arguments: *optional-args
breakHandler: breakpoint-handler
errorHandler: error-handler ))
(bt start)
(do (*rv) (if (nn? *rv)
(then (bt resume: (car *rv)))
(else (bt resume))))))
# breakpoint convenience macros
;; breakable evaluates the code with breakpoints
;; in contrary to breakable fun usage is more restricted and straightforward
;; breakable starts a programblock or function call
;; breakable returns a function, which returns the result of the evaluation, after all breakpoints are done
;; the breakpoint function is global, so every code that is called by the code in breakable can use it.
;; usage: (breakpoint value info) returns (after resuming) value or the value given by resume
;; resume is global too, as said, takes an opional argument, which will be passed to breakpoint.
;; example:
;;
;; (breakable
;; (set i 0)
;; (while (< i 20)
;; (set i (breakpoint i "index"))
;; (set i (+ i 1))))
;;
;; try
;; (resume)
;; (resume 7)
;; ...
;;
(macro breakable (*body)
`(progn
(set __bfun
(breakable-fun
(do (__breakpoint)
(progn
(global breakpoint __breakpoint)
(global __breakable-result
(progn ,@*body))
(global breakpoint empty-breakpoint)))
global-break-handler
global-error-handler))
(global resume (__bfun))
(do () (__breakable-result))))
;; more covenience is not possible
;; example: (!!! start-your-code-with-breakpoints "this way")
;;
(macro !!! (*body)
`(breakable ,*body))
;; convenience macro for the breakpoint function:
;; just put ! int the car of an expression and you have a breakpoint!
;; example: (breakable (! + 3 (! * 3 6 (! / 5 9 8))))
;; the info argument returned to the breakpoint-handler is nil
;; btw you can leave ! in the code it is replaced by ...
;;
(macro ! (*expr)
(list 'breakpoint *expr nil))
;; the same but with additional info like
;; (!: "second breakpoint" cons 'a (!: "first breakpoint" cons 'b '('c 'd)))
;; (!: (dict a: a b: b) * a b)
;;
(macro !: (info *expr)
(list 'breakpoint *expr info))
;; defines the global breakpoint handler for breakable.
;; it is possible to (maybe conditianally) resume from there
;; but if you want to do UI stuff, you need to let this end in the big main runLoop
;; you can also do slow-motion like this:
;;
;; (on-break (v i)
;; (puts "- #{i} : #{v}")
;; (sleep 1)
;; (resume))
;;
(macro on-break ((value-param info) *body)
`(global global-break-handler (do (,value-param ,info) ,@*body)))
;; defines global exception handler for breakable
;;
;; (on-error (error)
;; (puts "Error: #{error}"))
;;
(macro on-error ((value-param) *body)
`(global global-error-handler (do (,value-param) ,@*body)))
;; disables breakpoints in breakable
;;
(function disable-breakpoints ()
(if (not ($bp_disabled))
(then
(set $bp_cach breakpoint)
(global breakpoint empty-breakpoint)
(set $bp_disabled 't))))
;; re-enables breakpoints in breakable
;;
(function enable-breakpoints ()
(if $bp_disabled
(then
(global breakpoint $bp_cach)
(set $bp_disabled nil))))
;; disables breakpoints for an expression
;;
(macro !- (*expr)
`(progn
(disable-breakpoints)
(set __result ,*expr)
(enable-breakpoints)
__result))
;; enables breakpoints within an expression
;;
(macro !+ (*expr)
`(progn
(enable-breakpoints)
(set __result ,*expr)
(disable-breakpoints)
__result))
# breakpoint: helpers
;; breakpoints are only possible in a different thread than the main thread
;; that's something, cocoa doesn't like.
;; eg key value observing is (imho) disabled and many other things can cause trouble.
;; this macro sends an expression to the main thread, to circumvent this problem
;; returns the evaluated value (i hope)
;;
(macro !M (*expr)
`(progn
(set __dummy ((DummyObject alloc) initWithExpression: ',*expr))
(disable-breakpoints)
(__dummy
performSelectorOnMainThread: "call"
withObject: nil
waitUntilDone: YES)
(set __val (__dummy returnValue))
(__dummy release)
(enable-breakpoints)
__val))
;; needed for performSelectorIn... and other stuff
;;
(class DummyObject is NSObject
(- initWithExpression:e is
(set @expr e)
(super init))
(- call is
(set @return-value
(eval @expr)))
(- returnValue is @return-value)
)
# minimal breakpoint- and error-handler
;; slow motion in comments
(on-break (v i)
(puts "- #{i} : #{v}")
;; (sleep 1)
;; (resume)
)
(on-error (e)
(puts "!!! #{e}"))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment