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

隨筆-341  評論-2670  文章-0  trackbacks-0

手把手教你寫腳本引擎(二)——命令腳本

 

陳梓瀚

華南理工大學軟件本科05

vczh@163.com

http://m.shnenglu.com/vczh/

 

這次要實現的是一個形式最簡單的腳本。這種腳本僅有命令、標號及跳轉構成,看起來就跟匯編一樣,不過好是比較好讀的。雖然這種腳本語言的語法非常簡單,但是最基本的要素還是要有的。

 

作為一個腳本引擎,為了可以在各種各樣的合適的宿主程序中使用,腳本本身最好不要涉及到具體的領域。當然,如果這個腳本被創建的目的僅僅是為了某個領域的話,那就無所謂了。因此,一個腳本引擎需要一個檢查和運行代碼的機制、運行時環境的維護以及一個功能足夠使用的插件系統。一個完整的腳本引擎至少需要如下部件:

 

1、代碼數據結構。代碼的數據結構用來存放經過分析的腳本代碼。事實上解釋型的腳本引擎,也就是邊執行邊分析代碼字符串的腳本引擎是比較難做,而且效率也不高的。腳本代碼經過事先分析,可以檢查一出一些在運行之前就能夠檢查的錯誤。而且我們把腳本的代碼重新處理成一個數據結構之后,執行也變得更加容易控制。

 

2、運行時環境。運行時環境用于存放腳本在運行的過程中產生的數據,譬如堆棧、變量和狀態信息等。對于一個已知的代碼,不同的運行時環境代表不同的腳本執行流程。為了讓腳本可以同時(但不一定是并發)執行,將運行時環境獨立出來也就顯得必要了。

 

3、語法分析器。語法分析器用于將代碼轉換成等價的代碼數據結構,并在發現代碼出錯的時候輸出合適的錯誤信息。

 

4、插件。插件是腳本與外部環境交互的途徑之一。有了插件系統,我們可以為腳本引擎添加額外的、跟腳本引擎無關的功能,譬如文件操作、屏幕輸入輸出等。如果必要的話,插件系統可以將腳本引擎與領域信息互相隔離,系統將變得更加容易使用。

 

5、虛擬機。虛擬機用于執行代碼并返回相應的結果。我們在使用腳本引擎時直接跟虛擬機進行交互,虛擬機則協調上述4個部件的相互協作。

 

在知道了這些之后,我們就可以開始開發一個基于命令的腳本引擎了。為了更加詳細以及明確地講述開發過程以及原理,在這里將構造一門簡單的基于命令的語言。一門語言至少還是要有分支和循環的。但是為了簡化,我們將分支和循環分解成判斷與跳轉。語言可以自由添加標號,標號將作為跳轉的目標而出現。這門語言使用如下語法:

 

<>:值可以是整數、小數、字符串或名字。

<>:名可以是變量名或者標號等,使用字母與下劃線開始,后接不定數量的字母、下劃線與數字。

<>::名字后接冒號代表一個標號。這個標號代表著一個指令的位置,用于指定跳轉目標。

goto <>goto用于直接跳轉到一個位置繼續執行。

set <> <>set用于將一個值賦值給一個指定名字的變量。這個變量不存在則創建。

opcode <> <> <>opcode可以是add、minus、mul、dividivmod。這6個命令將兩個值進行加、減、乘、除、整除及求余,并將結果賦值給一個指定名字的變量。這個變量不存在則創建。

if <>[ opcode <>] goto <>if用于判斷一個條件并在條件滿足被滿足的時候跳轉到指定的地方。條件可以是一個值,這個值必須是整數,并且在這個值不為0的時候條件被滿足。條件也可以是一個比較,這個時候opcode可以是is、is_not、less_thangreater_than、less_equalgreater_equal,分別在第一個值等于、不等于、小于、大于、小于或等于、大于或等于第二個值的時候滿足條件。

exit:結束執行

<> <>*:如果命令名稱不是上面的5種的其中一種的話,那么這個命令將被傳遞給插件進行執行。這個時候,命令可以有任意的參數。

 

在這種語法下,我們可以假設宿主程序給了我們writewritelnread命令用于輸入輸出,并得到一個判斷輸入的數字是否質數的程序:

  write "請輸入一個數字:"

  read Number

  if Number less_then 2 goto FAIL

  if Number is 2 goto SUCCESS

  set Divisor 2

LOOP_BEGIN:

  if Number is Divisor goto SUCCESS

  mod Remainder Number Divisor

  if Remainder is 0 goto FAIL

  add Divisor Divisor 1

  goto LOOP_BEGIN

SUCCESS:

  writeln Number "是質數。"

  exit

FAIL:

  writeln Number "不是質數。"

這個程序首先判斷輸入是不是小于等于2,如果不是的話則使用一種簡單的方法來判斷輸入是不是質數。假設輸入的數字為n,那么在n>2的時候,如果2n-1中的任何一個數字能夠整除n的話,那么n就不是質數了。下圖是這個腳本的運行結果:

 

現在開始實現它。

 

在真正開始讀腳本之前,我們需要一個在內存中表達命令的方法。命令有兩種,一種是跳轉標號,另一種是普通的命令。于是我們可以大概給出一個數據結構。跳轉標號表用于查詢一個名字所指定的命令的位置,而一個命令就由一個名字和一個參數列表構成。參數列表中的參數不僅有內容,還有類型。主要用于區分字符串和名字:

     enum LexerType

     {

         ltString,

         ltName

     };

 

     class LexerToken

     {

     public:

         LexerType Type;

         wstring Token;

};

 

     class Command

     {

     public:

         wstring Name;

         vector<LexerToken> Parameters;

};

至于命令與標號的表示方法則用如下代碼:

     vector<Command> FCommands;

map<wstring , size_t> FLabels;

 

好了,現在讓我們看看一行代碼應該如何分析。由于腳本支持字符串,所以我們不能簡單地使用空格來分割。如果我們遇到了“  writeln Number "是質數。"”,那么我們期望的結果是這一行代碼被拆分成三個部分,分別是writelnNumber"是質數。"。于是我們可以寫一個函數,一次取出一個部分。那么我們只要一直取道換行符或者字符串結束,就能獲得一行的所有部分了。

 

腳本代碼由整數、小數、字符串、名字以及冒號組成。于是我們可以寫很多類似的代碼,然而格式都是int GetXXX(wchar_t*& Input);。這個函數檢查Input是否由XXX開始,返回值代表XXX用掉了多少個字符,然后把Input參數往后推那么多個字符返回給你。舉個例子:

wchar_t* Input=L”123vczh”;

int Chars=GetInt(Input);

這個時候Chars=3,而且Input已經往后推了三個字符,指向了”vczh”。

 

于是經過努力,我們就擁有了一些函數:GetIntGetReal、GetName、GetString、GetColonGetSpaceGetLineBreak。我們如何使用呢?首先,我們在每一次獲得一個部分之前,我們都要調用GetSpace以過濾所有空格。然后就按如下順序調用上面的5個函數:

GetColon

GetString

GetName

GetReal

GetInt

事實上只要GetIntGetReal之下就好了。因為如果123.456GetInt先吃掉了3個字符之后,剩下的就無法解釋了。

 

如果全都失?。ê瘮捣祷?/span>0,代表什么都沒檢查到)了,那么我們可以GetLineBreak。如果再次失敗,那么證明這個輸入的腳本就有問題了。那么報錯吧。在示例代碼的Lexer.h/Lexer.cpp中有一個非常類似的詞法分析器用于將一行代碼分段。

 

讓我們把“  writeln Number "是質數。"”分行吧。

 

首先調用GetSpace,字符串指向了“writeln Number "是質數。”,然后依次調用5個函數一直到GetName成功。GetName返回7,拿到了writeln,字符串指向了“” Number "是質數。”。

然后調用GetSpace,接著仍然到了GetName成功。GetName返回6,字符串指向了“"是質數。”。

接著調用GetSpace,調用到GetString的時候就成功了。GetString返回6(注意我們用的是wchar_t),字符串指向了“”。

后面所有的調用都失敗了。我們意識到字符串已經用完了,于是對這一行代碼的分析就到此為止了。

 

到了這里,我們把所有的行都分割成一堆東西了。于是下面可以在采取一個步驟。我們首先辨別出哪一些是標號,哪一些是命令,然后填入上面的代碼中提到的vector<Command>map<wstring , size_t>中。如果我們遇到了一個標號,那么就將標號名和命令表當前存在的命令的數量加入標號表,其余的都放進命令表。于是我們在goto的時候,就可以從標號表中查到命令在命令表中的位置,從而成功跳轉了。

 

對于上面那段檢查是否質數的代碼,最終的分析結果如下:

標號表:

LOOP_BEGIN: 05

SUCCESS: 10

FAIL:12

命令表:

00  write "請輸入一個數字:"

01  read Number

02  if Number less_then 2 goto FAIL

03  if Number is 2 goto SUCCESS

04  set Divisor 2

05  if Number is Divisor goto SUCCESS

06  mod Remainder Number Divisor

07  if Remainder is 0 goto FAIL

08  add Divisor Divisor 1

09  goto LOOP_BEGIN

10  writeln Number "是質數。"

11  exit

12  writeln Number "不是質數。"

 

命令表里面有13個項,每一個項都被分成了命令名和參數表兩個部分。執行的時候可以通過命令名來做相應的工作。讓我們來手工執行一下這個代碼。

 

執行00,執行01,我們輸入“5”。

02條件失敗,03條件失敗,04設置變量Divisor2。

05條件失敗,06設置Remainder=5%2=1,07條件失敗,08 Divisor變成309跳轉到05LOOP_BEGIN)。

05條件失敗,06設置Remainder=5%3=2,07條件失敗,08 Divisor變成4,09跳轉。

05條件失敗,06設置Remainder=5%4=1,07條件失敗,08 Divisor變成5,09跳轉。

05條件成功,跳轉到10SUCCESS)。

10輸出“是質數。”,11退出程序。

 

于是現在剩下了最后一個問題。write、writelnread原本是不存在于腳本引擎的。但是腳本引擎不具有輸入輸出的方法也是不行的,所以我們需要實現一個插件系統。這個插件系統可以讓我們在腳本引擎的外部添加命令。也就是說,我們構造了一個腳本引擎,然后在外部創建一個插件,包含write、writelnread,然后連接他們。最后做一些手段讓腳本引擎在執行到外部命令的時候將控制權轉移給插件。

 

在這里,我們可以使用責任鏈模式。腳本引擎在遇到一個不認識的命令的時候,就訪問第一個鏈接到腳本引擎的插件。這個時候插件可以返回三種結果:成功、失敗或者棄權。返回成功代表命令被成功執行,腳本引擎繼續往下走。返回失敗代表指令被執行了,但是執行出錯,這個時候腳本引擎返回錯誤信息并停止執行。返回棄權代表這個插件不受理這個命令,腳本引擎將這個命令傳遞給下一個插件。如果所有的插件都棄權的話,那么腳本引擎將返回“無效命令”并停止執行。

 

所以插件只需要有一個函數就行了。這個函數返回執行結果(成功、失敗或棄權),參數為當前的命令以及運行時環境(保存變量的地方)。腳本引擎使用一個vector去記錄所有鏈接的插件的指針,這樣的話腳本引擎在遇到不能解釋的命令的時候就可以依次訪問插件了。下面是插件的示例代碼:

 

     class Plugin

     {

     public:

         virtual PluginStatus Execute(const Command& aCommand , Environment& aEnvironment , wstring& ErrorMessage)=0;

};

vector<Plugin*> FPlugins;

 

命令腳本的東西就講到這里了。接下來的一些文章將講述如何處理高級語言,并且開發一門新的語言出來。這門語言將只支持boolint、double、string、數組和函數。

 

點擊這里下載本片文章的示例代碼。

代碼結構如下:

Lexer.h/Lexer.cpp:詞法分析器

ScriptCommand.h/ScriptCommand.cpp:腳本引擎

Main.cpp:主程序

這個程序(SE_02.exe)讀取一個文本文件(SE_02.txt)并執行,可以在debug文件夾下看到編譯結果。

posted on 2008-07-09 21:43 陳梓瀚(vczh) 閱讀(8918) 評論(10)  編輯 收藏 引用 所屬分類: 腳本技術

評論:
# re: 手把手教你寫腳本引擎(二)——命令腳本 2008-07-09 21:57 | Jetricy
沙發  回復  更多評論
  
# re: 手把手教你寫腳本引擎(二)——命令腳本 2008-07-10 05:08 | 空明流轉
讓我想起來一個曾經經常用到的工具,好像叫按鍵精靈的。那個腳本。。。  回復  更多評論
  
# re: 手把手教你寫腳本引擎(二)——命令腳本 2008-07-10 06:27 | 夢在天涯
en ,很好啊,看了這個就知道腳本的運行原理了,java,.net也很類似哦!



寫的非常的好,希望繼續??!  回復  更多評論
  
# re: 手把手教你寫腳本引擎(二)——命令腳本 2008-07-10 17:59 | flybest
不錯
學習  回復  更多評論
  
# re: 手把手教你寫腳本引擎(二)——命令腳本 2008-07-11 01:40 | 123
入門的吧.  回復  更多評論
  
# re: 手把手教你寫腳本引擎(二)——命令腳本 2008-07-12 06:59 | 陳梓瀚(vczh)
當然  回復  更多評論
  
# re: 手把手教你寫腳本引擎(二)——命令腳本 2008-07-16 18:54 | pdkui
int LexerChars(const wchar_t*& Input , const wchar_t* Chars)
{
const wchar_t* Temp=Input;
while(*Chars)
{
if(*Input++!=*Chars++)return 0;
}
Input=Temp;
return (int)(Chars-Input);
}
//最后的return 邏輯:
//Chars和Input必須在一個長字符串內,才能這樣做減法  回復  更多評論
  
# re: 手把手教你寫腳本引擎(二)——命令腳本 2008-07-17 01:49 | 陳梓瀚(vczh)
哦,那應該是bug。  回復  更多評論
  
# re: 手把手教你寫腳本引擎(二)——命令腳本[未登錄] 2008-10-22 19:11 | Kevin Lynx
雖然以前知道你發的這些文章,但是很少看過,理由很簡單,我覺得要用一些閑暇時間去看你的文章,是不夠的。

今天終于看完了你這個系列的第二篇,并且看了代碼。大致上算理解了你這篇文章講的東西。感覺就是,設計和代碼都很老練。
  回復  更多評論
  
# re: 手把手教你寫腳本引擎(二)——命令腳本 2010-01-11 20:36 | kuafoo
跟著牛人學習寫腳本了  回復  更多評論
  
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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>
            亚洲天堂第二页| 亚洲精品久久嫩草网站秘色| 欧美一区二区三区在线免费观看| 91久久精品www人人做人人爽| 新片速递亚洲合集欧美合集| 亚洲一区三区电影在线观看| 亚洲欧美综合v| 久久久欧美一区二区| 久久午夜精品一区二区| 免费成人黄色av| 亚洲日本理论电影| 日韩视频一区二区在线观看 | 欧美性大战久久久久| 欧美另类videos死尸| 欧美网站在线观看| 国产毛片一区二区| 在线播放日韩| 一本色道久久综合亚洲精品高清| 夜夜夜久久久| 欧美一区二区三区久久精品| 快播亚洲色图| 999在线观看精品免费不卡网站| 99re成人精品视频| 欧美在线看片| 欧美理论电影网| 国产亚洲欧美另类一区二区三区| 亚洲国产精彩中文乱码av在线播放| 夜夜嗨av色一区二区不卡| 久久精精品视频| 亚洲日本中文字幕免费在线不卡| 亚洲欧美精品在线| 欧美精品粉嫩高潮一区二区| 国模吧视频一区| 在线一区日本视频| 久久在线精品| 亚洲视频在线观看免费| 久久亚洲精品一区| 国产精品一区久久久| 日韩视频在线观看国产| 蜜桃精品一区二区三区 | 午夜欧美大尺度福利影院在线看| 欧美成人免费va影院高清| 亚洲国产天堂网精品网站| 亚洲午夜电影在线观看| 欧美成人精品福利| 在线观看日韩av先锋影音电影院| 新狼窝色av性久久久久久| 亚洲精品一品区二品区三品区| 久久精品免费电影| 国产亚洲欧洲997久久综合| 亚洲免费网址| 99re66热这里只有精品3直播| 欧美成人午夜激情| 亚洲国产天堂久久综合| 久久综合国产精品台湾中文娱乐网 | 亚洲日本中文字幕免费在线不卡| 久久成年人视频| 亚洲婷婷在线| 国产精品久久看| 亚洲欧美日韩国产综合精品二区 | 久久婷婷麻豆| 鲁鲁狠狠狠7777一区二区| 久久久久国内| 亚洲无亚洲人成网站77777| 另类图片综合电影| 亚洲国产精品999| 麻豆国产va免费精品高清在线| 亚洲一区尤物| 国产精品一区久久| 久久精品国产亚洲一区二区| 亚洲欧美资源在线| 国产亚洲欧美一区二区三区| 香港成人在线视频| 亚洲影音先锋| 国产亚洲精品久久久久动| 久久久久国产一区二区| 久久精品二区三区| 在线免费日韩片| 亚洲电影激情视频网站| 欧美成年视频| 亚洲亚洲精品在线观看 | 国外成人在线| 欧美国产国产综合| 欧美精品久久天天躁| 亚洲一区www| 午夜亚洲福利| 最新日韩欧美| 一区二区三区四区国产| 国产私拍一区| 欧美.日韩.国产.一区.二区| 欧美激情第10页| 亚洲欧美成人在线| 久久久www成人免费精品| 最新国产拍偷乱拍精品 | 欧美激情影音先锋| 亚洲欧美精品suv| 欧美在线观看一二区| 亚洲精品一区二区在线观看| 亚洲天堂成人在线观看| 在线播放豆国产99亚洲| 日韩亚洲精品电影| 国内揄拍国内精品少妇国语| 亚洲日本va午夜在线电影| 国产精品裸体一区二区三区| 亚洲第一免费播放区| 国产精品久久久久久久9999| 久久天天躁狠狠躁夜夜爽蜜月| 女人香蕉久久**毛片精品| 亚洲欧美日韩国产中文| 欧美大片国产精品| 欧美专区一区二区三区| 欧美精品一区在线发布| 久久午夜羞羞影院免费观看| 欧美日韩亚洲一区二区| 欧美成人免费观看| 国产亚洲欧美在线| 亚洲午夜影视影院在线观看| 亚洲精品乱码久久久久久| 久久精品国产一区二区三区| 亚洲自拍偷拍视频| 亚洲国产91色在线| 亚洲欧美日韩一区二区| 噜噜噜久久亚洲精品国产品小说| 亚洲欧美大片| 欧美伦理在线观看| 欧美第一黄网免费网站| 国产欧美一区二区精品秋霞影院| 最新成人在线| 亚洲丰满在线| 久久全球大尺度高清视频| 久久成年人视频| 国产三区二区一区久久| 亚洲一级电影| 亚洲欧美日韩成人| 国产精品久久久久秋霞鲁丝| 一区二区三区成人精品| 一区二区三区成人| 欧美黄污视频| 亚洲精品乱码久久久久久按摩观| 精品999久久久| 欧美一级艳片视频免费观看| 亚洲图片欧洲图片av| 欧美精品1区2区| 久久亚洲一区| 伊大人香蕉综合8在线视| 欧美中在线观看| 乱中年女人伦av一区二区| 狠狠色丁香久久婷婷综合丁香| 欧美与欧洲交xxxx免费观看| 欧美一区中文字幕| 国产亚洲制服色| 久久av资源网站| 免费久久99精品国产自在现线| 亚洲第一黄网| 欧美激情中文不卡| 亚洲精品久久久久久下一站 | 日韩视频欧美视频| 欧美精品一线| 最新成人av在线| 在线亚洲精品| 欧美亚州一区二区三区 | 久久精品色图| 欧美国产日韩一区二区在线观看| 好看的av在线不卡观看| 欧美阿v一级看视频| 亚洲伦理在线观看| 亚洲欧美日韩在线观看a三区| 国产麻豆视频精品| 免费观看成人www动漫视频| 日韩午夜三级在线| 久久久国产午夜精品| 亚洲国产美女| 国产精品一区二区黑丝| 久久亚洲高清| 中文亚洲免费| 蜜桃av一区二区在线观看| 9i看片成人免费高清| 国产欧美日韩高清| 欧美成人福利视频| 午夜免费日韩视频| 亚洲三级影院| 久久深夜福利免费观看| 99在线热播精品免费99热| 国产亚洲午夜| 欧美日韩成人综合| 欧美黄色成人网| 一区二区三区四区五区视频| 亚洲欧美另类国产| 亚洲成色最大综合在线| 欧美日韩中文字幕综合视频| 午夜亚洲精品| 亚洲午夜久久久久久久久电影院| 欧美/亚洲一区| 久久成人免费网| 在线亚洲免费视频| 亚洲精品美女久久久久| 国产伊人精品| 国产精品久久久久久久久果冻传媒| 久久综合久久88| 欧美中文字幕精品|