all repos

scratch @ cafea316f23d5a3b70a45185c9062fbdd9058b00

⭐ me doing recreational ~~drugs~~ programming

scratch/brainfuck/src/gbf/internal/vm.gleam (view raw)

1
import gbf/internal/ascii
2
import gleam/dict.{type Dict}
3
import gleam/list
4
import gleam/result
5
6
pub const tape_size = 30_000
7
8
pub const cell_size = 255
9
10
pub type Error {
11
  PointerRanOffTape
12
  IntegerOverflow
13
  IntegerUnderflow
14
  EmptyInput
15
  InvalidChar(Int)
16
}
17
18
/// The machine model we are going to use for this interpreter is very simple:
19
///   - Our memory consists of 30,000 cells (1000 rows * 30 columns).
20
///   - There's a data pointer which points to a specific cell and is initialized at
21
///     the leftmost cell, an error will be reported if the pointer runs off the
22
///     tape at either end.
23
///     pointer = 0
24
///   - A data cell is 8 bits, and an error will be reported if the program tries
25
///     to perform under- or overflow, i.e. decrement 0 or increment 255.
26
///   - Two streams of bytes for input and output using the ASCII character
27
///     encoding.
28
///
29
pub type VirtualMachine {
30
  VirtualMachine(pointer: Index, cells: Cells, output: String, input: List(Int))
31
}
32
33
pub type Cells =
34
  Dict(Int, Int)
35
36
pub type Index =
37
  Int
38
39
pub fn new(input: List(Int)) -> VirtualMachine {
40
  VirtualMachine(input:, pointer: 0, cells: dict.new(), output: "")
41
}
42
43
/// Returns the accumulated output string from the virtual machine.
44
///
45
pub fn output(vm: VirtualMachine) -> String {
46
  vm.output
47
}
48
49
/// Gets the value of the cell at the specified pointer.
50
/// Returns an error if the pointer is out of bounds.
51
///
52
/// ```gleam
53
/// get_cell(vm, 0)  // Ok(0)
54
/// get_cell(vm, -1) // Error(PointerRanOffTape)
55
/// ```
56
pub fn get_cell(vm: VirtualMachine, pointer: Index) -> Result(Index, Error) {
57
  use pointer <- result.try(validate_tape_size(pointer))
58
59
  case dict.get(vm.cells, pointer) {
60
    Ok(value) -> Ok(value)
61
    Error(_) -> Ok(0)
62
  }
63
}
64
65
/// Sets the value of the cell at the specified pointer.
66
///
67
/// Returns an updated virtual machine if successful, or an error if:
68
/// - The pointer is out of bounds (< 0 or > 30,000)
69
/// - The value is out of bounds (< 0 or > 255)
70
///
71
/// ```gleam
72
/// set_cell(vm, 0, 65)  // Ok(...)
73
/// set_cell(vm, 0, 256) // Error(IntegerOverflow)
74
/// ```
75
pub fn set_cell(
76
  vm: VirtualMachine,
77
  pointer: Index,
78
  value: Int,
79
) -> Result(VirtualMachine, Error) {
80
  use pointer <- result.try(validate_tape_size(pointer))
81
  use value <- result.try(validate_cell_size(value))
82
  let cells = dict.insert(vm.cells, pointer, value)
83
84
  VirtualMachine(..vm, cells:)
85
  |> Ok
86
}
87
88
/// Moves the data pointer to the specified position.
89
/// Returns error if pointer is out of bounds.
90
///
91
/// ```gleam
92
/// set_pointer(vm, 100) // Ok(...)
93
/// set_pointer(vm, -1)  // Error(PointerRanOffTape)
94
/// ```
95
pub fn set_pointer(
96
  vm: VirtualMachine,
97
  pointer: Index,
98
) -> Result(VirtualMachine, Error) {
99
  use pointer <- result.try(validate_tape_size(pointer))
100
101
  VirtualMachine(..vm, pointer:)
102
  |> Ok
103
}
104
105
/// Reads a byte from the input stream and stores it in the current cell.
106
///
107
/// Consumes the first byte from the input list and writes it to the cell
108
/// at the current pointer position.
109
/// Returns an error if the input is empty.
110
///
111
/// ```gleam
112
/// input_byte(vm)       // Ok(...)
113
/// input_byte(empty_vm) // Error(EmptyInput)
114
/// ```
115
pub fn input_byte(vm: VirtualMachine) -> Result(VirtualMachine, Error) {
116
  case vm.input {
117
    [] -> Error(EmptyInput)
118
    [first, ..] -> {
119
      use vm <- result.try(set_cell(vm, vm.pointer, first))
120
121
      VirtualMachine(..vm, input: list.drop(vm.input, 1))
122
      |> Ok
123
    }
124
  }
125
}
126
127
/// Reads the value from the current cell and appends it to the output as a character.
128
///
129
/// Converts the cell value to an ASCII character and adds it to the output string.
130
/// Returns an error if the cell value is not a valid ASCII code point.
131
///
132
pub fn output_byte(vm: VirtualMachine) -> Result(VirtualMachine, Error) {
133
  use cell_value <- result.try(get_cell(vm, vm.pointer))
134
135
  case ascii.from_code(cell_value) {
136
    "" -> Error(InvalidChar(cell_value))
137
    c ->
138
      VirtualMachine(..vm, output: vm.output <> c)
139
      |> Ok
140
  }
141
}
142
143
fn validate_tape_size(pointer: Index) {
144
  case pointer {
145
    p if p > tape_size -> Error(PointerRanOffTape)
146
    p if p < 0 -> Error(PointerRanOffTape)
147
    _ -> Ok(pointer)
148
  }
149
}
150
151
fn validate_cell_size(value: Int) {
152
  case value {
153
    v if v > cell_size -> Error(IntegerOverflow)
154
    v if v < 0 -> Error(IntegerUnderflow)
155
    _ -> Ok(value)
156
  }
157
}