String Replace in Gimp Script Fu - replace

I have a Gimp plugin that renames files and I needed a replace function. Unfortunately TinyScheme that Gimp uses does not have a replace function for strings. I searched a lot but couldn't find one that worked as a true string-replace. Answer to follow...

Here is the implementation that I created. Please feel free to let me know if there is a better solution.
(define (string-replace strIn strReplace strReplaceWith)
(let*
(
(curIndex 0)
(replaceLen (string-length strReplace))
(replaceWithLen (string-length strReplaceWith))
(inLen (string-length strIn))
(result strIn)
)
;loop through the main string searching for the substring
(while (<= (+ curIndex replaceLen) inLen)
;check to see if the substring is a match
(if (substring-equal? strReplace result curIndex (+ curIndex replaceLen))
(begin
;create the result string
(set! result (string-append (substring result 0 curIndex) strReplaceWith (substring result (+ curIndex replaceLen) inLen)))
;now set the current index to the end of the replacement. it will get incremented below so take 1 away so we don't miss anything
(set! curIndex (-(+ curIndex replaceWithLen) 1))
;set new length for inLen so we can accurately grab what we need
(set! inLen (string-length result))
)
)
(set! curIndex (+ curIndex 1))
)
(string-append result "")
)
)

Related

Clojure substring with negative index

I want to implement a clojure function that will return the substring in a given string,
Given a string
(def mystring "clojurestring")
(subs mystring 0 3) ;=> "clo"
I want to be able to use a negative index to get sub-string starting from the last character i.e
(subs mystring -13 3) ;=> "clo"
I also think
(subs mystring -9 7) ;=> "ure"
The format for getting the substring in clojure is
(subs 'string' start end)
start. Starting offset, negative offset counts from end of string.Parameter is a positive or negative number.
end. Parameter is an integer greater than zero
so far I have tried without any luck, I don't know if this is possible
Any suggestions? Thanks!
If start is negative, you simply want to subtract from the length of the string. If I understand your requirement correctly:
(defn subs* [s start end]
(if (neg? start)
(subs s (+ (.length s) start) end)
(subs s start end)))
(defn my-subs [st from end]
(let [f (if (neg? from)
(+ (count st) from)
from)]
(subs st f end)))
(my-subs "clojurestring" -9 7)
;"ure"

How to use regexp in Elisp to match ',' in the line but not inside quotation mark

How could I write a regexp to match , in the line but not inside ""?
For example:
`uvm_info("body", $sformatf("Value: a = %d, b = %d, c = %d", a, b, c), UVM_MEDIUM)
Hope to match those with ^ under it:
`uvm_info("body", $sformatf("Value: a = %d, b = %d, c = %d", a, b, c), UVM_MEDIUM)
^ ^ ^ ^ ^
The following function doesn't use a regular expression but rather parses a region of the buffer as sexps and returns a list of buffer positions of all commas excluding those within strings, or nil if there are no such commas.
(defun find-commas (start end)
(save-excursion
(goto-char start)
(let (matches)
(while (< (point) end)
(cond ((= (char-after) ?,)
(push (point) matches)
(forward-char))
((looking-at "[]\\[{}()]")
(forward-char))
(t
(forward-sexp))))
(nreverse matches))))
It works for the example you show, but might need tweaking for other examples or languages. If your example is in a buffer by itself, calling
(find-commas (point-min) (point-max))
returns
(17 60 63 66 70)
try this
"[^"]+"|(,)
the , in capture group 1
You can use the fact that font-lock first fontifies comments and strings, then applies your font-lock keywords.
The standard solution is to replace your regexp with a function that search for the regexp, and skips any occurrences in comments and strings.
The following is from my package lisp-extra-font-lock (a package that highlights variables bound by let, quoted expressions etc.) It search for quotes and backquotes, but the principle is the same:
(defun lisp-extra-font-lock-is-in-comment-or-string ()
"Return non-nil if point is in comment or string.
This assumes that Font Lock is active and has fontified comments
and strings."
(let ((props (text-properties-at (point)))
(faces '()))
(while props
(let ((pr (pop props))
(value (pop props)))
(if (eq pr 'face)
(setq faces value))))
(unless (listp faces)
(setq faces (list faces)))
(or (memq 'font-lock-comment-face faces)
(memq 'font-lock-string-face faces)
(memq 'font-lock-doc-face faces))))
(defun lisp-extra-font-lock-match-quote-and-backquote (limit)
"Search for quote and backquote in in code.
Set match data 1 if character matched is backquote."
(let (res)
(while (progn (setq res (re-search-forward "\\(?:\\(`\\)\\|'\\)" limit t))
(and res
(or
(lisp-extra-font-lock-is-in-comment-or-string)
;; Don't match ?' and ?`.
(eq (char-before (match-beginning 0)) ??)))))
res))
The font-lock keyword is as follows:
(;; Quote and backquote.
;;
;; Matcher: Set match-data 1 if backquote.
lisp-extra-font-lock-match-quote-and-backquote
(1 lisp-extra-font-lock-backquote-face nil t)
;; ...)

How to run multiple expressions when an if condition is true?

I'm new to Scheme and am trying to have an if-statement perform more than one action if its condition is true. I tried something like:
(if (char=? (string-ref str loc) #\a)
((+ count 1) (+ reference 1))
~else action, etc.~
And it complains about my action, saying
application: not a procedure
If I remove the parentheses, so that the action for a true condition is:
(+ count 1) (+ reference 1)
It complains
if: bad syntax
and fails to run at all. What am I missing?
There are two problems with the code. The first, an if form can't have more than one expression in the consequent and in the alternative. Two possible solutions for that - you can either use begin (not just a couple of parenthesis, that's for procedure application) to surround multiple expression:
(if (char=? (string-ref str loc) #\a)
; needs an explicit `begin` for more than one expression
(begin
(+ count 1)
(+ reference 1))
; needs an explicit `begin` for more than one expression
(begin
~else action, etc~))
... or use a cond, which is a better alternative because it already includes an implicit begin:
(cond ((char=? (string-ref str loc) #\a)
; already includes an implicit `begin`
(+ count 1)
(+ reference 1))
(else
; already includes an implicit `begin`
~else action, etc~))
The second problem is more subtle and serious, both of the expressions in the consequent part are probably not doing what you expect. This one: (+ count 1) is doing nothing at all, the incremented value is lost because you didn't use it after incrementing it. Same thing with the other one: (+ reference 1), but at least here the value is being returned as the result of the conditional expression. You should either pass both incremented values along to a procedure (probably as part of a recursion):
(cond ((char=? (string-ref str loc) #\a)
; let's say the procedure is called `loop`
(loop (+ count 1) (+ reference 1)))
(else
~else action, etc~))
Or directly update in the variables the result of the increments, although this is not the idiomatic way to write a solution in Scheme (it looks like a solution in C, Java, etc.):
(cond ((char=? (string-ref str loc) #\a)
; here we are mutating in-place the value of the variables
(set! count (+ count 1))
(set! reference (+ reference 1)))
(else
~else action, etc~))
If you're trying to perform multiple actions with side-effects in one arm of the if, then you'll need to put them into a begin, like this:
(if (char=? (string-ref str loc) #\a)
(begin (set! count (+ count 1))
(set! reference (+ reference 1)))
~else action, etc.~
If, instead of causing changes in variables, you want to return two values at once, then you'll either need to combine the expressions into a single object, like this:
(if (char=? (string-ref str loc) #\a)
(cons (+ count 1) (+ reference 1)))
~else expression~
in which case to pull out the count and reference, you'll need to apply car and cdr to the result of the if—or you could actually return multiple values, like this:
(if (char=? (string-ref str loc) #\a)
(values (+ count 1) (+ reference 1)))
~else expression~
in which case to pull out the count and reference, you'll need to bind the multiple values to variables somehow in the code that calls the if. One way to do that is with let-values, possibly something like this:
(define count&ref
(λ (str ch)
(let loop ([loc 0] [count 0] [reference 0])
; whatever other stuff you're doing
(if (char=? (string-ref str loc) ch)
(values (+ count 1) (+ reference 1)))
~else expression~ )))
(let-values ([(new-count new-ref) (count&ref "some stuff" #\u)])
;in here, new-count and new-ref are bound to whatever count&ref returned
)
On the other hand, if count and reference are variables that you're just keeping track of within a loop, the simplest way might be to call the next iteration of the loop inside the if, like this:
(let loop ([loc 0] [count 0] [reference 0])
; whatever other stuff you're doing
(if (char=? (string-ref str loc) #\a)
(loop (+ loc 1) (+ count 1) (+ reference 1))
~else expression~ ))
You can use begin to group a set of expressions to be executed one by one.
(if (char=? (string-ref str loc) #\a)
(begin (+ count 1) (+ reference 1))
~else action, etc.~
begin only returns the value of the last expression, that is (+ reference 1), so the value of (+ count 1) is not used.

How can I capture the results of splitting a string in elisp?

I am working in elisp and I have a string that represents a list of items. The string looks like
"apple orange 'tasty things' 'my lunch' zucchini 'my dinner'"
and I'm trying to split it into
("apple" "orange" "tasty things" "my lunch" "zucchini" "my dinner")
This is a familiar problem. My obstacles to solving it are less about the regex, and more about the specifics of elisp.
What I want to do is run a loop like :
(while (< (length my-string) 0) do-work)
where that do-work is:
applying the regex \('[^']*?'\|[[:alnum:]]+)\([[:space:]]*\(.+\) to my-string
appending \1 to my results list
re-binding my-string to \2
However, I can't figure out how to get split-string or replace-regexp-in-string to do that.
How can I split this string into values I can use?
(alternatively: "which built-in emacs function that does this have I not yet found?")
Something similar, but w/o regexp:
(defun parse-quotes (string)
(let ((i 0) result current quotep escapedp word)
(while (< i (length string))
(setq current (aref string i))
(cond
((and (char-equal current ?\ )
(not quotep))
(when word (push word result))
(setq word nil escapedp nil))
((and (char-equal current ?\')
(not escapedp)
(not quotep))
(setq quotep t escapedp nil))
((and (char-equal current ?\')
(not escapedp))
(push word result)
(setq quotep nil word nil escapedp nil))
((char-equal current ?\\)
(when escapedp (push current word))
(setq escapedp (not escapedp)))
(t (setq escapedp nil)
(push current word)))
(incf i))
(when quotep
(error (format "Unbalanced quotes at %d"
(- (length string) (length word)))))
(when word (push result word))
(mapcar (lambda (x) (coerce (reverse x) 'string))
(reverse result))))
(parse-quotes "apple orange 'tasty things' 'my lunch' zucchini 'my dinner'")
("apple" "orange" "tasty things" "my lunch" "zucchini" "my dinner")
(parse-quotes "apple orange 'tasty thing\\'s' 'my lunch' zucchini 'my dinner'")
("apple" "orange" "tasty thing's" "my lunch" "zucchini" "my dinner")
(parse-quotes "apple orange 'tasty things' 'my lunch zucchini 'my dinner'")
;; Debugger entered--Lisp error: (error "Unbalanced quotes at 52")
Bonus: it also allows escaping the quotes with "\" and will report it if the quotes aren't balanced (reached the end of the string, but didn't find the match for the opened quote).
Here is a straightforward way to implement your algorithm using a temporary buffer. I don't know if there would be a way to do this using replace-regexp-in-string or split-string.
(defun my-split (string)
(with-temp-buffer
(insert string " ") ;; insert the string in a temporary buffer
(goto-char (point-min)) ;; go back to the beginning of the buffer
(let ((result nil))
;; search for the regexp (and just return nil if nothing is found)
(while (re-search-forward "\\('[^']*?'\\|[[:alnum:]]+\\)\\([[:space:]]*\\(.+\\)\\)" nil t)
;; (match-string 1) is "\1"
;; append it after the current list
(setq result (append result (list (match-string 1))))
;; go back to the beginning of the second part
(goto-char (match-beginning 2)))
result)))
Example:
(my-split "apple orange 'tasty things' 'my lunch' zucchini 'my dinner'")
==> ("apple" "orange" "'tasty things'" "'my lunch'" "zucchini" "'my dinner'")
You might like to take a look at split-string-and-unquote.
If you manipulate strings often, you should install s.el library via package manager, it introduces a huge load of string utility functions under a constistent API. For this task you need function s-match, its optional 3rd argument accepts starting position. Then, you need a correct regexp, try:
(concat "\\b[a-z]+\\b" "\\|" "'[a-z ]+'")
\| means matching either sequence of letters that constitute a word (\b means a word boundary), or sequence of letters and space inside quotes. Then use loop:
;; let s = given string, r = regex
(loop for start = 0 then (+ start (length match))
for match = (car (s-match r s start))
while match
collect match)
For an educational purpose, i also implemented the same functionality with a recursive function:
;; labels is Common Lisp's local function definition macro
(labels
((i
(start result)
;; s-match searches from start
(let ((match (car (s-match r s start))))
(if match
;; recursive call
(i (+ start (length match))
(cons match result))
;; push/nreverse idiom
(nreverse result)))))
;; recursive helper function
(i 0 '()))
As Emacs lacks tail call optimization, executing it over a big list can cause stack overflow. Therefore you can rewrite it with do macro:
(do* ((start 0)
(match (car (s-match r s start)) (car (s-match r s start)))
(result '()))
((not match) (reverse result))
(push match result)
(incf start (length match)))

How can I add arity to this function?

I want this function
(defn ret-lowest-str-len
"Computes the lengths of two strings. Returns default length -- len --
if len is <= the length of str-1 and len is <= length of str-2.
Else, returns smaller of length of str-1 and str-2."
[str-1 str-2 len]
(let [l1 (count str-1)
l2 (count str-2)]
(if (and (<= len l1) (<= len l2))
len
(if (< l1 l2)
l1
l2))))
to be able to have two argument signatures. The example shows str-1 str-2 and len (a fixed length). This was done, so that if a string was less than the fixed default of say 15, a length value would be returned that would not cause an overrun exception.
I'd like to be able to pass just str-1 and len without str-2, and I'm not quite sure how to do it.
I'm aware the code will have to change if l2 is not passed in. I'm wondering how to set up the arity. Any examples would be appreciated.
Thanks.
defn has two syntaxes
(defn foo [] (expression) (expression))
and
(defn foo
([] (exp1) (exp2)) ; arity 0
([x] (exp1) (exp2)) ;arity 1
([x y] (exp1) (exp2)) ; arity 2
a common pattern is to have the lesser arity version fill in defaults and call into the higher arity case:
(defn ret-lowest-str-len
([str-1 len] (ret-lowest-str-len str-1 "" len))
([str-1 str-2 len]
(let [l1 (count str-1)
l2 (count str-2)]
(if (and (<= len l1) (<= len l2))
len
(if (< l1 l2)
l1
l2)))))
on a side note you could write a verry similar function using a variable number of arguments:
user> (defn ret-lowest-str-len [& args] (reduce min (map count args)))
#'user/ret-lowest-str-len
user> (ret-lowest-str-len "a" "aaaa" "aaaaaaaaa")
1
I would go further and make it work for any number of strings, using the & syntax for optional list of arguments, using something along the lines of:
(defn lowest-str-len [default-len & strs]
(let [lens (map count strs)]
(apply min default-len lens)))