PS: fix string escaping. Passes all tests.
[jackhill/mal.git] / ps / reader.ps
CommitLineData
1b4a9012
JM
1(in reader\n) print
2
ea81a808 3% requires types.ps to be included first
1b4a9012
JM
4
5/token_delim (;,"` \n{}\(\)[]) def
6/token_number (0123456789-) def
7
8% read_number: read a single number from string/idx
9% string idx -> read_number -> number string new_idx
10/read_number {
11 %(in read_number\n) print
12 /idx exch def
13 /str exch def
14 /start idx def
15 /cnt 0 def
16 { % loop
17 idx str length ge { exit } if % EOF, break loop
18 /ch str idx get def % current character
19 ch 48 ge ch 57 le and 45 ch eq or { %if number
20 /cnt cnt 1 add def
21 }{ % else
22 exit
23 } ifelse
24 /idx idx 1 add def % increment idx
25 } loop
26
27 str start cnt getinterval cvi % the matched number
28 str idx % return: number string new_idx
29} def
30
31
32% read_symbol: read a single symbol from string/idx
33% string idx -> read_symbol -> name string new_idx
34/read_symbol {
35 %(in read_symbol\n) print
36 /idx exch def
37 /str exch def
38 /start idx def
39 /cnt 0 def
40 { % loop
41 idx str length ge { exit } if % EOF, break loop
42 /ch str idx 1 getinterval def
43 token_delim ch search { % if token delimeter
44 pop pop pop exit
45 }{ % else not a delim
46 pop
47 /cnt cnt 1 add def
48 } ifelse
49 /idx idx 1 add def % increment idx
50 } loop
51
52 str start cnt getinterval cvn % the matched symbol
53 str idx % return: symbol string new_idx
54} def
55
56
57% read_string: read a single string from string/idx
58% string idx -> read_string -> new_string string new_idx
59/read_string {
60 %(in read_string\n) print
61 /idx exch 1 add def
62 /str exch def
63 /start idx def
64 /cnt 0 def
65 { % loop
66 idx str length ge { %if EOF
5ce65382 67 (unexpected EOF reading string) _throw
1b4a9012
JM
68 } if
69 /ch str idx get def % current character
70 /idx idx 1 add def
e7c1a2f6
JM
71 ch 92 eq { % if \
72 str idx get 34 eq { %if \"
73 /idx idx 1 add def
74 /cnt cnt 1 add def % 1 more below
75 } if
76 } if
1b4a9012
JM
77 ch 34 eq { exit } if % '"' is end of string
78 /cnt cnt 1 add def
79 } loop
80 str start cnt getinterval % the matched string
e7c1a2f6 81 (\\") (") replace
1b4a9012
JM
82 str idx % return: new_string string new_idx
83} def
84
85
86% read_atom: read a single atom from string/idx
87% string idx -> read_atom -> int string new_idx
88/read_atom {
89 %(in read_atom\n) print
90 /idx exch def
91 /str exch def
92 str length idx le { % ifelse
93 exit % EOF
94 }{
95 /ch str idx get def % current character
55e2bfa8
JM
96 %ch 48 ge ch 57 le and 45 ch eq or { %if number
97 ch 48 ge ch 57 le and { %if number
1b4a9012
JM
98 str idx read_number
99 }{ ch 34 eq { %elseif double-quote
100 str idx read_string
101 }{
102 str idx read_symbol
103 /idx exch def pop
104 dup /nil eq { %if nil
105 pop null str idx
106 }{ dup /true eq { %elseif true
107 pop true str idx
108 }{ dup /false eq { %elseif false
109 pop false str idx
110 }{ %else
111 str idx % return the original symbol/name
112 } ifelse } ifelse } ifelse
113 } ifelse } ifelse
114 }ifelse
115
116 % return: atom string new_idx
117} def
118
5ce65382
JM
119% read_until: read a list from string/idx until stopchar is found
120% string idx stopchar -> read_until -> list string new_idx
121/read_until {
122 %(in read_until\n) print
123 /stopchar exch def
1b4a9012
JM
124 /idx exch 1 add def
125 /str exch def
126 [
127 { % loop
128 str idx read_spaces /idx exch def pop
129 str length idx le { %if EOF
5ce65382 130 (unexpected EOF reading list) _throw
1b4a9012
JM
131 } if
132 /ch str idx get def % current character
5ce65382 133 ch stopchar eq { exit } if % stop at stopchar
1b4a9012
JM
134 str idx read_form /idx exch def pop
135 } loop
136 ]
137 str idx 1 add
138} def
139
140% read_spaces: advance idx to the first non-whitespace
141% string idx -> read_form -> string new_idx
142/read_spaces {
143 %(in read_spaces\n) print
144 /idx exch def
145 /str exch def
146 { % loop
147 str length idx le { exit } if % EOF, break loop
148 /ch str idx get def % current character
149 % if not whitespace then exit
150 ch 32 ne ch 10 ne ch 44 ne and and { exit } if
151 /idx idx 1 add def % increment idx
152 } loop
153
154 str idx % return: string new_idx
155} def
156
157% read_form: read the next form from string start at idx
158% string idx -> read_form -> ast string new_idx
8e7e339d 159/read_form { 3 dict begin
1b4a9012
JM
160 %(in read_form\n) print
161 read_spaces
162 /idx exch def
163 /str exch def
164
5ce65382 165 idx str length ge { (unexpected EOF) _throw } if % EOF
1b4a9012 166 /ch str idx get def % current character
5ce65382
JM
167 ch 59 eq { %if ';'
168 { % loop
169 /idx idx 1 add def % increment idx
170 str length idx le { exit } if % EOF, break loop
171 /ch str idx get def % current character
172 % if newline then add 1 more idx and exit
173 ch 10 eq {
174 /idx idx 1 add def
175 exit
176 } if
177 } loop
178 str idx read_form % recur to get next form
179 }{ ch 39 eq { %if '\''
8e7e339d
JM
180 /idx idx 1 add def
181 str idx read_form
182 3 -1 roll /quote exch 2 _list 3 1 roll
183 }{ ch 96 eq { %if '`'
184 /idx idx 1 add def
185 str idx read_form
186 3 -1 roll /quasiquote exch 2 _list 3 1 roll
187 }{ ch 126 eq { %if '~'
188 /idx idx 1 add def
189 /ch str idx get def % current character
190 ch 64 eq { %if '~@'
191 /idx idx 1 add def
192 str idx read_form
193 3 -1 roll /splice-unquote exch 2 _list 3 1 roll
194 }{ %else just '~'
195 str idx read_form
196 3 -1 roll /unquote exch 2 _list 3 1 roll
197 } ifelse
5ce65382
JM
198 }{ ch 94 eq { %if '^'
199 /idx idx 1 add def
200 str idx read_form read_form % stack: meta form str idx
201 4 2 roll exch /with-meta 3 1 roll 3 _list 3 1 roll
202 }{ ch 64 eq { %if '@'
203 /idx idx 1 add def
204 str idx read_form
205 3 -1 roll /deref exch 2 _list 3 1 roll
8e7e339d 206 }{ ch 40 eq { %if '('
5ce65382
JM
207 str idx 41 read_until
208 3 -1 roll _list_from_array 3 1 roll
8e7e339d 209 }{ ch 41 eq { %elseif ')'
5ce65382
JM
210 (unexpected '\)') _throw
211 }{ ch 91 eq { %if '('
212 str idx 93 read_until
213 3 -1 roll _vector_from_array 3 1 roll
8e7e339d 214 }{ ch 93 eq { %elseif ']'
5ce65382 215 (unexpected ']') _throw
950e3c76 216 }{ ch 123 eq { %elseif '{'
5ce65382
JM
217 str idx 125 read_until
218 3 -1 roll _hash_map_from_array 3 1 roll
950e3c76 219 }{ ch 125 eq { %elseif '}'
5ce65382 220 (unexpected '}') _throw
1b4a9012
JM
221 }{ % else
222 str idx read_atom
5ce65382 223 } ifelse } ifelse } ifelse } ifelse } ifelse } ifelse } ifelse } ifelse } ifelse } ifelse } ifelse } ifelse
1b4a9012
JM
224
225 % return: ast string new_idx
8e7e339d 226end } def
1b4a9012
JM
227
228% string -> read_str -> ast
229/read_str {
230 %(in read_str\n) print
231 0 % current index into the string
232 read_form
233
234 pop pop % drop the string, idx. return: ast
235} def