Programming Language

Nocter

A self-contained systems language built around simplicity, encapsulation, and foolproof design.

/examples/text-search/search.nct

search.nct

use std/fs.{FileType, read_dir}

use std/io.{File, Writer, stderr, stdout}

use std/io/buffer.{BufReader, BufWriter}

use std/num.usize_to_string

use std/process.{arg, arg_count}

use std/string.String

use std/vec.Vec

struct SearchPath {
    full: String
    display: String
}

instance SearchPath {
    operator (&self < other: &Self): bool {
        return &self.display < &other.display
    }
}

func run(): i32 {
    if arg_count() != 3 {
        report_usage() catch _ { return 2 }
        return 2
    }
    let needle = arg(1) catch failure {
        report_failure(&failure) catch _ { return 2 }
        return 2
    } otherwise {
        report_usage() catch _ { return 2 }
        return 2
    }
    let root = arg(2) catch failure {
        report_failure(&failure) catch _ { return 2 }
        return 2
    } otherwise {
        report_usage() catch _ { return 2 }
        return 2
    }
    let matched = search_tree(needle, root) catch failure {
        report_failure(&failure) catch _ { return 2 }
        return 2
    }
    if matched {
        return 0
    }
    return 1
}

func search_tree(needle: &str, root: &str): bool! {
    var paths: Vec<SearchPath> = Vec.empty()
    collect_directory(root, "", &+paths)?
    paths.sort()

    var output = BufWriter.new(stdout())
    var matched = false
    for path in &paths {
        if search_file(&+output, needle, path)? {
            matched = true
        }
    }
    output.close()?
    return matched
}

func collect_directory(
    full_directory: &str,
    display_directory: &str,
    files: &+Vec<SearchPath>,
): void! {
    var stream = read_dir(full_directory) catch failure {
        let context = String.concat("cannot read directory ", full_directory)
        return failure.context(&context)
    }
    var directories: Vec<SearchPath> = Vec.empty()
    while true {
        let entry = stream.next() catch failure {
            let context = String.concat("cannot continue directory ", full_directory)
            return failure.context(&context)
        } otherwise {
            break
        }
        let name = entry.file_name()
        let full: &str = entry.path()
        let display = display_child(display_directory, name)
        if entry.file_type() is FileType.regular {
            files.push(
                SearchPath {
                    full: String.copy(full),
                    display: move display,
                },
            )
        } else if entry.file_type() is FileType.directory {
            directories.push(
                SearchPath {
                    full: String.copy(full),
                    display: move display,
                },
            )
        }
    }
    stream.close()

    directories.sort()
    for directory in move directories {
        collect_directory(&directory.full, &directory.display, files)?
    }
    return
}

func display_child(parent: &str, name: &str): String {
    if parent.is_empty() {
        return String.copy(name)
    }
    return String.concat(parent, "/", name)
}

func search_file(output: &+BufWriter, needle: &str, path: &SearchPath): bool! {
    let source = File.open(&path.full) catch failure {
        let context = String.concat("cannot open ", &path.display)
        return failure.context(&context)
    }
    var reader = BufReader.new(move source)
    var line = String.empty()
    var line_number: usize = 1
    var matched = false
    while true {
        let present = reader.read_line_into(&+line) catch failure {
            let context = String.concat("cannot read ", &path.display)
            return failure.context(&context)
        }
        if !present {
            break
        }
        if line.contains(needle) {
            write_match(output, &path.display, line_number, &line)?
            matched = true
        }
        line_number += 1
    }
    reader.close()
    return matched
}

func write_match(
    output: &+BufWriter,
    path: &str,
    line_number: usize,
    line: &str,
): void! {
    let number = usize_to_string(line_number)
    output.write_text(path)?
    output.write_text(":")?
    output.write_text(&number)?
    output.write_text(":")?
    output.write_text(line)?
    output.write_text("\n")?
    return
}

func report_usage(): void! {
    var output = stderr()
    output.write_text("usage: text-search NEEDLE ROOT\n")?
    return
}

func report_failure(failure: &error): void! {
    var output = stderr()
    output.write_text("text-search: ")?
    output.write_text(failure.code())?
    output.write_text(": ")?
    output.write_text(failure.message())?
    output.write_text("\n")?
    return
}