diff --git a/lib/parser.rb b/lib/parser.rb index 07d43fd..86ef222 100644 --- a/lib/parser.rb +++ b/lib/parser.rb @@ -4,95 +4,57 @@ class Parser attr_reader :input def initialize(input, output) - @input = Enumerator.new do |yielder| - while input.has_more_tokens? - input.advance - - type = input.token_type - text = case type - when Tokenizer::KEYWORD - input.keyword - when Tokenizer::SYMBOL - input.symbol - when Tokenizer::IDENTIFIER - input.identifier - when Tokenizer::INT_CONST - input.int_val - when Tokenizer::STRING_CONST - input.string_val - end - - yielder.yield [type, text] - end - end + @input = input @builder = Builder::XmlMarkup.new(target: output, indent: 2) end def compile_class b.tag!(:class) do - consume(Tokenizer::KEYWORD, 'class') - - _, identifier = input.next # identifier - b.identifier(identifier) + # Get the ball moving! + input.advance - consume(Tokenizer::SYMBOL, '{') + consume(Tokenizer::KEYWORD, 'class') - _, keyword = input.peek # 'field' or 'static' + consume(Tokenizer::IDENTIFIER) - while %w[field static].include? keyword - compile_class_var_dec - _, keyword = input.peek - end + consume_wrapped('{') do + while %w[field static].include? current_token + compile_class_var_dec + end - while %w[constructor function method].include? keyword - compile_subroutine - _, keyword = input.peek + while %w[constructor function method].include? current_token + compile_subroutine + end end - - consume(Tokenizer::SYMBOL, '}') end end def compile_class_var_dec b.classVarDec do - _, keyword = input.next - b.keyword(keyword) + consume(Tokenizer::KEYWORD) - _, keyword = input.next - b.keyword(keyword) + consume_type - begin - _, identifier = input.next - b.identifier(identifier) + consume_seperated(',') do + consume(Tokenizer::IDENTIFIER) + end - _, symbol = input.next - b.symbol(symbol) - end while symbol == ',' + consume(Tokenizer::SYMBOL, ';') end end def compile_subroutine b.subroutineDec do - _, keyword = input.next - b.keyword(keyword) + consume(Tokenizer::KEYWORD) - type, text = input.next - case type - when Tokenizer::KEYWORD - b.keyword(text) - when Tokenizer::IDENTIFIER - b.identifier(text) - end + try_consume(Tokenizer::KEYWORD, 'void') || consume_type - _, identifier = input.next # subroutine name - b.identifier(identifier) + consume(Tokenizer::IDENTIFIER) # subroutine name - consume(Tokenizer::SYMBOL, '(') - - compile_parameter_list - - consume(Tokenizer::SYMBOL, ')') + consume_wrapped('(') do + compile_parameter_list + end compile_subroutine_body end @@ -100,39 +62,31 @@ def compile_subroutine def compile_parameter_list b.parameterList do - _, symbol = input.peek + return if current_token == ')' - until symbol == ')' - _, keyword = input.next - b.keyword(keyword) - - _, identifier = input.next - b.identifier(identifier) - - _, symbol = input.peek - if symbol == ',' - b.symbol(',') - input.next - end + consume_seperated(',') do + consume_type + consume(Tokenizer::IDENTIFIER) end end end def compile_subroutine_body b.subroutineBody do - consume(Tokenizer::SYMBOL, '{') - - compile_statements + consume_wrapped('{') do + while current_token == "var" + compile_var_dec + end - consume(Tokenizer::SYMBOL, '}') + compile_statements + end end end def compile_statements b.statements do loop do - _, text = input.peek - case text + case current_token when 'let' compile_let when 'do' @@ -141,6 +95,8 @@ def compile_statements compile_return when 'if' compile_if + when 'while' + compile_while else break end @@ -148,12 +104,43 @@ def compile_statements end end + def compile_while + b.whileStatement do + consume(Tokenizer::KEYWORD, 'while') + + consume_wrapped('(') do + compile_expression + end + + consume_wrapped('{') do + compile_statements + end + end + end + + def compile_var_dec + b.varDec do + consume(Tokenizer::KEYWORD, 'var') + + consume_type + + consume_seperated(',') do + consume(Tokenizer::IDENTIFIER) + end + + consume(Tokenizer::SYMBOL, ';') + end + end + def compile_let b.letStatement do consume(Tokenizer::KEYWORD, 'let') - _, identifier = input.next - b.identifier(identifier) + consume(Tokenizer::IDENTIFIER) + + try_consume_wrapped('[') do + compile_expression + end consume(Tokenizer::SYMBOL, '=') @@ -167,23 +154,8 @@ def compile_do b.doStatement do consume(Tokenizer::KEYWORD, 'do') - _, identifier = input.next - b.identifier(identifier) - - _, text = input.peek - if text == '.' - input.next - b.symbol('.') - - _, identifier = input.next - b.identifier(identifier) - end - - consume(Tokenizer::SYMBOL, '(') - - compile_expression_list + consume_subroutine_call - consume(Tokenizer::SYMBOL, ')') consume(Tokenizer::SYMBOL, ';') end end @@ -192,44 +164,38 @@ def compile_return b.returnStatement do consume(Tokenizer::KEYWORD, 'return') - _, text = input.peek - compile_expression unless text == ';' + compile_expression unless current_token == ';' - input.next - b.symbol(';') + consume(Tokenizer::SYMBOL, ';') end end def compile_if b.ifStatement do consume(Tokenizer::KEYWORD, 'if') - consume(Tokenizer::SYMBOL, '(') - - compile_expression - consume(Tokenizer::SYMBOL, ')') - - consume(Tokenizer::SYMBOL, '{') + consume_wrapped('(') do + compile_expression + end - compile_statements + consume_wrapped('{') do + compile_statements + end - consume(Tokenizer::SYMBOL, '}') + if try_consume(Tokenizer::KEYWORD, 'else') + consume_wrapped('{') do + compile_statements + end + end end end def compile_expression_list b.expressionList do - type, _ = input.peek - while type == Tokenizer::IDENTIFIER - compile_expression - - _, symbol = input.peek - if symbol == ',' - b.symbol(',') - input.next - end + return if current_token == ')' - type, _ = input.peek + consume_seperated(',') do + compile_expression end end end @@ -237,29 +203,133 @@ def compile_expression_list def compile_expression b.expression do compile_term + + while %w[+ - * / & | < > =].include? current_token + consume(Tokenizer::SYMBOL) + compile_term + end end end def compile_term b.term do - _, identifier = input.next - b.identifier(identifier) + return if try_consume(Tokenizer::INT_CONST) || + try_consume(Tokenizer::STRING_CONST) || + try_consume_wrapped('(') { compile_expression } + + case current_token + when 'true', 'false', 'null', 'this' #keywordConst + consume(Tokenizer::KEYWORD) + when '-', '~' # unary op + consume(Tokenizer::SYMBOL) + compile_term + else + consume(Tokenizer::IDENTIFIER) + + case current_token + when '[' + consume_wrapped('[') do + compile_expression + end + when '.', '(' + consume_subroutine_call(skip_identifier: true) + end + end end end private - def consume(expected_type, expected_token) - actual_type, actual_token = input.next + def consume(expected_type, expected_token = nil) + unless try_consume(expected_type, expected_token) + if expected_token + raise "expected a #{expected_type} `#{expected_token}`, got #{current_type} `#{current_token}`" + else + raise "expected any #{expected_type}, got #{current_type} `#{current_token}`" + end + end + end + + def try_consume(expected_type, expected_token = nil) + return false unless current_type == expected_type && + current_token == (expected_token || current_token) + + b.tag!(current_type, current_token) + + input.advance if input.has_more_tokens? + true + end - unless actual_type == expected_type && actual_token == expected_token - raise "expected a #{expected_type} `#{expected_token}`, got #{actual_type} `#{actual_token}`" + def consume_wrapped(opening, &block) + unless try_consume_wrapped(opening) { block.call } + raise "expected wrapping `#{opening}`, got #{current_type}, `#{current_token}`" + end + end + + def try_consume_wrapped(opening) + closing = case opening + when '(' then ')' + when '[' then ']' + when '{' then '}' + else raise "Unknown opening brace: `#{opening}`" end - b.tag!(expected_type, expected_token) + if try_consume(Tokenizer::SYMBOL, opening) + yield + consume(Tokenizer::SYMBOL, closing) + true + end end def b @builder end + + def current_type + input.token_type + end + + def current_token + case current_type + when Tokenizer::KEYWORD + input.keyword + when Tokenizer::SYMBOL + input.symbol + when Tokenizer::IDENTIFIER + input.identifier + when Tokenizer::INT_CONST + input.int_val + when Tokenizer::STRING_CONST + input.string_val + end + end + + def consume_seperated(sep) + begin + yield + end while try_consume(Tokenizer::SYMBOL, sep) + end + + def consume_type + case current_type + when Tokenizer::KEYWORD + if %w[int char boolean].include? current_token + consume(Tokenizer::KEYWORD) + end + else + consume(Tokenizer::IDENTIFIER) + end + end + + def consume_subroutine_call(skip_identifier: false) + consume(Tokenizer::IDENTIFIER) unless skip_identifier + + if try_consume(Tokenizer::SYMBOL, '.') + consume(Tokenizer::IDENTIFIER) + end + + consume_wrapped('(') do + compile_expression_list + end + end end diff --git a/lib/tokenizer.rb b/lib/tokenizer.rb index fb9a93a..00f619e 100644 --- a/lib/tokenizer.rb +++ b/lib/tokenizer.rb @@ -45,14 +45,14 @@ def advance self.token_type = STRING_CONST self.string_val = next_word else - next_word = input[%r{(\w+|[()\[\]{}.,;=<>&*/+~-])}] + next_word = input[%r{(\w+|[()\[\]{}.,;=<>&*/+~|-])}] input.sub!(next_word, '') case next_word when *ALL_KEYWORDS self.token_type = KEYWORD self.keyword = next_word - when '(', ')', '[', ']', '{', '}', '.', ',', ';', '=', '<', '>', '&', '*', '/', '+', '-', '~' + when '(', ')', '[', ']', '{', '}', '.', ',', ';', '=', '<', '>', '&', '*', '/', '+', '-', '~', '|' self.token_type = SYMBOL self.symbol = next_word when /\d+/ diff --git a/spec/unit/parser_spec.rb b/spec/unit/parser_spec.rb index cb533f2..6976e01 100644 --- a/spec/unit/parser_spec.rb +++ b/spec/unit/parser_spec.rb @@ -2,7 +2,7 @@ require 'tokenizer' RSpec.describe "Parser" do - it "parses" do + it "parses ExpressionlessSquare::Square" do input =<<-EOJACK // This file is part of www.nand2tetris.org // and the book "The Elements of Computing Systems" @@ -1095,14 +1095,3465 @@ class Square { EOXML + difftest(input, expected) + end + + it "parses ExpressionlessSquare::Main" do + input =<<-EOJACK +// 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/10/ExpressionlessSquare/Main.jack + +// Expressionless version of Main.jack. + +/** + * The Main class initializes a new Square Dance game and starts it. + */ +class Main { + + // Initializes the square game and starts it. + function void main() { + var SquareGame game; + + let game = game; + do game.run(); + do game.dispose(); + + return; + } +} + EOJACK + + expected =<<-EOXML + + class + Main + { + + function + void + main + ( + + + ) + + { + + var + SquareGame + game + ; + + + + let + game + = + + + game + + + ; + + + do + game + . + run + ( + + + ) + ; + + + do + game + . + dispose + ( + + + ) + ; + + + return + ; + + + } + + + } + + EOXML + + difftest(input, expected) + end + + it "parses ExpressionlessSquare::SquareGame" do + input =<<-EOJACK +// 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/10/ExpressionlessSquare/SquareGame.jack + +// Expressionless version of Square.jack. + +/** + * The SquareDance class implements the Square Dance game. + * In this game you can move a black square around the screen and + * change its size during the movement. + * In the beggining, the square is located at the top left corner. + * Use the arrow keys to move the square. + * Use 'z' & 'x' to decrement & increment the size. + * Use 'q' to quit. + */ +class SquareGame { + + // The square + field Square square; + + // The square's movement direction + field int direction; // 0=none,1=up,2=down,3=left,4=right + + // Constructs a new Square Game. + constructor SquareGame new() { + let square = square; + let direction = direction; + + return square; + } + + // Deallocates the object's memory. + method void dispose() { + do square.dispose(); + do Memory.deAlloc(square); + return; + } + + // Starts the game. Handles inputs from the user that controls + // the square movement direction and size. + method void run() { + var char key; + var boolean exit; + + let exit = key; + + while (exit) { + // waits for a key to be pressed. + while (key) { + let key = key; + do moveSquare(); + } + + if (key) { + let exit = exit; + } + if (key) { + do square.decSize(); + } + if (key) { + do square.incSize(); + } + if (key) { + let direction = exit; + } + if (key) { + let direction = key; + } + if (key) { + let direction = square; + } + if (key) { + let direction = direction; + } + + // waits for the key to be released. + while (key) { + let key = key; + do moveSquare(); + } + } + + return; + } + + // Moves the square by 2 in the current direction. + method void moveSquare() { + if (direction) { + do square.moveUp(); + } + if (direction) { + do square.moveDown(); + } + if (direction) { + do square.moveLeft(); + } + if (direction) { + do square.moveRight(); + } + + do Sys.wait(direction); // Delays the next movement. + return; + } +} + EOJACK + + expected =<<-EOXML + + class + SquareGame + { + + field + Square + square + ; + + + field + int + direction + ; + + + constructor + SquareGame + new + ( + + + ) + + { + + + let + square + = + + + square + + + ; + + + let + direction + = + + + direction + + + ; + + + return + + + square + + + ; + + + } + + + + method + void + dispose + ( + + + ) + + { + + + do + square + . + dispose + ( + + + ) + ; + + + do + Memory + . + deAlloc + ( + + + + square + + + + ) + ; + + + return + ; + + + } + + + + method + void + run + ( + + + ) + + { + + var + char + key + ; + + + var + boolean + exit + ; + + + + let + exit + = + + + key + + + ; + + + while + ( + + + exit + + + ) + { + + + while + ( + + + key + + + ) + { + + + let + key + = + + + key + + + ; + + + do + moveSquare + ( + + + ) + ; + + + } + + + if + ( + + + key + + + ) + { + + + let + exit + = + + + exit + + + ; + + + } + + + if + ( + + + key + + + ) + { + + + do + square + . + decSize + ( + + + ) + ; + + + } + + + if + ( + + + key + + + ) + { + + + do + square + . + incSize + ( + + + ) + ; + + + } + + + if + ( + + + key + + + ) + { + + + let + direction + = + + + exit + + + ; + + + } + + + if + ( + + + key + + + ) + { + + + let + direction + = + + + key + + + ; + + + } + + + if + ( + + + key + + + ) + { + + + let + direction + = + + + square + + + ; + + + } + + + if + ( + + + key + + + ) + { + + + let + direction + = + + + direction + + + ; + + + } + + + while + ( + + + key + + + ) + { + + + let + key + = + + + key + + + ; + + + do + moveSquare + ( + + + ) + ; + + + } + + + } + + + return + ; + + + } + + + + method + void + moveSquare + ( + + + ) + + { + + + if + ( + + + direction + + + ) + { + + + do + square + . + moveUp + ( + + + ) + ; + + + } + + + if + ( + + + direction + + + ) + { + + + do + square + . + moveDown + ( + + + ) + ; + + + } + + + if + ( + + + direction + + + ) + { + + + do + square + . + moveLeft + ( + + + ) + ; + + + } + + + if + ( + + + direction + + + ) + { + + + do + square + . + moveRight + ( + + + ) + ; + + + } + + + do + Sys + . + wait + ( + + + + direction + + + + ) + ; + + + return + ; + + + } + + + } + + EOXML + + difftest(input, expected) + end + + it "parses Square::Main" do + input =<<-EOJACK +// 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/09/Square/Main.jack + +/** + * The Main class initializes a new Square Dance game and starts it. + */ +class Main { + + /** Initializes a new game and starts it. */ + function void main() { + var SquareGame game; + + let game = SquareGame.new(); + do game.run(); + do game.dispose(); + + return; + } +} + EOJACK + + expected =<<-EOXML + + class + Main + { + + function + void + main + ( + + + ) + + { + + var + SquareGame + game + ; + + + + let + game + = + + + SquareGame + . + new + ( + + + ) + + + ; + + + do + game + . + run + ( + + + ) + ; + + + do + game + . + dispose + ( + + + ) + ; + + + return + ; + + + } + + + } + + EOXML + + difftest(input, expected) + end + + it "parses Square::Square" do + input =<<-EOJACK +// 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/09/Square/Square.jack + +/** + * Implements a graphic square. A graphic square has a screen location + * and a size. It also has methods for drawing, erasing, moving on the + * screen, and changing its size. + */ +class Square { + + // Location on the screen + field int x, y; + + // The size of the square + field int size; + + /** Constructs a new square with a given location and size. */ + constructor Square new(int Ax, int Ay, int Asize) { + let x = Ax; + let y = Ay; + let size = Asize; + + do draw(); + + return this; + } + + /** Deallocates the object's memory. */ + method void dispose() { + do Memory.deAlloc(this); + return; + } + + /** Draws the square on the screen. */ + method void draw() { + do Screen.setColor(true); + do Screen.drawRectangle(x, y, x + size, y + size); + return; + } + + /** Erases the square from the screen. */ + method void erase() { + do Screen.setColor(false); + do Screen.drawRectangle(x, y, x + size, y + size); + return; + } + + /** Increments the size by 2 pixels. */ + method void incSize() { + if (((y + size) < 254) & ((x + size) < 510)) { + do erase(); + let size = size + 2; + do draw(); + } + return; + } + + /** Decrements the size by 2 pixels. */ + method void decSize() { + if (size > 2) { + do erase(); + let size = size - 2; + do draw(); + } + return; + } + + /** Moves up by 2 pixels. */ + method void moveUp() { + if (y > 1) { + do Screen.setColor(false); + do Screen.drawRectangle(x, (y + size) - 1, x + size, y + size); + let y = y - 2; + do Screen.setColor(true); + do Screen.drawRectangle(x, y, x + size, y + 1); + } + return; + } + + /** Moves down by 2 pixels. */ + method void moveDown() { + if ((y + size) < 254) { + do Screen.setColor(false); + do Screen.drawRectangle(x, y, x + size, y + 1); + let y = y + 2; + do Screen.setColor(true); + do Screen.drawRectangle(x, (y + size) - 1, x + size, y + size); + } + return; + } + + /** Moves left by 2 pixels. */ + method void moveLeft() { + if (x > 1) { + do Screen.setColor(false); + do Screen.drawRectangle((x + size) - 1, y, x + size, y + size); + let x = x - 2; + do Screen.setColor(true); + do Screen.drawRectangle(x, y, x + 1, y + size); + } + return; + } + + /** Moves right by 2 pixels. */ + method void moveRight() { + if ((x + size) < 510) { + do Screen.setColor(false); + do Screen.drawRectangle(x, y, x + 1, y + size); + let x = x + 2; + do Screen.setColor(true); + do Screen.drawRectangle((x + size) - 1, y, x + size, y + size); + } + return; + } +} + EOJACK + + expected =<<-EOXML + + class + Square + { + + field + int + x + , + y + ; + + + field + int + size + ; + + + constructor + Square + new + ( + + int + Ax + , + int + Ay + , + int + Asize + + ) + + { + + + let + x + = + + + Ax + + + ; + + + let + y + = + + + Ay + + + ; + + + let + size + = + + + Asize + + + ; + + + do + draw + ( + + + ) + ; + + + return + + + this + + + ; + + + } + + + + method + void + dispose + ( + + + ) + + { + + + do + Memory + . + deAlloc + ( + + + + this + + + + ) + ; + + + return + ; + + + } + + + + method + void + draw + ( + + + ) + + { + + + do + Screen + . + setColor + ( + + + + true + + + + ) + ; + + + do + Screen + . + drawRectangle + ( + + + + x + + + , + + + y + + + , + + + x + + + + + size + + + , + + + y + + + + + size + + + + ) + ; + + + return + ; + + + } + + + + method + void + erase + ( + + + ) + + { + + + do + Screen + . + setColor + ( + + + + false + + + + ) + ; + + + do + Screen + . + drawRectangle + ( + + + + x + + + , + + + y + + + , + + + x + + + + + size + + + , + + + y + + + + + size + + + + ) + ; + + + return + ; + + + } + + + + method + void + incSize + ( + + + ) + + { + + + if + ( + + + ( + + + ( + + + y + + + + + size + + + ) + + < + + 254 + + + ) + + & + + ( + + + ( + + + x + + + + + size + + + ) + + < + + 510 + + + ) + + + ) + { + + + do + erase + ( + + + ) + ; + + + let + size + = + + + size + + + + + 2 + + + ; + + + do + draw + ( + + + ) + ; + + + } + + + return + ; + + + } + + + + method + void + decSize + ( + + + ) + + { + + + if + ( + + + size + + > + + 2 + + + ) + { + + + do + erase + ( + + + ) + ; + + + let + size + = + + + size + + - + + 2 + + + ; + + + do + draw + ( + + + ) + ; + + + } + + + return + ; + + + } + + + + method + void + moveUp + ( + + + ) + + { + + + if + ( + + + y + + > + + 1 + + + ) + { + + + do + Screen + . + setColor + ( + + + + false + + + + ) + ; + + + do + Screen + . + drawRectangle + ( + + + + x + + + , + + + ( + + + y + + + + + size + + + ) + + - + + 1 + + + , + + + x + + + + + size + + + , + + + y + + + + + size + + + + ) + ; + + + let + y + = + + + y + + - + + 2 + + + ; + + + do + Screen + . + setColor + ( + + + + true + + + + ) + ; + + + do + Screen + . + drawRectangle + ( + + + + x + + + , + + + y + + + , + + + x + + + + + size + + + , + + + y + + + + + 1 + + + + ) + ; + + + } + + + return + ; + + + } + + + + method + void + moveDown + ( + + + ) + + { + + + if + ( + + + ( + + + y + + + + + size + + + ) + + < + + 254 + + + ) + { + + + do + Screen + . + setColor + ( + + + + false + + + + ) + ; + + + do + Screen + . + drawRectangle + ( + + + + x + + + , + + + y + + + , + + + x + + + + + size + + + , + + + y + + + + + 1 + + + + ) + ; + + + let + y + = + + + y + + + + + 2 + + + ; + + + do + Screen + . + setColor + ( + + + + true + + + + ) + ; + + + do + Screen + . + drawRectangle + ( + + + + x + + + , + + + ( + + + y + + + + + size + + + ) + + - + + 1 + + + , + + + x + + + + + size + + + , + + + y + + + + + size + + + + ) + ; + + + } + + + return + ; + + + } + + + + method + void + moveLeft + ( + + + ) + + { + + + if + ( + + + x + + > + + 1 + + + ) + { + + + do + Screen + . + setColor + ( + + + + false + + + + ) + ; + + + do + Screen + . + drawRectangle + ( + + + + ( + + + x + + + + + size + + + ) + + - + + 1 + + + , + + + y + + + , + + + x + + + + + size + + + , + + + y + + + + + size + + + + ) + ; + + + let + x + = + + + x + + - + + 2 + + + ; + + + do + Screen + . + setColor + ( + + + + true + + + + ) + ; + + + do + Screen + . + drawRectangle + ( + + + + x + + + , + + + y + + + , + + + x + + + + + 1 + + + , + + + y + + + + + size + + + + ) + ; + + + } + + + return + ; + + + } + + + + method + void + moveRight + ( + + + ) + + { + + + if + ( + + + ( + + + x + + + + + size + + + ) + + < + + 510 + + + ) + { + + + do + Screen + . + setColor + ( + + + + false + + + + ) + ; + + + do + Screen + . + drawRectangle + ( + + + + x + + + , + + + y + + + , + + + x + + + + + 1 + + + , + + + y + + + + + size + + + + ) + ; + + + let + x + = + + + x + + + + + 2 + + + ; + + + do + Screen + . + setColor + ( + + + + true + + + + ) + ; + + + do + Screen + . + drawRectangle + ( + + + + ( + + + x + + + + + size + + + ) + + - + + 1 + + + , + + + y + + + , + + + x + + + + + size + + + , + + + y + + + + + size + + + + ) + ; + + + } + + + return + ; + + + } + + + } + + EOXML + + difftest(input, expected) + end + + it "parses ExpressionlessSquare::SquareDance" do + input =<<-EOJACK +// 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/09/Square/SquareGame.jack + +/** + * Implements the Square Dance game. + * In this game you can move a black square around the screen and + * change its size during the movement. + * In the beginning, the square is located at the top-left corner + * of the screen. The arrow keys are used to move the square. + * The 'z' & 'x' keys are used to decrement and increment the size. + * The 'q' key is used to quit the game. + */ +class SquareGame { + + // The square + field Square square; + + // The square's movement direction + field int direction; // 0=none,1=up,2=down,3=left,4=right + + /** Constructs a new Square Game. */ + constructor SquareGame new() { + let square = Square.new(0, 0, 30); + let direction = 0; + + return this; + } + + /** Deallocates the object's memory. */ + method void dispose() { + do square.dispose(); + do Memory.deAlloc(this); + return; + } + + /** Starts the game. Handles inputs from the user that control + * the square's movement, direction and size. */ + method void run() { + var char key; + var boolean exit; + + let exit = false; + + while (~exit) { + // waits for a key to be pressed. + while (key = 0) { + let key = Keyboard.keyPressed(); + do moveSquare(); + } + + if (key = 81) { + let exit = true; + } + if (key = 90) { + do square.decSize(); + } + if (key = 88) { + do square.incSize(); + } + if (key = 131) { + let direction = 1; + } + if (key = 133) { + let direction = 2; + } + if (key = 130) { + let direction = 3; + } + if (key = 132) { + let direction = 4; + } + + // waits for the key to be released. + while (~(key = 0)) { + let key = Keyboard.keyPressed(); + do moveSquare(); + } + } + + return; + } + + /** Moves the square by 2 pixels in the current direction. */ + method void moveSquare() { + if (direction = 1) { + do square.moveUp(); + } + if (direction = 2) { + do square.moveDown(); + } + if (direction = 3) { + do square.moveLeft(); + } + if (direction = 4) { + do square.moveRight(); + } + + do Sys.wait(5); // Delays the next movement. + return; + } +} + EOJACK + + expected =<<-EOXML + + class + SquareGame + { + + field + Square + square + ; + + + field + int + direction + ; + + + constructor + SquareGame + new + ( + + + ) + + { + + + let + square + = + + + Square + . + new + ( + + + + 0 + + + , + + + 0 + + + , + + + 30 + + + + ) + + + ; + + + let + direction + = + + + 0 + + + ; + + + return + + + this + + + ; + + + } + + + + method + void + dispose + ( + + + ) + + { + + + do + square + . + dispose + ( + + + ) + ; + + + do + Memory + . + deAlloc + ( + + + + this + + + + ) + ; + + + return + ; + + + } + + + + method + void + run + ( + + + ) + + { + + var + char + key + ; + + + var + boolean + exit + ; + + + + let + exit + = + + + false + + + ; + + + while + ( + + + ~ + + exit + + + + ) + { + + + while + ( + + + key + + = + + 0 + + + ) + { + + + let + key + = + + + Keyboard + . + keyPressed + ( + + + ) + + + ; + + + do + moveSquare + ( + + + ) + ; + + + } + + + if + ( + + + key + + = + + 81 + + + ) + { + + + let + exit + = + + + true + + + ; + + + } + + + if + ( + + + key + + = + + 90 + + + ) + { + + + do + square + . + decSize + ( + + + ) + ; + + + } + + + if + ( + + + key + + = + + 88 + + + ) + { + + + do + square + . + incSize + ( + + + ) + ; + + + } + + + if + ( + + + key + + = + + 131 + + + ) + { + + + let + direction + = + + + 1 + + + ; + + + } + + + if + ( + + + key + + = + + 133 + + + ) + { + + + let + direction + = + + + 2 + + + ; + + + } + + + if + ( + + + key + + = + + 130 + + + ) + { + + + let + direction + = + + + 3 + + + ; + + + } + + + if + ( + + + key + + = + + 132 + + + ) + { + + + let + direction + = + + + 4 + + + ; + + + } + + + while + ( + + + ~ + + ( + + + key + + = + + 0 + + + ) + + + + ) + { + + + let + key + = + + + Keyboard + . + keyPressed + ( + + + ) + + + ; + + + do + moveSquare + ( + + + ) + ; + + + } + + + } + + + return + ; + + + } + + + + method + void + moveSquare + ( + + + ) + + { + + + if + ( + + + direction + + = + + 1 + + + ) + { + + + do + square + . + moveUp + ( + + + ) + ; + + + } + + + if + ( + + + direction + + = + + 2 + + + ) + { + + + do + square + . + moveDown + ( + + + ) + ; + + + } + + + if + ( + + + direction + + = + + 3 + + + ) + { + + + do + square + . + moveLeft + ( + + + ) + ; + + + } + + + if + ( + + + direction + + = + + 4 + + + ) + { + + + do + square + . + moveRight + ( + + + ) + ; + + + } + + + do + Sys + . + wait + ( + + + + 5 + + + + ) + ; + + + return + ; + + + } + + + } + + EOXML + + difftest(input, expected) + end + + it "parses ExpressionlessSquare::Main" do + input =<<-EOJACK +// 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/10/ArrayTest/Main.jack + +/** Computes the average of a sequence of integers. */ +class Main { + function void main() { + var Array a; + var int length; + var int i, sum; + + let length = Keyboard.readInt("HOW MANY NUMBERS? "); + let a = Array.new(length); + let i = 0; + + while (i < length) { + let a[i] = Keyboard.readInt("ENTER THE NEXT NUMBER: "); + let i = i + 1; + } + + let i = 0; + let sum = 0; + + while (i < length) { + let sum = sum + a[i]; + let i = i + 1; + } + + do Output.printString("THE AVERAGE IS: "); + do Output.printInt(sum / length); + do Output.println(); + + return; + } +} + EOJACK + + expected =<<-EOXML + + class + Main + { + + function + void + main + ( + + + ) + + { + + var + Array + a + ; + + + var + int + length + ; + + + var + int + i + , + sum + ; + + + + let + length + = + + + Keyboard + . + readInt + ( + + + + HOW MANY NUMBERS? + + + + ) + + + ; + + + let + a + = + + + Array + . + new + ( + + + + length + + + + ) + + + ; + + + let + i + = + + + 0 + + + ; + + + while + ( + + + i + + < + + length + + + ) + { + + + let + a + [ + + + i + + + ] + = + + + Keyboard + . + readInt + ( + + + + ENTER THE NEXT NUMBER: + + + + ) + + + ; + + + let + i + = + + + i + + + + + 1 + + + ; + + + } + + + let + i + = + + + 0 + + + ; + + + let + sum + = + + + 0 + + + ; + + + while + ( + + + i + + < + + length + + + ) + { + + + let + sum + = + + + sum + + + + + a + [ + + + i + + + ] + + + ; + + + let + i + = + + + i + + + + + 1 + + + ; + + + } + + + do + Output + . + printString + ( + + + + THE AVERAGE IS: + + + + ) + ; + + + do + Output + . + printInt + ( + + + + sum + + / + + length + + + + ) + ; + + + do + Output + . + println + ( + + + ) + ; + + + return + ; + + + } + + + } + + EOXML + + difftest(input, expected) + end + + it "parses the remaining grammar" do + input =<<-EOJACK +// 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/10/ArrayTest/Main.jack + +/** Computes the average of a sequence of integers. */ +class Main { + function void main() { + // This is nonsense just to test extra things + if (i < 4) { + let i = i * a[-2]; + return false; + } else { + let i = -i | 4 + 1; + let i = this / null; + return true; + } + } +} + EOJACK + + expected =<<-EOXML + + class + Main + { + + function + void + main + ( + + + ) + + { + + + if + ( + + + i + + < + + 4 + + + ) + { + + + let + i + = + + + i + + * + + a + [ + + + - + + 2 + + + + ] + + + ; + + + return + + + false + + + ; + + + } + else + { + + + let + i + = + + + - + + i + + + | + + 4 + + + + + 1 + + + ; + + + let + i + = + + + this + + / + + null + + + ; + + + return + + + true + + + ; + + + } + + + } + + + } + + EOXML + + difftest(input, expected) + end + + def difftest(input, expected) tokenizer = Tokenizer.new(input) actual = StringIO.new parser = Parser.new(tokenizer, actual) - parser.compile_class + begin + parser.compile_class + rescue + puts nil, actual.string, nil + raise + end actual.string.lines.zip(expected.lines) do |a, e| - expect(a).to eq(e) + e.gsub!(/> /, ">") + e.gsub!(/