Решение на Четвърта задача от Никола Ненков

Обратно към всички решения

Към профила на Никола Ненков

Резултати

  • 6 точки от тестове
  • 3 бонус точки
  • 9 точки общо
  • 8 успешни тест(а)
  • 0 неуспешни тест(а)

Код

module Asm
class Compiler
attr_reader :instructions, :labels
def initialize
@instructions = []
@labels = {}
end
MUTATORS = [:mov, :inc, :dec, :cmp].freeze
JUMPERS = {
jmp: proc { true },
je: proc { @last_cmp.zero? },
jne: proc { not @last_cmp.zero? },
jl: proc { @last_cmp < 0 },
jle: proc { @last_cmp <= 0 },
jg: proc { @last_cmp > 0 },
jge: proc { @last_cmp >= 0 },
}.freeze
MUTATORS.each do |mutator_name|
define_method mutator_name do |*arguments|
@instructions << [mutator_name, *arguments]
end
end
JUMPERS.each do |jumper_name, condition|
define_method jumper_name do |position|
@instructions << [:jmp, condition, position]
end
end
def label(name)
@labels[name] = @instructions.count
end
def method_missing(label_or_register_name)
label_or_register_name
end
def self.compile(&program)
compiler = new
compiler.instance_eval &program
[compiler.instructions, compiler.labels]
end
end
class Executor < Struct.new(:instructions, :labels, :ax, :bx, :cx, :dx)
def mov(register, value)
self[register] = actual_value(value)
end
def inc(register, value = 1)
self[register] += actual_value(value)
end
def dec(register, value = 1)
self[register] -= actual_value(value)
end
def cmp(comparant_one, comparant_two)
@last_cmp = actual_value(comparant_one) <=> actual_value(comparant_two)
end
def jmp(condition, position)
@current_instruction = actual_position(position).pred if instance_eval &condition
end
def self.execute((instructions, labels))
new(instructions, labels, 0, 0, 0, 0).instance_eval do
@current_instruction = -1
while instructions[@current_instruction.next] do
@current_instruction += 1
send *instructions[@current_instruction]
end
[ax, bx, cx, dx]
end
end
private
def actual_value(value)
value.is_a?(Symbol) ? self[value] : value
end
def actual_position(position)
labels.fetch(position, position)
end
end
def self.asm(&program)
Executor.execute(Compiler.compile &program)
end
end

Лог от изпълнението

........

Finished in 0.00774 seconds
8 examples, 0 failures

История (7 версии и 7 коментара)

Никола обнови решението на 10.01.2014 22:20 (преди почти 11 години)

+class Asm
+ class Compiler
+ attr_reader :instructions, :labels
+
+ def initialize
+ @instructions = []
+ @labels = {}
+ end
+
+ mutators = [:mov, :inc, :dec, :cmp]#mby better name
+ jumpers = {
+ :jmp => proc { true },
+ :je => proc { @last_cmp.zero? },#mby shouldn't be instance variable so
+ :jne => proc { not @last_cmp.zero? },#no instance_eval will be required
+ :jl => proc { @last_cmp < 0},#in Evaluator's jmp
+ :jle => proc { @last_cmp <= 0},
+ :jg => proc { @last_cmp > 0},
+ :jge => proc { @last_cmp >= 0},
+ }
+
+ mutators.each do |mutator|
+ define_method mutator do |arguments|
+ @instructions << [mutator, *arguments]
+ end
+ end
+
+ jumpers.each do |jumper_name, jump_condition|
+ define_method jumper_name do |where|
+ @instructions << [:jmp, jump_condition, where]
+ end
+ end
+
+ def method_missing(label_name)
+ label_name
+ end
+
+ def label(name)
+ @labels[name] = @instructions.count
+ end
+ end
+
+ class Executor < Struct.new(:instructions, :labels, :ax, :bx, :cx, :dx)
+ def mov(register, value)
+ self[register] = actual_value(value)
+ end
+
+ def inc(register, value = 1)
+ self[register] += actual_value(value)
+ end
+
+ def dec(register, value = 1)
+ self[register] -= actual_value(value)
+ end
+
+ def cmp(comparant_one, comparant_two)
+ @last_cmp = actual_value(comparant_one) <=> actual_value(comparant_two)
+ end
+
+ def jmp(condition, where)
+ @current_instruction = actual_place(where).pred if instance_eval &condition
+ end
+
+ def execute
+ @current_instruction = -1
+ while instructions[@current_instruction.next] do
+ @current_instruction += 1
+ instruction = instructions[@current_instruction]
+ send instruction.first, *instruction[1..-1]
+ end
+ [ax, bx, cx, dx].map(&:to_i)
+ end
+
+ private
+ def actual_value(value)
+ if value.is_a? Symbol
+ self[value]
+ else
+ value
+ end
+ end
+
+ def actual_place(where)#mby better name
+ if labels.key? where
+ labels[where]
+ else
+ where
+ end
+ end
+ end
+
+ def self.asm(&program)
+ compiler = Compiler.new
+ compiler.instance_eval &program
+ Executor.new(compiler.instructions, compiler.labels).execute
+ end
+end

Никола обнови решението на 11.01.2014 00:29 (преди почти 11 години)

class Asm
class Compiler
attr_reader :instructions, :labels
def initialize
@instructions = []
@labels = {}
end
- mutators = [:mov, :inc, :dec, :cmp]#mby better name
+ mutators = [:mov, :inc, :dec, :cmp]
jumpers = {
:jmp => proc { true },
- :je => proc { @last_cmp.zero? },#mby shouldn't be instance variable so
- :jne => proc { not @last_cmp.zero? },#no instance_eval will be required
- :jl => proc { @last_cmp < 0},#in Evaluator's jmp
+ :je => proc { @last_cmp.zero? },
+ :jne => proc { not @last_cmp.zero? },
+ :jl => proc { @last_cmp < 0},
:jle => proc { @last_cmp <= 0},
:jg => proc { @last_cmp > 0},
:jge => proc { @last_cmp >= 0},
}
- mutators.each do |mutator|
- define_method mutator do |arguments|
- @instructions << [mutator, *arguments]
+ mutators.each do |mutator_name|
+ define_method mutator_name do |*arguments|
+ @instructions << [mutator_name, *arguments]
end
end
jumpers.each do |jumper_name, jump_condition|
- define_method jumper_name do |where|
- @instructions << [:jmp, jump_condition, where]
+ define_method jumper_name do |position|
+ @instructions << [:jmp, jump_condition, position]
end
end
def method_missing(label_name)
label_name
end
def label(name)
@labels[name] = @instructions.count
end
end
class Executor < Struct.new(:instructions, :labels, :ax, :bx, :cx, :dx)
def mov(register, value)
self[register] = actual_value(value)
end
def inc(register, value = 1)
self[register] += actual_value(value)
end
def dec(register, value = 1)
self[register] -= actual_value(value)
end
def cmp(comparant_one, comparant_two)
@last_cmp = actual_value(comparant_one) <=> actual_value(comparant_two)
end
- def jmp(condition, where)
- @current_instruction = actual_place(where).pred if instance_eval &condition
+ def jmp(condition, position)
+ @current_instruction = actual_position(position).pred if instance_eval &condition
end
def execute
+ self.ax, self.bx, self.cx, self.dx = 0, 0, 0, 0
@current_instruction = -1
while instructions[@current_instruction.next] do
@current_instruction += 1
instruction = instructions[@current_instruction]
send instruction.first, *instruction[1..-1]
end
- [ax, bx, cx, dx].map(&:to_i)
+ [ax, bx, cx, dx]
end
private
+
def actual_value(value)
if value.is_a? Symbol
self[value]
else
value
end
end
- def actual_place(where)#mby better name
- if labels.key? where
- labels[where]
+ def actual_position(position)
+ if labels.key? position
+ labels[position]
else
- where
+ position
end
end
end
def self.asm(&program)
compiler = Compiler.new
compiler.instance_eval &program
Executor.new(compiler.instructions, compiler.labels).execute
end
end

Никола обнови решението на 11.01.2014 00:37 (преди почти 11 години)

class Asm
class Compiler
attr_reader :instructions, :labels
def initialize
@instructions = []
@labels = {}
end
mutators = [:mov, :inc, :dec, :cmp]
jumpers = {
:jmp => proc { true },
:je => proc { @last_cmp.zero? },
:jne => proc { not @last_cmp.zero? },
:jl => proc { @last_cmp < 0},
:jle => proc { @last_cmp <= 0},
:jg => proc { @last_cmp > 0},
:jge => proc { @last_cmp >= 0},
}
mutators.each do |mutator_name|
define_method mutator_name do |*arguments|
@instructions << [mutator_name, *arguments]
end
end
jumpers.each do |jumper_name, jump_condition|
define_method jumper_name do |position|
@instructions << [:jmp, jump_condition, position]
end
end
def method_missing(label_name)
label_name
end
def label(name)
@labels[name] = @instructions.count
end
end
class Executor < Struct.new(:instructions, :labels, :ax, :bx, :cx, :dx)
def mov(register, value)
self[register] = actual_value(value)
end
def inc(register, value = 1)
self[register] += actual_value(value)
end
def dec(register, value = 1)
self[register] -= actual_value(value)
end
def cmp(comparant_one, comparant_two)
@last_cmp = actual_value(comparant_one) <=> actual_value(comparant_two)
end
def jmp(condition, position)
@current_instruction = actual_position(position).pred if instance_eval &condition
end
def execute
- self.ax, self.bx, self.cx, self.dx = 0, 0, 0, 0
+ self.ax, self.bx, self.cx, self.dx = 0, 0, 0, 0
@current_instruction = -1
while instructions[@current_instruction.next] do
@current_instruction += 1
instruction = instructions[@current_instruction]
send instruction.first, *instruction[1..-1]
end
[ax, bx, cx, dx]
end
private
def actual_value(value)
if value.is_a? Symbol
self[value]
else
value
end
end
def actual_position(position)
if labels.key? position
labels[position]
else
position
end
end
end
def self.asm(&program)
compiler = Compiler.new
compiler.instance_eval &program
Executor.new(compiler.instructions, compiler.labels).execute
end
end

Никола обнови решението на 11.01.2014 01:15 (преди почти 11 години)

class Asm
class Compiler
attr_reader :instructions, :labels
def initialize
@instructions = []
@labels = {}
end
mutators = [:mov, :inc, :dec, :cmp]
jumpers = {
:jmp => proc { true },
:je => proc { @last_cmp.zero? },
:jne => proc { not @last_cmp.zero? },
- :jl => proc { @last_cmp < 0},
- :jle => proc { @last_cmp <= 0},
- :jg => proc { @last_cmp > 0},
- :jge => proc { @last_cmp >= 0},
+ :jl => proc { @last_cmp < 0 },
+ :jle => proc { @last_cmp <= 0 },
+ :jg => proc { @last_cmp > 0 },
+ :jge => proc { @last_cmp >= 0 },
}
mutators.each do |mutator_name|
define_method mutator_name do |*arguments|
@instructions << [mutator_name, *arguments]
end
end
jumpers.each do |jumper_name, jump_condition|
define_method jumper_name do |position|
@instructions << [:jmp, jump_condition, position]
end
end
def method_missing(label_name)
label_name
end
def label(name)
@labels[name] = @instructions.count
end
end
class Executor < Struct.new(:instructions, :labels, :ax, :bx, :cx, :dx)
def mov(register, value)
self[register] = actual_value(value)
end
def inc(register, value = 1)
self[register] += actual_value(value)
end
def dec(register, value = 1)
self[register] -= actual_value(value)
end
def cmp(comparant_one, comparant_two)
@last_cmp = actual_value(comparant_one) <=> actual_value(comparant_two)
end
def jmp(condition, position)
@current_instruction = actual_position(position).pred if instance_eval &condition
end
def execute
self.ax, self.bx, self.cx, self.dx = 0, 0, 0, 0
@current_instruction = -1
while instructions[@current_instruction.next] do
@current_instruction += 1
instruction = instructions[@current_instruction]
send instruction.first, *instruction[1..-1]
end
[ax, bx, cx, dx]
end
private
def actual_value(value)
if value.is_a? Symbol
self[value]
else
value
end
end
def actual_position(position)
if labels.key? position
labels[position]
else
position
end
end
end
def self.asm(&program)
compiler = Compiler.new
compiler.instance_eval &program
Executor.new(compiler.instructions, compiler.labels).execute
end
end

Чудесно!

Само няколко дребни забележки:

  • Asm по условие не е клас, а модул.
  • Използвай новия синтаксис за хешове при jumpers, т.е. { jmp: proc { true } }
  • instruction[1..-1] изглежда по-добре така: instruction.drop 1
  • остави празен ред над [ax, bx, cx, dx].map(&:to_i), и махни скобите. Конвенция.
  • Бих направил actual_value и actual_place на one-liner-и, особено actual_place, където може да се ползва Hash#fetch
  • Можеш да експериментираш с имплементацията на Asm.asm, например как би се отразило на останалия код Asm.asm да беше написан така:

    def self.asm(&block)
      Executor.execute(Compiler.compile &block)
    end
    

Последното изобщо не е задължително, дори изглежда странно, заради повторенията. Не считам за необходимо да си преправяш задачата така, но може да го разцъкаш за упражнение.

Никола обнови решението на 11.01.2014 12:52 (преди почти 11 години)

-class Asm
+module Asm
class Compiler
attr_reader :instructions, :labels
def initialize
@instructions = []
@labels = {}
end
mutators = [:mov, :inc, :dec, :cmp]
jumpers = {
- :jmp => proc { true },
- :je => proc { @last_cmp.zero? },
- :jne => proc { not @last_cmp.zero? },
- :jl => proc { @last_cmp < 0 },
- :jle => proc { @last_cmp <= 0 },
- :jg => proc { @last_cmp > 0 },
- :jge => proc { @last_cmp >= 0 },
+ jmp: proc { true },
+ je: proc { @last_cmp.zero? },
+ jne: proc { not @last_cmp.zero? },
+ jl: proc { @last_cmp < 0},
+ jle: proc { @last_cmp <= 0},
+ jg: proc { @last_cmp > 0},
+ jge: proc { @last_cmp >= 0},
}
mutators.each do |mutator_name|
define_method mutator_name do |*arguments|
@instructions << [mutator_name, *arguments]
end
end
jumpers.each do |jumper_name, jump_condition|
define_method jumper_name do |position|
@instructions << [:jmp, jump_condition, position]
end
end
+ def label(name)
+ @labels[name] = @instructions.count
+ end
+
def method_missing(label_name)
label_name
end
- def label(name)
- @labels[name] = @instructions.count
+ def self.compile(&program)
+ compiler = new
+ compiler.instance_eval &program
+ [compiler.instructions, compiler.labels]
end
end
class Executor < Struct.new(:instructions, :labels, :ax, :bx, :cx, :dx)
def mov(register, value)
self[register] = actual_value(value)
end
def inc(register, value = 1)
self[register] += actual_value(value)
end
def dec(register, value = 1)
self[register] -= actual_value(value)
end
def cmp(comparant_one, comparant_two)
@last_cmp = actual_value(comparant_one) <=> actual_value(comparant_two)
end
def jmp(condition, position)
@current_instruction = actual_position(position).pred if instance_eval &condition
end
- def execute
- self.ax, self.bx, self.cx, self.dx = 0, 0, 0, 0
- @current_instruction = -1
- while instructions[@current_instruction.next] do
- @current_instruction += 1
- instruction = instructions[@current_instruction]
- send instruction.first, *instruction[1..-1]
+ def self.execute((instructions, labels))
+ new(instructions, labels, 0, 0, 0, 0).instance_eval do
+ @current_instruction = -1
+ while instructions[@current_instruction.next] do
+ @current_instruction += 1
+ instruction = instructions[@current_instruction]
+ send instruction.first, *(instruction.drop 1)
+ end
+
+ [ax, bx, cx, dx]
end
- [ax, bx, cx, dx]
end
private
def actual_value(value)
- if value.is_a? Symbol
- self[value]
- else
- value
- end
+ value.is_a?(Symbol) ? self[value] : value
end
def actual_position(position)
- if labels.key? position
- labels[position]
- else
- position
- end
+ labels.fetch(position, position)
end
end
def self.asm(&program)
- compiler = Compiler.new
- compiler.instance_eval &program
- Executor.new(compiler.instructions, compiler.labels).execute
+ Executor.execute(Compiler.compile &program)
end
end

Супер! Само 2 дреболии:

  1. Не си оставил празен ред над върнатата стойност в Compiler.compile
  2. Донякъде ми е странно, че си „вградил“ старата имплементация на Executor#execute в Executor.execute. Не е проблем, просто new(...).instance_eval do ... end изглежда по-странно от едно простичко new(...).execute. Но не съм сигурен доколко е добре да има instance method и class instance method с едно и също име, тъй че и сегашното положение не е зле.

Никола обнови решението на 12.01.2014 11:56 (преди почти 11 години)

module Asm
class Compiler
attr_reader :instructions, :labels
def initialize
@instructions = []
@labels = {}
end
- mutators = [:mov, :inc, :dec, :cmp]
+ mutators = [:mov, :inc, :dec, :cmp].freeze
jumpers = {
jmp: proc { true },
je: proc { @last_cmp.zero? },
jne: proc { not @last_cmp.zero? },
jl: proc { @last_cmp < 0},
jle: proc { @last_cmp <= 0},
jg: proc { @last_cmp > 0},
jge: proc { @last_cmp >= 0},
- }
+ }.freeze
mutators.each do |mutator_name|
define_method mutator_name do |*arguments|
@instructions << [mutator_name, *arguments]
end
end
jumpers.each do |jumper_name, jump_condition|
define_method jumper_name do |position|
@instructions << [:jmp, jump_condition, position]
end
end
def label(name)
@labels[name] = @instructions.count
end
def method_missing(label_name)
label_name
end
def self.compile(&program)
compiler = new
compiler.instance_eval &program
+
[compiler.instructions, compiler.labels]
end
end
class Executor < Struct.new(:instructions, :labels, :ax, :bx, :cx, :dx)
def mov(register, value)
self[register] = actual_value(value)
end
def inc(register, value = 1)
self[register] += actual_value(value)
end
def dec(register, value = 1)
self[register] -= actual_value(value)
end
def cmp(comparant_one, comparant_two)
@last_cmp = actual_value(comparant_one) <=> actual_value(comparant_two)
end
def jmp(condition, position)
@current_instruction = actual_position(position).pred if instance_eval &condition
end
def self.execute((instructions, labels))
new(instructions, labels, 0, 0, 0, 0).instance_eval do
@current_instruction = -1
while instructions[@current_instruction.next] do
@current_instruction += 1
instruction = instructions[@current_instruction]
send instruction.first, *(instruction.drop 1)
end
[ax, bx, cx, dx]
end
end
private
def actual_value(value)
value.is_a?(Symbol) ? self[value] : value
end
def actual_position(position)
labels.fetch(position, position)
end
end
def self.asm(&program)
Executor.execute(Compiler.compile &program)
end
end

Благодаря много за feedback-a.

За инстанционния execute: това ми беше първоначалната идея, но тогава Executor ще има 9 метода, а не искам да се разделям с никой от сегашните, също не мисля, че има смисъл да правя вътрешен клас.

За freeze: доколкото разбирам целта е readability. Кога се очаква да слагаме freeze на обектите, които няма да променяме?

Никола, добра практика е разни стойности, които се присвояват на константи, да сеfreeze-ват. Това е една допълнителна защита, ако случайно нечий код се опита да мутира обекта зад константата, подаван "по референция". Може да не е очевидно, че това идва от константа и някой да не спазва добрата практика да не мутира обектите, подадени му като аргументи на методи. Та, freeze е някаква форма на защита срещу това.

Обикновено се freeze-ва нещо, сложено като стойност на константа. Други неща – по-рядко. Няма строги правила за това.

Но в случая интерфейсът ми не предлага начин за достъп до mutators и jumpers, така че ако някой иска да ги променя трябва да прави магарии (предполага се, че знае какво прави). А дори и да тръгне да ги променя не виждам как това би променило резултата на програмата: define_method-ите се изпълняват веднага след присвояването на стойности на mutators и jumpers, а стойността им след това е без значение. За да промени поведението чрез промяна на mutators и jumpers трябва или да променя самия код, или да има някакъв начин в езика да се вмъкват инструкции между инструкции, които са почнали да се изпълняват.

Но съм напълно съгласен, че носи семантично значение. Като стана въпрос - не трябваше ли mutators и jumpers да са all caps?

Никола обнови решението на 13.01.2014 22:37 (преди почти 11 години)

module Asm
class Compiler
attr_reader :instructions, :labels
def initialize
@instructions = []
@labels = {}
end
- mutators = [:mov, :inc, :dec, :cmp].freeze
- jumpers = {
+ MUTATORS = [:mov, :inc, :dec, :cmp].freeze
+ JUMPERS = {
jmp: proc { true },
je: proc { @last_cmp.zero? },
jne: proc { not @last_cmp.zero? },
- jl: proc { @last_cmp < 0},
- jle: proc { @last_cmp <= 0},
- jg: proc { @last_cmp > 0},
- jge: proc { @last_cmp >= 0},
+ jl: proc { @last_cmp < 0 },
+ jle: proc { @last_cmp <= 0 },
+ jg: proc { @last_cmp > 0 },
+ jge: proc { @last_cmp >= 0 },
}.freeze
- mutators.each do |mutator_name|
+ MUTATORS.each do |mutator_name|
define_method mutator_name do |*arguments|
@instructions << [mutator_name, *arguments]
end
end
- jumpers.each do |jumper_name, jump_condition|
+ JUMPERS.each do |jumper_name, condition|
define_method jumper_name do |position|
- @instructions << [:jmp, jump_condition, position]
+ @instructions << [:jmp, condition, position]
end
end
def label(name)
@labels[name] = @instructions.count
end
- def method_missing(label_name)
- label_name
+ def method_missing(label_or_register_name)
+ label_or_register_name
end
def self.compile(&program)
compiler = new
compiler.instance_eval &program
[compiler.instructions, compiler.labels]
end
end
class Executor < Struct.new(:instructions, :labels, :ax, :bx, :cx, :dx)
def mov(register, value)
self[register] = actual_value(value)
end
def inc(register, value = 1)
self[register] += actual_value(value)
end
def dec(register, value = 1)
self[register] -= actual_value(value)
end
def cmp(comparant_one, comparant_two)
@last_cmp = actual_value(comparant_one) <=> actual_value(comparant_two)
end
def jmp(condition, position)
@current_instruction = actual_position(position).pred if instance_eval &condition
end
def self.execute((instructions, labels))
new(instructions, labels, 0, 0, 0, 0).instance_eval do
@current_instruction = -1
while instructions[@current_instruction.next] do
@current_instruction += 1
- instruction = instructions[@current_instruction]
- send instruction.first, *(instruction.drop 1)
+ send *instructions[@current_instruction]
end
[ax, bx, cx, dx]
end
end
private
def actual_value(value)
value.is_a?(Symbol) ? self[value] : value
end
def actual_position(position)
labels.fetch(position, position)
end
end
def self.asm(&program)
Executor.execute(Compiler.compile &program)
end
end