From: ···············@gmail.com
Subject: request comments: simple c program builder in lisp
Date: 
Message-ID: <bb50fa1e-1853-4f7e-bea4-bf332aff21d3@u36g2000prf.googlegroups.com>
Hello,

I am fairly new to Lisp and am trying to write a simple build tool for
C
files in order to learn lisp. I am not really interested in any
advanced
features for the build system yet as the main intent is to learn Lisp.

I am hoping that some of the lisp experts here can comment on the
style
of the program below and possibly give suggestions / pointers to what
may
be the "lispy" way of doing things.

I thought it may be best to send this across for comments before I
walk
too far down the wrong path and this becomes a 'code bomb' :)

Thanks very much.
Parth

Here is a sample interaction of what I have so far (I use clisp):

    [1]> (load "packages.lisp")
    ; loading system definition from /usr/s
    .............. snip ..................
    ; registering #<SYSTEM MD5 #x206F9E56> as MD5
    T
    [2]> (load "clsi.lisp")
    T
    [3]> (in-package :clsi)
    #<PACKAGE CLSI>
    CLSI[4]> (setf *p* (program "test" '("a.c" "b.c" "c.d")))
    (:NAME "test" :DEPS
     ((:NAME "a.o" :DEPS ((:NAME "a.c" :DEPS NIL :TYPE CFILE)) :TYPE
OFILE)
      (:NAME "b.o" :DEPS ((:NAME "b.c" :DEPS NIL :TYPE CFILE)) :TYPE
OFILE)
      (:NAME "c.d" :DEPS NIL :TYPE NIL))
     :TYPE EXE)
    CLSI[5]> (build *p*)
    [build] a.c: gcc -o a.o a.c
    [build] a.o: nothing to do: a.o
    [build] b.c: gcc -o b.o b.c
    [build] b.o: nothing to do: b.o
    [build] c.d: nothing to do: c.d
    [build] test: gcc -o test a.o b.o c.d
    NIL
    CLSI[6]>

Source Code Listing
===================
; clsi.lisp (sorry for the somewhat long listing :P)

(defpackage #:clsi
  (:use :common-lisp :fad :ppcre :md5)
  (:export #:search-replace-last-re
           #:search-replace-re
           #:concat-string-from-list
           #:checksum
           #:make-file))

(in-package #:clsi)

;; ===============
;; Utility Methods
;; ===============

(defun concat-strings-from-list (l)
  "take all strings in the list and concatenate them
   adding a space in between.
   e.g.: ('aa' 'bb' 'cc') => 'aa bb cc'"
  (reduce #'(lambda (x y) (concatenate 'string x " " y))
          l))

(defun find-last-subst-pos-re (sub str)
  "returns position of last sub-string.
   e.g.: 'xx' 'abcxxyy' => 3"
  (let ((m (all-matches sub str)))
    (first (last (butlast m)))))

(defun search-replace-re (old new str &key (start 0))
  "replace old with new in str. old and new are regular
   expressions"
  (concatenate 'string              ; need to use concat as regex-
replace
               (subseq str 0 start) ; drops begining when start is not
0
               (regex-replace old str new :start start)))

(defun search-replace-last-re (old new str)
  "replace the last occurance of old with new in str.
   used to change file extension in strings.
   e.g.: '\.c' '\.o' 'test.c' => 'test.o'"
  (let ((pos (find-last-subst-pos-re old str)))
    (when (numberp pos)
      (search-replace-re old new str :start pos))))

(defun checksum (pathname)
  (md5sum-file (pathname-as-file pathname)))


;;; ================================
;;; File Extension and Type Handling
;;; ================================

(defun extention-p (ext name)
  "return if name ends in ext. used to check file extension"
  (let ((ext-len (length ext)))
    (equal ext (subseq name (- (length name) ext-len)))))

(defun filetype-p (name type)
  "check if file is of a 'type'"
  (cond ((equal type 'cfile) (extention-p ".c" name))
        ((equal type 'ofile) (extention-p ".o" name))
        ((equal type 'hfile) (extention-p ".h" name))
        (t nil)))

(defun filetype? (name)
  "return the 'type' of file based on extension"
  (cond ((extention-p ".c" name) 'cfile)
        ((extention-p ".o" name) 'ofile)
        ((extention-p ".h" name) 'hfile)
        (t nil)))

;;; ===================
;;; File Representation
;;; ===================

(defun make-file (&key name (deps nil) (type nil))
  (list :name name :deps deps :type (filetype? name)))

(defun make-file-list (names)
  (mapcar #'(lambda (x) (get-intermediate-file (make-file :name x)))
names))

(defun get-intermediate-file (file)
  "takes a file and generates a plist for the possible intermediate
   file once this is processed. For example, for a test.c plist
   it will create a test.o plist. When there is no intermediate file,
   input is returned"
  (let ((name (getf file :name))
        (type (getf file :type)))
    (cond ((equal type 'cfile) (make-file
                                 :name (search-replace-last-re "\.c"
"\.o" name)
                                 :deps (list file)
                                 :type 'ofile))
          (t file))))

; Usage:
;   (setf *p* (program "test" '("a.c" "b.c" "c.d")))
;   (build *p*)

(defun program (target sources)
  "top level program creation function. returns a tree that represents
   files and their dependencies leading to the toplevel program."
  (let ((target-file (make-file :name target)))
    (setf (getf target-file :deps) (make-file-list sources))
    (setf (getf target-file :type) 'exe)
    target-file))

(defun build (program)
  (let ((deps (getf program :deps))
        (name (getf program :name))
        (type (getf program :type)))
    (cond ((null deps) (format t "[build] ~A: " name)
                       (build-file program))
          (t (mapcar #'build deps)
             (format t "[build] ~A: " name)
             (build-file program)))))

; "test.c" => "test.o"
(defun cfile->ofile (name)
  (search-replace-last-re "\.c" "\.o" name))

; deps-(<file0>, <file1>, <file2>) => ("depname0" "depname1"
"depname2")
(defun get-dep-names (file)
  (let ((deps (getf file :deps)))
    (mapcar #'(lambda (x) (getf x :name)) deps)))

(defun build-file (file)
  "print the string needed to build the file based on file type"
  (let ((type (getf file :type))
        (name (getf file :name))
        (deps (getf file :deps)))
    (cond ((equal type 'cfile)
           (format t "~A~%" (concat-strings-from-list
                              (list "gcc -o" (cfile->ofile name)
name))))
          ((equal type 'ofile)
           (format t "nothing to do: ~A~%" name))
          ((equal type 'exe)
           (format t "~A~%" (concatenate 'string
                                         "gcc -o "
                                         name " "
                                         (concat-strings-from-list
                                           (get-dep-names file)))))
          (t (format t "nothing to do: ~A~%" name)))))

From: Leslie P. Polzer
Subject: Re: request comments: simple c program builder in lisp
Date: 
Message-ID: <6b367c6e-87a0-4c7d-bfda-424814ef7004@k37g2000hsf.googlegroups.com>
Hello Parth,

some suggestions:

> (defun concat-strings-from-list (l)
>   "take all strings in the list and concatenate them
>    adding a space in between.
>    e.g.: ('aa' 'bb' 'cc') => 'aa bb cc'"
>   (reduce #'(lambda (x y) (concatenate 'string x " " y))
>           l))

LIST->STR. Separator should be adjustable via key.

You don't need the #' here.

> (defun find-last-subst-pos-re (sub str)
LAST-SUBSTR-POS

> (defun search-replace-re (old new str &key (start 0))

REPLACE-RE


> (defun search-replace-last-re (old new str)

REPLACE-LAST-RE


>   (let ((pos (find-last-subst-pos-re old str)))
>     (when (numberp pos)

Haven't looked closely at this function; maybe

  (when pos
    ...)

would suffice?


> (defun extention-p (ext name)

HAS-EXTENSION. EXTENSION-P would by an unary function that
checks whether its one arg is an extension.


>   "return if name ends in ext. used to check file extension"
>   (let ((ext-len (length ext)))
>     (equal ext (subseq name (- (length name) ext-len)))))

The LET is gratuitous here, just use LENGTH directly.


> (defun filetype-p (name type)

FILE-OF-TYPE


>   "check if file is of a 'type'"
>   (cond ((equal type 'cfile) (extention-p ".c" name))
>         ((equal type 'ofile) (extention-p ".o" name))
>         ((equal type 'hfile) (extention-p ".h" name))
>         (t nil)))

Use EQ to compare symbols.
Rewrite the COND with the help of CASE.


> (defun filetype? (name)

TYPE-OF-FILE, FILE-TYPE, FILE-TYPE-OF

Don't use the suffix ? on anything but Boolean functions.


>   "return the 'type' of file based on extension"
>   (cond ((extention-p ".c" name) 'cfile)
>         ((extention-p ".o" name) 'ofile)
>         ((extention-p ".h" name) 'hfile)
>         (t nil)))

Don't do this.

Create an ALIST of extensions and symbols:

  (defparameter *extension-map* '((".c" . CFILE) (".o" . OFILE)
(".h" . HFILE)))

or better yet

  (defparameter *extension-map* '((".c" . SOURCE) (".o" . OBJECT)
(".h" . HEADER)))


(defun fextension->symbol (ext)
  (assoc ext *extension-map* :test #'equal)) ; or EQUALP depending on
the file system...


> (defun make-file (&key name (deps nil) (type nil))
>   (list :name name :deps deps :type (filetype? name)))

NAME is mandatory here, move it out of the key section.


>     (cond ((equal type 'cfile) (make-file

Use EQ


>   (let ((target-file (make-file :name target)))
>     (setf (getf target-file :deps) (make-file-list sources))
>     (setf (getf target-file :type) 'exe)
>     target-file))

Whatcha doing here?


> (defun build (program)
>   (let ((deps (getf program :deps))
>         (name (getf program :name))
>         (type (getf program :type)))

Uh, might be worth defining a struct with accessors for PROGRAM.


>     (cond ((null deps) (format t "[build] ~A: " name)
>                        (build-file program))
>           (t (mapcar #'build deps)
>              (format t "[build] ~A: " name)
>              (build-file program)))))

Use IF for binary branches.


> ; "test.c" => "test.o"
> (defun cfile->ofile (name)
>   (search-replace-last-re "\.c" "\.o" name))

Define a function to change the extension and abstract the
stuff more:

(defun SOURCE-NAME->OBJECT-NAME ...)


> (defun get-dep-names (file)
>   (let ((deps (getf file :deps)))
>     (mapcar #'(lambda (x) (getf x :name)) deps)))

Just use DEPS inline.


> (defun build-file (file)
>   "print the string needed to build the file based on file type"

I suppose this is a simulator, then...


>   (let ((type (getf file :type))
>         (name (getf file :name))
>         (deps (getf file :deps)))

Again, use accessors.


>            (format t "~A~%" (concat-strings-from-list
>                               (list "gcc -o" (cfile->ofile name)
> name))))
>           ((equal type 'ofile)
>            (format t "nothing to do: ~A~%" name))
>           ((equal type 'exe)
>            (format t "~A~%" (concatenate 'string
>                                          "gcc -o "
>                                          name " "
>                                          (concat-strings-from-list
>                                            (get-dep-names file)))))
>           (t (format t "nothing to do: ~A~%" name)))))

Another ALIST mapping file types to actions would be appropriate.

You can also drag out the FORMAT call:

(apply #'format t "~A~%"
  (cond ...))

HTH,

  Leslie
From: Maciej Katafiasz
Subject: Re: request comments: simple c program builder in lisp
Date: 
Message-ID: <g30trr$e1c$1@news.net.uni-c.dk>
Den Sat, 14 Jun 2008 20:48:30 +0530 skrev Madhu:

> * "Leslie P. Polzer"
> <····································@k37g2000hsf.googlegroups.com> :
> Wrote on Sat, 14 Jun 2008 07:55:13 -0700 (PDT):
> 
> I think you can ignore most of Leslie P. Polzer's style advice safely as
> his personal style preference.

On the contrary, most of his remarks are spot on, very little is what I'd 
consider "personal style preference".

Cheers,
Maciej
From: Sacha
Subject: Re: request comments: simple c program builder in lisp
Date: 
Message-ID: <EIU4k.142542$x55.31728@newsfe17.ams2>
Maciej Katafiasz wrote:
> Den Sat, 14 Jun 2008 20:48:30 +0530 skrev Madhu:
> 
>> * "Leslie P. Polzer"
>> <····································@k37g2000hsf.googlegroups.com> :
>> Wrote on Sat, 14 Jun 2008 07:55:13 -0700 (PDT):
>>
>> I think you can ignore most of Leslie P. Polzer's style advice safely as
>> his personal style preference.
> 
> On the contrary, most of his remarks are spot on, very little is what I'd 
> consider "personal style preference".
> 
> Cheers,
> Maciej


Well there are some not-unary predicates defined in the common lisp 
library. Like typep, slot-boundp ...

Also all caps symbols are ugly.

On the other hand, using a-lists as decision tables is very lispy.

But i would use clos objects and generic functions for type dispatching, 
specially in this case where speed is really not important at all.

(defclass file ()
   (...))

(defclass c-file (file)
   (..))

(defclass object-file (file)
   (..))

(defun build-file (file)
   (format nil "..." (build-file-action file)

(defmethod build-file-action ((file c-file))
   ...)

(defmethod build-file-action ((file object-file))
   ...)

this way you can extend it for ... err ... resource files, without 
touching your existing functions ever again.

the concat-strings-from-list function has horrible complexity it's like 
O(n!) or something. I would leave the strings in their list, then write 
to a stream. use with-output-to-string. Write an interleave function to 
add all these spaces in between. You'll be able to use it again some day !

Sacha
From: Leslie P. Polzer
Subject: Re: request comments: simple c program builder in lisp
Date: 
Message-ID: <adfc92dd-cb83-42ab-a4d7-a045c04ca356@y21g2000hsf.googlegroups.com>
> Also all caps symbols are ugly.

It's good that you mention this. I just put the symbols uppercase
to make them stand-out in text. There was no intention on my
part to suggest actually writing your CL code that way...

  Leslie
From: ···············@gmail.com
Subject: Re: request comments: simple c program builder in lisp
Date: 
Message-ID: <75e13c5c-4d42-4067-bacc-72e16d33127b@x1g2000prh.googlegroups.com>
On Jun 15, 12:10 am, Sacha <····@address.spam> wrote:
> Maciej Katafiasz wrote:
> > Den Sat, 14 Jun 2008 20:48:30 +0530 skrev Madhu:
>
> >> * "Leslie P. Polzer"
> >> <····································@k37g2000hsf.googlegroups.com> :
> >> Wrote on Sat, 14 Jun 2008 07:55:13 -0700 (PDT):
>
> >> I think you can ignore most of Leslie P. Polzer's style advice safely as
> >> his personal style preference.
>
> > On the contrary, most of his remarks are spot on, very little is what I'd
> > consider "personal style preference".
>
> > Cheers,
> > Maciej
>
> Well there are some not-unary predicates defined in the common lisp
> library. Like typep, slot-boundp ...
>
> Also all caps symbols are ugly.
>
> On the other hand, using a-lists as decision tables is very lispy.
>
> But i would use clos objects and generic functions for type dispatching,
> specially in this case where speed is really not important at all.
>
> (defclass file ()
>    (...))
>
> (defclass c-file (file)
>    (..))
>
> (defclass object-file (file)
>    (..))
>
> (defun build-file (file)
>    (format nil "..." (build-file-action file)
>
> (defmethod build-file-action ((file c-file))
>    ...)
>
> (defmethod build-file-action ((file object-file))
>    ...)
>
> this way you can extend it for ... err ... resource files, without
> touching your existing functions ever again.
>
> the concat-strings-from-list function has horrible complexity it's like
> O(n!) or something. I would leave the strings in their list, then write
> to a stream. use with-output-to-string. Write an interleave function to
> add all these spaces in between. You'll be able to use it again some day !
>
> Sacha

Thanks Leslie / Pascal / Sacha. The comments were very insightful
and helpful.

For now I have upgraded to using structs. As Sasha suggested
I would probably upgrade to classes as I add this app evolves some
more.

For the 'build-file' function which was quite ugly, I tried to
do the cleanup using an alist returning a lambda based on filetype.

Here is what I came up with. Is my implementation of this clean enough
or can something be done better (code below) ?

Thanks.
Parth


  (defmacro concat-str (&rest args)
    `(concatenate 'string ,@args))


  (defparameter *build-cmd-map*
    (list
      (cons 'cfile
            (lambda (f)
              (concat-str "gcc -o " (cfile->ofile (unit-name f))
                          " " (unit-name f))))
      (cons 'ofile
            (lambda (f)
              (concat-str"nothing to do: " (unit-name f))))
      (cons 'exe
            (lambda (f) (concat-str "gcc -o " (unit-name f) " "
                                     (list->str (get-dep-names
f)))))))

  (defun file->build-cmd (f)
    "return the 'type' of file based on extension"
    (rest (assoc (unit-type f) *build-cmd-map* :test #'equal)))

  (defun build-file (file)
    "print the string needed to build the file based on file type"
    (funcall #'format t "~A~%"
             (funcall (file->build-cmd file) file)))
From: Sacha
Subject: Re: request comments: simple c program builder in lisp
Date: 
Message-ID: <E645k.69284$RE7.18354@newsfe08.ams2>
> 
>   (defmacro concat-str (&rest args)
>     `(concatenate 'string ,@args))
> 

You don't need a macro for this :

(defun concat-string (&rest args)
   (apply #'concatenate args))

But :

;CL-USER 12 > CALL-ARGUMENTS-LIMIT
;2047

So on my lisp implementation, your macro (my function as well) would 
break for lists with over 2047 items. besides we're not quite sure on 
the performance implications of such a big parameter list.

So :

(defun list->stream (list stream)
   (loop for item in list
         do (princ item stream)))

;pretty much the same thing, but for list of lists
(defun tree->stream (tree stream)
   (loop for item in tree
         if (listp item) do (tree->stream item stream)
         else do (princ item stream)))

(defun interleave (list separator)
   (loop for first = t then nil
         for item in list
         unless first collect separator
         collect item))

;CL-USER 1 > (interleave '(1 2 3) #\space)
;(1 #\Space 2 #\Space 3)
; common lisp is fun, i can mix characters with numbers !
; I'm not a native english speaker, maybe intersperse would be better as 
a name ?

;CL-USER 2 > (tree->stream (interleave `(1 ,(interleave '("two" three) 
#\space) #\z) #\space) *standard-output*)
;1 two THREE z

;literals are way easier to read
(defparameter *build-commands*
   '((c-file . build-c-file)
     (object-file . build-object-file)
     (exe-file . build-exe-file)))

;make it easier to change stuff
(defvar *compiler-options* '("-o"))
(defvar *compiler-name* "gcc")

;we're using lisp here, so let's work with lists !
(defun build-c-file (file)
   `(,*compiler-name* ,@*compiler-options* ,(c-file->object-file 
(unit-name file)) ,(unit-name file)))

(defun build-object-file (file)
   `(";nothing to do for" ,(unit-name file)))

(defun build-exe-file (file)
   `(,*compiler-name* ,@*compiler-options* ,(unit-name file) 
,@(get-dependency-names)))

;no need to use the 'equal test as we're working with symbols
(defun file->build-file-function (file)
   (cdr (assoc (unit-type file) *build-commands*)))

;still working with lists
(defun file->command (file)
   (interleave (funcall (file->build-file-function file) file)
                #\space)))

;still working with lists, notice how it makes this very readable
(defun files->command-list (files)
   (interleave (mapcar #'file->command files)
               #\newline))

now we print to a stream ... so it could be standard-output, a 
string-stream (with-output-to-string) for testing, or a file, which i 
guess is what you eventually want to do.

(defun files->stream (files &optional (stream *standard-output*))
   (tree->stream (files->command-list files) stream))

Ok i got a little bit carried away ...

This whole thing is totally untested
We're using small functions all along as these are easier to test, 
compose and understand. Doc strings are missing, which is pretty bad !

There are many ways to do things using common lisp, I like to work with 
lists when generating text.

Sacha
From: Sacha
Subject: Re: request comments: simple c program builder in lisp
Date: 
Message-ID: <Qm45k.69285$RE7.8613@newsfe08.ams2>
> (defun concat-string (&rest args)
>   (apply #'concatenate args))
> 

hum this should be

(defun concat-string (&rest args)
   (apply #'concatenate 'string args))

Well it should not be at all really !

Sacha
From: John Thingstad
Subject: Re: request comments: simple c program builder in lisp
Date: 
Message-ID: <op.ucsbgkteut4oq5@pandora.alfanett.no>
P� Sun, 15 Jun 2008 10:09:08 +0200, skrev Sacha <····@address.spam>:

>
>>    (defmacro concat-str (&rest args)
>>     `(concatenate 'string ,@args))
>>
>
> You don't need a macro for this :
>
> (defun concat-string (&rest args)
>    (apply #'concatenate args))
>

Right!

> But :
>
> ;CL-USER 12 > CALL-ARGUMENTS-LIMIT
> ;2047
>
> So on my lisp implementation, your macro (my function as well) would  
> break for lists with over 2047 items. besides we're not quite sure on  
> the performance implications of such a big parameter list.
>
> So :
>
> (defun list->stream (list stream)
>    (loop for item in list
>          do (princ item stream)))
>

Do you think you would ever want to concatenate more that 2048 elements in  
one line?

(defun concat-str (&rest list)
    (apply #'concatenate 'string list))

is fine here.

For printing a list with a delimiter how about format?

CL-USER 3 > (format nil "~{~A~^ ~}" (list 1 2 3 4 5))
"1 2 3 4 5"

or

CL-USER 17 > (defun list->string (list &optional (delim " "))
                (format nil (concat-str "~{~A~^" delim  "~}") list))
LIST->STRING

CL-USER 18 > (list->string (list 1 2 3 4 5))
"1 2 3 4 5"

CL-USER 19 > (list->string (list 1 2 3 4 5) ", ")
"1, 2, 3, 4, 5"

--------------
John Thingstad
From: ···············@gmail.com
Subject: Re: request comments: simple c program builder in lisp
Date: 
Message-ID: <2cd22314-1430-4ca2-bbc3-b9b15234321c@u6g2000prc.googlegroups.com>
On Jun 15, 3:01 pm, "John Thingstad" <·······@online.no> wrote:
> På Sun, 15 Jun 2008 10:09:08 +0200, skrev Sacha <····@address.spam>:
>
>
>
> >>    (defmacro concat-str (&rest args)
> >>     `(concatenate 'string ,@args))
>
> > You don't need a macro for this :
>
> > (defun concat-string (&rest args)
> >    (apply #'concatenate args))
>
> Right!
>
> > But :
>
> > ;CL-USER 12 > CALL-ARGUMENTS-LIMIT
> > ;2047
>
> > So on my lisp implementation, your macro (my function as well) would
> > break for lists with over 2047 items. besides we're not quite sure on
> > the performance implications of such a big parameter list.
>
> > So :
>
> > (defun list->stream (list stream)
> >    (loop for item in list
> >          do (princ item stream)))
>
> Do you think you would ever want to concatenate more that 2048 elements in
> one line?
>
> (defun concat-str (&rest list)
>     (apply #'concatenate 'string list))
>
> is fine here.
>
> For printing a list with a delimiter how about format?
>
> CL-USER 3 > (format nil "~{~A~^ ~}" (list 1 2 3 4 5))
> "1 2 3 4 5"
>
> or
>
> CL-USER 17 > (defun list->string (list &optional (delim " "))
>                 (format nil (concat-str "~{~A~^" delim  "~}") list))
> LIST->STRING
>
> CL-USER 18 > (list->string (list 1 2 3 4 5))
> "1 2 3 4 5"
>
> CL-USER 19 > (list->string (list 1 2 3 4 5) ", ")
> "1, 2, 3, 4, 5"
>
> --------------
> John Thingstad

Thanks everyone for your comments.
This really helped me understand the idiomatic lisp
programming. I did some more updates based on the comments.

 - Now I am using using the approach of keeping items in
   list format as much as possible and creating a string
   only in the end (lst->str).
   This was quite an interesting approach which didn't
   really occur to me possibly due to my background in
   languages like C and Python. I assume this will be
   much more efficient than what I was doing earlier.
   Thanks Sacha.

 - Using format for concatenation the list is very cool.
   I knew about this but some how it didn't occur to me
   ... probably need more practice :)

 - There is also the 'emit' and 'emit-to-screen' method
   that allows the user to choose between actually building
   the c code vs just printing the output on screen.

 - I also got a chance to add a 'clean' method that deletes
   all the generated files during the build process.

Here is a simple interaction in clisp:

    [1]> (load "packages.lisp")
    .............. snip ................................
    T
    [2]> (load "bld.lisp")
    T
    [3]> (in-package :bld)
    #<PACKAGE BLD>
    BLD[4]> (setf *p* (program "test" '("test.c" "a.c" "b.c")))
    #S(UNIT :NAME "test"
       :DEPS
       (#S(UNIT :NAME "test.o"
           :DEPS (#S(UNIT :NAME "test.c" :DEPS NIL :TYPE
CFILE :CLEANABLE NIL))
           :TYPE OFILE :CLEANABLE T)
        #S(UNIT :NAME "a.o"
           :DEPS (#S(UNIT :NAME "a.c" :DEPS NIL :TYPE CFILE :CLEANABLE
NIL))
           :TYPE OFILE :CLEANABLE T)
        #S(UNIT :NAME "b.o"
           :DEPS (#S(UNIT :NAME "b.c" :DEPS NIL :TYPE CFILE :CLEANABLE
NIL))
           :TYPE OFILE :CLEANABLE T))
       :TYPE XFILE :CLEANABLE T)
    BLD[5]> (build *p*)
    [BLD] test.c: gcc -c -o test.o test.c
    [BLD] a.c: gcc -c -o a.o a.c
    [BLD] b.c: gcc -c -o b.o b.c
    [BLD] test: gcc -o test test.o a.o b.o
    NIL
    BLD[6]> (clean *p*)
    [CLN] rm test.o
    [CLN] rm a.o
    [CLN] rm b.o
    [CLN] rm test
    NIL
    BLD[7]> (emit-to-screen nil)
    NIL
    BLD[8]> (emit-to-screen t)
    T
    BLD[9]>

Code is attached below for completeness. I will probably work
some more on this and continue to add more features in order to
learn lisp. :)

Parth

[My posts just seem to be getting longer and long ...
 Sorry about that!]

;; ======================
;; String Utility Methods
;; ======================

(defun interleave (list &optional (sep " "))
  "(interleave '('a' 'b' 'c' 'd') ' ') => ('a' ' ' 'b' ' ' 'c' ' '
'd')"
  (loop for first = t then nil
        for item in list
        unless first collect sep
        collect item))

(defun last-substr-pos-re (sub str)
  "returns position of last sub-string.
   e.g.: 'xx' 'abcxxyy' => 3"
  (let ((m (all-matches sub str)))
    (first (last (butlast m)))))

(defun replace-re (old new str &key (start 0))
  "replace old with new in str. old and new are regular expressions"
  ; need to use concat as regex-replace
  ; drops begining when start is not 0
  (concatenate 'string
               (subseq str 0 start)
               (regex-replace old str new :start start)))

(defun replace-last-re (old new str)
  "replace the last occurance of old with new in str.
   used to change file extension in strings.
   e.g.: '\.c' '\.o' 'test.c' => 'test.o'"
  (let ((pos (last-substr-pos-re old str)))
    (when pos
      (replace-re old new str :start pos))))

;; ========================
;; Checksum Utility Methods
;; ========================

; TODO: right now we just assume out of data tgt
;       in case of file-error. ideally we should
;       also check if the source exists
(defun target-out-of-date (src tgt)
  (handler-case
    (>= (file-write-date src) (file-write-date tgt))
    (file-error () t)))

;;; ================================
;;; File Extension and Type Handling
;;; ================================
(defparameter *extension-map* '((".c" . cfile)
                                (".o" . ofile)
                                (".h" . hfile)))

(defun fext->symbol (ext)
  "return the 'type' of file based on extension"
  (rest (assoc ext *extension-map* :test #'equal)))

(defun file-ext (name)
  (let ((dot-pos (search "." name :from-end t)))
    (when dot-pos
      (subseq name dot-pos))))

(defun fname->type (name)
  (fext->symbol (file-ext name)))

; "test.c" => "test.o"
(defun cfile->ofile (name)
  (replace-last-re "\.c" "\.o" name))

;;; ===================
;;; File Representation
;;; ===================

(defstruct (unit (:constructor %make-unit))
  name
  (deps nil :type list)
  (type nil)
  (cleanable nil))

(defun make-unit (&key name deps type (cleanable nil))
  (%make-unit :name name
              :deps deps
              :type (or type (fname->type name))
              :cleanable cleanable))

(defun make-deps-list (names)
  (mapcar (lambda (x) (get-intermediate-unit (make-unit :name x)))
names))

(defun make-ofile (cfile)
  (make-unit :name (cfile->ofile (unit-name cfile))
             :deps (list cfile) :type 'ofile :cleanable t))

(defun get-intermediate-unit (file)
  "takes a file and generates a plist for the possible intermediate
  file once this is processed. For example, for a test.c plist
  it will create a test.o plist. When there is no intermediate file,
  input is returned"
  (if (eq (fname->type (unit-name file)) 'cfile)
    (make-ofile file)
    file))

(defun program (target sources)
  "top level program creation function. returns a tree that represents
   files and their dependencies leading to the toplevel program."
  (let ((tgt-unit (make-unit :name target)))
    (setf (unit-deps tgt-unit) (make-deps-list sources))
    (setf (unit-type tgt-unit) 'xfile)
    (setf (unit-cleanable tgt-unit) t)
    tgt-unit))

;;; ================
;;; Build Related
;;; ================

(defvar *ccflags* '("-o"))
(defvar *cc* "gcc")

;;; Top Level Build Method
;;; ----------------------
(defun build (program)
  (if (null (unit-deps program))
    (build-unit program)
    (progn (mapcar #'build (unit-deps program))
           (build-unit program))))

(defun build-unit (file)
  "issues a build command"
  (let ((cmd (funcall (file->build-cmd file) file)))
    (when cmd
      (format t "~&[BLD] ~A: " (unit-name file))
      (funcall #'emit cmd))))

;;; Top Level Clean Method
;;; ----------------------
(defun clean (program)
  (if (null (unit-deps program))
    (clean-unit program)
    (progn (mapcar #'clean (unit-deps program))
           (clean-unit program))))

(defun clean-unit (program)
  (when (unit-cleanable program)
      (format t "[CLN] rm ~A~%" (unit-name program))
      (when (not *emit-to-screen*)
        (delete-file (unit-name program)))))

; deps-(<file0>, <file1>, <file2>) => ("depname0" "depname1"
"depname2")
(defun get-dep-names (file)
    (mapcar (lambda (x) (unit-name x)) (unit-deps file)))

(defun build-cfile (file)
  (let ((*ccflags* '("-c -o")))
  `(,*cc* ,@*ccflags* ,(cfile->ofile (unit-name file)) ,(unit-name
file))))

(defun build-ofile (file) nil)

(defun build-xfile (file)
  `(,*cc* ,@*ccflags* ,(unit-name file) ,@(get-dep-names file)))

(defparameter *build-cmd-map*
  '((cfile . build-cfile)
    (ofile . build-ofile)
    (xfile . build-xfile)))

(defun file->build-cmd (f)
  "return the 'type' of file based on extension"
  (rest (assoc (unit-type f) *build-cmd-map* :test #'equal)))

(defun dump-program (p &optional (fname "tmp.dump"))
  (with-open-file (out fname :direction :output :if-exists :supersede)
    (write p :stream out)))

;; when set to t, emitted commands are executed
(defparameter *emit-to-screen* t)

(defun emit (cmd)
  (if *emit-to-screen*
    (format t "~A~%" (lst->str cmd))
    (execute-shell-command-to-stdout (lst->str cmd))))

(defun emit-to-screen (&optional (to-screen t))
  (setf *emit-to-screen* to-screen))

(defun lst->str (lst)
  (format nil "~{~A~^ ~}" lst))

;;; ==================
;;; System Interaction
;;; ==================

; TODO: make this portable across implementations.
;       atleast throw errors on implementations other
;       than clisp.
(defun execute-shell-command (cmd)
  (ext:make-pipe-input-stream cmd))

(defun execute-shell-command-to-stdout (cmd)
  (format t "~A~%" cmd)
  (when cmd
    (let ((in (execute-shell-command cmd)))
      (when in
        (loop for line = (read-line in nil)
              while line do (format t "~A~%" line))
        (close in)))))
From: Leslie P. Polzer
Subject: Re: request comments: simple c program builder in lisp
Date: 
Message-ID: <b87eaacb-2ce1-44de-88ec-a09bbfc191c2@e53g2000hsa.googlegroups.com>
>     BLD[7]> (emit-to-screen nil)
>     NIL
>     BLD[8]> (emit-to-screen t)
>     T

This is a bit weird. On reading this I'd think that EMIT-TO-SCREEN,
will, uh, emit some thing to some screen. But it doesn't, it's just
a switch.

Functional interfaces can get very clumsy when
you need to keep a bunch of states.

EMIT-TO-SCREEN should be SETFable.

What I'd do:

(defstruct program
  ... emit-to-screen)

(defmethod build ((program program))
  ...)

To be used thus:

(let ((program (make-program :emit-to-screen t))
  (setf (program-emit-to-screen) nil)
  (build program))

Better yet:

(defstruct program
  ... emit-to)

(setf (program-emit-to) :screen)
From: ···············@gmail.com
Subject: Re: request comments: simple c program builder in lisp
Date: 
Message-ID: <1f5df1d9-bb14-453e-b29e-b937fc4f3441@w34g2000prm.googlegroups.com>
On Jun 16, 1:03 pm, "Leslie P. Polzer" <·············@gmx.net> wrote:
> >     BLD[7]> (emit-to-screen nil)
> >     NIL
> >     BLD[8]> (emit-to-screen t)
> >     T
>
> This is a bit weird. On reading this I'd think that EMIT-TO-SCREEN,
> will, uh, emit some thing to some screen. But it doesn't, it's just
> a switch.
>

I suppose the interaction sample I provied is a little confusion.
I was thinking of emit-to-screen as just a helper function. It
provide a convenient way of setting/resetting the *emit-to-screen*
global. The subsequent build / clean would either actually do
something or just print to screen based on this flag.

;; when set to t, emitted commands are executed
(defparameter *emit-to-screen* t)

(defun emit-to-screen (&optional (to-screen t))
  (setf *emit-to-screen* to-screen))

(defun emit (cmd)
  (if *emit-to-screen*
    (format t "~A~%" (lst->str cmd))
    (execute-shell-command-to-stdout (lst->str cmd))))

The 'emit' function takes this into account and either just
prints the commands to screen or executes them. I was thinking
of this more like the '-n' option in gmake.


> Functional interfaces can get very clumsy when
> you need to keep a bunch of states.
>

True.

> EMIT-TO-SCREEN should be SETFable.
>
> What I'd do:
>
> (defstruct program
>   ... emit-to-screen)
>
> (defmethod build ((program program))
>   ...)
>
> To be used thus:
>
> (let ((program (make-program :emit-to-screen t))
>   (setf (program-emit-to-screen) nil)
>   (build program))
>
> Better yet:
>
> (defstruct program
>   ... emit-to)
>
> (setf (program-emit-to) :screen)

This sounds like actually quite a good approach.
Along the same lines, rather than a separate
function *emit-to-screen* maintaining state, this could
be an optional parameter to 'build' and 'clean' (being
subsequently passed to emit.

This way when I add support for command line options, a
-n on the command line could correspond to t being
passed to :to-screen for build / clean as the case may
be.

Thanks.
From: ···············@gmail.com
Subject: Re: request comments: simple c program builder in lisp
Date: 
Message-ID: <d8820208-9ac4-43a5-bcbb-beb52d7d0b51@g16g2000pri.googlegroups.com>
On Jun 16, 1:03 pm, "Leslie P. Polzer" <·············@gmx.net> wrote:
> >     BLD[7]> (emit-to-screen nil)
> >     NIL
> >     BLD[8]> (emit-to-screen t)
> >     T
>
> This is a bit weird. On reading this I'd think that EMIT-TO-SCREEN,
> will, uh, emit some thing to some screen. But it doesn't, it's just
> a switch.
>
> Functional interfaces can get very clumsy when
> you need to keep a bunch of states.
>
> EMIT-TO-SCREEN should be SETFable.
>
> What I'd do:
>
> (defstruct program
>   ... emit-to-screen)
>
> (defmethod build ((program program))
>   ...)
>
> To be used thus:
>
> (let ((program (make-program :emit-to-screen t))
>   (setf (program-emit-to-screen) nil)
>   (build program))
>
> Better yet:
>
> (defstruct program
>   ... emit-to)
>
> (setf (program-emit-to) :screen)

This makes sense. I have pulled *emit-to-screen* along
with *ccflags* and *cc* into a build-env struct.

Two possible interactions:

;; Usage 0
;; =======
; (setf *p* (program "test" '("test.c" "a.c" "b.c")))
; (build *p*)
; (clean *p*)
;
;; Usage 1
;; =======
; (setf *env* (make-build-env :emit-to-screen nil))
; (setf *p* (program "test" '("test.c" "a.c" "b.c")))
; (build *p* *env*)
; (clean *p* *env*)

Basically the build-env now defaults to gcc and emits
to screen. The user has a choice to create another env
and pass it to build / clean. The typical usage would
be have multiple build environments like debug / release.

    (defstruct build-env
      (ccflags '("-o"))
      (cc "gcc")
      (program nil)
      (emit-to-screen t))

    (defparameter *build-environment* (make-build-env))


I have hosted the full code at google code to avoid cluttering
the newsgroup.
http://code.google.com/p/bld/source/browse/trunk/src/bld.lisp

Thanks very much for your suggestions.
Parth
From: Sacha
Subject: Re: request comments: simple c program builder in lisp
Date: 
Message-ID: <al65k.81938$X6.52739@newsfe30.ams2>
John Thingstad wrote:

> Do you think you would ever want to concatenate more that 2048 elements 
> in one line?
> 
> (defun concat-str (&rest list)
>    (apply #'concatenate 'string list))
> 
> is fine here.

True, but there's more to it !
As you nest more and more functions (let's say for html generation), 
you'll concatenate smaller strings, then concatenate these again at a 
higher level, and so on up to the document level. That's a lot of 
useless consing. Your processor has to move the same characters again 
and again.

A cost which isn't alleviated by easier programming or readability.

In the end odds are that you still want to print all this to a stream, 
so why all the consing ?

Also you can't concatenate strings to characters and numbers, you're 
loosing expressiveness.


> For printing a list with a delimiter how about format?
> 
> CL-USER 3 > (format nil "~{~A~^ ~}" (list 1 2 3 4 5))
> "1 2 3 4 5"

True enough, the tool is there and deserve to be used.

Then again when scaling to large nested trees/function calls this won't 
make it. And this time we're loosing readability. I tend to use format 
only for the lower levels.

Anyways that's a matter of taste, to each his own style =)

Sacha
From: Maciej Katafiasz
Subject: Re: request comments: simple c program builder in lisp
Date: 
Message-ID: <g33bit$dpe$1@news.net.uni-c.dk>
Den Sat, 14 Jun 2008 19:06:24 -0700 skrev parth.malwankar:
> For now I have upgraded to using structs. As Sasha suggested I would
> probably upgrade to classes as I add this app evolves some more.

Note that you can dispatch methods just fine on structs, they are types 
too.

Cheers,
Maciej
From: Madhu
Subject: Re: request comments: simple c program builder in lisp
Date: 
Message-ID: <m3d4mjy7j7.fsf@moon.robolove.meer.net>
* Maciej Katafiasz <············@news.net.uni-c.dk> :
Wrote on Sat, 14 Jun 2008 17:07:07 +0000 (UTC):
|> I think you can ignore most of Leslie P. Polzer's style advice safely as
|> his personal style preference.
|
| On the contrary, most of his remarks are spot on, very little is what I'd 
| consider "personal style preference".

I hope the OP can recognize that all the points touched on are matters
of taste, and is also competenet enough to distinguish advocacy for bad
taste from more useful coding guidelines if any show up on this thread.

--
Madhu
From: Pascal J. Bourguignon
Subject: Re: request comments: simple c program builder in lisp
Date: 
Message-ID: <87abho6ll1.fsf@hubble.informatimago.com>
···············@gmail.com writes:
> ;;; ===================
> ;;; File Representation
> ;;; ===================

Good.


> (defun make-file (&key name (deps nil) (type nil))
>   (list :name name :deps deps :type (filetype? name)))

So far, so good.


> (defun program (target sources)
>   "top level program creation function. returns a tree that represents
>    files and their dependencies leading to the toplevel program."
>   (let ((target-file (make-file :name target)))
>     (setf (getf target-file :deps) (make-file-list sources))
>     (setf (getf target-file :type) 'exe)

Why do you commit to this representation?  Are you sure this data
structure is the most adapted?


It would be better of you introduced some abstraction here.

One easy way to do so is to use DEFSTRUCT.

(defstruct file name deps type)

will provide you with a constructor make-file, accessors file-name,
file-deps, file-type, and other niceties.

If you want to keep the data as lists: 
   (defstruct (file (:type list)) name deps type)
If you prefer vectors:
   (defstruct (file (:type vector)) name deps type)

But structures are ok, since they have a print-readably 
representation:
#S(FILE :NAME "test.c" :DEPS (#S(FILE :NAME "test.h" :DEPS NIL :TYPE C-HEADER)) :TYPE C-SOURCE)

If you need to do some special processing in the constructor, or in
the copier, or the printer you can also define it:
(defstruct (file (:constructor %make-file)) ...)
(defun make-file (&key name deps type)
  (%make-file :name name :deps deps :type (or type (filetype? name))))





But now, since the rest of your program will be using these functional
abstractions: make-file, file-name, file-deps, file-type, ..., it will
be independant of your representation choices.





If you need to use CLOS objects instead of lists, you will only
replace the defstruct form by:

(defclass file ()
   ((name :accessor file-name :initarg :name :type string)
    (deps :accessor file-deps :initarg :deps :initform '() :type list)
    (type :accessor file-type :initarg :initform '())))
(defmethod initialize-instance :after ((self file) &rest initargs &key &allow-other-keys)
  (setf (file-type self) (or (file-type self) (filetype? (file-name self))))
  self)

and done.


-- 
__Pascal Bourguignon__                     http://www.informatimago.com/

COMPONENT EQUIVALENCY NOTICE: The subatomic particles (electrons,
protons, etc.) comprising this product are exactly the same in every
measurable respect as those used in the products of other
manufacturers, and no claim to the contrary may legitimately be
expressed or implied.
From: Rob Warnock
Subject: Re: request comments: simple c program builder in lisp
Date: 
Message-ID: <OOydncp6db45acnVnZ2dnUVZ_sLinZ2d@speakeasy.net>
<···············@gmail.com> wrote:
+---------------
| I am fairly new to Lisp and am trying to write a simple build tool
| for C files in order to learn lisp. I am not really interested in any
| advanced features for the build system yet as the main intent is to
| learn Lisp.
+---------------

Understood, and *welcome*!

That said, at some point [though probably not now!] you should
take a look at the various packages/libraries listed here:

    http://www.cliki.net/development

and especially at these:

    http://www.cliki.net/asdf          [currently quite popular]
    Another System Definition Facility

    http://www.cliki.net/mk-defsystem  [much older, but still used by some]
    MK-defsystem is a system definition utility; it fills a similar
    role for CL Development as make(1) does for C. ...

Note that ASDF (and possibly MK-DEFSYSTEM?) already has some hooks for
C source files, though you will have to define your own specializations
of the PERFORM method for the COMPILE-OP and LOAD-OP operations, as it
tends to be very application- and/or platform- and/or implementation-
dependent. [E.g. What do you want to *do* with your compiled C code?
Run it?  Build a DSO & link to it?] But several of the readily-available
CL libraries have useful examples in their ".asd" files:

    http://www.cliki.net/Osicat
    http://www.cliki.net/Linedit
    http://www.cliki.net/db-sockets
    http://www.cliki.net/CL-Ncurses

They're all pretty similar in their handling of C files. The main
thing you might have to do is tweak the RUN-SHELL-COMMAND calls
[and/or extend the definition of RUN-SHELL-COMMAND in "asdf.lisp"
if your platform or CL implementation isn't supported].


-Rob

-----
Rob Warnock			<····@rpw3.org>
627 26th Avenue			<URL:http://rpw3.org/>
San Mateo, CA 94403		(650)572-2607
From: Marco Antoniotti
Subject: Re: request comments: simple c program builder in lisp
Date: 
Message-ID: <834f2acc-bcfa-48f9-ac83-3e5329858433@8g2000hse.googlegroups.com>
On 15 Giu, 12:53, ····@rpw3.org (Rob Warnock) wrote:
> <···············@gmail.com> wrote:
>
> +---------------
> | I am fairly new to Lisp and am trying to write a simple build tool
> | for C files in order to learn lisp. I am not really interested in any
> | advanced features for the build system yet as the main intent is to
> | learn Lisp.
> +---------------
>
> Understood, and *welcome*!
>
> That said, at some point [though probably not now!] you should
> take a look at the various packages/libraries listed here:
>
>    http://www.cliki.net/development
>
> and especially at these:
>
>    http://www.cliki.net/asdf         [currently quite popular]
>     Another System Definition Facility
>
>    http://www.cliki.net/mk-defsystem [much older, but still used by some]
>     MK-defsystem is a system definition utility; it fills a similar
>     role for CL Development as make(1) does for C. ...
>
> Note that ASDF (and possibly MK-DEFSYSTEM?) already has some hooks for
> C source files,

MK:DEFSYSTEM does C and Fortran out of the box.  AFAIK Matlisp is
still compiled from scratch with MK:DEFSYSTEM as it needs to compile
largish Fortran libraries.

Cheers
--
Marco
From: Thomas A. Russ
Subject: Re: request comments: simple c program builder in lisp
Date: 
Message-ID: <ymi7icprxpz.fsf@blackcat.isi.edu>
···············@gmail.com writes:


Since a lot of commentary has already occurred, I'll just pick and
choose a few items (some of which are a bit redundant).

> Source Code Listing
> ===================

> (defun concat-strings-from-list (l)
>   "take all strings in the list and concatenate them
>    adding a space in between.
>    e.g.: ('aa' 'bb' 'cc') => 'aa bb cc'"
>   (reduce #'(lambda (x y) (concatenate 'string x " " y))
>           l))

There is also the FORMAT option, already mentioned:

    (format nil "~{~A~^ ~}" list)

This has the additional advantage that it will work with lists of
arbitrary objects and not just strings.  So symbols, numbers, etc will
also work easily.

If you want a more general purpose function, you could write something
like the following, which is actually a bit simpler to write using the
reduce method:

   (defun format-list (list &optional (delimiter " "))
     "Formats LIST items separated by DELIMITER"
     (reduce #'(lambda (x y) (concatenate 'string x delimiter y))
             list :initial-value ""))

This then can be used more generally.  Also, if you add the
:INITIAL-VALUE keyword, then the function will work correctly even if
you pass in NIL as the list to concatenate.  It will then return just
the empty string instead of signaling an error.

To do the same thing in format is a bit uglier, since you would need to
construct the format string as part of applying the function:

   ... (format (concatenate 'string "~{~A~^" delimiter "~}") ...)

> (defun find-last-subst-pos-re (sub str)
>   "returns position of last sub-string.
>    e.g.: 'xx' 'abcxxyy' => 3"
>   (let ((m (all-matches sub str)))
>     (first (last (butlast m)))))

If this is truly just looking for the substring rather than the
occurrence of of a regular expression, there is a more straightforward
method using built-in Common Lisp forms.  And if you really mean it to
be regular expressions, the documentation string should say that rather
than "sub-string".

  (search sub str :from-end t)

> ;;; ================================
> ;;; File Extension and Type Handling
> ;;; ================================
> 
> (defun extention-p (ext name)
>   "return if name ends in ext. used to check file extension"
>   (let ((ext-len (length ext)))
>     (equal ext (subseq name (- (length name) ext-len)))))

Should be called something like HAS-EXTENSION or EXTENSION-MATCHES.

I would also write another helper function called GET-EXTENSION that
attempts to just return the extension of a file.  This would typically
just look for the last "." in the name and return whatever starts there:

(defun get-extension (name)
  "Returns the extension part of "name".
  (let ((extension-start (position #/. name :from-end t)))
    (when extension-start
       (subseq name extension-start))))

Or even better, you could use the Common Lisp pathname functions and use
PATHNAME-TYPE to get the extension.  That will also insulate more of
your code from the underlying operating system's syntax for indicating
file types.  Then you would simply be able to write

  (defun has-extension (name ext)
    (string= ext (pathname-type name)))

Note that you would then have to use "c" rather than ".c" as the
extension name.  But this is a lot simpler.


> (defun filetype-p (name type)
>   "check if file is of a 'type'"
>   (cond ((equal type 'cfile) (extention-p ".c" name))
>         ((equal type 'ofile) (extention-p ".o" name))
>         ((equal type 'hfile) (extention-p ".h" name))
>         (t nil)))
> 
> (defun filetype? (name)
>   "return the 'type' of file based on extension"
>   (cond ((extention-p ".c" name) 'cfile)
>         ((extention-p ".o" name) 'ofile)
>         ((extention-p ".h" name) 'hfile)
>         (t nil)))

As has been noted, this would be better handled by using a declarative
data structure such as an association list to store the correspondences:

(defparameter *extension-mapping*
              '(("c" . cfile) ("o" . ofile) ("h" . hfile)))

This then lets you write some very simple functions to get items out of
the data structure.  By using this declarative approach, you are also
assured that any changes to make will always be reflected in the
underlying code, since you won't have to remember to update both
functions.

(defun get-file-type (name)
  (cdr (assoc (pathname-type name) *extension-mapping*
       :test #'string=)))

(defun get-file-extension (type)
  (car (rassoc type *extension-mapping*)))

(defun has-file-type (name type)
  (eql (get-file-type name) type))

The general theme is to break a lot of this down to very simple
functions that are generally applicable and that also build on one
another.

> ;;; ===================
> ;;; File Representation
> ;;; ===================

If you change the direct file representation to be pathnames instead of
just strings, then some of these manipulations would be easier.

> (defun make-file (&key name (deps nil) (type nil))
>   (list :name name :deps deps :type (filetype? name)))

I would use this to convert NAME to a pathname object with 
   (pathname name)

> (defun make-file-list (names)
>   (mapcar #'(lambda (x) (get-intermediate-file (make-file :name x)))
> names))
> 
> (defun get-intermediate-file (file)
>   "takes a file and generates a plist for the possible intermediate
>    file once this is processed. For example, for a test.c plist
>    it will create a test.o plist. When there is no intermediate file,
>    input is returned"
>   (let ((name (getf file :name))
>         (type (getf file :type)))
>     (cond ((equal type 'cfile) (make-file
>                                  :name (search-replace-last-re "\.c"
> "\.o" name)
>                                  :deps (list file)
>                                  :type 'ofile))
>           (t file))))

Here you might also want to build a data structure similar to the one
used to map file extensions to types and use that to contain the mapping
from files to successor types.  That then makes the function easily
extensible and keeps the correspondence between successors where you can
easily see it, instead of buried inside the code for this function.

Note that this assume you convert file names to pathnames.

(defparameter *file-successors* '((cfile . ofile)))

(defun change-file-type (name new-type)
  (let ((new-name (pathname (namestring name))))
    (setf (pathname-type new-name) (get-file-extension new-type))
    new-name))

(defun get-successor-file (file)
  (let ((name (getf file :name))
        (next-type (cdr (assoc (getf file :type) *file-successors*))))
     (if next-type
         (make-file :name (change-file-type name next-type)
                    :deps (list file)
                    :type next-type)
         file)))

> 
> ; Usage:
> ;   (setf *p* (program "test" '("a.c" "b.c" "c.d")))
> ;   (build *p*)
> 
> (defun program (target sources)
>   "top level program creation function. returns a tree that represents
>    files and their dependencies leading to the toplevel program."
>   (let ((target-file (make-file :name target)))
>     (setf (getf target-file :deps) (make-file-list sources))
>     (setf (getf target-file :type) 'exe)
>     target-file))
> 
> (defun build (program)
>   (let ((deps (getf program :deps))
>         (name (getf program :name))
>         (type (getf program :type)))
>     (cond ((null deps) (format t "[build] ~A: " name)
>                        (build-file program))
>           (t (mapcar #'build deps)
>              (format t "[build] ~A: " name)
>              (build-file program)))))
> 
> ; "test.c" => "test.o"
> (defun cfile->ofile (name)
>   (search-replace-last-re "\.c" "\.o" name))
> 
> ; deps-(<file0>, <file1>, <file2>) => ("depname0" "depname1"
> "depname2")
> (defun get-dep-names (file)
>   (let ((deps (getf file :deps)))
>     (mapcar #'(lambda (x) (getf x :name)) deps)))
> 
> (defun build-file (file)
>   "print the string needed to build the file based on file type"
>   (let ((type (getf file :type))
>         (name (getf file :name))
>         (deps (getf file :deps)))
>     (cond ((equal type 'cfile)
>            (format t "~A~%" (concat-strings-from-list
>                               (list "gcc -o" (cfile->ofile name)
> name))))
>           ((equal type 'ofile)
>            (format t "nothing to do: ~A~%" name))
>           ((equal type 'exe)
>            (format t "~A~%" (concatenate 'string
>                                          "gcc -o "
>                                          name " "
>                                          (concat-strings-from-list
>                                            (get-dep-names file)))))
>           (t (format t "nothing to do: ~A~%" name)))))
> 

-- 
Thomas A. Russ,  USC/Information Sciences Institute
From: ···············@gmail.com
Subject: Re: request comments: simple c program builder in lisp
Date: 
Message-ID: <640680be-879f-496e-a90e-f3ccf4465289@w34g2000prm.googlegroups.com>
On Jun 17, 12:22 am, ····@sevak.isi.edu (Thomas A. Russ) wrote:
> ···············@gmail.com writes:
>
> Since a lot of commentary has already occurred, I'll just pick and
> choose a few items (some of which are a bit redundant).
>
> > Source Code Listing
> > ===================
> > (defun concat-strings-from-list (l)
> >   "take all strings in the list and concatenate them
> >    adding a space in between.
> >    e.g.: ('aa' 'bb' 'cc') => 'aa bb cc'"
> >   (reduce #'(lambda (x y) (concatenate 'string x " " y))
> >           l))
>
> There is also the FORMAT option, already mentioned:
>
>     (format nil "~{~A~^ ~}" list)
>
> This has the additional advantage that it will work with lists of
> arbitrary objects and not just strings.  So symbols, numbers, etc will
> also work easily.
>
> If you want a more general purpose function, you could write something
> like the following, which is actually a bit simpler to write using the
> reduce method:
>
>    (defun format-list (list &optional (delimiter " "))
>      "Formats LIST items separated by DELIMITER"
>      (reduce #'(lambda (x y) (concatenate 'string x delimiter y))
>              list :initial-value ""))
>
> This then can be used more generally.  Also, if you add the
> :INITIAL-VALUE keyword, then the function will work correctly even if
> you pass in NIL as the list to concatenate.  It will then return just
> the empty string instead of signaling an error.
>
> To do the same thing in format is a bit uglier, since you would need to
> construct the format string as part of applying the function:
>
>    ... (format (concatenate 'string "~{~A~^" delimiter "~}") ...)
>
> > (defun find-last-subst-pos-re (sub str)
> >   "returns position of last sub-string.
> >    e.g.: 'xx' 'abcxxyy' => 3"
> >   (let ((m (all-matches sub str)))
> >     (first (last (butlast m)))))
>
> If this is truly just looking for the substring rather than the
> occurrence of of a regular expression, there is a more straightforward
> method using built-in Common Lisp forms.  And if you really mean it to
> be regular expressions, the documentation string should say that rather
> than "sub-string".
>
>   (search sub str :from-end t)
>
> > ;;; ================================
> > ;;; File Extension and Type Handling
> > ;;; ================================
>
> > (defun extention-p (ext name)
> >   "return if name ends in ext. used to check file extension"
> >   (let ((ext-len (length ext)))
> >     (equal ext (subseq name (- (length name) ext-len)))))
>
> Should be called something like HAS-EXTENSION or EXTENSION-MATCHES.
>
> I would also write another helper function called GET-EXTENSION that
> attempts to just return the extension of a file.  This would typically
> just look for the last "." in the name and return whatever starts there:
>
> (defun get-extension (name)
>   "Returns the extension part of "name".
>   (let ((extension-start (position #/. name :from-end t)))
>     (when extension-start
>        (subseq name extension-start))))
>
> Or even better, you could use the Common Lisp pathname functions and use
> PATHNAME-TYPE to get the extension.  That will also insulate more of
> your code from the underlying operating system's syntax for indicating
> file types.  Then you would simply be able to write
>
>   (defun has-extension (name ext)
>     (string= ext (pathname-type name)))
>
> Note that you would then have to use "c" rather than ".c" as the
> extension name.  But this is a lot simpler.
>
> > (defun filetype-p (name type)
> >   "check if file is of a 'type'"
> >   (cond ((equal type 'cfile) (extention-p ".c" name))
> >         ((equal type 'ofile) (extention-p ".o" name))
> >         ((equal type 'hfile) (extention-p ".h" name))
> >         (t nil)))
>
> > (defun filetype? (name)
> >   "return the 'type' of file based on extension"
> >   (cond ((extention-p ".c" name) 'cfile)
> >         ((extention-p ".o" name) 'ofile)
> >         ((extention-p ".h" name) 'hfile)
> >         (t nil)))
>
> As has been noted, this would be better handled by using a declarative
> data structure such as an association list to store the correspondences:
>
> (defparameter *extension-mapping*
>               '(("c" . cfile) ("o" . ofile) ("h" . hfile)))
>
> This then lets you write some very simple functions to get items out of
> the data structure.  By using this declarative approach, you are also
> assured that any changes to make will always be reflected in the
> underlying code, since you won't have to remember to update both
> functions.
>
> (defun get-file-type (name)
>   (cdr (assoc (pathname-type name) *extension-mapping*
>        :test #'string=)))
>
> (defun get-file-extension (type)
>   (car (rassoc type *extension-mapping*)))
>
> (defun has-file-type (name type)
>   (eql (get-file-type name) type))
>
> The general theme is to break a lot of this down to very simple
> functions that are generally applicable and that also build on one
> another.
>
> > ;;; ===================
> > ;;; File Representation
> > ;;; ===================
>
> If you change the direct file representation to be pathnames instead of
> just strings, then some of these manipulations would be easier.
>
> > (defun make-file (&key name (deps nil) (type nil))
> >   (list :name name :deps deps :type (filetype? name)))
>
> I would use this to convert NAME to a pathname object with
>    (pathname name)
>
>
>
> > (defun make-file-list (names)
> >   (mapcar #'(lambda (x) (get-intermediate-file (make-file :name x)))
> > names))
>
> > (defun get-intermediate-file (file)
> >   "takes a file and generates a plist for the possible intermediate
> >    file once this is processed. For example, for a test.c plist
> >    it will create a test.o plist. When there is no intermediate file,
> >    input is returned"
> >   (let ((name (getf file :name))
> >         (type (getf file :type)))
> >     (cond ((equal type 'cfile) (make-file
> >                                  :name (search-replace-last-re "\.c"
> > "\.o" name)
> >                                  :deps (list file)
> >                                  :type 'ofile))
> >           (t file))))
>
> Here you might also want to build a data structure similar to the one
> used to map file extensions to types and use that to contain the mapping
> from files to successor types.  That then makes the function easily
> extensible and keeps the correspondence between successors where you can
> easily see it, instead of buried inside the code for this function.
>
> Note that this assume you convert file names to pathnames.
>
> (defparameter *file-successors* '((cfile . ofile)))
>
> (defun change-file-type (name new-type)
>   (let ((new-name (pathname (namestring name))))
>     (setf (pathname-type new-name) (get-file-extension new-type))
>     new-name))
>
> (defun get-successor-file (file)
>   (let ((name (getf file :name))
>         (next-type (cdr (assoc (getf file :type) *file-successors*))))
>      (if next-type
>          (make-file :name (change-file-type name next-type)
>                     :deps (list file)
>                     :type next-type)
>          file)))
>
>

Thanks Thomas. Coming from other languages pathnames doesn't come
naturally to be (yet!) but this makes a lot of sense. :)

I have updated the code to use pathnames and also generalized
 using *file-successors*.
Was able to get rid of regex as this was being used only for
file extension which is easier and more reliable with pathname.

Parth

>
>
>
> > ; Usage:
> > ;   (setf *p* (program "test" '("a.c" "b.c" "c.d")))
> > ;   (build *p*)
>
> > (defun program (target sources)
> >   "top level program creation function. returns a tree that represents
> >    files and their dependencies leading to the toplevel program."
> >   (let ((target-file (make-file :name target)))
> >     (setf (getf target-file :deps) (make-file-list sources))
> >     (setf (getf target-file :type) 'exe)
> >     target-file))
>
> > (defun build (program)
> >   (let ((deps (getf program :deps))
> >         (name (getf program :name))
> >         (type (getf program :type)))
> >     (cond ((null deps) (format t "[build] ~A: " name)
> >                        (build-file program))
> >           (t (mapcar #'build deps)
> >              (format t "[build] ~A: " name)
> >              (build-file program)))))
>
> > ; "test.c" => "test.o"
> > (defun cfile->ofile (name)
> >   (search-replace-last-re "\.c" "\.o" name))
>
> > ; deps-(<file0>, <file1>, <file2>) => ("depname0" "depname1"
> > "depname2")
> > (defun get-dep-names (file)
> >   (let ((deps (getf file :deps)))
> >     (mapcar #'(lambda (x) (getf x :name)) deps)))
>
> > (defun build-file (file)
> >   "print the string needed to build the file based on file type"
> >   (let ((type (getf file :type))
> >         (name (getf file :name))
> >         (deps (getf file :deps)))
> >     (cond ((equal type 'cfile)
> >            (format t "~A~%" (concat-strings-from-list
> >                               (list "gcc -o" (cfile->ofile name)
> > name))))
> >           ((equal type 'ofile)
> >            (format t "nothing to do: ~A~%" name))
> >           ((equal type 'exe)
> >            (format t "~A~%" (concatenate 'string
> >                                          "gcc -o "
> >                                          name " "
> >                                          (concat-strings-from-list
> >                                            (get-dep-names file)))))
> >           (t (format t "nothing to do: ~A~%" name)))))
>
> --
> Thomas A. Russ,  USC/Information Sciences Institute