Revert "Revert "Made use of ::= in core libraries and defined a RebindError condition...
[cslatevm.git] / src / core / file.slate
blobc8b71447a43131711df4a692f9a0a9995de6287e
2 File traitsWindow _map delegates at: File traitsWindow _map delegates size - 2 put: ExternalResource traits.
3 File addSlotsFrom: ExternalResource.
4 File traits ReadWrite ::= Oddball clone.
5 File traits Read ::= Oddball clone.
6 File traits Write ::= Oddball clone.
7 File traits CreateWrite ::= Oddball clone.
8 File addSlot: #mode valued: File ReadWrite.
9 File traits ByLocation ::= Dictionary new.
10 "Maps locators to File objects, leading to a weak normalization.
11 TODO: This currently only maps open File objects, which can lead to conflicts. Weak references could fix this."
13 f@(File traits) size
14 "Get the file size in bytes."
16   f isOpen ifTrue: [f sizeOf: f handle]
17            ifFalse: [f fileInfo ifNotNilDo: #fileSize `er]
20 f@(File traits) position
21 "Answer the handle cursor position."
22 [f positionOf: f handle].
24 f@(File traits) position: index
25 "Set the handle cursor position."
26 [f reposition: f handle to: index].
28 f@(File traits) isAtEnd
29 "Whether the file handle cursor is at the end of the file. True also if closed."
30 [f isOpen ifTrue: [f atEndOf: f handle] ifFalse: [True]].
32 f@(File traits) restoringPositionDo: block
34   origPos ::= f position.
35   [block applyWith: f] ensure: [f position := origPos]
38 f@(File traits) at: index
39 [f restoringPositionDo: [| :f | f position := index. f reader next]].
41 f@(File traits) at: index put: byte
42 [f restoringPositionDo: [| :f | f position := index. f writer nextPut: byte]].
44 f@(File traits) register
45 "Adds the File to the registration dictionary."
46 [f ByLocation at: f locator ifAbsentPut: [f]].
48 f@(File traits) unregister
49 "Removes the File from the registration dictionary."
51   f ByLocation at: f locator ifPresent:
52     [| :reg | reg == f ifTrue: [f ByLocation removeKey: f locator]]
55 f@(File traits) named: filename &mode: mode
56 "Accesses or creates the File object associated with the specified Locator.
57 Notably, a File created this way needs to be #unregister`ed manually if it
58 is never enabled or disabled before being garbage-collected."
60   locator ::= filename as: f Locator.
61   f ByLocation at: locator ifAbsentPut: [f newNamed: locator &mode: mode]
64 f@(File traits) newNamed: filename &mode: mode
65 "Forces the creation of a new instance of a File for the given Locator. If none
66 is registered, this File gets registered, otherwise it does not interfere with
67 registration."
69   mode `defaultsTo: f mode.
70   locator ::= filename as: f Locator.
71   f ByLocation at: locator ifPresent: [| :file | ].
72   f cloneSettingSlots: #{#handle. #locator. #mode} to: {Nil. locator. mode}
75 s@(String traits) file
77   File newNamed: s
80 f@(File traits) enable
81 "Open the file in its associated mode."
83   filename ::= f locator as: String.
84   (f handle :=
85     f mode caseOf:
86       {f Read -> [f handleForInput: filename].
87        f Write -> [f handleForOutput: filename].
88        f ReadWrite -> [f handleFor: filename].
89        f CreateWrite -> [f handleForNew: filename]}
90          otherwise: [error: 'No mode specified.'])
91     ifNil: [f connectionFailure]
92     ifNotNil: [f register].
93   f
96 file@(File traits) disable
97 "Invokes the File close primitive, and also removes it from the registration
98 dictionary, to minimize the potential for leaks."
99 [file close: file handle. file unregister].
101 f@(File traits) sessionDo: block &mode: mode
102 "Extends the basic sessionDo: with a way to temporarily set the mode."
104   mode
105     ifNil: [resend]
106     ifNotNil: [oldMode ::= f mode. f mode := mode. resend. f mode := oldMode]
109 f@(File traits) touch
110 "Opens the file, creating it if necessary, and updating access time."
112   oldmode ::= f mode.
113   [f mode := f CreateWrite.
114    f sessionDo: [| :_ | ]]
115      ensure: [f mode := oldmode]
118 f@(File traits) create
119 "Create a File via #touch if it does not exist."
120 [f exists ifFalse: [f touch]. f].
122 f@(File traits) delete
123 "Deletes the file, ensuring that it is closed first."
125   f ensureClosed.
126   (f deleteFileNamed: (f locator as: String))
127     ifFalse: [error: 'Unable to delete this file.'].
130 f@(File traits) renameTo: newName
132   f ensureClosed.
133   (f renameFileNamed: (f locator as: String) to: (newName as: String))
134     ifTrue: [f locator := newName as: f locator]
135     ifFalse: [error: 'Unable to rename this file.']
138 f1@(File traits) with: f2@(File traits) sessionDo: block &mode1: mode1 &mode2: mode2 &mode: mode
139 "Same as sessionDo, but works with two files.
140 If &mode is defined then this mode will be used for both files."
142   mode ifNotNil: [ mode1 := mode. mode2 := mode ].
143   f1 sessionDo:
144     [| :file1 |
145       f2 sessionDo:
146         [| :file2 |
147           block applyWith: file1 with: file2
148         ] &mode: mode2
149     ] &mode: mode1
152 f@(File traits) withOpenNamed: filename do: block &mode: mode
153 "Calls sessionDo: on a File made for the given filename and mode."
155   (f newNamed: filename) sessionDo: block &mode: mode
158 f@(File traits) exists
159 "Answer whether the file as specified by its locator already exists.
160 If it's already open, it exists; otherwise try to open it and then close it
161 if the result is successful. Then answer whether it worked."
163   f fileInfo isNotNil
166 f@(File traits) checkExists
167 "Raise a NotFound condition if the file as specified by its locator does not
168 already exist.
169 If it's already open, it exists; otherwise try to open it and then close it
170 if the result is successful."
172   oldMode ::= f mode.
173   f isOpen \/
174     [[[f mode := f Read. f open] on: f ConnectionFailed do: [| :c | f notFound]]
175        ensure: [f isOpen ifTrue: [f close]. f mode := oldMode]]
178 file1@(File traits) hasSameContentsAs: file2@(File traits)
179 "Check for the exact same contents, using a quick file size check first to
180 be cheap."
182   file1 size = file2 size
183     /\ [[file1 reader with: file2 reader do:
184           [| :char1 :char2 | char1 = char2 ifFalse: [^ False]]]
185             on: Stream Exhaustion do: [| :c | ^ False]. True]
188 file1@(File traits) copyContentsTo: file2@(File traits)
190   file1 reader >> file2 writer.
191   file1
194 file@(File traits) copyContentsTo: s@(Sequence traits)
195 "TODO: rename this method to something more appropriate to what it does."
197   result ::= s new &capacity: file size.
198   file sessionDo: [| :f | f reader next: file size putInto: result].
199   result
202 File traits define: #NotFound &parents: {ExternalResource ConnectionFailed}.
204 f@(File traits) connectionFailure
205 [f notFound].
207 f@(File traits) notFound
208 [(f NotFound cloneSettingSlots: #{#resource} to: {f}) signal].
210 f@(File traits) noneExistsFor: filename
211 [(f NotFound cloneSettingSlots: #{#resource} to: {f newNamed: filename}) signal].
213 e@(File NotFound traits) describeOn: out
215   out ; 'A file does not exist for the pathname: '
216    ; e resource locator printString.
219 File NotFound traits define: #CreateFile &parents: {Restart}.
221 r@(File NotFound CreateFile traits) describeOn: out
222 [out ; 'Create a file with the given name.\n'].
224 r@(File NotFound CreateFile traits) defaultHandler
226   r condition resource `cache locator ensureExists.
227   r condition resource create.
230 File NotFound traits define: #SpecifyAnotherLocation &parents: {Restart} &slots: {#newLocator}.
232 r@(File NotFound SpecifyAnotherLocation traits) describeOn: out
233 [out ; 'Select a new pathname and retry opening the file\n'].
235 r@(File NotFound SpecifyAnotherLocation traits) queryFrom: d
237   d console ; 'Specify a new pathname to try to open: '.
238   r newLocator: (r condition resource Locator readFrom: d parser next).
239   "TODO: Avoid the parser since a Slate literal string is inconvenient."
242 r@(File NotFound SpecifyAnotherLocation traits) defaultHandler
244   oldLocator ::= r condition resource `cache locator.
245   r condition resource locator := r newLocator.
246   [r condition resource open]
247     on: File NotFound do:
248       [| :c | c exit. "r condition resource locator := oldLocator."
249        r condition signal]
252 File traits define: #Locator &parents: {ExternalResource Locator} 
253 &slots: {
254 #path -> ExtensibleArray new.
255 "The Sequence of path accessors (directory names) to reach the file's area."
256 #name -> ''.
257 "The name of a file(-group)."
258 #fileType.
259 "Corresponds to the filetype or suffix used in many filesystems."
260 #version.
261 "Corresponds to the version of a file, with optional support."
262 #host.
263 "The storage system or logical host of the file."
264 #device.
265 "The logical or physical device hosting the file. (optional)"
268 File locator := File Locator.
270 l@(File Locator traits) type
271 "So a Locator knows it's for a File and not a Directory."
272 [File].
274 s@(String traits) as: l@(File Locator traits)
275 [l readFrom: s].
277 l@(File Locator traits) copy
279   l cloneSettingSlots: #{#path. #name. #fileType. #version}
280     to: {l path copy. l name copy. l fileType copy. l version copy}
283 f1@(File Locator traits) = f2@(File Locator traits)
284 [f1 name = f2 name /\ [f1 path = f2 path] /\ [f1 fileType = f2 fileType]
285    /\ [f1 version = f2 version]].
287 l@(File Locator traits) new &capacity: n
289   l cloneSettingSlots: #{#path. #name. #fileType. #version}
290     to: {l path new &capacity: n. Nil. Nil. Nil}
293 l@(File Locator traits) baseName
294 "Performs the same function as POSIX basename(), answering the last element
295 of the path or the filename itself if not empty/Nil."
297   l name isNotNil /\ [l name isEmpty not]
298     ifTrue: [l name] ifFalse: [l path last]
301 l@(File Locator traits) openFile &mode: mode
303   (File newNamed: l &mode: mode) open
306 l@(File Locator traits) sessionDo: block &mode: mode
307 "Calls the block with the File object as input, opening and closing it
308 transparently in an error-tolerant way. The return value of the block is
309 answered if it completes without error."
310 "NOTE: This cannot protect the case where the handle is created but an error
311 occurs before it can be assigned to the #file slot."
312 [| file |
313   [file := l openFile &mode: mode.
314    block applyWith: file]
315      ensure:
316        [file ifNotNil: [file close]]
319 l1@(File Locator traits) with: l2@(File Locator traits) sessionDo: block &mode1: mode1 &mode2: mode2 &mode: mode
320 "Same as sessionDo, but works with two files.
321 If &mode is defined then this mode will be used for both files."
323   mode ifNotNil: [mode1 := mode. mode2 := mode].
324   l1 sessionDo:
325     [| :file1 |
326      l2 sessionDo:
327        [| :file2 |
328         block apply*, file1, file2] &mode: mode2] &mode: mode1
331 File Locator traits define: #hostSeparator -> $:.
332 File Locator traits define: #pathSeparator -> $/.
334 File traits define: #AbsoluteLocator &parents: {File Locator}.
336 s@(String traits) as: l@(File AbsoluteLocator traits)
337 "Parses the String representation of a path into an actual File Locator object;
338 this will assume the current platform's naming scheme."
340   endHostPart ::= s indexOf: l hostSeparator ifAbsent: [-1].
341   l new `>>
342     [| :newL |
343      host := s copyFrom: 0 to: endHostPart.
344      path := (s sliceFrom: endHostPart + 1) splitWith: l pathSeparator.
345      (s last = l pathSeparator) ifFalse: [newL path removeLast].
346      name := s last = l pathSeparator
347        ifTrue: ['']
348        ifFalse: [s copyFrom:
349                    (s lastIndexOf: l pathSeparator ifAbsent: [-1]) + 1
350                    to: s indexLast]. ]
353 l@(File Locator traits) as: _@(File Locator traits) [l].
355 File Locator traits define: #parentString -> '..'.
356 File Locator traits define: #hereString -> '.'.
357 File Locator traits define: #homeString -> '~'.
358 File Locator traits define: #dot -> $. .
360 File traits define: #RelativeLocator &parents: {File Locator} &slots: {#basePath -> File Locator}.
361 "A path taken relative to an existing one, which may be another RelativeLocator
362 but ultimately must be based on an AbsoluteLocator."
364 "The basis of the path. The other slots are treated as overrides of any
365 information in that path, except the #path which is appended to the path of
366 the basePath."
368 f1@(File RelativeLocator traits) = f2@(File RelativeLocator traits)
369 [f1 name = f2 name /\ [f1 basePath = f2 basePath] /\ [f1 path = f2 path] 
370    /\ [f1 fileType = f2 fileType] /\ [f1 version = f2 version]].
372 s@(String traits) as: l@(File RelativeLocator traits) &base: base
373 [resend `>> [basePath := base ifNil: [Directory Current]. ]].
375 l@(File RelativeLocator traits) newFrom: base
376 [l cloneSettingSlots: #{#basePath} to: {base}].
378 l@(File Locator traits) readFrom: src &pathSeparator: pathSeparator
380   src := src reader. "Should be a PositionableStream."
381   pathSeparator `defaultsTo: l pathSeparator.
382   firstPart ::= src upToAnyOf: ':\\/'.
383   src retract. "Position just before the first one found."
384   src isAtEnd not /\ [src peek = l hostSeparator]
385     ifTrue:
386       [src next.
387        firstPart size = 1 /\ [firstPart first isLetter]
388          "Windows-style path detection, detects a drive letter:"
389          ifTrue: [(l readFrom: src upToEnd &pathSeparator: $\\)
390                     `>> [device := firstPart. ]]
391          "It's a hostname. Read the rest from scratch."
392          ifFalse: [(l readFrom: src upToEnd) `>> [host := firstPart. ]]]
393     ifFalse: "Now detect initial relativity of location or a hostname."
394       [(({l hereString. l parentString. l homeString} includes:
395         firstPart)
396         ifTrue: [l type RelativeLocator newFrom: (firstPart caseOf:
397           {l hereString -> [Directory Current].
398            l parentString -> [Directory Current parent].
399            l homeString -> [Directory Home]})]
400         ifFalse:
401           [firstPart isEmpty
402              "Nothing before \ or / - means it is absolute."
403              ifTrue: [l type AbsoluteLocator clone]
404              "Implicitly relative to the current directory."
405              ifFalse: [src reset.
406                        l type RelativeLocator newFrom: Directory Current]])
407          "Now read the sequence of Path elements into it."
408          `>> [readPathElementsFrom: src &pathSeparator: pathSeparator. ]]
411 l@(File Locator traits) readPathElementsFrom: s &pathSeparator: pathSeparator
413   pathSeparator `defaultsTo: l pathSeparator.
414   pathElements ::= s upToEnd splitWith: pathSeparator.
415   l path := pathElements allButLast.
416   pathElements isEmpty ifFalse: [l readFilenameFrom: pathElements last].
419 l@(File Locator traits) readFilenameFrom: s
421   l name := s copyUpToLast: l dot.
422   (s lastIndexOf: l dot) ifNotNil: [l fileType := s copyAfterLast: l dot].
425 l@(File Locator traits) isRoot
426 "Whether this describes the Root path or an entry within it."
427 [l path isEmpty \/ [l path size = 1 /\ [l path first isEmpty]]].
429 l@(File RelativeLocator traits) isRoot
430 "Relative locators can never be root."
431 [resend /\ [l basePath isRoot]].
433 l@(File Locator traits) reduce
434 "Eliminate uses of . and .. in the path Sequence."
435 [| curIndex |
436   "First, remove all . entries."
437   [(curIndex := l path indexOf: l hereString) isNotNil] whileTrue:
438     [l path removeAt: curIndex].
439   "Now, remove all non-initial .. entries and those that precede them."
440   "Find the first non-.. entry."
441   (curIndex := l path indexOfFirstSatisfying: #(~= l parentString) `er)
442     ifNotNil:
443       [[(curIndex := l path indexOf: l parentString startingAt: curIndex)
444           isNotNil]
445          whileTrue: [l path removeAt: curIndex. l path removeAt: curIndex - 1]].
446   l
449 l@(File RelativeLocator traits) reduce
450 "Go one step further than the generic Locator reduce by manually re-adjusting
451 the basePath by climbing up the number of parents as there are initial ..
452 entries in the path."
454   resend.
455   (l path indexOfFirstSatisfying: #(~= l parentString) `er)
456     ifNotNilDo: [| :numParents |
457       numParents isPositive
458         ifTrue: [l basePath := l basePath locator copy. l basePath path removeLast: numParents].
459       l path removeFirst: numParents].
460   l
463 l@(File Locator traits) writeDeviceOrHostOn: s
465   l device
466     ifNil: [l host ifNotNil: [s ; l host. s nextPut: l hostSeparator]]
467     ifNotNil: [s ; l device. s nextPut: l hostSeparator].
470 l@(File Locator traits) writeNameVersionTypeOn: s
472   l name ifNotNil: [s ; l name].
473   l version ifNotNil: [s ; l hereString ; l version printString].
474   l fileType ifNotNil: [s ; l hereString ; l fileType].
477 l@(File Locator traits) as: s@(String traits) &pathSeparator: pathSeparator &relativeTo: basePath
479   l device ifNotNil: [pathSeparator := $\\].
480   pathSeparator `defaultsTo: l pathSeparator.
481   basePath `defaultsTo: Directory Current.
482   [| :s |
483    l writeDeviceOrHostOn: s.
484    l path do: [| :each | s ; each. s nextPut: pathSeparator].
485    l writeNameVersionTypeOn: s] writingAs: s
488 l@(File RelativeLocator traits) as: s@(String traits) &pathSeparator: pathSeparator &relativeTo: basePath
490   l device ifNotNil: [pathSeparator := $\\].
491   pathSeparator `defaultsTo: l pathSeparator.
492   basePath `defaultsTo: Directory Current.
493   [| :s |
494    l writeDeviceOrHostOn: s.
495    l basePath ifNotNil: [l basePath locator = basePath locator ifFalse:
496      [s ; (l basePath locator as: String &pathSeparator: pathSeparator &relativeTo: basePath)]].
497    l path do: [| :each | s ; each. s nextPut: pathSeparator].
498    l writeNameVersionTypeOn: s] writingAs: s
501 l@(File AbsoluteLocator traits) as: s@(String traits) &pathSeparator: pathSeparator &relativeTo: basePath
503   l device ifNotNil: [pathSeparator := $\\].
504   pathSeparator `defaultsTo: l pathSeparator.
505   basePath `defaultsTo: Directory root.
506   [| :s |
507    l writeDeviceOrHostOn: s.
508    s nextPut: pathSeparator.
509    l isRoot ifFalse:
510      [l path do: [| :each | s ; each. s nextPut: pathSeparator]].
511    l writeNameVersionTypeOn: s] writingAs: s
514 l@(File Locator traits) printOn: s &relativeTo: basePath
515 "Print out (each/path/element)/name.version.fileType."
517   s ; 'P\'' ; (l as: String &relativeTo: basePath) escaped ; '\''
520 l@(File Locator traits) newChildNamed: name
521 [l copy `>> [path addLast: name. ]].
523 "File traits define: #Stream
524             &parents: {ExternalResource ReadWriteStream. PositionableStream}.
525 File Stream removeSlot: #position."
527 "fix: this is a hack (i.e. problem with parent linearization) to make sure we know that
528 ExternalResource Stream is a more significant trait than PositionableStream but we need
529 both in the chain. The #on: function needs to resolve first to ExternalResource Stream."
531 File traits define: #Stream &parents: {ExternalResource ReadWriteStream. ExternalResource ReadStream. ExternalResource WriteStream. ExternalResource Stream. PositionableStream}.
532 File Stream removeSlot: #position.
533 File traits define: #ReadStream &parents: {File Stream}.
534 File traits define: #WriteStream &parents: {File Stream}.
535 File traits define: #ReadWriteStream &parents: {File Stream}.
537 File traits define: #ASCIIStream &parents: {File Stream}.
538 File traits define: #ASCIIReadStream &parents: {File ReadStream}.
539 File traits define: #ASCIIWriteStream &parents: {File WriteStream}.
540 File traits define: #ASCIIReadWriteStream &parents: {File ReadWriteStream}.
541 fs@(File ASCIIStream traits) elementType [ASCIIString Character].
542 fs@(File ASCIIStream traits) collectionType [ASCIIString].
543 fs@(File ASCIIReadStream traits) elementType [ASCIIString Character].
544 fs@(File ASCIIReadStream traits) collectionType [ASCIIString].
545 fs@(File ASCIIWriteStream traits) elementType [ASCIIString Character].
546 fs@(File ASCIIWriteStream traits) collectionType [ASCIIString].
547 fs@(File ASCIIReadWriteStream traits) elementType [ASCIIString Character].
548 fs@(File ASCIIReadWriteStream traits) collectionType [ASCIIString].
551 fs@(File Stream traits) on: target@(String traits)
552 "Open a File ReadWriteStream on the String path."
554    fs on: (File newNamed: target &mode: File ReadWrite) open
557 fs@(File ReadStream traits) on: target@(String traits)
558 "Open a File ReadStream on the String path."
560    fs on: (File newNamed: target &mode: File Read) open
563 fs@(File WriteStream traits) on: target@(String traits)
564 "Open a File WriteStream on the String path."
566    fs on: (File newNamed: target &mode: File Write) open
569 fs@(File ReadWriteStream traits) on: target@(String traits)
570 "Open a File ReadWriteStream on the String path."
572    fs on: (File newNamed: target &mode: File ReadWrite) open
575 fs@(File Stream traits) elementType
576 [Integer].
578 fs@(File Stream traits) collectionType
580   ByteArray
583 fs@(File Stream traits) position
584 [fs resource position].
586 fs@(File Stream traits) position: index
587 [fs resource position := index].
589 fs@(File Stream traits) isAtEnd
590 [fs resource isAtEnd].
592 fs@(File Stream traits) peekForwardBy: offset
593 "Saves the original position and moves forward by the given offset and then
594 restores before answering the element found."
596   ((origPos ::= fs position) + offset between: 0 and: fs resource size)
597     ifFalse: [error: 'Beyond the end of the file.'].
598   fs position := origPos + offset - 1.
599   elem ::= fs next.
600   fs position := origPos.
601   elem
604 fs@(File Stream traits) contents
605 "Get everything from the file at once, preserving the current position in the
606 file."
608   pos ::= fs position.
609   fs position := 0.
610   s ::= fs next: fs resource size.
611   fs position := pos.
612   s
615 f@(File traits) contents
617   f sessionDo: [| :in size result |
618     size := f size.
619     result := String newSize: size.
620     (in read: size into: result).
621     result
622   ]
625 fs@(File Stream traits) file
626 [fs resource].
628 File traits define: #Info &parents: {Cloneable} &slots: {#data -> Array new}.
630 l@(File Info traits) newNamed: locator
632   (File informationForFileNamed: (locator as: String))
633     ifNotNilDo: [| :data | l clone `>> [data := data. ]]
636 l@(File Locator traits) exists
638   l fileInfo isNotNil
641 l@(File Locator traits) fileInfo
643   File Info newNamed: l
646 f@(File traits) fileInfo
648   f Info newNamed: f locator
651 i@(File Info traits) fileSize
653   i data first
656 i@(File Info traits) accessTimestamp
658   i data second
661 i@(File Info traits) modificationTimestamp
663   i data third
666 i@(File Info traits) creationTimestamp
668   i data fourth
671 "We express the bitmask checks in octal to verify against stat.h"
673 i@(File Info traits) typeBits
675   i data last bitAnd: 8r0170000
678 i@(File Info traits) isFile
680   i typeBits = 8r100000
683 i@(File Info traits) isDirectory
685   i typeBits = 8r040000
688 i@(File Info traits) isLink
690   i typeBits = 8r120000
693 i@(File Info traits) isFIFO
695   i typeBits = 8r010000
698 i@(File Info traits) isCharacterDevice
700   i typeBits = 8r020000
703 i@(File Info traits) isBlockDevice
705   i typeBits = 8r060000
708 i@(File Info traits) isSocket
710   i typeBits = 8r140000
713 i@(File Info traits) accessBits
715   i data last bitAnd: 8r777