[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)