Class: Graphlyte::Parsing::BacktrackingParser

Inherits:
Object
  • Object
show all
Defined in:
lib/graphlyte/parsing/backtracking_parser.rb

Overview

Basic back-tracking parser, with parser-combinator methods and exceptional control-flow.

This class is just scaffolding - all domain specific parsing should go in subclasses.

Direct Known Subclasses

Graphlyte::Parser

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(tokens:, max_depth: nil) ⇒ BacktrackingParser

Returns a new instance of BacktrackingParser.



16
17
18
19
20
21
# File 'lib/graphlyte/parsing/backtracking_parser.rb', line 16

def initialize(tokens:, max_depth: nil)
  @tokens = tokens
  @index = -1
  @max_depth = max_depth
  @current_depth = 0
end

Instance Attribute Details

#max_depthObject

Returns the value of attribute max_depth.



14
15
16
# File 'lib/graphlyte/parsing/backtracking_parser.rb', line 14

def max_depth
  @max_depth
end

#tokensObject (readonly)

Returns the value of attribute tokens.



13
14
15
# File 'lib/graphlyte/parsing/backtracking_parser.rb', line 13

def tokens
  @tokens
end

Instance Method Details

#advanceObject



39
40
41
42
# File 'lib/graphlyte/parsing/backtracking_parser.rb', line 39

def advance
  @current = nil
  @index += 1
end

#bracket(lhs, rhs, &block) ⇒ Object

Raises:



133
134
135
136
137
138
139
140
141
142
# File 'lib/graphlyte/parsing/backtracking_parser.rb', line 133

def bracket(lhs, rhs, &block)
  expect(:PUNCTUATOR, lhs)
  raise TooDeep, current.location if too_deep?

  ret = subfeature(&block)

  expect(:PUNCTUATOR, rhs)

  ret
end

#currentObject



35
36
37
# File 'lib/graphlyte/parsing/backtracking_parser.rb', line 35

def current
  @current ||= peek
end

#expect(type, value = nil) ⇒ Object



49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/graphlyte/parsing/backtracking_parser.rb', line 49

def expect(type, value = nil)
  try_parse do
    t = next_token

    if value
      raise Expected.new(t, expected: value) unless t.type == type && t.value == value
    else
      raise Unexpected, t unless t.type == type
    end

    t.value
  end
end

#inspectObject



23
24
25
# File 'lib/graphlyte/parsing/backtracking_parser.rb', line 23

def inspect
  "#<#{self.class} @index=#{@index} @current=#{current.inspect} ...>"
end

#many(limit: nil, &block) ⇒ Object



73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/graphlyte/parsing/backtracking_parser.rb', line 73

def many(limit: nil, &block)
  ret = []

  until ret.length == limit
    begin
      ret << try_parse(&block)
    rescue ParseError
      return ret
    end
  end

  ret
end

#name(value = nil) ⇒ Object



129
130
131
# File 'lib/graphlyte/parsing/backtracking_parser.rb', line 129

def name(value = nil)
  expect(:NAME, value)
end

#next_tokenObject



44
45
46
47
# File 'lib/graphlyte/parsing/backtracking_parser.rb', line 44

def next_token
  advance
  current
end

#one_of(*alternatives) ⇒ Object

Raises:



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/graphlyte/parsing/backtracking_parser.rb', line 106

def one_of(*alternatives)
  err = nil
  all_symbols = alternatives.all? { _1.is_a?(Symbol) }

  alternatives.each do |alt|
    case alt
    when Symbol then return try_parse { send(alt) }
    when Proc then return try_parse { alt.call }
    else
      raise 'Not an alternative'
    end
  rescue ParseError => e
    err = e
  end

  raise ParseError, "At #{current.location}: Expected one of #{alternatives.join(', ')}" if err && all_symbols
  raise err if err
end

#optional(&block) ⇒ Object



63
64
65
66
67
# File 'lib/graphlyte/parsing/backtracking_parser.rb', line 63

def optional(&block)
  try_parse(&block)
rescue ParseError, IllegalValue
  nil
end

#optional_list(&block) ⇒ Object



69
70
71
# File 'lib/graphlyte/parsing/backtracking_parser.rb', line 69

def optional_list(&block)
  optional(&block) || []
end

#peek(offset: 0) ⇒ Object



31
32
33
# File 'lib/graphlyte/parsing/backtracking_parser.rb', line 31

def peek(offset: 0)
  @tokens[@index + offset] || raise("No token at #{@index + offset}")
end

#punctuator(token) ⇒ Object



125
126
127
# File 'lib/graphlyte/parsing/backtracking_parser.rb', line 125

def punctuator(token)
  expect(:PUNCTUATOR, token)
end

#some(&block) ⇒ Object



87
88
89
90
91
92
# File 'lib/graphlyte/parsing/backtracking_parser.rb', line 87

def some(&block)
  one = yield
  rest = many(&block)

  [one] + rest
end

#subfeatureObject



150
151
152
153
154
155
156
157
# File 'lib/graphlyte/parsing/backtracking_parser.rb', line 150

def subfeature
  d = @current_depth
  @current_depth += 1

  yield
ensure
  @current_depth = d
end

#to_sObject



27
28
29
# File 'lib/graphlyte/parsing/backtracking_parser.rb', line 27

def to_s
  inspect
end

#too_deep?Boolean

Returns:

  • (Boolean)


144
145
146
147
148
# File 'lib/graphlyte/parsing/backtracking_parser.rb', line 144

def too_deep?
  return false if max_depth.nil?

  @current_depth > max_depth
end

#try_parseObject



94
95
96
97
98
99
100
101
102
103
104
# File 'lib/graphlyte/parsing/backtracking_parser.rb', line 94

def try_parse
  idx = @index
  yield
rescue ParseError => e
  @index = idx
  raise e
rescue IllegalValue => e
  t = current
  @index = idx
  raise Illegal, t, e.message
end