all repos

gbf @ 0ca503c

⭐ gleaming brainfuck
6 files changed, 429 insertions(+), 14 deletions(-)
add evaluator and vm
Author: Oleksandr Smirnov olexsmir@gmail.com
Committed at: 2025-10-16 23:57:44 +0300
Change ID: xzmurrwrlslsxvvzpxsyxlqrtqvzuvpv
Parent: e827329
M gleam.toml

@@ -15,6 +15,7 @@

[dependencies] gleam_stdlib = ">= 0.44.0 and < 2.0.0" splitter = ">= 1.1.0 and < 2.0.0" +gleam_erlang = ">= 1.3.0 and < 2.0.0" [dev-dependencies] gleeunit = ">= 1.0.0 and < 2.0.0"
M manifest.toml

@@ -2,12 +2,14 @@ # This file was generated by Gleam

# You typically do not need to edit this file packages = [ + { name = "gleam_erlang", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "1124AD3AA21143E5AF0FC5CF3D9529F6DB8CA03E43A55711B60B6B7B3874375C" }, { name = "gleam_stdlib", version = "0.65.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "7C69C71D8C493AE11A5184828A77110EB05A7786EBF8B25B36A72F879C3EE107" }, { name = "gleeunit", version = "1.6.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "FDC68A8C492B1E9B429249062CD9BAC9B5538C6FBF584817205D0998C42E1DAC" }, { name = "splitter", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "splitter", source = "hex", outer_checksum = "05564A381580395DCDEFF4F88A64B021E8DAFA6540AE99B4623962F52976AA9D" }, ] [requirements] +gleam_erlang = { version = ">= 1.3.0 and < 2.0.0" } gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" } gleeunit = { version = ">= 1.0.0 and < 2.0.0" } splitter = { version = ">= 1.1.0 and < 2.0.0" }
M src/gbf.gleam

@@ -1,10 +1,133 @@

+import gbf/eval import gbf/lexer import gbf/parser +import gbf/vm +import gleam/io +import gleam/list +import gleam/result +import gleam/string pub fn main() { - "+.[<>]" - |> lexer.new - |> lexer.lex - |> parser.parse - |> echo + let input = + "++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++." + + let vm = + input + |> string.split(on: "") + |> list.map(char_to_code) + |> vm.new + + let ast = input |> lexer.new() |> lexer.lex |> parser.parse() + case ast { + Error(_) -> panic as "not yay failed" + Ok(ast) -> { + use res <- result.try(eval.eval(vm, ast)) + io.println("input: " <> input) + io.println("input: " <> res.output) + + Ok("") + } + } +} + +fn char_to_code(s: String) -> Int { + case s { + "\n" -> 0x0A + " " -> 0x20 + "!" -> 0x21 + "\"" -> 0x22 + "#" -> 0x23 + "$" -> 0x24 + "%" -> 0x25 + "&" -> 0x26 + "'" -> 0x27 + "(" -> 0x28 + ")" -> 0x29 + "*" -> 0x2A + "+" -> 0x2B + "," -> 0x2C + "-" -> 0x2D + "." -> 0x2E + "/" -> 0x2F + "0" -> 0x30 + "1" -> 0x31 + "2" -> 0x32 + "3" -> 0x33 + "4" -> 0x34 + "5" -> 0x35 + "6" -> 0x36 + "7" -> 0x37 + "8" -> 0x38 + "9" -> 0x39 + ":" -> 0x3A + ";" -> 0x3B + "<" -> 0x3C + "=" -> 0x3D + ">" -> 0x3E + "?" -> 0x3F + "@" -> 0x40 + "A" -> 0x41 + "B" -> 0x42 + "C" -> 0x43 + "D" -> 0x44 + "E" -> 0x45 + "F" -> 0x46 + "G" -> 0x47 + "H" -> 0x48 + "I" -> 0x49 + "J" -> 0x4A + "K" -> 0x4B + "L" -> 0x4C + "M" -> 0x4D + "N" -> 0x4E + "O" -> 0x4F + "P" -> 0x50 + "Q" -> 0x51 + "R" -> 0x52 + "S" -> 0x53 + "T" -> 0x54 + "U" -> 0x55 + "V" -> 0x56 + "W" -> 0x57 + "X" -> 0x58 + "Y" -> 0x59 + "Z" -> 0x5A + "[" -> 0x5B + "\\" -> 0x5C + "]" -> 0x5D + "^" -> 0x5E + "_" -> 0x5F + "`" -> 0x60 + "a" -> 0x61 + "b" -> 0x62 + "c" -> 0x63 + "d" -> 0x64 + "e" -> 0x65 + "f" -> 0x66 + "g" -> 0x67 + "h" -> 0x68 + "i" -> 0x69 + "j" -> 0x6A + "k" -> 0x6B + "l" -> 0x6C + "m" -> 0x6D + "n" -> 0x6E + "o" -> 0x6F + "p" -> 0x70 + "q" -> 0x71 + "r" -> 0x72 + "s" -> 0x73 + "t" -> 0x74 + "u" -> 0x75 + "v" -> 0x76 + "w" -> 0x77 + "x" -> 0x78 + "y" -> 0x79 + "z" -> 0x7A + "{" -> 0x7B + "|" -> 0x7C + "}" -> 0x7D + "~" -> 0x7E + _ -> 0x3F + } }
A src/gbf/eval.gleam

@@ -0,0 +1,231 @@

+import gbf/lexer +import gbf/parser.{type AST, type Block, type Command} +import gbf/token +import gbf/vm.{type VirtualMachine} +import gleam/list +import gleam/option.{type Option, None, Some} +import gleam/result + +pub type Error { + PointerRanOffTape + IntegerOverflow + IntegerUnderflow + EmptyInput + InvalidChar(Int) + UnexpectedCommand(pos: lexer.Position) +} + +pub fn eval(vm: VirtualMachine, node: AST) -> Result(VirtualMachine, Error) { + case node { + parser.Leaf(command) -> eval_command(command, vm) + parser.Node(block) -> eval_block(vm, block) + } +} + +fn eval_command( + command: Command, + vm: VirtualMachine, +) -> Result(VirtualMachine, Error) { + case command { + #(token.IncrementPointer, _) -> increment_pointer(vm) + #(token.DecrementPointer, _) -> decrement_pointer(vm) + #(token.IncrementByte, _) -> increment_byte(vm) + #(token.DecrementByte, _) -> decrement_byte(vm) + #(token.OutputByte, _) -> output_byte(vm) + #(token.InputByte, _) -> input_byte(vm) + + #(token.StartBlock, pos) -> Error(UnexpectedCommand(pos)) + #(token.EndBlock, pos) -> Error(UnexpectedCommand(pos)) + #(_, pos) -> Error(UnexpectedCommand(pos)) + } +} + +fn eval_block(vm: VirtualMachine, block: Block) -> Result(VirtualMachine, Error) { + use acc_vm, child <- list.fold(block.children, Ok(vm)) + case child { + parser.Leaf(command) -> result.try(acc_vm, eval_command(command, _)) + parser.Node(child_block) -> + result.try(acc_vm, eval_child_block(_, child_block)) + } +} + +fn eval_child_block(vm: VirtualMachine, child_block: Block) { + let cell_value = + vm.get_cell(vm, vm.pointer) + |> option.unwrap(0) + + case cell_value > 0 { + False -> Ok(vm) + True -> { + let new_acc = eval_block(vm, child_block) + result.try(new_acc, eval_child_block(_, child_block)) + } + } +} + +fn increment_pointer(vm: VirtualMachine) { + let pointer = vm.pointer + 1 + case pointer > vm.tape_size { + True -> PointerRanOffTape |> Error + False -> Ok(vm.VirtualMachine(..vm, pointer: pointer)) + } +} + +fn decrement_pointer(vm: VirtualMachine) { + let pointer = vm.pointer - 1 + case pointer < 0 { + True -> PointerRanOffTape |> Error + False -> Ok(vm.VirtualMachine(..vm, pointer: pointer)) + } +} + +fn increment_byte(vm: VirtualMachine) { + let cell_value = + vm.get_cell(vm, vm.pointer) + |> option.unwrap(0) + + let new_cell_value = cell_value + 1 + case new_cell_value > vm.cell_size { + True -> IntegerOverflow |> Error + False -> vm.set_cell(vm, vm.pointer, new_cell_value) |> Ok + } +} + +fn decrement_byte(vm: VirtualMachine) { + let cell_value = + vm.get_cell(vm, vm.pointer) + |> option.unwrap(0) + + let new_cell_value = cell_value - 1 + case new_cell_value < 0 { + True -> IntegerUnderflow |> Error + False -> vm.set_cell(vm, vm.pointer, new_cell_value) |> Ok + } +} + +fn input_byte(vm: VirtualMachine) { + case vm.input { + [] -> EmptyInput |> Error + [first, ..] -> { + let new_input = list.drop(vm.input, 1) + let vm = vm.set_cell(vm, vm.pointer, first) + Ok(vm.VirtualMachine(..vm, input: new_input)) + } + } +} + +fn output_byte(vm: VirtualMachine) { + let cell_value = + vm.get_cell(vm, vm.pointer) + |> option.unwrap(0) + + case to_char(cell_value) { + None -> InvalidChar(cell_value) |> Error + Some(char) -> { + let new_output = vm.output <> char + Ok(vm.VirtualMachine(..vm, output: new_output)) + } + } +} + +fn to_char(code: Int) -> Option(String) { + case code { + 0x0A -> Some("\n") + 0x20 -> Some(" ") + 0x21 -> Some("!") + 0x22 -> Some("\"") + 0x23 -> Some("#") + 0x24 -> Some("$") + 0x25 -> Some("%") + 0x26 -> Some("&") + 0x27 -> Some("'") + 0x28 -> Some("(") + 0x29 -> Some(")") + 0x2A -> Some("*") + 0x2B -> Some("+") + 0x2C -> Some(",") + 0x2D -> Some("-") + 0x2E -> Some(".") + 0x2F -> Some("/") + 0x30 -> Some("0") + 0x31 -> Some("1") + 0x32 -> Some("2") + 0x33 -> Some("3") + 0x34 -> Some("4") + 0x35 -> Some("5") + 0x36 -> Some("6") + 0x37 -> Some("7") + 0x38 -> Some("8") + 0x39 -> Some("9") + 0x3A -> Some(":") + 0x3B -> Some(";") + 0x3C -> Some("<") + 0x3D -> Some("=") + 0x3E -> Some(">") + 0x3F -> Some("?") + 0x40 -> Some("@") + 0x41 -> Some("A") + 0x42 -> Some("B") + 0x43 -> Some("C") + 0x44 -> Some("D") + 0x45 -> Some("E") + 0x46 -> Some("F") + 0x47 -> Some("G") + 0x48 -> Some("H") + 0x49 -> Some("I") + 0x4A -> Some("J") + 0x4B -> Some("K") + 0x4C -> Some("L") + 0x4D -> Some("M") + 0x4E -> Some("N") + 0x4F -> Some("O") + 0x50 -> Some("P") + 0x51 -> Some("Q") + 0x52 -> Some("R") + 0x53 -> Some("S") + 0x54 -> Some("T") + 0x55 -> Some("U") + 0x56 -> Some("V") + 0x57 -> Some("W") + 0x58 -> Some("X") + 0x59 -> Some("Y") + 0x5A -> Some("Z") + 0x5B -> Some("[") + 0x5C -> Some("\\") + 0x5D -> Some("]") + 0x5E -> Some("^") + 0x5F -> Some("_") + 0x60 -> Some("`") + 0x61 -> Some("a") + 0x62 -> Some("b") + 0x63 -> Some("c") + 0x64 -> Some("d") + 0x65 -> Some("e") + 0x66 -> Some("f") + 0x67 -> Some("g") + 0x68 -> Some("h") + 0x69 -> Some("i") + 0x6A -> Some("j") + 0x6B -> Some("k") + 0x6C -> Some("l") + 0x6D -> Some("m") + 0x6E -> Some("n") + 0x6F -> Some("o") + 0x70 -> Some("p") + 0x71 -> Some("q") + 0x72 -> Some("r") + 0x73 -> Some("s") + 0x74 -> Some("t") + 0x75 -> Some("u") + 0x76 -> Some("v") + 0x77 -> Some("w") + 0x78 -> Some("x") + 0x79 -> Some("y") + 0x7A -> Some("z") + 0x7B -> Some("{") + 0x7C -> Some("|") + 0x7D -> Some("}") + 0x7E -> Some("~") + _ -> None + } +}
M src/gbf/parser.gleam

@@ -5,8 +5,15 @@ import gleam/pair

import gleam/result pub type AST { - Leaf(#(Token, Position)) - Node(children: List(AST), position: Position) + Leaf(Command) + Node(Block) +} + +pub type Command = + #(Token, Position) + +pub type Block { + Block(children: List(AST), position: Position) } pub type Error {

@@ -16,7 +23,7 @@ UnexpectedBlock

} pub fn parse(tokens: List(#(Token, Position))) -> Result(AST, Error) { - let root = Node(children: [], position: Position(0)) + let root = Node(Block(children: [], position: Position(0))) use #(ast, remaining_tokens) <- result.try(parse_tokens(tokens, root))

@@ -46,15 +53,16 @@

fn parse_block(token, tokens, node) { case node { Leaf(_) -> Error(UnexpectedCommand) - Node(children, position) -> { - let child_block = Node(children: [], position: pair.second(token)) + Node(block) -> { + let child_block = Node(Block(children: [], position: pair.second(token))) use #(parsed_child_block, remaining_tokens) <- result.try(parse_tokens( tokens, child_block, )) - let new_children = list.append(children, [parsed_child_block]) - let new_node = Node(children: new_children, position:) + let new_children = list.append(block.children, [parsed_child_block]) + let new_node = + Node(Block(children: new_children, position: block.position)) parse_tokens(remaining_tokens, new_node) }

@@ -68,9 +76,13 @@ node: AST,

) { case node { Leaf(_) -> Error(UnexpectedBlock) - Node(children, position) -> { + Node(block) -> { let command = Leaf(token) - let node = Node(children: list.append(children, [command]), position:) + let node = + Node(Block( + children: list.append(block.children, [command]), + position: block.position, + )) parse_tokens(tokens, node) }
A src/gbf/vm.gleam

@@ -0,0 +1,46 @@

+import gleam/dict.{type Dict} +import gleam/option.{type Option, None, Some} + +pub const tape_size = 30_000 + +pub const cell_size = 255 + +/// The machine model we are going to use for this interpreter is very simple: +/// - Our memory consists of 30,000 cells (1000 rows * 30 columns). +/// - There's a data pointer which points to a specific cell and is initialized at +/// the leftmost cell, an error will be reported if the pointer runs off the +/// tape at either end. +/// pointer = 0 +/// - A data cell is 8 bits, and an error will be reported if the program tries +/// to perform under- or overflow, i.e. decrement 0 or increment 255. +/// - Two streams of bytes for input and output using the ASCII character +/// encoding. +pub type VirtualMachine { + VirtualMachine(pointer: Index, cells: Cells, output: String, input: List(Int)) +} + +pub type Cells = + Dict(Int, Int) + +pub type Index = + Int + +pub fn new(input: List(Int)) -> VirtualMachine { + VirtualMachine(input:, pointer: 0, cells: dict.new(), output: "") +} + +pub fn get_cell(vm: VirtualMachine, pointer: Index) -> Option(Int) { + case dict.get(vm.cells, pointer) { + Ok(value) -> Some(value) + Error(_) -> None + } +} + +pub fn set_cell( + vm: VirtualMachine, + pointer: Index, + value: Int, +) -> VirtualMachine { + let new_cells = dict.insert(vm.cells, pointer, value) + VirtualMachine(..vm, cells: new_cells) +}