#+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"))