Boo(19)-例外處理

例外處理的語法與 Python 相近,差別在於 Boo 使用 ensure,而 Python 使用 finally。
除此之外,Boo 統一使用 except 處理各種例外,而 Python 使用 else 處理無法處理的例外型態。
[python]
import System
class MyException(Exception):
_msg as string
def constructor( s as string ):
_msg = s
override def ToString() as string:
return “MyException::${_msg}”

// 試著調整這兩個變數試試看
isExceptionHappen = false
isMyExceptionHappen = true
try:
// .. do something …
if isExceptionHappen:
raise Exception(“Something wrong.”) // 提出例外情況
// …
if isMyExceptionHappen:
raise MyException(“Hey!!”)
except e as MyException:
print e.ToString()
except e as Exception:
print e.Message
ensure:
print “不管有沒有錯誤,這裡都會被執行。”
[/python]
參考:Boo Primer – 例外Python tutorial – 8. Errors and Exceptions

Boo(18)-命名空間

.NET上的語言幾乎都導入命名空間了,Boo 無法置身事外…

命名的方式,則是在原始檔第一行加上: namespace 命名空間名稱
撇開註解不算,命名空間的宣告,無論如何都要是程式碼的第一行,否則會有錯誤發生。

引用時,則是使用 import 關鍵字,例如:

import System
Console.WriteLine( "Hello world!" )
// 為甚麼要引用命名空間?因為這樣寫很累...
System.Console.WriteLine( "Hello again." )

你也可以指明組件(Assembly)的名稱,所以這幾種寫法也行:

import System.Data from System.Data
import Gtk from "gtk-sharp"

對了,組件不需要特別加上 “.dll”

Boo(17)-結構與列舉

結構(struct)跟類別很類似,最明顯的差別在於 class 被換成 struct 了,類別的一些特性也可以在結構上使用。
其他的差別:無法繼承類別、結構,只能實作 Interface﹔結構是值型別,在複製實體時,是整個克隆(Clone)而不是像類別一樣,只做參考。

struct Dog:
def constructor( name ):
_name=name
[property(Name)]
_name as string
emptydog=Dog()
print "emptydog.Name=${emptydog.Name}"  // 什麼都沒印出
lucky=Dog("Lucky")
print "lucky.Name=${lucky.Name}"  // 印出 Lucky

列舉(enum),如果你有用過 C/C++/C# 的話,應該不陌生:

// 宣告列舉
enum Day:
Sunday
Monday
Tuesday
Wednesday
Thursday
Friday
Sunday
// 也可以指定數值
enum Task:
TODO=100
FIXME=101
// 列印
print Day.Sunday
// 尋訪列舉型別裡所有元素
for s in Enum.GetNames(Day):
print s
// 另一種
for n,v in array( zip( Enum.GetNames(Task), Enum.GetValues(Task)) ):
print "${n}=${v}"

Boo(16)-Class

Boo 的類別(Class),跟 Python 很像,基本上不複雜。

class Animal:
pass
class Dog(Animal):
def constructor():
pass
def constructor( name ):
_name=name
def destructor():
pass
def Bark():
print "${_name} is barking..."
[property(Name)]
_name = "Anonymous"
spot=Dog( "spot" )
whity=Dog( Name:"whity" )
print spot.Name
whity.Bark()

class 跟 C# 一樣,可以加上 public、protected、internal、protected internal、private、abstract、final 等修飾詞,預設是 public。
繼承的話,就是在類別名稱後面加上小括號,並在括號內放置欲繼承的類別。
建構子與解構子分別是 constructor 與 destructor,可寫可不寫。
方法的宣告其實跟前面提到的函數很像,都是使用 def ,def 的前面還可以加上 abstract、static、virtual、override 等修飾詞。
最後是欄位,通常就跟寫運算式一樣,給定一個值就行了,像這樣:_name=””,前面的 [property()] 是 attribute,是一個偷懶的寫法,實際上是 get/set 的組合體:

class Cat(Animal):
def constructor():
pass
def constructor( name ):
_name=name
def destructor():
pass
def Meow():
print "${_name} is meowing..."
Name as string:
get:
return _name
set:
_name=value
_name = "Anonymous"

看到這裡,你有發現到這行嗎?whity=Dog( Name:”whity” )。咦,莫非在建構時可以直接指定屬性的值,沒錯,這寫起程式來方便很多啊~

參考資料:

Boo(15)-內建函數:容器操作

join()、map()、array()、matrix()、iterator()、enumerate()、range()、reversed()、zip()、cat()
這一類的函式還…蠻多的,大多都與 python 相容。
join(),把 Enumerator 裡面每個元素轉成字串,最後串成一個字串傳回。你也可以加上第二個引數,他會自動幫你加上,例如:join( [1,2,3,4,5], “:” ) 會得到 “1:2:3:4:5” 的字串。
map(),對 Enumerator 裡面每個元素施行指定的函式。
array(),傳入一個 Enumerator 回傳一個陣列。
matrix(),建立多維陣列。
iterator(),取得物件的 IEnumerable 介面,如果物件沒有 IEnumerable 介面,但有繼承 TextReader 的話,則改用 TextReaderEnumerator.lines() 取得 IEnumerable。這個函數在內部非常頻繁地被這裡提到的其他函數使用到。
enumerate(),先取得物件的 IEnumerable 介面,然後傳回類似 (index, value ) 的 Enumerator,舉例來說,List( enumerate( [ “a”, “b”, “c”, “d” ] ) ) 的結果會是:[(0, ‘a’), (1, ‘b’), (2, ‘c’), (3, ‘d’)]。
range() 很容易理解,傳入數值,會回傳有循序數值的 Enumerator,你也可以傳入起始與結束的數值或是傳入起始、結束與遞增數。
reversed(),將 Enumerator 裡面的元素以相反順序擺放,內部是使用 ReversedListEnumerator 類別來完成這件事情。
zip(),傳入多個 Enumerator,它會把每個 Enumerator 的第 0 個元素放到一起、第 1 個元素放到一起…以此類推,最後再傳回一個 Enumerator。這個函數看例子會比較容易了解,array(zip([‘a’,’b’,’c’],[4,5,6],[‘aa’,’bb’,’cc’])) 的結果會是 ((‘a’, 4, ‘aa’), (‘b’, 5, ‘bb’), (‘c’, 6, ‘cc’))。老實說,我還沒想到要怎麼用…
cat(),跟 join 有點像,不過不會傳回字串,而是把傳入的 Enumerator 串接起來成一個 Enumerator 再傳回。
這裡有的函數我沒舉例,要看例子的話,可以參考Boo Primer中文版對內建函數的說明

Boo(14)-內建函數:輸入與輸入

print、gets、prompt

print 就是調用 Console.WriteLine() 而已,官方建議使用 print macro,而不要使用這個函數。
gets 從標準輸入取得一個字串,實際上就是調用 Console.ReadLine()。
prompt 是 Console.ReadLine() + Console.Write() 的組合技,在印出你給的提示訊息之後,會接著從標準輸入取得字串。

從標準輸入取得字串的意思就是,畫面會停住,等你輸入字元,直到你按下 Enter 之後,才把你輸入的字元放到字串裡傳回。

print("Hello")
s = gets()
print s
s = prompt("Please input something:")
print s

當然,除了這些函數以外,你還是可以直接使用 .NET Framework 裡的 System.IO 來處理。

Boo(13)-內建函數:shell 類

shell()、shellp()、shellm()
顧名思義,就是執行外部的程式。

shell() 會等待外部程式執行完成以後,回傳一個字串,字串裡是執行的結果。
shellp() 不會等待外部程式執行完成,會直接回傳 Process 物件,事實上,shell() 也呼叫了這個函數,只是 shell() 拿到 Process 物件以後,利用 Process.StandardOutput() 去讀取執行結果,並使用 Process.WaitForExit() 等待程序執行完成。
shellm() 也是執行外部程式,但這個外部程式必須是 Managed,也就是 .NET 應用程式。老實說,看了 boo 源碼以後,我不是很懂。源碼裡面是建立一個新的 AppDomain,載入指定的程式,然後找到 EntryPoint 並執行。我猜想,這樣的作法主要用來避免再次建立新程序、啟動 CLR,在 CPU、記憶體使用上會比較有效率。如果你的外部程式正好也是 .NET 應用程式的話,就用 shellm(),我想會比較好。

input = shell( "booc.exe", "" )

booc 的 49 道工法

從 Visual Studio debugger 裡面截出來的…想不到編譯需要這麼多步驟…

-		_items	{維度:[64]}	object[]
+		[0]	{Boo.Lang.Parser.BooParsingStep}	object {Boo.Lang.Parser.BooParsingStep}
+		[1]	{Boo.Lang.Compiler.Steps.InitializeTypeSystemServices}	object {Boo.Lang.Compiler.Steps.InitializeTypeSystemServices}
+		[2]	{Boo.Lang.Compiler.Steps.PreErrorChecking}	object {Boo.Lang.Compiler.Steps.PreErrorChecking}
+		[3]	{Boo.Lang.Compiler.Steps.ExpandAstLiterals}	object {Boo.Lang.Compiler.Steps.ExpandAstLiterals}
+		[4]	{Boo.Lang.Compiler.Steps.MergePartialClasses}	object {Boo.Lang.Compiler.Steps.MergePartialClasses}
+		[5]	{Boo.Lang.Compiler.Steps.InitializeNameResolutionService}	object {Boo.Lang.Compiler.Steps.InitializeNameResolutionService}
+		[6]	{Boo.Lang.Compiler.Steps.IntroduceGlobalNamespaces}	object {Boo.Lang.Compiler.Steps.IntroduceGlobalNamespaces}
+		[7]	{Boo.Lang.Compiler.Steps.TransformCallableDefinitions}	object {Boo.Lang.Compiler.Steps.TransformCallableDefinitions}
+		[8]	{Boo.Lang.Compiler.Steps.BindTypeDefinitions}	object {Boo.Lang.Compiler.Steps.BindTypeDefinitions}
+		[9]	{Boo.Lang.Compiler.Steps.BindGenericParameters}	object {Boo.Lang.Compiler.Steps.BindGenericParameters}
+		[10]	{Boo.Lang.Compiler.Steps.BindNamespaces}	object {Boo.Lang.Compiler.Steps.BindNamespaces}
+		[11]	{Boo.Lang.Compiler.Steps.BindBaseTypes}	object {Boo.Lang.Compiler.Steps.BindBaseTypes}
+		[12]	{Boo.Lang.Compiler.Steps.BindAndApplyAttributes}	object {Boo.Lang.Compiler.Steps.BindAndApplyAttributes}
+		[13]	{Boo.Lang.Compiler.Steps.ExpandMacros}	object {Boo.Lang.Compiler.Steps.ExpandMacros}
+		[14]	{Boo.Lang.Compiler.Steps.IntroduceModuleClasses}	object {Boo.Lang.Compiler.Steps.IntroduceModuleClasses}
+		[15]	{Boo.Lang.Compiler.Steps.NormalizeStatementModifiers}	object {Boo.Lang.Compiler.Steps.NormalizeStatementModifiers}
+		[16]	{Boo.Lang.Compiler.Steps.NormalizeTypeAndMemberDefinitions}	object {Boo.Lang.Compiler.Steps.NormalizeTypeAndMemberDefinitions}
+		[17]	{Boo.Lang.Compiler.Steps.BindTypeDefinitions}	object {Boo.Lang.Compiler.Steps.BindTypeDefinitions}
+		[18]	{Boo.Lang.Compiler.Steps.BindGenericParameters}	object {Boo.Lang.Compiler.Steps.BindGenericParameters}
+		[19]	{Boo.Lang.Compiler.Steps.BindEnumMembers}	object {Boo.Lang.Compiler.Steps.BindEnumMembers}
+		[20]	{Boo.Lang.Compiler.Steps.BindBaseTypes}	object {Boo.Lang.Compiler.Steps.BindBaseTypes}
+		[21]	{Boo.Lang.Compiler.Steps.BindMethods}	object {Boo.Lang.Compiler.Steps.BindMethods}
+		[22]	{Boo.Lang.Compiler.Steps.ResolveTypeReferences}	object {Boo.Lang.Compiler.Steps.ResolveTypeReferences}
+		[23]	{Boo.Lang.Compiler.Steps.BindTypeMembers}	object {Boo.Lang.Compiler.Steps.BindTypeMembers}
+		[24]	{Boo.Lang.Compiler.Steps.ProcessInheritedAbstractMembers}	object {Boo.Lang.Compiler.Steps.ProcessInheritedAbstractMembers}
+		[25]	{Boo.Lang.Compiler.Steps.CheckMemberNames}	object {Boo.Lang.Compiler.Steps.CheckMemberNames}
+		[26]	{Boo.Lang.Compiler.Steps.ProcessMethodBodiesWithDuckTyping}	object {Boo.Lang.Compiler.Steps.ProcessMethodBodiesWithDuckTyping}
+		[27]	{Boo.Lang.Compiler.Steps.PreProcessExtensionMethods}	object {Boo.Lang.Compiler.Steps.PreProcessExtensionMethods}
+		[28]	{Boo.Lang.Compiler.Steps.UnfoldConstants}	object {Boo.Lang.Compiler.Steps.UnfoldConstants}
+		[29]	{Boo.Lang.Compiler.Steps.OptimizeIterationStatements}	object {Boo.Lang.Compiler.Steps.OptimizeIterationStatements}
+		[30]	{Boo.Lang.Compiler.Steps.BranchChecking}	object {Boo.Lang.Compiler.Steps.BranchChecking}
+		[31]	{Boo.Lang.Compiler.Steps.CheckIdentifiers}	object {Boo.Lang.Compiler.Steps.CheckIdentifiers}
+		[32]	{Boo.Lang.Compiler.Steps.StricterErrorChecking}	object {Boo.Lang.Compiler.Steps.StricterErrorChecking}
+		[33]	{Boo.Lang.Compiler.Steps.CheckAttributesUsage}	object {Boo.Lang.Compiler.Steps.CheckAttributesUsage}
+		[34]	{Boo.Lang.Compiler.Steps.ExpandDuckTypedExpressions}	object {Boo.Lang.Compiler.Steps.ExpandDuckTypedExpressions}
+		[35]	{Boo.Lang.Compiler.Steps.ProcessAssignmentsToValueTypeMembers}	object {Boo.Lang.Compiler.Steps.ProcessAssignmentsToValueTypeMembers}
+		[36]	{Boo.Lang.Compiler.Steps.ExpandProperties}	object {Boo.Lang.Compiler.Steps.ExpandProperties}
+		[37]	{Boo.Lang.Compiler.Steps.RemoveDeadCode}	object {Boo.Lang.Compiler.Steps.RemoveDeadCode}
+		[38]	{Boo.Lang.Compiler.Steps.CheckMembersProtectionLevel}	object {Boo.Lang.Compiler.Steps.CheckMembersProtectionLevel}
+		[39]	{Boo.Lang.Compiler.Steps.NormalizeIterationStatements}	object {Boo.Lang.Compiler.Steps.NormalizeIterationStatements}
+		[40]	{Boo.Lang.Compiler.Steps.ProcessSharedLocals}	object {Boo.Lang.Compiler.Steps.ProcessSharedLocals}
+		[41]	{Boo.Lang.Compiler.Steps.ProcessClosures}	object {Boo.Lang.Compiler.Steps.ProcessClosures}
+		[42]	{Boo.Lang.Compiler.Steps.ProcessGenerators}	object {Boo.Lang.Compiler.Steps.ProcessGenerators}
+		[43]	{Boo.Lang.Compiler.Steps.ExpandVarArgsMethodInvocations}	object {Boo.Lang.Compiler.Steps.ExpandVarArgsMethodInvocations}
+		[44]	{Boo.Lang.Compiler.Steps.InjectCallableConversions}	object {Boo.Lang.Compiler.Steps.InjectCallableConversions}
+		[45]	{Boo.Lang.Compiler.Steps.ImplementICallableOnCallableDefinitions}	object {Boo.Lang.Compiler.Steps.ImplementICallableOnCallableDefinitions}
+		[46]	{Boo.Lang.Compiler.Steps.CheckNeverUsedMembers}	object {Boo.Lang.Compiler.Steps.CheckNeverUsedMembers}
+		[47]	{Boo.Lang.Compiler.Steps.EmitAssembly}	object {Boo.Lang.Compiler.Steps.EmitAssembly}
+		[48]	{Boo.Lang.Compiler.Steps.SaveAssembly}	object {Boo.Lang.Compiler.Steps.SaveAssembly}

第 0 步由 Boo.Lang.Compiler.Pipelines.Parse (src\Boo.Lang.Compiler\Pipelines\Parse.cs) 加入。
第 1~27 步由 Boo.Lang.Compiler.Pipelines.ResolveExpressions (src\Boo.Lang.Compiler\Pipelines\ResolveExpressions.cs) 加入。
第 28~46 步由 Boo.Lang.Compiler.Pipelines.Compile (src\Boo.Lang.Compiler\Pipelines\Compile.cs)加入。
第 47 步由 Boo.Lang.Compiler.Pipelines.CompileToMemory (src\Boo.Lang.Compiler\Pipelines\CompileToMemory.cs) 加入。
第 48 步由 Boo.Lang.Compiler.Pipelines.CompileToFile (src\Boo.Lang.Compiler\Pipelines\CompileToFile.cs)加入。

這些步驟都是利用繼承的關係建立起來的:CompileToFile -> CompileToMemory -> Compile -> ResolveExpressions -> Parse
只應用了繼承的威力…

Boo(12)-函數

函數定義方法很簡單,比較特別的就是不定個數變數。

// Say
def Say( s as string):
print s
// 也是 Say
def Say( i as int):
print i
// 不定個數
def Say(*args as (object)):
print "len(args)=${len(args)}"
for arg in args:
print arg
// 求平方
def pow( i as int ) as int:
return i*i
Say( "Hello world!" )
Say( 20 )
Say( pow( 2 ) )
Say( 1, "s", join(range(10)) )
a = (5, 8, 1, "end")
Say(*a)

as string、as int…等,其實都可以省略不寫,別忘了 Boo 會自動判定。
然後有看到 Say() 定義了三次嗎?是的,Boo 支援多載(overloading)。
不定個數變數,定義的方法比較特別,要加上 *,然後用法就當作是 enumerator 來用就行了。

switch-case in boo

Boo本身並沒有類似 switch-case 語法,但是可以藉著 macro 來做到,Boo extensions這個專案已經寫好了。
由於這個專案沒有釋出二進位碼,所以你需要自己 checkout 並編譯。

編譯以後,用法也很簡單,一看就能懂了。

import Boo.PatternMatching  // match 與 case 這兩個 macro 都在這裡面
def getEnglish( i as int ) as string:
s = ""
match i:
case 0:
s = "zero"
case 1:
s = "one"
case 2:
s = "two"
case 3:
s = "three"
otherwise:
s = "unknown"
return s
l = array( typeof(int), range( 5 ) )
for item in l:
print getEnglish( item )

未來 Boo extensions 會包進 Boo 嗎?很難說…Boo extensions 目前仍然很具實驗性…

P.S.

  • 編譯Boo extensions前,請下載最新的 Boo,然後解壓縮以後,放到跟 boo-extensions 同一層。再切換到 boo-extensions/extensions 下執行 nant 即可。
  • 如果 Boo extensions 無法編譯成功,試著修改 extensions/default.build 將編譯 .Test.dll 的幾個地方註解掉,再次編譯即可。這些 .Test.dll 其實是用不到的。