I'd like to get string from user, parse it, then run the parsed commands.
The string input will be something like "F20N20E10L10", guaranteed no spaces.
This input I want to convert to LOGO commands with substitutions like these:
fd
seth 0 fd
seth 90 fd
lt 90 fd
So the string input above would be converted to these LOGO commands:
fd 20 seth 0 fd 20 seth 90 fd 10 lt 90 fd 10
All Forth dialects allow input, and interpreting a string of commands.
But I can't find any with search and replace string operations. Is this possible in any dialect of LOGO? Willing to consider any.
Thank you for reading.
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:
GET_NUMBER
which extracts a number (which could be multiple characters) from the start of the string.RUN_SINGLE_COMMAND
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. :)