aboutsummaryrefslogtreecommitdiff
path: root/src/parcom.cr
blob: 0f907c47f4acec3ff4d081a4cd0366fd6203174e (plain)
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
102
103
104
105
106
107
108
module Parcom
  VERSION = "0.1.0"

  class ParserException < Exception
  end

  class Result(T, V)
    getter tokens, value

    def initialize(@tokens : Array(T), @value : V)
    end
  end

  abstract class Parser(T, V)
    abstract def parse(tokens : Array(T)) : Result(T, V)

    def assert(f : V -> Bool)
      Assert.new(self, f)
    end

    def |(other : Parser(T, V)) : Parser(T, V)
      Alt.new(self, other)
    end
  end

  class Flunk(T, V) < Parser(T, V)
    def parse(tokens) : Result(T, V)
      raise ParserException.new("Flunk: parsing failed")
    end
  end

  class AnyToken(T) < Parser(T, T)
    def parse(tokens : Array(T)) : Result(T, T)
      if tokens.empty?
        raise ParserException.new("AnyToken: input was empty")
      else
        Result.new(tokens[1..], tokens[0])
      end
    end
  end

  class Eof(T) < Parser(T, Nil)
    def parse(tokens : Array(T)) : Result(T, Nil)
      if tokens.empty?
        Result.new(tokens, nil)
      else
        raise ParserException.new("Eof: input was not empty")
      end
    end
  end

  class Peek(T, V) < Parser(T, V)
    def initialize(@p : Parser(T, V))
    end

    def parse(tokens : Array(T)) : Result(T, V)
      result = @p.parse(tokens)
      Result.new(tokens, result.value)
    end
  end

  class Assert(T, V) < Parser(T, V)
    def initialize(@p : Parser(T, V), @f : V -> Bool)
    end

    def parse(tokens : Array(T)) : Result(T, V)
      result = @p.parse(tokens)

      unless @f.call(result.value)
        raise ParserException.new("Assert: predicate failed")
      end

      result
    end
  end

  class Satisfy(T) < Parser(T, T)
    def initialize(@f : T -> Bool)
    end

    def parse(tokens : Array(T)) : Result(T, T)
      AnyToken(T).new.assert(@f).parse(tokens)
    end
  end

  class Token(T) < Parser(T, T)
    def initialize(@expected : T)
    end

    def parse(tokens : Array(T)) : Result(T, T)
      Satisfy(T).new(->(x : T) { x == @expected }).parse(tokens)
    end
  end

  class Alt(T, V) < Parser(T, V)
    def initialize(@p1 : Parser(T, V), @p2 : Parser(T, V))
    end

    def parse(tokens : Array(T)) : Result(T, V)
      begin
        @p1.parse(tokens)
      rescue ParserException
        @p2.parse(tokens)
      end
    end
  end
end