How to implement a switch in Pharo Smalltalk

I am trying to parse the command and int to make a "turtle" on the board. I am a little overloaded, as this is no exception, and I can’t even figure out how to open the debugger without it.

My code is:

"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 ] ] ] ] ]. 
+6
source share
3 answers

You can do it the way you did it, and you can open the debugger by inserting the self halt instruction in your code.

Usually if s, as well as case-sytle if s, are a bad sign. So what you can do is break the functionality into classes such as DownMove , LeftMove , etc., and then each class will implement its own functions when you call, for example, the execute: method, which will do exactly that, what a team needs. But in your case, the approach will be cumbersome; in addition, you have very trivial actions.

You can use a dictionary with definitions. So imagine you are defining an instance variable or a class variable:

 moveActions := { 'down' -> [ :dist | turtle down: dist ] . 'left' -> [ :dist | turtle left: dist ] . ... } asDictionary 

Then in your code you do: (moveActions at: bo) value: valInt . This snippet will give you a block (value) for a string (key), then you evaluate a block with an integer.

On the other hand, since the action template is the same and only the message is changed, you can only display message names in the dictionary:

 moveActions := { 'down' -> #down: . 'left' -> #left: . ... } asDictionary 

Then you can ask your turtle to execute the dynamic message given by the line:

 `turtle perform: (moveActions at: bo) with: valInt` 

In addition, if you want to rely on the similarities between the commands you read and the messages you send to the turtle, you can dynamically compose a message line:

 `turtle perform: (bo, ':') asSymbol with: valInt` 

Please note that this is not recommended in your case, because, firstly, you bind user input and your code, that is, if you decide to change the user command down to moveDown, you will have to change your method name from down: to moveDown: . In addition, this approach allows the user to "inject" bad code into your system, as he can write a command such as become 42 , which will lead to the code:

 `turtle perform: #become: with: 42` 

which changes pointers between the tortoise object and 42. Or you can think of even worse cases. But I hope this meta tour was good for you. :)

+7
source

In Smalltalk, you do not use switch statements. Instead, you use "case methods" (I think the terminology was introduced by Ken Back, but I'm not sure).

In your case, it will be something like this:

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

As you can see, this is much more understandable, and you do not need to apply β€œlittle magic” to have an effective design. It also has the advantage of being crisp, fast to execute, and easily optimized by the compiler and JIT :)

+4
source

Just to point out another possibility: instead of writing Parser yourself, rather use one of the ParserGenerator available in Smalltalk (PetitParser, OMeta, Smacc, Xtreams, ...)

Here is an example from Xtreams https://code.google.com/p/xtreams/wiki/Parsing (the link will die soon, but I have nothing new ...) that can analyze the PEG format ( http: // en. wikipedia.org/wiki/Parsing_expression_grammar ).

First you define your grammar in a line:

 grammar := ' Number <- [0-9]+ Up <- "up" Number Down <- "down" Number Left <- "left" Number Right <- "right" Number Command <- Up / Down / Left / Right '. 

Then you define an interpreter to move the turtle:

 PEGActor subclass: #TurtleInterpreter instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Test-Parser'. 

using several methods of connecting the interpreter to aTurtle and associate the action with the grammar rules above using the so-called pragmas (annotations):

 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 

Then you simply create a parser and connect it to this interpreter:

 parser := PEGParser parserPEG parse: 'Grammar' stream: grammar actor: PEGParserParser new. interpreter := TurtleInterpreter new turtle: Turtle new. parser parse: 'Command' stream: 'left24' actor: interpreter. 

I’ll tell you how to specify spaces, newlines or a sequence of commands, but you see how your code can be decoupled and easily extensible: one new command = one line in the grammar description + one method in the interpreter to connect to the action Turtles ...

+2
source

Source: https://habr.com/ru/post/988079/


All Articles