hayro_postscript/
object.rs1use crate::array::{self, Array};
2use crate::error::{Error, Result};
3use crate::name::{self, Name};
4use crate::number::{self, Number};
5use crate::reader::Reader;
6use crate::string::{self, String};
7
8#[derive(Debug, Clone, PartialEq)]
10pub enum Object<'a> {
11 Number(Number),
13 Name(Name<'a>),
15 String(String<'a>),
17 Array(Array<'a>),
19}
20
21pub(crate) fn read<'a>(r: &mut Reader<'a>) -> Result<Object<'a>> {
22 skip_whitespace_and_comments(r);
23
24 let b = r.peek_byte().ok_or(Error::SyntaxError)?;
25
26 match b {
27 b'(' => string::parse_literal(r)
28 .map(|s| Object::String(String::from_literal(s)))
29 .ok_or(Error::SyntaxError),
30 b'<' => {
31 if r.peek_bytes(2) == Some(b"<~") {
32 string::parse_ascii85(r)
33 .map(|s| Object::String(String::from_ascii85(s)))
34 .ok_or(Error::SyntaxError)
35 } else if r.peek_bytes(2) == Some(b"<<") {
36 r.forward();
39 r.forward();
40 read(r)
42 } else {
43 string::parse_hex(r)
44 .map(|s| Object::String(String::from_hex(s)))
45 .ok_or(Error::SyntaxError)
46 }
47 }
48 b'/' => name::parse_literal(r)
49 .map(|s| Object::Name(Name::new(s, true)))
50 .ok_or(Error::SyntaxError),
51 b'[' => array::parse(r).map(|d| Object::Array(Array::new(d))),
52 b'>' => {
53 if r.peek_bytes(2) == Some(b">>") {
54 r.forward();
56 r.forward();
57 read(r)
58 } else {
59 Err(Error::SyntaxError)
60 }
61 }
62 b'{' => {
63 r.forward();
64 Err(Error::UnsupportedType)
65 }
66 b'.' | b'+' | b'-' | b'0'..=b'9' => number::read(r).map(Object::Number),
67 _ => name::parse_executable(r)
68 .map(|s| Object::Name(Name::new(s, false)))
69 .ok_or(Error::SyntaxError),
70 }
71}
72
73pub(crate) fn at_end(r: &mut Reader<'_>) -> bool {
74 skip_whitespace_and_comments(r);
75 r.peek_byte().is_none()
76}
77
78pub(crate) fn skip_whitespace_and_comments(r: &mut Reader<'_>) {
79 loop {
80 match r.peek_byte() {
81 Some(b) if crate::reader::is_whitespace(b) => {
82 r.forward();
83 }
84 Some(b'%') => {
85 r.forward();
86 r.forward_while(|b| !crate::reader::is_eol(b));
87 }
88 _ => return,
89 }
90 }
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96
97 fn read_one(input: &[u8]) -> Result<Object<'_>> {
98 let mut r = Reader::new(input);
99 read(&mut r)
100 }
101
102 fn read_ok(input: &[u8]) -> Object<'_> {
103 read_one(input).unwrap()
104 }
105
106 fn read_err(input: &[u8]) -> Error {
107 read_one(input).unwrap_err()
108 }
109
110 #[test]
111 fn integer() {
112 assert_eq!(read_ok(b"42 "), Object::Number(Number::Integer(42)));
113 }
114
115 #[test]
116 fn negative_integer() {
117 assert_eq!(read_ok(b"-7 "), Object::Number(Number::Integer(-7)));
118 }
119
120 #[test]
121 fn literal_name() {
122 assert_eq!(
123 read_ok(b"/CMapName "),
124 Object::Name(Name::new(b"CMapName", true))
125 );
126 }
127
128 #[test]
129 fn executable_name() {
130 let obj = read_ok(b"beginbfchar ");
131 assert_eq!(obj, Object::Name(Name::new(b"beginbfchar", false)));
132 }
133
134 #[test]
135 fn literal_string() {
136 assert_eq!(
137 read_ok(b"(Hello)"),
138 Object::String(String::from_literal(b"Hello"))
139 );
140 }
141
142 #[test]
143 fn hex_string() {
144 assert_eq!(
145 read_ok(b"<48656C6C6F>"),
146 Object::String(String::from_hex(b"48656C6C6F"))
147 );
148 }
149
150 #[test]
151 fn ascii85_string() {
152 assert_eq!(
153 read_ok(b"<~87cURDZ~>"),
154 Object::String(String::from_ascii85(b"87cURDZ"))
155 );
156 }
157
158 #[test]
159 fn array_simple() {
160 let obj = read_ok(b"[1 2 3]");
161 assert_eq!(obj, Object::Array(Array::new(b"1 2 3")));
162 }
163
164 #[test]
165 fn stray_close_bracket() {
166 assert_eq!(read_err(b"]"), Error::SyntaxError);
167 }
168
169 #[test]
170 fn stray_gt() {
171 assert_eq!(read_err(b">x"), Error::SyntaxError);
172 }
173
174 #[test]
175 fn skips_whitespace() {
176 assert_eq!(read_ok(b" \t\n 42 "), Object::Number(Number::Integer(42)));
177 }
178
179 #[test]
180 fn skips_comments() {
181 assert_eq!(
182 read_ok(b"% this is a comment\n42 "),
183 Object::Number(Number::Integer(42))
184 );
185 }
186
187 #[test]
188 fn eof() {
189 assert!(read_one(b"").is_err());
190 assert!(read_one(b" ").is_err());
191 assert!(read_one(b"% comment only\n").is_err());
192 }
193}