Repeat from the sign
JavaScript HTML CSS Other
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.

#Dal Segno

Write games, see your changes immediately: every time you change your code, Dal Segno rewinds your game back to the last time that piece of code was run.


try it fullscreen


It's sort of like Scheme.


Run this program fullscreen

;semicolons make the rest of a line a comment
(define x 10)           ;var x = 10
(defn recur ()          ;named function definitions are global
  (color 100 200 100)   ;sets the color to be used
                        ;for future draw operations
  (fillRect 0 0 width height) ;this is the canvas
                        ;context drawing operation
  (color 0 0 230)
  (drawArc x 100 111)   ;queues a circle to be drawn
  (render)              ;actually paints queued drawings
  (set! x (+ x .1))     ;change var wherever it was defined
  (if (> x 300)         ;you've got if, set!, define, defn,
      (set! x 0))       ;and lambda - that's it for special forms
  (recur))              ;no loop constructs - you have to recur!


  • (if cond expr1 [expr2])
  • (define name expr)
  • (set! name expr)
  • (lambda (param...) expr1...)
  • (defn name [param1...) expr1...)
  • (do expr1 [expr2...])

defn expressions update a global table of named functions in addition to evaluating to a function. Use do to put multiple expressions in the body of an if, set, or define


Identifier lookup checks the local scope, outer scopes, then the standard library and builtin functions:

  • standard library - written in this this language and can be stepped through
    • (map func array)
    • (reduce func array initial)
    • (filter func array)
    • (find func array)
  • builtins - written in JavaScript and execute in a single tick
    • (display expr [...]) - just (console.log(args))
    • binary operators (prefix notation, like (+ 2 2))
    • boolean logic
      • (and e1 e2)
      • (or e1 e2)
      • (not expr)
      • (any [expr...])
    • list operations
      • (list [e1...])
      • (get index list)
      • (first list)
      • (last list)
      • (rest list)
      • (append list expr)
      • (prepend list expr)
      • (length list)
      • (range n) - list of n numbers from 0 up to n, including 0 but not including n
      • (linspace start stop n) - list of n evenly spaced numbers from start to stop inclusive on both sides
      • (concat [list1...])
      • (zip list1)
      • (zip2 list1 list2)
      • (zip3 list1 list2 list3)
      • (zip4 list1 list2 list3 list4)
    • game math
      • (dist x1 y1 x2 y2) - Euclidian distance
      • (dist p1 p2) - Euclidian distance betwen two two-element lists
      • (randint lower upper)
      • (towards x1 y1 x2 y2) - degree heading to point
      • (towards p1 p2) - degree heading to point
      • (x_comp degrees) - float between -1 and 1
      • (y_comp degrees) - float between -1 and 1
      • (distToLine point line) - distance from point to line segment
      • (distToLine point p1 p2) - distance from point to line segment defined by p1 and p2
      • (linesIntersect line line) - whether two line segments intersect
      • (linesIntersect p1 p2 p3 p4) - whether two line segments intersect
      • (pointFromLineSegment p1 p2 p3) - distance from a point to a line segment
      • (closestPointOrLine p1 points) - the closest [x, y] point or [[x, y], [x, y]] line segment closest to the point p1 or null if no points given
      • (closestPointOrLine p1 points maxDist) - the closest [x, y] point or [[x, y], [x, y]] line segment closest to the point p1 or null if none closer than maxDist
      • (bounce x y dx dy pointOrLine) - New [dx, dy] after bouncing off of pointOrLine
      • (linesFromPoints points) - pairs of consecutive points to fom lines
    • JavaScript interop
      • (jsGet obj prop)
      • (jsSet obj prop value)
  • mouseTracker
    • (mousex) - horizontal position from the left
    • (mousey) - vertical position from the top
    • (mousepos) - list of [x, y]
  • drawHelpers
    • (color r g b)
    • (render) - runs queued canvas context drawing procedures
    • (drawText x y [text...])
    • (drawPoly x y list-of-x-y-pairs heading-in-degrees])
    • (drawInverseCircle x y radius)
    • (fillLine points)

If an identifier is not found in the above scopes lookup proceeds to JavaScript objects. If the found value is a function, a version of it bound to the object it was looked up on is returned. e.g. log -> console.log.bind(console)

  • main lazy canvas context which is similar to a CanvasRenderingContext2d but rendering does not occur until render is called. This is where drawing functions like (fillRect x y width height) come from.
  • main canvas
  • console
  • window


To run the code locally you'll need webpack and some loaders.

npm install
npm install -g webpack

Once those are installed, run the following:

python3 -m http.server 8000  # or any other static file server

and open localhost:8000 in a webbrowser.

To run the tests, install mocha and chai and run mocha on the tests:

npm install -g mocha
npm install chai
mocha src/test*

The ES5/6 features in the code are limited to those implemented in Node without requiring any flags so the tests can be run without compiling.


There are a lot of directions this could be taken in, I capped it off pretty arbitrarily because I wanted to be able to show it to people. Some particularly relevant things to do:

  • Fix up DalSegno.js to allow editing and stepping in the same embed. This mostly works, but not all of the state transitions are covered.
  • Move all effects to the effect canvas, currently the main lazycanvas is still doing extra work.
  • An alternate interpreter mode that does not save snapshots. This would be nice for publishing programs for use.
  • faster GC and fix memory leaks

I have a lot of smaller fixes I'd like to make, if you'd like to contribute let me know and I can advise on your PR. Here are a few simple fixes:

  • AST highlighting regions are slightly off (usually 1 extra character on each side)
  • Set focus on mouseover for Dal Segno widget for keyboard events
  • Save current program in local storage instead of url
  • Keep multiple programs saved at a time

There's lots more, if there's interest I can throw a lot of things up on an issue tracker.


I wrote this to play with interpreters and so I could be informed as I talked to Mary Rose Cook about her Code Lauren project.