Skip to content

Instantly share code, notes, and snippets.

@JoshCheek
Created November 27, 2015 21:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JoshCheek/c3cd01c9f15999a49e36 to your computer and use it in GitHub Desktop.
Save JoshCheek/c3cd01c9f15999a49e36 to your computer and use it in GitHub Desktop.
Writing an algorithm recursively, but accessing results sequentially
# Playing with the idea of continuations to allow a program to be written recursively,
# but accessed sequentially (I'm assuming enumerator uses Fibers underneath,
# but even if not, it's good enough)
class AST
def self.for(*attrs, &inspection)
Class.new(self) {
attr_reader *attrs
define_method(:initialize) do |*values|
attrs.zip(values).each { |name, value| instance_variable_set "@#{name}", value }
end
define_method(:inspect, &inspection)
}
end
def inspect(first=false)
"#{"#<AST:" if first}#{yield}#{">" if first}"
end
end
Times = AST.for(:left, :right) { |first=true| super(first) { "(#{left.inspect false}*#{right.inspect false})" } }
Plus = AST.for(:left, :right) { |first=true| super(first) { "(#{left.inspect false}+#{right.inspect false})" } }
Num = AST.for(:value) { |first=true| super(first) { value } }
class Interpreter
def initialize(ast)
@ast = ast
@evaluator = Enumerator.new do |y|
evaluate(@ast) { |*args| y.yield *args }
end
end
include Enumerable
def each
return to_enum :each unless block_given?
loop { yield *self.next }
end
def next
@evaluator.next
end
def evaluate(node, &block)
case node
when Times
result = evaluate(node.left, &block) * evaluate(node.right, &block)
block.call result, node
result
when Plus
result = evaluate(node.left, &block) + evaluate(node.right, &block)
block.call result, node
result
when Num
yield node.value, node
node.value
end
end
end
# (2+1)*(1+3)
ast = Times.new(
Plus.new(
Num.new(2),
Num.new(1),
),
Plus.new(
Num.new(1),
Num.new(3),
)
)
# iterate with .next
interpreter = Interpreter.new ast
# left
interpreter.next # => [2, #<AST:2>]
interpreter.next # => [1, #<AST:1>]
interpreter.next # => [3, #<AST:(2+1)>]
# right
interpreter.next # => [1, #<AST:1>]
interpreter.next # => [3, #<AST:3>]
interpreter.next # => [4, #<AST:(1+3)>]
# root
interpreter.next # => [12, #<AST:((2+1)*(1+3))>]
Interpreter.new(ast).to_a
# => [[2, #<AST:2>],
# [1, #<AST:1>],
# [3, #<AST:(2+1)>],
# [1, #<AST:1>],
# [3, #<AST:3>],
# [4, #<AST:(1+3)>],
# [12, #<AST:((2+1)*(1+3))>]]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment