From 199b8af9ed1a33e9d0834cae8666481b12c3cf59 Mon Sep 17 00:00:00 2001 From: Kevin Butler Date: Wed, 3 Jun 2015 23:47:16 +0100 Subject: [PATCH 01/12] Swap parser behaviour to align with the books specification. The book talks about the contract being that when you enter a method `compile_xxxx` you are already looking at the first token being valid for an `xxxx`. So, the behaviour should be to `advance then check`, always, instead of our `check(peek) then advance then check`. It also says that we should always leave a `compile_xxxx` method by advancing past the `xxxx`, which actually then works out nicely for continued compilation. --- lib/parser.rb | 119 ++++++++++++++++++++++++++++---------------------- 1 file changed, 68 insertions(+), 51 deletions(-) diff --git a/lib/parser.rb b/lib/parser.rb index 07d43fd..a6c01ab 100644 --- a/lib/parser.rb +++ b/lib/parser.rb @@ -4,50 +4,34 @@ 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 + # Get the ball moving! + input.advance + consume(Tokenizer::KEYWORD, 'class') - _, identifier = input.next # identifier + _, identifier = current_token # identifier b.identifier(identifier) + input.advance consume(Tokenizer::SYMBOL, '{') - _, keyword = input.peek # 'field' or 'static' + _, keyword = current_token # 'field' or 'static' while %w[field static].include? keyword compile_class_var_dec - _, keyword = input.peek + _, keyword = current_token end while %w[constructor function method].include? keyword compile_subroutine - _, keyword = input.peek + _, keyword = current_token end consume(Tokenizer::SYMBOL, '}') @@ -56,37 +40,44 @@ def compile_class def compile_class_var_dec b.classVarDec do - _, keyword = input.next + _, keyword = current_token b.keyword(keyword) + input.advance - _, keyword = input.next + _, keyword = current_token b.keyword(keyword) + input.advance begin - _, identifier = input.next + _, identifier = current_token b.identifier(identifier) + input.advance - _, symbol = input.next + _, symbol = current_token b.symbol(symbol) + input.advance end while symbol == ',' end end def compile_subroutine b.subroutineDec do - _, keyword = input.next + _, keyword = current_token b.keyword(keyword) + input.advance - type, text = input.next + type, text = current_token case type when Tokenizer::KEYWORD b.keyword(text) when Tokenizer::IDENTIFIER b.identifier(text) end + input.advance - _, identifier = input.next # subroutine name + _, identifier = current_token # subroutine name b.identifier(identifier) + input.advance consume(Tokenizer::SYMBOL, '(') @@ -100,19 +91,21 @@ def compile_subroutine def compile_parameter_list b.parameterList do - _, symbol = input.peek + _, symbol = current_token until symbol == ')' - _, keyword = input.next + _, keyword = current_token b.keyword(keyword) + input.advance - _, identifier = input.next + _, identifier = current_token b.identifier(identifier) + input.advance - _, symbol = input.peek + _, symbol = current_token if symbol == ',' b.symbol(',') - input.next + input.advance end end end @@ -131,7 +124,7 @@ def compile_subroutine_body def compile_statements b.statements do loop do - _, text = input.peek + _, text = current_token case text when 'let' compile_let @@ -152,8 +145,9 @@ def compile_let b.letStatement do consume(Tokenizer::KEYWORD, 'let') - _, identifier = input.next + _, identifier = current_token b.identifier(identifier) + input.advance consume(Tokenizer::SYMBOL, '=') @@ -167,16 +161,18 @@ def compile_do b.doStatement do consume(Tokenizer::KEYWORD, 'do') - _, identifier = input.next + _, identifier = current_token b.identifier(identifier) + input.advance - _, text = input.peek + _, text = current_token if text == '.' - input.next b.symbol('.') + input.advance - _, identifier = input.next + _, identifier = current_token b.identifier(identifier) + input.advance end consume(Tokenizer::SYMBOL, '(') @@ -192,11 +188,11 @@ def compile_return b.returnStatement do consume(Tokenizer::KEYWORD, 'return') - _, text = input.peek + _, text = current_token compile_expression unless text == ';' - input.next b.symbol(';') + input.advance end end @@ -219,17 +215,17 @@ def compile_if def compile_expression_list b.expressionList do - type, _ = input.peek + type, _ = current_token while type == Tokenizer::IDENTIFIER compile_expression - _, symbol = input.peek + _, symbol = current_token if symbol == ',' b.symbol(',') - input.next + input.advance end - type, _ = input.peek + type, _ = current_token end end end @@ -242,24 +238,45 @@ def compile_expression def compile_term b.term do - _, identifier = input.next + _, identifier = current_token b.identifier(identifier) + input.advance end end private def consume(expected_type, expected_token) - actual_type, actual_token = input.next + actual_type, actual_token = current_token unless actual_type == expected_type && actual_token == expected_token raise "expected a #{expected_type} `#{expected_token}`, got #{actual_type} `#{actual_token}`" end b.tag!(expected_type, expected_token) + + input.advance if input.has_more_tokens? end def b @builder end + + def current_token + type = input.token_type + text = case input.token_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 + + [type, text] + end end From db6980a76f9e55bb2c511fb50c742627cd6b466f Mon Sep 17 00:00:00 2001 From: Kevin Butler Date: Wed, 3 Jun 2015 23:52:54 +0100 Subject: [PATCH 02/12] Make expected_token for Parser#consume optional --- lib/parser.rb | 77 +++++++++++++++++---------------------------------- 1 file changed, 25 insertions(+), 52 deletions(-) diff --git a/lib/parser.rb b/lib/parser.rb index a6c01ab..55c77db 100644 --- a/lib/parser.rb +++ b/lib/parser.rb @@ -16,9 +16,7 @@ def compile_class consume(Tokenizer::KEYWORD, 'class') - _, identifier = current_token # identifier - b.identifier(identifier) - input.advance + consume(Tokenizer::IDENTIFIER) consume(Tokenizer::SYMBOL, '{') @@ -40,31 +38,21 @@ def compile_class def compile_class_var_dec b.classVarDec do - _, keyword = current_token - b.keyword(keyword) - input.advance - - _, keyword = current_token - b.keyword(keyword) - input.advance + consume(Tokenizer::KEYWORD) + consume(Tokenizer::KEYWORD) begin - _, identifier = current_token - b.identifier(identifier) - input.advance + consume(Tokenizer::IDENTIFIER) _, symbol = current_token - b.symbol(symbol) - input.advance + consume(Tokenizer::SYMBOL) end while symbol == ',' end end def compile_subroutine b.subroutineDec do - _, keyword = current_token - b.keyword(keyword) - input.advance + consume(Tokenizer::KEYWORD) type, text = current_token case type @@ -75,9 +63,7 @@ def compile_subroutine end input.advance - _, identifier = current_token # subroutine name - b.identifier(identifier) - input.advance + consume(Tokenizer::IDENTIFIER) # subroutine name consume(Tokenizer::SYMBOL, '(') @@ -94,18 +80,12 @@ def compile_parameter_list _, symbol = current_token until symbol == ')' - _, keyword = current_token - b.keyword(keyword) - input.advance - - _, identifier = current_token - b.identifier(identifier) - input.advance + consume(Tokenizer::KEYWORD) + consume(Tokenizer::IDENTIFIER) _, symbol = current_token if symbol == ',' - b.symbol(',') - input.advance + consume(Tokenizer::SYMBOL, ',') end end end @@ -145,9 +125,7 @@ def compile_let b.letStatement do consume(Tokenizer::KEYWORD, 'let') - _, identifier = current_token - b.identifier(identifier) - input.advance + consume(Tokenizer::IDENTIFIER) consume(Tokenizer::SYMBOL, '=') @@ -161,18 +139,13 @@ def compile_do b.doStatement do consume(Tokenizer::KEYWORD, 'do') - _, identifier = current_token - b.identifier(identifier) - input.advance + consume(Tokenizer::IDENTIFIER) _, text = current_token if text == '.' - b.symbol('.') - input.advance + consume(Tokenizer::SYMBOL, '.') - _, identifier = current_token - b.identifier(identifier) - input.advance + consume(Tokenizer::IDENTIFIER) end consume(Tokenizer::SYMBOL, '(') @@ -191,8 +164,7 @@ def compile_return _, text = current_token compile_expression unless text == ';' - b.symbol(';') - input.advance + consume(Tokenizer::SYMBOL, ';') end end @@ -221,8 +193,7 @@ def compile_expression_list _, symbol = current_token if symbol == ',' - b.symbol(',') - input.advance + consume(Tokenizer::SYMBOL, ',') end type, _ = current_token @@ -238,22 +209,24 @@ def compile_expression def compile_term b.term do - _, identifier = current_token - b.identifier(identifier) - input.advance + consume(Tokenizer::IDENTIFIER) end end private - def consume(expected_type, expected_token) + def consume(expected_type, expected_token = nil) actual_type, actual_token = current_token - unless actual_type == expected_type && actual_token == expected_token - raise "expected a #{expected_type} `#{expected_token}`, got #{actual_type} `#{actual_token}`" + unless actual_type == expected_type && actual_token == (expected_token || actual_token) + if expected_token + raise "expected a #{expected_type} `#{expected_token}`, got #{actual_type} `#{actual_token}`" + else + raise "expected any #{expected_type}, got #{actual_type} `#{actual_token}`" + end end - b.tag!(expected_type, expected_token) + b.tag!(actual_type, actual_token) input.advance if input.has_more_tokens? end From f99b20ee922f16b1315ad17d34090f164d940dd7 Mon Sep 17 00:00:00 2001 From: Kevin Butler Date: Thu, 4 Jun 2015 00:53:32 +0100 Subject: [PATCH 03/12] Refactor to add current_type and current_token methods --- lib/parser.rb | 60 +++++++++++++++++++-------------------------------- 1 file changed, 22 insertions(+), 38 deletions(-) diff --git a/lib/parser.rb b/lib/parser.rb index 55c77db..290f662 100644 --- a/lib/parser.rb +++ b/lib/parser.rb @@ -20,16 +20,12 @@ def compile_class consume(Tokenizer::SYMBOL, '{') - _, keyword = current_token # 'field' or 'static' - - while %w[field static].include? keyword + while %w[field static].include? current_token compile_class_var_dec - _, keyword = current_token end - while %w[constructor function method].include? keyword + while %w[constructor function method].include? current_token compile_subroutine - _, keyword = current_token end consume(Tokenizer::SYMBOL, '}') @@ -44,7 +40,7 @@ def compile_class_var_dec begin consume(Tokenizer::IDENTIFIER) - _, symbol = current_token + symbol = current_token consume(Tokenizer::SYMBOL) end while symbol == ',' end @@ -54,12 +50,11 @@ def compile_subroutine b.subroutineDec do consume(Tokenizer::KEYWORD) - type, text = current_token - case type + case current_type when Tokenizer::KEYWORD - b.keyword(text) + b.keyword(current_token) when Tokenizer::IDENTIFIER - b.identifier(text) + b.identifier(current_token) end input.advance @@ -77,14 +72,11 @@ def compile_subroutine def compile_parameter_list b.parameterList do - _, symbol = current_token - - until symbol == ')' + until current_token == ')' consume(Tokenizer::KEYWORD) consume(Tokenizer::IDENTIFIER) - _, symbol = current_token - if symbol == ',' + if current_token == ',' consume(Tokenizer::SYMBOL, ',') end end @@ -104,8 +96,7 @@ def compile_subroutine_body def compile_statements b.statements do loop do - _, text = current_token - case text + case current_token when 'let' compile_let when 'do' @@ -141,8 +132,7 @@ def compile_do consume(Tokenizer::IDENTIFIER) - _, text = current_token - if text == '.' + if current_token == '.' consume(Tokenizer::SYMBOL, '.') consume(Tokenizer::IDENTIFIER) @@ -161,8 +151,7 @@ def compile_return b.returnStatement do consume(Tokenizer::KEYWORD, 'return') - _, text = current_token - compile_expression unless text == ';' + compile_expression unless current_token == ';' consume(Tokenizer::SYMBOL, ';') end @@ -187,16 +176,12 @@ def compile_if def compile_expression_list b.expressionList do - type, _ = current_token - while type == Tokenizer::IDENTIFIER + while current_type == Tokenizer::IDENTIFIER compile_expression - _, symbol = current_token - if symbol == ',' + if current_token == ',' consume(Tokenizer::SYMBOL, ',') end - - type, _ = current_token end end end @@ -216,17 +201,15 @@ def compile_term private def consume(expected_type, expected_token = nil) - actual_type, actual_token = current_token - - unless actual_type == expected_type && actual_token == (expected_token || actual_token) + unless current_type == expected_type && current_token == (expected_token || current_token) if expected_token - raise "expected a #{expected_type} `#{expected_token}`, got #{actual_type} `#{actual_token}`" + raise "expected a #{expected_type} `#{expected_token}`, got #{current_type} `#{current_token}`" else - raise "expected any #{expected_type}, got #{actual_type} `#{actual_token}`" + raise "expected any #{expected_type}, got #{current_type} `#{current_token}`" end end - b.tag!(actual_type, actual_token) + b.tag!(current_type, current_token) input.advance if input.has_more_tokens? end @@ -235,9 +218,12 @@ def b @builder end + def current_type + input.token_type + end + def current_token - type = input.token_type - text = case input.token_type + case current_type when Tokenizer::KEYWORD input.keyword when Tokenizer::SYMBOL @@ -249,7 +235,5 @@ def current_token when Tokenizer::STRING_CONST input.string_val end - - [type, text] end end From 454a38fd7cfa571e8138abd3c2c98401a43c0828 Mon Sep 17 00:00:00 2001 From: Kevin Butler Date: Thu, 4 Jun 2015 01:28:57 +0100 Subject: [PATCH 04/12] Implement Parser#compile_var_dec to compile ExpressionlessSquare::Main --- lib/parser.rb | 16 ++++++ spec/unit/parser_spec.rb | 115 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 128 insertions(+), 3 deletions(-) diff --git a/lib/parser.rb b/lib/parser.rb index 290f662..d7d9d53 100644 --- a/lib/parser.rb +++ b/lib/parser.rb @@ -87,6 +87,10 @@ def compile_subroutine_body b.subroutineBody do consume(Tokenizer::SYMBOL, '{') + while current_token == "var" + compile_var_dec + end + compile_statements consume(Tokenizer::SYMBOL, '}') @@ -112,6 +116,18 @@ def compile_statements end end + def compile_var_dec + b.varDec do + consume(Tokenizer::KEYWORD, 'var') + + consume(Tokenizer::IDENTIFIER) + + consume(Tokenizer::IDENTIFIER) + + consume(Tokenizer::SYMBOL, ';') + end + end + def compile_let b.letStatement do consume(Tokenizer::KEYWORD, 'let') diff --git a/spec/unit/parser_spec.rb b/spec/unit/parser_spec.rb index cb533f2..2670b25 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,123 @@ 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 + + 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!(/>\s+/, ">") + e.gsub!(/\s+ Date: Thu, 4 Jun 2015 01:39:11 +0100 Subject: [PATCH 05/12] Implement Parser#compile_while and allow identifiers in typenames to compile ExpressionlessSquare::SquareGame --- lib/parser.rb | 39 ++- spec/unit/parser_spec.rb | 659 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 696 insertions(+), 2 deletions(-) diff --git a/lib/parser.rb b/lib/parser.rb index d7d9d53..1f608e9 100644 --- a/lib/parser.rb +++ b/lib/parser.rb @@ -35,7 +35,15 @@ def compile_class def compile_class_var_dec b.classVarDec do consume(Tokenizer::KEYWORD) - consume(Tokenizer::KEYWORD) + + case current_type + when Tokenizer::KEYWORD + if %w[int char boolean].include? current_token + consume(Tokenizer::KEYWORD) + end + else + consume(Tokenizer::IDENTIFIER) + end begin consume(Tokenizer::IDENTIFIER) @@ -109,6 +117,8 @@ def compile_statements compile_return when 'if' compile_if + when 'while' + compile_while else break end @@ -116,11 +126,36 @@ def compile_statements end end + def compile_while + b.whileStatement do + consume(Tokenizer::KEYWORD, 'while') + + consume(Tokenizer::SYMBOL, '(') + + compile_expression + + consume(Tokenizer::SYMBOL, ')') + + consume(Tokenizer::SYMBOL, '{') + + compile_statements + + consume(Tokenizer::SYMBOL, '}') + end + end + def compile_var_dec b.varDec do consume(Tokenizer::KEYWORD, 'var') - consume(Tokenizer::IDENTIFIER) + case current_type + when Tokenizer::KEYWORD + if %w[int char boolean].include? current_token + consume(Tokenizer::KEYWORD) + end + else + consume(Tokenizer::IDENTIFIER) + end consume(Tokenizer::IDENTIFIER) diff --git a/spec/unit/parser_spec.rb b/spec/unit/parser_spec.rb index 2670b25..c41ae5d 100644 --- a/spec/unit/parser_spec.rb +++ b/spec/unit/parser_spec.rb @@ -1195,6 +1195,665 @@ class Main { 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 + def difftest(input, expected) tokenizer = Tokenizer.new(input) actual = StringIO.new From b010de2ddf055b1bed1fad234ae26f578ce1b358 Mon Sep 17 00:00:00 2001 From: Kevin Butler Date: Thu, 4 Jun 2015 01:54:57 +0100 Subject: [PATCH 06/12] Handle subroutine calls in terms to compile Square::Main --- lib/parser.rb | 15 ++++++ spec/unit/parser_spec.rb | 101 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+) diff --git a/lib/parser.rb b/lib/parser.rb index 1f608e9..31e87b2 100644 --- a/lib/parser.rb +++ b/lib/parser.rb @@ -246,6 +246,21 @@ def compile_expression def compile_term b.term do consume(Tokenizer::IDENTIFIER) + + # Possible subroutine calls + if current_token == '.' + consume(Tokenizer::SYMBOL, '.') + + consume(Tokenizer::IDENTIFIER) + end + + if current_token == '(' + consume(Tokenizer::SYMBOL, '(') + + compile_expression_list + + consume(Tokenizer::SYMBOL, ')') + end end end diff --git a/spec/unit/parser_spec.rb b/spec/unit/parser_spec.rb index c41ae5d..48871a3 100644 --- a/spec/unit/parser_spec.rb +++ b/spec/unit/parser_spec.rb @@ -1854,6 +1854,107 @@ class SquareGame { 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 + def difftest(input, expected) tokenizer = Tokenizer.new(input) actual = StringIO.new From cc3b9068551343175b53a2a0a9d44819bdbeca5f Mon Sep 17 00:00:00 2001 From: Kevin Butler Date: Thu, 4 Jun 2015 02:12:10 +0100 Subject: [PATCH 07/12] Handle more term variants to compile Square::Square --- lib/parser.rb | 41 +- spec/unit/parser_spec.rb | 1341 +++++++++++++++++++++++++++++++++++++- 2 files changed, 1370 insertions(+), 12 deletions(-) diff --git a/lib/parser.rb b/lib/parser.rb index 31e87b2..c467e5b 100644 --- a/lib/parser.rb +++ b/lib/parser.rb @@ -227,7 +227,7 @@ def compile_if def compile_expression_list b.expressionList do - while current_type == Tokenizer::IDENTIFIER + until current_token == ')' compile_expression if current_token == ',' @@ -240,26 +240,45 @@ def compile_expression_list def compile_expression b.expression do compile_term + + case current_token + when '+', '<', '&', '>', '-' + consume(Tokenizer::SYMBOL) + compile_term + end end end def compile_term b.term do - consume(Tokenizer::IDENTIFIER) + case current_type + when Tokenizer::KEYWORD + consume(Tokenizer::KEYWORD) + when Tokenizer::SYMBOL + if current_token == '(' + consume(Tokenizer::SYMBOL, '(') + compile_expression + consume(Tokenizer::SYMBOL, ')') + end + when Tokenizer::INT_CONST + consume(Tokenizer::INT_CONST) + else + consume(Tokenizer::IDENTIFIER) - # Possible subroutine calls - if current_token == '.' - consume(Tokenizer::SYMBOL, '.') + # Possible subroutine calls + if current_token == '.' + consume(Tokenizer::SYMBOL, '.') - consume(Tokenizer::IDENTIFIER) - end + consume(Tokenizer::IDENTIFIER) + end - if current_token == '(' - consume(Tokenizer::SYMBOL, '(') + if current_token == '(' + consume(Tokenizer::SYMBOL, '(') - compile_expression_list + compile_expression_list - consume(Tokenizer::SYMBOL, ')') + consume(Tokenizer::SYMBOL, ')') + end end end end diff --git a/spec/unit/parser_spec.rb b/spec/unit/parser_spec.rb index 48871a3..d88a6f0 100644 --- a/spec/unit/parser_spec.rb +++ b/spec/unit/parser_spec.rb @@ -1955,6 +1955,1344 @@ class Main { 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 + def difftest(input, expected) tokenizer = Tokenizer.new(input) actual = StringIO.new @@ -1970,8 +3308,9 @@ def difftest(input, expected) actual.string.lines.zip(expected.lines) do |a, e| e.gsub!(/>\s+/, ">") e.gsub!(/\s+ Date: Thu, 4 Jun 2015 02:21:44 +0100 Subject: [PATCH 08/12] Handle unaryOps in term to compile Square::SquareGame --- lib/parser.rb | 7 +- spec/unit/parser_spec.rb | 756 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 761 insertions(+), 2 deletions(-) diff --git a/lib/parser.rb b/lib/parser.rb index c467e5b..b819d40 100644 --- a/lib/parser.rb +++ b/lib/parser.rb @@ -241,8 +241,7 @@ def compile_expression b.expression do compile_term - case current_token - when '+', '<', '&', '>', '-' + if %w[+ < & > - ~ =].include? current_token consume(Tokenizer::SYMBOL) compile_term end @@ -259,6 +258,10 @@ def compile_term consume(Tokenizer::SYMBOL, '(') compile_expression consume(Tokenizer::SYMBOL, ')') + elsif %w[- ~].include? current_token + # unary op + consume(Tokenizer::SYMBOL) + compile_term end when Tokenizer::INT_CONST consume(Tokenizer::INT_CONST) diff --git a/spec/unit/parser_spec.rb b/spec/unit/parser_spec.rb index d88a6f0..a8aa185 100644 --- a/spec/unit/parser_spec.rb +++ b/spec/unit/parser_spec.rb @@ -3293,6 +3293,762 @@ class Square { 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 + def difftest(input, expected) tokenizer = Tokenizer.new(input) actual = StringIO.new From bda8f3b8abf4020086c8a8fc1b2452f87d4738b9 Mon Sep 17 00:00:00 2001 From: Kevin Butler Date: Thu, 4 Jun 2015 02:35:14 +0100 Subject: [PATCH 09/12] Handle string constants and array indexing to compile ArrayTest --- lib/parser.rb | 28 +++- spec/unit/parser_spec.rb | 338 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 361 insertions(+), 5 deletions(-) diff --git a/lib/parser.rb b/lib/parser.rb index b819d40..56adc8c 100644 --- a/lib/parser.rb +++ b/lib/parser.rb @@ -148,6 +148,7 @@ def compile_var_dec b.varDec do consume(Tokenizer::KEYWORD, 'var') + case current_type when Tokenizer::KEYWORD if %w[int char boolean].include? current_token @@ -157,9 +158,12 @@ def compile_var_dec consume(Tokenizer::IDENTIFIER) end - consume(Tokenizer::IDENTIFIER) + begin + consume(Tokenizer::IDENTIFIER) - consume(Tokenizer::SYMBOL, ';') + last_symbol = current_token + consume(Tokenizer::SYMBOL) + end until last_symbol == ';' end end @@ -169,6 +173,14 @@ def compile_let consume(Tokenizer::IDENTIFIER) + if current_token == '[' + consume(Tokenizer::SYMBOL, '[') + + compile_expression + + consume(Tokenizer::SYMBOL, ']') + end + consume(Tokenizer::SYMBOL, '=') compile_expression @@ -241,7 +253,7 @@ def compile_expression b.expression do compile_term - if %w[+ < & > - ~ =].include? current_token + if %w[+ < & > - ~ = /].include? current_token consume(Tokenizer::SYMBOL) compile_term end @@ -265,9 +277,19 @@ def compile_term end when Tokenizer::INT_CONST consume(Tokenizer::INT_CONST) + when Tokenizer::STRING_CONST + consume(Tokenizer::STRING_CONST) else consume(Tokenizer::IDENTIFIER) + if current_token == '[' + consume(Tokenizer::SYMBOL, '[') + + compile_expression + + consume(Tokenizer::SYMBOL, ']') + end + # Possible subroutine calls if current_token == '.' consume(Tokenizer::SYMBOL, '.') diff --git a/spec/unit/parser_spec.rb b/spec/unit/parser_spec.rb index a8aa185..f480d7d 100644 --- a/spec/unit/parser_spec.rb +++ b/spec/unit/parser_spec.rb @@ -4049,6 +4049,338 @@ class SquareGame { 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 + def difftest(input, expected) tokenizer = Tokenizer.new(input) actual = StringIO.new @@ -4062,9 +4394,11 @@ def difftest(input, expected) end actual.string.lines.zip(expected.lines) do |a, e| - e.gsub!(/>\s+/, ">") - e.gsub!(/\s+ /, ">") + e.gsub!(/ Date: Thu, 4 Jun 2015 02:49:51 +0100 Subject: [PATCH 10/12] Refactor `if token == foo then consume(foo) ..` into `if try_consume(foo) then ..` --- lib/parser.rb | 52 +++++++++++++++++++-------------------------------- 1 file changed, 19 insertions(+), 33 deletions(-) diff --git a/lib/parser.rb b/lib/parser.rb index 56adc8c..41646af 100644 --- a/lib/parser.rb +++ b/lib/parser.rb @@ -84,9 +84,7 @@ def compile_parameter_list consume(Tokenizer::KEYWORD) consume(Tokenizer::IDENTIFIER) - if current_token == ',' - consume(Tokenizer::SYMBOL, ',') - end + try_consume(Tokenizer::SYMBOL, ',') end end end @@ -173,11 +171,8 @@ def compile_let consume(Tokenizer::IDENTIFIER) - if current_token == '[' - consume(Tokenizer::SYMBOL, '[') - + if try_consume(Tokenizer::SYMBOL, '[') compile_expression - consume(Tokenizer::SYMBOL, ']') end @@ -195,9 +190,7 @@ def compile_do consume(Tokenizer::IDENTIFIER) - if current_token == '.' - consume(Tokenizer::SYMBOL, '.') - + if try_consume(Tokenizer::SYMBOL, '.') consume(Tokenizer::IDENTIFIER) end @@ -242,9 +235,7 @@ def compile_expression_list until current_token == ')' compile_expression - if current_token == ',' - consume(Tokenizer::SYMBOL, ',') - end + try_consume(Tokenizer::SYMBOL, ',') end end end @@ -262,12 +253,13 @@ def compile_expression def compile_term b.term do + return if try_consume(Tokenizer::KEYWORD) || + try_consume(Tokenizer::INT_CONST) || + try_consume(Tokenizer::STRING_CONST) + case current_type - when Tokenizer::KEYWORD - consume(Tokenizer::KEYWORD) when Tokenizer::SYMBOL - if current_token == '(' - consume(Tokenizer::SYMBOL, '(') + if try_consume(Tokenizer::SYMBOL, '(') compile_expression consume(Tokenizer::SYMBOL, ')') elsif %w[- ~].include? current_token @@ -275,33 +267,21 @@ def compile_term consume(Tokenizer::SYMBOL) compile_term end - when Tokenizer::INT_CONST - consume(Tokenizer::INT_CONST) - when Tokenizer::STRING_CONST - consume(Tokenizer::STRING_CONST) else consume(Tokenizer::IDENTIFIER) - if current_token == '[' - consume(Tokenizer::SYMBOL, '[') - + if try_consume(Tokenizer::SYMBOL, '[') compile_expression - consume(Tokenizer::SYMBOL, ']') end # Possible subroutine calls - if current_token == '.' - consume(Tokenizer::SYMBOL, '.') - + if try_consume(Tokenizer::SYMBOL, '.') consume(Tokenizer::IDENTIFIER) end - if current_token == '(' - consume(Tokenizer::SYMBOL, '(') - + if try_consume(Tokenizer::SYMBOL, '(') compile_expression_list - consume(Tokenizer::SYMBOL, ')') end end @@ -311,17 +291,23 @@ def compile_term private def consume(expected_type, expected_token = nil) - unless current_type == expected_type && current_token == (expected_token || current_token) + 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 def b From 2ba650a98e9b70ffaa43d6ff4786edb6213737f3 Mon Sep 17 00:00:00 2001 From: Kevin Butler Date: Thu, 4 Jun 2015 03:15:55 +0100 Subject: [PATCH 11/12] Refactor wrapped braces into blocks to highlight nesting --- lib/parser.rb | 112 ++++++++++++++++++++++++++------------------------ 1 file changed, 58 insertions(+), 54 deletions(-) diff --git a/lib/parser.rb b/lib/parser.rb index 41646af..e181a9c 100644 --- a/lib/parser.rb +++ b/lib/parser.rb @@ -18,17 +18,15 @@ def compile_class consume(Tokenizer::IDENTIFIER) - consume(Tokenizer::SYMBOL, '{') - - while %w[field static].include? current_token - compile_class_var_dec - end + consume_wrapped('{') do + while %w[field static].include? current_token + compile_class_var_dec + end - while %w[constructor function method].include? current_token - compile_subroutine + while %w[constructor function method].include? current_token + compile_subroutine + end end - - consume(Tokenizer::SYMBOL, '}') end end @@ -68,11 +66,9 @@ def compile_subroutine 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 @@ -91,15 +87,13 @@ def compile_parameter_list def compile_subroutine_body b.subroutineBody do - consume(Tokenizer::SYMBOL, '{') + consume_wrapped('{') do + while current_token == "var" + compile_var_dec + end - while current_token == "var" - compile_var_dec + compile_statements end - - compile_statements - - consume(Tokenizer::SYMBOL, '}') end end @@ -128,17 +122,13 @@ def compile_while b.whileStatement do consume(Tokenizer::KEYWORD, 'while') - consume(Tokenizer::SYMBOL, '(') - - compile_expression - - consume(Tokenizer::SYMBOL, ')') - - consume(Tokenizer::SYMBOL, '{') - - compile_statements + consume_wrapped('(') do + compile_expression + end - consume(Tokenizer::SYMBOL, '}') + consume_wrapped('{') do + compile_statements + end end end @@ -171,9 +161,8 @@ def compile_let consume(Tokenizer::IDENTIFIER) - if try_consume(Tokenizer::SYMBOL, '[') + try_consume_wrapped('[') do compile_expression - consume(Tokenizer::SYMBOL, ']') end consume(Tokenizer::SYMBOL, '=') @@ -194,11 +183,10 @@ def compile_do consume(Tokenizer::IDENTIFIER) end - consume(Tokenizer::SYMBOL, '(') - - compile_expression_list + consume_wrapped('(') do + compile_expression_list + end - consume(Tokenizer::SYMBOL, ')') consume(Tokenizer::SYMBOL, ';') end end @@ -216,17 +204,14 @@ def compile_return def compile_if b.ifStatement do consume(Tokenizer::KEYWORD, 'if') - consume(Tokenizer::SYMBOL, '(') - - compile_expression - - consume(Tokenizer::SYMBOL, ')') - consume(Tokenizer::SYMBOL, '{') - - compile_statements + consume_wrapped('(') do + compile_expression + end - consume(Tokenizer::SYMBOL, '}') + consume_wrapped('{') do + compile_statements + end end end @@ -259,20 +244,19 @@ def compile_term case current_type when Tokenizer::SYMBOL - if try_consume(Tokenizer::SYMBOL, '(') - compile_expression - consume(Tokenizer::SYMBOL, ')') - elsif %w[- ~].include? current_token + if %w[- ~].include? current_token # unary op consume(Tokenizer::SYMBOL) compile_term + elsif try_consume_wrapped('(') do + compile_expression + end end else consume(Tokenizer::IDENTIFIER) - if try_consume(Tokenizer::SYMBOL, '[') + try_consume_wrapped('[') do compile_expression - consume(Tokenizer::SYMBOL, ']') end # Possible subroutine calls @@ -280,9 +264,8 @@ def compile_term consume(Tokenizer::IDENTIFIER) end - if try_consume(Tokenizer::SYMBOL, '(') + try_consume_wrapped('(') do compile_expression_list - consume(Tokenizer::SYMBOL, ')') end end end @@ -310,6 +293,27 @@ def try_consume(expected_type, expected_token = nil) true end + 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 + + if try_consume(Tokenizer::SYMBOL, opening) + yield + consume(Tokenizer::SYMBOL, closing) + true + end + end + def b @builder end From e183ec2e9031512d43399f226cd64b7bb35b7d7a Mon Sep 17 00:00:00 2001 From: Kevin Butler Date: Fri, 5 Jun 2015 00:45:16 +0100 Subject: [PATCH 12/12] Tighten up the adherence to the grammar and add test for some untested rules --- lib/parser.rb | 140 +++++++++++++++++------------------ lib/tokenizer.rb | 4 +- spec/unit/parser_spec.rb | 153 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 223 insertions(+), 74 deletions(-) diff --git a/lib/parser.rb b/lib/parser.rb index e181a9c..86ef222 100644 --- a/lib/parser.rb +++ b/lib/parser.rb @@ -34,21 +34,13 @@ def compile_class_var_dec b.classVarDec do consume(Tokenizer::KEYWORD) - case current_type - when Tokenizer::KEYWORD - if %w[int char boolean].include? current_token - consume(Tokenizer::KEYWORD) - end - else - consume(Tokenizer::IDENTIFIER) - end + consume_type - begin + consume_seperated(',') do consume(Tokenizer::IDENTIFIER) + end - symbol = current_token - consume(Tokenizer::SYMBOL) - end while symbol == ',' + consume(Tokenizer::SYMBOL, ';') end end @@ -56,13 +48,7 @@ def compile_subroutine b.subroutineDec do consume(Tokenizer::KEYWORD) - case current_type - when Tokenizer::KEYWORD - b.keyword(current_token) - when Tokenizer::IDENTIFIER - b.identifier(current_token) - end - input.advance + try_consume(Tokenizer::KEYWORD, 'void') || consume_type consume(Tokenizer::IDENTIFIER) # subroutine name @@ -76,11 +62,11 @@ def compile_subroutine def compile_parameter_list b.parameterList do - until current_token == ')' - consume(Tokenizer::KEYWORD) - consume(Tokenizer::IDENTIFIER) + return if current_token == ')' - try_consume(Tokenizer::SYMBOL, ',') + consume_seperated(',') do + consume_type + consume(Tokenizer::IDENTIFIER) end end end @@ -136,22 +122,13 @@ def compile_var_dec b.varDec do consume(Tokenizer::KEYWORD, 'var') + consume_type - case current_type - when Tokenizer::KEYWORD - if %w[int char boolean].include? current_token - consume(Tokenizer::KEYWORD) - end - else + consume_seperated(',') do consume(Tokenizer::IDENTIFIER) end - begin - consume(Tokenizer::IDENTIFIER) - - last_symbol = current_token - consume(Tokenizer::SYMBOL) - end until last_symbol == ';' + consume(Tokenizer::SYMBOL, ';') end end @@ -177,15 +154,7 @@ def compile_do b.doStatement do consume(Tokenizer::KEYWORD, 'do') - consume(Tokenizer::IDENTIFIER) - - if try_consume(Tokenizer::SYMBOL, '.') - consume(Tokenizer::IDENTIFIER) - end - - consume_wrapped('(') do - compile_expression_list - end + consume_subroutine_call consume(Tokenizer::SYMBOL, ';') end @@ -212,15 +181,21 @@ def compile_if consume_wrapped('{') do compile_statements end + + if try_consume(Tokenizer::KEYWORD, 'else') + consume_wrapped('{') do + compile_statements + end + end end end def compile_expression_list b.expressionList do - until current_token == ')' - compile_expression + return if current_token == ')' - try_consume(Tokenizer::SYMBOL, ',') + consume_seperated(',') do + compile_expression end end end @@ -229,7 +204,7 @@ def compile_expression b.expression do compile_term - if %w[+ < & > - ~ = /].include? current_token + while %w[+ - * / & | < > =].include? current_token consume(Tokenizer::SYMBOL) compile_term end @@ -238,34 +213,26 @@ def compile_expression def compile_term b.term do - return if try_consume(Tokenizer::KEYWORD) || - try_consume(Tokenizer::INT_CONST) || - try_consume(Tokenizer::STRING_CONST) + return if try_consume(Tokenizer::INT_CONST) || + try_consume(Tokenizer::STRING_CONST) || + try_consume_wrapped('(') { compile_expression } - case current_type - when Tokenizer::SYMBOL - if %w[- ~].include? current_token - # unary op - consume(Tokenizer::SYMBOL) - compile_term - elsif try_consume_wrapped('(') do - compile_expression - end - end + case current_token + when 'true', 'false', 'null', 'this' #keywordConst + consume(Tokenizer::KEYWORD) + when '-', '~' # unary op + consume(Tokenizer::SYMBOL) + compile_term else consume(Tokenizer::IDENTIFIER) - try_consume_wrapped('[') do - compile_expression - end - - # Possible subroutine calls - if try_consume(Tokenizer::SYMBOL, '.') - consume(Tokenizer::IDENTIFIER) - end - - try_consume_wrapped('(') do - compile_expression_list + case current_token + when '[' + consume_wrapped('[') do + compile_expression + end + when '.', '(' + consume_subroutine_call(skip_identifier: true) end end end @@ -336,4 +303,33 @@ def current_token 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 f480d7d..6976e01 100644 --- a/spec/unit/parser_spec.rb +++ b/spec/unit/parser_spec.rb @@ -4381,6 +4381,159 @@ class Main { 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