2011年12月28日 星期三

[python]dict sort by key or value

熊熊忘記該怎麼 sort by value,查了一下網路記下來。

for key in sorted(mydict.iterkeys()):
print "%s: %s" % (key, mydict[key])

 

for key, value in sorted(mydict.iteritems(), key=lambda (k,v): (v,k)):
print "%s: %s" % (key, value)

 



# (IMHO) the simplest approach:
def sortedDictValues1(adict):
items = adict.items()
items.sort()
return [value for key, value in items]

# an alternative implementation, which
# happens to run a bit faster for large
# dictionaries on my machine:
def sortedDictValues2(adict):
keys = adict.keys()
keys.sort()
return [dict[key] for key in keys]

# a further slight speed-up on my box
# is to map a bound-method:
def sortedDictValues3(adict):
keys = adict.keys()
keys.sort()
return map(adict.get, keys)

 



d= [ {'id':0, 'value':9}, {'id':1, 'value':100}, {'id':5, 'value':99}, {'id':3, 'value':19}, {'id':2, 'value':59} ]
d.sort(key=lambda x:x['id']) #針對key 'id' 做sort
d.sort(key=lambda x:x['value']) #針對key 'value' 做sort

#以下用cmp,可以達到上面指定key的效果
d.sort(cmp=lambda x,y: cmp(x['id'], y['id']))
d.sort(cmp=lambda x,y: cmp(x['value'], y['value']))

 

class data:
def __init__(self, _id, _value):
self.id = _id
self.value = _value
c = [ data(0,9), data(1,100), data(5,99), data(3,19), data(2,59) ]
c.sort(key=lambda x:x.id)#針對class member id 做sort
c.sort(key=lambda x:x.value)#針對class member value 做sort

#以下用cmp,可以達到上面指定key的效果
c.sort(cmp=lambda x,y: cmp(x.id, y.id) )
c.sort(cmp=lambda x,y: cmp(x.value, y.value))

2011年12月23日 星期五

[python]datetime

從 javascript 的 datetime 的 timestamp 了解到,
python 無法直接獲得 timestamp 的值。
一般來說這個 timestamp 指的是Unix epoch (or Unix time or POSIX time or Unix timestamp)
根據這裡提到,也許是 datetime 的表示法有很多種,且精準度高於 timestamp,
所以就沒有直接提供方法。

但是也不是沒辦法。

在得到一個 datetime object 後

import datetime
x = datetime.datetime.now()

# datetime.datetime(2011, 12, 23, 17, 17, 58, 625000)

第一種是藉 calendar 模組

>>> import calendar
>>> calendar.timegm(x.timetuple())
1324660678

這是得到標準時區的 timestamp,所以要轉回原來時區(local)的時間,不能用 fromtimestamp,
而是要用 utcfromtimestamp

>>> y = calendar.timegm(x.timetuple())
>>> z = datetime.datetime.fromtimestamp(y)
>>> z
datetime.datetime(2011, 12, 24, 1, 17, 58)
>>> y = calendar.timegm(x.timetuple())
>>> z = datetime.datetime.utcfromtimestamp(y)
>>> z
datetime.datetime(2011, 12, 23, 17, 17, 58)

另一個方法是用 time 模組,產生出來的 timestamp 就長不一樣。
這個用 fromtimestamp 就可以得到local datetime。

>>> import time
>>> y = time.mktime(x.timetuple())
>>> y
1324631878.0
>>> z = datetime.datetime.fromtimestamp(y)
>>> z
datetime.datetime(2011, 12, 23, 17, 17, 58)

這裡有個貼心的網站,幫你整理各語言如何得到 timestamp。
http://www.epochconverter.com/

在寫程式時一定要先測試一下數值是否如你所想喔

[javascript]時間的操作

時間物件的實體化有四種

var dt = new Date();
var dt = new Date(milliseconds);
var dt = new Date(dateString);
var dt = new Date(year, month, day, hours, minutes, seconds, milliseconds);

如果是要現在時間,就用第一個即可。

第二種方法的 milliseconds 是從 1970/01/01 開始算。
這個數字通常不會是自己算,而是從別的地方傳來。
他的好處是,用一個 float 就可以表示一個時間,比字串更省空間。
同時排序、計算(時間加減)也更快速。
在 .Net 裡,所謂的 timestamp 就是這個數值;
python 裡,據說有些轉換上的問題, 也就沒有提供 datetime 直接產生 timestamp 的方法。
這裡先不提。
javascript 則是有 getTime() 方法可以產生這個數字。
但是要注意時區問題,不然會有時間差喔。

第三種方法的 dateString 就要看運氣了,格式還是要對 javascript 的胃口。
如果沒把握,以下格式是可以的:
"October 13, 1975 11:13:00"
"12-23 13:44:32, 2011"
"Dec 23 14:00:12 2011"
而我最愛的格式不行:
"2011-12-23 13:44:32"

第四種方法就穩定,每一項分開就不會有問題。

全部要注意的是千禧年問題,也就是以前常把四位數年份表示縮成兩位數,
請記得不要再這樣做了,這不是一個好習慣。

2011年11月23日 星期三

[dotnet]webbrowser 做為 UI log 的眉眉角角

一開始的動作

'WebBrowser1 一開始,可以設定 DocumentText 來顯示第一個畫面。
若是想用,WebBrowser1.Document.Write,
因為一開始,WebBrowser1.Document 是 nothing,所以此路不通。
網路上也滿多人提出,先 WebBrowser1.Navigate("about:blank"),
然後用迴圈加上 Application.DoEvents() 等待 'WebBrowser1.Document 長好,
再用 WebBrowser1.Document.Write。

最後我的決定是,準備好一個檔案直讀進來就好。
因為程式是 multithread,保險起見,還是用 while 等 Document 長好。
但我不確定是否真有用,因為一定要用 Application.DoEvents() 讓 WebBrowser 去做事,
當然這時有其他事情發生也沒辦法阻止。

    Sub initUILogComponet()
        WebBrowser1.Navigate("UI.html")
        WebBrowser1.IsWebBrowserContextMenuEnabled = False
        While (True)
            Application.DoEvents()
            If Not WebBrowser1.Document.Body Is Nothing Then
                Exit While
            End If
        End While
    End Sub

更新畫面

要更新 UI 畫面,基本要做的就是檢查 thread 是否回到 form。
這裡用 Document 物件新增新的 tr。
等待之後,取出 Document 的 html 元素的 OuterHtml 再寫進檔案。
接著Navigate。
清除舊資料的方式就是把最舊的那一個元素的 OuterHtml 取出,
把它從 html 元素的 OuterHtml 裡消掉,
再寫進檔案後Navigate。

    Sub UpdateUILog(ByVal inType As EQS_ScreenType, ByVal intxt As String)
        If Me.InvokeRequired Then
            Me.Invoke(New LogDele(AddressOf UpdateUILog), New Object() {inType, intxt})
        Else
            Try
                Dim tr As HtmlElement
                Dim td As HtmlElement
                tr = WebBrowser1.Document.CreateElement("tr")
                td = WebBrowser1.Document.CreateElement("td")
                td.InnerText = System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")
                tr.AppendChild(td)
                td = WebBrowser1.Document.CreateElement("td")
                td.InnerText = intxt
                tr.AppendChild(td)
                Select Case inType
                    Case ERROR
                        tr.SetAttribute("class", "error")
                    Case M_INFO
                        tr.SetAttribute("class", "info")
                    Case M_MES
                End Select
                WebBrowser1.Document.Body.FirstChild.FirstChild.FirstChild.InsertAdjacentElement(HtmlElementInsertionOrientation.AfterEnd, tr)
                While (True)
                    Application.DoEvents()
                    If Not WebBrowser1.Document.Body Is Nothing Then
                        Exit While
                    End If
                End While
                Dim n_html = "<!doctype html>" & WebBrowser1.Document.GetElementsByTagName("html").Item(0).OuterHtml
                Dim n_sw As System.IO.StreamWriter = System.IO.File.CreateText("UI.html")
                n_sw.Write(n_html)
                n_sw.Close()
                WebBrowser1.Navigate("UI.html")
                Dim v As Integer
                v = 5
                If WebBrowser1.Document.Body.FirstChild.FirstChild.Children.Count > v Then
                    Dim t As String = WebBrowser1.Document.Body.FirstChild.FirstChild.Children(v).OuterHtml
                    Dim new_html As String = WebBrowser1.Document.GetElementsByTagName("html").Item(0).OuterHtml
                    t = t.Replace(vbNewLine, "")
                    new_html = new_html.Replace(vbNewLine, "")
                    new_html = new_html.Replace(t, "")
                    new_html = "<!doctype html>" & new_html

                    Dim sw As System.IO.StreamWriter = System.IO.File.CreateText("UI.html")
                    sw.Write(new_html)
                    sw.Close()
                    WebBrowser1.Navigate("UI.html")
                End If
            Catch ex As Exception

            End Try
        End If
    End Sub

 

保證一定得到更新後內容的方式

在 DocumentCompleted 事件去拿一定可以。
要注意的是,當初設定 DocumentText 的話,更新的內容會在 DocumentText。
而使用 Document 物件操作的,更新的內容要從 Document 物件拿。

    Private Sub WebBrowser1_DocumentCompleted(ByVal sender As Object, ByVal e As System.Windows.Forms.WebBrowserDocumentCompletedEventArgs) Handles WebBrowser1.DocumentCompleted
        If firstContextSetComplete = False Then
            firstContextSetComplete = True
            'UpdateUILog(FACETEA.EQS_ScreenType.M_INFO, "StartUp")
        End If
        Dim control As Control = Me.ActiveControl
        WebBrowser1.Focus()
        control.Focus()
    End Sub

用jQuery清除舊資料

因為 WebBrowser 控制項沒辦法支援 addEventHandler,所以我的腦筋動到 setInterval。
這個方法可行,但是無法存檔。
而且考量到使用 class 控制外觀非得存檔再Navigate,在兩個因素考量下,
所以清舊資料的事只能交給主程式本身來做。

[dotnet]WebBrowser控制項對於 tr 的 border 顯示

在WebBrowser控制項裡,

tr { border-bottom: 1px solid #000000;}

不會顯示線。

在 ie9 的話,上面那行再加

table { border-collapse:collapse;}

就會顯示線。

有人提到

tr td{ border-bottom: 1px solid #000000; }

可以解決問題。但是兩個 td 之間可能會有斷線。

[dotnet]WebBrowser控制項不可用script偵測新增節點

經過研究及試驗,在 ie9 以下這行可以成功

document.addEventListener("DOMNodeInserted",function(){alert('change')});

但是在WebBrowser控制項就失敗。

[dotnet]WebBrowser控制項可吃 css 及 script

經過研究及實驗,可正常使用一般在 html 中加入 css 及 script 的標記,
也可以引用外部 css 檔案及 script 檔案。

用 WebBrowser.Document 新增新的 element,有幾個現象:

  1. 新的 element 並不會套上 class 的設定。
  2. 新的 element 會直接套用 style 的設定。

要解決第1點,可以先寫檔,再 navigate,就會套用設定。
所以一氣呵成的流程就是:

  1. WebBrowser.Document 新增新的 element
  2. 確定 WebBrowser1.Document.GetElementsByTagName("html").Item(0).OuterHtml 已經是新的內容
  3. 開檔寫檔
  4. navigate

[dotnet].NET Framework 還是有分 32 和 64

這個算是記憶刷新,並留下資料。

http://msdn.microsoft.com/zh-tw/windows/gg537087

[dotnet]讓WebBrowser預設右鍵menu不出現

因為打算使用 WebBrowser 這個控制項當做 UI 上顯示 log 的控制項,
這是一個快速筆記。

為了讓右鍵不顯示,只要一行code的設定:

WebBrowser1.IsWebBrowserContextMenuEnabled = False

http://evabow.blogspot.com/2011/07/webbrowsermenu.html

[dotnet]WebBrowser1.DocumentText

因為打算使用 WebBrowser 這個控制項當做 UI 上顯示 log 的控制項,
這是一個快速筆記。

http://msdn.microsoft.com/zh-tw/library/system.windows.forms.webbrowser.documenttext(v=vs.85).aspx 

即使已要求另一份文件,這個屬性仍會包含目前文件的文字。如果設定這個屬性的值,然後立即重新擷取這個值,則在 WebBrowser 控制項尚未有時間載入新內容時,所擷取到的值可能會與設定的值不同。您可以在 DocumentCompleted 事件處理常式中擷取新的值。

有人說,用doevents等一下可以

        While (True)
            Application.DoEvents()
            If Not WebBrowser1.Document.Body Is Nothing Then
                Exit While
            End If
        End While

2011年10月28日 星期五

[備忘]雲端的資料庫系統

資料來源

http://www.ic975.com/Main/Rundown.ic?id=7496

  • 節目來賓:立即科技有限公司 技術經理 郭家甫

因為發現身為資訊相關的工程師的生活,實在是有很多不為人知的痛苦跟麻煩,
因此決定開發出一系列的軟體與服務來讓自己、以及所有相關的工程師的生活更加輕鬆愉快。
Ragic Builder可以快速建立企業所需要的資料庫,
而不需要傳統資料庫的困難開發技術、冗長過程。
利用簡單Excel的開發介面,您不需要寫一行程式,
也可以建立網站式的資料庫系統。
本周的數位領航家
將為您邀請即科技有限公司技術經理郭家甫先生
與各位分享--雲端的資料庫系統

感想:

  1. 它不是用真的excel當開發介面,而是長得像excel的網頁介面,讓人使用控制遠端的資料庫的表格及設定。
  2. 把有關資料庫的開發方式中,比較固定的部份用這套軟體搞定。
  3. 一般企業可以雲端方式來使用這軟體,算月租,也可以買斷放在自家。
  4. 這公司的網站:http://www.ragic.com/tw/index.html

2011年10月17日 星期一

[C#]集合已修改; 列舉作業可能尚未執行

使用 dictionary 會有的一個使用狀況是,
把dictionary 裡的所有值都處理過一遍,像是
foreach( string k in dict.Keys)
{
  dict[k] = dict[k] + “abcd”;
}

這時候會出現「集合已修改; 列舉作業可能尚未執行」的錯誤。
我非常想不懂,這樣,dictionary 就超難用的!
還好有人也遇到。
http://godleon.blogspot.com/2011/06/linq.html
裡面提到的解決方法,把 foreach 的集合,先變成不會變化的 array。
免得 runtime 覺得有危險。
foreach ( string k in dict.Keys.ToArray() )
或者使用
System.Collections.Concurrent namespace 中的 thread safe 集合物件
搞這樣,還滿麻煩的…說真的。

我習慣把資料放在 dictionary 裡傳遞。
因為我用 python 習慣了。
尤其 javascript 的物件,事實上跟 dictionary 沒兩樣…。
這在小程式的時候很好用,如果是超大團隊的話,還是建專用物件來傳會好一點。
這又是題外話了…

2011年9月9日 星期五

[python]python on mac

我還記得以前,在 mac 上要跑 python,不是很開心。
因為 mac 上的 python 版本舊很多。
所以要另外裝所謂的 macpython 才有比較新的。
而且,裝了之後,在 finder 裡點兩下,應該啟動的 python 程式,也不正常。
另外,在 windows 底下寫的程式,無法直接拿到 mac 下執行,
原因居然是換行符號的問題。
因此,我隔了很久都不再 mac 上使用 python。

我去年裝了 mac OS 10.5 之後,我也一樣沒再去想 python 的問題。
最近想把一些每天要執行的程式搬到 mac 上,才能每天有股票資訊可看。
才突然發現,它居然更新到 python 2.7 了,而且,
在 console 底下,打 python xxx.py 也可以順利執行,沒有換行的問題了。
(雖然直接執行還是有問題,因為沒有加上 !#/bin/python)
(直接點兩下也沒辦法執行,這個問題我還沒有找出來)
這樣,就讓我很開心了。

原本,還想說,要不要把 python 程式轉成 jython 可用之後拿到 mac 上執行,
看來除非程式執行需要gui,否則我可以不用花這麼大的工夫了。
哈哈。

[jython]json到哪兒去了?(2)

經過了一段掙扎,回頭再看一下jyson的文件。
http://opensource.xhaus.com/projects/jyson/wiki/JysonUserObjects
這裡面提到,要輸出自己的 class 就要實作 __json__() 這個 method。

回頭再測試一下,jyson 的 loads 及 dumps 根本不看 object_hook 及 cls 的參數。
那麼,
a = json.loads(b,object_hook=ext.as_orderdict)
這個 a 只是一個 dict,所以,要自己再產生物件,於是乎從檔案讀回來就要改成

f = open(json_filename,'r')
  b = f.read()
  f.close()
  a = json.loads(b,object_hook=ext.as_orderdict)
  if sys.version.find('Java')>-1:
    c = ext.OrderDict()
    c.list = a['list']
    c.dict = a['dict']
    a = c
  return a

還有,物件  的定義裡要加上 __json__(self) 的方法。

def __json__(self): # for jyson
    return u"""{'list':%s,'dict':%s}""" % (json.dumps(self.list),json.dumps(self.dict))

文件中,說明一定要回傳 unicode 的字串。

呼,終於搞定了。

[jython]json到哪兒去了?

看著 python 2.6.5 可以快樂的使用 json,
jython 卻還不行使用。
網路上有 jyson,雖然他的用法已經很接近 python 的,
但是,就是不一樣,就是會遇到:

class OrderDictEncoder(json.JSONEncoder)

JSONEncoder 不在 jyson,所以沒辦法照用。

唉~~~,事情不會這麼美好。

以下就開始改變程式,讓 python 及 jython 都能用。

(1)
import sys
if sys.version.find('Java')>-1:
  import com.xhaus.jyson.JysonCodec as json
else:
  import json

(2) 這一段我超喜歡的,條件式的宣告在 python 及 jython 裡,就跟寫程式沒兩樣。讚!
if sys.version.find('Java')>-1:
  import com.xhaus.jyson
  class OrderDictEncoder(com.xhaus.jyson.JysonEncoder):
    def default(self, obj):
      if isinstance(obj, OrderDict):
        return {'list':obj.list,'dict':obj.dict}
      return com.xhaus.jyson.JysonEncoder.default(self, obj)
else:
  class OrderDictEncoder(json.JSONEncoder):
    def default(self, obj):
      if isinstance(obj, OrderDict):
        return {'list':obj.list,'dict':obj.dict}
      return json.JSONEncoder.default(self, obj)

(3) 因為 jyson 不支援 load 及 dump,只有 loads 及 dumps,所以全改成 loads 及 dumps。
例如:
f = open(json_filename,'r')
a = json.load(f,object_hook=ext.as_orderdict)
改成:
f = open(json_filename,'r')
b = f.read()
a = json.loads(b,object_hook=ext.as_orderdict)

但是…天不從人願…
    b = json.dumps(result,cls=ext.OrderDictEncoder)
        at com.xhaus.jyson.JysonEncoder.append_json_repr(Unknown Source)
        at com.xhaus.jyson.JysonEncoder.json_repr(Unknown Source)
        at com.xhaus.jyson.JysonCodec.dumps(Unknown Source)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
        at java.lang.reflect.Method.invoke(Unknown Source)

java.lang.ClassCastException: java.lang.ClassCastException: org.python.core.PyCl
ass cannot be cast to org.python.core.PyType

這個我就不知道該怎麼辦才好啦…
jython 還是跑不過去…

[python][jython]關於Exception的不同

在 python 裡以下的敘述可用

except Exception as ex:

但是,在 jython 卻會出問題:

except Exception as ex:
SyntaxError: mismatched input 'as' expecting COLON

耶~~~~

http://jythonpodcast.hostjava.net/jythonbook/en/1.0/ExceptionHandlingDebug.html

經過查詢上列網頁,jython 2.5.2 還不支援這種寫法。

那就只好先寫舊寫法。

except Exception , ex:

擋著先。

2011年8月31日 星期三

[python]reduce的觀念

從python裡,我學到一組對我來說很重要的觀念。
一個是 map,另一個是 reduce。
這兩個對我寫程式的影響很大。
而 reduce 對我來說,真的是比較難理解。
而我最近才比較了解一點點。
在網路上,大多文章談到 reduce,多是舉官方文件的例子:

def sum_num(x,y):
    return x + y
a = [1,2,3,4,5]
b = reduce(sum_num, a)

或者是更簡潔地:

a = [1,2,3,4,5]
b = reduce(lambda x,y : x + y, a)

在我想要做更複雜的事情的時候,腦筋就轉不過來。
難道,只能做數值運算嗎?

讓我們來看一下 reduce 的函數定義,其為

reduce(func, iter, [initial_value])

套用於上面的例子,你可以認為
b = reduce(sum_num, a)
等同於
b = reduce(sum_num, a, 0)

然後,把 reduce(sum_num, a, 0) 想像成如下的函數:

def reduce(func_handler, value_list, init_value):
    b = init_value
    for value in value_list:
        b = func_handler(b, value)
    return b

所以,想像一下,要使用 reduce,要準備三個東西,
(1)一個函數,有兩個傳入值,一個輸出值,這些值,都是屬於同一集合的元素。
(2)一個list,是要被處理的元素。
(3)起始值。

例如,我有一組資料長相如下:
a = [(1,a,3),(2,b,6),(3,c,9),(4,d,12)]
我想要把計算每個tuple的第一個總和、每個tuple的第二個字元要連接、每個tuple的第三個乘積。
也就是要得到
(1+2+3+4,’abcd’,3*6*9*12)
我要準備函數,為了好理解,特別寫成下面的樣子
def sum_special(x, y):
    i0 = x[0]+y[0]
    i1 = x[1]+y[1] #字串相加
    i2 = x[2]*y[2]
    return (i0, i1, i2)
要處理的 list 就是 a,
起始值就是 (0, ‘’, 0) 了。

reduce 這樣用就對了。

當你的程式看到很多次的

a = func(a, b)
a = func(a, c)
a = func(a, d)

或是

b = init_value
for value in value_list:
    b = func_handler(b, value)

那就轉用 reduce 吧。

2011年8月29日 星期一

[python]自己寫一個dict

當寫程式到後來,一定會想要自己寫一個物件來用。
而且,希望能跟 build-in 的物件一樣的使用。
舉例來說,我們想要一個 ordered dict,在 iteration 的時候,
能按照我們加進去的順序列出。

這個 dict 據說在 python 3 已經有了,
這裡只是練習一下該怎麼寫這一類的物件。
這方面的資料在官方文件的
The Python Language Reference >> 3. Data model

首先,要能夠使用 a[‘1a’] = “a” 這個語法,
物件要提供 def __setitem__(self,key,value)。
要能夠使用 b = a[‘1a’] 這個語法,
物件要提供 def __getitem__(self,key)
按照文件及我的需求(也就是要像一個 dict),
該物件要提供以下的 method,我完整的列出來
a = orderdict()

def has_key(self,key): 仿 dict 的 has_key
__setitem__(self,key,value): 提供 a[‘1a’] = “a”
__getitem__(self,key) 提供 b = a[‘1a’]
def __len__(self): 提供 len(a)
def __iter__(self): 提供 for i in a:
def __contains__(self,item): 依照文件,提供這個method,可以提高 in 這個 operation 的效率。參見3.4.5
def __repr__(self): 自定被 print 呼叫時的文字表達

完整的物件長像:

class orderdict():
    def __init__(self):
        self.list = []
        self.dict = {}
    def has_key(self,key):
        return self.dict.has_key(key)
    def __setitem__(self,key,value):
        if self.dict.has_key(key):
            self.dict[key] = value
        else:
            self.list.append(key)
            self.dict[key] = value
    def __getitem__(self,key):
        if self.has_key(key):
            return self.dict[key]
        else:
            raise KeyError
    def __delitem__(self, key):
        if self.has_key(key):
            del self.dict[key]
            self.list.remove(key)
    def __len__(self):
        return len(self.dict)
    def __iter__(self):
        return self.list.__iter__()
    def __contains__(self,item):
        return self.dict.has_key(item)
    def __repr__(self):
        return str(a.dict)

這樣子就有看來不錯的物件可以用了。
以後要寫物件自己用,一定要參考 The Python Language Reference >> 3. Data model
才會有好用的物件。

2011年8月26日 星期五

[編譯器的研究]parse tree之後?

是的,如同標題,在 parse tree 完成之後,應該做些什麼?
原本我是有目的的,要幫 log 上顏色。
在寫完以下的程式之後,我感覺到一陣的空虛。

f = open("genOutput.htm","w")
f.write("<html>")
f.write("<body>")
f.write("<table border='1'>")
for i in ms.get_attr("msg"):
  f.write("<tr><td>"+str(i)+"</td></tr>")
f.write("</table>")
f.write("</body>")
f.write("</html>")
f.close()

這段程式會輸出一個 html,瀏覽器會看到

image

要更好看,當然要繼續下去改善,像是顏色,欄位。或是加上搜尋功能,過濾功能。
但是在繼續下去之前,我想到一件事。
在使用上,是不是要即時一點?
例如,有新的事件進來,要即時 scan/parse,而現在,scanner/parser 是整個檔案 parse。
而我認為最好是採遞增式的 scanner/parser,就減少卡畫面的機會,一方面使用者會有比較好的使用者經驗;另一方面,處理時也不會突然讓 cpu usage 大爆增。
所以我想在這裡停下來,不往下繼續走。而是回頭再修改之前的 scanner/parser。

當然,如果有人要從這裡開始往下走,(我想大概也沒人看我這系列吧),
應該就開始偏離編譯器的範圍,也就不太會用這個系列開頭了。

這個系列算是結束了嗎?
不!
而是要開始比較正式的朝向 scanner/parser 的方向,以解析程式語言為目標,
以產生可執行程式或輔助開發環境為目的。
log monitor 的部份,會另外再開一條路來做。
而且我想,會需要遞增式 scanner/parser 的經驗來幫助。
所以要先把遞增式 scanner/parser 做好才對。

下次再見!

2011年8月24日 星期三

2011年8月15日 星期一

[VB6toVB.NET2010]

公司要把 VB6 的東西轉成 .NET 2010
上網找了一下,答案是不行直接轉。
http://www.google.com.tw/url?sa=t&source=web&cd=1&ved=0CBkQFjAA&url=http%3A%2F%2Fsocial.msdn.microsoft.com%2FForums%2Fzh-CN%2F2212%2Fthread%2Fd444bbea-d23d-48d2-b005-49d057405523%2F&ei=QeRJTtmaE4jniALamcGiBw&usg=AFQjCNFp3e09h6ajRzYMXJ7GCMGyEHRjwQ&sig2=nnlC759IJECozFq013x0aA

這一篇講得深得我心,尤其是最後一PO,真是爽啊。

所以該怎辦呢?
VS2003可以把VB6轉成.NET 的 project。
然後再用 VS2010 開啟轉換完的 project,再轉一次。
然後,form.vb 的部份只能看見UI程式碼,看不見UI外觀,哈哈!
執行一下失敗,需要許多舊時代的dll。
結論是還是要重寫一次。

恭喜!

[網路]Bounce Rate是什麼意思?

我有申請google的 analytics。
我把這個網站還有我用GAE做的一個網頁都加了進去。
我老是對於Bounce Rate很高不是很滿意。
但是,我內心是覺得我本來就不該在乎bounce rate。

bounce rate的嚴格描述在wiki上有說明http://en.wikipedia.org/wiki/Bounce_rate
我就不多說了。
依照定義,一般認為bounce rate越高越代表網頁的內容不好。
但是!
依照定義,部落格的bounce rate容易高,
假設,有人的部落格的讀者都是每天來看的人,今天的文章看完就走掉了,
bounce rate超高。
假設,我的網頁是提供當日訊息,只有一頁(我的另一個網頁就是如此),
根本沒其他頁,bounce rate沒有100%我還覺得奇怪。

好吧,我只是幫我的網頁沒什麼內容找藉口。

2011年8月4日 星期四

[web_service]自己寫一個 webservice server 及 client

因為要開發測試 web service 的應用程式,
最痛苦的地方就是 client 要連到已存在的 server,
而該 server 不能亂搞,而又不是自己寫的,沒有模擬器。
在一種最尷尬的情況就是,
你的程式就是 web service,還需要使用別人的 web service,
在沒有模擬器的狀況下,開發及除錯都很難,
一遇到要 call out 就跳過,是最保險,但又是最麻煩的做法。
有辦法自己寫一個 web service 的 client 及 server 嗎?
當然有,這裡介紹兩種方法:

第一種是用微軟的 Web Service 模組,這種是最偷懶的方法。
但這種方法一不小心就是會找不到問題在哪,
而我就是因為粗心而花了兩天找問題。
第二種是用任何語言的 http listener / client 模組,
這種方法是最直覺,最能夠把問題分離化的方法。
可以控制到 http 層當然控制度最好。
這一篇是因為開發測試的時候因為粗心大意,結果浪費時間的心得報告。

-----------------------------------------

使用微軟的Web Service 模組當「模擬 server」的時候,
可以只模擬部份的 web method,
如果 client 端有下列方法,

string LoadProduct( string xml)

「模擬 server」就要宣告一個方法如下:

[Web Method]
string LoadProduct( string xml) { ….. }

要非常注意兩件事,

一、WebServcie 的 namespace,要跟 client 端的 namespace「一模一樣」,
少一個,多一個字元都會導致解不開 soap 物件,而且不會有任何錯誤訊息出現。
我這次就是最尾端少一個斜線,http://tempurl.orghttp://tempurl.org/ 的差別。
找了兩天,做了很多測試,甚至自己用 http listener 來回應測試。

二、web method 的參數名字,也要跟 client 端的參數名字「一模一樣」,
名字不一樣,參數值一樣解不開。

我出錯在這兩個地方,只要這兩個地方沒有錯,雖然不保證一定成功,
但至少錯不在這!

使用微軟的Web Service 模組當「模擬 client」的時候,非常簡單,
這時候一定會有 server 來的 wsdl,就當正式的 client 來寫程式就行了。

-------------------------------------------------

用 http listener 當「模擬 server」的時候,要處理到 http 的回應,
若是不知道 soap 規格,也不知道 wsdl 怎麼看,又該如何處置呢?

抄!這是這次解決方法的最高秘技。
在微軟的2008開發環境中,把 web service 啟動之後,
可以連到類似 http://localhost:1234/abcd.asmx
點擊任何一個 web method 就會看見測試框。

image

也有範例,這就是我們要抄的地方。

image

如果要當 client,就抄第一段。如果是要當 server,就抄第二段。
如果你要模擬的 server 是用微軟工具開發的,網址尾端是 asmx 的,
就會有這個網頁,就有範例可以抄。

用 http listener 當「模擬 server」,以 .Net 為例,
System.Net.HttpListener 可以拿到對方送來的 context,
request 裡有對方送來的資料,需要的話在 request 裡找。
response 的話,從範例來看,要設定

response.ContentType = "text/xml; charset=utf-8";

把範例抄進來,弄一個變數 responseString 來存放。
為了要把它弄成 utf8,需要

byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);

response 要設定長度

response.ContentLength64 = buffer.Length;

最後把 byte 寫下去,還要記得關起來,就行了。

System.IO.Stream output = response.OutputStream;
output.Write(buffer, 0, buffer.Length);
output.Close();

這就完成一次傳輸。
最簡單可重覆就是用一個無限迴圈,不要用的時候直接把程式關了。
有問題就關掉重開。這是最簡單的方式。

---------------------------------------------------------------

用 http client 當「模擬 client」,以 .Net 為例,
System.Net.WebClient 物件產生之後,
參考範例,要在 headers 設定 Content-Type,及 SOAPAction。
內容的部份用一個 string 變數 soapcontent,存起來。
然後用 UploadString 把 soapcontent 送出去,該 method 回傳 server 的回應。

比較完整的程式碼如下,有些可能在貼上來的時候,雙引號被弄掉了,
我有看到的我都補上了,剪下貼上的時候要看一下。
完整的 server 的 main 如下:

static void Main(string[] args)
        {
            System.Net.HttpListener hl = new System.Net.HttpListener();
            hl.Prefixes.Add("http://*:1234/");
            hl.Start();
            while (true)
            {
                System.Net.HttpListenerContext context = hl.GetContext();
                System.Net.HttpListenerRequest request = context.Request;
                System.IO.StreamReader sr = new System.IO.StreamReader(request.InputStream);
                Console.WriteLine("read:");
                Console.WriteLine(sr.ReadToEnd());
                System.Net.HttpListenerResponse response = context.Response;
                response.ContentType = "text/xml; charset=utf-8";
                string responseString = "<?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\"><soap:Body><LoadProductResponse xmlns=\”http://tempurl.org/\”><LoadProductResult>string</LoadProductResult></LoadProductResponse></soap:Body></soap:Envelope>";
                Console.WriteLine("write:");
                Console.WriteLine(responseString);

                byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
                response.ContentLength64 = buffer.Length;
                System.IO.Stream output = response.OutputStream;
                output.Write(buffer, 0, buffer.Length);
                // You must close the output stream.
                output.Close();
            }
            hl.Stop();
        }

完整的 client 的 main 程式碼如下:

static void Main(string[] args)
{
                System.Net.WebClient wc = new System.Net.WebClient();
                string soapcontent = "<?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">  <soap:Body>    <LoadProduct xmlns=\"http://mirlmes.itri.org.tw\">      <InXml>string</InXml>    </LoadProduct>  </soap:Body></soap:Envelope>";
                wc.Headers["SOAPAction"] = "\"http://tempurl.org/LoadProduct\"";
                wc.Headers["Content-Type"] = "text/xml; charset=utf-8";
                Console.WriteLine(wc.UploadString(“http://localhost:1234/”, soapcontent));
}

2011年8月1日 星期一

[編譯器的研究]parser的實作(2)

一般 parse tree 是一個有序(ordered)、有唯一根(rooted)的樹。
因此,之前的 grammar 要再改變一下,加上<log> ::= <msg> {<msg>} 在最前面。

<log> ::= <msg> {<msg>}
<msg> ::= timestamp <secs_type_msg>
          | timestamp <host_type_msg>
<secs_type_msg> ::= <desc> secs_comment <secs_msg>
<host_type_msg> ::= eap_log_1 {xml_transaction}
<desc> ::= <secs_msg_name> colon <secs_SxFy_sym>
          | normal_word {normal_word}
<secs_SxFy_sym> ::= secs_SxFy {<secs_w_bit>}
<secs_msg_name> ::= normal_word
<secs_w_bit> ::= normal_word
<secs_msg> ::= {<secs_msg_body>} secs_end
<secs_msg_body> ::= <secs_msg_item> {secs_comment }
<secs_msg_item> ::= secs_item_a
                    | secs_item_u
                    | secs_item_b
                    | secs_item_bool
                    | secs_item_f
                    | secs_item_i
                    | secs_list_start {secs_comment} {<secs_msg_item>} secs_list_end

接下來是設計 tree node 存放物件。

之前的程式實際上只有判斷 input 是否為可接受。
所以每一個函式傳回 true 的地方都改為傳回 node ,傳回 false 的地方則改為傳回 false。

最基本的一個 node,就是只有一個 name 屬性的物件。
把它當做抽象類別。為了方便序列化,我們把其他的屬性都用一個 dict 裝起來,
為了讓屬性按加入順序序列化,把屬性名字加到一個 list 中。

class c_node:
  def __init__(self):
    self.keyvalue = {}
    self.keyarray = []
  def add_attr(self,k,v):
    if self.keyvalue.has_key(k):
      self.keyvalue[k] = v
    else:
      self.keyarray.append(k)
      self.keyvalue[k] = v
  def set_attr(self,k,v):
    if self.keyvalue.has_key(k):
      self.keyvalue[k] = v
    else:
      self.keyarray.append(k)
      self.keyvalue[k] = v

  def get_attr(self,k):
    if self.keyvalue.has_key(k):
      return self.keyvalue[k]
    else:
      return None
  def __str__(self):
    _str = "["
    _str1 = []
    for i in self.keyarray:
      if self.keyvalue[i] != None:
        if type(self.keyvalue[i]) != list:
          _str1.append(i + ":" + str(self.keyvalue[i]))
        else:
          _str2 = []
          for j in self.keyvalue[i]:
            _str2.append( str(j) )
          _str1.append(i + ":" + "[" + ",".join(_str2) + "]")
      else:
        _str1.append(i + ":" + "None")
    _str += ",".join(_str1)
    _str += "]"
    return _str

我們為 log 設計一個 c_log 的類別。
class c_log(c_node):
  def __init__(self):
    c_node.__init__(self)
    self.add_attr('name','msgs')
    self.add_attr('msg',None)

其他的類別也很單純,只要按 grammar 中的順序把 symbol (terminal 及 non-terminal) 當做屬性加入即可。
但是到了 c_secs_msg_item,就不太一樣,因為他有可能是 terminal 也可能是 non-terminal 的 symbol,
所以屬性除了當做 terminal 存放值的 value,也有當做 non-terminal 存放值的 children。

class c_secs_msg_item(c_node):
  def __init__(self):
    c_node.__init__(self)
    self.add_attr('name','secs_msg_item')
    self.add_attr('list_start',None)
    self.add_attr('secs_comment',None)
    self.add_attr('children',None)
    self.add_attr('value',None)
    self.add_attr('list_end',None)

在 parser 的程式裡,遇到 secs_msg_item 的時候,
如果不是遇到 secs_list_start,就把值放到 value 去。
否則就把值放到 children 裡面,就算該值是空的。(因為允許空的 list)

(secs_msg_item 的程式碼請參考底下)

老實說,我也沒辦法一開始就知道需要 c_node,也沒辦法知道 c_node 就要長這個樣子。
我是邊寫邊改的。因為要 debug 才生出 __str__ 這個方法。
因為要 __str__ 才知道要把屬性放到 list 及 dict 裡面方便列舉。
在寫的過程中,參考了 google code 裡面的 traceur。(它是用 javascript 寫的)
因為難以了解之後會遇到什麼,所以花了一點時間研究才隔了兩星期沒有產生(有一點找藉口)
完整的程式碼我分成兩個,一個是 node 們的定義。一個是 parser 的程式。

node_def.py

class c_node:
  def __init__(self):
    self.keyvalue = {}
    self.keyarray = []
  def add_attr(self,k,v):
    if self.keyvalue.has_key(k):
      self.keyvalue[k] = v
    else:
      self.keyarray.append(k)
      self.keyvalue[k] = v
  def set_attr(self,k,v):
    if self.keyvalue.has_key(k):
      self.keyvalue[k] = v
    else:
      self.keyarray.append(k)
      self.keyvalue[k] = v

  def get_attr(self,k):
    if self.keyvalue.has_key(k):
      return self.keyvalue[k]
    else:
      return None
  def __str__(self):
    _str = "["
    _str1 = []
    for i in self.keyarray:
      if self.keyvalue[i] != None:
        if type(self.keyvalue[i]) != list:
          _str1.append(i + ":" + str(self.keyvalue[i]))
        else:
          _str2 = []
          for j in self.keyvalue[i]:
            _str2.append( str(j) )
          _str1.append(i + ":" + "[" + ",".join(_str2) + "]")
      else:
        _str1.append(i + ":" + "None")
    _str += ",".join(_str1)
    _str += "]"
    return _str

class c_log(c_node):
  def __init__(self):
    c_node.__init__(self)
    self.add_attr('name','msgs')
    self.add_attr('msg',None)

class c_msg(c_node):
  def __init__(self):
    c_node.__init__(self)
    self.add_attr('name','msg')
    self.add_attr('timestamp',None)
    self.add_attr('host_type_msg',None)
    self.add_attr('secs_type_msg',None)

class c_secs_type_msg(c_node):
  def __init__(self):
    c_node.__init__(self)
    self.add_attr('name','secs_type_msg')
    self.add_attr('desc',None)
    self.add_attr('secs_comment',None)
    self.add_attr('secs_msg',None)
class c_secs_SxFy_sym(c_node):
  def __init__(self):
    c_node.__init__(self)
    self.add_attr('name','secs_SxFy_sym')
    self.add_attr('secs_SxFy',None)
    self.add_attr('secs_w_bit',None)

class c_desc(c_node):
  def __init__(self):
    c_node.__init__(self)
    self.name = 'desc'
    self.add_attr('name','desc')
    self.add_attr('secs_msg_name',None)
    self.add_attr('colon',None)
    self.add_attr('secs_SxFy_sym',None)
    self.add_attr('normal_word',None)

class c_secs_msg_name(c_node):
  def __init__(self):
    c_node.__init__(self)
    self.add_attr('name','secs_msg_name')
    self.add_attr('normal_word',None)

class c_colon(c_node):
  def __init__(self):
    c_node.__init__(self)
    self.add_attr('name','colon')
    self.add_attr('value',':')

class c_secs_w_bit(c_node):
  def __init__(self):
    c_node.__init__(self)
    self.name = 'secs_w_bit'
    self.add_attr('name',self.name)
    self.normal_word = None   
    self.add_attr('normal_word',self.normal_word)

class c_secs_msg(c_node):
  def __init__(self):
    c_node.__init__(self)
    self.add_attr('name','secs_msg')
    self.add_attr('secs_msg_body',None)
    self.add_attr('secs_end',None)

class c_secs_msg_body(c_node):
  def __init__(self):
    c_node.__init__(self)
    self.add_attr('name','secs_msg_body')
    self.add_attr('secs_msg_item',None)
    self.add_attr('secs_comment',None)
class c_secs_msg_item(c_node):
  def __init__(self):
    c_node.__init__(self)
    self.add_attr('name','secs_msg_item')
    self.add_attr('list_start',None)
    self.add_attr('secs_comment',None)
    self.add_attr('children',None)
    self.add_attr('value',None)
    self.add_attr('list_end',None)

class c_host_type_msg(c_node):
  def __init__(self):
    c_node.__init__(self)
    self.add_attr('name','host_type_msg')
    self.add_attr('eap_log_1',None)
    self.add_attr('xml_transaction',None)

------------------------------------------------------------------------------
ll_parser_v4.py  # 看到這個 v4 就知道我大改了4次。

import node_def

t_file = open("tokens.txt","r")
t_file_content = t_file.read()
t_file.close()

tokens = t_file_content.split("\n\n\n")
global tokenindex
tokenindex = 0

global_stack = []

def token_cut(token):
  r = []
  sep = ":"
  t1 = token[:token.find(sep)]
  token = token[token.find(sep)+1:]
  sep = ","
  t1 = token[:token.find(sep)]
  token = token[token.find(sep)+1:]
  r.append(t1)
  sep = ","
  t1 = token[:token.find(sep)]
  token = token[token.find(sep)+1:]
  r.append(t1)
  sep = ","
  t1 = token[:token.find(sep)]
  token = token[token.find(sep)+1:]
  r.append(t1)

  sep = ","
  t1 = token[:token.find(sep)]
  token = token[token.find(sep)+1:]
  r.append(t1)
  sep = ","
  t1 = token[:token.find(sep)]
  token = token[token.find(sep)+1:]
  r.append(t1)
  r.append(token)
  return r

input_index = -1
sym = None
tokencount = len(tokens)
def getsym():
  global input_index
  input_index += 1
  global sym
  if input_index > tokencount - 1:
    sym = None
  else:
    if len(tokens[input_index])==0:
      sym = None
    else:
      sym = token_cut(tokens[input_index])

def lookahead():
  global input_index
  if input_index + 1 > tokencount - 1:
    return None
  else:
    return token_cut(tokens[input_index + 1])
def accept(s):
  global sym
  global global_stack
  if sym != None and sym[0] == s:
    #print 'accept',sym
    global_stack.append(sym[5])
    getsym()
    return True
  return False

def expect(s):
  if accept(s):
    return True
  print "expect: unexpected symbol"
  print "expect:", s
  global sym
  print "encounter:",sym
  sym = None
  return False

def secs_w_bit():
  print 'secs_w_bit()'
  my = node_def.c_secs_w_bit()
  r = accept("normal_word")
  if r:
    v = global_stack.pop()
    my.set_attr('normal_word',v)
    #print 'normal_word pop:',v
  #print str(my)
  print 'secs_w_bit() complete'
  return my

def secs_SxFy_sym():
  print 'secs_SxFy_sym()'
  my = node_def.c_secs_SxFy_sym()
  r = expect("secs_SxFy")
  if r:
    v = global_stack.pop()
    my.set_attr("secs_SxFy",v)
    #print 'secs_SxFy pop:',v
  my.set_attr("secs_w_bit",secs_w_bit())
  #print str(my)
  print 'secs_SxFy_sym() complete'
  return my

def secs_msg_name():
  print 'secs_msg_name()'
  my = node_def.c_secs_msg_name()
  r = expect("normal_word")
  if r:
    v = global_stack.pop()
    my.normal_word = v
    #print 'normal_word pop:',v
  #print str(my)
  print 'secs_msg_name() complete'
  return my

def desc():
  print 'desc()'
  my =  node_def.c_desc()
  global input_index
  _sym = lookahead()
  if _sym[0]=="colon":
    my.set_attr("secs_msg_name",secs_msg_name())
    r = expect("colon")
    if r:
      v = global_stack.pop()
      my.set_attr("colon",v)
      #print 'colon pop:', v
    my.set_attr("secs_SxFy_sym",secs_SxFy_sym())
  else:
    r = accept("normal_word")
    if r:
      v = global_stack.pop()
      my.set_attr("normal_word",v)
      #print 'normal_word pop:',v
    while r == True:
      r = accept("normal_word")
      if r:
        v = global_stack.pop()
        if type(my.get_attr("normal_word")) == list:
          my.get_attr("normal_word").append(v)
        else:
          tmpv = my.get_attr("normal_word")
          my.set_attr("normal_word",[])
          my.get_attr("normal_word").append(tmpv)
          my.get_attr("normal_word").append(v)
        #print 'normal_word pop:',v
  #print str(my)
  print 'desc() complete'
  return my

def secs_msg_item():
  print 'secs_msg_item()'
  my = node_def.c_secs_msg_item()
  if accept("secs_item_a"):
    v = global_stack.pop()
    my.set_attr("value", v)
    r = accept("secs_comment")
    if r:
      v = global_stack.pop()
      my.set_attr("secs_comment", v)
  elif accept("secs_item_u"):
    v = global_stack.pop()
    my.set_attr("value", v)
    r = accept("secs_comment")
    if r:
      v = global_stack.pop()
      my.set_attr("secs_comment", v)
  elif accept("secs_item_b"):
    v = global_stack.pop()
    my.set_attr("value", v)
    r = accept("secs_comment")
    if r:
      v = global_stack.pop()
      my.set_attr("secs_comment", v)
  elif accept("secs_item_bool"):
    v = global_stack.pop()
    my.set_attr("value", v)
    r = accept("secs_comment")
    if r:
      v = global_stack.pop()
      my.set_attr("secs_comment", v)
  elif accept("secs_item_f"):
    v = global_stack.pop()
    my.set_attr("value", v)
    r = accept("secs_comment")
    if r:
      v = global_stack.pop()
      my.set_attr("secs_comment", v)
  elif accept("secs_item_i"):
    v = global_stack.pop()
    my.set_attr("value", v)
    r = accept("secs_comment")
    if r:
      v = global_stack.pop()
      my.set_attr("secs_comment", v)
  elif accept("secs_list_start"):
    v = global_stack.pop()
    my.set_attr("list_start", v)
    r = accept("secs_comment")
    if r:
      v = global_stack.pop()
      my.set_attr("secs_comment", v)
    r = secs_msg_item()
    if r != None:
      my.set_attr("children", [])
    while r != None:
      my.get_attr("children").append(r)
      r = secs_msg_item()
    r = expect("secs_list_end")
    if r:
      v = global_stack.pop()
      my.set_attr("list_end", v)
  else:
    print 'secs_msg_item() complete'
    return None
  #print str(my)
  print 'secs_msg_item() complete'
  return my

def secs_msg_body():
  print 'secs_msg_body()'
  my = node_def.c_secs_msg_body()
  my.secs_msg_item = secs_msg_item()
  r = accept("secs_comment")
  if r:
    v = global_stack.pop()
    self.secs_comment = v
    #print 'secs_comment pop:',global_stack.pop()
  #print str(my)
  print 'secs_msg_body() complete'
  return my

def secs_msg():
  print 'secs_msg()'
  my = node_def.c_secs_msg()
  if accept("secs_end"):
    v = global_stack.pop()
    my.set_attr("secs_end",v)
    #print 'secs_end pop:',v
  else:
    my.secs_msg_body = secs_msg_body()
    r = accept("secs_end")
    if r:
      v = global_stack.pop()
      my.set_attr("secs_end",v)
      #print 'secs_end pop:',v
  #print str(my)
  print 'secs_msg() complete'
  return my

def secs_type_msg():
  print 'secs_type_msg()'
  my = node_def.c_secs_type_msg()
  my.set_attr('desc',desc())
  r = accept("secs_comment")
  if r:
    v = global_stack.pop()
    my.set_attr('secs_comment',v)
    #print 'secs_comment pop:',v
  my.set_attr('secs_msg',secs_msg())
  #print str(my)
  print 'secs_type_msg() complete'
  return my

def host_type_msg():
  print 'host_type_msg()'
  my = node_def.c_host_type_msg()
  r = expect("eap_log_1")
  if r:
    v = global_stack.pop()
    my.set_attr('eap_log_1',v)
    r = accept("eap_log_1")
    while r:
      v = global_stack.pop()
      if type(my.get_attr('eap_log_1')) == list:
        my.get_attr('eap_log_1').append(v)
      else:
        tmpv = my.get_attr('eap_log_1')
        my.set_attr('eap_log_1',[])
        my.get_attr('eap_log_1').append(tmpv)
        my.get_attr('eap_log_1').append(v)
      r = accept("eap_log_1")
    #print 'eap_log_1 pop:',global_stack.pop()
  r = accept("xml_transaction")
  if r:
    v = global_stack.pop()
    my.set_attr('xml_transaction',v)
    #print 'xml_transaction pop:',global_stack.pop()
  print 'host_type_msg() complete'
  return my
def msg():
  print 'msg()'
  my = node_def.c_msg()
  if expect("timestamp"):
    v = global_stack.pop()
    my.add_attr('timestamp',v)
    #print 'timestamp pop:',v
    if sym[0]=="eap_log_1":
      my.add_attr('host_type_msg',host_type_msg())
    else:
      my.add_attr('secs_type_msg',secs_type_msg())
  print 'msg() complete'
  return my

ms = node_def.c_log()
getsym()
while sym != None:
  print 'token count',input_index
  a = msg()
##  print 'a.timestamp',a.timestamp
##  print 'a.host_type_msg',a.host_type_msg
##  print 'a.secs_type_msg',a.secs_type_msg
  if type(ms.get_attr("msg")) != list:
    ms.set_attr("msg",[])
  ms.get_attr("msg").append(a)

f = open("v4output.txt","w")
f.write(str(ms))
f.close()

-------------------------
tokens_for_dev.txt  #用來測試用的輸入檔

tokens(248743):timestamp,1,True,3302688,62420,2011-06-22-23:59:55

tokens(248745):normal_word,3,True,3302708,62420,AYT_Host

tokens(248746):colon,4,True,3302716,62420,:

tokens(248748):secs_SxFy,5,True,3302718,62420,'S1F1'

tokens(248750):normal_word,3,True,3302725,62420,W

tokens(248752):secs_comment,6,True,3302727,62420,/* Name=AreYouThere_Host Dir=2 Header=[00 00 81 01 00 00 00 01 CA 55] Rcvd=2 Time=23:59:55 TID=31094 */

tokens(248754):secs_end,8,True,3302831,62421,.

tokens(248756):timestamp,1,True,3302833,62422,2011-06-22-23:59:55

tokens(248758):normal_word,3,True,3302853,62422,OLD

tokens(248759):colon,4,True,3302856,62422,:

tokens(248761):secs_SxFy,5,True,3302858,62422,'S1F2'

tokens(248763):secs_comment,6,True,3302865,62422,/* Name=OnlineData Dir=1 Header=[00 00 01 02 00 00 00 01 CA 55] Rcvd=1 Time=23:59:55 TID=31094 */

tokens(248766):secs_list_start,9,True,3302967,62423,<L [2]

tokens(248769):secs_item_a,10,True,3302982,62424,<A [0] >

tokens(248771):secs_comment,6,True,3302991,62424,/* Name=MDLN Keyword=EquipmentModelType */

tokens(248774):secs_item_a,10,True,3303042,62425,<A [0] >

tokens(248776):secs_comment,6,True,3303051,62425,/* Name=SOFTREV Keyword=SoftwareRevision */

tokens(248779):secs_list_end,11,True,3303100,62426,>

tokens(248781):secs_end,8,True,3303102,62427,.

tokens(41):timestamp,1,True,416,8,2011-06-22-07:56:17

tokens(43):eap_log_1,12,True,436,8,Receiving PP_Upload request from TCS...

tokens(45):xml_transaction,13,True,476,9,<?xml version="1.0"?>
<Transaction TxName="PP_Upload" Type="Request" MessageKey="0629">
    <Tool ToolID="CLB02" fromOPI="true" Type=""/>
    <Recipes>
        <Recipe RecipeID="APM_6MIN" MachineRecipeID="CLB02.APM_6MIN.AA" LocalFilePath="D:\share\110622_075617_304_0082706463_00" FilePath="" FormatFlag="1" Type="" RecipeLevel="1" Option=""/>
        <Recipe RecipeID="Production;MXIC_SC1_360;1" MachineRecipeID="CLB02.APM_6MIN.AA" LocalFilePath="D:\share\110622_075617_304_0082706463_01" FilePath="" FormatFlag="1" Type="" RecipeLevel="2" Option=""/>
        <Recipe RecipeID="Production;POR QDR-HCL;1" MachineRecipeID="CLB02.APM_6MIN.AA" LocalFilePath="D:\share\110622_075617_304_0082706463_02" FilePath="" FormatFlag="1" Type="" RecipeLevel="2" Option=""/>
        <Recipe RecipeID="Production;POR-philic;1" MachineRecipeID="CLB02.APM_6MIN.AA" LocalFilePath="D:\share\110622_075617_304_0082706463_03" FilePath="" FormatFlag="1" Type="" RecipeLevel="2" Option=""/>
    </Recipes>
</Transaction>

tokens(47):timestamp,1,True,1442,20,2011-06-22-07:56:17

tokens(49):eap_log_1,12,True,1462,20,[S7F5] Sent by Host

--------------------------------------------------------------
v4output.txt # 最後的結果給大家參考,有點像是 json。也許有天我會改成 json。

[name:msgs,msg:[[name:msg,timestamp:2011-06-22-23:59:55,host_type_msg:None,secs_type_msg:[name:secs_type_msg,desc:[name:desc,secs_msg_name:[name:secs_msg_name,normal_word:None],colon::,secs_SxFy_sym:[name:secs_SxFy_sym,secs_SxFy:'S1F1',secs_w_bit:[name:secs_w_bit,normal_word:W]],normal_word:None],secs_comment:/* Name=AreYouThere_Host Dir=2 Header=[00 00 81 01 00 00 00 01 CA 55] Rcvd=2 Time=23:59:55 TID=31094 */,secs_msg:[name:secs_msg,secs_msg_body:None,secs_end:.]]],[name:msg,timestamp:2011-06-22-23:59:55,host_type_msg:None,secs_type_msg:[name:secs_type_msg,desc:[name:desc,secs_msg_name:[name:secs_msg_name,normal_word:None],colon::,secs_SxFy_sym:[name:secs_SxFy_sym,secs_SxFy:'S1F2',secs_w_bit:[name:secs_w_bit,normal_word:None]],normal_word:None],secs_comment:/* Name=OnlineData Dir=1 Header=[00 00 01 02 00 00 00 01 CA 55] Rcvd=1 Time=23:59:55 TID=31094 */,secs_msg:[name:secs_msg,secs_msg_body:None,secs_end:.]]],[name:msg,timestamp:2011-06-22-07:56:17,host_type_msg:[name:host_type_msg,eap_log_1:Receiving PP_Upload request from TCS...,xml_transaction:<?xml version="1.0"?>
<Transaction TxName="PP_Upload" Type="Request" MessageKey="0629">
    <Tool ToolID="CLB02" fromOPI="true" Type=""/>
    <Recipes>
        <Recipe RecipeID="APM_6MIN" MachineRecipeID="CLB02.APM_6MIN.AA" LocalFilePath="D:\share\110622_075617_304_0082706463_00" FilePath="" FormatFlag="1" Type="" RecipeLevel="1" Option=""/>
        <Recipe RecipeID="Production;MXIC_SC1_360;1" MachineRecipeID="CLB02.APM_6MIN.AA" LocalFilePath="D:\share\110622_075617_304_0082706463_01" FilePath="" FormatFlag="1" Type="" RecipeLevel="2" Option=""/>
        <Recipe RecipeID="Production;POR QDR-HCL;1" MachineRecipeID="CLB02.APM_6MIN.AA" LocalFilePath="D:\share\110622_075617_304_0082706463_02" FilePath="" FormatFlag="1" Type="" RecipeLevel="2" Option=""/>
        <Recipe RecipeID="Production;POR-philic;1" MachineRecipeID="CLB02.APM_6MIN.AA" LocalFilePath="D:\share\110622_075617_304_0082706463_03" FilePath="" FormatFlag="1" Type="" RecipeLevel="2" Option=""/>
    </Recipes>
</Transaction>],secs_type_msg:None],[name:msg,timestamp:2011-06-22-07:56:17,host_type_msg:[name:host_type_msg,eap_log_1:[S7F5] Sent by Host

,xml_transaction:None],secs_type_msg:None]]]

--------------------------------------------------------------
能看到這的人不是正常人,恭喜你。
正常的編譯器接下來進到 code generation。
而我們這次的題目,接下來呢?
應該要上色了。

2011年7月18日 星期一

[javascript] === 是什麼?

常常看到別人寫的程式裡面有

return token === TokenType.EQUAL;

這跟

return token == TokenType.EQUAL;

有什麼不同?

依據http://www.w3schools.com/js/js_comparisons.asp的說明

=== is exactly equal to (value and type)
所以
x = 5
x===5 is true
x==="5" is false
那我推測
x == 5 is true
x == “5” 應該是 true。

2011年7月17日 星期日

[編譯器的研究]parser的實作(1)

一般資料都說,遞迴下降的程式寫法,就是為每個非終端符號寫一個 function 來做 parse。
這裡提醒一些小事項。
(1) 準備兩個function,一個 accept,一個 expect。(註1)
def accept(s):
  global sym
  if sym[0] == s:
    print 'accept',sym
    getsym()
    return True
  return False

def expect(s):
  if accept(s):
    return True
  print "expect: unexpected symbol"
  global sym
  print sym
  sym = None
  return False

這些會有幾個用途:
(a)處理可省略項目的 rule,例如:
<host_type_msg> ::= eap_log_1 [xml_transaction]
accept 的意思是,如果傳入參數,是預期的就回 True,input 往下移動一個。
如果不是預期的,就回 False。呼叫的人看到回傳是 False 不停止動作。
expect 的意思是,如果傳入參數,是預期的就回 True,input 往下移動一個。
如果不是預期的,就回 False。呼叫的人看到回傳是 False 則停止動作。
所以 host_type_msg 的 parse function 寫成

def host_type_msg():
  if expect("eap_log_1"):
    accept("xml_transaction")
    return True
  else:
    return False
(b)accept可當做look ahead的測試工具,例如:
<secs_msg_item> ::= secs_item_a
                    | secs_item_u
                    | secs_item_b
                    | secs_item_bool
                    | secs_item_f
                    | secs_item_i
                    | secs_list_start [secs_comment] {<secs_msg_item>} secs_list_end
secs_msg_item 的 parse function 裡面可寫成
if accept("secs_item_a"):
  return True
elif accept("secs_item_u"):
  return True
....省略
elif accept("secs_list_start"):
  accept("secs_comment")
....省略

(2)每個 parse function 要回傳 True 或 False。
基本上,可以不用每個 parse function 都加。
但是有「出現0次或多次」這種規則的 parse function,需要回傳值才知道什麼時候要停下來。
因為這個規則的程式會轉換成迴圈,parse成功,才需要再 parse 否則就要停。
例如:
{<secs_msg_item>}
會轉成
r = secs_msg_item()
while r == True:
  r = secs_msg_item()

(3)因為以下的規則,必須使用 look ahead。
<desc> ::= <secs_msg_name> colon <secs_SxFy_sym>
          | normal_word {normal_word}
<secs_msg_name> ::= normal_word
因為 secs_msg_name() 的呼叫結果是 expect("normal_word")
在看到 input 的 normal_word 時,無法決定要呼叫 secs_msg_name()
還是 expect("normal_word"),可是往下看一個 input 若是 colon 就呼叫 secs_msg_name(),
否則呼叫 accept("normal_word")。

把 grammar 跟程式兩個互相比較,你就會知道有多直覺(前提是寫一些方便函數)
========================================================
<msg> ::= timestamp <secs_type_msg>
          | timestamp <host_type_msg>
--------------------------------------------------------
def msg():
  print 'msg()'
  if expect("timestamp"):
    if sym[0]=="eap_log_1":
      host_type_msg()
    else:
      secs_type_msg()
  print 'msg() complete'
========================================================
<host_type_msg> ::= eap_log_1 {xml_transaction}
--------------------------------------------------------
def host_type_msg():
  print 'host_type_msg()'
  expect("eap_log_1")
  accept("xml_transaction")
========================================================
<secs_type_msg> ::= <desc> secs_comment <secs_msg>
--------------------------------------------------------
def secs_type_msg():
  print 'secs_type_msg()'
  desc()
  accept("secs_comment")
  secs_msg()
========================================================
<desc> ::= <secs_msg_name> colon <secs_SxFy_sym>
          | normal_word {normal_word}
--------------------------------------------------------
def desc():
  print 'desc'
  global input_index
  _sym = lookahead()
  if _sym[0]=="colon":
    secs_msg_name()
    expect("colon")
    secs_SxFy_sym()
  else:
    r = accept("normal_word")
    while r == True:
      r = accept("normal_word")
========================================================
<secs_SxFy_sym> ::= secs_SxFy {<secs_w_bit>}
--------------------------------------------------------
def secs_SxFy_sym():
  print 'secs_SxFy_sym()'
  expect("secs_SxFy")
  secs_w_bit()
========================================================
<secs_msg_name> ::= normal_word
--------------------------------------------------------
def secs_msg_name():
  print 'secs_msg_name()'
  expect("normal_word")
========================================================
<secs_w_bit> ::= normal_word
--------------------------------------------------------
def secs_w_bit():
  print 'secs_w_bit'
  accept("normal_word")
========================================================
<secs_msg> ::= {<secs_msg_body>} secs_end
--------------------------------------------------------
def secs_msg():
  print 'secs_msg()'
  if accept("secs_end"):
    pass
  else:
    secs_msg_body()
    accept("secs_end")
========================================================
<secs_msg_body> ::= <secs_msg_item> {secs_comment }
--------------------------------------------------------
def secs_msg_body():
  print 'secs_msg_body()'
  secs_msg_item()
  accept("secs_comment")
========================================================
<secs_msg_item> ::= secs_item_a {secs_comment}
                    | secs_item_u {secs_comment}
                    | secs_item_b {secs_comment}
                    | secs_item_bool {secs_comment}
                    | secs_item_f {secs_comment}
                    | secs_item_i {secs_comment}
                    | secs_list_start {secs_comment} {<secs_msg_item>} secs_list_end
--------------------------------------------------------
def secs_msg_item():
  print 'secs_msg_item()'
  if accept("secs_item_a"):
    accept("secs_comment")
    return True
  elif accept("secs_item_u"):
    accept("secs_comment")
    return True
  elif accept("secs_item_b"):
    accept("secs_comment")
    return True
  elif accept("secs_item_bool"):
    accept("secs_comment")
    return True
  elif accept("secs_item_f"):
    accept("secs_comment")
    return True
  elif accept("secs_item_i"):
    accept("secs_comment")
    return True
  elif accept("secs_list_start"):
    accept("secs_comment")
    r = secs_msg_item()
    while r == True:
      r = secs_msg_item()
    expect("secs_list_end")
    return True
  else:
    return False
========================================================

相信有仔細看程式的人會注意到,怎麼 accept 就只是把 input 接受而已,
怎麼沒有 output ?
對的,再來就是要產生 parser 的 output,通常是長成 tree,
叫做 contrete syntax tree 或 parse tree 或 parsing tree。
或者是abstract syntax tree(把 parse tree 中,將來不需要用到的資訊去除)。
這兩種 tree 的差異,我認為只是相對的。程式可以控制你想表現的是哪一種 tree。
所以重點是要把 parse 的結果表現成 tree,讓後續的動作能利用。
當然把所有accept 的 token 都呈現的 tree 是 parse tree。
我們先從這裡開始。

(註1):http://en.wikipedia.org/wiki/Recursive_descent_parser

2011年7月15日 星期五

[ftp] ftp 指令 chmod 出現 invalid command.

問題是這樣出現的:
我在 windows xp 底下的 cmd 環境,用 ftp 指令連到 AIX 環境。
put 檔案之後,要改遠端的檔案的讀寫權限為 755。
直接再下 chmod 755 檔案
會出現 invalid command。
先用 remotehelp 查一下,有支援 SITE,而且 SITE 也支援 CHMOD,但直接下 site chmod 755 檔案 也失敗。

後來看到 http://www.net527.cn/a/caozuoxitong/Linux/2011/0203/16360.html
這裡試了一些 quote 或是 literal 的組合還是失敗。
但是,從這裡出發是對的,他應該少試了我成功的組合。

quote site chmod 755 檔案

quote 及 literal 都是傳送指令,我猜一定要加上 SITE 才能讓遠端執行遠端允許的指令,
加上要執行的指令後,也一定要把該指令的參數加上去,
不加完整的話,只下 quote site chmod
遠端也只回傳 500 ‘CHMOD’: command nto understood,
還以為遠端不支援呢。

2011年7月13日 星期三

[編譯器的研究]parser grammar

這次有找到新的參考,是國內大學(註1)的講義,因此把一些英文名詞換成中文來寫。
文章也比較順眼一點。
parser 的工作是語法分析(syntactic analysis),要完成這項工作,
需要兩個先決條件,一個是字彙分析(lexical analysis)的結果,
一個是文法(grammer)已經設計完畢。文法是由一組推導(derivation)所形成。
用白話一點的方式來說,parser的工作就是
找出字彙分析(lexical analysis)的結果是由哪些推導、依據什麼順序產生的。
找出這些推導及順序,記錄下來這些推導及順序,
就是所謂的建立 parse tree 了。

字彙分析(lexical analysis)的結果,之前已經有提過。
所以現在要來看一下文法的表示方式。
在一般的編譯器課程裡,大多使用的是 BNF 的表示方式(註2)。
之前的課本提到的方式則是CNF(註3)。

BNF 的表示方式的元素有(1)終端符號、(2)非終端符號、(3)指定符號、(4)選擇符號。
一個推導(derivation)規則,可以表示成

<符號> ::= __expression__

其中,<符號> 這是指非終端符號,所有的非終端符號都用 < > 括起來。
::= 這是指定符號,用來將表示式分成左邊跟右邊。
| 這個是選擇符號。
其他出現的符號,都算是終端符號。
在指定符號的左邊,只能出現非終端符號,
在指定符號的右邊,叫做 expression,因為他可以是所有符號的組合。
一個推導規則表示,在左邊的非終端符號,可以被替換右邊的 expression。
因為這個操作規則,只出現在右邊的符號,永遠不能被替換,
不能再被推導的符號叫做終端符號。
選擇符號是用來分隔不同的選擇。
一個綜合範例:

<A> ::= <B> | <C> + <D>

<A> <B> <C> <D> 是四個終端符號
+ 是終端符號
| 是分隔符號
這個規則,就是表示<A> 可以被替換成 <B> 或著是 <C> + <D>
一般而言,一組推導之中,列在最上面的那一個與別人不太一樣。
因為推導過程需要開始,而最開始的非終端符號就需要寫在最上面。

有了文法,就需要有尋找推導規則的方法,這個方法叫做剖析方法(parse)
有分成LL及LR兩大類。
LL = parse from left construct leftmost derivation
LR = parse from left construct rightmost derivation
因為文法的表示方式,可以表達到 context-free 語言,
而單純的 LL 及 LR 的尋找方法無法涵蓋整個 context-free 語言。
因此,大多的實作上,都要使用到LL(1) 或 LALR 來滿足實際需求。
選擇 LL 或是 LR 對於文法的設計,也會有一點點的影響。
例如使用 LL 方法的時候會遇到的 left recursion 的問題,
就要在設計文法的時候避開。

簡單講完文法設計的原則,就來試試設計自己的文法。
字彙分析(lexical analysis)的結果要回頭看一下 scanner 那篇的最後。
我設計文法如下:

<msg> ::= timestamp <desc> secs_comment <secs_msg> | timestamp eap_log_1 xml_transaction | timestamp eap_log_1
<desc> ::= <secs_msg_name> colon secs_SxFy <secs_w_bit> | <secs_msg_name> colon secs_SxFy
<secs_msg_name> ::= normal_word
<secs_w_bit> ::= normal_word
<secs_msg> ::= <secs_msg_body> secs_end | secs_end
<secs_msg_body> ::= <secs_msg_item> secs_comment | <secs_msg_item>
<secs_msg_item> ::= secs_item_a | secs_item_u | secs_item_b | secs_item_bool | secs_item_f | secs_item_i  | secs_list_start secs_comment <secs_msg_item> secs_list_end | secs_list_start <secs_msg_item> secs_list_end | secs_list_start secs_comment secs_list_end | secs_list_start secs_list_end

接下來挑選最符合人的思考的剖析方法叫做遞迴-下降方法,屬於LL類型。
因為這個方法想法直接,用程式來實作時非常直覺,
據說 c# 的 parser 也是用這種方法寫的。
在設計文法的時候儘量把同一個非終端符號寫在一行,
接下來檢查幾個項目,減少實作上的問題:
(1)檢查一下有無 left recursion 問題。
left recursion 的問題是就文法中,左邊的符號跟右邊的第一個符號一樣,
例如:
<a> ::= <a> + <b>
或是
<a> ::= <a> + c

(2)檢查文法中,有無需要「選擇」,
例如以下的文法,不先偷看一下,沒辦法知道選哪一個推導:

<desc> ::= <secs_msg_name> colon secs_SxFy <secs_w_bit> | <secs_msg_name> colon secs_SxFy

所以LL(0)是不太可能的。
如果希望LL(1)就能夠剖析,還是需要再改寫
<desc> 可以有兩個選擇,這兩個選擇前面都一樣,只差在尾巴,
一個有<secs_w_bit>,一個沒有。要先偷看的話,是要看到 4 個之後。
所以改寫成
<desc> ::= <secs_msg_name> colon <secs_SxFy_sym>
<secs_SxFy_sym> ::= secs_SxFy | secs_SxFy <secs_w_bit>
同理:
<msg> ::= timestamp <secs_type_msg>| timestamp <host_type_msg>
<secs_type_msg> ::= <desc> secs_comment <secs_msg>
<host_type_msg> ::= eap_log_1 | eap_log_1 xml_transaction

(3)把「出現0次或1次」、「出現0次或多次」、「出現1次或多次」的關係整理一下。
這一步需要用到多一點的BNF變種,{ 出現0次或多次 }、[出現0次或1次],
這樣寫在實作時也方便參考。
例如:

<secs_msg> ::= <secs_msg_body> secs_end | secs_end

改成

<secs_msg> ::= {<secs_msg_body>} secs_end

整理完的文法如下:

<msg> ::= timestamp <secs_type_msg>
          | timestamp <host_type_msg>
<secs_type_msg> ::= <desc> secs_comment <secs_msg>
<host_type_msg> ::= eap_log_1 {xml_transaction}
<desc> ::= <secs_msg_name> colon <secs_SxFy_sym>
          | normal_word {normal_word}
<secs_SxFy_sym> ::= secs_SxFy {<secs_w_bit>}
<secs_msg_name> ::= normal_word
<secs_w_bit> ::= normal_word
<secs_msg> ::= {<secs_msg_body>} secs_end
<secs_msg_body> ::= <secs_msg_item> {secs_comment }
<secs_msg_item> ::= secs_item_a {secs_comment}
                    | secs_item_u {secs_comment}
                    | secs_item_b {secs_comment}
                    | secs_item_bool {secs_comment}
                    | secs_item_f {secs_comment}
                    | secs_item_i {secs_comment}
                    | secs_list_start {secs_comment} {<secs_msg_item>} secs_list_end

接下來就可以開始把 parser 的程式寫出來

(註1):http://www.nhu.edu.tw/~ycliaw/Teaching/9901/SP/05_Compiler.pdf
(註2):http://en.wikipedia.org/wiki/Backus%E2%80%93Naur_Form
(註3):http://en.wikipedia.org/wiki/Chomsky_normal_form

2011年7月6日 星期三

[編譯器的研究]scanner

前面提到 scanner 的工作,是詞法分析,
針對這項工作,要依序解釋每個詞意義,也就是轉換成 token。
token 將是 parser 處理時的最小單位,scanner 的這項工作做不好,parser 也很難做得好。
因此 scanner 與 parser 通常是排在一起,因為相依性很高。

----------------------
程式的設計路線通常是跟實作路線反過來,我們在設計的過程會先想到需求,
把需求從抽象到實際從上到下一一描述出來,
而實作的時候,卻要由下往上一一實作,才能組合回去。
物件導向的程式語言,是可以幫上一點忙,一開始設計抽象物件,
但是程式要能夠跑起來,物件實作還是要寫好才會能夠動。
寫文章要講求思考脈絡又要講求實作順序,還真的傷腦筋。
----------------------

接下來要為 scanner 準備好它所需要的 function 及 class,
包含字串辨認及字串確定兩個工作。
scanner 要做的字串辨認工作,
要準備 Token 類別來儲存「字串辨認定義」、「字串辨認結果」的資訊及「字串辨認」的操作。
字串辨認結果,要包含字串類別,字串值。

舉一個 timestamp 的 Token 類別來說
Token 定義是 regular express 的 '[0-9]+-[0-9]+-[0-9]+-[0-9]+:[0-9]+:[0-9]+ '
Token 名字是 "timestamp"
Token 的 test 方法,會比對來源字串是否符合 pattern。
如果辨認成功,token.reg 為 true,token.value 為所要的值
因為要連續辨認,所以 test 的回傳值是一個新的 token 物件。
原來的物件留著下一次比對用。
為了輸出的時候,知道 Token 是在檔案的第幾個 byte 及第幾行,
也多兩個欄位 Token.cur_pos 及 Token.cur_line 來儲存訊息。

有人會想用 regular express,但是在 MIT 課程裡有特別提到
它無法應付 recursive,這是什麼意思呢?

我認為是這樣的。
例如,我們有兩個 token,一個是 timestamp token,及 number token。
在 log 裡,2011-06-22-07:56:06 會辨認到一個 timestamp token,及數個 number token。
依照最長辨認的原則,應該是選擇 timestamp token。
那要找到這裡面的年,月,日…等等的 number token,就沒辦法把它分開。
只得把它辨認為 number token,也就是把 timestamp token 的定義拿掉。
在 scanner 階段就無法區分 number token 是否為年,月,日…等等。
要等到 parser 階段才能決定。
但是,若辨認工作是自己實作特殊的 DFA 或 NFA,就可以自行增加 stack、queue 或 tape 等等不同的操作,
提供特殊辨認及輸出。例如處理 non-regular language,{a{n}b{n}},
只要在 DFA 裡面加一個變數記一下看到a有幾次,就可用程式的方法解決這個問題。

這個問題想到這裡就好,因為我們目前只求「大部 分解」因此,
timestamp token 裡面的年,月,日…等等就先不理會。
我個人認為若有必要,可以用二次 scan 來解決。
目標是先把大區塊標顏色,token 切太細,parser 的規則就會變多。
如果 token 切很細,parser 的規則又不想寫很多,那標出來的顏色就是花花緣緣的。
這是一個「資源取捨」問題。

因應各種辨認的問題,我們設計的 Token 類別實際上是個抽象類別。
如果是可以用 regular express 辨認的,就用 Token_RE 類別,
它的實例化方法需要一個 regular express,它的 test 方法就是該 regular express 來辨認。
另外,我們會遇到 XML transaction,因此特別寫了一個 Token_XML 類別,
把 root element <Transaction> 整個視為一個 xml transaction token。

在我們的範例 log,我就定了 16 個 token,實際有用到的是 15 個。

NULL_TOKEN = 0 ### 沒用到
TIMESTAMP_TOKEN = 1
SPACE_TOKEN = 2
NWORD_TOKEN = 3
COLON_TOKEN = 4
SECSSTREAMFUNCTION_TOKEN = 5
SECSCOMMENT_TOKEN = 6
WHITESPACE_TOKEN = 7
SECSEND_TOKEN = 8
SECSLISTSTART_TOKEN = 9
SECSITEMA_TOKEN = 10
SECSLISTEND_TOKEN = 11
EAPLOG1_TOKEN = 12
XMLTRANSACTION_TOKEN = 14
SECSITEMU_TOKEN = 15
SECSITEMB_TOKEN =16
SECSITEMBOOL_TOKEN = 17

這 15 個 token 裡 XMLTRANSACTION_TOKEN 是 Token_XML。其他都是 Token_RE,
Token_RE 的 test 方法,我偷懶用了 python 的 re module 的 match。
因為每次傳入的都是要從頭開始比,所以是 match 而不是 search。

class Token_RE(Token_abs):
  ### 省略部份
  def test(self,source):
    r = self.p_compile.match(source)
    if r == None:
      self.reg = False
      self.value = ""
      a = Token_RE(self.token_name,self.token_index,self.re_pattern)
      a.reg = self.reg
      a.value = self.value
      return a
    else:
      self.reg = True
      if len(r.groups())>0:
        self.value = r.groups(0)[0]
      else:
        self.value = r.group()

      a = Token_RE(self.token_name,self.token_index,self.re_pattern)
      a.reg = self.reg
      a.value = self.value
      return a

scanner 主要工作在 read 方法,大部分的實作都是執行一次 read 方法,就傳回一個 token。也就是在這裡就要做字串確定的工作。
在執行 read 方法之前,要把 log 讀進來,指定給 source 這個變數。
在執行 read 方法的時候,scanner 把 source 給 15 個 token 比對,找出比對成功的。
然後選擇比對結果最長的一個 token 當成辨認結果。
接下來,把 source 切掉已辨認的部份,剩下未辨認的部份。
傳回辨認結果之前,要順手把 cur_pos 及 cur_line 算完記下來,下次要用,
也要把值存進 token 裡,讓呼叫的人知道。
然後才是把 token 傳回去。
read 方法回傳 None 的情況有三個,代表 scanner 要停止辨認。
一個是 source 長度為零,因為上次辨認最後了。
一個是所有的 token 都沒有辨認到。
最後一個就是辨認結果的字串長度為零,這是一個很特別的狀況,也要停止辨認。
因為 regular express 有使用到 star operation 的時候,
也就是比對到 empty string 的狀況。
依照 scanner 的設計,empty string 長度為零,
source 等於沒有進行切除已辨認的動作,
下次 read 方法再執行時, source 還是原來的 source,
辨認結果還會是一樣的 empty string。
因此針這個情況要停下來,以免進入無窮迴圈。

class Scanner:
  ### 省略部份
  def Read(self):
    if len(self.source) == 0:
      return None
    r = []
    for i in self.tokens_info:
      r.append( i.test(self.source) )
    r2 = []
    resultlength=[]
    for i in r:
      if i.reg == True:
        r2.append(i)
        resultlength.append(len(i.value))
        print '  match:%s' % i
    if len(resultlength) == 0:
      print 'source a:%s' % self.source[:30]
      print 'pos:%s' % self.cur_pos
      print 'no match!!!!'
      return None
    maxlen = max(resultlength)
    if maxlen == 0:
      print 'source a:%s' % self.source[:30]
      print 'pos:%s' % self.cur_pos
      print 'no match!!!!'
      return None

    self.source = self.source[maxlen:]
    #print 'source b:%s' % self.source[:30]
    #print "new:%s\nold:%s" % (self.source[:40],self._keepsource[:40])
    #print r2
    for i in r2:
      if len(i.value) == maxlen:
        i.cur_pos = self.cur_pos
        i.cur_line = self.cur_line
        r3 = i
        self.cur_pos += maxlen
        line_object = re.findall("\\n",i.value)
        self.cur_line += len(line_object)
        break
    return r3 #r2[0]

scanner 使用方法,就是加入 token 定義,設定 source 的內容,
執行 read 方法直到回傳為 None。
每一次拿到的 token,裡面的資訊都有 token 的名字、內容、位置及行號。
接下來就是交給 parser 處理。

### token 定義
re_1 = '[0-9]+-[0-9]+-[0-9]+-[0-9]+:[0-9]+:[0-9]+' 
lex_timestamp = Token_RE("timestamp",TIMESTAMP_TOKEN, re_1)
### 其餘省略

### 加入 token 定義
s = Scanner()
s.AddTokenDefinition(lex_timestamp)
### 其餘省略

### 設定 source 內容
s.SetSource(source)

### 讀取 token
r =  s.Read()
while r != None:
  count += 1
  if r.token_index != 2 and r.token_index != 7:   ### 不想印出空白及分行
    print '    result(%s):%s' % (count,r)

因為還沒有想到 parser 要怎麼辦,所以先把 token 全寫到檔案裡,用三個連續分行符號分開。像這樣:

tokens(1):timestamp,1,True,0,0,2011-06-22-07:56:06

tokens(3):normal word,3,True,20,0,AYT_Host

tokens(4):colon,4,True,28,0,:

tokens(6):secs SxFy,5,True,30,0,'S1F1'

tokens(8):normal word,3,True,37,0,W

tokens(10):secs comment,6,True,39,0,/* Name=AreYouThere_Host Dir=2 Header=[00 00 81 01 00 00 00 01 BA A3] Rcvd=2 Time=07:56:06 TID=28848 */

tokens(12):secs end,8,True,143,1,.

tokens(14):timestamp,1,True,145,2,2011-06-22-07:56:06

tokens(16):normal word,3,True,165,2,OLD

tokens(17):colon,4,True,168,2,:

2011年7月3日 星期日

[編譯器的研究]文獻探討

在 MIT 的課程「6.035 電腦語言工程(SMA 5502),2005秋季課程」(註1)提到
課程裡要完成的 5 項工作,連接起來就會完成一個編譯器。
這 5 項工作是

掃描器和解析器、
語義檢查器、
代碼生成器、
資料流程優化器、
指令優化器

編譯器的工作裡,一開始是 scanner 及 parser (掃描器和解析器)。

scanner 的工作是把一連串的字元(character),轉換成一連串的token。(註2)(註3)
stream of characters --> stream of tokens
sequence fo tokens 是compiler所在乎的部份。

<---
(註1):http://www.myoops.org/main.php?act=course&id=1946
(註2):來源:http://www.myoops.org/twocw/mit/NR/rdonlyres/Electrical-Engineering-and-Computer-Science/6-035Fall-2005/EF1122A1-77A5-49CA-8BF6-EF6C9AC64F42/0/2scanerparsr_prj.pdf
(註3):token 的定義是可被當成處理的單元,它是由一連串的字元(sequence of character)所組成。
---->

parser 的工作是把一連串的token轉換成語言中的entities。
且把language的結構建構出來,通常的表達方式是樹狀結構。

在scanner及parser所產生的結果,可以用來替原始碼上色(註4)。

<----
(註4):http://www.cnblogs.com/Ninputer/archive/2011/06/07/2074632.html
---->

從字元轉換成 token 需要辨認recognize。
這個層級通常在regular language的範圍內。因此使用 DFA,NFA 的方式來實作。
或者利用現成的 regular expression 模組來做,但是無法處理 recursive。
所以自己寫 DFA/NFA 來處理是必須要會的。

例如一般程式語言會有保留字,及變數id。
這兩個是不同的意義,遇到保留字要做的事與遇到變數要做的事是不同的。
像是在c++語言裡面

string string1;

DFA,NFA做的工作是辨認,有一個 string 的字出現在第0個位置,是保留字。
有一個 string1 的字出現在第7個位置,是變數id
因為規則不同,所以需要兩個 DFA/NFA 來處理。
而一般的程式語言,會需要很多的意義。例如:

保留字
變數id
註解
常數
空白
分行
...

之前課本(註5)學寫的 DFA/NFA 的能力,是等待輸入結束後,
看最後一個state留在哪裡來判斷是否有被辨視到。
而對應於整串的程式語言,就不能等輸入結束後才來判斷。
針對這樣的需求,DFA/NFA 需要被修正。

需求是輸入還沒結束前,就判斷是否有辨認到屬於哪個類別。
並且在多個辨認都成立的狀況下,選擇最長辨認。
舉前面的例子,當一看到 string 的時候就當做辨認到 string 的話,
string string1 會被認為兩個 string 的保留字及一個 1 的常數。
因此, DFA/NFA 要一直辨視到失敗為止,
然後檢查最後一個 accept 的 state 是否存在。
若存在,才能算是正確的辨認。

<---
(註5):introduction to the theory of computation
--->

一個來自MIT上課程中,說明的 scanner 的工作應該是做到以下的轉換:

語言範例:
class Program {
    // uninformative comment
    void main() {
      callout("printf", "%d", 42);
    }
}

轉換結果:(原始結果沒有換行,為了排版才換行)
CLASS IDEN("Program") LBRACE VOID IDEN("main") LPAREN RPAREN LBRACE
CALLOUT LPAREN STR("printf") COMMA STR("%d") COMMA NUM(42) RPAREN
SEMI RBRACE RBRACE

在這個講義中,有趣的是,本來要用手寫的 scanner,後來可用 JLex 產生。

到了 parser 的階段,parse algorithm 有幾種,分類的方法也有好幾種。
按照處理 input 及 derivation 的方向來分,傳統的有 LL,LR。
按照組成 tree 的順序來分,有 top-down 及 bottom-up。
按照處理 transition 的方法來分,有 tabular 及 non-tabular。(註6)
按照 parser code 是誰寫的有分,hand-made 及 generator。

<----
(註6):http://people.few.eur.nl/pijls/parsing.pdf
---->

LL parser指的是從左邊開始處理字元(parse from left),
從左邊開始建構(construct leftmost derivation),
它自然就是屬於 top-down parser。
這種的 parser 可以使用 recursive descent,程式碼可用人來寫。
也可以轉化 grammer 成 parsing table 來查詢。
parsing table 可用手工或是用 generator 產生。
LL recursive descent 這種方法可以處理的 language
只是 context-free language 的一部份子集而不是全部,
因此有強化版本的 LL(k)版本,這種方法,會先偷看下 k 個輸入來判斷 state,
大多教科書舉例都是 LL(1) 的。
但是 LL(k) 仍然無法滿足所有的 context-free language。
(再延申下去還有LL(*),使用 regular expression 檢查下個 input)
兩種 LL parser 在製作 grammer 的時候,要注意 left recursion 的問題,
有固定的方法重排 grammer 以解決這個問題。
否則會出現 conflicts 的問題。
因為 LL 的 parser 處理邏輯非常直覺,教學上常會拿來當作範例,
容易用人來寫,也比較容易被人看懂。
(當然也有用 parser generator 產生的 LL parser。)

另一種傳統是 LR parser,從左邊開始處理字元(parse from left),
從右邊開始建構(construct rightmost derivation),
是屬於 bottom-up parser。
這種的 parser 可以使用 recursive ascent
但是大多數的實作上都是使用 generator 產生 parsing table,
所以大多數的是 tabular parser。
LR 在不進行偷看動作(lookahead)的時候(也就是 LR(0)),處理的 language 就比較廣,
但是還是會遇到無法處理的 grammer。
LR 的 parser 的程式碼相對比較複雜,比較難用人來寫,
同時 parser 的錯誤與 grammer 的錯誤比較難分別,
在 parse input 的時候可以產生比較清楚的錯誤訊息,但是難在 parser 要判斷發生哪種錯誤。(註7)

<----
(註7):http://en.wikipedia.org/wiki/LR_parser
---->

[編譯器的研究]開頭

不是資訊科系出身的我,非常敬畏compiler。
因為要看得懂人寫的東西,是非常困難的,
而compiler就是能夠看得懂。

這次研究的開頭是因為看到 google 啟動一個 traceur 的計畫。
這個計畫要介紹給大家 google 對於 javascript 語言的進化的規劃。
讓大家了解,新的 javascript 會有什麼新的語法及功能、
新的語法帶來什麼方便。
這個計畫,寫了一個 javascript to javascript 的轉換,
把新語法對應到舊語法。而且這工作是用 javascript 寫的。

這很可愛!

擴展一個語言的新功能,是重寫 compiler,這很正常。
可以用原來的語言再寫一個新的 compiler,這就有點可愛,不覺得嗎?
這一類的情況也不是只發生在這裡。
gnu 的 c/c++ compiler,python 的 pypy,也都是這一類的東西。

compiler 是一個很大的題目,陳公覺得我很無聊。
花這個時間能做出個 compiler 又怎樣?
是啊,以一個人來說,做出來的 compiler 應該也只是個玩具。
在這次研究中,我個人是希望,至少,
前面的 scanner + parser 階段,
可以開發個工具來 parse 工作上的 log,替 log 上色。
這個工具是吃 config 的,所以才要用到 compiler 的技術。
就不用每次換 log 都要重寫一次。
至於後續的部份,那就再看看囉。

2011年6月22日 星期三

[regexp]實作遇到的問題

在單一state的實作裡,若想到辨別三位以上,尾巴是91的數字。
當寫出  [1-9]+91,卻是無法簡單對應實作出來。

image

遇到991的時候,我們會希望,當第一個9出現時,要移到state 9。
接下來就會辨別出來。
而9991的時候,第一個9出現時,不能移到state 9。
這個情況出現就是說明有模糊的地方出現,也就是state 1-9這裡,
兩個transition可以同時成立,而產生模糊。
把模糊消除的方法就是增加state,每個transition的成立條件是互斥。
 image

當場就複雜很多了。沒有模糊是單一state實作的重要前提。
但是,想從這個圖轉回去regexp,又寫不出來。
雖然意義相同,但是要轉換需要一番工夫。
看來,有了多state的實作,轉換工作可以少一點。
可是多state的實作,本身就難倒我了。

把實作與理論合起來看,單一state的實作,就是DFA (deterministic finite automaton),因為每一個state,在看到input進來的時候,
就必須要決定唯一一個的下一個state。
而NFA (nondeterministic finite automaton)則是可以有多個可能的下個state。
因此實作上得要記得所有的可能的state。並對那些state做input測試。

既然看到NFA是真的比較容易讓人腦了解,設計時也比較容易。
單一state的實作應該就到此結束,開始來做多state的實作吧。