%lex %x comment %% ";" { this.pushState('comment'); return "COMMENT"; } "\n" { this.popState(); return "NEWLINE"; } . { /* 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); } ;