ics - tokeniser.go
1 package ics
2
3 import (
4 "errors"
5 "io"
6
7 "vimagination.zapto.org/parser"
8 )
9
10 const (
11 tokenName parser.TokenType = iota
12 tokenParamName
13 tokenParamQuotedValue
14 tokenParamValue
15 tokenValue
16 )
17
18 const phraseContentLine parser.PhraseType = iota
19
20 const (
21 ianaToken = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-"
22 nonsafeChars = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f\n\";:,"
23 )
24
25 type tokeniser interface {
26 GetPhrase() (parser.Phrase, error)
27 }
28
29 func newTokeniser(r io.Reader) *parser.Parser {
30 p := parser.New(parser.NewReaderTokeniser(r))
31 p.TokeniserState(parseName)
32 p.PhraserState(phrase)
33 return &p
34 }
35
36 func parseName(t *parser.Tokeniser) (parser.Token, parser.TokenFunc) {
37 next := t.AcceptRun(ianaToken)
38 name := t.Get()
39 if len(name) == 0 {
40 if next == -1 {
41 return t.Done()
42 }
43 t.Err = ErrInvalidContentLine
44 return t.Error()
45 }
46 switch next {
47 case ';':
48 return parser.Token{
49 Type: tokenName,
50 Data: name,
51 }, parseParamName
52 case ':':
53 return parser.Token{
54 Type: tokenName,
55 Data: name,
56 }, parseValue
57 default:
58 t.Err = ErrInvalidContentLineName
59 return t.Error()
60 }
61 }
62
63 func parseParamName(t *parser.Tokeniser) (parser.Token, parser.TokenFunc) {
64 t.Accept(";")
65 t.Get()
66 if t.AcceptRun(ianaToken) != '=' {
67 t.Err = ErrInvalidContentLineParamName
68 return t.Error()
69 }
70 return parser.Token{
71 Type: tokenParamName,
72 Data: t.Get(),
73 }, parseParamValue
74 }
75
76 func parseParamValue(t *parser.Tokeniser) (parser.Token, parser.TokenFunc) {
77 t.Accept("=,")
78 t.Get()
79 var tk parser.Token
80 if t.Accept("\"") {
81 t.Get()
82 if t.ExceptRun(nonsafeChars[:33]) != '"' {
83 t.Err = ErrInvalidContentLineQuotedParamValue
84 return t.Error()
85 }
86 tk.Type = tokenParamQuotedValue
87 tk.Data = t.Get()
88 t.Accept("\"")
89 } else {
90 t.ExceptRun(nonsafeChars)
91 tk.Type = tokenParamValue
92 tk.Data = t.Get()
93 }
94 switch t.Peek() {
95 case ',':
96 return tk, parseParamValue
97 case ';':
98 return tk, parseParamName
99 case ':':
100 return tk, parseValue
101 }
102 t.Err = ErrInvalidContentLineParamValue
103 return t.Error()
104 }
105
106 func parseValue(t *parser.Tokeniser) (parser.Token, parser.TokenFunc) {
107 t.Accept(":")
108 t.Get()
109 switch t.ExceptRun(nonsafeChars[:32]) {
110 case '\r':
111 data := t.Get()
112 t.Accept("\r")
113 if t.Accept("\n") {
114 t.Get()
115 return parser.Token{
116 Type: tokenValue,
117 Data: data,
118 }, parseName
119 }
120 case -1:
121 return parser.Token{
122 Type: tokenValue,
123 Data: t.Get(),
124 }, (*parser.Tokeniser).Done
125 }
126 t.Err = ErrInvalidContentLineValue
127 return t.Error()
128 }
129
130 func phrase(p *parser.Parser) (parser.Phrase, parser.PhraseFunc) {
131 if !p.Accept(tokenName) {
132 if p.Accept(parser.TokenDone) {
133 return p.Done()
134 }
135 return p.Error()
136 }
137 for p.Accept(tokenParamName) {
138 if !p.Accept(tokenParamValue, tokenParamQuotedValue) {
139 return p.Error()
140 }
141 p.AcceptRun(tokenParamValue, tokenParamQuotedValue)
142 }
143 if !p.Accept(tokenValue) {
144 return p.Error()
145 }
146 return parser.Phrase{
147 Type: phraseContentLine,
148 Data: p.Get(),
149 }, phrase
150 }
151
152 // Errors
153 var (
154 ErrInvalidContentLine = errors.New("invalid content line")
155 ErrInvalidContentLineName = errors.New("invalid content line name")
156 ErrInvalidContentLineParamName = errors.New("invalid content line param name")
157 ErrInvalidContentLineQuotedParamValue = errors.New("invalid content line quoted param value")
158 ErrInvalidContentLineParamValue = errors.New("invalid content line param value")
159 ErrInvalidContentLineValue = errors.New("invalid content line value")
160 )