Programming Language Foundations

Booleans and Conditional Computation


Learning Objectives

  • You can define a simple language using formation rules and represent it as an AST in Rust.
  • You understand how evaluation rules for if-then-else work.
  • You know what syntax sugar is and how desugaring (lowering) works.
  • You can implement desugaring transformations from a sugared AST to a core language.

Overview

In this chapter we will focus on another language, which instead of operating on integers, operates on booleans. We start by defining its grammar with formation rules, followed by a Rust enum representing its abstract syntax. After that, we naturally define how its terms evaluate to values. Finally, we will learn about lowering surface language to a lower, less human-readable language with fewer operations.

In the next chapter, we will merge the prefix arithmetic language and this boolean language.

Boolean Language ℬ︀

Our boolean language, denoted ℬ︀, has two literals true and false. In addition, we will initially only have one boolean operation: if-then-else (the boolean recursor). For example, the following is a term in the language:

if true then false else true

The if-then-else operation might be familiar to some as the ternary conditional operator (? :) available in some programming languages.

Grammar of The Boolean Language

Together, true, false and if-then-else are expressed with the following formation rules:

trueℬ︀falseℬ︀𝑡1ℬ︀𝑡2ℬ︀𝑡3ℬ︀ite𝑓if𝑡1then𝑡2else𝑡3ℬ︀

The first two are “axioms” so we treat them as base rules and don’t bother drawing a line on top.

As an example, the term if true then false else true has the following formation tree:

trueℬ︀falseℬ︀trueℬ︀ite𝑓if true then false else trueℬ︀

AST in Rust

The type of abstract syntax tree for the boolean language can be written as the following enum in Rust

enum AST {
True,
False,
Ite {
cond: Box<AST>,
if_true: Box<AST>,
if_false: Box<AST>,
}
}

Evaluation Rules

We will use 𝟚 for the set/type of booleans, i.e. the two-element set {0,1} where 0 denotes false and 1 true.

The evaluation relation has the following shape: 𝑡𝑏, where 𝑡ℬ︀ and 𝑏{0,1}. The base rules are straight-forward:

false0true1

Let’s try to think about what the evaluation rules of if-then-else look like.

?if𝑡1then𝑡2else𝑡3?

First, what are the possible values of an if-then-else expression? Clearly there are two options:

  • The value of 𝑡2 if 𝑡1 evaluates to true.
  • The value of 𝑡3 if 𝑡1 evaluates to false.

In addition, even though it’s mathematically irrelevant, we want to avoid unnecessarily evaluating the other branch.

Next, what are the premises in each option?

  • If 𝑡1 evaluates to true, we need for 𝑡2 to evaluate to some value, e.g. 𝑡2𝑏2.
  • If 𝑡1 evaluates to false, we need for 𝑡3 to evaluate to some value, e.g. 𝑡3𝑏3.

These two options can be defined nicely with two evaluation rules.

𝑡11𝑡2𝑏2IfTrueif𝑡1then𝑡2else𝑡3𝑏2𝑡10𝑡3𝑏3IfFalseif𝑡1then𝑡2else𝑡3𝑏3

Alternative IfTrue Rule

0 / 3 points

How does evaluation change if instead of the IfTrue rule, we use the following rule?

Alternative IfTrue Rule proof tree

Ignoring the Condition

0 / 3 points

How does evaluation change if we add the following rule (in addition to the existing rules)?

Ignoring the Condition proof tree

Evaluating Both Branches

0 / 3 points

How does evaluation change if instead of the IfFalse rule, we use the following rule?

Evaluating Both Branches proof tree

Instances of IfTrue

0 / 2 points

How many different values can b₂ take in valid instances of the IfTrue rule?

Instances of IfTrue proof tree

Eval for Boolean Terms

0 / 20 points

Implement AST::eval.

Syntax Sugar

Syntax sugar is the term used for syntax which gets converted to a lower, less human readable syntax. For example lists in 𝕊𝕋𝕃++ can be written as [1, 2, 3:Int] which is not converted to an AST node List(1, 2, 3) but rather to a nested Cons(1, Cons(2, Cons(3, Nil(Integer)))) structure.

The process of lowering (or desugaring) syntax sugar can take place between a high-level AST and a lower level AST. One of the primary reasons behind lowering syntax sugar comes the fact that evaluation is likely simpler to implement at the lower level.

Let’s add some more syntax and operators to ℬ︀:

𝑡ℬ︀not𝑓!𝑡ℬ︀𝑡1ℬ︀𝑡2ℬ︀and𝑓𝑡1and𝑡2ℬ︀𝑡1ℬ︀𝑡2ℬ︀or𝑓𝑡1or𝑡2ℬ︀𝑡1ℬ︀𝑡2ℬ︀xor𝑓𝑡1xor𝑡2ℬ︀

We represent the binary operators in infix style (as opposed to prefix) as we don’t need to write a parser for them and it makes them more natural to read.

We would like to avoid having to define any extra semantics for these operators by desugaring the sugared syntax to the “core language” consisting only of true, false and if-then-else terms. After parsing an expression in ℬ︀, we can lower it to an AST using a translation function.

This is a nice way programming languages can add convenient features which don’t fundamentally alter the behavior of the language.

First, let’s define the SugaredAST in Rust:

enum AST {
True,
False,
Ite {
cond: Box<AST>,
if_true: Box<AST>,
if_false: Box<AST>,
},
Not(Box<AST>),
And(Box<AST>, Box<AST>),
Or(Box<AST>, Box<AST>),
Xor(Box<AST>, Box<AST>),
}

Let’s denote the translate function with double square bracket notation

:SugaredASTAST

We want the unsugared fragment (True, False and Ite) of the sugared language to effectively “remain unchaged” during the desugaring.

SugaredAST::True=AST::TrueSugaredAST::False=AST::Falseif𝑡1then𝑡2else𝑡3=if𝑡1then𝑡2else𝑡3

Next, let’s start with how to desugar ! b. The definition of negation of a boolean 𝑏{0,1} is:

¬𝑏={01if𝑏=1otherwise

This definition has the mathematical structure of an if-then-else expression. Therefore it’s straight-forward to encode it in AST with an if-then-else:

! t=if𝑡then false else true

Pulling Back Across The Sugar

Using this operation, we can simply pull back the evaluation function to get

evalSugaredAST:SugaredAST𝟚evalSugaredAST(𝑡)=evalAST𝑡

Now all of the following terms evaluate to the values we expect.

ExampleThe following statements are true:evalSugaredAST(false)=0evalSugaredAST(if false then true else not false)=1evalSugaredAST(false or (false and true))=0evalSugaredAST(false xor true)=1

In the next exercise, your task is to desugar And, Or, Xor into just True, False and Ite.

Desugar Sugared AST

0 / 100 points

Start by copying your AST::eval solution from the previous exercise.

Next, implement SugaredAST::Desugar such that And, Or and Xor (exclusive or) can be desugared to terms using just True, False and Ite of the core AST.

Only the evaluation after desugaring is tested, i.e. the implementation details of your translation don’t matter as long as the evaluated value is correct. You should not add any code in SugaredAST::eval.

Tip: The assertion failures print the expression that doesn’t evaluate to the expected value after “sugared expression:”

If-Then-Else Formatter

0 / 15 points

Implement AST::format which converts an AST to a formatted string with line breaks between if, then, and else.