青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

loop_in_codes

低調做技術__歡迎移步我的獨立博客 codemaro.com 微博 kevinlynx

#

強大的bcb

早就聽說bcb(borland c++ builder)是一個強大的RAD開發工具,也早就聽說曾經的borland搞出的編譯器堪稱經典。

恰好最近在做一個GUI工具,想在界面開發上盡量快一點。每一次用上MFC都讓我覺得渾身難受,總有些常用的

界面功能它就是沒有。在接口實現上,MFC基本上就只是封裝了WIN API而已。想想世界上還有什么強大的GUI庫,

找了一下,其實不管GUI庫封裝的怎么樣,我更多地還是需要一個工具,能夠快速地堆積出界面。

 

于是,在網上下載了被國人精簡了的bcb2009。然后,噩夢開始了。首先,我需要把邏輯層代碼(也就是實現具體

功能的那一層)移植到BCB下。然后得到了很多和語法相關的編譯錯誤:

 

1.
E2397: Template argument cannot have static or local linkage

這個錯誤發生于:

void func()
{

    struct Info

    {

        

    };

   std::queue<Info> abc;
}

它的意思是,模板參數必須是全局鏈接的,總之它不允許std::queue的參數是一個在函數內部臨時定義

的類型(誰來告訴我這是C++標準)。

 

2.

E2357 Reference initialized with 'FileLoader::RawData', needs lvalue of type 'FileLoader::RawData'

這個錯誤發生于:

FileLoader::RawData FileLoader::GetRawData() const;

FileLoader::RawData &raw = loader.GetRawData(); //不能用引用

很久沒看C++書,所以,誰又來告訴我C++標準里,這里到底能不能用引用?

 

3.

E2515 Cannot explicitly specialize a member of a generic template class

這個錯誤發生的情景更復雜些:

template <typename _Tp>

class Test
{

   template <typename _U>

   class Other;

   template <>

   class Other<void>

   {

   };
};

意思是說,我不能在一個模板類里特化成員模板類。誰又來告訴我標準規定的是什么?

 

4.

void func( Obj &a )
{
}

func( Obj() );這個也被視為錯誤。必須得在調用func之前自己定義個臨時變量。

 

5.

我曾經留下了關于宏遞歸的一些代碼,被用在我寫的lua-binder和lua-caller中自動生成代碼。這下好了,

BCB開始警告我,我的這些宏不能工作了。它和MSVC在某些事情上分歧可真是大:

#define PARAM( n ) ,typename P##n //注意這個宏包含一個逗號

#define CHR( x, y ) CHR1( x, y )

#define CHR1( x, y ) x##y

#define BCB_ERROR( a, b ) CHR( a, b )

BCB_ERROR( 1, PARAM( 1 ) ) 當這樣使用宏時,基于我在GNU C上看到的關于宏的規則,會先展開

PARAM(1),于是得到BCB_ERROR( 1, ,typename P2 )。然后,BCB認為PARAM(1)展開的逗號需要參與

BCB_ERROR的展開了。于是,我的整個宏庫無法工作了。

關于這個問題,我直接用MSVC寫了個生成器,讓MSVC替我生成各種參數的lua-binder和lua-caller,然后

寫成外部頭文件,最后直接在BCB里包含了這些頭文件。從而使我的lua-binder和lua-caller可以繼續使用。

 

然后,我的1W多行代碼終于在BCB下50多個WARNINGS的提示下編譯成功了。懷揣著興奮的心情,想自己終

于可以rapid開發界面了。創建了個VCL FORM APPLICATION,噩夢又開始了:

 

1.

BCB莫名其妙地在我編譯一個CPP文件時給出如下提示:

F1004 Internal compiler error at 0x59b4ea8 with base 0x5980000

看起來像是BCB的編譯器給崩潰了。囧。google了一下,發現不是我人品問題,很多人遇到相同的問題。

別人給出的解決方案是:restart your bcb。從昨天晚上到現在為止,這個錯誤發生了好幾次。

 

2.

new std::ofstream();會讓程序崩潰,往不該寫的地方寫了東西。我就奇怪了,你BCB自己帶的C++IO實現,

難道還有BUG?再次google,還真發現是BCB自己的BUG,并且在幾個版本之前就存在這個BUG。那個天真

的老外還說希望在BCB2009下能被修復。修改方案如下:

1)xlocale文件里把這句話注釋了:*(size_t *)&table_size = 1 << CHAR_BIT;

2)xlocale里把成員_Id_cnt訪問屬性改為public,然后在自己的文件里定義一次。

 

3.

程序終于可以運行了。但是BCB的IDE環境總是不那么貼心。我移動了幾個窗口改成我習慣的樣子,但是一重啟

居然又恢復成default(難道是因為盜版)。它的智能提示似乎總是跟著鼠標指針,有時候指向某個符號,鼠標

就顯示忙。為了提示某個類的成員,某個函數的原型,BCB偶爾都會卡一下。其實我不介意我的編輯器沒有這

些提示功能,在MSVC下我也從不用VA來幫我寫代碼。我甚至不厭其煩地在VIM下敲代碼切窗口去看函數原型,

但是,你他媽作為一個IDE就得像個IDE的樣子,要不,你干脆關掉所有功能,別給我卡就行了。

 

這個時候我開始懷疑選擇BCB會不會是一個錯誤的開始,或者說在使用某個東西時,總會帶著使用其他同類東西

的感覺甚至偏見去看待這個新事物。但是,在我想堅持繼續使用BCB時,我一compile,它又提示我:

F1004 Internal compiler error at 0x59b4ea8 with base 0x5980000

posted @ 2009-08-15 11:17 Kevin Lynx 閱讀(5821) | 評論 (17)編輯 收藏

指針和模塊健壯

仔細想想能導致一個C++程序崩潰的幾乎90%原因都是跟指針有關。空指針野指針,一不小心
程序就崩了。寫C++程序的人基本上都知道這個問題。在我們周圍避免這些問題的常規方法
也很多,諸如auto_ptr(及其他基于template的原始指針wrapper)、SAFE_DELETE。當然也
會有很多人在實現一個函數時會很勤勞地對每一個parameter進行合法判斷。

其實,我們都知道,auto_ptr這些東西始終是無法避免野指針和空指針帶來的災難。
SAFE_DELETE也不能阻止別人使用這個空指針。

在我看過的一些開源項目的代碼中,這些代碼給人的感覺就是別人總能詳細地掌控各種資源
(包括指針及其他變量)的使用情況。相比之下,公司隔壁組的老大則顯得保守很多。他要
求我們幾乎要對所有指針的使用進行空值判斷(野指針也判斷不了),當然,各種成員變量
也要進行即使現在看上去沒多大用的初始化。

也許,這樣做后程序是不會掛掉了。但是,就我們的觀點來看,這樣反而會隱藏一些BUG。
為什么我們不能詳盡地去管理一個指針?一個指針變為空了,總是因為在這之前發生了錯誤
。當然,野指針本身就是愚蠢代碼產生的東西,這里沒必要討論。空指針之所以為空,也是
因為在很多時候我們把空作為失敗/錯誤/無效的標志。

恰好上周我的一些代碼就真的在空指針上出現了問題。外網的服務器隨時會因為玩家的一些
臨界操作行為而崩潰掉。雖然我通過修改腳本來屏蔽這個問題(因為不能說停機維護就停機
維護),但是總感覺程序是不安全的。人不吃點教訓絕對不學乖。

后來我對這個問題徹底思考了一下。很多程序員都自認聰明。在寫C++程序時,我從來不提
供沒用的public接口,尤其是set/get。我也從來不對沒必要的成員變量進行初始化。我給
的理由是對于這些東西我都有很清晰的把握,我為什么要做stupid的事情?

但是,我幾乎從來沒有界定,指針在哪些情況下需要去判斷為空?函數的參數絕對不需要。
假如函數的參數就是個空指針,那是client程序員的責任。僅供模塊內使用的指針(包含其
他資源)在內部使用時也不需要去判斷。如果去判斷了,那說明你對你自己寫的模塊都缺乏
精確的把握,證明你的設計思維不夠清晰。

什么時候需要判斷?當指針依賴于外部環境時,例如讀配置文件、載入資源,因為外部因素
不確定不在自己控制范圍內,那么進行判斷。同樣,當使用了其他模塊返回的指針值時,也
需要判斷。這個其實和“外部環境”屬于同一種情況。因為我們對其他模塊也不清楚,更為
隱蔽的是(隨著其他模塊的改變,將來會在你的模塊里爆發崩潰錯誤),其他模塊由別人維
護,其變化更不受自己控制。之前我對這一點界定不是很清楚,這也是我犯錯的原因。

現在想想,像游戲服務器這種程序,里面塞著各種各樣的游戲功能。無論是哪一個模塊出現
個空指針訪問出錯的問題,都會直接讓服務器崩掉。關鍵是這個結果經常伴隨著玩家的損失
。所以理想狀態下,把每一個模塊都放置在單獨的進程里,確實是很有好處的。

posted @ 2009-06-28 19:35 Kevin Lynx 閱讀(2361) | 評論 (5)編輯 收藏

GDI+中從內存讀取圖片/保存圖片到內存

要給項目中增加一個新的模塊,需要先在服務器端做一些圖片處理相關的工作。本來,對圖片

做一些諸如ALPHA混合旋轉縮放的操作,在游戲客戶端應該是很容易的事。但是這事要在服務

器做,就不得不引入一些第三方庫。反正我們的服務器運行于WINDOWS下,這里又需要處理

JPG圖片的加載,我就考慮到了GDI+。

 

在這之前對GDI+沒有過任何接觸。直接翻了MSDN,還好居然有個一系列的usage。GDI+的Image

本身支持JPG的直接載入。但是并沒有我理想中的CreateFromMemory( const void *buf )接口。

看起來唯一可以從內存創建Gdiplus::Image對象的方法是從一個叫IStream*的COM東西。我揣摩

微軟為什么沒有提供我理想中的那個接口,或者說要把GDI+設計成這樣,可能還是考慮到對多語

言的支持。于是問題轉換為如何將一個C語言的const void*轉換為IStream*。我甚至在開始的時候

感覺到是不是要自己實現個Stream。后來在google上找到了一個似乎是標準的方法:首先創建個

HGLOBAL對象,然后通過GlobalLock就可以將一個C的const void*直接memcpy到這個HGLOBAL

里,最后,通過CreateStreamOnHGlobal這樣的接口就可以得到一個IStream。

 

惡心的是,基于之前對服務器內存使用的優化,我現在對于內存的使用非常敏感(誰說現在內存

大了就可以任意malloc了??)。上面那個過程對于資源的管理在MSDN文檔中似乎顯得有點

模糊。CreateStreamOnHGlobal函數的第二個參數指定當IStream->Release的時候,是否會自動

刪除這個HGLOBAL對象。我雖然對COM不懂,但也知道它的對象是基于一種引用計數的管理方式。

逐字看了下文檔,發現一個final單詞,原來是IStream->Release最后一次釋放時,會同時釋放掉

這個HGLOBAL對象。更讓人發指的是,我猜測Image( IStream * )來創建Image時,Image又

會對這個IStream進行一次AddRef。我發覺MSDN對于Gdiplus::Image::FromStream函數的說明

也有點模糊。我揣摩使用FromStream獲得的Image*,是否需要手動去delete?這個地方的內存

資源管理,一定得搞個水落石出。結果是,FromStream的實現就是簡單地new了個Image。而

Image內部肯定會對IStream進行AddRef,并且,如果在Image銷毀前銷毀這個HGLOBAL,這個

Image基本也就廢了。

 

也就是說,Image本身不對HGLOBAL中的圖片數據進行復制。囧。別想讓我再寫個wrap class把

HGLOBAL和Image糾結在一起,簡單考慮,將CreateStreamOnHGlobal第二個參數設為TRUE。

 

要將一個Image保存為一段內存,也比較麻煩。我的方法和google上的相同。當然,微軟的庫依

然讓我在很多細節上栽跟斗(如前所說,可能這是基于多語言支持的考慮)。首先需要創建個空

的IStream,即CreateStreamOnHGlobal第一個參數為NULL。然后將Image Save到這個IStream。

再根據該IStream::Seek獲取其大小,自己再分配段內存,最后IStream::Read讀取進來。同樣,

需要注意相關內存資源的管理。

 

下午簡單把以上兩個過程簡單封裝了下。

下載代碼。

posted @ 2009-05-28 20:23 Kevin Lynx 閱讀(13262) | 評論 (4)編輯 收藏

DNF游戲聲音資源提取

    上午公司斷網,晚上失眠頭痛沒精神,于是隨便打開了DNF游戲目錄下的資源文件。以
前一直對提取游戲資源存在好奇,需要對一些關鍵字節猜測其加密方式。
    DNF游戲目錄下soundpacks下的npk文件看起來似乎比較簡單,這里直接給出文件格式,
懶得寫分析思路了。
    文件開頭的十六個字節是一個固定字符串:NeoplePack_Bill\0。
    接下來四個字節表示本npk文件里打包了多少個WAV文件。npk文件是一個包含了很多聲
音或者圖片的打包文件。類似這種打包文件,一般文件頭都會保存一個文件列表。而這個列
表里又會附加上偏移量和大小等信息。
    接下來的數據就是這里所說的列表。每一個列表項包含三個數據域:偏移、大小、文件
名。如下示意:

    NeoplPack_Bill\0 (16 bytes)
    file_count( 4 bytes)
    item1:offset(4 bytes), size(4 bytes)
    item2:offset(4 bytes), size(4 bytes)
    ...
    itemn:offset(4 bytes), size(4 bytes)
    ...

    文件列表之后,就是具體的每個文件的內容。開始我還在擔心npk會為每一個聲音文件
加密。或者只保存聲音文件的具體數據,而聲音文件文件頭則只保存一份(因為所有文件的
文件頭很有可能全部是一樣的)。后來稍微搜索了下WAV的格式,只需要比對下npk中某一個
文件內容的頭部是否和WAV格式的頭部相同,就可以基本斷定其是否加密。
    結果是,npk對包內的每一個WAV文件沒做加密。
    然后立即寫了個程序,根據文件列表中的偏移值和大小值,將每一個WAV單獨取出來,就
OK了。
    完整的格式為:

    NeoplPack_Bill\0 (16 bytes)
    file_count( 4 bytes)
    item1:offset(4 bytes), size(4 bytes)
    item2:offset(4 bytes), size(4 bytes)
    ...
    itemn:offset(4 bytes), size(4 bytes)
    file1
    file2
    ...
    filen

    我想圖片資源也應該差不多,不過圖片資源肯定要復雜些。下午公司網絡好了,網上搜
索了下,發現居然已經有了DNF資源提取工具了,唉。

    提供下源代碼和MingW編譯好的可執行文件,另聲明:本文及相關工具代碼只作學習研究
用,任何后果與作者無關。

posted @ 2009-05-12 22:50 Kevin Lynx 閱讀(5362) | 評論 (2)編輯 收藏

kl中的錯誤處理

kl中的錯誤處理

    之前我一直說錯誤處理是kl里的軟肋,由于一直在關注一些具體功能的改進,也沒有對
這方面進行改善。

    我這里所說的錯誤處理,包括語言本身和作為庫本身兩方面。
    語言本身指的是對于腳本代碼里的各種語法錯誤、運行時錯誤等的處理。好的處理應該
不僅僅可以報告錯誤,而且還能忽視錯誤讓處理過程繼續。
    而把kl解釋器作為一個庫使用時,庫本身也應該對一些錯誤情況進行報告。

    整體上,kl簡單地通過回調函數指針來把錯誤信息傳給庫的應用層。而因為我希望整個
kl實現的幾層(詞法分析、語法分析、符號表、解釋器等)可以盡可能地獨立。例如雖然語
法分析依賴于詞法分析(依賴于詞法分析提供的接口),但是因為詞法分析并不對語法分析
依賴,所以完全可以把詞法分析模塊拿出來單獨使用。所以,在日志方面,我幾乎為每一層
都附加了個error_log函數指針。
    而用戶層在通過kllib層使用整個庫時,傳入的回調函數會被間接地傳到詞法分析層。
實際上,當kl作為一個庫時,kllib正是用于橋接庫本身和用戶層的bridge。

    另一方面,語言本身在處理錯誤的腳本代碼時,錯誤分為幾大類型層次:
    1.詞法錯誤 lex error,如掃描字符串出錯
    2.語法錯誤 syntax error,整理語法樹時出錯
    3.運行時錯誤 runtime error,在解釋執行代碼時出錯
    4.庫錯誤 lib error,發生在kllib這個bridge層的錯誤
    kl在報告錯誤信息時,會首先附加該錯誤是什么類型的錯誤。

    這里最麻煩的是語法錯誤的處理。因為語法分析時發生錯誤的可能性最大,錯誤類型也
有很多。例如你少寫了分號,少寫了括號,都會導致錯誤。這個階段發生錯誤不僅要求能準
確報告錯誤,還需要忽略錯誤讓整個過程盡量正確地下去。

    語法分析階段最根本的就是符號推導(單就kl的實現而言),所謂的符號推導是這樣一
個過程,例如有賦值語句:a = 1;語法分析時,語法分析器希望(所謂的推導)等號后面會
是一個表達式,當分析完了表達式后,又希望接下來的符號(token)是分號作為該語句的結
束。
    所以,klparser.c中的syn_match正是完成這個過程。每次你傳入你希望的符號,例如
分號,該函數就檢查詞法分析中當前符號(token)是否是分號。當然,對于正確的腳本代碼,
它是一個分號,但是如果是錯誤的代碼,syn_match就會打印諸如:
    >>syntax error->unexpected token-> ....
    即當前的符號是不被期望的。

    上面完成了錯誤的檢測。對于錯誤的忽略,或者更高級點地對錯誤的校正,kl中處理得
比較簡單,即:直接消耗掉這個不是期望中的符號。例如:
    a = 1 /* 忘加了分號 */
    b = 1;
    上面兩句代碼被處理時,在處理完a=1后,發現當前的符號(token)b(是一個ID token)不
是期望(expect)中的分號,首先報告b不是期望的符號,然后kl直接掠過b,獲取下個符號=。
然后處理a=1這個過程結束。當然,下次處理其他語句時,發現=符號,又會繼續發生錯誤。

    錯誤信息中比較重要的還有行號信息。之前kl這方面一直存在BUG,我在寫貪食蛇例子
的時候每次新加代碼都不敢加太多。因為解釋器報告的錯誤行號總是錯誤的,我只能靠有沒
有錯誤來找錯誤,而不能通過錯誤信息找錯誤。
    行號信息被保存在詞法分析狀態中(lexState:lineno),語法分析中獲取token時,會取
出當前的行號,保存到語法樹樹節點中。因為包括解釋模塊都是基于樹節點的,所以詞法分
析語法分析解釋器三層都可以準確報告行號。

    但是之前解釋器報告的行號始終很詭異。癥結在于我在載入腳本代碼文件時,以rb方式
載入,即二進制形式。于是,在windows下,每行文本尾都會有\r\n兩個字符。而在詞法分
析階段對于行號的增加是:
    case '\n':
    case '\r':
        ls->lineno ++;
    不同OS對于文本文件的換行所添加的字符都不一樣,例如windows用\r\n,unix系用\n
,貌似Mac用\r。所以,詞法分析這里寫應該可以準確地處理行號。

    但是對于windows,這里就直接將行號增加了兩次,所以也就導致了行號出錯的問題。查
了下文檔,發現以文本方式打開文件("r"),調用fread函數讀入文件內容時,就會自動把
\r\n替換為\n。

    代碼改后,又出問題。這個時候,通過fseek和ftell獲取到的文件尺寸,貌似包括了
\r\n,而fread出來的內容卻因為替換\r\n為\n而沒有這么多。
    不過文件載入不屬于kl庫本身,kl只接收以字符串形式表示的腳本代碼,所以也算不了
核心問題。

    同樣,最新代碼可以從google SVN獲取。當然,我也在考慮是否換一個新的項目地址。

posted @ 2009-03-26 17:17 Kevin Lynx 閱讀(3187) | 評論 (0)編輯 收藏

kl sample:貪食蛇

 

    貌似最近CPPBLOG寫一門腳本語言比較流行,連我這種山寨程序員都搞出一個像C又像
BASIC的所謂腳本語言,可見其流行程度。


    這個kl腳本例子,是一個具有基本功能的貪食蛇游戲。這個例子中使用了兩個插件:
HGE引擎、以及一個撇腳的二維數組插件。因為kl對于數組的實現不是那么漂亮,而我實在
不想因為加入二維數組的支持而讓代碼看起來更亂,所以直接不支持這個特性。考慮到二維
數組的應用在一些小游戲中還是比較重要(例如這個貪食蛇,總需要個容器去保存游戲區域
的屬性),所以撇腳地加了個支持number的二維數組插件。

    HGE插件我只port了部分接口,也就是注冊了一部分函數到腳本里,提供基本的貼圖功
能。(port--我實在找不到一個合適的詞語來形容這種行為---HGE到一門腳本語言里,我似
乎做過幾次)

    不知道有沒必要提供貪食蛇的實現算法,這似乎說出來有點弱智。- - 不過為了方便別
人閱讀kl腳本代碼,我還是稍微講一下。游戲中使用一個二維數組保存整個游戲區域,所謂
的游戲區域就是蛇可以活動到的地方。每一個二維數組元素對應游戲區域中的一個格子,姑
且稱為tile。每個tile有一個整數值表示其屬性,如BODY、WALL、FOOD、NONE。蛇體的移動
歸根結底就是蛇頭和蛇尾的移動。蛇頭和蛇尾屬性一樣,但是蛇頭負責把所經過的tile設置
為BODY,而蛇尾則把經過的tile設置為NONE。蛇頭的移動方向靠玩家控制,每次蛇頭轉彎時
,都會記錄一個轉彎點到一個隊列。轉彎點包括轉彎XY坐標以及轉向的方向。蛇尾每次移動
時都會檢查是否到達了一個轉彎點,是的話就設置自己的移動方向為該轉彎點記錄的方向。

    雖然我寫了kl這個腳本語言,但是語言特性并不是我設計的。我只是取了C語言的一些
特性。所以在寫這個sample的時候,我對于kl這個腳本語言的感覺,就是一個像basic的C。
因為它太單一,就像BASIC一樣只擁有語言的一些基本功能,不能定義復雜的結構,沒有天
生的對各種數據結構的支持(例如某些語言直接有list, tuple之類)。

    以前中學的時候在電子詞典上用GVBASIC寫小游戲,當時除了BASIC什么也不知道。今天
寫這個貪食蛇例子,感覺就像以前用BASIC。

    回頭說說一些kl腳本里的特性。從這個例子里(見下載包里的snake.kl),諸如while,
for,if...else if...被支持(之前發布的版本里還不支持for和else if)。全局變量支持
賦初值(上個版本不支持)。當然,還演示了如何使用插件函數。

    但是,仍有一些特性在我的懶惰之下被置之不理。例如return后必須跟一個表達式,這
意味著單純的return;將被視為語法錯誤。對于if( a && b ),kl會計算所有的表達式,而
別的語言也許會在a會false后不計算b,這也許不算個問題,但起碼我還沒修正。還有,kl
內部對于錯誤的報告依然沒被修復,少打一個分號你會得到一系列錯誤的報告,但是卻沒有
準確的行號。甚至,你會看到解釋器崩掉。不要緊,在我心里,它作為當年電子詞典上那個
GVBASIC而言,已經很強大的了。:DD

    最近接觸了很多UNIX和GNU之類的東西,發覺沒有提供版權說明的‘開源’,原來都是偽
開源。雖然我也想按照GNU編碼標準里所說為kl的發布包里附加Changelog之類的說明,但是
出于懶惰,還是以后再說吧。同樣,這次提供的下載里包含了一些編譯好的東西,所以我不
保證它在你的機器上依然可以運行。我使用了MingW來編譯這些,并且提供有點丑陋的Makefile。
HGE使用了1.81版本。
    貼張圖給懶得下載的人:

snake_screenshot

    下載例子,包含腳本代碼。

    如果要獲取kl實現代碼,建議從我在google的SVN獲取:
http://code.google.com/p/klcommon/

posted @ 2009-03-25 21:17 Kevin Lynx 閱讀(4416) | 評論 (1)編輯 收藏

實現一種解釋性腳本語言(七)

author: Kevin Lynx email: zmhn320#163.com date: 3.12.2009

腳本與C語言交互

    這其實是這一系列的最后一篇,因為我覺得沒什么其他需要寫的了。
    一般而言,腳本語言同C語言交互,包括在C語言中注冊C函數到腳本,從而擴展腳本的
功能,以及在C語言中調用腳本函數。
    為了擴展腳本的功能,這里引入插件的概念。kl在這方面大致上實現得和lua相似。kl
支持靜態插件和動態插件。
    在C語言中調用腳本函數,kl中提供了一些簡單的接口用于滿足需求。

靜態插件

    靜態插件其意思是在C代碼中注冊函數到腳本中,并隨腳本庫一起編譯鏈接成最終執行
程序。因為其綁定是在開發一個程序的過程中,所以被稱為靜態的。
    一個插件函數,指的是可以被注冊進腳本的C函數。這種函數必須原型一樣,在kl中這
個函數的原型為:typedef struct TValue (*kl_func)( ArgType arg_list );   
    當你定義了一個這樣的原型的函數時,可以通過kl庫提供的:
    int kl_register( struct klState *kl, kl_func f, const char *name )來注冊該
函數到kl腳本中。該函數參數很簡單,第三個參數指定注冊進腳本中時的名字。

    原理比較簡單:在解釋器中保存著一個插件符號表,該符號表的符號名就是這個函數提
供的名字,符號對應的值就是第二個參數,也就是插件函數的函數地址。
    解釋器解釋到函數調用時,先從插件符號表中查找,如果找到符號,就將符號的值轉換
為插件函數,并調用之。

    插件函數的參數其實是一個參數鏈表。腳本里調用插件函數時,所傳遞的參數將被解釋
器整理成參數鏈表并傳遞給插件函數。kl庫中(集中在kllib.h中)提供了一些方便的接口用
于獲取每個參數。
    插件函數的返回值也將被解釋器轉換為腳本內部識別的格式,并在必要的時候參與運算

動態插件

    動態插件同靜態插件的運作方式相同,所不同的是動態插件的插件函數被放在動態運行
時庫里,例如windows下的dll。
    kl插件編寫標準里要求每個動態插件必須提供一個lib_open函數。kl解釋器(或者kl庫
--當被用作庫時)載入一個動態插件時,會直接調用lib_open函數。lib_open函數的主要目
的就是把該插件中的所有函數都注冊進腳本里。

    因為動態插件在設計之初沒有被考慮,所以我并沒有為kl加入一些原生的關鍵字用于導
入動態插件,例如import、require之類。我在靜態插件層次提供了這個功能。即我提供了
一個libloader靜態插件,鏈接進kl解釋器程序。該靜態插件提供腳本一個名為import的函
數。該函數負責動態載入dll之類的動態庫,并調用里面的lib_open函數完成動態插件的注
冊。

C程序里調用腳本函數

    這個比較簡單,通常C語言想調用一個腳本函數時,會傳入腳本函數名。因為腳本函數名
都保存在全局符號表里,kl庫從全局符號表找到該函數符號,并轉換其值為語法樹節點指針
,然后傳入解釋器模塊解釋執行。
    kl庫提供struct TValue kl_call( struct klState *kl, const char *name, ArgType args );
用于在C里調用腳本函數。

代碼導讀

    kllib.h/kllib.c作為一個橋接層,用于封裝其他模塊可以提供給外部模塊使用的接口,
如果將kl作為一個庫使用,用戶代碼大部分時候只需要使用kllib.h中提供出來的接口。
    源碼目錄plugin下的kllibbase.c中提供了靜態插件的例子,kllibloader.c提供了裝載
動態插件的功能。
    源碼目錄plugin/hge目錄下是一個封裝2D游戲引擎HGE部分接口到kl腳本中的動態插件
例子。
    源碼目錄test/kl.c是一個簡單的kl解釋程序,它用于執行一段kl代碼。這個程序同之前
說的解釋器不是同一回事。當我說到解釋器時,它通常指的是klinterpret.c中實現的解釋
模塊,而解釋器程序則指的是一個使用了kl庫的獨立解釋器可執行程序。

posted @ 2009-03-12 09:35 Kevin Lynx 閱讀(4949) | 評論 (4)編輯 收藏

實現一種解釋性腳本語言(六)

author: Kevin Lynx email: zmhn320#163.com date: 3.11.2009

解釋器

    整理出語法樹后,我們就可以根據語法樹,并配合符號表開始解釋執行腳本代碼。這就
是接下來要涉及到的解釋器。

工作原理

    在第四節中講語法樹時,其實就已經提到解釋器的大致工作原理。
    一個kl的hello world例子代碼大致為:
    function main()
    {
        print( "hello world\n" );
    }
    在第二節中我描述了kl代碼整體上的結構,是以函數為單位的。因此,對于一個完整的
kl腳本代碼,其經過語法處理后,將建立一棵大的語法樹,該語法樹大致結構為:
    fn1_node
        stmt_node1
        stmt_node2
        ...
    fn2_node
        stmt_node1
        stmt_node2
        ...

    fn1_node和fn2_node同屬于同一個作用域,fn1_node的sibling指針指向fn2_node,即在
整個樹結構中,每一個node通過child[3]成員連接其子節點,通過sibling指針連接其相鄰
的節點。   
    解釋器解釋執行時,就是從main函數所對應的節點開始遞歸執行的。對于每個節點,都
可以知道該節點對應了哪種程序邏輯:是加法運算、比較運算、還是一些控制語句等等。
    以這樣的控制語句舉例:
    if( 1 ) print( "true" );
    對if語句而言,其語法樹結構為:
          if_node
         /   |    \
        /    |     \
    con_exp    then_stmt else_stmt

    即,if語句有最多有三個子節點(child[3]),child[0]指向if的條件表達式,child[1]
指向條件表達式為真時執行的語句序列,如果if有else部分,那么child[2]就指向else部分
的語句序列。
    那么,在發現某個節點是if節點時,就首先計算其條件表達式節點。這個節點的計算方
式同腳本中其他所有表達式的計算方式相同,當然,它也是一個遞歸操作。計算完后判斷該
表達式的值是否為真,為真則遞歸執行if節點的child[1]節點,否則檢查是否有else節點,
有的話就執行child[2]節點。

    其他所有節點的解釋方式都是相同的。


解釋器環境

    解釋器環境指的是解釋器在解釋執行腳本代碼時,所需要的運行時環境。kl中主要是符
號表信息。一個解釋器環境會有三個符號表:全局符號表,主要保存全局變量以及腳本函數
符號;函數局部符號表,在解釋調用一個腳本函數時,會建立臨時的符號表;插件符號表,
用于保存插件注冊的函數。

如何解釋執行函數

    函數主要有兩大類型:腳本內定義的函數以及插件注冊進符號表的函數。無論是哪種函
數,都會在符號表中建立對應的符號。對于前者,符號被保存于全局符號表,其保存的內容
是該函數節點的節點指針;而對于后者,則保存的插件函數的函數地址值。

    每一次解釋器解釋到一個函數調用節點時,會優先在插件符號表中查找該函數符號。如
果找到,就將其值轉換為約定的插件函數類型(如同lua里注冊的C函數一樣),然后整理參
數調用之。這個時候代碼執行權轉接到插件函數里。如果沒找到,就在全局符號表里查找,
找到后就強轉為語法樹節點指針,并解釋執行該節點下的語句。

代碼導讀

    解釋器的代碼位于klinterpret.h/klinterpret.c中。整體上而言沒什么特別的地方,
主要是利用語法樹的特點。
    完成了這一節后,kl就已經可以解釋執行所有的腳本語句。當然,因為沒有輸出功能,
只能在調試器里看看計算結果。下一節里會講到將腳本結合進C語言,從而可以讓C語言注冊
所謂的插件函數到腳本里,也就可以讓腳本具有print這樣的輸出函數。

posted @ 2009-03-11 09:12 Kevin Lynx 閱讀(3635) | 評論 (0)編輯 收藏

實現一種解釋性腳本語言(五)

author: Kevin Lynx email: zmhn320#163.com date: 3.10.2009

符號表

    在上一節中,當我們的解釋器解釋執行age=age+1這個語法樹時,會涉及到變量age的值
。實際上我們還需要個保存腳本中相關變量的模塊,當我們的解釋器獲取到一個ID樹節點時
,需要從這個模塊中獲取出該變量的值,并參與運算。
    這個我稱之為符號表。我想到這里,我所說的概念很可能和教科書有點不一樣了。

什么是符號表?

    符號表(symbol table)就如同其字面意思一樣,是一個表,更寬泛地說是一個保存符號
的容器。
    腳本中諸如變量函數之類的東西都算作符號,例如age。符號表就是保存這些符號的容
器。
    在kl中,符號表保存著某一個作用域里的變量。其全局符號表還保存著函數符號,對于
函數符號而言,其值為語法樹樹節點的指針值。當調用一個函數時,將該值轉換為樹節點,
然后執行。當然,這應該算做解釋執行一節的細節,不多說。

    再明確下符號表的作用,舉例,在上一節中,涉及到這么一個例子函數:
    value factor( TreeNode *node )
    {
        switch( node->type )
        {
            case ID:
                /* 在這里,發現一個樹節點類型為ID,就需要根據ID對應的名字,也就
                 是age,在符號表中查找age的值 */
                return age;   
        /* ... */
        }
    }
    以上注釋闡述了符號表的作用。

符號表的實現

    其實不管符號表如何實現,對于其他模塊而言,對符號表的唯一要求就是提供幾個類似
這樣的接口:
    value sym_lookup( const char *name );
    void sym_insert( const char *name, value val );
    也就是說,提供查找符號值,以及插入新符號的接口。

    在kl中,使用了<編譯原理與實踐>中相同的符號表數據結構實現。即使用了hash表,
hash數組中每個元素保存的是一個鏈表頭節點。每一個符號字符串通過散列函數得到hash數
組索引,然后在該索引里進行一次線性查找。很典型的hash結構。

    另一方面,因為kl支持全局和函數局部兩個作用域。所以kl中有一個全局符號表,用于
保存全局變量以及所有的函數符號;同時每一次進入一個函數時,就會創建一個臨時的局部
符號表,用于存儲局部變量;后來,為了支持插件,插件函數被特定地保存在另一個全局符
號表里。

代碼導讀

    kl中的符號表實現代碼在klsymtab.h/klsymtab.c中,實現比較簡單,無需多言。

posted @ 2009-03-10 08:58 Kevin Lynx 閱讀(3232) | 評論 (0)編輯 收藏

實現一種解釋性腳本語言(四)

author: Kevin Lynx email: zmhn320#163.com date: 3.9.2009

語法分析

    語法分析接收詞法分析階段的token集合為輸入,將這些沒有關系的tokens整理為相互
之間有關系的結構。書面點的說法叫語法樹。
    每一次讓我寫這些文縐縐的概念真讓我受不了:D。

語法樹

    語法樹簡單來說就是一個以token作為每個節點的樹型結構。例如我們有表達式age =
age + 1;,在詞法階段它被整理為token集合:age, =, age, +, 1。那么在經過語法分析后
,這些tokens將被整理為大致如下的樹形結構:
        =
      /   \
    age    +
         /   \
       age     1

    整理成這樣的結構有什么好處?就kl解釋器而言,最直接的好處就是我可以遞歸地解釋
這棵樹執行。例如:

    value compute( TreeNode *root )
    {
        /* child[0]保存結果值age,child[1]是那個+表達式 */
        return op_exp( root->child[1] );
    }

    value op_exp( TreeNode *node )
    {
        switch( node->op )
        {
            case '+':
            {
                /* + 表達式必然有左右操作數 */
                value left = factor( node->child[0] );
                value right = factor( node->child[1] );
                return left + right;
            }
        }
    }
    value factor( TreeNode *node )
    {
        switch( node->type )
        {
            case ID:
                /* 查找age的值 */
                return age;

            case CONST:
                /* 1 是常量 */
                return node->cvalue;
        }
    }

    如你所見,當我們完成了語法分析階段,我們就可以完成我們的解釋器了。后面我會單
獨講解下整個解釋過程,包括每個模塊是如何協作的。我不知道其他解釋器是怎么做的,但
是我這樣做,起碼結果是對的。

如何整理出語法樹?

    這里不得不提到所謂的BNF文法,很明顯你還是無法從我這里獲取編譯原理里某個概念
的講解。我這里提這個概念完全是方便我提到這個東西。
    每一種語言都有其自己的BNF文法,因為萬惡的先知告訴我們,每一門語言都需要建立
其語法樹。- -!
    就像詞法分析一樣,因為大部分語言的結構都差不多,所以我覺得詞法分析和語法分析
基本上都沒有任何特別之處。也就是說,別的語言的BNF你可以直接拿來改改用。
    抄個BNF如下:
    exp -> exp adop term | term
    addop -> + | -
    term -> term mulop factor | factor
    mulop -> *
    factor -> (exp) | number
    這個BNF用來描述一般的算數表達式(+-*/)。簡單來說,一門語言的BNF就是用于描述該
語言所有語句的東西,包括if、while、函數定義之類。建議你google一下C語言的BNF,并
改造之用于你自己的語言。

    那么有了BNF之后,該如何整理出語法樹呢?
    通常,我們的代碼里都會直接有對應exp、term、addop之類的函數。按照我這句話的意
思,上面抄的BNF被翻譯為程序代碼后,就可能為:
    exp()
    {
        if( ... ) left = exp()
        right = term();
        left addop right;
    }
    term()
    {
        if( ... ) left = term()
        right = factor();
        left mulop right;
    }
    factor()
    {
        if( ... ) return exp();
        else return number;
    }

    (可能還會涉及到EBNF,用于處理重復和選擇的一些情況---不用管這句話)

    每一個函數基本上都會返回一個樹節點,當然,該節點下可能會有很多子節點。   

總結

    語法分析基本上就是以上信息。它將詞法分析輸出的token集合整理成一顆語法樹。為
了整理出這棵語法樹,你需要找一份用于描述你語言的BNF,然后根據BNF翻譯成處理代碼。

代碼導讀

    kl中的整個語法分析代碼位于klparser.c/klparser.h中,其BNF基本上取自<編譯原理與
實踐>附錄中的C_語言。

posted @ 2009-03-09 11:12 Kevin Lynx 閱讀(3661) | 評論 (3)編輯 收藏

僅列出標題
共12頁: First 4 5 6 7 8 9 10 11 12 
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            久久久一本精品99久久精品66| 麻豆精品一区二区av白丝在线| 黑人巨大精品欧美一区二区| 国产精品美女诱惑| 欧美日韩中文字幕在线| 欧美精品日日鲁夜夜添| 欧美亚男人的天堂| 国产视频丨精品|在线观看| 在线电影欧美日韩一区二区私密| 亚洲电影av| 99re6这里只有精品| 亚洲永久精品大片| 久久先锋影音av| 91久久精品一区| 99精品免费| 欧美在线观看网址综合| 免费久久99精品国产自| 欧美三级乱人伦电影| 国产三级欧美三级| 日韩亚洲成人av在线| 欧美一区视频在线| 欧美高清视频免费观看| 亚洲视频一二三| 欧美电影免费网站| 国产视频在线观看一区二区三区| 亚洲精品日韩久久| 久久精品五月婷婷| 9l视频自拍蝌蚪9l视频成人| 欧美中在线观看| 国产精品久久久久影院亚瑟| 亚洲国产婷婷香蕉久久久久久| 性欧美xxxx大乳国产app| 91久久久久久久久| 裸体女人亚洲精品一区| 国产亚洲精品一区二555| 一本色道久久综合亚洲二区三区| 久久精品99国产精品日本 | 国产精品丝袜白浆摸在线| 狠狠色狠狠色综合| 香蕉av福利精品导航| 91久久精品国产91性色| 久久精品一本| 国产亚洲福利| 亚洲欧美亚洲| 亚洲精品自在在线观看| 久久精品视频免费| 99亚洲一区二区| 欧美多人爱爱视频网站| 黄色成人在线| 久久免费少妇高潮久久精品99| 亚洲午夜激情| 国产精品视频一区二区三区| 亚洲图片在线| 亚洲视频在线看| 国产精品久久久久aaaa樱花 | 亚洲人成在线观看一区二区| 久久精品人人爽| 性欧美精品高清| 国产日产欧产精品推荐色| 午夜亚洲伦理| 亚洲欧美在线磁力| 国产欧美日韩另类视频免费观看| 欧美一级网站| 性欧美大战久久久久久久免费观看| 国产精品乱码一区二区三区| 亚洲一区二区三区涩| 一区二区三区精品| 国产精品成人一区二区网站软件| 亚洲校园激情| 亚洲欧美国产制服动漫| 国产亚洲成av人在线观看导航| 久久激情综合网| 两个人的视频www国产精品| 亚洲免费观看高清完整版在线观看熊| 亚洲国产欧美不卡在线观看| 欧美色视频日本高清在线观看| 亚洲综合电影| 久久久久久久久蜜桃| 日韩亚洲国产精品| 亚洲综合社区| 亚洲欧洲综合另类| 亚洲私人影院在线观看| 国产欧美一二三区| 美女国产精品| 欧美日韩一区二区在线播放| 久久精品99国产精品| 免费成人高清| 午夜伦理片一区| 卡一卡二国产精品| 亚洲视频欧美在线| 久久精品午夜| 亚洲免费在线| 麻豆精品传媒视频| 亚洲欧美精品在线| 老司机免费视频一区二区三区| 在线视频欧美精品| 久久久久国产一区二区三区| 一区二区免费看| 久久午夜视频| 久久av红桃一区二区小说| 欧美成人一区二免费视频软件| 亚洲男人天堂2024| 欧美大片专区| 久久一区二区三区av| 欧美日韩精品一区二区天天拍小说 | 美女脱光内衣内裤视频久久网站| 欧美黄色aaaa| 久久久久久亚洲精品杨幂换脸| 欧美日韩国产在线播放网站| 看欧美日韩国产| 国产情侣久久| 亚洲永久精品大片| 9i看片成人免费高清| 久久久久久久久久久成人| 亚洲欧美在线播放| 欧美国产日韩精品| 欧美成人免费va影院高清| 国产一区二区三区久久久久久久久| 亚洲人成网站影音先锋播放| 一区二区亚洲精品国产| 午夜免费日韩视频| 欧美伊人久久大香线蕉综合69| 欧美三级小说| 日韩一区二区免费看| 亚洲精品国产系列| 久热精品视频| 免费在线看成人av| 玉米视频成人免费看| 欧美一区二区三区四区夜夜大片 | 欧美成人蜜桃| 亚洲福利在线观看| 久久人91精品久久久久久不卡| 欧美有码视频| 国产日韩一区二区三区| 亚洲视频在线一区观看| 亚洲自拍偷拍一区| 国产精品一区二区三区观看| 亚洲尤物视频网| 欧美一区2区三区4区公司二百| 国产精品外国| 欧美在线关看| 久久夜色精品国产| 亚洲电影在线观看| 久久先锋影音av| 亚洲国产精品国自产拍av秋霞| 亚洲精品一区二区三区福利| 欧美黑人多人双交| 亚洲一区二区三区精品在线观看| 亚洲欧美网站| 国产一级久久| 欧美sm视频| 一区二区三区日韩在线观看| 午夜精品在线| 亚洲福利视频二区| 国产精品扒开腿爽爽爽视频 | 亚洲线精品一区二区三区八戒| 欧美网站在线观看| 午夜精品国产| 麻豆国产精品777777在线| 亚洲欧洲一区二区三区| 欧美色123| 久久av老司机精品网站导航| 欧美黄网免费在线观看| 亚洲视屏一区| 伊人狠狠色j香婷婷综合| 欧美成年人网| 亚洲尤物在线视频观看| 在线观看精品| 欧美日韩免费一区二区三区| 羞羞视频在线观看欧美| 欧美激情1区| 欧美一区二区三区视频| 亚洲精品女av网站| 国产主播精品| 欧美午夜a级限制福利片| 久久久久久久国产| 一区二区精品在线| 欧美电影在线| 久久精品国产久精国产思思| 99re66热这里只有精品4| 国产亚洲精品aa| 欧美色中文字幕| 免费不卡在线观看av| 羞羞视频在线观看欧美| 日韩视频中午一区| 欧美不卡在线| 久久久www成人免费毛片麻豆| 一本久久综合亚洲鲁鲁五月天| 激情另类综合| 国产日韩精品一区二区| 欧美日韩免费精品| 欧美国产日韩在线| 久久人人97超碰国产公开结果| 亚洲免费视频观看| 一区二区三区国产盗摄| 亚洲国产欧美在线人成| 噜噜噜久久亚洲精品国产品小说| 欧美一区二区观看视频| 亚洲一区国产| 亚洲欧美日韩精品在线|