You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

298 lines
8.9 KiB

3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using System.IO;
  5. using System.Text.RegularExpressions;
  6. namespace SuperBASIC
  7. {
  8. class Parser
  9. {
  10. [Serializable]
  11. public class ParseException : Exception
  12. {
  13. public ParseException() { }
  14. public ParseException(string message) : base(message) { }
  15. public ParseException(string message, Exception inner) : base(message, inner) { }
  16. protected ParseException(
  17. System.Runtime.Serialization.SerializationInfo info,
  18. System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
  19. }
  20. readonly Library library;
  21. readonly Runtime runtime;
  22. Dictionary<string, int> namedLabels;
  23. List<Tuple<string, int>> positionedLabels;
  24. public Parser(Runtime rt)
  25. {
  26. runtime = rt;
  27. library = rt.lib;
  28. }
  29. internal enum Controls
  30. {
  31. If, Then, Else, EndIf,
  32. For, To, Step, Next,
  33. While, Wend,
  34. Repeat, Until,
  35. Do, LoopCond
  36. }
  37. internal struct FlowControlTag
  38. {
  39. public Controls ctrl;
  40. public int index;
  41. }
  42. private void ParseExpression(Bytecode c, string[] components, int idx, List<int> lineSpans)
  43. {
  44. if (library.nameResolution.ContainsKey(components[0]))
  45. {
  46. int opcode = library.nameResolution[components[0]];
  47. int arity = library.arities[opcode];
  48. if (arity != components.Length - 1)
  49. {
  50. int lineIndex = 0;
  51. foreach (int cnt in lineSpans.GetRange(0, idx + 1)) lineIndex += cnt;
  52. throw new ParseException($"Operation {components[0]} was provided with the wrong number of arguments\n\tExpected {arity} found {components.Length - 1}\n\tat line {lineIndex}");
  53. }
  54. c.bytecode.Add(new BasicNumber(runtime, opcode));
  55. foreach (string elem in components.AsSpan(1))
  56. {
  57. #if MEMORY
  58. if (elem.StartsWith("M"))
  59. {
  60. try
  61. {
  62. Int16 v = Int16.Parse(elem[1..]);
  63. c.bytecode.Add(new BasicNumber(runtime, v, NumberType.Memory));
  64. }
  65. catch (Exception)
  66. {
  67. int lineIndex = 0;
  68. foreach (int cnt in lineSpans.GetRange(0, idx + 1)) lineIndex += cnt;
  69. throw new ParseException($"Cannot parse {elem} as argument to memory address\n\tExpected 'M' followed by an integer lower than {Int16.MaxValue}\n\tat line {lineIndex}");
  70. }
  71. }
  72. else
  73. #endif
  74. if (namedLabels.ContainsKey(elem))
  75. {
  76. positionedLabels.Add(new Tuple<string, int>(elem, c.bytecode.Count));
  77. c.bytecode.Add(new BasicNumber(runtime, (float)namedLabels[elem]));
  78. }
  79. else if (elem == "$")
  80. {
  81. c.bytecode.Add(new BasicNumber(runtime));
  82. }
  83. else
  84. {
  85. try
  86. {
  87. float v = float.Parse(elem);
  88. c.bytecode.Add(new BasicNumber(runtime, v));
  89. }
  90. catch (Exception)
  91. {
  92. int lineIndex = 0;
  93. foreach (int cnt in lineSpans.GetRange(0, idx + 1)) lineIndex += cnt;
  94. throw new ParseException($"Cannot parse {elem} as argument\n\tExpected floating point number or '$' or memory argument\n\tat line {lineIndex}");
  95. }
  96. }
  97. }
  98. }
  99. else
  100. {
  101. int lineIndex = 0;
  102. foreach (int cnt in lineSpans.GetRange(0, idx + 1)) lineIndex += cnt;
  103. throw new ParseException($"Unknown operation \"{components[0]}\"\n\tat line {lineIndex}");
  104. }
  105. }
  106. public Bytecode ParseFile(string filepath)
  107. {
  108. Bytecode c = new Bytecode();
  109. string[] sourceLines = File.ReadAllLines(filepath);
  110. Regex lws = new Regex(@"\s+");
  111. Regex leadings = new Regex(@"^\s+");
  112. Regex trailings = new Regex(@"\s+$");
  113. List<string> codeLines = new List<string>();
  114. List<int> lineSpans = new List<int>();
  115. Stack<FlowControlTag> labelStack = new Stack<FlowControlTag>();
  116. namedLabels = new Dictionary<string, int>();
  117. positionedLabels = new List<Tuple<string, int>>();
  118. int a = 0;
  119. foreach (string line in sourceLines)
  120. {
  121. a++;
  122. string l = lws.Replace(line, " ");
  123. l = leadings.Replace(l, "");
  124. l = trailings.Replace(l, "");
  125. if (l != String.Empty)
  126. {
  127. lineSpans.Add(a);
  128. a = 0;
  129. codeLines.Add(l);
  130. }
  131. }
  132. for (int idx = 0; idx < codeLines.Count; idx++)
  133. {
  134. string line = codeLines[idx];
  135. var components = line.Split(' ');
  136. if (components[0] == "LABEL")
  137. {
  138. if (components.Length != 2)
  139. {
  140. int lineIndex = 0;
  141. foreach (int cnt in lineSpans.GetRange(0, idx + 1)) lineIndex += cnt;
  142. throw new ParseException($"Bad LABEL statement\n\tat line {lineIndex}");
  143. }
  144. namedLabels[components[1]] = -1;
  145. }
  146. }
  147. for (int idx = 0; idx < codeLines.Count; idx++)
  148. {
  149. string line = codeLines[idx];
  150. var components = line.Split(' ');
  151. var label = new FlowControlTag();
  152. switch (components[0])
  153. {
  154. case "IF":
  155. // Role: manages the expression
  156. label.ctrl = Controls.If;
  157. label.index = c.bytecode.Count;
  158. labelStack.Push(label);
  159. ParseExpression(c, components[1..], idx, lineSpans);
  160. break;
  161. case "LABEL":
  162. if (components.Length != 2)
  163. {
  164. int lineIndex = 0;
  165. foreach (int cnt in lineSpans.GetRange(0, idx + 1)) lineIndex += cnt;
  166. throw new ParseException($"Bad LABEL statement\n\tat line {lineIndex}");
  167. }
  168. namedLabels[components[1]] = c.bytecode.Count;
  169. break;
  170. case "THEN":
  171. // Role: Jumps to the far block if false
  172. // 1. Adds the label to update the destination
  173. label.ctrl = Controls.Then;
  174. label.index = c.bytecode.Count;
  175. labelStack.Push(label);
  176. // 2. Adds the jumper
  177. c.bytecode.Add(new BasicNumber(runtime, library.nameResolution["JZ"]));
  178. c.bytecode.Add(new BasicNumber(runtime));
  179. c.bytecode.Add(new BasicNumber(runtime, 0f));
  180. break;
  181. case "ELSE":
  182. //Role: skips its section if encountered, marks the THEN for JZ
  183. //1. Gets the THEN label
  184. label = labelStack.Pop();
  185. if(label.ctrl != Controls.Then)
  186. {
  187. int lineIndex = 0;
  188. foreach (int cnt in lineSpans.GetRange(0, idx + 1)) lineIndex += cnt;
  189. throw new ParseException($"Orphaned ELSE\n\tat line {lineIndex}");
  190. }
  191. //2. Self tags for the ENDIF to complete the GOTO
  192. var label_self = new FlowControlTag
  193. {
  194. ctrl = Controls.Else,
  195. index = c.bytecode.Count
  196. };
  197. labelStack.Push(label_self);
  198. //3. Pushes the GOTO
  199. c.bytecode.Add(new BasicNumber(runtime, library.nameResolution["GOTO"]));
  200. c.bytecode.Add(new BasicNumber(runtime, 0f));
  201. //4. Place the THEN destination after the GOTO
  202. c.bytecode[label.index+2] = new BasicNumber(runtime, (float)c.bytecode.Count);
  203. break;
  204. case "ENDIF":
  205. //Role: Destination if depending on what is skipped
  206. label = labelStack.Pop();
  207. if (label.ctrl == Controls.Else)
  208. {
  209. //Case 1: update the GOTO
  210. c.bytecode[label.index + 1] = new BasicNumber(runtime, (float)c.bytecode.Count);
  211. }
  212. else if (label.ctrl == Controls.Then)
  213. {
  214. //Case 2: update the JZ
  215. c.bytecode[label.index + 2] = new BasicNumber(runtime, (float)c.bytecode.Count);
  216. }
  217. else
  218. {
  219. int lineIndex = 0;
  220. foreach (int cnt in lineSpans.GetRange(0, idx + 1)) lineIndex += cnt;
  221. throw new ParseException($"Orphaned ENDIF\n\tat line {lineIndex}");
  222. }
  223. // Controls the syntax
  224. if (labelStack.Pop().ctrl != Controls.If)
  225. {
  226. int lineIndex = 0;
  227. foreach (int cnt in lineSpans.GetRange(0, idx + 1)) lineIndex += cnt;
  228. throw new ParseException($"Orphaned ENDIF despite THEN\n\tat line {lineIndex}");
  229. }
  230. break;
  231. case "WHILE":
  232. // Role: manages the expression
  233. // 1. Deal with expression
  234. label.ctrl = Controls.While;
  235. label.index = c.bytecode.Count;
  236. labelStack.Push(label);
  237. ParseExpression(c, components[1..], idx, lineSpans);
  238. // 2. Adds the label to update the destination
  239. label.ctrl = Controls.Wend;
  240. label.index = c.bytecode.Count;
  241. labelStack.Push(label);
  242. // 3. Adds the jumper
  243. c.bytecode.Add(new BasicNumber(runtime, library.nameResolution["JZ"]));
  244. c.bytecode.Add(new BasicNumber(runtime));
  245. c.bytecode.Add(new BasicNumber(runtime, 0f));
  246. break;
  247. case "WEND":
  248. //Role: Destination if depending on what is skipped
  249. var labelA = labelStack.Pop();
  250. if (labelA.ctrl == Controls.Wend)
  251. {
  252. label = labelStack.Pop();
  253. if (label.ctrl == Controls.While)
  254. {
  255. c.bytecode.Add(new BasicNumber(runtime, library.nameResolution["GOTO"]));
  256. c.bytecode.Add(new BasicNumber(runtime, (float)label.index));
  257. c.bytecode[labelA.index + 2] = new BasicNumber(runtime, (float)c.bytecode.Count);
  258. }
  259. }
  260. else
  261. {
  262. int lineIndex = 0;
  263. foreach (int cnt in lineSpans.GetRange(0, idx + 1)) lineIndex += cnt;
  264. throw new ParseException($"Orphaned WEND\n\tat line {lineIndex}");
  265. }
  266. break;
  267. default:
  268. ParseExpression(c, components, idx, lineSpans);
  269. break;
  270. }
  271. }
  272. foreach(var lbl in positionedLabels)
  273. {
  274. c.bytecode[lbl.Item2] = new BasicNumber(runtime, (float)namedLabels[lbl.Item1]);
  275. }
  276. return c;
  277. }
  278. }
  279. }