all repos

gbf @ a3c25d0

⭐ gleaming brainfuck

gbf/src/gbf/parser.gleam(view raw)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import gbf/lexer.{type Position, Position}
import gbf/token.{type Token}
import gleam/list
import gleam/pair
import gleam/result

pub type AST {
  /// A single command
  ///
  Leaf(Command)

  /// A block with nested children (used for loops)
  ///
  Node(Block)
}

pub type Command =
  #(Token, Position)

pub type Block {
  Block(children: List(AST), position: Position)
}

pub type Error {
  FinishedTooEarly
  UnexpectedCommand
  UnexpectedBlock
}

/// Parses a list of tokens into an Abstract Syntax Tree.
///
/// Takes a flat list of tokens with their positions and constructs
/// a hierarchical tree structure where loop blocks become nested nodes.
/// All tokens must be consumed for successful parsing.
///
pub fn parse(tokens: List(#(Token, Position))) -> Result(AST, Error) {
  let root = Node(Block(children: [], position: Position(0)))

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

  case remaining_tokens {
    [] -> Ok(ast)
    _ -> Error(FinishedTooEarly)
  }
}

fn parse_tokens(tokens: List(#(Token, Position)), node: AST) {
  case tokens {
    [] -> Ok(#(node, []))
    [token, ..rest] -> {
      case token {
        #(token.StartBlock, _) -> parse_block(token, rest, node)
        #(token.EndBlock, _) -> parse_block_end(rest, node)
        _ -> parse_command(token, rest, node)
      }
    }
  }
}

fn parse_block_end(tokens: List(#(Token, Position)), node: AST) {
  Ok(#(node, tokens))
}

fn parse_block(token, tokens, node) {
  case node {
    Leaf(_) -> Error(UnexpectedCommand)
    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(block.children, [parsed_child_block])
      let new_node =
        Node(Block(children: new_children, position: block.position))

      parse_tokens(remaining_tokens, new_node)
    }
  }
}

fn parse_command(
  token: #(Token, Position),
  tokens: List(#(Token, Position)),
  node: AST,
) {
  case node {
    Leaf(_) -> Error(UnexpectedBlock)
    Node(block) -> {
      let command = Leaf(token)
      let node =
        Node(Block(
          children: list.append(block.children, [command]),
          position: block.position,
        ))

      parse_tokens(tokens, node)
    }
  }
}