How can I read all .text files in Lisp? - lisp

I want to read all text files in from a folder in Lisp? Like "foldername/*.txt" in c when I use shell script.

CL-USER 33 > (directory "/usr/share/examples/DTTk/*.txt")
(#P"/usr/share/examples/DTTk/hotspot_example.txt"
#P"/usr/share/examples/DTTk/pridist_example.txt"
#P"/usr/share/examples/DTTk/opensnoop_example.txt"
#P"/usr/share/examples/DTTk/syscallbysysc_example.txt"
#P"/usr/share/examples/DTTk/rwbytype_example.txt" ...)
CL-USER 34 > (mapcar (lambda (path)
(with-output-to-string (o)
(with-open-file (s path)
(loop for line = (read-line s nil)
while line do (write-line line o)))))
*)
("The following is a demonstration of the hotspot.d script. ... " ...)

Related

LISP macro that process variables and data structure inside at runtime

I have LISP written in JavaScript (https://jcubic.github.io/lips/ with online demo where you can try it) and I have macro like this:
(define-macro (globalize symbol)
(let ((obj (--> (. lips 'env) (get symbol))))
`(begin
,#(map (lambda (key)
(print (concat key " " (function? (. obj key))))
(if (function? (. obj key))
(let* ((fname (gensym))
(args (gensym))
(code `(define (,(string->symbol key) . ,args)
(apply (. ,obj ,key) ,args))))
(print code)
code)))
;; native Object.key function call on input object
(array->list (--> Object (keys obj)))))))
In this code I use this:
(let ((obj (--> (. lips 'env) (get symbol))))
and I call this macro using:
(globalize pfs)
to create function for each static method of pfs (which is LightingFS from isomorphic-git where each function return a promise, it's like fs from node).
But it will not work for something like this:
(let ((x pfs))
(globalize x))
because lips.env is global enviroment.
So my question is this how macro should work? Should they only process input data as symbols so they never have access to object before evaluation of lisp code?
How the LISP macro that generate bunch of functions based on variable should look like. For instance in scheme if I have alist in variable and want to generate function for each key that will return a value:
input:
(define input `((foo . 10) (bar . 20)))
output:
(begin
(define (foo) 10)
(define (bar) 20))
Can I write macro that will give such output if I use (macro input)? Or the only option is (macro ((foo . 10) (bar . 20)))?
I can accept generic Scheme or Common LISP answer but please don't post define-syntax and hygienic macros from scheme, My lisp don't have them and will never have.
The problem seems to be that I want to access value at macro expansion time and it need to have the value that in runtime. And second question Is eval in this case the only option?
This works in biwascheme:
(define-macro (macro obj)
(let ((obj (eval obj)))
`(begin
,#(map (lambda (pair)
(let ((name (car pair))
(value (cdr pair)))
`(define (,name) ,value)))
obj))))
(define input `((foo . 10) (bar . 20)))
(macro input)
(foo)
;; ==> 10
(bar)
;; ==> 20
(in my lisp eval don't work like in biwascheme but that's other issue).
but this don't work, because x is not global:
(let ((x '((g . 10)))) (macro x))
Is macro with eval something you would normally do, or should them be avoided? Is there other way to generate bunch of functions based on runtime object.
In Common Lisp: creating and compiling functions at runtime.
CL-USER 20 > (defparameter *input* '((foo . 10) (bar . 20)))
*INPUT*
CL-USER 21 > (defun make-my-functions (input)
(loop for (symbol . number) in input
do (compile symbol `(lambda () ,number))))
MAKE-MY-FUNCTIONS
CL-USER 22 > (make-my-functions *input*)
NIL
CL-USER 23 > (foo)
10
CL-USER 24 > (bar)
20
From a local variable:
CL-USER 25 > (let ((input '((foo2 . 102) (bar3 . 303))))
(make-my-functions input))
NIL
CL-USER 26 > (bar3)
303
With a macro, more clumsy and limited:
CL-USER 37 > (defparameter *input* '((foo1 . 101) (bar2 . 202)))
*INPUT*
CL-USER 38 > (defmacro def-my-functions (input &optional getter)
`(progn
,#(loop for (symbol . number) in (if getter
(funcall getter input)
input)
collect `(defun ,symbol () ,number))))
DEF-MY-FUNCTIONS
CL-USER 39 > (def-my-functions *input* symbol-value)
BAR2
CL-USER 40 > (foo1)
101

Read input into string in Lisp reader macro

I am trying to make a reader macro that would convert #this into "this".
This is what I currently have:
(defun string-reader (stream char)
(declare (ignore char))
(format nil "\"~a\"" (read-line stream t nil t))
)
(set-macro-character #\# #'string-reader )
The problem is that this requires that I put a newline after ever #this. I've also tried it with (read), but that just returns the variable test, which has not been set. I can't just hard-code the number of characters after the # symbol, because I don't know how many there would be. Is there any way to fix this?
Edit: is the only way to do this to loop over read-char and peek-char, reading until I get to #),#\space, or #\Newline?
You can try to use read and then look at what it returns:
(defun string-reader (stream char)
(declare (ignore char))
(let ((this (let ((*readtable* (copy-readtable)))
(setf (readtable-case *readtable*) :preserve)
(read stream t nil t))))
(etypecase this
(string this)
(symbol (symbol-name this)))))
(set-macro-character #\# #'string-reader)
Above would allow #This and #"This", but not #333.
This version just reads a string until whitespace:
(defun read-as-string-until-whitespace (stream)
(with-output-to-string (out-stream)
(loop for next = (peek-char nil stream t nil t)
until (member next '(#\space #\newline #\tab))
do (write-char (read-char stream t nil t) out-stream))))
(defun string-reader (stream char)
(declare (ignore char))
(read-as-string-until-whitespace stream))
(set-macro-character #\# #'string-reader)
Example:
CL-USER 21 > #this
"this"
CL-USER 22 > #42
"42"
CL-USER 23 > #FooBar
"FooBar"

Find empy lines in text file

I've been learning racket for a few days and I'm puzzled with this task, I'm trying to find empty lines in a text file and select a random empty line to INSERT the text "calculation here", this is as far as I have gotten so far.
for example: myfile.txt has the contents:
line1
line2
line3
line4
after the script is run, myfile.txt should now look like:
line1
calculation here
line2
line3
line4
or:
line1
line2
line3
calculation here
line4
un-working code below:
#lang racket
(define (write-to-file host text) (
with-output-to-file host (
lambda () (
write text))
#:exists 'replace))
(define empty-lines '()) ;store line number of empty line (if any)
(define (file-lines text-file)
(file->lines text-file))
(define (add-to-list line-num)
(set! empty-lines (cons line-num empty-lines)))
(let loop ((l (file-lines "myfile.txt")))
(cond ((null? l) #f)
(else
(printf "~s\n" (first l)) ; just for debugging
(cond ((equal? (first l) "") (add-to-list (first l)))(else #f))
(loop (rest l)))))
;now i need to select a random line from the list of empty-lines.
;and write "calculations here" to that line
there's no problem with the read lines method i am using, the problem is detecting and selecting a random empty space to insert my text.
Given a file name, you can read it into a list of lines using file->lines. So for instance:
(for ([line (in-list (file->lines "some-file"))])
(displayln (cond [(zero? (string-length line)) (make-random-line)]
[else line])))
Where make-random-line is some function you define to return a
random string, as you said you wanted to do.
The above reads the entire file into a list in memory. For larger files, it would be better to process things line by line. You can do this using the in-lines sequence:
(with-input-from-file "some-file"
(thunk
(for ([line (in-lines)])
(displayln (cond [(zero? (string-length line)) (make-random-line)]
[else line])))))
Update
Now that I understand your question:
#lang racket
(define lines (file->lines "some-file-name"))
(define empty-line-numbers (for/list ([line (in-list lines)]
[n (in-naturals)]
#:when (zero? (string-length line)))
n))
(define random-line-number (list-ref empty-line-numbers
(random (length empty-line-numbers))))
(for ([line (in-list lines)]
[n (in-naturals)])
(displayln (cond [(= n random-line-number) "SOME NEW STRING"]
[else line])))
(define (read-next-line-iter file)
(let ((line (read-line file)))
(unless (eof-object? line)
(display line)
(newline)
(read-next-line-iter file))))
(call-with-input-file "foobar.txt" read-next-line-iter)
http://rosettacode.org/wiki/Read_a_file_line_by_line#Racket
this function can help you read a file line by line.
check if the length is 0. and replace that line with the comment
look for 2 concurrent \n in the file. I am pretty sure there is a way in racket to do that. store those indices in a list select a pair randomly and replace the second \n with "calculation here\n".

Looping over lines in a string in Common Lisp

I'm trying to understand why this small piece of code does not work as expected.
I would expect it to print out "foo", but in fact what I get is
CL-USER> (stringloop)
null output T
line output NIL
NIL
I expect I am using do wrong, but I've not be able to figure out what.
(defun stringloop ()
(with-input-from-string (s "foo" :index j )
(do ((line (read-line s nil) ;; var init-form
(read-line s nil))) ;; step=form
((null line) (progn (format t "null output ~a~% "(null line)) (format t "line output ~a~% " line))))))
You didn't put anything in the loop body. Your function reads a line ("foo"), does nothing with it, then reads another line (nil), your termination condition becomes true, and you print the null line.
Run this modified version to see what's happening:
(defun stringloop ()
(with-input-from-string (s "foo")
(do ((line (read-line s nil) ;; var init-form
(read-line s nil))) ;; step=form
((null line) (format t "termination condition - line: ~s~% " line))
(format t "in loop - line: ~s~%" line))))

How to replace string in a file with lisp?

What's the lisp way of replacing a string in a file.
There is a file identified by *file-path*, a search string *search-term* and a replacement string *replace-term*.
How to make file with all instances of *search-term*s replaced with *replace-term*s, preferably in place of the old file?
One more take at the problem, but few warnings first:
To make this really robust and usable in the real-life situation you would need to wrap this into handler-case and handle various errors, like insufficient disc space, device not ready, insufficient permission for reading / writing, insufficient memory to allocate for the buffer and so on.
This does not do regular expression-like replacement, it's simple string replacement. Making a regular expression based replacement on large files may appear far less trivial than it looks like from the start, it would be worth writing a separate program, something like sed or awk or an entire language, like Perl or awk ;)
Unlike other solutions it will create a temporary file near the file being replaced and will save the data processed so far into this file. This may be worse in the sense that it will use more disc space, but this is safer because in case the program fails in the middle, the original file will remain intact, more than that, with some more effort you could later resume replacing from the temporary file if, for example, you were saving the offset into the original file in the temporary file too.
(defun file-replace-string (search-for replace-with file
&key (element-type 'base-char)
(temp-suffix ".tmp"))
(with-open-file (open-stream
file
:direction :input
:if-exists :supersede
:element-type element-type)
(with-open-file (temp-stream
(concatenate 'string file temp-suffix)
:direction :output
:element-type element-type)
(do ((buffer (make-string (length search-for)))
(buffer-fill-pointer 0)
(next-matching-char (aref search-for 0))
(in-char (read-char open-stream nil :eof)
(read-char open-stream nil :eof)))
((eql in-char :eof)
(when (/= 0 buffer-fill-pointer)
(dotimes (i buffer-fill-pointer)
(write-char (aref buffer i) temp-stream))))
(if (char= in-char next-matching-char)
(progn
(setf (aref buffer buffer-fill-pointer) in-char
buffer-fill-pointer (1+ buffer-fill-pointer))
(when (= buffer-fill-pointer (length search-for))
(dotimes (i (length replace-with))
(write-char (aref replace-with i) temp-stream))
(setf buffer-fill-pointer 0)))
(progn
(dotimes (i buffer-fill-pointer)
(write-char (aref buffer i) temp-stream))
(write-char in-char temp-stream)
(setf buffer-fill-pointer 0)))
(setf next-matching-char (aref search-for buffer-fill-pointer)))))
(delete-file file)
(rename-file (concatenate 'string file temp-suffix) file))
It can be accomplished in many ways, for example with regexes. The most self-contained way I see is something like the following:
(defun replace-in-file (search-term file-path replace-term)
(let ((contents (rutil:read-file file-path)))
(with-open-file (out file-path :direction :output :if-exists :supersede)
(do* ((start 0 (+ pos (length search-term)))
(pos (search search-term contents)
(search search-term contents :start2 start)))
((null pos) (write-string (subseq contents start) out))
(format out "~A~A" (subseq contents start pos) replace-term))))
(values))
See the implementation of rutil:read-file here: https://github.com/vseloved/rutils/blob/master/core/string.lisp#L33
Also note, that this function will replace search terms with any characters, including newlines.
in chicken scheme with the ireggex egg:
(use irregex) ; irregex, the regular expression library, is one of the
; libraries included with CHICKEN.
(define (process-line line re rplc)
(irregex-replace/all re line rplc))
(define (quickrep re rplc)
(let ((line (read-line)))
(if (not (eof-object? line))
(begin
(display (process-line line re rplc))
(newline)
(quickrep re rplc)))))
(define (main args)
(quickrep (irregex (car args)) (cadr args)))
Edit: in the above example buffering the input doesn't permit the regexp to span over
many lines.
To counter that here is an even simpler implementation which scans the whole file as one string:
(use ireggex)
(use utils)
(define (process-line line re rplc)
(irregex-replace/all re line rplc))
(define (quickrep re rplc file)
(let ((line (read-all file)))
(display (process-line line re rplc))))
(define (main args)
(quickrep (irregex (car args)) (cadr args) (caddr args)))