6.102
6.102 — Software Construction
Spring 2025

Code Examples for Reading 9

Download

You can also download a ZIP file containing this code.

After downloading and unpacking the ZIP file:

  • npm install to install package dependencies;
  • npm run to see a list of the ways this code can be run.
    • For example, if npm run shows that there are scripts called foo and bar, you can run the code with either npm run foo or npm run bar.
    • Two conventional script names are test and start. If the code includes one of those script names, then you can omit run and just say npm test or npm start.
Seems to be necessary to have a triple-backtick block at the start of this page,
otherwise all the pre/code tags below aren't syntax highlighted.
So create a hidden one.

Words1.ts

import fs from 'node:fs';
import path from 'node:path';

/**
 * Words example: familiar TS implementation
 */

/**
 * Find names of all files in the filesystem subtree rooted at folder.
 * @param folder root of subtree, requires fs.lstatSync(folder).isDirectory() === true
 * @returns array of names of all ordinary files (not folders) that have folder as
 *          their ancestor
 */
function allFilesIn(folder: string): Array<string> {
    let files: Array<string> = [];
    for (const child of fs.readdirSync(folder)) {
        const fullNameOfChild = path.join(folder, child);
        if (fs.lstatSync(fullNameOfChild).isDirectory()) {
            files = files.concat(allFilesIn(fullNameOfChild));
        } else if (fs.lstatSync(fullNameOfChild).isFile()) {
            files.push(fullNameOfChild);
        }
    }
    return files;
}

/**
 * Filter an array of files to those that end with suffix.
 * @param filenames array of filenames
 * @param suffix string to test
 * @returns a new array consisting of only those files whose names end with suffix
 */
function onlyFilesWithSuffix(filenames: Array<string>, suffix: string): Array<string> {
    const result: Array<string> = [];
    for (const f of filenames) {
        if (f.endsWith(suffix)) {
            result.push(f);
        }
    }
    return result;
}

/**
 * Find all the non-word-character-separated words in files.
 * @param filenames list of files (all non-null)
 * @returns list of words from all the files
 * @throws IOException if an error occurs reading a file
 */
function getWords(filenames: Array<string>): Array<string> {
    const words: Array<string> = [];
    for (const f of filenames) {
        const data = fs.readFileSync(f, { encoding: "utf8", flag: "r" });
        const lines = data.split(/\r?\n/);
        for (const line of lines) {
            // split on \W (non-word characters, like spaces and punctuation)
            for (const word of line.split(/\W+/)) {
                // split can return empty strings, so omit them
                if (word.length > 0) {
                    words.push(word);
                }
            }
        }
    }
    return words;
}


const allFiles = allFilesIn(".");
const tsFiles = onlyFilesWithSuffix(allFiles, ".ts");
const words = getWords(tsFiles);
for (const s of words) { console.log(s); }

Words2.ts

import fs from 'node:fs';
import path from 'node:path';

/**
 * Words example: TS implementation with map/filter/reduce
 */

/**
 * Find all files in the filesystem subtree rooted at folder.
 * @param folder root of subtree, requires folder.isDirectory() == true
 * @returns stream of all ordinary files (not folders) that have folder as
 *          their ancestor
 */
function allFilesIn(folder: string): Array<string> {
    const children: Array<string> = fs.readdirSync(folder)
                                    .map(f => path.join(folder, f));
    const descendants: Array<string> = children
                                       .filter(f => fs.lstatSync(f).isDirectory())
                                       .map(allFilesIn)
                                       .flat();
    return [
        ...descendants,
        ...children.filter(f => fs.lstatSync(f).isFile())
    ];
}

/**
 * Make a filename suffix testing predicate.
 * @param suffix string to test
 * @returns a predicate that returns true iff the filename ends with suffix.
 */
function endsWith(suffix: string): (filename: string) => boolean {
    return (filename: string) => filename.endsWith(suffix);
}

const filenames: Array<string> = allFilesIn(".")
                                 .filter(s => s.endsWith(".ts"));
const fileContents: Array<Array<string>> = filenames.map(f => {
    const data = fs.readFileSync(f, { encoding: "utf8", flag: "r" });
    return data.split(/\r?\n/);
});
const lines: Array<string> = fileContents.flat();
const words: Array<string> = lines.map(line => line.split(/\W+/)
                                               .filter(s => s.length > 0))
                             .flat();
words.forEach(s => console.log(s));