Lesson 1.14: Defining Evaluation Order
The purpose of this lesson is to explain how to use the heat and cool
attributes, context and context alias sentences, and the strict and
seqstrict attributes to more compactly express heating and cooling in K,
and to express more advanced evaluation strategies in K.
The heat and cool attributes
Thus far, we have been using rule priority and casts to express when to heat an expression and when to cool it. For example, the rules for heating have lower priority, so they do not apply if the term could be evaluated instead, and the rules for heating are expressly written only to apply if the argument of the expression is a value.
However, K has built-in support for deciding when to heat and when to cool.
This support comes in the form of the rule attributes heat and cool as
well as the specially named function isKResult.
Consider the following definition, which is equivalent to LESSON-13-C
(lesson-14-a.k):
kmodule LESSON-14-A-SYNTAX imports UNSIGNED-INT-SYNTAX imports BOOL-SYNTAX syntax Exp ::= Int | Bool > left: Exp "+" Exp > left: Exp "&&" Exp endmodule module LESSON-14-A imports LESSON-14-A-SYNTAX imports INT imports BOOL rule <k> I1:Int + I2:Int => I1 +Int I2 ...</k> rule <k> B1:Bool && B2:Bool => B1 andBool B2 ...</k> syntax KItem ::= freezer1(Exp) | freezer2(Exp) | freezer3(Exp) | freezer4(Exp) rule <k> E:Exp + HOLE:Exp => HOLE ~> freezer1(E) ...</k> requires isKResult(E) [heat] rule <k> HOLE:Exp + E:Exp => HOLE ~> freezer2(E) ...</k> [heat] rule <k> E:Exp && HOLE:Exp => HOLE ~> freezer3(E) ...</k> requires isKResult(E) [heat] rule <k> HOLE:Exp && E:Exp => HOLE ~> freezer4(E) ...</k> [heat] rule <k> HOLE:Exp ~> freezer1(E) => E + HOLE ...</k> [cool] rule <k> HOLE:Exp ~> freezer2(E) => HOLE + E ...</k> [cool] rule <k> HOLE:Exp ~> freezer3(E) => E && HOLE ...</k> [cool] rule <k> HOLE:Exp ~> freezer4(E) => HOLE && E ...</k> [cool] syntax Bool ::= isKResult(K) [function, symbol] rule isKResult(_:Int) => true rule isKResult(_:Bool) => true rule isKResult(_) => false [owise] endmodule
We have introduced three major changes to this definition. First, we have
removed the Val sort. We replace it instead with a function isKResult.
The function in question must have the same signature and attributes as seen in
this example. It ought to return true whenever a term should not be heated
(because it is a value) and false when it should be heated (because it is not
a value). We thus also insert isKResult calls in the side condition of two
of the heating rules, where the Val sort was previously used.
Second, we have removed the rule priorities on the heating rules and the use of
the Val sort on the cooling rules, and replaced them with the heat and
cool attributes. These attributes instruct the compiler that these rules are
heating and cooling rules, and thus should implicitly apply only when certain
terms on the LHS either are or are not a KResult (i.e., isKResult returns
true versus false).
Third, we have renamed some of the variables in the heating and cooling rules
to the special variable HOLE. Syntactically, HOLE is just a special name
for a variable, but it is treated specially by the compiler. By naming a
variable HOLE, we have informed the compiler which term is being heated
or cooled. The compiler will automatically insert the side condition
requires isKResult(HOLE) to cooling rules and the side condition
requires notBool isKResult(HOLE) to heating rules.
Exercise
Modify LESSON-14-A to add rules to evaluate integer subtraction.
Simplifying further with Contexts
The above example is still rather cumbersome to write. We must explicitly write both the heating and the cooling rule separately, even though they are essentially inverses of one another. It would be nice to instead simply indicate which terms should be heated and cooled, and what part of them to operate on.
To do this, K introduces a new type of sentence, the context. Contexts
begin with the context keyword instead of the rule keyword, and usually
do not contain a rewrite operator.
Consider the following definition which is equivalent to LESSON-14-A
(lesson-14-b.k):
kmodule LESSON-14-B-SYNTAX imports UNSIGNED-INT-SYNTAX imports BOOL-SYNTAX syntax Exp ::= Int | Bool > left: Exp "+" Exp > left: Exp "&&" Exp endmodule module LESSON-14-B imports LESSON-14-B-SYNTAX imports INT imports BOOL rule <k> I1:Int + I2:Int => I1 +Int I2 ...</k> rule <k> B1:Bool && B2:Bool => B1 andBool B2 ...</k> context <k> E:Exp + HOLE:Exp ...</k> requires isKResult(E) context <k> HOLE:Exp + _:Exp ...</k> context <k> E:Exp && HOLE:Exp ...</k> requires isKResult(E) context <k> HOLE:Exp && _:Exp ...</k> syntax Bool ::= isKResult(K) [function, symbol] rule isKResult(_:Int) => true rule isKResult(_:Bool) => true rule isKResult(_) => false [owise] endmodule
In this example, the heat and cool rules have been removed entirely, as
have been the productions defining the freezers. Don't worry, they still exist
under the hood; the compiler is just generating them automatically. For each
context sentence like above, the compiler generates a #freezer production,
a heat rule, and a cool rule. The generated form is equivalent to the
rules we wrote manually in LESSON-14-A. However, we are now starting to
considerably simplify the definition. Instead of 3 sentences, we just have one.
context alias sentences and the strict and seqstrict attributes
Notice that the contexts we included in LESSON-14-B still seem rather
similar in form. For each expression we want to evaluate, we are declaring
one context for each operand of that expression, and they are each rather
similar to one another. We would like to be able to simplify further by
simply annotating each expression production with information about how
it is to be evaluated instead. We can do this with the seqstrict attribute.
Consider the following definition, once again equivalent to those above
(lesson-14-c.k):
.k .aliasmodule LESSON-14-C-SYNTAX imports UNSIGNED-INT-SYNTAX imports BOOL-SYNTAX syntax Exp ::= Int | Bool > left: Exp "+" Exp [seqstrict(exp; 1, 2)] > left: Exp "&&" Exp [seqstrict(exp; 1, 2)] endmodule module LESSON-14-C imports LESSON-14-C-SYNTAX imports INT imports BOOL rule <k> I1:Int + I2:Int => I1 +Int I2 ...</k> rule <k> B1:Bool && B2:Bool => B1 andBool B2 ...</k> context alias [exp]: <k> HERE ...</k> syntax Bool ::= isKResult(K) [function, symbol] rule isKResult(_:Int) => true rule isKResult(_:Bool) => true rule isKResult(_) => false [owise] endmodule
This definition has two important changes from the one above. The first is
that the individual context sentences have been removed and have been
replaced with a single context alias sentence. You may notice that this
sentence begins with an identifier in square brackets followed by a colon. This
syntax is a way of naming individual sentences in K for reference by the tool
or by other sentences. The context alias sentence also has a special variable
HERE.
The second is that the productions in LESSON-14-C-SYNTAX have been given a
seqstrict attribute. The value of this attribute has two parts. The first
is the name of a context alias sentence. The second is a comma-separated list
of integers. Each integer represents an index of a non-terminal in the
production, counting from 1. For each integer present, the compiler implicitly
generates a new context sentence according to the following rules:
- The compiler starts by looking for the
context aliassentence named. If there is more than one, then onecontextsentence is created percontext aliassentence with that name. - For each
contextcreated, the variableHEREin the context alias is substituted with an instance of the production theseqstrictattribute is attached to. Each child of that production is a variable. The non-terminal indicated by the integer offset of theseqstrictattribute is given the nameHOLE. - For each integer offset prior in the list to the one currently being
processed, the predicate
isKResult(E)is conjuncted together and included as a side condition, whereEis the child of the production term with that offset, starting from 1. For example, if the attribute lists1, 2, then the rule generated for the2will includeisKResult(E1)whereE1is the first child of the production.
As you can see if you work through the process, the above code will ultimately
generate the same contexts present in LESSON-14-B.
Finally, note that there are a few minor syntactic conveniences provided by the
seqstrict attribute. First, in the special case of the context alias sentence
being <k> HERE ...</k>, you can omit both the context alias sentence
and the name from the seqstrict attribute.
Second, if the numbered list of offsets contains every non-terminal in the production, it can be omitted from the attribute value.
Thus, we can finally produce the idiomatic K definition for this example
(lesson-14-d.k):
kmodule LESSON-14-D-SYNTAX imports UNSIGNED-INT-SYNTAX imports BOOL-SYNTAX syntax Exp ::= Int | Bool > left: Exp "+" Exp [seqstrict] > left: Exp "&&" Exp [seqstrict] endmodule module LESSON-14-D imports LESSON-14-D-SYNTAX imports INT imports BOOL rule <k> I1:Int + I2:Int => I1 +Int I2 ...</k> rule <k> B1:Bool && B2:Bool => B1 andBool B2 ...</k> syntax Bool ::= isKResult(K) [function, symbol] rule isKResult(_:Int) => true rule isKResult(_:Bool) => true rule isKResult(_) => false [owise] endmodule
Exercise
Modify LESSON-14-D to add a production and rule to evaluate integer
subtraction.
Nondeterministic evaluation order with the strict attribute
Thus far, we have focused entirely on deterministic evaluation order. However,
not all languages are deterministic in the order they evaluate expressions.
For example, in C, the expression a() + b() + c() is guaranteed to parse
to (a() + b()) + c(), but it is not guaranteed that a will be called before
b before c. In fact, this evaluation order is non-deterministic.
We can express non-deterministic evaluation orders with the strict attribute.
Its behavior is identical to the seqstrict attribute, except that step 3 in
the above list (with the side condition automatically added) does not take
place. In other words, if we wrote syntax Exp ::= Exp "+" Exp [strict]
instead of syntax Exp ::= Exp "+" Exp [seqstrict], it would generate the
following two contexts instead of the ones found in LESSON-14-B:
context <k> _:Exp + HOLE:Exp ...</k>
context <k> HOLE:Exp + _:Exp ...</k>
As you can see, these contexts will generate heating rules that can both
apply to the same term. As a result, the choice of which heating rule
applies first is non-deterministic, and as we saw in Lesson 1.13, we can
get all possible behaviors by passing --search to krun.
Exercises
-
Add integer division to
LESSON-14-D. Make division and additionstrictinstead ofseqstrict, and write a rule evaluating integer division with a side condition that the denominator is non-zero. Runkrun --searchon the program1 / 0 + 2 / 1and observe all possible outputs of the program. How many are there total, and why? -
Rework your solution from Lesson 1.9, Exercise 2 to evaluate expressions from left to right using the
seqstrictattribute.
Next lesson
Once you have completed the above exercises, you can continue to Lesson 1.15: Configuration Declarations and Cell Nesting.