lib/rasl.rb
# CASL Assembler / Simulator
require 'optparse'
require 'pathname'
require 'readline'
require 'kconv'
require 'active_support/core_ext/string'
require 'active_support/configurable'
require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/hash_with_indifferent_access'
require 'active_model'
require_relative 'rasl/version'
module Rasl
include ActiveSupport::Configurable
config.spec = 2
config.bit = 16
config.memory_size = 65536
config.disassemble_rows = 8
config.ds_init_value = 0
config.memory_defval = 0
config.bol_order = false # 最初から命令を書けるか?
config.dump_cols = 8
config.dump_rows = 4
END_OF_FILE = -1
class RaslError < StandardError
def message
[super, current_file_line].compact.join("\n")
end
private
def current_file_line
if Rasl::Parser.line_count
if ARGF.filename == '-'
path = '<STDIN>'
else
path = ARGF.path
end
path_line = "#{path}:#{Rasl::Parser.line_count}: "
to = Rasl::Parser.scanner.pointer
Rasl::Parser.scanner.unscan rescue nil
from = Rasl::Parser.scanner.pointer
padding = ' ' * (path_line.size + from)
out = []
out << '-' * 75
out << "#{path_line}#{Rasl::Parser.raw_line.rstrip}"
if to != from
out << padding + '^' * (to - from)
else
out << padding + '^'
end
out << '-' * 75
out << Rasl::Parser.scanner.inspect
out * "\n"
end
end
end
# 主にアセンブル時のエラー
class SyntaxError < RaslError; end
class LabelNotFound < SyntaxError; end
class LabelDuplicate < SyntaxError; end
class InvalidIndexRegister < SyntaxError; end
class InvalidOrder < SyntaxError; end
class RegisterNotFound < SyntaxError; end
# 実行時エラー
class RunError < RaslError; end
class MemoryViolate < RunError; end
class Operand
include ActiveModel::Model
attr_accessor :key, :encode, :decode, :op_code, :printer, :with_imm, :alias
def ==(object)
case object
when Integer
op_code == object
else
match_names.include?(object.to_s.downcase)
end
end
def match_names
[key, self.alias].flatten.compact.collect(&:to_s)
end
def name
key.upcase.to_s
end
end
class Value
class << self
def cast_value(value, signed)
format = {8 => 'c', 16 => 's', 32 => 'l', 64 => 'q'}.fetch(Value.bit)
[value].pack(format).unpack(signed ? format : format.upcase).first
end
def signed(value)
cast_value(value, true)
end
def unsigned(value)
cast_value(value, false)
end
def lsb
1
end
def msb
1 << (bit - 1)
end
def signed_min
-1 << (bit - 1)
end
def signed_max
(1 << (bit - 1)) - 1
end
def unsigned_min
0
end
def unsigned_max
(1 << bit) - 1
end
def bit
Rasl.config.bit
end
def signed_range
signed_min .. signed_max
end
def unsigned_range
unsigned_min .. unsigned_max
end
def hex_format(value)
'%0*X' % [hex_width, unsigned(value)]
end
def hex_width
bit / 4
end
end
def initialize(raw = 0)
@raw = raw
end
def reset
@raw = 0
end
def u_value
self.class.unsigned(@raw)
end
alias value u_value
alias logical u_value
alias unsigned u_value
def u_value=(v)
@raw = self.class.unsigned(v)
end
alias value= u_value=;
alias logical= u_value=;
alias unsigned= u_value=;
def s_value
self.class.signed(@raw)
end
alias arithmetic s_value
alias signed s_value
def s_value=(v)
@raw = self.class.signed(v)
end
alias arithmetic= s_value=;
alias signed= s_value=;
def hex_format
self.class.hex_format(@raw)
end
alias to_s hex_format
end
class Register < Value
attr_reader :key
def initialize(key, attributes = {})
super()
@key = key.to_sym
@attributes = attributes
end
def pos
@attributes[:pos]
end
def to_s
"#{name}=#{super}"
end
def useful_as_xr?
pos && pos.nonzero?
end
def name
@key.upcase.to_s
end
end
class NullRegister < Register
def initialize(code)
super("gr#{code}(?)")
end
end
class FlagRegister < Register
cattr_accessor :flags_hash do
{
of: (1 << 2),
sf: (1 << 1),
zf: (1 << 0),
}
end
def to_s
'%s=%s(%s)' % [name, to_s_flags, to_s_sign]
end
def to_s_flags
flags_hash.keys.collect {|key|
send("#{key}?") ? key.to_s[0].upcase : '_'
}.join
end
def to_s_sign
case
when sf?
'-'
when zf?
'0'
else
'+'
end
end
def available_flags
flags_hash.keys.each_with_object([]) do |key, a|
if send("#{key}?")
a << key
end
end
end
flags_hash.each do |key, bit|
define_method(key) do
!!(@raw & bit).nonzero?
end
alias_method "#{key}?", key
define_method("#{key}=") do |flag|
flag.tap do
@raw &= ~bit
if flag
@raw |= bit
end
end
end
end
end
module Environment
attr_reader :gr, :memory, :labels
attr_accessor :exit_key, :code_size, :boot_pc, :data
def initialize
create_registers
@gr[:pr] = @gr[:pc]
if Rasl.config.spec == 1
@gr[:sp] = @gr.values.last
end
@memory = Array.new(Rasl.config.memory_size)
@operands = operand_list.collect{|v|Operand.new(v)}
@operands_hash = @operands.inject(ActiveSupport::HashWithIndifferentAccess.new){|h, o|h.merge(o.key => o)}
@code_size = 0
@boot_pc = 0
init_env
end
def init_env
@labels = {}
@memory.fill(Rasl.config.memory_defval)
@gr.values.each(&:reset)
@inline_addr_list = []
end
def assemble(buffer)
init_env
assemble_without_init_env(buffer)
end
def assemble_without_init_env(buffer)
@current_buffer = buffer.dup
@pass_count = 0
assemble_once
@pass_count += 1
assemble_once
self
end
def disassemble
out = ''
pc = 0
until pc >= @code_size
code_fetch(pc)
pc = @cur_code[:next_pc]
out << disasm_current << "\n"
end
out
end
def create_map_file(map_file)
Pathname(map_file).write(disassemble)
end
def gr_count
Rasl.config.spec == 1 ? 5 : 8
end
def store_object(gr: nil, imm: nil, xr: nil)
raise SyntaxError if (gr && !gr.pos) || (gr && !gr.pos)
store_prim_op(@current_op.op_code, (gr ? gr.pos : nil), (xr ? xr.pos : nil), imm)
end
def store_prim_op(op_code, r1, r2, imm = nil)
store_value((op_code << 8) | (((r1 || 0) & 0xf) << 4) | ((r2 || 0) & 0xf))
if imm
store_value(imm)
end
end
def store_value(value)
@memory[@code_size] = value
@code_size += 1
@encoded = true
end
def code_fetch(pc)
@cur_code = prefetch(pc)
end
def disasm_current
if @cur_code[:operand] && @cur_code[:operand].decode
params = disasm_op
else
params = disasm_dc
end
('%04X %s %-*s %-7s %s' % [@cur_code[:addr], Value.hex_format(@cur_code[:raw]), Value.hex_width, *params]).strip
end
def disasm_op
arg = ''
if @cur_code[:operand].printer
arg = send(@cur_code[:operand].printer)
end
[(@cur_code[:imm] ? Value.hex_format(@cur_code[:imm]) : ''), @cur_code[:operand].name, arg]
end
def disasm_dc
arg = '%-*d' % [Value.signed_min.to_s.length, Value.signed(@cur_code[:raw])]
begin
if @cur_code[:raw].chr.match(/[[:print:]]/)
arg << " ; '#{@cur_code[:raw].chr}'"
end
rescue RangeError
end
['', 'DC', arg]
end
def code_dump(**options)
mem_dump(@memory, {range: 0...@code_size}.merge(options))
end
def regs_info
out = []
out += gr_count.times.collect{|i|@gr["gr#{i}"]}.collect(&:to_s)
out += [@gr[:pc], @gr[:sp], @gr[:fr]].collect(&:to_s)
out << (@exit_key ? "[#{@exit_key}]" : nil)
out.join(' ').strip
end
def info
[regs_info, @labels.inspect] * "\n"
end
alias to_s info
def label_fetch(str)
((@labels[@current_namespace] || {})[str]) || ((@labels[:__global__] || {})[str])
end
def label_set(namespace, label)
@labels[namespace] ||= {}
if @labels[namespace][label] && @pass_count == 0
raise LabelDuplicate, "ラベル重複 : #{label.inspect} (namespace: #{namespace})"
end
@labels[namespace][label] = @code_size
@current_label = label
end
private
def create_registers
@gr = ActiveSupport::HashWithIndifferentAccess.new
gr_count.times do |i|
r = Register.new("gr#{i}", pos: @gr.count)
@gr[r.key] = r
# @gr[i.to_s] = r # for CASL1
end
if Rasl.config.spec == 2
@gr[:sp] = Register.new(:sp, pos: @gr.count)
end
@gr[:pc] = Register.new(:pc, pos: @gr.count)
@gr[:fr] = FlagRegister.new(:fr, pos: @gr.count)
end
def assemble_once
Rasl::Parser.line_count = nil
@code_size = 0
@boot_pc = nil
@inline_dc_list = []
@inline_index = 0
@start_index = 0
@current_namespace = :__global__
@namespaces = []
@current_buffer.lines.each.with_index do |line, i|
Rasl::Parser.raw_line = line
Rasl::Parser.line_count = i.next
line = line.sub(syntax[:comment], '').rstrip
if line.blank?
next
end
if line == '__END__'
unless @data
@data ||= @current_buffer.rstrip.lines.drop(i.next).collect(&:rstrip)
end
break
end
@scanner = StringScanner.new(line)
Rasl::Parser.scanner = @scanner
parse_label_part
skip_blank
parse_order_part
end
inline_dc_store
Rasl::Parser.scanner = nil
Rasl::Parser.line_count = nil
Rasl::Parser.raw_line = nil
end
def parse_label_part
@current_label = nil
if label = @scanner.scan(/#{syntax[:label]}:?/)
if Rasl.config.bol_order && !label.end_with?(':') && @operands.collect(&:match_names).flatten.include?(label.downcase)
@scanner.unscan
else
label = label.sub(':', '')
if label.start_with?('$')
label_set(:__global__, label)
else
label_set(@current_namespace, label)
end
end
end
end
def parse_order_part
if str = @scanner.scan(syntax[:symbol])
@encoded = false
skip_blank
pointer = @scanner.pointer
@operands.each do |operand|
next unless operand == str
@current_op = operand
@scanner.pointer = pointer
send operand.encode
if @encoded
break
end
end
unless @encoded
raise InvalidOrder, "無効な命令 : #{str.inspect}"
end
end
end
def mem_set(index, value)
valid_addr(index)
@memory[index] = Value.unsigned(value)
end
def mem_get(index)
valid_addr(index)
Value.unsigned(@memory[index])
end
def valid_addr(index)
unless @memory[index]
raise MemoryViolate, "メモリ外アクセス : #{index} メモリサイズ:#{@memory.size}"
end
end
def mem_dump(memory, options = {})
options = {
columns: Rasl.config.dump_cols,
range: 0...memory.size,
}.merge(options)
pos = options[:range].begin
if body = memory[options[:range]]
body.each_slice(options[:columns]) do |values|
chars = values.collect do |value|
begin
value.chr
rescue RangeError
'.'
end
end
puts '%04X: %-*s%s' % [
pos,
(Value.hex_width + 1) * options[:columns],
values.collect{|v|Value.hex_format(v)} * ' ',
chars.join.gsub(/[^[:ascii:]]/, '.').gsub(/[^[:print:]]/, '.'),
]
pos += values.length
end
end
end
def store_object(gr: nil, imm: nil, xr: nil)
raise SyntaxError if gr && !gr.pos
raise SyntaxError if xr && !xr.pos
store_prim_op(@current_op.op_code, (gr ? gr.pos : nil), (xr ? xr.pos : nil), imm)
end
def store_prim_op(op_code, r1, r2, imm = nil)
store_value((op_code << 8) | (((r1 || 0) & 0xf) << 4) | ((r2 || 0) & 0xf))
if imm
store_value(imm)
end
end
def store_asm(key, *args)
args = args.collect{|e|e.kind_of?(Symbol) ? @gr[e].pos : e}
store_prim_op(@operands_hash[key].op_code, *args)
end
def store_value(value)
@memory[@code_size] = value
@code_size += 1
@encoded = true
end
def code_fetch(pc)
@cur_code = prefetch(pc)
end
def prefetch(pc)
attrs = {}
attrs[:addr] = pc
attrs[:raw] = mem_get(pc)
pc += 1
attrs[:op_code] = (attrs[:raw] >> 8) & 0xff
attrs[:operand] = @operands.find{|e|e == attrs[:op_code] && e.decode}
r1_r2 = attrs[:raw] & 0xff
r1 = (r1_r2 >> 4) & 0xf
attrs[:r1] = @gr.values.find{|e|e.pos == r1} || NullRegister.new(r1)
attrs[:r] = attrs[:r1]
r2 = r1_r2 & 0xf
attrs[:r2] = @gr.values.find{|e|e.pos == r2} || NullRegister.new(r2)
attrs[:xr] = @gr.values.find{|e|e.pos == r2 && e.useful_as_xr?}
if attrs[:operand] && attrs[:operand].with_imm
attrs[:imm] = mem_get(pc)
pc += 1
attrs[:imm_xr] = attrs[:imm]
if attrs[:xr]
attrs[:imm_xr] += attrs[:xr].value
end
end
attrs[:next_pc] = pc
attrs.freeze
end
end
module OperandPresets1
private
def operand_list
[
{ key: :nop, op_code: 0x09, encode: :encode_blank, decode: :decode_nop },
{ key: :ld, op_code: 0x10, encode: :encode_rix, with_imm: true, decode: :decode_ld_rix, printer: :prt_rix },
{ key: :st, op_code: 0x11, encode: :encode_rix, with_imm: true, decode: :decode_st_rix, printer: :prt_rix },
{ key: :lad, op_code: 0x12, encode: :encode_rixf, with_imm: true, decode: :decode_lad_rix, printer: :prt_rix },
{ key: :ld, op_code: 0x14, encode: :encode_r_r, decode: :decode_ld_r_r, printer: :prt_r_r },
{ key: :lea, op_code: 0x1f, encode: :encode_rix, with_imm: true, decode: :decode_lea_rix, printer: :prt_rix },
{ key: :adda, op_code: 0x20, encode: :encode_rix, with_imm: true, decode: :decode_adda_rix, printer: :prt_rix, alias: :add },
{ key: :suba, op_code: 0x21, encode: :encode_rix, with_imm: true, decode: :decode_suba_rix, printer: :prt_rix, alias: :sub },
{ key: :addl, op_code: 0x22, encode: :encode_rix, with_imm: true, decode: :decode_addl_rix, printer: :prt_rix },
{ key: :subl, op_code: 0x23, encode: :encode_rix, with_imm: true, decode: :decode_subl_rix, printer: :prt_rix },
{ key: :adda, op_code: 0x24, encode: :encode_r_r, decode: :decode_adda_r_r, printer: :prt_r_r },
{ key: :suba, op_code: 0x25, encode: :encode_r_r, decode: :decode_suba_r_r, printer: :prt_r_r },
{ key: :addl, op_code: 0x26, encode: :encode_r_r, decode: :decode_addl_r_r, printer: :prt_r_r },
{ key: :subl, op_code: 0x27, encode: :encode_r_r, decode: :decode_subl_r_r, printer: :prt_r_r },
{ key: :and, op_code: 0x30, encode: :encode_rix, with_imm: true, decode: :decode_and_rix, printer: :prt_rix },
{ key: :or, op_code: 0x31, encode: :encode_rix, with_imm: true, decode: :decode_or_rix, printer: :prt_rix },
{ key: :xor, op_code: 0x32, encode: :encode_rix, with_imm: true, decode: :decode_xor_rix, printer: :prt_rix, alias: :eor },
{ key: :and, op_code: 0x34, encode: :encode_r_r, decode: :decode_and_r_r, printer: :prt_r_r },
{ key: :or, op_code: 0x35, encode: :encode_r_r, decode: :decode_or_r_r, printer: :prt_r_r },
{ key: :xor, op_code: 0x36, encode: :encode_r_r, decode: :decode_xor_r_r, printer: :prt_r_r },
{ key: :cpa, op_code: 0x40, encode: :encode_rix, with_imm: true, decode: :decode_cpa_rix, printer: :prt_rix },
{ key: :cpl, op_code: 0x41, encode: :encode_rix, with_imm: true, decode: :decode_cpl_rix, printer: :prt_rix },
{ key: :cpa, op_code: 0x44, encode: :encode_r_r, decode: :decode_cpa_r_r, printer: :prt_r_r },
{ key: :cpl, op_code: 0x45, encode: :encode_r_r, decode: :decode_cpl_r_r, printer: :prt_r_r },
{ key: :sla, op_code: 0x50, encode: :encode_rix, with_imm: true, decode: :decode_sla_rix, printer: :prt_rix },
{ key: :sra, op_code: 0x51, encode: :encode_rix, with_imm: true, decode: :decode_sra_rix, printer: :prt_rix },
{ key: :sll, op_code: 0x52, encode: :encode_rix, with_imm: true, decode: :decode_sll, printer: :prt_rix },
{ key: :srl, op_code: 0x53, encode: :encode_rix, with_imm: true, decode: :decode_srl, printer: :prt_rix },
{ key: :jpz, op_code: 0x60, encode: :encode_ix, with_imm: true, decode: :decode_jpz, printer: :prt_ix },
{ key: :jmi, op_code: 0x61, encode: :encode_ix, with_imm: true, decode: :decode_jmi, printer: :prt_ix },
{ key: :jnz, op_code: 0x62, encode: :encode_ix, with_imm: true, decode: :decode_jnz, printer: :prt_ix },
{ key: :jze, op_code: 0x63, encode: :encode_ix, with_imm: true, decode: :decode_jze, printer: :prt_ix },
{ key: :jump, op_code: 0x64, encode: :encode_ix, with_imm: true, decode: :decode_jump, printer: :prt_ix, alias: :jmp },
{ key: :jpl, op_code: 0x65, encode: :encode_ix, with_imm: true, decode: :decode_jpl, printer: :prt_ix },
{ key: :jov, op_code: 0x66, encode: :encode_ix, with_imm: true, decode: :decode_jov, printer: :prt_ix },
{ key: :push, op_code: 0x70, encode: :encode_ix, with_imm: true, decode: :decode_push, printer: :prt_ix },
{ key: :pop, op_code: 0x71, encode: :encode_gr, decode: :decode_pop, printer: :prt_gr },
{ key: :call, op_code: 0x80, encode: :encode_ix, with_imm: true, decode: :decode_call, printer: :prt_ix },
{ key: :ret, op_code: 0x81, encode: :encode_blank, decode: :decode_ret },
{ key: :svc, op_code: 0xf0, encode: :encode_ix, with_imm: true, decode: :decode_svc, printer: :prt_ix },
{ key: :prt, op_code: 0xe0, encode: :encode_ix, with_imm: true, decode: :decode_prt, printer: :prt_ix },
{ key: :start, encode: :encode_start },
{ key: :end, encode: :encode_end },
{ key: :ds, encode: :encode_ds },
{ key: :dc, encode: :encode_dc },
{ key: :in, encode: :encode_in },
{ key: :out, encode: :encode_out },
{ key: :exit, encode: :encode__exit },
{ key: :rpush, encode: :encode_rpush },
{ key: :rpop, encode: :encode_rpop },
{ key: :copy, encode: :encode_copy },
]
end
def encode_blank
store_object
end
def encode_r_r
r1 = scan_gr
skip_sep
if @scanner.check(register_regexp)
store_object(gr: r1, xr: scan_gr)
end
end
def encode_rix
r1 = scan_gr
skip_sep
unless @scanner.check(register_regexp)
store_object(gr: r1, imm: scan_imm, xr: scan_xr)
end
end
def encode_rixf
r1 = scan_gr
skip_sep
store_object(gr: r1, imm: scan_imm, xr: scan_xr)
end
def encode_gr
store_object(gr: scan_gr)
end
def encode_ix
store_object(imm: scan_imm, xr: scan_xr)
end
def encode_imm
store_object(imm: scan_imm)
end
def encode_in
encode_inout(:input)
end
def encode_out
encode_inout(:output)
end
def encode_inout(function)
store_asm :push, nil, :gr1, 0
store_asm :push, nil, :gr2, 0
store_asm :lad, :gr1, 0, scan_imm
skip_sep
store_asm :lad, :gr2, 0, scan_imm
store_asm :svc, nil, nil, svc_hash[function][:code]
store_asm :pop, :gr2, nil
store_asm :pop, :gr1, nil
end
def encode_copy
store_asm :push, nil, :gr1, 0
store_asm :push, nil, :gr2, 0
store_asm :push, nil, :gr3, 0
store_asm :lad, :gr1, 0, scan_imm
skip_sep
store_asm :lad, :gr2, 0, scan_imm
skip_sep
store_asm :lad, :gr3, 0, scan_imm
store_asm :svc, nil, nil, svc_hash[:copy][:code]
store_asm :pop, :gr3, nil
store_asm :pop, :gr2, nil
store_asm :pop, :gr1, nil
end
def encode__exit
store_prim_op(@operands_hash[:svc].op_code, nil, nil, svc_hash[:exit][:code])
end
def encode_start
@start_index += 1
@namespaces.push(@current_namespace)
if @current_label
@current_namespace = @current_label
else
@current_namespace = "__proc_#{@start_index}__"
end
v = 0
if @scanner.check(/\S+/)
v = scan_imm
end
@boot_pc ||= v
@encoded = true
end
def encode_end
inline_dc_store
@current_namespace = @namespaces.pop
@encoded = true
end
def inline_dc_store
@inline_dc_list.each do |v|
@inline_addr_list << @code_size
dc_store(v)
end
@inline_dc_list.clear
end
def encode_dc
loop do
dc_store(scan_imm_or_str)
skip_sep
if @scanner.eos?
break
end
end
@encoded = true
end
def dc_store(value)
if value.kind_of? String
value.each_byte{|ch|store_value(ch)}
else
store_value(value)
end
end
def scan_imm_or_str
if str = scan_str_literal(%{'}) || scan_str_literal(%{"})
str
else
scan_imm
end
end
def scan_str_literal(mark)
if @scanner.check(/#{mark}/)
from = @scanner.pointer
nil while @scanner.scan(/#{mark}[^#{mark}]*#{mark}/) && @scanner.check(/#{mark}/)
str = @scanner.string[from...@scanner.pointer] # 【'a''b'】
if str == ''
raise SyntaxError, "対応する #{mark} がない"
end
str = str.match(/\A.(?<str>.*).\z/)[:str] # 【a''b】
str.gsub(/#{mark}{2}/, mark) # 【a'b】
end
end
def encode_ds
scan_imm.times.each{store_value(Rasl.config.ds_init_value)}
@encoded = true
end
def decode_ld_rix
@cur_code[:r1].value = mem_get(@cur_code[:imm_xr])
set_fr(@cur_code[:r1].s_value)
@gr[:fr].of = false
end
def decode_ld_r_r
@cur_code[:r1].value = @cur_code[:r2].value
set_fr(@cur_code[:r1].s_value)
@gr[:fr].of = false
end
def decode_st_rix
mem_set(@cur_code[:imm_xr], @cur_code[:r1].value)
end
def decode_lea_rix
decode_lad_rix
set_fr(@cur_code[:r1].s_value)
end
def decode_lad_rix
@cur_code[:r1].value = @cur_code[:imm_xr]
end
def decode_adda_rix
decode_calc(:+, :rix, :signed)
end
def decode_addl_rix
decode_calc(:+, :rix, :unsigned)
end
def decode_suba_rix
decode_calc(:-, :rix, :signed)
end
def decode_subl_rix
decode_calc(:-, :rix, :unsigned)
end
def decode_adda_r_r
decode_calc(:+, :r_r, :signed)
end
def decode_suba_r_r
decode_calc(:-, :r_r, :signed)
end
def decode_addl_r_r
decode_calc(:+, :r_r, :unsigned)
end
def decode_subl_r_r
decode_calc(:-, :r_r, :unsigned)
end
def decode_and_rix
decode_logical_bit(:&, mem_get(@cur_code[:imm_xr]))
end
def decode_or_rix
decode_logical_bit(:|, mem_get(@cur_code[:imm_xr]))
end
def decode_xor_rix
decode_logical_bit(:^, mem_get(@cur_code[:imm_xr]))
end
def decode_and_r_r
decode_logical_bit(:&, @cur_code[:r2].value)
end
def decode_or_r_r
decode_logical_bit(:|, @cur_code[:r2].value)
end
def decode_xor_r_r
decode_logical_bit(:^, @cur_code[:r2].value)
end
def decode_calc(method, syntax_type, value_type)
v1 = @cur_code[:r1].value
if syntax_type == :r_r
v2 = @cur_code[:r2].value
else
v2 = mem_get(@cur_code[:imm_xr])
end
v1 = Value.send(value_type, v1)
v2 = Value.send(value_type, v2)
value = v1.send(method, v2)
@gr[:fr].of = !Value.send("#{value_type}_range").include?(value)
@cur_code[:r1].value = value
set_fr(@cur_code[:r1].s_value)
end
def decode_logical_bit(method, right_value)
@cur_code[:r1].value = @cur_code[:r1].value.send(method, right_value)
set_fr(@cur_code[:r1].s_value)
@gr[:fr].of = false
end
def decode_cpa_rix
decode_cpx(mem_get(@cur_code[:imm_xr]), :signed)
end
def decode_cpl_rix
decode_cpx(mem_get(@cur_code[:imm_xr]), :unsigned)
end
def decode_cpa_r_r
decode_cpx(@cur_code[:r2].value, :signed)
end
def decode_cpl_r_r
decode_cpx(@cur_code[:r2].value, :unsigned)
end
def decode_cpx(right_value, value_type)
set_fr(Value.send(value_type, @cur_code[:r1].value) - Value.send(value_type, right_value))
@gr[:fr].of = false
end
def set_fr(value)
@gr[:fr].zf = value.zero?
@gr[:fr].sf = value < 0
end
def set_of(value)
@gr[:fr].of = value.nonzero?
end
def decode_sla_rix
decode_sxa(:<<, :signed)
end
def decode_sra_rix
decode_sxa(:>>, :signed)
end
def decode_sll
decode_sxa(:<<, :unsigned)
end
def decode_srl
decode_sxa(:>>, :unsigned)
end
def decode_sxa(method, value_type)
shift = mem_get(@cur_code[:imm_xr])
if shift > Value.bit
shift = Value.bit
end
of_bit = (method == :<< ? Value.msb : Value.lsb)
v = @cur_code[:r1].send(value_type)
of = 0
shift.times do
of = v & of_bit
sf_bit = (v & Value.msb)
v = v.send(method, 1)
if value_type == :signed
v |= sf_bit
end
end
@cur_code[:r1].value = v
set_fr(@cur_code[:r1].s_value)
set_of(of)
end
def decode_jpz
unless @gr[:fr].sf?
decode_jump
end
end
def decode_jmi
if @gr[:fr].sf?
decode_jump
end
end
def decode_jnz
unless @gr[:fr].zf?
decode_jump
end
end
def decode_jze
if @gr[:fr].zf?
decode_jump
end
end
def decode_jump
@gr[:pc].value = @cur_code[:imm_xr]
end
def decode_push
value_push(@cur_code[:imm_xr])
end
def value_push(value)
@gr[:sp].value -= 1
mem_set(@gr[:sp].value, value)
end
def value_pop
mem_get(@gr[:sp].value).tap { @gr[:sp].value += 1 }
end
def decode_pop
@cur_code[:r1].value = value_pop
end
def decode_call
value_push(@gr[:pc].value)
decode_jump
end
def decode_ret
@gr[:pc].value = value_pop
if @gr[:sp].value >= @memory.size || @gr[:sp].value == 0
@exit_key = :ret
end
end
def decode_nop
end
def decode_svc
if elem = svc_list.find{|e|e[:code] == @cur_code[:imm_xr]}
send "decode_svc_#{elem[:key]}"
end
end
def svc_list
[
{key: :input, code: 0},
{key: :output, code: 1},
{key: :exit, code: 2},
{key: :copy, code: 3},
]
end
def svc_hash
svc_list.inject({}){|h, v|h.merge(v[:key] => v)}
end
def decode_svc_input
base = @gr[:gr1].value
sizep = @gr[:gr2].value
if @data
str = @data[@data_pos].tap { @data_pos += 1 }
else
str = $stdin.gets
end
if str
str = str.chomp
str.chars.each.with_index{|ch, i|
mem_set(base + i, ch.ord)
}
mem_set(sizep, str.length)
else
mem_set(sizep, END_OF_FILE)
end
end
def decode_svc_output
base = @gr[:gr1].value
size = Value.signed(mem_get(@gr[:gr2].value))
if size == END_OF_FILE
return
end
str = size.times.collect {|i|
v = mem_get(base + i)
begin
v.chr
rescue RangeError
"(##{Value.hex_format(v)})"
end
}.join
puts str
end
def decode_svc_exit
@exit_key = :exit
end
def decode_svc_copy
dist = @gr[:gr1].value
src = @gr[:gr2].value
size = @gr[:gr3].value
size.times do |i|
mem_set(dist + i, mem_get(src + i))
end
end
def decode_prt
puts @cur_code[:imm_xr]
end
def prt_r_r
[@cur_code[:r1].name, separator, @cur_code[:r2].name].join
end
def prt_rix
[prt_gr, separator, prt_ix].join
end
def prt_ix
["##{Value.hex_format(@cur_code[:imm])}", prt_xr].join
end
def prt_gr
@cur_code[:r1].name
end
def prt_xr
if @cur_code[:xr]
"#{separator}#{@cur_code[:xr].name}"
end
end
def separator
', '
end
def prt_blank
end
end
module OperandPresets2
def decode_jpl
if !@gr[:fr].sf? && !@gr[:fr].zf?
decode_jump
end
end
def decode_jov
if @gr[:fr].of?
decode_jump
end
end
def encode_rpush
rpush_registers.each do |r|
store_prim_op(@operands_hash[:push].op_code, 0, r.pos, 0)
end
end
def encode_rpop
rpush_registers.reverse_each do |r|
store_prim_op(@operands_hash[:pop].op_code, r.pos, 0)
end
end
def rpush_registers
(1 ... gr_count).collect{|i|@gr["gr#{i}"]}
end
end
module Parser
mattr_accessor :raw_line, :line_count, :scanner
private
def syntax
{
comment: /([^\\];|\A#).*/,
label: /\A[$@_a-z]\w*/i,
symbol: /\A[_a-z]\w*/i,
imm: /[+-]?(#|0x|0b)?[\da-f]+/i,
sepalator: /[,\s]+/, # 厳密にするなら /\s*,\s*/
blank: /[,\s]+/, # 厳密にするなら /\s+/
inline_dc: /\s*=\s*/i,
}
end
def register_regexp
Regexp.union(/\b#{@gr.keys.join('|')}\b/i)
end
def scan_gr
if str = @scanner.scan(register_regexp)
@gr[str.downcase]
else
raise RegisterNotFound, 'レジスタの指定がありません'
end
end
def scan_imm
case
when str = @scanner.scan(syntax[:label])
if @pass_count == 0
undecision
else
v = label_fetch(str)
unless v
raise LabelNotFound, "ラベルが見つかりません : #{str.inspect} in #{@labels.inspect}"
end
v
end
when str = @scanner.scan(syntax[:imm])
cast_int(str)
when @scanner.scan(syntax[:inline_dc])
@inline_dc_list << scan_imm_or_str
if @pass_count == 0
v = undecision
else
v = @inline_addr_list[@inline_index]
end
@inline_index += 1
v
else
raise SyntaxError, '即値が見つかりません'
end
end
def scan_xr
skip_sep
if str = @scanner.scan(register_regexp)
xr = @gr[str.downcase]
unless xr
raise SyntaxError, "指標レジスタの表記が間違っています : #{str.inspect}"
end
unless xr.useful_as_xr?
raise InvalidIndexRegister, "指標レジスタに #{xr.name} は使えません"
end
end
xr
end
def imm_or_int(str)
case
when v = label_fetch(str)
v
when str.match(syntax[:imm])
cast_int(str)
end
end
def cast_int(str)
Integer(str.sub(/\A#/, '0x'))
end
def label_or_imm_regexp
Regexp.union(syntax[:label], syntax[:imm])
end
def skip_sep
@scanner.skip(syntax[:sepalator])
end
def skip_blank
@scanner.skip(syntax[:blank])
end
def undecision
-1
end
end
module Simulator
def simulator
command_init
loop do
if defined? Readline
getline(Readline.readline('-'))
else
print '-'
getline($stdin.gets)
end
next unless @command
if @command == 'q'
break
end
if command = command_table[@command]
send command
end
end
end
def go
command_init
command_go
self
end
def command_table
{
'i' => :command_init,
'g' => :command_go,
'r' => :command_register,
'd' => :command_dump,
't' => :command_trace,
'u' => :command_disasm,
'?' => :command_help,
'h' => :command_help,
}
end
def command_dump
if @hex_args
@dump_point = @hex_args.first
end
size = Rasl.config.dump_cols * Rasl.config.dump_rows
mem_dump(@memory, range: (@dump_point...(@dump_point + size)))
@dump_point += size
end
def command_disasm
if @hex_args
@unencode_point = @hex_args.first
end
Rasl.config.disassemble_rows.times do
if @unencode_point >= @memory.size
break
end
code_fetch(@unencode_point)
@unencode_point = @cur_code[:next_pc]
puts disasm_current
end
end
def command_init
@gr.values.each(&:reset)
before_go
end
def before_go
@gr[:sp].value = @memory.size
value_push(-1)
@boot_pc ||= 0
@gr[:pc].value = @boot_pc
@unencode_point = @boot_pc
@dump_point = @boot_pc
@exit_key = false
@data_pos = 0
end
def command_go
set_pc
until @exit_key
command_step
end
end
def command_trace
set_pc
unless @exit_key
command_step
current_state
end
end
def command_register
if @args
@args.each do |arg|
if md = arg.match(/(?<lhv>.+)=(?<rhv>.+)/)
@gr[md[:lhv].downcase].value = imm_or_int(md[:rhv])
end
end
else
current_state
end
end
def command_step
code_fetch(@gr[:pc].value)
unless @cur_code[:operand]
raise RunError, "不明な命令のため実行できない : #{@cur_code[:raw]}"
end
@gr[:pc].value = @cur_code[:next_pc]
send @cur_code[:operand].decode
end
def current_state
code_fetch(@gr[:pc].value)
puts regs_info
puts disasm_current
end
def post_command(s)
getline(s)
send command_table[@command]
end
private
def getline(s)
@command = nil
@args = nil
@hex_args = nil
if md = s.strip.match(/(.)(.*)/)
@command, _args = md.captures.to_a
@command = @command.downcase
unless _args.empty?
@args = _args.split(/\s+|,/)
@hex_args = @args.collect{|e|e.to_i(16)}
end
end
end
def set_pc
if @hex_args
@gr[:pc].value = @hex_args.first
end
end
def command_help
puts <<-EOT
D[address] memory-dump
U[address] unassemble
G[address] go
T[address] trace
R[reg=n] register
? or H usage
I init
Q quit
EOT
end
end
class Processor
prepend Environment
prepend OperandPresets1
prepend OperandPresets2
prepend Parser
prepend Simulator
end
class CLI
def self.execute(args = ARGV)
new.execute(args)
end
def initialize
@file = nil
@options = {}
end
def parser
OptionParser.new do |o|
o.version = VERSION
o.banner = [
"CASL Assembler / Simulator #{o.ver}\n",
"使い方: #{o.program_name} [OPTIONS] [ファイル]\n",
].join
o.on("-s", "--simulator", "シミュレータ起動") do |v|
@options[:simulator] = v
end
o.on("-p", "--print-map", "MAP情報の標準出力") do |v|
@options[:print_map] = v
end
o.on("-m", "--output-map", "MAP情報のファイル出力。-g オプションがあるときは実行後に出力する") do |v|
@options[:output_map] = v
end
o.on("-g", "--go", "実行") do |v|
@options[:go] = v
end
o.on("-e CODE", "--eval=CODE", "指定コードの評価。指定があると標準入力からは読み込まない") do |v|
@options[:eval] = v
end
o.on("-r", "--register", "実行後レジスタ一覧表示") do |v|
@options[:register] = v
end
o.on("--in=STRING", "INマクロに対する入力文字列(__END__ 行のデータよりも優先)") do |v|
@options[:data] = v
end
o.on("--memory-size=SIZE", Integer, "メモリサイズの指定(デフォルト:#{(Rasl.config.memory_size)})") do |v|
Rasl.config.memory_size = v
end
o.on("--spec=NUMBER", Integer, "1:レジスタ数5個でGR4==SP 2:レジスタ数8 GR4!=SP") do |v|
Rasl.config.spec = v
end
o.on("--ds-init-value=VAL","DSで領域確保したときの初期値(デフォルト:#{Rasl.config.ds_init_value})") do |v|
Rasl.config.ds_init_value = Integer(v)
end
o.on("--memory-defval=VAL", "メモリの初期値(デフォルト:#{Rasl.config.memory_defval})") do |v|
Rasl.config.memory_defval = Integer(v)
end
o.on("--[no-]bol-order", "命令の前に空白を書かなくてよいことにする(デフォルト:#{Rasl.config.bol_order})") do |v|
Rasl.config.bol_order = v
end
end
end
def execute(args)
begin
parser.parse!(args)
rescue OptionParser::InvalidOption => error
puts error
usage
end
if File === ARGF.file
@file = ARGF.file
end
@processor = Processor.new
if @options[:data]
@processor.data = @options[:data].lines.collect(&:chomp)
end
if @options[:eval]
@processor.assemble(@options[:eval])
else
@processor.assemble(ARGF.read.toutf8)
end
if @file && @options[:output_map]
@processor.create_map_file(file_name_of(:map))
end
if @options[:go]
@processor.go
end
if @options[:print_map]
puts @processor.disassemble
end
if @options[:register]
puts @processor.regs_info
end
if @options[:simulator]
@processor.simulator
end
end
def usage
puts "使い方: #{parser.program_name} [オプション] <ファイル>..."
puts "`#{parser.program_name}' --help でより詳しい情報を表示します。"
abort
end
def file_name_of(extname)
Pathname("#{Pathname(@file).basename(".*")}.#{extname}")
end
end
end
if $0 == __FILE__
if false
Rasl.config.memory_size = 256
object = Rasl::Processor.new
object.assemble("
A START
RET
A DS 1
END
A START
RET
A DS 1
END
; IN A,B
; IN A,B
; IN A,B
; RET
;A DS 5
;B DS 1
;__END__
;a
;b
")
puts object.disassemble
object.go
p object.labels
puts object.disassemble
object.command_dump
end
if true
object = Rasl::Processor.new
object.assemble("
MAIN START
CALL FGET
RET
END
FGET START
EXIT LD GR1,GR7
RET
JUMP EXIT
END
")
puts object.disassemble
object.go
p object.labels
puts object.disassemble
object.command_dump
end
end