Fantasy 8Bit system (F8), is a fantasy 8bit console and a set of libraries for creating fantasy 8bit consoles.
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

375 lines
8.0KB

  1. const PRECEDENCE = {
  2. "#": 0, // Precedence 0 should be ignored!
  3. "=": 1,
  4. "<":7, "<=":7, ">":7, ">=":7, "==":7, "!=":7,
  5. "+": 10, "-": 10,
  6. "*": 20, "/": 20
  7. };
  8. function TokenStream(input){
  9. var pos = 0;
  10. function peek(off){
  11. off = off || 0;
  12. return (pos+off < input.length) ? input[pos+off] : null;
  13. }
  14. function next(){
  15. if (pos < input.length){
  16. let v = input[pos];
  17. pos += 1;
  18. return v;
  19. }
  20. return null;
  21. }
  22. function line(){
  23. return (pos < input.length) ? input[pos].line : -1;
  24. }
  25. function col(){
  26. return (pos < input.length) ? input[pos].col : -1;
  27. }
  28. function eol(){
  29. return (pos > 0 && pos < input.length) ? (input[pos-1].line !== input[pos].line) : true;
  30. }
  31. function eof(){
  32. return (pos >= input.length);
  33. }
  34. function die(msg){
  35. throw new Error(msg + " Line: " + input[pos].line + ", Col: " + input[pos].col);
  36. }
  37. function getPos(){return pos;}
  38. return {
  39. peek: peek,
  40. next: next,
  41. line: line,
  42. col: col,
  43. pos: getPos,
  44. eol: eol,
  45. eof, eof,
  46. die: die
  47. };
  48. }
  49. // var stream = null;
  50. // var SKIPEOL = false;
  51. function isTokenType(stream, type, val){
  52. let t = stream.peek();
  53. return (t && t.type === type && (!val || t.val === val) && t);
  54. }
  55. class Parser{
  56. constructor(pof){
  57. if (typeof(pof) !== 'function')
  58. throw new TypeError("Expected op code parsing function.");
  59. this.__stream = null;
  60. this.__SKIPEOL = false;
  61. this.__output = null;
  62. this.__PCLabel = "__PC__";
  63. this.parseOpCode = pof(this);
  64. }
  65. set tokens(t){
  66. this.__stream = new TokenStream(t);
  67. this.__output = null;
  68. this.__SKIPEOL = false;
  69. }
  70. get stream(){return this.__stream;}
  71. get PCLabel(){return this.__PCLabel;}
  72. set PCLabel(pcl){
  73. if (typeof(pcl) !== 'string' || pcl === "")
  74. throw new TypeError("PC Label must be a non-zero-length string.");
  75. this.__PCLabel = pcl;
  76. }
  77. isPunc(ch){
  78. return isTokenType(this.stream, "punc", ch);
  79. }
  80. isOpCode(ch){
  81. return isTokenType(this.stream, "opcode", ch);
  82. }
  83. isLabel(ch){
  84. return isTokenType(this.stream, "label", ch);
  85. }
  86. isDirective(ch){
  87. return isTokenType(this.stream, "directive", ch);
  88. }
  89. isOp(ch){
  90. return isTokenType(this.stream, "op", ch);
  91. }
  92. skipPunc(ch){
  93. if (!this.isPunc(ch))
  94. this.stream.die("Unexpected punctuation '" + ch + "'.");
  95. this.stream.next();
  96. }
  97. skipDirective(ch){
  98. if (!this.isDirective(ch))
  99. this.stream.die("Unexpected directive '" + ch + "'.");
  100. this.stream.next();
  101. }
  102. parseDelimited(s,e,d,parser){
  103. let toEOL = (s === null || e === null);
  104. let a = [];
  105. let first = true;
  106. if (!toEOL){this.skipPunc(s);}
  107. while (!this.stream.eof() && ((!toEOL && !this.isPunc(e)) || (toEOL && !this.stream.eol()))){
  108. if (first){
  109. first = false;
  110. } else {this.skipPunc(d);}
  111. a.push(parser());
  112. }
  113. if (!toEOL){this.skipPunc(e);}
  114. return a;
  115. }
  116. parseByteDirective(){
  117. let line = this.stream.line();
  118. let col = this.stream.col();
  119. this.stream.next();
  120. return {
  121. type: "directive",
  122. op: ".byte",
  123. args: this.parseDelimited(null, null,
  124. ",",
  125. this.parseExpression.bind(this)
  126. ),
  127. line: line,
  128. col: col
  129. }
  130. }
  131. parseElseIfDirective(){
  132. this.skipDirective(".elif");
  133. let cond = this.parseExpression();
  134. this.skipDirective(".then");
  135. let then = this.parseBlock([".else", ".elif", ".endif"]);
  136. let f = {
  137. type: "directive",
  138. op: "if",
  139. cond: cond,
  140. then: then
  141. }
  142. if (this.isDirective(".elif")){
  143. f["else"] = this.parseElseIfDirective();
  144. } else if (this.isDirective(".else")){
  145. this.skipDirective(".else");
  146. f["else"] = this.parseBlock([".endif"]);
  147. } else if(!this.isDirective(".endif")){
  148. this.stream.die("Expected '.endif' Directive.");
  149. }
  150. return f;
  151. }
  152. parseIfDirective(){
  153. this.skipDirective(".if");
  154. let cond = this.parseExpression();
  155. this.skipDirective(".then");
  156. let then = this.parseBlock([".elif", ".else", ".endif"]);
  157. let f = {
  158. type: "directive",
  159. op: "if",
  160. cond: cond,
  161. then: then
  162. };
  163. if (this.isDirective(".elif")){
  164. f["else"] = this.parseElseIfDirective();
  165. } else if (this.isDirective(".else")){
  166. this.skipDirective(".else");
  167. f["else"] = this.parseBlock([".endif"]);
  168. }
  169. this.skipDirective(".endif");
  170. return f;
  171. }
  172. parseLabel(t){
  173. if (!this.stream.eof()){
  174. let ct = this.stream.peek();
  175. if (ct.type === "opcode" && t.col === t.val.length){
  176. this.__SKIPEOL = true;
  177. return {
  178. type: "assign",
  179. op: "=",
  180. left: t,
  181. right: this.__PCLabel, // To designate "current program counter".
  182. line: t.line,
  183. col: t.col
  184. }
  185. }
  186. }
  187. return t;
  188. }
  189. parseTempLabel(){
  190. let isEOL = this.stream.eol();
  191. this.stream.next();
  192. if (isEOL){
  193. this.__SKIPEOL = true;
  194. return {
  195. type:"temp-label",
  196. val:-1, // Value of -1 means "set new temp label"
  197. line:this.stream.line(),
  198. col:this.stream.col()
  199. };
  200. }
  201. let t = this.stream.next();
  202. if (t.type === "number"){
  203. t.type = "temp-label";
  204. return t;
  205. }
  206. this.stream.die("Temp label missing index. New temp labels must be defined at the start of the line.");
  207. }
  208. parseAtom(){
  209. if (this.isPunc("(")){
  210. this.stream.next();
  211. let exp = this.parseExpression();
  212. if (this.isPunc(")")){
  213. this.stream.next();
  214. return exp;
  215. }
  216. } else if (this.isPunc(":")){
  217. return this.parseTempLabel();
  218. } else if (this.isOpCode()){
  219. return this.parseOpCode();
  220. } else if (this.isDirective(".if")){
  221. return this.parseIfDirective();
  222. } else if (this.isDirective(".bytes")){
  223. return this.parseByteDirective();
  224. }
  225. let tok = this.stream.next();
  226. if (tok.type === "number" || tok.type === "string"){
  227. return tok;
  228. } else if (tok.type === "label"){
  229. return this.parseLabel(tok);
  230. }
  231. this.stream.die("Unexpected token {type:" + tok.type + ", val:'" + tok.val + "'}.");
  232. }
  233. parseExpression(){
  234. return this.maybeCall((function(){
  235. return this.maybeBinary(this.parseAtom(), 0);
  236. }).bind(this));
  237. }
  238. parseCall(func){
  239. return {
  240. type: "call",
  241. func: func,
  242. args: this.parseDelimited(
  243. "(", ")",
  244. ",",
  245. this.parseExpression.bind(this)
  246. )
  247. };
  248. }
  249. maybeCall(expr){
  250. expr = expr();
  251. return (this.isOp("(")) ? this.parseCall(expr) : expr;
  252. }
  253. maybeBinary(left, pres){
  254. let tok = this.isOp();
  255. if (tok){
  256. let cpres = PRECEDENCE[tok.val];
  257. if (cpres > pres){
  258. this.stream.next();
  259. return this.maybeBinary({
  260. type: (tok.val === "=") ? "assign" : "binary",
  261. op: tok.val,
  262. left: left,
  263. right: this.maybeBinary(this.parseAtom(), cpres),
  264. line: tok.line,
  265. col: tok.col
  266. }, pres);
  267. }
  268. }
  269. return left;
  270. }
  271. parseBlock(bed){
  272. let line = this.stream.line();
  273. let col = this.stream.col();
  274. let exp = [];
  275. let isBlockEnd = (t) => {
  276. return (bed && t.type === 'directive' && bed.indexOf(t.val) >= 0);
  277. }
  278. while (!this.stream.eof()){
  279. if (isBlockEnd(this.stream.peek()))
  280. break;
  281. exp.push(this.parseExpression());
  282. if (!this.stream.eol()){
  283. if (!this.__SKIPEOL)
  284. this.stream.die("Expected End of Line.");
  285. this.__SKIPEOL = false;
  286. }
  287. }
  288. return {
  289. type: "block",
  290. expressions: exp,
  291. line: line,
  292. col: col
  293. };
  294. }
  295. /*
  296. parseProg(){
  297. let line = this.stream.line();
  298. let col = this.stream.col();
  299. return {
  300. type: "prog",
  301. block: this.parseBlock(),
  302. line: line,
  303. col: col
  304. };
  305. }
  306. */
  307. parse(tokens){
  308. if (tokens)
  309. this.tokens = tokens;
  310. if (this.__stream !== null){
  311. if (this.__output === null && this.__stream.pos() === 0)
  312. this.__output = this.parseBlock();
  313. return this.__output;
  314. }
  315. return null;
  316. }
  317. }
  318. module.exports = Parser;