123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293 |
- import shlex, term, os, subprocess, sys
- #region
- # This will be moved to ProtoLib
- import ctypes
- from ctypes import c_long, c_wchar_p, c_ulong, c_void_p
- gHandle = ctypes.windll.kernel32.GetStdHandle(c_long(-11))
- def move (y, x):
- """Move cursor to position indicated by x and y."""
- value = x + (y << 16)
- ctypes.windll.kernel32.SetConsoleCursorPosition(gHandle, c_ulong(value))
- def addstr (string):
- """Write string"""
- ctypes.windll.kernel32.WriteConsoleW(gHandle, c_wchar_p(string), c_ulong(len(string)), c_void_p(), None)
- #endregion
- def strtobool(val):
- """Convert a string representation of truth to true (1) or false (0).
- True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values
- are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if
- 'val' is anything else.
- """
- val = val.lower()
- if val in ('y', 'yes', 't', 'true', 'on', '1'):
- return True
- elif val in ('n', 'no', 'f', 'false', 'off', '0'):
- return False
- else:
- raise ValueError("invalid truth value %r" % (val,))
- def _str(input):
- return str(input)
- def _int(input):
- return int(input)
- def _float(input):
- return float(input)
- def _null(val):
- val = val.lower()
- if val in ('null', 'none', 'nil'):
- return None
- else:
- raise ValueError("invalid truth value %r" % (val,))
- class FeatherScriptProcessor:
- def __init__(self):
- self.data = {}
- self.data_types = {
- 'str': _str,
- 'int': _int,
- 'float': _float,
- 'bool': strtobool,
- 'null': _null,
- }
- self.function_return_value = None
- def process_string(self, input: str, replace_line_break_escape_sequence=True):
- out = input
- for name in self.data:
- if '$' + name in out:
- if self.data[name]['obj_type'] == 'function':
- if '$' + name in input:
- out = out.replace('$' + name, str(self.handle_function(name)))
- elif self.data[name]['obj_type'] == 'collide':
- if '$' + name in input:
- out = out.replace('$' + name, str(self.handle_collision(name)))
- else:
- out = out.replace('$' + name, str(self.data[name]['value']))
- if replace_line_break_escape_sequence:
- out = out.replace('\\n', '\n')
- return out
- def handle_function(self, name):
- self.interpret_code(self.data[name]['value'])
- out = self.function_return_value
- self.function_return_value = None
- return out
- def handle_collision(self, name):
- return name
- def process_data_type(self, _type: str, data: str):
- if _type.lower() in self.data_types:
- return self.data_types[_type](data)
- else:
- return None
- def interpret_code(self, code: str):
- lines = code.strip().replace(";", "\n").split("\n")
- line_number = 0
- while line_number < len(lines):
- line = lines[line_number]
- # Remove comments from the line
- line = line.split("#")[0].strip()
- if not line:
- line_number += 1
- continue
- parts = shlex.split(line)
- # print(parts, line_number + 1) # DEBUG
- if parts[0].lower() == "echo":
- print(self.process_string(parts[1]))
- elif parts[0].startswith('$'):
- parts[0] = parts[0].split('$', 1)[1]
- if self.data[parts[0]]['obj_type'] == 'function':
- self.handle_function(parts[0])
- elif self.data[parts[0]]['obj_type'] == 'collide':
- self.handle_collision(parts[0])
- else:
- raise TypeError(f'{self.data[parts[0]]["obj_type"]} can\'t be executed as function')
- elif parts[0].lower() == "write":
- term.write(self.process_string(parts[1]))
- elif parts[0].lower() == "cursor":
- if os.name == 'nt':
- move(int(parts[2]), int(parts[1]))
- else:
- term.pos(int(parts[1]), int(parts[2]))
- elif parts[0].lower() == "clear":
- if os.name == 'nt':
- subprocess.run('cls')
- else:
- subprocess.run('clear')
- elif parts[0].lower() == "exec":
- with open(parts[1]) as f:
- script = f.read()
- f.close()
- self.interpret_code(script)
- elif parts[0].lower() == "var":
- _content = self.process_data_type(parts[1].lower(), parts[3])
- _type = parts[1].lower()
- if parts[2] in self.data:
- if self.data[parts[2]]['obj_type'] == 'constant':
- print('A constant with that name has already been defined')
- break
- elif self.data[parts[2]]['obj_type'] == 'function':
- print('A function with that name has already been defined')
- break
- self.data[parts[2]] = {
- 'obj_type': 'variable',
- 'data_type': _type,
- 'value': _content
- }
- elif parts[0].lower() == "const":
- _content = self.process_data_type(parts[1].lower(), parts[3])
- _type = parts[1].lower()
- if parts[2] in self.data:
- if self.data[parts[2]]['obj_type'] == 'constant':
- print('A constant with that name has already been defined')
- break
- elif self.data[parts[2]]['obj_type'] == 'function':
- print('A function with that name has already been defined')
- break
- self.data[parts[2]] = {
- 'obj_type': 'constant',
- 'data_type': _type,
- 'value': _content
- }
- elif parts[0].lower() == "func":
- func = ''
- name = parts[1]
- line_number += 1
- while line_number < len(lines):
- if lines[line_number].startswith('func'):
- raise SyntaxError('Functions can\'t be defined in functions')
- if lines[line_number].startswith('endfunc'):
- break
- func += lines[line_number] + '\n'
- line_number += 1
- if name in self.data:
- if self.data[name]['obj_type'] == 'constant':
- raise ValueError('A constant with that name has already been defined')
- elif self.data[name]['obj_type'] == 'function':
- raise ValueError('A function with that name has already been defined')
- elif self.data[name]['obj_type'] == 'collide':
- raise ValueError('A colliding function with that name has already been defined')
- self.data[name] = {
- 'obj_type': 'function',
- 'data_type': 'str',
- 'value': func
- }
- elif parts[0].lower() == "collide":
- func = ''
- lang = parts[1]
- name = parts[2]
- line_number += 1
- while line_number < len(lines):
- if lines[line_number].startswith('func'):
- raise SyntaxError('Functions can\'t be defined in colliding functions')
- elif lines[line_number].startswith('collide'):
- raise SyntaxError('Colliding functions can\'t be defined in colliding functions')
- if lines[line_number].startswith('endcollide'):
- break
- func += lines[line_number] + '\n'
- line_number += 1
- if name in self.data:
- if self.data[name]['obj_type'] == 'constant':
- raise ValueError('A constant with that name has already been defined')
- elif self.data[name]['obj_type'] == 'function':
- raise ValueError('A function with that name has already been defined')
- elif self.data[name]['obj_type'] == 'collide':
- raise ValueError('A colliding function with that name has already been defined')
- self.data[name] = {
- 'obj_type': 'collide',
- 'data_type': 'str',
- 'value': func,
- 'lang': lang
- }
- elif parts[0].lower() == "if":
- exec_code = ''
- a, b = None, None
- if parts[1].startswith('$'):
- a = str(self.process_string(parts[1]))
- else:
- a = str(self.process_data_type(*parts[1].split(':', 1)))
- if parts[3].startswith('$'):
- b = str(self.process_string(parts[3]))
- else:
- b = str(self.process_data_type(*parts[3].split(':', 1)))
- def handle_true(line_number, exec_code):
- line_number += 1
- while line_number < len(lines):
- if lines[line_number].startswith('endif') or lines[line_number].startswith('else'):
- break
- if lines[line_number].startswith('func'):
- raise SyntaxError('Functions can\'t be defined in if statements')
- elif lines[line_number].startswith('collide'):
- raise SyntaxError('Colliding functions can\'t be defined in if statements')
- exec_code += lines[line_number] + '\n'
- line_number += 1
- if lines[line_number].startswith('else'):
- line_number += 1
- while line_number < len(lines):
- if lines[line_number].startswith('endif'):
- break
- line_number += 1
- return line_number, exec_code
- def handle_false(line_number, exec_code):
- while line_number < len(lines):
- if lines[line_number].startswith('func'):
- raise SyntaxError('Functions can\'t be defined in if statements')
- elif lines[line_number].startswith('collide'):
- raise SyntaxError('Colliding functions can\'t be defined in if statements')
- if lines[line_number].startswith('endif'):
- break
- if lines[line_number].startswith('else'):
- line_number += 1
- while line_number < len(lines):
- if lines[line_number].startswith('endif'):
- break
- exec_code += lines[line_number] + '\n'
- line_number += 1
- line_number += 1
- return line_number, exec_code
- if parts[2] == '==':
- if a == b:
- line_number, exec_code = handle_true(line_number, exec_code)
- else:
- line_number, exec_code = handle_false(line_number, exec_code)
- if parts[2] == '!=':
- if a != b:
- line_number, exec_code = handle_true(line_number, exec_code)
- else:
- line_number, exec_code = handle_false(line_number, exec_code)
- self.interpret_code(exec_code)
- line_number += 1
- elif parts[0].lower() == "return":
- out = self.process_data_type(parts[1], parts[2])
- self.function_return_value = out
- elif parts[0].lower() == "endif" or parts[0].lower() == "endfunc" or parts[0].lower() == "endcollide" or parts[0].lower() == "else": # unexpected keyword
- raise SyntaxError(f'Unexpected {parts[0]}')
- elif parts[0].lower() == "jump":
- line_number = int(parts[1]) - 1
- continue
- elif parts[0].lower() == "skip":
- line_number += 1
- elif parts[0].lower() == "halt":
- print(f'Code execution loop stopped at line {line_number}')
- break
- elif parts[0].lower() == "quit":
- if len(parts) >= 1:
- sys.exit(parts[1])
- else:
- sys.exit()
- else:
- raise SyntaxError(f"Invalid command: {parts[0]}")
- line_number += 1
|