# -*- merd -*-
#
# THIS IS NOT WORKING YET, WORKING IN PROGRESS
#
#
# The examples are taken from the Perl Cookbook
# By Tom Christiansen & Nathan Torkington
# see http://www.oreilly.com/catalog/cookbook for more

#@ #@@PLEAC@@_NAME
#@ Merd
#@ 
#@ #@@PLEAC@@_WEB
#@ http://merd.net/

# 1.1. Accessing Substrings

s = "This is what you have"
first = s[0]                            # "T"
start = s[5..7]                         # "is"
rest  = s[13..]                         # "you have"
last  = s.last                          # "e"
end   = s.lasts(4)                      # "have"
piece = s.lasts(8 .. 5)                 # "you"

# magic substr not usefull (?)

s.subst!(G, "is", "at")


# 1.2. Establishing a Default Value

2 &&& "ee" # -> "ee"
0 &&& "ee" # -> ""

"a" ||| "b" # -> "a"
 "" ||| "b" # -> "b"

x |||= "b"


# 1.3. Exchanging Values Without Using Temporary Variables
a, b = b, a

# 1.4. Converting Between ASCII Characters and Values
# Char::ord !! int
# Int::chr !! char

"HAL".map(next)                         # "IBM"

# 1.5. Processing a String One Character at a Time

# a string is a list, no pb
println(  "unique chars are: {"an apple a day".uniq.sort}")
println(qq(unique chars are: {"an apple a day".uniq.sort}))

# 1.6. Reversing a String by Word or Character
println(s.reverse)                          # reverse letters
println(s.words.reverse.join(" "))          # reverse words

long_palindromes = cat("/usr/dict/words").map(chomp).filter(s -> s == s.reverse && s.size > 4)

# 1.7. Expanding and Compressing Tabs
s.expand_tabs = s.fixpoint(
    break(, "\t",
          a,b -> a + " ".times(8 - a.size modulo 8) + b
    )
)

# 1.8. Expanding Variables in User Input

# forbidden, use one of:
s = "You owe {debt} to me"
fs(s) = "You owe {s} to me"
fs':= sprintf("You owe %s to me",)

"I am 17 years old".subst(G, "(\d+)", (n -> "{2 * n}"))
"I am 17 years old".subst(G, "(\d+)", *2 ~ to_string)

# 1.9. Controlling Case
e = "bo peep".upcase
e.downcase!
e.capitalize!

"thIS is a loNG liNE".words.map(capitalize)

# 1.10. Interpolating Functions and Expressions Within Strings
println("I have {n + 1} guanacos.")

w = "work"
very := () -> "very"
well() = "well"
"this is gonna {w} {very()} {well()}"

# 1.12. Reformatting Paragraphs
wrap(str, max_size) =
    all = []
    line = ""
    str.words.each.(l ->
        if size(line+l) >= max_size then
            all += [line]
            line = ""
        line += (line &&& " ") + l
    )
    join(all + [line], "\n")

s.wrap(width) =
    ls, s' = s.words.foldl(([], ""), ((ls, s), word ->
        if "{s} {word}" > width then
            ls + [s], word
        else
            s' = (s &&& "{s} ") + word
            ls, s'
    ))
    join(ls + [s'], "\n")
    
# 1.14. Trimming Blanks from the Ends of a String
s.subst!("^\s+", "")
s.subst!("\s+$", "")

s.trim = s.subst("^\s+", "").subst("\s+$", "")

# 2.5. Operating on a Series of Integers
print("Infancy is: ") ; (0..2).each(e -> print("{e} "))
print("Infancy is: ") ; (0..2).map(to_string).join(" ").print

# 2.7. Generating Random Numbers
rand = random(25 .. 75)                 # rand is in [25,75[

chars = "A".."Z" + "a".."z" + "0".."9" + q(!@$%^&*)
password = 8.n_times(chars[random(chars.size)]).flatten

# 2.16. Converting Between Octal and Hexadecimal
number = hex(hexadecimal) # hexadecimal
number = to_int(octal, 8) # octal

print("Gimme a number in decimal, octal, or hex: ")
num = Sys::stdin.line.chomp
num = num.pattern_matches(
    "^0x", hex(num),
    "^0", to_string(num, 8),
    "", num,
)
"%d %x %o\n".printf(num, num, num)

# 2.17. Putting Commas in Numbers
s.commify =
    integer.fixpoint(subst(, "(\d)(\d\{3})($|\.|,)", (a,b,c -> "{a},{b}{c}")))
s.commify' =
    s.pattern_matches(
        "(.*)(...)", (a,b -> a.commify' + "," + b)
        "", s
    )
    
# 4.1. Specifying a List In Your Program
a = ("quick", "brown", "fox")
a = "Why are you teasing me?".words
big_array = "mydatafile".open(Sep(crlf)).lines

banner = "The Mines of Moria"
banner = q(The Mines of Moria)

name   = "Gandalf"
banner = "Speak, {name}, and enter!"
banner = qq(Speak, {name}, and welcome!)
ships  = "Niña Pinta Santa María".words # WRONG
ships  = ("Niña", "Pinta", "Santa María")

# 4.2. Printing a List with Commas
commify_series =
    [] -> ""
    l ->
        l', e = l.pop
        l'.join(", ") + " and " + e

array = ("red", "yellow", "green")
println("I have {array.join(" ")} marbles.") #=> I have red yellow green marbles.

# 4.3. Array Size
l.what_about_that_array =
    println("The array now has {l.size} elements.")
    println("The index of the last element is {l.size - 1}.")
    println("Element #3 is `{l[3]}'.")

"Crosby Stills Nash Young".words.what_about_that_array

# 4.4. Doing Something with Every Element in a List
bad_users.each(complain)
bad_users.each(user -> complain(user))

Sys::env.keys.sort.each(var -> "{var}={Sys::env{var}}".println)
Sys::env.each(var, val -> "{var}={val}".println) # but non-sorted
Sys::env.sort_by(a,_ , b,_ -> a <=> b).each(var, val -> "{var}={val}".println)

bad_users.each(user ->
    if user.get_usage > Max_quota then
        complain(user)
)

"who".popen.lines(s ->
    s.m!("tchrist", s.print)
)

df.lines(s ->
    s.words.each(s -> s.reverse.println)
)

array.each(item -> "i = {item}".println)
array.each(--)

a = ( 0.5, 3 ); b = ( 0, 1 )
(a,b).each(l ->
    l.map(a ->
        a *= 7
        "{a} ".print
    )
)

# 4.6. Extracting Unique Elements from a List
unique = list.uniq

# generate a list of users logged in, removing duplicates
users = "who".popen.lines.map(m(,"(\S+)")).sort.uniq
println("users logged in: {users}")

# 4.7. Finding Elements in One Array but Not Another
difference(a, b)

# 4.8. Computing Union, Intersection, or Difference of Unique Lists
union(a, b)
intersection(a, b)
difference(union(a,b), intersection(a,b))

# 4.9. Appending One Array to Another
members = ("Time", "Flies")
initiates = ("An", "Arrow")
members += initiates
# members is now ("Time", "Flies", "An", "Arrow")

members = ("Time", "Flies")
initiates = ("An", "Arrow")
members.insert!(2, "Like" + initiates)
# members is now ("Time", "Flies", "Like", "An", "Arrow")
members[0] = "Fruit"
members[2,3] = "A", "Banana"
# members is now ("Fruit", "Flies", "Like", "A", "Banana")

# 4.10. Reversing an Array
reversed = array.reverse

array.reverse.each(e ->
    # do something with e
    ()
)

# 4.11. Processing Multiple Elements of an Array
a.shift2 = a.shift!, a.shift!
a.pop2 = swap(a.pop!, a.pop!)

friends = "Peter Paul Mary Jim Tim".words
(this, that) = friends.shift2
# this contains Peter, that has Paul, and
# friends has Mary, Jim, and Tim

beverages = "Dew Jolt Cola Sprite Fresca".words
pair = beverages.pop2
# pair[0] contains Sprite, pair[1] has Fresca,
# and beverages has (Dew, Jolt, Cola)

# 4.12. Finding the First List Element That Passes a Test
highest_engineer = employees.find(employee -> employee.category == "engineer")
println("Highest paid engineer is: {highest_engineer.name}")

# 4.13. Finding All Elements in an Array Matching Certain Criteria
bigs = nums.filter(n -> n > 1_000_000)
pigs = users.filter(_, n -> n > 1e7).keys

matching = popen("who").lines.filter(m?(, "^gnat "))
engineers = employees.filter(employee -> employee.category == "engineer")

secondary_assistance = applicants.filter(e -> e{Income} >= 26_000 && e{Income} < 30_000)

# 4.14. Sorting an Array Numerically
sorted = sort(non_sorted)               # if non_sorted !> Vector(Int)

# pids is an unsorted array of process IDs
pids.sort.each(println)

println("Select a process ID to kill:")
try
    pid = Sys::stdin.line.chomp.to_int
    pid.kill(TERM)
    sleep(2)
    pid.kill(KILL)
with Failed(To_int) -> die("Exiting ... \n")

descending = non_sorted.sort_by(a,b -> b <=> a)

# 4.15. Sorting a List by Computable Field
ordered = unordered.sort_by(compare)

precomputed = unordered.map(e -> e.compute, e)
ordered_precomputed = precomputed.sort_by(a,b -> a[0] <=> b[0])
ordered = ordered_precomputed.map(a -> a[0])

ordered = unordered.map(e -> e.compute, e)
                   .sort_by(a,b -> a[0] <=> b[0])
                   .map(a -> a[0])

sorted = employees.sort_by(a,b -> a.name <=> b.name ||| b.age <=> a.age)

# getpwent is an iterator
users = getpwent().to_list
users.sort_by!(a,b -> a{Name} <=> b{Name})
users.each(user -> user{Name}.println)

sorted = names.sort_by(a,b -> a[1] <=> b[1])
sorted = string.sort_by(a,b -> a.size <=> b.size)

sorted_fields = fields.map(e -> e.m("(\d+)"), e)
                      .sort_by(a,b -> a[0] <=> b[0])
                      .map(a -> a[0])

"/etc/passwd".open
    .lmap(e -> e.split(":")[3,2,0], e)
    .sort_by(a,b ->
        a[1] <=> b[1] ||| # gid
        a[2] <=> b[2] ||| # uid
        a[3] <=> b[3]     # login
    )
    .map(a -> a[0])

# 4.16. Implementing a Circular List
circular.unshift!(circular.pop!)        # the last shall be first
circular.push!(circular.shift!)         # and vice versa

l.grab_and_rotate =
    e = l.shift!
    l.push!(e)
    e

processes = (1, 2, 3, 4, 5)
loop
    process = grab_and_rotate(processes)
    println("Handling process {process}")
    sleep(1)


# 4.17. Randomizing an Array
l.randomize!

l.fisher_yates_shuffle =
    (l.size - 1 .. 0).each(i ->
        j = random(i+1)
        l[i,j] = l[j,i] if i != j
    )

l.naive_shuffle =
    l.each_with_index(_, i ->
        j = random(l.size)
        l[i,j] = l[j,i] if i != j
    )

# 5. Hashes

age = {
    "Nat"  , 24,
    "Jules", 25,
    "Josh" , 17,
}
age{"Nat"}   = 24
age{"Jules"} = 25
age{"Josh"}  = 17


# 5.1. Adding an Element to a Hash
food_color = {
    "Apple" , "red",
    "Banana", "yellow",
    "Lemon" , "yellow",
    "Carrot", "orange",
}
food_color{"Raspberry"} = "pink"
println("Known foods:")
food_color.keys.each(println)

# 5.2. Testing for the Presence of a Key in a Hash
("Banana", "Martini").each(name ->
    println("{name} is a {if food_color.key?(name) then "food" else "drink"}")
)
# Banana is a food.
# Martini is a drink.

# 5.3. Deleting from a Hash
food_color.remove_key!("Banana")

# 5.4. Traversing a Hash
food_color.each(food, color -> "{food} is {color}.".println)
food_color.keys.sort.each(food -> "{food} is {food_color{food}}.".println)

# 5.5. Printing a Hash
hash.each(k,v -> "{k} => {v}".println)

# 5.6. Retrieving from a Hash in Insertion Order
# the default should be to keep the order, so no pb here.

# 5.7. Hashes with Multiple Values Per Key
ttys = {}
popen("who").each(s ->
    user, tty = s.words[0,1]
    #ttys{user} = [] if not ttys.key?(user)    <- not needed, auto created with ***
    ttys{user}.push!(tty)
)
ttys.keys.sort.each(user -> "{user}: {ttys{user}.join(", ")}".println)

# or the more functional:
popen("who").foldl({}, (ttys, s ->
    user, tty = s.words[0,1]
    #ttys{user} = [] if not ttys.key?(user)    <- not needed, auto created with ***
    ttys{user}.push(tty)
)).keys.sort.each(user -> "{user}: {ttys{user}.join(", ")}".println)

# 5.8. Inverting a Hash
surname = { "Mickey","Mantle" , "Babe","Ruth" }
println(surname.value2key("Mantle"))    # => Mickey

# 5.9. Sorting a Hash
food_color.keys.sort.each(food -> "{food} is {food_color{food}}.".println)

# 5.10. Merging Hashes
merged = a + b

drink_color = { "Galliano","yellow" , "Mai Tai","blue" }
ingested_colors = drink_color + food_color

substance_color = drink_color + food_color
intersection(drink_color, food_color).each(k,_ ->
    println("Warning: {k} seen twice. Using the last definition.")
)

# 5.11. Finding Common or Different Keys in Two Hashes
all_colors += new_colors

common = intersection(hash1, hash2).keys
common = intersection(hash1.keys, hash2.keys)

citrus_color = {
    "Lemon", "yellow",
    "Orange", "orange",
    "Lime", "green",
}
non_citrus = difference(food_color, citrus_color).keys
non_citrus = difference(food_color.keys, citrus_color.keys)

# 5.12. Hashing References
# no pb, merd's dicts handle any kind of objects which have Eq

# 5.13. Presizing a Hash
# TODO

# 5.14. Finding the Most Common Anything
count = {}
array.each(e -> count{e}++)

# 5.15. Representing Relationships Between Data
father = {
    "Cain"      , "Adam",
    "Abel"      , "Adam",
    "Seth"      , "Adam",
    "Enoch"     , "Cain",
    "Irad"      , "Enoch",
    "Mehujael"  , "Irad",
    "Methusael" , "Mehujael",
    "Lamech"    , "Methusael",
    "Jabal"     , "Lamech",
    "Jubal"     , "Lamech",
    "Tubalcain" , "Lamech",
    "Enos"      , "Seth",
}
Sys::stdall.each(e ->
    e.chomp!
    
    #fathers = e : e.unfoldr1(e -> Some(father{e}) or None)
    #println(fathers.join(" "))

    print("{e}")
    try loop
        e = fathers{e}
        print("{e} ")
    with _ -> println("")
)

Sys::stdall.each(e ->
    e.chomp!
    children = father.key2values(e) ||| [ "nobody" ]
    println("{e} begat {children.join(", ")}.")
)

includes = {}
files.each(file ->
    try
        file.open.each(s ->
            s.pattern_matches(
                "^\s*#\s+include\s+<(.*?)>",
                f -> includes.push!(f)
            )
        )
    with File_error(err) -> warn("Couldn't read {file}: {err}; skipping.\n")
)
include_free =
    # list of files that don't include others
    difference(includes.values.flatten.uniq, includes.keys)

# 6. Pattern Matching
meadow.m!(I, "\bovines?\b", println("Here be sheep!"))

string = "good food"
string.subst!("o*", "e")

"ababacaca".m!(
    "((a|ba|b)+(a|ac)+)",
    s,_,_ -> s.println
)                                       # => ababa

s.m!(G, "(\d+)", e->println("Found number {e}"))
numbers = s.m(G, "(\d+)")

# 6.1. Copying and Substituting Simultaneously
dst = src.subst("this", "that")

# Make All Words Title-Cased
capword = word.subst(G, "(\w+)", capitalize)

# /usr/man/man3/foo.1 changes to /usr/man/cat3/foo.1
catpage = manpage.subst("man(?=\d)", "cat")

bindirs = " /usr/bin /bin /usr/local/bin ".words
libdirs = bindirs.map(subst(, "bin", "lib"))
println(libdirs.join(" "))              # /usr/lib /lib /usr/local/lib

# 6.5. Finding the Nth Occurrence of a Match
s = "One fish two fish red fish blue fish"
want = 3
count = 0
s.m!(
    G|I,
    "(\w+)\s+fish\b",
    s ->
        count++
        if count == want then
            println("The third fish is a {s} one.") # => red
)
s.m!(
    I, "(?:\w+\s+fish\s+)\{2}(\w+)\s+fish",
    s -> println("The third fish is a {s} one.") # => red
)
colors = s.m(G|I, "(\w+)\s+fish\b")
println("The third fish is a {colors[2]} one.") # => red

pond = "One fish two fish red fish blue fish swim here."
color = pond.m(G|I, "\b(\w+)\s+fish\b").last
println("Last fish is {color}.")        #=> Last fish is blue.

# 6.8. Extracting a Range of Lines
file.open.to_list.each_with_index(s,i -> println(s) if i.member?(15 .. 17))

r = Regexp::new_range(m?(, I, "<XMP>"), m?(, I, "</XMP>"))
l.each(s -> if s.member?(r) then print(s))

header = Regexp::new_range(_ -> True, == "")
body = Regexp::new_range(== "", _ -> True)
l.each(s ->
    in_header = s.member?(header)
    in_body = s.member?(body)
)

header = Regexp::new_range(m?(, I, "^From:?\s"), == "")
Sys::stdall.collect(s ->
    if s.member?(header) then
        c = "[^<>(),;\s]"
        s.m(G, "({c}+\@{c}+)")
    else []
).flatten.uniq.each(println)

# 6.9. Matching Shell Globs as Regular Expressions
s.glob2pat =
    patmap = {
        "*", ".*",
        "?", ".",
        "[", "[",
        "]", "]",
    }
    s.subst!(G, "(.)", (c -> patmap{c} or c.quotemeta))
    "^{s}$"

# 6.10. Speeding Up Interpolated Matches
# no need

# 6.11. Testing for a Valid Pattern
# not possible? do not allow runtime constructed regexps?

# 7. File Access
"/usr/local/widgets/data".open.each(
    s -> s.m!("bleu", s.println)
)

Sys::stdall.lines(s ->
    #s.m!("\d", warn("No digit found.\n"))
    warn("No digit found.\n") if not s.m?("\d")
    print("Read: {s}")
)

# 7.1. Opening a File

fd = open(filename, W)
fd.print(s)

filename.output(s)

# 7.2. Opening Files with Unusual Filenames
# ;pp

# 7.3. Expanding Tildes in Filenames
"~".expand_path                         # a la ruby

# 7.4. Making Perl Report Filenames in Errors
# default is exception on error => good error messages
# with open(f, Safe), no exception on file access

# 7.5. Creating Temporary Files
f = tmpfile("foo") or tmpfile("/tmp/fooXXXXXX") # "foo" gives file $TMPDIR/fooXXXXXX
f.output("bar")                 # output "bar" to a temporary file named f

# 7.6. Storing Files Inside Your Program Text
# use a string:
DATA = q(
# your data goes here
)

# 7.7. Writing a Filter

# 7.10. Modifying a File in Place Without a Temporary File
l = file.open.lines.map(subst(, "DATE", localtime()))
file.output(l)

# 7.11. Locking a File
f = "numfile".open(R | W | Lock)
# Now we have acquired the lock, it's safe for I/O
num = f.whole.m("(\d+)") or 0
f.truncate!(0)            # truncate! ensure the current seek is <= current size
f.print("{num}\n")

# 7.12. Flushing Output
output_handle.autoflush!(True)

# 7.13. Reading from Many Filehandles Without Blocking
l = [ fh1, fh2, fh3 ].select
if fh1.mem?(l) then
    # do something with fh1
if fh2.mem?(l) then
    # do something with fh2

# 7.14. Doing Non-Blocking I/O
modem = "/dev/cua0".open(Non_block | W)

# 7.15. Determining the Number of Bytes to Read
# don't do it, use "whole" with Non_block

# 7.16. Storing Filehandles in Variables
# ;pp

# 7.18. Printing to Many Filehandles Simultaneously
# maybe introduce Out_files !< Out_file

# 7.19. Opening and Closing File Descriptors by Number
fh = fdopen(fdnum, R) # open file descriptor 3 for reading

# 7.20. Copying Filehandles
alias = original
outcopy = Sys::stdout.clone
incopy = Sys::stdin.clone

# 8. File Contents
datafile.each(s ->
    s.chomp!
    s.size.println                      # output size of line
)

lines = datafile.lines
whole_file = file.whole

["One", "two", "three" ].each(print(handle,))  # "Onetwothree"
println("Baa baa black sheep.")         # Sent to default output handle

buffer = handle.read(4096)

handle.truncate(length)
"tmp/{Sys::pid}.pid".truncate(length)

pos = datafile.tell_seek
println("I'm {pos} bytes from the start of DATAFILE.")

logfile.seek!(End)
datafile.seek!(0)
out.seek!(out.tell_seek - 20)

datafile = datafile.print(mystring)
block = infile.read(256)                # can't do offset writing of 5

# 8.1. Reading Lines with Continuation Characters

while not fh.empty? do
    lines = fh.take_while(substr!?(, "\\$", ""))
    line = lines.join("")
    # process full record in {line} here

# 8.2. Counting Lines (or Paragraphs or Records) in a File

file.open.lines.size

f = file.open(Sep_keep("\n+"))         # enable paragraph mode for "line", "lines", "each"...
f.lines.size

# 8.3. Processing Every Word in a File
Sys::stdall.each(s ->
    s.words.each(word ->
        # do something with {word}
    )
)

Sys::stdall.reopen(Sep("\s+")).each(s ->
    # do something with {word}
)

# Make a word frequency count
seen = {}
Sys::stdall.reopen(Sep("\s+")).each(s -> seen{s.downcase}++)
# output hash in a descending numeric sort of its values
seen.keys.sort_by(a,b -> seen{b} <=> seen{a})
    .iter(word -> "%5d %s\n".printf(seen{word}, word))

# Line frequency count
seen = {}
Sys::stdall.each(s -> seen{s.downcase}++)
# output hash in a descending numeric sort of its values
seen.keys.sort_by(a,b -> seen{b} <=> seen{a})
    .iter(line -> "%5d %s".printf(seen{line}, line))

# 8.4. Reading a File Backwards by Line or Paragraph
file.lines.reverse.each(line ->
    # do something with {line}
)

file.open(Sep_keep("\n+")).lines.reverse.each(paragraph ->
    # do something with paragraph    
)

# 8.5. Trailing a Growing File
loop
    fh.each(line ->
        # ...
    )
    sleep(sometime)
    # clearerr?

# 8.6. Picking a Random Line from a File
i = 0
line = stdall.find(_ -> i++ ; random(i) == 0)
# {line} is the random line

i = 0
adage = "/usr/share/games/fortunes"
    .open(Sep("%\n"))
    .find(_ -> i++ ; random(i) == 0)
adage.print 

# 8.7. Randomizing All Lines
input.lines.randomize.each(s -> output.print(s))

# 8.8. Reading a Particular Line in a File
i = 0
line = handle.find(_ -> i++ ; i == desired_line_number)

line = handle.to_list[desired_line_number]


build_index(data_file, index_file) =
    offset = 0
    in.each(_ ->
        out.print(offset.pack("N"))
        offset = in.tell_seek
    )
    
line_with_index(data_file, index_file, line_number) =
    i_offset = line_number-1 * pack_size("N")
    index_file.seek!(i_offset) or return
    
    entry = index_file.read(pack_size("N"))
    d_offset = entry.unpack("N")
    data_file.seek!(d_offset)
    data_file.line

# usage:
in = file.open
index = "{file}.idx".open(R | W)
build_index(in, index)
line = line_with_index(in, index, seeking)

# 8.9. Processing Variable-Length Text Fields
"3+5-2".split("([+-])")                 #=> [ "3", "+", "5", "-", "2" ]
fields = record.split(":")
fields = record.split("\s+")
fields = record.words

# 8.10. Removing the Last Line of a File
fh = file.open(R | W)
addr = 0
fh.each(_ -> addr = fh.tell_seek if not file.empty?)
fh.truncate!(addr)

# 8.11. Processing Binary Files
# don't care about non-UNIX ;pp

# 8.12. Using Random-Access I/O
address = recsize * recno
fh.seek!(address)
buffer = fh.read(recsize)

# 8.13. Updating a Random-Access File
address = recno * pack_size(format)
fh.seek!(address)
fields = fh.read_unpack(format)
# update fields, then
fh.seek!(address)
fields.pack_print(format, fh)

# weekearly -- set someone's login date back a week
user    = Sys::args[0] or Sys::env{"USER"} or Sys::env{"LOGNAME"}

typedef = "L A12 A16"                   # linux fmt; sunos is "L A8 A16"
address = Sys::getpwnam(user){Uid} * pack_size(typedef)

lastlog = "/var/log/lastlog".open(R | W)
    or die("can't update /usr/adm/lastlog: {Sys::errno_string}")
lastlog.seek!(address)

(time, line, host) = lastlog.read_unpack(typedef)
time -= 24 * 7 * 60 * 60                # back-date a week

lastlog.seek!(address)
(time, line, host).pack_print(typedef, lastlog)


# 8.14. Reading a String from a Binary File
file : addrs = Sys::args
if addrs == [] then die("usage: {Sys::progname} addr ...\n")
        
fh = file.open(Sep("\0"))
addrs.each(s ->
    fh.seek!(s.to_int)
    qq(%#x %#o %d "%s"\n).printf(addr, addr, addr, fh.line)  
)

# 8.15. Reading Fixed-Length Records
while not file.empty? do
    fields = file.read_unpack(template)
    # use fields

# 8.16. Reading Configuration Files
config.each(s ->
    s.chomp!                            # no newline
    s.subst!("#.*", "")                 # no comments
    s.chop_bounding_spaces!             # no leading white, no trailing white
    s.m!(
        "(\S*)\s*=\s*(.*)", 
        var, value -> User_Preferences{var} = value
    )
)

# 8.17. Testing a File for Trustworthiness
info = File::stat(filename)
println("Superuser owns filename") if info{Uid} == 0
println("filename has been read since it was written.") if info{Atime} > info{Mtime}

# 9.1. Getting and Setting Timestamps

(reatime, writetime) = File::stat(filename){Readtime, Writetime}
# modify readtime, writetime
filename.File::utime(readtime, writetime)

# 9.2. Deleting a File
File::delete(filename)

# 9.3. Copying or Moving a File
File::copy(oldfile, newfile)
File::rename(oldfile, newfile)
File::move(oldfile, newfile)

# 9.4. Recognizing Two Names for the Same File
seen = {}
files.each(f ->
    seen{File::stat(f){Device, Inode}}.push!(f)
)
seen.to_list.sort.each((dev,ino), l ->
    if l.size > 1 then
        # l is a list of filenames for the same file
)

# 9.5. Processing All Files in a Directory
Dir::open(dirname, Absolute).each(file ->
    # do something with "{file}"
)

# 9.6. Globbing, or Getting a List of Filenames Matching a Pattern
list = Dir::glob("*.c")

# 9.7. Processing All Files in a Directory Recursively
dirlist.each(dir ->
    Dir::open(dir, Recursive | Absolute | Depth_only).each(f ->
    )
)

# 9.8. Removing a Directory and Its Contents
Sys::args.each(dir ->
    Dir::open(dir, Recursive | Absolute | Depth_first).each(File::delete)
)

# 9.9. Renaming Files
names.each(file ->
    newname = compute_newname(file)
    File::rename(file, newname) or warn("Couldn't rename {file} to {newname}: {Sys::errno_string}\n")
)

# 9.10. Splitting a Filename into Its Component Parts
File::basename(path)
File::dirname(path)