It's been a while since I've written any Logo, so I'm not sure if this is the easiest way, but here's one way you can do it. The general idea is you can work with strings as lists of characters, using FIRST
, LAST
, BUTFIRST
, and BUTLAST
to get at different parts of the string. (I tested this on the first two online Logo interpreters I could find -- http://www.logointerpreter.com/turtle-editor.php and http://www.calormen.com/jslogo/ -- and it ran fine on both, but you might need some small changes for other Logo dialects.)
TO RUN_COMMANDS :commands
IF (EMPTY? :commands) [STOP]
MAKE "first_command (FIRST :commands)
MAKE "rest_of_commands (BUTFIRST :commands)
IF (NOT EMPTY? :rest_of_commands) [MAKE "split (GET_NUMBER :rest_of_commands ")]
MAKE "numeric_argument (LAST :split)
MAKE "rest_of_commands (FIRST :split)
RUN_SINGLE_COMMAND :first_command :numeric_argument
RUN_COMMANDS :rest_of_commands
END
TO MERGE_STRING :word :characters
IF (NOT EMPTY? :characters) [OP (MERGE_STRING (WORD :word (FIRST :characters)) (BUTFIRST :characters))]
OP :WORD
END
TO GET_NUMBER :word :number
IF (AND (NOT (EMPTY? :word)) (IS_DIGIT (FIRST :word))) [OP (SE (GET_NUMBER (BUTFIRST :word) (LPUT (FIRST :word) :number))]
OP (SE (MERGE_STRING " :word) (MERGE_STRING " :number))
END
TO IS_DIGIT :character
OP (OR
:character = "0
:character = "1
:character = "2
:character = "3
:character = "4
:character = "5
:character = "6
:character = "7
:character = "8
:character = "9)
END
TO RUN_SINGLE_COMMAND :command :parameter
(PRINT_COMMAND :command :parameter)
IF (:command = "F) [FD :parameter]
IF (:command = "B) [BK :parameter]
IF (:command = "L) [LT 90 FD :parameter]
IF (:command = "R) [RT 90 FD :parameter]
IF (:command = "N) [SETH 0 FD :parameter]
IF (:command = "S) [SETH 180 FD :parameter]
IF (:command = "E) [SETH 90 FD :parameter]
IF (:command = "W) [SETH 270 FD :parameter]
END
TO PRINT_COMMAND :command :parameter
IF (:command = "F) [PRINT (SE "FD :parameter)]
IF (:command = "B) [PRINT (SE "BK :parameter)]
IF (:command = "L) [PRINT (SE "LT 90 "FD :parameter)]
IF (:command = "R) [PRINT (SE "RT 90 "FD :parameter)]
IF (:command = "N) [PRINT (SE "SETH 0 "FD :parameter)]
IF (:command = "S) [PRINT (SE "SETH 180 "FD :parameter)]
IF (:command = "E) [PRINT (SE "SETH 90 "FD :parameter)]
IF (:command = "W) [PRINT (SE "SETH 270 "FD :parameter)]
END
Then, try running:
RUN_COMMANDS "F20N20E10L10
This prints and executes the following:
FD 20
SETH 0 FD 20
SETH 90 FD 10
LT 90 FD 10
Some Explanation
RUN_COMMANDS
is the main function. It:
- Extracts the first letter from the sting (I'm assuming each command is abbreviated as a single letter)
- Calls
GET_NUMBER
which extracts a number (which could be multiple characters) from the start of the string.
- Passes the single-letter abbreviated command and number to
RUN_SINGLE_COMMAND
- Recurses to repeat the process
IS_DIGIT
is used within GET_NUMBER
to check if a character is numeric (although I would bet some Logo dialects have a built-in function for this.)
MERGE_STRING
is used because I had some multi-character Words ("word" is Logo-speak for a string) which I had turned into Lists of single-character Words, and I wanted to merge the list back into a single Word. This might not actually be necessary, though.
RUN_SINGLE_COMMAND
executes each individual command that was parsed from the input string. I just used a big IF
statement, rather than using a function which interprets the string as code as you suggested. (Some Logo dialects might have such a function, but I'm not sure of a standard one.) RUN_SINGLE_COMMAND
also calls PRINT_COMMAND
, which prints out the unabbreviated command as it's run.
Potential Stack Overflows
I used lots of recursion because it's more idiomatic of Logo, and because Logo dialects often don't have a lot of looping constructs (other than REPEAT
). But I did it in a careless way, since I was just writing this quickly to give you the general idea. In particular, I didn't worry about stack overflows (no pun intended), which I think could occur if you provided a long input string. To deal with this, you should make sure any recursive path that can be called arbitrarily many times is a tail call, which Logo will optimize away.
At a glance, it looks like RUN_COMMANDS
is fine but MERGE_STRING
and GET_NUMBER
are reversed -- instead of IF <condition> [<recursive_call>]
followed by OUTPUT <return_value>
, it would be better to do IF (NOT <condition>) [OUTPUT <return_value>]
followed by <recursive_call>
. Testing for stack overflows and applying this fix I have left as an exercise for the reader. :)