A tiny GCC front end – Part 9
Today we will do something relatively easy: let's add a way to declare boolean variables and express boolean literals.
Syntax
Since tiny already has a boolean type (for instance when doing a comparison like a > b
) it is just a matter of making it explicit in the language. First let's extend the syntax of types. Some programming languages call this type logical, but we will call it bool
.
〈type〉 → int
| float
| bool
| 〈type〉[
〈expression〉]
| 〈type〉(
〈expression〉:
〈expression〉)
Booleans only have two values: true and false. Technically speaking we already can express these two values in many different ways. For instance a way to express a true value is 1 = 1 and a false value is 1 != 1. So technically, nothing else is mandatory at this point. That said, this would be a poor language design choice, as it would make our programs look a bit weird. So we will add two boolean literals true
and false
that express a true boolean value and a false boolean value respectively.
We will have to extend our primary syntax.
〈primary〉 → (
expression )
| 〈identifier〉
| 〈integer-literal〉
| 〈bool-literal〉
| 〈float-literal〉
| 〈string-literal〉
| 〈array-element〉
〈bool-literal〉 → true
| false
Semantics
bool
designates the boolean type of tiny.
A 〈bool-literal〉 of the form true
is an expression with boolean type and true boolean value. Similarly, a 〈bool-literal〉 of the form false
is an expression with boolean type and false boolean value.
Note that in contrast to some programming languages (like C/C++), boolean and integer are different types in tiny and there are no implicit conversions between them.
Implementation
Given that much of the required infrastructure is already there, adding boolean types and literals is quite straightforward.
Lexer
We only have to define three new tokens bool, true and false. Since they are keywords, nothing else is required in the lexer.
diff --git a/gcc/tiny/tiny-token.h b/gcc/tiny/tiny-token.h
index 2d81386..fe4974e 100644
@@ -44,9 +44,11 @@ namespace Tiny
TINY_TOKEN (RIGHT_SQUARE, "]") \
\
TINY_TOKEN_KEYWORD (AND, "and") \
+ TINY_TOKEN_KEYWORD (BOOL, "bool") \
TINY_TOKEN_KEYWORD (DO, "do") \
TINY_TOKEN_KEYWORD (ELSE, "else") \
TINY_TOKEN_KEYWORD (END, "end") \
+ TINY_TOKEN_KEYWORD (FALSE_LITERAL, "false") \
TINY_TOKEN_KEYWORD (FLOAT, "float") \
TINY_TOKEN_KEYWORD (FOR, "for") \
TINY_TOKEN_KEYWORD (IF, "if") \
@@ -56,6 +58,7 @@ namespace Tiny
TINY_TOKEN_KEYWORD (READ, "read") \
TINY_TOKEN_KEYWORD (THEN, "then") \
TINY_TOKEN_KEYWORD (TO, "to") \
+ TINY_TOKEN_KEYWORD (TRUE_LITERAL, "true") \
TINY_TOKEN_KEYWORD (VAR, "var") \
TINY_TOKEN_KEYWORD (WHILE, "while") \
TINY_TOKEN_KEYWORD (WRITE, "write") \
Parser
Member function Parser::parse_type
will have to recognize the bool token. The GENERIC tree type used will be boolean_type_node
(we already use this one in relational operators).
@@ -551,6 +552,10 @@ Parser::parse_type ()
lexer.skip_token ();
type = float_type_node;
break;
+ case Tiny::BOOL:
+ lexer.skip_token ();
+ type = boolean_type_node;
+ break;
default:
unexpected_token (t);
return Tree::error ();
Finally, member function Parser::null_denotation has to handle the two new literals.
@@ -1333,6 +1338,18 @@ Parser::null_denotation (const_TokenPtr tok)
tok->get_locus ());
}
break;
+ case Tiny::TRUE_LITERAL :
+ {
+ return Tree (build_int_cst_type (boolean_type_node, 1),
+ tok->get_locus ());
+ }
+ break;
+ case Tiny::FALSE_LITERAL :
+ {
+ return Tree (build_int_cst_type (boolean_type_node, 0),
+ tok->get_locus ());
+ }
+ break;
case Tiny::LEFT_PAREN:
Note that GCC function build_int_cst_type
constructs a GENERIC tree with code INTEGER_CST
. This does not mean that he node must have integer type. In our case a true boolean value will be represented using the integer 1 (and 0 for the false value), but note that the tree itself has boolean_type_node
.
Nothing else is required. Compared to arrays this was easy-peasy.
Smoke test
Now we can use boolean variables and use them as operators of logical operators.
var a : bool;
var b : bool;
a := true;
b := false;
if a
then
write "OK 1";
end
if not b
then
write "OK 2";
end
if a or b
then
write "OK 3";
end
if b or a
then
write "OK 4";
end
if not (a and b)
then
write "OK 5";
end
if not (b and a)
then
write "OK 6";
end
$ gcctiny -o bool bool.tiny
$ ./bool
OK 1
OK 2
OK 3
OK 4
OK 5
OK 6
Yay!
Now we can rewrite our bubble.tiny
program from part 8 in a nicer way.
--- bubble.orig.tiny 2016-01-31 10:28:22.486504492 +0100
+++ bubble.new.tiny 2016-01-31 10:28:58.314177652 +0100
@@ -15,11 +15,11 @@
# Very inefficient bubble sort used
# only as an example
-var swaps : int;
-swaps := 1;
-while swaps > 0
+var done : bool;
+done := false;
+while not done
do
- swaps := 0;
+ done := true;
for i := 1 to n - 1
do
if a[i - 1] > a[i]
@@ -28,7 +28,7 @@
t := a[i-1];
a[i-1] := a[i];
a[i] := t;
- swaps := swaps + 1;
+ done := false;
end
end
end
That's all for today