lambda-v/lambdaV.py

384 lines
16 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("")
def condition_in_front_of_wall(inverted):
return False
def condition_goal_reached(inverted):
return False
def condition_on_apple(inverted):
return False
def condition_facing_north(inverted):
return False
def condition_facing_south(inverted):
return False
def condition_facing_east(inverted):
return False
def condition_facing_west(inverted):
return False
# condition prefixed with !
def modifier_not(condition):
return condition(true)
# return value: "" or error message
def command_step():
#TODO
return ""
def command_left():
return ""
def command_right():
return ""
def command_repeat(number, parsed_code):
return ""
def command_while(parsed_condition, parsed_code):
return ""
def command_take():
return ""
def command_if(parsed_condition, parsed_then_code, parsed_else_code):
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, ""]
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"]
elif next_command == "while":
return [[], [], next_space, "Syntax error: Condition missing"]
elif next_command == "if":
return [[], [], next_space, "Syntax error: Condition missing"]
elif next_command == "else":
return [[], [], parse_position, "Syntax error: Else without if statement"]
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]
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 '<'"]
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]]
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"]
if contained_code_length == -2:
debug_message(" Mismatched ()")
return [[], [], parse_position, "Syntax error: Cannot not find matching ')' for this '('"]
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]]
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 '<'"]
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]]
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"]
if then_code_length == -2:
debug_message(" Mismatched ()")
return [[], [], parse_position, "Syntax error: Cannot not find matching ')' for this '('"]
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]]
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"]
if else_code_length == -2:
debug_message(" Mismatched ()")
return [[], [], parse_position, "Syntax error: Cannot not find matching ')' for this '('"]
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 '<'"]
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]]
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"]
if contained_code_length == -2:
debug_message(" Mismatched ()")
return [[], [], parse_position, "Syntax error: Cannot not find matching ')' for this '('"]
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]]
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]
return parsed_code
# returns success or error message
# Parsed code is the result of running the parser.
def run_code(parsed_code):
return ""
if __name__ == "__main__":
pass