Created
April 22, 2009 04:52
-
-
Save macournoyer/99586 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Nice = Object clone do( | |
yo = method( | |
"yo" | |
). | |
is = method( | |
"indeed" | |
). | |
). | |
x = Nice clone. | |
x yo print. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Metayo | |
class Object | |
attr_accessor :protos, :value | |
def initialize(value=nil, proto=Lobby[self.class.name.split("::").last]) | |
@value, @slots, @protos = value, {}, [proto].compact | |
end | |
def [](name) | |
message = nil | |
return message if message = @slots[name] | |
@protos.each { |proto| return message if message = proto[name] } | |
raise "Slot not found: #{name.inspect}" | |
end | |
def []=(name, message) | |
@slots[name] = message | |
end | |
def call(*a) | |
self | |
end | |
def to_ruby | |
@value || self | |
end | |
def clone | |
Object.new(@value, self) | |
end | |
def with(value) | |
@value = value | |
self | |
end | |
def inspect | |
"<#{@value || to_s} @slots=#{@slots.keys.inspect}>" | |
end | |
end | |
# Message is a chain of tokens produced when parsing. | |
# 1 print. | |
# is parsed to: | |
# Message.new("1", | |
# Message.new("print")) | |
# You can then +call+ the top level Message to eval it. | |
class Message < Object | |
attr_accessor :next, :previous, :name, :args | |
def initialize(name=nil, _next=nil) | |
@name, @args, self.next = name, [], _next | |
super() | |
end | |
def next=(_next) | |
_next.previous = self if _next | |
@next = _next | |
end | |
def term? | |
@name == "." | |
end | |
def replace(with) | |
@name, @args, @next, @previous = with.name, with.args, with.next, with.previous | |
self | |
end | |
# Remove the message from the chain | |
def pop(until_term=true) | |
tail = @next | |
tail = tail.next while until_term && tail && !tail.term? | |
prev = tail.previous if tail | |
@previous.next = tail | |
@previous = nil | |
prev.next = nil if tail | |
self | |
end | |
# The simplest parsing code I could come up with. | |
# Has some repetition I should get rid of. | |
def self.parse(code) | |
code = code.strip | |
i = 0 | |
messages = [Message.new] | |
while i < code.size | |
case code[i] | |
when ?. | |
if messages.last.name | |
messages.last.next = parse(code[i+1..-1]).last | |
end | |
messages.last.next = Message.new(code[i,1], messages.last.next) | |
break | |
when ?\s | |
if messages.last.name | |
messages.last.next = parse(code[i+1..-1]).last | |
break | |
end | |
when ?" | |
s = i | |
i += 1 | |
i += 1 while code[i] != ?" && i < code.size | |
messages.last.name = code[s..i] | |
when ?\( | |
s = i+1 | |
p = 1 | |
while p > 0 && i < code.size | |
i += 1 | |
p += 1 if code[i] == ?\( | |
p -= 1 if code[i] == ?\) | |
end | |
messages.last.args = parse(code[s..i-1]) | |
when ?, | |
messages.concat parse(code[i+1..-1]) | |
break | |
else | |
messages.last.name = code[0..i] | |
end | |
i += 1 | |
end | |
messages | |
end | |
# Reorder messages so that: | |
# x = 1 id becomes =(x, 1 id) | |
# x + 1 becomes x +(1) | |
def shuffle | |
case @name | |
when "=" | |
var = @previous.dup | |
var.next = nil | |
@args = [var, @next.pop] | |
@previous.replace(self) | |
when "+", "-", "*", "/", "<", ">", "==", "!=" | |
@args = [@next.pop(false)] | |
end | |
@args.each { |arg| arg.shuffle } | |
@next.shuffle if @next | |
self | |
end | |
# Call (eval) the message on the +receiver+. | |
def call(receiver, lobby=receiver, *args) | |
# Find the slot | |
slot = case @name | |
when /^\./ | |
receiver = lobby # reset receiver | |
nil | |
when NilClass | |
nil # HACK Fix this in parser instead | |
when /^\d+/ | |
Lobby["Number"].clone.with(@name.to_i) | |
when /^"(.*)"$/ | |
Lobby["String"].clone.with($1) | |
else | |
raise "calling #{@name} on nil" unless receiver | |
receiver[@name] | |
end | |
# activate the slot | |
if slot | |
value = slot.call(receiver, receiver, *@args) | |
else | |
value = receiver | |
end | |
# pass to next if some | |
if @next | |
@next.call(value, lobby) | |
else | |
value | |
end | |
end | |
def inspect | |
s = @name.inspect | |
s << @args.inspect unless @args.empty? | |
s << " " + @next.inspect.to_s if @next | |
s | |
end | |
end | |
class Method < Object | |
def initialize(receiver, lobby, message) | |
@receiver, @lobby, @message = receiver, lobby, message | |
super() | |
end | |
def call(receiver, lobby, *args) | |
context = @lobby.clone | |
context["self"] = @receiver | |
context["message"] = @message | |
@message.call(context, context, *args) | |
end | |
end | |
def self.eval(code) | |
# Parse | |
message = Message.parse(code).first.shuffle | |
# puts message.inspect | |
# Eval | |
message.call(Lobby) | |
end | |
# Bootstrap | |
base = Object.new(nil, nil) | |
object = base.clone | |
base["clone"] = proc { |caller, context| caller.clone } | |
base["="] = proc { |caller, context, name, value| caller[name.name] = value.call(caller) } | |
base["set_slot"] = proc { |caller, context, name, value| caller[name.call(caller).to_ruby] = value.call(caller) } | |
base["inspect"] = proc { |caller, context| Lobby["String"].clone.with(caller.inspect) } | |
base["id"] = proc { |caller, context| Lobby["Number"].clone.with(caller.object_id) } | |
base["print"] = proc { |caller, context| puts caller.call(caller).to_ruby } | |
base["do"] = proc { |caller, context, body| body.call(caller); caller } | |
base["ruby"] = proc { |caller, context, code| Kernel.eval(code.call(caller).to_ruby) } | |
base["method"] = proc { |caller, context, message| Method.new(caller, context, message) } | |
Lobby = base.clone | |
object.protos.unshift(Lobby) | |
Lobby["Lobby"] = Lobby | |
Lobby["Object"] = object | |
Lobby["Number"] = object.clone.with(0) | |
Lobby["String"] = object.clone.with("") | |
Lobby["Message"] = object.clone | |
Lobby["Method"] = object.clone | |
end | |
if __FILE__ == $PROGRAM_NAME | |
abort "usage: metayo [file.my]" if ARGV.empty? | |
Metayo.eval(File.read(ARGV[0])) | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment