Fantasy 8Bit system (F8), is a fantasy 8bit console and a set of libraries for creating fantasy 8bit consoles.
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.

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;