module Lignite # The commands that cannot appear in a .rbf program, # used mostly for program manipulation class SystemCommands include Bytes include Logger extend Logger def self.run(conn = Connection.create, &block) sc = new(conn) sc.instance_exec(&block) sc.close end # @param conn [Connection] def initialize(conn = Connection.create) @conn = conn load_yml end def close @conn.close end private def load_yml fname = File.expand_path("../../../data/sysops.yml", __FILE__) op_hash = YAML.load_file(fname)["sysops"] op_hash.each do |oname, odata| load_op(oname, odata) end end # oname LIST_FILES def load_op(oname, odata) ovalue = odata["value"] param_handlers, return_handlers = handlers(odata) osym = oname.downcase.to_sym self.class.send(:define_method, osym) do |*args| logger.debug "called #{osym} with #{args.inspect}" if args.size != param_handlers.size raise ArgumentError, "expected #{param_handlers.size} arguments, got #{args.size}" end bytes = u8(ovalue) bytes += param_handlers.zip(args).map do |h, a| # h.call(a) would have self = Op instead of #<Op> instance_exec(a, &h) end.join("") logger.debug "sysop to execute: #{bytes.inspect}" reply = system_command_with_reply(bytes) parse_reply(reply, return_handlers) end end # @param reply [ByteString] # @param return_handlers [Array<Proc>] # @return [Object,Array<Object>] def parse_reply(reply, return_handlers) replies = return_handlers.map do |h| parsed, reply = h.call(reply) parsed end raise "Unparsed reply #{reply.inspect}" unless reply.empty? # A single reply is returned as a scalar, not an array replies.size == 1 ? replies.first : replies end def handlers(odata) oparams = odata["params"] param_handlers = [] return_handlers = [] oparams.each do |p| if p["dir"] == "in" param_handlers << param_handler(p) else return_handlers << return_handler(p) end end [param_handlers, return_handlers] end def param_handler(oparam) case oparam["type"] when "U8" then ->(x) { u8(x) } when "U16" then ->(x) { u16(x) } when "U32" then ->(x) { u32(x) } when "BYTES" then ->(x) { x } when "ZBYTES" then ->(x) { x + u8(0) } else raise end end # the handler is a lambda returning a pair: # a parsed value and the rest of the input def return_handler(oparam) case oparam["type"] when "U8" then ->(i) { [unpack_u8(i[0, 1]), i[1..-1]] } when "U16" then ->(i) { [unpack_u16(i[0, 2]), i[2..-1]] } when "U32" then ->(i) { [unpack_u32(i[0, 4]), i[4..-1]] } when "BYTES" then ->(i) { [i, ""] } else raise end end def system_command_with_reply(instr_bytes) cmd = Message.system_command_with_reply(instr_bytes) @conn.send(cmd.bytes) reply = Message.reply_from_bytes(@conn.receive) assert_match(reply.msgid, cmd.msgid, "Reply id") assert_match(reply.command, unpack_u8(instr_bytes[0]), "Command num") raise VMError, format("Error: %u", reply.status) if reply.error? reply.data end def assert_match(actual, expected, description) return if actual == expected raise "#{description} does not match, expected #{expected}, actual #{actual}" end end end