gedcom - tokeniser.go
1 package gedcom
2
3 import (
4 "errors"
5 "io"
6 "strings"
7
8 "vimagination.zapto.org/parser"
9 )
10
11 const (
12 alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_"
13 delim = " "
14 digit = "0123456789"
15 alphanum = alpha + digit
16 otherchar = "!\"$%&'()*+,-./:;<=>?[\\]^`{|}~" // + "\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe" // ANSEL
17 nonAt = alpha + digit + otherchar + " #"
18 terminators = "\r\n"
19 // anyChar = alpha + digit + otherchar + "# @"
20 levelIgnore = terminators + delim + " "
21 invalidchar = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\xab\xac\xad\x1e\x1f\x7f" + terminators + "@"
22 )
23
24 const (
25 tokenLevel parser.TokenType = iota
26 tokenXref
27 tokenTag
28 tokenPointer
29 tokenLine
30 tokenEndLine
31 )
32
33 type tokeniser struct {
34 parser.Parser
35 options
36 }
37
38 func newTokeniser(r io.Reader, o options) *tokeniser {
39 t := &tokeniser{
40 Parser: parser.New(parser.NewReaderTokeniser(r)),
41 options: o,
42 }
43 t.TokeniserState(t.level)
44 return t
45 }
46
47 func (t *tokeniser) level(p *parser.Tokeniser) (parser.Token, parser.TokenFunc) {
48 p.AcceptRun(levelIgnore)
49 p.Get()
50 if p.Peek() == -1 {
51 return p.Done()
52 }
53 if !p.Accept(digit) {
54 p.Err = ErrInvalidLevel
55 return p.Error()
56 }
57 p.AcceptRun(digit)
58 if !p.Accept(delim) {
59 p.Err = ErrMissingDelim
60 return p.Error()
61 }
62 return parser.Token{
63 Type: tokenLevel,
64 Data: strings.TrimSpace(p.Get()),
65 }, t.optionalXrefID
66 }
67
68 func (t *tokeniser) optionalXrefID(p *parser.Tokeniser) (parser.Token, parser.TokenFunc) {
69 if p.Peek() == '@' {
70 return t.xrefID(p)
71 }
72 return t.tag(p)
73 }
74
75 func (t *tokeniser) readPointer(p *parser.Tokeniser) (string, error) {
76 if !p.Accept(alphanum) {
77 return "", ErrInvalidPointer
78 }
79 p.AcceptRun(nonAt)
80 if !p.Accept("@") {
81 return "", ErrInvalidPointer
82 }
83 pointer := p.Get()
84 return pointer[1 : len(pointer)-1], nil
85 }
86
87 func (t *tokeniser) xrefID(p *parser.Tokeniser) (parser.Token, parser.TokenFunc) {
88 p.Accept("@")
89 pointer, err := t.readPointer(p)
90 if err != nil {
91 p.Err = err
92 return p.Error()
93 }
94 if !p.Accept(delim) {
95 p.Err = ErrMissingDelim
96 return p.Error()
97 }
98 p.Get()
99 return parser.Token{
100 Type: tokenXref,
101 Data: strings.Trim(pointer, "@"),
102 }, t.tag
103 }
104
105 func (t *tokeniser) tag(p *parser.Tokeniser) (parser.Token, parser.TokenFunc) {
106 if !p.Accept(alphanum) {
107 p.Err = ErrInvalidTag
108 return p.Error()
109 }
110 p.AcceptRun(alphanum)
111 tag := p.Get()
112 if p.Accept(delim) {
113 p.Get()
114 return parser.Token{
115 Type: tokenTag,
116 Data: tag,
117 }, t.lineValue
118 }
119 next := t.endLine
120 if p.Peek() == -1 {
121 next = (*parser.Tokeniser).Done
122 } else {
123 if !p.Accept(terminators) {
124 p.Err = ErrInvalidTag
125 return p.Error()
126 }
127 p.AcceptRun(terminators)
128 p.Get()
129 }
130 return parser.Token{
131 Type: tokenTag,
132 Data: tag,
133 }, next
134 }
135
136 func (t *tokeniser) endLine(p *parser.Tokeniser) (parser.Token, parser.TokenFunc) {
137 return parser.Token{
138 Type: tokenEndLine,
139 Data: "",
140 }, t.level
141 }
142
143 func (t *tokeniser) lineValue(p *parser.Tokeniser) (parser.Token, parser.TokenFunc) {
144 if p.Peek() == '@' {
145 p.Accept("@")
146 if p.Peek() != '@' {
147 pointer, err := t.readPointer(p)
148 if err != nil {
149 if !t.allowInvalidEscape {
150 p.Err = err
151 return p.Error()
152 }
153 } else {
154 p.AcceptRun(terminators)
155 p.Get()
156 return parser.Token{
157 Type: tokenPointer,
158 Data: pointer,
159 }, t.level
160 }
161 }
162 }
163 next := t.level
164 for {
165 for {
166 if t.allowUnknownCharset && p.Except(invalidchar) {
167 p.ExceptRun(invalidchar)
168 } else if p.Accept(nonAt) {
169 p.AcceptRun(nonAt)
170 } else if p.Accept("@") {
171 if t.allowInvalidEscape || p.Accept("@") {
172 continue
173 }
174 if !p.Accept("#") {
175 p.Err = ErrBadEscape
176 return p.Error()
177 }
178 if t.allowUnknownCharset {
179 p.ExceptRun(invalidchar)
180 } else {
181 p.AcceptRun(nonAt)
182 }
183 if !p.Accept("@") {
184 p.Err = ErrBadEscape
185 return p.Error()
186 }
187 } else {
188 break
189 }
190 }
191 pe := p.Peek()
192 if pe == -1 {
193 next = (*parser.Tokeniser).Done
194 break
195 }
196 if strings.ContainsRune(terminators, pe) {
197 p.AcceptRun(terminators)
198 if !t.allowTerminatorsInValue {
199 break
200 }
201 pe = p.Peek()
202 if pe == -1 {
203 next = (*parser.Tokeniser).Done
204 break
205 }
206 if strings.ContainsRune(digit, pe) {
207 break
208 }
209 } else {
210 if !t.allowInvalidChars {
211 p.Err = ErrBadChar
212 return p.Error()
213 }
214 p.Except("")
215 }
216 }
217 return parser.Token{
218 Type: tokenLine,
219 Data: strings.TrimSpace(p.Get()),
220 }, next
221 }
222
223 // Errors
224 var (
225 ErrInvalidLevel = errors.New("invalid level num")
226 ErrMissingDelim = errors.New("missing delminitator")
227 ErrInvalidPointer = errors.New("invalid pointer string")
228 ErrInvalidTag = errors.New("invalid tag")
229 ErrBadEscape = errors.New("bad escape sequence")
230 ErrBadChar = errors.New("bad character")
231 )
232