bufferedio.ck 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008
  1. /*++
  2. Copyright (c) 2016 Minoca Corp.
  3. This file is licensed under the terms of the GNU General Public License
  4. version 3. Alternative licensing terms are available. Contact
  5. info@minocacorp.com for details. See the LICENSE file at the root of this
  6. project for complete licensing information.
  7. Module Name:
  8. bufferedio.ck
  9. Abstract:
  10. This module implements support for performing buffered file I/O. It's
  11. inspired by Python's buffered I/O classes.
  12. Author:
  13. Evan Green 10-Jan-2016
  14. Environment:
  15. Chalk
  16. --*/
  17. //
  18. // ------------------------------------------------------------------- Includes
  19. //
  20. from iobase import BufferedIoBase, IoError, DEFAULT_BUFFER_SIZE, IO_SEEK_CUR;
  21. //
  22. // ---------------------------------------------------------------- Definitions
  23. //
  24. //
  25. // ----------------------------------------------- Internal Function Prototypes
  26. //
  27. //
  28. // -------------------------------------------------------------------- Globals
  29. //
  30. //
  31. // ------------------------------------------------------------------ Functions
  32. //
  33. //
  34. // BufferedRwSeekable is the fully functional read/write implementation of the
  35. // Buffered I/O base for a seekable file.
  36. //
  37. class BufferedIo is BufferedIoBase {
  38. var _raw;
  39. var _readable;
  40. var _writable;
  41. var _position;
  42. var _bufferSize;
  43. var _readBuffer;
  44. var _readOffset;
  45. var _writeBuffers;
  46. var _writeSize;
  47. function
  48. __init (
  49. raw,
  50. bufferSize
  51. )
  52. /*++
  53. Routine Description:
  54. This routine instantiates a new BufferdIo object.
  55. Arguments:
  56. raw - Supplies the raw I/O object, an instance of a RawIoBase subclass.
  57. bufferSize - Supplies the buffer size to use. If -1,
  58. DEFAULT_BUFFER_SIZE will be used.
  59. Return Value:
  60. Returns the object.
  61. --*/
  62. {
  63. _raw = raw;
  64. this.name = _raw.name;
  65. this.mode = _raw.mode;
  66. this.closed = false;
  67. this.raw = _raw;
  68. if (bufferSize <= 0) {
  69. bufferSize = DEFAULT_BUFFER_SIZE;
  70. }
  71. _bufferSize = bufferSize;
  72. _readable = _raw.isReadable();
  73. _writable = _raw.isWritable();
  74. _position = 0;
  75. _readBuffer = "";
  76. _readOffset = 0;
  77. _writeBuffers = [];
  78. _writeSize = 0;
  79. return this;
  80. }
  81. function
  82. flush (
  83. )
  84. /*++
  85. Routine Description:
  86. This routine flushes the stream data. This does nothing for read-only
  87. or non-blocking streams.
  88. Arguments:
  89. None.
  90. Return Value:
  91. None.
  92. --*/
  93. {
  94. if (_raw.isClosed()) {
  95. Core.raise(ValueError("I/O operation on closed file"));
  96. }
  97. if (_writable) {
  98. this._flushWrite();
  99. }
  100. this._flushRead();
  101. return;
  102. }
  103. function
  104. peek (
  105. size
  106. )
  107. /*++
  108. Routine Description:
  109. This routine returns data from the stream without advancing the
  110. position. At most one single read on the raw stream is done to
  111. satisfy the call. The number of bytes returned may be more or less
  112. than requested.
  113. Arguments:
  114. size - Supplies the size to peek at.
  115. Return Value:
  116. Returns the peeked data.
  117. --*/
  118. {
  119. var data;
  120. var existingLength;
  121. var readData;
  122. var readSize;
  123. var result;
  124. if (_writable) {
  125. this._flushWrite();
  126. }
  127. existingLength = _readBuffer.length() - _readOffset;
  128. if (size <= 0) {
  129. if (existingLength > 0) {
  130. size = existingLength;
  131. } else {
  132. size = _bufferSize;
  133. }
  134. }
  135. //
  136. // See if the request can be satisfied completely from the buffer.
  137. //
  138. if (existingLength >= size) {
  139. data = _readBuffer[_readOffset..(_readOffset + size)];
  140. return data;
  141. }
  142. //
  143. // Do one read to try and get the rest of the data requested. Round up
  144. // to try and read the whole buffer.
  145. //
  146. readSize = size - existingLength;
  147. if (readSize < _bufferSize) {
  148. readSize = _bufferSize;
  149. }
  150. readData = _raw.read(readSize);
  151. _position += readData.length();
  152. _readBuffer = _readBuffer[_readOffset...-1] + readData;
  153. _readOffset = 0;
  154. return _readBuffer[0..size];
  155. }
  156. function
  157. read (
  158. size
  159. )
  160. /*++
  161. Routine Description:
  162. This routine performs a read of the requested size.
  163. Arguments:
  164. size - Supplies the amount to read. If -1 is supplied, then readall is
  165. called.
  166. Return Value:
  167. Returns the bytes read. If the empty string is returned and more than
  168. zero bytes were requested, then this is end-of-file.
  169. --*/
  170. {
  171. var data;
  172. var existingLength;
  173. var length = 0;
  174. var result;
  175. if (size < -1) {
  176. Core.raise(ValueError("Size must be positive or -1"));
  177. }
  178. if (size == -1) {
  179. return this.readall();
  180. }
  181. if (_writable) {
  182. this._flushWrite();
  183. }
  184. //
  185. // Grab as much as possible from the existing buffer. Maybe it's all
  186. // that's needed.
  187. //
  188. existingLength = _readBuffer.length() - _readOffset;
  189. if (existingLength > size) {
  190. result = _readBuffer[_readOffset..(_readOffset + size)];
  191. _readOffset += size;
  192. return result;
  193. }
  194. //
  195. // Wipe out the remaining buffer, and start a list of buffers that
  196. // represents the result.
  197. //
  198. data = _readBuffer[_readOffset...-1];
  199. length = data.length();
  200. if (length) {
  201. size -= length;
  202. result = [data];
  203. } else {
  204. result = [];
  205. }
  206. _readBuffer = "";
  207. _readOffset = 0;
  208. //
  209. // Read directly into the result if huge amounts are requested.
  210. //
  211. length = 1;
  212. while (size >= _bufferSize) {
  213. data = _raw.read(size);
  214. length = data.length();
  215. if (!length) {
  216. break;
  217. }
  218. _position += length;
  219. result.append(data);
  220. size -= length;
  221. }
  222. //
  223. // Now refill the buffer and do the partial read out of it.
  224. //
  225. if (length != 0) {
  226. while (size != 0) {
  227. _readBuffer = _raw.read(_bufferSize);
  228. length = _readBuffer.length();
  229. if (!length) {
  230. break;
  231. }
  232. _position += length;
  233. if (size <= length) {
  234. result.append(_readBuffer[0..size]);
  235. _readOffset = size;
  236. break;
  237. } else {
  238. result.append(_readBuffer);
  239. size -= length;
  240. }
  241. }
  242. }
  243. return "".joinList(result);
  244. }
  245. function
  246. read1 (
  247. size
  248. )
  249. /*++
  250. Routine Description:
  251. This routine performs a single read.
  252. Arguments:
  253. size - Supplies the amount to read. If -1 is supplied, then data is
  254. read until the end of file is encountered.
  255. Return Value:
  256. Returns the bytes read. If the empty string is returned and more than
  257. zero bytes were requested, then this is end-of-file.
  258. --*/
  259. {
  260. var data;
  261. var existingLength;
  262. var length = 0;
  263. var result;
  264. if (size < 0) {
  265. Core.raise(ValueError("Size must be positive"));
  266. }
  267. if (_writable) {
  268. this._flushWrite();
  269. }
  270. //
  271. // Grab as much as possible from the existing buffer. Maybe it's all
  272. // that's needed.
  273. //
  274. existingLength = _readBuffer.length() - _readOffset;
  275. if (existingLength > size) {
  276. result = _readBuffer[_readOffset..(_readOffset + size)];
  277. _readOffset += size;
  278. return result;
  279. }
  280. //
  281. // Wipe out the remaining buffer, and start a list of buffers that
  282. // represents the result.
  283. //
  284. data = _readBuffer[_readOffset...-1];
  285. if (length) {
  286. size -= existingLength;
  287. result = [data];
  288. } else {
  289. result = [];
  290. }
  291. _readBuffer = "";
  292. _readOffset = 0;
  293. //
  294. // If the caller just wants a little, refill the buffer. Otherwise,
  295. // do all of what the caller wants.
  296. //
  297. if (size < _bufferSize) {
  298. _readBuffer = _raw.read(_bufferSize);
  299. length = _readBuffer.length();
  300. if (length) {
  301. _position += length;
  302. if (size > length) {
  303. size = length;
  304. }
  305. _readOffset = size;
  306. result.append(_readBuffer[0..size]);
  307. }
  308. } else {
  309. data = _raw.read(size);
  310. result.append(data);
  311. _position += data.length();
  312. }
  313. return "".joinList(result);
  314. }
  315. function
  316. readline (
  317. limit
  318. )
  319. /*++
  320. Routine Description:
  321. This routine reads a single line from the stream.
  322. Arguments:
  323. limit - Supplies the maximum number of bytes to read. Supply -1 for no
  324. limit.
  325. Return Value:
  326. Returns the line read from the file.
  327. --*/
  328. {
  329. var data;
  330. var index;
  331. var length;
  332. var result;
  333. if (_writable) {
  334. this._flushWrite();
  335. }
  336. //
  337. // Clip the buffer so that indexOf doesn't find something already found.
  338. //
  339. if (_readOffset) {
  340. _readBuffer = _readBuffer[_readOffset...-1];
  341. _readOffset = 0;
  342. }
  343. //
  344. // See if the buffer already contains a complete line.
  345. //
  346. index = _readBuffer.indexOf("\n");
  347. if (index != -1) {
  348. result = _readBuffer[_readOffset...index];
  349. _readOffset = index + 1;
  350. return result;
  351. }
  352. //
  353. // Pull the whole buffer into the result.
  354. //
  355. if (_readBuffer.length()) {
  356. data = _readBuffer[_readOffset...-1];
  357. result = [data];
  358. _readBuffer = "";
  359. _readOffset = 0;
  360. } else {
  361. result = [];
  362. }
  363. //
  364. // Loop reading blocks until a newline or EOF is found.
  365. //
  366. while (true) {
  367. _readBuffer = _raw.read(_bufferSize);
  368. length = _readBuffer.length();
  369. if (!length) {
  370. break;
  371. }
  372. _position += length;
  373. index = _readBuffer.indexOf("\n");
  374. if (index != -1) {
  375. result.append(_readBuffer[0...index]);
  376. _readOffset = index + 1;
  377. break;
  378. }
  379. result.append(_readBuffer);
  380. }
  381. return "".join(result);
  382. }
  383. function
  384. tell (
  385. )
  386. /*++
  387. Routine Description:
  388. This routine returns the current file position.
  389. Arguments:
  390. None.
  391. Return Value:
  392. Returns the current file position.
  393. --*/
  394. {
  395. return _position + _writeSize - (_readBuffer.length() - _readOffset);
  396. }
  397. function
  398. seek (
  399. offset,
  400. whence
  401. )
  402. /*++
  403. Routine Description:
  404. This routine seeks into the file.
  405. Arguments:
  406. offset - Supplies the desired offset to seek from or to.
  407. whence - Supplies the anchor point for the seek. Supply IO_SEEK_SET to
  408. seek from the beginning of the file, IO_SEEK_CUR to seek from the
  409. current position, or IO_SEEK_END to seek from the end of the file.
  410. Return Value:
  411. Returns the new absolute position within the file.
  412. --*/
  413. {
  414. this.flush();
  415. _position = _raw.seek(offset, whence);
  416. return _position;
  417. }
  418. function
  419. truncate (
  420. size
  421. )
  422. /*++
  423. Routine Description:
  424. This routine truncates the file to the given size. The stream position
  425. is not changed.
  426. Arguments:
  427. size - Supplies the new size of the file.
  428. Return Value:
  429. Returns the new file size.
  430. --*/
  431. {
  432. var result;
  433. this.flush();
  434. result = _raw.truncate(size);
  435. _position = _raw.tell();
  436. return result;
  437. }
  438. function
  439. readall (
  440. )
  441. /*++
  442. Routine Description:
  443. This routine reads the entire file.
  444. Arguments:
  445. None.
  446. Return Value:
  447. Returns the contents of the entire file.
  448. --*/
  449. {
  450. var data;
  451. var existingLength;
  452. var result;
  453. if (_writable) {
  454. this._flushWrite();
  455. }
  456. existingLength = _readBuffer.length() - _readOffset;
  457. if (!existingLength) {
  458. result = _raw.readall();
  459. _position += result.length();
  460. return result;
  461. }
  462. result = [_readBuffer[_readOffset...-1]];
  463. data = _raw.readall();
  464. _position += data.length();
  465. result.append(data);
  466. return "".join(result);
  467. }
  468. function
  469. write (
  470. data
  471. )
  472. /*++
  473. Routine Description:
  474. This routine performs a single write.
  475. Arguments:
  476. size - Supplies the string of data to write.
  477. Return Value:
  478. Returns the number of bytes written, which may be less than the size of
  479. the data.
  480. --*/
  481. {
  482. var length = data.length();
  483. var result = 0;
  484. if (_raw.isClosed()) {
  485. Core.raise(ValueError("I/O operation on closed file"));
  486. }
  487. if (!_writable) {
  488. Core.raise(ValueError("File not open for writing"));
  489. }
  490. if (_readable) {
  491. this._flushRead();
  492. }
  493. if (length > _bufferSize) {
  494. this._flushWrite();
  495. result = _raw.write(data);
  496. _position += result;
  497. return result;
  498. }
  499. if (!(data is String)) {
  500. Core.raise(TypeError("Expected a string"));
  501. }
  502. _writeBuffers.append(data);
  503. _writeSize += length;
  504. if (_writeSize > _bufferSize) {
  505. this._flushWrite();
  506. }
  507. return length;
  508. }
  509. //
  510. // IoBase functions.
  511. //
  512. function
  513. close (
  514. )
  515. /*++
  516. Routine Description:
  517. This routine closes and flushes a stream. If the stream is already
  518. closed, this does nothing.
  519. Arguments:
  520. None.
  521. Return Value:
  522. None.
  523. --*/
  524. {
  525. if (!this.closed) {
  526. this.flush();
  527. _raw.close();
  528. this.closed = true;
  529. }
  530. return 0;
  531. }
  532. function
  533. detach (
  534. )
  535. /*++
  536. Routine Description:
  537. This routine returns the raw file I/O object this object is buffering.
  538. Afterwards, this object is left in an unusable state.
  539. Arguments:
  540. None.
  541. Return Value:
  542. Returns the raw object.
  543. --*/
  544. {
  545. var raw = _raw;
  546. this.flush();
  547. _raw = null;
  548. return raw;
  549. }
  550. function
  551. fileno (
  552. )
  553. /*++
  554. Routine Description:
  555. This routine returns the OS file descriptor number associated with the
  556. stream.
  557. Arguments:
  558. None.
  559. Return Value:
  560. Returns the file descriptor number associated with the stream.
  561. -1 if no file descriptor is associated with the stream or the file has
  562. been closed.
  563. --*/
  564. {
  565. return _raw.fileno();
  566. }
  567. function
  568. isatty (
  569. )
  570. /*++
  571. Routine Description:
  572. This routine determines if the file descriptor backing this stream is
  573. a terminal device.
  574. Arguments:
  575. None.
  576. Return Value:
  577. true if the file descriptor is a terminal device.
  578. false if the file descriptor is not a terminal.
  579. --*/
  580. {
  581. return _raw.isatty();
  582. }
  583. function
  584. isReadable (
  585. )
  586. /*++
  587. Routine Description:
  588. This routine determines if the given stream can be read from.
  589. Arguments:
  590. None.
  591. Return Value:
  592. true if the stream was opened with read permissions.
  593. false if the stream cannot be read from.
  594. --*/
  595. {
  596. return _raw.isReadable();
  597. }
  598. function
  599. isSeekable (
  600. )
  601. /*++
  602. Routine Description:
  603. This routine determines if the file backing the stream can be seeked on.
  604. Arguments:
  605. None.
  606. Return Value:
  607. true if the underlying file is seekable.
  608. false if the underlying file is not seekable.
  609. --*/
  610. {
  611. return _raw.isSeekable();
  612. }
  613. function
  614. isWritable (
  615. )
  616. /*++
  617. Routine Description:
  618. This routine determines if the given file can be written to.
  619. Arguments:
  620. None.
  621. Return Value:
  622. true if the file can be written to.
  623. false if the file cannot be written to.
  624. --*/
  625. {
  626. return _raw.isWritable();
  627. }
  628. //
  629. // Internal functions
  630. //
  631. function
  632. _flushRead (
  633. )
  634. /*++
  635. Routine Description:
  636. This routine closes and flushes a stream and seeks back over the
  637. buffered contents.
  638. Arguments:
  639. None.
  640. Return Value:
  641. None.
  642. --*/
  643. {
  644. var readLength;
  645. if (_readable) {
  646. readLength = _readBuffer.length() - _readOffset;
  647. if (readLength) {
  648. _position = _raw.seek(-readLength, IO_SEEK_CUR);
  649. _readBuffer = "";
  650. _readOffset = 0;
  651. }
  652. }
  653. return;
  654. }
  655. function
  656. _flushWrite (
  657. )
  658. /*++
  659. Routine Description:
  660. This routine flushes the dirty write buffer.
  661. Arguments:
  662. None.
  663. Return Value:
  664. None.
  665. --*/
  666. {
  667. var data = "".join(_writeBuffers);
  668. var length = data.length();
  669. var written;
  670. _writeBuffers = [];
  671. _writeSize = 0;
  672. while (length) {
  673. written = _raw.write(data);
  674. if (written <= 0) {
  675. Core.raise(IoError("Unable to write"));
  676. }
  677. _position += written;
  678. data = data[written...-1];
  679. length = data.length();
  680. }
  681. return;
  682. }
  683. }
  684. //
  685. // --------------------------------------------------------- Internal Functions
  686. //