Difficulty is a budget

The questions of chapter 1 are all equally hard. This file asks sums, differences and products of any length, and how long they are is decided when the questions are generated.

# Sums, differences and products whose difficulty is a budget.

attribute cost = 4 split

synth value on E
synth latex on E

precedence 1 left + -
precedence 2 left *

start E

E -> E + E   when cost >= 1   weight 3 {
  cost  = -1
  value = $1.value + $3.value
  latex = infix($1, "+", $3)
}

E -> E - E   when cost >= 2   weight 2 {
  cost  = -2
  require $1.value != $3.value
  value = $1.value - $3.value
  latex = infix($1, "-", $3)
}

E -> E * E   when cost >= 3   weight 2 {
  cost  = -3
  value = $1.value * $3.value
  latex = infix($1, "\\cdot", $3)
}

E -> integer_between(2, 9)   when cost = 0 {
  value = $1.value
  latex = str($1.value)
}

prompt "What is the value of [`{E.latex}`]?"
answer E.value
$ rhema run budget.rhm --set cost=2 --count 3
 1. What is the value of [`4 + (4 + 6)`]?
    answer: 14
 2. What is the value of [`3 - 4`]?
    answer: -1
 3. What is the value of [`8 + 2 + 3`]?
    answer: 13

$ rhema run budget.rhm --set cost=8 --count 3
 1. What is the value of [`(4 - 6 + (3 + 4)) \cdot (5 + 8)`]?
    answer: 65
 2. What is the value of [`3 + (7 + 7 + (5 + 7) + (4 - 2 + 5))`]?
    answer: 36
 3. What is the value of [`(6 + 2 - (8 + 5)) \cdot (7 + 8)`]?
    answer: -75

Productions that refer to themselves

E -> E + E says that an E may be two smaller Es with a plus between them. A grammar like this can make expressions of any size; on its own it would never stop. Something has to say how far to go.

Inherited attributes

That something is an inherited attribute, declared with attribute:

attribute cost = 4 split

Synthesized attributes, from chapter 1, travel up the tree: a node computes them from its children. Inherited attributes travel down: a node receives one from its parent before its children exist. cost starts at 4 at the root (or at whatever --set cost=N says) and is handed down.

split says how it is handed down. At each production, what is left of the budget is divided between the children that are symbols of the grammar, in every possible way with equal chance. The other choice, copy, gives every child the whole value; it suits settings that are not spent, such as a limit on the number of digits.

Spending

Each production says what it costs:

E -> E + E   when cost >= 1   weight 3 {
  cost  = -1
  ...
}

cost = -1 takes one from the budget before it is divided between the two Es. So a sum costs 1, a difference 2 and a product 3, and a number costs nothing. Whatever budget the root starts with, the finished question has spent exactly that much: --set cost=2 gives one difference or two sums, never a product.

when cost >= 1 is a guard: the production may only be chosen when it holds. A guard can only look at inherited attributes, because the children do not exist yet when the choice is made. The number production says when cost = 0, so a number appears exactly where the budget has run out.

Among the productions whose guards hold, one is chosen at random, in proportion to its weight (1 when not given). The weights here make sums more common than products.

Every budget must be spendable

When a node receives a budget that no production can take, generation stops. Remove the sum production and ask for a budget of 1, which only a sum could spend:

$ rhema run gap.rhm --set cost=1
gap.rhm: error: no rule for 'E' fits {cost=1}

This is not retried, because no amount of retrying could help: the grammar has no way to spend 1. A grammar that spends a budget should be able to spend every whole number from its smallest question upwards. With prices 1, 2 and 3 and a leaf at 0, this one can.

The compiler also refuses a recursion that does not spend. Delete the cost = -1 line from the sum production and check the file:

$ rhema check loop.rhm
loop.rhm:13:1: error: this rule can reach 'E' again without spending any inherited attribute, so a derivation could circle forever; make the loop consume a budget, for example 'cost = -1'

Requirements

The difference production says

require $1.value != $3.value

A require is checked after the children are built. When it fails, that subtree is built again with fresh random choices. Here it rules out differences of zero, such as 7 - 7, which would make a question cheaper than its price.

Prefer guards to requirements where you can: a guard prevents a bad choice, a requirement discards one after the work is done.

Brackets come from precedence

precedence 1 left + -
precedence 2 left *

infix($1, "+", $3) writes its operands with brackets only where the precedence table needs them, which is why the questions above read 4 + (4 + 6) and (6 + 2 - (8 + 5)) \cdot (7 + 8) with no extra brackets. The operator's text may differ from its symbol: the product is the symbol * in the grammar and \cdot in the question.

Choosing a difficulty

The budget is how a teacher asks for an easier or a harder question of the same kind:

$ rhema run budget.rhm --set cost=3
$ rhema run budget.rhm --set cost=12

Every grammar in the library spends a budget called cost in this way, with prices chosen for its subject; the difficulty page lists them. Chapter 7 shows how a whole assignment fixes the average budget of its questions rather than each one.

Next: chapter 3 looks at what the attributes can hold besides numbers and text.