30 August 2009

5. Base 2

If you think I'm talking about baseball here, you're way off base. (That was a baseball expression. Not the kind of expressions we are working with.)
What we need is a new base.pb with more functionality. Make a copy of base.pb and name it base2.pb.

We want to add some new stuff. Here is a list:
- Move IsDigit() and GetDigit() into base2.pb
- Add support for integers consisting of multiple digits (like 128)
- Add support for names (IsAlpha(), IsAlphaNumeric() and GetName())
- Add support for whitespace

You should try to write these procedures yourself. But not without some guidance. Here's what you do:

- Copy IsDigit() from add2.pb into base2.pb

- Write a new procedure IsWhite(). BNF for whitespace:
white ::= #SPACE | #TAB
Return 1 if yes, 0 else.

- Write a procedure EatWhite() which calls GetLook() while Look is whitespace.

- Write GetLookWhite() which calls GetLook() and then EatWhite().

- Write a new procedure: GetInteger() to replace GetDigit(). Here is the BNF grammar for what input it should accept:
integer ::= <digit> {<digit>}
The integer should be returned as a string. If you're into error handling, give an error if the number is outside the range of a 32-bit signed integer. Be sure to give an error if Look is not a digit when the procedure is called (because this is REQUIRED, see the BNF).
Since we want support for whitespace after numbers (so you can type 1+ 2 instead of 1+2) call EatWhite() at the end.

- Write a new procedure IsAlpha(C.c) that returns 1 if the parameter is an alphabetic character. Here is the BNF:
alpha ::= A..Z | _ | $
As you can see, I included underline and $. You may or may not want to do that.

- Write a new procedure IsAlphaNumeric(). Use IsAlpha() and IsDigit() to prevent doing the same work twice.

- Write a new procedure GetName() that returns a name as a string. If you ever heard the word "identifier", this is the same, only a much longer word. Grammar:
name ::= <alpha> {<digit> | <alpha>}
Notice that a name must start with an alpha (defined above), but subsequent characters may be alphanumeric.
Call EatWhite() at the end to allow for program whitespace.

Test all your procedures to make sure they do what you expect.

Here's my new base2.pb. You should compare your procedures with mine to be sure they work the same:
Global Look.c
Global Instream.s

Procedure GetLook()
Look = Asc(Instream)
Instream = Mid(Instream, 2)
EndProcedure

Procedure Error(E.s)
PrintN("Error: " + E)
Input()
End
EndProcedure

Procedure Expected(S.s)
Error(S + " expected.")
EndProcedure

Procedure Emit(S.s)
PrintN(#TAB$ + S)
EndProcedure

Procedure Init()
OpenConsole()
Instream = Input()
GetLook()
EndProcedure

;- Additions:

Procedure IsDigit(C.c)
If C >= '0' And C <= '9'
ProcedureReturn 1
EndIf
EndProcedure

Procedure IsAlpha(C.c)
Select C
Case 'a' To 'z', 'A' To 'Z', '_', '$'
ProcedureReturn 1
EndSelect
EndProcedure

Procedure IsAlphaNumeric(C.c)
If IsAlpha(C) Or IsDigit(C)
ProcedureReturn 1
EndIf
EndProcedure

Procedure IsWhite(C.c)
If C = ' ' Or C = #TAB
ProcedureReturn 1
EndIf
EndProcedure

Procedure EatWhite()
While IsWhite(Look)
GetLook()
Wend
EndProcedure

Procedure.s GetInteger()
Protected Name.s
While IsDigit(Look)
Name + Chr(Look)
GetLook()
Wend
If Not Name
Expected("Integer")
EndIf
EatWhite()
ProcedureReturn Name
EndProcedure

Procedure.s GetName()
Protected Name.s
While IsAlphaNumeric(Look)
Name + Chr(Look)
GetLook()
Wend
If Not Name
Expected("Name")
EndIf
EatWhite()
ProcedureReturn Name
EndProcedure

Procedure GetLookWhite()
GetLook()
EatWhite()
EndProcedure

With so much new features it would be sad not to use them right away. So make a copy of add2.pb (call it add2_base2.pb).
You only need to make three changes to it. The first is to include base2.pb instead of base.pb. Then change GetDigit() into GetInteger() and GetLook() in AddExp() into GetLookWhite().
The result: we now handle any integers, including arbitary whitespace. Try, for example,
"128 +    56+8"


Here it is:
; add2_base2.pb
IncludeFile "..\base2.pb" ; Changed from base.pb
Init()

Procedure Value()
; value ::= <integer>
Emit("mov eax, " + GetInteger()) ; Changed from GetDigit()
EndProcedure

Procedure AddExp()
; addexp ::= <value> {+ <value>}
Value()
While Look = '+'
GetLookWhite() ; Changed from GetLook()
Emit("push eax")
Value()
Emit("pop ecx")
Emit("add eax, ecx")
Wend
EndProcedure

Procedure Expression()
; expression ::= <addexp>
AddExp()

If Look <> 0
Expected("End of line")
EndIf
EndProcedure

; Parse an expression
Expression()
Input()

No comments: