A sidescrolling game as a Pratt parser


1. NUD

is my movement towards a Pratt parser.

Tbh, I was having a lot of difficulty with where-to-put the lisp code parsed from new-grammar-language.

So I wrote a silly program first, themed around moving a cursor around inside lists via cdr-coding.

Well, at least I could actually write this. Next, I'm going to use the silly physical-intuition cdr-coded cons manipulation at a cursor to insert the results of the Pratt parser I'm working on implementing into an existing lisp cons.

There are a bunch of interesting properties of my cons-traverser already, I think, though I'm not an expert on this topic.

1.1. Anyway, let's check it out.

1.1.1. Example

CL-USER> (in-package :nud/user)
#<PACKAGE "NUD/USER">
NUD/USER> '(1 2 (3 4 (5 6)) 7)
(1 2 (3 4 (5 6)) 7)
NUD/USER> (gird *)
; in: GIRD *
;     (NUD/USER::GIRD *)
; 
; caught STYLE-WARNING:
;   undefined function: NUD/USER::GIRD
; 
; compilation unit finished
;   Undefined function:
;     GIRD
;   caught 1 STYLE-WARNING condition
; Evaluation aborted on #<UNDEFINED-FUNCTION GIRD {1002C73DE3}>.
NUD/USER> (clad *) ; oops it was named clad not gird

(NUD/MAIN::START #:CURSOR) 
(NUD/MAIN::START #:CURSOR (1 2 (3 4 (5 6)) 7)) 
#1=(NUD/MAIN::START #:CURSOR (1 2 (3 4 (5 6)) 7) . #1#) 
(> . #1=(#:CURSOR (1 2 (3 4 (5 6)) 7) NUD/MAIN::START . #1#))
(1 2 (3 4 (5 6)) 7)
NUD/USER> ; so, clad creates a circular outside list with an uninterned cursor.
; No value
NUD/USER> ^^ ; go into the list after the cursor (focus of second return)
(> #:CURSOR 2 (3 4 (5 6)) 7)
2
NUD/USER> ; Note we "swapped" with the first element of the list. That's how I chose it to work.
; No value
NUD/USER> >>
(> #:CURSOR (3 4 (5 6)) 7)
(3 4 (5 6))
NUD/USER> >>
(> #:CURSOR 7)
7
NUD/USER> >>
(> #:CURSOR)
NIL
NUD/USER> >>
(> . #1=(#:CURSOR (1 2 (3 4 (5 6)) 7) NUD/MAIN::START . #1#))
(1 2 (3 4 (5 6)) 7)
NUD/USER> ; back to the start
; No value
NUD/USER> ^^
(> #:CURSOR 2 (3 4 (5 6)) 7)
2
NUD/USER> >>
(> #:CURSOR (3 4 (5 6)) 7)
(3 4 (5 6))
NUD/USER> ^^
(> #:CURSOR 4 (5 6))
4
NUD/USER> >>
(> #:CURSOR (5 6))
(5 6)
NUD/USER> ^^
(> #:CURSOR 6)
6
NUD/USER> >>
(> #:CURSOR)
NIL
NUD/USER> >>
(> #:CURSOR (5 6))
(5 6)
NUD/USER> ; When we run off the end of the list, we actually swap the initial element back into the list.
; No value
NUD/USER> >>
(> #:CURSOR)
NIL
NUD/USER> >>
(> #:CURSOR (3 4 (5 6)) 7)
(3 4 (5 6))
NUD/USER> ; Oh, right, it was nested, wasn't it.
; No value
NUD/USER> >>
(> #:CURSOR 7)
7
NUD/USER> >>
(> #:CURSOR)
NIL
NUD/USER> >>
(> . #1=(#:CURSOR (1 2 (3 4 (5 6)) 7) NUD/MAIN::START . #1#))
(1 2 (3 4 (5 6)) 7)
NUD/USER> 

1.1.2. Choices

So, I had to consider that cdr-coding basically lets you destructively mess up the cons (list, tree) you're working with. It does not seem possible to easily undo these destructive operations. The lisp term for destructive here is "nonconsing" which is denoted by the letter n at the start of function names, like nreverse - nreverse doesn't make any new conses (automatically garbage collected lisp memory allocations), but this means it used the conses already consed for its input to build its output, so any connections to its inputs conses now point inside nreverse's output somewhere.

Since there is no obvious way to reverse destruction, or to dry-run destruction, I elected not to give much control to the nud/user user. Instead they just get two operations: =>>= and =^^=.

  1. We only see forwards

    The cursor-view is always from the cursor, so we don't see the swapped element behind us after swapping.

  2. =>>=

    Destructively swaps right, or falls off a list that had been swapped into by swapping back out.

    1. Swap right
        (> #:CURSOR 2 (3 4 (5 6)) 7)
      2
      NUD/USER> >>
      (> #:CURSOR (3 4 (5 6)) 7)
      (3 4 (5 6))
      
    2. Fall off/swap back out
      (> #:CURSOR 6)
      6
      NUD/USER> >>
      (> #:CURSOR)
      NIL
      NUD/USER> >>
      (> #:CURSOR (5 6))
      (5 6)
      
  3. =^^=
    1. Climb into
      (> #:CURSOR (5 6))
      (5 6)
      NUD/USER> ^^
      (> #:CURSOR 6)
      6
      

1.2. Game Feels

I am quite happy with this gamification of player-driven traversals of a cons. It evokes feelings of sidescrollers that can only move forward; and there's a sense of beginning and finishing a (game) traversal, when you start in and drop back into the outer circular list 'start screen' location.

It clearly shows how the cons is a tree of singly linked lists, with the rule-breaking witchcraft of swapping-back-out of a subtree when you reach the end. Since you can either go past, or climb into nested lists, these seem like doors in classic side-scroller games to me: And when you have traversed the room, you just exit the door to where you started.

1.3. Grammar parser feels

The fact that there is no turning-around-and-going-back feature, just finishing the level, and restarting the level if you wanted to go in a certain door and accidentally pushed the screen past it in your last try connects with host language (i.e. lisp code generation) from parsing a tenant language grammar. The unavailability of the past without finishing this traversal, and starting a new pass (from the beginning).

This acts to prevent explodingly complicated infinite-random-access-style processing, while acknowledging that a two-pass approach as a strategy; and that it is really literally two passes through a singly linked list.

The rule-breaking fact that any list entered by ^^ can be traversed any number of times as a consequence of the same rule being used to enter and exit the original cons at all, I guess softens this. Inside lists can be the subject of multiple passes, even if the outer list is ultimately only traversed with a single pass.

1.4. Next

Of course, there is no way of making changes currently. I guess there will be

  • push in front of me from register
  • pop in front of me to register
  • push inside list in front of me from register
  • pop from list in front of me to register

as well as something like

  • execute symbol

that is aware of the left and right binding precedence of the symbol (whether the NUD or LED of the symbol is used / receives an input or returns an input). I'm still shaky on that.

1.4.1. Games

I am excited to make a text-based side-scroller game in this format. I think that performing passes to collect artifacts / remove hazards / add features to the side-scrolling world are perfectly exciting though slightly mindbending and cdr-coding oriented games.

1.4.2. Grammar parser

The implication is that the original list being traversed would be the tokens of the language whose grammar is being parsed.

  1. Psychically,
    NUD/USER> '( 2 + 3 * 4 + 5 )
    (2 + 3 * 4 + 5)
    NUD/USER> (clad *)
    
    (NUD/MAIN::START #:CURSOR) 
    (NUD/MAIN::START #:CURSOR (2 + 3 * 4 + 5)) 
    #1=(NUD/MAIN::START #:CURSOR (2 + 3 * 4 + 5) . #1#) 
    (> . #1=(#:CURSOR (2 + 3 * 4 + 5) NUD/MAIN::START . #1#))
    (2 + 3 * 4 + 5)
    NUD/USER> ^^
    (> #:CURSOR + 3 * 4 + 5)
    +
    NUD/USER> >>
    (> #:CURSOR 3 * 4 + 5)
    3
    NUD/USER> 
    

    We can imagine here "inserting"

    (+ 2)
    

    before continuing

    (> #:CURSOR 3 * 4 + 5)
    3
    NUD/USER> >>
    (> #:CURSOR * 4 + 5)
    *
    NUD/USER> >>
    (> #:CURSOR 4 + 5)
    4
    NUD/USER> >>
    (> #:CURSOR + 5)
    +
    

    and "inserting"

    ((* 3 4))
    

    and continuing:

    (> #:CURSOR + 5)
    +
    NUD/USER> >>
    (> #:CURSOR 5)
    5
    NUD/USER> >>
    (> #:CURSOR)
    NIL
    

    and "inserting"

    ((+ 5))
    

    Now, on those running nconc, in a second pass:

    (apply 'nconc
     '((+ 2) ((* 3 4)) ((+ 5))))
    

    I.e.

    (eval form)
    
</div>

Author: screwlisp (tfw)

Created: 2025-01-19 Sun 11:06

Validate

Files

lispmoo2.tar.gz 2.4 kB
67 days ago

Get lispmoo2

Comments

Log in with itch.io to leave a comment.

It occurs to me there's no link to the git.

https://codeberg.org/tfw/nud

mkdir -p ~/common-lisp/
cd ~/common-lisp/
git clone https://codeberg.org/tfw/nud.git
cd;
sbcl
> (require :nud)
> (in-package :nud/user) ;as above.

Well, it's not perfect and for some reason my :export both didn't work but happy 2025 everyone. Chatter at me on the 'stodon plz. Remember the live show is every Wednesday  0UTC (Tuesday night in Americas various). We're mostly doing interviews when we can now. Check the pinned Mastodon toots for recent interviews or the peertube https://communitymedia.video/c/screwtape_channel/videos:

https://mastodon.sdf.org/@screwtape/