<!-- canonical: https://efficientnewlanguage.org/eml-p/projects/P018-parking-lot/ | updated: 2026-10-03 -->

# P018 Parking lot

Run an eight-space parking lot: cars come in and go out at typed times, each exit gets a ticket that shows how its fee was worked out - free up to 10 minutes, 3.00 for the first hour, 1.00 for each half hour after, at most 15.00 per 24 hours - and each day has a report of its takings, from a text menu.

EML-P project `projects/parking-lot` in the EML language repo: 3 module(s), entry `main.eml`, terminal UI. There, `eml project run projects/parking-lot` runs it and `eml project verify projects/parking-lot` replays every session under CPython (two hash seeds) and in the interpreter; the site build replays every session in the interpreter again and publishes a session only if its screen matches.

Built on verified corpus cases: parking-lot-tracker (https://efficientnewlanguage.org/cases/032-parking-lot-tracker/).

## Sessions

### bad-input - interpreter: byte-equal to the golden

Input:

```text
7
3
5
2

2
A
TOO-LONG-PLATE
AB 12
ab-12
8:30
25:00
07:60
07:30
2
ab-12
3
ZZ-99
3
AB-12
07:00
07:40
2
C1
08:00
2
C2
08:00
2
C3
08:05
2
C4
08:10
2
C5
08:15
2
C6
08:20
2
C7
08:25
2
C8
08:30
2
1
4
3
C1
07:59
3
C2

6
```

Screen:

```text

== Parking lot == day 1, 00:00
1) spaces  2) car in  3) car out  4) next day  5) day report  6) quit
choice> 7
Pick a number from 1 to 6.

== Parking lot == day 1, 00:00
1) spaces  2) car in  3) car out  4) next day  5) day report  6) quit
choice> 3
No cars are parked.

== Parking lot == day 1, 00:00
1) spaces  2) car in  3) car out  4) next day  5) day report  6) quit
choice> 5

-- day 1 --
  no cars went out today
  0 cars out, takings 0.00; 0 still parked

== Parking lot == day 1, 00:00
1) spaces  2) car in  3) car out  4) next day  5) day report  6) quit
choice> 2
plate> 
Cancelled.

== Parking lot == day 1, 00:00
1) spaces  2) car in  3) car out  4) next day  5) day report  6) quit
choice> 2
plate> A
A plate has 2 to 10 characters.
plate> TOO-LONG-PLATE
A plate has 2 to 10 characters.
plate> AB 12
A plate has only letters, digits and dashes.
plate> ab-12
time (HH:MM)> 8:30
Type the time as HH:MM, like 08:30, or nothing to cancel.
time (HH:MM)> 25:00
Type the time as HH:MM, like 08:30, or nothing to cancel.
time (HH:MM)> 07:60
Type the time as HH:MM, like 08:30, or nothing to cancel.
time (HH:MM)> 07:30
AB-12 parks in space 1 at day 1 07:30.

== Parking lot == day 1, 07:30
1) spaces  2) car in  3) car out  4) next day  5) day report  6) quit
choice> 2
plate> ab-12
AB-12 is already parked in space 1.

== Parking lot == day 1, 07:30
1) spaces  2) car in  3) car out  4) next day  5) day report  6) quit
choice> 3
plate> ZZ-99
No car ZZ-99 is parked here.

== Parking lot == day 1, 07:30
1) spaces  2) car in  3) car out  4) next day  5) day report  6) quit
choice> 3
plate> AB-12
time (HH:MM)> 07:00
It is already 07:30 on day 1; times only move forward.
time (HH:MM)> 07:40

AB-12 leaves space 1.
  in    day 1 07:30
  out   day 1 07:40
  stay  10 min
  free: 10 minutes or less
  fee   0.00

== Parking lot == day 1, 07:40
1) spaces  2) car in  3) car out  4) next day  5) day report  6) quit
choice> 2
plate> C1
time (HH:MM)> 08:00
C1 parks in space 1 at day 1 08:00.

== Parking lot == day 1, 08:00
1) spaces  2) car in  3) car out  4) next day  5) day report  6) quit
choice> 2
plate> C2
time (HH:MM)> 08:00
C2 parks in space 2 at day 1 08:00.

== Parking lot == day 1, 08:00
1) spaces  2) car in  3) car out  4) next day  5) day report  6) quit
choice> 2
plate> C3
time (HH:MM)> 08:05
C3 parks in space 3 at day 1 08:05.

== Parking lot == day 1, 08:05
1) spaces  2) car in  3) car out  4) next day  5) day report  6) quit
choice> 2
plate> C4
time (HH:MM)> 08:10
C4 parks in space 4 at day 1 08:10.

== Parking lot == day 1, 08:10
1) spaces  2) car in  3) car out  4) next day  5) day report  6) quit
choice> 2
plate> C5
time (HH:MM)> 08:15
C5 parks in space 5 at day 1 08:15.

== Parking lot == day 1, 08:15
1) spaces  2) car in  3) car out  4) next day  5) day report  6) quit
choice> 2
plate> C6
time (HH:MM)> 08:20
C6 parks in space 6 at day 1 08:20.

== Parking lot == day 1, 08:20
1) spaces  2) car in  3) car out  4) next day  5) day report  6) quit
choice> 2
plate> C7
time (HH:MM)> 08:25
C7 parks in space 7 at day 1 08:25.

== Parking lot == day 1, 08:25
1) spaces  2) car in  3) car out  4) next day  5) day report  6) quit
choice> 2
plate> C8
time (HH:MM)> 08:30
C8 parks in space 8 at day 1 08:30.

== Parking lot == day 1, 08:30
1) spaces  2) car in  3) car out  4) next day  5) day report  6) quit
choice> 2
The lot is full.

== Parking lot == day 1, 08:30
1) spaces  2) car in  3) car out  4) next day  5) day report  6) quit
choice> 1

-- spaces: 8 of 8 taken --
   1  C1          in since day 1 08:00
   2  C2          in since day 1 08:00
   3  C3          in since day 1 08:05
   4  C4          in since day 1 08:10
   5  C5          in since day 1 08:15
   6  C6          in since day 1 08:20
   7  C7          in since day 1 08:25
   8  C8          in since day 1 08:30

== Parking lot == day 1, 08:30
1) spaces  2) car in  3) car out  4) next day  5) day report  6) quit
choice> 4
Day 2 begins; it is 00:00. 8 cars still parked.

== Parking lot == day 2, 00:00
1) spaces  2) car in  3) car out  4) next day  5) day report  6) quit
choice> 3
plate> C1
time (HH:MM)> 07:59

C1 leaves space 1.
  in    day 1 08:00
  out   day 2 07:59
  stay  23 h 59 min
  first hour 3.00, then 46 half hours at 1.00: 46.00 - 15.00 at most: 15.00
  fee   15.00

== Parking lot == day 2, 07:59
1) spaces  2) car in  3) car out  4) next day  5) day report  6) quit
choice> 3
plate> C2
time (HH:MM)> 
Cancelled.

== Parking lot == day 2, 07:59
1) spaces  2) car in  3) car out  4) next day  5) day report  6) quit
choice> 6
Bye.
```

### basic - interpreter: byte-equal to the golden

Input:

```text
1
2
ABC-123
08:30
2
XYZ-789
08:45
3
XYZ-789
08:52
2
KLM-456
09:00
3
ABC-123
11:05
2
def-321
12:00
1
3
KLM-456
21:40
5
4
3
DEF-321
14:30
5
6
```

Screen:

```text

== Parking lot == day 1, 00:00
1) spaces  2) car in  3) car out  4) next day  5) day report  6) quit
choice> 1

-- spaces: 0 of 8 taken --
   1  free
   2  free
   3  free
   4  free
   5  free
   6  free
   7  free
   8  free

== Parking lot == day 1, 00:00
1) spaces  2) car in  3) car out  4) next day  5) day report  6) quit
choice> 2
plate> ABC-123
time (HH:MM)> 08:30
ABC-123 parks in space 1 at day 1 08:30.

== Parking lot == day 1, 08:30
1) spaces  2) car in  3) car out  4) next day  5) day report  6) quit
choice> 2
plate> XYZ-789
time (HH:MM)> 08:45
XYZ-789 parks in space 2 at day 1 08:45.

== Parking lot == day 1, 08:45
1) spaces  2) car in  3) car out  4) next day  5) day report  6) quit
choice> 3
plate> XYZ-789
time (HH:MM)> 08:52

XYZ-789 leaves space 2.
  in    day 1 08:45
  out   day 1 08:52
  stay  7 min
  free: 10 minutes or less
  fee   0.00

== Parking lot == day 1, 08:52
1) spaces  2) car in  3) car out  4) next day  5) day report  6) quit
choice> 2
plate> KLM-456
time (HH:MM)> 09:00
KLM-456 parks in space 2 at day 1 09:00.

== Parking lot == day 1, 09:00
1) spaces  2) car in  3) car out  4) next day  5) day report  6) quit
choice> 3
plate> ABC-123
time (HH:MM)> 11:05

ABC-123 leaves space 1.
  in    day 1 08:30
  out   day 1 11:05
  stay  2 h 35 min
  first hour 3.00, then 4 half hours at 1.00: 4.00
  fee   7.00

== Parking lot == day 1, 11:05
1) spaces  2) car in  3) car out  4) next day  5) day report  6) quit
choice> 2
plate> def-321
time (HH:MM)> 12:00
DEF-321 parks in space 1 at day 1 12:00.

== Parking lot == day 1, 12:00
1) spaces  2) car in  3) car out  4) next day  5) day report  6) quit
choice> 1

-- spaces: 2 of 8 taken --
   1  DEF-321     in since day 1 12:00
   2  KLM-456     in since day 1 09:00
   3  free
   4  free
   5  free
   6  free
   7  free
   8  free

== Parking lot == day 1, 12:00
1) spaces  2) car in  3) car out  4) next day  5) day report  6) quit
choice> 3
plate> KLM-456
time (HH:MM)> 21:40

KLM-456 leaves space 2.
  in    day 1 09:00
  out   day 1 21:40
  stay  12 h 40 min
  first hour 3.00, then 24 half hours at 1.00: 24.00 - 15.00 at most: 15.00
  fee   15.00

== Parking lot == day 1, 21:40
1) spaces  2) car in  3) car out  4) next day  5) day report  6) quit
choice> 5

-- day 1 --
  XYZ-789     day 1 08:45 - 08:52             0.00
  ABC-123     day 1 08:30 - 11:05             7.00
  KLM-456     day 1 09:00 - 21:40            15.00
  3 cars out, takings 22.00; 1 still parked

== Parking lot == day 1, 21:40
1) spaces  2) car in  3) car out  4) next day  5) day report  6) quit
choice> 4
Day 2 begins; it is 00:00. 1 car still parked.

== Parking lot == day 2, 00:00
1) spaces  2) car in  3) car out  4) next day  5) day report  6) quit
choice> 3
plate> DEF-321
time (HH:MM)> 14:30

DEF-321 leaves space 1.
  in    day 1 12:00
  out   day 2 14:30
  stay  1 day 2 h 30 min
  1 full day at 15.00: 15.00
  first hour 3.00, then 3 half hours at 1.00: 3.00
  fee   21.00

== Parking lot == day 2, 14:30
1) spaces  2) car in  3) car out  4) next day  5) day report  6) quit
choice> 5

-- day 2 --
  DEF-321     day 1 12:00 - day 2 14:30      21.00
  1 car out, takings 21.00; 0 still parked

== Parking lot == day 2, 14:30
1) spaces  2) car in  3) car out  4) next day  5) day report  6) quit
choice> 6
Bye.
```

## Modules

### main.eml

```eml
# P018 parking lot: an eight-space lot. Cars come in and go out at times the
# attendant types; the clock only moves forward, and "next day" starts the
# following day at 00:00, so a car can stay overnight or longer. Each exit
# gets a ticket showing how its fee was worked out, and each day has a report
# of its takings. A time is kept as minutes since day 1, 00:00.
import fees
import text

8 => space_count

class ParkingLot:
    # Spaces numbered from 1; each holds None or [plate, time in].
    def __init__(self, size):
        [] => self.spaces
        for i in [1:size]:
            self.spaces + [None] => self.spaces

    def free_space(self):
        # The lowest free space number, or 0 if the lot is full.
        for i in [0:len(self.spaces) - 1]:
            if self.spaces[i] == None:
                return i + 1
        return 0

    def find(self, plate):
        # The space number of plate, or 0 if it is not parked here.
        for i in [0:len(self.spaces) - 1]:
            if self.spaces[i] != None and self.spaces[i][0] == plate:
                return i + 1
        return 0

    def taken(self):
        0 => n
        for s in self.spaces:
            if s != None:
                n + 1 => n
        return n

    def park(self, plate, t):
        self.free_space() => n
        if n == 0:
            raise ValueError("the lot is full")
        [plate, t] => self.spaces[n - 1]
        return n

    def leave(self, n):
        self.spaces[n - 1] => car
        None => self.spaces[n - 1]
        return car

def day_of(t):
    return int((t - t % 1440) / 1440) + 1

def stamp(t):
    return "day " + str(day_of(t)) + " " + text.clock(t % 1440)

def ask_plate():
    # A plate in capitals, asked again until a good one is typed; "" cancels.
    while True:
        text.upper(text.trim(input("plate> "))) => plate
        if plate == "":
            return ""
        True => good
        for c in plate:
            if not (c in "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-"):
                False => good
        if len(plate) < 2 or len(plate) > 10:
            "A plate has 2 to 10 characters." ^0
        elif not good:
            "A plate has only letters, digits and dashes." ^0
        else:
            return plate

def ask_time(now):
    # A time today, not before now, as minutes since day 1; -1 cancels.
    while True:
        text.trim(input("time (HH:MM)> ")) => answer
        if answer == "":
            return 0 - 1
        text.parse_time(answer) => m
        (day_of(now) - 1) * 1440 + m => t
        if m == 0 - 1:
            "Type the time as HH:MM, like 08:30, or nothing to cancel." ^0
        elif t < now:
            ("It is already " + text.clock(now % 1440) + " on day " + str(day_of(now)) + "; times only move forward.") ^0
        else:
            return t

ParkingLot(space_count) => lot
0 => now
[] => log
True => running
while running:
    "" ^0
    ("== Parking lot == day " + str(day_of(now)) + ", " + text.clock(now % 1440)) ^0
    "1) spaces  2) car in  3) car out  4) next day  5) day report  6) quit" ^0
    text.trim(input("choice> ")) => choice
    if choice == "1":
        "" ^0
        ("-- spaces: " + str(lot.taken()) + " of " + str(space_count) + " taken --") ^0
        for i in [0:space_count - 1]:
            lot.spaces[i] => car
            if car == None:
                ("  " + ("%2d" % (i + 1)) + "  free") ^0
            else:
                ("  " + ("%2d" % (i + 1)) + "  " + ("%-12s" % car[0]) + "in since " + stamp(car[1])) ^0
    elif choice == "2":
        if lot.free_space() == 0:
            "The lot is full." ^0
        else:
            ask_plate() => plate
            0 - 1 => t
            if plate != "" and lot.find(plate) > 0:
                (plate + " is already parked in space " + str(lot.find(plate)) + ".") ^0
            else:
                if plate != "":
                    ask_time(now) => t
                if t == 0 - 1:
                    "Cancelled." ^0
                else:
                    t => now
                    lot.park(plate, t) => n
                    (plate + " parks in space " + str(n) + " at " + stamp(t) + ".") ^0
    elif choice == "3":
        if lot.taken() == 0:
            "No cars are parked." ^0
        else:
            ask_plate() => plate
            0 - 1 => t
            if plate != "" and lot.find(plate) == 0:
                ("No car " + plate + " is parked here.") ^0
            else:
                if plate != "":
                    ask_time(now) => t
                if t == 0 - 1:
                    "Cancelled." ^0
                else:
                    t => now
                    lot.find(plate) => n
                    lot.leave(n) => car
                    t - car[1] => minutes
                    fees.fee(minutes) => cents
                    log + [[plate, n, car[1], t, cents]] => log
                    "" ^0
                    (plate + " leaves space " + str(n) + ".") ^0
                    ("  in    " + stamp(car[1])) ^0
                    ("  out   " + stamp(t)) ^0
                    ("  stay  " + text.duration(minutes)) ^0
                    for line in fees.explain(minutes):
                        ("  " + line) ^0
                    ("  fee   " + text.money(cents)) ^0
    elif choice == "4":
        day_of(now) * 1440 => now
        ("Day " + str(day_of(now)) + " begins; it is 00:00. " + text.count(lot.taken(), "car") + " still parked.") ^0
    elif choice == "5":
        0 => takings
        "" ^0
        ("-- day " + str(day_of(now)) + " --") ^0
        0 => outs
        for e in log:
            if day_of(e[3]) == day_of(now):
                outs + 1 => outs
                takings + e[4] => takings
                text.clock(e[3] % 1440) => out_time
                if day_of(e[2]) != day_of(e[3]):
                    stamp(e[3]) => out_time
                ("  " + ("%-12s" % e[0]) + ("%-28s" % (stamp(e[2]) + " - " + out_time)) + ("%8s" % text.money(e[4]))) ^0
        if outs == 0:
            "  no cars went out today" ^0
        ("  " + text.count(outs, "car") + " out, takings " + text.money(takings) + "; " + str(lot.taken()) + " still parked") ^0
    elif choice == "6":
        False => running
    else:
        "Pick a number from 1 to 6." ^0
"Bye." ^0
```

Python projection of main.eml:

```python
import fees
import text
space_count = 8

class ParkingLot:
    def __init__(self, size):
        self.spaces = []
        for i in range(1, size+1):
            self.spaces = self.spaces + [None]
    def free_space(self):
        for i in range(0, len(self.spaces)):
            if self.spaces[i] == None:
                return i + 1
        return 0
    def find(self, plate):
        for i in range(0, len(self.spaces)):
            if self.spaces[i] != None and self.spaces[i][0] == plate:
                return i + 1
        return 0
    def taken(self):
        n = 0
        for s in self.spaces:
            if s != None:
                n = n + 1
        return n
    def park(self, plate, t):
        n = self.free_space()
        if n == 0:
            raise ValueError("the lot is full")
        self.spaces[n - 1] = [plate, t]
        return n
    def leave(self, n):
        car = self.spaces[n - 1]
        self.spaces[n - 1] = None
        return car

def day_of(t):
    return int((t - t % 1440) / 1440) + 1

def stamp(t):
    return "day " + str(day_of(t)) + " " + text.clock(t % 1440)

def ask_plate():
    while True:
        plate = text.upper(text.trim(input("plate> ")))
        if plate == "":
            return ""
        good = True
        for c in plate:
            if not c in "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-":
                good = False
        if len(plate) < 2 or len(plate) > 10:
            print("A plate has 2 to 10 characters.")
        elif not good:
            print("A plate has only letters, digits and dashes.")
        else:
            return plate

def ask_time(now):
    while True:
        answer = text.trim(input("time (HH:MM)> "))
        if answer == "":
            return 0 - 1
        m = text.parse_time(answer)
        t = (day_of(now) - 1) * 1440 + m
        if m == 0 - 1:
            print("Type the time as HH:MM, like 08:30, or nothing to cancel.")
        elif t < now:
            print("It is already " + text.clock(now % 1440) + " on day " + str(day_of(now)) + "; times only move forward.")
        else:
            return t

lot = ParkingLot(space_count)
now = 0
log = []
running = True
while running:
    print("")
    print("== Parking lot == day " + str(day_of(now)) + ", " + text.clock(now % 1440))
    print("1) spaces  2) car in  3) car out  4) next day  5) day report  6) quit")
    choice = text.trim(input("choice> "))
    if choice == "1":
        print("")
        print("-- spaces: " + str(lot.taken()) + " of " + str(space_count) + " taken --")
        for i in range(0, space_count):
            car = lot.spaces[i]
            if car == None:
                print("  " + "%2d" % (i + 1) + "  free")
            else:
                print("  " + "%2d" % (i + 1) + "  " + "%-12s" % car[0] + "in since " + stamp(car[1]))
    elif choice == "2":
        if lot.free_space() == 0:
            print("The lot is full.")
        else:
            plate = ask_plate()
            t = 0 - 1
            if plate != "" and lot.find(plate) > 0:
                print(plate + " is already parked in space " + str(lot.find(plate)) + ".")
            else:
                if plate != "":
                    t = ask_time(now)
                if t == 0 - 1:
                    print("Cancelled.")
                else:
                    now = t
                    n = lot.park(plate, t)
                    print(plate + " parks in space " + str(n) + " at " + stamp(t) + ".")
    elif choice == "3":
        if lot.taken() == 0:
            print("No cars are parked.")
        else:
            plate = ask_plate()
            t = 0 - 1
            if plate != "" and lot.find(plate) == 0:
                print("No car " + plate + " is parked here.")
            else:
                if plate != "":
                    t = ask_time(now)
                if t == 0 - 1:
                    print("Cancelled.")
                else:
                    now = t
                    n = lot.find(plate)
                    car = lot.leave(n)
                    minutes = t - car[1]
                    cents = fees.fee(minutes)
                    log = log + [[plate, n, car[1], t, cents]]
                    print("")
                    print(plate + " leaves space " + str(n) + ".")
                    print("  in    " + stamp(car[1]))
                    print("  out   " + stamp(t))
                    print("  stay  " + text.duration(minutes))
                    for line in fees.explain(minutes):
                        print("  " + line)
                    print("  fee   " + text.money(cents))
    elif choice == "4":
        now = day_of(now) * 1440
        print("Day " + str(day_of(now)) + " begins; it is 00:00. " + text.count(lot.taken(), "car") + " still parked.")
    elif choice == "5":
        takings = 0
        print("")
        print("-- day " + str(day_of(now)) + " --")
        outs = 0
        for e in log:
            if day_of(e[3]) == day_of(now):
                outs = outs + 1
                takings = takings + e[4]
                out_time = text.clock(e[3] % 1440)
                if day_of(e[2]) != day_of(e[3]):
                    out_time = stamp(e[3])
                print("  " + "%-12s" % e[0] + "%-28s" % (stamp(e[2]) + " - " + out_time) + "%8s" % text.money(e[4]))
        if outs == 0:
            print("  no cars went out today")
        print("  " + text.count(outs, "car") + " out, takings " + text.money(takings) + "; " + str(lot.taken()) + " still parked")
    elif choice == "6":
        running = False
    else:
        print("Pick a number from 1 to 6.")
print("Bye.")
```

### fees.eml

```eml
# P018 parking lot - the fee rules. A stay is a whole number of minutes and
# money is whole cents. Up to 10 minutes is free; otherwise every full 24
# hours costs the day's maximum, and what is left costs 3.00 for the first
# hour (or part of it) and 1.00 for each half hour (or part of one) after
# that, but never more than the day's maximum.
import text

10 => grace_minutes
300 => first_hour
100 => per_half_hour
1500 => day_cap
1440 => day_minutes

def part_of_day(minutes):
    # The fee for less than 24 hours: [cents, half hours after the first
    # hour, whether the day's maximum was reached].
    if minutes == 0:
        return [0, 0, False]
    0 => halves
    if minutes > 60:
        minutes - 60 => after
        int((after + 29 - (after + 29) % 30) / 30) => halves
    first_hour + halves * per_half_hour => cents
    if cents > day_cap:
        return [day_cap, halves, True]
    return [cents, halves, False]

def fee(minutes):
    # The fee for a stay of minutes, in cents.
    if minutes <= grace_minutes:
        return 0
    int((minutes - minutes % day_minutes) / day_minutes) => days
    return days * day_cap + part_of_day(minutes % day_minutes)[0]

def explain(minutes):
    # The lines that show how fee(minutes) was worked out.
    if minutes <= grace_minutes:
        return ["free: " + str(grace_minutes) + " minutes or less"]
    [] => out
    int((minutes - minutes % day_minutes) / day_minutes) => days
    if days > 0:
        out + [text.count(days, "full day") + " at " + text.money(day_cap) + ": " + text.money(days * day_cap)] => out
    part_of_day(minutes % day_minutes) => p
    if minutes % day_minutes > 0:
        "first hour " + text.money(first_hour) => line
        if p[1] > 0:
            line + ", then " + text.count(p[1], "half hour") + " at " + text.money(per_half_hour) + ": " + text.money(p[1] * per_half_hour) => line
        if p[2]:
            line + " - " + text.money(day_cap) + " at most: " + text.money(day_cap) => line
        out + [line] => out
    return out
```

Python projection of fees.eml:

```python
import text
grace_minutes = 10
first_hour = 300
per_half_hour = 100
day_cap = 1500
day_minutes = 1440

def part_of_day(minutes):
    if minutes == 0:
        return [0, 0, False]
    halves = 0
    if minutes > 60:
        after = minutes - 60
        halves = int((after + 29 - (after + 29) % 30) / 30)
    cents = first_hour + halves * per_half_hour
    if cents > day_cap:
        return [day_cap, halves, True]
    return [cents, halves, False]

def fee(minutes):
    if minutes <= grace_minutes:
        return 0
    days = int((minutes - minutes % day_minutes) / day_minutes)
    return days * day_cap + part_of_day(minutes % day_minutes)[0]

def explain(minutes):
    if minutes <= grace_minutes:
        return ["free: " + str(grace_minutes) + " minutes or less"]
    out = []
    days = int((minutes - minutes % day_minutes) / day_minutes)
    if days > 0:
        out = out + [text.count(days, "full day") + " at " + text.money(day_cap) + ": " + text.money(days * day_cap)]
    p = part_of_day(minutes % day_minutes)
    if minutes % day_minutes > 0:
        line = "first hour " + text.money(first_hour)
        if p[1] > 0:
            line = line + ", then " + text.count(p[1], "half hour") + " at " + text.money(per_half_hour) + ": " + text.money(p[1] * per_half_hour)
        if p[2]:
            line = line + " - " + text.money(day_cap) + " at most: " + text.money(day_cap)
        out = out + [line]
    return out
```

### text.eml

```eml
# P018 parking lot - reading what is typed and writing times and money. The
# interpreter that checks every session does not run string methods yet, so
# the text handling is written out here.

def trim(s):
    # s without the spaces at either end.
    0 => i
    len(s) => j
    while i < j and s[i] == " ":
        i + 1 => i
    while j > i and s[j - 1] == " ":
        j - 1 => j
    return s[i:j]

def upper(s):
    # s with a-z turned into A-Z; everything else as it was.
    "abcdefghijklmnopqrstuvwxyz" => lower_letters
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ" => upper_letters
    "" => out
    for c in s:
        c => d
        for i in [0:25]:
            if lower_letters[i] == c:
                upper_letters[i] => d
        out + d => out
    return out

def clock(t):
    # Minutes since midnight as HH:MM.
    return ("%02d" % int((t - t % 60) / 60)) + ":" + ("%02d" % (t % 60))

def parse_time(s):
    # Minutes since midnight for HH:MM (00:00 to 23:59), or -1.
    if len(s) != 5 or s[2] != ":":
        return 0 - 1
    for i in [0, 1, 3, 4]:
        if not (s[i] in "0123456789"):
            return 0 - 1
    int(s[0:2]) => h
    int(s[3:5]) => m
    if h > 23 or m > 59:
        return 0 - 1
    return h * 60 + m

def duration(minutes):
    # A stay as "2 h 35 min", "45 min" or "1 day 2 h 30 min".
    int((minutes - minutes % 1440) / 1440) => days
    minutes % 1440 => rest
    int((rest - rest % 60) / 60) => hours
    rest % 60 => mins
    "" => out
    if days > 0:
        count(days, "day") + " " => out
    if hours > 0 or days > 0:
        out + str(hours) + " h " => out
    return out + str(mins) + " min"

def count(n, word):
    if n == 1:
        return "1 " + word
    return str(n) + " " + word + "s"

def money(c):
    # Whole cents as 12.50.
    str(c) => s
    while len(s) < 3:
        "0" + s => s
    return s[0:len(s) - 2] + "." + s[len(s) - 2:len(s)]
```

Python projection of text.eml:

```python
def trim(s):
    i = 0
    j = len(s)
    while i < j and s[i] == " ":
        i = i + 1
    while j > i and s[j - 1] == " ":
        j = j - 1
    return s[i:j]

def upper(s):
    lower_letters = "abcdefghijklmnopqrstuvwxyz"
    upper_letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    out = ""
    for c in s:
        d = c
        for i in range(0, 26):
            if lower_letters[i] == c:
                d = upper_letters[i]
        out = out + d
    return out

def clock(t):
    return "%02d" % int((t - t % 60) / 60) + ":" + "%02d" % (t % 60)

def parse_time(s):
    if len(s) != 5 or s[2] != ":":
        return 0 - 1
    for i in [0, 1, 3, 4]:
        if not s[i] in "0123456789":
            return 0 - 1
    h = int(s[0:2])
    m = int(s[3:5])
    if h > 23 or m > 59:
        return 0 - 1
    return h * 60 + m

def duration(minutes):
    days = int((minutes - minutes % 1440) / 1440)
    rest = minutes % 1440
    hours = int((rest - rest % 60) / 60)
    mins = rest % 60
    out = ""
    if days > 0:
        out = count(days, "day") + " "
    if hours > 0 or days > 0:
        out = out + str(hours) + " h "
    return out + str(mins) + " min"

def count(n, word):
    if n == 1:
        return "1 " + word
    return str(n) + " " + word + "s"

def money(c):
    s = str(c)
    while len(s) < 3:
        s = "0" + s
    return s[0:len(s) - 2] + "." + s[len(s) - 2:len(s)]
```

## README

# P018 - Parking lot

An eight-space parking lot run from a text menu. Cars come in and go out at
times the attendant types; each car takes the lowest free space. Each exit
gets a ticket that shows the stay and how its fee was worked out, and each day
has a report of the cars that went out and the takings.

- `main.eml` - the menu and its questions, the lot itself (a class with its
  spaces), the tickets and the day report
- `fees.eml` - the fee rules and the lines that explain a fee
- `text.eml` - trimming, capitals, reading and writing times, stays, counts
  and money

There is no clock: a time is typed as HH:MM and may not be earlier than the
last time typed that day, and "next day" starts the following day at 00:00,
so a car can stay overnight or for days. A time is kept as minutes since day
1, 00:00, so a stay is a subtraction.

The fee rules: a stay of 10 minutes or less is free. Otherwise every full 24
hours costs the day's maximum, 15.00, and what is left costs 3.00 for the
first hour (or part of it) and 1.00 for each half hour (or part of one)
after that - but never more than 15.00. So 2 h 35 min costs 7.00, 12 h 40 min
reaches the 15.00 maximum, and from 12:00 one day to 14:30 the next costs
15.00 for the full day and 6.00 for the 2 h 30 min after it. Money is whole
cents throughout.

What is checked: a plate has 2 to 10 letters, digits or dashes (typed in any
case, kept in capitals) and is not already parked; a car going out must be
parked; a time is HH:MM from 00:00 to 23:59 and does not go back; a car cannot
come in when the lot is full. Anything else asks again; nothing cancels.

Sessions: `sessions/basic.in` parks four cars and takes them out - one free
after 7 minutes, one for 7.00, one that reaches the day's maximum - shows the
spaces and the day's report, then lets one car stay overnight into day 2 and
goes out at 21.00; `sessions/bad-input.in` takes a car out of an empty lot,
types plates that are empty, too short, too long or have a space, times in the
wrong shape, out of range or earlier than the clock, parks a car twice, takes
out a car that is not there, stays exactly 10 minutes (free), fills all eight
spaces, tries a ninth car, and keeps one car until 07:59 the next day - 23 h
59 min, which stops at the 15.00 maximum.

Built on the verified corpus case `parking-lot-tracker` (a lot as a class
whose spaces are taken from the lowest free one, refusing a car when full).
