target.bend
bend-kit-router@0.2.0.0 · 221 lines · 35 definitions
1import Base
2import bend-kit-url@0.4.1.0/url.bend as Url
3
4type Query is Data:
5 Query{key: String, value: String}
6
7type Target is Data:
8 Target{segments: List<&2, String>, query: List<&2, Query>, authority: Maybe<&2, String>, star: Bool}
9
10type Utf is Data:
11 Utf{need: U32, lo: U32, hi: U32, cp: U32, out: String}
12
13def between(+c: U32, lo: U32, hi: U32) -> Bool:
14 Bool.and(U32.is_le(lo, c), U32.is_le(c, hi))
15
16def utf.lead(+c: U32, out: String, ascii: Bool) -> Maybe<&2, Utf>:
17 match ascii:
18 case True{}:
19 Some{Utf{0, 128, 191, 0, SCon{Chr{c}, out}}}
20 case False{}:
21 +valid = between(c, 194, 244)
22 +need = Bool.pick(U32, U32.is_lt(c, 224), 1, Bool.pick(U32, U32.is_lt(c, 240), 2, 3))
23 +lo = Bool.pick(U32, U32.is_eq(c, 224), 160, Bool.pick(U32, U32.is_eq(c, 240), 144, 128))
24 +hi = Bool.pick(U32, U32.is_eq(c, 237), 159, Bool.pick(U32, U32.is_eq(c, 244), 143, 191))
25 mask = Bool.pick(U32, U32.is_eq(need, 1), 31, Bool.pick(U32, U32.is_eq(need, 2), 15, 7))
26 Bool.pick(Maybe<&2, Utf>, valid, Some{Utf{need, lo, hi, (c .&. mask : U32), out}}, None{})
27
28def utf.more(ok: Bool, +need: U32, +cp: U32, +out: String) -> Maybe<&2, Utf>:
29 match ok:
30 case False{}:
31 None{}
32 case True{}:
33 Some{Utf{(need - 1 : U32), 128, 191, cp, Bool.pick(String, U32.is_eq(need, 1), SCon{Chr{cp}, out}, out)}}
34
35def utf.step(idle: Bool, +c: U32, need: U32, lo: U32, hi: U32, cp: U32, out: String) -> Maybe<&2, Utf>:
36 match idle:
37 case True{}:
38 utf.lead(c, out, U32.is_lt(c, 128))
39 case False{}:
40 utf.more(between(c, lo, hi), need, (cp * 64 + (c .&. 63 : U32) : U32), out)
41
42def utf.finish(st: Maybe<&2, Utf>) -> Maybe<&2, String>:
43 match st:
44 case None{}:
45 None{}
46 case Some{Utf{need, lo, hi, cp, out}}:
47 Bool.pick(Maybe<&2, String>, U32.is_eq(need, 0), Some{String.reverse(out)}, None{})
48
49def utf.go(s: String, st: Maybe<&2, Utf>) -> Maybe<&2, String>:
50 match s:
51 case SNil{}:
52 utf.finish(st)
53 case SCon{Chr{+c}, t}:
54 match st:
55 case None{}:
56 None{}
57 case Some{Utf{+need, lo, hi, cp, out}}:
58 utf.go(t, utf.step(U32.is_eq(need, 0), c, need, lo, hi, cp, out))
59
60def text.valid(s: String, +slash: Bool) -> Bool:
61 match s:
62 case SNil{}:
63 True{}
64 case SCon{Chr{+c}, t}:
65 Bool.and(Bool.and(U32.is_ne(c, 0), Bool.not(Bool.and(slash, U32.is_eq(c, 47)))), text.valid(t, slash))
66
67def decoded.valid(+s: String, slash: Bool) -> Maybe<&2, String>:
68 Bool.pick(Maybe<&2, String>, text.valid(s, slash), Some{s}, None{})
69
70def decoded(st: Maybe<&2, String>, slash: Bool) -> Maybe<&2, String>:
71 match st:
72 case None{}:
73 None{}
74 case Some{s}:
75 decoded.valid(s, slash)
76
77def percent.got(st: Maybe<&2, String>, slash: Bool) -> Maybe<&2, String>:
78 match st:
79 case None{}:
80 None{}
81 case Some{s}:
82 decoded(utf.go(s, Some{Utf{0, 128, 191, 0, ""}}), slash)
83
84def percent(s: String, slash: Bool) -> Maybe<&2, String>:
85 percent.got(Url.pct.decode(s), slash)
86
87def segments(xs: List<&2, String>) -> Maybe<&2, List<&2, String>>:
88 match xs:
89 case Nil{}:
90 Some{Nil{}}
91 case Con{h, t}:
92 do Maybe<&2, List<&2, String>>:
93 head : String <- percent(h, True{})
94 tail : List<&2, String> <- segments(t)
95 return head <> tail
96
97def split.path(+path: String) -> List<&2, String>:
98 Bool.pick(List<&2, String>, String.eq(path, "/"), Nil{}, String.split(String.drop(path, 1n), '/'))
99
100def plus(s: String) -> String:
101 match s:
102 case SNil{}:
103 SNil{}
104 case SCon{Chr{+c}, t}:
105 SCon{Chr{Bool.pick(U32, U32.is_eq(c, 43), 32, c)}, plus(t)}
106
107def query.pair(pair: String & String) -> Maybe<&2, Query>:
108 (key, value) = pair
109 do Maybe<&2, Query>:
110 k : String <- percent(plus(key), False{})
111 v : String <- percent(plus(value), False{})
112 return Query{k, v}
113
114def query.field(field: String) -> Maybe<&2, Query>:
115 query.pair(Url.take_to(field, 61))
116
117def query(xs: List<&2, String>, acc: List<&2, Query>) -> Maybe<&2, List<&2, Query>>:
118 match xs:
119 case Nil{}:
120 Some{List.reverse(&2, Query, acc)}
121 case "" <> t:
122 query(t, acc)
123 case h <> t:
124 do Maybe<&2, List<&2, Query>>:
125 pair : Query <- query.field(h)
126 query(t, pair <> acc)
127
128def raw.valid(s: String) -> Bool:
129 match s:
130 case SNil{}:
131 True{}
132 case SCon{Chr{+c}, t}:
133 Bool.and(Bool.and(between(c, 33, 126), U32.is_ne(c, 35)), raw.valid(t))
134
135def origin(path: String, q: String, authority: Maybe<&2, String>) -> Maybe<&2, Target>:
136 do Maybe<&2, Target>:
137 ss : List<&2, String> <- segments(split.path(path))
138 pairs : List<&2, Query> <- query(String.split(q, '&'), Nil{})
139 return Target{ss, pairs, authority, False{}}
140
141def origin.split(pair: String & String, authority: Maybe<&2, String>) -> Maybe<&2, Target>:
142 (path, q) = pair
143 origin(path, q, authority)
144
145def alpha(+c: U32) -> Bool:
146 Bool.or(between(c, 65, 90), between(c, 97, 122))
147
148def scheme.tail(s: String) -> Bool:
149 match s:
150 case SNil{}:
151 True{}
152 case SCon{Chr{+c}, t}:
153 Bool.and(Bool.or(alpha(c), Bool.or(between(c, 48, 57), Bool.or(U32.is_eq(c, 43), Bool.or(U32.is_eq(c, 45), U32.is_eq(c, 46))))), scheme.tail(t))
154
155def scheme(s: String) -> Bool:
156 match s:
157 case SNil{}:
158 False{}
159 case SCon{Chr{c}, t}:
160 Bool.and(alpha(c), scheme.tail(t))
161
162def authority.take(s: String, acc: String) -> String & String:
163 match s:
164 case SNil{}:
165 (String.reverse(acc), "/")
166 case SCon{Chr{47}, t}:
167 (String.reverse(acc), SCon{Chr{47}, t})
168 case SCon{Chr{63}, t}:
169 (String.reverse(acc), SCon{Chr{47}, SCon{Chr{63}, t}})
170 case SCon{h, t}:
171 authority.take(t, SCon{h, acc})
172
173def absolute.authority(ok: Bool, authority: String, path: String) -> Maybe<&2, Target>:
174 match ok:
175 case False{}:
176 None{}
177 case True{}:
178 origin.split(Url.take_to(path, 63), Some{authority})
179
180def absolute.taken(pair: String & String) -> Maybe<&2, Target>:
181 (+host, path) = pair
182 absolute.authority(Bool.not(String.is_empty(host)), host, path)
183
184def absolute.rest(rest: String) -> Maybe<&2, Target>:
185 match rest:
186 case SCon{Chr{47}, SCon{Chr{47}, t}}:
187 absolute.taken(authority.take(t, ""))
188 case _:
189 None{}
190
191def absolute.scheme(ok: Bool, rest: String) -> Maybe<&2, Target>:
192 match ok:
193 case False{}:
194 None{}
195 case True{}:
196 absolute.rest(rest)
197
198def absolute.split(pair: String & String) -> Maybe<&2, Target>:
199 (+protocol, rest) = pair
200 absolute.scheme(scheme(protocol), rest)
201
202def parse.form(target: String, method: String) -> Maybe<&2, Target>:
203 match target:
204 case "*":
205 Bool.pick(Maybe<&2, Target>, String.eq(method, "OPTIONS"), Some{Target{Nil{}, Nil{}, None{}, True{}}}, None{})
206 case SCon{Chr{47}, t}:
207 origin.split(Url.take_to(SCon{Chr{47}, t}, 63), None{})
208 case SNil{}:
209 None{}
210 case SCon{h, t}:
211 absolute.split(Url.take_to(SCon{h, t}, 58))
212
213def parse.valid(ok: Bool, target: String, method: String) -> Maybe<&2, Target>:
214 match ok:
215 case False{}:
216 None{}
217 case True{}:
218 parse.form(target, method)
219
220def parse(+target: String, method: String) -> Maybe<&2, Target>:
221 parse.valid(raw.valid(target), target, method)