From 060c08c9599cc5e520d3979cd31c663e6555882c Mon Sep 17 00:00:00 2001 From: Ian Dees Date: Tue, 6 Mar 2012 14:30:36 -0800 Subject: [PATCH] Rough cut at function definition; still need code cleanup, passing tests; @brixen rocks! --- bin/thnad | 2 - example.thnad | 20 +++++-- lib/thnad/compiler.rb | 131 +++++++++++++++++++++++++++++++++++++---- lib/thnad/nodes.rb | 19 +++++- lib/thnad/parser.rb | 14 ++++- lib/thnad/transform.rb | 11 ++++ test/fake_builder.rb | 5 ++ test/test_nodes.rb | 20 ++++++- test/test_parser.rb | 12 ++++ test/test_transform.rb | 12 ++++ 10 files changed, 221 insertions(+), 25 deletions(-) diff --git a/bin/thnad b/bin/thnad index 4e593ba..c074b39 100755 --- a/bin/thnad +++ b/bin/thnad @@ -5,5 +5,3 @@ loader = Rubinius::CodeLoader.new(ARGV.first) method = loader.load_compiled_file(ARGV.first, Rubinius::Signature, 18) result = Rubinius.run_script(method) - - diff --git a/example.thnad b/example.thnad index e8ee2c6..896c7f7 100644 --- a/example.thnad +++ b/example.thnad @@ -1,5 +1,17 @@ -if (0) { - print(42) -} else { - print(667) +function eq(a,b) { + if(minus(a,b)) { + 0 + } else { + 1 + } } + +function factorial(n) { + if (eq(n, 1)) { + 1 + } else { + times(n, factorial(minus(n, 1))) + } +} + +print(factorial(4)) diff --git a/lib/thnad/compiler.rb b/lib/thnad/compiler.rb index a7b2097..5ff08bc 100644 --- a/lib/thnad/compiler.rb +++ b/lib/thnad/compiler.rb @@ -2,6 +2,22 @@ require 'thnad/transform' require 'thnad/builtins' +class Rubinius::Generator + def set_thnad_receiver + push_rubinius + push_literal :Thnad + push_const :Object + send :new, 0 + send :const_set, 2 + pop + end + + def push_thnad_receiver + push_const :Thnad + end +end + + module Thnad class Compiler def initialize(filename) @@ -12,19 +28,110 @@ def initialize(filename) def compile tree = parse_source + funcs, exprs = split_functions tree classname = @classname klass = make_class(classname) - method = klass.dynamic_method :main do |generator| + g = Rubinius::Generator.new + g.name = @classname.to_sym + g.file = @filename.to_sym + g.set_line 1 + g.push_rubinius + g.add_scope + g.set_thnad_receiver + + g.push_thnad_receiver + inner = Rubinius::Generator.new + inner.name = :minus + inner.file = @filename.to_sym + inner.set_line 1 + inner.required_args = 2 + inner.total_args = inner.required_args + inner.push_local 0 + inner.push_local 1 + inner.send :-, 1 + inner.ret + inner.close + inner.use_detected + inner.encode + + cm = inner.package Rubinius::CompiledMethod + + g.push_rubinius + g.push_literal :minus + g.push_literal cm + g.push_scope + g.push_thnad_receiver + g.send :attach_method, 4 + g.pop + + g.push_thnad_receiver + inner = Rubinius::Generator.new + inner.name = :times + inner.file = @filename.to_sym + inner.set_line 1 + inner.required_args = 2 + inner.total_args = inner.required_args + inner.push_local 0 + inner.push_local 1 + inner.send :*, 1 + inner.ret + inner.close + inner.use_detected + inner.encode + + cm = inner.package Rubinius::CompiledMethod + + g.push_rubinius + g.push_literal :times + g.push_literal cm + g.push_scope + g.push_thnad_receiver + g.send :attach_method, 4 + g.pop + + funcs.each do |f| + inner = Rubinius::Generator.new + inner.name = f.name.to_sym + inner.file = @filename.to_sym + inner.set_line 1 + inner.required_args = f.params.count + inner.total_args = inner.required_args + context = Hash.new - tree.each do |e| - e.eval(context, generator) - end + f.eval(context, inner) - generator.ret + inner.close + inner.use_detected + inner.encode + + cm = inner.package Rubinius::CompiledMethod + + g.push_rubinius + g.push_literal f.name.to_sym + g.push_literal cm + g.push_scope + g.push_thnad_receiver + g.send :attach_method, 4 + g.pop end - Rubinius::CompiledFile.dump method, @outname, Rubinius::Signature, 18 + context = Hash.new + exprs.each do |e| + e.eval(context, g) + end + + g.push_true + g.ret + + g.close + g.use_detected + p g.stream + g.encode + + main = g.package Rubinius::CompiledMethod + + Rubinius::CompiledFile.dump main, @outname, Rubinius::Signature, 18 end private @@ -41,14 +148,12 @@ def parse_source tree.is_a?(Array) ? tree : [tree] end - def write_result(builder) - destination = File.expand_path(@classname + '.class') + def split_functions(tree) + first_expr = tree.index { |t| ! t.is_a?(Function) } + funcs = first_expr ? tree[0...first_expr] : tree + exprs = first_expr ? tree[first_expr..-1] : [] - builder.generate do |n, b| - File.open(destination, 'wb') do |f| - f.write b.generate - end - end + [funcs, exprs] end def make_class(name) diff --git a/lib/thnad/nodes.rb b/lib/thnad/nodes.rb index de7ef17..b90bef2 100644 --- a/lib/thnad/nodes.rb +++ b/lib/thnad/nodes.rb @@ -7,14 +7,17 @@ def eval(context, builder) class Name < Struct.new :name def eval(context, builder) - value = context.fetch(name) { raise "Unknown parameter #{name}" } - builder.push value + param_names = context[:params] || [] + position = param_names.index(name) + raise "Unknown parameter #{name}" unless position + + builder.push_local position end end class Funcall < Struct.new :name, :args def eval(context, builder) - builder.push_self + builder.push_thnad_receiver args.each { |a| a.eval(context, builder) } builder.allow_private builder.send name.to_sym, args.length @@ -47,4 +50,14 @@ def eval(context, builder) endif_label.set! end end + + class Function < Struct.new :name, :params, :body + def eval(context, builder) + param_names = (params.is_a?(Array) ? params : [params]).map(&:name) + context[:params] = param_names + + self.body.eval(context, builder) + builder.ret + end + end end diff --git a/lib/thnad/parser.rb b/lib/thnad/parser.rb index 9cbc57d..d12421f 100644 --- a/lib/thnad/parser.rb +++ b/lib/thnad/parser.rb @@ -35,6 +35,18 @@ class Parser < Parslet::Parser rule(:if_kw) { str('if') >> space? } rule(:else_kw) { str('else') >> space? } - rule(:root) { expression } + rule(:func) { + func_kw >> name.as(:func) >> params >> body + } + + rule(:func_kw) { str('function') >> space? } + + rule(:params) { + lparen >> + ((name.as(:param) >> (comma >> name.as(:param)).repeat(0)).maybe).as(:params) >> + rparen + } + + rule(:root) { func.repeat(0) >> expression } end end diff --git a/lib/thnad/transform.rb b/lib/thnad/transform.rb index ef9020f..99a6fbb 100644 --- a/lib/thnad/transform.rb +++ b/lib/thnad/transform.rb @@ -18,5 +18,16 @@ class Transform < Parslet::Transform rule(:cond => simple(:cond), :if_true => {:body => simple(:if_true)}, :if_false => {:body => simple(:if_false)}) { Conditional.new(cond, if_true, if_false) } + + rule(:param => simple(:param)) { param } + rule(:params => sequence(:params)) { params } + + rule(:func => simple(:func), + :params => simple(:name), + :body => simple(:body)) { Function.new(func.name, [name], body) } + + rule(:func => simple(:func), + :params => sequence(:params), + :body => simple(:body)) { Function.new(func.name, params, body) } end end diff --git a/test/fake_builder.rb b/test/fake_builder.rb index 477f3c1..e853b61 100644 --- a/test/fake_builder.rb +++ b/test/fake_builder.rb @@ -21,6 +21,10 @@ def initialize @num_labels = 0 end + def int + 'int' + end + def new_label @num_labels += 1 FakeLabel.new self, @num_labels @@ -32,5 +36,6 @@ def method_missing(name, *args, &block) @result += args.empty? ? "#{name}\n" : "#{name} #{args.map(&:inspect).join(', ')}\n" + block.call(self) if name.to_s == 'dynamic_method' end end diff --git a/test/test_nodes.rb b/test/test_nodes.rb index eb493ce..cb0deee 100644 --- a/test/test_nodes.rb +++ b/test/test_nodes.rb @@ -25,14 +25,14 @@ end it 'emits a function call' do - @context['foo'] = 667 + @context[:params] = 'foo' input = Thnad::Funcall.new 'baz', [Thnad::Number.new(42), Thnad::Name.new('foo')] expected = < {:name => 'foo'}, + :params => {:param => {:name => 'x'}}, + :body => {:number => '5'}} + @parser.func.parse(input).must_equal expected + end end diff --git a/test/test_transform.rb b/test/test_transform.rb index b78e9a3..5166995 100644 --- a/test/test_transform.rb +++ b/test/test_transform.rb @@ -63,4 +63,16 @@ @transform.apply(input).must_equal expected end + + it 'transforms a function definition' do + input = {:func => {:name => 'foo'}, + :params => {:param => {:name => 'x'}}, + :body => {:number => '5'}} + expected = Thnad::Function.new \ + 'foo', + [Thnad::Name.new('x')], + Thnad::Number.new(5) + + @transform.apply(input).must_equal expected + end end