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

Similar documents