11 September 2009

13. Loops

The tutorial on if's was quite long, so i'll try to make this a short one. What we'll do is to make some loops. First the while loop.

The relevant parts of the grammar is as following:
...
while ::= while <expression> <block> wend
statement ::= ( <if> | <while> | <assignment> ) <newline>
...


Make a copy of the file with the if statements and call it while.pb.

The first thing to do, is to add the follow token "wend" to the list recognized in Block():

...
Case "endif", "else", "wend"
...


Then modify Statement() to recognize a while statement:

Procedure Statement(Name.s)
; statement ::= ( <if> | <while> | <assignment> ) <newline>
Select Name
Case "if": DoIf()
Case "while": DoWhile()
Default: Assignment(Name)
EndSelect
Match(#LF)
EndProcedure


A while loop is just like an if statement, with a goto start at the end:
while expression
...
wend
equals
ifstart:
if expression
...
jmp ifstart
endif


Thus, DoWhile() will be pretty similar to the version of DoIf() that didn't support else.

Procedure DoWhile()
; while ::= while <expression> <block> wend
Protected L1.s = NewLabel()
Protected L2.s = NewLabel()
Protected Follow.s

Emit(L1 + ":")
Expression()
Emit("cmp eax, 0")
Emit("jz " + L2)
Follow = Block()
Emit("jmp " + L1)
Emit(L2 + ":")

If Follow <> "wend"
Expected("wend, got " + Follow)
EndIf
EndProcedure


While we're at it, let's implement a for loop. Our for loop will have the following syntax:
for ::= for <variable> = <expression> to <expression> <block> next

A for loop works like this:

First the result of expression 1 is assigned to the variable.
Then we do this:
LblStart:
if expression 2 >= variable
... code within for loop goes here
variable = variable + 1
jmp LblStart
endif
LblStop:


The loop part (after the initial assignment) can also be written as a while loop:
while expression 2 >= variable
variable = variable + 1
wend


You need to add "next" to the list of follow tokens in block, and add "for" to statement() to call DoFor(). Then write DoFor():
Procedure DoFor()
; for ::= for <variable> = <expression>(1) to <expression>(2) <block> next
Protected Variable.s
Protected Temp.s
Protected Follow.s
Protected LblStart.s = NewLabel()
Protected LblStop.s = NewLabel()

Variable = GetName()
MatchWhite('=')
Expression() ; (1)
Emit2("mov ", VarValue(Variable), "eax")

Emit(LblStart + ":")

Temp = GetName()
If Temp <> "to"
Expected("To, got " + Temp)
EndIf

Expression() ; (2)
Emit2("cmp ", "eax", VarValue(Variable))
Emit( "jl " + LblStop)

Follow = Block()
If Follow <> "next"
Expected("Next, got " + Follow)
EndIf

Emit("inc " + VarValue(Variable))
Emit("jmp " + LblStart)

Emit(LblStop + ":")
EndProcedure


Read the code thorougly until you understand it. Step through it with the debugger if necessary.

As you can see, we must go through Block() (and Statement()) to open a for loop. Since the for loop calls Block() for its body, and it uses no global or static variables, we already have support for nested for loops, and if's within for's and for's within if's.

Exercise:
1. Write the grammar and PureBasic code to compile a loop like PureBasic's Repeat ... Until .
2. Write the grammar and PureBasic code to compile a loop that looks like a while loop, but the expression should only be evaluated once, and it should be the number of times that the loop loops.
Example of such a loop:
I = 10 (assignment statement is not part of the loop)
Times I
Print "Hello " + Str(I) (this is some dummy code for the Block())
Loop

The loop would run 10 times, each time printing "Hello 10". (I should not be changed during the loop.) Of course, you only implement the loop, not the print function.
Remember to allow nesting of loops. Hint: The loop counter may be stored on the stack.

Here is the listing (I added variables and parentheses as described in another chapter):


; for.pb
IncludeFile "..\base2.pb"
IncludeFile "..\labels.pb"

Procedure ToBool(reg.s)
Emit("cmp " + reg + ", 0")
Emit("mov ecx, 0")
Emit("setnz cl")
Emit("mov " + reg + ", ecx")
EndProcedure

Procedure RelOp(Op.s)
Emit("cmp ebp, eax")
Emit("mov eax, 0")
Select Op
Case "=": Emit("sete al") ; set if equal
Case "<": Emit("setl al") ; set if less
Case "<=": Emit("setle al") ; less or equal
Case ">": Emit("setg al") ; set if greater
Case ">=": Emit("setge al")
Case "<>": Emit("setne al") ; set if not equal
Default
Expected("Relational operator, got " + Op)
EndSelect
EndProcedure

Procedure.s VarValue(Var.s)
ProcedureReturn "[v_" + Var + "]"
EndProcedure

Declare Expression()
Procedure Value()
; value ::= <integer> | <variable> | '(' <expression> ')'
If IsDigit(Look)
Emit("mov eax, " + GetInteger())
ElseIf IsAlpha(Look)
Emit("mov eax, " + VarValue(GetName()))
ElseIf Look = '('
MatchWhite('(')
Expression()
MatchWhite(')')
Else
Expected("value")
EndIf
EndProcedure

Procedure MulExp()
; mulexp ::= <value> {<mulop> <value>}
Protected Operation.c
Value()
While Look = '*' Or Look = '/' Or Look = '%'
Operation = Look
GetLookWhite()
Emit("push eax")
Value()
Emit("pop ebp")
Select Operation
Case '*': Emit("imul eax, ebp")
Case '/', '%'
Emit("xchg eax, ebp")
Emit("cdq") ; expands eax to edx:eax
Emit("idiv ebp")
EndSelect
If Operation = '%'
Emit("mov eax, edx")
EndIf
Wend
EndProcedure

Procedure AddExp()
; addexp ::= <mulexp> {<addop> <mulexp> }
Protected Operation.c
MulExp()
While Look = '+' Or Look = '-'
Operation = Look
GetLookWhite()
Emit("push eax")
MulExp()
Emit("pop ebp")
Select Operation
Case '+': Emit("add eax, ebp")
Case '-': Emit("sub eax, ebp")
Emit("neg eax")
EndSelect
Wend
EndProcedure

Procedure RelExp()
; relexp ::= <addexp> { <boolop> <addexp> }
Protected Operation.s
AddExp()
While Look = '<' Or Look = '>' Or Look = '='
Operation = Chr(Look)
GetLook()
Select Look
Case '<', '>', '='
Operation + Chr(Look)
GetLook()
EndSelect
EatWhite()
Emit("push eax")
AddExp()
Emit("pop ebp")
RelOp(Operation) ; Generate code
Wend
EndProcedure

Procedure BoolExp()
; boolexp ::= <relexp> { <boolop> <relexp> }
Protected Operation.s
RelExp()
While Look = 'a' Or Look = 'o' Or Look = 'x'
Operation = GetName()
Emit("push eax")
RelExp()
Emit("pop ebp")
ToBool("eax")
ToBool("ebp")
Select Operation
Case "and": Emit("test eax, ebp")
Case "or": Emit("or eax, ebp")
Case "xor": Emit("xor eax, ebp")
Default
Expected("Operator, got " + Operation)
EndSelect
Emit("mov eax, 0")
Emit("setnz al")
Wend
EndProcedure

Procedure Expression()
; expression ::= <BoolExp>
BoolExp()
EndProcedure

Procedure Assignment(Name.s)
; assignment ::= <name> = <expression>
MatchWhite('=')
Expression()
Emit2("mov ", VarValue(Name), "eax")
EndProcedure

Declare.s Block()

Procedure.s DoElse(Label.s)
Protected Label2.s = NewLabel()
Protected Follow.s

Emit("jmp " + Label2)
Emit(Label + ":")
Follow = Block()
Emit(Label2 + ":")

ProcedureReturn Follow
EndProcedure

Procedure DoIf()
Protected Label.s
Protected FollowToken.s
Label = NewLabel() ; Used when jumping around the block

Expression() ; The conditional expression (do we jump or not?)
Emit("cmp eax, 0") ; Check if the expression is true or false
Emit("jz " + Label) ; if it's false (zero) jump to label
FollowToken = Block() ; else fall through to this block (which is always compiled)

If FollowToken = "else"
FollowToken = DoElse(Label)
Else
Emit(Label + ":")
EndIf

If FollowToken <> "endif"
Expected("endif, got " + FollowToken)
EndIf
EndProcedure

Procedure DoWhile()
; while ::= while <expression> <block> wend
Protected L1.s = NewLabel()
Protected L2.s = NewLabel()
Protected Follow.s

Emit(L1 + ":")
Expression()
Emit("cmp eax, 0")
Emit("jz " + L2)
Follow = Block()
Emit("jmp " + L1)
Emit(L2 + ":")

If Follow <> "wend"
Expected("wend, got " + Follow)
EndIf
EndProcedure

Procedure DoFor()
; for ::= for <variable> = <expression>(1) to <expression>(2) <block> next
Protected Variable.s
Protected Temp.s
Protected Follow.s
Protected LblStart.s = NewLabel()
Protected LblStop.s = NewLabel()

Variable = GetName()
MatchWhite('=')
Expression() ; (1)
Emit2("mov ", VarValue(Variable), "eax")

Emit(LblStart + ":")

Temp = GetName()
If Temp <> "to"
Expected("To, got " + Temp)
EndIf

Expression() ; (2)
Emit2("cmp ", "eax", VarValue(Variable))
Emit( "jl " + LblStop)

Follow = Block()
If Follow <> "next"
Expected("Next, got " + Follow)
EndIf

Emit("inc " + VarValue(Variable))
Emit("jmp " + LblStart)

Emit(LblStop + ":")
EndProcedure

Procedure Statement(Name.s)
; statement ::= ( <if> | <while> | <assignment> ) <newline>
Select Name
Case "if": DoIf()
Case "while": DoWhile()
Case "for": DoFor()
Default: Assignment(Name)
EndSelect
Match(#LF)
EndProcedure

Procedure.s Block()
; block ::= { <statement> }
Protected Name.s
EatWhiteNewlines()
While Look <> 0
Name = GetName()
Select Name
Case "endif", "else", "wend", "next"
ProcedureReturn Name
Default
Statement(Name)
EndSelect
EatWhiteNewlines()
Wend
EndProcedure

Procedure Program()
; program ::= <block>
EatWhiteNewlines() ; Allow for whitespace before program start
Protected Follow.s = Block()
If Follow <> ""
Expected("Dangling " + Follow)
EndIf
EndProcedure

InitMulti()
Program()
Input()

No comments: