From 003640b43862e66256dd8e21711246d909431d98 Mon Sep 17 00:00:00 2001
From: sanine <sanine.not@pm.me>
Date: Mon, 22 May 2023 13:08:56 -0500
Subject: update grammar

---
 src/parser/grammar.jison     |  97 ++++--
 src/parser/grammar.jison.bak | 113 +++++++
 src/parser/grammar.js        | 762 +++++++++++++++++++++++++++++++++++++++++++
 src/parser/test.red          |   3 +
 4 files changed, 954 insertions(+), 21 deletions(-)
 create mode 100644 src/parser/grammar.jison.bak
 create mode 100644 src/parser/grammar.js
 create mode 100644 src/parser/test.red

(limited to 'src')

diff --git a/src/parser/grammar.jison b/src/parser/grammar.jison
index 4a17d2b..ae8793b 100644
--- a/src/parser/grammar.jison
+++ b/src/parser/grammar.jison
@@ -1,15 +1,18 @@
 %lex
 
+%s comment
+
 %%
 
-";"              { this.pushState('comment'); return "COMMENT"; }
+<<EOF>>          { return "EOF";     }
 <comment>"\n"    { this.popState(); return "NEWLINE";           }
 <comment>.       { /* ignore anything else inside a comment */  }
+";"              { this.pushState('comment'); }
 
-"\n"             { return "NEWLINE";       }
-\s+              { /* ignore whitespace */ }
+[\n]             { console.log("new!"); return "NEWLINE"; }
+\s              { /* ignore whitespace */ }
 
-"MOV"            { return "MOV"; }
+"MOV"            { console.log("mov"); return "MOV"; }
 "ADD"            { return "ADD"; }
 "SUB"            { return "SUB"; }
 "CMP"            { return "CMP"; }
@@ -24,6 +27,7 @@
 "EQU"            { return "EQU"; }
 "END"            { return "END"; }
 ":"              { return ":"; }
+","              { return ","; }
 
 "#"              { return "#"; }
 "@"              { return "@"; }
@@ -41,28 +45,73 @@
 
 /lex
 
-%%
+%left '+' '-'
+%left '*' '/'
+%left UMINUS
 
+%%
 
 program
-	: lines { return $lines; }
+	: lines NEWLINE END coda { yy.start = 0; return $lines; }
+	| lines NEWLINE END label coda { yy.start = yy.getLabel($label); return $lines; }
 	;
 
+coda
+	: newlines EOF
+	| EOF
+	;
+
+newlines
+	: newlines NEWLINE
+	| NEWLINE
+	;
 
 
 lines
-	: lines line { $lines.push($line); $$ = $lines; }
-	| line { $$ = [ $line ]; }
+	: lines line 
+	{ 
+		if ($line !== null) {
+			$lines.push($line);
+		}
+		$$ = $lines;
+	}
+	| line 
+	{
+		if ($line === null) {
+			$$ = [];
+		} else {
+			$$ = [ $line ];
+		}
+	}
 	;
 
 line
-	: op NEWLINE { $$ = $op; }
-	| op EOF     { $$ = $op; }
+	: row NEWLINE { $$ = $row; }
+	| NEWLINE { $$ = null; }
+	;
+
+row
+	: op 
+	{ 
+		yy.pc += 1;
+		$$ = $op; 
+	}
+	| label ":" op
+	{
+		yy.setLabel($label, yy.pc);
+		yy.pc += 1;
+		$$ = $op;
+	}
+	| label EQU e
+	{
+		yy.setLabel($label, $e);
+		$$ = null;
+	}
 	;
 
 
 op
-	: opcode address address
+	: opcode address "," address
 	{ $$ = { opcode: $opcode, a: $address1, b: $address2 }; }
 	;
 
@@ -82,7 +131,7 @@ opcode
 	;
 
 
-address:
+address
 	: address_mode e { $$ = { mode: $1, value: $2 }; }
 	| e              { $$ = { mode: 'direct', value: $1 }; }
 	;
@@ -90,22 +139,28 @@ address:
 
 address_mode
 	: "#" { $$ = 'immediate';    }
-	| "@" { $$ = 'indirect';     } 
+	| "@" { $$ = 'indirect';     }
 	| "<" { $$ = 'predecrement'; }
 	| "$" { $$ = 'direct';       }
 	;
 
+
 e
-	: NUMBER { $$ = Math.floor(Number(yytext)); }
-	| LABEL  {
-		$$ = yy.getLabel(yytext);
-		if ($$ === null) {
-			YYABORT;
-		}
-	}
-	| e '+' e   { $$ = Math.floor($e1 + $e2); }
+	: e '+' e   { $$ = Math.floor($e1 + $e2); }
 	| e '-' e   { $$ = Math.floor($e1 - $e2); }
 	| e '*' e   { $$ = Math.floor($e1 * $e2); }
 	| e '/' e   { $$ = Math.floor($e1 / $e2); }
 	| '(' e ')' { $$ = Math.floor($e); }
+	| '-' e %prec UMINUS { $$ = - $e; }
+	| NUMBER { $$ = Math.floor(Number(yytext)); }
+	| label {
+		$$ = yy.getLabel($label);
+	}
+	;
+
+
+label
+	: LABEL { $$ = yytext; }
 	;
+
+
diff --git a/src/parser/grammar.jison.bak b/src/parser/grammar.jison.bak
new file mode 100644
index 0000000..6ac55df
--- /dev/null
+++ b/src/parser/grammar.jison.bak
@@ -0,0 +1,113 @@
+%lex
+
+%x comment
+
+%%
+
+";"              { this.pushState('comment'); return "COMMENT"; }
+<comment>"\n"    { this.popState(); return "NEWLINE";           }
+<comment>.       { /* ignore anything else inside a comment */  }
+
+"\n"             { return "NEWLINE";       }
+\s+              { /* ignore whitespace */ }
+
+"MOV"            { return "MOV"; }
+"ADD"            { return "ADD"; }
+"SUB"            { return "SUB"; }
+"CMP"            { return "CMP"; }
+"SLT"            { return "SLT"; }
+"JMP"            { return "JMP"; }
+"JMZ"            { return "JMZ"; }
+"JMN"            { return "JMN"; }
+"DJN"            { return "DJN"; }
+"SPL"            { return "SPL"; }
+"DAT"            { return "DAT"; }
+
+"EQU"            { return "EQU"; }
+"END"            { return "END"; }
+":"              { return ":"; }
+
+"#"              { return "#"; }
+"@"              { return "@"; }
+"<"              { return "<"; }
+"$"              { return "$"; }
+
+"("              { return "("; }
+")"              { return ")"; }
+"+"              { return "+"; }
+"-"              { return "-"; }
+"*"              { return "*"; }
+"/"              { return "/"; }
+[0-9]+           { return "NUMBER"; }
+[A-Z][A-Z0-9_]+  { return "LABEL";  }
+
+/lex
+
+%%
+
+
+program
+	: lines { return $lines; }
+	;
+
+
+
+lines
+	: lines line { $lines.push($line); $$ = $lines; }
+	| line { $$ = [ $line ]; }
+	;
+
+line
+	: op NEWLINE { $$ = $op; }
+	| op EOF     { $$ = $op; }
+	;
+
+
+op
+	: opcode address address
+	{ $$ = { opcode: $opcode, a: $address1, b: $address2 }; }
+	;
+
+
+opcode
+	: MOV { $$ = $1; }
+	| ADD { $$ = $1; }
+	| SUB { $$ = $1; }
+	| CMP { $$ = $1; }
+	| SLT { $$ = $1; }
+	| JMP { $$ = $1; }
+	| JMZ { $$ = $1; }
+	| JMN { $$ = $1; }
+	| DJN { $$ = $1; }
+	| SPL { $$ = $1; }
+	| DAT { $$ = $1; }
+	;
+
+
+address:
+	: address_mode e { $$ = { mode: $1, value: $2 }; }
+	| e              { $$ = { mode: 'direct', value: $1 }; }
+	;
+
+
+address_mode
+	: "#" { $$ = 'immediate';    }
+	| "@" { $$ = 'indirect';     } 
+	| "<" { $$ = 'predecrement'; }
+	| "$" { $$ = 'direct';       }
+	;
+
+e
+	: NUMBER { $$ = Math.floor(Number(yytext)); }
+	| LABEL  {
+		$$ = yy.getLabel(yytext);
+		if ($$ === null) {
+			YYABORT;
+		}
+	}
+	| e '+' e   { $$ = Math.floor($e1 + $e2); }
+	| e '-' e   { $$ = Math.floor($e1 - $e2); }
+	| e '*' e   { $$ = Math.floor($e1 * $e2); }
+	| e '/' e   { $$ = Math.floor($e1 / $e2); }
+	| '(' e ')' { $$ = Math.floor($e); }
+	;
diff --git a/src/parser/grammar.js b/src/parser/grammar.js
new file mode 100644
index 0000000..73505c3
--- /dev/null
+++ b/src/parser/grammar.js
@@ -0,0 +1,762 @@
+/* parser generated by jison 0.4.18 */
+/*
+  Returns a Parser object of the following structure:
+
+  Parser: {
+    yy: {}
+  }
+
+  Parser.prototype: {
+    yy: {},
+    trace: function(),
+    symbols_: {associative list: name ==> number},
+    terminals_: {associative list: number ==> name},
+    productions_: [...],
+    performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$),
+    table: [...],
+    defaultActions: {...},
+    parseError: function(str, hash),
+    parse: function(input),
+
+    lexer: {
+        EOF: 1,
+        parseError: function(str, hash),
+        setInput: function(input),
+        input: function(),
+        unput: function(str),
+        more: function(),
+        less: function(n),
+        pastInput: function(),
+        upcomingInput: function(),
+        showPosition: function(),
+        test_match: function(regex_match_array, rule_index),
+        next: function(),
+        lex: function(),
+        begin: function(condition),
+        popState: function(),
+        _currentRules: function(),
+        topState: function(),
+        pushState: function(condition),
+
+        options: {
+            ranges: boolean           (optional: true ==> token location info will include a .range[] member)
+            flex: boolean             (optional: true ==> flex-like lexing behaviour where the rules are tested exhaustively to find the longest match)
+            backtrack_lexer: boolean  (optional: true ==> lexer regexes are tested in order and for each matching regex the action code is invoked; the lexer terminates the scan when a token is returned by the action code)
+        },
+
+        performAction: function(yy, yy_, $avoiding_name_collisions, YY_START),
+        rules: [...],
+        conditions: {associative list: name ==> set},
+    }
+  }
+
+
+  token location info (@$, _$, etc.): {
+    first_line: n,
+    last_line: n,
+    first_column: n,
+    last_column: n,
+    range: [start_number, end_number]       (where the numbers are indexes into the input string, regular zero-based)
+  }
+
+
+  the parseError function receives a 'hash' object with these members for lexer and parser errors: {
+    text:        (matched text)
+    token:       (the produced terminal token, if any)
+    line:        (yylineno)
+  }
+  while parser (grammar) errors will also provide these members, i.e. parser errors deliver a superset of attributes: {
+    loc:         (yylloc)
+    expected:    (string describing the set of expected tokens)
+    recoverable: (boolean: TRUE when the parser has a error recovery rule available for this particular error)
+  }
+*/
+var grammar = (function(){
+var o=function(k,v,o,l){for(o=o||{},l=k.length;l--;o[k[l]]=v);return o},$V0=[1,10],$V1=[1,11],$V2=[1,12],$V3=[1,13],$V4=[1,14],$V5=[1,15],$V6=[1,16],$V7=[1,17],$V8=[1,18],$V9=[1,19],$Va=[1,20],$Vb=[1,9],$Vc=[5,20,21,22,23,24,25,26,27,28,29,30,43],$Vd=[2,10],$Ve=[1,29],$Vf=[1,30],$Vg=[1,31],$Vh=[1,32],$Vi=[1,34],$Vj=[1,33],$Vk=[1,35],$Vl=[32,33,34,35,37,40,42,43],$Vm=[5,19],$Vn=[1,42],$Vo=[1,43],$Vp=[1,44],$Vq=[1,45],$Vr=[37,40,42,43],$Vs=[5,19,36,37,38,39,41],$Vt=[1,52],$Vu=[1,51],$Vv=[5,10],$Vw=[5,19,36,37,41];
+var parser = {trace: function trace () { },
+yy: {},
+symbols_: {"error":2,"program":3,"lines":4,"NEWLINE":5,"END":6,"coda":7,"label":8,"newlines":9,"EOF":10,"line":11,"row":12,"op":13,":":14,"EQU":15,"e":16,"opcode":17,"address":18,",":19,"MOV":20,"ADD":21,"SUB":22,"CMP":23,"SLT":24,"JMP":25,"JMZ":26,"JMN":27,"DJN":28,"SPL":29,"DAT":30,"address_mode":31,"#":32,"@":33,"<":34,"$":35,"+":36,"-":37,"*":38,"/":39,"(":40,")":41,"NUMBER":42,"LABEL":43,"$accept":0,"$end":1},
+terminals_: {2:"error",5:"NEWLINE",6:"END",10:"EOF",14:":",15:"EQU",19:",",20:"MOV",21:"ADD",22:"SUB",23:"CMP",24:"SLT",25:"JMP",26:"JMZ",27:"JMN",28:"DJN",29:"SPL",30:"DAT",32:"#",33:"@",34:"<",35:"$",36:"+",37:"-",38:"*",39:"/",40:"(",41:")",42:"NUMBER",43:"LABEL"},
+productions_: [0,[3,4],[3,5],[7,2],[7,1],[9,2],[9,1],[4,2],[4,1],[11,2],[11,1],[12,1],[12,3],[12,3],[13,4],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[18,2],[18,1],[31,1],[31,1],[31,1],[31,1],[16,3],[16,3],[16,3],[16,3],[16,3],[16,2],[16,1],[16,1],[8,1]],
+performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */, $$ /* vstack */, _$ /* lstack */) {
+/* this == yyval */
+
+var $0 = $$.length - 1;
+switch (yystate) {
+case 1:
+ yy.start = 0; return $$[$0-3]; 
+break;
+case 2:
+ yy.start = yy.getLabel($$[$0-1]); return $$[$0-4]; 
+break;
+case 7:
+ 
+		if ($$[$0] !== null) {
+			$$[$0-1].push($$[$0]);
+		}
+		this.$ = $$[$0-1];
+	
+break;
+case 8:
+
+		if ($$[$0] === null) {
+			this.$ = [];
+		} else {
+			this.$ = [ $$[$0] ];
+		}
+	
+break;
+case 9:
+ this.$ = $$[$0-1]; 
+break;
+case 10:
+ this.$ = null; 
+break;
+case 11:
+ 
+		yy.pc += 1;
+		this.$ = $$[$0]; 
+	
+break;
+case 12:
+
+		yy.setLabel($$[$0-2], yy.pc);
+		yy.pc += 1;
+		this.$ = $$[$0];
+	
+break;
+case 13:
+
+		yy.setLabel($$[$0-2], $$[$0]);
+		this.$ = null;
+	
+break;
+case 14:
+ this.$ = { opcode: $$[$0-3], a: $$[$0-2], b: $$[$0] }; 
+break;
+case 15: case 16: case 17: case 18: case 19: case 20: case 21: case 22: case 23: case 24: case 25:
+ this.$ = $$[$0]; 
+break;
+case 26:
+ this.$ = { mode: $$[$0-1], value: $$[$0] }; 
+break;
+case 27:
+ this.$ = { mode: 'direct', value: $$[$0] }; 
+break;
+case 28:
+ this.$ = 'immediate';    
+break;
+case 29:
+ this.$ = 'indirect';     
+break;
+case 30:
+ this.$ = 'predecrement'; 
+break;
+case 31:
+ this.$ = 'direct';       
+break;
+case 32:
+ this.$ = Math.floor($$[$0-2] + $$[$0]); 
+break;
+case 33:
+ this.$ = Math.floor($$[$0-2] - $$[$0]); 
+break;
+case 34:
+ this.$ = Math.floor($$[$0-2] * $$[$0]); 
+break;
+case 35:
+ this.$ = Math.floor($$[$0-2] / $$[$0]); 
+break;
+case 36:
+ this.$ = Math.floor($$[$0-1]); 
+break;
+case 37:
+ this.$ = - $$[$0]; 
+break;
+case 38:
+ this.$ = Math.floor(Number(yytext)); 
+break;
+case 39:
+
+		this.$ = yy.getLabel($$[$0]);
+	
+break;
+case 40:
+ this.$ = yytext; 
+break;
+}
+},
+table: [{3:1,4:2,5:[1,5],8:7,11:3,12:4,13:6,17:8,20:$V0,21:$V1,22:$V2,23:$V3,24:$V4,25:$V5,26:$V6,27:$V7,28:$V8,29:$V9,30:$Va,43:$Vb},{1:[3]},{5:[1,21],8:7,11:22,12:4,13:6,17:8,20:$V0,21:$V1,22:$V2,23:$V3,24:$V4,25:$V5,26:$V6,27:$V7,28:$V8,29:$V9,30:$Va,43:$Vb},o($Vc,[2,8]),{5:[1,23]},o($Vc,$Vd),{5:[2,11]},{14:[1,24],15:[1,25]},{8:36,16:28,18:26,31:27,32:$Ve,33:$Vf,34:$Vg,35:$Vh,37:$Vi,40:$Vj,42:$Vk,43:$Vb},o([5,10,14,15,19,36,37,38,39,41],[2,40]),o($Vl,[2,15]),o($Vl,[2,16]),o($Vl,[2,17]),o($Vl,[2,18]),o($Vl,[2,19]),o($Vl,[2,20]),o($Vl,[2,21]),o($Vl,[2,22]),o($Vl,[2,23]),o($Vl,[2,24]),o($Vl,[2,25]),o($Vc,$Vd,{6:[1,37]}),o($Vc,[2,7]),o($Vc,[2,9]),{13:38,17:8,20:$V0,21:$V1,22:$V2,23:$V3,24:$V4,25:$V5,26:$V6,27:$V7,28:$V8,29:$V9,30:$Va},{8:36,16:39,37:$Vi,40:$Vj,42:$Vk,43:$Vb},{19:[1,40]},{8:36,16:41,37:$Vi,40:$Vj,42:$Vk,43:$Vb},o($Vm,[2,27],{36:$Vn,37:$Vo,38:$Vp,39:$Vq}),o($Vr,[2,28]),o($Vr,[2,29]),o($Vr,[2,30]),o($Vr,[2,31]),{8:36,16:46,37:$Vi,40:$Vj,42:$Vk,43:$Vb},{8:36,16:47,37:$Vi,40:$Vj,42:$Vk,43:$Vb},o($Vs,[2,38]),o($Vs,[2,39]),{5:$Vt,7:48,8:49,9:50,10:$Vu,43:$Vb},{5:[2,12]},{5:[2,13],36:$Vn,37:$Vo,38:$Vp,39:$Vq},{8:36,16:28,18:53,31:27,32:$Ve,33:$Vf,34:$Vg,35:$Vh,37:$Vi,40:$Vj,42:$Vk,43:$Vb},o($Vm,[2,26],{36:$Vn,37:$Vo,38:$Vp,39:$Vq}),{8:36,16:54,37:$Vi,40:$Vj,42:$Vk,43:$Vb},{8:36,16:55,37:$Vi,40:$Vj,42:$Vk,43:$Vb},{8:36,16:56,37:$Vi,40:$Vj,42:$Vk,43:$Vb},{8:36,16:57,37:$Vi,40:$Vj,42:$Vk,43:$Vb},{36:$Vn,37:$Vo,38:$Vp,39:$Vq,41:[1,58]},o($Vs,[2,37]),{1:[2,1]},{5:$Vt,7:59,9:50,10:$Vu},{5:[1,61],10:[1,60]},{1:[2,4]},o($Vv,[2,6]),{5:[2,14]},o($Vw,[2,32],{38:$Vp,39:$Vq}),o($Vw,[2,33],{38:$Vp,39:$Vq}),o($Vs,[2,34]),o($Vs,[2,35]),o($Vs,[2,36]),{1:[2,2]},{1:[2,3]},o($Vv,[2,5])],
+defaultActions: {6:[2,11],38:[2,12],48:[2,1],51:[2,4],53:[2,14],59:[2,2],60:[2,3]},
+parseError: function parseError (str, hash) {
+    if (hash.recoverable) {
+        this.trace(str);
+    } else {
+        var error = new Error(str);
+        error.hash = hash;
+        throw error;
+    }
+},
+parse: function parse(input) {
+    var self = this, stack = [0], tstack = [], vstack = [null], lstack = [], table = this.table, yytext = '', yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1;
+    var args = lstack.slice.call(arguments, 1);
+    var lexer = Object.create(this.lexer);
+    var sharedState = { yy: {} };
+    for (var k in this.yy) {
+        if (Object.prototype.hasOwnProperty.call(this.yy, k)) {
+            sharedState.yy[k] = this.yy[k];
+        }
+    }
+    lexer.setInput(input, sharedState.yy);
+    sharedState.yy.lexer = lexer;
+    sharedState.yy.parser = this;
+    if (typeof lexer.yylloc == 'undefined') {
+        lexer.yylloc = {};
+    }
+    var yyloc = lexer.yylloc;
+    lstack.push(yyloc);
+    var ranges = lexer.options && lexer.options.ranges;
+    if (typeof sharedState.yy.parseError === 'function') {
+        this.parseError = sharedState.yy.parseError;
+    } else {
+        this.parseError = Object.getPrototypeOf(this).parseError;
+    }
+    function popStack(n) {
+        stack.length = stack.length - 2 * n;
+        vstack.length = vstack.length - n;
+        lstack.length = lstack.length - n;
+    }
+    _token_stack:
+        var lex = function () {
+            var token;
+            token = lexer.lex() || EOF;
+            if (typeof token !== 'number') {
+                token = self.symbols_[token] || token;
+            }
+            return token;
+        };
+    var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected;
+    while (true) {
+        state = stack[stack.length - 1];
+        if (this.defaultActions[state]) {
+            action = this.defaultActions[state];
+        } else {
+            if (symbol === null || typeof symbol == 'undefined') {
+                symbol = lex();
+            }
+            action = table[state] && table[state][symbol];
+        }
+                    if (typeof action === 'undefined' || !action.length || !action[0]) {
+                var errStr = '';
+                expected = [];
+                for (p in table[state]) {
+                    if (this.terminals_[p] && p > TERROR) {
+                        expected.push('\'' + this.terminals_[p] + '\'');
+                    }
+                }
+                if (lexer.showPosition) {
+                    errStr = 'Parse error on line ' + (yylineno + 1) + ':\n' + lexer.showPosition() + '\nExpecting ' + expected.join(', ') + ', got \'' + (this.terminals_[symbol] || symbol) + '\'';
+                } else {
+                    errStr = 'Parse error on line ' + (yylineno + 1) + ': Unexpected ' + (symbol == EOF ? 'end of input' : '\'' + (this.terminals_[symbol] || symbol) + '\'');
+                }
+                this.parseError(errStr, {
+                    text: lexer.match,
+                    token: this.terminals_[symbol] || symbol,
+                    line: lexer.yylineno,
+                    loc: yyloc,
+                    expected: expected
+                });
+            }
+        if (action[0] instanceof Array && action.length > 1) {
+            throw new Error('Parse Error: multiple actions possible at state: ' + state + ', token: ' + symbol);
+        }
+        switch (action[0]) {
+        case 1:
+            stack.push(symbol);
+            vstack.push(lexer.yytext);
+            lstack.push(lexer.yylloc);
+            stack.push(action[1]);
+            symbol = null;
+            if (!preErrorSymbol) {
+                yyleng = lexer.yyleng;
+                yytext = lexer.yytext;
+                yylineno = lexer.yylineno;
+                yyloc = lexer.yylloc;
+                if (recovering > 0) {
+                    recovering--;
+                }
+            } else {
+                symbol = preErrorSymbol;
+                preErrorSymbol = null;
+            }
+            break;
+        case 2:
+            len = this.productions_[action[1]][1];
+            yyval.$ = vstack[vstack.length - len];
+            yyval._$ = {
+                first_line: lstack[lstack.length - (len || 1)].first_line,
+                last_line: lstack[lstack.length - 1].last_line,
+                first_column: lstack[lstack.length - (len || 1)].first_column,
+                last_column: lstack[lstack.length - 1].last_column
+            };
+            if (ranges) {
+                yyval._$.range = [
+                    lstack[lstack.length - (len || 1)].range[0],
+                    lstack[lstack.length - 1].range[1]
+                ];
+            }
+            r = this.performAction.apply(yyval, [
+                yytext,
+                yyleng,
+                yylineno,
+                sharedState.yy,
+                action[1],
+                vstack,
+                lstack
+            ].concat(args));
+            if (typeof r !== 'undefined') {
+                return r;
+            }
+            if (len) {
+                stack = stack.slice(0, -1 * len * 2);
+                vstack = vstack.slice(0, -1 * len);
+                lstack = lstack.slice(0, -1 * len);
+            }
+            stack.push(this.productions_[action[1]][0]);
+            vstack.push(yyval.$);
+            lstack.push(yyval._$);
+            newState = table[stack[stack.length - 2]][stack[stack.length - 1]];
+            stack.push(newState);
+            break;
+        case 3:
+            return true;
+        }
+    }
+    return true;
+}};
+/* generated by jison-lex 0.3.4 */
+var lexer = (function(){
+var lexer = ({
+
+EOF:1,
+
+parseError:function parseError(str, hash) {
+        if (this.yy.parser) {
+            this.yy.parser.parseError(str, hash);
+        } else {
+            throw new Error(str);
+        }
+    },
+
+// resets the lexer, sets new input
+setInput:function (input, yy) {
+        this.yy = yy || this.yy || {};
+        this._input = input;
+        this._more = this._backtrack = this.done = false;
+        this.yylineno = this.yyleng = 0;
+        this.yytext = this.matched = this.match = '';
+        this.conditionStack = ['INITIAL'];
+        this.yylloc = {
+            first_line: 1,
+            first_column: 0,
+            last_line: 1,
+            last_column: 0
+        };
+        if (this.options.ranges) {
+            this.yylloc.range = [0,0];
+        }
+        this.offset = 0;
+        return this;
+    },
+
+// consumes and returns one char from the input
+input:function () {
+        var ch = this._input[0];
+        this.yytext += ch;
+        this.yyleng++;
+        this.offset++;
+        this.match += ch;
+        this.matched += ch;
+        var lines = ch.match(/(?:\r\n?|\n).*/g);
+        if (lines) {
+            this.yylineno++;
+            this.yylloc.last_line++;
+        } else {
+            this.yylloc.last_column++;
+        }
+        if (this.options.ranges) {
+            this.yylloc.range[1]++;
+        }
+
+        this._input = this._input.slice(1);
+        return ch;
+    },
+
+// unshifts one char (or a string) into the input
+unput:function (ch) {
+        var len = ch.length;
+        var lines = ch.split(/(?:\r\n?|\n)/g);
+
+        this._input = ch + this._input;
+        this.yytext = this.yytext.substr(0, this.yytext.length - len);
+        //this.yyleng -= len;
+        this.offset -= len;
+        var oldLines = this.match.split(/(?:\r\n?|\n)/g);
+        this.match = this.match.substr(0, this.match.length - 1);
+        this.matched = this.matched.substr(0, this.matched.length - 1);
+
+        if (lines.length - 1) {
+            this.yylineno -= lines.length - 1;
+        }
+        var r = this.yylloc.range;
+
+        this.yylloc = {
+            first_line: this.yylloc.first_line,
+            last_line: this.yylineno + 1,
+            first_column: this.yylloc.first_column,
+            last_column: lines ?
+                (lines.length === oldLines.length ? this.yylloc.first_column : 0)
+                 + oldLines[oldLines.length - lines.length].length - lines[0].length :
+              this.yylloc.first_column - len
+        };
+
+        if (this.options.ranges) {
+            this.yylloc.range = [r[0], r[0] + this.yyleng - len];
+        }
+        this.yyleng = this.yytext.length;
+        return this;
+    },
+
+// When called from action, caches matched text and appends it on next action
+more:function () {
+        this._more = true;
+        return this;
+    },
+
+// When called from action, signals the lexer that this rule fails to match the input, so the next matching rule (regex) should be tested instead.
+reject:function () {
+        if (this.options.backtrack_lexer) {
+            this._backtrack = true;
+        } else {
+            return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n' + this.showPosition(), {
+                text: "",
+                token: null,
+                line: this.yylineno
+            });
+
+        }
+        return this;
+    },
+
+// retain first n characters of the match
+less:function (n) {
+        this.unput(this.match.slice(n));
+    },
+
+// displays already matched input, i.e. for error messages
+pastInput:function () {
+        var past = this.matched.substr(0, this.matched.length - this.match.length);
+        return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
+    },
+
+// displays upcoming input, i.e. for error messages
+upcomingInput:function () {
+        var next = this.match;
+        if (next.length < 20) {
+            next += this._input.substr(0, 20-next.length);
+        }
+        return (next.substr(0,20) + (next.length > 20 ? '...' : '')).replace(/\n/g, "");
+    },
+
+// displays the character position where the lexing error occurred, i.e. for error messages
+showPosition:function () {
+        var pre = this.pastInput();
+        var c = new Array(pre.length + 1).join("-");
+        return pre + this.upcomingInput() + "\n" + c + "^";
+    },
+
+// test the lexed token: return FALSE when not a match, otherwise return token
+test_match:function(match, indexed_rule) {
+        var token,
+            lines,
+            backup;
+
+        if (this.options.backtrack_lexer) {
+            // save context
+            backup = {
+                yylineno: this.yylineno,
+                yylloc: {
+                    first_line: this.yylloc.first_line,
+                    last_line: this.last_line,
+                    first_column: this.yylloc.first_column,
+                    last_column: this.yylloc.last_column
+                },
+                yytext: this.yytext,
+                match: this.match,
+                matches: this.matches,
+                matched: this.matched,
+                yyleng: this.yyleng,
+                offset: this.offset,
+                _more: this._more,
+                _input: this._input,
+                yy: this.yy,
+                conditionStack: this.conditionStack.slice(0),
+                done: this.done
+            };
+            if (this.options.ranges) {
+                backup.yylloc.range = this.yylloc.range.slice(0);
+            }
+        }
+
+        lines = match[0].match(/(?:\r\n?|\n).*/g);
+        if (lines) {
+            this.yylineno += lines.length;
+        }
+        this.yylloc = {
+            first_line: this.yylloc.last_line,
+            last_line: this.yylineno + 1,
+            first_column: this.yylloc.last_column,
+            last_column: lines ?
+                         lines[lines.length - 1].length - lines[lines.length - 1].match(/\r?\n?/)[0].length :
+                         this.yylloc.last_column + match[0].length
+        };
+        this.yytext += match[0];
+        this.match += match[0];
+        this.matches = match;
+        this.yyleng = this.yytext.length;
+        if (this.options.ranges) {
+            this.yylloc.range = [this.offset, this.offset += this.yyleng];
+        }
+        this._more = false;
+        this._backtrack = false;
+        this._input = this._input.slice(match[0].length);
+        this.matched += match[0];
+        token = this.performAction.call(this, this.yy, this, indexed_rule, this.conditionStack[this.conditionStack.length - 1]);
+        if (this.done && this._input) {
+            this.done = false;
+        }
+        if (token) {
+            return token;
+        } else if (this._backtrack) {
+            // recover context
+            for (var k in backup) {
+                this[k] = backup[k];
+            }
+            return false; // rule action called reject() implying the next rule should be tested instead.
+        }
+        return false;
+    },
+
+// return next match in input
+next:function () {
+        if (this.done) {
+            return this.EOF;
+        }
+        if (!this._input) {
+            this.done = true;
+        }
+
+        var token,
+            match,
+            tempMatch,
+            index;
+        if (!this._more) {
+            this.yytext = '';
+            this.match = '';
+        }
+        var rules = this._currentRules();
+        for (var i = 0; i < rules.length; i++) {
+            tempMatch = this._input.match(this.rules[rules[i]]);
+            if (tempMatch && (!match || tempMatch[0].length > match[0].length)) {
+                match = tempMatch;
+                index = i;
+                if (this.options.backtrack_lexer) {
+                    token = this.test_match(tempMatch, rules[i]);
+                    if (token !== false) {
+                        return token;
+                    } else if (this._backtrack) {
+                        match = false;
+                        continue; // rule action called reject() implying a rule MISmatch.
+                    } else {
+                        // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
+                        return false;
+                    }
+                } else if (!this.options.flex) {
+                    break;
+                }
+            }
+        }
+        if (match) {
+            token = this.test_match(match, rules[index]);
+            if (token !== false) {
+                return token;
+            }
+            // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
+            return false;
+        }
+        if (this._input === "") {
+            return this.EOF;
+        } else {
+            return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. Unrecognized text.\n' + this.showPosition(), {
+                text: "",
+                token: null,
+                line: this.yylineno
+            });
+        }
+    },
+
+// return next match that has a token
+lex:function lex () {
+        var r = this.next();
+        if (r) {
+            return r;
+        } else {
+            return this.lex();
+        }
+    },
+
+// activates a new lexer condition state (pushes the new lexer condition state onto the condition stack)
+begin:function begin (condition) {
+        this.conditionStack.push(condition);
+    },
+
+// pop the previously active lexer condition state off the condition stack
+popState:function popState () {
+        var n = this.conditionStack.length - 1;
+        if (n > 0) {
+            return this.conditionStack.pop();
+        } else {
+            return this.conditionStack[0];
+        }
+    },
+
+// produce the lexer rule set which is active for the currently active lexer condition state
+_currentRules:function _currentRules () {
+        if (this.conditionStack.length && this.conditionStack[this.conditionStack.length - 1]) {
+            return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules;
+        } else {
+            return this.conditions["INITIAL"].rules;
+        }
+    },
+
+// return the currently active lexer condition state; when an index argument is provided it produces the N-th previous condition state, if available
+topState:function topState (n) {
+        n = this.conditionStack.length - 1 - Math.abs(n || 0);
+        if (n >= 0) {
+            return this.conditionStack[n];
+        } else {
+            return "INITIAL";
+        }
+    },
+
+// alias for begin(condition)
+pushState:function pushState (condition) {
+        this.begin(condition);
+    },
+
+// return the number of states currently on the stack
+stateStackSize:function stateStackSize() {
+        return this.conditionStack.length;
+    },
+options: {},
+performAction: function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
+var YYSTATE=YY_START;
+switch($avoiding_name_collisions) {
+case 0: return "EOF";     
+break;
+case 1: this.popState(); return "NEWLINE";           
+break;
+case 2: /* ignore anything else inside a comment */  
+break;
+case 3: this.pushState('comment'); 
+break;
+case 4: console.log("new!"); return "NEWLINE"; 
+break;
+case 5: /* ignore whitespace */ 
+break;
+case 6: console.log("mov"); return "MOV"; 
+break;
+case 7: return "ADD"; 
+break;
+case 8: return "SUB"; 
+break;
+case 9: return "CMP"; 
+break;
+case 10: return "SLT"; 
+break;
+case 11: return "JMP"; 
+break;
+case 12: return "JMZ"; 
+break;
+case 13: return "JMN"; 
+break;
+case 14: return "DJN"; 
+break;
+case 15: return "SPL"; 
+break;
+case 16: return "DAT"; 
+break;
+case 17: return "EQU"; 
+break;
+case 18: return "END"; 
+break;
+case 19: return ":"; 
+break;
+case 20: return ","; 
+break;
+case 21: return "#"; 
+break;
+case 22: return "@"; 
+break;
+case 23: return "<"; 
+break;
+case 24: return "$"; 
+break;
+case 25: return "("; 
+break;
+case 26: return ")"; 
+break;
+case 27: return "+"; 
+break;
+case 28: return "-"; 
+break;
+case 29: return "*"; 
+break;
+case 30: return "/"; 
+break;
+case 31: return "NUMBER"; 
+break;
+case 32: return "LABEL";  
+break;
+}
+},
+rules: [/^(?:$)/,/^(?:\\n)/,/^(?:.)/,/^(?:;)/,/^(?:[\n])/,/^(?:\s)/,/^(?:MOV\b)/,/^(?:ADD\b)/,/^(?:SUB\b)/,/^(?:CMP\b)/,/^(?:SLT\b)/,/^(?:JMP\b)/,/^(?:JMZ\b)/,/^(?:JMN\b)/,/^(?:DJN\b)/,/^(?:SPL\b)/,/^(?:DAT\b)/,/^(?:EQU\b)/,/^(?:END\b)/,/^(?::)/,/^(?:,)/,/^(?:#)/,/^(?:@)/,/^(?:<)/,/^(?:\$)/,/^(?:\()/,/^(?:\))/,/^(?:\+)/,/^(?:-)/,/^(?:\*)/,/^(?:\/)/,/^(?:[0-9]+)/,/^(?:[A-Z][A-Z0-9_]+)/],
+conditions: {"comment":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32],"inclusive":true},"INITIAL":{"rules":[0,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32],"inclusive":true}}
+});
+return lexer;
+})();
+parser.lexer = lexer;
+function Parser () {
+  this.yy = {};
+}
+Parser.prototype = parser;parser.Parser = Parser;
+return new Parser;
+})();
+
+
+if (typeof require !== 'undefined' && typeof exports !== 'undefined') {
+exports.parser = grammar;
+exports.Parser = grammar.Parser;
+exports.parse = function () { return grammar.parse.apply(grammar, arguments); };
+exports.main = function commonjsMain (args) {
+    if (!args[1]) {
+        console.log('Usage: '+args[0]+' FILE');
+        process.exit(1);
+    }
+    var source = require('fs').readFileSync(require('path').normalize(args[1]), "utf8");
+    return exports.parser.parse(source);
+};
+if (typeof module !== 'undefined' && require.main === module) {
+  exports.main(process.argv.slice(1));
+}
+}
\ No newline at end of file
diff --git a/src/parser/test.red b/src/parser/test.red
new file mode 100644
index 0000000..54e3453
--- /dev/null
+++ b/src/parser/test.red
@@ -0,0 +1,3 @@
+MOV 0, 0
+
+END
-- 
cgit v1.2.1