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!(/ , "<")
+ e.gsub!(/integerConstant/, "int_const")
+ e.gsub!(/stringConstant/, "string_const")
+ e.strip!
+ a.strip!
+ expect(a).to eq(e)#, "'#{a}' != '#{e}' in: #{actual.string}"
end
end
end