calcsheet-native 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,309 @@
1
+ //! A CSV tokenizer that accepts and refuses what the parser of Ruby's stdlib CSV does with `liberal_parsing: false`,
2
+ //! for a one-byte separator and quote and a known row separator. It works on UTF-8 bytes, fed in chunks, and never
3
+ //! touches Ruby: the bindings in `lib.rs` turn its records into Ruby objects.
4
+
5
+ /// The line ending of the records.
6
+ #[derive(Clone, Copy, PartialEq, Eq)]
7
+ pub enum RowSep {
8
+ Lf,
9
+ Cr,
10
+ CrLf,
11
+ }
12
+
13
+ /// A field of a record: nil for an empty unquoted field, else its text as a span of the buffer or, for a quoted
14
+ /// field with doubled quotes, the unescaped bytes.
15
+ pub enum Field {
16
+ Nil,
17
+ Span(usize, usize),
18
+ Owned(Vec<u8>),
19
+ }
20
+
21
+ /// A record and what calcsheet reads from its raw text: the physical lines it ends ("\r\n", "\n" and a lone "\r"
22
+ /// each end one, inside quoted fields too) and whether the line break that does not belong to the row separator
23
+ /// appears outside quoted fields.
24
+ pub struct Record {
25
+ pub fields: Vec<Field>,
26
+ pub breaks: usize,
27
+ pub stray: bool,
28
+ }
29
+
30
+ /// What a batch ends with.
31
+ pub enum Stop {
32
+ /// The text ends inside a record: feed more, or finish.
33
+ NeedMore,
34
+ /// No record is left.
35
+ End,
36
+ /// The next record is malformed: the message of the stdlib's CSV::MalformedCSVError.
37
+ Error(String),
38
+ /// The batch has its records.
39
+ Full,
40
+ }
41
+
42
+ enum Step {
43
+ Record(Record, usize),
44
+ Stop(Stop),
45
+ }
46
+
47
+ /// Whether the row separator starts at a position.
48
+ enum SepAt {
49
+ Yes(usize),
50
+ No,
51
+ /// A CR at the end of the text, which may start a CRLF.
52
+ Partial,
53
+ }
54
+
55
+ pub struct Tokenizer {
56
+ col_sep: u8,
57
+ quote: u8,
58
+ row_sep: RowSep,
59
+ buf: Vec<u8>,
60
+ pos: usize,
61
+ eof: bool,
62
+ records: usize,
63
+ }
64
+
65
+ impl Tokenizer {
66
+ pub fn new(col_sep: u8, quote: u8, row_sep: RowSep) -> Self {
67
+ Tokenizer {
68
+ col_sep,
69
+ quote,
70
+ row_sep,
71
+ buf: Vec::new(),
72
+ pos: 0,
73
+ eof: false,
74
+ records: 0,
75
+ }
76
+ }
77
+
78
+ /// Appends text, dropping the records already handed out.
79
+ pub fn feed(&mut self, chunk: &[u8]) {
80
+ if self.pos > 0 {
81
+ self.buf.drain(..self.pos);
82
+ self.pos = 0;
83
+ }
84
+ self.buf.extend_from_slice(chunk);
85
+ }
86
+
87
+ /// No more text comes.
88
+ pub fn finish(&mut self) {
89
+ self.eof = true;
90
+ }
91
+
92
+ /// The text of the buffer that the spans of the last batch point into.
93
+ pub fn buffer(&self) -> &[u8] {
94
+ &self.buf
95
+ }
96
+
97
+ /// The stdlib's line number for an error: the record that fails, counted from 1.
98
+ pub fn line(&self) -> usize {
99
+ self.records + 1
100
+ }
101
+
102
+ /// The message of the malformed record at the position, if the next record is one.
103
+ pub fn pending_error(&self) -> Option<String> {
104
+ match self.record(self.pos) {
105
+ Step::Stop(Stop::Error(message)) => Some(message),
106
+ _ => None,
107
+ }
108
+ }
109
+
110
+ /// Parses up to `max` records from the text fed so far.
111
+ pub fn batch(&mut self, max: usize) -> (Vec<Record>, Stop) {
112
+ let mut records = Vec::new();
113
+ while records.len() < max {
114
+ match self.record(self.pos) {
115
+ Step::Record(record, end) => {
116
+ records.push(record);
117
+ self.pos = end;
118
+ self.records += 1;
119
+ }
120
+ Step::Stop(stop) => return (records, stop),
121
+ }
122
+ }
123
+ (records, Stop::Full)
124
+ }
125
+
126
+ fn sep_at(&self, at: usize) -> SepAt {
127
+ let b = &self.buf;
128
+ match (self.row_sep, b[at]) {
129
+ (RowSep::Lf, b'\n') | (RowSep::Cr, b'\r') => SepAt::Yes(1),
130
+ (RowSep::CrLf, b'\r') => match b.get(at + 1) {
131
+ Some(b'\n') => SepAt::Yes(2),
132
+ None if !self.eof => SepAt::Partial,
133
+ _ => SepAt::No,
134
+ },
135
+ _ => SepAt::No,
136
+ }
137
+ }
138
+
139
+ fn need_more(&self) -> Step {
140
+ Step::Stop(if self.eof { Stop::End } else { Stop::NeedMore })
141
+ }
142
+
143
+ fn done(&self, fields: Vec<Field>, start: usize, end: usize) -> Step {
144
+ let (breaks, stray) = self.raw_facts(start, end);
145
+ Step::Record(
146
+ Record {
147
+ fields,
148
+ breaks,
149
+ stray,
150
+ },
151
+ end,
152
+ )
153
+ }
154
+
155
+ fn malformed(message: String) -> Step {
156
+ Step::Stop(Stop::Error(message))
157
+ }
158
+
159
+ /// The record that starts at `start`, the position after it, or why there is none.
160
+ fn record(&self, start: usize) -> Step {
161
+ let b = &self.buf;
162
+ let n = b.len();
163
+ if start == n {
164
+ return self.need_more();
165
+ }
166
+ // An empty line is a record without fields.
167
+ match self.sep_at(start) {
168
+ SepAt::Yes(len) => return self.done(Vec::new(), start, start + len),
169
+ SepAt::Partial => return Step::Stop(Stop::NeedMore),
170
+ SepAt::No => {}
171
+ }
172
+
173
+ let mut fields = Vec::new();
174
+ let mut i = start;
175
+ loop {
176
+ if i == n {
177
+ // After a separator at the end of the text: an empty last field.
178
+ if !self.eof {
179
+ return Step::Stop(Stop::NeedMore);
180
+ }
181
+ fields.push(Field::Nil);
182
+ return self.done(fields, start, n);
183
+ }
184
+ if b[i] == self.quote {
185
+ let mut owned: Option<Vec<u8>> = None;
186
+ let mut segment = i + 1;
187
+ let mut j = i + 1;
188
+ let close = loop {
189
+ let Some(q) = b[j..].iter().position(|&c| c == self.quote).map(|k| j + k)
190
+ else {
191
+ if !self.eof {
192
+ return Step::Stop(Stop::NeedMore);
193
+ }
194
+ return Self::malformed("Unclosed quoted field".into());
195
+ };
196
+ if q + 1 == n && !self.eof {
197
+ return Step::Stop(Stop::NeedMore);
198
+ }
199
+ if q + 1 < n && b[q + 1] == self.quote {
200
+ owned
201
+ .get_or_insert_with(Vec::new)
202
+ .extend_from_slice(&b[segment..=q]);
203
+ j = q + 2;
204
+ segment = j;
205
+ continue;
206
+ }
207
+ break q;
208
+ };
209
+ let field = match owned {
210
+ Some(mut bytes) => {
211
+ bytes.extend_from_slice(&b[segment..close]);
212
+ Field::Owned(bytes)
213
+ }
214
+ None => Field::Span(i + 1, close),
215
+ };
216
+ fields.push(field);
217
+ let after = close + 1;
218
+ if after == n {
219
+ return self.done(fields, start, n);
220
+ }
221
+ if b[after] == self.col_sep {
222
+ i = after + 1;
223
+ continue;
224
+ }
225
+ return match self.sep_at(after) {
226
+ SepAt::Yes(len) => self.done(fields, start, after + len),
227
+ SepAt::Partial => Step::Stop(Stop::NeedMore),
228
+ SepAt::No => {
229
+ Self::malformed("Any value after quoted field isn't allowed".into())
230
+ }
231
+ };
232
+ }
233
+
234
+ let mut j = i;
235
+ loop {
236
+ if j == n {
237
+ if !self.eof {
238
+ return Step::Stop(Stop::NeedMore);
239
+ }
240
+ fields.push(unquoted(i, n));
241
+ return self.done(fields, start, n);
242
+ }
243
+ let c = b[j];
244
+ if c == self.col_sep {
245
+ fields.push(unquoted(i, j));
246
+ i = j + 1;
247
+ break;
248
+ }
249
+ if c == self.quote {
250
+ return Self::malformed("Illegal quoting".into());
251
+ }
252
+ if c == b'\n' || c == b'\r' {
253
+ match self.sep_at(j) {
254
+ SepAt::Yes(len) => {
255
+ fields.push(unquoted(i, j));
256
+ return self.done(fields, start, j + len);
257
+ }
258
+ SepAt::Partial => return Step::Stop(Stop::NeedMore),
259
+ // The stdlib keeps the line break of another ending in an unquoted field, except in a CRLF
260
+ // file, where it names it as a wrong line ending when it starts the field.
261
+ SepAt::No if self.row_sep == RowSep::CrLf => {
262
+ let shown = if c == b'\n' { "\\n" } else { "\\r" };
263
+ return Self::malformed(if j == i {
264
+ format!("New line must be <\"\\r\\n\"> not <\"{shown}\">")
265
+ } else {
266
+ format!("Unquoted fields do not allow new line <\"{shown}\">")
267
+ });
268
+ }
269
+ SepAt::No => {}
270
+ }
271
+ }
272
+ j += 1;
273
+ }
274
+ }
275
+ }
276
+
277
+ /// The physical lines the raw record ends and whether it has the stray line break outside quoted fields: the
278
+ /// parts between quote characters alternate outside and inside.
279
+ fn raw_facts(&self, start: usize, end: usize) -> (usize, bool) {
280
+ let raw = &self.buf[start..end];
281
+ let stray = match self.row_sep {
282
+ RowSep::Lf => Some(b'\r'),
283
+ RowSep::Cr => Some(b'\n'),
284
+ RowSep::CrLf => None,
285
+ };
286
+ let mut breaks = 0;
287
+ let mut inside = false;
288
+ let mut found = false;
289
+ for (index, &c) in raw.iter().enumerate() {
290
+ if c == b'\n' || (c == b'\r' && raw.get(index + 1) != Some(&b'\n')) {
291
+ breaks += 1;
292
+ }
293
+ if c == self.quote {
294
+ inside = !inside;
295
+ } else if !inside && Some(c) == stray {
296
+ found = true;
297
+ }
298
+ }
299
+ (breaks, found)
300
+ }
301
+ }
302
+
303
+ fn unquoted(start: usize, end: usize) -> Field {
304
+ if start == end {
305
+ Field::Nil
306
+ } else {
307
+ Field::Span(start, end)
308
+ }
309
+ }
@@ -0,0 +1,47 @@
1
+ //! Running Rust code without Ruby's global VM lock.
2
+
3
+ use std::ffi::c_void;
4
+ use std::panic::{AssertUnwindSafe, catch_unwind};
5
+
6
+ /// Runs `f` with the GVL released, so that other Ruby threads run meanwhile. `f` must not touch Ruby objects. A
7
+ /// panic in `f` is caught (it must not unwind through Ruby's C frames) and returned as an error.
8
+ pub fn without_gvl<F: FnOnce() -> R, R>(f: F) -> Result<R, String> {
9
+ struct Call<F, R> {
10
+ f: Option<F>,
11
+ result: Option<std::thread::Result<R>>,
12
+ }
13
+
14
+ unsafe extern "C" fn trampoline<F: FnOnce() -> R, R>(data: *mut c_void) -> *mut c_void {
15
+ // SAFETY: `data` is the `Call` below, alive and borrowed by nothing else until this returns.
16
+ let call = unsafe { &mut *(data as *mut Call<F, R>) };
17
+ let f = call.f.take();
18
+ call.result = Some(catch_unwind(AssertUnwindSafe(|| {
19
+ (f.expect("called once"))()
20
+ })));
21
+ std::ptr::null_mut()
22
+ }
23
+
24
+ let mut call = Call {
25
+ f: Some(f),
26
+ result: None,
27
+ };
28
+ // SAFETY: the trampoline only uses `call`, which outlives this blocking call; no unblocking function is given,
29
+ // so an interrupt waits for `f` to return.
30
+ unsafe {
31
+ rb_sys::rb_thread_call_without_gvl(
32
+ Some(trampoline::<F, R>),
33
+ &mut call as *mut Call<F, R> as *mut c_void,
34
+ None,
35
+ std::ptr::null_mut(),
36
+ );
37
+ }
38
+ match call.result {
39
+ Some(Ok(value)) => Ok(value),
40
+ Some(Err(panic)) => Err(panic
41
+ .downcast_ref::<&str>()
42
+ .map(|s| s.to_string())
43
+ .or_else(|| panic.downcast_ref::<String>().cloned())
44
+ .unwrap_or_else(|| "a panic without a message".into())),
45
+ None => Err("the call did not run".into()),
46
+ }
47
+ }