profile
viewpoint
If you are wondering where the data of this site comes from, please visit https://api.github.com/users/tomstuart/events. GitMemory does not store any data, but only uses NGINX to cache data for a period of time. The idea behind GitMemory is simply to give users a better reading experience.

oracle/truffleruby 2581

A high performance implementation of the Ruby programming language. Built on the GraalVM by Oracle Labs.

tomstuart/computationbook 464

Example code for Understanding Computation

raganwald/javascript-allonge-six 79

https://leanpub.com/javascriptallongesix

aanand/wick 33

NOT MAINTAINED

kddnewton/regular_expression 27

A regular expression engine written in Ruby.

tomstuart/dual_number 17

A Ruby implementation of dual numbers.

tomstuart/govuk-exhibit 4

A GOV.UK exhibit

PullRequestReviewEvent
PullRequestReviewEvent
PullRequestReviewEvent
PullRequestReviewEvent

Pull request review commentwildmaples/jack-virtual-machine

Add StaticTest.vm

+@111+D=A+@SP+A=M+M=D+@SP+M=M+1+@333+D=A+@SP+A=M+M=D+@SP+M=M+1+@888+D=A+@SP+A=M+M=D+@SP+M=M+1+@SP+AM=M-1+D=M+@R13+M=D+@24+D=A+@R14+M=D+@R13+D=M+@R14+A=M+M=D+@SP+AM=M-1+D=M+@R13+M=D+@19+D=A+@R14+M=D+@R13+D=M+@R14+A=M+M=D+@SP+AM=M-1+D=M+@R13+M=D+@17
@StaticTest.1
wildmaples

comment created time in 4 days

Pull request review commentwildmaples/jack-virtual-machine

Add StaticTest.vm

+@111+D=A+@SP+A=M+M=D+@SP+M=M+1+@333+D=A+@SP+A=M+M=D+@SP+M=M+1+@888+D=A+@SP+A=M+M=D+@SP+M=M+1+@SP+AM=M-1+D=M+@R13+M=D+@24
@StaticTest.8
wildmaples

comment created time in 4 days

Pull request review commentwildmaples/jack-virtual-machine

Add StaticTest.vm

+@111+D=A+@SP+A=M+M=D+@SP+M=M+1+@333+D=A+@SP+A=M+M=D+@SP+M=M+1+@888+D=A+@SP+A=M+M=D+@SP+M=M+1+@SP+AM=M-1+D=M+@R13+M=D+@24+D=A+@R14+M=D+@R13+D=M+@R14+A=M+M=D+@SP+AM=M-1+D=M+@R13+M=D+@19
@StaticTest.3
wildmaples

comment created time in 4 days

Pull request review commentwildmaples/jack-virtual-machine

Add StaticTest.vm

+@111+D=A+@SP+A=M+M=D+@SP+M=M+1+@333+D=A+@SP+A=M+M=D+@SP+M=M+1+@888+D=A+@SP+A=M+M=D+@SP+M=M+1+@SP+AM=M-1+D=M+@R13+M=D+@24+D=A+@R14+M=D+@R13+D=M+@R14+A=M+M=D+@SP+AM=M-1+D=M+@R13+M=D+@19+D=A+@R14+M=D+@R13+D=M+@R14+A=M+M=D+@SP+AM=M-1+D=M+@R13+M=D+@17+D=A+@R14+M=D+@R13+D=M+@R14+A=M+M=D+@19+D=M+@SP+A=M+M=D+@SP+M=M+1+@17
@StaticTest.1
wildmaples

comment created time in 4 days

Pull request review commentwildmaples/jack-virtual-machine

Add StaticTest.vm

+@111+D=A+@SP+A=M+M=D+@SP+M=M+1+@333+D=A+@SP+A=M+M=D+@SP+M=M+1+@888+D=A+@SP+A=M+M=D+@SP+M=M+1+@SP+AM=M-1+D=M+@R13+M=D+@24+D=A+@R14+M=D+@R13+D=M+@R14+A=M+M=D+@SP+AM=M-1+D=M+@R13+M=D+@19+D=A+@R14+M=D+@R13+D=M+@R14+A=M+M=D+@SP+AM=M-1+D=M+@R13+M=D+@17+D=A+@R14+M=D+@R13+D=M+@R14+A=M+M=D+@19
@StaticTest.3
wildmaples

comment created time in 4 days

PullRequestReviewEvent

Pull request review commentwildmaples/jack-virtual-machine

Add StaticTest.vm

+@111+D=A+@SP+A=M+M=D+@SP+M=M+1+@333+D=A+@SP+A=M+M=D+@SP+M=M+1+@888+D=A+@SP+A=M+M=D+@SP+M=M+1+@SP+AM=M-1+D=M+@R13+M=D+@24+D=A+@R14+M=D+@R13+D=M+@R14+A=M+M=D+@SP+AM=M-1+D=M+@R13+M=D+@19+D=A+@R14+M=D+@R13+D=M+@R14+A=M+M=D+@SP+AM=M-1+D=M+@R13+M=D+@17+D=A+@R14+M=D+@R13+D=M+@R14+A=M+M=D+@19+D=M+@SP+A=M+M=D+@SP+M=M+1+@17+D=M+@SP+A=M+M=D+@SP+M=M+1+@SP+AM=M-1+D=M+A=A-1+M=M-D+@24
@StaticTest.8
wildmaples

comment created time in 4 days

Pull request review commentwildmaples/jack-virtual-machine

Metaprogram the integration tests

 require "test_helper"  class VMTranslatorIntegrationTest < Minitest::Test-  def test_integration_test-    assembly_code = `bin/vm-translator examples/Push.vm`-    expected = File.read("test/fixtures/Push.asm")-    assert_equal(expected, assembly_code)-  end--  def test_integration_test_simple_add-    assembly_code = `bin/vm-translator examples/SimpleAdd.vm`-    expected = File.read("test/fixtures/SimpleAdd.asm")-    assert_equal(expected, assembly_code)-  end--  def test_integration_test_simple_eq-    assembly_code = `bin/vm-translator examples/SimpleEq.vm`-    expected = File.read("test/fixtures/SimpleEq.asm")-    assert_equal(expected, assembly_code)-  end--  def test_integration_test_stack_test-    assembly_code = `bin/vm-translator examples/StackTest.vm`-    expected = File.read("test/fixtures/StackTest.asm")-    assert_equal(expected, assembly_code)-  end--  def test_integration_test_basic_test-    assembly_code = `bin/vm-translator examples/BasicTest.vm`-    expected = File.read("test/fixtures/BasicTest.asm")-    assert_equal(expected, assembly_code)-  end--  def test_integration_test_pointer_test-    assembly_code = `bin/vm-translator examples/PointerTest.vm`-    expected = File.read("test/fixtures/PointerTest.asm")-    assert_equal(expected, assembly_code)+  Dir.glob("examples/**").each do |file_path|+    file_name = /examples\/(.*).vm/.match(file_path)[1]

This is a common operation, so happily there’s an easier way to do it. Ruby calls the last part of a file path its “base name”, and provides the File.basename method to extract it:

>> File.basename("/foo/bar/baz.txt")
=> "baz.txt"

What’s particularly useful is that File.basename takes an optional second argument which is a suffix to remove from the base name if present:

>> File.basename("/foo/bar/baz.txt", ".txt")
=> "baz"
>> File.basename("/foo/bar/qux.html", ".txt")
=> "qux.html"

Because this can get a bit repetitive (e.g. “I’ve just globbed for all the *.txt files, and now I want to remove all their .txt suffixes”), File.basename supports a special ".*" suffix which means “remove any extension” so we don’t have to restate the exact suffix we want to strip:

>> File.basename("/foo/bar/baz.txt", ".*")
=> "baz"
>> File.basename("/foo/bar/qux.html", ".*")
=> "qux"
wildmaples

comment created time in 4 days

Pull request review commentwildmaples/jack-virtual-machine

Metaprogram the integration tests

 require "test_helper"  class VMTranslatorIntegrationTest < Minitest::Test-  def test_integration_test-    assembly_code = `bin/vm-translator examples/Push.vm`-    expected = File.read("test/fixtures/Push.asm")-    assert_equal(expected, assembly_code)-  end--  def test_integration_test_simple_add-    assembly_code = `bin/vm-translator examples/SimpleAdd.vm`-    expected = File.read("test/fixtures/SimpleAdd.asm")-    assert_equal(expected, assembly_code)-  end--  def test_integration_test_simple_eq-    assembly_code = `bin/vm-translator examples/SimpleEq.vm`-    expected = File.read("test/fixtures/SimpleEq.asm")-    assert_equal(expected, assembly_code)-  end--  def test_integration_test_stack_test-    assembly_code = `bin/vm-translator examples/StackTest.vm`-    expected = File.read("test/fixtures/StackTest.asm")-    assert_equal(expected, assembly_code)-  end--  def test_integration_test_basic_test-    assembly_code = `bin/vm-translator examples/BasicTest.vm`-    expected = File.read("test/fixtures/BasicTest.asm")-    assert_equal(expected, assembly_code)-  end--  def test_integration_test_pointer_test-    assembly_code = `bin/vm-translator examples/PointerTest.vm`-    expected = File.read("test/fixtures/PointerTest.asm")-    assert_equal(expected, assembly_code)+  Dir.glob("examples/**").each do |file_path|

If we’re going to use Dir.glob here (versus e.g. the simpler Dir.each_child(…) or Dir.new(…).each_child) then we should take more advantage of its power. Two ideas come to mind:

  • if we use *.vm instead of ** in the pattern, it’ll explicitly select only files with a .vm extension, which more clearly provides the guarantee that the subsequent lines of code assume (although I admit it doesn’t make any practical difference right now!); and
  • if we pass a base: "examples" argument instead of including examples/ in the pattern, the resulting paths will be relative to that base directory (e.g. "BasicTest.vm") rather than including it as a prefix (e.g. "examples/BasicTest.vm"), which will save us the work of removing that prefix later.
wildmaples

comment created time in 4 days

Pull request review commentwildmaples/jack-virtual-machine

Metaprogram the integration tests

 require "test_helper"  class VMTranslatorIntegrationTest < Minitest::Test-  def test_integration_test-    assembly_code = `bin/vm-translator examples/Push.vm`-    expected = File.read("test/fixtures/Push.asm")-    assert_equal(expected, assembly_code)-  end--  def test_integration_test_simple_add-    assembly_code = `bin/vm-translator examples/SimpleAdd.vm`-    expected = File.read("test/fixtures/SimpleAdd.asm")-    assert_equal(expected, assembly_code)-  end--  def test_integration_test_simple_eq-    assembly_code = `bin/vm-translator examples/SimpleEq.vm`-    expected = File.read("test/fixtures/SimpleEq.asm")-    assert_equal(expected, assembly_code)-  end--  def test_integration_test_stack_test-    assembly_code = `bin/vm-translator examples/StackTest.vm`-    expected = File.read("test/fixtures/StackTest.asm")-    assert_equal(expected, assembly_code)-  end--  def test_integration_test_basic_test-    assembly_code = `bin/vm-translator examples/BasicTest.vm`-    expected = File.read("test/fixtures/BasicTest.asm")-    assert_equal(expected, assembly_code)-  end--  def test_integration_test_pointer_test-    assembly_code = `bin/vm-translator examples/PointerTest.vm`-    expected = File.read("test/fixtures/PointerTest.asm")-    assert_equal(expected, assembly_code)+  Dir.glob("examples/**").each do |file_path|+    file_name = /examples\/(.*).vm/.match(file_path)[1]+    test_name = "test_integration_#{file_name}"++    define_method(test_name) do

Excellent! 👏🏻

wildmaples

comment created time in 4 days

PullRequestReviewEvent
PullRequestReviewEvent

Pull request review commentwildmaples/jack-virtual-machine

Implement PointerTest.vm

 def write_arithmetic(command)   def close     @out.close   end++  private++  def get_memory_address_for_pop(segment, index)+    case segment+    when *STATIC_SEGMENT_BASE_ADDRESS.keys+      "@#{STATIC_SEGMENT_BASE_ADDRESS[segment] + index}"+    when *DYNAMIC_SEGMENT_BASE_ADDRESS.keys+       <<~EOF.chomp+        @#{index}+        D=A+        @#{DYNAMIC_SEGMENT_BASE_ADDRESS[segment]}+        A=M+D+      EOF+    end+  end++  def get_value_for_push(segment, index)+    case segment+    when *STATIC_SEGMENT_BASE_ADDRESS.keys+      "@#{STATIC_SEGMENT_BASE_ADDRESS[segment] + index}\nD=M"+    when *DYNAMIC_SEGMENT_BASE_ADDRESS.keys+      <<~EOF.chomp+        @#{index}+        D=A+        @#{DYNAMIC_SEGMENT_BASE_ADDRESS[segment]}+        A=M+D+        D=M+      EOF+    when "constant"+      "@#{index}\nD=A"

Fantastic. This change, plus the consistent shape of the two helper methods, makes an interesting fact way more obvious at a glance: push supports the constant segment (as a separate special case), but pop doesn’t.

wildmaples

comment created time in 4 days

Pull request review commentwildmaples/jack-virtual-machine

Implement PointerTest.vm

 def write_arithmetic(command)   def close     @out.close   end++  private++  def get_memory_address_for_pop(segment, index)

This is definitely a better name. FWIW I think you could go even further and say #get_address_for_pop — the “memory” is redundant because “address” already implies it.

wildmaples

comment created time in 4 days

Pull request review commentwildmaples/jack-virtual-machine

Implement PointerTest.vm

 def initialize(out)     @label_counter = 0   end -  SEGMENT_TO_SYMBOL_HASH = {+  DYNAMIC_SEGMENT_BASE_ADDRESS = {

The name DYNAMIC_SEGMENT_BASE_ADDRESS is a big improvement and I appreciate the consistency with STATIC_SEGMENT_BASE_ADDRESS, so I’m happy to stick with it if you are.

But I’d like to highlight a small distinction to make sure you’re comfortable with it: ARG (for example) is not the base address of the argument segment, it’s the address of the memory location where that base address is stored. This is a meaningfully different situation from STATIC_SEGMENT_BASE_ADDRESS, where e.g. 5 really is the base address of the temp segment.

You already had to understand that to get the implementation working in the first place, but it’s the sort of fiddly detail you could easily forget later, and the naming of these variables doesn’t give your future self any clue that a subtle distinction exists. So an alternative might be something like DYNAMIC_SEGMENT_POINTER_ADDRESS — I’ve chosen the word “pointer” out of nowhere and perhaps it isn’t immediately obvious what it means in this context, but it’s different from the word “base” in the other variable name so it at least provides a hint that there’s something different about this hash even if it doesn’t constitute a full explanation.

wildmaples

comment created time in 4 days

Pull request review commentwildmaples/jack-virtual-machine

Implement PointerTest.vm

 def write_arithmetic(command)   def close     @out.close   end++  private++  def get_memory_address_for_pop(segment, index)+    case segment+    when *STATIC_SEGMENT_BASE_ADDRESS.keys+      "@#{STATIC_SEGMENT_BASE_ADDRESS[segment] + index}"+    when *DYNAMIC_SEGMENT_BASE_ADDRESS.keys+       <<~EOF.chomp

😅

      <<~EOF.chomp
wildmaples

comment created time in 4 days

PullRequestReviewEvent
PullRequestReviewEvent

Pull request review commentwildmaples/jack-virtual-machine

Implement PointerTest.vm

 def write_arithmetic(command)   def close     @out.close   end++  private++  def get_memory_address_for_pop(segment, index)+    case segment+    when *STATIC_SEGMENT_BASE_ADDRESS.keys+      "@#{STATIC_SEGMENT_BASE_ADDRESS[segment] + index}"+    when *DYNAMIC_SEGMENT_BASE_ADDRESS.keys

Hell yeah! I love this! 👏🏻

wildmaples

comment created time in 4 days

Pull request review commentwildmaples/jack-virtual-machine

Implement PointerTest.vm

 def write_arithmetic(command)   def close     @out.close   end++  private++  def get_final_memory_address_for_pop(segment, index)+    case segment+    when "temp", "pointer"+      starting_index = segment == "temp" ? 5 : 3+      "@#{starting_index + index}"+    else+      <<~EOF+        @#{index}+        D=A+        @#{SEGMENT_TO_SYMBOL_HASH[segment]}+        A=M+D+      EOF+    end+  end++  def get_value_for_push(segment, index)+    case segment+    when "temp", "pointer"+      starting_index = segment == "temp" ? 5 : 3+      "@#{starting_index + index}\nD=M"+    when "argument", "local", "this", "that"+      <<~EOF+        @#{index}+        D=A+        @#{SEGMENT_TO_SYMBOL_HASH[segment]}+        A=M+D+        D=M+      EOF+    else

Again, what value(s) of segment is this else intended for? Can we be explicit so that the reader doesn’t have to puzzle it out?

wildmaples

comment created time in 6 days

Pull request review commentwildmaples/jack-virtual-machine

Implement PointerTest.vm

 def initialize(out)   }    def write_push_pop(command, segment, index)-    if command == :C_POP-      if segment == "temp"-        final_memory_address = "@#{5+index}"-      else-        final_memory_address = <<~EOF-          @#{index}-          D=A-          @#{SEGMENT_TO_SYMBOL_HASH[segment]}-          A=M+D-        EOF-      end-+    case command+    when :C_POP       @out.puts <<~EOF         @SP         AM=M-1         D=M         @R13         M=D-        #{final_memory_address.chomp}+        #{get_final_memory_address_for_pop(segment, index).chomp}

Great name! 👏🏻

On reflection, now that you’ve moved the address computation logic itself elsewhere, maybe “final” and “memory” are no longer adding much value here? #get_address_for_pop means basically the same thing, and would be nicely consistent with #get_value_for_push.

wildmaples

comment created time in 6 days

Pull request review commentwildmaples/jack-virtual-machine

Implement PointerTest.vm

 def write_arithmetic(command)   def close     @out.close   end++  private++  def get_final_memory_address_for_pop(segment, index)+    case segment+    when "temp", "pointer"+      starting_index = segment == "temp" ? 5 : 3+      "@#{starting_index + index}"+    else+      <<~EOF

In my opinion, extracting this (and #get_value_for_push) into a helper method makes it even more tempting to chomp the heredoc

      <<~EOF.chomp

…so that the caller gets a guarantee that they can use the return value as a drop-in replacement for a single line of assembly and have it work correctly in that context without having to worry about extraneous newlines. (I guess what I’m saying is: the helper method provides an abstraction, and it’s a slightly more convenient one if it can avoid leaking this incidental detail.)

wildmaples

comment created time in 6 days

Pull request review commentwildmaples/jack-virtual-machine

Implement PointerTest.vm

 def initialize(out)   }    def write_push_pop(command, segment, index)-    if command == :C_POP-      if segment == "temp"-        final_memory_address = "@#{5+index}"-      else-        final_memory_address = <<~EOF-          @#{index}-          D=A-          @#{SEGMENT_TO_SYMBOL_HASH[segment]}-          A=M+D-        EOF-      end-+    case command

Nice! This communicates useful detail up-front about this long conditional: only the value of command affects which branch will be taken.

wildmaples

comment created time in 6 days

Pull request review commentwildmaples/jack-virtual-machine

Implement PointerTest.vm

 def write_push_pop(command, segment, index)         M=D       EOF -    else-      if segment == "temp"-        final_memory_address = "@#{5+index}\nD=M"-      elsif SEGMENT_TO_SYMBOL_HASH.key?(segment)-        final_memory_address = <<~EOF-          @#{index}-          D=A-          @#{SEGMENT_TO_SYMBOL_HASH[segment]}-          A=M+D-          D=M-        EOF-      else-        final_memory_address = "@#{index}\nD=A"-      end-+    when :C_PUSH       @out.puts <<~EOF-        #{final_memory_address.chomp}+        #{get_value_for_push(segment, index).chomp}

Another great name! 👏🏻

wildmaples

comment created time in 6 days

Pull request review commentwildmaples/jack-virtual-machine

Implement PointerTest.vm

 def write_arithmetic(command)   def close     @out.close   end++  private++  def get_final_memory_address_for_pop(segment, index)+    case segment+    when "temp", "pointer"+      starting_index = segment == "temp" ? 5 : 3+      "@#{starting_index + index}"+    else+      <<~EOF+        @#{index}+        D=A+        @#{SEGMENT_TO_SYMBOL_HASH[segment]}+        A=M+D+      EOF+    end+  end++  def get_value_for_push(segment, index)+    case segment+    when "temp", "pointer"+      starting_index = segment == "temp" ? 5 : 3+      "@#{starting_index + index}\nD=M"+    when "argument", "local", "this", "that"

This former else clause has gone too far to the other extreme — it’s listing the names of the “dynamic” segments verbatim. That makes it completely explicit which literal values of segment it’s looking for, but obscures the motivation that we’re choosing those values precisely because they’re the ones which can be successfully looked up in SEGMENT_TO_SYMBOL_HASH. A reader can figure that out by looking at the rest of the code but it’s not super obvious at this point.

As well as being a little unclear, this approach is saving up maintenance pain for the future if we ever need to add a new “dynamic” segment, because after adding it to SEGMENT_TO_SYMBOL_HASH we’d have to remember to update this when clause as well.

We could address both these problems by using the hash as a single source of truth:

    when *SEGMENT_TO_SYMBOL_HASH.keys
wildmaples

comment created time in 6 days

Pull request review commentwildmaples/jack-virtual-machine

Implement PointerTest.vm

 def write_arithmetic(command)   def close     @out.close   end++  private++  def get_final_memory_address_for_pop(segment, index)+    case segment+    when "temp", "pointer"+      starting_index = segment == "temp" ? 5 : 3

The same lines of code appear here and below (in #get_value_for_push) and it’s not obvious why. When I think about it I believe it’s because temp and pointer are the “static” segments whose base addresses are fixed at some known value (5 and 3 respectively) rather than a mutable value which lives in RAM and needs to be loaded at runtime.

Is it meaningful that we use a ternary operator to look up the base address of a static segment, but a hash to look up the storage location of the base address of a dynamic segment? If so then fine, but if it’s just incidental then maybe we should use the same mechanism everywhere to more clearly communicate that it’s a similar idea.

wildmaples

comment created time in 6 days