lambda-v/lambdaV.py

481 lines
20 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#!/usr/bin/env python3
# Copyright 2022, Jan Danielzick (aka. BodgeMaster)
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published
# by the Free Software Foundation, version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# version 3 along with this program.
# If not, see https://www.gnu.org/licenses/gpl-3.0.en.html
import re
debug_mode = False
def debug_message(text):
if debug_mode:
print("DEBUG: ", end="")
print(text)
cursor_north = 'Λ'
cursor_south = 'V'
cursor_west = '<'
cursor_east = '>'
cursor_current = ' '
field = [
" _________________ ",
"| |",
"| |",
"| |",
"| |",
"| |",
"| |",
"| |",
"| |",
" ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ "
]
wall = '#'
apple = 'ó'
goal = '$'
cursor_position = [0, 0]
def clear_field():
field = [
" _________________ ",
"| |",
"| |",
"| |",
"| |",
"| |",
"| |",
"| |",
"| |",
" ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ "
]
def draw_field():
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("")
# returns "" or error message
# 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()
return ""
def condition_facing_north(inverted):
if inverted:
return not cursor_current == cursor_north
return cursor_current == cursor_north
def condition_facing_south(inverted):
if inverted:
return not cursor_current == cursor_south
return cursor_current == cursor_south
def condition_facing_east(inverted):
if inverted:
return not cursor_current == cursor_east
return cursor_current == cursor_east
def condition_facing_west(inverted):
if inverted:
return not cursor_current == cursor_west
return cursor_current == cursor_west
def condition_in_front_of_wall(inverted):
if condition_facing_north(False):
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
def command_step():
if condition_in_front_of_wall(False):
return "You walked 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 ""
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 ""
def command_right():
global cursor_current
if condition_facing_north(False):
cursor_current = cursor_east
elif condition_facing_south(False):
cursor_current = cursor_west
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 ""
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 ""
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 ""
# returns -1 for generic error
# returns -2 if end cant be found
def get_length_of_condition(code):
if len(code)==0:
return -1
if code[0] == '<':
length = 0
while length<len(code) and code[length]!='>':
length = length+1
if length==len(code) and code[-1]!='>':
return -2
return length+1
else:
return -1
# returns -1 for generic error
# returns -2 if end cant be found
def get_length_of_contained_code(code):
if len(code)==0:
return -1
if code[0] == '(':
length = 1
nesting_level = 1
while length<len(code) and nesting_level>0:
if code[length] == '(':
nesting_level = nesting_level+1
elif code[length] == ')':
nesting_level = nesting_level-1
length = length+1
if length==len(code) and nesting_level > 0:
return -2
return length
else:
return -1
# clean up whitespace
def format_code(code):
regex_pattern = re.compile(r'\s+')
code = " ".join(re.sub(regex_pattern, ' ', code).lower().split())
code = code.replace(" (", "(")
code = code.replace("( ", "(")
code = code.replace(" )", ")")
code = code.replace(") ", ")")
code = code.replace(" <", "<")
code = code.replace("< ", "<")
code = code.replace(" >", ">")
code = code.replace("< ", "<")
code = code.replace("! ", "!")
# add spaces back after closing parenthesis
code = code.replace(")", ") ")
code = code.replace(") )", "))")
code = code.replace(") )", "))")
if len(code)>0 and code[-1]==' ':
return code[:-1]
return code
# returns [function, inverted?, error message]
def parse_condition(condition_code, allowed_conditions):
inverted = False
condition = None
if len(condition_code)==0:
debug_message(" No condition specified")
return [condition, inverted, "Syntax error: No condition specified"]
if condition_code[0]=='!':
debug_message(" Condition is inverted")
inverted = True
condition_code = condition_code[1:]
if len(condition_code)==0:
debug_message(" No condition specified")
return [condition, inverted, "Syntax error: No condition specified"]
if condition_code == "in front of wall":
condition = condition_in_front_of_wall
elif condition_code == "goal reached":
condition = condition_goal_reached
elif condition_code == "on apple":
condition = condition_on_apple
elif condition_code == "facing north":
condition = condition_facing_north
elif condition_code == "facing south":
condition = condition_facing_south
elif condition_code == "facing east":
condition = condition_facing_east
elif condition_code == "facing west":
condition = condition_facing_west
else:
debug_message(" Unknown condition: "+condition_code)
return [condition, inverted, "Unknown condition: "+condition_code]
return [condition, inverted, ""]
# returns [[functions], [arguments], -1, ""] or [[], [], error_position, "error message"]
def parse_code(code, allowed_commands, allowed_conditions, unformatted_code=True):
if unformatted_code:
code = format_code(code)
debug_message("Formatted code: "+code)
parse_position = 0
parsed_code = [[], [], -1, "", code]
while parse_position < len(code):
# find next command
next_space = code.find(' ', parse_position)
next_condition = code.find('<', parse_position)
if next_space == -1:
next_space = len(code)
if next_condition == -1:
next_condition = len(code)
# Are we working with a simple command?
if next_condition>next_space or next_condition==next_space:
next_command = code[parse_position:next_space]
debug_message("Next command is: "+next_command)
parsed_code[1].append(())
#TODO: add function to to the function list
if next_command == "step":
parsed_code[0].append(command_step)
elif next_command == "left":
parsed_code[0].append(command_left)
elif next_command == "right":
parsed_code[0].append(command_right)
elif next_command == "take":
parsed_code[0].append(command_take)
elif next_command == "repeat":
return [[], [], next_space, "Syntax error: Condition missing", code]
elif next_command == "while":
return [[], [], next_space, "Syntax error: Condition missing", code]
elif next_command == "if":
return [[], [], next_space, "Syntax error: Condition missing", code]
elif next_command[0:4] == "else":
return [[], [], parse_position, "Syntax error: Else without if statement", code]
else:
#TODO: better error message (especially when special chars are detected inside next_command or a known command)
return [[], [], parse_position, "Unknown command: "+next_command, code]
parse_position = next_space+1
else:
debug_message("Found control structure...")
control_structure = code[parse_position:next_condition]
if control_structure == "repeat":
debug_message(" Type: repeat loop")
parse_position = next_condition
repetitions_length = get_length_of_condition(code[parse_position:])
if repetitions_length == -2:
debug_message(" Mismatched <>")
return [[], [], parse_position, "Syntax error: Cannot not find matching '>' for this '<'", code]
repetitions = code[parse_position:parse_position+repetitions_length]
debug_message(" Number of repetitions: "+repetitions)
repetitions_int = 0
try:
repetitions_int = int(repetitions[1:-1])
except ValueError:
return [[], [], parse_position, "Not a valid number: "+repetitions[1:-1], code]
parse_position = parse_position+len(repetitions)
contained_code_length = get_length_of_contained_code(code[parse_position:])
if contained_code_length == -1:
debug_message(" Missing (")
return [[], [], parse_position, "Syntax error: Cannot not find '(' for contained code", code]
if contained_code_length == -2:
debug_message(" Mismatched ()")
return [[], [], parse_position, "Syntax error: Cannot not find matching ')' for this '('", code]
contained_code = code[parse_position:parse_position+contained_code_length]
debug_message(" Contained code: "+contained_code)
parsed_contained_code = parse_code(contained_code[1:-1], allowed_commands, allowed_conditions, unformatted_code=False)
if not parsed_contained_code[2]==-1:
debug_message(" Error while parsing contained code")
return [[], [], parse_position+1+parsed_contained_code[2], parsed_contained_code[3], code]
parse_position = parse_position+contained_code_length+1
parsed_code[1].append((repetitions_int, parsed_contained_code))
parsed_code[0].append(command_repeat)
continue
elif control_structure == "if":
debug_message(" Type: if statement")
parse_position = next_condition
condition_length = get_length_of_condition(code[parse_position:])
if condition_length == -2:
debug_message(" Mismatched <>")
return [[], [], parse_position, "Syntax error: Cannot not find matching '>' for this '<'", code]
condition = code[parse_position:parse_position+condition_length]
debug_message(" Condition is: "+condition)
parsed_condition = parse_condition(condition[1:-1], allowed_conditions)
if not parsed_condition[2]=="":
return [[], [], parse_position, parsed_condition[2], code]
parse_position = parse_position+len(condition)
then_code_length = get_length_of_contained_code(code[parse_position:])
if then_code_length == -1:
debug_message(" Missing (")
return [[], [], parse_position, "Syntax error: Cannot not find '(' for then-code", code]
if then_code_length == -2:
debug_message(" Mismatched ()")
return [[], [], parse_position, "Syntax error: Cannot not find matching ')' for this '('", code]
then_code = code[parse_position:parse_position+then_code_length]
debug_message(" Then-code: "+then_code)
parsed_then_code = parse_code(then_code[1:-1], allowed_commands, allowed_conditions, unformatted_code=False)
if not parsed_then_code[2]==-1:
debug_message(" Error while parsing then-code")
return [[], [], parse_position+1+parsed_then_code[2], parsed_then_code[3], code]
parse_position = parse_position+then_code_length+1
parsed_else_code = None
if parse_position<len(code)+4 and code[parse_position:parse_position+4]=="else":
parse_position = parse_position+4
else_code_length = get_length_of_contained_code(code[parse_position:])
if else_code_length == -1:
debug_message(" Missing (")
return [[], [], parse_position, "Syntax error: Cannot not find '(' for else-code", code]
if else_code_length == -2:
debug_message(" Mismatched ()")
return [[], [], parse_position, "Syntax error: Cannot not find matching ')' for this '('", code]
else_code = code[parse_position:parse_position+else_code_length]
debug_message(" Else-code: "+else_code)
parsed_else_code = parse_code(else_code[1:-1], allowed_commands, allowed_conditions, unformatted_code=False)
parse_position = parse_position+else_code_length+1
parsed_code[1].append((parsed_condition, parsed_then_code, parsed_else_code))
parsed_code[0].append(command_if)
continue
elif control_structure == "while":
debug_message(" Type: while loop")
parse_position = next_condition
condition_length = get_length_of_condition(code[parse_position:])
if condition_length == -2:
debug_message(" Mismatched <>")
return [[], [], parse_position, "Syntax error: Cannot not find matching '>' for this '<'", code]
condition = code[parse_position:parse_position+condition_length]
debug_message(" Condition is: "+condition)
parsed_condition = parse_condition(condition[1:-1], allowed_conditions)
if not parsed_condition[2]=="":
return [[], [], parse_position, parsed_condition[2], code]
parse_position = parse_position+len(condition)
contained_code_length = get_length_of_contained_code(code[parse_position:])
if contained_code_length == -1:
debug_message(" Missing (")
return [[], [], parse_position, "Syntax error: Cannot not find '(' for contained code", code]
if contained_code_length == -2:
debug_message(" Mismatched ()")
return [[], [], parse_position, "Syntax error: Cannot not find matching ')' for this '('", code]
contained_code = code[parse_position:parse_position+contained_code_length]
debug_message(" Contained code: "+contained_code)
parsed_contained_code = parse_code(contained_code[1:-1], allowed_commands, allowed_conditions, unformatted_code=False)
if not parsed_contained_code[2]==-1:
debug_message(" Error while parsing contained code")
return [[], [], parse_position+1+parsed_contained_code[2], parsed_contained_code[3], code]
parse_position = parse_position+contained_code_length+1
parsed_code[1].append((parsed_condition, parsed_contained_code))
parsed_code[0].append(command_while)
else:
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)
return [[], [], parse_position, "Syntax error: Unknown control structure: "+control_structure, code]
return parsed_code
# A wrapper for run_code that deals with potential errors produced by the parser.
# returns success or error message
def evaluate_parser_result(parsed_code):
if not parsed_code[2]==-1:
# 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!"
if __name__ == "__main__":
pass