Project P115

Thermostat

Three rooms with their own settings over factory defaults that they read live, heated minute by minute through a day with a band around the target, hour by hour, and the band's trade-off between switching the heater and holding the temperature.

2 modules · 2 recorded sessionstext-menu UI in the terminalupdated 2026-10-16

Every screen below was recorded under CPython. When this page was built, the EML interpreter replayed each session from the same input and printed the same bytes.

About

Three rooms heated through a day. Each room has its own settings over the factory defaults, and a band around the target keeps the heater from switching every minute.

  • main.eml - the menu, the rooms table, setting a room or the factory, a day, one room hour by hour, and other bands
  • heat.eml - the factory defaults, the rooms, the target for a minute, and the day minute by minute

How each part works:

  • The factory defaults are on a class: a day target of 20.0 degrees, a night target of 17.0, a band of 0.5, and the day from 06:00 to 22:00. A room keeps only its own settings. As in the corpus case thermostat-class-defaults: - reading a setting falls back to the factory at the moment of asking; - changing the factory moves at once every room without its own value; - setting one room's value moves no other room, and not the factory.
  • Temperatures are whole tenths of a degree, and time runs in minutes. A heating room warms by 0.2 degrees a minute, and a room warmer than outside (5.0) cools by 0.1. The heater comes on at target - band and goes off at target + band.
  • The band's trade-off: within the day, from the moment the room first comes within the band, the program records the lowest and highest temperature. A band of 0 switches the heater on at least 9 times as often as a band of 1.0, and holds the temperature closer. Bands less than 0.3 apart can come out the other way by a little, since the heater warms in steps of 0.2.

What is checked: menu choices 1 to 7; one of the three rooms; one of the five settings; a temperature from 5.0 to 30.0, a band from 0 to 3.0, a time as hh:mm; a day that starts before it ends, for every room a factory change would move. An empty answer cancels; factory as a value makes a room follow the factory again.

Sessions: sessions/basic.in gives: - the rooms (the bedroom with its own night, the nursery with its own day and band) and a day; - the living room hour by hour, and with bands 0, 0.2, 0.5 and 1.0: 463 switches down to 46; - the factory day lowered to 19.0, which the living room and the bedroom follow and the nursery does not; - the living room's own night of 15.5, the nursery back to the factory day, and the day again.

sessions/bad-input.in gives: - menu choices 0 and x; - settings: a cancel, a kitchen, a cancel, an unknown setting, a cancel, 35, 21.55, a band of 4, the times 6:30 and 24:00, and a morning after the evening; - the factory: a cancel, a cancel, abc, and an evening before the morning; - a cancel for hour by hour, and a kitchen for the bands.

Built on the verified corpus case thermostat-class-defaults (a default on the class, an override on the instance, and a live fallback between them).

Recorded sessions

What the screen shows while someone uses the program. Each typed line appears after its prompt, the way a terminal shows it.

bad-input

interpreter: byte-equal
== Thermostat ==
Three rooms over the factory settings, each with its own changes, heated through a day.

1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit
choice> 0
Pick a number from 1 to 7.

1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit
choice> x
Pick a number from 1 to 7.

1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit
choice> 2
room (living, bedroom or nursery)> 
Cancelled.

1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit
choice> 2
room (living, bedroom or nursery)> kitchen
Type living, bedroom or nursery.

1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit
choice> 2
room (living, bedroom or nursery)> living
setting (day, night, band, morning or evening)> 
Cancelled.

1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit
choice> 2
room (living, bedroom or nursery)> living
setting (day, night, band, morning or evening)> colour
Type day, night, band, morning or evening.

1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit
choice> 2
room (living, bedroom or nursery)> living
setting (day, night, band, morning or evening)> day
value, or factory to follow the factory again> 
Cancelled.

1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit
choice> 2
room (living, bedroom or nursery)> living
setting (day, night, band, morning or evening)> day
value, or factory to follow the factory again> 35
Type a temperature from 5.0 to 30.0 degrees.

1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit
choice> 2
room (living, bedroom or nursery)> living
setting (day, night, band, morning or evening)> day
value, or factory to follow the factory again> 21.55
Type a temperature from 5.0 to 30.0 degrees.

1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit
choice> 2
room (living, bedroom or nursery)> living
setting (day, night, band, morning or evening)> band
value, or factory to follow the factory again> 4
Type a band from 0 to 3.0 degrees.

1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit
choice> 2
room (living, bedroom or nursery)> living
setting (day, night, band, morning or evening)> morning
value, or factory to follow the factory again> 6:30
Type a time such as 06:30.

1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit
choice> 2
room (living, bedroom or nursery)> living
setting (day, night, band, morning or evening)> morning
value, or factory to follow the factory again> 24:00
Type a time such as 06:30.

1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit
choice> 2
room (living, bedroom or nursery)> living
setting (day, night, band, morning or evening)> morning
value, or factory to follow the factory again> 22:30
The day has to start before it ends.

1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit
choice> 3
setting (day, night, band, morning or evening)> 
Cancelled.

1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit
choice> 3
setting (day, night, band, morning or evening)> day
factory value> 
Cancelled.

1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit
choice> 3
setting (day, night, band, morning or evening)> day
factory value> abc
Type a temperature from 5.0 to 30.0 degrees.

1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit
choice> 3
setting (day, night, band, morning or evening)> evening
factory value> 05:00
The day has to start before it ends, in living too.

1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit
choice> 5
room (living, bedroom or nursery)> 
Cancelled.

1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit
choice> 6
room (living, bedroom or nursery)> kitchen
Type living, bedroom or nursery.

1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit
choice> 7
Bye.
What was typed (56 lines)
0
x
2

2
kitchen
2
living

2
living
colour
2
living
day

2
living
day
35
2
living
day
21.55
2
living
band
4
2
living
morning
6:30
2
living
morning
24:00
2
living
morning
22:30
3

3
day

3
day
abc
3
evening
05:00
5

6
kitchen
7

basic

interpreter: byte-equal
== Thermostat ==
Three rooms over the factory settings, each with its own changes, heated through a day.

1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit
choice> 1
  room       day           night         band          morning        evening
  living     20.0 factory  17.0 factory  0.5 factory   06:00 factory 22:00 factory
  bedroom    20.0 factory  16.0 own      0.5 factory   06:00 factory 22:00 factory
  nursery    22.0 own      17.0 factory  0.3 own       06:00 factory 22:00 factory
Factory: day 20.0, night 17.0, band 0.5, day from 06:00 to 22:00.

1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit
choice> 4
A day from 00:00, each room starting at 18.0 degrees with 5.0 outside:
  room       heating   switched on   lowest   highest
  living      7h 55m            92     16.5      20.5
  bedroom     7h 55m            91     15.5      20.5
  nursery     7h 57m           152     16.7      22.3

1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit
choice> 5
room (living, bedroom or nursery)> living
living, hour by hour (the temperature on the hour, minutes heating in the hour):
 00:00  18.0  15 01:00  16.5  20 02:00  16.5  20 03:00  16.5  20
 04:00  16.5  20 05:00  16.5  20 06:00  16.5  30 07:00  19.5  20
 08:00  19.5  20 09:00  19.5  20 10:00  19.5  20 11:00  19.5  20
 12:00  19.5  20 13:00  19.5  20 14:00  19.5  20 15:00  19.5  20
 16:00  19.5  20 17:00  19.5  20 18:00  19.5  20 19:00  19.5  20
 20:00  19.5  20 21:00  19.5  20 22:00  19.5  10 23:00  16.5  20

1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit
choice> 6
room (living, bedroom or nursery)> living
living over a day with other bands:
  band   switched on   heating   lowest   highest
   0.0           463    7h 57m     20.0      20.2
   0.2           231    7h 57m     19.8      20.2
   0.5            92    7h 55m     19.5      20.5
   1.0            46    7h 55m     19.0      21.0
The lowest and highest are in the day, from the moment the room first comes within the band. A narrow band holds the temperature close to the target and switches the heater far more often; a wide one switches less and lets it swing. Bands less than 0.3 degrees apart can come out the other way by a little, since the heater warms in steps of 0.2 degrees.

1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit
choice> 3
setting (day, night, band, morning or evening)> day
factory value> 19.0
Factory day is now 19.0.
  following it at once: living, bedroom; keeping their own: nursery (22.0).

1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit
choice> 1
  room       day           night         band          morning        evening
  living     19.0 factory  17.0 factory  0.5 factory   06:00 factory 22:00 factory
  bedroom    19.0 factory  16.0 own      0.5 factory   06:00 factory 22:00 factory
  nursery    22.0 own      17.0 factory  0.3 own       06:00 factory 22:00 factory
Factory: day 19.0, night 17.0, band 0.5, day from 06:00 to 22:00.

1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit
choice> 2
room (living, bedroom or nursery)> living
setting (day, night, band, morning or evening)> night
value, or factory to follow the factory again> 15.5
living night is now its own: 15.5. The other rooms are as they were.

1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit
choice> 2
room (living, bedroom or nursery)> nursery
setting (day, night, band, morning or evening)> day
value, or factory to follow the factory again> factory
nursery day follows the factory again: 19.0.

1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit
choice> 1
  room       day           night         band          morning        evening
  living     19.0 factory  15.5 own      0.5 factory   06:00 factory 22:00 factory
  bedroom    19.0 factory  16.0 own      0.5 factory   06:00 factory 22:00 factory
  nursery    19.0 factory  17.0 factory  0.3 own       06:00 factory 22:00 factory
Factory: day 19.0, night 17.0, band 0.5, day from 06:00 to 22:00.

1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit
choice> 4
A day from 00:00, each room starting at 18.0 degrees with 5.0 outside:
  room       heating   switched on   lowest   highest
  living      7h 53m            91     15.0      19.6
  bedroom     7h 55m            92     15.5      19.5
  nursery     7h 56m           156     16.7      19.3

1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit
choice> 7
Bye.
What was typed (21 lines)
1
4
5
living
6
living
3
day
19.0
1
2
living
night
15.5
2
nursery
day
factory
1
4
7

Modules

The program as written, entry module first. Each module transpiles to its own Python file, which is what eml project run executes.

main.eml(entry)

eml
# P115 thermostat: rooms with their own settings over factory defaults, a
# day minute by minute, one room hour by hour, and the band that keeps the
# heater from switching every minute.
import heat

def plain(s):
    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 pad(s, n):
    while len(s) < n:
        s + " " => s
    return s

def lpad(s, n):
    while len(s) < n:
        " " + s => s
    return s

def keys():
    return ["day", "night", "band", "morning", "evening"]

def shown(key, v):
    if key == "morning" or key == "evening":
        return heat.clock(v)
    return heat.tenths(v)

def parse_tenths(t):
    # "21.5" or "18" -> 215 or 180, or -1
    "" => whole
    "" => frac
    False => dot
    for c in t:
        if c == "." and not dot:
            True => dot
        elif c >= "0" and c <= "9":
            if dot:
                frac + c => frac
            else:
                whole + c => whole
        else:
            return 0 - 1
    if whole == "" or len(whole) > 2 or len(frac) > 1 or (dot and frac == ""):
        return 0 - 1
    if frac == "":
        "0" => frac
    return int(whole) * 10 + int(frac)

def parse_clock(t):
    # "06:30" -> 390, or -1
    if len(t) != 5 or t[2] != ":":
        return 0 - 1
    for i in [0, 1, 3, 4]:
        if not (t[i] >= "0" and t[i] <= "9"):
            return 0 - 1
    int(t[0:2]) => h
    int(t[3:5]) => m
    if h > 23 or m > 59:
        return 0 - 1
    return h * 60 + m

def parse_setting(key, t):
    if key == "morning" or key == "evening":
        return parse_clock(t)
    parse_tenths(t) => v
    if key == "band":
        if v > 30:
            return 0 - 1
        return v
    if v < 50 or v > 300:
        return 0 - 1
    return v

def what(key):
    if key == "morning" or key == "evening":
        return "a time such as 06:30"
    if key == "band":
        return "a band from 0 to 3.0 degrees"
    return "a temperature from 5.0 to 30.0 degrees"

def show_rooms(rooms):
    "  room       day           night         band          morning        evening" ^0
    for r in rooms:
        "  " + pad(r.name, 11) => line
        for k in keys():
            shown(k, r.get(k)) + " " + r.source(k) => cell
            if k == "evening":
                line + cell => line
            else:
                line + pad(cell, 14) => line
        line ^0
    ("Factory: day " + heat.tenths(heat.Factory.day) + ", night " + heat.tenths(heat.Factory.night) + ", band " + heat.tenths(heat.Factory.band) + ", day from " + heat.clock(heat.Factory.morning) + " to " + heat.clock(heat.Factory.evening) + ".") ^0

def ask_room(rooms):
    plain(input("room (living, bedroom or nursery)> ")) => n
    if n == "":
        return 0 - 1
    for i in [0:len(rooms) - 1]:
        if rooms[i].name == n:
            return i
    "Type living, bedroom or nursery." ^0
    return 0 - 2

def ask_key():
    plain(input("setting (day, night, band, morning or evening)> ")) => k
    if k == "":
        return ""
    for x in keys():
        if x == k:
            return k
    "Type day, night, band, morning or evening." ^0
    return "!"

def day_fits(r, k, v):
    # would the room's day still start before it ends with k set to v?
    r.get("morning") => m
    r.get("evening") => e
    if k == "morning":
        v => m
    if k == "evening":
        v => e
    return m < e

def set_room(rooms):
    ask_room(rooms) => i
    if i == 0 - 1:
        "Cancelled." ^0
        return
    if i == 0 - 2:
        return
    ask_key() => k
    if k == "":
        "Cancelled." ^0
        return
    if k == "!":
        return
    plain(input("value, or factory to follow the factory again> ")) => t
    if t == "":
        "Cancelled." ^0
        return
    rooms[i] => r
    if t == "factory":
        if k in r.own:
            {} => keep
            for x in r.own:
                if x != k:
                    r.own[x] => keep[x]
            keep => r.own
        (r.name + " " + k + " follows the factory again: " + shown(k, r.get(k)) + ".") ^0
        return
    parse_setting(k, t) => v
    if v < 0:
        ("Type " + what(k) + ".") ^0
        return
    if not day_fits(r, k, v):
        "The day has to start before it ends." ^0
        return
    v => r.own[k]
    (r.name + " " + k + " is now its own: " + shown(k, v) + ". The other rooms are as they were.") ^0

def set_factory(rooms):
    ask_key() => k
    if k == "":
        "Cancelled." ^0
        return
    if k == "!":
        return
    plain(input("factory value> ")) => t
    if t == "":
        "Cancelled." ^0
        return
    parse_setting(k, t) => v
    if v < 0:
        ("Type " + what(k) + ".") ^0
        return
    for r in rooms:
        if r.source(k) == "factory" and not day_fits(r, k, v):
            ("The day has to start before it ends, in " + r.name + " too.") ^0
            return
    heat.set_factory(k, v)
    [] => follow
    [] => keep
    for r in rooms:
        if r.source(k) == "factory":
            follow + [r.name] => follow
        else:
            keep + [r.name + " (" + shown(k, r.get(k)) + ")"] => keep
    ("Factory " + k + " is now " + shown(k, v) + ".") ^0
    ("  following it at once: " + listed(follow) + "; keeping their own: " + listed(keep) + ".") ^0

def listed(xs):
    if len(xs) == 0:
        return "none"
    "" => out
    for x in xs:
        if out != "":
            out + ", " => out
        out + x => out
    return out

def hm(minutes):
    return str(int((minutes - minutes % 60) / 60)) + "h " + lpad(str(minutes % 60), 2) + "m"

def day(rooms):
    "A day from 00:00, each room starting at 18.0 degrees with 5.0 outside:" ^0
    "  room       heating   switched on   lowest   highest" ^0
    for r in rooms:
        heat.run_day(r, 180, 50, 0 - 1) => d
        ("  " + pad(r.name, 11) + lpad(hm(d[0]), 7) + lpad(str(d[1]), 14) + lpad(heat.tenths(d[2]), 9) + lpad(heat.tenths(d[3]), 10)) ^0

def hours(rooms):
    ask_room(rooms) => i
    if i == 0 - 1:
        "Cancelled." ^0
        return
    if i == 0 - 2:
        return
    rooms[i] => r
    heat.run_day(r, 180, 50, 0 - 1) => d
    (r.name + ", hour by hour (the temperature on the hour, minutes heating in the hour):") ^0
    "" => line
    0 => n
    for h in [0:23]:
        line + lpad(heat.clock(h * 60), 6) + lpad(heat.tenths(d[4][h][0]), 6) + lpad(str(d[4][h][1]), 4) => line
        n + 1 => n
        if n == 4:
            line ^0
            "" => line
            0 => n

def bands(rooms):
    ask_room(rooms) => i
    if i == 0 - 1:
        "Cancelled." ^0
        return
    if i == 0 - 2:
        return
    rooms[i] => r
    (r.name + " over a day with other bands:") ^0
    "  band   switched on   heating   lowest   highest" ^0
    for b in [0, 2, 5, 10]:
        heat.run_day(r, 180, 50, b) => d
        ("  " + lpad(heat.tenths(b), 4) + lpad(str(d[1]), 14) + lpad(hm(d[0]), 10) + lpad(heat.tenths(d[5]), 9) + lpad(heat.tenths(d[6]), 10)) ^0
    "The lowest and highest are in the day, from the moment the room first comes within the band. A narrow band holds the temperature close to the target and switches the heater far more often; a wide one switches less and lets it swing. Bands less than 0.3 degrees apart can come out the other way by a little, since the heater warms in steps of 0.2 degrees." ^0

[heat.Room("living"), heat.Room("bedroom"), heat.Room("nursery")] => rooms
160 => rooms[1].own["night"]
220 => rooms[2].own["day"]
3 => rooms[2].own["band"]

"== Thermostat ==" ^0
"Three rooms over the factory settings, each with its own changes, heated through a day." ^0
True => running
while running:
    "" ^0
    "1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit" ^0
    plain(input("choice> ")) => choice
    if choice == "1":
        show_rooms(rooms)
    elif choice == "2":
        set_room(rooms)
    elif choice == "3":
        set_factory(rooms)
    elif choice == "4":
        day(rooms)
    elif choice == "5":
        hours(rooms)
    elif choice == "6":
        bands(rooms)
    elif choice == "7":
        False => running
    else:
        "Pick a number from 1 to 7." ^0
"Bye." ^0
Python projection (main.py)
import heat

def plain(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 pad(s, n):
    while len(s) < n:
        s = s + " "
    return s

def lpad(s, n):
    while len(s) < n:
        s = " " + s
    return s

def keys():
    return ["day", "night", "band", "morning", "evening"]

def shown(key, v):
    if key == "morning" or key == "evening":
        return heat.clock(v)
    return heat.tenths(v)

def parse_tenths(t):
    whole = ""
    frac = ""
    dot = False
    for c in t:
        if c == "." and not dot:
            dot = True
        elif c >= "0" and c <= "9":
            if dot:
                frac = frac + c
            else:
                whole = whole + c
        else:
            return 0 - 1
    if whole == "" or len(whole) > 2 or len(frac) > 1 or dot and frac == "":
        return 0 - 1
    if frac == "":
        frac = "0"
    return int(whole) * 10 + int(frac)

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

def parse_setting(key, t):
    if key == "morning" or key == "evening":
        return parse_clock(t)
    v = parse_tenths(t)
    if key == "band":
        if v > 30:
            return 0 - 1
        return v
    if v < 50 or v > 300:
        return 0 - 1
    return v

def what(key):
    if key == "morning" or key == "evening":
        return "a time such as 06:30"
    if key == "band":
        return "a band from 0 to 3.0 degrees"
    return "a temperature from 5.0 to 30.0 degrees"

def show_rooms(rooms):
    print("  room       day           night         band          morning        evening")
    for r in rooms:
        line = "  " + pad(r.name, 11)
        for k in keys():
            cell = shown(k, r.get(k)) + " " + r.source(k)
            if k == "evening":
                line = line + cell
            else:
                line = line + pad(cell, 14)
        print(line)
    print("Factory: day " + heat.tenths(heat.Factory.day) + ", night " + heat.tenths(heat.Factory.night) + ", band " + heat.tenths(heat.Factory.band) + ", day from " + heat.clock(heat.Factory.morning) + " to " + heat.clock(heat.Factory.evening) + ".")

def ask_room(rooms):
    n = plain(input("room (living, bedroom or nursery)> "))
    if n == "":
        return 0 - 1
    for i in range(0, len(rooms)):
        if rooms[i].name == n:
            return i
    print("Type living, bedroom or nursery.")
    return 0 - 2

def ask_key():
    k = plain(input("setting (day, night, band, morning or evening)> "))
    if k == "":
        return ""
    for x in keys():
        if x == k:
            return k
    print("Type day, night, band, morning or evening.")
    return "!"

def day_fits(r, k, v):
    m = r.get("morning")
    e = r.get("evening")
    if k == "morning":
        m = v
    if k == "evening":
        e = v
    return m < e

def set_room(rooms):
    i = ask_room(rooms)
    if i == 0 - 1:
        print("Cancelled.")
        return
    if i == 0 - 2:
        return
    k = ask_key()
    if k == "":
        print("Cancelled.")
        return
    if k == "!":
        return
    t = plain(input("value, or factory to follow the factory again> "))
    if t == "":
        print("Cancelled.")
        return
    r = rooms[i]
    if t == "factory":
        if k in r.own:
            keep = {}
            for x in r.own:
                if x != k:
                    keep[x] = r.own[x]
            r.own = keep
        print(r.name + " " + k + " follows the factory again: " + shown(k, r.get(k)) + ".")
        return
    v = parse_setting(k, t)
    if v < 0:
        print("Type " + what(k) + ".")
        return
    if not day_fits(r, k, v):
        print("The day has to start before it ends.")
        return
    r.own[k] = v
    print(r.name + " " + k + " is now its own: " + shown(k, v) + ". The other rooms are as they were.")

def set_factory(rooms):
    k = ask_key()
    if k == "":
        print("Cancelled.")
        return
    if k == "!":
        return
    t = plain(input("factory value> "))
    if t == "":
        print("Cancelled.")
        return
    v = parse_setting(k, t)
    if v < 0:
        print("Type " + what(k) + ".")
        return
    for r in rooms:
        if r.source(k) == "factory" and not day_fits(r, k, v):
            print("The day has to start before it ends, in " + r.name + " too.")
            return
    heat.set_factory(k, v)
    follow = []
    keep = []
    for r in rooms:
        if r.source(k) == "factory":
            follow = follow + [r.name]
        else:
            keep = keep + [r.name + " (" + shown(k, r.get(k)) + ")"]
    print("Factory " + k + " is now " + shown(k, v) + ".")
    print("  following it at once: " + listed(follow) + "; keeping their own: " + listed(keep) + ".")

def listed(xs):
    if len(xs) == 0:
        return "none"
    out = ""
    for x in xs:
        if out != "":
            out = out + ", "
        out = out + x
    return out

def hm(minutes):
    return str(int((minutes - minutes % 60) / 60)) + "h " + lpad(str(minutes % 60), 2) + "m"

def day(rooms):
    print("A day from 00:00, each room starting at 18.0 degrees with 5.0 outside:")
    print("  room       heating   switched on   lowest   highest")
    for r in rooms:
        d = heat.run_day(r, 180, 50, 0 - 1)
        print("  " + pad(r.name, 11) + lpad(hm(d[0]), 7) + lpad(str(d[1]), 14) + lpad(heat.tenths(d[2]), 9) + lpad(heat.tenths(d[3]), 10))

def hours(rooms):
    i = ask_room(rooms)
    if i == 0 - 1:
        print("Cancelled.")
        return
    if i == 0 - 2:
        return
    r = rooms[i]
    d = heat.run_day(r, 180, 50, 0 - 1)
    print(r.name + ", hour by hour (the temperature on the hour, minutes heating in the hour):")
    line = ""
    n = 0
    for h in range(0, 24):
        line = line + lpad(heat.clock(h * 60), 6) + lpad(heat.tenths(d[4][h][0]), 6) + lpad(str(d[4][h][1]), 4)
        n = n + 1
        if n == 4:
            print(line)
            line = ""
            n = 0

def bands(rooms):
    i = ask_room(rooms)
    if i == 0 - 1:
        print("Cancelled.")
        return
    if i == 0 - 2:
        return
    r = rooms[i]
    print(r.name + " over a day with other bands:")
    print("  band   switched on   heating   lowest   highest")
    for b in [0, 2, 5, 10]:
        d = heat.run_day(r, 180, 50, b)
        print("  " + lpad(heat.tenths(b), 4) + lpad(str(d[1]), 14) + lpad(hm(d[0]), 10) + lpad(heat.tenths(d[5]), 9) + lpad(heat.tenths(d[6]), 10))
    print("The lowest and highest are in the day, from the moment the room first comes within the band. A narrow band holds the temperature close to the target and switches the heater far more often; a wide one switches less and lets it swing. Bands less than 0.3 degrees apart can come out the other way by a little, since the heater warms in steps of 0.2 degrees.")

rooms = [heat.Room("living"), heat.Room("bedroom"), heat.Room("nursery")]
rooms[1].own["night"] = 160
rooms[2].own["day"] = 220
rooms[2].own["band"] = 3
print("== Thermostat ==")
print("Three rooms over the factory settings, each with its own changes, heated through a day.")
running = True
while running:
    print("")
    print("1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit")
    choice = plain(input("choice> "))
    if choice == "1":
        show_rooms(rooms)
    elif choice == "2":
        set_room(rooms)
    elif choice == "3":
        set_factory(rooms)
    elif choice == "4":
        day(rooms)
    elif choice == "5":
        hours(rooms)
    elif choice == "6":
        bands(rooms)
    elif choice == "7":
        running = False
    else:
        print("Pick a number from 1 to 7.")
print("Bye.")

heat.eml

eml
# P115 thermostat - rooms that heat with a band around the target
# (hysteresis): the heater comes on at target - band and goes off at
# target + band, so it does not switch every minute. As in the corpus case
# thermostat-class-defaults, the factory settings live on the class and a
# room's own settings on the room: reading falls back to the class, live,
# and writing a room's setting never touches the class or another room.
#
# Temperatures are whole tenths of a degree; times are minutes from
# midnight. Each minute a heating room warms by 0.2 degrees, and a room
# warmer than outside cools by 0.1.

class Factory:
    200 => day
    170 => night
    5 => band
    360 => morning
    1320 => evening

class Room:
    def __init__(self, name):
        name => self.name
        {} => self.own

    def get(self, key):
        # the room's own value, or the factory's at the moment of asking
        if key in self.own:
            return self.own[key]
        if key == "day":
            return Factory.day
        if key == "night":
            return Factory.night
        if key == "band":
            return Factory.band
        if key == "morning":
            return Factory.morning
        return Factory.evening

    def source(self, key):
        if key in self.own:
            return "own"
        return "factory"

    def target(self, minute):
        if minute >= self.get("morning") and minute < self.get("evening"):
            return self.get("day")
        return self.get("night")

def set_factory(key, value):
    if key == "day":
        value => Factory.day
    elif key == "night":
        value => Factory.night
    elif key == "band":
        value => Factory.band
    elif key == "morning":
        value => Factory.morning
    else:
        value => Factory.evening

def run_day(room, start, outside, band_override):
    # [minutes heating, times switched on, lowest, highest, hourly
    # [temperature at the hour, minutes heating in the hour], lowest and
    # highest in the day period once the room has first come within the
    # band, target - band to target + band - the swing the band allows,
    # without the change from the night's temperature]; a band override of
    # -1 uses the room's band
    start => t
    False => on
    0 => heating
    0 => switches
    t => low
    t => high
    0 - 1 => day_low
    0 - 1 => day_high
    False => settled
    [] => hours
    0 => this_hour
    room.get("band") => band
    if band_override >= 0:
        band_override => band
    for m in [0:1439]:
        if m % 60 == 0:
            hours + [[t, 0]] => hours
            0 => this_hour
        room.target(m) => goal
        if not on and t <= goal - band:
            True => on
            switches + 1 => switches
        elif on and t >= goal + band:
            False => on
        if on:
            t + 2 => t
            heating + 1 => heating
            hours[len(hours) - 1][1] + 1 => hours[len(hours) - 1][1]
        elif t > outside:
            t - 1 => t
        if t < low:
            t => low
        if t > high:
            t => high
        if m >= room.get("morning") and m < room.get("evening"):
            if t >= goal - band and t <= goal + band:
                True => settled
            if settled:
                if day_low == 0 - 1 or t < day_low:
                    t => day_low
                if t > day_high:
                    t => day_high
        else:
            False => settled
    return [heating, switches, low, high, hours, day_low, day_high]

def tenths(t):
    "" => sign
    t => a
    if t < 0:
        "-" => sign
        0 - t => a
    return sign + str(int((a - a % 10) / 10)) + "." + str(a % 10)

def clock(m):
    m % 60 => mm
    int((m - mm) / 60) => h
    str(h) => hs
    if h < 10:
        "0" + hs => hs
    str(mm) => ms
    if mm < 10:
        "0" + ms => ms
    return hs + ":" + ms
Python projection (heat.py)
class Factory:
    day = 200
    night = 170
    band = 5
    morning = 360
    evening = 1320

class Room:
    def __init__(self, name):
        self.name = name
        self.own = {}
    def get(self, key):
        if key in self.own:
            return self.own[key]
        if key == "day":
            return Factory.day
        if key == "night":
            return Factory.night
        if key == "band":
            return Factory.band
        if key == "morning":
            return Factory.morning
        return Factory.evening
    def source(self, key):
        if key in self.own:
            return "own"
        return "factory"
    def target(self, minute):
        if minute >= self.get("morning") and minute < self.get("evening"):
            return self.get("day")
        return self.get("night")

def set_factory(key, value):
    if key == "day":
        Factory.day = value
    elif key == "night":
        Factory.night = value
    elif key == "band":
        Factory.band = value
    elif key == "morning":
        Factory.morning = value
    else:
        Factory.evening = value

def run_day(room, start, outside, band_override):
    t = start
    on = False
    heating = 0
    switches = 0
    low = t
    high = t
    day_low = 0 - 1
    day_high = 0 - 1
    settled = False
    hours = []
    this_hour = 0
    band = room.get("band")
    if band_override >= 0:
        band = band_override
    for m in range(0, 1440):
        if m % 60 == 0:
            hours = hours + [[t, 0]]
            this_hour = 0
        goal = room.target(m)
        if not on and t <= goal - band:
            on = True
            switches = switches + 1
        elif on and t >= goal + band:
            on = False
        if on:
            t = t + 2
            heating = heating + 1
            hours[len(hours) - 1][1] = hours[len(hours) - 1][1] + 1
        elif t > outside:
            t = t - 1
        if t < low:
            low = t
        if t > high:
            high = t
        if m >= room.get("morning") and m < room.get("evening"):
            if t >= goal - band and t <= goal + band:
                settled = True
            if settled:
                if day_low == 0 - 1 or t < day_low:
                    day_low = t
                if t > day_high:
                    day_high = t
        else:
            settled = False
    return [heating, switches, low, high, hours, day_low, day_high]

def tenths(t):
    sign = ""
    a = t
    if t < 0:
        sign = "-"
        a = 0 - t
    return sign + str(int((a - a % 10) / 10)) + "." + str(a % 10)

def clock(m):
    mm = m % 60
    h = int((m - mm) / 60)
    hs = str(h)
    if h < 10:
        hs = "0" + hs
    ms = str(mm)
    if mm < 10:
        ms = "0" + ms
    return hs + ":" + ms

Built on these corpus cases

ΣEML 2026

Efficient New Language — a high-density semantic overlay for humans and AI agents.

This site’s demos run on the real EML toolchain, in your browser.

© 2026 EveMissLab (一言諾科技有限公司) / Neo.K. All rights reserved.