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

loop_in_codes

低調(diào)做技術(shù)__歡迎移步我的獨(dú)立博客 codemaro.com 微博 kevinlynx

#

強(qiáng)大的bcb

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

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

界面功能它就是沒有。在接口實(shí)現(xiàn)上,MFC基本上就只是封裝了WIN API而已。想想世界上還有什么強(qiáng)大的GUI庫,

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

 

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

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

 

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

這個錯誤發(fā)生于:

void func()
{

    struct Info

    {

        

    };

   std::queue<Info> abc;
}

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

的類型(誰來告訴我這是C++標(biāo)準(zhǔn))。

 

2.

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

這個錯誤發(fā)生于:

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

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

很久沒看C++書,所以,誰又來告訴我C++標(biāo)準(zhǔn)里,這里到底能不能用引用?

 

3.

E2515 Cannot explicitly specialize a member of a generic template class

這個錯誤發(fā)生的情景更復(fù)雜些:

template <typename _Tp>

class Test
{

   template <typename _U>

   class Other;

   template <>

   class Other<void>

   {

   };
};

意思是說,我不能在一個模板類里特化成員模板類。誰又來告訴我標(biāo)準(zhǔn)規(guī)定的是什么?

 

4.

void func( Obj &a )
{
}

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

 

5.

我曾經(jīng)留下了關(guān)于宏遞歸的一些代碼,被用在我寫的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 ) ) 當(dāng)這樣使用宏時,基于我在GNU C上看到的關(guān)于宏的規(guī)則,會先展開

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

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

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

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

 

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

于可以rapid開發(fā)界面了。創(chuàng)建了個VCL FORM APPLICATION,噩夢又開始了:

 

1.

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

F1004 Internal compiler error at 0x59b4ea8 with base 0x5980000

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

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

 

2.

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

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

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

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

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

 

3.

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

居然又恢復(fù)成default(難道是因?yàn)楸I版)。它的智能提示似乎總是跟著鼠標(biāo)指針,有時候指向某個符號,鼠標(biāo)

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

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

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

 

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

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

F1004 Internal compiler error at 0x59b4ea8 with base 0x5980000

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

指針和模塊健壯

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

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

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

也許,這樣做后程序是不會掛掉了。但是,就我們的觀點(diǎn)來看,這樣反而會隱藏一些BUG。
為什么我們不能詳盡地去管理一個指針?一個指針變?yōu)榭樟耍偸且驗(yàn)樵谶@之前發(fā)生了錯誤
。當(dāng)然,野指針本身就是愚蠢代碼產(chǎn)生的東西,這里沒必要討論。空指針之所以為空,也是
因?yàn)樵诤芏鄷r候我們把空作為失敗/錯誤/無效的標(biāo)志。

恰好上周我的一些代碼就真的在空指針上出現(xiàn)了問題。外網(wǎng)的服務(wù)器隨時會因?yàn)橥婕业囊恍?br>臨界操作行為而崩潰掉。雖然我通過修改腳本來屏蔽這個問題(因?yàn)椴荒苷f停機(jī)維護(hù)就停機(jī)
維護(hù)),但是總感覺程序是不安全的。人不吃點(diǎn)教訓(xùn)絕對不學(xué)乖。

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

但是,我?guī)缀鯊膩頉]有界定,指針在哪些情況下需要去判斷為空?函數(shù)的參數(shù)絕對不需要。
假如函數(shù)的參數(shù)就是個空指針,那是client程序員的責(zé)任。僅供模塊內(nèi)使用的指針(包含其
他資源)在內(nèi)部使用時也不需要去判斷。如果去判斷了,那說明你對你自己寫的模塊都缺乏
精確的把握,證明你的設(shè)計(jì)思維不夠清晰。

什么時候需要判斷?當(dāng)指針依賴于外部環(huán)境時,例如讀配置文件、載入資源,因?yàn)橥獠恳蛩?br>不確定不在自己控制范圍內(nèi),那么進(jìn)行判斷。同樣,當(dāng)使用了其他模塊返回的指針值時,也
需要判斷。這個其實(shí)和“外部環(huán)境”屬于同一種情況。因?yàn)槲覀儗ζ渌K也不清楚,更為
隱蔽的是(隨著其他模塊的改變,將來會在你的模塊里爆發(fā)崩潰錯誤),其他模塊由別人維
護(hù),其變化更不受自己控制。之前我對這一點(diǎn)界定不是很清楚,這也是我犯錯的原因。

現(xiàn)在想想,像游戲服務(wù)器這種程序,里面塞著各種各樣的游戲功能。無論是哪一個模塊出現(xiàn)
個空指針訪問出錯的問題,都會直接讓服務(wù)器崩掉。關(guān)鍵是這個結(jié)果經(jīng)常伴隨著玩家的損失
。所以理想狀態(tài)下,把每一個模塊都放置在單獨(dú)的進(jìn)程里,確實(shí)是很有好處的。

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

GDI+中從內(nèi)存讀取圖片/保存圖片到內(nèi)存

要給項(xiàng)目中增加一個新的模塊,需要先在服務(wù)器端做一些圖片處理相關(guān)的工作。本來,對圖片

做一些諸如ALPHA混合旋轉(zhuǎn)縮放的操作,在游戲客戶端應(yīng)該是很容易的事。但是這事要在服務(wù)

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

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

 

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

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

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

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

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

感覺到是不是要自己實(shí)現(xiàn)個Stream。后來在google上找到了一個似乎是標(biāo)準(zhǔn)的方法:首先創(chuàng)建個

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

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

 

惡心的是,基于之前對服務(wù)器內(nèi)存使用的優(yōu)化,我現(xiàn)在對于內(nèi)存的使用非常敏感(誰說現(xiàn)在內(nèi)存

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

模糊。CreateStreamOnHGlobal函數(shù)的第二個參數(shù)指定當(dāng)IStream->Release的時候,是否會自動

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

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

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

會對這個IStream進(jìn)行一次AddRef。我發(fā)覺MSDN對于Gdiplus::Image::FromStream函數(shù)的說明

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

資源管理,一定得搞個水落石出。結(jié)果是,F(xiàn)romStream的實(shí)現(xiàn)就是簡單地new了個Image。而

Image內(nèi)部肯定會對IStream進(jìn)行AddRef,并且,如果在Image銷毀前銷毀這個HGLOBAL,這個

Image基本也就廢了。

 

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

HGLOBAL和Image糾結(jié)在一起,簡單考慮,將CreateStreamOnHGlobal第二個參數(shù)設(shè)為TRUE。

 

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

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

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

再根據(jù)該IStream::Seek獲取其大小,自己再分配段內(nèi)存,最后IStream::Read讀取進(jìn)來。同樣,

需要注意相關(guān)內(nèi)存資源的管理。

 

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

下載代碼。

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

DNF游戲聲音資源提取

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

    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)
    ...

    文件列表之后,就是具體的每個文件的內(nèi)容。開始我還在擔(dān)心npk會為每一個聲音文件
加密。或者只保存聲音文件的具體數(shù)據(jù),而聲音文件文件頭則只保存一份(因?yàn)樗形募?br>文件頭很有可能全部是一樣的)。后來稍微搜索了下WAV的格式,只需要比對下npk中某一個
文件內(nèi)容的頭部是否和WAV格式的頭部相同,就可以基本斷定其是否加密。
    結(jié)果是,npk對包內(nèi)的每一個WAV文件沒做加密。
    然后立即寫了個程序,根據(jù)文件列表中的偏移值和大小值,將每一個WAV單獨(dú)取出來,就
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

    我想圖片資源也應(yīng)該差不多,不過圖片資源肯定要復(fù)雜些。下午公司網(wǎng)絡(luò)好了,網(wǎng)上搜
索了下,發(fā)現(xiàn)居然已經(jīng)有了DNF資源提取工具了,唉。

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

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

kl中的錯誤處理

kl中的錯誤處理

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

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

    整體上,kl簡單地通過回調(diào)函數(shù)指針來把錯誤信息傳給庫的應(yīng)用層。而因?yàn)槲蚁M麄€
kl實(shí)現(xiàn)的幾層(詞法分析、語法分析、符號表、解釋器等)可以盡可能地獨(dú)立。例如雖然語
法分析依賴于詞法分析(依賴于詞法分析提供的接口),但是因?yàn)樵~法分析并不對語法分析
依賴,所以完全可以把詞法分析模塊拿出來單獨(dú)使用。所以,在日志方面,我?guī)缀鯙槊恳粚?br>都附加了個error_log函數(shù)指針。
    而用戶層在通過kllib層使用整個庫時,傳入的回調(diào)函數(shù)會被間接地傳到詞法分析層。
實(shí)際上,當(dāng)kl作為一個庫時,kllib正是用于橋接庫本身和用戶層的bridge。

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

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

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

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

    錯誤信息中比較重要的還有行號信息。之前kl這方面一直存在BUG,我在寫貪食蛇例子
的時候每次新加代碼都不敢加太多。因?yàn)榻忉屍鲌?bào)告的錯誤行號總是錯誤的,我只能靠有沒
有錯誤來找錯誤,而不能通過錯誤信息找錯誤。
    行號信息被保存在詞法分析狀態(tài)中(lexState:lineno),語法分析中獲取token時,會取
出當(dāng)前的行號,保存到語法樹樹節(jié)點(diǎn)中。因?yàn)榘ń忉屇K都是基于樹節(jié)點(diǎn)的,所以詞法分
析語法分析解釋器三層都可以準(zhǔn)確報(bào)告行號。

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

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

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

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

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

kl sample:貪食蛇

 

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


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

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

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

    雖然我寫了kl這個腳本語言,但是語言特性并不是我設(shè)計(jì)的。我只是取了C語言的一些
特性。所以在寫這個sample的時候,我對于kl這個腳本語言的感覺,就是一個像basic的C。
因?yàn)樗珕我唬拖馚ASIC一樣只擁有語言的一些基本功能,不能定義復(fù)雜的結(jié)構(gòu),沒有天
生的對各種數(shù)據(jù)結(jié)構(gòu)的支持(例如某些語言直接有l(wèi)ist, tuple之類)。

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

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

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

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

snake_screenshot

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

    如果要獲取kl實(shí)現(xiàn)代碼,建議從我在google的SVN獲取:
http://code.google.com/p/klcommon/

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

實(shí)現(xiàn)一種解釋性腳本語言(七)

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

腳本與C語言交互

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

靜態(tài)插件

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

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

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

動態(tài)插件

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

    因?yàn)閯討B(tài)插件在設(shè)計(jì)之初沒有被考慮,所以我并沒有為kl加入一些原生的關(guān)鍵字用于導(dǎo)
入動態(tài)插件,例如import、require之類。我在靜態(tài)插件層次提供了這個功能。即我提供了
一個libloader靜態(tài)插件,鏈接進(jìn)kl解釋器程序。該靜態(tài)插件提供腳本一個名為import的函
數(shù)。該函數(shù)負(fù)責(zé)動態(tài)載入dll之類的動態(tài)庫,并調(diào)用里面的lib_open函數(shù)完成動態(tài)插件的注
冊。

C程序里調(diào)用腳本函數(shù)

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

代碼導(dǎo)讀

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

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

實(shí)現(xiàn)一種解釋性腳本語言(六)

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

解釋器

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

工作原理

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

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

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

    其他所有節(jié)點(diǎn)的解釋方式都是相同的。


解釋器環(huán)境

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

如何解釋執(zhí)行函數(shù)

    函數(shù)主要有兩大類型:腳本內(nèi)定義的函數(shù)以及插件注冊進(jìn)符號表的函數(shù)。無論是哪種函
數(shù),都會在符號表中建立對應(yīng)的符號。對于前者,符號被保存于全局符號表,其保存的內(nèi)容
是該函數(shù)節(jié)點(diǎn)的節(jié)點(diǎn)指針;而對于后者,則保存的插件函數(shù)的函數(shù)地址值。

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

代碼導(dǎo)讀

    解釋器的代碼位于klinterpret.h/klinterpret.c中。整體上而言沒什么特別的地方,
主要是利用語法樹的特點(diǎn)。
    完成了這一節(jié)后,kl就已經(jīng)可以解釋執(zhí)行所有的腳本語句。當(dāng)然,因?yàn)闆]有輸出功能,
只能在調(diào)試器里看看計(jì)算結(jié)果。下一節(jié)里會講到將腳本結(jié)合進(jìn)C語言,從而可以讓C語言注冊
所謂的插件函數(shù)到腳本里,也就可以讓腳本具有print這樣的輸出函數(shù)。

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

實(shí)現(xiàn)一種解釋性腳本語言(五)

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

符號表

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

什么是符號表?

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

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

符號表的實(shí)現(xiàn)

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

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

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

代碼導(dǎo)讀

    kl中的符號表實(shí)現(xiàn)代碼在klsymtab.h/klsymtab.c中,實(shí)現(xiàn)比較簡單,無需多言。

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

實(shí)現(xiàn)一種解釋性腳本語言(四)

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

語法分析

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

語法樹

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

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

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

    value op_exp( TreeNode *node )
    {
        switch( node->op )
        {
            case '+':
            {
                /* + 表達(dá)式必然有左右操作數(shù) */
                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;
        }
    }

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

如何整理出語法樹?

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

    那么有了BNF之后,該如何整理出語法樹呢?
    通常,我們的代碼里都會直接有對應(yīng)exp、term、addop之類的函數(shù)。按照我這句話的意
思,上面抄的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,用于處理重復(fù)和選擇的一些情況---不用管這句話)

    每一個函數(shù)基本上都會返回一個樹節(jié)點(diǎn),當(dāng)然,該節(jié)點(diǎn)下可能會有很多子節(jié)點(diǎn)。   

總結(jié)

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

代碼導(dǎo)讀

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

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

僅列出標(biāo)題
共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综合在线| 欧美日韩一区二区三区视频| 亚洲伦理在线观看| 亚洲欧洲精品成人久久奇米网| 美女精品在线| 99视频有精品| 亚洲午夜av| 国产一区深夜福利| 亚洲欧美国产高清va在线播| 在线亚洲欧美视频| 国产欧美日韩综合一区在线播放| 久久国产免费看| 久久综合影视| 亚洲一区二区欧美日韩| 午夜精品久久一牛影视| 性高湖久久久久久久久| 韩国精品在线观看| 亚洲国产精品久久91精品| 欧美日韩视频在线一区二区观看视频| 亚洲欧美国产高清va在线播| 午夜精品久久久久久久久久久| 黄色亚洲精品| 一区二区国产在线观看| 国内精品写真在线观看| 亚洲第一在线视频| 国产精品美女999| 国产精品乱子久久久久| 国产一区二区中文| 亚洲精品国产欧美| 国产九九精品视频| 亚洲高清视频在线观看| 国产精品视频免费一区| 欧美激情一区二区三区| 国产精品推荐精品| 亚洲激情电影中文字幕| 国产无一区二区| 国产精品久久午夜夜伦鲁鲁| 欧美1区3d| 国产日韩欧美二区| 日韩亚洲视频| 久久噜噜噜精品国产亚洲综合| 一区二区三区久久| 免费成人av在线看| 久久亚洲国产精品日日av夜夜| 欧美日韩在线第一页| 欧美激情一区二区三区四区| 国产欧美不卡| 亚洲视频导航| 亚洲无吗在线| 欧美激情精品久久久久久大尺度 | 亚洲午夜精品17c| 亚洲高清视频在线观看| 欧美在线亚洲| 久久av最新网址| 国产精品欧美日韩久久| 亚洲精品在线免费| 日韩视频一区| 欧美国产第二页| 欧美激情一区二区三区不卡| 黄色成人在线免费| 欧美中文字幕不卡| 久久久久欧美精品| 亚洲一区在线免费观看| 午夜亚洲福利在线老司机| 欧美日韩另类在线| 妖精视频成人观看www| 一本到高清视频免费精品| 欧美国产日韩二区| 亚洲国产一区二区三区a毛片| 美女在线一区二区| 亚洲高清资源综合久久精品| 91久久久久久久久| 欧美极品在线播放| 99在线观看免费视频精品观看| 亚洲精品一区二区三区99| 亚洲七七久久综合桃花剧情介绍| 久热精品视频在线| 欧美电影在线免费观看网站 | 欧美激情精品| 日韩午夜在线播放| 亚洲中午字幕| 国产色综合久久| 久久精品视频导航| 亚洲电影网站| 亚洲午夜日本在线观看| 国产精品一区二区久久久久| 欧美亚洲一区| 亚洲大黄网站| 一区二区三区高清在线观看| 国产一区二区三区的电影 | 国产精品久久久久久久久久ktv| 亚洲精品孕妇| 久久国产精品亚洲va麻豆| 亚洲成人在线| 欧美日韩免费观看一区=区三区| 亚洲天堂免费观看| 久久亚洲精品网站| 99精品免费| 国产性猛交xxxx免费看久久| 久久婷婷丁香| 亚洲校园激情| 欧美激情欧美激情在线五月| 亚洲一区二区不卡免费| 韩日午夜在线资源一区二区| 欧美高清视频一二三区| 午夜一区二区三区不卡视频| 欧美国产日本在线| 亚欧成人在线| 日韩视频在线一区二区三区| 国产精品日日摸夜夜摸av| 久久在线91| 亚洲欧美日韩精品久久亚洲区 | 久久亚洲美女| 亚洲视频导航| 91久久精品www人人做人人爽| 国产精品福利片| 免费观看亚洲视频大全| 亚洲欧美日韩国产成人精品影院| 亚洲高清视频一区| 乱码第一页成人| 欧美亚洲一级| 亚洲一区精品在线| 亚洲精品永久免费精品| 欧美主播一区二区三区美女 久久精品人| 欧美激情精品久久久久久久变态| 午夜精品久久久久久久久| 亚洲区国产区| 黄网站免费久久| 亚洲免费视频一区二区| 亚洲日本中文| 欧美黄色小视频| 久久裸体视频| 久久久久久久成人| 欧美亚洲一区三区| 亚洲伊人观看| 99国产欧美久久久精品| 亚洲精华国产欧美| 在线精品高清中文字幕| 狠狠色狠狠色综合日日小说| 艳妇臀荡乳欲伦亚洲一区| 亚洲日本乱码在线观看| 亚洲高清二区| 亚洲国产精品www| 亚洲国产合集| 亚洲国产小视频在线观看| 亚洲国产精品成人精品| 亚洲第一网站免费视频| 亚洲激情六月丁香| 亚洲精品国产精品久久清纯直播| 亚洲激情午夜| 亚洲精品资源| 亚洲图片激情小说| 一区二区三区偷拍| 亚洲性视频网址| 欧美影院一区| 久久久久久久国产| 美女91精品| 亚洲国产精品成人综合色在线婷婷| 欧美高清在线一区| 亚洲国产精品毛片| 亚洲精品影视| 性久久久久久| 欧美sm视频| 欧美午夜激情在线| 国产日韩亚洲欧美精品| 在线观看国产精品网站| 亚洲激情在线激情| 亚洲一区区二区| 国产精品乱码一区二三区小蝌蚪| 国产欧美精品一区aⅴ影院| 激情自拍一区| 99天天综合性| 久久国产加勒比精品无码| 你懂的一区二区| 99精品欧美一区| 久久大香伊蕉在人线观看热2| 免费看精品久久片| 国产精品v欧美精品v日韩| 韩国三级在线一区| 一区二区高清在线观看| 性久久久久久久久| 91久久久在线| 欧美怡红院视频| 欧美日韩色综合| 在线电影一区| 亚洲欧洲av一区二区| 欧美不卡视频一区发布| 中文日韩电影网站| 欧美sm极限捆绑bd| 国产亚洲电影| 亚洲视屏在线播放| 欧美不卡在线视频| 亚洲欧美影音先锋| 欧美激情亚洲一区| 一区二区三区在线观看国产| 亚洲图片欧洲图片日韩av| 欧美电影免费观看| 久久99伊人|