Project P179

Recipe scaler

A roast for four scaled to more or fewer servings: the ingredients scale with them, the time with the 2/3 power of the weight, and a teaspoon of fine salt holds twice a teaspoon of flakes.

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

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

A roast for four, scaled to more or fewer servings: the ingredients by the servings, and the time by something else.

  • main.eml - the menu, the recipe, the time for bigger joints, the salt, and servings of your own
  • recipe.eml - the ingredients, the time for the 1 kg joint, what a teaspoon of each salt holds, a cube root by Newton's method, and the scaled time

How each part works:

  • The ingredients scale with the servings, rounded half-up to the gram.
  • As in the corpus case the-roast-eight-times-the-size-took-four-times-the-time, the time goes with the thickness squared, the 2/3 power of the weight: the 1 kg joint takes 30 minutes, 2 kg 48, 4 kg 76 and 8 kg 120 - four times as long for eight times the size, not 240 minutes.
  • As in the-same-spoon-held-twice-the-seasoning, a teaspoon is a volume: the recipe's 2 teaspoons of flake salt are 6 g, and the same spoon of fine table salt holds 12 g, twice as salty. Scaled, the salt is given in grams.

What is checked: menu choices 1 to 5; 1 to 32 servings. An empty answer cancels.

Sessions: sessions/basic.in gives: - the recipe, the bigger joints and the salt; - 10, 1 and 4 servings.

sessions/bad-input.in gives: - menu choices 0 and x; - servings: a cancel, 0, 33, x and 2.5; - 32 and 3 servings.

Built on the verified corpus cases the-roast-eight-times-the-size-took-four-times-the-time (a joint scaled to eight times the weight and cooked eight times as long) and the-same-spoon-held-twice-the-seasoning (two teaspoons of fine salt measured for a recipe written for flake salt).

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
== Recipe scaler ==
A roast for four, scaled - the ingredients by the servings, and the time by something else.

1) the recipe  2) bigger joints  3) the salt  4) servings of your own  5) quit
choice> 0
Pick a number from 1 to 5.

1) the recipe  2) bigger joints  3) the salt  4) servings of your own  5) quit
choice> x
Pick a number from 1 to 5.

1) the recipe  2) bigger joints  3) the salt  4) servings of your own  5) quit
choice> 4
servings (1 to 32)> 
Cancelled.

1) the recipe  2) bigger joints  3) the salt  4) servings of your own  5) quit
choice> 4
servings (1 to 32)> 0
Type a whole number from 1 to 32.

1) the recipe  2) bigger joints  3) the salt  4) servings of your own  5) quit
choice> 4
servings (1 to 32)> 33
Type a whole number from 1 to 32.

1) the recipe  2) bigger joints  3) the salt  4) servings of your own  5) quit
choice> 4
servings (1 to 32)> x
Type a whole number from 1 to 32.

1) the recipe  2) bigger joints  3) the salt  4) servings of your own  5) quit
choice> 4
servings (1 to 32)> 2.5
Type a whole number from 1 to 32.

1) the recipe  2) bigger joints  3) the salt  4) servings of your own  5) quit
choice> 4
servings (1 to 32)> 32
For 32:
  beef joint   8,000 g
  onions       2,400 g
  carrots      3,200 g
  stock        2,000 g
  salt            48 g  weighed; by the spoon that is flake or fine, not the same
Roast the 8,000 g joint 120 minutes.
Scaled like the ingredients, the time would be off: it grows more slowly than the weight when the joint is bigger, and falls more slowly when it is smaller.

1) the recipe  2) bigger joints  3) the salt  4) servings of your own  5) quit
choice> 4
servings (1 to 32)> 3
For 3:
  beef joint     750 g
  onions         225 g
  carrots        300 g
  stock          188 g
  salt             5 g  weighed; by the spoon that is flake or fine, not the same
Roast the 750 g joint 25 minutes.
Scaled like the ingredients, the time would be off: it grows more slowly than the weight when the joint is bigger, and falls more slowly when it is smaller.

1) the recipe  2) bigger joints  3) the salt  4) servings of your own  5) quit
choice> 5
Bye.
What was typed (17 lines)
0
x
4

4
0
4
33
4
x
4
2.5
4
32
4
3
5

basic

interpreter: byte-equal
== Recipe scaler ==
A roast for four, scaled - the ingredients by the servings, and the time by something else.

1) the recipe  2) bigger joints  3) the salt  4) servings of your own  5) quit
choice> 1
A roast for 4:
  beef joint   1,000 g
  onions         300 g
  carrots        400 g
  stock          250 g
  salt        2 teaspoons of flake salt
Roast the 1.0 kg joint 30 minutes at 200 C.

1) the recipe  2) bigger joints  3) the salt  4) servings of your own  5) quit
choice> 2
Bigger joints of the same shape, and their roasting time:
  servings  joint      times the time, scaled   needed
  4         1.0 kg                     30 min   30 min
  8         2.0 kg                     60 min   48 min
  16        4.0 kg                    120 min   76 min
  32        8.0 kg                    240 min  120 min
A joint eight times the size takes 120 minutes, four times as long, not 240: the heat has to reach the middle, and that time goes with the thickness squared, the 2/3 power of the weight.

1) the recipe  2) bigger joints  3) the salt  4) servings of your own  5) quit
choice> 3
The recipe's 2 teaspoons are written for flake salt: 6 g. Measured with the same spoon, fine table salt is 12 g, twice as salty: a teaspoon is a volume, and fine grains pack it.
The same 6 g of fine salt is 1 teaspoon. Better: weigh the salt.

1) the recipe  2) bigger joints  3) the salt  4) servings of your own  5) quit
choice> 4
servings (1 to 32)> 10
For 10:
  beef joint   2,500 g
  onions         750 g
  carrots      1,000 g
  stock          625 g
  salt            15 g  weighed; by the spoon that is flake or fine, not the same
Roast the 2,500 g joint 55 minutes.
Scaled like the ingredients, the time would be off: it grows more slowly than the weight when the joint is bigger, and falls more slowly when it is smaller.

1) the recipe  2) bigger joints  3) the salt  4) servings of your own  5) quit
choice> 4
servings (1 to 32)> 1
For 1:
  beef joint     250 g
  onions          75 g
  carrots        100 g
  stock           63 g
  salt             2 g  weighed; by the spoon that is flake or fine, not the same
Roast the 250 g joint 12 minutes.
Scaled like the ingredients, the time would be off: it grows more slowly than the weight when the joint is bigger, and falls more slowly when it is smaller.

1) the recipe  2) bigger joints  3) the salt  4) servings of your own  5) quit
choice> 4
servings (1 to 32)> 4
For 4:
  beef joint   1,000 g
  onions         300 g
  carrots        400 g
  stock          250 g
  salt             6 g  weighed; by the spoon that is flake or fine, not the same
Roast the 1,000 g joint 30 minutes.

1) the recipe  2) bigger joints  3) the salt  4) servings of your own  5) quit
choice> 5
Bye.
What was typed (10 lines)
1
2
3
4
10
4
1
4
4
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
# P179 recipe scaler: a roast for four - the recipe, the time for bigger
# joints, the salt by the spoon, and the recipe scaled to servings of your
# own.
import recipe

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):
    s => out
    while len(out) < n:
        out + " " => out
    return out

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

def digits(t):
    for c in t:
        if c < "0" or c > "9":
            return False
    return len(t) > 0

def commas(n):
    str(n) => s
    "" => out
    0 => k
    len(s) => i
    while i > 0:
        if k == 3:
            "," + out => out
            0 => k
        s[i - 1] + out => out
        k + 1 => k
        i - 1 => i
    return out

def scaled(grams, n):
    # grams for four, scaled to n servings, rounded half-up
    grams * n * 2 + recipe.serves() => y
    return int((y - y % (2 * recipe.serves())) / (2 * recipe.serves()))

def kg(grams):
    # grams as kilograms to one place, half-up
    grams + 50 => y
    int((y - y % 100) / 100) => t
    return str(int((t - t % 10) / 10)) + "." + str(t % 10) + " kg"

def show_recipe():
    "A roast for 4:" ^0
    for i in recipe.ingredients():
        ("  " + pad(i[0], 12) + lpad(commas(i[1]) + " g", 8)) ^0
    ("  " + pad("salt", 12) + str(recipe.salt_tsp()) + " teaspoons of flake salt") ^0
    ("Roast the " + kg(recipe.ingredients()[0][1]) + " joint " + str(recipe.minutes()) + " minutes at 200 C.") ^0

def show_joints():
    "Bigger joints of the same shape, and their roasting time:" ^0
    ("  " + pad("servings", 10) + pad("joint", 9) + lpad("times the time, scaled", 24) + lpad("needed", 9)) ^0
    for n in [4, 8, 16, 32]:
        ("  " + pad(str(n), 10) + pad(kg(scaled(1000, n)), 9) + lpad(str(int(recipe.minutes() * n / recipe.serves())) + " min", 24) + lpad(str(recipe.time_for(n)) + " min", 9)) ^0
    ("A joint eight times the size takes " + str(recipe.time_for(32)) + " minutes, four times as long, not " + str(8 * recipe.minutes()) + ": the heat has to reach the middle, and that time goes with the thickness squared, the 2/3 power of the weight.") ^0

def show_salt():
    recipe.salt_tsp() => t
    t * recipe.grams_per_tsp("flake") => g
    t * recipe.grams_per_tsp("fine") => f
    ("The recipe's " + str(t) + " teaspoons are written for flake salt: " + str(g) + " g. Measured with the same spoon, fine table salt is " + str(f) + " g, twice as salty: a teaspoon is a volume, and fine grains pack it.") ^0
    ("The same " + str(g) + " g of fine salt is " + str(int(g / recipe.grams_per_tsp("fine"))) + " teaspoon. Better: weigh the salt.") ^0

def own():
    plain(input("servings (1 to 32)> ")) => t
    if t == "":
        "Cancelled." ^0
        return
    if len(t) > 2 or not digits(t) or int(t) < 1 or int(t) > 32:
        "Type a whole number from 1 to 32." ^0
        return
    int(t) => n
    ("For " + str(n) + ":") ^0
    for i in recipe.ingredients():
        ("  " + pad(i[0], 12) + lpad(commas(scaled(i[1], n)) + " g", 8)) ^0
    scaled(recipe.salt_tsp() * recipe.grams_per_tsp("flake"), n) => salt
    ("  " + pad("salt", 12) + lpad(str(salt) + " g", 8) + "  weighed; by the spoon that is flake or fine, not the same") ^0
    recipe.time_for(n) => m
    ("Roast the " + commas(scaled(1000, n)) + " g joint " + str(m) + " minutes.") ^0
    if n != recipe.serves():
        "Scaled like the ingredients, the time would be off: it grows more slowly than the weight when the joint is bigger, and falls more slowly when it is smaller." ^0

"== Recipe scaler ==" ^0
"A roast for four, scaled - the ingredients by the servings, and the time by something else." ^0
True => running
while running:
    "" ^0
    "1) the recipe  2) bigger joints  3) the salt  4) servings of your own  5) quit" ^0
    plain(input("choice> ")) => choice
    if choice == "1":
        show_recipe()
    elif choice == "2":
        show_joints()
    elif choice == "3":
        show_salt()
    elif choice == "4":
        own()
    elif choice == "5":
        False => running
    else:
        "Pick a number from 1 to 5." ^0
"Bye." ^0
Python projection (main.py)
import recipe

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):
    out = s
    while len(out) < n:
        out = out + " "
    return out

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

def digits(t):
    for c in t:
        if c < "0" or c > "9":
            return False
    return len(t) > 0

def commas(n):
    s = str(n)
    out = ""
    k = 0
    i = len(s)
    while i > 0:
        if k == 3:
            out = "," + out
            k = 0
        out = s[i - 1] + out
        k = k + 1
        i = i - 1
    return out

def scaled(grams, n):
    y = grams * n * 2 + recipe.serves()
    return int((y - y % (2 * recipe.serves())) / (2 * recipe.serves()))

def kg(grams):
    y = grams + 50
    t = int((y - y % 100) / 100)
    return str(int((t - t % 10) / 10)) + "." + str(t % 10) + " kg"

def show_recipe():
    print("A roast for 4:")
    for i in recipe.ingredients():
        print("  " + pad(i[0], 12) + lpad(commas(i[1]) + " g", 8))
    print("  " + pad("salt", 12) + str(recipe.salt_tsp()) + " teaspoons of flake salt")
    print("Roast the " + kg(recipe.ingredients()[0][1]) + " joint " + str(recipe.minutes()) + " minutes at 200 C.")

def show_joints():
    print("Bigger joints of the same shape, and their roasting time:")
    print("  " + pad("servings", 10) + pad("joint", 9) + lpad("times the time, scaled", 24) + lpad("needed", 9))
    for n in [4, 8, 16, 32]:
        print("  " + pad(str(n), 10) + pad(kg(scaled(1000, n)), 9) + lpad(str(int(recipe.minutes() * n / recipe.serves())) + " min", 24) + lpad(str(recipe.time_for(n)) + " min", 9))
    print("A joint eight times the size takes " + str(recipe.time_for(32)) + " minutes, four times as long, not " + str(8 * recipe.minutes()) + ": the heat has to reach the middle, and that time goes with the thickness squared, the 2/3 power of the weight.")

def show_salt():
    t = recipe.salt_tsp()
    g = t * recipe.grams_per_tsp("flake")
    f = t * recipe.grams_per_tsp("fine")
    print("The recipe's " + str(t) + " teaspoons are written for flake salt: " + str(g) + " g. Measured with the same spoon, fine table salt is " + str(f) + " g, twice as salty: a teaspoon is a volume, and fine grains pack it.")
    print("The same " + str(g) + " g of fine salt is " + str(int(g / recipe.grams_per_tsp("fine"))) + " teaspoon. Better: weigh the salt.")

def own():
    t = plain(input("servings (1 to 32)> "))
    if t == "":
        print("Cancelled.")
        return
    if len(t) > 2 or not digits(t) or int(t) < 1 or int(t) > 32:
        print("Type a whole number from 1 to 32.")
        return
    n = int(t)
    print("For " + str(n) + ":")
    for i in recipe.ingredients():
        print("  " + pad(i[0], 12) + lpad(commas(scaled(i[1], n)) + " g", 8))
    salt = scaled(recipe.salt_tsp() * recipe.grams_per_tsp("flake"), n)
    print("  " + pad("salt", 12) + lpad(str(salt) + " g", 8) + "  weighed; by the spoon that is flake or fine, not the same")
    m = recipe.time_for(n)
    print("Roast the " + commas(scaled(1000, n)) + " g joint " + str(m) + " minutes.")
    if n != recipe.serves():
        print("Scaled like the ingredients, the time would be off: it grows more slowly than the weight when the joint is bigger, and falls more slowly when it is smaller.")

print("== Recipe scaler ==")
print("A roast for four, scaled - the ingredients by the servings, and the time by something else.")
running = True
while running:
    print("")
    print("1) the recipe  2) bigger joints  3) the salt  4) servings of your own  5) quit")
    choice = plain(input("choice> "))
    if choice == "1":
        show_recipe()
    elif choice == "2":
        show_joints()
    elif choice == "3":
        show_salt()
    elif choice == "4":
        own()
    elif choice == "5":
        running = False
    else:
        print("Pick a number from 1 to 5.")
print("Bye.")

recipe.eml

eml
# P179 recipe scaler - a roast for four, scaled to more or fewer servings.
# The ingredients scale with the servings. As in the corpus case
# the-roast-eight-times-the-size-took-four-times-the-time, the cooking time
# does not: heat has to reach the middle, and the time goes with the
# thickness squared, the 2/3 power of the weight, so a joint eight times
# the size takes four times as long. And as in
# the-same-spoon-held-twice-the-seasoning, a teaspoon is a volume, and a
# teaspoon of fine salt holds twice the salt of a teaspoon of flakes.

def serves():
    return 4

def ingredients():
    # [what, grams for four]
    return [["beef joint", 1000], ["onions", 300], ["carrots", 400], ["stock", 250]]

def minutes():
    # cooking time for the 1 kg joint
    return 30

def grams_per_tsp(salt):
    # how much a level teaspoon holds
    if salt == "flake":
        return 3
    return 6

def salt_tsp():
    # the recipe's salt for four: 2 teaspoons, written for flake salt
    return 2

def cube_root(x):
    # x > 0, by Newton's method from a start above the root
    1.0 => r
    while r * r * r < x:
        r * 2.0 => r
    0 => k
    while k < 60:
        (2.0 * r + x / (r * r)) / 3.0 => r
        k + 1 => k
    return r

def time_for(n):
    # whole minutes for the joint scaled to n servings: 30 x (n / 4)^(2/3)
    n / serves() => ratio
    cube_root(ratio * ratio) => f
    return int(minutes() * f + 0.5)
Python projection (recipe.py)
def serves():
    return 4

def ingredients():
    return [["beef joint", 1000], ["onions", 300], ["carrots", 400], ["stock", 250]]

def minutes():
    return 30

def grams_per_tsp(salt):
    if salt == "flake":
        return 3
    return 6

def salt_tsp():
    return 2

def cube_root(x):
    r = 1.0
    while r * r * r < x:
        r = r * 2.0
    k = 0
    while k < 60:
        r = (2.0 * r + x / (r * r)) / 3.0
        k = k + 1
    return r

def time_for(n):
    ratio = n / serves()
    f = cube_root(ratio * ratio)
    return int(minutes() * f + 0.5)

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.