copied thnad to get a kickstart

This commit is contained in:
Torsten Ruger 2014-04-24 15:43:20 +03:00
parent f97205300f
commit d90ea3dd26
11 changed files with 580 additions and 0 deletions

27
lib/vm/builtins.rb Normal file
View File

@ -0,0 +1,27 @@
require 'java'
java_import java.lang.System
java_import java.io.PrintStream
module Thnad
module Builtins
def add_builtins
public_static_method 'print', [], int, int do
iload 0
getstatic System, :out, PrintStream
swap
invokevirtual PrintStream, :print, [void, int]
ldc 0
ireturn
end
public_static_method 'minus', [], int, int, int do
iload 0
iload 1
isub
ireturn
end
end
end
end

74
lib/vm/compiler.rb Normal file
View File

@ -0,0 +1,74 @@
require 'bitescript'
require 'thnad/parser'
require 'thnad/transform'
require 'thnad/builtins'
module Thnad
class Compiler
def initialize(filename)
@filename = filename
@classname = File.basename(@filename, '.thnad')
end
def compile
tree = parse_source
funcs, exprs = split_functions tree
classname = @classname
builder = BiteScript::FileBuilder.build(@filename) do
public_class classname, object do |klass|
klass.extend(Builtins)
klass.add_builtins
funcs.each do |f|
context = Hash.new
f.eval(context, klass)
end
klass.public_static_method 'main', [], void, string[] do |method|
context = Hash.new
exprs.each do |e|
e.eval(context, method)
end
method.returnvoid
end
end
end
write_result builder
end
private
def parse_source
source = File.expand_path(@filename)
program = IO.read source
parser = Parser.new
transform = Transform.new
syntax = parser.parse(program)
tree = transform.apply(syntax)
Array(tree)
end
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] : []
[funcs, exprs]
end
def write_result(builder)
destination = File.expand_path(@classname + '.class')
builder.generate do |n, b|
File.open(destination, 'wb') do |f|
f.write b.generate
end
end
end
end
end

70
lib/vm/copied_parser.rb Normal file
View File

@ -0,0 +1,70 @@
#start from an example of parslet (assumed to be checked out at the same level as crystal for now)
$:.unshift File.dirname(__FILE__) + "/../../../parslet/lib"
require 'pp'
require 'rspec'
require 'parslet'
require 'parslet/rig/rspec'
require 'parslet/convenience'
class InfixExpressionParser < Parslet::Parser
root :variable_assignment_list
rule(:space) { match[' '] }
def cts atom
atom >> space.repeat
end
def infix *args
Infix.new(*args)
end
# This is the heart of the infix expression parser: real simple definitions
# for all the pieces we need.
rule(:mul_op) { cts match['*/'] }
rule(:add_op) { cts match['+-'] }
rule(:digit) { match['0-9'] }
rule(:integer) { cts digit.repeat(1).as(:int) }
rule(:expression) { infix_expression(integer,
[mul_op, 2, :left],
[add_op, 1, :right]) }
# And now adding variable assignments to that, just to a) demonstrate this
# embedded in a bigger parser, and b) make the example interesting.
rule(:variable_assignment_list) {
variable_assignment.repeat(1) }
rule(:variable_assignment) {
identifier.as(:ident) >> equal_sign >> expression.as(:exp) >> eol }
rule(:identifier) {
cts (match['a-z'] >> match['a-zA-Z0-9'].repeat) }
rule(:equal_sign) {
cts str('=') }
rule(:eol) {
cts(str("\n")) | any.absent? }
end
class InfixInterpreter < Parslet::Transform
rule(int: simple(:int)) { Integer(int) }
rule(ident: simple(:ident), exp: simple(:result)) { |d|
d[:doc][d[:ident].to_s.strip.to_sym] = d[:result] }
rule(l: simple(:l), o: /^\*/, r: simple(:r)) { l * r }
rule(l: simple(:l), o: /^\+/, r: simple(:r)) { l + r }
end
input = <<ASSIGNMENTS
a = 1
b = 2
c = 3 * 25
d = 100 + 3*4
ASSIGNMENTS
puts input
int_tree = InfixExpressionParser.new.parse_with_debug(input)
bindings = {}
result = InfixInterpreter.new.apply(int_tree, doc: bindings)
pp bindings

54
lib/vm/nodes.rb Normal file
View File

@ -0,0 +1,54 @@
module Thnad
class Number < Struct.new :value
def eval(context, builder)
builder.ldc value
end
end
class Name < Struct.new :name
def eval(context, builder)
param_names = context[:params] || []
position = param_names.index(name)
raise "Unknown parameter #{name}" unless position
builder.iload position
end
end
class Funcall < Struct.new :name, :args
def eval(context, builder)
args.each { |a| a.eval(context, builder) }
types = [builder.int] * (args.length + 1)
builder.invokestatic builder.class_builder, name, types
end
end
class Conditional < Struct.new :cond, :if_true, :if_false
def eval(context, builder)
cond.eval context, builder
builder.ifeq :else
if_true.eval context, builder
builder.goto :endif
builder.label :else
if_false.eval context, builder
builder.label :endif
end
end
class Function < Struct.new :name, :params, :body
def eval(context, builder)
param_names = [params].flatten.map(&:name)
context[:params] = param_names
types = [builder.int] * (param_names.count + 1)
builder.public_static_method(self.name, [], *types) do |method|
self.body.eval(context, method)
method.ireturn
end
end
end
end

52
lib/vm/parser.rb Normal file
View File

@ -0,0 +1,52 @@
require 'parslet'
module Thnad
class Parser < Parslet::Parser
rule(:name) { match('[a-z]').repeat(1).as(:name) >> space? }
rule(:number) { match('[0-9]').repeat(1).as(:number) >> space? }
rule(:space) { match('\s').repeat(1) }
rule(:space?) { space.maybe }
rule(:args) {
lparen >>
((expression.as(:arg) >> (comma >> expression.as(:arg)).repeat(0)).maybe).as(:args) >>
rparen
}
rule(:funcall) { name.as(:funcall) >> args }
rule(:expression) { cond | funcall | number | name }
rule(:lparen) { str('(') >> space? }
rule(:rparen) { str(')') >> space? }
rule(:comma) { str(',') >> space? }
rule(:cond) {
if_kw >> lparen >> expression.as(:cond) >> rparen >>
body.as(:if_true) >>
else_kw >>
body.as(:if_false)
}
rule(:body) { lbrace >> expression.as(:body) >> rbrace }
rule(:lbrace) { str('{') >> space? }
rule(:rbrace) { str('}') >> space? }
rule(:comma) { str(',') >> space? }
rule(:if_kw) { str('if') >> space? }
rule(:else_kw) { str('else') >> space? }
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

33
lib/vm/transform.rb Normal file
View File

@ -0,0 +1,33 @@
require 'parslet'
require 'thnad/nodes'
module Thnad
class Transform < Parslet::Transform
rule(:number => simple(:value)) { Number.new(value.to_i) }
rule(:name => simple(:name)) { Name.new(name.to_s) }
rule(:arg => simple(:arg)) { arg }
rule(:args => sequence(:args)) { args }
rule(:funcall => simple(:funcall),
:args => simple(:args)) { Funcall.new(funcall.name, [args]) }
rule(:funcall => simple(:funcall),
:args => sequence(:args)) { Funcall.new(funcall.name, args) }
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

21
test/test/fake_builder.rb Normal file
View File

@ -0,0 +1,21 @@
class FakeBuilder
attr_reader :result
def initialize
@result = ''
end
def class_builder
'example'
end
def int
'int'
end
def method_missing(name, *args, &block)
@result += ([name] + args.flatten).join(', ').sub(',', '')
@result += "\n"
block.call(self) if name.to_s == 'public_static_method'
end
end

22
test/test/graph_helper.rb Normal file
View File

@ -0,0 +1,22 @@
require 'gritty'
def dump(obj, name)
g = digraph do
graph_attribs << 'bgcolor=black'
node_attribs << 'color=white'
node_attribs << 'penwidth=2'
node_attribs << 'fontcolor=white'
node_attribs << 'labelloc=c'
node_attribs << 'fontname="Courier New"'
node_attribs << 'fontsize=36'
edge_attribs << 'color=white'
edge_attribs << 'penwidth=2'
builder = Gritty::NodeBuilder.new self
builder.build obj, 'root'
save name, 'pdf'
end
end

77
test/test/test_nodes.rb Normal file
View File

@ -0,0 +1,77 @@
$: << File.expand_path(File.dirname(__FILE__) + '/../lib')
$: << File.expand_path(File.dirname(__FILE__))
require 'minitest/autorun'
require 'minitest/spec'
require 'thnad/nodes'
require 'fake_builder'
include Thnad
describe 'Nodes' do
before do
@context = Hash.new
@builder = FakeBuilder.new
end
it 'emits a number' do
input = Thnad::Number.new 42
expected = <<HERE
ldc 42
HERE
input.eval @context, @builder
@builder.result.must_equal expected
end
it 'emits a function call' do
@context[:params] = ['foo']
input = Thnad::Funcall.new 'baz', [Thnad::Number.new(42),
Thnad::Name.new('foo')]
expected = <<HERE
ldc 42
iload 0
invokestatic example, baz, int, int, int
HERE
input.eval @context, @builder
@builder.result.must_equal expected
end
it 'emits a conditional' do
input = Thnad::Conditional.new \
Thnad::Number.new(0),
Thnad::Number.new(42),
Thnad::Number.new(667)
expected = <<HERE
ldc 0
ifeq else
ldc 42
goto endif
label else
ldc 667
label endif
HERE
input.eval @context, @builder
@builder.result.must_equal expected
end
it 'emits a function definition' do
input = Thnad::Function.new \
'foo',
Thnad::Name.new('x'),
Thnad::Number.new(5)
expected = <<HERE
public_static_method foo, int, int
ldc 5
ireturn
HERE
input.eval @context, @builder
@builder.result.must_equal expected
end
end

72
test/test/test_parser.rb Normal file
View File

@ -0,0 +1,72 @@
$: << File.expand_path(File.dirname(__FILE__) + '/../lib')
require 'minitest/autorun'
require 'minitest/spec'
require 'thnad/parser'
include Thnad
describe Parser do
before do
@parser = Thnad::Parser.new
end
it 'reads a number' do
input = '42 '
expected = {:number => '42'}
@parser.number.parse(input).must_equal expected
end
it 'reads a name' do
input = 'foo '
expected = {:name => 'foo'}
@parser.name.parse(input).must_equal expected
end
it 'reads an argument list' do
input = '(42, foo)'
expected = {:args => [{:arg => {:number => '42'}},
{:arg => {:name => 'foo'}}]}
@parser.args.parse(input).must_equal expected
end
it 'reads a function call' do
input = 'baz(42, foo)'
expected = {:funcall => {:name => 'baz' },
:args => [{:arg => {:number => '42'}},
{:arg => {:name => 'foo'}}]}
@parser.funcall.parse(input).must_equal expected
end
it 'reads a conditional' do
input = <<HERE
if (0) {
42
} else {
667
}
HERE
expected = {:cond => {:number => '0'},
:if_true => {:body => {:number => '42'}},
:if_false => {:body => {:number => '667'}}}
@parser.cond.parse(input).must_equal expected
end
it 'reads a function definition' do
input = <<HERE
function foo(x) {
5
}
HERE
expected = {:func => {:name => 'foo'},
:params => {:param => {:name => 'x'}},
:body => {:number => '5'}}
@parser.func.parse(input).must_equal expected
end
end

View File

@ -0,0 +1,78 @@
$: << File.expand_path(File.dirname(__FILE__) + '/../lib')
require 'minitest/autorun'
require 'minitest/spec'
require 'thnad/transform'
include Thnad
describe Transform do
before do
@transform = Thnad::Transform.new
end
it 'transforms a number' do
input = {:number => '42'}
expected = Thnad::Number.new(42)
@transform.apply(input).must_equal expected
end
it 'transforms a name' do
input = {:name => 'foo'}
expected = Thnad::Name.new('foo')
@transform.apply(input).must_equal expected
end
it 'transforms an argument list' do
input = {:args => [{:arg => {:number => '42'}},
{:arg => {:name => 'foo'}}]}
expected = [Thnad::Number.new(42),
Thnad::Name.new('foo')]
@transform.apply(input).must_equal expected
end
it 'transforms a single-argument function call' do
input = {:funcall => {:name => 'foo'},
:args => [{:arg => {:number => '42'}}]}
expected = Thnad::Funcall.new 'foo', [Thnad::Number.new(42)]
@transform.apply(input).must_equal expected
end
it 'transforms a multi-argument function call' do
input = {:funcall => {:name => 'baz'},
:args => [{:arg => {:number => '42'}},
{:arg => {:name => 'foo'}}]}
expected = Thnad::Funcall.new 'baz', [Thnad::Number.new(42),
Thnad::Name.new('foo')]
@transform.apply(input).must_equal expected
end
it 'transforms a conditional' do
input = {:cond => {:number => '0'},
:if_true => {:body => {:number => '42'}},
:if_false => {:body => {:number => '667'}}}
expected = Thnad::Conditional.new \
Thnad::Number.new(0),
Thnad::Number.new(42),
Thnad::Number.new(667)
@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