Created
April 28, 2012 13:37
-
-
Save heiko-henrich/2519124 to your computer and use it in GitHub Desktop.
breakpoints for Nu
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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