Rc-lang开发周记3 生成C++代码
由于元旦第二天开始状态奇差,本周并没有增加太多内容,周记的内容也会相对少一些。以及本周的内容主要在于生成C++的代码,更多的是Ruby的元编程技巧。
指令定义
每个指令有一个InstType的枚举字段来标明类型
所有指令继承自一个VMInst类
struct VMInst {
InstType type;
protected:
VMInst(InstType t) : type(t) {};
};
struct Addr : VMInst {
public:
Addr(int offset, string seg) : VMInst(InstType::Addr), _offset(offset), _seg(seg) {}
private:
int _offset;
string _seg;
};
C++解析
最主要的问题是要如何让C++解析这边生成的东西。我目前就选用了最简单粗暴的方法,直接生成字符串,用空格分离参数,用换行分离指令
获取所有指令信息
获取有哪些指令
我将所有的指令都放到了Rc::VM::Inst中,通过获取这个module的所有constant,判断哪些是Class
def get_classes(mod)
mod.constants.map{|c| mod.const_get(c)}.select{|c| c.is_a? Class}.sort_by{ |klass| klass.to_s }
end
classes = get_classes(Rc::VM::Inst)
通过这个代码能够获取到Inst这个模块中的所有指令
- 获取每个指令里面是怎么样的
由于ruby并没有定义成员类型的东西,因此我选择自己造一个指定成员类型的东西
有两种实现
实现方式
TypeStruct
第一种是将Struct给包装一层,我给其命名为TypeStruct
使用方式
class CondJump < TypeStruct.new(:cond, :addr => :int)
def to_s
"CondJump #{cond} #{addr}"
end
end
类似于常规的Struct的使用方式,但是输入变成了可以是一个hash
实现
- 实现的一个要点在于new返回的东西需要是一个class。那么我们需要知道Ruby中new是怎么运作的 常规的对象来说,new中会做三件事。class MemberMap def initialize(type_defines) @type_defines = type_defines end def generate(c = “\n”, &f) @type_defines.generate(c, &f) end def keys @type_defines.map { |td| td.name } endend通过allocate分配空间,send initialize方法进行构造对象,最后将obj返回。而在这里只要修改返回的内容即可
- 另一个要点在于需要给返回的class添加一些实例方法 这里我们需要先理解常规的Struct.new做了什么,在我的理解本质上是返回了一个通过动态添加定义的匿名class,那么我们需要的是给这个匿名class添加一些方法来定义 那么我们很自然的就会想到将所有传给new的参数转换为每一个成员以及与之相应的类型定义,之后再对其中每一对“成员名⇒类型”定义对应的获取类型的方法
- 保存一个type_map,用于后面获取信息使用
来看一下代码
def args_to_hash(*args)
args.reduce({}) do |sum, arg|
sum.merge(
if arg.is_a? Hash
arg
else
{ arg => :str }
end)
end
end
class TypeStruct
include TypeCheck
def self.new(*args, &block)
# if don't have allocate, will be nil class
obj = allocate
# initialize is a private method
# initialize must be send instead of direct call
obj.send(:initialize, *args, &block)
end
def initialize(*args)
args = args_to_hash(*args)
Struct.new(*args.keys).tap do |klass|
args.each do |attr, type|
check(type)
# per class Struct is different
klass.define_method "#{attr}_t" do
type
end
end
klass.define_method "type_map" do
args
end
end
end
end
还有一个点是需要在这里检查type的合法性,这里想过生成类的,但是最后想或许现在没必要,还是先用符号吧。检查相关的代码如下
module TypeCheck
VALID_TYPE = {:int => :int, :str => :string}
def invalid?(type)
VALID_TYPE.keys.include? type
end
def check(type)
unless invalid? type
raise "invalid type #{type}, only supported #{VALID_TYPE.map(&:to_s).join(',')}"
end
end
module_function :check, :invalid?
end
attr_type
第二种是增加了一个像attr_reader一样叫做attr_type的东西,但是这个要依赖于常规的Struct,我还是想要常规Struct内部的东西来避免重复代码。虽然有办法不依赖Struct,但是那样需要在这个attr_type里面引入更多不属于这个函数的功能,于是还是放弃吧
使用示例
class Push < Struct.new(:value)
attr_type :value => :int
def to_s
"Push #{value}"
end
end
实现
实现的核心原理还是参数转到hash再对每一对值define_method,只是这次我们要直接hack Module。attr_reader等函数也是采用的类似的做法
type_map的处置有一些不同,type_map需要将成员初始化,所有成员默认str类型,接着需要不断的merge新的参数,这个时候会将type_map中在args出现过的key所关联的值更新,这么解释可能比较复杂,看代码更直接一些
{:a => 1}.merge({:a => 2})
=> {:a=>2}
class Module
def attr_type(*args)
args = args_to_hash(*args)
args.map do |attr, type|
TypeCheck::check(type)
define_method "#{attr}_t" do
type
end
end
@type_map ||= self.members.reduce({}) {|mem| {mem => :str}}
@type_map.merge!(args)
end
end
二者的选择
最后的结果嘛…ide分析不出来,不想看到各种报错的红线。遇到需要手动new的时候只能改成第二种了
在获取成员的时候也用了很脏的做法,没找到什么在不new的情况下获取成员的好方法,因此也只有先new再从里面找。
生成
以前没做的坑
这里其实做一个dsl来描述然后生成是最好的。在好久之前了解rv的时候我甚至一度想开一个坑,用一个dsl来描述一个isa,之后生成对应的C++的读写代码。最后也是咕咕咕了,后续有时间可以做一下,还是挺有意思的。
这是一个描述load store的例子。当时做的时候没想到,现在一想其实也可以直接用Struct来描述,采用和我上面一致的方案
ISA.define :LOAD do field :rd, 5 field :funct3, 3 field :rs1, 5 field :imm, 12endISA::define :STORE do field :offset_4_0, 5 field :width, 3 field :base, 5 field :src, 5 field :offset, 5end
这是一个只做了外观没有做内部实现的例子,属实有点问题,正经人谁会搞出这玩意
namespace :Suica do namespace :T do struct :F do auto :a1 auto :a2, 1 void :f2, ['a', 'b'] do end end endend
生成的实现
有点扯远了,我们来看一下实际生成C++代码的部分。
我们需要生成如下几步
- 获取所有指令信息
- include头文件,名称空间等内容
- InstType的enum定义
- 所有指令类的定义
- 解析输入的部分
每个部分生成一个源码字符串,最后将这些拼接为一个长的字符串就好了
捋清这个流程以后就简单贴一下部分代码好了,源码中<<SRC的部分是一个字符串块的开始,SRC是结束,中间的任何字符都会保留,除了#{expr},这个是将expr to_s以后再嵌入进去
帮助方法
这是我自己加给Array的辅助函数,因为经常会有需要遍历array的所有对象做一套统一的操作最后再join连接的情况
class Array
def generate(c = "\n", &f)
map {|a| f[a] }.join(c)
end
def pure_generate(&f)
map { |a| a.demodulize_class }.generate(&f)
end
end
demodulize_class的话就是简单的将类名去除了module前缀
获取所有指令信息
虽然上面提过,这里再放一下代码
def get_classes(mod)
mod.constants.map{|c| mod.const_get(c)}.select{|c| c.is_a? Class}.sort_by{ |klass| klass.to_s }
end
头文件
def gen_header_namespace
<<SRC
#include <string>
#include <vector>
#include <memory>
#pragma once
using std::string;
SRC
end
InstType的enum定义
def gen_enum_inst_type(classes)
<<SRC
enum class InstType {
#{classes.pure_generate {|c| "#{c},"}}
};
SRC
end
生成的样子
enum class InstType {
Add,
Label,
SetLocal,
};
指令类定义
def gen_class_define(klass)
class_name = klass.demodulize_class
member_map = klass.get_member_map
params = member_map.generate(', ') {|td| gen_class_member(td)}
init_member = "#{member_map.keys.generate(', ') {|name| "_#{name}(#{name})"}}"
init_member = ", #{init_member}" unless init_member.empty?
init_inst = "VMInst(InstType::#{class_name})"
<<SRC
struct #{class_name} : VMInst
{
public:
#{class_name}(#{params}):#{init_inst}#{init_member} {}
private:
#{member_map.generate {|mem_ty| "#{gen_class_member(mem_ty, '_')};"}}
};
SRC
end
这里可能有一些需要提一下的东西,比如说有一个get_member_map
class Class
def get_member_map
instance = self.new
# need keep same order
MemberMap.new(instance.try(:type_map).or_else{[]}.map do |name, type|
TypeDefine.new(name, type)
end)
end
end
为了保持顺序,我选择了用数组来存放。指令最多无外乎一两百条,对于这个数据量不需要太去关心什么高效算法。
为了有更多的类型信息来帮助写易读和更可用的代码,一个名称类型对也转转换为了一个类型
class TypeDefine < Struct.new(:name, :type)
end
而MemberMap是一层包装,内部用typedefine的array存储,但也是可以像hash一样取出所有的key
class MemberMap
def initialize(type_defines)
@type_defines = type_defines
end
def generate(c = "\n", &f)
@type_defines.generate(c, &f)
end
def keys
@type_defines.map { |td| td.name }
end
end
生成的样子
struct Label : VMInst
{
public:
Label(string name):VMInst(InstType::Label), _name(name) {}
private:
string _name;
};
解析代码
def gen_all_parser(classes)
<<SRC
std::unique_ptr<VMInst> get_inst(const std::vector<std::string> &list)
{
#{classes.generate {|x| gen_parser(x)}}
throw std::runtime_error("Unknown inst type" + list[0]);
}
SRC
end
生成的样子(这里只放一个示例
std::unique_ptr<VMInst> get_inst(const std::vector<std::string> &list) {
if (list[0] == "Addr")
return std::make_unique<Addr>(std::stoi(list[1]), list[2]);
}
C++代码格式
这里应该提一下,这种生成方式代码格式一定会乱七八糟,所以还应该调用一下clang-format处理一下。但是VM那边的clang-format之类的许多东西还没有加好,之后再做一下吧
最后
感谢你能看到这里,我再闲谈几句没什么关联的
这个系列我已经到了四篇,也就是一个月。持续做了这么几次已经可以确定只要不出意外自己就能连载下去,于是之后都会在推特推送我的更新(本周的就先算了,ruby本身所占比例有点大)RealAkemiHomura’ Twitter
如果对我的日常有兴趣可以点个关注,如果并不在意这个只想看后续的文章,那么可以通过rss订阅,或者每周一查看我的文章,更新一定是在周末
前面也提到元旦状态差,这些天甚至几次觉得这个系列过于玩具没有意义,想要断更、项目不想做下去了。但我最后还是决定继续更新,不为别的,只因为我还想接着做这个项目,哪怕内容如此简陋,只是一个过于简单的玩具,但我确实从中收获了知识和乐趣
相关文章
- C++ Socket套接字概述
- 【系列教程】 C++项目开发配置最佳实践(vscode远程开发配置、格式化、代码检查、cmake管理配置)
- 学c++还是学java就业「建议收藏」
- C++模板
- 算法(c/c++入门)第一章第一节
- windows平台下,c++获取cpu型号,读取注册表获取系统软硬件信息代码
- 深入理解C++11_c++ string char
- c++ auto类型_auto C++
- c++发送post请求_request的post方法作用
- Visual Studio Code——做嵌入式C/C++开发常用的编辑器软件安装及基本使用总结
- C++回炉之_C++PrimerPlus_第十四章 C++中的代码重用
- c++中fstream是什么意思_汽车配置参数图文详解
- 如何在不使用 sizeof 的情况下在 Cu002FC++ 中找到数组的大小?
- 《安富莱嵌入式周报》第294期:将C/C++代码转换为各种高级语言,超炫渲染着色器,VS2022新闻插件,基于以太网的开源步进电机控制器,Arduino PLC
- C/C++ 调用API获取当前时间
- 【Android FFMPEG 开发】C++ 回调 Java 方法 模板 ( JavaVM *vm | JNIEnv *env | jobject instance | 引用类型 | 模板代码示例 )
- 【C++修炼之路】21.红黑树封装map和set
- 【Android NDK 开发】NDK C/C++ 代码崩溃调试 - Tombstone 报错信息日志文件分析 ( 获取 tombstone_0X 崩溃日志信息 )
- 【Android NDK 开发】NDK C/C++ 代码崩溃调试 - Tombstone 报错信息日志文件分析 ( 使用 addr2line 命令行工具查找动态库中的报错代码位置 )
- 高效易用的C++单元测试框架:轻松构建高质量代码
- 线性表的顺序存储结构的实现及其应用(C/C++实现)详解编程语言
- 新闻速读 > Facebook 的 TransCoder AI 可在 Java、Python 和 C++ 之间转换代码
- 养成良好的C++编程习惯之内存管理的应用详解
- 用c++实现x的y次幂的代码
- 用C++实现一个链式栈的实例代码
- C++创建桌面快捷方式开始菜单的实现代码
- C++代码规范之命名规则
- c++builderTreeView控件节点遍历代码
- C#的锯齿数组以及C++实现代码
- C++中给二维指针分配内存(实现代码)
- 马尔可夫链算法(markov算法)的awk、C++、C语言实现代码