Compare commits

...

8 Commits

Author SHA1 Message Date
BodgeMaster c00f788d27 Minor improvements all over the place 2022-11-06 06:22:20 +01:00
BodgeMaster b8b23249a0 Add runner portion of the interpreter
The interpreter is now complete. Apart from bugs that may be hiding in the code, it should work now.
2022-11-06 06:21:01 +01:00
BodgeMaster fbdf2e42f2 Implement remaining command_... functions for control structures 2022-11-06 06:18:07 +01:00
BodgeMaster f9f454625b Return formatted code from the parser so it can be used in error messages. 2022-11-06 03:51:31 +01:00
BodgeMaster 12dd5f50d6 Implement motion and take commands 2022-11-06 03:30:58 +01:00
BodgeMaster 5f6990050b Fix condition_goal_reached() and condition_on_apple() 2022-11-06 03:30:03 +01:00
BodgeMaster cf311ae3fa Implement condition functions 2022-11-06 02:24:21 +01:00
BodgeMaster 70456794a4 Start working on engine 2022-11-06 02:23:38 +01:00
1 changed files with 203 additions and 69 deletions

View File

@ -15,17 +15,25 @@
# version 3 along with this program. # version 3 along with this program.
# If not, see https://www.gnu.org/licenses/gpl-3.0.en.html # If not, see https://www.gnu.org/licenses/gpl-3.0.en.html
import re import re, time
debug_mode = True debug_mode = False
cursor_up = 'Λ' def debug_message(text):
cursor_down = 'V' if debug_mode:
cursor_left = '<' print("DEBUG: ", end="")
cursor_right = '>' print(text)
empty_field = [
" ________________ ", cursor_north = 'Λ'
cursor_south = 'V'
cursor_west = '<'
cursor_east = '>'
cursor_current = ' '
field = [
" _________________ ",
"| |", "| |",
"| |", "| |",
"| |", "| |",
@ -34,7 +42,7 @@ empty_field = [
"| |", "| |",
"| |", "| |",
"| |", "| |",
" ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ " " ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ "
] ]
wall = '#' wall = '#'
@ -43,58 +51,167 @@ goal = '$'
cursor_position = [0, 0] cursor_position = [0, 0]
command_delay = 0.3
def debug_message(text): def clear_field():
if debug_mode: field = [
print("DEBUG: ", end="") " _________________ ",
print(text) "| |",
"| |",
"| |",
"| |",
"| |",
"| |",
"| |",
"| |",
" ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ "
]
def condition_in_front_of_wall(inverted): def draw_field():
return False print("\033[2J\033[H")
for row in range(len(field)):
for column in range(len(field[row])):
if column==cursor_position[0] and row==cursor_position[1]:
print(cursor_current, end="")
else:
print(field[row][column], end="")
print("")
def condition_goal_reached(inverted):
return False
def condition_on_apple(inverted): # returns "" or error message
return False # Parsed code is the result of running the parser,
# though at this point the input is assumed to be valid
# and no additional checks are performed on what the parser produced.
def run_code(parsed_code):
for i in range(len(parsed_code[0])):
result = parsed_code[0][i](*parsed_code[1][i])
if not result=="":
return result
draw_field()
time.sleep(command_delay)
return ""
def condition_facing_north(inverted): def condition_facing_north(inverted):
return False if inverted:
return not cursor_current == cursor_north
return cursor_current == cursor_north
def condition_facing_south(inverted): def condition_facing_south(inverted):
return False if inverted:
return not cursor_current == cursor_south
return cursor_current == cursor_south
def condition_facing_east(inverted): def condition_facing_east(inverted):
return False if inverted:
return not cursor_current == cursor_east
return cursor_current == cursor_east
def condition_facing_west(inverted): def condition_facing_west(inverted):
return False if inverted:
return not cursor_current == cursor_west
return cursor_current == cursor_west
# condition prefixed with ! def condition_in_front_of_wall(inverted):
def modifier_not(condition): if condition_facing_north(False):
return condition(true) result = field[cursor_position[1]-1][cursor_position[0]]==wall or field[cursor_position[1]-1][cursor_position[0]]=='_'
elif condition_facing_south(False):
result = field[cursor_position[1]+1][cursor_position[0]]==wall or field[cursor_position[1]+1][cursor_position[0]]=='¯'
elif condition_facing_east(False):
result = field[cursor_position[1]][cursor_position[0]+1]==wall or field[cursor_position[1]][cursor_position[0]+1]=='|'
elif condition_facing_west(False):
result = field[cursor_position[1]][cursor_position[0]-1]==wall or field[cursor_position[1]][cursor_position[0]-1]=='|'
else:
result = False
if inverted:
return not result
return result
def condition_goal_reached(inverted):
if inverted:
return not field[cursor_position[1]][cursor_position[0]]==goal
return field[cursor_position[1]][cursor_position[0]]==goal
def condition_on_apple(inverted):
if inverted:
return not field[cursor_position[1]][cursor_position[0]]==apple
return field[cursor_position[1]][cursor_position[0]]==apple
# return value: "" or error message # return value: "" or error message
def command_step(): def command_step():
#TODO if condition_in_front_of_wall(False):
return "You stepped into a wall."
if condition_facing_north(False):
cursor_position[1] = cursor_position[1]-1
elif condition_facing_south(False):
cursor_position[1] = cursor_position[1]+1
elif condition_facing_east(False):
cursor_position[0] = cursor_position[0]+1
elif condition_facing_west(False):
cursor_position[0] = cursor_position[0]-1
else:
return "Cannot step because direction is unknown."
return "" return ""
def command_left(): def command_left():
global cursor_current
if condition_facing_north(False):
cursor_current = cursor_west
elif condition_facing_south(False):
cursor_current = cursor_east
elif condition_facing_east(False):
cursor_current = cursor_north
elif condition_facing_west(False):
cursor_current = cursor_south
else:
return "Cannot turn left because direction is unknown."
return "" return ""
def command_right(): def command_right():
return "" global cursor_current
if condition_facing_north(False):
def command_repeat(number, parsed_code): cursor_current = cursor_east
return "" elif condition_facing_south(False):
cursor_current = cursor_west
def command_while(parsed_condition, parsed_code): elif condition_facing_east(False):
cursor_current = cursor_south
elif condition_facing_west(False):
cursor_current = cursor_north
else:
return "Cannot turn right because direction is unknown."
return "" return ""
def command_take(): def command_take():
if condition_on_apple(True):
# Note: condition is inverted
return "Cannot take apple, there is no apple here."
field[cursor_position[1]] = field[cursor_position[1]][:cursor_position[0]]+" "+field[cursor_position[1]][cursor_position[0]+1:]
return ""
def command_repeat(number, parsed_code):
for i in range(number):
result = run_code(parsed_code)
if not result=="":
return result
return ""
def command_while(parsed_condition, parsed_code):
while parsed_condition[0](parsed_condition[1]):
result = run_code(parsed_code)
if not result=="":
return result
return "" return ""
def command_if(parsed_condition, parsed_then_code, parsed_else_code): def command_if(parsed_condition, parsed_then_code, parsed_else_code):
if parsed_condition[0](parsed_condition[1]):
result = run_code(parsed_then_code)
if not result=="":
return result
elif not parsed_else_code==None:
result = run_code(parsed_else_code)
if not result=="":
return result
return "" return ""
@ -196,7 +313,7 @@ def parse_code(code, allowed_commands, allowed_conditions, unformatted_code=True
debug_message("Formatted code: "+code) debug_message("Formatted code: "+code)
parse_position = 0 parse_position = 0
parsed_code = [[], [], -1, ""] parsed_code = [[], [], -1, "", code]
while parse_position < len(code): while parse_position < len(code):
# find next command # find next command
next_space = code.find(' ', parse_position) next_space = code.find(' ', parse_position)
@ -221,16 +338,16 @@ def parse_code(code, allowed_commands, allowed_conditions, unformatted_code=True
elif next_command == "take": elif next_command == "take":
parsed_code[0].append(command_take) parsed_code[0].append(command_take)
elif next_command == "repeat": elif next_command == "repeat":
return [[], [], next_space, "Syntax error: Condition missing"] return [[], [], next_space, "Syntax error: Number of repetitions missing", code]
elif next_command == "while": elif next_command == "while":
return [[], [], next_space, "Syntax error: Condition missing"] return [[], [], next_space, "Syntax error: Condition missing", code]
elif next_command == "if": elif next_command == "if":
return [[], [], next_space, "Syntax error: Condition missing"] return [[], [], next_space, "Syntax error: Condition missing", code]
elif next_command == "else": elif next_command[0:4] == "else":
return [[], [], parse_position, "Syntax error: Else without if statement"] return [[], [], parse_position, "Syntax error: Else without if statement", code]
else: else:
#TODO: better error message (especially when special chars are detected inside next_command or a known command) #TODO: better error message (especially when special chars are detected inside next_command or a known command)
return [[], [], parse_position, "Unknown command: "+next_command] return [[], [], parse_position, "Unknown command: "+next_command, code]
parse_position = next_space+1 parse_position = next_space+1
else: else:
debug_message("Found control structure...") debug_message("Found control structure...")
@ -241,28 +358,28 @@ def parse_code(code, allowed_commands, allowed_conditions, unformatted_code=True
repetitions_length = get_length_of_condition(code[parse_position:]) repetitions_length = get_length_of_condition(code[parse_position:])
if repetitions_length == -2: if repetitions_length == -2:
debug_message(" Mismatched <>") debug_message(" Mismatched <>")
return [[], [], parse_position, "Syntax error: Cannot not find matching '>' for this '<'"] return [[], [], parse_position, "Syntax error: Cannot not find matching '>' for this '<'", code]
repetitions = code[parse_position:parse_position+repetitions_length] repetitions = code[parse_position:parse_position+repetitions_length]
debug_message(" Number of repetitions: "+repetitions) debug_message(" Number of repetitions: "+repetitions)
repetitions_int = 0 repetitions_int = 0
try: try:
repetitions_int = int(repetitions[1:-1]) repetitions_int = int(repetitions[1:-1])
except ValueError: except ValueError:
return [[], [], parse_position, "Not a valid number: "+repetitions[1:-1]] return [[], [], parse_position, "Not a valid number: "+repetitions[1:-1], code]
parse_position = parse_position+len(repetitions) parse_position = parse_position+len(repetitions)
contained_code_length = get_length_of_contained_code(code[parse_position:]) contained_code_length = get_length_of_contained_code(code[parse_position:])
if contained_code_length == -1: if contained_code_length == -1:
debug_message(" Missing (") debug_message(" Missing (")
return [[], [], parse_position, "Syntax error: Cannot not find '(' for contained code"] return [[], [], parse_position, "Syntax error: Cannot not find '(' for contained code", code]
if contained_code_length == -2: if contained_code_length == -2:
debug_message(" Mismatched ()") debug_message(" Mismatched ()")
return [[], [], parse_position, "Syntax error: Cannot not find matching ')' for this '('"] return [[], [], parse_position, "Syntax error: Cannot not find matching ')' for this '('", code]
contained_code = code[parse_position:parse_position+contained_code_length] contained_code = code[parse_position:parse_position+contained_code_length]
debug_message(" Contained code: "+contained_code) debug_message(" Contained code: "+contained_code)
parsed_contained_code = parse_code(contained_code[1:-1], allowed_commands, allowed_conditions, unformatted_code=False) parsed_contained_code = parse_code(contained_code[1:-1], allowed_commands, allowed_conditions, unformatted_code=False)
if not parsed_contained_code[2]==-1: if not parsed_contained_code[2]==-1:
debug_message(" Error while parsing contained code") debug_message(" Error while parsing contained code")
return [[], [], parse_position+1+parsed_contained_code[2], parsed_contained_code[3]] return [[], [], parse_position+1+parsed_contained_code[2], parsed_contained_code[3], code]
parse_position = parse_position+contained_code_length+1 parse_position = parse_position+contained_code_length+1
parsed_code[1].append((repetitions_int, parsed_contained_code)) parsed_code[1].append((repetitions_int, parsed_contained_code))
@ -274,26 +391,26 @@ def parse_code(code, allowed_commands, allowed_conditions, unformatted_code=True
condition_length = get_length_of_condition(code[parse_position:]) condition_length = get_length_of_condition(code[parse_position:])
if condition_length == -2: if condition_length == -2:
debug_message(" Mismatched <>") debug_message(" Mismatched <>")
return [[], [], parse_position, "Syntax error: Cannot not find matching '>' for this '<'"] return [[], [], parse_position, "Syntax error: Cannot not find matching '>' for this '<'", code]
condition = code[parse_position:parse_position+condition_length] condition = code[parse_position:parse_position+condition_length]
debug_message(" Condition is: "+condition) debug_message(" Condition is: "+condition)
parsed_condition = parse_condition(condition[1:-1], allowed_conditions) parsed_condition = parse_condition(condition[1:-1], allowed_conditions)
if not parsed_condition[2]=="": if not parsed_condition[2]=="":
return [[], [], parse_position, parsed_condition[2]] return [[], [], parse_position, parsed_condition[2], code]
parse_position = parse_position+len(condition) parse_position = parse_position+len(condition)
then_code_length = get_length_of_contained_code(code[parse_position:]) then_code_length = get_length_of_contained_code(code[parse_position:])
if then_code_length == -1: if then_code_length == -1:
debug_message(" Missing (") debug_message(" Missing (")
return [[], [], parse_position, "Syntax error: Cannot not find '(' for then-code"] return [[], [], parse_position, "Syntax error: Cannot not find '(' for then-code", code]
if then_code_length == -2: if then_code_length == -2:
debug_message(" Mismatched ()") debug_message(" Mismatched ()")
return [[], [], parse_position, "Syntax error: Cannot not find matching ')' for this '('"] return [[], [], parse_position, "Syntax error: Cannot not find matching ')' for this '('", code]
then_code = code[parse_position:parse_position+then_code_length] then_code = code[parse_position:parse_position+then_code_length]
debug_message(" Then-code: "+then_code) debug_message(" Then-code: "+then_code)
parsed_then_code = parse_code(then_code[1:-1], allowed_commands, allowed_conditions, unformatted_code=False) parsed_then_code = parse_code(then_code[1:-1], allowed_commands, allowed_conditions, unformatted_code=False)
if not parsed_then_code[2]==-1: if not parsed_then_code[2]==-1:
debug_message(" Error while parsing then-code") debug_message(" Error while parsing then-code")
return [[], [], parse_position+1+parsed_then_code[2], parsed_then_code[3]] return [[], [], parse_position+1+parsed_then_code[2], parsed_then_code[3], code]
parse_position = parse_position+then_code_length+1 parse_position = parse_position+then_code_length+1
parsed_else_code = None parsed_else_code = None
if parse_position<len(code)+4 and code[parse_position:parse_position+4]=="else": if parse_position<len(code)+4 and code[parse_position:parse_position+4]=="else":
@ -301,10 +418,10 @@ def parse_code(code, allowed_commands, allowed_conditions, unformatted_code=True
else_code_length = get_length_of_contained_code(code[parse_position:]) else_code_length = get_length_of_contained_code(code[parse_position:])
if else_code_length == -1: if else_code_length == -1:
debug_message(" Missing (") debug_message(" Missing (")
return [[], [], parse_position, "Syntax error: Cannot not find '(' for else-code"] return [[], [], parse_position, "Syntax error: Cannot not find '(' for else-code", code]
if else_code_length == -2: if else_code_length == -2:
debug_message(" Mismatched ()") debug_message(" Mismatched ()")
return [[], [], parse_position, "Syntax error: Cannot not find matching ')' for this '('"] return [[], [], parse_position, "Syntax error: Cannot not find matching ')' for this '('", code]
else_code = code[parse_position:parse_position+else_code_length] else_code = code[parse_position:parse_position+else_code_length]
debug_message(" Else-code: "+else_code) debug_message(" Else-code: "+else_code)
parsed_else_code = parse_code(else_code[1:-1], allowed_commands, allowed_conditions, unformatted_code=False) parsed_else_code = parse_code(else_code[1:-1], allowed_commands, allowed_conditions, unformatted_code=False)
@ -319,26 +436,26 @@ def parse_code(code, allowed_commands, allowed_conditions, unformatted_code=True
condition_length = get_length_of_condition(code[parse_position:]) condition_length = get_length_of_condition(code[parse_position:])
if condition_length == -2: if condition_length == -2:
debug_message(" Mismatched <>") debug_message(" Mismatched <>")
return [[], [], parse_position, "Syntax error: Cannot not find matching '>' for this '<'"] return [[], [], parse_position, "Syntax error: Cannot not find matching '>' for this '<'", code]
condition = code[parse_position:parse_position+condition_length] condition = code[parse_position:parse_position+condition_length]
debug_message(" Condition is: "+condition) debug_message(" Condition is: "+condition)
parsed_condition = parse_condition(condition[1:-1], allowed_conditions) parsed_condition = parse_condition(condition[1:-1], allowed_conditions)
if not parsed_condition[2]=="": if not parsed_condition[2]=="":
return [[], [], parse_position, parsed_condition[2]] return [[], [], parse_position, parsed_condition[2], code]
parse_position = parse_position+len(condition) parse_position = parse_position+len(condition)
contained_code_length = get_length_of_contained_code(code[parse_position:]) contained_code_length = get_length_of_contained_code(code[parse_position:])
if contained_code_length == -1: if contained_code_length == -1:
debug_message(" Missing (") debug_message(" Missing (")
return [[], [], parse_position, "Syntax error: Cannot not find '(' for contained code"] return [[], [], parse_position, "Syntax error: Cannot not find '(' for contained code", code]
if contained_code_length == -2: if contained_code_length == -2:
debug_message(" Mismatched ()") debug_message(" Mismatched ()")
return [[], [], parse_position, "Syntax error: Cannot not find matching ')' for this '('"] return [[], [], parse_position, "Syntax error: Cannot not find matching ')' for this '('", code]
contained_code = code[parse_position:parse_position+contained_code_length] contained_code = code[parse_position:parse_position+contained_code_length]
debug_message(" Contained code: "+contained_code) debug_message(" Contained code: "+contained_code)
parsed_contained_code = parse_code(contained_code[1:-1], allowed_commands, allowed_conditions, unformatted_code=False) parsed_contained_code = parse_code(contained_code[1:-1], allowed_commands, allowed_conditions, unformatted_code=False)
if not parsed_contained_code[2]==-1: if not parsed_contained_code[2]==-1:
debug_message(" Error while parsing contained code") debug_message(" Error while parsing contained code")
return [[], [], parse_position+1+parsed_contained_code[2], parsed_contained_code[3]] return [[], [], parse_position+1+parsed_contained_code[2], parsed_contained_code[3], code]
parse_position = parse_position+contained_code_length+1 parse_position = parse_position+contained_code_length+1
parsed_code[1].append((parsed_condition, parsed_contained_code)) parsed_code[1].append((parsed_condition, parsed_contained_code))
@ -346,13 +463,30 @@ def parse_code(code, allowed_commands, allowed_conditions, unformatted_code=True
else: else:
debug_message(" Type: unknown control structure: "+control_structure) debug_message(" Type: unknown control structure: "+control_structure)
#TODO: better error message (especially when special chars are detected inside control_structure or a known command) #TODO: better error message (especially when special chars are detected inside control_structure or a known command)
return [[], [], parse_position, "Syntax error: Unknown control structure: "+control_structure] return [[], [], parse_position, "Syntax error: Unknown control structure: "+control_structure, code]
return parsed_code return parsed_code
# A wrapper for run_code that deals with potential errors produced by the parser.
# returns success or error message # returns success or error message
# Parsed code is the result of running the parser. def evaluate_parser_result(parsed_code):
def run_code(parsed_code): if not parsed_code[2]==-1:
return "" # parser error
return parsed_code[3] + "\n" + (" "*parsed_code[2]) + "\n" + parsed_code[4]
outcome = run_code(parsed_code)
if not outcome=="":
# runtime error
# TODO: pass back information about where the error occurred
return outcome
#TODO: check if goal reached
return "Success!"
def debug_setup():
global cursor_position
cursor_position = [1,1]
global cursor_current
cursor_current = cursor_south
draw_field()
if __name__ == "__main__": if __name__ == "__main__":
pass pass