Skip to content

Commit

Permalink
Refactored so that functions can be declared before the statements th…
Browse files Browse the repository at this point in the history
…at comprise them. Manually tested that var declaration counting is working
  • Loading branch information
Isaiah Becker-Mayer committed Apr 7, 2021
1 parent 570f5f2 commit 6792483
Show file tree
Hide file tree
Showing 11 changed files with 4,097 additions and 69 deletions.
104 changes: 35 additions & 69 deletions Compiler/pkg/compiler/compilationengine.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
package compiler

import (
"encoding/xml"
"errors"
"fmt"
"log"
"os"
"runtime"
"strconv"
)

//SyntaxError logs the function name, file, and line number
Expand All @@ -29,7 +27,6 @@ type CompilationEngine struct {
jackFilePath string // The name of the .jack input file to be compiled.
jt *JackTokenizer // A tokenizer set up to tokenize the file we want to compile
outputFile *os.File // The output file
xmlEnc *xml.Encoder // The xml encoder for testing
st *SymbolTable // The symbol table
className string // The class name being compiled. Set at compileClass
}
Expand All @@ -50,16 +47,12 @@ func NewCompilationEngine(jackFilePath string) (*CompilationEngine, error) {
ce.jt = jt

// Create the output file
outputFile, err := os.Create(fmt.Sprintf("%v_out.xml", ce.jackFilePath[0:len(ce.jackFilePath)-len(".jack")]))
outputFile, err := os.Create(fmt.Sprintf("%v.vm", ce.jackFilePath[0:len(ce.jackFilePath)-len(".jack")]))
if err != nil {
return nil, err
}
ce.outputFile = outputFile

// Create xml encoder
ce.xmlEnc = xml.NewEncoder(outputFile)
ce.xmlEnc.Indent("", " ")

// Create the symbol table
st := NewSymbolTable()
ce.st = st
Expand All @@ -70,7 +63,6 @@ func NewCompilationEngine(jackFilePath string) (*CompilationEngine, error) {
// Run runs the compiler on ce.jackFilePath
func (ce *CompilationEngine) Run() error {
defer ce.outputFile.Close()
defer ce.xmlEnc.Flush()

// Advance to eat the first token and call compileClass, which will recursively compile the entire file
if err := ce.advance(); err != nil {
Expand All @@ -79,26 +71,6 @@ func (ce *CompilationEngine) Run() error {
return ce.compileClass()
}

// Shorthand for opening an xml tag
func (ce *CompilationEngine) openXMLTag(tag string) error {
return ce.xmlEnc.EncodeToken(xml.StartElement{Name: xml.Name{Space: "", Local: tag}, Attr: []xml.Attr{}})
}

// Shorthand for writing raw data in an XML tag, prefixed and postfixed with a space character
func (ce *CompilationEngine) writeXMLData(data string) error {
return ce.xmlEnc.EncodeToken(xml.CharData(fmt.Sprintf(" %v ", data)))
}

// Shorthand for closing an xml tag
func (ce *CompilationEngine) closeXMLTag(tag string) error {
return ce.xmlEnc.EncodeToken(xml.EndElement{Name: xml.Name{Space: "", Local: tag}})
}

// Shorthand for calling jt.MarshalXML to take advantage of already existing xml encoding logic
func (ce *CompilationEngine) marshaljt() error {
return ce.jt.MarshalXML(ce.xmlEnc, xml.StartElement{Name: xml.Name{Space: "not used", Local: "not used"}, Attr: []xml.Attr{}})
}

// advance throws an error if there are no more tokens, else it returns ce.jt.Advance()
func (ce *CompilationEngine) advance() error {
if !ce.jt.HasMoreTokens() {
Expand Down Expand Up @@ -273,14 +245,17 @@ func (ce *CompilationEngine) compileSubroutine() error {
// Clear the subroutine table
ce.st.StartSubroutine()

// Confirm that current token is "constructor" or "function" or "method"
// Calling function checked that current token is "constructor" or "function" or "method"
if kw, err := ce.jt.KeyWord(); !(kw == "constructor" || kw == "function" || kw == "method") {
if err != nil {
return SyntaxError(err)
}
return SyntaxError(fmt.Errorf("expected %v \"constructor\" or \"function\" or \"method\"", keyWord))
}

// begin to declare the function
ce.outputFile.WriteString(fmt.Sprintf("function %v.", ce.className))

if err := ce.advance(); err != nil {
return err
}
Expand All @@ -295,10 +270,11 @@ func (ce *CompilationEngine) compileSubroutine() error {
if err := ce.advance(); err != nil {
return err
}
_, err = ce.jt.Identifier()
subroutineName, err := ce.jt.Identifier()
if err != nil {
return SyntaxError(err)
}
ce.outputFile.WriteString(fmt.Sprintf("%v ", subroutineName))

// Eat the subroutineName
if err := ce.advance(); err != nil {
Expand All @@ -309,16 +285,7 @@ func (ce *CompilationEngine) compileSubroutine() error {
return SyntaxError(err)
}

if err := ce.compileSubroutineBody(); err != nil {
return SyntaxError(err)
}

return nil
}

// '{' varDec* statements '}'
func (ce *CompilationEngine) compileSubroutineBody() error {
var err error
// subroutineBody: '{' varDec* statements '}'

// Eat what should be a "{"
if err = ce.advance(); err != nil {
Expand All @@ -327,23 +294,20 @@ func (ce *CompilationEngine) compileSubroutineBody() error {
if err = ce.checkForSymbol("{"); err != nil {
return SyntaxError(err)
}

// Eat the "{"
if err = ce.advance(); err != nil {
return SyntaxError(err)
}

// varDec*
for kw, _ := ce.jt.KeyWord(); kw == "var"; kw, _ = ce.jt.KeyWord() {
if err = ce.compileVarDec(); err != nil {
return SyntaxError(err)
}
// compiled a varDec, advance and try again or move on
if err = ce.advance(); err != nil {
return SyntaxError(err)
}
if err := ce.compileVarDecs(); err != nil {
return SyntaxError(err)
}

// now that all the vars have been declared, we know how to declare the vm code function
ce.outputFile.WriteString(fmt.Sprintf("%v\n", ce.st.VarCount(KIND_VAR)))

// statements
if err = ce.compileStatements(); err != nil {
return SyntaxError(err)
}
Expand All @@ -355,6 +319,24 @@ func (ce *CompilationEngine) compileSubroutineBody() error {
return nil
}

// varDec*
// compileVarDecs compiles all the variable declarations, so that when it returns a call to
// ce.st.VarCount(KIND_VAR) will give you the number of local variables for the current subroutine
func (ce *CompilationEngine) compileVarDecs() error {
var err error
// varDec*
for kw, _ := ce.jt.KeyWord(); kw == "var"; kw, _ = ce.jt.KeyWord() {
if err = ce.compileVarDec(); err != nil {
return SyntaxError(err)
}
// compiled a varDec, advance and try again or move on
if err = ce.advance(); err != nil {
return SyntaxError(err)
}
}
return nil
}

// checks that token is identifier and returns it, or a error
func (ce *CompilationEngine) getVarName() (string, error) {
id, err := ce.jt.Identifier()
Expand Down Expand Up @@ -497,7 +479,7 @@ func (ce *CompilationEngine) compileVarDec() error {
}

// Add this var to symbol table as well
ce.st.Define(name, type_, KIND_ARG)
ce.st.Define(name, type_, KIND_VAR)

// Advance to either the next "," and repeat the loop, or break and check for ";"
if err := ce.advance(); err != nil {
Expand Down Expand Up @@ -613,9 +595,6 @@ func (ce *CompilationEngine) compileSubroutineCall() error {
// 'do' subroutineCall ';'
// Eats it's own final character, so caller needn't immediately call advance() upon this function returning
func (ce *CompilationEngine) compileDo() error {
ce.openXMLTag("doStatement")
defer ce.closeXMLTag("doStatement")

if err := ce.checkForKeyword("do"); err != nil {
return SyntaxError(err)
}
Expand Down Expand Up @@ -711,9 +690,6 @@ func (ce *CompilationEngine) compileLet() error {
// 'while '(' expression ')' '{' statements '}'
// eats its own final token before returning, so calling function can expect to already be on the next token
func (ce *CompilationEngine) compileWhile() error {
ce.openXMLTag("whileStatement")
defer ce.closeXMLTag("whileStatement")

if err := ce.checkForKeyword("while"); err != nil {
return SyntaxError(err)
}
Expand Down Expand Up @@ -820,9 +796,6 @@ func (ce *CompilationEngine) compileIf() error {
return nil
}

ce.openXMLTag("ifStatement")
defer ce.closeXMLTag("ifStatement")

if err := ce.checkForKeyword("if"); err != nil {
return SyntaxError(err)
}
Expand Down Expand Up @@ -915,21 +888,15 @@ func (ce *CompilationEngine) compileExpression() error {
// '(' expression ')' | unaryOp term
func (ce *CompilationEngine) compileTerm() error {
if ce.jt.TokenType() == intConst {
ce.openXMLTag("integerConstant")
ic, err := ce.jt.IntVal()
_, err := ce.jt.IntVal()
if err != nil {
return SyntaxError(err)
}
ce.writeXMLData(strconv.Itoa(ic))
ce.closeXMLTag("integerConstant")
} else if ce.jt.TokenType() == strConst {
ce.openXMLTag("stringConstant")
sc, err := ce.jt.StringVal()
_, err := ce.jt.StringVal()
if err != nil {
return SyntaxError(err)
}
ce.writeXMLData(sc)
ce.closeXMLTag("stringConstant")
} else if ce.jt.TokenType() == keyWord {
kw, err := ce.jt.KeyWord()
if err != nil {
Expand All @@ -938,7 +905,6 @@ func (ce *CompilationEngine) compileTerm() error {
if !(kw == "true" || kw == "false" || kw == "null" || kw == "this") {
return SyntaxError(fmt.Errorf("term keyWord must be one of \"true\", \"false\", \"null\", or \"this\""))
}
return ce.marshaljt()
} else if ce.jt.TokenType() == symbol {
// '(' expression ')' | unaryOp term
sym, err := ce.jt.Symbol()
Expand Down
2 changes: 2 additions & 0 deletions Compiler/test/Seven/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# ignore the files we generate with the compiler
Main.vm
23 changes: 23 additions & 0 deletions Compiler/test/Seven/Array.vm
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
function Array.new 0
push argument 0
push constant 0
gt
not
if-goto IF_TRUE0
goto IF_FALSE0
label IF_TRUE0
push constant 2
call Sys.error 1
pop temp 0
label IF_FALSE0
push argument 0
call Memory.alloc 1
return
function Array.dispose 0
push argument 0
pop pointer 0
push pointer 0
call Memory.deAlloc 1
pop temp 0
push constant 0
return
102 changes: 102 additions & 0 deletions Compiler/test/Seven/Keyboard.vm
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
function Keyboard.init 0
push constant 0
return
function Keyboard.keyPressed 0
push constant 24576
call Memory.peek 1
return
function Keyboard.readChar 2
push constant 0
call Output.printChar 1
pop temp 0
label WHILE_EXP0
push local 1
push constant 0
eq
push local 0
push constant 0
gt
or
not
if-goto WHILE_END0
call Keyboard.keyPressed 0
pop local 0
push local 0
push constant 0
gt
if-goto IF_TRUE0
goto IF_FALSE0
label IF_TRUE0
push local 0
pop local 1
label IF_FALSE0
goto WHILE_EXP0
label WHILE_END0
call String.backSpace 0
call Output.printChar 1
pop temp 0
push local 1
call Output.printChar 1
pop temp 0
push local 1
return
function Keyboard.readLine 5
push constant 80
call String.new 1
pop local 3
push argument 0
call Output.printString 1
pop temp 0
call String.newLine 0
pop local 1
call String.backSpace 0
pop local 2
label WHILE_EXP0
push local 4
not
not
if-goto WHILE_END0
call Keyboard.readChar 0
pop local 0
push local 0
push local 1
eq
pop local 4
push local 4
not
if-goto IF_TRUE0
goto IF_FALSE0
label IF_TRUE0
push local 0
push local 2
eq
if-goto IF_TRUE1
goto IF_FALSE1
label IF_TRUE1
push local 3
call String.eraseLastChar 1
pop temp 0
goto IF_END1
label IF_FALSE1
push local 3
push local 0
call String.appendChar 2
pop local 3
label IF_END1
label IF_FALSE0
goto WHILE_EXP0
label WHILE_END0
push local 3
return
function Keyboard.readInt 2
push argument 0
call Keyboard.readLine 1
pop local 0
push local 0
call String.intValue 1
pop local 1
push local 0
call String.dispose 1
pop temp 0
push local 1
return
17 changes: 17 additions & 0 deletions Compiler/test/Seven/Main.jack
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// This file is part of www.nand2tetris.org
// and the book "The Elements of Computing Systems"
// by Nisan and Schocken, MIT Press.
// File name: projects/11/Seven/Main.jack

/**
* Computes the value of 1 + (2 * 3) and prints the result
* at the top-left of the screen.
*/
class Main {

function void main() {
do Output.printInt(1 + (2 * 3));
return;
}

}
Loading

0 comments on commit 6792483

Please sign in to comment.