[HN Gopher] Writing a Game in C: Parsing S-expressions
       ___________________________________________________________________
        
       Writing a Game in C: Parsing S-expressions
        
       Author : azhenley
       Score  : 66 points
       Date   : 2020-08-20 19:31 UTC (3 hours ago)
        
 (HTM) web link (benpaulhanna.com)
 (TXT) w3m dump (benpaulhanna.com)
        
       | krapp wrote:
       | No mention of the game anywhere else on the blog, and this post
       | is from last year. I wonder what happened?
        
       | outworlder wrote:
       | I am scratching my head.
       | 
       | If we are parsing S-expressions... why not embed Scheme already?
       | This way, not only you get an S-expression parser for 'free', you
       | can easily manipulate S-expressions, you can add macros and so
       | on. And you also get your scripting language out of this deal!
       | 
       | Something like Racket or Chicken (and possibly others) would work
       | nicely. Chicken specifically has a very nice FFI - which you
       | don't even have to use. As an experiment, I have asked Chicken's
       | compiler to stop at the C code generation, added the generated
       | code to my XCode project, and deployed the whole package to
       | iPhone. Not even cross compilation shenanigans were required,
       | since this was handled by XCode.
       | 
       | As a bonus*2, with a few lines of code, I opened a TCP listener,
       | and fed straight to the REPL. Which meant that I could telnet and
       | have a REPL, which could be used to replace functions on the fly
       | and observe the results immediately, while the app was running.
       | 
       | They would have to rename the project at this point, though :)
        
         | grugagag wrote:
         | Yes, preferably chicken scheme as the compiled c code is
         | snappier. I was just looking at chicken scheme on a pocketchip,
         | admittedly I am just just scratching the surface with lisps but
         | this is so cool. For learning I find racket to be quite good
         | but chicken seems a lot better at performance. How long have
         | you been a lisper? Newbie here
        
         | samatman wrote:
         | It wouldn't be a C project if it didn't embody Greenspun's
         | Tenth Rule.
        
         | codemac wrote:
         | guile for embedding works really nicely.
        
           | outworlder wrote:
           | Sorry, I keep forgetting about Guile. That thing needs more
           | love indeed.
        
             | grugagag wrote:
             | As a newbie im getting a bit confused. What is the
             | difference between guile and chicken scheme? There's also
             | chez. Im even more confused
        
               | Jtsummers wrote:
               | They're each different implementations of Scheme,
               | sometimes with different design goals (like embedding or
               | scripting or for standalone programs). Scheme is a pretty
               | simple language (in comparison to many others) and
               | relatively small. There are also several versions (not
               | just implementations). Common ones out there now are
               | R5RS, R6RS (it was considered "big" and was somewhat
               | unpopular), and R7RS. Consequently, different
               | implementations are also conforming to different versions
               | of the spec.
               | 
               | Really, if you're wanting to learn Scheme, just pick one
               | and run with it. For learning purposes they're mostly
               | equivalent if you stick to the more popular ones, but be
               | aware of your learning material and the version of Scheme
               | that they're using.
               | 
               | If you're familiar with other programming languages, it's
               | somewhat like the relationship between different
               | compilers. Though usually they adhere a bit more strictly
               | to the same standards. Since Scheme is such a small
               | language, there's more room for differences between
               | implementations. This mostly doesn't affect you as a
               | learner (most learning materials focus on the core parts
               | of the language which all implementations should be
               | consistent on).
        
               | [deleted]
        
               | outworlder wrote:
               | These are different implementations. The language spec
               | they follow is slightly different so they are not
               | completely compatible (although I've been able to use
               | some OpenGL-ES Racket code with no modifications in
               | Chicken by using macros).
               | 
               | Where they will differ the most is things like the
               | packaging system (if they have packages, not all Scheme
               | implementations will), as well as their main use-case.
               | 
               | If you are interested, just pick one and start to learn
               | Scheme, using any implementation. Their differences will
               | become apparent quickly.
        
               | grugagag wrote:
               | Thank you. Quite a few options out there in terms of
               | LISPS and a beginner like me can be quite confused. I
               | started looking at SBCL, Chicken Scheme and Racket and
               | since I'm a newbie I'll stick with racket for the
               | learning phase. I seem to like more the uniformity of
               | Scheme over the cl for example. Do people usually
               | specialize in one or the other or use them
               | interchangeably depending on libraries and such? Another
               | slight barrier has been learning Emacs and that's another
               | reason I chose racket. I like what Emacs is capable but I
               | most likely revisit later.
        
               | cycloptic wrote:
               | There is a basic explanation that was written by the
               | guile maintainer in 2013:
               | https://www.wingolog.org/archives/2013/01/07/an-
               | opinionated-...
        
         | svvcfb1212 wrote:
         | ...and this is how we get unnecessarily bloated software with
         | ungodly dependency chains and complex build systems.
         | 
         | Sometimes a purpose-built module that addresses the need and
         | maintains simplicity is preferable over complexity in the
         | interest of some future potential feature that nobody has asked
         | for yet, and may never ask for.
        
           | zem wrote:
           | s7 scheme is pretty trivial to embed in a c project, it
           | consists of an s7.c and s7.h file that you just add to your
           | source tree, no other dependencies.
           | https://ccrma.stanford.edu/software/snd/snd/s7.html
        
           | outworlder wrote:
           | Those Scheme interpreters are tiny!
           | 
           | I can guarantee you that a game will need a scripting
           | language. Better get that done already.
           | 
           | That's if you prefer Scheme. You could use Lua too.
        
             | dfox wrote:
             | The issue with scripting engine for game is that for game
             | your almost certainly need scripting engine with completely
             | different semantics than whatever existing implementation
             | will provide. Ie. you either need to save the state or
             | replicate it over network, both with reasonable
             | performance. And looking at various AAA games (from modding
             | standpoint) there is embedded Lua or Python that mostly
             | only changes state of some lower level VM, which more often
             | that not is itself turing-complete.
        
             | learc83 wrote:
             | >I can guarantee you that a game will need a scripting
             | language. Better get that done already.
             | 
             | Myself and thousands of people before and after me have
             | written games without a scripting language.
        
               | outworlder wrote:
               | You _can_ write a game without any scripting language
               | support whatsoever. You could also write the whole game
               | in assembly if you so desired.
               | 
               | However, if every single time you want to change any
               | piece of logic in your game, you have to do the
               | edit/compile cycle, your development will be extremely
               | slow. Especially if you compare with the ability to
               | change code while the game is running.
               | 
               | Not having all the game logic in C or what have you makes
               | the game easier to mod too.
        
               | pengaru wrote:
               | Fast iteration can be achieved with compiled game logic
               | as well, it all comes down to how things are factored in
               | your project.
               | 
               | An excellent example most with OpenGL experience may be
               | familiar with is supporting hot reloading of GLSL for
               | fast vfx development iteration. It's a compiled language,
               | it gets compiled at runtime without significant delay,
               | and most of us making games have created simple tools to
               | support live editing of GLSL, maybe even supporting it
               | in-game for dev builds.
               | 
               | The demoscene has even turned it into a form of
               | competitive performance art:
               | 
               | https://github.com/Gargaj/Bonzomatic
               | 
               | Edit:
               | 
               | Forgot to mention Casey also goes through support of hot-
               | reloading game logic using C in the Handmade Hero series
               | on YouTube.
        
               | learc83 wrote:
               | >However, if every single time you want to change any
               | piece of logic in your game, you have to do the
               | edit/compile cycle, your development will be extremely
               | slow. Especially if you compare with the ability to
               | change code while the game is running.
               | 
               | 1. I'm working on a game in Unity right now that takes
               | about 10 seconds to compile and reload the assemblies for
               | the scripting language (only about 2 seconds of that is
               | actual compilation, the rest is time that would be
               | applicable to an interpreted scripting language).
               | 
               | 2. I've built games in C where the entire project took 4
               | seconds to compile. Incremental compilation and modular
               | architecture can get that down to near instantaneous for
               | most changes.
               | 
               | >Especially if you compare with the ability to change
               | code while the game is running.
               | 
               | Take a look at handmade hero for an example of an
               | architecture that allows you to hot-reload C code while
               | the game is running.
               | 
               | >Not having all the game logic in C or what have you
               | makes the game easier to mod too.
               | 
               | That's completely architecturally dependent. You can make
               | a game easy to mod for non programmers by storing data in
               | a human readable/editable format. If you want to make it
               | easy for players to write code there's a lot more work
               | that needs to be done than just using a scripting
               | language. For most games worrying about allowing modders
               | to easily edit code is premature optimization.
        
               | svvcfb1212 wrote:
               | > For most games worrying about allowing modders to
               | easily edit code is premature optimization.
               | 
               | I was involved in a game project that died extremely
               | quickly a few years ago. The project lead made every
               | decision re: architecture, tooling, etc. somehow involve
               | worrying about modding. As a result, we ended up with
               | nothing to show since everything got derailed by a
               | bizarre obsession by one person with modding before we
               | even had anything to mod. I believe that person doesn't
               | take lead roles on projects any longer...
        
             | pengaru wrote:
             | > I can guarantee you that a game will need a scripting
             | language. Better get that done already.
             | 
             | Is that really so certain?
             | 
             | http://www.youtube.com/watch?v=y2Wmz15aXk0
        
         | tgb wrote:
         | The author does say the following:
         | 
         | > I'd rather be as library independent as possible, ideally
         | with my SDL being my only cross-platform dependency to worry
         | about.
        
         | platinumrad wrote:
         | Because he needs to parse some config files, not embed a second
         | language.
        
         | lurquer wrote:
         | All you professional programmer-types seem to have forgotten
         | that solving problems (even if they've been solved a thousand
         | times before) is FUN.
         | 
         | I wrote a custom-made script parser... knowing full-well there
         | were many libraries available. (For a goofy program I wrote
         | available at cycell2d.com) Why? Because I enjoyed it!
        
         | rumanator wrote:
         | > If we are parsing S-expressions... why not embed Scheme
         | already?
         | 
         | This sort of nonsense is a good example of how some projects
         | die due to absurd feature creep that no one at all can possibly
         | justify. Who in their right mind would ever want to embed a
         | full blown scripting language if your usecase is... Open a file
         | and input data?
         | 
         | If you want to input data following a document format, you
         | parse the document format. That's it.
        
       | platinumrad wrote:
       | Someone: Here is how I put a sink in my bathroom.
       | 
       | The geniuses of news dot ycombinator dot com: I am scratching my
       | head. If you had simply put a kitchen in your bathroom you would
       | have gotten a sink for free. As a bonus, you would now be able to
       | cook your dinner there.
        
       | tobyhinloopen wrote:
       | And 0 value was added to the actual game. This is exactly how my
       | side projects are never finished
        
         | jstimpfle wrote:
         | With some practice, things like this take between a couple of
         | hours to a couple of days of investment, total. I would say it
         | is a fair price to pay, whenin exchange you get to explore the
         | design space, get to practice some important skills, and get a
         | lot of control over how you want to do things, and get rid of a
         | nasty dependency that might bite you later.
         | 
         | Of course, there are some things that you are not going to do
         | that a solid library gets you for free.
        
         | outworlder wrote:
         | 0 value may have been added to the game. A lot of value was
         | added to the author.
        
         | emmanueloga_ wrote:
         | Writing a custom configuration format seems like some prime
         | quality yak shaving :-) Probably flatbuffers would be a good
         | choice for the author needs.
        
           | makapuf wrote:
           | C literals const struct files are actually a nice format with
           | no runtime. If you compile in .3s a big C file your compile
           | time wont matter.
        
           | wahern wrote:
           | One of the reasons service configurations on OpenBSD (e.g.
           | pf.conf, ipsec.conf, etc) are so convenient and expressive is
           | because they don't shy away from defining and implementing
           | custom configuration syntax using yacc.
           | 
           | At some point _somebody_ needs to implement the configuration
           | interface for the human. Punting things by preferring
           | boilerplate, machinable formats and libraries doesn 't
           | actually solve the problem.
        
         | Bishop_ wrote:
         | Same here, but maybe the goal of his side project is to learn
         | and not ship a product.
         | 
         | In any case, you're not wrong, but I assume most people writing
         | a game in C as opposed to leveraging a tool like Unity are more
         | interested in diving in and learning something new rather than
         | shiping something.
        
           | ThePadawan wrote:
           | They're very different attitudes indeed. E.g. compare OP to
           | the level storage of VVVVVV: https://github.com/TerryCavanagh
           | /VVVVVV/blob/cf53de9ed5cd82a...
        
             | emmanueloga_ wrote:
             | Although to be fair I'm sure the author wrote a program
             | that generated that array :-)
        
               | Ma8ee wrote:
               | I certainly hope so.
        
             | allenu wrote:
             | I love the reference to this because I'm working on a side
             | project and treating it as "something I definitely want to
             | ship" and have found that I have higher tolerance for code
             | that I just copy/paste and slapdash together. I've worked
             | on lots of side projects in the past where I was more
             | deliberate about every line of code and learned that
             | shipping is more important to me now than learning
             | something new.
             | 
             | I will say most of the choices I'm making with respect to
             | when to go "deep" with good design are very much
             | calculated. I'm designing the architecture with a lot of
             | thought, but when it comes to laying out UI elements, I
             | will copy-paste and not refactor even if there's a good
             | opportunity for it. It's just plumbing to me, so if it's
             | ugly, it's not a big deal.
        
       | codemonkey-zeta wrote:
       | Whoa this thread is way more harsh than I would have expected.
       | Coding isn't always about maximizing development speed or
       | avoiding arbitrary dogmas. Some things are just fun to code, and
       | parsing S-expressions is one of those things. I'm glad the author
       | shared their experience and actually plans to use it in a game.
       | Better than writing the parser for fun and then throwing it away.
       | 
       | Also, the author admits they didn't go to University for CS. You
       | know what you do as part of your CS degree? You write a parser
       | for S-expressions. This is a curious and driven programmer
       | learning CS and sharing their discoveries. No need to berate them
       | for not embedding a scheme....
        
         | dbtc wrote:
         | I think it is responding to a project as if it were a product.
        
           | platinumrad wrote:
           | Even if it were a product there is simply no justification
           | for embedded a second language when all you want to do is
           | read some config files.
        
             | wizzwizz4 wrote:
             | Sure there is! Shaves a few hours off the initial
             | development time, adds a few milliseconds to execution time
             | (this _is_ scheme we 're talking about), adds a few days to
             | later development.
        
       | neilv wrote:
       | For people not already very familiar with S-expressions, a
       | slightly different way to formulate and format the example from
       | the post is:                   (sprites (sprite :x 32
       | :y 104                          (animation :texture
       | "assets/idle.bmp"                                     :frame-
       | length 2                                     :frame-width  24
       | :frame-height 24                                     :frame-span
       | 5                                     :loop         0)))
       | 
       | A benefit of formatting this way comes when it's not only a
       | nested chain, like in this contrived example, but you have more
       | varied tree structure, like in real-world code and data, and want
       | to be able to grasp the nesting structure visually. And without
       | using too many vertical lines of screen space.
        
       | ravenide wrote:
       | "Even though I'm never expecting my game to read an empty string
       | in from an s-expression, I didn't want that to be a limitation of
       | the parser."
       | 
       | This thing is never going to ship. Coming from someone who has
       | written an IDE and wasted a bunch of time on the parser because
       | it was the cool theoretical portion.
       | 
       | That's fine, I assume learning rather than shipping is the goal
       | here. A fine goal it is.
       | 
       | By the way, the problem is NOT that he wrote his own parser and
       | other homemade tools. This is actually a great thing to do
       | --third party tools usually end up being more trouble than
       | they're worth. The problem is that he's ratholing on tiny details
       | that don't matter.
        
       | unoti wrote:
       | This is fun and interesting, and I'm not sure what to think of
       | it.
       | 
       | There's an old saying in game dev, which is often good advice for
       | business as well: "Build the game, not a library"[1].
       | 
       | Or as applied to this case, that might mean trying to shoot
       | straight for what you need in the game. Arguably, S-expressions
       | might be the smallest possible thing you can do.
       | 
       | But another approach that is often used: Have your game process a
       | binary file format, and build tools that read text files and
       | compile them into the binary format. It's very common in games to
       | have a tools pipeline that prepares assets for production use.
       | Those tools can safely and easily take whatever dependencies help
       | you get the job done, such as a JSON or YAML parser, or even real
       | LISP if that feels right. This kind of approach also fulfills the
       | laudible goal of keeping the dependencies down (for the
       | production game).
       | 
       | The output of the tool chain could be binary files. Or, depending
       | on your situation, the toolchain could produce code which is then
       | run through your compiler, and then it's ready for direct
       | execution, skipping a parsing step entirely.
       | 
       | [1] https://geometrian.com/programming/tutorials/write-games-
       | not...
        
         | f00zz wrote:
         | For what it's worth, I once wrote a game that had a
         | configuration file parser written in bison/flex (I actually
         | finished that game).
        
       | lokl wrote:
       | Why not make parsing S-expressions the game? HN crowd would love
       | to play.
        
       | lispm wrote:
       | While you follow Greenspun's tenth rule of programming (Any
       | sufficiently complicated C or Fortran program contains an ad hoc,
       | informally-specified, bug-ridden, slow implementation of half of
       | Common Lisp) you could embed a Lisp system, like ECL ->
       | Embeddable Common Lisp
       | 
       | https://common-lisp.net/project/ecl/
       | 
       | There are a bunch of applications which include some kind of Lisp
       | implementation in C. From GNU Emacs, Audacity, AutoCAD (and its
       | various clones), and various others.
        
       | [deleted]
        
       | rsecora wrote:
       | Nice, and descriptive. I love the explanation.
       | 
       | It's also an example of Greenspun's tenth rule [0]
       | 
       | "Any sufficiently complicated C or Fortran program contains an ad
       | hoc, informally-specified, bug-ridden, slow implementation of
       | half of Common Lisp."
       | 
       | [0] https://en.wikipedia.org/wiki/Greenspun%27s_tenth_rule
        
         | platinumrad wrote:
         | A config file parser is not anywhere close to half of Common
         | Lisp.
        
       | CoolGuySteve wrote:
       | It's fine for a personal project, but if you're thinking of
       | writing a custom parser and grammar for configuration for a
       | commercial project: Don't.
       | 
       | There's a lot of flexibility that comes from being able to
       | programmatically generate configs in your scripting language of
       | choice and then ingest them in C++ with little effort. For
       | example, being able to serialize a Python dict as
       | json/ini/yaml/sqlite/whatever and then ingest it in C++ with some
       | library.
       | 
       | It turns your complicated C++ program into a function with a
       | dictionary argument.
       | 
       | I've worked on a few large programs now that have used custom
       | config formats and it sucks. It's a waste of time trying to
       | output correct syntax while fighting through idiosyncratic syntax
       | choices and poorly explained parsing errors in the custom code.
       | 
       | It doesn't create value, it's just a thing you have to do to get
       | to the automation you're trying to accomplish. Avoid it by not
       | making the mistake in the first place.
        
         | setzer22 wrote:
         | To be fair, S-expressions are not a made up format and in fact
         | predate most other formats you mention.
        
           | CoolGuySteve wrote:
           | You're missing the point, S-expression parsers aren't
           | "batteries included" in most scripting languages. The age of
           | the format is irrelevant.
        
         | outworlder wrote:
         | > It's fine for a personal project, but if you're thinking of
         | writing a custom parser and grammar for configuration for a
         | commercial project: Don't.
         | 
         | I would agree. Even more so as parsers for S-expressions have
         | existed... since 1960 or so?
        
       | bigdict wrote:
       | Tree is a more natural data structure for s-expressions.
        
         | moonchild wrote:
         | Usually it's called a cons cell in this context, not a tree.
         | The base lisp structure looks something like this:
         | data Obj =       | Cons(Obj car * Obj cdr)       |
         | Symbol(string)       | Nil
         | 
         | (Nil is important, cannot be elided.)
         | 
         | In c:                 typedef enum { Cons, Sym, Nil } NodeType;
         | typedef struct Node Node;       struct Node {
         | NodeType type;               union {
         | struct { Node *car, *cdr; };                       const char
         | *symbol;               };       };
        
           | rumanator wrote:
           | You're being needlessly pedantic. A cons cell is basically a
           | node of a binary tree. It has fancy names for the left and
           | right child node because of its relation with lisp, but it's
           | nonetheless a tree node.
        
       | mark-probst wrote:
       | I wrote a Lisp reader for C a long time ago:
       | https://github.com/schani/lispreader
       | 
       | An interesting problem that came up was freeing deeply nested
       | conses without overflowing the stack. I got a bug report about
       | that from a user. They must have generated the structure in
       | memory, because if they were reading it in, the reader would
       | probably also have overflown the stack. In any case, the simple
       | solution was to rotate the structure to be freed so that it can
       | be freed without stack overflow or using an additional queue or
       | stack:
       | 
       | https://github.com/schani/lispreader/blob/master/lispreader....
        
       ___________________________________________________________________
       (page generated 2020-08-20 23:01 UTC)