lang.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. import shlex, term, os, subprocess, sys
  2. #region
  3. # This will be moved to ProtoLib
  4. import ctypes
  5. from ctypes import c_long, c_wchar_p, c_ulong, c_void_p
  6. gHandle = ctypes.windll.kernel32.GetStdHandle(c_long(-11))
  7. def move (y, x):
  8. """Move cursor to position indicated by x and y."""
  9. value = x + (y << 16)
  10. ctypes.windll.kernel32.SetConsoleCursorPosition(gHandle, c_ulong(value))
  11. def addstr (string):
  12. """Write string"""
  13. ctypes.windll.kernel32.WriteConsoleW(gHandle, c_wchar_p(string), c_ulong(len(string)), c_void_p(), None)
  14. #endregion
  15. def strtobool(val):
  16. """Convert a string representation of truth to true (1) or false (0).
  17. True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values
  18. are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if
  19. 'val' is anything else.
  20. """
  21. val = val.lower()
  22. if val in ('y', 'yes', 't', 'true', 'on', '1'):
  23. return True
  24. elif val in ('n', 'no', 'f', 'false', 'off', '0'):
  25. return False
  26. else:
  27. raise ValueError("invalid truth value %r" % (val,))
  28. def _str(input):
  29. return str(input)
  30. def _int(input):
  31. return int(input)
  32. def _float(input):
  33. return float(input)
  34. def _null(val):
  35. val = val.lower()
  36. if val in ('null', 'none', 'nil'):
  37. return None
  38. else:
  39. raise ValueError("invalid truth value %r" % (val,))
  40. class FeatherScriptProcessor:
  41. def __init__(self):
  42. self.data = {}
  43. self.data_types = {
  44. 'str': _str,
  45. 'int': _int,
  46. 'float': _float,
  47. 'bool': strtobool,
  48. 'null': _null,
  49. }
  50. self.function_return_value = None
  51. def process_string(self, input: str, replace_line_break_escape_sequence=True):
  52. out = input
  53. for name in self.data:
  54. if '$' + name in out:
  55. if self.data[name]['obj_type'] == 'function':
  56. if '$' + name in input:
  57. out = out.replace('$' + name, str(self.handle_function(name)))
  58. elif self.data[name]['obj_type'] == 'collide':
  59. if '$' + name in input:
  60. out = out.replace('$' + name, str(self.handle_collision(name)))
  61. else:
  62. out = out.replace('$' + name, str(self.data[name]['value']))
  63. if replace_line_break_escape_sequence:
  64. out = out.replace('\\n', '\n')
  65. return out
  66. def handle_function(self, name):
  67. self.interpret_code(self.data[name]['value'])
  68. out = self.function_return_value
  69. self.function_return_value = None
  70. return out
  71. def handle_collision(self, name):
  72. return name
  73. def process_data_type(self, _type: str, data: str):
  74. if _type.lower() in self.data_types:
  75. return self.data_types[_type](data)
  76. else:
  77. return None
  78. def interpret_code(self, code: str):
  79. lines = code.strip().replace(";", "\n").split("\n")
  80. line_number = 0
  81. while line_number < len(lines):
  82. line = lines[line_number]
  83. # Remove comments from the line
  84. line = line.split("#")[0].strip()
  85. if not line:
  86. line_number += 1
  87. continue
  88. parts = shlex.split(line)
  89. # print(parts, line_number + 1) # DEBUG
  90. if parts[0].lower() == "echo":
  91. print(self.process_string(parts[1]))
  92. elif parts[0].startswith('$'):
  93. parts[0] = parts[0].split('$', 1)[1]
  94. if self.data[parts[0]]['obj_type'] == 'function':
  95. self.handle_function(parts[0])
  96. elif self.data[parts[0]]['obj_type'] == 'collide':
  97. self.handle_collision(parts[0])
  98. else:
  99. raise TypeError(f'{self.data[parts[0]]["obj_type"]} can\'t be executed as function')
  100. elif parts[0].lower() == "write":
  101. term.write(self.process_string(parts[1]))
  102. elif parts[0].lower() == "cursor":
  103. if os.name == 'nt':
  104. move(int(parts[2]), int(parts[1]))
  105. else:
  106. term.pos(int(parts[1]), int(parts[2]))
  107. elif parts[0].lower() == "clear":
  108. if os.name == 'nt':
  109. subprocess.run('cls')
  110. else:
  111. subprocess.run('clear')
  112. elif parts[0].lower() == "exec":
  113. with open(parts[1]) as f:
  114. script = f.read()
  115. f.close()
  116. self.interpret_code(script)
  117. elif parts[0].lower() == "var":
  118. _content = self.process_data_type(parts[1].lower(), parts[3])
  119. _type = parts[1].lower()
  120. if parts[2] in self.data:
  121. if self.data[parts[2]]['obj_type'] == 'constant':
  122. print('A constant with that name has already been defined')
  123. break
  124. elif self.data[parts[2]]['obj_type'] == 'function':
  125. print('A function with that name has already been defined')
  126. break
  127. self.data[parts[2]] = {
  128. 'obj_type': 'variable',
  129. 'data_type': _type,
  130. 'value': _content
  131. }
  132. elif parts[0].lower() == "const":
  133. _content = self.process_data_type(parts[1].lower(), parts[3])
  134. _type = parts[1].lower()
  135. if parts[2] in self.data:
  136. if self.data[parts[2]]['obj_type'] == 'constant':
  137. print('A constant with that name has already been defined')
  138. break
  139. elif self.data[parts[2]]['obj_type'] == 'function':
  140. print('A function with that name has already been defined')
  141. break
  142. self.data[parts[2]] = {
  143. 'obj_type': 'constant',
  144. 'data_type': _type,
  145. 'value': _content
  146. }
  147. elif parts[0].lower() == "func":
  148. func = ''
  149. name = parts[1]
  150. line_number += 1
  151. while line_number < len(lines):
  152. if lines[line_number].startswith('func'):
  153. raise SyntaxError('Functions can\'t be defined in functions')
  154. if lines[line_number].startswith('endfunc'):
  155. break
  156. func += lines[line_number] + '\n'
  157. line_number += 1
  158. if name in self.data:
  159. if self.data[name]['obj_type'] == 'constant':
  160. raise ValueError('A constant with that name has already been defined')
  161. elif self.data[name]['obj_type'] == 'function':
  162. raise ValueError('A function with that name has already been defined')
  163. elif self.data[name]['obj_type'] == 'collide':
  164. raise ValueError('A colliding function with that name has already been defined')
  165. self.data[name] = {
  166. 'obj_type': 'function',
  167. 'data_type': 'str',
  168. 'value': func
  169. }
  170. elif parts[0].lower() == "collide":
  171. func = ''
  172. lang = parts[1]
  173. name = parts[2]
  174. line_number += 1
  175. while line_number < len(lines):
  176. if lines[line_number].startswith('func'):
  177. raise SyntaxError('Functions can\'t be defined in colliding functions')
  178. elif lines[line_number].startswith('collide'):
  179. raise SyntaxError('Colliding functions can\'t be defined in colliding functions')
  180. if lines[line_number].startswith('endcollide'):
  181. break
  182. func += lines[line_number] + '\n'
  183. line_number += 1
  184. if name in self.data:
  185. if self.data[name]['obj_type'] == 'constant':
  186. raise ValueError('A constant with that name has already been defined')
  187. elif self.data[name]['obj_type'] == 'function':
  188. raise ValueError('A function with that name has already been defined')
  189. elif self.data[name]['obj_type'] == 'collide':
  190. raise ValueError('A colliding function with that name has already been defined')
  191. self.data[name] = {
  192. 'obj_type': 'collide',
  193. 'data_type': 'str',
  194. 'value': func,
  195. 'lang': lang
  196. }
  197. elif parts[0].lower() == "if":
  198. exec_code = ''
  199. a, b = None, None
  200. if parts[1].startswith('$'):
  201. a = str(self.process_string(parts[1]))
  202. else:
  203. a = str(self.process_data_type(*parts[1].split(':', 1)))
  204. if parts[3].startswith('$'):
  205. b = str(self.process_string(parts[3]))
  206. else:
  207. b = str(self.process_data_type(*parts[3].split(':', 1)))
  208. def handle_true(line_number, exec_code):
  209. line_number += 1
  210. while line_number < len(lines):
  211. if lines[line_number].startswith('endif') or lines[line_number].startswith('else'):
  212. break
  213. if lines[line_number].startswith('func'):
  214. raise SyntaxError('Functions can\'t be defined in if statements')
  215. elif lines[line_number].startswith('collide'):
  216. raise SyntaxError('Colliding functions can\'t be defined in if statements')
  217. exec_code += lines[line_number] + '\n'
  218. line_number += 1
  219. if lines[line_number].startswith('else'):
  220. line_number += 1
  221. while line_number < len(lines):
  222. if lines[line_number].startswith('endif'):
  223. break
  224. line_number += 1
  225. return line_number, exec_code
  226. def handle_false(line_number, exec_code):
  227. while line_number < len(lines):
  228. if lines[line_number].startswith('func'):
  229. raise SyntaxError('Functions can\'t be defined in if statements')
  230. elif lines[line_number].startswith('collide'):
  231. raise SyntaxError('Colliding functions can\'t be defined in if statements')
  232. if lines[line_number].startswith('endif'):
  233. break
  234. if lines[line_number].startswith('else'):
  235. line_number += 1
  236. while line_number < len(lines):
  237. if lines[line_number].startswith('endif'):
  238. break
  239. exec_code += lines[line_number] + '\n'
  240. line_number += 1
  241. line_number += 1
  242. return line_number, exec_code
  243. if parts[2] == '==':
  244. if a == b:
  245. line_number, exec_code = handle_true(line_number, exec_code)
  246. else:
  247. line_number, exec_code = handle_false(line_number, exec_code)
  248. if parts[2] == '!=':
  249. if a != b:
  250. line_number, exec_code = handle_true(line_number, exec_code)
  251. else:
  252. line_number, exec_code = handle_false(line_number, exec_code)
  253. self.interpret_code(exec_code)
  254. line_number += 1
  255. elif parts[0].lower() == "return":
  256. out = self.process_data_type(parts[1], parts[2])
  257. self.function_return_value = out
  258. elif parts[0].lower() == "endif" or parts[0].lower() == "endfunc" or parts[0].lower() == "endcollide" or parts[0].lower() == "else": # unexpected keyword
  259. raise SyntaxError(f'Unexpected {parts[0]}')
  260. elif parts[0].lower() == "jump":
  261. line_number = int(parts[1]) - 1
  262. continue
  263. elif parts[0].lower() == "skip":
  264. line_number += 1
  265. elif parts[0].lower() == "halt":
  266. print(f'Code execution loop stopped at line {line_number}')
  267. break
  268. elif parts[0].lower() == "quit":
  269. if len(parts) >= 1:
  270. sys.exit(parts[1])
  271. else:
  272. sys.exit()
  273. else:
  274. raise SyntaxError(f"Invalid command: {parts[0]}")
  275. line_number += 1