copied thnad to get a kickstart
This commit is contained in:
parent
f97205300f
commit
d90ea3dd26
27
lib/vm/builtins.rb
Normal file
27
lib/vm/builtins.rb
Normal 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
74
lib/vm/compiler.rb
Normal 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
70
lib/vm/copied_parser.rb
Normal 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
54
lib/vm/nodes.rb
Normal 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
52
lib/vm/parser.rb
Normal 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
33
lib/vm/transform.rb
Normal 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
21
test/test/fake_builder.rb
Normal 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
22
test/test/graph_helper.rb
Normal 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
77
test/test/test_nodes.rb
Normal 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
72
test/test/test_parser.rb
Normal 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
|
78
test/test/test_transform.rb
Normal file
78
test/test/test_transform.rb
Normal 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
|
Loading…
x
Reference in New Issue
Block a user