Is there a way to toggle a string between single and double quotes in emacs? - emacs

I'm looking for an emacs command that will toggle the surrounding quote characters on the string under the point, e.g. with the cursor in the string 'bar', hit a key and change it between:
foo = 'bar' <---> foo = "bar"
For bonus points it would:
handle toggling Python triple-quote strings (''' <---> """)
automatically change backslash escaping inside the string as appropriate.
e.g.
foo = 'bar "quote"' <---> foo = "bar \"quote\""

This could be a bit more robust:
(defun toggle-quotes ()
(interactive)
(save-excursion
(let ((start (nth 8 (syntax-ppss)))
(quote-length 0) sub kind replacement)
(goto-char start)
(setq sub (buffer-substring start (progn (forward-sexp) (point)))
kind (aref sub 0))
(while (char-equal kind (aref sub 0))
(setq sub (substring sub 1)
quote-length (1+ quote-length)))
(setq sub (substring sub 0 (- (length sub) quote-length)))
(goto-char start)
(delete-region start (+ start (* 2 quote-length) (length sub)))
(setq kind (if (char-equal kind ?\") ?\' ?\"))
(loop for i from 0
for c across sub
for slash = (char-equal c ?\\)
then (if (and (not slash) (char-equal c ?\\)) t nil) do
(unless slash
(when (member c '(?\" ?\'))
(aset sub i
(if (char-equal kind ?\") ?\' ?\")))))
(setq replacement (make-string quote-length kind))
(insert replacement sub replacement))))
It will use syntax information from the buffer to find the quotes at the beginning of the string (that is given that the strings are quoted), and will also try to flip quotes inside the string, unless they are escaped with backslash - which looks like it could be a common case.
PS. I've just realized you also wanted it to find triple quotes, so her goes.

Here's a quick hack to get you started:
(defun toggle-quotes ()
"Toggle single quoted string to double or vice versa, and
flip the internal quotes as well. Best to run on the first
character of the string."
(interactive)
(save-excursion
(re-search-backward "[\"']")
(let* ((start (point))
(old-c (char-after start))
new-c)
(setq new-c
(case old-c
(?\" "'")
(?\' "\"")))
(setq old-c (char-to-string old-c))
(delete-char 1)
(insert new-c)
(re-search-forward old-c)
(backward-char 1)
(let ((end (point)))
(delete-char 1)
(insert new-c)
(replace-string new-c old-c nil (1+ start) end)))))
The function swaps the internal quotes to the opposite, which is close to bonus 2.

Here's something even more robust, in that it doesn't delete the whole text between the quotes (doing so prevents save-excursion from keeping the point where it was, which is a pain). Also handles (un)backslash-ing nested quotes.
(defun toggle-quotes ()
(interactive)
(let* ((beg (nth 8 (syntax-ppss)))
(orig-quote (char-after beg))
(new-quote (case orig-quote
(?\' ?\")
(?\" ?\'))))
(save-restriction
(widen)
(save-excursion
(catch 'done
(unless new-quote
(message "Not inside a string")
(throw 'done nil))
(goto-char beg)
(delete-char 1)
(insert-char new-quote)
(while t
(cond ((eobp)
(throw 'done nil))
((= (char-after) orig-quote)
(delete-char 1)
(insert-char new-quote)
(throw 'done nil))
((= (char-after) ?\\)
(forward-char 1)
(when (= (char-after) orig-quote)
(delete-char -1))
(forward-char 1))
((= (char-after) new-quote)
(insert-char ?\\)
(forward-char 1))
(t (forward-char 1)))))))))

Here's a function I made for JavaScript, might help?
function swap_str(e, r, t) {
return e = e.split(r).join("WHAK_a_SWAP"), e = e.split(t).join("WHAK_b_SWAP"), e = e.split("WHAK_a_SWAP").join(t),
e = e.split("WHAK_b_SWAP").join(r);
}
//test 1
var str = 'this is "test" of a \'test\' of swapping strings';
var manipulated = swap_str(str,"'",'"');
document.writeln(manipulated)
//test 2
manipulated = swap_str(manipulated,"'",'"');
document.writeln('<hr>'+manipulated)

Related

Destructuring bind for regex matches

In elisp, how can I get a destructuring bind for regex matches?
For example,
;; what is the equivalent of this with destructuring?
(with-temp-buffer
(save-excursion (insert "a b"))
(re-search-forward "\\(a\\) \\(b\\)")
(cons (match-string 1)
(match-string 2)))
;; trying to do something like the following
(with-temp-buffer
(save-excursion (insert "a b"))
(cl-destructuring-bind (a b) (re-search-forward "\\(a\\) \\(b\\)")
(cons a b)))
I was thinking I would have to write a macro to expand matches if there isn't another way.
Here is one way: you first extend pcase to accept a new re-match pattern, with a definition such as:
(pcase-defmacro re-match (re)
"Matches a string if that string matches RE.
RE should be a regular expression (a string).
It can use the special syntax \\(?VAR: to bind a sub-match
to variable VAR. All other subgroups will be treated as shy.
Multiple uses of this macro in a single `pcase' are not optimized
together, so don't expect lex-like performance. But in order for
such optimization to be possible in some distant future, back-references
are not supported."
(let ((start 0)
(last 0)
(new-re '())
(vars '())
(gn 0))
(while (string-match "\\\\(\\(?:\\?\\([-[:alnum:]]*\\):\\)?" re start)
(setq start (match-end 0))
(let ((beg (match-beginning 0))
(name (match-string 1 re)))
;; Skip false positives, either backslash-escaped or within [...].
(when (subregexp-context-p re start last)
(cond
((null name)
(push (concat (substring re last beg) "\\(?:") new-re))
((string-match "\\`[0-9]" name)
(error "Variable can't start with a digit: %S" name))
(t
(let* ((var (intern name))
(id (cdr (assq var vars))))
(unless id
(setq gn (1+ gn))
(setq id gn)
(push (cons var gn) vars))
(push (concat (substring re last beg) (format "\\(?%d:" id))
new-re))))
(setq last start))))
(push (substring re last) new-re)
(setq new-re (mapconcat #'identity (nreverse new-re) ""))
`(and (pred stringp)
(app (lambda (s)
(save-match-data
(when (string-match ,new-re s)
(vector ,#(mapcar (lambda (x) `(match-string ,(cdr x) s))
vars)))))
(,'\` [,#(mapcar (lambda (x) (list '\, (car x))) vars)])))))
and once that is done, you can use it as follows:
(pcase X
((re-match "\\(?var:[[:alpha:]]*\\)=\\(?val:.*\\)")
(cons var val)))
or
(pcase-let
(((re-match "\\(?var:[[:alpha:]]*\\)=\\(?val:.*\\)") X))
(cons var val))
This has not been heavily tested, and as mentioned in the docstring it doesn't work as efficiently as it (c|sh)ould when matching a string against various regexps at the same time. Also you only get the matched substrings, not their position. And finally, it applies the regexp search to a string, whereas in manny/most cases regexps searches are used in a buffer. But you may still find it useful.

Removing enclosed text in Emacs

I would like to delete enclosed text between special characters like: ["{'<( etc .. this way I can remove text like "this is a very ... long text" with a simple keyboard shortcut. I was looking for some already existing mode that performs something similar but I didn't found any so I created some lisp code which performs good in most of situations, however it's not working correctly in all cases. For example if I have the following text entry and I put the cursor in the position"^" then I would liek to remove all the text enclosed by " but it doesn't work:
"aaaaa ] > [more text] aaaaa"
------------ ^
My lisp code is the following:
;; returns the enclosing character for the character "c"
(defun get-enc-char (c) (cond
((string= c "(") ")")
((string= c "[") "]")
((string= c "{") "}")
((string= c ">") "<")
((string= c "<") ">")
((string= c "'") "'")
((string= c "\"") "\"")
(t nil)
)
)
(defun delete-enclosed-text ()
"Delete texts between any pair of delimiters."
(interactive)
(save-excursion
(let (p1 p2 mychar)
; look for one of those characters and store the cursor position
(skip-chars-backward "^([\'\"><{") (setq p1 (point))
; store the char at this point, look for its enclosed char and advance
; the cursor newly (this done to avoid the cases when the char and
; its enclosed-char are the same like " or ' chars.
(backward-char 1) (setq mychar (thing-at-point 'char)) (forward-char 1)
; look forward for the enclosed char
(skip-chars-forward (concatenate 'string "^" (get-enc-char mychar))) (setq p2 (point))
; only delete the region if we found the enclosed character
(if (looking-at "[\]\}\"\'\)<>]") (kill-region p1 p2)))))
Following is an example:
Here a solution based on your code
;; returns the enclosing character for the character "c"
(defun get-enc-char (c) (cond
((string= c "(") ")")
((string= c "[") "]")
((string= c "{") "}")
((string= c ">") "<")
((string= c "<") ">")
((string= c "'") "'")
((string= c "\"") "\"")
(t nil)
))
(defvar empty-enclose 0)
(defun delete-enclosed-text ()
"Delete texts between any pair of delimiters."
(interactive)
(setq empty-enclose 0)
(save-excursion
(let (p1 p2 orig)
(setq orig (point))
(setq p1 (point))
(setq p2 (point))
(setq find 0)
(setq mychar (thing-at-point 'char))
(if (-contains? '("(" "[" "{" "<" "'" "\"") mychar)
(progn
(setq left_encloser (thing-at-point 'char))
(backward-char -1)
(if (string-equal (thing-at-point 'char) (get-enc-char left_encloser))
(progn
(backward-char -1)
(setq p2 (point))
(setq find 1)
(setq empty-enclose 1)))))
(while (eq find 0)
(skip-chars-backward "^({[<>\"'")
(setq p1 (point))
(backward-char 1)
(setq left_encloser (thing-at-point 'char))
(goto-char orig)
(while (and (not (eobp)) (eq find 0))
(backward-char -1)
(skip-chars-forward "^)}]<>\"'")
(setq right_encloser (thing-at-point 'char))
(if (string-equal right_encloser (get-enc-char left_encloser))
(progn
(setq p2 (point))
(setq find 1))))
(goto-char p1)
(backward-char 1))
(delete-region p1 p2)))
(if (eq empty-enclose 0)
(backward-char 1)))
I rapid-sketched something, it doesn't match exactly what you're asking for but I think it could fullfill the same requirements in an even more comfortable way, give it a try and let me know! This interactive function is called with no arguments after selecting a region and asks you for an enclosing mark: this can be any char or string that is directly recognized by replace-regex (direct use of *,.,[ etc wouldn't be the case, but you still can use other chars like {},% etc or even HTML-like markups like <idx>).
The function will delete all text within the selected region, from the very first apparition of the mark to the very last (even if there is an odd number of them), marks are also deleted.
(defun remove-enclosed-in-selection (beginning end)
"select a region, call this function and type any valid regex
markup. All characters from its first to its last appearance will
be removed (including the symbol itself. Example: try with § and %:
aaaa§bbbbcc%c§cc§ddddeeee§ffffgggghhhhiiii§jjjj§kkkkllll§mmmm%nnnn"
(interactive "r")
(let ((x (read-string "type enclosing mark: ")))
(narrow-to-region beginning end)
(replace-regexp (concat x ".*" x) "")
(widen)))
Then you can globally bind it to any keyboard shortcut you want as usual:
(global-set-key (kbd "C-. <C-return>") 'remove-enclosed-in-selection)
or locally to any custom hook you may have:
(defun custom-whatever-hook ()
(local-set-key (kbd "C-. <C-return>")) 'remove-enclosed-in-selection)
(add-hook 'whatever-hook 'custom-whatever-hook)
so, summarizing:
select region
M-x remove-enclosed-in-selection or your custom keystroke
press RET, type valid marker, press RET
the enclosed contents should be removed
The narrow-widen approach seems quick&dirty to me, but I couldn't find another way in the short term. So it may still need a couple of fixes, let me know if it works as expected. Plus, I'm not that an emacs hacker... yet! :P
cheers

In elisp, how to evaluate a string of "var=value\n..." into lisp variables of the same name?

An mplayer tool (midentify) outputs "shell-ready" lines intended to be evaluated by a bash/sh/whatever interpreter.
How can I assign these var-names to their corresponding values as elisp var-names in emacs?
The data is in a string (via shell-command-to-string)
Here is the data
ID_AUDIO_ID=0
ID_FILENAME=/home/axiom/abc.wav
ID_DEMUXER=audio
ID_AUDIO_FORMAT=1
ID_AUDIO_BITRATE=512000
ID_AUDIO_RATE=0
ID_AUDIO_NCH=1
ID_LENGTH=3207.00
ID_SEEKABLE=1
ID_CHAPTERS=0
ID_AUDIO_BITRATE=512000
ID_AUDIO_RATE=32000
ID_AUDIO_NCH=1
ID_AUDIO_CODEC=pcm
ID_EXIT=EOF
Here's a routine that takes a string containing midentify output, and returns an association list of the key-value pairs (which is safer than setting Emacs variables willy-nilly). It also has the advantage that it parses numeric values into actual numbers:
(require 'cl) ; for "loop"
(defun midentify-output-to-alist (str)
(setq str (replace-regexp-in-string "\n+" "\n" str))
(setq str (replace-regexp-in-string "\n+\\'" "" str))
(loop for index = 0 then (match-end 0)
while (string-match "^\\(?:\\([A-Z_]+\\)=\\(?:\\([0-9]+\\(?:\\.[0-9]+\\)?\\)\\|\\(.*\\)\\)\\|\\(.*\\)\\)\n?" str index)
if (match-string 4 str)
do (error "Invalid line: %s" (match-string 4 str))
collect (cons (match-string 1 str)
(if (match-string 2 str)
(string-to-number (match-string 2 str))
(match-string 3 str)))))
You'd use this function like so:
(setq alist (midentify-output-to-alist my-output))
(if (assoc "ID_LENGTH" alist)
(setq id-length (cdr (assoc "ID_LENGTH" alist)))
(error "Didn't find an ID_LENGTH!"))
EDIT: Modified function to handle blank lines and trailing newlines correctly.
The regexp is indeed a beast; Emacs regexps are not known for their easiness on the eyes. To break it down a bit:
The outermost pattern is ^(?:valid-line)|(.*). It tries to match a valid line, or else matches the entire line (the .*) in match-group 4. If (match-group 4 str) is not nil, that indicates that an invalid line was encountered, and an error is raised.
valid-line is (word)=(?:(number)|(.*)). If this matches, then the name part of the name-value pair is in match-string 1, and if the rest of the line matches a number, then the number is in match-string 2, otherwise the entire rest of the line is in match-string 3.
There's probably a better way but this should do it:
(require 'cl)
(let ((s "ID_AUDIO_ID=0
ID_FILENAME=/home/axiom/abc.wav
ID_DEMUXER=audio
ID_AUDIO_FORMAT=1
ID_AUDIO_BITRATE=512000
ID_AUDIO_RATE=0
ID_AUDIO_NCH=1
ID_LENGTH=3207.00
ID_SEEKABLE=1
ID_CHAPTERS=0
ID_AUDIO_BITRATE=512000
ID_AUDIO_RATE=32000
ID_AUDIO_NCH=1
ID_AUDIO_CODEC=pcm
ID_EXIT=EOF"))
(loop for p in (split-string s "\n")
do
(let* ((elements (split-string p "="))
(key (elt elements 0))
(value (elt elements 1)))
(set (intern key) value))))
Here's a function you can run on the output buffer:
(defun set-variables-from-shell-assignments ()
(goto-char (point-min))
(while (< (point) (point-max))
(and (looking-at "\\([A-Z_]+\\)=\\(.*\\)$")
(set (intern (match-string 1)) (match-string 2)))
(forward-line 1)))
I don't think regexp is what really need. You need to split your string by \n and =, so you just say exactly the same to interpreter.
I think you can also use intern to get symbol from string(and set variables). I use it for the first time, so comment here if i am wrong. Anyways, if list is what you want, just remove top-level mapcar.
(defun set=(str)
(mapcar (lambda(arg)
(set
(intern (car arg))
(cadr arg)))
(mapcar (lambda(arg)
(split-string arg "=" t))
(split-string
str
"\n" t))))
(set=
"ID_AUDIO_ID=0
ID_FILENAME=/home/axiom/abc.wav
ID_DEMUXER=audio
ID_AUDIO_FORMAT=1
ID_AUDIO_BITRATE=512000
ID_AUDIO_RATE=0
ID_AUDIO_NCH=1
ID_LENGTH=3207.00
ID_SEEKABLE=1
ID_CHAPTERS=0
ID_AUDIO_BITRATE=512000
ID_AUDIO_RATE=32000
ID_AUDIO_NCH=1
ID_AUDIO_CODEC=pcm
ID_EXIT=EOF")

Using ispell/aspell to spell check camelcased words

I need to spell check a large document containing many camelcased words. I want ispell or aspell to check if the individual words are spelled correctly.
So, in case of this word:
ScientificProgrezGoesBoink
I would love to have it suggest this instead:
ScientificProgressGoesBoink
Is there any way to do this? (And I mean, while running it on an Emacs buffer.) Note that I don't necessarily want it to suggest the complete alternative. However, if it understands that Progrez is not recognized, I would love to be able to replace that part at least, or add that word to my private dictionary, rather than including every camel-cased word into the dictionary.
I took #phils suggestions and dug around a little deeper. It turns out that if you get camelCase-mode and reconfigure some of ispell like this:
(defun ispell-get-word (following)
(when following
(camelCase-forward-word 1))
(let* ((start (progn (camelCase-backward-word 1)
(point)))
(end (progn (camelCase-forward-word 1)
(point))))
(list (buffer-substring-no-properties start end)
start end)))
then, in that case, individual camel cased words suchAsThisOne will actually be spell-checked correctly. (Unless you're at the beginning of a document -- I just found out.)
So this clearly isn't the fullblown solution, but at least it's something.
There is "--run-together" option in aspell. Hunspell can't check camelcased word.
If you read the code of aspell, you will find its algorithm actually does not split camelcase word into a list of sub-words. Maybe this algorithm is faster, but it will wrongly report word containing two character sub-word as typo. Don't waste time to tweak other aspell options. I tried and they didn't work.
So we got two problems:
aspell reports SOME camelcased words as typos
hunspell reports ALL camelcased words as typos
Solution to solve BOTH problems is to write our own predicate in Emacs Lisp.
Here is a sample predicate written for javascript:
(defun split-camel-case (word)
"Split camel case WORD into a list of strings.
Ported from 'https://github.com/fatih/camelcase/blob/master/camelcase.go'."
(let* ((case-fold-search nil)
(len (length word))
;; ten sub-words is enough
(runes [nil nil nil nil nil nil nil nil nil nil])
(runes-length 0)
(i 0)
ch
(last-class 0)
(class 0)
rlt)
;; split into fields based on class of character
(while (< i len)
(setq ch (elt word i))
(cond
;; lower case
((and (>= ch ?a) (<= ch ?z))
(setq class 1))
;; upper case
((and (>= ch ?A) (<= ch ?Z))
(setq class 2))
((and (>= ch ?0) (<= ch ?9))
(setq class 3))
(t
(setq class 4)))
(cond
((= class last-class)
(aset runes
(1- runes-length)
(concat (aref runes (1- runes-length)) (char-to-string ch))))
(t
(aset runes runes-length (char-to-string ch))
(setq runes-length (1+ runes-length))))
(setq last-class class)
;; end of while
(setq i (1+ i)))
;; handle upper case -> lower case sequences, e.g.
;; "PDFL", "oader" -> "PDF", "Loader"
(setq i 0)
(while (< i (1- runes-length))
(let* ((ch-first (aref (aref runes i) 0))
(ch-second (aref (aref runes (1+ i)) 0)))
(when (and (and (>= ch-first ?A) (<= ch-first ?Z))
(and (>= ch-second ?a) (<= ch-second ?z)))
(aset runes (1+ i) (concat (substring (aref runes i) -1) (aref runes (1+ i))))
(aset runes i (substring (aref runes i) 0 -1))))
(setq i (1+ i)))
;; construct final result
(setq i 0)
(while (< i runes-length)
(when (> (length (aref runes i)) 0)
(setq rlt (add-to-list 'rlt (aref runes i) t)))
(setq i (1+ i)))
rlt))
(defun flyspell-detect-ispell-args (&optional run-together)
"If RUN-TOGETHER is true, spell check the CamelCase words.
Please note RUN-TOGETHER will make aspell less capable. So it should only be used in prog-mode-hook."
;; force the English dictionary, support Camel Case spelling check (tested with aspell 0.6)
(let* ((args (list "--sug-mode=ultra" "--lang=en_US"))args)
(if run-together
(setq args (append args '("--run-together" "--run-together-limit=16"))))
args))
;; {{ for aspell only, hunspell does not need setup `ispell-extra-args'
(setq ispell-program-name "aspell")
(setq-default ispell-extra-args (flyspell-detect-ispell-args t))
;; }}
;; ;; {{ hunspell setup, please note we use dictionary "en_US" here
;; (setq ispell-program-name "hunspell")
;; (setq ispell-local-dictionary "en_US")
;; (setq ispell-local-dictionary-alist
;; '(("en_US" "[[:alpha:]]" "[^[:alpha:]]" "[']" nil ("-d" "en_US") nil utf-8)))
;; ;; }}
(defvar extra-flyspell-predicate '(lambda (word) t)
"A callback to check WORD. Return t if WORD is typo.")
(defun my-flyspell-predicate (word)
"Use aspell to check WORD. If it's typo return t."
(let* ((cmd (cond
;; aspell: `echo "helle world" | aspell pipe`
((string-match-p "aspell$" ispell-program-name)
(format "echo \"%s\" | %s pipe"
word
ispell-program-name))
;; hunspell: `echo "helle world" | hunspell -a -d en_US`
(t
(format "echo \"%s\" | %s -a -d en_US"
word
ispell-program-name))))
(cmd-output (shell-command-to-string cmd))
rlt)
;; (message "word=%s cmd=%s" word cmd)
;; (message "cmd-output=%s" cmd-output)
(cond
((string-match-p "^&" cmd-output)
;; it's a typo because at least one sub-word is typo
(setq rlt t))
(t
;; not a typo
(setq rlt nil)))
rlt))
(defun js-flyspell-verify ()
(let* ((case-fold-search nil)
(font-matched (memq (get-text-property (- (point) 1) 'face)
'(js2-function-call
js2-function-param
js2-object-property
js2-object-property-access
font-lock-variable-name-face
font-lock-string-face
font-lock-function-name-face
font-lock-builtin-face
rjsx-text
rjsx-tag
rjsx-attr)))
subwords
word
(rlt t))
(cond
((not font-matched)
(setq rlt nil))
;; ignore two character word
((< (length (setq word (thing-at-point 'word))) 2)
(setq rlt nil))
;; handle camel case word
((and (setq subwords (split-camel-case word)) (> (length subwords) 1))
(let* ((s (mapconcat (lambda (w)
(cond
;; sub-word wholse length is less than three
((< (length w) 3)
"")
;; special characters
((not (string-match-p "^[a-zA-Z]*$" w))
"")
(t
w))) subwords " ")))
(setq rlt (my-flyspell-predicate s))))
(t
(setq rlt (funcall extra-flyspell-predicate word))))
rlt))
(put 'js2-mode 'flyspell-mode-predicate 'js-flyspell-verify)
Or just use my new pacakge https://github.com/redguardtoo/wucuo
You should parse the camel cased words and split them, then check the individual spelling for each one and assemble a suggestion taking into account the single suggestion for each misspelled token. Considering that each misspelled token can have multiple suggestions this sounds a bit inefficient to me.

How to define a function which repeats itself when passed an argument

Is there an easy way to define a function which repeats itself when passed an argument?
For example, I've defined the following function
(defun swap-sign ()
(interactive)
(search-forward-regexp "[+-]")
(if (equal (match-string 0) "-")
(replace-match "+")
(replace-match "-"))
)
I'd like C-u swap-sign to call swap-sign four times.
I've tried
(defun swap-sign (&optional num)
(interactive)
(let ((counter 0)
(num (if num (string-to-number num) 0)))
(while (<= counter num)
(search-forward-regexp "[+-]")
(if (equal (match-string 0) "-")
(replace-match "+")
(replace-match "-"))
(setq counter (1+ counter)))))
but C-u swap-sign still only runs swap-sign (or perhaps more precisely, the body of the while-loop) once. I'm guessing it is because if num is not the right way to test if num is an empty string.
Am I on the right track, or is there a better/easier way to extend swap-sign?
(defun swap-sign (arg)
(interactive "p")
(dotimes (i arg)
(search-forward-regexp "[+-]")
(if (equal (match-string 0) "-")
(replace-match "+")
(replace-match "-"))))
See the documentation of the interactive special form for more details:
C-h finteractiveRET.
You need to tell emacs to expect, and pass the parameter in, by adding a "p" as the parameter specification for interactive (M-x apropos interactive to get the documentation). Here I've made the minimal change to your code to get it to work - note, however, that you don't need the let/while to do the iteration, and the arg doesn't need to be optional.
(defun swap-sign (&optional num)
(interactive "p")
(let ((counter 1))
(while (<= counter num)
(search-forward-regexp "[+-]")
(if (equal (match-string 0) "-")
(replace-match "+")
(replace-match "-"))
(setq counter (1+ counter)))))
Note that you don't need to convert the parameter from a string - using "p" tells emacs to do this for you.