From: Jordan Katz
Subject: parsing configuration files with Lisp.
Date: 
Message-ID: <m3y9g0oeh4.fsf@underlevel.underlevel.net>
Hi,

  I'm trying to write a very simple utility to parse a config file of
  this format (more on the format later):

    -name
    "<html><body>some HTML code.."

  The goal is to associate "name" with the code that follows it.  The
  config file will only be a series of these names with their HTML
  snippets.

  I'm having trouble finding a neat way to do this.  This is my
  initial attempt:

  (defparameter *tag-to-content* nil)

  (defun read-config (filename)
    (let* ((config-path (make-pathname :name filename))
           (a-tag "-"))
      (with-open-file (config-in config-path :direction :input)
        (let* ((line (read-line config-in nil 'eof)))
          (if (string= a-tag (aref line 0))
              (progn
                ;;
                ;; I thought it would be silly to have yet another let
                ;; here, but I don't need tag-name and tag-content
                ;; to be global.
                ;;
                (setf tag-name (subseq line 1))
                (setf tag-content (read-line config-in nil 'eof))
                (print (cons (cons tag-name tag-content)
                      *tag-to-content*))))))))

  (read-config "site.config")


  This "works" because it prints

  (("name" . "\"<html><body>some HTML code..\""))

  But it has many problems that I don't know how to solve, the most
  important one being how would I go about handling more than one
  -name label neatly, (I'm sure there are much cleaner ways than my
  pathetic hack) and how would I neatly handle HTML snippets that span
  over more than one line?

  And finally, I've been hearing a lot that one of Lisp's great
  advantages is that it can be very easy to create "sub" languages for
  it to parse that (usually?) have Lisp-like syntax.  Because of this,
  I initially wanted my configuration file to look like this:

  (define-label name "HTML snippet here")

  But I have no idea how to work with that.  I thought maybe I could
  defun the define-label function in my code and then use (read)
  instead of read-line but I doubt that's the way--regardless, is this
  approach better?  And assuming I use it, what elements of the
  language do I have to look at to go about parsing it?

Thanks a lot for the help, sorry for asking so many questions at once :),
-- 
Jordan Katz <····@underlevel.net>  |  Mind the gap

From: Nils Goesche
Subject: Re: parsing configuration files with Lisp.
Date: 
Message-ID: <873cy8d6nv.fsf@darkstar.cartan>
Jordan Katz <····@underlevel.net> writes:

>   And finally, I've been hearing a lot that one of Lisp's great
>   advantages is that it can be very easy to create "sub" languages for
>   it to parse that (usually?) have Lisp-like syntax.  Because of this,
>   I initially wanted my configuration file to look like this:
> 
>   (define-label name "HTML snippet here")
> 
>   But I have no idea how to work with that.  I thought maybe I could
>   defun the define-label function in my code and then use (read)
>   instead of read-line but I doubt that's the way--regardless, is this
>   approach better?  And assuming I use it, what elements of the
>   language do I have to look at to go about parsing it?

Why don't you simply do something like

(defvar *config* '((foo . "blark")
                   (bar . "quux")))

(defun save-config (filename)
  (with-open-file (str filename
                       :direction :output
                       :if-exists :supersede)
    (pprint *config* str)))

(defun load-config (filename)
  (with-open-file (str filename)
    (setq *config* (read str))))


Regards,
-- 
Nils Goesche
Ask not for whom the <CONTROL-G> tolls.

PGP key ID #xC66D6E6F
From: Jordan Katz
Subject: Re: parsing configuration files with Lisp.
Date: 
Message-ID: <m3pu1cobfm.fsf@underlevel.underlevel.net>
Nils Goesche <···@cartan.de> writes:

> Jordan Katz <····@underlevel.net> writes:
> 
> >   And finally, I've been hearing a lot that one of Lisp's great
> >   advantages is that it can be very easy to create "sub" languages for
> >   it to parse that (usually?) have Lisp-like syntax.  Because of this,
> >   I initially wanted my configuration file to look like this:
> > 
> >   (define-label name "HTML snippet here")
> > 
> >   But I have no idea how to work with that.  I thought maybe I could
> >   defun the define-label function in my code and then use (read)
> >   instead of read-line but I doubt that's the way--regardless, is this
> >   approach better?  And assuming I use it, what elements of the
> >   language do I have to look at to go about parsing it?
> 
> Why don't you simply do something like
> 
> (defvar *config* '((foo . "blark")
>                    (bar . "quux")))
> 
> (defun save-config (filename)
>   (with-open-file (str filename
>                        :direction :output
>                        :if-exists :supersede)
>     (pprint *config* str)))
> 
> (defun load-config (filename)
>   (with-open-file (str filename)
>     (setq *config* (read str))))

Because the configuration file is written by a regular user with a
text editor, not from the program itself.  I want someone to be able
to save site.config and have my program grok it.

Thanks a lot, 
-- 
Jordan Katz <····@underlevel.net>  |  Mind the gap
From: Wade Humeniuk
Subject: Re: parsing configuration files with Lisp.
Date: 
Message-ID: <a8nr6p$cok$1@news3.cadvision.com>
"Jordan Katz" <····@underlevel.net> wrote in message
···················@underlevel.underlevel.net...
> Because the configuration file is written by a regular user with a
> text editor, not from the program itself.  I want someone to be able
> to save site.config and have my program grok it.
>
> Thanks a lot,

Why is it easier for the user to type

 -name
    "<html><body>some HTML code.."


instead of

(name "<html><body>some HTML code..")

?

The first method above is full of potential errors that your parsing
routines have to account for, the second (Lisp) format is fully protected by
the reader's error detection.

Basically I am saying, why make your life difficult when it does not matter
to the user?

Wade
From: Kenny Tilton
Subject: Re: parsing configuration files with Lisp.
Date: 
Message-ID: <3CAF612C.80F48806@nyc.rr.com>
Can your user find these keys?: ( . ) "

<g>

-- 

 kenny tilton
 clinisys, inc
 ---------------------------------------------------------------
"Harvey has overcome not only time and space but any objections."
                                                        Elwood P. Dowd
From: Nils Goesche
Subject: Re: parsing configuration files with Lisp.
Date: 
Message-ID: <87y9g0bq81.fsf@darkstar.cartan>
Jordan Katz <····@underlevel.net> writes:

> Nils Goesche <···@cartan.de> writes:
> 
> > Why don't you simply do something like
> > 
> > (defvar *config* '((foo . "blark")
> >                    (bar . "quux")))
> > 
> > (defun save-config (filename)
> >   (with-open-file (str filename
> >                        :direction :output
> >                        :if-exists :supersede)
> >     (pprint *config* str)))
> > 
> > (defun load-config (filename)
> >   (with-open-file (str filename)
> >     (setq *config* (read str))))
> 
> Because the configuration file is written by a regular user with a
> text editor, not from the program itself.  I want someone to be able
> to save site.config and have my program grok it.

Well, you could READ in seperate forms in a loop then; look at
the car of each to know what kind of form it is and interpret it
somehow; where is the problem?

Regards,
-- 
Nils Goesche
Ask not for whom the <CONTROL-G> tolls.

PGP key ID #xC66D6E6F
From: Lieven Marchand
Subject: Re: parsing configuration files with Lisp.
Date: 
Message-ID: <m34rio8qk9.fsf@localhost.localdomain>
Jordan Katz <····@underlevel.net> writes:

>   (defparameter *tag-to-content* nil)

Consider whether you need a global variable to keep track of this. You
could just accumulate this internally in your function and return the
result.

>   (defun read-config (filename)
>     (let* ((config-path (make-pathname :name filename))

This is cute and our resident pathname spec debaters will love you for
it but with-open-file can take a string and will do the pathname
parsing by itself.

>            (a-tag "-"))
>       (with-open-file (config-in config-path :direction :input)
>         (let* ((line (read-line config-in nil 'eof)))

let* is a bit overkill here since you only introduce one
variable. It's ok though, there are reasons why some people take let*
as the default of the let/let* pair.

>           (if (string= a-tag (aref line 0))

This works, but I guess you don't realize why. You compare a string of
length one to a character, and the string comparison functions will
convert a character to a string of length one. You could use char= and
change your definition of a-tag to match or use the :end2 keyword
argument of string= to keep it correct if you define a-tag to be "+++"
e.g.

>               (progn
>                 ;;
>                 ;; I thought it would be silly to have yet another let
>                 ;; here, but I don't need tag-name and tag-content
>                 ;; to be global.
>                 ;;
>                 (setf tag-name (subseq line 1))
>                 (setf tag-content (read-line config-in nil 'eof))

This assigns to variables you haven't defined. In most implementations
it will create special (global) variables but it's not a good
idea. You could use PROG to create those scoped variables, to get an
oldtimer feel to your code, introduce a let or just put them in the
let* that created line. In that case you might want to use WHEN
instead of IF to be able to use multiple forms in the implicit progn.

>                 (print (cons (cons tag-name tag-content)
>                       *tag-to-content*))))))))
> 
>   (read-config "site.config")

This gives us

(defun read-config (filename)
  (let* ((a-tag #\-)
         (tag-to-content '()))
    (with-open-file (config-in filename :direction :input)
      (let* ((line (read-line config-in nil 'eof))
             (tag-name "")
             (tag-content ""))
        (when (char= a-tag (aref line 0))
          (setf tag-name (subseq line 1))
          (setf tag-content (read-line config-in nil 'eof))
          (push (cons tag-name tag-content) tag-to-content))))
    tag-to-content))

>   This "works" because it prints
> 
>   (("name" . "\"<html><body>some HTML code..\""))
> 
>   But it has many problems that I don't know how to solve, the most
>   important one being how would I go about handling more than one
>   -name label neatly, (I'm sure there are much cleaner ways than my
>   pathetic hack) and how would I neatly handle HTML snippets that span
>   over more than one line?

Just start looping over the above. Since I'm not sure whether this is
homework, I'll only give you a hint. loop reading the file line by
line, if it starts a new tag, output the previously saved name +
collected content, otherwise add the line to the content being
collected.

>   And finally, I've been hearing a lot that one of Lisp's great
>   advantages is that it can be very easy to create "sub" languages for
>   it to parse that (usually?) have Lisp-like syntax.  Because of this,
>   I initially wanted my configuration file to look like this:
> 
>   (define-label name "HTML snippet here")
> 
>   But I have no idea how to work with that.  I thought maybe I could
>   defun the define-label function in my code and then use (read)
>   instead of read-line but I doubt that's the way--regardless, is this
>   approach better?  And assuming I use it, what elements of the
>   language do I have to look at to go about parsing it?

You would typically compile the configuration file and defining
define-label as a macro.

(defvar *label-definitions* '())

(defmacro define-label (name value)
    `(push (cons ,name ,value) *label-definitions*))

-- 
Lieven Marchand <···@wyrd.be>
She says, "Honey, you're a Bastard of great proportion."
He says, "Darling, I plead guilty to that sin."
Cowboy Junkies -- A few simple words
From: Jordan Katz
Subject: Re: parsing configuration files with Lisp.
Date: 
Message-ID: <m3elhphcp2.fsf@underlevel.underlevel.net>
Lieven Marchand <···@wyrd.be> writes:

[snip other comments]
> You would typically compile the configuration file and defining
> define-label as a macro.
> 
> (defvar *label-definitions* '())
> 
> (defmacro define-label (name value)
>     `(push (cons ,name ,value) *label-definitions*))

That's initially what I was thinking of, and I was under the
misconception that read evaluates the object it reads, so
(define-label foo "bar") in the configuration would call (define-label
...) in my source, but that's clearly not the case.

Thanks for your other comments and for everyone else who responded, I
now have a solution to my problem.
-- 
Jordan Katz <····@underlevel.net>  |  Mind the gap