vm.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. import sys
  2. from enum import Enum
  3. def eprint(*args):
  4. print(*args, file=sys.stderr)
  5. class Line:
  6. def __init__(self, text, line_number):
  7. self.text = text
  8. self.orig = text
  9. self.lineno = line_number
  10. self.clean()
  11. def clean(self):
  12. # Remove the comments
  13. self.text = self.text.split("#", 1)[0]
  14. # Remove whitespace
  15. self.text = self.text.strip()
  16. def is_empty(self):
  17. return bool(self.text)
  18. def __repr__(self):
  19. return "Line %s: %s" % (self.lineno, self.orig.lstrip())
  20. def command(self):
  21. if not self.is_empty():
  22. return None
  23. return self.text.split(" ")[0]
  24. def args(self):
  25. if not self.is_empty():
  26. return None
  27. return self.text.split(" ")[1:]
  28. def clean(contents):
  29. # Split input into lines
  30. contents = contents.split("\n")
  31. contents = [Line(line, i) for i, line in enumerate(contents)]
  32. # Remove empty blank lines
  33. contents = [line for line in contents if line.is_empty()]
  34. return contents
  35. def divide_sections(contents):
  36. state = "NOSCOPE"
  37. segments = {}
  38. current_segment = []
  39. contract_name = None
  40. for line in contents:
  41. if line.command() == "contract":
  42. if len(line.args()) != 1:
  43. eprint("error: missing contract name")
  44. eprint(line)
  45. return None
  46. contract_name = line.args()[0]
  47. if state == "NOSCOPE":
  48. assert not current_segment
  49. state = "INSCOPE"
  50. continue
  51. else:
  52. assert state == "INSCOPE"
  53. eprint("error: double contract entry violation")
  54. eprint(line)
  55. return None
  56. elif line.command() == "end":
  57. if len(line.args()) != 0:
  58. eprint("error: end takes no args")
  59. eprint(line)
  60. return None
  61. if state == "NOSCOPE":
  62. eprint("error: missing contract start for end")
  63. eprint(line)
  64. return None
  65. else:
  66. assert state == "INSCOPE"
  67. state = "NOSCOPE"
  68. segments[contract_name] = current_segment
  69. current_segment = []
  70. continue
  71. elif state == "NOSCOPE":
  72. # Ignore lines outside any contract
  73. continue
  74. current_segment.append(line)
  75. if state != "NOSCOPE":
  76. eprint("error: reached end of file with unclosed scope")
  77. return None
  78. return segments
  79. alloc_commands = {
  80. "param": 1,
  81. "private": 1,
  82. "public": 1,
  83. }
  84. op_commands = {
  85. "set": 2,
  86. "mul": 2,
  87. }
  88. constraint_commands = {
  89. "lc0_add": 1,
  90. "lc1_add": 1,
  91. "lc2_add": 1,
  92. "lc0_add_one": 0,
  93. "lc1_add_one": 0,
  94. "lc2_add_one": 0,
  95. "enforce": 0,
  96. }
  97. def extract_relevant_lines(contract, commands_table):
  98. relevant_lines = []
  99. for line in contract:
  100. command = line.command()
  101. if command not in commands_table.keys():
  102. continue
  103. define = commands_table[command]
  104. if len(line.args()) != define:
  105. eprint("error: wrong number of args")
  106. return None
  107. relevant_lines.append(line)
  108. return relevant_lines
  109. class VariableType(Enum):
  110. PUBLIC = 1
  111. PRIVATE = 2
  112. class Variable:
  113. def __init__(self, symbol, index, type, is_param):
  114. self.symbol = symbol
  115. self.index = index
  116. self.type = type
  117. self.is_param = is_param
  118. def __repr__(self):
  119. return "<Variable %s:%s>" % (self.symbol, self.index)
  120. def generate_alloc_table(contract):
  121. relevant_lines = extract_relevant_lines(contract, alloc_commands)
  122. alloc_table = {}
  123. for i, line in enumerate(relevant_lines):
  124. assert len(line.args()) == 1
  125. symbol = line.args()[0]
  126. command = line.command()
  127. if command == "param":
  128. type = VariableType.PRIVATE
  129. is_param = True
  130. elif command == "private":
  131. type = VariableType.PRIVATE
  132. is_param = False
  133. elif command == "public":
  134. type = VariableType.PUBLIC
  135. is_param = False
  136. else:
  137. assert False
  138. alloc_table[symbol] = Variable(symbol, i, type, is_param)
  139. return alloc_table
  140. def symbols_list_to_indexes(line, alloc):
  141. indexes = []
  142. for symbol in line.args():
  143. if symbol not in alloc:
  144. eprint("error: missing unallocated symbol")
  145. eprint(line)
  146. return None
  147. # Lookup variable index
  148. index = alloc[symbol].index
  149. indexes.append(index)
  150. return indexes
  151. class Operation:
  152. def __init__(self, line, indexes):
  153. self.command = line.command()
  154. self.args = indexes
  155. self.line = line
  156. def generate_ops_table(contract, alloc):
  157. relevant_lines = extract_relevant_lines(contract, op_commands)
  158. ops = []
  159. for line in relevant_lines:
  160. indexes = symbols_list_to_indexes(line, alloc)
  161. ops.append(Operation(line, indexes))
  162. return ops
  163. class Constraint:
  164. def __init__(self, line, indexes):
  165. self.command = line.command()
  166. self.args = indexes
  167. self.line = line
  168. def args_comment(self):
  169. return ", ".join("%s" % symbol for symbol in self.line.args())
  170. def generate_constraints_table(contract, alloc):
  171. relevant_lines = extract_relevant_lines(contract, constraint_commands)
  172. constraints = []
  173. for line in relevant_lines:
  174. indexes = symbols_list_to_indexes(line, alloc)
  175. constraints.append(Constraint(line, indexes))
  176. return constraints
  177. def compile(contract, constants):
  178. # Allocation table
  179. # symbol: Private/Public, is_param, index
  180. alloc = generate_alloc_table(contract)
  181. # Operations lines list
  182. if (ops := generate_ops_table(contract, alloc)) is None:
  183. return False
  184. # Constraint commands
  185. if (constraints := generate_constraints_table(contract, alloc)) is None:
  186. return False
  187. display(alloc, ops, constraints)
  188. return True
  189. def display(alloc, ops, constraints):
  190. print("Alloc table:")
  191. for symbol, variable in alloc.items():
  192. print(" //", symbol)
  193. print(" ", variable.type, variable.index)
  194. print()
  195. print("Operations:")
  196. for op in ops:
  197. print(" //", op.line)
  198. print(" ", op.command, op.args)
  199. print()
  200. print("Constraints:")
  201. for constraint in constraints:
  202. if constraint.args:
  203. print(" //", constraint.args_comment())
  204. print(" ", constraint.command, constraint.args)
  205. print()
  206. def process(contents):
  207. # Remove left whitespace
  208. contents = clean(contents)
  209. # Parse all constants
  210. constants = [line for line in contents if line.command() == "constant"]
  211. # Divide into contract sections
  212. if (contracts := divide_sections(contents)) is None:
  213. return False
  214. # Process each contract
  215. for contract_name, contract in contracts.items():
  216. if not compile(contract, constants):
  217. return False
  218. return True
  219. def main(argv):
  220. if len(argv) != 2:
  221. eprint("pism FILENAME")
  222. return -1
  223. src_filename = argv[1]
  224. contents = open(src_filename).read()
  225. if not process(contents):
  226. return -2
  227. return 0
  228. if __name__ == "__main__":
  229. sys.exit(main(sys.argv))