Well-typed programs cannot "go wrong".
- Robin Milner, A Theory of Type Polymorphism in Programming (1978)
The quality of software depends primarily on the programming methodology in use. [...] A methodology can be easy or difficult to apply in a given language, depending on how well the language constructs match the structures that the methodology deems desirable. [...]
- Barbara Liskov, Abstractions Mechanisms in CLU (1977)
import gleam/int
import gleam/string
pub opaque type Counter {
Counter(value: Int, step: Int)
}
pub type Msg {
Increment
Decrement
Reset
}
pub fn init(step: Int) -> Counter {
Counter(value: 0, step: step)
}
pub fn update(counter: Counter, msg: Msg) -> Counter {
case msg {
Increment -> Counter(..counter, value: counter.value + counter.step)
Decrement -> Counter(..counter, value: counter.value - counter.step)
Reset -> init(counter.step)
}
}
pub fn render(counter: Counter) -> String {
string.append("The current count is: ", int.to_string(counter.value))
}
init(Step) ->
{counter, 0, Step}.
update(Counter, Msg) ->
case Msg of
increment ->
erlang:setelement(
2,
Counter,
erlang:element(2, Counter)
+ erlang:element(3, Counter)
);
decrement ->
erlang:setelement(
2,
Counter,
erlang:element(2, Counter)
- erlang:element(3, Counter)
);
reset ->
init(erlang:element(3, Counter))
end.
render(Counter) ->
gleam@string:append(
<<"The current count is: "/utf8>>,
gleam@int:to_string(erlang:element(2, Counter))
).
-record(counter, {count, step}).
Sometimes I think the only universal in the computing field is the fetch-execute cycle.
- Alan J. Perlis, Epigrams on Programming (1982)
pub type State {
Running
AwaitingROM
AwaitingInput(vx: registers.DataRegister)
}
pub type Command {
SoundOn
SoundOff
}
pub type Emulator {
Emulator(
state: State,
registers: registers.RegisterFile,
keyboard: keyboard.Keyboard,
pc: Int,
stack: stack.Stack,
memory: memory.Memory,
screen: screen.Screen,
)
}
pub fn init() -> Emulator {
Emulator(
state: AwaitingROM,
registers: registers.new(),
keyboard: keyboard.new(),
pc: 0x200,
stack: stack.new(),
memory: memory.put(memory.new(4096), 0, font),
screen: screen.new(64, 32),
)
}
import gleam/bit_string
import chip8/helpers
pub opaque type Memory {
Memory(data: BitString)
}
pub fn new(size: Int) -> Memory {
Memory(data: helpers.bitstring_copy(<<0>>, size))
}
pub fn put(memory: Memory, position: Int, data: BitString) -> Memory {
assert Ok(left) = bit_string.part(memory.data, 0, position)
assert Ok(right) =
bit_string.part(
memory.data,
position + bit_string.byte_size(data),
bit_string.byte_size(memory.data) - position - bit_string.byte_size(data),
)
let data =
left
|> bit_string.append(data)
|> bit_string.append(right)
Memory(data: data)
}
pub fn read(
memory: Memory,
position: Int,
length: Int,
) -> Result(BitString, Nil) {
bit_string.part(memory.data, position, length)
}
import gleam/int
import gleam/string
pub external fn bitstring_copy(subject: BitString, n: Int) -> BitString =
"binary" "copy"
pub external fn bitstring_to_list(subject: BitString) -> List(Int) =
"binary" "bin_to_list"
pub external fn rand_uniform(n: Int) -> Int =
"rand" "uniform"
pub fn int_to_hex_string(n: Int) -> String {
string.append("0x", int.to_base_string(n, 16))
}
defmodule Chip8.Emulator do
use Strucord,
name: :emulator,
from: "gen/src/chip8@emulator_Emulator.hrl"
alias __MODULE__
def init() do
from_record(:chip8@emulator.init())
end
def reset(%Emulator{} = emulator) do
with_record(emulator, fn record ->
:chip8@emulator.reset(record)
end)
end
def load_rom(%Emulator{} = emulator, rom) when is_binary(rom) do
with_record(emulator, fn record ->
:chip8@emulator.load_rom(record, rom)
end)
end
def step(%Emulator{} = emulator) do
{emulator, commands} = :chip8@emulator.step(to_record(emulator))
{from_record(emulator), commands}
end
def handle_timers(%Emulator{} = emulator) do
{emulator, commands} = :chip8@emulator.handle_timers(to_record(emulator))
{from_record(emulator), commands}
end
end
defmodule Chip8Web.PageLive do
use Chip8Web, :live_view
def mount(_params, _session, socket) do
emulator = Chip8.Emulator.init()
socket = assign(socket, emulator: Emulator.init())
{:ok, socket}
end
def handle_event("load_rom", %{"name" => rom_name}, socket) do
rom = Chip8.ROMs.get_rom(rom_name)
emulator =
Chip8.Emulator.load_rom(
socket.assigns.emulator,
rom.data
)
socket = assign(socket, emulator: emulator)
{:noreply, socket}
end
def handle_event("step", _value, socket) do
{emulator, commands} = Chip8.Emulator.step(socket.assigns.emulator)
socket =
socket
|> assign(:emulator, emulator)
|> handle_commands(commands)
{:noreply, socket}
end
end
Hooks.SoundCard = {
mounted() {
this.synth = createAudio();
this.handleEvent("enable_soundcard", () => {
this.synth.on();
});
this.handleEvent("disable_soundcard", () => {
this.synth.off();
});
},
beforeDestroy() {
this.synth.close();
},
disconnected() {
this.synth.off();
}
}
defp handle_commands(socket, commands) do
Enum.reduce(commands, socket, fn command, socket ->
case command do
:sound_on -> push_event(socket, "enable_soundcard", %{})
:sound_off -> push_event(socket, "disable_soundcard", %{})
end
end)
end
<div phx-window-keyup="key_up">
<div phx-window-keydown="key_down" phx-throttle="100">
<div class="grid-container">
<div class="window window-screen">
<h1 class="header">Screen</h1>
<div class="body">
<svg id="screen" class="screen" viewBox="0 0 64 32">
<%= for {x, y, opacity} <- :chip8@screen.to_list(@emulator.screen) do %>
<%= if opacity > 0 do %>
<rect x="<%= x %>" y="<%= y %>" width="1" height="1" opacity="<%= opacity %>"/>
<% end %>
<% end %>
</svg>
</div>
</div>
<div>
pub type Pixel {
OnPixel
OffPixel
DecayingPixel(lifetime: Float)
}
pub opaque type Screen {
Screen(width: Int, height: Int, contents: List(List(Pixel)))
}
pub fn new(width: Int, height: Int) -> Screen {
Screen(
width: width,
height: height,
contents: list.repeat(list.repeat(OffPixel, width), height),
)
}
pub type Instruction {
ClearScreen
ReturnFromSubroutine
JumpRelative(offset: Int)
SkipNextIfEqualConstant(vx: registers.DataRegister, value: Int)
...
}
pub fn decode_instruction(instruction: BitString) -> Instruction {
case instruction {
<<0:4, 0:4, 0xE:4, 0:4>> -> ClearScreen
<<0:4, 0:4, 0xE:4, 0xE:4>> -> ReturnFromSubroutine
<<0xB:4, nnn:12>> -> JumpRelative(nnn)
<<3:4, x:4, kk:8>> ->
SkipNextIfEqualConstant(registers.to_data_register(x), kk)
...
}
}
pub fn execute_instruction(
emulator: Emulator,
instruction: instruction.Instruction,
) -> Emulator {
case instruction {
instruction.ClearScreen ->
Emulator(..emulator, screen: screen.clear(emulator.screen))
instruction.JumpAbsolute(address) ->
Emulator(..emulator, pc: address - 2)
...
}
}