-
Notifications
You must be signed in to change notification settings - Fork 1
/
parser.v
165 lines (137 loc) · 3.92 KB
/
parser.v
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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
module gura
import math
// Base parser
struct Parser {
mut:
cache map[string][]string
len int
line int
pos int
text string
}
// is_end returns if the parser has reached the end of file
fn (p Parser) is_end() bool {
return p.pos >= p.len
}
// split_char_ranges returns a list of chars from a list of chars which could contain char ranges (i.e. a-z or 0-9)
fn split_char_ranges(chars string) ?[]string {
mut result := []string{}
mut idx := 0
mut len := chars.len
for idx < len {
if idx + 2 < len && chars[idx + 1] == `-` {
if chars[idx] >= chars[idx + 2] {
return error('Bad character error')
}
result << chars[idx..idx + 3]
idx += 3
continue
}
result << chars[idx..idx + 1]
idx++
}
return result
}
// get_char_ranges returns a list of chars from a list of chars which could contain char ranges (i.e. a-z or 0-9)
fn (mut p Parser) get_char_ranges(chars string) ?[]string {
if chars in p.cache {
return p.cache[chars]
}
result := split_char_ranges(chars)?
p.cache[chars] = result
return result
}
// char matches a list of specific chars and returns the first that matched
fn (mut p Parser) char(chars string) ?string {
if p.is_end() {
expected_str := if chars == '' { 'character' } else { '[$chars]' }
return new_parse_error(p.pos + 1, p.line, 'Expected $expected_str but got end of string')
}
next_char := p.text[p.pos + 1..p.pos + 2]
if chars == '' {
p.pos++
return next_char
}
if split := p.get_char_ranges(chars) {
for char_range in split {
if char_range.len == 1 {
if next_char == char_range {
p.pos++
return next_char
}
} else if char_range[0..1] <= next_char && next_char <= char_range[2..3] {
p.pos++
return next_char
}
}
} else {
return err
}
return new_parse_error(p.pos + 1, p.line, 'Expected [$chars] but got $next_char')
}
// maybe_char like char but returns none instead of ParseError
fn (mut p Parser) maybe_char(chars string) ?string {
return p.char(chars) or { return if err is ParseError { none } else { err } }
}
// keyword matches specific keywords
fn (mut p Parser) keyword(keywords ...string) ?string {
if p.is_end() {
return new_parse_error(p.pos + 1, p.line, 'Expected ${keywords.join(',')} but got end of string')
}
for keyword in keywords {
low := p.pos + 1
high := low + keyword.len
if p.text.len < high {
continue
}
if p.text[low..high] == keyword {
p.pos += keyword.len
debug('Keyword $keyword.runes() matched')
return keyword
}
}
return new_parse_error(p.pos + 1, p.line, 'Expected [${keywords.join(',')}] but got ${p.text.runes()[
p.pos + 1..p.pos + 2]}')
}
// maybe_keyword like keyword but returns none instead of ParseError
fn (mut p Parser) maybe_keyword(keywords ...string) ?string {
return p.keyword(...keywords) or { return if err is ParseError { none } else { err } }
}
fn (mut p GuraParser) match_rule(rules ...Rule) ?RuleResult {
mut last_error_pos := -1
mut last_error := IError(none)
mut last_error_rules := []Rule{}
for rule in rules {
init_pos := p.pos
if res := rule(mut p) {
match_rule_debug(true, '$res')
return res
} else {
match_rule_debug(false, err.msg())
if err is ParseError {
p.pos = init_pos
if err.pos > last_error_pos {
last_error = *err
last_error_pos = err.pos
last_error_rules = [rule]
} else {
if err.pos == last_error_pos {
last_error_rules << rule
}
}
} else {
// if it is not a ParseError, return it to stop parsing
return err
}
}
}
if last_error_rules.len == 1 {
return last_error
}
last_error_pos = math.min(p.text.len - 1, last_error_pos)
return new_parse_error(last_error_pos, p.line, 'Expected $last_error_rules.str() but got ${p.text[last_error_pos]}')
}
// maybe_match like match_rule but returns none instead of ParseError
fn (mut p GuraParser) maybe_match(rules ...Rule) ?RuleResult {
return p.match_rule(...rules) or { return if err is ParseError { none } else { err } }
}