Common List: Use handler-case in different packages - common-lisp

I've got the following lisp code :
;;; hop.lisp
(defpackage #:hop
(:use #:cl ))
(in-package :hop)
(export 'hop)
(defun hop ()
(restart-case
(error "Hop")
(hop ()
(format t "hop"))))
Where I define a dummy function that always fails but provides a restart : hop.
In another package, in this file :
;;; hip.lisp
(defpackage #:hip
(:use #:cl #:hop))
(in-package :hip)
(defun dhip ()
(hop:hop))
(defun hip ()
(handler-case
(hop:hop)
(error (e)
(declare (ignore e))
(format t "restarts: ~a~%" (compute-restarts))
(invoke-restart 'hop))))
I define the function (hip) and (dhip) that call the function (hop) from the first package.
When I call (dhip), sbcl offers me a prompt where I can choose to restart using my restart hop :
Hop
[Condition of type SIMPLE-ERROR]
Restarts:
0: [HOP] HOP
1: [RETRY] Retry SLIME REPL evaluation request.
2: [*ABORT] Return to SLIME's top level.
3: [ABORT] abort thread (#<THREAD "repl-thread" RUNNING {1009868103}>)
Backtrace:
0: (HOP)
1: (DHIP)
2: (SB-INT:SIMPLE-EVAL-IN-LEXENV (DHIP) #<NULL-LEXENV>)
3: (EVAL (DHIP))
--more--
Which is what I expected.
However, when I call (hip), my restart hop is not listed by (compute-restarts), and it fails to use it :(
No restart HOP is active.
[Condition of type SB-INT:SIMPLE-CONTROL-ERROR]
Restarts:
0: [RETRY] Retry SLIME REPL evaluation request.
1: [*ABORT] Return to SLIME's top level.
2: [ABORT] abort thread (#<THREAD "repl-thread" RUNNING {1009868103}>)
Backtrace:
0: (SB-INT:FIND-RESTART-OR-CONTROL-ERROR HOP NIL T)
1: (INVOKE-RESTART HOP)
2: ((FLET #:FUN1 :IN HIP) #<unused argument>)
3: (HIP)
4: (SB-INT:SIMPLE-EVAL-IN-LEXENV (HIP) #<NULL-LEXENV>)
5: (EVAL (HIP))
Do you know what can be done to make this works ?
Thanks,
Guillaule

This has nothing to do with the packages.
With HANDLER-CASE the stack is already unwound, when the handler runs. Thus the restart established in the function is gone.
Use HANDLER-BIND instead. It runs the handler in the context of the error and thus the restarts of the function are available.
Example:
(defun hip ()
(handler-bind ((error (lambda (e)
(declare (ignore e))
(format t "restarts: ~a~%" (compute-restarts))
(invoke-restart 'hop))))
(hop)))

Related

'Required argument is not a symbol' error in let binding

In the following code, I get a Required argument is not a symbol error.
(defconstant +localhost+ (vector 127 0 0 1))
(defun ip-from-hostname (hostname)
(sb-bsd-sockets:host-ent-addresses
(sb-bsd-sockets:get-host-by-name hostname)))
(defun test-connect
(let ((ip (car (ip-from-hostname "www.google.com")))
(socket (make-instance 'sb-bsd-sockets:inet-socket :type :stream :protocol :tcp)))
(sb-bsd-sockets:socket-bind socket +localhost+ 8080)
(sb-bsd-sockets:socket-connect socket ip)
(sb-bsd-sockets:socket-send socket "GET / HTTP/1.1" nil)
(write-line (sb-bsd-sockets:socket-receive socket nil 2048))))
(test-connect)
More complete error message:
Required argument is not a symbol: ((IP
(CAR
(IP-FROM-HOSTNAME "www.google.com")))
(SOCKET
(MAKE-INSTANCE
'SB-BSD-SOCKETS:INET-SOCKET :TYPE
:STREAM :PROTOCOL :TCP)))
I've narrowed down the issue to the section calling ip-from-hostname, but the strange thing is a boiled down version of the let binding works in the REPL:
(let ((ip (sb-bsd-sockets:host-ent-addresses (sb-bsd-sockets:get-host-by-name "www.google.com"))))
(write-line (write-to-string (car ip))))
I also tried replacing the ip-from-hostname call with its body thinking that it might be something to do with the arguments, but still no luck. Any thoughts?
(defun test-connect ...
... should start with a lambda list (the list of parameters), which is missing.
Remember, the syntax for DEFUN is:
defun function-name lambda-list
[[declaration* | documentation]]
form*

Error connecting to Slack IRC gateway

I'm using the cl-irc library to connect to Slack, via the IRC gateway Slack provides.
However I'm getting the following error when I try to start the message loop with read-message-loop:
error while parsing arguments to DESTRUCTURING-BIND:
invalid number of elements in
("duncan_bayne" "Welcome" "to" "Slack" "IRC" "Gateway"
"server" "duncan_bayne!~duncan_bayne#1.2.3.4")
to satisfy lambda list
(CL-IRC:NICKNAME CL-IRC::WELCOME-MESSAGE):
exactly 2 expected, but 8 found
[Condition of type SB-KERNEL::ARG-COUNT-ERROR]
...
Backtrace:
0: ((:METHOD CL-IRC::DEFAULT-HOOK (CL-IRC:IRC-RPL_WELCOME-MESSAGE)) #<CL-IRC:IRC-RPL_WELCOME-MESSAGE irc.tinyspeck.com RPL_WELCOME {1007FC6293}>) [fast-method]
1: ((:METHOD CL-IRC::APPLY-TO-HOOKS (T)) #<CL-IRC:IRC-RPL_WELCOME-MESSAGE irc.tinyspeck.com RPL_WELCOME {1007FC6293}>) [fast-method]
2: ((:METHOD CL-IRC:IRC-MESSAGE-EVENT (T CL-IRC:IRC-MESSAGE)) #<unavailable argument> #<CL-IRC:IRC-RPL_WELCOME-MESSAGE irc.tinyspeck.com RPL_WELCOME {1007FC6293}>) [fast-method]
3: ((:METHOD CL-IRC:READ-MESSAGE (CL-IRC:CONNECTION)) #<CL-IRC:CONNECTION myob.irc.slack.com {10068E8ED3}>) [fast-method]
4: ((:METHOD CL-IRC:READ-MESSAGE-LOOP (T)) #<CL-IRC:CONNECTION myob.irc.slack.com {10068E8ED3}>) [fast-method]
5: (SB-INT:SIMPLE-EVAL-IN-LEXENV (CL-IRC:READ-MESSAGE-LOOP *CONN*) #<NULL-LEXENV>)
6: (EVAL (CL-IRC:READ-MESSAGE-LOOP *CONN*))
While in the REPL I see:
UNHANDLED-EVENT:3672562852: RPL_MYINFO: irc.tinyspeck.com duncan_bayne "IRC-SLACK gateway"
I'm not sure what I'm doing wrong here; I'm fairly sure it's not my hooks, because the problem persists even if I disable them all.
Also, I can use the connection as expected - say, joining a channel and sending messages - provided I don't try to start the message loop.
At a guess, I'd say Slack is responding to connection with an unexpected message?
The fix as suggested by #jkilski is to modify cl-irc to accept the slightly unusual (but probably standards-compilant?) responses from Slack:
(in-package #:cl-irc)
(defmethod default-hook ((message irc-rpl_welcome-message))
(with-slots
(connection host user arguments)
message
(destructuring-bind
(nickname &rest welcome-message)
arguments
(setf (user connection)
(make-user connection
:nickname nickname
:hostname host
:username user)))))
(in-package #:irc)
(defmethod default-hook ((message irc-rpl_namreply-message))
(let* ((connection (connection message)))
(destructuring-bind
(nick chan-visibility channel &optional names)
(arguments message)
(declare (ignore nick))
(let ((channel (find-channel connection channel)))
(setf (visibility channel)
(or (second (assoc chan-visibility
'(("=" :public) ("*" :private) ("#" :secret))
:test #'string=))
:unknown))
(unless (has-mode-p channel 'namreply-in-progress)
(add-mode channel 'namreply-in-progress
(make-instance 'list-value-mode :value-type :user)))
(dolist (nickname (tokenize-string names))
(let ((user (find-or-make-user connection
(canonicalize-nickname connection
nickname))))
(unless (equal user (user connection))
(add-user connection user)
(add-user channel user))
(set-mode channel 'namreply-in-progress user)
(let* ((mode-char (getf (nick-prefixes connection)
(elt nickname 0)))
(mode-name (when mode-char
(mode-name-from-char connection
channel mode-char))))
(when mode-name
(if (has-mode-p channel mode-name)
(set-mode channel mode-name user)
(set-mode-value (add-mode channel mode-name
(make-mode connection
channel mode-name))
user))))))))))
I've applied to join the dev mailing list and will be submitting a patch shortly.

CLISP open-http example

I am trying to read a series of web pages with CLISP, if they exist, but I don't understand how open-http works to skip non existing web pages.
I have the following:
(dolist (word '(a b c))
(with-open-stream (stream (ext:open-http
(format nil
"https://en.wikipedia.org/wiki/~a.html"
word)
:if-does-not-exist nil))
(when stream
(print word))))
I want to simply skip a web-page if it doesn't exist, but CLISP seems to hang and returns an "Invalid argument" error.
Could anyone explain how the argument :if-does-not-exist works and/or provide examples of how to use open-http. Thanks!
It does work for me:
(with-open-stream (stream (ext:open-http
"http://stackoverflow.com/questions/234242424242"
:if-does-not-exist nil))
(format t "~&Stream: ~A~%" stream))
Output:
;; connecting to "http://stackoverflow.com/questions/234242424242"...connected...HTTP/1.1 404 Not Found
;; HTML source of Page not found
Stream: NIL
NIL
There is a delay to get the connection, but it works.
If the page does exist:
[7]> (with-open-stream (stream (ext:open-http
"http://stackoverflow.com/questions/36003343/clisp-open-http-example"
:if-does-not-exist nil))
(format t "~&Stream: ~A~%" stream))
;; connecting to "http://stackoverflow.com/questions/36003343/clisp-open-http-example"...connected...HTTP/1.1 200 OK
Stream: #<IO INPUT-BUFFERED SOCKET-STREAM CHARACTER stackoverflow.com:80>
NIL
With Wikipedia I couldn't make it work since Wikipedia.org re-directs it to HTTPS and EXT:OPEN-HTTP neither can handle HTTPS directly, nor it can handle redirects:
Here if HTTPS is used directly:
[10]> (with-open-stream (stream (ext:open-http
"https://en.wikipedia.org/wiki/Common_Lisp"
:if-does-not-exist nil))
(format t "~&Stream: ~A~%" stream))
*** - OPEN-HTTP: "https://en.wikipedia.org/wiki/Common_Lisp" is not an HTTP URL
The following restarts are available:
ABORT :R1 Abort main loop
Break 1 [11]> :r1
If "https" is replaced by "http", CLISP doesn't construct a proper address:
[12]> (with-open-stream (stream (ext:open-http
"http://en.wikipedia.org/wiki/Common_Lisp"
:if-does-not-exist nil))
(format t "~&Stream: ~A~%" stream))
;; connecting to "http://en.wikipedia.org/wiki/Common_Lisp"...connected...HTTP/1.1 301 TLS Redirect --> "https://en.wikipedia.org/wiki/Common_Lisp"
;; connecting to "http://en.wikipedia.orghttps://en.wikipedia.org/wiki/Common_Lisp"...
*** - PARSE-INTEGER: substring "" does not have integer syntax at position 0
The following restarts are available:
ABORT :R1 Abort main loop
Break 1 [13]>

How to reduce code duplication using method combination but keeping possible early return

I got a set of classes which represent a message that has to be handled. But there is only a limited amount of open spots for handlers. Therefore any "dispatch" of a handler handling an message object has to check first whether there is a free spot.
If there is -> dispatch.
If there is not -> do not dispatch and return corresponding message
As this part of the code will be the same in any dispatch method I figured it would be best to use the method combination facility to enforce that, but I cannot figure out how.
In my current code base I tried to use a :before method, but apparently you cannot use return in such context:
(defclass message () ((msg :initarg :msg :reader msg)))
(defclass message-ext (message)
((univ-time :initarg :univ-time :reader univ-time)))
(defparameter *open-handler* nil)
(defgeneric handle (message)
(:documentation "handle the given message appropriately"))
(defmethod handle :before ((message message))
(when (> (length *open-handler*) 1)
(return :full)))
(defmethod handle ((message message))
(push (FORMAT nil "dispatched handler") *open-handler*))
(defmethod handle ((message-ext message-ext))
(push (FORMAT nil "dispatched ext handler") *open-handler*))
(handle (make-instance 'message :msg "allemeineentchen"))
(handle (make-instance 'message-ext
:msg "rowrowrowyourboat"
:univ-time (get-universal-time)))
(handle (make-instance 'message-ext
:msg "gentlydownthestreet"
:univ-time (get-universal-time)))
Execution of a form compiled with errors.
Form:
(RETURN-FROM NIL FULL)
Compile-time error:
return for unknown block: NIL
[Condition of type SB-INT:COMPILED-PROGRAM-ERROR]
Restarts:
0: [RETRY] Retry SLIME interactive evaluation request.
1: [*ABORT] Return to SLIME's top level.
2: [TERMINATE-THREAD] Terminate this thread (#<THREAD "worker" RUNNING {100594F743}>)
Backtrace:
0: ((SB-PCL::FAST-METHOD HANDLE :BEFORE (MESSAGE)) #<unavailable argument> #<unavailable argument> #<unavailable argument>)
1: ((SB-PCL::EMF HANDLE) #<unavailable argument> #<unavailable argument> #<MESSAGE-EXT {1005961733}>)
2: (SB-INT:SIMPLE-EVAL-IN-LEXENV (HANDLE (MAKE-INSTANCE 'MESSAGE-EXT :MSG "gentlydownthestreet" :UNIV-TIME (GET-UNIVERSAL-TIME))) #<NULL-LEXENV>)
3: (EVAL (HANDLE (MAKE-INSTANCE 'MESSAGE-EXT :MSG "gentlydownthestreet" :UNIV-TIME (GET-UNIVERSAL-TIME))))
4: ((LAMBDA () :IN SWANK:INTERACTIVE-EVAL))
Is this approach even sane, and if yes how can I do it in a working fashion? (I did already try return-from with the same result)
I think you should be using the :around method qualifier instead:
(defmethod handle :around ((message message))
(if (cddr *open-handler*)
:full
(call-next-method)))
However, a more "lispy" approach is to use the CL Condition System, e.g., something like this:
(define-condition too-many-messages (...) (...) ...)
(defun add-message (message)
(when (cddr *open-handler*)
(signal 'too-many-messages))
(push message *open-handler*))
(defmethod handle ((message message))
(add-message (FORMAT nil "dispatched handler")))
You will have to handle the condition (using, e.g., handler-bind) in addition to checking the return values of your handle function.
PS. Calling length on a list to check that it is long enough is not a very good idea - although in your case, when the list is guaranteed to be short, this might be more of a style issue.
PPS. It is not a very good idea to use the word handle as a name of your function because CL has functions which contain it (e.g., handler-case). This will complicate the search in your code in addition to confusing people reading your code.
You can't call RETURN to return from a function like that.
You would need to use RETURN-FROM with the function name. But here it would return from the method - not the generic function.
#sds has an answer. Another one would be to signal a user defined condition and handle it somewhere else. Older code used catch and throw.
A more complex undertaking would be a user defined method combination.

endless recursion SB-KERNEL:OUTPUT-OBJECT

I am using the package drakma, which properly executes:
(drakma:http-request "http://www.google.de")
When used alone. But as soon as I start using a package I wrote by myself it results in an control-stack overflow.
The backtrace looks like this:
...htmlstuff.....
200
((:DATE . "Sat, 08 Dec 2012 01:00:23 GMT") (:EXPIRES . "-1")
(:CACHE-CONTROL . "private, max-age=0")
(:CONTENT-TYPE . "text/html; charset=ISO-8859-1")
(:SET-COOKIE
. "PREF=ID=5c4b30f4308d3e16:FF=0:TM=1354928423:LM=1354928423:S=1Z5pCWaGYqp7vYxW; expires=Mon, 08-Dec-2014 01:00:23 GMT; path=/; domain=.google.de,NID=66=QXQcXBWPNkcLtxxp5Hmlb7enfDS_wlNOA5bfxT-GsokTpAH4fulI8zxOIl_3IQQzeIcIodmcWDc0JC80k7-d-kOPznrhCJYACNu-zpp7wpPXypilOyjK2mebDUnUl3Xj; expires=Sun, 09-Jun-2013 01:00:23 GMT; path=/; domain=.google.de; HttpOnly")
(:P3P
. "CP=\"This is not a P3P policy! See http://www.google.com/support/accounts/bin/answer.py?hl=en&answer=151657 for more info.\"")
(:SERVER . "gws") (:X-XSS-PROTECTION . "1; mode=block")
(:X-FRAME-OPTIONS . "SAMEORIGIN") (:CONNECTION . "close"))
#<PURI:URI http://www.google.de/>
INFO: Control stack guard page unprotected
Control stack guard page temporarily disabled: proceed with caution
debugger invoked on a SB-KERNEL::CONTROL-STACK-EXHAUSTED in thread
#<THREAD "main thread" RUNNING {1002978CA3}>:
Control stack exhausted (no more space for function call frames).
This is probably due to heavily nested or infinitely recursive function
calls, or a tail call that SBCL cannot or has not optimized away.
PROCEED WITH CAUTION.
Type HELP for debugger help, or (SB-EXT:QUIT) to exit from SBCL.
restarts (invokable by number or by possibly-abbreviated name):
0: [ABORT] Exit debugger, returning to top level.
(SB-KERNEL::CONTROL-STACK-EXHAUSTED-ERROR)
0]
....way more of those....
15854: ((SB-PCL::FAST-METHOD PRINT-OBJECT (T T))
#<unavailable argument>
#<unavailable argument>
#1# #1=
#<unavailable argument>)
15855: ((LABELS SB-IMPL::HANDLE-IT :IN SB-KERNEL:OUTPUT-OBJECT)
#<SYNONYM-STREAM :SYMBOL SB-SYS:*TTY* {10001B3103}>)
15856: ((SB-PCL::FAST-METHOD PRINT-OBJECT (T T))
#<unavailable argument>
#<unavailable argument>
#1# #1=
#<unavailable argument>)
15857: ((LABELS SB-IMPL::HANDLE-IT :IN SB-KERNEL:OUTPUT-OBJECT)
#<SYNONYM-STREAM :SYMBOL SB-SYS:*TTY* {10001B3103}>)
15858: ((SB-PCL::FAST-METHOD PRINT-OBJECT (T T))
#<unavailable argument>
#<unavailable argument>
#1# #1=
#<unavailable argument>)
15859: ((LABELS SB-IMPL::HANDLE-IT :IN SB-KERNEL:OUTPUT-OBJECT)
#<SYNONYM-STREAM :SYMBOL SB-SYS:*STDOUT* {10001DCB03}>)
15860: #1#(PRIN1 #1= NIL)
15861: (SB-IMPL::REPL-FUN NIL)
15862: ((LAMBDA () :IN SB-IMPL::TOPLEVEL-REPL))
15863: (SB-IMPL::%WITH-REBOUND-IO-SYNTAX
#<CLOSURE (LAMBDA # :IN SB-IMPL::TOPLEVEL-REPL) {10076F355B}>)
15864: (SB-IMPL::TOPLEVEL-REPL NIL)
15865: (SB-IMPL::TOPLEVEL-INIT)
15866: ((FLET #:WITHOUT-INTERRUPTS-BODY-236911 :IN SAVE-LISP-AND-DIE))
15867: ((LABELS SB-IMPL::RESTART-LISP :IN SAVE-LISP-AND-DIE))
with around 15k print-objects calling each other.
I found the error to be in those three lines:
(define-condition recepie-action-errornous (simple-error) ())
(defmethod print-object (err recepie-action-errornous)
(rstyl:LOG-ERROR err))
wheras (rstyl:LOG-ERROR err) is a macro expanding to:
(WRITE ERR :ESCAPE NIL :STREAM A-PACKAGE:*LOG-STREAM-ERROR*)
The value of *LOG-STREAM-ERROR* is: #<SYNONYM-STREAM :SYMBOL SB-SYS:*TTY* {10001B3103}>
How can this lines have such an massive effect?
There are few things, actually.
simple-error is a kind of condition that defines special slots for printing it, the :format-control and :format-arguments. Unfortunately, they are of a very little utility, as you can't override them neither in the definition of a child condition, nor in any post-initialization hook, because there's none. In general, I find simple-error of a very limited usefulness because it can't just capture the message it needs to print, instead you have to provide the message every time you create an instance of this condition.
So, if you wanted to extend simple-error, you could do something like the following:
(define-condition recepie-action-errornous (simple-error) ()
(:report
(lambda (condition stream)
(declare (ignore condition))
(format stream "Erroneous recepie action happened"))))
Then, your logging could look like so:
(write (make-condition 'recepie-action-errornous) :escape nil)
And it would print the "Erroneous recepie action happened" message. Not so bad, but you aren't using the only feature that distinguish this condition from its ancestor, the condition condition, i.e. its ability to print formatted output.
In other words, I don't really see a point in your situation in extending simple-error I see it's primarily function in facilitating reporting based on the arguments you give it when you are constructing it, but if you don't give any, then it's a bit of a waste.

Resources