[英]How to implement a switch in Pharo Smalltalk
我試圖解析一個命令和一個int,以使“烏龜”動作在板上。 我有點不知所措,因為它沒有引發異常,而且我什至不知道如何在沒有調試器的情況下打開調試器。
我的代碼:
"only should begin parsing on new line"
endsWithNewLine:= aTurtleProgram endsWith: String cr.
endsWithNewLine ifTrue:[
"splits commands based on new lines"
commands := aTurtleProgram splitOn: String cr.
commands do:[:com |
"should split into command and value for command"
i := com splitOn: ' '.
"command"
bo := ci at: 1.
val := ci at: 2.
"value for command"
valInt := val asInteger.
^ bo = 'down' "attempted switch"
ifTrue: [ turtle down: valInt ]
ifFalse: [
bo = 'left'
ifTrue: [ turtle left: valInt ]
ifFalse: [
bo = 'right'
ifTrue: [ turtle right: valInt ]
ifFalse: [
bo = 'up'
ifTrue: [ turtle up: valInt ]
ifFalse: [ self assert: false ] ] ] ] ].
您可以按照自己的方式進行操作,也可以通過在代碼中插入self halt
語句來打開調試器。
通常情況下, if
S,而且案例sytle if
S,是一個不好的跡象。 所以你可以做的是打破了功能集成到諸如類DownMove
, LeftMove
等,然后當你打電話時,例如,每個類將實現其自身的功能execute:
方法將做什么是由命令需要的。 但是,對於您而言,這種方法將很麻煩。 此外,您的動作非常簡單。
您可以使用帶有定義的字典。 因此,假設您定義了一個實例變量或類變量:
moveActions := {
'down' -> [ :dist | turtle down: dist ] .
'left' -> [ :dist | turtle left: dist ] .
... } asDictionary
然后在您的代碼中執行: (moveActions at: bo) value: valInt
。 此代碼段將為您提供字符串(鍵)的塊(值),然后使用整數對塊進行評估。
另一方面,由於操作模式相同且僅消息有所更改,因此您只能在字典中映射消息名稱:
moveActions := {
'down' -> #down: .
'left' -> #left: .
... } asDictionary
然后,您可以要求烏龜執行由字符串動態給出的消息:
`turtle perform: (moveActions at: bo) with: valInt`
另外,如果您想依靠閱讀的命令和發送給烏龜的消息之間的相似性,則可以動態地編寫消息字符串:
`turtle perform: (bo, ':') asSymbol with: valInt`
請注意,在這種情況下不建議這樣做,因為首先,您正在將用戶輸入和代碼耦合在一起,即,如果您決定將用戶命令從down更改為moveDown ,則必須從down:
更改方法名稱。 down:
要moveDown:
。 另外,這種方法還允許用戶將錯誤代碼“注入”到您的系統中,因為他可以編寫諸如become 42
這樣的命令,這將導致代碼:
`turtle perform: #become: with: 42`
這將在烏龜對象和42之間交換指針。或者您可以考慮更糟的情況。 但我希望這種元游覽對您有好處。 :)
在Smalltalk中,您不使用switch語句。 相反,您使用“案例方法”(我認為術語是Ken Beck引入的,但我不確定)。
在您的情況下,將是這樣的:
method1
"only should begin parsing on new line"
endsWithNewLine:= aTurtleProgram endsWith: String cr.
endsWithNewLine ifTrue:[
"splits commands based on new lines"
commands := aTurtleProgram splitOn: String cr.
commands do:[ :eachCommand |
| tuple direction value |
tuple := eachCommand splitOn: ' '.
direction := tuple first.
value := tuple second asInteger.
self moveTurtleDirection: direction value: value ].
moveTurtleDirection: direction value: value
direction = 'down' ifTrue: [ ^turtle down: value ].
direction = 'left' ifTrue: [ ^turtle left: value ].
direction = 'right' ifTrue: [ ^turtle right: value ].
direction = 'up' ifTrue: [ ^turtle up: value ].
self error: 'Invalid direction'.
如您所見,這更加清楚了,您無需應用“小講堂魔術”即可獲得高效的設計。 這還有一個優點,就是清晰,執行速度快,並且易於被編譯器和 JIT優化:)
只是列舉了另一種可能性:與其自己編寫解析器,不如使用Smalltalk中可用的一種ParserGenerator(PetitParser,OMeta,Smacc,Xtreams等)。
這是Xtreams https://code.google.com/p/xtreams/wiki/Parsing的示例(鏈接可能很快就會消失,但是我沒有更新的...),可以解析PEG格式( http://en.wikipedia.org/wiki/Parsing_expression_grammar )。
首先,您在字符串中定義語法:
grammar := '
Number <- [0-9]+
Up <- "up" Number
Down <- "down" Number
Left <- "left" Number
Right <- "right" Number
Command <- Up / Down / Left / Right
'.
然后定義一個解釋器來移動烏龜:
PEGActor subclass: #TurtleInterpreter
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Test-Parser'.
通過幾種方法將解釋器連接到aTurtle,並通過所謂的編譯指示(注解)將動作連接到上述語法規則:
turtle: aTurtle
turtle := aTurtle
Number: digits
<action: 'Number'>
^digits inject: 0 into: [ :total :digit | total * 10 + ('0123456789' indexOf: digit) - 1 ]
Up: aNumber
<action: 'Up'>
turtle moveUp: aNumber
Down: aNumber
<action: 'Down'>
turtle moveDown: aNumber
Left: aNumber
<action: 'Left'>
turtle moveLeft: aNumber
Right: aNumber
<action: 'Right'>
turtle moveRight: aNumber
然后,您只需創建一個解析器並將其連接到此解釋器:
parser := PEGParser parserPEG
parse: 'Grammar'
stream: grammar
actor: PEGParserParser new.
interpreter := TurtleInterpreter new turtle: Turtle new.
parser parse: 'Command' stream: 'left24' actor: interpreter.
我讓您發現如何指定空格,換行符或命令序列,但是您會發現使用這種框架可以使代碼脫鈎並易於擴展:一條新命令=語法描述中的一行+解釋器中用於連接至的一種方法龜行動...
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.