update to use new ast

soml was updated to have a typed ast layer to make programatic creation
easier
this brings LOTS of syntax change with it, that does not really mean
anything at all
All tests pass again so back to the same
This commit is contained in:
Torsten Ruger 2016-03-07 11:55:28 +02:00
parent d7b210d63a
commit 229f5896c6
22 changed files with 126 additions and 118 deletions

View File

@ -12,7 +12,7 @@ GIT
GIT GIT
remote: git://github.com/salama/soml-parser.git remote: git://github.com/salama/soml-parser.git
revision: 4a9b492dd9fb1d7cbc6f114aee226ff227753568 revision: 5c03db709fa3a326288a57a65643bd9ddf905bec
specs: specs:
soml-parser (0.5.0) soml-parser (0.5.0)
ast (~> 2.1.0) ast (~> 2.1.0)

View File

@ -18,10 +18,10 @@ module Register
def div10 context def div10 context
s = "div_10" s = "div_10"
compiler = Soml::Compiler.new.create_method(:Integer,:div10 ).init_method compiler = Soml::Compiler.new.create_method(:Integer,:div10 ).init_method
me = compiler.process( s(:name , :self) ) me = compiler.process( Soml::NameExpression.new( :self) )
tmp = compiler.process( s(:name , :self) ) tmp = compiler.process( Soml::NameExpression.new( :self) )
q = compiler.process( s(:name , :self) ) q = compiler.process( Soml::NameExpression.new( :self) )
const = compiler.process( s(:int , 1) ) const = compiler.process( Soml::IntegerExpression.new(1) )
# int tmp = self >> 1 # int tmp = self >> 1
compiler.add_code Register.op( s , ">>" , tmp , const) compiler.add_code Register.op( s , ">>" , tmp , const)
# int q = self >> 2 # int q = self >> 2

View File

@ -13,7 +13,7 @@ module Register
compiler = Soml::Compiler.new.create_method(:Object , :get_internal_word , {:index => :Integer}).init_method compiler = Soml::Compiler.new.create_method(:Object , :get_internal_word , {:index => :Integer}).init_method
source = "get_internal_word" source = "get_internal_word"
#Load self by "calling" on_name #Load self by "calling" on_name
me = compiler.process( s(:name , :self) ) me = compiler.process( Soml::NameExpression.new( :self) )
# Load the argument # Load the argument
index = compiler.use_reg :Integer index = compiler.use_reg :Integer
compiler.add_code Register.get_slot(source , :message , Parfait::Message.get_indexed(1), index ) compiler.add_code Register.get_slot(source , :message , Parfait::Message.get_indexed(1), index )
@ -31,7 +31,7 @@ module Register
{:index => :Integer, :value => :Object} ).init_method {:index => :Integer, :value => :Object} ).init_method
source = "set_internal_word" source = "set_internal_word"
#Load self by "calling" on_name #Load self by "calling" on_name
me = compiler.process( s(:name , :self) ) me = compiler.process( Soml::NameExpression.new( :self) )
# Load the index # Load the index
index = compiler.use_reg :Integer index = compiler.use_reg :Integer
compiler.add_code Register.get_slot(source , :message , Parfait::Message.get_indexed(1), index ) compiler.add_code Register.get_slot(source , :message , Parfait::Message.get_indexed(1), index )

View File

@ -13,14 +13,14 @@ module Register
Kernel.emit_syscall( compiler , :putstring ) Kernel.emit_syscall( compiler , :putstring )
compiler.method compiler.method
end end
# self[index] basically. Index is the first arg > 0 # self[index] basically. Index is the first arg > 0
# return (and word sized int) is stored in return_value # return (and word sized int) is stored in return_value
def get_internal_byte context def get_internal_byte context
compiler = Soml::Compiler.new.create_method(:Word , :get_internal_byte , {:index => :Integer }).init_method compiler = Soml::Compiler.new.create_method(:Word , :get_internal_byte , {:index => :Integer }).init_method
source = "get_internal_word" source = "get_internal_word"
#Load self by "calling" on_name #Load self by "calling" on_name
me = compiler.process( s(:name , :self) ) me = compiler.process( Soml::NameExpression.new( :self) )
# Load the argument # Load the argument
index = compiler.use_reg :Integer index = compiler.use_reg :Integer
compiler.add_code Register.get_slot(source , :message , Parfait::Message.get_indexed(1), index ) compiler.add_code Register.get_slot(source , :message , Parfait::Message.get_indexed(1), index )
@ -39,7 +39,7 @@ module Register
{:index => :Integer, :value => :Integer } ).init_method {:index => :Integer, :value => :Integer } ).init_method
source = "set_internal_word" source = "set_internal_word"
#Load self by "calling" on_name #Load self by "calling" on_name
me = compiler.process( s(:name , :self) ) me = compiler.process( Soml::NameExpression.new( :self) )
# Load the index # Load the index
index = compiler.use_reg :Integer index = compiler.use_reg :Integer
compiler.add_code Register.get_slot(source , :message , Parfait::Message.get_indexed(1), index ) compiler.add_code Register.get_slot(source , :message , Parfait::Message.get_indexed(1), index )

View File

@ -19,7 +19,7 @@ module Register
@source = source @source = source
@next = nekst @next = nekst
return unless source return unless source
raise "Source must be string or ast node, not #{source.class}" unless source.is_a?(String) or source.is_a?(AST::Node) raise "Source must be string or ast node, not #{source.class}" unless source.is_a?(String) or source.is_a?(Soml::Code)
end end
attr_reader :source attr_reader :source

View File

@ -32,10 +32,11 @@ module Soml
# Helper function to create a new compiler and compie the statement(s) # Helper function to create a new compiler and compie the statement(s)
def self.compile statement def self.compile statement
compiler = Compiler.new compiler = Compiler.new
compiler.process statement code = Soml.ast_to_code statement
compiler.process code
end end
class Compiler < AST::Processor class Compiler
def initialize( method = nil ) def initialize( method = nil )
@regs = [] @regs = []
@ -46,8 +47,33 @@ module Soml
end end
attr_reader :clazz , :method attr_reader :clazz , :method
def handler_missing node
raise "No handler on_#{node.type}(node)" # Dispatches `code` according to it's class name, for class NameExpression
# a method named `on_NameExpression` is invoked with one argument, the `code`
#
# @param [Soml::Code, nil] code
def process(code)
name = code.class.name.split("::").last
# Invoke a specific handler
on_handler = :"on_#{name}"
if respond_to? on_handler
return send on_handler, code
else
raise "No handler on_#{name}(code) #{code.inspect}"
end
end
# {#process}es each code from `codes` and returns an array of
# results.
#
def process_all(codes)
codes.to_a.map do |code|
process code
end
end
def on_Statements(codes)
process_all codes.statements
end end
# create the method, do some checks and set it as the current method to be added to # create the method, do some checks and set it as the current method to be added to
@ -150,7 +176,8 @@ module Soml
def self.load_parfait def self.load_parfait
each_parfait do |parts| each_parfait do |parts|
self.new.process( parts ) code = Soml.ast_to_code parts
self.new.process( code )
end end
end end

View File

@ -1,19 +1,19 @@
module Soml module Soml
Compiler.class_eval do Compiler.class_eval do
def on_assignment statement def on_Assignment statement
reset_regs # statements reset registers, ie have all at their disposal reset_regs # statements reset registers, ie have all at their disposal
#puts statement.inspect #puts statement.inspect
name , value = *statement # name , value = *statement
name = no_space name.to_a.first name_s = no_space statement.name
v = process(value) v = process(statement.value)
raise "Not register #{v}" unless v.is_a?(Register::RegisterValue) raise "Not register #{v}" unless v.is_a?(Register::RegisterValue)
code = nil code = nil
if( index = @method.has_arg(name)) if( index = @method.has_arg(name_s.name))
# TODO, check type @method.arguments[index].type # TODO, check type @method.arguments[index].type
code = Register.set_slot(statement , v , :message , Parfait::Message.get_indexed(index) ) code = Register.set_slot(statement , v , :message , Parfait::Message.get_indexed(index) )
else # or a local so it is in the frame else # or a local so it is in the frame
index = @method.has_local( name ) index = @method.has_local( name_s.name )
if(index) if(index)
# TODO, check type @method.locals[index].type # TODO, check type @method.locals[index].type
frame = use_reg(:Frame) frame = use_reg(:Frame)

View File

@ -10,41 +10,41 @@ module Soml
# But in the future (in the one that holds great things) we optimize those unneccesay moves away # But in the future (in the one that holds great things) we optimize those unneccesay moves away
def on_int expression def on_IntegerExpression expression
int = expression.first int = expression.value
reg = use_reg :Integer , int reg = use_reg :Integer , int
add_code Register::LoadConstant.new( expression, int , reg ) add_code Register::LoadConstant.new( expression, int , reg )
return reg return reg
end end
def on_true expression def on_TrueExpression expression
reg = use_reg :Boolean reg = use_reg :Boolean
add_code Register::LoadConstant.new( expression, true , reg ) add_code Register::LoadConstant.new( expression, true , reg )
return reg return reg
end end
def on_false expression def on_FalseExpression expression
reg = use_reg :Boolean reg = use_reg :Boolean
add_code Register::LoadConstant.new( expression, false , reg ) add_code Register::LoadConstant.new( expression, false , reg )
return reg return reg
end end
def on_nil expression def on_NilExpression expression
reg = use_reg :NilClass reg = use_reg :NilClass
add_code Register::LoadConstant.new( expression, nil , reg ) add_code Register::LoadConstant.new( expression, nil , reg )
return reg return reg
end end
def on_string expression def on_StringExpression expression
value = Parfait.new_word expression.first.to_sym value = Parfait.new_word expression.value.to_sym
reg = use_reg :Word reg = use_reg :Word
Register.machine.constants << value Register.machine.constants << value
add_code Register::LoadConstant.new( expression, value , reg ) add_code Register::LoadConstant.new( expression, value , reg )
return reg return reg
end end
def on_class_name expression def on_ClassExpression expression
name = expression.first name = expression.value
clazz = Parfait::Space.object_space.get_class_by_name! name clazz = Parfait::Space.object_space.get_class_by_name! name
raise "No such class #{name}" unless clazz raise "No such class #{name}" unless clazz
reg = use_reg :MetaClass , clazz reg = use_reg :MetaClass , clazz

View File

@ -1,16 +1,16 @@
module Soml module Soml
Compiler.class_eval do Compiler.class_eval do
def on_call statement def on_CallSite statement
#puts statement #puts statement
name_s , arguments , receiver = *statement # name_s , arguments , receiver = *statement
raise "not inside method " unless @method raise "not inside method " unless @method
reset_regs reset_regs
#move the new message (that we need to populate to make a call) to std register #move the new message (that we need to populate to make a call) to std register
new_message = Register.resolve_to_register(:new_message) new_message = Register.resolve_to_register(:new_message)
add_code Register.get_slot(statement, :message , :next_message , new_message ) add_code Register.get_slot(statement, :message , :next_message , new_message )
if receiver if statement.receiver
me = process( receiver.first ) me = process( statement.receiver )
else else
me = use_reg @method.for_class.name me = use_reg @method.for_class.name
add_code Register.get_slot(statement, :message , :receiver , me ) add_code Register.get_slot(statement, :message , :receiver , me )
@ -24,8 +24,8 @@ module Soml
# move our receiver there # move our receiver there
add_code Register.set_slot( statement , me , :new_message , :receiver) add_code Register.set_slot( statement , me , :new_message , :receiver)
set_message_details(name_s , arguments) set_message_details(statement , statement.arguments)
set_arguments(arguments) set_arguments(statement.arguments)
ret = use_reg( :Integer ) #TODO real return type ret = use_reg( :Integer ) #TODO real return type
do_call(clazz , statement) do_call(clazz , statement)
# the effect of the method is that the NewMessage Return slot will be filled, return it # the effect of the method is that the NewMessage Return slot will be filled, return it
@ -36,7 +36,7 @@ module Soml
private private
def do_call clazz , statement def do_call clazz , statement
name = statement.first.first name = statement.name
#puts "clazz #{clazz.name}" #puts "clazz #{clazz.name}"
raise "No such class" unless clazz raise "No such class" unless clazz
method = clazz.resolve_method(name) method = clazz.resolve_method(name)
@ -45,15 +45,15 @@ module Soml
Register.issue_call( self , method ) Register.issue_call( self , method )
end end
def set_message_details name_s , arguments def set_message_details name_s , arguments
name = name_s.to_a.first name = name_s.name
# load method name and set to new message (for exceptions/debug) # load method name and set to new message (for exceptions/debug)
name_tmp = use_reg(:Word) name_tmp = use_reg(:Word)
add_code Register::LoadConstant.new(name_s, name , name_tmp) add_code Register::LoadConstant.new(name_s, name , name_tmp)
add_code Register.set_slot( name_s , name_tmp , :new_message , :name) add_code Register.set_slot( name_s , name_tmp , :new_message , :name)
# next arguments. first length then args # next arguments. first length then args
len_tmp = use_reg(:Integer , arguments.to_a.length ) len_tmp = use_reg(:Integer , arguments.to_a.length )
add_code Register::LoadConstant.new(arguments, arguments.to_a.length , len_tmp) add_code Register::LoadConstant.new(name_s, arguments.to_a.length , len_tmp)
add_code Register.set_slot( arguments , len_tmp , :new_message , :indexed_length) add_code Register.set_slot( name_s , len_tmp , :new_message , :indexed_length)
end end
def set_arguments arguments def set_arguments arguments
# reset tmp regs for each and load result into new_message # reset tmp regs for each and load result into new_message

View File

@ -1,19 +1,16 @@
module Soml module Soml
Compiler.class_eval do Compiler.class_eval do
def on_class_field statement def on_ClassField statement
#puts statement.inspect #puts statement.inspect
type , name , value = *statement #type , name , value = *statement
for_class = @clazz for_class = @clazz
raise "no class" unless for_class raise "no class" unless for_class
index = for_class.instance_type.variable_index(name) index = for_class.instance_type.variable_index(statement.name)
#raise "class field already defined:#{name} for class #{for_class.name}" if index #raise "class field already defined:#{name} for class #{for_class.name}" if index
#puts "Define field #{name} on class #{for_class.name}" #puts "Define field #{name} on class #{for_class.name}"
index = for_class.instance_type.add_instance_variable( name , type ) index = for_class.instance_type.add_instance_variable( statement.name , statement.type )
# not sure how to run class code yet. later
raise "value #{value}" if value
return nil # statements don't reurn values, only expressions return nil # statements don't reurn values, only expressions
end end

View File

@ -1,13 +1,12 @@
module Soml module Soml
Compiler.class_eval do Compiler.class_eval do
def on_class statement def on_ClassStatement statement
#puts statement.inspect #puts statement.inspect
name , derives , statements = *statement
raise "classes dont yet play babushka, get coding #{name}" if @clazz raise "classes dont yet play babushka, get coding #{name}" if @clazz
@clazz = Parfait::Space.object_space.get_class_by_name! name @clazz = Parfait::Space.object_space.get_class_by_name! statement.name
#puts "Compiling class #{@clazz.name.inspect}" #puts "Compiling class #{@clazz.name.inspect}"
statement_value = process_all(statements).last statement_value = process(statement.statements).last
@clazz = nil @clazz = nil
return statement_value return statement_value
end end

View File

@ -1,13 +1,13 @@
module Soml module Soml
Compiler.class_eval do Compiler.class_eval do
def on_field_access statement def on_FieldAccess statement
receiver_ast , field_ast = *statement # receiver_ast , field_ast = *statement
receiver = process(receiver_ast) receiver = process(statement.receiver)
clazz = Register.machine.space.get_class_by_name receiver.type clazz = Register.machine.space.get_class_by_name receiver.type
field_name = field_ast.first_from(:name) field_name = statement.field.name
index = clazz.instance_type.variable_index(field_name) index = clazz.instance_type.variable_index(field_name)

View File

@ -2,14 +2,14 @@ module Soml
Compiler.class_eval do Compiler.class_eval do
include AST::Sexp include AST::Sexp
def on_field_def statement def on_FieldDef statement
reset_regs # field_def is a statement, no return and all regs reset_regs # field_def is a statement, no return and all regs
#puts statement.inspect #puts statement.inspect
type , name , value = *statement # type , name , value = *statement
name_s = no_space( name.first ) name_s = no_space( statement.name.value )
@method.ensure_local( name_s, type ) unless( @method.has_arg(name_s)) @method.ensure_local( name_s, statement.type ) unless( @method.has_arg(name_s))
# if there is a value assigned, process it as am assignemnt statement (kind of call on_assign) # if there is a value assigned, process it as am assignemnt statement (kind of call on_assign)
process( s(:assignment , name , value ) ) if value process( Soml::Assignment.new(statement.name , statement.value ) ) if statement.value
return nil return nil
end end
end end

View File

@ -1,24 +1,23 @@
module Soml module Soml
Compiler.class_eval do Compiler.class_eval do
def on_function statement def on_FunctionStatement statement
#puts statement.inspect #puts statement.inspect
return_type , name , parameters, kids , receiver = *statement # return_type , name , parameters, kids , receiver = *statement
name = name.to_a.first name = statement.name
raise "Already in method #{@method}" if @method raise "Already in method #{@method}" if @method
args = parameters.to_a.collect do |p| args = statement.parameters.collect do |p|
raise "error, argument must be a identifier, not #{p}" unless p.type == :parameter Parfait::Variable.new( *p )
Parfait::Variable.new( *p)
end end
class_method = nil class_method = nil
if(receiver ) if(statement.receiver )
if( receiver.first == :self) #class method if( statement.receiver.first == :self) #class method
class_method = @clazz class_method = @clazz
@clazz = @clazz.meta @clazz = @clazz.meta
else else
raise "Not covered #{receiver}" raise "Not covered #{statement.receiver}"
end end
end end
@ -33,9 +32,8 @@ module Soml
@method.source = statement @method.source = statement
#puts "compile method #{@method.name}" #puts "compile method #{@method.name}"
kids.to_a.each do |ex| process(statement.statements)
process(ex)
end
@clazz = class_method if class_method @clazz = class_method if class_method
@method = nil @method = nil
# function definition is a statement, does not return any value # function definition is a statement, does not return any value

View File

@ -4,27 +4,27 @@ module Soml
# an if evaluates the condition and jumps to the true block if true # an if evaluates the condition and jumps to the true block if true
# so the else block is automatically after that. # so the else block is automatically after that.
# But then the else needs to jump over the true block unconditionally. # But then the else needs to jump over the true block unconditionally.
def on_if_statement statement def on_IfStatement statement
branch_type , condition , if_true , if_false = *statement # branch_type , condition , if_true , if_false = *statement
condition = condition.first # condition = condition.first
reset_regs reset_regs
process(condition) process(statement.condition)
branch_class = Object.const_get "Register::Is#{branch_type.capitalize}" branch_class = Object.const_get "Register::Is#{statement.branch_type.capitalize}"
true_block = Register::Label.new(if_true, "if_true") true_block = Register::Label.new(statement, "if_true")
add_code branch_class.new( condition , true_block ) add_code branch_class.new( statement.condition , true_block )
# compile the false block # compile the false block
reset_regs reset_regs
process_all(if_false) if if_false process(statement.if_false) if statement.if_false.statements
merge = Register::Label.new(statement , "if_merge") merge = Register::Label.new(statement , "if_merge")
add_code Register::Branch.new(if_false, merge ) add_code Register::Branch.new(statement.if_false, merge )
# compile the true block # compile the true block
add_code true_block add_code true_block
reset_regs reset_regs
process_all(if_true) process(statement.if_true)
#puts "compiled if: end" #puts "compiled if: end"
add_code merge add_code merge

View File

@ -2,11 +2,11 @@ module Soml
Compiler.class_eval do Compiler.class_eval do
# attr_reader :name # attr_reader :name
# compiling name needs to check if it's a variable and if so resolve it # compiling name needs to check if it's a local variable
# otherwise it's a method without args and a send is issued. # or an argument
# whichever way this goes the result is stored in the return slot (as all compiles) # whichever way this goes the result is stored in the return slot (as all compiles)
def on_name statement def on_NameExpression statement
name = statement.to_a.first name = statement.name
if( name == :self) if( name == :self)
ret = use_reg @clazz.name ret = use_reg @clazz.name
add_code Register.get_slot(statement , :message , :receiver , ret ) add_code Register.get_slot(statement , :message , :receiver , ret )

View File

@ -1,17 +1,17 @@
module Soml module Soml
Compiler.class_eval do Compiler.class_eval do
def on_operator_value statement def on_OperatorExpression statement
#puts "operator #{statement.inspect}" #puts "operator #{statement.inspect}"
operator , left_e , right_e = *statement # operator , left_e , right_e = *statement
# left and right must be expressions. Expressions return a register when compiled # left and right must be expressions. Expressions return a register when compiled
left_reg = process(left_e) left_reg = process(statement.left_expression)
right_reg = process(right_e) right_reg = process(statement.right_expression)
raise "Not register #{left_reg}" unless left_reg.is_a?(Register::RegisterValue) raise "Not register #{left_reg}" unless left_reg.is_a?(Register::RegisterValue)
raise "Not register #{right_reg}" unless right_reg.is_a?(Register::RegisterValue) raise "Not register #{right_reg}" unless right_reg.is_a?(Register::RegisterValue)
#puts "left #{left_reg}" #puts "left #{left_reg}"
#puts "right #{right_reg}" #puts "right #{right_reg}"
add_code Register::OperatorInstruction.new(statement,operator,left_reg,right_reg) add_code Register::OperatorInstruction.new(statement,statement.operator,left_reg,right_reg)
return left_reg # though this has wrong value attached return left_reg # though this has wrong value attached
end end
end end

View File

@ -1,8 +1,8 @@
module Soml module Soml
Compiler.class_eval do Compiler.class_eval do
def on_return statement def on_ReturnStatement statement
reg = process(statement.first) reg = process(statement.return_value)
add_code Register.set_slot( statement, reg , :message , :return_value) add_code Register.set_slot( statement, reg , :message , :return_value)
nil # statements don't return nil # statements don't return
end end

View File

@ -1,27 +1,26 @@
module Soml module Soml
Compiler.class_eval do Compiler.class_eval do
def on_while_statement statement def on_WhileStatement statement
#puts statement.inspect #puts statement.inspect
branch_type , condition , statements = *statement #branch_type , condition , statements = *statement
condition = condition.first
condition_label = Register::Label.new(statement , "condition_label") condition_label = Register::Label.new(statement.condition , "condition_label")
# unconditionally branch to the condition upon entering the loop # unconditionally branch to the condition upon entering the loop
add_code Register::Branch.new(statement,condition_label) add_code Register::Branch.new(statement.condition,condition_label)
add_code start = Register::Label.new(statement , "while_start" ) add_code start = Register::Label.new(statement , "while_start" )
reset_regs reset_regs
process_all(statements) process(statement.statements)
# This is where the loop starts, though in subsequent iterations it's in the middle # This is where the loop starts, though in subsequent iterations it's in the middle
add_code condition_label add_code condition_label
reset_regs reset_regs
process(condition) process(statement.condition)
branch_class = Object.const_get "Register::Is#{branch_type.capitalize}" branch_class = Object.const_get "Register::Is#{statement.branch_type.capitalize}"
# this is where the while ends and both branches meet # this is where the while ends and both branches meet
add_code branch_class.new( condition , start ) add_code branch_class.new( statement.condition , start )
nil # statements don't return anything nil # statements don't return anything
end end

View File

@ -21,10 +21,11 @@ module ExpressionHelper
parser = parser.send @root parser = parser.send @root
syntax = parser.parse_with_debug(@string_input, reporter: Parslet::ErrorReporter::Deepest.new) syntax = parser.parse_with_debug(@string_input, reporter: Parslet::ErrorReporter::Deepest.new)
parts = Parser::Transform.new.apply(syntax) parts = Parser::Transform.new.apply(syntax)
codes = Soml.ast_to_code parts
#puts parts.inspect #puts parts.inspect
compiler = Soml::Compiler.new compiler = Soml::Compiler.new
set_main(compiler) set_main(compiler)
produced = compiler.process( parts ) produced = compiler.process( codes )
assert @output , "No output given" assert @output , "No output given"
assert_equal produced.class, @output , "Wrong class" assert_equal produced.class, @output , "Wrong class"
produced produced

View File

@ -30,7 +30,7 @@ class TestBasic < MiniTest::Test
def test_var def test_var
@string_input = 'int foo ' @string_input = 'int foo '
@root = :field_def @root = :field_def
@output = AST::Node @output = NilClass
check check
end end

View File

@ -40,19 +40,6 @@ HERE
check check
end end
def test_class_field_value
@string_input = <<HERE
class Space
field int boo1 = 1
int main()
return 1
end
end
HERE
@expect = [Label, LoadConstant,SetSlot,Label,FunctionReturn]
assert_raises{check}
end
def test_class_field def test_class_field
@string_input = <<HERE @string_input = <<HERE
class Space class Space