Trolls of 2013 - zenspider.com
Transcription
Trolls of 2013 - zenspider.com
Trolls of 2013 Ryan Davis, Seattle.rb Trolls of 2013 MWRC 2013, Salt Lake City, UT Trolls of 2013 Ryan Davis, Seattle.rb Setting Expectations • Followup of my talk, “Occupy Ruby: Why We Need to Moderate the 1%” • 130 Slides in 30 minutes. 4.3 spm • I must go quickly, so please hold questions. • Ummm... no. MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Let’s Write an Interpreter! MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Setting Expectations • TODO • 160 Slides in 30 minutes. 5.3 spm. • Boatloads of content. Almost all code. • I was asked to “Hurt Brains”. MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Expanded to 45 minutes?!? MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Added 15 minutes of kittens to the end MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb OMG WHY?!? MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Because I’m a pervert! MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Wait… No! MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Because we can! MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb The “Uby” Interpreter MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Branching Logic: if/else/elsif if condition then true_code else false_code end MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Loops: while while condition code end MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Local Variables meaning = 42 pi = 3 MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Functions def fib n if n <= 2 then 1 else fib(n-2) + fib(n-1) end end MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Functions local variables “primitive” function calls def fib n if n <= 2 then recursive function calls 1 else fib(n-2) + fib(n-1) end end MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Uby Roadmap Functions Loops Variables Conditionals Environment Runtime Parser MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Parsing: ruby_parser MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Functions Loops Variables Conditionals Environment Runtime Parser MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb RubyParser.new.parse " def fib n if n <= 2 then 1 else fib(n-2) + fib(n-1) end end " MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb :lvar :fib :defn :args :if :n :n :< :call :lit 2 :fib :lit 1 nil :lvar :call :call :- :n :call :+ :call :lit nil 2 :fib :lvar :n :call :- :lit MWRC 2013, Salt Lake City, UT 1 Let’s Write an Interpreter! Ryan Davis, Seattle.rb :lvar :fib :defn :args :if :n :n :< :call :lit 2 :fib :lit 1 nil :lvar :call :call :- :n :call :+ :call :lit nil 2 :fib :lvar :n :call :- :lit MWRC 2013, Salt Lake City, UT 1 Let’s Write an Interpreter! Ryan Davis, Seattle.rb :lvar :fib :n :defn :< :args :call :if :lit :n :lit 2 1 :fib :call nil :lvar :call :- :n :call :+ :call :lit nil 2 :fib :lvar :n :call :- :lit MWRC 2013, Salt Lake City, UT 1 Let’s Write an Interpreter! Ryan Davis, Seattle.rb :lvar :fib :n :defn :< :args :call :if :lit :n :lit 2 1 :fib :call :call nil :lvar :call :- :+ :lit :call nil :n 2 :fib :lvar :n :call :- :lit MWRC 2013, Salt Lake City, UT 1 Let’s Write an Interpreter! Ryan Davis, Seattle.rb condition :defn :fib :lvar :call :if :n :< :lit :args 2 then :n :lit 1 else :call nil :fib :lvar :call :call :n :- :+ :lit :call nil 2 :fib :lvar :n :call :- :lit MWRC 2013, Salt Lake City, UT 1 Let’s Write an Interpreter! Ryan Davis, Seattle.rb s(:defn, :fib, s(:args, :n), s(:if, s(:call, s(:lvar, :n), :<=, s(:lit, 2)), s(:lit, 1), s(:call, s(:call, nil, :fib, s(:call, s(:lvar, :n), :-, s(:lit, 2))), :+, s(:call, nil, :fib, s(:call, s(:lvar, :n), :-, s(:lit, 1)))))) MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb S-Expression Vocabulary Sexp Sub-sexps s(:call, s(:lit, 3), :+, s(:lit, 4)) Head/ Type (car) Rest (cdr) MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb All for free using ruby_parser MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb OK… No more talk about parsing. MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Further study on parsers: Michael Jackson’s Parsing Expressions in Ruby MWRC 2011 http://www.confreaks.com/videos/582 MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Interpreting via sexp_processor MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Functions Loops Variables Conditionals Environment Runtime Parser MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb How to get 7 from 3+4? 3 + 4 ??? ??? MWRC 2013, Salt Lake City, UT 7 Let’s Write an Interpreter! Ryan Davis, Seattle.rb Start with the source 3 + 4 MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Parse it 3 + 4 s(:call, s(:lit, 3), :+, s(:lit, 4)) MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Then process by type process_call 3 + 4 s(:call, s(:lit, 3), :+, s(:lit, 4)) process_lit MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb First with the inner values 3 + 4 s(:call, s(:lit, 3), :+, s(:lit, 4)) process_lit MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb First with the inner values 3 + 4 s(:call, 3, :+, s(:lit, 4)) process_lit MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb First with the inner values 3 + 4 s(:call, 3, :+, 4) MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb then with the outer process_call 3 + 4 s(:call, 3, :+, 4) MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb then with the outer 3 + 4 proc { |a, b| a + b }[3, 4] MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb resulting in a final value 3 + 4 proc { |a, b| a + b }[3, 4] MWRC 2013, Salt Lake City, UT 7 Let’s Write an Interpreter! Ryan Davis, Seattle.rb SexpProcessor class UbyInterpreter def process_lit s s.last end end s(:lit, 3) 3 MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Test Driven Interpreters MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Test Driven Functions Loops Variables Conditionals Environment Runtime Parser MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Unit Tests def test_sanity val = lang.eval("3 + 4") assert_equal 7, val end MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Unit Tests Input Source def test_sanity val = lang.eval("3 + 4") assert_equal 7, val end Expected Value MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Premature Refactoring def assert_eval exp, src assert_equal exp, lang.eval(src) end def test_sanity assert_eval 7, "3 + 4" end Expected Value Input Source MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Sanity Test: 3+4 MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Test Driven Functions Loops Variables Conditionals Environment Runtime Parser MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Test Infrastructure class TestUbyInterpreter < MiniTest::Unit::TestCase attr_accessor :int def setup self.int = UbyInterpreter.new end def assert_eval exp, src, msg = nil assert_equal exp, int.eval(src), msg end end MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Our First Test: class TestUbyInterpreter < MiniTest::Unit::TestCase attr_accessor :int def setup self.int = UbyInterpreter.new end Old Code def assert_eval exp, src assert_equal exp, ri.eval(src) end def test_sanity assert_eval 3, "3" assert_eval 7, "3 + 4" end end New Code MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Our First Test: class TestUbyInterpreter < MiniTest::Unit::TestCase Old Code # ... def test_sanity assert_eval 3, "3" assert_eval 7, "3 + 4" end end New Code MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Our first error: # Running tests: E Finished tests in 0.000364s, 2747.2527 tests/s, 0.0000 assertions/s. 1) Error: TestUbyInterpreter#test_sanity: NameError: uninitialized constant TestUbyInterpreter::UbyInterpreter ./test/test_ruby_interpreter.rb:11:in `setup' MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Define the constant class UbyInterpreter < SexpInterpreter VERSION = "1.0.0" end MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Wash, rinse… # Running tests: E Finished tests in 0.000578s, 1730.1038 tests/s, 0.0000 assertions/s. 1) Error: TestUbyInterpreter#test_sanity: NoMethodError: private method `eval' called for #<UbyInterpreter:0x105be17d8> ./test/test_ruby_interpreter.rb:15:in `assert_eval' ./test/test_ruby_interpreter.rb:19:in `test_sanity' MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Implement eval & parse class UbyInterpreter < SexpInterpreter # ... attr_accessor :parser def initialize super self.parser = Ruby19Parser.new end def eval src process parse src end def parse src self.parser.process src end end MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Finally! # Running tests: E Finished tests in 0.001365s, 732.6007 tests/s, 0.0000 assertions/s. 1) Error: TestUbyInterpreter#test_sanity: UnknownNodeError: Bug! Unknown node-type :lit to UbyInterpreter ./lib/ruby_interpreter.rb:17:in `eval' ./test/test_ruby_interpreter.rb:15:in `assert_eval' ./test/test_ruby_interpreter.rb:19:in `test_sanity' MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Implement process_lit class UbyInterpreter < SexpInterpreter # ... def process_lit s s.last end end MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Tada! Second Assertion! 1) Error: TestUbyInterpreter#test_sanity: UnknownNodeError: Bug! Unknown node-type :call to UbyInterpreter ./lib/ruby_interpreter.rb:17:in `eval' ./test/test_ruby_interpreter.rb:15:in `assert_eval' ./test/test_ruby_interpreter.rb:20:in `test_sanity' MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Define :call generically class UbyInterpreter < SexpInterpreter # ... def process_call s raise "Boom: #{s.inspect}" end end MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb See sub-sexps 1) Error: TestUbyInterpreter#test_sanity: RuntimeError: Boom: s(:call, s(:lit, 3), :+, s(:lit, 4)) ./lib/ruby_interpreter.rb:35:in `process_call' ./lib/ruby_interpreter.rb:19:in `eval' ./test/test_ruby_interpreter.rb:17:in `assert_eval' ./test/test_ruby_interpreter.rb:22:in `test_sanity' MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Unpack sub-sexps class UbyInterpreter < SexpInterpreter # ... def process_call s _, recv, msg, *args = s raise "Boom: #{recv}, #{msg}, #{args.inspect}" end end MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Unpacked sub-sexps 1) Error: TestUbyInterpreter#test_sanity: RuntimeError: Boom: s(:lit, 3), +, [s(:lit, 4)] ./lib/ruby_interpreter.rb:31:in `process_call' MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Process sub-sexps class UbyInterpreter < SexpInterpreter def process_call s _, recv, msg, *args = s recv = process recv args.map! { |sub| process sub } raise "Boom: #{recv}, #{msg}, #{args.inspect}" end end MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Processed Values 1) Error: TestUbyInterpreter#test_sanity: RuntimeError: Boom: 3, +, [4] ./lib/ruby_interpreter.rb:43:in `process_call' MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Cheat: Send to Ruby class UbyInterpreter < SexpInterpreter def process_call s _, recv, msg, *args = s recv = process recv args.map! { |sub| process sub } recv.send(msg, *args) # big ol' hack… end end MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Sanity! # Running tests: . Finished tests in 0.001625s, 615.3846 tests/s, 1230.7692 assertions/s. 1 tests, 2 assertions, 0 failures, 0 errors, 0 skips MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb class UbyInterpreter < SexpInterpreter VERSION = "1.0.0" attr_accessor :parser def initialize super self.parser = Ruby19Parser.new end def eval src process parse src end Yay! n w o l b Over or! t a l u c l Ca def parse src self.parser.process src end def process_lit s s.last end def process_call s _, recv, msg, *args = s recv = process recv args.map! { |sub| process sub } recv.send(msg, *args) # big ol' hack… end end MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Conditionals & Truthiness MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Test Driven Functions Loops Variables Conditionals Environment Runtime Parser MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Add :if tests class TestUbyInterpreter < MiniTest::Unit::TestCase # ... def test_if assert_eval 42, "if true end then 42 else 24 end" def test_if_falsey assert_eval 24, "if nil then 42 else 24 end" assert_eval 24, "if false then 42 else 24 end" end end MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Unknown :if 1) Error: TestUbyInterpreter#test_if: UnknownNodeError: Bug! Unknown node-type :if to UbyInterpreter ./lib/ruby_interpreter.rb:17:in `eval' ./test/test_ruby_interpreter.rb:15:in `assert_eval' ./test/test_ruby_interpreter.rb:34:in `test_if' 2) Error: TestUbyInterpreter#test_if_falsey: UnknownNodeError: Bug! Unknown node-type :if to UbyInterpreter ./lib/ruby_interpreter.rb:17:in `eval' ./test/test_ruby_interpreter.rb:15:in `assert_eval' ./test/test_ruby_interpreter.rb:38:in `test_if_falsey' 3 tests, 2 assertions, 0 failures, 2 errors, 0 skips MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Define :if generically class UbyInterpreter < SexpInterpreter # ... def process_if s raise "Boom: #{s.inspect}" end end MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb See sub-sexps 1) Error: TestUbyInterpreter#test_if: RuntimeError: Boom: s(:if, s(:true), s(:lit, 42), s(:lit, 24)) ./lib/ruby_interpreter.rb:38:in `process_if' ... ./lib/ruby_interpreter.rb:17:in `eval' ./test/test_ruby_interpreter.rb:15:in `assert_eval' ./test/test_ruby_interpreter.rb:34:in `test_if' 2) Error: TestUbyInterpreter#test_if_falsey: RuntimeError: Boom: s(:if, s(:nil), s(:lit, 42), s(:lit, 24)) ./lib/ruby_interpreter.rb:38:in `process_if' ... ./lib/ruby_interpreter.rb:17:in `eval' ./test/test_ruby_interpreter.rb:15:in `assert_eval' ./test/test_ruby_interpreter.rb:38:in `test_if_falsey' MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Process sub-sexps class UbyInterpreter < SexpInterpreter # ... def process_if s _, c, t, f = s c = process c if c then process t else process f end end end Why evaluate c before t & f? if true then puts "happy" else system "rm -rf /" end MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Sub-sexps fail 1) Error: TestUbyInterpreter#test_if: UnknownNodeError: Bug! Unknown node-type :true to UbyInterpreter ./lib/ruby_interpreter.rb:40:in `process_if' ./lib/ruby_interpreter.rb:17:in `eval' ./test/test_ruby_interpreter.rb:15:in `assert_eval' ./test/test_ruby_interpreter.rb:34:in `test_if' 2) Error: TestUbyInterpreter#test_if_falsey: UnknownNodeError: Bug! Unknown node-type :nil to UbyInterpreter ./lib/ruby_interpreter.rb:40:in `process_if' ./lib/ruby_interpreter.rb:17:in `eval' ./test/test_ruby_interpreter.rb:15:in `assert_eval' ./test/test_ruby_interpreter.rb:38:in `test_if_falsey' MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Define :nil and :true class UbyInterpreter < SexpInterpreter # ... def process_nil s nil end def process_true s true end end MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Third failure 1) Error: TestUbyInterpreter#test_if_falsey: UnknownNodeError: Bug! Unknown node-type :false to UbyInterpreter ./lib/ruby_interpreter.rb:40:in `process_if' ./lib/ruby_interpreter.rb:17:in `eval' ./test/test_ruby_interpreter.rb:15:in `assert_eval' ./test/test_ruby_interpreter.rb:39:in `test_if_falsey' MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Define :false class UbyInterpreter < SexpInterpreter # ... def process_false s false end end MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Voilà! # Running tests: ... Finished tests in 0.002860s, 1048.9510 tests/s, 1748.2517 assertions/s. 3 tests, 5 assertions, 0 failures, 0 errors, 0 skips MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Test Driven Process Add a failing test Tests Pass Process sub-sexps Error node-type Add generic node-type Failure sub-sexp components MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Yes, development this fast… with autotest. MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Brain Hurt Yet? MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Good MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Wait till I get going! Where was I? MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Local Variables MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Test Driven Functions Loops Variables Conditionals Environment Runtime Parser MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Local Variables! class TestUbyInterpreter < MiniTest::Unit::TestCase # ... def test_lvar assert_eval 42, "x = 42; x" end end MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Block?!? 1) Error: TestUbyInterpreter#test_lvar: UnknownNodeError: Bug! Unknown node-type :block to UbyInterpreter ./lib/ruby_interpreter.rb:17:in `eval' ./test/test_ruby_interpreter.rb:15:in `assert_eval' ./test/test_ruby_interpreter.rb:24:in `test_lvar' MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Define :block class UbyInterpreter < SexpInterpreter # ... def process_block s raise "Boom: #{s.inspect}" end end MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Ohhh… 1) Error: TestUbyInterpreter#test_lvar: RuntimeError: Boom: s(:block, s(:lasgn, :x, s(:lit, 42)), s(:lvar, :x)) ./lib/ruby_interpreter.rb:25:in `process_block' ./lib/ruby_interpreter.rb:17:in `eval' ./test/test_ruby_interpreter.rb:15:in `assert_eval' ./test/test_ruby_interpreter.rb:24:in `test_lvar' MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Process all sub-sexps class UbyInterpreter < SexpInterpreter # ... def process_block s result = nil s.rest.each do |sub| result = process sub end result end end MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb :lasgn is undefined 1) Error: TestUbyInterpreter#test_lvar: UnknownNodeError: Bug! Unknown node-type :lasgn to UbyInterpreter ./lib/ruby_interpreter.rb:27:in `process_block' ./lib/ruby_interpreter.rb:26:in `each' ./lib/ruby_interpreter.rb:26:in `process_block' ./lib/ruby_interpreter.rb:17:in `eval' ./test/test_ruby_interpreter.rb:15:in `assert_eval' ./test/test_ruby_interpreter.rb:24:in `test_lvar' MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Define :lasgn class UbyInterpreter < SexpInterpreter # ... def process_lasgn s raise "Boom: #{s.inspect}" end end MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Looks like name/value 1) Error: TestUbyInterpreter#test_lvar: RuntimeError: Boom: s(:lasgn, :x, s(:lit, 42)) ./lib/ruby_interpreter.rb:58:in `process_lasgn' ./lib/ruby_interpreter.rb:27:in `process_block' ./lib/ruby_interpreter.rb:26:in `each' ./lib/ruby_interpreter.rb:26:in `process_block' ./lib/ruby_interpreter.rb:17:in `eval' ./test/test_ruby_interpreter.rb:15:in `assert_eval' ./test/test_ruby_interpreter.rb:24:in `test_lvar' MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Add a stupid table class UbyInterpreter < SexpInterpreter # ... attr_accessor :env def initialize # ... self.env = {} # omg this is horrible end def process_lasgn s _, n, v = s self.env[n] = process v end end Environment x MWRC 2013, Salt Lake City, UT 42 Let’s Write an Interpreter! Ryan Davis, Seattle.rb Now we need to read 1) Error: TestUbyInterpreter#test_lvar: UnknownNodeError: Bug! Unknown node-type :lvar to UbyInterpreter ./lib/ruby_interpreter.rb:29:in `process_block' ./lib/ruby_interpreter.rb:28:in `each' ./lib/ruby_interpreter.rb:28:in `process_block' ./lib/ruby_interpreter.rb:19:in `eval' ./test/test_ruby_interpreter.rb:15:in `assert_eval' ./test/test_ruby_interpreter.rb:24:in `test_lvar' MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Access the table class UbyInterpreter < SexpInterpreter # ... def process_lvar s _, name = s self.env[name] end end MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Voilà # Running tests: .... Finished tests in 0.003666s, 1091.1075 tests/s, 1636.6612 assertions/s. 4 tests, 6 assertions, 0 failures, 0 errors, 0 skips MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Just wait… MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb It gets “better” MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Functions MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Test Driven Functions Loops Variables Conditionals Environment Runtime Parser MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Add :defn Test class TestUbyInterpreter < MiniTest::Unit::TestCase # ... def test_defn assert_eval nil, <<-EOM def double n 2 * n end EOM assert_eval 42, "double(21)" end end MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Unknown :defn 1) Error: TestUbyInterpreter#test_defn: UnknownNodeError: Bug! Unknown node-type :defn to UbyInterpreter ./lib/ruby_interpreter.rb:19:in `eval' ./test/test_ruby_interpreter.rb:17:in `assert_eval' ./test/test_ruby_interpreter.rb:45:in `test_defn' MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Generic :defn class UbyInterpreter < SexpInterpreter # ... def process_defn s raise "Boom: #{s.inspect}" end end MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Examine sub-sexps 1) Error: TestUbyInterpreter#test_defn: RuntimeError: Boom: s(:defn, :double, s(:args, :n), s(:call, s(:lit, 2), :*, s(:lvar, :n))) ./lib/ruby_interpreter.rb:44:in `process_defn' ./lib/ruby_interpreter.rb:19:in `eval' ./test/test_ruby_interpreter.rb:17:in `assert_eval' ./test/test_ruby_interpreter.rb:45:in `test_defn' MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Unpack & Store class UbyInterpreter < SexpInterpreter # ... def process_defn s _, name, args, *body = s self.env[name] = [args, body] nil end end Environment n 8 life 42 pi 3 [ s(:args, :n), [s(:call, s(:lit, 2), :*, s(:lvar, :n))] double ] MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb What’s up with :call? 1) Error: TestUbyInterpreter#test_defn: NoMethodError: undefined method `double' for nil:NilClass ./lib/ruby_interpreter.rb:40:in `send' ./lib/ruby_interpreter.rb:40:in `process_call' ./lib/ruby_interpreter.rb:19:in `eval' ./test/test_ruby_interpreter.rb:17:in `assert_eval' ./test/test_ruby_interpreter.rb:54:in `test_defn' MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb No NilClass#double class UbyInterpreter < SexpInterpreter # ... def process_call s _, recv, msg, *args = s recv = process recv args.map! { |sub| process sub } recv.send(msg, *args) # big ol' hack… end end MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Isolate the Problem class UbyInterpreter < SexpInterpreter # ... def process_call s _, recv, msg, *args = s recv = process recv args.map! { |sub| process sub } if recv then recv.send(msg, *args) # less of a hack else raise "argh" end end end MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Error, Isolated 1) Error: TestUbyInterpreter#test_defn: RuntimeError: argh ./lib/ruby_interpreter.rb:43:in `process_call' ./lib/ruby_interpreter.rb:19:in `eval' ./test/test_ruby_interpreter.rb:17:in `assert_eval' ./test/test_ruby_interpreter.rb:54:in `test_defn' MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Set vars & run the code class UbyInterpreter < SexpInterpreter # ... def process_call s _, recv, msg, *args = s recv = process recv args.map! { |sub| process sub } if recv then recv.send(msg, *args) # less of a hack else decls, body = self.env[msg] decls.rest.zip(args).each do |name, val| self.env[name] = val end process_block s(:block, *body) end end end MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb args... decls... zip bwuh? MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb args s(:call, nil, :double, s(:lit, 21)) Environment n 21 8 life 42 pi 3 [ double ] decls decls.rest.zip(args).each do |k, v| self.env[k] = v end s(:args, :n), [:n].zip([21]).each do |k, v| self.env[k] = v end s(:call, s(:lit, 2), :*, s(:lvar, :n)) self.env[:n] = 21 MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Voilà! # Running tests: ..... Finished tests in 0.011826s, 422.7972 tests/s, 676.4756 assertions/s. 5 tests, 8 assertions, 0 failures, 0 errors, 0 skips MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb “Getting to green just means you don’t have enough tests.” – Kent Beck MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Fibonacci MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Test Driven Functions Loops Variables Conditionals Environment Runtime Parser MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Finally Freakin’ Fibonacci! class TestUbyInterpreter < MiniTest::Unit::TestCase # ... def test_fib assert_eval nil, <<-END def fib n if n <= 2 then 1 else fib(n-2) + fib(n-1) end end END assert_eval 8, "fib(6)" end end MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Whoa… it ran… but failed 1) Failure: TestUbyInterpreter#test_fib [./test/test_ruby_interpreter.rb: 71]: Expected: 8 Actual: 3 6 tests, 10 assertions, 1 failures, 0 errors, 0 skips MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb That stupid Table? Yeah... Environment n 4 6 8 life 42 pi 3 [ fib ] def fib n if n <= 2 then 1 else fib(n-2) + fib(n-1) end end s(:args, :n), n = 8; fib(6) s(:if, ...) n = 6; fib(4) + fib(5) n = 4; ... and so on... MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Wanted: Stacked Hash fib(1) fib(3) fib(5) original n n 1 but acts like: n n 3 life n 5 n 8 n life 42 life pi 3 pi pi 42 5 fib fib fib s(:if, ...) ] MWRC 2013, Salt Lake City, UT 42 3 [ 3 s(:args, :n), [ 42 s(:if, ...) 8 s(:args, ] :n), 3 42 s(:if, ...) [ ] s(:args, :n), 3 fib s(:if, ...) ] 3 s(:if, ...) [ ] s(:args, :n), [ s(:args, :n), fib pi life pi life n 1 Let’s Write an Interpreter! Ryan Davis, Seattle.rb Environment Class env[:x] = 42 scope: scope: scope: env[:x] == ? x 42 a 1 b 2 c 3 x 4 y 5 z 6 all: a 1 b 2 c 3 x 42 y 5 z 6 MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb class UbyInterpreter < SexpInterpreter # ... class Environment def [] k self.all[k] end def []= k, v @env.last[k] = v end def all @env.inject(&:merge) end def scope @env.push({}) Reads from everything Writes to top layer only Flattened. Newest wins Automatic layering yield ensure @env.pop end def initialize @env = [{}] end end end MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Replace stupid table class UbyInterpreter < SexpInterpreter # ... def initialize # ... self.env = Environment.new end # ... end MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Scope every call class UbyInterpreter < SexpInterpreter # ... def process_call s _, recv, msg, *args = s recv = process recv args.map! { |sub| process sub } if recv then recv.send(msg, *args) # less of a hack else self.env.scope do decls.rest.zip(args).each do |name, val| self.env[name] = val end process_block s(:block, *body) end end end end MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Finally # Running tests: ...... Finished tests in 0.016849s, 356.1042 tests/s, 712.2084 assertions/s. 6 tests, 12 assertions, 0 failures, 0 errors, 0 skips MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb While Loops MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Test Driven Functions Loops Variables Conditionals Environment Runtime Parser MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Refactor Fibonnaci class TestUbyInterpreter < MiniTest::Unit::TestCase # ... def define_fib assert_eval nil, <<-END def fib n if n <= 2 then 1 else fib(n-2) + fib(n-1) end end END end end MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Add :while tests class TestUbyInterpreter < MiniTest::Unit::TestCase # ... def test_while_sum_of_fibs define_fib assert_eval 1+1+2+3+5+8+13+21+34+55, <<-EOM n = 1 sum = 0 while n <= 10 sum += fib(n) n += 1 end sum EOM end end MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Unknown :while 1) Error: TestUbyInterpreter#test_while_fib: UnknownNodeError: Bug! Unknown node-type :while to UbyInterpreter ./lib/ruby_interpreter.rb:29:in `process_block' ./lib/ruby_interpreter.rb:28:in `each' ./lib/ruby_interpreter.rb:28:in `process_block' ./lib/ruby_interpreter.rb:19:in `eval' ./test/test_ruby_interpreter.rb:15:in `assert_eval' ./test/test_ruby_interpreter.rb:86:in `test_while_fib' MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Define :while generically class UbyInterpreter < SexpInterpreter # ... def process_while s raise "Boom: #{s.inspect} end end MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb See sub-sexps 1) Error: TestUbyInterpreter#test_while: RuntimeError: Boom: s(:while, s(:call, s(:lvar, :n), :<, s(:lit, 41)), s(:lasgn, :n, s(:call, s(:lvar, :n), :+, s(:lit, 1))), true) ./lib/ruby_interpreter.rb:104:in `process_while' ./lib/ruby_interpreter.rb:29:in `process_block' ./lib/ruby_interpreter.rb:28:in `each' ./lib/ruby_interpreter.rb:28:in `process_block' ./lib/ruby_interpreter.rb:19:in `eval' ./test/test_ruby_interpreter.rb:15:in `assert_eval' ./test/test_ruby_interpreter.rb:62:in `test_while' MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Define :while class UbyInterpreter < SexpInterpreter # ... def process_while s _, cond, *body = s body.pop # pre vs post condition -- ignore for now while process cond process_block s(:block, *body) end end end MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Voilà # Running tests: ....... Finished tests in 0.020218s, 346.2261 tests/s, 544.0696 assertions/s. 7 tests, 11 assertions, 0 failures, 0 errors, 0 skips MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb What Have We Done? MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! require "ruby_parser" require "sexp_processor" class UbyInterpreter < SexpInterpreter VERSION = "1.0.0" attr_accessor :parser attr_accessor :env def initialize super self.parser = Ruby19Parser. new self.env = Environment.new end def eval src process parse src end def parse src self.parser.process src end def process_block s result = nil s.rest.each do |sub| result = process sub end result end def process_call s _, recv, msg, *args = s recv = process recv args.map! { |sub| process sub } if recv then recv.send(msg, *args) else d, body = self.env[msg] self.env.scope do d.rest.zip(args). each do |k, v| self.env[k] = v end process_block s(:block, *body) end end end def process_defn s _, name, args, *body = s self.env[name] = [args, body] nil end def process_false s false end Ryan Davis, Seattle.rb def process_if s _, c, t, f = s c = process c if c then process t else process f end end def process_lasgn s _, name, val = s self.env[name] = process val end def process_lit s s.last end def process_lvar s _, name = s self.env[name] end def process_nil s nil end def process_true s true end def process_while s _, cond, *body = s body.pop while process cond process_block s(:block, *body) end end MWRC 2013, Salt Lake City, UT class Environment def [] k self.all[k] end def []= k, v @env.last[k] = v end def all @env.inject(&:merge) end def scope @env.push({}) yield ensure @env.pop end def initialize @env = [{}] end end end Let’s Write an Interpreter! class TestUbyInterpreter < MiniTest::Unit::TestCase attr_accessor :int Ryan Davis, Seattle.rb def test_fib define_fib def setup self.int = UbyInterpreter.new end assert_eval 8, "fib(6)" end def assert_eval exp, src, msg = nil assert_equal exp, int.eval(src), msg end def test_if assert_eval 42, "if true then 42 else 24 end" end def define_fib assert_eval nil, <<-END def fib n if n <= 2 then 1 else fib(n-2) + fib(n-1) end end END end def test_sanity assert_eval 3, "3" assert_eval 7, "3 + 4" end def test_defn assert_eval nil, <<-EOM n = 24 def double n 2 * n end EOM assert_eval 42, "double(21)" end def test_if_falsey assert_eval 24, "if nil then 42 else 24 end" assert_eval 24, "if false then 42 else 24 end" end def test_lvar assert_eval 42, "x = 42; x" end def test_while_fib define_fib assert_eval 1+1+2+3+5+8+13+21+34+55, <<-EOM n = 1 sum = 0 while n <= 10 sum += fib(n) n += 1 end sum EOM end end MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb “Uby” Language • • • • • • • Basic numeric types, true, false, nil. Conditional branching and looping. Primitive & user defined functions. Local variables & variable scoping. Test-driven, extensible, patterns-based design. ~2 hours, ~130 LOC impl, ~70 LOC test. Fits in one head. MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb What Else Can We Do? • Add richer types: strings, arrays, hashes, etc. • Enforce different scoping rules (eg. ruby's opaque def). • Implement recursive tail-calls. • Add an object system. • Change the way functions are called. MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Further Study MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! s a t n a F Ryan Davis, Seattle.rb o B c i t MWRC 2013, Salt Lake City, UT s k o Let’s Write an Interpreter! Ryan Davis, Seattle.rb MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb No time for questions? Please grab me in the hallway. MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Thank You… MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Bonus: Life Tip MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Typeface: Livory MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Ruby bgkpq Th Qu ∂ MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Scoring Ligatured Words ligatures = /NN|OO|TT|Qu|fff|tt|www|[HMN]E|[HN]K|(?:ff|[Tcfs])h| (?:ff|[cfs])k|(?:ff|[cfi])t|(?:ff|it|tt|[frt])y|[is]p|tt?i|O[CG]| i[vw]|ff?[bj]/ dict = File.readlines("/usr/share/dict/words").map(&:chomp) words = Hash.new 0 dict.each do |word| start = 0 hits = word.scan(ligatures) words[word] = hits.size unless hits.empty? end words.sort_by { |w,v| [-v, w] }.each do |word, score| puts "%2d: %s" % [score, word] end MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Architectonica penitentiaryship antiproductivity MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb MWRC 2013, Salt Lake City, UT Let’s Write an Interpreter! Ryan Davis, Seattle.rb Thank You MWRC 2013, Salt Lake City, UT