Compiler.java 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691
  1. package me.hammerle.snuviscript.code;
  2. import java.util.ArrayList;
  3. import java.util.HashMap;
  4. import java.util.LinkedList;
  5. import java.util.List;
  6. import java.util.Stack;
  7. import me.hammerle.snuviscript.constants.ConstantFraction;
  8. import me.hammerle.snuviscript.constants.ConstantNull;
  9. import me.hammerle.snuviscript.constants.ConstantString;
  10. import me.hammerle.snuviscript.array.DynamicArray;
  11. import me.hammerle.snuviscript.constants.ConstantBoolean;
  12. import me.hammerle.snuviscript.variable.ArrayVariable;
  13. import me.hammerle.snuviscript.variable.LocalArrayVariable;
  14. import me.hammerle.snuviscript.variable.LocalVariable;
  15. import me.hammerle.snuviscript.variable.Variable;
  16. import me.hammerle.snuviscript.exceptions.PreScriptException;
  17. import me.hammerle.snuviscript.math.Fraction;
  18. public class Compiler
  19. {
  20. public static Instruction[] compile(Script sc, List<String> sCode, HashMap<String, Integer> labels, boolean locale, int lineOffset)
  21. {
  22. Compiler compiler = new Compiler(sc, sCode, labels, locale);
  23. compiler.lineOffset = lineOffset;
  24. return compiler.compile();
  25. }
  26. private final List<String> sCode;
  27. private final HashMap<String, Variable> vars;
  28. private final HashMap<String, Variable> localVars;
  29. private final HashMap<String, Integer> labels;
  30. private final LinkedList<Instruction> code;
  31. private int line;
  32. private int lineOffset;
  33. private int layer;
  34. private class JumpWrapper
  35. {
  36. private final JumpData data;
  37. private final String function;
  38. public JumpWrapper(JumpData data, String function)
  39. {
  40. this.data = data;
  41. this.function = function;
  42. }
  43. }
  44. private final Stack<JumpWrapper> jumps;
  45. private final Stack<JumpWrapper> loopJumps;
  46. private final LinkedList<JumpData> breakContinueJumps;
  47. private final Script parentScript;
  48. private final HashMap<String, String> strings;
  49. private int stringCounter;
  50. private final boolean locale;
  51. private Compiler(Script sc, List<String> sCode, HashMap<String, Integer> labels, boolean locale)
  52. {
  53. this.parentScript = sc;
  54. this.sCode = sCode;
  55. this.vars = new HashMap<>();
  56. this.localVars = new HashMap<>();
  57. this.labels = labels;
  58. this.code = new LinkedList<>();
  59. this.line = 0;
  60. this.layer = 0;
  61. this.jumps = new Stack<>();
  62. this.loopJumps = new Stack<>();
  63. this.breakContinueJumps = new LinkedList<>();
  64. this.strings = new HashMap<>();
  65. this.stringCounter = 0;
  66. this.locale = locale;
  67. }
  68. private void addCodeInstruction(String function, InputProvider[] input)
  69. {
  70. code.add(new Instruction(line + lineOffset, (byte) layer, FunctionLoader.getFunction(function), input));
  71. }
  72. private Instruction[] compile()
  73. {
  74. int size = sCode.size();
  75. System.out.println("__________________________________");
  76. StringBuilder sb = new StringBuilder();
  77. String replacement;
  78. String check;
  79. int pos;
  80. int old = 0;
  81. boolean text = false;
  82. boolean comment = false;
  83. for(line = 0; line < size; line++)
  84. {
  85. pos = sb.length();
  86. sb.append(sCode.get(line));
  87. while(pos < sb.length())
  88. {
  89. if(comment)
  90. {
  91. if(pos + 1 < sb.length() && sb.charAt(pos) == '*' && sb.charAt(pos + 1) == '/')
  92. {
  93. comment = false;
  94. sb.delete(old, pos + 2);
  95. }
  96. pos++;
  97. continue;
  98. }
  99. else if(text)
  100. {
  101. if(sb.charAt(pos) == '"')
  102. {
  103. replacement = "#" + stringCounter++;
  104. strings.put(replacement, sb.substring(old, pos + 1));
  105. text = false;
  106. sb.replace(old, pos + 1, replacement);
  107. pos = old - 1 + replacement.length();
  108. }
  109. pos++;
  110. continue;
  111. }
  112. switch(sb.charAt(pos))
  113. {
  114. case '/':
  115. if(pos + 1 < sb.length())
  116. {
  117. switch(sb.charAt(pos + 1))
  118. {
  119. case '/':
  120. sb.delete(pos, sb.length());
  121. break;
  122. case '*':
  123. comment = true;
  124. old = pos;
  125. pos++;
  126. break;
  127. }
  128. }
  129. break;
  130. case '}':
  131. sb.delete(0, pos + 1);
  132. pos = -1;
  133. layer--;
  134. if(jumps.isEmpty())
  135. {
  136. throw new PreScriptException("} without a corresponding function and / or {", line);
  137. }
  138. JumpWrapper data = jumps.pop();
  139. switch(data.function)
  140. {
  141. case "if":
  142. {
  143. data.data.setRelativeJump(code.size());
  144. break;
  145. }
  146. case "for":
  147. {
  148. loopJumps.pop();
  149. createBreakContinue(code.size());
  150. JumpData jump = data.data;
  151. jump.setRelativeJump(code.size());
  152. addCodeInstruction("next", new InputProvider[] {new JumpData(-jump.getInt(null) - 1)});
  153. break;
  154. }
  155. case "while":
  156. {
  157. loopJumps.pop();
  158. createBreakContinue(code.size());
  159. JumpData jump = data.data;
  160. jump.setRelativeJump(code.size() + 1);
  161. addCodeInstruction("wend", new InputProvider[] {new JumpData(-jump.getInt(null) - 1)});
  162. break;
  163. }
  164. }
  165. break;
  166. case '{':
  167. int currentJumps = jumps.size();
  168. check = sb.toString();
  169. if(check.startsWith("function "))
  170. {
  171. if(parentScript.subScript)
  172. {
  173. throw new PreScriptException("function not allowed in another function", line);
  174. }
  175. int index = check.indexOf("(");
  176. if(index == -1)
  177. {
  178. throw new PreScriptException("missing function syntax", line);
  179. }
  180. String function = check.substring(9, index);
  181. int endIndex = check.indexOf(")", index);
  182. if(index == -1)
  183. {
  184. throw new PreScriptException("missing function syntax", line);
  185. }
  186. String[] inputs;
  187. if(index + 1 == endIndex)
  188. {
  189. inputs = new String[0];
  190. }
  191. else
  192. {
  193. inputs = check.substring(index + 1, endIndex).split("[ ]*,[ ]*");
  194. }
  195. ArrayList<String> subList = new ArrayList<>();
  196. int bCounter = 1;
  197. String subLine = check;
  198. pos++;
  199. int oldLine = line;
  200. out: while(true)
  201. {
  202. old = pos;
  203. while(pos < subLine.length())
  204. {
  205. switch(subLine.charAt(pos))
  206. {
  207. case '"':
  208. text = !text;
  209. break;
  210. case '{':
  211. if(!text)
  212. {
  213. bCounter++;
  214. }
  215. break;
  216. case '}':
  217. if(!text)
  218. {
  219. bCounter--;
  220. if(bCounter == 0)
  221. {
  222. subList.add(subLine.substring(old, pos).trim());
  223. sb = new StringBuilder();
  224. sCode.set(line, subLine.substring(pos + 1));
  225. line--;
  226. break out;
  227. }
  228. }
  229. break;
  230. }
  231. pos++;
  232. }
  233. subList.add(subLine.substring(old, pos).trim());
  234. line++;
  235. if(line >= sCode.size())
  236. {
  237. throw new PreScriptException("{ without }", line);
  238. }
  239. pos = 0;
  240. subLine = sCode.get(line);
  241. }
  242. Script sub = new Script(subList, inputs, parentScript, oldLine);
  243. parentScript.subScripts.put(function, sub);
  244. }
  245. else
  246. {
  247. check = sb.substring(0, pos);
  248. compileLine(check);
  249. sb.delete(0, pos + 1);
  250. layer++;
  251. if(currentJumps == jumps.size())
  252. {
  253. throw new PreScriptException("{ without a corresponding function", line);
  254. }
  255. }
  256. pos = -1;
  257. break;
  258. case ';':
  259. compileLine(sb.substring(0, pos).trim());
  260. sb.delete(0, pos + 1);
  261. pos = -1;
  262. break;
  263. case '"':
  264. text = true;
  265. old = pos;
  266. break;
  267. }
  268. pos++;
  269. }
  270. }
  271. System.out.println("__________________________________");
  272. Instruction[] input = code.toArray(new Instruction[code.size()]);
  273. for(Instruction in : input)
  274. {
  275. System.out.println(in);
  276. }
  277. System.out.println("__________________________________");
  278. labels.entrySet().stream().forEach((e) ->
  279. {
  280. System.out.println("LABEL " + e.getKey() + " " + e.getValue());
  281. });
  282. System.out.println("__________________________________");
  283. return input;
  284. }
  285. private void compileLine(String currentCode)
  286. {
  287. if(currentCode.startsWith("'"))
  288. {
  289. return;
  290. }
  291. //System.out.println(">>>" + currentCode);
  292. String[] parts = DataUtils.split(strings, currentCode, line);
  293. //System.out.println(">>> " + String.join("_", parts));
  294. if(parts.length == 0)
  295. {
  296. return;
  297. }
  298. else if(parts[0].equals("return"))
  299. {
  300. addCodeInstruction("return", compileFunction(parts, true));
  301. return;
  302. }
  303. else if(parts[0].startsWith("@"))
  304. {
  305. if(parts.length > 1)
  306. {
  307. throw new PreScriptException("arguments after label", line);
  308. }
  309. labels.put(parts[0].substring(1), code.size() - 1);
  310. return;
  311. }
  312. String input;
  313. if(parts.length == 1)
  314. {
  315. int bPos = parts[0].indexOf('(');
  316. if(bPos != -1)
  317. {
  318. input = parts[0].substring(0, bPos);
  319. parts = DataUtils.split(strings, parts[0].substring(bPos + 1, parts[0].length() - 1), line);
  320. }
  321. else
  322. {
  323. switch(parts[0])
  324. {
  325. case "while":
  326. throw new PreScriptException("missing syntax at while", line);
  327. case "if":
  328. throw new PreScriptException("missing syntax at if", line);
  329. case "for":
  330. throw new PreScriptException("missing syntax at for", line);
  331. case "break":
  332. {
  333. if(loopJumps.isEmpty())
  334. {
  335. throw new IllegalStateException("break without a loop");
  336. }
  337. JumpData jump = new JumpData(code.size() - 1);
  338. breakContinueJumps.add(jump);
  339. addCodeInstruction("break", new InputProvider[] {jump});
  340. return;
  341. }
  342. case "continue":
  343. {
  344. if(loopJumps.isEmpty())
  345. {
  346. throw new IllegalStateException("continue without a loop");
  347. }
  348. JumpData jump = new JumpData(code.size());
  349. breakContinueJumps.add(jump);
  350. addCodeInstruction("continue", new InputProvider[] {jump});
  351. return;
  352. }
  353. }
  354. return;
  355. }
  356. }
  357. else
  358. {
  359. switch(parts[1])
  360. {
  361. case "=":
  362. case "+=":
  363. case "-=":
  364. case "*=":
  365. case "/=":
  366. case "%=":
  367. case "<<=":
  368. case ">>=":
  369. case "&=":
  370. case "^=":
  371. case "|=":
  372. {
  373. input = parts[1];
  374. parts[1] = ",";
  375. break;
  376. }
  377. default:
  378. throw new PreScriptException("unknown operation " + parts[1], line);
  379. }
  380. }
  381. switch(input)
  382. {
  383. case "break":
  384. throw new PreScriptException("break does not accept arguments", line);
  385. case "continue":
  386. throw new PreScriptException("continue does not accept arguments", line);
  387. }
  388. //System.out.println(input + " " + String.join("__", parts));
  389. switch(input)
  390. {
  391. case "if":
  392. createIf(parts);
  393. break;
  394. case "for":
  395. createFor(parts);
  396. break;
  397. case "while":
  398. createWhile(parts);
  399. break;
  400. default:
  401. addCodeInstruction(input, compileFunction(parts, false));
  402. }
  403. }
  404. private void addSyntax(LinkedList<InputProvider> list, Syntax sy)
  405. {
  406. int pars = sy.getParameters();
  407. if(pars > list.size())
  408. {
  409. throw new PreScriptException("missing syntax argument", line);
  410. }
  411. if(sy == Syntax.UNARY_SUB)
  412. {
  413. list.add(new SignInverter(list.pollLast()));
  414. return;
  415. }
  416. InputProvider[] input = new InputProvider[pars];
  417. for(int j = input.length - 1; j >= 0; j--)
  418. {
  419. input[j] = list.pollLast();
  420. }
  421. list.add(new Function(FunctionLoader.getFunction(sy.getFunction()), input));
  422. }
  423. private void validateStackCounter(int stackCounter)
  424. {
  425. if(stackCounter < 0)
  426. {
  427. throw new PreScriptException("missing syntax argument", line);
  428. }
  429. }
  430. private InputProvider[] compileFunction(String[] parts, boolean first)
  431. {
  432. LinkedList<InputProvider> list = new LinkedList<>();
  433. int stackCounter = 0;
  434. Stack<Syntax> syntax = new Stack<>();
  435. int bottom = first ? 1 : 0;
  436. Syntax sy;
  437. for(int i = bottom; i < parts.length; i++)
  438. {
  439. if(parts[i].equals(","))
  440. {
  441. // finding a comma means pushing all syntax functions
  442. while(!syntax.isEmpty())
  443. {
  444. addSyntax(list, syntax.pop());
  445. }
  446. stackCounter = 0;
  447. continue;
  448. }
  449. sy = Syntax.getSyntax(parts[i]);
  450. if(sy != Syntax.UNKNOWN && sy != Syntax.MAYBE)
  451. {
  452. if(stackCounter <= 0)
  453. {
  454. if(sy == Syntax.SUB)
  455. {
  456. sy = Syntax.UNARY_SUB;
  457. }
  458. else
  459. {
  460. throw new PreScriptException("missing syntax argument", line);
  461. }
  462. }
  463. // pushing weaker functions
  464. int weight = sy.getWeight();
  465. while(!syntax.isEmpty() && syntax.peek().getWeight() <= weight)
  466. {
  467. addSyntax(list, syntax.pop());
  468. }
  469. validateStackCounter(stackCounter);
  470. syntax.add(sy);
  471. stackCounter -= sy.getParameters() - 1;
  472. continue;
  473. }
  474. stackCounter++;
  475. list.add(convertString(parts[i]));
  476. }
  477. // pushing left over syntax functions because no comma happened
  478. while(!syntax.isEmpty())
  479. {
  480. addSyntax(list, syntax.pop());
  481. }
  482. validateStackCounter(stackCounter);
  483. return list.toArray(new InputProvider[list.size()]);
  484. }
  485. private InputProvider convertString(String input)
  486. {
  487. if(input.startsWith("@"))
  488. {
  489. return new ConstantString(input.substring(1));
  490. }
  491. else if(input.startsWith("\"") && input.endsWith("\""))
  492. {
  493. return new ConstantString(input.substring(1, input.length() - 1));
  494. }
  495. else if(input.equals("true"))
  496. {
  497. return ConstantBoolean.TRUE;
  498. }
  499. else if(input.equals("false"))
  500. {
  501. return ConstantBoolean.FALSE;
  502. }
  503. else if(input.equals("null"))
  504. {
  505. return ConstantNull.NULL;
  506. }
  507. else if(DataUtils.isNumber(input))
  508. {
  509. return new ConstantFraction(Fraction.fromDouble(Double.parseDouble(input)));
  510. }
  511. else if(DataUtils.isFunction(input))
  512. {
  513. int bPos = input.indexOf('(');
  514. String[] parts = DataUtils.split(strings, input.substring(bPos + 1, input.length() - 1), line);
  515. if(parts.length > 0)
  516. {
  517. return new Function(FunctionLoader.getFunction(input.substring(0, bPos)), compileFunction(parts, false));
  518. }
  519. else
  520. {
  521. return new Function(FunctionLoader.getFunction(input.substring(0, bPos)), new InputProvider[0]);
  522. }
  523. }
  524. else if(DataUtils.isArray(input))
  525. {
  526. int bPos = input.indexOf('[');
  527. String[] parts = DataUtils.split(strings, input.substring(bPos + 1, input.length() - 1), line);
  528. if(parts.length > 0)
  529. {
  530. return createArray(input.substring(0, bPos), compileFunction(parts, false));
  531. }
  532. else
  533. {
  534. return createArray(input.substring(0, bPos), new InputProvider[0]);
  535. }
  536. }
  537. else
  538. {
  539. return getOrCreateVariable(input);
  540. }
  541. }
  542. private Variable getOrCreateVariable(String var)
  543. {
  544. if(locale)
  545. {
  546. Variable oldVar = localVars.get(var);
  547. if(oldVar == null)
  548. {
  549. oldVar = new LocalVariable(var);
  550. localVars.put(var, oldVar);
  551. }
  552. return oldVar;
  553. }
  554. else
  555. {
  556. Variable oldVar = vars.get(var);
  557. if(oldVar == null)
  558. {
  559. oldVar = new Variable(var);
  560. vars.put(var, oldVar);
  561. }
  562. return oldVar;
  563. }
  564. }
  565. private DynamicArray createArray(String var, InputProvider[] in)
  566. {
  567. if(locale)
  568. {
  569. Variable oldVar = localVars.get(var);
  570. if(oldVar == null)
  571. {
  572. oldVar = new LocalArrayVariable(var);
  573. localVars.put(var, oldVar);
  574. }
  575. return new DynamicArray(oldVar, in);
  576. }
  577. else
  578. {
  579. Variable oldVar = vars.get(var);
  580. if(oldVar == null)
  581. {
  582. oldVar = new ArrayVariable(var);
  583. vars.put(var, oldVar);
  584. }
  585. return new DynamicArray(oldVar, in);
  586. }
  587. }
  588. private void createIf(String[] parts)
  589. {
  590. InputProvider[] input = compileFunction(parts, false);
  591. InputProvider[] realInput = new InputProvider[input.length + 1];
  592. System.arraycopy(input, 0, realInput, 0, input.length);
  593. JumpData jump = new JumpData(code.size());
  594. realInput[input.length] = jump;
  595. jumps.push(new JumpWrapper(jump, "if"));
  596. addCodeInstruction("if", realInput);
  597. }
  598. private void createFor(String[] parts)
  599. {
  600. // expected syntax
  601. // for(var, start, end, step)
  602. // for(var, start, end)
  603. InputProvider[] input = compileFunction(parts, false);
  604. if(input.length != 3 && input.length != 4)
  605. {
  606. throw new PreScriptException("missing 'for' syntax at", line);
  607. }
  608. InputProvider[] realInput = new InputProvider[5];
  609. System.arraycopy(input, 0, realInput, 0, input.length);
  610. if(input.length == 3)
  611. {
  612. realInput[3] = new ConstantFraction(new Fraction(1));
  613. }
  614. JumpData jump = new JumpData(code.size());
  615. realInput[4] = jump;
  616. JumpWrapper wrapper = new JumpWrapper(jump, "for");
  617. jumps.push(wrapper);
  618. loopJumps.push(wrapper);
  619. addCodeInstruction("for", realInput);
  620. }
  621. private void createWhile(String[] parts)
  622. {
  623. // expected syntax
  624. // while(condition)
  625. InputProvider[] input = compileFunction(parts, false);
  626. if(input.length != 1)
  627. {
  628. throw new PreScriptException("invalid conditions at 'while'", line);
  629. }
  630. InputProvider[] realInput = new InputProvider[2];
  631. realInput[0] = input[0];
  632. JumpData jump = new JumpData(code.size());
  633. realInput[1] = jump;
  634. JumpWrapper wrapper = new JumpWrapper(jump, "while");
  635. jumps.push(wrapper);
  636. loopJumps.push(wrapper);
  637. addCodeInstruction("while", realInput);
  638. }
  639. private void createBreakContinue(int current)
  640. {
  641. breakContinueJumps.forEach(jump -> jump.setRelativeJump(current));
  642. breakContinueJumps.clear();
  643. }
  644. }
  645. // 811