像C#或者Haskell這樣的先進的語言都有一個跟語法分不開的最核心的庫。譬如說C#的int,是mscorlib.dll里面的System.SInt32,Haskell的(x:xs)則定義在了prelude里面。Vczh Library++ 3.0的ManagedX語言也有一個類似mscorlib.dll的東西。之前的NativeX提供了一個核心的函數庫叫System.CoreNative (syscrnat.assembly),因此ManagedX的就命名為System.CoreManaged (syscrman.assembly)。System.CoreManaged里面的預定義對象都是一些基本的、不可缺少的類型,例如System.SInt32、System.IEnumerable<T>或者System.Reflection.Type。昨天晚上我的未完成的語義分析器的完成程度已經足以完全分析System.CoreManaged里面的托管代碼了,因此符號表里面的類型系統也基本上是一個完整的類型系統。在開發的過程中得到的心得體會便是寫著一篇文章的來源。
如今,先進的面向對象語言的類型都離不開下面的幾個特征:對象類型、函數類型和接口類型。修飾類型的工具則有泛型和延遲綁定等等。譬如說C#,對象類型便是object,函數類型則有.net framework支持的很好,但是不是核心類型的Func和Action,接口類型則類似IEnumerable。泛型大家都很熟悉,延遲綁定則類似于dynamic關鍵字。var關鍵字是編譯期綁定的,因此不計算在內。Java的int是魔法類型,其設計的錯誤已經嚴重影響到了類庫的優美程度,其使用“類型擦除”的泛型系統也為今后的發展留下了一些禍根,因此這些旁門左道本文章就不去詳細討論了。這篇文章講針對重要的那三個類型和兩個修飾進行討論,并解釋他們之間互相換算的方法。
在C#里面,函數類型也是對象類型的一部分,但是由于C#可以在編譯過程中把一個不完整的函數類型推導為一個完整的函數類型,因此在這里將它和對象類型區分開來。Haskell則在推導上做得更加徹底,這都是先進的有類型語言所不可缺少的一個特征。由于類型之間的互相換算是本文所關心的內容,因此下面先給出幾個定義。當然這些定義在數學上是不嚴謹的,而我也并不追求這個。namespace在這里也不是非常重要,因為存在namespace和不存在namespace所帶來的區別僅僅是一個對象被如何解釋(黑話稱之為Resolving),并不影響推導過程。
我們可以將一個類型命名為T,它是不帶泛型的。一般來說,因為類型存在成員函數,所以類型便有幾個基本的屬性,稱之為this類型和base類型(在C#,代表自己的關鍵字分別是this和base)。this指的是類型T的成員函數所看到的自己的類型。而base類型則是父類的類型。在這里有必要做出一點解釋。只有對象類型才具有base類型,而且其base類型指的是所有父類中唯一一個不是接口類型的那個。函數類型和接口類型都有this類型。
因此對于任何一個具有下面描述的類型T:
class T : U, I1, I2, I3{}
this(T) == T
base(T) == U
現在讓我們來考察一個帶泛型的類型聲明T[U, V],和他的實例化類型T<A, B>之間的關系。我們知道,一個帶泛型的類型聲明T[U, V]實際上是一個不完整的類型,因為這個類型還有U和V兩個參數待填,正如下面的代碼所示:
class T<U, V>{}
而當你實例化他之后,令U==A,V==B,則T類型被A和B實例化成了T<A, B>。這就有點象我們把一個Dictionary[K, V]給實例化成Dictionary<int, string>一樣。一個實例化后的類型才可以被當成另一個泛型類型的類型參數,或者直接使用它來定義一些符號,或者創建一個它的實例等等。但是不完整的泛型類型T[U, V]和它的實例化類型T<A, B>都具有共同的屬性——this類型和base類型。按照上面的定義,this類型是該類型的成員函數所看到的自己的類型。
因此對于任何一個具有下面描述的類型T[U, V]:
class T<U, V> : W<U, V>{}
this(T[U, V]) == T<U, V>
base(T[U, V]) == W<U, V>
當然,對于T<A, B>來說,它也具有this類型T<A, B>和base類型W<A, B>。一般情況下,非泛型類型T的聲明可以被處理成T[],我們令T[]等于T<>,就可以將所有泛型類型的規則實例化到一個帶有0個泛型參數的泛型類型——也就是非泛型類型上面了。因此下面的討論將不作區分。
現在我們考慮如何獲得一個泛型類型的所有成員的類型。我們考慮下面的一組類型:
interface IEnumerable<T>
{
IEnumerator<T> GetEnumerator();
}
class Base<T> : IEnumerable<T>
{
public T Value{get; set;}
}
class Derived<T, U> : Base<U>
{
}
我們來考慮一個問題:如何知道Derived<int, string>的GetEnumerator函數的返回值類型是什么呢?乍一看似乎很簡單,其實對于人類來說這個問題的確是僅靠直覺就可以瞬間回答出來的、根本沒有任何障礙的問題了。這里我一直佩服大自然可以將人類進化到如此牛逼的地步。不過這個問題困擾了我很久,主要是在開發語義分析器的時候,安排各種各樣的類型運算、符號表的結構和其它的一些相關問題的時候,這個問題的難度就提高了。
不過在這里我并不想多說什么廢話,我們僅需要給類型對增加幾個屬性和運算規則,就可以很容易的將這個問題組合成一個表達式了。
首先,我們需要有一個replace操作。replace操作很難一下子嚴謹的定義出來,不過可以給一個直觀的定義,就是:
replace(Derived<T, U>, {T=>int, U=>string}) == Derived<int, string>
相信大家已經可以很輕松的理解了,因此對于一個類型映射tm={T=>string}來說,replace(Derived<IEnumerable<T>>, tk)的結果就是Derived<IEnumerable<string>>了。
其次,我們需要一個decl操作,這個操作返回一個泛型類型的實例類型的定義:
decl(T<A, B>) == T[U, V]
然后,我們還需要一個params操作。這個操作將一個泛型類型的實例類型和他的泛型定義相比較,提取出可以從泛型定義replace到實例類型的那個類型映射:
params(T<A, B>) == {T=>A, U=>B}
因此一般來說,我們有下面的規則。只要類型T是一個泛型類型的實例類型,那么總是有:
replace(this(decl(T)), params(T)) == T
現在我們就可以開始回答上面提到的那個問題了。
首先對于類型Derived<int, string>,我們需要找到他的父類。因此我們可以做如下幾步操作:
tm = params(Derived<int, string>) = {T=>int, U=>string}
tb = base(decl(Derived<int, string>)) = base(Derived[T, U]) = Base<U>
result = replace(tb, tm) = replace(Base<U>, {T=>int, U=>string}) = Base<string>
這樣我們就成功求出T=Derived<int, string>的父類B=replace(base(decl(T)), params(T))=Base<string>了
其次,我們指定要計算類型Base<string>所繼承的那個接口Base[T]=>IEnumerable<T>,我們可以使用
tm = params(Base<string>) = {T=>string}
result = replace(IEnumerable<T>, tm) = IEnumerable<string>
因此對于一個泛型聲明decl(T)所繼承的一個接口Id,泛型聲明D的實例類型T所對應的接口It等于replace(Td, params(T))。
因此對于IEnumerable[T]的函數GetEnumerator的返回值類型IEnumerator<T>,聰明的讀者肯定想到,IEnumerable<string>所對應的類型就是replace(IEnumerator<T>, params(IEnumerable<string>)) == IEnumerator<string>了。這個結果跟求實例類型所繼承的接口類型的方法一樣。
我們可以知道,在計算泛型類型的實例類型的成員類型中,我們總是不斷地在計算replace(A, params(B))的結果。因此在我實現的帶泛型的面向對象托管語言:Vczh Library++ 3.0的ManagedX語言的語義分析器的符號表的代碼里面,真實出現了使用C++所完成的this、base、decl、params、replace和replace_by_type = replace(A, params(B))這樣的六個函數。因為在C++里面,一個類型的實例只能被表示為一個帶有復雜結構的對象的指針。因此只要符號表在計算類型的過程中,把所有產生出來的類型保存下來,建立索引,并且使得“只要類型A和類型B是同一個類型則有他們的指針P(A)和P(B)相等”的這個條件恒成立的話,類型系統的計算速度將直接提高。
至于函數類型的推導法則(主要是應用于lambda表達式的縮寫語法),則等到我開發到那里的時候再寫后續的文章了。System.CoreManaged有幸不需要使用lambda表達式,使得我的第一個里程碑提前到來。
posted @
2011-09-27 05:54 陳梓瀚(vczh) 閱讀(7109) |
評論 (4) |
編輯 收藏
C#真TMD復雜,讓我好生搞了幾個月都沒把一個山寨C#的語法分析器寫完,就想干脆去搞別的東西好了。因此山寨了個小WCF還順帶實踐了一下不需要傳遞password和MD5(password)然后用RSA傳遞AESkey的加密方法,做了一個client可以new server的類來做網絡通訊的東西。完了還搞了搞圖像識別,不過最后除了一個超爛的邊緣檢測以外也沒做成什么東西。
結果昨天晚上夢見我把C#編譯到GPU上面的事情給搞定了(?。。。。缓笤趬衾锩婢驮谙耄?#8220;現在距離javascript編譯到GPU也不遠了,干脆秒掉他好了。”正要開秒,就醒了。
所以想了想,從今天開始,還是繼續做下去罷。
posted @
2011-09-11 10:13 陳梓瀚(vczh) 閱讀(4838) |
評論 (18) |
編輯 收藏
今天終于把雛形給做出來了。主要的方法是牛頓迭代法,把屏幕上的所有點都收斂到函數圖像上面。為了提速,我是用了ThreadTool.QueueUserWorkItem和Parallel.For,還把那顆函數的語法樹用Linq.Expression編譯成了機器碼。下面的這些圖都是二十秒鐘左右就可以畫出來的了。代碼仍然在
Vczh Library++3.0的Candidate\Games\FunctionVisualizer里面。直接F5太慢,要編譯后在資源管理器打開。
下面幾個圖來自于博客園的這篇新聞(
http://news.cnblogs.com/n/106212/)。因為我還沒做絕對值函數,所以只畫了一半。結果還是有點瑕疵,再想想辦法優化一下。




posted @
2011-08-10 22:36 陳梓瀚(vczh) 閱讀(5898) |
評論 (10) |
編輯 收藏
摘要: 今天看到了校內上一個batman equation,覺得很順不舒服。第一個是因為我覺得那個圖是錯的,第二個是因為這讓我開始思考如何對任意的f(x, y)進行繪制。其實這是個很困難的問題。但是如果我假設f(x, y)是處處可微的,那么問題說不定會簡單一點。因此今天晚上就忍不住開始寫了。我的想法是,對于屏幕上的所有點,分別令x或者y等于該點的其中一個坐標元素,對f...
閱讀全文
posted @
2011-08-10 10:40 陳梓瀚(vczh) 閱讀(4712) |
評論 (9) |
編輯 收藏
自己本來想體驗一下開發一個簡陋的模型編輯器以便于把我高中做的那個破即時RPG游戲給弄個續集的,沒想到這東西也不是一件好差事啊。先上圖。
這個模型編輯器才剛剛開始做,菜單都還沒做完整。暫時有添加模型、選擇模型、平面或者點、旋轉放大平移選中的模型或他的一部分。這樣就花了我兩三個星期了。DirectX11竟然沒有D3D11Font,結果10的Font、D2D10和D3D11Device結合起來又十分麻煩,因此我才去了個猥瑣的做法:創建DIBSections,用GDI畫字,然后逐個像素變換成D3D11Texture2D需要的格式,最后貼上去。點的高亮矩形也是這么干的。
然后我創建了一個sphere,然后拖動幾個選中的點,因此就變成了這樣……
(代碼可以在
Vczh Library++3.0的Candidate\Simulator\DirectX\EditorSolution找到)
posted @
2011-08-03 04:54 陳梓瀚(vczh) 閱讀(4423) |
評論 (6) |
編輯 收藏
摘要: Vczh Library++3.0的山寨C#的ManagedX今天完成了一個功能,就是編譯器檢查一個類型在他上下文里面是否可用。這個過程足夠復雜,我寫了足足3個小時。 ManagedX的符號表里面的類型已經被大大簡化了。函數指針是類,基本數字類型也是類,所以歸根結底只有 1:子類型&nbs...
閱讀全文
posted @
2011-07-16 01:38 陳梓瀚(vczh) 閱讀(2844) |
評論 (1) |
編輯 收藏
摘要: DirectX最振奮人心的一點就是,取消了所有固定管線,功能全部用shader來做。這樣就再也不需要受到各種功能上的限制的束縛了。譬如opengl舊版本的時候,可以輸入多個貼圖,然后每一層的貼圖給一個blend function,搞得好生復雜。到了后來,大家都不用固定管線了?,F在DirectX11直接取消了固定管線,API反而簡潔了許多,也不會有各種鳥事發生。...
閱讀全文
posted @
2011-07-15 04:25 陳梓瀚(vczh) 閱讀(10189) |
評論 (17) |
編輯 收藏
摘要: 其實編譯器也不是不做了,只是長時間連續做一個不能看的編譯器,總是有點不爽。因此我決定把業余的時間的主要部分花在編譯器上,而次要的部分則玩游戲,出去游樂,學DirectX11。 在我還是14歲的時候那會,剛開始學習編程不久就買了那本《Visual Basic高級圖形程序設計教程》,從此我走上了做圖形的道路上。后來做游戲,到了大...
閱讀全文
posted @
2011-07-08 08:18 陳梓瀚(vczh) 閱讀(5008) |
評論 (11) |
編輯 收藏
摘要: 由于Vczh Library++3.0的托管語言ManagedX被設計成編譯到本地語言NativeX,因此山寨一個mscorlib.dll是必不可少的。不過我的mscorlib.dll只包含最低限度的代碼。譬如說string,譬如說數組,譬如說函數類型等等這些本不能用托管語言自己來實現的類(C++是唯一的一個所有東西都可以用類庫來彌補的語言)。因此花費了數日,...
閱讀全文
posted @
2011-06-26 09:15 陳梓瀚(vczh) 閱讀(4002) |
評論 (17) |
編輯 收藏
Vczh Library++3.0的ManagedX(山寨C#)語法分析器寫好了。將近1000行的語法樹聲明,使用了ParserCombinator還有93k的語法分析器。寫了好久。其中遇到了一些問題,譬如說C#的語法實在太復雜,parse一個method也好property也好都會有一大堆東西。舉個例子,一個method的文法如下:
1 (attributeInfo + opt(genericInfo) + accessor + memberType + inheritation + internalExternal +
2 type +
3 opt(type << COLON(NeedColon) << COLON(NeedColon)) + ID(NeedId) +
4 (OPEN_EXP_BRACE(NeedOpenExpBrace) >> plist(opt(parameter + *(COMMA >> parameter))) << CLOSE_EXP_BRACE(NeedCloseExpBrace)) +
5 statement
6 )[ToMethodMember]
7
最頂級的operator+一共有10個,也就是說這個東西的返回結果是pair<pair<pair<pair<pair<pair<pair<pair<pair<pair<a, b>, c>, d>, e>, f>, g>, f>, i>, j>, k>。因此ToMethodMember函數的參數也是這個類型。這顯然很令人討厭。
再舉一個例子,property的文法如下:
1 (attributeInfo + accessor + memberType + inheritation + type + opt(type << COLON(NeedColon) << COLON(NeedColon)) + ID(NeedId) +
2 (OPEN_DECL_BRACE(NeedOpenDeclBrace) >> (
3 opt(GET >> statement) +
4 opt(opt(setterAccessor) + (SET >> statement))
5 ) << CLOSE_DECL_BRACE(NeedCloseDeclBrace))
6 )[ToPropertyMember]
7
這個東西的返回結果是pair<pair<pair<pair<pair<a, b>, c>, d>, e>, pair<list<f>, list<list<pair<g, h>>>>。寫起來也很令人發瘋。因此這幾天就想了一種方法來解決這種問題。
首先,我們一定要采取一種方法來讓這種火箭一樣的代碼給平坦化。由于operator+的左結合特性,實際上我們無法去掉這些pair,因此只能換一種方法,譬如說讓pair<pair<pair<a, b>, c>, d>總是等價于tuple<a, b, c, d>。這顯然是可能的,只需要重載足夠數量的tuple類型,就可以讓typename tuple<a, b, c, d>::ResultType等于pair<pair<pair<a, b>, c>, d>。
其次,當我們面對這些pair<pair<pair<a, b>, c>, d>的時候,如何將他賦值到一個struct呢?假設struct的聲明如下:
1 struct s
2 {
3 a _a;
4 b _b;
5 c _c;
6 d _d;
7 };
我們可以用下面的代碼:
1 struct s;
2
3 auto x = ref(s._a)
4 .ref(s._b)
5 .ref(s._c)
6 .ref(s._d)
7 ;
來讓x等于pair<pair<pair<*a, *b>, *c>, *d>。因為“點”也是左結合的。后面只需要再用模板元編程就可以把pair<pair<pair<a, b>, c>, d>賦值給pair<pair<pair<*a, *b>, *c>, *d>了。
讓我們看看
Vczh Library++3.0源代碼(Library\Scripting\Languages\ManagedX\ManagedXParser_Declaration.cpp)在使用了這個構造之前和之后的代碼。首先是直接使用和讀取pair的:
1 Ptr<ManagedMember> ToPropertyMember(const ParsingPair<ParsingPair<ParsingPair<ParsingPair<ParsingPair<ParsingPair<ParsingPair<
2 Ptr<ManagedAttributeInfo>,
3 declatt::Accessor>,
4 declatt::MemberType>,
5 declatt::Inheritation>,
6 Ptr<ManagedType>>,
7 ParsingList<Ptr<ManagedType>>>,
8 RegexToken>,
9 ParsingPair<
10 ParsingList<Ptr<ManagedStatement>>,
11 ParsingList<ParsingPair<ParsingList<declatt::Accessor>, Ptr<ManagedStatement>>>
12 >>& input)
13 {
14 Ptr<ManagedProperty> member=CreateNode<ManagedProperty>(input.First().Second());
15 CopyAttributeInfo(member->attributeInfo, input.First().First().First().First().First().First().First());
16 member->accessor=input.First().First().First().First().First().First().Second();
17 member->memberType=input.First().First().First().First().First().Second();
18 member->inheritation=input.First().First().First().First().Second();
19 member->type=input.First().First().First().Second();
20 if(input.First().First().Second().Head())
21 {
22 member->implementedInterfaceType=input.First().First().Second().Head()->Value();
23 }
24 member->name=ConvertID(WString(input.First().Second().reading, input.First().Second().length));
25
26 member->setterAccessor=member->accessor;
27 if(input.Second().First().Head())
28 {
29 member->getter=input.Second().First().Head()->Value();
30 }
31 if(input.Second().Second().Head())
32 {
33 if(input.Second().Second().Head()->Value().First().Head())
34 {
35 member->setterAccessor=input.Second().Second().Head()->Value().First().Head()->Value();
36 }
37 member->setter=input.Second().Second().Head()->Value().Second();
38 }
39 return member;
40 }
41
其次是用tuple和ref來賦值的:
1 Ptr<ManagedMember> ToPropertyMember(const x::tp<
2 Ptr<ManagedAttributeInfo>,
3 declatt::Accessor,
4 declatt::MemberType,
5 declatt::Inheritation,
6 Ptr<ManagedType>,
7 x::opt<Ptr<ManagedType>>,
8 RegexToken,
9 x::tp<
10 x::opt<Ptr<ManagedStatement>>,
11 x::opt<x::tp<x::opt<declatt::Accessor>, Ptr<ManagedStatement>>>
12 >
13 >::ResultType& input)
14 {
15 Ptr<ManagedProperty> member=CreateNode<ManagedProperty>(input.First().Second());
16 x::Fill(
17 x::ref(member->attributeInfo)
18 .ref(member->accessor)
19 .ref(member->memberType)
20 .ref(member->inheritation)
21 .ref(member->type)
22 .ref(member->implementedInterfaceType)
23 .ref(member->name)
24 .ref(
25 x::ref(member->getter)
26 .ref(
27 x::ref(member->setterAccessor)
28 .ref(member->setter)
29 )
30 )
31 , input);
32 member->name=ConvertID(member->name);
33 return member;
34 }
35
其簡潔程度完全不同。
posted @
2011-06-13 23:01 陳梓瀚(vczh) 閱讀(2955) |
評論 (0) |
編輯 收藏