Creating a Scripting Language in 5 Days

Today's goal: Replace Guile with a custom scripting language!

Why create a language?

After the amount of time we've spent fighting with Guile Scheme to invoke C functions correctly, I started thinking about whether it would make sense to switch to a different Scheme implementation like Chibi.

But then it occurred to me: if I'm only using Scheme as a control language that binds to the C layer, why use a full language for that?

So this weekend I had a crazy idea: create my own Scheme-inspired control language that will be purpose-built for this project. I won't actually call it a scripting language yet because we're going to avoid adding control flow and other typical language features as long as we can!

Another benefit is that I get complete control over memory management and how code execution is integrated with the runtime. We might not need a separate execution thread anymore!

And of course the most important reason: because it's fun!

Language design

Since this language is being created specifically for this project, we get to choose how it works. More importantly, we get to choose what not to implement!

What do we really need?

  • Specific set of primitive data types (string, integer, float, time, list)
  • Invoke C functions, declare bindings in a convenient way
  • Limited concept of scope for now, only module scope
  • Make sure the language enables an interactive workflow (REPL, eval-able constructs)
  • Ability to send individual commands from the editor (preview, transport control, project change)

What we don't need (for now)

  • Logic constructs and control flow
  • Functions and lexical scoping
  • Data structure definitions (complex data structures created via C functions)
  • Macros (but maybe later if it makes sense)
  • Library modules defined in the language (this will likely change, though)

This is more than just a data format! Since we can invoke functions in the C layer, it also allows us to automate behavior where necessary.

An example

This code snippet demonstrates some of the ideas I have in mind:

  • Symbol in first position of a list is always a function to be called, defined in C
  • Functions will often be called with keyword arguments, but can also have positional args
  • Time will be a first-class concept in the syntax
(define font-jost (font :family "Jost*" :weight 'medium))

(define moving-circles
  (scene :members
	 (list (circle :name 'circle1
		       :x 200
		       :y 500
		       :color (rgb 255 0 0)))

	 :timeline
	 (list (event :at 0:05
		      :actions (list (move :target 'circle1
					   :to (pos 400 200)
					   :for 150ms
					   :ease 'out)))
	       (event :at 0:10
		      :actions (list (fade :target 'circle1
					   :for 1000ms)))))

(scene-preview moving-circles)

Tasks

DONE Implement basic evaluation of expressions

TODO Enable special form (function) registration

TODO Fix issue with setting integer values

TODO Evaluate call expressions

TODO Port previous Guile interface code over to the new language

TODO Finish writing the render-to-file function

Notes

Here is the commit for what we accomplished today: https://github.com/FluxHarmonic/flux-compose/commit/dd3e2ccf8e30f94acc4808dfe5c120066b6afbc7

Documentation on setting watchpoints in memory with gdb: https://sourceware.org/gdb/download/onlinedocs/gdb/Set-Watchpoints.html

Next Steps

TODO Clean up some of the repeated patterns in the code

TODO Use gdb watch to figure out why token kinds are being overwritten

TODO Enable special form (function) registration

TODO Fix issue with setting integer values

TODO Evaluate call expressions

TODO Port previous Guile interface code over to the new language

TODO Finish writing the render-to-file function

TODO Get back to the tasks from the previous stream!