#+TITLE: awful fur sock : rmoo room parser
#+AUTHOR: screwlisp
It's messy, but we made it.

The gist is that we're just processing a string, offline.

The string happens to be a room description a la the lambdamoo
living room (#17).

Honestly, I don't understand why there can be two separate lines
of items present.

#+BEGIN_QUOTE
It works on my machine
#+END_QUOTE
* awul fur sock
It's solderpunk's OFFLine-FIrst sOftware CHallenge.

And with many apologies.

* Some emacs org-mode stuff towards this working
** Register lisp with org-mode
#+begin_src elisp :results none
 (org-babel-do-load-languages 'org-babel-load-languages
                              '((lisp . t)))
#+end_src

** Start slime
#+begin_src elisp
 (slime)
#+end_src

Hopefully you had slime.

* rmoo mode room description parser
** Example text
Basically I want to change something like this:
#+name: living-room
#+BEGIN_VERSE
The Living Room
It is very bright, open, and airy here, with large plate-glass windows looking southward over the
pool to the gardens beyond.  On the north wall, there is a rough stonework fireplace.  The east
and west walls are almost completely covered with large, well-stocked bookcases.  An exit in the
northwest corner leads to the kitchen and, in a more northerly direction, to the entrance hall.
The door into the coat closet is at the north end of the east wall, and at the south end is a
sliding glass door leading out onto a wooden deck.  There are two sets of couches, one clustered
around the fireplace and one with a view out the windows.
You see Welcome Poster, a fireplace, the living room couch, Statue, a map of LambdaHouse, Fun
Activities Board, Helpful Person Finder, lag meter, The Birthday Machine, and Cockatoo here.
YD (out on his feet), lisdude (out on his feet), and Nosredna are here.
You see jade ball here.
#+END_VERSE
** Example output, which is wrong now but what can you do.
I used a struct instead of alist.

Into this:
#+begin_src lisp
 '("The Living Room"
   "It is very bright, open, and airy here, with large plate-glass windows looking southward over the pool to the gardens beyond.  On the north wall, there is a rough stonework fireplace.  The east and west walls are almost completely covered with large, well-stocked bookcases.  An exit in the northwest corner leads to the kitchen and, in a more northerly direction, to the entrance hall.  The door into the coat closet is at the north end of the east wall, and at the south end is a sliding glass door leading out onto a wooden deck.  There are two sets of couches, one clustered around the fireplace and one with a view out the windows."
   ("Welcome Poster" "a fireplace" "the living room couch" "Statue" "a map of LambdaHouse" "Fun Activities Board" "Helpful Person Finder" "lag meter" "The Birthday Machine" "Cockatoo")
   (("YD" "out on his feet") ("lisdude" "out on his feet") ("Nosredna"))
   ("jade ball"))
#+end_src
Such that I can write a format control string to reconstitute the
original.

I don't think it's possible to distinguish the first list of items in
the room from the second list of items in the room, but I think this
is never important: Except that there are two lists of items when
there are two lists of items, which should be represented like that.

I didn't check out the living room's class (maybe just $room ?). That
will be for an ONLINE month followup.

Basically only the three contents sections are interesting: Important
items, people other than you, and other items. The two items sections
are basically the same and should only be dealt with once.

Each of these three contents have three non-trivial cases: one
element, two elements and three or more elements. Trivially, if there
are no elements no additional line is printed.
* Implementation
** Get rid of wrapped new lines in input text
#+begin_src lisp
 (defun un-linewrap (string
                     &aux
                       (sep (format nil "~% "))) "
 (un-linewrap string)
 idx = SEARCHes through string for \"~% \"
 princing subseq (1+ idx) to a new string, ie skipping the ~%.
 "
   (with-output-to-string (*standard-output*)
     (loop
       for bound = 0 then (1+ idx)
       for idx = (search sep string :start2 bound)
       while idx do
         (princ (subseq string bound idx))
       finally (princ (subseq string bound)))))
#+end_src

#+RESULTS:
: UN-LINEWRAP

*** Indication that it works
Basically remove nice line wrapping.
#+begin_src lisp :var text=living-room
 (un-linewrap  text)
#+end_src

#+RESULTS:
: The Living Room
: It is very bright, open, and airy here, with large plate-glass windows looking southward over the pool to the gardens beyond.  On the north wall, there is a rough stonework fireplace.  The east and west walls are almost completely covered with large, well-stocked bookcases.  An exit in the northwest corner leads to the kitchen and, in a more northerly direction, to the entrance hall.  The door into the coat closet is at the north end of the east wall, and at the south end is a sliding glass door leading out onto a wooden deck.  There are two sets of couches, one clustered around the fireplace and one with a view out the windows.
: You see Welcome Poster, a fireplace, the living room couch, Statue, a map of LambdaHouse, Fun Activities Board, Helpful Person Finder, lag meter, The Birthday Machine, and Cockatoo here.
: YD (out on his feet), lisdude (out on his feet), and Nosredna are here.
: You see jade ball here.

** parse item line
#+begin_src lisp
 (defun item-line-p (line &aux (item-lines-start "You see ")) "
 item-line-p
 Args:
       line - a string. Should be one line
               depicting items in a moo room.
 "
   (zerop (search item-lines-start line)))

 (defun parse-item-line (line &aux (item-lines-start "You see ")
                                (last-item-start "and ")
                                (sep ",")
                                (last-item-end " here.")) "
 parse-item-line
       line: a single line string from a $room (with many rules)
 does not attempt to deal with pathological cases including
 - items with ' and ' in their name.
 - weird commas similarly
 because it will be replaced with oop from moo later.
 "
   (setf line (subseq line (length item-lines-start)))
   (cond
     ((and (not (search last-item-start line))
           (not (search sep line)))
      "one item."
      (list (subseq line 0 (- (length line) (length last-item-end)))))
     ((and (search last-item-start line)
           (not (search sep line)))
      "Two items."
      (let ((first-item-ends (1- (search last-item-start line))))
        (list (subseq line 0 first-item-ends)
              (subseq line
                      (+ 1 first-item-ends
                         (length last-item-start))
                      (- (length line) (length last-item-end))))))


     ((search "," line)
      "Three or more items."
      (loop for idx = (search sep line)
            while idx
            collect (subseq line 0 idx) into items
            do (setf line (subseq line (1+ (1+ idx))))
            finally
               (let ((extra-item
                       (when (zerop (search last-item-start line))
                         (list
                          (subseq line
                                  (length last-item-start)
                                  (- (length line)
                                     (length last-item-end)))))))
                 (return (append items extra-item)))))))
#+end_src

#+RESULTS:
: PARSE-ITEM-LINE

*** Indication that it works
**** One item.
#+name: just-items-1
#+BEGIN_EXAMPLE
You see jade ball here.
#+END_EXAMPLE
***** Working.
#+begin_src lisp :var text=just-items-1
 (parse-item-line text)
#+end_src

#+RESULTS:
| jade ball |

**** Two items
#+name: just-items-2
#+BEGIN_EXAMPLE
You see jade ball and cup of tea here.
#+END_EXAMPLE
***** Working.
#+begin_src lisp :var text=just-items-2
 (parse-item-line text)
#+end_src

#+RESULTS:
| jade ball | cup of tea |

**** Three items
#+name: just-items-3
#+BEGIN_EXAMPLE
You see fruitcake, jade ball, and cup of tea here.
#+END_EXAMPLE
***** Working.
#+begin_src lisp :var text=just-items-3
 (parse-item-line text)
#+end_src

#+RESULTS:
| fruitcake | jade ball | cup of tea |

** parse player line
#+begin_src lisp
 (defun player-line-p (line &aux
                            (player-line-end1 " is here.")
                            (player-line-end2 " are here.")) "
 player-line-p
 Args:
       line - a string. Should be one line
               depicting players in a moo room.
 "
   (or (search player-line-end1 line)
       (search player-line-end2 line)))

 (defun parse-player-line (line
                           &aux
                             (player-line-end1 " is here.")
                             (player-line-end2 " are here.")
                             (sep ",")
                             (2player-sep "and ")
                             (more-player-sep ", and "))
   "
 parse-item-line
       line: a single line string from a $room (with many rules)
 does not attempt to deal with pathological cases including
 - items with ' and ' in their name.
 - weird commas similarly
 because it will be replaced with oop from moo later.
 "
   (flet ((no-description (player-string)
            (let ((idx (search " (" player-string)))
              (if idx
                  (subseq player-string 0 idx)
                  player-string))))
     (cond
       ((search player-line-end1 line)
        "one player."
        (let ((idx (search player-line-end1 line)))
          (list (no-description (subseq line 0 idx)))))

     ((and (search player-line-end2 line)
           (not (search sep line)))
      "Two players."
      (list (no-description
             (subseq line 0 (1- (search 2player-sep line))))
            (no-description
             (subseq
              line
              (+ (search 2player-sep line)
                 (length 2player-sep))
              (search player-line-end2 line)))))

     ((and (search player-line-end2 line) (search sep line))
      "Three or more players."
      (loop for idx = (search sep line)
            while idx
            collect (no-description (subseq line 0 idx))
              into items
            do (setf line (subseq line (1+ (1+ idx))))
            finally
               (let ((extra-item
                       (list
                        (subseq line
                                (length 2player-sep)
                                (- (length line)
                                   (length player-line-end2))))))
                 (return (append items extra-item))))))))
#+end_src

#+RESULTS:
: PARSE-PLAYER-LINE

*** Indication that it works
**** One player.
#+name: just-player-1
#+BEGIN_EXAMPLE
jeremy_list is here.
#+END_EXAMPLE
***** Working.
#+begin_src lisp :var text=just-player-1
 (parse-player-line text)
#+end_src

#+RESULTS:
| jeremy_list |

**** Two players
#+name: just-players-2
#+BEGIN_EXAMPLE
YD (out on his feet) and lisdude (out on his feet) are here.
#+END_EXAMPLE
***** Working.
#+begin_src lisp :var text=just-players-2
 (parse-player-line text)
#+end_src

#+RESULTS:
| YD | lisdude |

**** Three items
#+name: just-players-3
#+BEGIN_EXAMPLE
YD (out on his feet), lisdude (out on his feet), and Nosredna are here.
#+END_EXAMPLE
***** Working.
#+begin_src lisp :var text=just-players-3
 (parse-player-line text)
#+end_src

#+RESULTS:
| YD | lisdude | Nosredna |

** Do a whole room description.
#+begin_src lisp
 (defstruct mooroom
   (name nil :type (or null string))
   (description nil :type (or null string))
   (items nil :type list)
   (players nil :type list)
   (more-items nil :type list))
 (defun comprehend-room-description (string) "
 comprehend-room-description
 args:
       string - as from rmoo, a moo room description as from look
 "
   (setf string (un-linewrap string))
   (let* ((lines (with-input-from-string (in string)
                   (loop for line = (read-line in nil nil)
                         while line
                         collect line)))
          (item-slots '(:items :more-items))
          (collection-keys
            (loop for line in (cddr lines)
                  unless (player-line-p line)
                    collect (pop item-slots)
                  when (player-line-p line)
                    collect :players)))
     (apply 'make-mooroom
            :name (first lines)
            :description (second lines)
            (loop for line in (cddr lines)
                  for slot-key in collection-keys
                  for playerp = (equal slot-key :players)
                  for contents = (if playerp
                                     (parse-player-line line)
                                     (parse-item-line line))
                  nconcing `(,slot-key ,contents)))))

   )
#+end_src

#+RESULTS:
: COMPREHEND-ROOM-DESCRIPTION

*** Indication this is working.
#+begin_src lisp :var text=living-room
 (comprehend-room-description text)
#+end_src

#+RESULTS:
: #S(MOOROOM
:    :NAME "The Living Room"
:    :DESCRIPTION "It is very bright, open, and airy here, with large plate-glass windows looking southward over the pool to the gardens beyond.  On the north wall, there is a rough stonework fireplace.  The east and west walls are almost completely covered with large, well-stocked bookcases.  An exit in the northwest corner leads to the kitchen and, in a more northerly direction, to the entrance hall.  The door into the coat closet is at the north end of the east wall, and at the south end is a sliding glass door leading out onto a wooden deck.  There are two sets of couches, one clustered around the fireplace and one with a view out the windows."
:    :ITEMS ("Welcome Poster" "a fireplace" "the living room couch" "Statue"
:            "a map of LambdaHouse" "Fun Activities Board"
:            "Helpful Person Finder" "lag meter" "The Birthday Machine"
:            "Cockatoo")
:    :PLAYERS ("YD" "lisdude" "Nosredna")
:    :MORE-ITEMS ("jade ball"))