Skip to content
Browse files

ACTORS

  • Loading branch information...
1 parent 57cf1c0 commit 6f2ffc88ddc65ec31632bb53a36ee4736300f36c @txus committed Feb 13, 2014
View
6 examples/actors.lm
@@ -0,0 +1,6 @@
+(let [echo (spawn
+ (fn []
+ (receive [pid msg] (send pid self msg))))]
+ (send echo self "hello world"))
+
+(receive [pid msg] msg)
View
1 lib/lambra.rb
@@ -4,5 +4,6 @@
require 'lambra/syntax'
require 'lambra/parser'
require 'lambra/compiler'
+require 'lambra/generator'
require 'lambra/bytecode_compiler'
require 'lambra/process'
View
2 lib/lambra/bootstrap.rb
@@ -57,7 +57,7 @@ module GlobalScope
:- => PrimitiveFunction.new { |*args| args.inject(:-) },
:/ => PrimitiveFunction.new { |a, b| a / b },
:* => PrimitiveFunction.new { |a, b| a * b },
- :send => PrimitiveFunction.new { |pid, *args| Process[pid].push(*args) },
+ :send => PrimitiveFunction.new { |pid, *args| Lambra::Process[pid].push(*args) },
:sleep => PrimitiveFunction.new { |seconds| sleep(seconds); nil }
})
View
64 lib/lambra/bytecode_compiler.rb
@@ -3,8 +3,8 @@ class BytecodeCompiler
attr_reader :generator
alias g generator
- SPECIAL_FORMS = %w(def fn let spawn)
- PRIMITIVE_FORMS = %w(println + - / * send sleep)
+ SPECIAL_FORMS = %w(def fn let spawn receive)
+ PRIMITIVE_FORMS = %w(println + - / * send sleep self)
def initialize(generator=nil)
@generator = generator || RBX::Generator.new
@@ -40,11 +40,22 @@ def compile(ast, variables=nil, debugging=false)
end
def visit_EvalExpression(o)
+ register_main_process
o.body.accept(self)
end
+ alias_method :visit_Script, :visit_EvalExpression
- def visit_Script(o)
- o.body.accept(self)
+ def register_main_process
+ g.push_cpath_top
+ g.find_const :Lambra
+ g.find_const :Process
+
+ g.push_cpath_top
+ g.find_const :Lambra
+ g.find_const :Process
+ g.send :new, 0
+
+ g.send :register, 1
end
def visit_List(o)
@@ -79,13 +90,15 @@ def visit_SpecialForm(car, cdr)
when 'fn'
args_vector = cdr.shift
arguments = Lambra::AST::ClosureArguments.new(args_vector.line, args_vector.column, args_vector)
- closure = Lambra::AST::Closure.new(arguments.line, arguments.column, arguments, cdr.shift)
+ body = cdr.size > 1 ? Lambra::AST::Sequence.new(cdr.first.line, cdr.first.column, cdr) : cdr.shift
+ closure = Lambra::AST::Closure.new(arguments.line, arguments.column, arguments, body)
closure.accept(self)
when 'let'
args_vector = cdr.shift
arguments = Lambra::AST::LetArguments.new(args_vector.line, args_vector.column, args_vector)
- closure = Lambra::AST::Closure.new(arguments.line, arguments.column, arguments, cdr.shift)
+ body = cdr.size > 1 ? Lambra::AST::Sequence.new(cdr.first.line, cdr.first.column, cdr) : cdr.shift
+ closure = Lambra::AST::Closure.new(arguments.line, arguments.column, arguments, body)
closure.accept(self)
@@ -103,6 +116,9 @@ def visit_SpecialForm(car, cdr)
fn.accept(self)
g.send_with_block :spawn, 0
+
+ when 'receive'
+ Lambra::AST::Receive.new(cdr).accept(self)
end
end
@@ -158,7 +174,12 @@ def visit_PrimitiveForm(car, cdr)
end
def visit_Symbol(o)
+ if o.name.to_sym == :self
+ return visit_Self(o)
+ end
+
set_line(o)
+
local = g.state.scope.search_local(o.name)
if local
local.get_bytecode(g)
@@ -167,6 +188,11 @@ def visit_Symbol(o)
end
end
+ def visit_Self(o)
+ g.push_process
+ g.send :pid, 0
+ end
+
def visit_Number(o)
set_line(o)
g.push_literal o.value
@@ -208,8 +234,10 @@ def visit_Keyword(o)
def visit_Sequence(o)
set_line(o)
- o.elements.compact.each do |element|
+ elems = o.elements.compact
+ elems.each_with_index do |element, idx|
element.accept(self)
+ g.pop unless elems.count - 1 == idx
end
end
@@ -265,6 +293,28 @@ def visit_Map(o)
end
end
+ def visit_Receive(o)
+ args_vector = o.pattern
+ arguments = Lambra::AST::ClosureArguments.new(args_vector.line, args_vector.column, args_vector)
+ cdr = o.actions
+ body = cdr.size > 1 ? Lambra::AST::Sequence.new(cdr.first.line, cdr.first.column, cdr) : cdr.shift
+ closure = Lambra::AST::Closure.new(arguments.line, arguments.column, arguments, body)
+
+ closure.accept(self)
+
+ g.push_process
+ g.send :pop, 0
+ g.send :contents, 0
+
+ arguments.arguments.each do |value|
+ g.shift_array
+ g.swap_stack
+ end
+ g.pop
+
+ g.send :call, arguments.count
+ end
+
def finalize
g.local_names = g.state.scope.local_names
g.local_count = g.state.scope.local_count
View
7 lib/lambra/compiler.rb
@@ -70,14 +70,15 @@ class Generator < RBX::Compiler::Generator
def initialize(*)
super
+ ensure
+ @processor = Lambra::Generator
end
def run
@output = @processor.new
@input.variable_scope = @variable_scope
- b = Lambra::BytecodeCompiler.new(@output)
- b.compile(@input)
- # @input.bytecode @output
+ c = Lambra::BytecodeCompiler.new(@output)
+ c.compile(@input)
@output.close
run_next
end
View
19 lib/lambra/generator.rb
@@ -0,0 +1,19 @@
+module Lambra
+ class Generator < RBX::Generator
+ def push_process
+ push_cpath_top
+ find_const :Lambra
+ find_const :Process
+ send :current, 0
+ end
+
+ def inspect
+ dup_top
+ push_self
+ swap_stack
+ send :inspect, 0
+ send :puts, 1, true
+ pop
+ end
+ end
+end
View
5 lib/lambra/parser/lambra.kpeg
@@ -23,7 +23,7 @@ space = " " | "\t"
nl = "\n"
sp = space+
-- = space*
+- = space*
comment = ";" (!nl .)* nl
@@ -87,7 +87,8 @@ expr = list
many_expr = comment:e many_expr:m { [e] + m }
| expr:e br-sp many_expr:m { [e] + m }
- | expr:e { [e] }
+ | br-sp many_expr:m br-sp { m }
+ | expr:e { [e] }
sequence = many_expr:e { e.size > 1 ? seq(current_line, current_column, e) : e.first }
View
141 lib/lambra/parser/parser.rb
@@ -13,8 +13,7 @@ def initialize(str, debug=false)
# Prepares for parsing +str+. If you define a custom initialize you must
# call this method before #parse
def setup_parser(str, debug=false)
- @string = str
- @pos = 0
+ set_string str, 0
@memoizations = Hash.new { |h,k| h[k] = {} }
@result = nil
@failed_rule = nil
@@ -27,7 +26,6 @@ def setup_parser(str, debug=false)
attr_reader :failing_rule_offset
attr_accessor :result, :pos
-
def current_column(target=pos)
if c = string.rindex("\n", target-1)
return target - c - 1
@@ -61,6 +59,13 @@ def get_text(start)
@string[start..@pos-1]
end
+ # Sets the string and current parsing position for the parser.
+ def set_string string, pos
+ @string = string
+ @string_size = string ? string.size : 0
+ @pos = pos
+ end
+
def show_pos
width = 10
if @pos < width
@@ -167,19 +172,19 @@ def scan(reg)
return nil
end
- if "".respond_to? :getbyte
+ if "".respond_to? :ord
def get_byte
- if @pos >= @string.size
+ if @pos >= @string_size
return nil
end
- s = @string.getbyte @pos
+ s = @string[@pos].ord
@pos += 1
s
end
else
def get_byte
- if @pos >= @string.size
+ if @pos >= @string_size
return nil
end
@@ -228,8 +233,7 @@ def external_invoke(other, rule, *args)
old_pos = @pos
old_string = @string
- @pos = other.pos
- @string = other.string
+ set_string other.string, other.pos
begin
if val = __send__(rule, *args)
@@ -240,8 +244,7 @@ def external_invoke(other, rule, *args)
end
val
ensure
- @pos = old_pos
- @string = old_string
+ set_string old_string, old_pos
end
end
@@ -483,45 +486,48 @@ def initialize(line, column, elements)
attr_reader :elements
end
end
- def char_value(line, column, value)
- ::Lambra::AST::Character.new(line, column, value)
- end
- def false_value(line, column)
- ::Lambra::AST::False.new(line, column)
- end
- def keyword(line, column, name)
- ::Lambra::AST::Keyword.new(line, column, name)
- end
- def list(line, column, elements)
- ::Lambra::AST::List.new(line, column, elements)
- end
- def map(line, column, elements)
- ::Lambra::AST::Map.new(line, column, elements)
- end
- def nil_value(line, column)
- ::Lambra::AST::Nil.new(line, column)
- end
- def number(line, column, value)
- ::Lambra::AST::Number.new(line, column, value)
- end
- def seq(line, column, elements)
- ::Lambra::AST::Sequence.new(line, column, elements)
- end
- def set(line, column, elements)
- ::Lambra::AST::Set.new(line, column, elements)
- end
- def string_value(line, column, value)
- ::Lambra::AST::String.new(line, column, value)
- end
- def symbol(line, column, name)
- ::Lambra::AST::Symbol.new(line, column, name)
- end
- def true_value(line, column)
- ::Lambra::AST::True.new(line, column)
- end
- def vector(line, column, elements)
- ::Lambra::AST::Vector.new(line, column, elements)
+ module ::Lambra::ASTConstruction
+ def char_value(line, column, value)
+ ::Lambra::AST::Character.new(line, column, value)
+ end
+ def false_value(line, column)
+ ::Lambra::AST::False.new(line, column)
+ end
+ def keyword(line, column, name)
+ ::Lambra::AST::Keyword.new(line, column, name)
+ end
+ def list(line, column, elements)
+ ::Lambra::AST::List.new(line, column, elements)
+ end
+ def map(line, column, elements)
+ ::Lambra::AST::Map.new(line, column, elements)
+ end
+ def nil_value(line, column)
+ ::Lambra::AST::Nil.new(line, column)
+ end
+ def number(line, column, value)
+ ::Lambra::AST::Number.new(line, column, value)
+ end
+ def seq(line, column, elements)
+ ::Lambra::AST::Sequence.new(line, column, elements)
+ end
+ def set(line, column, elements)
+ ::Lambra::AST::Set.new(line, column, elements)
+ end
+ def string_value(line, column, value)
+ ::Lambra::AST::String.new(line, column, value)
+ end
+ def symbol(line, column, name)
+ ::Lambra::AST::Symbol.new(line, column, name)
+ end
+ def true_value(line, column)
+ ::Lambra::AST::True.new(line, column)
+ end
+ def vector(line, column, elements)
+ ::Lambra::AST::Vector.new(line, column, elements)
+ end
end
+ include ::Lambra::ASTConstruction
def setup_foreign_grammar; end
# eof = !.
@@ -1399,7 +1405,7 @@ def _expr
return _tmp
end
- # many_expr = (comment:e many_expr:m { [e] + m } | expr:e br-sp many_expr:m { [e] + m } | expr:e { [e] })
+ # many_expr = (comment:e many_expr:m { [e] + m } | expr:e br-sp many_expr:m { [e] + m } | br-sp many_expr:m br-sp { m } | expr:e { [e] })
def _many_expr
_save = self.pos
@@ -1462,16 +1468,45 @@ def _many_expr
_save3 = self.pos
while true # sequence
+ _tmp = apply(:_br_hyphen_sp)
+ unless _tmp
+ self.pos = _save3
+ break
+ end
+ _tmp = apply(:_many_expr)
+ m = @result
+ unless _tmp
+ self.pos = _save3
+ break
+ end
+ _tmp = apply(:_br_hyphen_sp)
+ unless _tmp
+ self.pos = _save3
+ break
+ end
+ @result = begin; m ; end
+ _tmp = true
+ unless _tmp
+ self.pos = _save3
+ end
+ break
+ end # end sequence
+
+ break if _tmp
+ self.pos = _save
+
+ _save4 = self.pos
+ while true # sequence
_tmp = apply(:_expr)
e = @result
unless _tmp
- self.pos = _save3
+ self.pos = _save4
break
end
@result = begin; [e] ; end
_tmp = true
unless _tmp
- self.pos = _save3
+ self.pos = _save4
end
break
end # end sequence
@@ -1660,7 +1695,7 @@ def _root
Rules[:_set] = rule_info("set", "(\"\#{\" expr_list:e \"}\" {set(current_line, current_column, e)} | \"\#{\" \"}\" {set(current_line, current_column, [])})")
Rules[:_map] = rule_info("map", "(\"{\" expr_list:e \"}\" {map(current_line, current_column, Hash[*e])} | \"{\" \"}\" {map(current_line, current_column, {})})")
Rules[:_expr] = rule_info("expr", "(list | literal)")
- Rules[:_many_expr] = rule_info("many_expr", "(comment:e many_expr:m { [e] + m } | expr:e br-sp many_expr:m { [e] + m } | expr:e { [e] })")
+ Rules[:_many_expr] = rule_info("many_expr", "(comment:e many_expr:m { [e] + m } | expr:e br-sp many_expr:m { [e] + m } | br-sp many_expr:m br-sp { m } | expr:e { [e] })")
Rules[:_sequence] = rule_info("sequence", "many_expr:e { e.size > 1 ? seq(current_line, current_column, e) : e.first }")
Rules[:_expr_list_b] = rule_info("expr_list_b", "(expr:e br-sp expr_list_b:l { [e] + l } | expr:e { [e] })")
Rules[:_expr_list] = rule_info("expr_list", "br-sp expr_list_b:b br-sp { b }")
View
36 lib/lambra/process.rb
@@ -2,6 +2,7 @@
module Lambra
class Message
+ attr_reader :contents
def initialize(*args)
@contents = args
end
@@ -27,13 +28,32 @@ def self.count
end
def self.spawn(&fn)
- Thread.new {
- new(&fn).tap { |process|
- pid = Thread.current.object_id
- self[pid] = process
- Thread.current[:process] = process
- }.call
+ x = Thread.new {
+ begin
+ process = new(&fn)
+ register(process)
+ process.call
+ ensure
+ unregister(process)
+ end
}.object_id
+ sleep 0.01 # FIXME
+ x
+ end
+
+ def self.current
+ self[Thread.current.object_id]
+ end
+
+ def self.register(process)
+ pid = Thread.current.object_id
+ self[pid] = process
+ Thread.current[:process] = process
+ end
+
+ def self.unregister(process)
+ @processes.delete(Thread.current.object_id)
+ Thread.current.delete(:process)
end
def initialize(&fn)
@@ -45,6 +65,10 @@ def call
@fn.call
end
+ def pid
+ Thread.current.object_id
+ end
+
def push(*args)
@mailbox.push(Message.new(*args))
end
View
8 lib/lambra/syntax/ast.rb
@@ -137,6 +137,14 @@ def count
@arguments.count
end
end
+
+ class Receive < Node
+ attr_reader :pattern, :actions
+
+ def initialize(clauses)
+ @pattern, *@actions = clauses
+ end
+ end
end
end
View
18 spec/compiler_spec.rb
@@ -37,4 +37,22 @@
'(spawn (fn [] (sleep 10)))'.should eval_to_kind_of(Integer)
end
end
+
+ describe 'self' do
+ it 'is bound to the current process pid' do
+ 'self'.should eval_to_kind_of(Integer)
+ end
+ end
+
+ describe 'receive' do
+ it 'blocks until a new message arrives' do
+ %Q{
+ (let [echo (spawn
+ (fn []
+ (receive [pid msg] (send pid self msg))))]
+ (send echo self "hello world"))
+ (receive [pid msg] msg)
+ }.should eval_to "hello world"
+ end
+ end
end

0 comments on commit 6f2ffc8

Please sign in to comment.
Something went wrong with that request. Please try again.