Project P095

Sensor logger

Temperature readings kept in a ring buffer of the last N, shown slot by slot with where the next write goes and how many were overwritten, and the average, lowest and highest over a window sliding along them, the extremes by a monotonic queue.

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

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

Temperature readings kept in a ring buffer of the last N. The program shows the buffer slot by slot, with where the next write goes and how many readings have been overwritten. It also gives the average, lowest and highest over a window sliding along the readings. It starts with ten readings in a buffer of eight, so two are already gone.

  • main.eml - the menu, reading temperatures, the buffer and the window table
  • logger.eml - the ring buffer, its readings in order, window sums and window extremes

How each part works:

  • Readings are whole tenths of a degree (21.5 is 215), so sums and averages are exact.
  • The ring buffer writes at the next slot and moves on, wrapping round to slot 0. Once it is full, that slot held the oldest reading, which is overwritten. As in the corpus case ring-buffer-bounds-report, every index stays inside 0 to N - 1, so nothing can be read or written out of range. The oldest reading sits where the next write will go.
  • Windows, as in the corpus case sliding-window-slices: only full windows, len - k + 1 of them. Each window's sum adds the reading coming in and takes off the one going out.
  • The lowest and highest come from a queue of positions whose readings only rise (or only fall) from front to back. A new reading removes from the back everything it beats, and the front leaves when it slides out of the window. Each reading enters once and leaves at most once, so this is one pass, whatever the window size.

What is checked: menu choices 1 to 5; 1 to 20 readings at a time, each from -50.0 to 60.0 with at most one decimal; a window from 1 to the number of readings held; a capacity from 2 to 12. An empty answer cancels.

Sessions: sessions/basic.in gives: - the starting buffer, wrapped round, and windows of 3; - two more readings overwriting the two oldest; the one window of 8; - a new buffer of 4: three readings, then two more, the second overwriting the oldest; windows of 2.

sessions/bad-input.in gives: - menu choices 0 and x; - the readings abc, 21.55, 70 and -50.1; - window sizes 0, 9 and x; capacities 13 and 1; then cancels.

Built on the verified corpus cases ring-buffer-bounds-report (a fixed buffer whose indices never leave it) and sliding-window-slices (full windows, counted at both ends).

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
== Sensor logger ==
The last readings in a ring buffer, and a window sliding along them.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 0
Pick a number from 1 to 5.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> x
Pick a number from 1 to 5.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 1
readings (up to 20, like 21.5 22 -3.4)> abc
Type 1 to 20 readings from -50.0 to 60.0, at most one decimal each.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 1
readings (up to 20, like 21.5 22 -3.4)> 21.55
Type 1 to 20 readings from -50.0 to 60.0, at most one decimal each.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 1
readings (up to 20, like 21.5 22 -3.4)> 70
Type 1 to 20 readings from -50.0 to 60.0, at most one decimal each.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 1
readings (up to 20, like 21.5 22 -3.4)> -50.1
Type 1 to 20 readings from -50.0 to 60.0, at most one decimal each.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 1
readings (up to 20, like 21.5 22 -3.4)> 
Cancelled.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 3
window size (1 to 8)> 0
Type a whole number from 1 to 8.
window size (1 to 8)> 9
Type a whole number from 1 to 8.
window size (1 to 8)> x
Type a whole number from 1 to 8.
window size (1 to 8)> 
Cancelled.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 4
capacity (2 to 12)> 13
Type a whole number from 2 to 12.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 4
capacity (2 to 12)> 1
Type a whole number from 2 to 12.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 4
capacity (2 to 12)> 
Cancelled.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 5
Bye.
What was typed (24 lines)
0
x
1
abc
1
21.55
1
70
1
-50.1
1

3
0
9
x

4
13
4
1
4

5

basic

interpreter: byte-equal
== Sensor logger ==
The last readings in a ring buffer, and a window sliding along them.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 2
  slot   20.8  20.1  21.4  22.1  23.0  22.6  21.9  21.2
                        ^  next write
Holding 8 of 8; 2 overwritten so far.
Oldest to newest: 21.4 22.1 23.0 22.6 21.9 21.2 20.8 20.1

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 3
window size (1 to 8)> 3
  readings   average   lowest  highest
   1 to  3     22.17     21.4     23.0
   2 to  4     22.57     22.1     23.0
   3 to  5     22.50     21.9     23.0
   4 to  6     21.90     21.2     22.6
   5 to  7     21.30     20.8     21.9
   6 to  8     20.70     20.1     21.2
6 windows of 3 in 8 readings: each sum adds the reading coming in and drops the one going out.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 1
readings (up to 20, like 21.5 22 -3.4)> 19.5 -2.3
Logged 2; the 2 oldest readings were overwritten to make room.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 2
  slot   20.8  20.1  19.5  -2.3  23.0  22.6  21.9  21.2
                                    ^  next write
Holding 8 of 8; 4 overwritten so far.
Oldest to newest: 23.0 22.6 21.9 21.2 20.8 20.1 19.5 -2.3

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 3
window size (1 to 8)> 8
  readings   average   lowest  highest
   1 to  8     18.35     -2.3     23.0
1 window of 8 in 8 readings: it is all of them.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 4
capacity (2 to 12)> 4
A new, empty buffer of 4.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 2
  slot      -     -     -     -
            ^  next write
Holding 0 of 4; 0 overwritten so far.
Nothing logged yet.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 1
readings (up to 20, like 21.5 22 -3.4)> 1 2 3
Logged 3; nothing had to be overwritten.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 1
readings (up to 20, like 21.5 22 -3.4)> 4.5 5
Logged 2; the oldest reading was overwritten to make room.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 2
  slot    5.0   2.0   3.0   4.5
                  ^  next write
Holding 4 of 4; 1 overwritten so far.
Oldest to newest: 2.0 3.0 4.5 5.0

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 3
window size (1 to 4)> 2
  readings   average   lowest  highest
   1 to  2      2.50      2.0      3.0
   2 to  3      3.75      3.0      4.5
   3 to  4      4.75      4.5      5.0
3 windows of 2 in 4 readings: each sum adds the reading coming in and drops the one going out.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 5
Bye.
What was typed (19 lines)
2
3
3
1
19.5 -2.3
2
3
8
4
4
2
1
1 2 3
1
4.5 5
2
3
2
5

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
# P095 sensor logger: temperature readings kept in a ring buffer of the
# last N, the slots and where the next one goes, and the average, lowest
# and highest over a window sliding along what is kept.
import logger

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 tenths_of(s):
    # "21.5", "-3", "0.4" as whole tenths, or [] if not a reading from
    # -50.0 to 60.0 with at most one decimal.
    s => t
    1 => sg
    if len(t) > 1 and t[0] == "-":
        t[1:len(t)] => t
        -1 => sg
    0 => k
    while k < len(t) and t[k] != ".":
        k + 1 => k
    t[0:k] => whole
    "0" => frac
    if k < len(t):
        t[k + 1:len(t)] => frac
    if len(whole) == 0 or len(whole) > 2 or len(frac) != 1:
        return []
    0 => n
    for c in whole + frac:
        if not (c >= "0" and c <= "9"):
            return []
        n * 10 + int(c) => n
    sg * n => v
    if v < -500 or v > 600:
        return []
    return [v]

def show(v):
    if v < 0:
        return "-" + show(0 - v)
    return str(int((v - v % 10) / 10)) + "." + str(v % 10)

def average(s, k):
    # s tenths over k readings, in degrees to two places, halves away from 0.
    if s < 0:
        return "-" + average(0 - s, k)
    # hundredths: s / k tenths x 10 = 10 s / k
    20 * s + k => n
    2 * k => d
    0 => q
    while n >= d:
        n - d => n
        q + 1 => q
    "" => z
    if q % 100 < 10:
        "0" => z
    return str(int((q - q % 100) / 100)) + "." + z + str(q % 100)

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

def add(r):
    plain(input("readings (up to 20, like 21.5 22 -3.4)> ")) => s
    if s == "":
        "Cancelled." ^0
        return r
    [] => vs
    "" => w
    True => ok
    for c in s + " ":
        if c == " ":
            if w != "":
                tenths_of(w) => v
                if len(v) == 0:
                    False => ok
                else:
                    vs + v => vs
            "" => w
        else:
            w + c => w
    if not ok or len(vs) == 0 or len(vs) > 20:
        "Type 1 to 20 readings from -50.0 to 60.0, at most one decimal each." ^0
        return r
    r[3] => before
    for v in vs:
        logger.put(r, v) => r
    r[3] - before => gone
    if gone == 0:
        ("Logged " + str(len(vs)) + "; nothing had to be overwritten.") ^0
    elif gone == 1:
        ("Logged " + str(len(vs)) + "; the oldest reading was overwritten to make room.") ^0
    else:
        ("Logged " + str(len(vs)) + "; the " + str(gone) + " oldest readings were overwritten to make room.") ^0
    return r

def buffer(r):
    r[0] => slots
    "" => line
    for i in [0:len(slots) - 1]:
        "     -" => cell
        if i < r[2] or r[2] == len(slots):
            lpad(show(slots[i]), 6) => cell
        line + cell => line
    ("  slot " + line) ^0
    "" => arrow
    for i in [0:len(slots) - 1]:
        if i == r[1]:
            arrow + "     ^" => arrow
        else:
            arrow + "      " => arrow
    ("       " + logger_trim(arrow) + "  next write") ^0
    ("Holding " + str(r[2]) + " of " + str(len(slots)) + "; " + str(r[3]) + " overwritten so far.") ^0
    "" => seq
    for v in logger.in_order(r):
        if seq != "":
            seq + " " => seq
        seq + show(v) => seq
    if seq == "":
        "Nothing logged yet." ^0
    else:
        ("Oldest to newest: " + seq) ^0

def logger_trim(s):
    len(s) => j
    while j > 0 and s[j - 1] == " ":
        j - 1 => j
    return s[0:j]

def windows(r):
    logger.in_order(r) => xs
    if len(xs) == 0:
        "Nothing logged yet." ^0
        return
    while True:
        plain(input("window size (1 to " + str(len(xs)) + ")> ")) => s
        if s == "":
            "Cancelled." ^0
            return
        0 => k
        True => ok
        if len(s) > 2:
            False => ok
        for c in s:
            if c >= "0" and c <= "9":
                k * 10 + int(c) => k
            else:
                False => ok
        if ok and k >= 1 and k <= len(xs):
            logger.window_sums(xs, k) => sums
            logger.window_extremes(xs, k, False) => lows
            logger.window_extremes(xs, k, True) => highs
            "  readings   average   lowest  highest" ^0
            for i in [0:len(sums) - 1]:
                ("  " + lpad(str(i + 1), 2) + " to " + lpad(str(i + k), 2) + lpad(average(sums[i], k), 10) + lpad(show(lows[i]), 9) + lpad(show(highs[i]), 9)) ^0
            if len(sums) == 1:
                ("1 window of " + str(k) + " in " + str(len(xs)) + " readings: it is all of them.") ^0
            else:
                (str(len(sums)) + " windows of " + str(k) + " in " + str(len(xs)) + " readings: each sum adds the reading coming in and drops the one going out.") ^0
            return
        ("Type a whole number from 1 to " + str(len(xs)) + ".") ^0

def resize(r):
    plain(input("capacity (2 to 12)> ")) => s
    if s == "":
        "Cancelled." ^0
        return r
    if s == "2" or s == "3" or s == "4" or s == "5" or s == "6" or s == "7" or s == "8" or s == "9" or s == "10" or s == "11" or s == "12":
        ("A new, empty buffer of " + s + ".") ^0
        return logger.ring(int(s))
    "Type a whole number from 2 to 12." ^0
    return r

logger.ring(8) => r
for v in [205, 210, 214, 221, 230, 226, 219, 212, 208, 201]:
    logger.put(r, v) => r

"== Sensor logger ==" ^0
"The last readings in a ring buffer, and a window sliding along them." ^0
True => running
while running:
    "" ^0
    "1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit" ^0
    plain(input("choice> ")) => choice
    if choice == "1":
        add(r) => r
    elif choice == "2":
        buffer(r)
    elif choice == "3":
        windows(r)
    elif choice == "4":
        resize(r) => r
    elif choice == "5":
        False => running
    else:
        "Pick a number from 1 to 5." ^0
"Bye." ^0
Python projection (main.py)
import logger

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 tenths_of(s):
    t = s
    sg = 1
    if len(t) > 1 and t[0] == "-":
        t = t[1:len(t)]
        sg = -1
    k = 0
    while k < len(t) and t[k] != ".":
        k = k + 1
    whole = t[0:k]
    frac = "0"
    if k < len(t):
        frac = t[k + 1:len(t)]
    if len(whole) == 0 or len(whole) > 2 or len(frac) != 1:
        return []
    n = 0
    for c in whole + frac:
        if not (c >= "0" and c <= "9"):
            return []
        n = n * 10 + int(c)
    v = sg * n
    if v < -500 or v > 600:
        return []
    return [v]

def show(v):
    if v < 0:
        return "-" + show(0 - v)
    return str(int((v - v % 10) / 10)) + "." + str(v % 10)

def average(s, k):
    if s < 0:
        return "-" + average(0 - s, k)
    n = 20 * s + k
    d = 2 * k
    q = 0
    while n >= d:
        n = n - d
        q = q + 1
    z = ""
    if q % 100 < 10:
        z = "0"
    return str(int((q - q % 100) / 100)) + "." + z + str(q % 100)

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

def add(r):
    s = plain(input("readings (up to 20, like 21.5 22 -3.4)> "))
    if s == "":
        print("Cancelled.")
        return r
    vs = []
    w = ""
    ok = True
    for c in s + " ":
        if c == " ":
            if w != "":
                v = tenths_of(w)
                if len(v) == 0:
                    ok = False
                else:
                    vs = vs + v
            w = ""
        else:
            w = w + c
    if not ok or len(vs) == 0 or len(vs) > 20:
        print("Type 1 to 20 readings from -50.0 to 60.0, at most one decimal each.")
        return r
    before = r[3]
    for v in vs:
        r = logger.put(r, v)
    gone = r[3] - before
    if gone == 0:
        print("Logged " + str(len(vs)) + "; nothing had to be overwritten.")
    elif gone == 1:
        print("Logged " + str(len(vs)) + "; the oldest reading was overwritten to make room.")
    else:
        print("Logged " + str(len(vs)) + "; the " + str(gone) + " oldest readings were overwritten to make room.")
    return r

def buffer(r):
    slots = r[0]
    line = ""
    for i in range(0, len(slots)):
        cell = "     -"
        if i < r[2] or r[2] == len(slots):
            cell = lpad(show(slots[i]), 6)
        line = line + cell
    print("  slot " + line)
    arrow = ""
    for i in range(0, len(slots)):
        if i == r[1]:
            arrow = arrow + "     ^"
        else:
            arrow = arrow + "      "
    print("       " + logger_trim(arrow) + "  next write")
    print("Holding " + str(r[2]) + " of " + str(len(slots)) + "; " + str(r[3]) + " overwritten so far.")
    seq = ""
    for v in logger.in_order(r):
        if seq != "":
            seq = seq + " "
        seq = seq + show(v)
    if seq == "":
        print("Nothing logged yet.")
    else:
        print("Oldest to newest: " + seq)

def logger_trim(s):
    j = len(s)
    while j > 0 and s[j - 1] == " ":
        j = j - 1
    return s[0:j]

def windows(r):
    xs = logger.in_order(r)
    if len(xs) == 0:
        print("Nothing logged yet.")
        return
    while True:
        s = plain(input("window size (1 to " + str(len(xs)) + ")> "))
        if s == "":
            print("Cancelled.")
            return
        k = 0
        ok = True
        if len(s) > 2:
            ok = False
        for c in s:
            if c >= "0" and c <= "9":
                k = k * 10 + int(c)
            else:
                ok = False
        if ok and k >= 1 and k <= len(xs):
            sums = logger.window_sums(xs, k)
            lows = logger.window_extremes(xs, k, False)
            highs = logger.window_extremes(xs, k, True)
            print("  readings   average   lowest  highest")
            for i in range(0, len(sums)):
                print("  " + lpad(str(i + 1), 2) + " to " + lpad(str(i + k), 2) + lpad(average(sums[i], k), 10) + lpad(show(lows[i]), 9) + lpad(show(highs[i]), 9))
            if len(sums) == 1:
                print("1 window of " + str(k) + " in " + str(len(xs)) + " readings: it is all of them.")
            else:
                print(str(len(sums)) + " windows of " + str(k) + " in " + str(len(xs)) + " readings: each sum adds the reading coming in and drops the one going out.")
            return
        print("Type a whole number from 1 to " + str(len(xs)) + ".")

def resize(r):
    s = plain(input("capacity (2 to 12)> "))
    if s == "":
        print("Cancelled.")
        return r
    if s == "2" or s == "3" or s == "4" or s == "5" or s == "6" or s == "7" or s == "8" or s == "9" or s == "10" or s == "11" or s == "12":
        print("A new, empty buffer of " + s + ".")
        return logger.ring(int(s))
    print("Type a whole number from 2 to 12.")
    return r

r = logger.ring(8)
for v in [205, 210, 214, 221, 230, 226, 219, 212, 208, 201]:
    r = logger.put(r, v)
print("== Sensor logger ==")
print("The last readings in a ring buffer, and a window sliding along them.")
running = True
while running:
    print("")
    print("1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit")
    choice = plain(input("choice> "))
    if choice == "1":
        r = add(r)
    elif choice == "2":
        buffer(r)
    elif choice == "3":
        windows(r)
    elif choice == "4":
        r = resize(r)
    elif choice == "5":
        running = False
    else:
        print("Pick a number from 1 to 5.")
print("Bye.")

logger.eml

eml
# P095 sensor logger - readings in a ring buffer that keeps the last N and
# overwrites the oldest, and statistics over a window sliding along them.
# Readings are whole tenths of a degree (21.5 is 215). A buffer is
# [slots, next slot to write, how many are held, how many overwritten].

def ring(n):
    return [[0] * n, 0, 0, 0]

def put(r, value):
    # Write at the next slot and move it on, wrapping round; when the
    # buffer is full that slot held the oldest reading, which is lost. As in
    # the corpus case ring-buffer-bounds-report, every index stays inside
    # 0 .. N-1, so no read or write can go out of range.
    r[0] => slots
    len(slots) => n
    value => slots[r[1]]
    (r[1] + 1) % n => nxt
    r[2] => held
    r[3] => lost
    if held < n:
        held + 1 => held
    else:
        lost + 1 => lost
    return [slots, nxt, held, lost]

def in_order(r):
    # The held readings, oldest first: they start where the next write goes
    # once the buffer is full, at slot 0 before that.
    r[0] => slots
    len(slots) => n
    0 => start
    if r[2] == n:
        r[1] => start
    [0] * r[2] => out
    for k in [0:r[2] - 1]:
        slots[(start + k) % n] => out[k]
    return out

def window_sums(xs, k):
    # The sum of every full window of k in a row - as in the corpus case
    # sliding-window-slices, exactly len(xs) - k + 1 of them - by adding the
    # reading coming in and taking off the one going out.
    [] => out
    if k > len(xs):
        return out
    0 => s
    for i in [0:k - 1]:
        s + xs[i] => s
    out + [s] => out
    for i in [k:len(xs) - 1]:
        s + xs[i] - xs[i - k] => s
        out + [s] => out
    return out

def window_extremes(xs, k, biggest):
    # The largest (or smallest) of every full window, in one pass: a queue
    # of positions whose readings only fall (or only rise) from front to
    # back. A newcomer removes everything it beats from the back; the front
    # leaves when it slides out of the window. Each reading goes in once and
    # out at most once.
    [] => out
    [0] * len(xs) => q
    0 => head
    0 => tail
    for i in [0:len(xs) - 1]:
        True => going
        while going and tail > head:
            q[tail - 1] => j
            if (biggest and xs[j] <= xs[i]) or (not biggest and xs[j] >= xs[i]):
                tail - 1 => tail
            else:
                False => going
        i => q[tail]
        tail + 1 => tail
        if q[head] <= i - k:
            head + 1 => head
        if i >= k - 1:
            out + [xs[q[head]]] => out
    return out
Python projection (logger.py)
def ring(n):
    return [[0] * n, 0, 0, 0]

def put(r, value):
    slots = r[0]
    n = len(slots)
    slots[r[1]] = value
    nxt = (r[1] + 1) % n
    held = r[2]
    lost = r[3]
    if held < n:
        held = held + 1
    else:
        lost = lost + 1
    return [slots, nxt, held, lost]

def in_order(r):
    slots = r[0]
    n = len(slots)
    start = 0
    if r[2] == n:
        start = r[1]
    out = [0] * r[2]
    for k in range(0, r[2]):
        out[k] = slots[(start + k) % n]
    return out

def window_sums(xs, k):
    out = []
    if k > len(xs):
        return out
    s = 0
    for i in range(0, k):
        s = s + xs[i]
    out = out + [s]
    for i in range(k, len(xs)):
        s = s + xs[i] - xs[i - k]
        out = out + [s]
    return out

def window_extremes(xs, k, biggest):
    out = []
    q = [0] * len(xs)
    head = 0
    tail = 0
    for i in range(0, len(xs)):
        going = True
        while going and tail > head:
            j = q[tail - 1]
            if biggest and xs[j] <= xs[i] or not biggest and xs[j] >= xs[i]:
                tail = tail - 1
            else:
                going = False
        q[tail] = i
        tail = tail + 1
        if q[head] <= i - k:
            head = head + 1
        if i >= k - 1:
            out = out + [xs[q[head]]]
    return out

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.