The Machines · Data & storage

file

Line-Oriented Text Files — machines/file.shoddy

the file machine's icon

Summary

file lets a Shoddy program treat a text file as what it usually is: a stack of lines. You hand it a path and get back a plain list of strings, one per line — or you hand it a list of strings and it lays them down as lines on disk. That's the whole idea. It sits on top of Shoddy's whole-file builtins (ReadFile, WriteFile, AppendFile and their guarded twins). It does the two fiddly bits you'd otherwise do by hand: it splits the text on newlines (the invisible characters that end a line), and it tidies up the line endings so a file written on Windows reads back the same as one written anywhere else. No handles to open or close — you read the file all at once, or you write it all at once.

A Brief History of the Line

A "line" of text ends differently depending on where it grew up, and the reason is mechanical. On a teletype — a typewriter-like printing terminal — returning the carriage to the left margin and rolling the paper up a row were two separate physical acts. So the machines were sent two separate characters — carriage return, then line feed — and early operating systems dutifully stored both. The CP/M → DOS → Windows line kept the pair. Unix, in 1969, decided the file should record the idea of a line ending rather than the choreography of a 1960s printer. It stored a single line feed and translated on the way to the device. Fifty years later both conventions are still with us. That is why "the same file" can be a different length on two machines, and why half the noise in a file comparison — a diff — is invisible characters. This machine takes the Unix view of what a file means: a list of lines, endings nobody's business. It does the tidying at the boundary, so a file written on Windows reads back the same everywhere.

Why It's Useful

An enormous amount of everyday data is just lines of text: a list of names, a to-do file, a log, a little config, the output of yesterday's run that today's run needs to read back. Working with that as one giant string is awkward — you end up hunting for newlines and worrying about whether the last line has one. file takes that chore off your hands so you can think in lines: read a file into a list, loop over it, filter it, sort it, write it back. Reach for it whenever a program needs to remember something between runs and that something is naturally a list of lines. When you need to look individual records up by key rather than read the whole thing top to bottom, reach for its bigger cousin isam instead.

User's Guide

The whole machine does the obvious things: read all the lines, write all the lines, or add one line to the end. Because reading and writing each touch the whole file in one go, there's nothing to open and nothing to close — you just call the word and you're done.

Include "file.shoddy"

Def Main()
    WriteLines("names.txt", { "ADA", "LIN", "GRACE" })
    AppendLine("names.txt", "HEDY")

    Let xs = ReadLines("names.txt")
    Print(Length(xs))                   ' 4
    Print(Nth(xs, 1))                   ' ADA
    Each(xs, Fn(s) => Print(s))         ' ADA LIN GRACE HEDY, one per line

A few things worth knowing:

Builtins

The eight whole-file words the runtime dispatches — not defined here, documented here

These are not file's Defs. The engine dispatches them — runs them itself — and a Def whose name is a builtin is refused. They are listed on this page because file is the machine whose domain they belong to. A reader who opens the files machine looking for ReadFile should find it here, rather than be expected to know that the flat reference exists. They need no Include — a builtin is callable from any program. The same eight are documented word for word in machines/file.shoddy's own header block.

No handles: a text file is read or written whole. A handle is the open-file ticket other languages make you hold and give back; there is none here. Paths resolve against the working directory (the folder the program was started in), and every one of these is effectful.

The Try* words are not duplicates, and the reason is the shape of error handling in this language. Shoddy has no catchable errors, so an aborting word cannot be asked "would this work?" — the asking is the doing, and a failure ends the run. The Try* forms are how a program survives a path it did not choose. FileExists cannot stand in for any of them: a directory reports False, an existing file that cannot be opened reports True, and a file that is there when you ask can be locked by the time a delete lands.

Reading

WordDescription
ReadFile(path)The whole file as one string, newlines and all. Aborts if the path cannot be read. ReadLines below is the one that splits it.
TryReadFile(path)The same read with the failure reported rather than fatal: Ok(text), or Err(why, 0). The why is CANNOT READ 'path' (…) ending in one of NO SUCH FILE, IS A DIRECTORY, ACCESS DENIED or UNREADABLE — a closed set of Shoddy's own, never the host platform's exception text, which varies by OS and locale. At is 0: a failure to open a file has no position in it. It answers a Result rather than a Boolean because, unlike TryWriteFile, it has a payload to carry on success.

Writing

WordDescription
WriteFile(path, s)Write the string to path, replacing whatever was there. Aborts on failure.
TryWriteFile(path, s)The same write, answering True on success rather than aborting. A Boolean and not a Result: a write has nothing to hand back.
AppendFile(path, s)Add the string to the end of path, creating the file if it is not there. Aborts on failure, and has no guarded twin.

Asking and removing

WordDescription
FileExists(path)Whether there is a readable file at path. A directory answers False. Not a pre-flight for the aborting words — see above.
DeleteFile(path)Delete the file at path. A missing file aborts.
TryDeleteFile(path)The same delete, answering True if the file went and False if it was not there or could not be removed.

Word Reference

Every word this machine exports

Reading and writing lines

WordDescription
ReadLines(path)Reads the whole file at path and hands it back as a list of strings, one per line. Carriage returns (\r) are stripped first, so Windows-style files read back the same as any other, and a single trailing newline is dropped. An empty file gives you an empty list.
WriteLines(path, xs)Writes the list of strings xs to the file at path, one line each, with a newline after every line (including the last). Overwrites whatever was there before.
AppendLine(path, s)Adds the single string s as a new line at the end of the file at path, followed by a newline. Leaves the existing contents untouched — good for growing a log.

Who Uses It

UserHow
demographicsReadLines loads the census rows off disk in the trainer.
irisReadLines loads the flowers off disk in the trainer.
neuralReadLines / WriteLines carry the text model format.
tallyReadLines reads the golden report the headless suite grades against, line for line.
weather-glassThe offline test reads the captured fixtures and the golden render with ReadLines.

The Machines It Uses

MachineWhy
strSplit, Join, Replace and EndsWith for line and path chores.