It's like VRChat, but in netcat

What in the everlasting, godforsaken, neverfalling, bloody hell is that?

Well, let me explain.

If you never played VRChat, great! Me neither! But from what I heard/seen, it's basically a way to socialize, but you are the anime pfp for real this time. By the wonderful power of peak 2017 technology, you can use virtual reality and join virtual worlds where you can interact with people. It's something like The Wired from Serial Experiments Lain, except less hackers and more weebs and furries.

The point is that it's basically IRC, but with worlds and using the most advanced tech your run-of-the-mill consumer can get their hands on. So what if I made IRC, but with worlds in the most primitive tech your run-of-the-mill consumer can reasonably use: a simple nc(1) tcp connection?

Scheme?

Yes, Scheme! CHICKEN Scheme in fact.

For few years by now, I have been cursed. I wanted to learn some Lisp language (and yes, I use Lisp as a name of the family) on several occasions, but I have always got distracted by some other project. Last one was the pascal experiment, but I'm doing it for real this time.

I have decided that I will finish this project, not because I have some use for it, but because I want to finally do something in Scheme. (and because I don't have any clear vision for it, so I can basically finish it whenever)

So how will it work?

Well, you will connect to a server via netcat and you will chat with people and you will be able to issue commands. Commands that will say something to the entire server will be prefixed with '!' and commands that will only respond to you with '/'. I always hate when I don't know which commands are private and which will annoy others.

That is the plan. Again, I have no Idea what I'm doing.

So what do you have?

A GutHub repository!

As for the scheme implementation, I chose CHICKEN for multiple reasons:

CHICKEN also has a nice module system, which I got accustomed to while working in pascal. I thought that it will also have a smart compiler that can find and compile all the modules by itself, but I was wrong... kinda.

You see, csc is a very dumb compiler, but just like pip or npm, chicken-install can install both libraries and executables. One of those third party executables is csm, which is a smart compiler, so I was saved from Makefile once again!

I should probably also mention the Scheme ecosystem. For starters, Scheme is a lot like JavaScript. (This will anger al lot of people, ever tho JavaScript was inspired by scheme) There is one standard and a bunch of implementations that implement at least some of it, while also doing their own thing.

Except I lied, there are two standards: 'R5RS' and 'R7RS'. 'R5RS' is the more minimal one and 'R7RS' is the more full-featured one. The community can't decide which one is better, so both are in active use. CHICKEN can serve as both, but I'm using 'R5RS'.

In addition to the standard, there are unofficial extensions agreed on by the community. These so-called 'SRFIs' are all super cool and useful and stuff and it's very nice to see the community of such a decentralised language working together, but just like SCPs, they are mostly referred to by their number.

Want advanced list operations? That's srfi-1. Want record/struct types? That's srfi-9. Want string operations? That will be srfi-13.

And those are just the ones I'm using. I know it has it's reasons, but it can be a bit inconvenient having to search for the number of the library every time you want to use it.

In CHICKEN, few are built-in, few are in the package repo and the rest usually has an example implementation in its specification at srfi.schemers.org, so you can do without a library.

But enough about the ecosystem, to the code!

The CODE!!!!!!!!!!!!!!!!!!

For the server, I decided to use the tcp-server egg. Yes packages are called eggs, I kinda forgot to mention that.

It allows you to handle tcp connections with little-to-no code. That is great, as the point of this project is supposed to be suffering from scheme, not suffering from networking.

The library works fine, but the connection used to crash when the user killed the process. But that is not a problem, I just catch it with the 'handle-exceptions' macro and disconnect the user as usual. There are multiple macros for catching exceptions, but I chose this one for no particular reason. Scheme function/macro names can get quite long...

At connection, a new user is created as a srfi-9 record (I think gnu explains it better than schemers.org here) Users are stored in a list, which is used for broadcasting messages. For removal from list, I just use filter (which is in srfi-1 for a change).

User i/o is red just like stdin/out. Commands are then parsed with srfi-13.

Well, unlike FORTH, Scheme at least has the libraries I guess...

Scheme (and lisps in general) are also known for their macros. In scheme, macros are hygienic. That means that they are scoped like functions, so you don't need to worry about name conflicts. They also often look like functions, so sorry if I at some point call some macro a function or vice versa.

Macros are defined in quite an interesting way:

(module common (inc! add-to-list!)
  (import scheme (chicken base))

  (define-syntax inc!
    (syntax-rules ()
      [(inc! x y)
       (set! x (+ x y))]
      [(inc! x)
       (inc! x 1)]))

  (define-syntax add-to-list!
    (syntax-rules ()
      [(add-to-list! lst itm)
       (set! lst (cons itm lst))])))

This is the current state of the 'common.scm' file. At the top, you can see the module definition, followed by a list of exported symbols.

Then I import the language itself, which I guess I have to do?

finally, I declare two macros. Macros start with the 'define-macro'... builtin? I really don't know what that is. Anyways, it's followed by it's name and 'syntax-rules'. In syntax rules you define other keywords for your macro. This is important, because then you will do some pattern matching. You start writing pairs of possible matches and what to replace them with. Anything that is not the macro itself or one of the reserved keywords will turn into a variable in the replacement.

As you can see, macros can be recursive, which is actually rather nice.

One more nice thing about macros:

(define-syntax red
  (syntax-rules ()
    [(red str ...)
     (string-append "\x1b[31m" str ... "\x1b[39m")]))

You can use the special ellipsis keyword to catch multiple args in the previous symbol and then expand them in the replacement. I really like Scheme macros.

Well, I guess that's it. I could talk about the server command system, but it's just as simple as you would expect.

Some tips

If you decide to do some Lisp, which I would recommend, PLEASE, install a rainbow delimiter extension of some sort and use parinfer. It will make your life so much easier.

If you want to flex on your friends, learning Lisp is a great way to do it. Highly recommended!