all repos

scratch @ 85452e1838bdb2cabee2e46380e8d38e2b5abc8f

⭐ me doing recreational ~~drugs~~ programming
19 files changed, 797 insertions(+), 0 deletions(-)
⭐ gleaming brainfuck
Author: Oleksandr Smirnov olexsmir@gmail.com
Committed at: 2025-10-23 13:26:17 +0300
Change ID: qootszsvnqntntwpxutookswmxtprlzv
Parent: 26124b5
A brainfuck/.gitignore
···
                
                1
                +*.beam

              
                
                2
                +*.ez

              
                
                3
                +/build

              
                
                4
                +erl_crash.dump

              
A brainfuck/README.md
···
                
                1
                +# gbf

              
                
                2
                +

              
                
                3
                +I was bored and made this :star: gleaming brainfuck interpreter.

              
                
                4
                +

              
                
                5
                +## How to use?

              
                
                6
                +### As library

              
                
                7
                +```gleam

              
                
                8
                +import gbf

              
                
                9
                +import gleam/io

              
                
                10
                +

              
                
                11
                +pub fn main() -> Nil {

              
                
                12
                +  let input =

              
                
                13
                +    "++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++."

              
                
                14
                +

              
                
                15
                +  let assert Ok(virtual_machine) = gbf.run(input)

              
                
                16
                +

              
                
                17
                +  virtual_machine

              
                
                18
                +  |> gbf.output

              
                
                19
                +  |> io.println

              
                
                20
                +//>  Hello World!

              
                
                21
                +}

              
                
                22
                +```

              
                
                23
                +

              
                
                24
                +### As CLI tool

              
                
                25
                +```bash

              
                
                26
                +gleam run -m gbf/run ./examples/helloworld.bf

              
                
                27
                +#> Hello World!

              
                
                28
                +```

              
A brainfuck/examples/helloworld.bf
···
                
                1
                +++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.

              
A brainfuck/examples/piramide.b
···
                
                1
                +,>++++++++[-<----->],

              
                
                2
                +[>++++++++[-<------>]

              
                
                3
                +  <<[->++++++++++<]>

              
                
                4
                +>]

              
                
                5
                +<[->+>+>>>>>>>+<<<<<<<<<]

              
                
                6
                +>>>>++++++++[-<++++>]

              
                
                7
                +>++++++[-<+++++++>]

              
                
                8
                ++>>>++[-<+++++>]

              
                
                9
                +<<<<<<[-[>.<-]

              
                
                10
                +  <[-<+>>+<]

              
                
                11
                +  <[->+<]

              
                
                12
                +  >>>>>[-<.>>+<]

              
                
                13
                +  >[-<+>]

              
                
                14
                +  >.<<++<<<-<->]

              
                
                15
                +>>>>>>>-[-<<<<<<.>>>>>>]

              
                
                16
                +<<<<<.

              
A brainfuck/examples/random_number.bf
···
                
                1
                +https://xkcd.com/221

              
                
                2
                +

              
                
                3
                +++++[->+++<]>+[->++++<]>.

              
A brainfuck/gleam.toml
···
                
                1
                +name = "gbf"

              
                
                2
                +version = "1.0.0"

              
                
                3
                +description = "gleaming brainfuck"

              
                
                4
                +licences = ["GLWTPL"]

              
                
                5
                +repository = { type = "github", user = "olexsmir", repo = "gbf" }

              
                
                6
                +

              
                
                7
                +[dependencies]

              
                
                8
                +gleam_stdlib = ">= 0.44.0 and < 2.0.0"

              
                
                9
                +splitter = ">= 1.1.0 and < 2.0.0"

              
                
                10
                +argv = ">= 1.0.2 and < 2.0.0"

              
                
                11
                +simplifile = ">= 2.3.0 and < 3.0.0"

              
                
                12
                +

              
                
                13
                +[dev-dependencies]

              
                
                14
                +gleeunit = ">= 1.0.0 and < 2.0.0"

              
A brainfuck/manifest.toml
···
                
                1
                +# This file was generated by Gleam

              
                
                2
                +# You typically do not need to edit this file

              
                
                3
                +

              
                
                4
                +packages = [

              
                
                5
                +  { name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" },

              
                
                6
                +  { name = "filepath", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "B06A9AF0BF10E51401D64B98E4B627F1D2E48C154967DA7AF4D0914780A6D40A" },

              
                
                7
                +  { name = "gleam_stdlib", version = "0.65.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "7C69C71D8C493AE11A5184828A77110EB05A7786EBF8B25B36A72F879C3EE107" },

              
                
                8
                +  { name = "gleeunit", version = "1.6.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "FDC68A8C492B1E9B429249062CD9BAC9B5538C6FBF584817205D0998C42E1DAC" },

              
                
                9
                +  { name = "simplifile", version = "2.3.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "0A868DAC6063D9E983477981839810DC2E553285AB4588B87E3E9C96A7FB4CB4" },

              
                
                10
                +  { name = "splitter", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "splitter", source = "hex", outer_checksum = "05564A381580395DCDEFF4F88A64B021E8DAFA6540AE99B4623962F52976AA9D" },

              
                
                11
                +]

              
                
                12
                +

              
                
                13
                +[requirements]

              
                
                14
                +argv = { version = ">= 1.0.2 and < 2.0.0" }

              
                
                15
                +gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" }

              
                
                16
                +gleeunit = { version = ">= 1.0.0 and < 2.0.0" }

              
                
                17
                +simplifile = { version = ">= 2.3.0 and < 3.0.0" }

              
                
                18
                +splitter = { version = ">= 1.1.0 and < 2.0.0" }

              
A brainfuck/src/gbf.gleam
···
                
                1
                +import gbf/internal/ascii

              
                
                2
                +import gbf/internal/eval

              
                
                3
                +import gbf/internal/lexer

              
                
                4
                +import gbf/internal/parser

              
                
                5
                +import gbf/internal/vm.{type VirtualMachine}

              
                
                6
                +import gleam/list

              
                
                7
                +import gleam/result

              
                
                8
                +import gleam/string

              
                
                9
                +

              
                
                10
                +pub type Error {

              
                
                11
                +  Parser(reason: parser.Error)

              
                
                12
                +  Eval(reason: eval.Error)

              
                
                13
                +}

              
                
                14
                +

              
                
                15
                +pub fn run(source: String) -> Result(VirtualMachine, Error) {

              
                
                16
                +  let bvm =

              
                
                17
                +    source

              
                
                18
                +    |> string.split(on: "")

              
                
                19
                +    |> list.map(ascii.to_code)

              
                
                20
                +    |> vm.new

              
                
                21
                +

              
                
                22
                +  use ast <- result.try(parse_ast(source))

              
                
                23
                +  use bvm <- result.try(eval_ast(bvm, ast))

              
                
                24
                +

              
                
                25
                +  Ok(bvm)

              
                
                26
                +}

              
                
                27
                +

              
                
                28
                +pub fn output(virtual_machine: VirtualMachine) -> String {

              
                
                29
                +  vm.output(virtual_machine)

              
                
                30
                +}

              
                
                31
                +

              
                
                32
                +fn parse_ast(source: String) {

              
                
                33
                +  source

              
                
                34
                +  |> lexer.new

              
                
                35
                +  |> lexer.lex

              
                
                36
                +  |> parser.parse

              
                
                37
                +  |> result.map_error(fn(e) { Parser(e) })

              
                
                38
                +}

              
                
                39
                +

              
                
                40
                +fn eval_ast(vm, ast) {

              
                
                41
                +  eval.eval(vm, ast)

              
                
                42
                +  |> result.map_error(fn(e) { Eval(e) })

              
                
                43
                +}

              
A brainfuck/src/gbf/internal/ascii.gleam
···
                
                1
                +import gleam/bit_array

              
                
                2
                +import gleam/string

              
                
                3
                +

              
                
                4
                +pub fn to_code(s: String) {

              
                
                5
                +  let bytes = bit_array.from_string(s)

              
                
                6
                +  case bit_array.byte_size(bytes) {

              
                
                7
                +    1 ->

              
                
                8
                +      case bytes {

              
                
                9
                +        <<value>> -> value

              
                
                10
                +        _ -> 0

              
                
                11
                +      }

              
                
                12
                +    _ -> 0

              
                
                13
                +  }

              
                
                14
                +}

              
                
                15
                +

              
                
                16
                +pub fn from_code(code: Int) {

              
                
                17
                +  case code {

              
                
                18
                +    c if c >= 1 && c <= 255 -> {

              
                
                19
                +      case string.utf_codepoint(code) {

              
                
                20
                +        Ok(codepoint) -> string.from_utf_codepoints([codepoint])

              
                
                21
                +        Error(_) -> ""

              
                
                22
                +      }

              
                
                23
                +    }

              
                
                24
                +    _ -> ""

              
                
                25
                +  }

              
                
                26
                +}

              
A brainfuck/src/gbf/internal/eval.gleam
···
                
                1
                +import gbf/internal/lexer

              
                
                2
                +import gbf/internal/parser.{type AST, type Block, type Command}

              
                
                3
                +import gbf/internal/token

              
                
                4
                +import gbf/internal/vm.{type VirtualMachine}

              
                
                5
                +import gleam/int

              
                
                6
                +import gleam/list

              
                
                7
                +import gleam/result

              
                
                8
                +

              
                
                9
                +pub type Error {

              
                
                10
                +  /// An unexpected command was encountered at the given position.

              
                
                11
                +  UnexpectedCommand(pos: lexer.Position)

              
                
                12
                +

              
                
                13
                +  /// An error occurred in the virtual machine

              
                
                14
                +  VmError(reason: vm.Error, pos: lexer.Position)

              
                
                15
                +}

              
                
                16
                +

              
                
                17
                +/// Evaluates an AST node against the virtual machine.

              
                
                18
                +///

              
                
                19
                +pub fn eval(vm: VirtualMachine, node: AST) -> Result(VirtualMachine, Error) {

              
                
                20
                +  case node {

              
                
                21
                +    parser.Leaf(command) -> eval_command(vm, command)

              
                
                22
                +    parser.Node(block) -> eval_block(vm, block)

              
                
                23
                +  }

              
                
                24
                +}

              
                
                25
                +

              
                
                26
                +fn eval_command(

              
                
                27
                +  vm: VirtualMachine,

              
                
                28
                +  command: Command,

              
                
                29
                +) -> Result(VirtualMachine, Error) {

              
                
                30
                +  case command {

              
                
                31
                +    #(token.Comment(_), _) -> Ok(vm)

              
                
                32
                +

              
                
                33
                +    #(token.IncrementPointer, p) ->

              
                
                34
                +      vm.set_pointer(vm, vm.pointer + 1) |> wrap_vm_error(p)

              
                
                35
                +    #(token.DecrementPointer, p) ->

              
                
                36
                +      vm.set_pointer(vm, vm.pointer - 1) |> wrap_vm_error(p)

              
                
                37
                +

              
                
                38
                +    #(token.IncrementByte, p) -> mut_byte(vm, int.add) |> wrap_vm_error(p)

              
                
                39
                +    #(token.DecrementByte, p) -> mut_byte(vm, int.subtract) |> wrap_vm_error(p)

              
                
                40
                +

              
                
                41
                +    #(token.InputByte, p) -> vm.input_byte(vm) |> wrap_vm_error(p)

              
                
                42
                +    #(token.OutputByte, p) -> vm.output_byte(vm) |> wrap_vm_error(p)

              
                
                43
                +

              
                
                44
                +    #(token.StartBlock, p) -> Error(UnexpectedCommand(p))

              
                
                45
                +    #(token.EndBlock, p) -> Error(UnexpectedCommand(p))

              
                
                46
                +    #(_, p) -> Error(UnexpectedCommand(p))

              
                
                47
                +  }

              
                
                48
                +}

              
                
                49
                +

              
                
                50
                +fn eval_block(vm: VirtualMachine, block: Block) -> Result(VirtualMachine, Error) {

              
                
                51
                +  use acc_vm, child <- list.fold(block.children, Ok(vm))

              
                
                52
                +  case child {

              
                
                53
                +    parser.Leaf(command) -> result.try(acc_vm, eval_command(_, command))

              
                
                54
                +    parser.Node(child_block) ->

              
                
                55
                +      result.try(acc_vm, eval_child_block(_, child_block))

              
                
                56
                +  }

              
                
                57
                +}

              
                
                58
                +

              
                
                59
                +fn eval_child_block(vm: VirtualMachine, child_block: Block) {

              
                
                60
                +  use cell_value <- result.try(

              
                
                61
                +    vm.get_cell(vm, vm.pointer)

              
                
                62
                +    |> result.map_error(VmError(_, pos: child_block.position)),

              
                
                63
                +  )

              
                
                64
                +

              
                
                65
                +  case cell_value > 0 {

              
                
                66
                +    False -> Ok(vm)

              
                
                67
                +    True -> {

              
                
                68
                +      let acc = eval_block(vm, child_block)

              
                
                69
                +      result.try(acc, eval_child_block(_, child_block))

              
                
                70
                +    }

              
                
                71
                +  }

              
                
                72
                +}

              
                
                73
                +

              
                
                74
                +fn mut_byte(vm: VirtualMachine, op: fn(Int, Int) -> Int) {

              
                
                75
                +  use cell <- result.try(vm.get_cell(vm, vm.pointer))

              
                
                76
                +  let cell = op(cell, 1)

              
                
                77
                +  vm.set_cell(vm, vm.pointer, cell)

              
                
                78
                +}

              
                
                79
                +

              
                
                80
                +fn wrap_vm_error(

              
                
                81
                +  r: Result(VirtualMachine, vm.Error),

              
                
                82
                +  pos: lexer.Position,

              
                
                83
                +) -> Result(VirtualMachine, Error) {

              
                
                84
                +  result.map_error(r, VmError(_, pos))

              
                
                85
                +}

              
A brainfuck/src/gbf/internal/lexer.gleam
···
                
                1
                +import gbf/internal/token.{type Token}

              
                
                2
                +import gleam/list

              
                
                3
                +import gleam/string

              
                
                4
                +import splitter

              
                
                5
                +

              
                
                6
                +pub opaque type Lexer {

              
                
                7
                +  Lexer(source: String, offset: Int, newlines: splitter.Splitter)

              
                
                8
                +}

              
                
                9
                +

              
                
                10
                +pub type Position {

              
                
                11
                +  /// A token position in a wile, represented as offset of bytes

              
                
                12
                +  Position(offset: Int)

              
                
                13
                +}

              
                
                14
                +

              
                
                15
                +pub fn new(source) {

              
                
                16
                +  Lexer(source:, offset: 0, newlines: splitter.new(["\r\n", "\n"]))

              
                
                17
                +}

              
                
                18
                +

              
                
                19
                +pub fn lex(lexer: Lexer) -> List(#(Token, Position)) {

              
                
                20
                +  do_lex(lexer, [])

              
                
                21
                +  |> list.reverse

              
                
                22
                +}

              
                
                23
                +

              
                
                24
                +fn do_lex(lexer: Lexer, tokens: List(#(Token, Position))) {

              
                
                25
                +  case next(lexer) {

              
                
                26
                +    #(_, #(token.EndOfFile, _)) -> tokens

              
                
                27
                +    #(lexer, token) -> do_lex(lexer, [token, ..tokens])

              
                
                28
                +  }

              
                
                29
                +}

              
                
                30
                +

              
                
                31
                +fn next(lexer: Lexer) {

              
                
                32
                +  case lexer.source {

              
                
                33
                +    " " <> source | "\n" <> source | "\r" <> source | "\t" <> source ->

              
                
                34
                +      advance(lexer, source, 1) |> next

              
                
                35
                +

              
                
                36
                +    ">" <> source -> token(lexer, token.IncrementPointer, source, 1)

              
                
                37
                +    "<" <> source -> token(lexer, token.DecrementPointer, source, 1)

              
                
                38
                +    "+" <> source -> token(lexer, token.IncrementByte, source, 1)

              
                
                39
                +    "-" <> source -> token(lexer, token.DecrementByte, source, 1)

              
                
                40
                +    "." <> source -> token(lexer, token.OutputByte, source, 1)

              
                
                41
                +    "," <> source -> token(lexer, token.InputByte, source, 1)

              
                
                42
                +    "[" <> source -> token(lexer, token.StartBlock, source, 1)

              
                
                43
                +    "]" <> source -> token(lexer, token.EndBlock, source, 1)

              
                
                44
                +

              
                
                45
                +    _ ->

              
                
                46
                +      case string.pop_grapheme(lexer.source) {

              
                
                47
                +        Error(_) -> #(lexer, #(token.EndOfFile, Position(lexer.offset)))

              
                
                48
                +        Ok(_) -> comment(lexer, lexer.offset)

              
                
                49
                +      }

              
                
                50
                +  }

              
                
                51
                +}

              
                
                52
                +

              
                
                53
                +fn advance(lexer, source, offset) {

              
                
                54
                +  Lexer(..lexer, source:, offset: lexer.offset + offset)

              
                
                55
                +}

              
                
                56
                +

              
                
                57
                +fn advanced(

              
                
                58
                +  token: #(Token, Position),

              
                
                59
                +  lexer: Lexer,

              
                
                60
                +  source: String,

              
                
                61
                +  offset: Int,

              
                
                62
                +) -> #(Lexer, #(Token, Position)) {

              
                
                63
                +  #(advance(lexer, source, offset), token)

              
                
                64
                +}

              
                
                65
                +

              
                
                66
                +fn token(

              
                
                67
                +  lexer: Lexer,

              
                
                68
                +  token: Token,

              
                
                69
                +  source: String,

              
                
                70
                +  offset: Int,

              
                
                71
                +) -> #(Lexer, #(Token, Position)) {

              
                
                72
                +  #(token, Position(offset: lexer.offset))

              
                
                73
                +  |> advanced(lexer, source, offset)

              
                
                74
                +}

              
                
                75
                +

              
                
                76
                +fn comment(lexer: Lexer, start: Int) -> #(Lexer, #(Token, Position)) {

              
                
                77
                +  let #(prefix, suffix) = splitter.split_before(lexer.newlines, lexer.source)

              
                
                78
                +  let eaten = string.byte_size(prefix)

              
                
                79
                +  let lexer = advance(lexer, suffix, eaten)

              
                
                80
                +

              
                
                81
                +  #(lexer, #(token.Comment(prefix), Position(start)))

              
                
                82
                +}

              
A brainfuck/src/gbf/internal/parser.gleam
···
                
                1
                +import gbf/internal/lexer.{type Position, Position}

              
                
                2
                +import gbf/internal/token.{type Token}

              
                
                3
                +import gleam/list

              
                
                4
                +import gleam/pair

              
                
                5
                +import gleam/result

              
                
                6
                +

              
                
                7
                +pub type AST {

              
                
                8
                +  /// A single command

              
                
                9
                +  ///

              
                
                10
                +  Leaf(Command)

              
                
                11
                +

              
                
                12
                +  /// A block with nested children (used for loops)

              
                
                13
                +  ///

              
                
                14
                +  Node(Block)

              
                
                15
                +}

              
                
                16
                +

              
                
                17
                +pub type Command =

              
                
                18
                +  #(Token, Position)

              
                
                19
                +

              
                
                20
                +pub type Block {

              
                
                21
                +  Block(children: List(AST), position: Position)

              
                
                22
                +}

              
                
                23
                +

              
                
                24
                +pub type Error {

              
                
                25
                +  FinishedTooEarly

              
                
                26
                +  UnexpectedCommand

              
                
                27
                +  UnexpectedBlock

              
                
                28
                +}

              
                
                29
                +

              
                
                30
                +/// Parses a list of tokens into an Abstract Syntax Tree.

              
                
                31
                +///

              
                
                32
                +/// Takes a flat list of tokens with their positions and constructs

              
                
                33
                +/// a hierarchical tree structure where loop blocks become nested nodes.

              
                
                34
                +/// All tokens must be consumed for successful parsing.

              
                
                35
                +///

              
                
                36
                +pub fn parse(tokens: List(#(Token, Position))) -> Result(AST, Error) {

              
                
                37
                +  let root = Node(Block(children: [], position: Position(0)))

              
                
                38
                +  use #(ast, remaining_tokens) <- result.try(parse_tokens(tokens, root))

              
                
                39
                +

              
                
                40
                +  case remaining_tokens {

              
                
                41
                +    [] -> Ok(ast)

              
                
                42
                +    _ -> Error(FinishedTooEarly)

              
                
                43
                +  }

              
                
                44
                +}

              
                
                45
                +

              
                
                46
                +fn parse_tokens(tokens: List(#(Token, Position)), node: AST) {

              
                
                47
                +  case tokens {

              
                
                48
                +    [] -> Ok(#(node, []))

              
                
                49
                +    [token, ..rest] -> {

              
                
                50
                +      case token {

              
                
                51
                +        #(token.StartBlock, _) -> parse_block(token, rest, node)

              
                
                52
                +        #(token.EndBlock, _) -> parse_block_end(rest, node)

              
                
                53
                +        _ -> parse_command(token, rest, node)

              
                
                54
                +      }

              
                
                55
                +    }

              
                
                56
                +  }

              
                
                57
                +}

              
                
                58
                +

              
                
                59
                +fn parse_block_end(tokens: List(#(Token, Position)), node: AST) {

              
                
                60
                +  Ok(#(node, tokens))

              
                
                61
                +}

              
                
                62
                +

              
                
                63
                +fn parse_block(token, tokens, node) {

              
                
                64
                +  case node {

              
                
                65
                +    Leaf(_) -> Error(UnexpectedCommand)

              
                
                66
                +    Node(block) -> {

              
                
                67
                +      let child_block = Node(Block(children: [], position: pair.second(token)))

              
                
                68
                +      use #(parsed_child_block, remaining_tokens) <- result.try(parse_tokens(

              
                
                69
                +        tokens,

              
                
                70
                +        child_block,

              
                
                71
                +      ))

              
                
                72
                +

              
                
                73
                +      let children = list.append(block.children, [parsed_child_block])

              
                
                74
                +      let node = Node(Block(children: children, position: block.position))

              
                
                75
                +

              
                
                76
                +      parse_tokens(remaining_tokens, node)

              
                
                77
                +    }

              
                
                78
                +  }

              
                
                79
                +}

              
                
                80
                +

              
                
                81
                +fn parse_command(

              
                
                82
                +  token: #(Token, Position),

              
                
                83
                +  tokens: List(#(Token, Position)),

              
                
                84
                +  node: AST,

              
                
                85
                +) {

              
                
                86
                +  case node {

              
                
                87
                +    Leaf(_) -> Error(UnexpectedBlock)

              
                
                88
                +    Node(block) -> {

              
                
                89
                +      let command = Leaf(token)

              
                
                90
                +      let node =

              
                
                91
                +        Node(Block(

              
                
                92
                +          children: list.append(block.children, [command]),

              
                
                93
                +          position: block.position,

              
                
                94
                +        ))

              
                
                95
                +

              
                
                96
                +      parse_tokens(tokens, node)

              
                
                97
                +    }

              
                
                98
                +  }

              
                
                99
                +}

              
A brainfuck/src/gbf/internal/token.gleam
···
                
                1
                +pub type Token {

              
                
                2
                +  /// Everything that is not one of the tokens below, considered the be

              
                
                3
                +  /// a comment.

              
                
                4
                +  Comment(String)

              
                
                5
                +

              
                
                6
                +  /// Increment the data pointer by one.

              
                
                7
                +  /// `>` symbol

              
                
                8
                +  IncrementPointer

              
                
                9
                +

              
                
                10
                +  /// Decrement the data pointer by one.

              
                
                11
                +  /// `<` symbol

              
                
                12
                +  DecrementPointer

              
                
                13
                +

              
                
                14
                +  /// Increment the byte at the data pointer by one.

              
                
                15
                +  /// `+` symbol

              
                
                16
                +  IncrementByte

              
                
                17
                +

              
                
                18
                +  /// Decrement the byte at the data pointer by one.

              
                
                19
                +  /// `-` symbol

              
                
                20
                +  DecrementByte

              
                
                21
                +

              
                
                22
                +  /// Output the byte at the data pointer.

              
                
                23
                +  /// `.` symbol

              
                
                24
                +  OutputByte

              
                
                25
                +

              
                
                26
                +  /// Accept one byte of input, storing its value in the byte at the data pointer.

              
                
                27
                +  /// `,` symbol

              
                
                28
                +  InputByte

              
                
                29
                +

              
                
                30
                +  /// If the byte at the data pointer is zero, then instead of moving the

              
                
                31
                +  /// instruction pointer forward to the next command, jump it forward to the

              
                
                32
                +  /// command after the matching ] command.

              
                
                33
                +  /// `[` symbol

              
                
                34
                +  StartBlock

              
                
                35
                +

              
                
                36
                +  /// If the byte at the data pointer is nonzero, then instead of moving the

              
                
                37
                +  /// instruction pointer forward to the next command, jump it back to the

              
                
                38
                +  /// command after the matching [ command.

              
                
                39
                +  /// `]` symbol

              
                
                40
                +  EndBlock

              
                
                41
                +

              
                
                42
                +  /// End of file

              
                
                43
                +  EndOfFile

              
                
                44
                +}

              
A brainfuck/src/gbf/internal/vm.gleam
···
                
                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
                +}

              
A brainfuck/src/gbf/run.gleam
···
                
                1
                +import argv

              
                
                2
                +import gbf

              
                
                3
                +import gleam/io

              
                
                4
                +import simplifile

              
                
                5
                +

              
                
                6
                +pub fn main() -> Nil {

              
                
                7
                +  case argv.load().arguments {

              
                
                8
                +    [filename] -> {

              
                
                9
                +      let assert Ok(source) = simplifile.read(filename)

              
                
                10
                +      let assert Ok(virtual_machine) = gbf.run(source)

              
                
                11
                +

              
                
                12
                +      virtual_machine

              
                
                13
                +      |> gbf.output

              
                
                14
                +      |> io.println

              
                
                15
                +    }

              
                
                16
                +    _ -> io.println("usage: ./program filename.bf")

              
                
                17
                +  }

              
                
                18
                +}

              
A brainfuck/test/gbf_lexer_test.gleam
···
                
                1
                +import gbf/internal/lexer

              
                
                2
                +import gbf/internal/token

              
                
                3
                +import gleeunit/should

              
                
                4
                +

              
                
                5
                +pub fn can_lex_test() {

              
                
                6
                +  "><+-.,[] this is a comment"

              
                
                7
                +  |> lexer.new

              
                
                8
                +  |> lexer.lex

              
                
                9
                +  |> should.equal([

              
                
                10
                +    #(token.IncrementPointer, lexer.Position(0)),

              
                
                11
                +    #(token.DecrementPointer, lexer.Position(1)),

              
                
                12
                +    #(token.IncrementByte, lexer.Position(2)),

              
                
                13
                +    #(token.DecrementByte, lexer.Position(3)),

              
                
                14
                +    #(token.OutputByte, lexer.Position(4)),

              
                
                15
                +    #(token.InputByte, lexer.Position(5)),

              
                
                16
                +    #(token.StartBlock, lexer.Position(6)),

              
                
                17
                +    #(token.EndBlock, lexer.Position(7)),

              
                
                18
                +    #(token.Comment("this is a comment"), lexer.Position(9)),

              
                
                19
                +  ])

              
                
                20
                +}

              
                
                21
                +

              
                
                22
                +pub fn multiline_test() {

              
                
                23
                +  "this is a comment

              
                
                24
                ++++

              
                
                25
                +<.

              
                
                26
                +  "

              
                
                27
                +  |> lexer.new

              
                
                28
                +  |> lexer.lex

              
                
                29
                +  |> should.equal([

              
                
                30
                +    #(token.Comment("this is a comment"), lexer.Position(0)),

              
                
                31
                +    #(token.IncrementByte, lexer.Position(18)),

              
                
                32
                +    #(token.IncrementByte, lexer.Position(19)),

              
                
                33
                +    #(token.IncrementByte, lexer.Position(20)),

              
                
                34
                +    #(token.DecrementPointer, lexer.Position(22)),

              
                
                35
                +    #(token.OutputByte, lexer.Position(23)),

              
                
                36
                +  ])

              
                
                37
                +}

              
A brainfuck/test/gbf_parser_test.gleam
···
                
                1
                +import gbf/internal/lexer.{Position}

              
                
                2
                +import gbf/internal/parser.{Block, Leaf, Node}

              
                
                3
                +import gbf/internal/token.{DecrementByte, IncrementByte, IncrementPointer}

              
                
                4
                +import gleeunit/should

              
                
                5
                +

              
                
                6
                +pub fn should_parse_test() {

              
                
                7
                +  "+[->+]>"

              
                
                8
                +  |> lexer.new

              
                
                9
                +  |> lexer.lex

              
                
                10
                +  |> parser.parse

              
                
                11
                +  |> should.equal(

              
                
                12
                +    Ok(

              
                
                13
                +      Node(

              
                
                14
                +        Block(position: Position(0), children: [

              
                
                15
                +          Leaf(#(IncrementByte, Position(0))),

              
                
                16
                +          Node(

              
                
                17
                +            Block(position: Position(1), children: [

              
                
                18
                +              Leaf(#(DecrementByte, Position(2))),

              
                
                19
                +              Leaf(#(IncrementPointer, Position(3))),

              
                
                20
                +              Leaf(#(IncrementByte, Position(4))),

              
                
                21
                +            ]),

              
                
                22
                +          ),

              
                
                23
                +          Leaf(#(IncrementPointer, Position(6))),

              
                
                24
                +        ]),

              
                
                25
                +      ),

              
                
                26
                +    ),

              
                
                27
                +  )

              
                
                28
                +}

              
A brainfuck/test/gbf_test.gleam
···
                
                1
                +import gbf

              
                
                2
                +import gleeunit

              
                
                3
                +import gleeunit/should

              
                
                4
                +

              
                
                5
                +pub fn main() -> Nil {

              
                
                6
                +  gleeunit.main()

              
                
                7
                +}

              
                
                8
                +

              
                
                9
                +pub fn should_run_hello_world_test() {

              
                
                10
                +  let input =

              
                
                11
                +    "++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++."

              
                
                12
                +

              
                
                13
                +  let assert Ok(bvm) = gbf.run(input)

              
                
                14
                +

              
                
                15
                +  bvm

              
                
                16
                +  |> gbf.output

              
                
                17
                +  |> should.equal("Hello World!\n")

              
                
                18
                +}

              
A brainfuck/test/gbf_vm_test.gleam
···
                
                1
                +import gbf/internal/ascii

              
                
                2
                +import gbf/internal/vm.{type VirtualMachine}

              
                
                3
                +import gleam/result

              
                
                4
                +import gleeunit/should

              
                
                5
                +

              
                
                6
                +fn setup(input) -> VirtualMachine {

              
                
                7
                +  vm.new(input)

              
                
                8
                +}

              
                
                9
                +

              
                
                10
                +pub fn get_cell_test() {

              
                
                11
                +  let vm = setup([])

              
                
                12
                +

              
                
                13
                +  vm.get_cell(vm, 3)

              
                
                14
                +  |> should.equal(Ok(0))

              
                
                15
                +}

              
                
                16
                +

              
                
                17
                +pub fn get_cell_out_of_tape_test() {

              
                
                18
                +  let vm = setup([])

              
                
                19
                +

              
                
                20
                +  vm.get_cell(vm, vm.tape_size + 1)

              
                
                21
                +  |> should.equal(Error(vm.PointerRanOffTape))

              
                
                22
                +}

              
                
                23
                +

              
                
                24
                +pub fn set_cell_test() {

              
                
                25
                +  let vm = setup([])

              
                
                26
                +

              
                
                27
                +  vm.set_cell(vm, 2, 22)

              
                
                28
                +  |> should.be_ok

              
                
                29
                +}

              
                
                30
                +

              
                
                31
                +pub fn set_cell_errors_test() {

              
                
                32
                +  let vm = setup([])

              
                
                33
                +

              
                
                34
                +  vm.set_cell(vm, vm.tape_size + 1, 22)

              
                
                35
                +  |> should.be_error

              
                
                36
                +

              
                
                37
                +  vm.set_cell(vm, 2, vm.cell_size + 1)

              
                
                38
                +  |> should.be_error

              
                
                39
                +}

              
                
                40
                +

              
                
                41
                +pub fn set_pointer_test() {

              
                
                42
                +  let vm = setup([])

              
                
                43
                +

              
                
                44
                +  vm.set_pointer(vm, 2)

              
                
                45
                +  |> should.be_ok

              
                
                46
                +}

              
                
                47
                +

              
                
                48
                +pub fn output_byte_empty_test() {

              
                
                49
                +  let vm = setup([])

              
                
                50
                +

              
                
                51
                +  vm.output_byte(vm)

              
                
                52
                +  |> should.equal(Error(vm.InvalidChar(0)))

              
                
                53
                +}

              
                
                54
                +

              
                
                55
                +pub fn output_byte_test() {

              
                
                56
                +  let vm = setup([ascii.to_code("a")])

              
                
                57
                +  use vm <- result.try(vm.output_byte(vm))

              
                
                58
                +

              
                
                59
                +  should.equal(vm.output, "a")

              
                
                60
                +  Ok("")

              
                
                61
                +}

              
                
                62
                +

              
                
                63
                +pub fn input_byte_empty_test() {

              
                
                64
                +  let vm = setup([])

              
                
                65
                +

              
                
                66
                +  vm.input_byte(vm)

              
                
                67
                +  |> should.equal(Error(vm.EmptyInput))

              
                
                68
                +}

              
                
                69
                +

              
                
                70
                +pub fn input_byte_test() {

              
                
                71
                +  let vm = setup([ascii.to_code("a"), ascii.to_code("b")])

              
                
                72
                +  use vm <- result.try(vm.input_byte(vm))

              
                
                73
                +

              
                
                74
                +  should.equal(vm.input, [ascii.to_code("b")])

              
                
                75
                +  Ok("")

              
                
                76
                +}