UP | HOME

Dew Lang POC

kyleperik.com

—

I've finished a proof of concept for my new language: Dew Lang

Still I need to implement proper GC, and introduce a few more basic constructs, but the core is there and I've been able to have some fun with it.

I have two "platforms" supported already, one in the terminal with just basic stdin and stdout, and one with graphics using SDL3.

With the terminal platform, I did also add a "system" device, which allows me to quit the program or to change the input mode between "line" (the default) and "char", which hides output from stdin and immediately consumes characters.

This enables printing ANSI Escape Codes (I always refer back to this gist when using them). Essentially now I can write a little game in the terminal.

Now on the graphics side, it's not super special right now, I can just draw rectangles, change the color and clear the screen.

With these two basic platforms I made a program that runs on both of them, I'll include it below verbatim:

clear = @require("clear")
draw = @require("draw")
input = @require("input")
head = [5, 5]
tail = [
  [5, 5],
  [5, 5],
  [5, 5],
  [5, 5],
  [5, 5],
  [5, 5],
]
vel = [0, 0]

on tick
  for (1..6) |i| do
    tail[i - 1] = tail[i]
  end
  tail[5] = head
  head = [head[0] + vel[0], head[1] + vel[1]]

  clear()
  for (tail) |value| do
    draw(value[0], value[1])
  end
  draw(head[0], head[1])
end

on input
  c = input()
  if c == "h" then vel[0] = -1 vel[1] = 0 end
  if c == "l" then vel[0] = 1 vel[1] = 0 end
  if c == "k" then vel[0] = 0 vel[1] = -1 end
  if c == "j" then vel[0] = 0 vel[1] = 1 end
end

Nothing really special here, it's just an abstract dumbed down implementation of snake.

This is a implicitly a root level component definition, so to include it in my main program simply @import and bind it to a platform. Here's how it looks now for the terminal version:

input = @require("input")
print = @require("print")

game = @import("snake.dew") | {
  input,

  clear: fn () print("\e[2J") end,

  draw: fn (x, y)
    print("\e[s")
    print("\e[", y, ";", x, "H", "o")
    print("\e[u")
  end
}

The terminal escape sequences are a bit obscure, but just know `clear` triggers a screen clear, and `draw` prints an `o` at the location specified.

Now for graphical version

print = @require("print")
draw = @require("draw")
keyboard = @require("keyboard")

game = @import("snake.dew") | {
  input: keyboard,

  clear: fn ()
    draw("color", 0, 0, 0)
    draw("clear")
  end,

  draw: fn (x, y)
    draw("color", 0, 255, 255)
    draw("rect", x * 10, y * 10, 10, 10)
  end,
}

Here we take input from the dedicated keyboard device, and interface with the screen with the provided `draw` capability to draw rectangles in a grid.

The rest that I didn't show is just lifecycle related stuff and triggers for the components.

This demonstrates the core idea expored with dewlang, that components can be used as a means of abstracting stateful processes without giving up on "purity".

If you want to try these examples, just clone my repo and follow the instructions in the readme.

Next, I just want to expore more ways I can use components to write tools and games in a modular way. I'd also like to focus on writing more platform capabilities to see what other contexts I could use this language in.

I may also try compiling to WASM to get some examples working in the web so they are more sharable, but I might not get to that right away.

I planned to rewrite the bytecode interpreter in another language/system to see how portable it is. I don't want to do that right away, as the opcodes are not quite stable yet, but I think it's imporant for portability sake.

I don't have plans to bootstrap the compiler at the moment, though it could be a good exercise to get the language working with something real.

Long term, I'm thinking I'd rather stick with zig, since I do really like it, and I won't be able to beat the speed elsewhere.