這是一個基於AST的類型安全的boost::variant
的快速刺戳。
我包含了一個簡單的「結構保留變換」,它簡單地改變了存儲在每個AST節點中的數據類型。然而,從理論上講,你可以編寫一個任意的astFunc
,它們都執行節點的基於結構和數據的轉換 - 只需編寫一個type_list
,其中包含前後每個節點中的有效類型。
template<typename... Ts>
struct type_list {};
// specialize data_type to store something special in your AST node:
// (by default, an entry means "the type of the data")
tempalte<typename T>
struct data_type { typedef T type; };
template<typename T>
using DataType = typename data_type<T>::type;
template<template<typename>class F, typename typelist>
struct map_types;
template<template<typename>class F, template<typename...>L, typename... Ts>
struct map_types<F, L<Ts...>> {
typedef L< F<Ts>... > type;
};
template<template<typename>class F, typename typelist>
using MapTypes = typename map_types<F, typelist>::type;
template<template<typename...>class F, typename typelist>
struct apply_list;
template<template<typename...>class F, template<typename...>class L, typename... Ts>
struct apply_list<F, L<Ts...>> {
typedef F<Ts...> type;
};
template<template<typename...>class F, typename typelist>
using ApplyList = typename apply_list<F, typelist>::type;
template<typename typelist>
using Var = ApplyList< boost::variant, MapTypes<DataType, typelist> >;
template<typename type_list>
struct AST_Node {
typedef std::unique_ptr<AST_Node> upAST_Node;
std::vector<upAST_Node> children;
Var<type_list> data;
template<typename T>
AST_Node(T&& t):data(std::forward<T>(t)) {}
};
template<typename type_list>
using upAST_Node = typename AST_Node<type_list>::upAST_Node;
template<typename before_types, typename after_types>
using typeFunc = std::function< Var<after_types>(Var<before_types>) >;
template<typename before_types, typename after_types>
using astFunc = std::function< upAST_Node<after_types>(upAST_Node<before_types>) >;
template<typename before_types, typename after_types>
astFunc<before_types, after_types> elementWiseTransform(typeFunc<before_types, after_types> func) {
return [func](upAST_Node<before_types> before)->upAST_Nodes<after_types> {
upAST_Node<after_types> after(new AST_Node<after_types>(func(before)));
after->children.reserve(before->children.size());
for(auto& child: before->children) {
after->children.push_back(elementWiseTransform(func)(std::move(child)));
}
return after;
};
}
現在,這只是一個開始。
你可以走得更遠了,每種類型的節點都有一組不同的子類型,甚至不同的數字。只需爲我的data_type
中的每種類型節點創建traits類,如children_types
。然後使用類似的技術來定義Var
來定義你的孩子的類型。基本上,通過鏈接MapTypes
可以獲得variant
的std::vector< AST_Node<ChildType<type_list_element>>>
。你可以將兒童的std::vector
和data
合併爲一個變體。
這將允許您編寫一個個人AST_Node
類型的映射(這使得另一個AST_Node
類型),將它們聚集在一起並生成一個函數,然後將遍歷該樹。有些函子會對數據進行操作,只有讓父邏輯接管孩子,一些會轉換整個子樹,一些會對數據進行操作,並停止父邏輯運行在孩子身上。
該技巧變得棘手,因爲您必須以不需要將它們堆疊在一起的方式綜合來自各個功能的提升變體訪問者。如果你看here,你會看到一些關於如何使用一堆std::function<T(U)>
的技巧,並將它們變成一個函子,它可以使用U
中的任何一個。折騰一些工作來計算返回類型的聯合(一個簡單的type_list
刪除重複類型,然後抽入boost::variant
,可能會這樣做) - 這樣的「合併函數」將是一個有效的訪問者。
現在,您可以編寫「重新映射operator_add類型的AST節點」函子,並「重新映射operator_mult類型的AST節點」等幾個函數,將它們組合成一個超級函子,將它們放在AST遍歷算法,並且它將一些類型轉換爲其他類型的AST樹映射出來...
但是,這將是很多工作。
哦,我們可能需要「階段標記」,階段1和階段2 AST是不同的類型。我們可以在type_list
的階段標記每種類型,或者我們可以標記AST
樹本身。哎呀,我們可以使用其他未使用的struct
s來命名AST
的階段,並將階段的階段定義爲類型函數的類型,並在astFunc<before_phase, before_types, after_phase, after_types>
的簽名中應用和執行。
所以這並不壞。我們創建了一個節點類型的type_list
。這些類型不一定是存儲的實際數據。但它可以。
我們創建了一個data_type
特徵類,它將每個節點類型映射到存儲的數據。我們創建一個child_types
traits類,它將每個節點類型映射到子AST的type_list。
每個AST_Node
存儲一個variant<AST_Concrete_Node<Ts>...>
。 AST_Concrete_Node
包含一個DataType<T> data;
和一個MapTypes< std::vector, MapTypes< AST_Node, ChildTypes<T> > > children;
(又名,std::vector< AST_Node<ChildrenTypes...> >
,但您不能直接輕鬆地說)。
接下來,AST_Concrete_Node<T>
轉換函數在模板元編程的一些棘手問題中連接在一起,形成提升變體訪問者。這一步真的很棘手,但我認爲可行。額外的工作是爲了避免未提及的類型,所以我們不必經常說「哦,我們不想改變節點X」,而是必須說「如果我們打到節點Y,不要改變孩子「。
在這一點上,我要說我在喋喋不休 - 以前沒有這樣做過,在具體實施這種類型的體操混亂中遇到的問題將壓倒我抽象地推理它的能力。但這個想法有希望有用 - 我們有類型安全的節點類型轉換,我們聚合在一起並生成類型安全的樹轉換。樹不僅僅是一個通用變體的抽象樹,而是一棵樹,每個節點都知道它的子元素允許的類型,它遞歸地知道相同的類型。我們甚至可以處理「如果我們進入兔子洞的距離足夠遠,那麼」必須有三個孩子,其中第一個是int
,第二個是Bob
,第三個是double
「。
不錯的問題,+1。 – 2013-04-16 17:08:42
包含子類型,文字和矢量的通用樹節點有什麼問題? –
你想在階段A發生什麼樣的類型限制,而階段B不應該發生在階段B?是否有任何限制,你想要允許什麼樣的解析?在控制點運行時失敗的理論可能性是否可以? (也就是說,假設在第一階段中,你的節點可以包含'Foo'和其他類型的其他類型,但是在階段2中它們不應該被使用。你可以確定你將'Foo'作爲一個選項剝離從樹中,如果你還沒有做到這一點,運行時失敗?) – Yakk