more semi random code, a checkpoint
This commit is contained in:
parent
1c86ecb84e
commit
845a8cab8b
@ -1,4 +1,4 @@
|
||||
require_relative "code"
|
||||
require_relative "../vm/code"
|
||||
|
||||
module Asm
|
||||
# The name really says it all.
|
||||
@ -6,7 +6,7 @@ module Asm
|
||||
# Currently string are stored "inline" , ie in the code segment.
|
||||
# Mainly because that works an i aint no elf expert.
|
||||
|
||||
class StringLiteral < Code
|
||||
class StringLiteral < Vm::Code
|
||||
|
||||
# currently aligned to 4 (ie padded with 0) and off course 0 at the end
|
||||
def initialize(str)
|
||||
|
@ -5,3 +5,4 @@ require "elf/object_writer"
|
||||
require 'parser/composed'
|
||||
require 'parser/transform'
|
||||
require "vm/context"
|
||||
require "vm/machine"
|
||||
|
41
lib/vm/block.rb
Normal file
41
lib/vm/block.rb
Normal file
@ -0,0 +1,41 @@
|
||||
require_relative "values"
|
||||
|
||||
module Vm
|
||||
|
||||
# Think flowcharts: blocks are the boxes. The smallest unit of linear code
|
||||
|
||||
# Blocks must end in control instructions (jump/call/return).
|
||||
# And the only valid argument for a jump is a Block
|
||||
|
||||
# Blocks for a double linked list so one can traverse back and forth
|
||||
|
||||
# There are four ways for a block to get data (to work on)
|
||||
# - hard coded constants (embedded in code)
|
||||
# - memory move
|
||||
# - values passed in (from previous blocks. ie local variables)
|
||||
|
||||
# See Value description on how to create code/instructions
|
||||
|
||||
class Block < Value
|
||||
|
||||
def initialize(name)
|
||||
super()
|
||||
@name = name.to_sym
|
||||
end
|
||||
|
||||
attr_reader :name
|
||||
|
||||
def verify
|
||||
raise "Empty #{self.inspect}" if @values.empty?
|
||||
end
|
||||
private
|
||||
|
||||
# possibly misguided ??
|
||||
def add_arg value
|
||||
# TODO check
|
||||
@args << value
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
@ -1,8 +1,7 @@
|
||||
|
||||
module Asm
|
||||
module Vm
|
||||
# Base class for anything that we can assemble
|
||||
|
||||
# Derived classes include instrucitons, blocks and data(strings)
|
||||
|
||||
# Derived classes include instructions and data(strings)
|
||||
|
||||
# The commonality abstracted here is the length and position
|
||||
# and the ability to assemble itself into the stream
|
||||
|
@ -1,53 +1,32 @@
|
||||
require_relative "kernel"
|
||||
require_relative "program"
|
||||
|
||||
module Vm
|
||||
|
||||
#currently just holding the program in here so we can have global access
|
||||
class Context
|
||||
def initialize
|
||||
@locals = {}
|
||||
def initialize program
|
||||
@attributes = {}
|
||||
@attributes["program"] = program
|
||||
end
|
||||
def get name
|
||||
@locals[name]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# ast classes
|
||||
module Parser
|
||||
Expression.class_eval do
|
||||
def compile builder , context
|
||||
raise "abstract #{self.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
IntegerExpression.class_eval do
|
||||
end
|
||||
|
||||
NameExpression.class_eval do
|
||||
end
|
||||
|
||||
StringExpression.class_eval do
|
||||
def compile builder , context
|
||||
return string
|
||||
end
|
||||
end
|
||||
|
||||
FuncallExpression.class_eval do
|
||||
def compile builder , context
|
||||
arguments = args.collect{|arg| arg.compile(builder , context) }
|
||||
function = context.get(name)
|
||||
unless function
|
||||
function = Vm::Kernel.send(name)
|
||||
context.add_function( name , function )
|
||||
|
||||
# map any function call to an attribute if possible
|
||||
def method_missing name , *args , &block
|
||||
if args.length > 1 or block_given?
|
||||
puts "NO -#{args.length} BLOCK #{block_given?}"
|
||||
super
|
||||
else
|
||||
name = name.to_s
|
||||
if args.length == 1 #must be assignemnt for ir attr= val
|
||||
if name.include? "="
|
||||
return @attributes[name.chop] = args[0]
|
||||
else
|
||||
super
|
||||
end
|
||||
else
|
||||
return @attributes[name]
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
ConditionalExpression.class_eval do
|
||||
end
|
||||
|
||||
AssignmentExpression.class_eval do
|
||||
end
|
||||
FunctionExpression.class_eval do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
26
lib/vm/conversion.rb
Normal file
26
lib/vm/conversion.rb
Normal file
@ -0,0 +1,26 @@
|
||||
module Vm
|
||||
# Convert ast to vm-values via visitor pattern
|
||||
# We do this (what would otherwise seem like foot-shuffling) to keep the layers seperated
|
||||
# Ie towards the feature goal of reusing the same parse for several binary outputs
|
||||
|
||||
# scope of the funcitons is thus class scope ie self is the expression and all attributes work
|
||||
# gets included into Value
|
||||
module Conversion
|
||||
def to_value
|
||||
cl_name = self.class.name.to_s.split("::").last.gsub("Expression","").downcase
|
||||
send "#{cl_name}_value"
|
||||
end
|
||||
def funcall_value
|
||||
FunctionCall.new( name , args.collect{ |a| a.to_value } )
|
||||
end
|
||||
def string_value
|
||||
ObjectReference.new( string )
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
require_relative "../parser/nodes"
|
||||
|
||||
Parser::Expression.class_eval do
|
||||
include Vm::Conversion
|
||||
end
|
52
lib/vm/function.rb
Normal file
52
lib/vm/function.rb
Normal file
@ -0,0 +1,52 @@
|
||||
require_relative "block"
|
||||
|
||||
module Vm
|
||||
|
||||
# Functions are similar to Blocks. Where Blocks can be jumped to, Functions can be called.
|
||||
|
||||
# Functions also have arguments, though they are handled differently (in register allocation)
|
||||
|
||||
# Functions have a minimum of two blocks, entry and exit, which are created for you
|
||||
# but there is no branch created between them, this must be done by the programmer.
|
||||
|
||||
|
||||
class Function < Value
|
||||
|
||||
def initialize(name , args = [])
|
||||
super()
|
||||
@name = name
|
||||
@args = args
|
||||
@entry = Block.new("entry_#{name}")
|
||||
@exit = Block.new("exit_#{name}")
|
||||
end
|
||||
attr_reader :name , :args , :entry , :exit
|
||||
|
||||
def arity
|
||||
@args.length
|
||||
end
|
||||
|
||||
def compile function_expression , context
|
||||
arguments = function_expression.args.collect do |arg|
|
||||
add_arg arg.to_value
|
||||
end
|
||||
function = context.program.get_function(name)
|
||||
unless function
|
||||
function = Vm::Kernel.send(name)
|
||||
context.program.get_or_create_function( name , function , arity )
|
||||
end
|
||||
end
|
||||
|
||||
def verify
|
||||
@entry.verify
|
||||
@exit.verify
|
||||
end
|
||||
|
||||
private
|
||||
def add_arg value
|
||||
# TODO check
|
||||
@args << value
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
33
lib/vm/function_call.rb
Normal file
33
lib/vm/function_call.rb
Normal file
@ -0,0 +1,33 @@
|
||||
module Vm
|
||||
|
||||
# name and args , return
|
||||
|
||||
class FunctionCall < Value
|
||||
|
||||
def initialize(name , args)
|
||||
@name = name
|
||||
@args = args
|
||||
@function = nil
|
||||
end
|
||||
attr_reader :name , :args , :function
|
||||
|
||||
def compile context
|
||||
@function = context.program.get_function @name
|
||||
if @function
|
||||
raise "error #{self}" unless function.arity != @args.length
|
||||
else
|
||||
@function = context.program.get_or_create_function @name
|
||||
end
|
||||
args.each_with_index do |arg , index|
|
||||
arg.load
|
||||
arg.compile context
|
||||
end
|
||||
#puts "funcall #{self.inspect}"
|
||||
self.call
|
||||
end
|
||||
|
||||
def call
|
||||
Machine.instance.function_call self
|
||||
end
|
||||
end
|
||||
end
|
@ -1,6 +1,48 @@
|
||||
|
||||
module Vm
|
||||
class Instruction
|
||||
# Instruction represent the actions that affect change on Values
|
||||
# In an OO way of thinking the Value is data, Instruction the functionality
|
||||
|
||||
# But to allow flexibility, the value api bounces back to the machine api, so machines instantiate
|
||||
# intructions.
|
||||
|
||||
# When Instructions are instantiated the create a linked list of Values and Instructions.
|
||||
# So Value links to Instruction and Instruction links to Value
|
||||
# Also, because the idea of what one instruction does, does not always map one to one to real machine
|
||||
# instructions, and instruction may link to another instruction thus creating an arbitrary list
|
||||
# to get the job (the original instruciton) done
|
||||
# Admittately it would be simpler just to create the (abstract) instructions and let the machine
|
||||
# encode them into what-ever is neccessary, but this approach leaves more possibility to
|
||||
# optimize the actual instruction stream (not just the crystal instruction stream). Makes sense?
|
||||
|
||||
# We have basic classes (literally) of instructions
|
||||
# - Memory
|
||||
# - Stack
|
||||
# - Logic
|
||||
# - Math
|
||||
# - Control/Compare
|
||||
# - Move
|
||||
# - Call
|
||||
|
||||
# Instruction derives from Code, for the assembly api
|
||||
class Code ; end
|
||||
|
||||
class Instruction < Code
|
||||
|
||||
end
|
||||
|
||||
class StackInstruction < Instruction
|
||||
end
|
||||
class MemoryInstruction < Instruction
|
||||
end
|
||||
class LogicInstruction < Instruction
|
||||
end
|
||||
class MathInstruction < Instruction
|
||||
end
|
||||
class CompareInstruction < Instruction
|
||||
end
|
||||
class MoveInstruction < Instruction
|
||||
end
|
||||
class CallInstruction < Instruction
|
||||
end
|
||||
end
|
||||
|
@ -1,5 +1,11 @@
|
||||
module Vm
|
||||
module Kernel
|
||||
def self.start
|
||||
#TODO extract args into array of strings
|
||||
end
|
||||
def self.exit
|
||||
# Machine.exit swi 0
|
||||
end
|
||||
def self.puts
|
||||
"me"
|
||||
end
|
||||
|
@ -11,10 +11,18 @@ module Vm
|
||||
# * Note that register content is typed externally. Not as in mri, where int's are tagged. Floats can's
|
||||
# be tagged and lambda should be it's own type, so tagging does not work
|
||||
|
||||
# Note: subclasses should/will be used once we have the basic architecture clear and working
|
||||
# Programs are created by invoking methods on subclasses of Value.
|
||||
# But executable code is a sequence of Instructions and subclasses.
|
||||
|
||||
# A Machines main responsibility in the framework is to instantiate Instruction
|
||||
# Value functions are mapped to machines by concatenating the values class name + the methd name
|
||||
# Example: SignedValue.plus( value ) -> Machine.signed_plus (value )
|
||||
|
||||
class Machine
|
||||
|
||||
# hmm, not pretty but for now
|
||||
@@instance = nil
|
||||
|
||||
attr_reader :registers
|
||||
attr_reader :scratch
|
||||
attr_reader :pc
|
||||
@ -23,5 +31,12 @@ module Vm
|
||||
# Still, using if to express tests makes sense, not just for
|
||||
# consistency in this code, but also because that is what is actually done
|
||||
attr_reader :status
|
||||
|
||||
def self.instance
|
||||
@@instance
|
||||
end
|
||||
def self.instance= machine
|
||||
@@instance = machine
|
||||
end
|
||||
end
|
||||
end
|
||||
|
75
lib/vm/program.rb
Normal file
75
lib/vm/program.rb
Normal file
@ -0,0 +1,75 @@
|
||||
require_relative "function"
|
||||
require_relative "function_call"
|
||||
require "arm/arm_machine"
|
||||
|
||||
module Vm
|
||||
# A Program represents an executable that we want to build
|
||||
# it has a list of functions and (global) objects
|
||||
|
||||
# The main entry is a function called (of all things) "main", This _must be supplied by the compling
|
||||
# There is a start and exit block that call main, which receives an array of strings
|
||||
|
||||
# While data "ususally" would live in a .data section, we may also "inline" it into the code
|
||||
# in an oo system all data is represented as objects
|
||||
|
||||
# in terms of variables and their visibility, things are simple. They are either local or global
|
||||
|
||||
# throwing in a context for unspecified use (well one is to pass the programm/globals around)
|
||||
|
||||
|
||||
class Program < Block
|
||||
|
||||
# should init for a machine and pass that on to start/exit / register alloc and the like
|
||||
def initialize
|
||||
super("start")
|
||||
# this aint pretty. but i'll go soon enough
|
||||
Machine.instance = Arm::ArmMachine.new
|
||||
|
||||
@context = Context.new(self)
|
||||
@functions = []
|
||||
@objects = []
|
||||
@entry = Vm::Kernel::start
|
||||
@exit = Vm::Kernel::exit
|
||||
end
|
||||
attr_reader :context
|
||||
|
||||
def add_object o
|
||||
@objects << o # TODO check type , no basic values allowed (must be wrapped)
|
||||
end
|
||||
|
||||
def get_function name
|
||||
@functions.detect{ |f| f.name == name }
|
||||
end
|
||||
|
||||
# preferred way of creating new functions (also forward declarations, will flag unresolved later)
|
||||
def get_or_create_function name
|
||||
fun = get_function name
|
||||
unless fun
|
||||
fun = Function.new(name)
|
||||
@functions << fun
|
||||
end
|
||||
fun
|
||||
end
|
||||
|
||||
def compile
|
||||
super
|
||||
string_table
|
||||
end
|
||||
|
||||
def wrap_as_main value
|
||||
|
||||
end
|
||||
def verify
|
||||
main = @functions.find{|f| f.name == "main"}
|
||||
raise "No main in Program" unless main
|
||||
@functions.each do |funct|
|
||||
funct.verify
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
# the main function
|
||||
def create_main
|
||||
end
|
||||
end
|
||||
end
|
@ -3,14 +3,15 @@ module Vm
|
||||
# Values represent the information as it is processed. Different subclasses for different types,
|
||||
# each type with different operations.
|
||||
# The oprerations on values is what makes a machine do things.
|
||||
# For compilation, values are mopped to the machines registers and the functions (on values) map
|
||||
# to machine instructions
|
||||
# For compilation, values are moved to the machines registers and the methods (on values) map
|
||||
# to machine instructions
|
||||
|
||||
# Values are immutable! (that's why they are called values)
|
||||
# Operations on values _always_ produce new values (conceptionally)
|
||||
|
||||
# Values are a way to reason about (create/validate) instructions. The final executable is mostly
|
||||
# instrucions.
|
||||
# Values are a way to reason about (create/validate) instructions.
|
||||
# In fact a linked lists of values is created by invoking instructions
|
||||
# the linked list goes from value to instruction to value, backwards
|
||||
|
||||
# Word Values are what fits in a register. Derived classes
|
||||
# Float, Reference , Integer(s) must fit the same registers
|
||||
@ -23,19 +24,18 @@ module Vm
|
||||
def byte_size
|
||||
raise "abstract method called #{self.inspect}"
|
||||
end
|
||||
attr :register
|
||||
|
||||
def initialize reg = nil
|
||||
@register = nil
|
||||
end
|
||||
end
|
||||
|
||||
class Word < Value
|
||||
def load
|
||||
Machine.instance.word_load self
|
||||
end
|
||||
end
|
||||
|
||||
class Unsigned < Word
|
||||
|
||||
def + unsigned
|
||||
def plus unsigned
|
||||
unless unsigned.is_a? Unsigned
|
||||
unsigned = Conversion.new( unsigned , Unsigned )
|
||||
end
|
||||
@ -44,7 +44,7 @@ module Vm
|
||||
end
|
||||
|
||||
class Signed < Word
|
||||
def + signed
|
||||
def plus signed
|
||||
unless signed.is_a? Signed
|
||||
signed = Conversion.new( signed , Signed )
|
||||
end
|
||||
@ -62,9 +62,22 @@ module Vm
|
||||
end
|
||||
|
||||
class ObjectReference < Reference
|
||||
end
|
||||
|
||||
class Byte < Value
|
||||
def initialize obj
|
||||
@object = obj
|
||||
end
|
||||
attr_reader :object
|
||||
|
||||
def compile context
|
||||
if object.is_a? String
|
||||
context.program.add_object object
|
||||
else
|
||||
#TODO define object layout more generally and let objects lay themselves out
|
||||
# as it is the program does this (in the objectwriter/stringtable)
|
||||
un.done
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
require_relative "conversion"
|
@ -1,5 +1,5 @@
|
||||
require_relative 'helper'
|
||||
|
||||
require "yaml"
|
||||
class TestRunner < MiniTest::Test
|
||||
|
||||
# this creates test methods dynamically , one for each file in runners directory
|
||||
@ -25,10 +25,14 @@ class TestRunner < MiniTest::Test
|
||||
#link
|
||||
# execute
|
||||
# check result ?
|
||||
context = Vm::Context.new
|
||||
builder = Asm::Assembler.new
|
||||
compiled = tree.compile( builder , context )
|
||||
puts compiled.inspect
|
||||
program = Vm::Program.new
|
||||
expression = tree.to_value
|
||||
compiled = expression.compile( program.context )
|
||||
# do some stuff with mains and what not ??
|
||||
program.wrap_as_main compiled
|
||||
puts program.to_yaml
|
||||
program.verify
|
||||
puts program.to_yaml
|
||||
end
|
||||
|
||||
end
|
Loading…
Reference in New Issue
Block a user