├── src ├── app.js ├── ui │ ├── flag.filter.js │ ├── start.filter.js │ ├── number.filter.js │ ├── tab-support.directive.js │ ├── select-line.directive.js │ └── controller.js ├── emulator │ ├── memory.js │ ├── opcodes.js │ └── cpu.js └── assembler │ └── asm.js ├── .gitignore ├── package.json ├── assets ├── style.css ├── asmsimulator.min.js └── asmsimulator.js ├── Gruntfile.js ├── README.md ├── index.html └── instruction-set.html /src/app.js: -------------------------------------------------------------------------------- 1 | var app = angular.module('ASMSimulator', []); 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | .idea 3 | .DS_Store 4 | node_modules/* 5 | ASMSimulator.iml 6 | -------------------------------------------------------------------------------- /src/ui/flag.filter.js: -------------------------------------------------------------------------------- 1 | app.filter('flag', function() { 2 | return function(input) { 3 | return input.toString().toUpperCase(); 4 | }; 5 | }); 6 | -------------------------------------------------------------------------------- /src/ui/start.filter.js: -------------------------------------------------------------------------------- 1 | app.filter('startFrom', function() { 2 | return function(input, start) { 3 | start = +start; //parse to int 4 | return input.slice(start); 5 | }; 6 | }); 7 | -------------------------------------------------------------------------------- /src/ui/number.filter.js: -------------------------------------------------------------------------------- 1 | app.filter('number', function() { 2 | return function(input, isHex) { 3 | if (isHex) { 4 | var hex = input.toString(16).toUpperCase(); 5 | return hex.length == 1 ? "0" + hex: hex; 6 | } else { 7 | return input.toString(10); 8 | } 9 | }; 10 | }); 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "asmsimulator", 3 | "version": "0.5.1", 4 | "description": "Simple 8-bit Assembler Simulator in Javascript", 5 | "author": "Marco Schweighauser", 6 | "license": "MIT", 7 | "devDependencies": { 8 | "grunt": "~0.4.1", 9 | "grunt-contrib-concat": "~0.3.0", 10 | "grunt-contrib-uglify": "~0.2.4", 11 | "grunt-contrib-jshint": "~0.7.0", 12 | "grunt-contrib-watch": "~0.5.3" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/ui/tab-support.directive.js: -------------------------------------------------------------------------------- 1 | app.directive('tabSupport', [function () { 2 | return { 3 | restrict: 'A', 4 | link: function (scope, element, attrs, controller) { 5 | element.bind("keydown", function (e) { 6 | if (e.keyCode === 9) { 7 | var val = this.value; 8 | var start = this.selectionStart; 9 | var end = this.selectionEnd; 10 | 11 | this.value = val.substring(0, start) + '\t' + val.substring(end); 12 | this.selectionStart = this.selectionEnd = start + 1; 13 | 14 | e.preventDefault(); 15 | return false; 16 | } 17 | }); 18 | } 19 | }; 20 | }]); 21 | -------------------------------------------------------------------------------- /src/emulator/memory.js: -------------------------------------------------------------------------------- 1 | app.service('memory', [function () { 2 | var memory = { 3 | data: Array(256), 4 | lastAccess: -1, 5 | load: function (address) { 6 | var self = this; 7 | 8 | if (address < 0 || address >= self.data.length) { 9 | throw "Memory access violation at " + address; 10 | } 11 | 12 | self.lastAccess = address; 13 | return self.data[address]; 14 | }, 15 | store: function (address, value) { 16 | var self = this; 17 | 18 | if (address < 0 || address >= self.data.length) { 19 | throw "Memory access violation at " + address; 20 | } 21 | 22 | self.lastAccess = address; 23 | self.data[address] = value; 24 | }, 25 | reset: function () { 26 | var self = this; 27 | 28 | self.lastAccess = -1; 29 | for (var i = 0, l = self.data.length; i < l; i++) { 30 | self.data[i] = 0; 31 | } 32 | } 33 | }; 34 | 35 | memory.reset(); 36 | return memory; 37 | }]); 38 | -------------------------------------------------------------------------------- /assets/style.css: -------------------------------------------------------------------------------- 1 | .source-code { 2 | font-family: source-code-pro, Courier New; 3 | } 4 | 5 | .output, .output-bg { 6 | background-color: #DFDFDF; 7 | } 8 | 9 | .output { 10 | width: 1em; 11 | margin-right: 1px; 12 | margin-bottom: 1px; 13 | text-align: center; 14 | } 15 | 16 | .instr-bg { 17 | background-color: #E6F3FF; 18 | } 19 | 20 | .instr-bg a { 21 | color: inherit; 22 | cursor: pointer; 23 | } 24 | 25 | .stack-bg { 26 | background-color: #F8DCB4; 27 | } 28 | 29 | .memory-block { 30 | width: 1.8em; 31 | display: inline-block; 32 | text-align: center; 33 | } 34 | 35 | .marker { 36 | width: 1.8em; 37 | color: #FFF; 38 | border-radius: 2px; 39 | } 40 | 41 | .marker-ip { 42 | background-color: #428BCA; 43 | } 44 | 45 | .marker-sp { 46 | background-color: #EEA236; 47 | } 48 | 49 | .marker-a { 50 | background-color: #4D8C20; 51 | } 52 | 53 | .marker-b { 54 | background-color: #22D19D; 55 | } 56 | 57 | .marker-c { 58 | background-color: #A137ED; 59 | } 60 | 61 | .marker-d { 62 | background-color: #E81F1F; 63 | } 64 | 65 | .codelabel-line a { 66 | cursor: pointer; 67 | } -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | grunt.initConfig({ 4 | pkg: grunt.file.readJSON('package.json'), 5 | concat: { 6 | options: { 7 | separator: ';' 8 | }, 9 | dist: { 10 | src: ['src/app.js', 'src/**/*.js'], 11 | dest: 'assets/<%= pkg.name %>.js' 12 | } 13 | }, 14 | uglify: { 15 | options: { 16 | banner: '/*! <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */\n' 17 | }, 18 | dist: { 19 | files: { 20 | 'assets/<%= pkg.name %>.min.js': ['<%= concat.dist.dest %>'] 21 | } 22 | } 23 | }, 24 | jshint: { 25 | files: ['Gruntfile.js', 'src/**/*.js'], 26 | options: { 27 | // options here to override JSHint defaults 28 | trailing: true, 29 | globals: { 30 | browser: true, 31 | console: true 32 | } 33 | } 34 | }, 35 | watch: { 36 | files: ['<%= jshint.files %>'], 37 | tasks: ['jshint', 'concat'] 38 | } 39 | }); 40 | 41 | grunt.loadNpmTasks('grunt-contrib-uglify'); 42 | grunt.loadNpmTasks('grunt-contrib-jshint'); 43 | grunt.loadNpmTasks('grunt-contrib-watch'); 44 | grunt.loadNpmTasks('grunt-contrib-concat'); 45 | 46 | grunt.registerTask('default', ['jshint', 'concat', 'uglify']); 47 | 48 | }; 49 | -------------------------------------------------------------------------------- /src/ui/select-line.directive.js: -------------------------------------------------------------------------------- 1 | // Source: http://lostsource.com/2012/11/30/selecting-textarea-line.html 2 | app.directive('selectLine', [function () { 3 | return { 4 | restrict: 'A', 5 | link: function (scope, element, attrs, controller) { 6 | scope.$watch('selectedLine', function () { 7 | if (scope.selectedLine >= 0) { 8 | var lines = element[0].value.split("\n"); 9 | 10 | // Calculate start/end 11 | var startPos = 0; 12 | for (var x = 0; x < lines.length; x++) { 13 | if (x == scope.selectedLine) { 14 | break; 15 | } 16 | startPos += (lines[x].length + 1); 17 | } 18 | 19 | var endPos = lines[scope.selectedLine].length + startPos; 20 | 21 | // Chrome / Firefox 22 | if (typeof(element[0].selectionStart) != "undefined") { 23 | element[0].focus(); 24 | element[0].selectionStart = startPos; 25 | element[0].selectionEnd = endPos; 26 | } 27 | 28 | // IE 29 | if (document.selection && document.selection.createRange) { 30 | element[0].focus(); 31 | element[0].select(); 32 | var range = document.selection.createRange(); 33 | range.collapse(true); 34 | range.moveEnd("character", endPos); 35 | range.moveStart("character", startPos); 36 | range.select(); 37 | } 38 | } 39 | }); 40 | } 41 | }; 42 | }]); 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple 8-bit Assembler Simulator 2 | A simulator which provides a simplified assembler syntax (based on NASM) and is simulating a x86 like cpu. Press Help inside the simulator to see an overview about the supported instructions. 3 | 4 | ####TRY IT ONLINE 5 | 6 | ### Features 7 | - 8-bit CPU 8 | - 4 general purpose registers 9 | - 256 bytes of memory 10 | - Console output 11 | 12 | ### How to build 13 | Make sure you have Grunt installed to compile the `asmsimulator.js` script. 14 | Run `grunt` to build the project. 15 | 16 | ### Background 17 | A technical introduction is available on my blog: [www.mschweighauser.com](https://www.mschweighauser.com/make-your-own-assembler-simulator-in-javascript-part1/). 18 | 19 | ### License 20 | **The MIT License** 21 | 22 | Copyright (c) 2015 Marco Schweighauser 23 | 24 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 25 | 26 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 27 | 28 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 29 | -------------------------------------------------------------------------------- /src/emulator/opcodes.js: -------------------------------------------------------------------------------- 1 | app.service('opcodes', [function() { 2 | var opcodes = { 3 | NONE: 0, 4 | MOV_REG_TO_REG: 1, 5 | MOV_ADDRESS_TO_REG: 2, 6 | MOV_REGADDRESS_TO_REG: 3, 7 | MOV_REG_TO_ADDRESS: 4, 8 | MOV_REG_TO_REGADDRESS: 5, 9 | MOV_NUMBER_TO_REG: 6, 10 | MOV_NUMBER_TO_ADDRESS: 7, 11 | MOV_NUMBER_TO_REGADDRESS: 8, 12 | ADD_REG_TO_REG: 10, 13 | ADD_REGADDRESS_TO_REG: 11, 14 | ADD_ADDRESS_TO_REG: 12, 15 | ADD_NUMBER_TO_REG: 13, 16 | SUB_REG_FROM_REG: 14, 17 | SUB_REGADDRESS_FROM_REG: 15, 18 | SUB_ADDRESS_FROM_REG: 16, 19 | SUB_NUMBER_FROM_REG: 17, 20 | INC_REG: 18, 21 | DEC_REG: 19, 22 | CMP_REG_WITH_REG: 20, 23 | CMP_REGADDRESS_WITH_REG: 21, 24 | CMP_ADDRESS_WITH_REG: 22, 25 | CMP_NUMBER_WITH_REG: 23, 26 | JMP_REGADDRESS: 30, 27 | JMP_ADDRESS: 31, 28 | JC_REGADDRESS: 32, 29 | JC_ADDRESS: 33, 30 | JNC_REGADDRESS: 34, 31 | JNC_ADDRESS: 35, 32 | JZ_REGADDRESS: 36, 33 | JZ_ADDRESS: 37, 34 | JNZ_REGADDRESS: 38, 35 | JNZ_ADDRESS: 39, 36 | JA_REGADDRESS: 40, 37 | JA_ADDRESS: 41, 38 | JNA_REGADDRESS: 42, 39 | JNA_ADDRESS: 43, 40 | PUSH_REG: 50, 41 | PUSH_REGADDRESS: 51, 42 | PUSH_ADDRESS: 52, 43 | PUSH_NUMBER: 53, 44 | POP_REG: 54, 45 | CALL_REGADDRESS: 55, 46 | CALL_ADDRESS: 56, 47 | RET: 57, 48 | MUL_REG: 60, 49 | MUL_REGADDRESS: 61, 50 | MUL_ADDRESS: 62, 51 | MUL_NUMBER: 63, 52 | DIV_REG: 64, 53 | DIV_REGADDRESS: 65, 54 | DIV_ADDRESS: 66, 55 | DIV_NUMBER: 67, 56 | AND_REG_WITH_REG: 70, 57 | AND_REGADDRESS_WITH_REG: 71, 58 | AND_ADDRESS_WITH_REG: 72, 59 | AND_NUMBER_WITH_REG: 73, 60 | OR_REG_WITH_REG: 74, 61 | OR_REGADDRESS_WITH_REG: 75, 62 | OR_ADDRESS_WITH_REG: 76, 63 | OR_NUMBER_WITH_REG: 77, 64 | XOR_REG_WITH_REG: 78, 65 | XOR_REGADDRESS_WITH_REG: 79, 66 | XOR_ADDRESS_WITH_REG: 80, 67 | XOR_NUMBER_WITH_REG: 81, 68 | NOT_REG: 82, 69 | SHL_REG_WITH_REG: 90, 70 | SHL_REGADDRESS_WITH_REG: 91, 71 | SHL_ADDRESS_WITH_REG: 92, 72 | SHL_NUMBER_WITH_REG: 93, 73 | SHR_REG_WITH_REG: 94, 74 | SHR_REGADDRESS_WITH_REG: 95, 75 | SHR_ADDRESS_WITH_REG: 96, 76 | SHR_NUMBER_WITH_REG: 97 77 | }; 78 | 79 | return opcodes; 80 | }]); 81 | -------------------------------------------------------------------------------- /src/ui/controller.js: -------------------------------------------------------------------------------- 1 | app.controller('Ctrl', ['$document', '$scope', '$timeout', 'cpu', 'memory', 'assembler', function ($document, $scope, $timeout, cpu, memory, assembler) { 2 | $scope.memory = memory; 3 | $scope.cpu = cpu; 4 | $scope.error = ''; 5 | $scope.isRunning = false; 6 | $scope.displayHex = true; 7 | $scope.displayInstr = true; 8 | $scope.displayA = false; 9 | $scope.displayB = false; 10 | $scope.displayC = false; 11 | $scope.displayD = false; 12 | $scope.speeds = [{speed: 1, desc: "1 HZ"}, 13 | {speed: 4, desc: "4 HZ"}, 14 | {speed: 8, desc: "8 HZ"}, 15 | {speed: 16, desc: "16 HZ"}]; 16 | $scope.speed = 4; 17 | $scope.outputStartIndex = 232; 18 | 19 | $scope.code = "; Simple example\n; Writes Hello World to the output\n\n JMP start\nhello: DB \"Hello World!\" ; Variable\n DB 0 ; String terminator\n\nstart:\n MOV C, hello ; Point to var \n MOV D, 232 ; Point to output\n CALL print\n HLT ; Stop execution\n\nprint: ; print(C:*from, D:*to)\n PUSH A\n PUSH B\n MOV B, 0\n.loop:\n MOV A, [C] ; Get char from var\n MOV [D], A ; Write to output\n INC C\n INC D \n CMP B, [C] ; Check if end\n JNZ .loop ; jump if not\n\n POP B\n POP A\n RET"; 20 | 21 | $scope.reset = function () { 22 | cpu.reset(); 23 | memory.reset(); 24 | $scope.error = ''; 25 | $scope.selectedLine = -1; 26 | }; 27 | 28 | $scope.executeStep = function () { 29 | if (!$scope.checkPrgrmLoaded()) { 30 | $scope.assemble(); 31 | } 32 | 33 | try { 34 | // Execute 35 | var res = cpu.step(); 36 | 37 | // Mark in code 38 | if (cpu.ip in $scope.mapping) { 39 | $scope.selectedLine = $scope.mapping[cpu.ip]; 40 | } 41 | 42 | return res; 43 | } catch (e) { 44 | $scope.error = e; 45 | return false; 46 | } 47 | }; 48 | 49 | var runner; 50 | $scope.run = function () { 51 | if (!$scope.checkPrgrmLoaded()) { 52 | $scope.assemble(); 53 | } 54 | 55 | $scope.isRunning = true; 56 | runner = $timeout(function () { 57 | if ($scope.executeStep() === true) { 58 | $scope.run(); 59 | } else { 60 | $scope.isRunning = false; 61 | } 62 | }, 1000 / $scope.speed); 63 | }; 64 | 65 | $scope.stop = function () { 66 | $timeout.cancel(runner); 67 | $scope.isRunning = false; 68 | }; 69 | 70 | $scope.checkPrgrmLoaded = function () { 71 | for (var i = 0, l = memory.data.length; i < l; i++) { 72 | if (memory.data[i] !== 0) { 73 | return true; 74 | } 75 | } 76 | 77 | return false; 78 | }; 79 | 80 | $scope.getChar = function (value) { 81 | var text = String.fromCharCode(value); 82 | 83 | if (text.trim() === '') { 84 | return '\u00A0\u00A0'; 85 | } else { 86 | return text; 87 | } 88 | }; 89 | 90 | $scope.assemble = function () { 91 | try { 92 | $scope.reset(); 93 | 94 | var assembly = assembler.go($scope.code); 95 | $scope.mapping = assembly.mapping; 96 | var binary = assembly.code; 97 | $scope.labels = assembly.labels; 98 | 99 | if (binary.length > memory.data.length) 100 | throw "Binary code does not fit into the memory. Max " + memory.data.length + " bytes are allowed"; 101 | 102 | for (var i = 0, l = binary.length; i < l; i++) { 103 | memory.data[i] = binary[i]; 104 | } 105 | } catch (e) { 106 | if (e.line !== undefined) { 107 | $scope.error = e.line + " | " + e.error; 108 | $scope.selectedLine = e.line; 109 | } else { 110 | $scope.error = e.error; 111 | } 112 | } 113 | }; 114 | 115 | $scope.jumpToLine = function (index) { 116 | $document[0].getElementById('sourceCode').scrollIntoView(); 117 | $scope.selectedLine = $scope.mapping[index]; 118 | }; 119 | 120 | 121 | $scope.isInstruction = function (index) { 122 | return $scope.mapping !== undefined && 123 | $scope.mapping[index] !== undefined && 124 | $scope.displayInstr; 125 | }; 126 | 127 | $scope.getMemoryCellCss = function (index) { 128 | if (index >= $scope.outputStartIndex) { 129 | return 'output-bg'; 130 | } else if ($scope.isInstruction(index)) { 131 | return 'instr-bg'; 132 | } else if (index > cpu.sp && index <= cpu.maxSP) { 133 | return 'stack-bg'; 134 | } else { 135 | return ''; 136 | } 137 | }; 138 | 139 | $scope.getMemoryInnerCellCss = function (index) { 140 | if (index === cpu.ip) { 141 | return 'marker marker-ip'; 142 | } else if (index === cpu.sp) { 143 | return 'marker marker-sp'; 144 | } else if (index === cpu.gpr[0] && $scope.displayA) { 145 | return 'marker marker-a'; 146 | } else if (index === cpu.gpr[1] && $scope.displayB) { 147 | return 'marker marker-b'; 148 | } else if (index === cpu.gpr[2] && $scope.displayC) { 149 | return 'marker marker-c'; 150 | } else if (index === cpu.gpr[3] && $scope.displayD) { 151 | return 'marker marker-d'; 152 | } else { 153 | return ''; 154 | } 155 | }; 156 | }]); 157 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Simple 8-bit Assembler Simulator in Javascript 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Fork me on GitHub 13 | 28 |
29 |
{{ error }}
30 |
31 |
32 |
33 |
34 |

Code (Instruction Set)

35 |
36 |
37 |
38 | 45 | 46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |

Output

55 |
56 |
57 |
59 | {{ getChar(m) }} 60 |
61 |
62 |
63 |
64 |
65 |

CPU & Memory

66 |
67 |
68 |

Registers / Flags

69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 |
ABCDIPSPZCF
{{ cpu.gpr[0] | number:displayHex }}
{{ cpu.gpr[1] | number:displayHex }}
{{ cpu.gpr[2] | number:displayHex }}
{{ cpu.gpr[3] | number:displayHex }}
{{ cpu.ip | number:displayHex }}
{{ cpu.sp | number:displayHex }}
{{ cpu.zero | flag }}{{ cpu.carry | flag }}{{ cpu.fault | flag }}
97 |

RAM

98 |
99 |
102 |
103 | {{ m | number:displayHex }} 104 | 105 | {{ m | number:displayHex }} 106 | 107 |
108 |
109 |
110 |

111 | 112 | Clock speed: 113 | 114 | Instructions: 115 | Show 116 | Hide 117 | View: 118 | Hex 119 | Decimal 120 |
121 | Register addressing: 122 | A: 123 | Show 124 | Hide 125 | B: 126 | Show 127 | Hide 128 | C: 129 | Show 130 | Hide 131 | D: 132 | Show 133 | Hide 134 |
135 |

136 |
137 |
138 |
139 |
140 |

Labels

141 |
142 |
143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 157 | 158 |
NameAddressValue
{{ name }}{{ value | number:displayHex }}{{ memory.data[value] | number:displayHex }} 153 | 154 | ('{{ getChar(memory.data[value]) }}') 155 | 156 |
159 |
160 |
161 |
162 |
163 |
164 |

by Marco Schweighauser (2015) | MIT License | Blog

165 |
166 | 167 | 168 | 169 | 170 | -------------------------------------------------------------------------------- /instruction-set.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Simple 8-bit Assembler - Instruction Set Help 6 | 7 | 8 | 9 | 10 | Fork me on GitHub 11 | 21 |
22 |

Introduction

23 |

This simulator provides a simplified assembler syntax (based on NASM) and is simulating a x86 like cpu. In depth documentation and introduction to assembler can be found on the following websites:

24 | 29 |

The simulator consists of a 8-bit cpu and 256 bytes of memory. All instructions (code) and variables (data) needs to fit inside the memory. For simplicity every instruction (and operand) is 1 byte. Therefore a MOV instruction will use 3 bytes of memory. The simulator provides a console output which is memory mapped from 0xE8 to 0xFF. Memory mapped means that every value written to this memory block is visible on the console.

30 |

Syntax

31 |

The syntax is similar as most assemblers are using. Every instruction must be on their own line. Labels are optional and must either start with a letter or a dot (.) and end with a colon.

32 |
label: instruction operands	; Comment
33 |

Valid number formats for constants are:

34 |
 35 | Decimal: 200
 36 | Decimal: 200d
 37 | Hex: 0xA4
 38 | Octal: 0o48
 39 | Binary: 101b
 40 | 
41 |

It is possible to define a number using a character or multiple numbers (see instruction DB) by using a string.

42 |
 43 | Character: 'A'
 44 | String: "Hello World!"
 45 | 
46 |

Operands can either be one of the four general purpose registers, stack pointer register, a memory address or a constant. 47 | Stack pointer register can only be used as operand in MOV, ADD, SUB, CMP, INC and DEC instructions. 48 | Instead of defining an address as a constant or by using a register you can use labels. The assembler will then replace the label with the corresponding constant.

49 |
 50 | General purpose (GP) register: A, B, C, D
 51 | Stack pointer register: SP
 52 | Address using a GP register: [A]
 53 | Address using a GP register and offset: [D-3]
 54 | Address using SP register and offset: [SP+2]
 55 | Address using a constant: [100]
 56 | Address using a label: label
 57 | Constant: Any number between 0..255 (8bit unsigned)
 58 | Offset for indirect addressing: Integer between -16..+15 (sign is mandatory)
 59 | 
60 |

MOV - Copy a value

61 |

Copies a value from src to dest. The MOV instruction is the only one able to directly modify the memory. SP can be used as operand with MOV.

62 |
 63 | MOV reg, reg
 64 | MOV reg, address
 65 | MOV reg, constant
 66 | MOV address, reg
 67 | MOV address, constant
 68 | 
69 |

DB - Variable

70 |

Defines a variable. A variable can either be a single number, character or a string.

71 |
 72 | DB constant
 73 | 
74 |

Math operations

75 | Addition and Subtraction 76 |

Adds two numbers together or subtract one number form another. This operations will modify the carry and zero flag. SP can be used as operand with ADD and SUB.

77 |
 78 | ADD reg, reg
 79 | ADD reg, address
 80 | ADD reg, constant
 81 | SUB reg, reg
 82 | SUB reg, address
 83 | SUB reg, constant
 84 | 
85 | Increment and Decrement 86 |

Increments or decrements a register by one. This operations will modify the carry and zero flag. SP can be used as operand with INC and DEC.

87 |
 88 | INC reg
 89 | DEC reg
 90 | 
91 | Multiplication and division 92 |

Multiplies or divides the A register with the given value. This operations will modify the carry and zero flag.

93 |
 94 | MUL reg
 95 | MUL address
 96 | MUL constant
 97 | DIV reg
 98 | DIV address
 99 | DIV constant
100 | 
101 | Logical instructions 102 |

The following logical instructions are supported: AND, OR, XOR, NOT. This operations will modify the carry and zero flag.

103 |
104 | AND reg, reg
105 | AND reg, address
106 | AND reg, constant
107 | OR reg, reg
108 | OR reg, address
109 | OR reg, constant
110 | XOR reg, reg
111 | XOR reg, address
112 | XOR reg, constant
113 | NOT reg
114 | 
115 | Shift instructions 116 |

The following shift instructions are supported: SHL/SAL and SHR/SAR. As this simulator only supports unsigned numbers SHR and SAR yield the same result. This operations will modify the carry and zero flag.

117 |
118 | SHL reg, reg
119 | SHL reg, address
120 | SHL reg, constant
121 | SHR reg, reg
122 | SHR reg, address
123 | SHR reg, constant
124 | 
125 |

CMP - Compare

126 |

Compares two values and sets the zero flag to true if they are equal. SP can be used as operand with CMP. Use this instruction before a conditional jump.

127 |
128 | CMP reg, reg
129 | CMP reg, address
130 | CMP reg, constant
131 | 
132 |

Jumps

133 | JMP - Unconditional jump 134 |

Let the instruction pointer do a unconditional jump to the defined address.

135 |
136 | JMP address
137 | 
138 | Conditional jumps 139 |

Let the instruction pointer do a conditional jump to the defined address. See the table below for the available conditions.

140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 |
InstructionDescriptionConditionAlternatives
JCJump if carryCarry = TRUEJB, JNAE
JNCJump if no carryCarry = FALSEJNB, JAE
JZJump if zeroZero = TRUEJB, JE
JNZJump if no zeroZero = FALSEJNE
JA>Carry = FALSE && Zero = FALSEJNBE
JNBEnot <=Carry = FALSE && Zero = FALSEJA
JAE>=Carry = FALSEJNC, JNB
JNBnot <Carry = FALSEJNC, JAE
JB<Carry = TRUEJC, JNAE
JNAEnot >=Carry = TRUEJC, JB
JBE<=C = TRUE or Z = TRUEJNA
JNAnot >C = TRUE or Z = TRUEJBE
JE=Z = TRUEJZ
JNE!=Z = FALSEJNZ
236 | CALL - Function call 237 |

Call can be used to jump into a subroutine (function). Pushes the instruction address of the next instruction to the stack and jumps to the specified address.

238 |
239 | CALL address
240 | 
241 | RET - Exit a subroutine 242 |

Exits a subroutines by popping the return address previously pushed by the CALL instruction. Make sure the SP is balanced before calling RET otherwise the instruction pointer will have an ambiguous value.

243 |
244 | RET
245 | 
246 |

Stack instructions

247 | PUSH - Push to stack 248 |

Pushes a value to the stack. The stack grows down and the current position is available in the stack pointer register (SP). This instruction will decrease the SP.

249 |
250 | PUSH reg
251 | PUSH address
252 | PUSH constant
253 | 
254 | POP - Pop from stack 255 |

Pops a value from the stack to a register. This instruction will increase the SP.

256 |
257 | POP reg
258 | 
259 |

Other instructions

260 | HLT - Stops the processor. 261 |

Stops operation of the processor. Hit Reset button to reset IP before restarting.

262 |
263 | HLT
264 | 
265 |
266 |

by Marco Schweighauser (2015) | MIT License | Blog

267 |
268 | 269 | 270 | -------------------------------------------------------------------------------- /assets/asmsimulator.min.js: -------------------------------------------------------------------------------- 1 | /*! asmsimulator 19-07-2015 */ 2 | var app=angular.module("ASMSimulator",[]);app.service("assembler",["opcodes",function(a){return{go:function(b){for(var c=/^[\t ]*(?:([.A-Za-z]\w*)[:])?(?:[\t ]*([A-Za-z]{2,4})(?:[\t ]+(\[(\w+((\+|-)\d+)?)\]|\".+?\"|\'.+?\'|[.A-Za-z0-9]\w*)(?:[\t ]*[,][\t ]*(\[(\w+((\+|-)\d+)?)\]|\".+?\"|\'.+?\'|[.A-Za-z0-9]\w*))?)?)?/,d=3,e=7,f=/^[-+]?[0-9]+$/,g=/^[.A-Za-z]\w*$/,h=[],i={},j={},k={},l=b.split("\n"),m=function(a){if("0x"===a.slice(0,2))return parseInt(a.slice(2),16);if("0o"===a.slice(0,2))return parseInt(a.slice(2),8);if("b"===a.slice(a.length-1))return parseInt(a.slice(0,a.length-1),2);if("d"===a.slice(a.length-1))return parseInt(a.slice(0,a.length-1),10);if(f.exec(a))return parseInt(a,10);throw"Invalid number format"},n=function(a){return a=a.toUpperCase(),"A"===a?0:"B"===a?1:"C"===a?2:"D"===a?3:"SP"===a?4:void 0},o=function(a){a=a.toUpperCase();var b=0,c=0;if("A"===a[0])c=0;else if("B"===a[0])c=1;else if("C"===a[0])c=2;else if("D"===a[0])c=3;else{if("SP"!==a.slice(0,2))return void 0;c=4}var d=1;if(4===c&&(d=2),"-"===a[d])b=-1;else{if("+"!==a[d])return void 0;b=1}var e=b*parseInt(a.slice(d+1),10);if(-16>e||e>15)throw"offset must be a value between -16...+15";return 0>e&&(e=32+e),8*e+c},p=function(a,b,c){var d=n(a);if(void 0!==d)return{type:b,value:d};var e=q(a);if(void 0!==e)return{type:c,value:e};if("regaddress"===b&&(d=o(a),void 0!==d))return{type:b,value:d};var f=m(a);if(isNaN(f))throw"Not a "+c+": "+f;if(0>f||f>255)throw c+" must have a value between 0-255";return{type:c,value:f}},q=function(a){return g.exec(a)?a:void 0},r=function(a){switch(a.slice(0,1)){case"[":var b=a.slice(1,a.length-1);return p(b,"regaddress","address");case'"':for(var c=a.slice(1,a.length-1),d=[],e=0,f=c.length;f>e;e++)d.push(c.charCodeAt(e));return{type:"numbers",value:d};case"'":var g=a.slice(1,a.length-1);if(g.length>1)throw"Only one character is allowed. Use String instead";return{type:"number",value:g.charCodeAt(0)};default:return p(a,"register","number")}},s=(function(a){var b=a.toUpperCase();if(b in k)throw"Duplicate label: "+a;if("A"===b||"B"===b||"C"===b||"D"===b)throw"Label contains keyword: "+b;j[a]=h.length}),t=function(a,b){if(void 0!==b)throw a+": too many arguments"},u=0,v=l.length;v>u;u++)try{var w=c.exec(l[u]);if(void 0!==w[1]||void 0!==w[2]){if(void 0!==w[1]&&s(w[1]),void 0!==w[2]){var x,y,z,A=w[2].toUpperCase();switch("DB"!==A&&(i[h.length]=u),A){case"DB":if(x=r(w[d]),"number"===x.type)h.push(x.value);else{if("numbers"!==x.type)throw"DB does not support this operand";for(var B=0,C=x.value.length;C>B;B++)h.push(x.value[B])}break;case"HLT":t("HLT",w[d]),z=a.NONE,h.push(z);break;case"MOV":if(x=r(w[d]),y=r(w[e]),"register"===x.type&&"register"===y.type)z=a.MOV_REG_TO_REG;else if("register"===x.type&&"address"===y.type)z=a.MOV_ADDRESS_TO_REG;else if("register"===x.type&&"regaddress"===y.type)z=a.MOV_REGADDRESS_TO_REG;else if("address"===x.type&&"register"===y.type)z=a.MOV_REG_TO_ADDRESS;else if("regaddress"===x.type&&"register"===y.type)z=a.MOV_REG_TO_REGADDRESS;else if("register"===x.type&&"number"===y.type)z=a.MOV_NUMBER_TO_REG;else if("address"===x.type&&"number"===y.type)z=a.MOV_NUMBER_TO_ADDRESS;else{if("regaddress"!==x.type||"number"!==y.type)throw"MOV does not support this operands";z=a.MOV_NUMBER_TO_REGADDRESS}h.push(z,x.value,y.value);break;case"ADD":if(x=r(w[d]),y=r(w[e]),"register"===x.type&&"register"===y.type)z=a.ADD_REG_TO_REG;else if("register"===x.type&&"regaddress"===y.type)z=a.ADD_REGADDRESS_TO_REG;else if("register"===x.type&&"address"===y.type)z=a.ADD_ADDRESS_TO_REG;else{if("register"!==x.type||"number"!==y.type)throw"ADD does not support this operands";z=a.ADD_NUMBER_TO_REG}h.push(z,x.value,y.value);break;case"SUB":if(x=r(w[d]),y=r(w[e]),"register"===x.type&&"register"===y.type)z=a.SUB_REG_FROM_REG;else if("register"===x.type&&"regaddress"===y.type)z=a.SUB_REGADDRESS_FROM_REG;else if("register"===x.type&&"address"===y.type)z=a.SUB_ADDRESS_FROM_REG;else{if("register"!==x.type||"number"!==y.type)throw"SUB does not support this operands";z=a.SUB_NUMBER_FROM_REG}h.push(z,x.value,y.value);break;case"INC":if(x=r(w[d]),t("INC",w[e]),"register"!==x.type)throw"INC does not support this operand";z=a.INC_REG,h.push(z,x.value);break;case"DEC":if(x=r(w[d]),t("DEC",w[e]),"register"!==x.type)throw"DEC does not support this operand";z=a.DEC_REG,h.push(z,x.value);break;case"CMP":if(x=r(w[d]),y=r(w[e]),"register"===x.type&&"register"===y.type)z=a.CMP_REG_WITH_REG;else if("register"===x.type&&"regaddress"===y.type)z=a.CMP_REGADDRESS_WITH_REG;else if("register"===x.type&&"address"===y.type)z=a.CMP_ADDRESS_WITH_REG;else{if("register"!==x.type||"number"!==y.type)throw"CMP does not support this operands";z=a.CMP_NUMBER_WITH_REG}h.push(z,x.value,y.value);break;case"JMP":if(x=r(w[d]),t("JMP",w[e]),"register"===x.type)z=a.JMP_REGADDRESS;else{if("number"!==x.type)throw"JMP does not support this operands";z=a.JMP_ADDRESS}h.push(z,x.value);break;case"JC":case"JB":case"JNAE":if(x=r(w[d]),t(A,w[e]),"register"===x.type)z=a.JC_REGADDRESS;else{if("number"!==x.type)throw A+" does not support this operand";z=a.JC_ADDRESS}h.push(z,x.value);break;case"JNC":case"JNB":case"JAE":if(x=r(w[d]),t(A,w[e]),"register"===x.type)z=a.JNC_REGADDRESS;else{if("number"!==x.type)throw A+"does not support this operand";z=a.JNC_ADDRESS}h.push(z,x.value);break;case"JZ":case"JE":if(x=r(w[d]),t(A,w[e]),"register"===x.type)z=a.JZ_REGADDRESS;else{if("number"!==x.type)throw A+" does not support this operand";z=a.JZ_ADDRESS}h.push(z,x.value);break;case"JNZ":case"JNE":if(x=r(w[d]),t(A,w[e]),"register"===x.type)z=a.JNZ_REGADDRESS;else{if("number"!==x.type)throw A+" does not support this operand";z=a.JNZ_ADDRESS}h.push(z,x.value);break;case"JA":case"JNBE":if(x=r(w[d]),t(A,w[e]),"register"===x.type)z=a.JA_REGADDRESS;else{if("number"!==x.type)throw A+" does not support this operand";z=a.JA_ADDRESS}h.push(z,x.value);break;case"JNA":case"JBE":if(x=r(w[d]),t(A,w[e]),"register"===x.type)z=a.JNA_REGADDRESS;else{if("number"!==x.type)throw A+" does not support this operand";z=a.JNA_ADDRESS}h.push(z,x.value);break;case"PUSH":if(x=r(w[d]),t(A,w[e]),"register"===x.type)z=a.PUSH_REG;else if("regaddress"===x.type)z=a.PUSH_REGADDRESS;else if("address"===x.type)z=a.PUSH_ADDRESS;else{if("number"!==x.type)throw"PUSH does not support this operand";z=a.PUSH_NUMBER}h.push(z,x.value);break;case"POP":if(x=r(w[d]),t(A,w[e]),"register"!==x.type)throw"PUSH does not support this operand";z=a.POP_REG,h.push(z,x.value);break;case"CALL":if(x=r(w[d]),t(A,w[e]),"register"===x.type)z=a.CALL_REGADDRESS;else{if("number"!==x.type)throw"CALL does not support this operand";z=a.CALL_ADDRESS}h.push(z,x.value);break;case"RET":t(A,w[d]),z=a.RET,h.push(z);break;case"MUL":if(x=r(w[d]),t(A,w[e]),"register"===x.type)z=a.MUL_REG;else if("regaddress"===x.type)z=a.MUL_REGADDRESS;else if("address"===x.type)z=a.MUL_ADDRESS;else{if("number"!==x.type)throw"MULL does not support this operand";z=a.MUL_NUMBER}h.push(z,x.value);break;case"DIV":if(x=r(w[d]),t(A,w[e]),"register"===x.type)z=a.DIV_REG;else if("regaddress"===x.type)z=a.DIV_REGADDRESS;else if("address"===x.type)z=a.DIV_ADDRESS;else{if("number"!==x.type)throw"DIV does not support this operand";z=a.DIV_NUMBER}h.push(z,x.value);break;case"AND":if(x=r(w[d]),y=r(w[e]),"register"===x.type&&"register"===y.type)z=a.AND_REG_WITH_REG;else if("register"===x.type&&"regaddress"===y.type)z=a.AND_REGADDRESS_WITH_REG;else if("register"===x.type&&"address"===y.type)z=a.AND_ADDRESS_WITH_REG;else{if("register"!==x.type||"number"!==y.type)throw"AND does not support this operands";z=a.AND_NUMBER_WITH_REG}h.push(z,x.value,y.value);break;case"OR":if(x=r(w[d]),y=r(w[e]),"register"===x.type&&"register"===y.type)z=a.OR_REG_WITH_REG;else if("register"===x.type&&"regaddress"===y.type)z=a.OR_REGADDRESS_WITH_REG;else if("register"===x.type&&"address"===y.type)z=a.OR_ADDRESS_WITH_REG;else{if("register"!==x.type||"number"!==y.type)throw"OR does not support this operands";z=a.OR_NUMBER_WITH_REG}h.push(z,x.value,y.value);break;case"XOR":if(x=r(w[d]),y=r(w[e]),"register"===x.type&&"register"===y.type)z=a.XOR_REG_WITH_REG;else if("register"===x.type&&"regaddress"===y.type)z=a.XOR_REGADDRESS_WITH_REG;else if("register"===x.type&&"address"===y.type)z=a.XOR_ADDRESS_WITH_REG;else{if("register"!==x.type||"number"!==y.type)throw"XOR does not support this operands";z=a.XOR_NUMBER_WITH_REG}h.push(z,x.value,y.value);break;case"NOT":if(x=r(w[d]),t(A,w[e]),"register"!==x.type)throw"NOT does not support this operand";z=a.NOT_REG,h.push(z,x.value);break;case"SHL":case"SAL":if(x=r(w[d]),y=r(w[e]),"register"===x.type&&"register"===y.type)z=a.SHL_REG_WITH_REG;else if("register"===x.type&&"regaddress"===y.type)z=a.SHL_REGADDRESS_WITH_REG;else if("register"===x.type&&"address"===y.type)z=a.SHL_ADDRESS_WITH_REG;else{if("register"!==x.type||"number"!==y.type)throw A+" does not support this operands";z=a.SHL_NUMBER_WITH_REG}h.push(z,x.value,y.value);break;case"SHR":case"SAR":if(x=r(w[d]),y=r(w[e]),"register"===x.type&&"register"===y.type)z=a.SHR_REG_WITH_REG;else if("register"===x.type&&"regaddress"===y.type)z=a.SHR_REGADDRESS_WITH_REG;else if("register"===x.type&&"address"===y.type)z=a.SHR_ADDRESS_WITH_REG;else{if("register"!==x.type||"number"!==y.type)throw A+" does not support this operands";z=a.SHR_NUMBER_WITH_REG}h.push(z,x.value,y.value);break;default:throw"Invalid instruction: "+w[2]}}}else{var D=l[u].trim();if(""!==D&&";"!==D.slice(0,1))throw"Syntax error"}}catch(E){throw{error:E,line:u}}for(u=0,v=h.length;v>u;u++)if(!angular.isNumber(h[u])){if(!(h[u]in j))throw{error:"Undefined label: "+h[u]};h[u]=j[h[u]]}return{code:h,mapping:i,labels:j}}}}]),app.service("cpu",["opcodes","memory",function(a,b){var c={step:function(){var c=this;if(c.fault===!0)throw"FAULT. Reset to continue.";try{var d=function(a){if(0>a||a>=c.gpr.length)throw"Invalid register: "+a;return a},e=function(a){if(0>a||a>=1+c.gpr.length)throw"Invalid register: "+a;return a},f=function(a,b){if(a>=0&&ac.maxSP)throw"Stack underflow"}},g=function(a){if(a>=0&&a15&&(e-=32),b+e},i=function(a){return c.zero=!1,c.carry=!1,a>=256?(c.carry=!0,a%=256):0===a?c.zero=!0:0>a&&(c.carry=!0,a=256- -a%256),a},j=function(a){if(0>a||a>=b.data.length)throw"IP outside memory";c.ip=a},k=function(a){if(b.store(c.sp--,a),c.spc.maxSP)throw"Stack underflow";return a},m=function(a){if(0===a)throw"Division by 0";return Math.floor(c.gpr[0]/a)};if(c.ip<0||c.ip>=b.data.length)throw"Instruction pointer is outside of memory";var n,o,p,q,r,s=b.load(c.ip);switch(s){case a.NONE:return!1;case a.MOV_REG_TO_REG:n=e(b.load(++c.ip)),o=e(b.load(++c.ip)),f(n,g(o)),c.ip++;break;case a.MOV_ADDRESS_TO_REG:n=e(b.load(++c.ip)),p=b.load(++c.ip),f(n,b.load(p)),c.ip++;break;case a.MOV_REGADDRESS_TO_REG:n=e(b.load(++c.ip)),o=b.load(++c.ip),f(n,b.load(h(o))),c.ip++;break;case a.MOV_REG_TO_ADDRESS:q=b.load(++c.ip),o=e(b.load(++c.ip)),b.store(q,g(o)),c.ip++;break;case a.MOV_REG_TO_REGADDRESS:n=b.load(++c.ip),o=e(b.load(++c.ip)),b.store(h(n),g(o)),c.ip++;break;case a.MOV_NUMBER_TO_REG:n=e(b.load(++c.ip)),r=b.load(++c.ip),f(n,r),c.ip++;break;case a.MOV_NUMBER_TO_ADDRESS:q=b.load(++c.ip),r=b.load(++c.ip),b.store(q,r),c.ip++;break;case a.MOV_NUMBER_TO_REGADDRESS:n=b.load(++c.ip),r=b.load(++c.ip),b.store(h(n),r),c.ip++;break;case a.ADD_REG_TO_REG:n=e(b.load(++c.ip)),o=e(b.load(++c.ip)),f(n,i(g(n)+g(o))),c.ip++;break;case a.ADD_REGADDRESS_TO_REG:n=e(b.load(++c.ip)),o=b.load(++c.ip),f(n,i(g(n)+b.load(h(o)))),c.ip++;break;case a.ADD_ADDRESS_TO_REG:n=e(b.load(++c.ip)),p=b.load(++c.ip),f(n,i(g(n)+b.load(p))),c.ip++;break;case a.ADD_NUMBER_TO_REG:n=e(b.load(++c.ip)),r=b.load(++c.ip),f(n,i(g(n)+r)),c.ip++;break;case a.SUB_REG_FROM_REG:n=e(b.load(++c.ip)),o=e(b.load(++c.ip)),f(n,i(g(n)-c.gpr[o])),c.ip++;break;case a.SUB_REGADDRESS_FROM_REG:n=e(b.load(++c.ip)),o=b.load(++c.ip),f(n,i(g(n)-b.load(h(o)))),c.ip++;break;case a.SUB_ADDRESS_FROM_REG:n=e(b.load(++c.ip)),p=b.load(++c.ip),f(n,i(g(n)-b.load(p))),c.ip++;break;case a.SUB_NUMBER_FROM_REG:n=e(b.load(++c.ip)),r=b.load(++c.ip),f(n,i(g(n)-r)),c.ip++;break;case a.INC_REG:n=e(b.load(++c.ip)),f(n,i(g(n)+1)),c.ip++;break;case a.DEC_REG:n=e(b.load(++c.ip)),f(n,i(g(n)-1)),c.ip++;break;case a.CMP_REG_WITH_REG:n=e(b.load(++c.ip)),o=e(b.load(++c.ip)),i(g(n)-g(o)),c.ip++;break;case a.CMP_REGADDRESS_WITH_REG:n=e(b.load(++c.ip)),o=b.load(++c.ip),i(g(n)-b.load(h(o))),c.ip++;break;case a.CMP_ADDRESS_WITH_REG:n=e(b.load(++c.ip)),p=b.load(++c.ip),i(g(n)-b.load(p)),c.ip++;break;case a.CMP_NUMBER_WITH_REG:n=e(b.load(++c.ip)),r=b.load(++c.ip),i(g(n)-r),c.ip++;break;case a.JMP_REGADDRESS:n=d(b.load(++c.ip)),j(c.gpr[n]);break;case a.JMP_ADDRESS:r=b.load(++c.ip),j(r);break;case a.JC_REGADDRESS:n=d(b.load(++c.ip)),c.carry?j(c.gpr[n]):c.ip++;break;case a.JC_ADDRESS:r=b.load(++c.ip),c.carry?j(r):c.ip++;break;case a.JNC_REGADDRESS:n=d(b.load(++c.ip)),c.carry?c.ip++:j(c.gpr[n]);break;case a.JNC_ADDRESS:r=b.load(++c.ip),c.carry?c.ip++:j(r);break;case a.JZ_REGADDRESS:n=d(b.load(++c.ip)),c.zero?j(c.gpr[n]):c.ip++;break;case a.JZ_ADDRESS:r=b.load(++c.ip),c.zero?j(r):c.ip++;break;case a.JNZ_REGADDRESS:n=d(b.load(++c.ip)),c.zero?c.ip++:j(c.gpr[n]);break;case a.JNZ_ADDRESS:r=b.load(++c.ip),c.zero?c.ip++:j(r);break;case a.JA_REGADDRESS:n=d(b.load(++c.ip)),c.zero||c.carry?c.ip++:j(c.gpr[n]);break;case a.JA_ADDRESS:r=b.load(++c.ip),c.zero||c.carry?c.ip++:j(r);break;case a.JNA_REGADDRESS:n=d(b.load(++c.ip)),c.zero||c.carry?j(c.gpr[n]):c.ip++;break;case a.JNA_ADDRESS:r=b.load(++c.ip),c.zero||c.carry?j(r):c.ip++;break;case a.PUSH_REG:o=d(b.load(++c.ip)),k(c.gpr[o]),c.ip++;break;case a.PUSH_REGADDRESS:o=b.load(++c.ip),k(b.load(h(o))),c.ip++;break;case a.PUSH_ADDRESS:p=b.load(++c.ip),k(b.load(p)),c.ip++;break;case a.PUSH_NUMBER:r=b.load(++c.ip),k(r),c.ip++;break;case a.POP_REG:n=d(b.load(++c.ip)),c.gpr[n]=l(),c.ip++;break;case a.CALL_REGADDRESS:n=d(b.load(++c.ip)),k(c.ip+1),j(c.gpr[n]);break;case a.CALL_ADDRESS:r=b.load(++c.ip),k(c.ip+1),j(r);break;case a.RET:j(l());break;case a.MUL_REG:o=d(b.load(++c.ip)),c.gpr[0]=i(c.gpr[0]*c.gpr[o]),c.ip++;break;case a.MUL_REGADDRESS:o=b.load(++c.ip),c.gpr[0]=i(c.gpr[0]*b.load(h(o))),c.ip++;break;case a.MUL_ADDRESS:p=b.load(++c.ip),c.gpr[0]=i(c.gpr[0]*b.load(p)),c.ip++;break;case a.MUL_NUMBER:r=b.load(++c.ip),c.gpr[0]=i(c.gpr[0]*r),c.ip++;break;case a.DIV_REG:o=d(b.load(++c.ip)),c.gpr[0]=i(m(c.gpr[o])),c.ip++;break;case a.DIV_REGADDRESS:o=b.load(++c.ip),c.gpr[0]=i(m(b.load(h(o)))),c.ip++;break;case a.DIV_ADDRESS:p=b.load(++c.ip),c.gpr[0]=i(m(b.load(p))),c.ip++;break;case a.DIV_NUMBER:r=b.load(++c.ip),c.gpr[0]=i(m(r)),c.ip++;break;case a.AND_REG_WITH_REG:n=d(b.load(++c.ip)),o=d(b.load(++c.ip)),c.gpr[n]=i(c.gpr[n]&c.gpr[o]),c.ip++;break;case a.AND_REGADDRESS_WITH_REG:n=d(b.load(++c.ip)),o=b.load(++c.ip),c.gpr[n]=i(c.gpr[n]&b.load(h(o))),c.ip++;break;case a.AND_ADDRESS_WITH_REG:n=d(b.load(++c.ip)),p=b.load(++c.ip),c.gpr[n]=i(c.gpr[n]&b.load(p)),c.ip++;break;case a.AND_NUMBER_WITH_REG:n=d(b.load(++c.ip)),r=b.load(++c.ip),c.gpr[n]=i(c.gpr[n]&r),c.ip++;break;case a.OR_REG_WITH_REG:n=d(b.load(++c.ip)),o=d(b.load(++c.ip)),c.gpr[n]=i(c.gpr[n]|c.gpr[o]),c.ip++;break;case a.OR_REGADDRESS_WITH_REG:n=d(b.load(++c.ip)),o=b.load(++c.ip),c.gpr[n]=i(c.gpr[n]|b.load(h(o))),c.ip++;break;case a.OR_ADDRESS_WITH_REG:n=d(b.load(++c.ip)),p=b.load(++c.ip),c.gpr[n]=i(c.gpr[n]|b.load(p)),c.ip++;break;case a.OR_NUMBER_WITH_REG:n=d(b.load(++c.ip)),r=b.load(++c.ip),c.gpr[n]=i(c.gpr[n]|r),c.ip++;break;case a.XOR_REG_WITH_REG:n=d(b.load(++c.ip)),o=d(b.load(++c.ip)),c.gpr[n]=i(c.gpr[n]^c.gpr[o]),c.ip++;break;case a.XOR_REGADDRESS_WITH_REG:n=d(b.load(++c.ip)),o=b.load(++c.ip),c.gpr[n]=i(c.gpr[n]^b.load(h(o))),c.ip++;break;case a.XOR_ADDRESS_WITH_REG:n=d(b.load(++c.ip)),p=b.load(++c.ip),c.gpr[n]=i(c.gpr[n]^b.load(p)),c.ip++;break;case a.XOR_NUMBER_WITH_REG:n=d(b.load(++c.ip)),r=b.load(++c.ip),c.gpr[n]=i(c.gpr[n]^r),c.ip++;break;case a.NOT_REG:n=d(b.load(++c.ip)),c.gpr[n]=i(~c.gpr[n]),c.ip++;break;case a.SHL_REG_WITH_REG:n=d(b.load(++c.ip)),o=d(b.load(++c.ip)),c.gpr[n]=i(c.gpr[n]<>>c.gpr[o]),c.ip++;break;case a.SHR_REGADDRESS_WITH_REG:n=d(b.load(++c.ip)),o=b.load(++c.ip),c.gpr[n]=i(c.gpr[n]>>>b.load(h(o))),c.ip++;break;case a.SHR_ADDRESS_WITH_REG:n=d(b.load(++c.ip)),p=b.load(++c.ip),c.gpr[n]=i(c.gpr[n]>>>b.load(p)),c.ip++;break;case a.SHR_NUMBER_WITH_REG:n=d(b.load(++c.ip)),r=b.load(++c.ip),c.gpr[n]=i(c.gpr[n]>>>r),c.ip++;break;default:throw"Invalid op code: "+s}return!0}catch(t){throw c.fault=!0,t}},reset:function(){var a=this;a.maxSP=231,a.minSP=0,a.gpr=[0,0,0,0],a.sp=a.maxSP,a.ip=0,a.zero=!1,a.carry=!1,a.fault=!1}};return c.reset(),c}]),app.service("memory",[function(){var a={data:Array(256),lastAccess:-1,load:function(a){var b=this;if(0>a||a>=b.data.length)throw"Memory access violation at "+a;return b.lastAccess=a,b.data[a]},store:function(a,b){var c=this;if(0>a||a>=c.data.length)throw"Memory access violation at "+a;c.lastAccess=a,c.data[a]=b},reset:function(){var a=this;a.lastAccess=-1;for(var b=0,c=a.data.length;c>b;b++)a.data[b]=0}};return a.reset(),a}]),app.service("opcodes",[function(){var a={NONE:0,MOV_REG_TO_REG:1,MOV_ADDRESS_TO_REG:2,MOV_REGADDRESS_TO_REG:3,MOV_REG_TO_ADDRESS:4,MOV_REG_TO_REGADDRESS:5,MOV_NUMBER_TO_REG:6,MOV_NUMBER_TO_ADDRESS:7,MOV_NUMBER_TO_REGADDRESS:8,ADD_REG_TO_REG:10,ADD_REGADDRESS_TO_REG:11,ADD_ADDRESS_TO_REG:12,ADD_NUMBER_TO_REG:13,SUB_REG_FROM_REG:14,SUB_REGADDRESS_FROM_REG:15,SUB_ADDRESS_FROM_REG:16,SUB_NUMBER_FROM_REG:17,INC_REG:18,DEC_REG:19,CMP_REG_WITH_REG:20,CMP_REGADDRESS_WITH_REG:21,CMP_ADDRESS_WITH_REG:22,CMP_NUMBER_WITH_REG:23,JMP_REGADDRESS:30,JMP_ADDRESS:31,JC_REGADDRESS:32,JC_ADDRESS:33,JNC_REGADDRESS:34,JNC_ADDRESS:35,JZ_REGADDRESS:36,JZ_ADDRESS:37,JNZ_REGADDRESS:38,JNZ_ADDRESS:39,JA_REGADDRESS:40,JA_ADDRESS:41,JNA_REGADDRESS:42,JNA_ADDRESS:43,PUSH_REG:50,PUSH_REGADDRESS:51,PUSH_ADDRESS:52,PUSH_NUMBER:53,POP_REG:54,CALL_REGADDRESS:55,CALL_ADDRESS:56,RET:57,MUL_REG:60,MUL_REGADDRESS:61,MUL_ADDRESS:62,MUL_NUMBER:63,DIV_REG:64,DIV_REGADDRESS:65,DIV_ADDRESS:66,DIV_NUMBER:67,AND_REG_WITH_REG:70,AND_REGADDRESS_WITH_REG:71,AND_ADDRESS_WITH_REG:72,AND_NUMBER_WITH_REG:73,OR_REG_WITH_REG:74,OR_REGADDRESS_WITH_REG:75,OR_ADDRESS_WITH_REG:76,OR_NUMBER_WITH_REG:77,XOR_REG_WITH_REG:78,XOR_REGADDRESS_WITH_REG:79,XOR_ADDRESS_WITH_REG:80,XOR_NUMBER_WITH_REG:81,NOT_REG:82,SHL_REG_WITH_REG:90,SHL_REGADDRESS_WITH_REG:91,SHL_ADDRESS_WITH_REG:92,SHL_NUMBER_WITH_REG:93,SHR_REG_WITH_REG:94,SHR_REGADDRESS_WITH_REG:95,SHR_ADDRESS_WITH_REG:96,SHR_NUMBER_WITH_REG:97};return a}]),app.controller("Ctrl",["$document","$scope","$timeout","cpu","memory","assembler",function(a,b,c,d,e,f){b.memory=e,b.cpu=d,b.error="",b.isRunning=!1,b.displayHex=!0,b.displayInstr=!0,b.displayA=!1,b.displayB=!1,b.displayC=!1,b.displayD=!1,b.speeds=[{speed:1,desc:"1 HZ"},{speed:4,desc:"4 HZ"},{speed:8,desc:"8 HZ"},{speed:16,desc:"16 HZ"}],b.speed=4,b.outputStartIndex=232,b.code='; Simple example\n; Writes Hello World to the output\n\n JMP start\nhello: DB "Hello World!" ; Variable\n DB 0 ; String terminator\n\nstart:\n MOV C, hello ; Point to var \n MOV D, 232 ; Point to output\n CALL print\n HLT ; Stop execution\n\nprint: ; print(C:*from, D:*to)\n PUSH A\n PUSH B\n MOV B, 0\n.loop:\n MOV A, [C] ; Get char from var\n MOV [D], A ; Write to output\n INC C\n INC D \n CMP B, [C] ; Check if end\n JNZ .loop ; jump if not\n\n POP B\n POP A\n RET',b.reset=function(){d.reset(),e.reset(),b.error="",b.selectedLine=-1},b.executeStep=function(){b.checkPrgrmLoaded()||b.assemble();try{var a=d.step();return d.ip in b.mapping&&(b.selectedLine=b.mapping[d.ip]),a}catch(c){return b.error=c,!1}};var g;b.run=function(){b.checkPrgrmLoaded()||b.assemble(),b.isRunning=!0,g=c(function(){b.executeStep()===!0?b.run():b.isRunning=!1},1e3/b.speed)},b.stop=function(){c.cancel(g),b.isRunning=!1},b.checkPrgrmLoaded=function(){for(var a=0,b=e.data.length;b>a;a++)if(0!==e.data[a])return!0;return!1},b.getChar=function(a){var b=String.fromCharCode(a);return""===b.trim()?"  ":b},b.assemble=function(){try{b.reset();var a=f.go(b.code);b.mapping=a.mapping;var c=a.code;if(b.labels=a.labels,c.length>e.data.length)throw"Binary code does not fit into the memory. Max "+e.data.length+" bytes are allowed";for(var d=0,g=c.length;g>d;d++)e.data[d]=c[d]}catch(h){void 0!==h.line?(b.error=h.line+" | "+h.error,b.selectedLine=h.line):b.error=h.error}},b.jumpToLine=function(c){a[0].getElementById("sourceCode").scrollIntoView(),b.selectedLine=b.mapping[c]},b.isInstruction=function(a){return void 0!==b.mapping&&void 0!==b.mapping[a]&&b.displayInstr},b.getMemoryCellCss=function(a){return a>=b.outputStartIndex?"output-bg":b.isInstruction(a)?"instr-bg":a>d.sp&&a<=d.maxSP?"stack-bg":""},b.getMemoryInnerCellCss=function(a){return a===d.ip?"marker marker-ip":a===d.sp?"marker marker-sp":a===d.gpr[0]&&b.displayA?"marker marker-a":a===d.gpr[1]&&b.displayB?"marker marker-b":a===d.gpr[2]&&b.displayC?"marker marker-c":a===d.gpr[3]&&b.displayD?"marker marker-d":""}}]),app.filter("flag",function(){return function(a){return a.toString().toUpperCase()}}),app.filter("number",function(){return function(a,b){if(b){var c=a.toString(16).toUpperCase();return 1==c.length?"0"+c:c}return a.toString(10)}}),app.directive("selectLine",[function(){return{restrict:"A",link:function(a,b,c,d){a.$watch("selectedLine",function(){if(a.selectedLine>=0){for(var c=b[0].value.split("\n"),d=0,e=0;e= self.gpr.length) { 13 | throw "Invalid register: " + reg; 14 | } else { 15 | return reg; 16 | } 17 | }; 18 | 19 | var checkGPR_SP = function(reg) { 20 | if (reg < 0 || reg >= 1 + self.gpr.length) { 21 | throw "Invalid register: " + reg; 22 | } else { 23 | return reg; 24 | } 25 | }; 26 | 27 | var setGPR_SP = function(reg,value) 28 | { 29 | if(reg >= 0 && reg < self.gpr.length) { 30 | self.gpr[reg] = value; 31 | } else if(reg == self.gpr.length) { 32 | self.sp = value; 33 | 34 | // Not likely to happen, since we always get here after checkOpertion(). 35 | if (self.sp < self.minSP) { 36 | throw "Stack overflow"; 37 | } else if (self.sp > self.maxSP) { 38 | throw "Stack underflow"; 39 | } 40 | } else { 41 | throw "Invalid register: " + reg; 42 | } 43 | }; 44 | 45 | var getGPR_SP = function(reg) 46 | { 47 | if(reg >= 0 && reg < self.gpr.length) { 48 | return self.gpr[reg]; 49 | } else if(reg == self.gpr.length) { 50 | return self.sp; 51 | } else { 52 | throw "Invalid register: " + reg; 53 | } 54 | }; 55 | 56 | var indirectRegisterAddress = function(value) { 57 | var reg = value % 8; 58 | 59 | var base; 60 | if (reg < self.gpr.length) { 61 | base = self.gpr[reg]; 62 | } else { 63 | base = self.sp; 64 | } 65 | 66 | var offset = Math.floor(value / 8); 67 | if ( offset > 15 ) { 68 | offset = offset - 32; 69 | } 70 | 71 | return base+offset; 72 | }; 73 | 74 | var checkOperation = function(value) { 75 | self.zero = false; 76 | self.carry = false; 77 | 78 | if (value >= 256) { 79 | self.carry = true; 80 | value = value % 256; 81 | } else if (value === 0) { 82 | self.zero = true; 83 | } else if (value < 0) { 84 | self.carry = true; 85 | value = 256 - (-value) % 256; 86 | } 87 | 88 | return value; 89 | }; 90 | 91 | var jump = function(newIP) { 92 | if (newIP < 0 || newIP >= memory.data.length) { 93 | throw "IP outside memory"; 94 | } else { 95 | self.ip = newIP; 96 | } 97 | }; 98 | 99 | var push = function(value) { 100 | memory.store(self.sp--, value); 101 | if (self.sp < self.minSP) { 102 | throw "Stack overflow"; 103 | } 104 | }; 105 | 106 | var pop = function() { 107 | var value = memory.load(++self.sp); 108 | if (self.sp > self.maxSP) { 109 | throw "Stack underflow"; 110 | } 111 | 112 | return value; 113 | }; 114 | 115 | var division = function(divisor) { 116 | if (divisor === 0) { 117 | throw "Division by 0"; 118 | } 119 | 120 | return Math.floor(self.gpr[0] / divisor); 121 | }; 122 | 123 | if (self.ip < 0 || self.ip >= memory.data.length) { 124 | throw "Instruction pointer is outside of memory"; 125 | } 126 | 127 | var regTo, regFrom, memFrom, memTo, number; 128 | var instr = memory.load(self.ip); 129 | switch(instr) { 130 | case opcodes.NONE: 131 | return false; // Abort step 132 | case opcodes.MOV_REG_TO_REG: 133 | regTo = checkGPR_SP(memory.load(++self.ip)); 134 | regFrom = checkGPR_SP(memory.load(++self.ip)); 135 | setGPR_SP(regTo,getGPR_SP(regFrom)); 136 | self.ip++; 137 | break; 138 | case opcodes.MOV_ADDRESS_TO_REG: 139 | regTo = checkGPR_SP(memory.load(++self.ip)); 140 | memFrom = memory.load(++self.ip); 141 | setGPR_SP(regTo,memory.load(memFrom)); 142 | self.ip++; 143 | break; 144 | case opcodes.MOV_REGADDRESS_TO_REG: 145 | regTo = checkGPR_SP(memory.load(++self.ip)); 146 | regFrom = memory.load(++self.ip); 147 | setGPR_SP(regTo,memory.load(indirectRegisterAddress(regFrom))); 148 | self.ip++; 149 | break; 150 | case opcodes.MOV_REG_TO_ADDRESS: 151 | memTo = memory.load(++self.ip); 152 | regFrom = checkGPR_SP(memory.load(++self.ip)); 153 | memory.store(memTo, getGPR_SP(regFrom)); 154 | self.ip++; 155 | break; 156 | case opcodes.MOV_REG_TO_REGADDRESS: 157 | regTo = memory.load(++self.ip); 158 | regFrom = checkGPR_SP(memory.load(++self.ip)); 159 | memory.store(indirectRegisterAddress(regTo), getGPR_SP(regFrom)); 160 | self.ip++; 161 | break; 162 | case opcodes.MOV_NUMBER_TO_REG: 163 | regTo = checkGPR_SP(memory.load(++self.ip)); 164 | number = memory.load(++self.ip); 165 | setGPR_SP(regTo,number); 166 | self.ip++; 167 | break; 168 | case opcodes.MOV_NUMBER_TO_ADDRESS: 169 | memTo = memory.load(++self.ip); 170 | number = memory.load(++self.ip); 171 | memory.store(memTo, number); 172 | self.ip++; 173 | break; 174 | case opcodes.MOV_NUMBER_TO_REGADDRESS: 175 | regTo = memory.load(++self.ip); 176 | number = memory.load(++self.ip); 177 | memory.store(indirectRegisterAddress(regTo), number); 178 | self.ip++; 179 | break; 180 | case opcodes.ADD_REG_TO_REG: 181 | regTo = checkGPR_SP(memory.load(++self.ip)); 182 | regFrom = checkGPR_SP(memory.load(++self.ip)); 183 | setGPR_SP(regTo,checkOperation(getGPR_SP(regTo) + getGPR_SP(regFrom))); 184 | self.ip++; 185 | break; 186 | case opcodes.ADD_REGADDRESS_TO_REG: 187 | regTo = checkGPR_SP(memory.load(++self.ip)); 188 | regFrom = memory.load(++self.ip); 189 | setGPR_SP(regTo,checkOperation(getGPR_SP(regTo) + memory.load(indirectRegisterAddress(regFrom)))); 190 | self.ip++; 191 | break; 192 | case opcodes.ADD_ADDRESS_TO_REG: 193 | regTo = checkGPR_SP(memory.load(++self.ip)); 194 | memFrom = memory.load(++self.ip); 195 | setGPR_SP(regTo,checkOperation(getGPR_SP(regTo) + memory.load(memFrom))); 196 | self.ip++; 197 | break; 198 | case opcodes.ADD_NUMBER_TO_REG: 199 | regTo = checkGPR_SP(memory.load(++self.ip)); 200 | number = memory.load(++self.ip); 201 | setGPR_SP(regTo,checkOperation(getGPR_SP(regTo) + number)); 202 | self.ip++; 203 | break; 204 | case opcodes.SUB_REG_FROM_REG: 205 | regTo = checkGPR_SP(memory.load(++self.ip)); 206 | regFrom = checkGPR_SP(memory.load(++self.ip)); 207 | setGPR_SP(regTo,checkOperation(getGPR_SP(regTo) - self.gpr[regFrom])); 208 | self.ip++; 209 | break; 210 | case opcodes.SUB_REGADDRESS_FROM_REG: 211 | regTo = checkGPR_SP(memory.load(++self.ip)); 212 | regFrom = memory.load(++self.ip); 213 | setGPR_SP(regTo,checkOperation(getGPR_SP(regTo) - memory.load(indirectRegisterAddress(regFrom)))); 214 | self.ip++; 215 | break; 216 | case opcodes.SUB_ADDRESS_FROM_REG: 217 | regTo = checkGPR_SP(memory.load(++self.ip)); 218 | memFrom = memory.load(++self.ip); 219 | setGPR_SP(regTo,checkOperation(getGPR_SP(regTo) - memory.load(memFrom))); 220 | self.ip++; 221 | break; 222 | case opcodes.SUB_NUMBER_FROM_REG: 223 | regTo = checkGPR_SP(memory.load(++self.ip)); 224 | number = memory.load(++self.ip); 225 | setGPR_SP(regTo,checkOperation(getGPR_SP(regTo) - number)); 226 | self.ip++; 227 | break; 228 | case opcodes.INC_REG: 229 | regTo = checkGPR_SP(memory.load(++self.ip)); 230 | setGPR_SP(regTo,checkOperation(getGPR_SP(regTo) + 1)); 231 | self.ip++; 232 | break; 233 | case opcodes.DEC_REG: 234 | regTo = checkGPR_SP(memory.load(++self.ip)); 235 | setGPR_SP(regTo,checkOperation(getGPR_SP(regTo) - 1)); 236 | self.ip++; 237 | break; 238 | case opcodes.CMP_REG_WITH_REG: 239 | regTo = checkGPR_SP(memory.load(++self.ip)); 240 | regFrom = checkGPR_SP(memory.load(++self.ip)); 241 | checkOperation(getGPR_SP(regTo) - getGPR_SP(regFrom)); 242 | self.ip++; 243 | break; 244 | case opcodes.CMP_REGADDRESS_WITH_REG: 245 | regTo = checkGPR_SP(memory.load(++self.ip)); 246 | regFrom = memory.load(++self.ip); 247 | checkOperation(getGPR_SP(regTo) - memory.load(indirectRegisterAddress(regFrom))); 248 | self.ip++; 249 | break; 250 | case opcodes.CMP_ADDRESS_WITH_REG: 251 | regTo = checkGPR_SP(memory.load(++self.ip)); 252 | memFrom = memory.load(++self.ip); 253 | checkOperation(getGPR_SP(regTo) - memory.load(memFrom)); 254 | self.ip++; 255 | break; 256 | case opcodes.CMP_NUMBER_WITH_REG: 257 | regTo = checkGPR_SP(memory.load(++self.ip)); 258 | number = memory.load(++self.ip); 259 | checkOperation(getGPR_SP(regTo) - number); 260 | self.ip++; 261 | break; 262 | case opcodes.JMP_REGADDRESS: 263 | regTo = checkGPR(memory.load(++self.ip)); 264 | jump(self.gpr[regTo]); 265 | break; 266 | case opcodes.JMP_ADDRESS: 267 | number = memory.load(++self.ip); 268 | jump(number); 269 | break; 270 | case opcodes.JC_REGADDRESS: 271 | regTo = checkGPR(memory.load(++self.ip)); 272 | if (self.carry) { 273 | jump(self.gpr[regTo]); 274 | } else { 275 | self.ip++; 276 | } 277 | break; 278 | case opcodes.JC_ADDRESS: 279 | number = memory.load(++self.ip); 280 | if (self.carry) { 281 | jump(number); 282 | } else { 283 | self.ip++; 284 | } 285 | break; 286 | case opcodes.JNC_REGADDRESS: 287 | regTo = checkGPR(memory.load(++self.ip)); 288 | if (!self.carry) { 289 | jump(self.gpr[regTo]); 290 | } else { 291 | self.ip++; 292 | } 293 | break; 294 | case opcodes.JNC_ADDRESS: 295 | number = memory.load(++self.ip); 296 | if (!self.carry) { 297 | jump(number); 298 | } else { 299 | self.ip++; 300 | } 301 | break; 302 | case opcodes.JZ_REGADDRESS: 303 | regTo = checkGPR(memory.load(++self.ip)); 304 | if (self.zero) { 305 | jump(self.gpr[regTo]); 306 | } else { 307 | self.ip++; 308 | } 309 | break; 310 | case opcodes.JZ_ADDRESS: 311 | number = memory.load(++self.ip); 312 | if (self.zero) { 313 | jump(number); 314 | } else { 315 | self.ip++; 316 | } 317 | break; 318 | case opcodes.JNZ_REGADDRESS: 319 | regTo = checkGPR(memory.load(++self.ip)); 320 | if (!self.zero) { 321 | jump(self.gpr[regTo]); 322 | } else { 323 | self.ip++; 324 | } 325 | break; 326 | case opcodes.JNZ_ADDRESS: 327 | number = memory.load(++self.ip); 328 | if (!self.zero) { 329 | jump(number); 330 | } else { 331 | self.ip++; 332 | } 333 | break; 334 | case opcodes.JA_REGADDRESS: 335 | regTo = checkGPR(memory.load(++self.ip)); 336 | if (!self.zero && !self.carry) { 337 | jump(self.gpr[regTo]); 338 | } else { 339 | self.ip++; 340 | } 341 | break; 342 | case opcodes.JA_ADDRESS: 343 | number = memory.load(++self.ip); 344 | if (!self.zero && !self.carry) { 345 | jump(number); 346 | } else { 347 | self.ip++; 348 | } 349 | break; 350 | case opcodes.JNA_REGADDRESS: // JNA REG 351 | regTo = checkGPR(memory.load(++self.ip)); 352 | if (self.zero || self.carry) { 353 | jump(self.gpr[regTo]); 354 | } else { 355 | self.ip++; 356 | } 357 | break; 358 | case opcodes.JNA_ADDRESS: 359 | number = memory.load(++self.ip); 360 | if (self.zero || self.carry) { 361 | jump(number); 362 | } else { 363 | self.ip++; 364 | } 365 | break; 366 | case opcodes.PUSH_REG: 367 | regFrom = checkGPR(memory.load(++self.ip)); 368 | push(self.gpr[regFrom]); 369 | self.ip++; 370 | break; 371 | case opcodes.PUSH_REGADDRESS: 372 | regFrom = memory.load(++self.ip); 373 | push(memory.load(indirectRegisterAddress(regFrom))); 374 | self.ip++; 375 | break; 376 | case opcodes.PUSH_ADDRESS: 377 | memFrom = memory.load(++self.ip); 378 | push(memory.load(memFrom)); 379 | self.ip++; 380 | break; 381 | case opcodes.PUSH_NUMBER: 382 | number = memory.load(++self.ip); 383 | push(number); 384 | self.ip++; 385 | break; 386 | case opcodes.POP_REG: 387 | regTo = checkGPR(memory.load(++self.ip)); 388 | self.gpr[regTo] = pop(); 389 | self.ip++; 390 | break; 391 | case opcodes.CALL_REGADDRESS: 392 | regTo = checkGPR(memory.load(++self.ip)); 393 | push(self.ip+1); 394 | jump(self.gpr[regTo]); 395 | break; 396 | case opcodes.CALL_ADDRESS: 397 | number = memory.load(++self.ip); 398 | push(self.ip+1); 399 | jump(number); 400 | break; 401 | case opcodes.RET: 402 | jump(pop()); 403 | break; 404 | case opcodes.MUL_REG: // A = A * REG 405 | regFrom = checkGPR(memory.load(++self.ip)); 406 | self.gpr[0] = checkOperation(self.gpr[0] * self.gpr[regFrom]); 407 | self.ip++; 408 | break; 409 | case opcodes.MUL_REGADDRESS: // A = A * [REG] 410 | regFrom = memory.load(++self.ip); 411 | self.gpr[0] = checkOperation(self.gpr[0] * memory.load(indirectRegisterAddress(regFrom))); 412 | self.ip++; 413 | break; 414 | case opcodes.MUL_ADDRESS: // A = A * [NUMBER] 415 | memFrom = memory.load(++self.ip); 416 | self.gpr[0] = checkOperation(self.gpr[0] * memory.load(memFrom)); 417 | self.ip++; 418 | break; 419 | case opcodes.MUL_NUMBER: // A = A * NUMBER 420 | number = memory.load(++self.ip); 421 | self.gpr[0] = checkOperation(self.gpr[0] * number); 422 | self.ip++; 423 | break; 424 | case opcodes.DIV_REG: // A = A / REG 425 | regFrom = checkGPR(memory.load(++self.ip)); 426 | self.gpr[0] = checkOperation(division(self.gpr[regFrom])); 427 | self.ip++; 428 | break; 429 | case opcodes.DIV_REGADDRESS: // A = A / [REG] 430 | regFrom = memory.load(++self.ip); 431 | self.gpr[0] = checkOperation(division(memory.load(indirectRegisterAddress(regFrom)))); 432 | self.ip++; 433 | break; 434 | case opcodes.DIV_ADDRESS: // A = A / [NUMBER] 435 | memFrom = memory.load(++self.ip); 436 | self.gpr[0] = checkOperation(division(memory.load(memFrom))); 437 | self.ip++; 438 | break; 439 | case opcodes.DIV_NUMBER: // A = A / NUMBER 440 | number = memory.load(++self.ip); 441 | self.gpr[0] = checkOperation(division(number)); 442 | self.ip++; 443 | break; 444 | case opcodes.AND_REG_WITH_REG: 445 | regTo = checkGPR(memory.load(++self.ip)); 446 | regFrom = checkGPR(memory.load(++self.ip)); 447 | self.gpr[regTo] = checkOperation(self.gpr[regTo] & self.gpr[regFrom]); 448 | self.ip++; 449 | break; 450 | case opcodes.AND_REGADDRESS_WITH_REG: 451 | regTo = checkGPR(memory.load(++self.ip)); 452 | regFrom = memory.load(++self.ip); 453 | self.gpr[regTo] = checkOperation(self.gpr[regTo] & memory.load(indirectRegisterAddress(regFrom))); 454 | self.ip++; 455 | break; 456 | case opcodes.AND_ADDRESS_WITH_REG: 457 | regTo = checkGPR(memory.load(++self.ip)); 458 | memFrom = memory.load(++self.ip); 459 | self.gpr[regTo] = checkOperation(self.gpr[regTo] & memory.load(memFrom)); 460 | self.ip++; 461 | break; 462 | case opcodes.AND_NUMBER_WITH_REG: 463 | regTo = checkGPR(memory.load(++self.ip)); 464 | number = memory.load(++self.ip); 465 | self.gpr[regTo] = checkOperation(self.gpr[regTo] & number); 466 | self.ip++; 467 | break; 468 | case opcodes.OR_REG_WITH_REG: 469 | regTo = checkGPR(memory.load(++self.ip)); 470 | regFrom = checkGPR(memory.load(++self.ip)); 471 | self.gpr[regTo] = checkOperation(self.gpr[regTo] | self.gpr[regFrom]); 472 | self.ip++; 473 | break; 474 | case opcodes.OR_REGADDRESS_WITH_REG: 475 | regTo = checkGPR(memory.load(++self.ip)); 476 | regFrom = memory.load(++self.ip); 477 | self.gpr[regTo] = checkOperation(self.gpr[regTo] | memory.load(indirectRegisterAddress(regFrom))); 478 | self.ip++; 479 | break; 480 | case opcodes.OR_ADDRESS_WITH_REG: 481 | regTo = checkGPR(memory.load(++self.ip)); 482 | memFrom = memory.load(++self.ip); 483 | self.gpr[regTo] = checkOperation(self.gpr[regTo] | memory.load(memFrom)); 484 | self.ip++; 485 | break; 486 | case opcodes.OR_NUMBER_WITH_REG: 487 | regTo = checkGPR(memory.load(++self.ip)); 488 | number = memory.load(++self.ip); 489 | self.gpr[regTo] = checkOperation(self.gpr[regTo] | number); 490 | self.ip++; 491 | break; 492 | case opcodes.XOR_REG_WITH_REG: 493 | regTo = checkGPR(memory.load(++self.ip)); 494 | regFrom = checkGPR(memory.load(++self.ip)); 495 | self.gpr[regTo] = checkOperation(self.gpr[regTo] ^ self.gpr[regFrom]); 496 | self.ip++; 497 | break; 498 | case opcodes.XOR_REGADDRESS_WITH_REG: 499 | regTo = checkGPR(memory.load(++self.ip)); 500 | regFrom = memory.load(++self.ip); 501 | self.gpr[regTo] = checkOperation(self.gpr[regTo] ^ memory.load(indirectRegisterAddress(regFrom))); 502 | self.ip++; 503 | break; 504 | case opcodes.XOR_ADDRESS_WITH_REG: 505 | regTo = checkGPR(memory.load(++self.ip)); 506 | memFrom = memory.load(++self.ip); 507 | self.gpr[regTo] = checkOperation(self.gpr[regTo] ^ memory.load(memFrom)); 508 | self.ip++; 509 | break; 510 | case opcodes.XOR_NUMBER_WITH_REG: 511 | regTo = checkGPR(memory.load(++self.ip)); 512 | number = memory.load(++self.ip); 513 | self.gpr[regTo] = checkOperation(self.gpr[regTo] ^ number); 514 | self.ip++; 515 | break; 516 | case opcodes.NOT_REG: 517 | regTo = checkGPR(memory.load(++self.ip)); 518 | self.gpr[regTo] = checkOperation(~self.gpr[regTo]); 519 | self.ip++; 520 | break; 521 | case opcodes.SHL_REG_WITH_REG: 522 | regTo = checkGPR(memory.load(++self.ip)); 523 | regFrom = checkGPR(memory.load(++self.ip)); 524 | self.gpr[regTo] = checkOperation(self.gpr[regTo] << self.gpr[regFrom]); 525 | self.ip++; 526 | break; 527 | case opcodes.SHL_REGADDRESS_WITH_REG: 528 | regTo = checkGPR(memory.load(++self.ip)); 529 | regFrom = memory.load(++self.ip); 530 | self.gpr[regTo] = checkOperation(self.gpr[regTo] << memory.load(indirectRegisterAddress(regFrom))); 531 | self.ip++; 532 | break; 533 | case opcodes.SHL_ADDRESS_WITH_REG: 534 | regTo = checkGPR(memory.load(++self.ip)); 535 | memFrom = memory.load(++self.ip); 536 | self.gpr[regTo] = checkOperation(self.gpr[regTo] << memory.load(memFrom)); 537 | self.ip++; 538 | break; 539 | case opcodes.SHL_NUMBER_WITH_REG: 540 | regTo = checkGPR(memory.load(++self.ip)); 541 | number = memory.load(++self.ip); 542 | self.gpr[regTo] = checkOperation(self.gpr[regTo] << number); 543 | self.ip++; 544 | break; 545 | case opcodes.SHR_REG_WITH_REG: 546 | regTo = checkGPR(memory.load(++self.ip)); 547 | regFrom = checkGPR(memory.load(++self.ip)); 548 | self.gpr[regTo] = checkOperation(self.gpr[regTo] >>> self.gpr[regFrom]); 549 | self.ip++; 550 | break; 551 | case opcodes.SHR_REGADDRESS_WITH_REG: 552 | regTo = checkGPR(memory.load(++self.ip)); 553 | regFrom = memory.load(++self.ip); 554 | self.gpr[regTo] = checkOperation(self.gpr[regTo] >>> memory.load(indirectRegisterAddress(regFrom))); 555 | self.ip++; 556 | break; 557 | case opcodes.SHR_ADDRESS_WITH_REG: 558 | regTo = checkGPR(memory.load(++self.ip)); 559 | memFrom = memory.load(++self.ip); 560 | self.gpr[regTo] = checkOperation(self.gpr[regTo] >>> memory.load(memFrom)); 561 | self.ip++; 562 | break; 563 | case opcodes.SHR_NUMBER_WITH_REG: 564 | regTo = checkGPR(memory.load(++self.ip)); 565 | number = memory.load(++self.ip); 566 | self.gpr[regTo] = checkOperation(self.gpr[regTo] >>> number); 567 | self.ip++; 568 | break; 569 | default: 570 | throw "Invalid op code: " + instr; 571 | } 572 | 573 | return true; 574 | } catch(e) { 575 | self.fault = true; 576 | throw e; 577 | } 578 | }, 579 | reset: function() { 580 | var self = this; 581 | self.maxSP = 231; 582 | self.minSP = 0; 583 | 584 | self.gpr = [0, 0, 0, 0]; 585 | self.sp = self.maxSP; 586 | self.ip = 0; 587 | self.zero = false; 588 | self.carry = false; 589 | self.fault = false; 590 | } 591 | }; 592 | 593 | cpu.reset(); 594 | return cpu; 595 | }]); 596 | -------------------------------------------------------------------------------- /src/assembler/asm.js: -------------------------------------------------------------------------------- 1 | app.service('assembler', ['opcodes', function (opcodes) { 2 | return { 3 | go: function (input) { 4 | // Use https://www.debuggex.com/ 5 | // Matches: "label: INSTRUCTION (["')OPERAND1(]"'), (["')OPERAND2(]"') 6 | // GROUPS: 1 2 3 7 7 | var regex = /^[\t ]*(?:([.A-Za-z]\w*)[:])?(?:[\t ]*([A-Za-z]{2,4})(?:[\t ]+(\[(\w+((\+|-)\d+)?)\]|\".+?\"|\'.+?\'|[.A-Za-z0-9]\w*)(?:[\t ]*[,][\t ]*(\[(\w+((\+|-)\d+)?)\]|\".+?\"|\'.+?\'|[.A-Za-z0-9]\w*))?)?)?/; 8 | 9 | // Regex group indexes for operands 10 | var op1_group = 3; 11 | var op2_group = 7; 12 | 13 | // MATCHES: "(+|-)INTEGER" 14 | var regexNum = /^[-+]?[0-9]+$/; 15 | // MATCHES: "(.L)abel" 16 | var regexLabel = /^[.A-Za-z]\w*$/; 17 | // Contains the program code & data generated by the assembler 18 | var code = []; 19 | // Contains the mapping from instructions to assembler line 20 | var mapping = {}; 21 | // Hash map of label used to replace the labels after the assembler generated the code 22 | var labels = {}; 23 | // Hash of uppercase labels used to detect duplicates 24 | var normalizedLabels = {}; 25 | 26 | // Split text into code lines 27 | var lines = input.split('\n'); 28 | 29 | // Allowed formats: 200, 200d, 0xA4, 0o48, 101b 30 | var parseNumber = function (input) { 31 | if (input.slice(0, 2) === "0x") { 32 | return parseInt(input.slice(2), 16); 33 | } else if (input.slice(0, 2) === "0o") { 34 | return parseInt(input.slice(2), 8); 35 | } else if (input.slice(input.length - 1) === "b") { 36 | return parseInt(input.slice(0, input.length - 1), 2); 37 | } else if (input.slice(input.length - 1) === "d") { 38 | return parseInt(input.slice(0, input.length - 1), 10); 39 | } else if (regexNum.exec(input)) { 40 | return parseInt(input, 10); 41 | } else { 42 | throw "Invalid number format"; 43 | } 44 | }; 45 | 46 | // Allowed registers: A, B, C, D, SP 47 | var parseRegister = function (input) { 48 | input = input.toUpperCase(); 49 | 50 | if (input === 'A') { 51 | return 0; 52 | } else if (input === 'B') { 53 | return 1; 54 | } else if (input === 'C') { 55 | return 2; 56 | } else if (input === 'D') { 57 | return 3; 58 | } else if (input === 'SP') { 59 | return 4; 60 | } else { 61 | return undefined; 62 | } 63 | }; 64 | 65 | var parseOffsetAddressing = function (input) { 66 | input = input.toUpperCase(); 67 | var m = 0; 68 | var base = 0; 69 | 70 | if (input[0] === 'A') { 71 | base = 0; 72 | } else if (input[0] === 'B') { 73 | base = 1; 74 | } else if (input[0] === 'C') { 75 | base = 2; 76 | } else if (input[0] === 'D') { 77 | base = 3; 78 | } else if (input.slice(0, 2) === "SP") { 79 | base = 4; 80 | } else { 81 | return undefined; 82 | } 83 | var offset_start = 1; 84 | if (base === 4) { 85 | offset_start = 2; 86 | } 87 | 88 | if (input[offset_start] === '-') { 89 | m = -1; 90 | } else if (input[offset_start] === '+') { 91 | m = 1; 92 | } else { 93 | return undefined; 94 | } 95 | 96 | var offset = m * parseInt(input.slice(offset_start + 1), 10); 97 | 98 | if (offset < -16 || offset > 15) 99 | throw "offset must be a value between -16...+15"; 100 | 101 | if (offset < 0) { 102 | offset = 32 + offset; // two's complement representation in 5-bit 103 | } 104 | 105 | return offset * 8 + base; // shift offset 3 bits right and add code for register 106 | }; 107 | 108 | // Allowed: Register, Label or Number; SP+/-Number is allowed for 'regaddress' type 109 | var parseRegOrNumber = function (input, typeReg, typeNumber) { 110 | var register = parseRegister(input); 111 | 112 | if (register !== undefined) { 113 | return {type: typeReg, value: register}; 114 | } else { 115 | var label = parseLabel(input); 116 | if (label !== undefined) { 117 | return {type: typeNumber, value: label}; 118 | } else { 119 | if (typeReg === "regaddress") { 120 | 121 | register = parseOffsetAddressing(input); 122 | 123 | if (register !== undefined) { 124 | return {type: typeReg, value: register}; 125 | } 126 | } 127 | 128 | var value = parseNumber(input); 129 | 130 | if (isNaN(value)) { 131 | throw "Not a " + typeNumber + ": " + value; 132 | } 133 | else if (value < 0 || value > 255) 134 | throw typeNumber + " must have a value between 0-255"; 135 | 136 | return {type: typeNumber, value: value}; 137 | } 138 | } 139 | }; 140 | 141 | var parseLabel = function (input) { 142 | return regexLabel.exec(input) ? input : undefined; 143 | }; 144 | 145 | var getValue = function (input) { 146 | switch (input.slice(0, 1)) { 147 | case '[': // [number] or [register] 148 | var address = input.slice(1, input.length - 1); 149 | return parseRegOrNumber(address, "regaddress", "address"); 150 | case '"': // "String" 151 | var text = input.slice(1, input.length - 1); 152 | var chars = []; 153 | 154 | for (var i = 0, l = text.length; i < l; i++) { 155 | chars.push(text.charCodeAt(i)); 156 | } 157 | 158 | return {type: "numbers", value: chars}; 159 | case "'": // 'C' 160 | var character = input.slice(1, input.length - 1); 161 | if (character.length > 1) 162 | throw "Only one character is allowed. Use String instead"; 163 | 164 | return {type: "number", value: character.charCodeAt(0)}; 165 | default: // REGISTER, NUMBER or LABEL 166 | return parseRegOrNumber(input, "register", "number"); 167 | } 168 | }; 169 | 170 | var addLabel = function (label) { 171 | var upperLabel = label.toUpperCase(); 172 | if (upperLabel in normalizedLabels) 173 | throw "Duplicate label: " + label; 174 | 175 | if (upperLabel === "A" || upperLabel === "B" || upperLabel === "C" || upperLabel === "D") 176 | throw "Label contains keyword: " + upperLabel; 177 | 178 | labels[label] = code.length; 179 | }; 180 | 181 | var checkNoExtraArg = function (instr, arg) { 182 | if (arg !== undefined) { 183 | throw instr + ": too many arguments"; 184 | } 185 | }; 186 | 187 | for (var i = 0, l = lines.length; i < l; i++) { 188 | try { 189 | var match = regex.exec(lines[i]); 190 | if (match[1] !== undefined || match[2] !== undefined) { 191 | if (match[1] !== undefined) { 192 | addLabel(match[1]); 193 | } 194 | 195 | if (match[2] !== undefined) { 196 | var instr = match[2].toUpperCase(); 197 | var p1, p2, opCode; 198 | 199 | // Add mapping instr pos to line number 200 | // Don't do it for DB as this is not a real instruction 201 | if (instr !== 'DB') { 202 | mapping[code.length] = i; 203 | } 204 | 205 | switch (instr) { 206 | case 'DB': 207 | p1 = getValue(match[op1_group]); 208 | 209 | if (p1.type === "number") 210 | code.push(p1.value); 211 | else if (p1.type === "numbers") 212 | for (var j = 0, k = p1.value.length; j < k; j++) { 213 | code.push(p1.value[j]); 214 | } 215 | else 216 | throw "DB does not support this operand"; 217 | 218 | break; 219 | case 'HLT': 220 | checkNoExtraArg('HLT', match[op1_group]); 221 | opCode = opcodes.NONE; 222 | code.push(opCode); 223 | break; 224 | 225 | case 'MOV': 226 | p1 = getValue(match[op1_group]); 227 | p2 = getValue(match[op2_group]); 228 | 229 | if (p1.type === "register" && p2.type === "register") 230 | opCode = opcodes.MOV_REG_TO_REG; 231 | else if (p1.type === "register" && p2.type === "address") 232 | opCode = opcodes.MOV_ADDRESS_TO_REG; 233 | else if (p1.type === "register" && p2.type === "regaddress") 234 | opCode = opcodes.MOV_REGADDRESS_TO_REG; 235 | else if (p1.type === "address" && p2.type === "register") 236 | opCode = opcodes.MOV_REG_TO_ADDRESS; 237 | else if (p1.type === "regaddress" && p2.type === "register") 238 | opCode = opcodes.MOV_REG_TO_REGADDRESS; 239 | else if (p1.type === "register" && p2.type === "number") 240 | opCode = opcodes.MOV_NUMBER_TO_REG; 241 | else if (p1.type === "address" && p2.type === "number") 242 | opCode = opcodes.MOV_NUMBER_TO_ADDRESS; 243 | else if (p1.type === "regaddress" && p2.type === "number") 244 | opCode = opcodes.MOV_NUMBER_TO_REGADDRESS; 245 | else 246 | throw "MOV does not support this operands"; 247 | 248 | code.push(opCode, p1.value, p2.value); 249 | break; 250 | case 'ADD': 251 | p1 = getValue(match[op1_group]); 252 | p2 = getValue(match[op2_group]); 253 | 254 | if (p1.type === "register" && p2.type === "register") 255 | opCode = opcodes.ADD_REG_TO_REG; 256 | else if (p1.type === "register" && p2.type === "regaddress") 257 | opCode = opcodes.ADD_REGADDRESS_TO_REG; 258 | else if (p1.type === "register" && p2.type === "address") 259 | opCode = opcodes.ADD_ADDRESS_TO_REG; 260 | else if (p1.type === "register" && p2.type === "number") 261 | opCode = opcodes.ADD_NUMBER_TO_REG; 262 | else 263 | throw "ADD does not support this operands"; 264 | 265 | code.push(opCode, p1.value, p2.value); 266 | break; 267 | case 'SUB': 268 | p1 = getValue(match[op1_group]); 269 | p2 = getValue(match[op2_group]); 270 | 271 | if (p1.type === "register" && p2.type === "register") 272 | opCode = opcodes.SUB_REG_FROM_REG; 273 | else if (p1.type === "register" && p2.type === "regaddress") 274 | opCode = opcodes.SUB_REGADDRESS_FROM_REG; 275 | else if (p1.type === "register" && p2.type === "address") 276 | opCode = opcodes.SUB_ADDRESS_FROM_REG; 277 | else if (p1.type === "register" && p2.type === "number") 278 | opCode = opcodes.SUB_NUMBER_FROM_REG; 279 | else 280 | throw "SUB does not support this operands"; 281 | 282 | code.push(opCode, p1.value, p2.value); 283 | break; 284 | case 'INC': 285 | p1 = getValue(match[op1_group]); 286 | checkNoExtraArg('INC', match[op2_group]); 287 | 288 | if (p1.type === "register") 289 | opCode = opcodes.INC_REG; 290 | else 291 | throw "INC does not support this operand"; 292 | 293 | code.push(opCode, p1.value); 294 | 295 | break; 296 | case 'DEC': 297 | p1 = getValue(match[op1_group]); 298 | checkNoExtraArg('DEC', match[op2_group]); 299 | 300 | if (p1.type === "register") 301 | opCode = opcodes.DEC_REG; 302 | else 303 | throw "DEC does not support this operand"; 304 | 305 | code.push(opCode, p1.value); 306 | 307 | break; 308 | case 'CMP': 309 | p1 = getValue(match[op1_group]); 310 | p2 = getValue(match[op2_group]); 311 | 312 | if (p1.type === "register" && p2.type === "register") 313 | opCode = opcodes.CMP_REG_WITH_REG; 314 | else if (p1.type === "register" && p2.type === "regaddress") 315 | opCode = opcodes.CMP_REGADDRESS_WITH_REG; 316 | else if (p1.type === "register" && p2.type === "address") 317 | opCode = opcodes.CMP_ADDRESS_WITH_REG; 318 | else if (p1.type === "register" && p2.type === "number") 319 | opCode = opcodes.CMP_NUMBER_WITH_REG; 320 | else 321 | throw "CMP does not support this operands"; 322 | 323 | code.push(opCode, p1.value, p2.value); 324 | break; 325 | case 'JMP': 326 | p1 = getValue(match[op1_group]); 327 | checkNoExtraArg('JMP', match[op2_group]); 328 | 329 | if (p1.type === "register") 330 | opCode = opcodes.JMP_REGADDRESS; 331 | else if (p1.type === "number") 332 | opCode = opcodes.JMP_ADDRESS; 333 | else 334 | throw "JMP does not support this operands"; 335 | 336 | code.push(opCode, p1.value); 337 | break; 338 | case 'JC': 339 | case 'JB': 340 | case 'JNAE': 341 | p1 = getValue(match[op1_group]); 342 | checkNoExtraArg(instr, match[op2_group]); 343 | 344 | if (p1.type === "register") 345 | opCode = opcodes.JC_REGADDRESS; 346 | else if (p1.type === "number") 347 | opCode = opcodes.JC_ADDRESS; 348 | else 349 | throw instr + " does not support this operand"; 350 | 351 | code.push(opCode, p1.value); 352 | break; 353 | case 'JNC': 354 | case 'JNB': 355 | case 'JAE': 356 | p1 = getValue(match[op1_group]); 357 | checkNoExtraArg(instr, match[op2_group]); 358 | 359 | if (p1.type === "register") 360 | opCode = opcodes.JNC_REGADDRESS; 361 | else if (p1.type === "number") 362 | opCode = opcodes.JNC_ADDRESS; 363 | else 364 | throw instr + "does not support this operand"; 365 | 366 | code.push(opCode, p1.value); 367 | break; 368 | case 'JZ': 369 | case 'JE': 370 | p1 = getValue(match[op1_group]); 371 | checkNoExtraArg(instr, match[op2_group]); 372 | 373 | if (p1.type === "register") 374 | opCode = opcodes.JZ_REGADDRESS; 375 | else if (p1.type === "number") 376 | opCode = opcodes.JZ_ADDRESS; 377 | else 378 | throw instr + " does not support this operand"; 379 | 380 | code.push(opCode, p1.value); 381 | break; 382 | case 'JNZ': 383 | case 'JNE': 384 | p1 = getValue(match[op1_group]); 385 | checkNoExtraArg(instr, match[op2_group]); 386 | 387 | if (p1.type === "register") 388 | opCode = opcodes.JNZ_REGADDRESS; 389 | else if (p1.type === "number") 390 | opCode = opcodes.JNZ_ADDRESS; 391 | else 392 | throw instr + " does not support this operand"; 393 | 394 | code.push(opCode, p1.value); 395 | break; 396 | case 'JA': 397 | case 'JNBE': 398 | p1 = getValue(match[op1_group]); 399 | checkNoExtraArg(instr, match[op2_group]); 400 | 401 | if (p1.type === "register") 402 | opCode = opcodes.JA_REGADDRESS; 403 | else if (p1.type === "number") 404 | opCode = opcodes.JA_ADDRESS; 405 | else 406 | throw instr + " does not support this operand"; 407 | 408 | code.push(opCode, p1.value); 409 | break; 410 | case 'JNA': 411 | case 'JBE': 412 | p1 = getValue(match[op1_group]); 413 | checkNoExtraArg(instr, match[op2_group]); 414 | 415 | if (p1.type === "register") 416 | opCode = opcodes.JNA_REGADDRESS; 417 | else if (p1.type === "number") 418 | opCode = opcodes.JNA_ADDRESS; 419 | else 420 | throw instr + " does not support this operand"; 421 | 422 | code.push(opCode, p1.value); 423 | break; 424 | case 'PUSH': 425 | p1 = getValue(match[op1_group]); 426 | checkNoExtraArg(instr, match[op2_group]); 427 | 428 | if (p1.type === "register") 429 | opCode = opcodes.PUSH_REG; 430 | else if (p1.type === "regaddress") 431 | opCode = opcodes.PUSH_REGADDRESS; 432 | else if (p1.type === "address") 433 | opCode = opcodes.PUSH_ADDRESS; 434 | else if (p1.type === "number") 435 | opCode = opcodes.PUSH_NUMBER; 436 | else 437 | throw "PUSH does not support this operand"; 438 | 439 | code.push(opCode, p1.value); 440 | break; 441 | case 'POP': 442 | p1 = getValue(match[op1_group]); 443 | checkNoExtraArg(instr, match[op2_group]); 444 | 445 | if (p1.type === "register") 446 | opCode = opcodes.POP_REG; 447 | else 448 | throw "PUSH does not support this operand"; 449 | 450 | code.push(opCode, p1.value); 451 | break; 452 | case 'CALL': 453 | p1 = getValue(match[op1_group]); 454 | checkNoExtraArg(instr, match[op2_group]); 455 | 456 | if (p1.type === "register") 457 | opCode = opcodes.CALL_REGADDRESS; 458 | else if (p1.type === "number") 459 | opCode = opcodes.CALL_ADDRESS; 460 | else 461 | throw "CALL does not support this operand"; 462 | 463 | code.push(opCode, p1.value); 464 | break; 465 | case 'RET': 466 | checkNoExtraArg(instr, match[op1_group]); 467 | 468 | opCode = opcodes.RET; 469 | 470 | code.push(opCode); 471 | break; 472 | 473 | case 'MUL': 474 | p1 = getValue(match[op1_group]); 475 | checkNoExtraArg(instr, match[op2_group]); 476 | 477 | if (p1.type === "register") 478 | opCode = opcodes.MUL_REG; 479 | else if (p1.type === "regaddress") 480 | opCode = opcodes.MUL_REGADDRESS; 481 | else if (p1.type === "address") 482 | opCode = opcodes.MUL_ADDRESS; 483 | else if (p1.type === "number") 484 | opCode = opcodes.MUL_NUMBER; 485 | else 486 | throw "MULL does not support this operand"; 487 | 488 | code.push(opCode, p1.value); 489 | break; 490 | case 'DIV': 491 | p1 = getValue(match[op1_group]); 492 | checkNoExtraArg(instr, match[op2_group]); 493 | 494 | if (p1.type === "register") 495 | opCode = opcodes.DIV_REG; 496 | else if (p1.type === "regaddress") 497 | opCode = opcodes.DIV_REGADDRESS; 498 | else if (p1.type === "address") 499 | opCode = opcodes.DIV_ADDRESS; 500 | else if (p1.type === "number") 501 | opCode = opcodes.DIV_NUMBER; 502 | else 503 | throw "DIV does not support this operand"; 504 | 505 | code.push(opCode, p1.value); 506 | break; 507 | case 'AND': 508 | p1 = getValue(match[op1_group]); 509 | p2 = getValue(match[op2_group]); 510 | 511 | if (p1.type === "register" && p2.type === "register") 512 | opCode = opcodes.AND_REG_WITH_REG; 513 | else if (p1.type === "register" && p2.type === "regaddress") 514 | opCode = opcodes.AND_REGADDRESS_WITH_REG; 515 | else if (p1.type === "register" && p2.type === "address") 516 | opCode = opcodes.AND_ADDRESS_WITH_REG; 517 | else if (p1.type === "register" && p2.type === "number") 518 | opCode = opcodes.AND_NUMBER_WITH_REG; 519 | else 520 | throw "AND does not support this operands"; 521 | 522 | code.push(opCode, p1.value, p2.value); 523 | break; 524 | case 'OR': 525 | p1 = getValue(match[op1_group]); 526 | p2 = getValue(match[op2_group]); 527 | 528 | if (p1.type === "register" && p2.type === "register") 529 | opCode = opcodes.OR_REG_WITH_REG; 530 | else if (p1.type === "register" && p2.type === "regaddress") 531 | opCode = opcodes.OR_REGADDRESS_WITH_REG; 532 | else if (p1.type === "register" && p2.type === "address") 533 | opCode = opcodes.OR_ADDRESS_WITH_REG; 534 | else if (p1.type === "register" && p2.type === "number") 535 | opCode = opcodes.OR_NUMBER_WITH_REG; 536 | else 537 | throw "OR does not support this operands"; 538 | 539 | code.push(opCode, p1.value, p2.value); 540 | break; 541 | case 'XOR': 542 | p1 = getValue(match[op1_group]); 543 | p2 = getValue(match[op2_group]); 544 | 545 | if (p1.type === "register" && p2.type === "register") 546 | opCode = opcodes.XOR_REG_WITH_REG; 547 | else if (p1.type === "register" && p2.type === "regaddress") 548 | opCode = opcodes.XOR_REGADDRESS_WITH_REG; 549 | else if (p1.type === "register" && p2.type === "address") 550 | opCode = opcodes.XOR_ADDRESS_WITH_REG; 551 | else if (p1.type === "register" && p2.type === "number") 552 | opCode = opcodes.XOR_NUMBER_WITH_REG; 553 | else 554 | throw "XOR does not support this operands"; 555 | 556 | code.push(opCode, p1.value, p2.value); 557 | break; 558 | case 'NOT': 559 | p1 = getValue(match[op1_group]); 560 | checkNoExtraArg(instr, match[op2_group]); 561 | 562 | if (p1.type === "register") 563 | opCode = opcodes.NOT_REG; 564 | else 565 | throw "NOT does not support this operand"; 566 | 567 | code.push(opCode, p1.value); 568 | break; 569 | case 'SHL': 570 | case 'SAL': 571 | p1 = getValue(match[op1_group]); 572 | p2 = getValue(match[op2_group]); 573 | 574 | if (p1.type === "register" && p2.type === "register") 575 | opCode = opcodes.SHL_REG_WITH_REG; 576 | else if (p1.type === "register" && p2.type === "regaddress") 577 | opCode = opcodes.SHL_REGADDRESS_WITH_REG; 578 | else if (p1.type === "register" && p2.type === "address") 579 | opCode = opcodes.SHL_ADDRESS_WITH_REG; 580 | else if (p1.type === "register" && p2.type === "number") 581 | opCode = opcodes.SHL_NUMBER_WITH_REG; 582 | else 583 | throw instr + " does not support this operands"; 584 | 585 | code.push(opCode, p1.value, p2.value); 586 | break; 587 | case 'SHR': 588 | case 'SAR': 589 | p1 = getValue(match[op1_group]); 590 | p2 = getValue(match[op2_group]); 591 | 592 | if (p1.type === "register" && p2.type === "register") 593 | opCode = opcodes.SHR_REG_WITH_REG; 594 | else if (p1.type === "register" && p2.type === "regaddress") 595 | opCode = opcodes.SHR_REGADDRESS_WITH_REG; 596 | else if (p1.type === "register" && p2.type === "address") 597 | opCode = opcodes.SHR_ADDRESS_WITH_REG; 598 | else if (p1.type === "register" && p2.type === "number") 599 | opCode = opcodes.SHR_NUMBER_WITH_REG; 600 | else 601 | throw instr + " does not support this operands"; 602 | 603 | code.push(opCode, p1.value, p2.value); 604 | break; 605 | default: 606 | throw "Invalid instruction: " + match[2]; 607 | } 608 | } 609 | } else { 610 | // Check if line starts with a comment otherwise the line contains an error and can not be parsed 611 | var line = lines[i].trim(); 612 | if (line !== "" && line.slice(0, 1) !== ";") { 613 | throw "Syntax error"; 614 | } 615 | } 616 | } catch (e) { 617 | throw {error: e, line: i}; 618 | } 619 | } 620 | 621 | // Replace label 622 | for (i = 0, l = code.length; i < l; i++) { 623 | if (!angular.isNumber(code[i])) { 624 | if (code[i] in labels) { 625 | code[i] = labels[code[i]]; 626 | } else { 627 | 628 | throw {error: "Undefined label: " + code[i]}; 629 | } 630 | } 631 | } 632 | 633 | return {code: code, mapping: mapping, labels: labels}; 634 | } 635 | }; 636 | }]); 637 | -------------------------------------------------------------------------------- /assets/asmsimulator.js: -------------------------------------------------------------------------------- 1 | var app = angular.module('ASMSimulator', []); 2 | ;app.service('assembler', ['opcodes', function (opcodes) { 3 | return { 4 | go: function (input) { 5 | // Use https://www.debuggex.com/ 6 | // Matches: "label: INSTRUCTION (["')OPERAND1(]"'), (["')OPERAND2(]"') 7 | // GROUPS: 1 2 3 7 8 | var regex = /^[\t ]*(?:([.A-Za-z]\w*)[:])?(?:[\t ]*([A-Za-z]{2,4})(?:[\t ]+(\[(\w+((\+|-)\d+)?)\]|\".+?\"|\'.+?\'|[.A-Za-z0-9]\w*)(?:[\t ]*[,][\t ]*(\[(\w+((\+|-)\d+)?)\]|\".+?\"|\'.+?\'|[.A-Za-z0-9]\w*))?)?)?/; 9 | 10 | // Regex group indexes for operands 11 | var op1_group = 3; 12 | var op2_group = 7; 13 | 14 | // MATCHES: "(+|-)INTEGER" 15 | var regexNum = /^[-+]?[0-9]+$/; 16 | // MATCHES: "(.L)abel" 17 | var regexLabel = /^[.A-Za-z]\w*$/; 18 | // Contains the program code & data generated by the assembler 19 | var code = []; 20 | // Contains the mapping from instructions to assembler line 21 | var mapping = {}; 22 | // Hash map of label used to replace the labels after the assembler generated the code 23 | var labels = {}; 24 | // Hash of uppercase labels used to detect duplicates 25 | var normalizedLabels = {}; 26 | 27 | // Split text into code lines 28 | var lines = input.split('\n'); 29 | 30 | // Allowed formats: 200, 200d, 0xA4, 0o48, 101b 31 | var parseNumber = function (input) { 32 | if (input.slice(0, 2) === "0x") { 33 | return parseInt(input.slice(2), 16); 34 | } else if (input.slice(0, 2) === "0o") { 35 | return parseInt(input.slice(2), 8); 36 | } else if (input.slice(input.length - 1) === "b") { 37 | return parseInt(input.slice(0, input.length - 1), 2); 38 | } else if (input.slice(input.length - 1) === "d") { 39 | return parseInt(input.slice(0, input.length - 1), 10); 40 | } else if (regexNum.exec(input)) { 41 | return parseInt(input, 10); 42 | } else { 43 | throw "Invalid number format"; 44 | } 45 | }; 46 | 47 | // Allowed registers: A, B, C, D, SP 48 | var parseRegister = function (input) { 49 | input = input.toUpperCase(); 50 | 51 | if (input === 'A') { 52 | return 0; 53 | } else if (input === 'B') { 54 | return 1; 55 | } else if (input === 'C') { 56 | return 2; 57 | } else if (input === 'D') { 58 | return 3; 59 | } else if (input === 'SP') { 60 | return 4; 61 | } else { 62 | return undefined; 63 | } 64 | }; 65 | 66 | var parseOffsetAddressing = function (input) { 67 | input = input.toUpperCase(); 68 | var m = 0; 69 | var base = 0; 70 | 71 | if (input[0] === 'A') { 72 | base = 0; 73 | } else if (input[0] === 'B') { 74 | base = 1; 75 | } else if (input[0] === 'C') { 76 | base = 2; 77 | } else if (input[0] === 'D') { 78 | base = 3; 79 | } else if (input.slice(0, 2) === "SP") { 80 | base = 4; 81 | } else { 82 | return undefined; 83 | } 84 | var offset_start = 1; 85 | if (base === 4) { 86 | offset_start = 2; 87 | } 88 | 89 | if (input[offset_start] === '-') { 90 | m = -1; 91 | } else if (input[offset_start] === '+') { 92 | m = 1; 93 | } else { 94 | return undefined; 95 | } 96 | 97 | var offset = m * parseInt(input.slice(offset_start + 1), 10); 98 | 99 | if (offset < -16 || offset > 15) 100 | throw "offset must be a value between -16...+15"; 101 | 102 | if (offset < 0) { 103 | offset = 32 + offset; // two's complement representation in 5-bit 104 | } 105 | 106 | return offset * 8 + base; // shift offset 3 bits right and add code for register 107 | }; 108 | 109 | // Allowed: Register, Label or Number; SP+/-Number is allowed for 'regaddress' type 110 | var parseRegOrNumber = function (input, typeReg, typeNumber) { 111 | var register = parseRegister(input); 112 | 113 | if (register !== undefined) { 114 | return {type: typeReg, value: register}; 115 | } else { 116 | var label = parseLabel(input); 117 | if (label !== undefined) { 118 | return {type: typeNumber, value: label}; 119 | } else { 120 | if (typeReg === "regaddress") { 121 | 122 | register = parseOffsetAddressing(input); 123 | 124 | if (register !== undefined) { 125 | return {type: typeReg, value: register}; 126 | } 127 | } 128 | 129 | var value = parseNumber(input); 130 | 131 | if (isNaN(value)) { 132 | throw "Not a " + typeNumber + ": " + value; 133 | } 134 | else if (value < 0 || value > 255) 135 | throw typeNumber + " must have a value between 0-255"; 136 | 137 | return {type: typeNumber, value: value}; 138 | } 139 | } 140 | }; 141 | 142 | var parseLabel = function (input) { 143 | return regexLabel.exec(input) ? input : undefined; 144 | }; 145 | 146 | var getValue = function (input) { 147 | switch (input.slice(0, 1)) { 148 | case '[': // [number] or [register] 149 | var address = input.slice(1, input.length - 1); 150 | return parseRegOrNumber(address, "regaddress", "address"); 151 | case '"': // "String" 152 | var text = input.slice(1, input.length - 1); 153 | var chars = []; 154 | 155 | for (var i = 0, l = text.length; i < l; i++) { 156 | chars.push(text.charCodeAt(i)); 157 | } 158 | 159 | return {type: "numbers", value: chars}; 160 | case "'": // 'C' 161 | var character = input.slice(1, input.length - 1); 162 | if (character.length > 1) 163 | throw "Only one character is allowed. Use String instead"; 164 | 165 | return {type: "number", value: character.charCodeAt(0)}; 166 | default: // REGISTER, NUMBER or LABEL 167 | return parseRegOrNumber(input, "register", "number"); 168 | } 169 | }; 170 | 171 | var addLabel = function (label) { 172 | var upperLabel = label.toUpperCase(); 173 | if (upperLabel in normalizedLabels) 174 | throw "Duplicate label: " + label; 175 | 176 | if (upperLabel === "A" || upperLabel === "B" || upperLabel === "C" || upperLabel === "D") 177 | throw "Label contains keyword: " + upperLabel; 178 | 179 | labels[label] = code.length; 180 | }; 181 | 182 | var checkNoExtraArg = function (instr, arg) { 183 | if (arg !== undefined) { 184 | throw instr + ": too many arguments"; 185 | } 186 | }; 187 | 188 | for (var i = 0, l = lines.length; i < l; i++) { 189 | try { 190 | var match = regex.exec(lines[i]); 191 | if (match[1] !== undefined || match[2] !== undefined) { 192 | if (match[1] !== undefined) { 193 | addLabel(match[1]); 194 | } 195 | 196 | if (match[2] !== undefined) { 197 | var instr = match[2].toUpperCase(); 198 | var p1, p2, opCode; 199 | 200 | // Add mapping instr pos to line number 201 | // Don't do it for DB as this is not a real instruction 202 | if (instr !== 'DB') { 203 | mapping[code.length] = i; 204 | } 205 | 206 | switch (instr) { 207 | case 'DB': 208 | p1 = getValue(match[op1_group]); 209 | 210 | if (p1.type === "number") 211 | code.push(p1.value); 212 | else if (p1.type === "numbers") 213 | for (var j = 0, k = p1.value.length; j < k; j++) { 214 | code.push(p1.value[j]); 215 | } 216 | else 217 | throw "DB does not support this operand"; 218 | 219 | break; 220 | case 'HLT': 221 | checkNoExtraArg('HLT', match[op1_group]); 222 | opCode = opcodes.NONE; 223 | code.push(opCode); 224 | break; 225 | 226 | case 'MOV': 227 | p1 = getValue(match[op1_group]); 228 | p2 = getValue(match[op2_group]); 229 | 230 | if (p1.type === "register" && p2.type === "register") 231 | opCode = opcodes.MOV_REG_TO_REG; 232 | else if (p1.type === "register" && p2.type === "address") 233 | opCode = opcodes.MOV_ADDRESS_TO_REG; 234 | else if (p1.type === "register" && p2.type === "regaddress") 235 | opCode = opcodes.MOV_REGADDRESS_TO_REG; 236 | else if (p1.type === "address" && p2.type === "register") 237 | opCode = opcodes.MOV_REG_TO_ADDRESS; 238 | else if (p1.type === "regaddress" && p2.type === "register") 239 | opCode = opcodes.MOV_REG_TO_REGADDRESS; 240 | else if (p1.type === "register" && p2.type === "number") 241 | opCode = opcodes.MOV_NUMBER_TO_REG; 242 | else if (p1.type === "address" && p2.type === "number") 243 | opCode = opcodes.MOV_NUMBER_TO_ADDRESS; 244 | else if (p1.type === "regaddress" && p2.type === "number") 245 | opCode = opcodes.MOV_NUMBER_TO_REGADDRESS; 246 | else 247 | throw "MOV does not support this operands"; 248 | 249 | code.push(opCode, p1.value, p2.value); 250 | break; 251 | case 'ADD': 252 | p1 = getValue(match[op1_group]); 253 | p2 = getValue(match[op2_group]); 254 | 255 | if (p1.type === "register" && p2.type === "register") 256 | opCode = opcodes.ADD_REG_TO_REG; 257 | else if (p1.type === "register" && p2.type === "regaddress") 258 | opCode = opcodes.ADD_REGADDRESS_TO_REG; 259 | else if (p1.type === "register" && p2.type === "address") 260 | opCode = opcodes.ADD_ADDRESS_TO_REG; 261 | else if (p1.type === "register" && p2.type === "number") 262 | opCode = opcodes.ADD_NUMBER_TO_REG; 263 | else 264 | throw "ADD does not support this operands"; 265 | 266 | code.push(opCode, p1.value, p2.value); 267 | break; 268 | case 'SUB': 269 | p1 = getValue(match[op1_group]); 270 | p2 = getValue(match[op2_group]); 271 | 272 | if (p1.type === "register" && p2.type === "register") 273 | opCode = opcodes.SUB_REG_FROM_REG; 274 | else if (p1.type === "register" && p2.type === "regaddress") 275 | opCode = opcodes.SUB_REGADDRESS_FROM_REG; 276 | else if (p1.type === "register" && p2.type === "address") 277 | opCode = opcodes.SUB_ADDRESS_FROM_REG; 278 | else if (p1.type === "register" && p2.type === "number") 279 | opCode = opcodes.SUB_NUMBER_FROM_REG; 280 | else 281 | throw "SUB does not support this operands"; 282 | 283 | code.push(opCode, p1.value, p2.value); 284 | break; 285 | case 'INC': 286 | p1 = getValue(match[op1_group]); 287 | checkNoExtraArg('INC', match[op2_group]); 288 | 289 | if (p1.type === "register") 290 | opCode = opcodes.INC_REG; 291 | else 292 | throw "INC does not support this operand"; 293 | 294 | code.push(opCode, p1.value); 295 | 296 | break; 297 | case 'DEC': 298 | p1 = getValue(match[op1_group]); 299 | checkNoExtraArg('DEC', match[op2_group]); 300 | 301 | if (p1.type === "register") 302 | opCode = opcodes.DEC_REG; 303 | else 304 | throw "DEC does not support this operand"; 305 | 306 | code.push(opCode, p1.value); 307 | 308 | break; 309 | case 'CMP': 310 | p1 = getValue(match[op1_group]); 311 | p2 = getValue(match[op2_group]); 312 | 313 | if (p1.type === "register" && p2.type === "register") 314 | opCode = opcodes.CMP_REG_WITH_REG; 315 | else if (p1.type === "register" && p2.type === "regaddress") 316 | opCode = opcodes.CMP_REGADDRESS_WITH_REG; 317 | else if (p1.type === "register" && p2.type === "address") 318 | opCode = opcodes.CMP_ADDRESS_WITH_REG; 319 | else if (p1.type === "register" && p2.type === "number") 320 | opCode = opcodes.CMP_NUMBER_WITH_REG; 321 | else 322 | throw "CMP does not support this operands"; 323 | 324 | code.push(opCode, p1.value, p2.value); 325 | break; 326 | case 'JMP': 327 | p1 = getValue(match[op1_group]); 328 | checkNoExtraArg('JMP', match[op2_group]); 329 | 330 | if (p1.type === "register") 331 | opCode = opcodes.JMP_REGADDRESS; 332 | else if (p1.type === "number") 333 | opCode = opcodes.JMP_ADDRESS; 334 | else 335 | throw "JMP does not support this operands"; 336 | 337 | code.push(opCode, p1.value); 338 | break; 339 | case 'JC': 340 | case 'JB': 341 | case 'JNAE': 342 | p1 = getValue(match[op1_group]); 343 | checkNoExtraArg(instr, match[op2_group]); 344 | 345 | if (p1.type === "register") 346 | opCode = opcodes.JC_REGADDRESS; 347 | else if (p1.type === "number") 348 | opCode = opcodes.JC_ADDRESS; 349 | else 350 | throw instr + " does not support this operand"; 351 | 352 | code.push(opCode, p1.value); 353 | break; 354 | case 'JNC': 355 | case 'JNB': 356 | case 'JAE': 357 | p1 = getValue(match[op1_group]); 358 | checkNoExtraArg(instr, match[op2_group]); 359 | 360 | if (p1.type === "register") 361 | opCode = opcodes.JNC_REGADDRESS; 362 | else if (p1.type === "number") 363 | opCode = opcodes.JNC_ADDRESS; 364 | else 365 | throw instr + "does not support this operand"; 366 | 367 | code.push(opCode, p1.value); 368 | break; 369 | case 'JZ': 370 | case 'JE': 371 | p1 = getValue(match[op1_group]); 372 | checkNoExtraArg(instr, match[op2_group]); 373 | 374 | if (p1.type === "register") 375 | opCode = opcodes.JZ_REGADDRESS; 376 | else if (p1.type === "number") 377 | opCode = opcodes.JZ_ADDRESS; 378 | else 379 | throw instr + " does not support this operand"; 380 | 381 | code.push(opCode, p1.value); 382 | break; 383 | case 'JNZ': 384 | case 'JNE': 385 | p1 = getValue(match[op1_group]); 386 | checkNoExtraArg(instr, match[op2_group]); 387 | 388 | if (p1.type === "register") 389 | opCode = opcodes.JNZ_REGADDRESS; 390 | else if (p1.type === "number") 391 | opCode = opcodes.JNZ_ADDRESS; 392 | else 393 | throw instr + " does not support this operand"; 394 | 395 | code.push(opCode, p1.value); 396 | break; 397 | case 'JA': 398 | case 'JNBE': 399 | p1 = getValue(match[op1_group]); 400 | checkNoExtraArg(instr, match[op2_group]); 401 | 402 | if (p1.type === "register") 403 | opCode = opcodes.JA_REGADDRESS; 404 | else if (p1.type === "number") 405 | opCode = opcodes.JA_ADDRESS; 406 | else 407 | throw instr + " does not support this operand"; 408 | 409 | code.push(opCode, p1.value); 410 | break; 411 | case 'JNA': 412 | case 'JBE': 413 | p1 = getValue(match[op1_group]); 414 | checkNoExtraArg(instr, match[op2_group]); 415 | 416 | if (p1.type === "register") 417 | opCode = opcodes.JNA_REGADDRESS; 418 | else if (p1.type === "number") 419 | opCode = opcodes.JNA_ADDRESS; 420 | else 421 | throw instr + " does not support this operand"; 422 | 423 | code.push(opCode, p1.value); 424 | break; 425 | case 'PUSH': 426 | p1 = getValue(match[op1_group]); 427 | checkNoExtraArg(instr, match[op2_group]); 428 | 429 | if (p1.type === "register") 430 | opCode = opcodes.PUSH_REG; 431 | else if (p1.type === "regaddress") 432 | opCode = opcodes.PUSH_REGADDRESS; 433 | else if (p1.type === "address") 434 | opCode = opcodes.PUSH_ADDRESS; 435 | else if (p1.type === "number") 436 | opCode = opcodes.PUSH_NUMBER; 437 | else 438 | throw "PUSH does not support this operand"; 439 | 440 | code.push(opCode, p1.value); 441 | break; 442 | case 'POP': 443 | p1 = getValue(match[op1_group]); 444 | checkNoExtraArg(instr, match[op2_group]); 445 | 446 | if (p1.type === "register") 447 | opCode = opcodes.POP_REG; 448 | else 449 | throw "PUSH does not support this operand"; 450 | 451 | code.push(opCode, p1.value); 452 | break; 453 | case 'CALL': 454 | p1 = getValue(match[op1_group]); 455 | checkNoExtraArg(instr, match[op2_group]); 456 | 457 | if (p1.type === "register") 458 | opCode = opcodes.CALL_REGADDRESS; 459 | else if (p1.type === "number") 460 | opCode = opcodes.CALL_ADDRESS; 461 | else 462 | throw "CALL does not support this operand"; 463 | 464 | code.push(opCode, p1.value); 465 | break; 466 | case 'RET': 467 | checkNoExtraArg(instr, match[op1_group]); 468 | 469 | opCode = opcodes.RET; 470 | 471 | code.push(opCode); 472 | break; 473 | 474 | case 'MUL': 475 | p1 = getValue(match[op1_group]); 476 | checkNoExtraArg(instr, match[op2_group]); 477 | 478 | if (p1.type === "register") 479 | opCode = opcodes.MUL_REG; 480 | else if (p1.type === "regaddress") 481 | opCode = opcodes.MUL_REGADDRESS; 482 | else if (p1.type === "address") 483 | opCode = opcodes.MUL_ADDRESS; 484 | else if (p1.type === "number") 485 | opCode = opcodes.MUL_NUMBER; 486 | else 487 | throw "MULL does not support this operand"; 488 | 489 | code.push(opCode, p1.value); 490 | break; 491 | case 'DIV': 492 | p1 = getValue(match[op1_group]); 493 | checkNoExtraArg(instr, match[op2_group]); 494 | 495 | if (p1.type === "register") 496 | opCode = opcodes.DIV_REG; 497 | else if (p1.type === "regaddress") 498 | opCode = opcodes.DIV_REGADDRESS; 499 | else if (p1.type === "address") 500 | opCode = opcodes.DIV_ADDRESS; 501 | else if (p1.type === "number") 502 | opCode = opcodes.DIV_NUMBER; 503 | else 504 | throw "DIV does not support this operand"; 505 | 506 | code.push(opCode, p1.value); 507 | break; 508 | case 'AND': 509 | p1 = getValue(match[op1_group]); 510 | p2 = getValue(match[op2_group]); 511 | 512 | if (p1.type === "register" && p2.type === "register") 513 | opCode = opcodes.AND_REG_WITH_REG; 514 | else if (p1.type === "register" && p2.type === "regaddress") 515 | opCode = opcodes.AND_REGADDRESS_WITH_REG; 516 | else if (p1.type === "register" && p2.type === "address") 517 | opCode = opcodes.AND_ADDRESS_WITH_REG; 518 | else if (p1.type === "register" && p2.type === "number") 519 | opCode = opcodes.AND_NUMBER_WITH_REG; 520 | else 521 | throw "AND does not support this operands"; 522 | 523 | code.push(opCode, p1.value, p2.value); 524 | break; 525 | case 'OR': 526 | p1 = getValue(match[op1_group]); 527 | p2 = getValue(match[op2_group]); 528 | 529 | if (p1.type === "register" && p2.type === "register") 530 | opCode = opcodes.OR_REG_WITH_REG; 531 | else if (p1.type === "register" && p2.type === "regaddress") 532 | opCode = opcodes.OR_REGADDRESS_WITH_REG; 533 | else if (p1.type === "register" && p2.type === "address") 534 | opCode = opcodes.OR_ADDRESS_WITH_REG; 535 | else if (p1.type === "register" && p2.type === "number") 536 | opCode = opcodes.OR_NUMBER_WITH_REG; 537 | else 538 | throw "OR does not support this operands"; 539 | 540 | code.push(opCode, p1.value, p2.value); 541 | break; 542 | case 'XOR': 543 | p1 = getValue(match[op1_group]); 544 | p2 = getValue(match[op2_group]); 545 | 546 | if (p1.type === "register" && p2.type === "register") 547 | opCode = opcodes.XOR_REG_WITH_REG; 548 | else if (p1.type === "register" && p2.type === "regaddress") 549 | opCode = opcodes.XOR_REGADDRESS_WITH_REG; 550 | else if (p1.type === "register" && p2.type === "address") 551 | opCode = opcodes.XOR_ADDRESS_WITH_REG; 552 | else if (p1.type === "register" && p2.type === "number") 553 | opCode = opcodes.XOR_NUMBER_WITH_REG; 554 | else 555 | throw "XOR does not support this operands"; 556 | 557 | code.push(opCode, p1.value, p2.value); 558 | break; 559 | case 'NOT': 560 | p1 = getValue(match[op1_group]); 561 | checkNoExtraArg(instr, match[op2_group]); 562 | 563 | if (p1.type === "register") 564 | opCode = opcodes.NOT_REG; 565 | else 566 | throw "NOT does not support this operand"; 567 | 568 | code.push(opCode, p1.value); 569 | break; 570 | case 'SHL': 571 | case 'SAL': 572 | p1 = getValue(match[op1_group]); 573 | p2 = getValue(match[op2_group]); 574 | 575 | if (p1.type === "register" && p2.type === "register") 576 | opCode = opcodes.SHL_REG_WITH_REG; 577 | else if (p1.type === "register" && p2.type === "regaddress") 578 | opCode = opcodes.SHL_REGADDRESS_WITH_REG; 579 | else if (p1.type === "register" && p2.type === "address") 580 | opCode = opcodes.SHL_ADDRESS_WITH_REG; 581 | else if (p1.type === "register" && p2.type === "number") 582 | opCode = opcodes.SHL_NUMBER_WITH_REG; 583 | else 584 | throw instr + " does not support this operands"; 585 | 586 | code.push(opCode, p1.value, p2.value); 587 | break; 588 | case 'SHR': 589 | case 'SAR': 590 | p1 = getValue(match[op1_group]); 591 | p2 = getValue(match[op2_group]); 592 | 593 | if (p1.type === "register" && p2.type === "register") 594 | opCode = opcodes.SHR_REG_WITH_REG; 595 | else if (p1.type === "register" && p2.type === "regaddress") 596 | opCode = opcodes.SHR_REGADDRESS_WITH_REG; 597 | else if (p1.type === "register" && p2.type === "address") 598 | opCode = opcodes.SHR_ADDRESS_WITH_REG; 599 | else if (p1.type === "register" && p2.type === "number") 600 | opCode = opcodes.SHR_NUMBER_WITH_REG; 601 | else 602 | throw instr + " does not support this operands"; 603 | 604 | code.push(opCode, p1.value, p2.value); 605 | break; 606 | default: 607 | throw "Invalid instruction: " + match[2]; 608 | } 609 | } 610 | } else { 611 | // Check if line starts with a comment otherwise the line contains an error and can not be parsed 612 | var line = lines[i].trim(); 613 | if (line !== "" && line.slice(0, 1) !== ";") { 614 | throw "Syntax error"; 615 | } 616 | } 617 | } catch (e) { 618 | throw {error: e, line: i}; 619 | } 620 | } 621 | 622 | // Replace label 623 | for (i = 0, l = code.length; i < l; i++) { 624 | if (!angular.isNumber(code[i])) { 625 | if (code[i] in labels) { 626 | code[i] = labels[code[i]]; 627 | } else { 628 | 629 | throw {error: "Undefined label: " + code[i]}; 630 | } 631 | } 632 | } 633 | 634 | return {code: code, mapping: mapping, labels: labels}; 635 | } 636 | }; 637 | }]); 638 | ;app.service('cpu', ['opcodes', 'memory', function(opcodes, memory) { 639 | var cpu = { 640 | step: function() { 641 | var self = this; 642 | 643 | if (self.fault === true) { 644 | throw "FAULT. Reset to continue."; 645 | } 646 | 647 | try { 648 | var checkGPR = function(reg) { 649 | if (reg < 0 || reg >= self.gpr.length) { 650 | throw "Invalid register: " + reg; 651 | } else { 652 | return reg; 653 | } 654 | }; 655 | 656 | var checkGPR_SP = function(reg) { 657 | if (reg < 0 || reg >= 1 + self.gpr.length) { 658 | throw "Invalid register: " + reg; 659 | } else { 660 | return reg; 661 | } 662 | }; 663 | 664 | var setGPR_SP = function(reg,value) 665 | { 666 | if(reg >= 0 && reg < self.gpr.length) { 667 | self.gpr[reg] = value; 668 | } else if(reg == self.gpr.length) { 669 | self.sp = value; 670 | 671 | // Not likely to happen, since we always get here after checkOpertion(). 672 | if (self.sp < self.minSP) { 673 | throw "Stack overflow"; 674 | } else if (self.sp > self.maxSP) { 675 | throw "Stack underflow"; 676 | } 677 | } else { 678 | throw "Invalid register: " + reg; 679 | } 680 | }; 681 | 682 | var getGPR_SP = function(reg) 683 | { 684 | if(reg >= 0 && reg < self.gpr.length) { 685 | return self.gpr[reg]; 686 | } else if(reg == self.gpr.length) { 687 | return self.sp; 688 | } else { 689 | throw "Invalid register: " + reg; 690 | } 691 | }; 692 | 693 | var indirectRegisterAddress = function(value) { 694 | var reg = value % 8; 695 | 696 | var base; 697 | if (reg < self.gpr.length) { 698 | base = self.gpr[reg]; 699 | } else { 700 | base = self.sp; 701 | } 702 | 703 | var offset = Math.floor(value / 8); 704 | if ( offset > 15 ) { 705 | offset = offset - 32; 706 | } 707 | 708 | return base+offset; 709 | }; 710 | 711 | var checkOperation = function(value) { 712 | self.zero = false; 713 | self.carry = false; 714 | 715 | if (value >= 256) { 716 | self.carry = true; 717 | value = value % 256; 718 | } else if (value === 0) { 719 | self.zero = true; 720 | } else if (value < 0) { 721 | self.carry = true; 722 | value = 256 - (-value) % 256; 723 | } 724 | 725 | return value; 726 | }; 727 | 728 | var jump = function(newIP) { 729 | if (newIP < 0 || newIP >= memory.data.length) { 730 | throw "IP outside memory"; 731 | } else { 732 | self.ip = newIP; 733 | } 734 | }; 735 | 736 | var push = function(value) { 737 | memory.store(self.sp--, value); 738 | if (self.sp < self.minSP) { 739 | throw "Stack overflow"; 740 | } 741 | }; 742 | 743 | var pop = function() { 744 | var value = memory.load(++self.sp); 745 | if (self.sp > self.maxSP) { 746 | throw "Stack underflow"; 747 | } 748 | 749 | return value; 750 | }; 751 | 752 | var division = function(divisor) { 753 | if (divisor === 0) { 754 | throw "Division by 0"; 755 | } 756 | 757 | return Math.floor(self.gpr[0] / divisor); 758 | }; 759 | 760 | if (self.ip < 0 || self.ip >= memory.data.length) { 761 | throw "Instruction pointer is outside of memory"; 762 | } 763 | 764 | var regTo, regFrom, memFrom, memTo, number; 765 | var instr = memory.load(self.ip); 766 | switch(instr) { 767 | case opcodes.NONE: 768 | return false; // Abort step 769 | case opcodes.MOV_REG_TO_REG: 770 | regTo = checkGPR_SP(memory.load(++self.ip)); 771 | regFrom = checkGPR_SP(memory.load(++self.ip)); 772 | setGPR_SP(regTo,getGPR_SP(regFrom)); 773 | self.ip++; 774 | break; 775 | case opcodes.MOV_ADDRESS_TO_REG: 776 | regTo = checkGPR_SP(memory.load(++self.ip)); 777 | memFrom = memory.load(++self.ip); 778 | setGPR_SP(regTo,memory.load(memFrom)); 779 | self.ip++; 780 | break; 781 | case opcodes.MOV_REGADDRESS_TO_REG: 782 | regTo = checkGPR_SP(memory.load(++self.ip)); 783 | regFrom = memory.load(++self.ip); 784 | setGPR_SP(regTo,memory.load(indirectRegisterAddress(regFrom))); 785 | self.ip++; 786 | break; 787 | case opcodes.MOV_REG_TO_ADDRESS: 788 | memTo = memory.load(++self.ip); 789 | regFrom = checkGPR_SP(memory.load(++self.ip)); 790 | memory.store(memTo, getGPR_SP(regFrom)); 791 | self.ip++; 792 | break; 793 | case opcodes.MOV_REG_TO_REGADDRESS: 794 | regTo = memory.load(++self.ip); 795 | regFrom = checkGPR_SP(memory.load(++self.ip)); 796 | memory.store(indirectRegisterAddress(regTo), getGPR_SP(regFrom)); 797 | self.ip++; 798 | break; 799 | case opcodes.MOV_NUMBER_TO_REG: 800 | regTo = checkGPR_SP(memory.load(++self.ip)); 801 | number = memory.load(++self.ip); 802 | setGPR_SP(regTo,number); 803 | self.ip++; 804 | break; 805 | case opcodes.MOV_NUMBER_TO_ADDRESS: 806 | memTo = memory.load(++self.ip); 807 | number = memory.load(++self.ip); 808 | memory.store(memTo, number); 809 | self.ip++; 810 | break; 811 | case opcodes.MOV_NUMBER_TO_REGADDRESS: 812 | regTo = memory.load(++self.ip); 813 | number = memory.load(++self.ip); 814 | memory.store(indirectRegisterAddress(regTo), number); 815 | self.ip++; 816 | break; 817 | case opcodes.ADD_REG_TO_REG: 818 | regTo = checkGPR_SP(memory.load(++self.ip)); 819 | regFrom = checkGPR_SP(memory.load(++self.ip)); 820 | setGPR_SP(regTo,checkOperation(getGPR_SP(regTo) + getGPR_SP(regFrom))); 821 | self.ip++; 822 | break; 823 | case opcodes.ADD_REGADDRESS_TO_REG: 824 | regTo = checkGPR_SP(memory.load(++self.ip)); 825 | regFrom = memory.load(++self.ip); 826 | setGPR_SP(regTo,checkOperation(getGPR_SP(regTo) + memory.load(indirectRegisterAddress(regFrom)))); 827 | self.ip++; 828 | break; 829 | case opcodes.ADD_ADDRESS_TO_REG: 830 | regTo = checkGPR_SP(memory.load(++self.ip)); 831 | memFrom = memory.load(++self.ip); 832 | setGPR_SP(regTo,checkOperation(getGPR_SP(regTo) + memory.load(memFrom))); 833 | self.ip++; 834 | break; 835 | case opcodes.ADD_NUMBER_TO_REG: 836 | regTo = checkGPR_SP(memory.load(++self.ip)); 837 | number = memory.load(++self.ip); 838 | setGPR_SP(regTo,checkOperation(getGPR_SP(regTo) + number)); 839 | self.ip++; 840 | break; 841 | case opcodes.SUB_REG_FROM_REG: 842 | regTo = checkGPR_SP(memory.load(++self.ip)); 843 | regFrom = checkGPR_SP(memory.load(++self.ip)); 844 | setGPR_SP(regTo,checkOperation(getGPR_SP(regTo) - self.gpr[regFrom])); 845 | self.ip++; 846 | break; 847 | case opcodes.SUB_REGADDRESS_FROM_REG: 848 | regTo = checkGPR_SP(memory.load(++self.ip)); 849 | regFrom = memory.load(++self.ip); 850 | setGPR_SP(regTo,checkOperation(getGPR_SP(regTo) - memory.load(indirectRegisterAddress(regFrom)))); 851 | self.ip++; 852 | break; 853 | case opcodes.SUB_ADDRESS_FROM_REG: 854 | regTo = checkGPR_SP(memory.load(++self.ip)); 855 | memFrom = memory.load(++self.ip); 856 | setGPR_SP(regTo,checkOperation(getGPR_SP(regTo) - memory.load(memFrom))); 857 | self.ip++; 858 | break; 859 | case opcodes.SUB_NUMBER_FROM_REG: 860 | regTo = checkGPR_SP(memory.load(++self.ip)); 861 | number = memory.load(++self.ip); 862 | setGPR_SP(regTo,checkOperation(getGPR_SP(regTo) - number)); 863 | self.ip++; 864 | break; 865 | case opcodes.INC_REG: 866 | regTo = checkGPR_SP(memory.load(++self.ip)); 867 | setGPR_SP(regTo,checkOperation(getGPR_SP(regTo) + 1)); 868 | self.ip++; 869 | break; 870 | case opcodes.DEC_REG: 871 | regTo = checkGPR_SP(memory.load(++self.ip)); 872 | setGPR_SP(regTo,checkOperation(getGPR_SP(regTo) - 1)); 873 | self.ip++; 874 | break; 875 | case opcodes.CMP_REG_WITH_REG: 876 | regTo = checkGPR_SP(memory.load(++self.ip)); 877 | regFrom = checkGPR_SP(memory.load(++self.ip)); 878 | checkOperation(getGPR_SP(regTo) - getGPR_SP(regFrom)); 879 | self.ip++; 880 | break; 881 | case opcodes.CMP_REGADDRESS_WITH_REG: 882 | regTo = checkGPR_SP(memory.load(++self.ip)); 883 | regFrom = memory.load(++self.ip); 884 | checkOperation(getGPR_SP(regTo) - memory.load(indirectRegisterAddress(regFrom))); 885 | self.ip++; 886 | break; 887 | case opcodes.CMP_ADDRESS_WITH_REG: 888 | regTo = checkGPR_SP(memory.load(++self.ip)); 889 | memFrom = memory.load(++self.ip); 890 | checkOperation(getGPR_SP(regTo) - memory.load(memFrom)); 891 | self.ip++; 892 | break; 893 | case opcodes.CMP_NUMBER_WITH_REG: 894 | regTo = checkGPR_SP(memory.load(++self.ip)); 895 | number = memory.load(++self.ip); 896 | checkOperation(getGPR_SP(regTo) - number); 897 | self.ip++; 898 | break; 899 | case opcodes.JMP_REGADDRESS: 900 | regTo = checkGPR(memory.load(++self.ip)); 901 | jump(self.gpr[regTo]); 902 | break; 903 | case opcodes.JMP_ADDRESS: 904 | number = memory.load(++self.ip); 905 | jump(number); 906 | break; 907 | case opcodes.JC_REGADDRESS: 908 | regTo = checkGPR(memory.load(++self.ip)); 909 | if (self.carry) { 910 | jump(self.gpr[regTo]); 911 | } else { 912 | self.ip++; 913 | } 914 | break; 915 | case opcodes.JC_ADDRESS: 916 | number = memory.load(++self.ip); 917 | if (self.carry) { 918 | jump(number); 919 | } else { 920 | self.ip++; 921 | } 922 | break; 923 | case opcodes.JNC_REGADDRESS: 924 | regTo = checkGPR(memory.load(++self.ip)); 925 | if (!self.carry) { 926 | jump(self.gpr[regTo]); 927 | } else { 928 | self.ip++; 929 | } 930 | break; 931 | case opcodes.JNC_ADDRESS: 932 | number = memory.load(++self.ip); 933 | if (!self.carry) { 934 | jump(number); 935 | } else { 936 | self.ip++; 937 | } 938 | break; 939 | case opcodes.JZ_REGADDRESS: 940 | regTo = checkGPR(memory.load(++self.ip)); 941 | if (self.zero) { 942 | jump(self.gpr[regTo]); 943 | } else { 944 | self.ip++; 945 | } 946 | break; 947 | case opcodes.JZ_ADDRESS: 948 | number = memory.load(++self.ip); 949 | if (self.zero) { 950 | jump(number); 951 | } else { 952 | self.ip++; 953 | } 954 | break; 955 | case opcodes.JNZ_REGADDRESS: 956 | regTo = checkGPR(memory.load(++self.ip)); 957 | if (!self.zero) { 958 | jump(self.gpr[regTo]); 959 | } else { 960 | self.ip++; 961 | } 962 | break; 963 | case opcodes.JNZ_ADDRESS: 964 | number = memory.load(++self.ip); 965 | if (!self.zero) { 966 | jump(number); 967 | } else { 968 | self.ip++; 969 | } 970 | break; 971 | case opcodes.JA_REGADDRESS: 972 | regTo = checkGPR(memory.load(++self.ip)); 973 | if (!self.zero && !self.carry) { 974 | jump(self.gpr[regTo]); 975 | } else { 976 | self.ip++; 977 | } 978 | break; 979 | case opcodes.JA_ADDRESS: 980 | number = memory.load(++self.ip); 981 | if (!self.zero && !self.carry) { 982 | jump(number); 983 | } else { 984 | self.ip++; 985 | } 986 | break; 987 | case opcodes.JNA_REGADDRESS: // JNA REG 988 | regTo = checkGPR(memory.load(++self.ip)); 989 | if (self.zero || self.carry) { 990 | jump(self.gpr[regTo]); 991 | } else { 992 | self.ip++; 993 | } 994 | break; 995 | case opcodes.JNA_ADDRESS: 996 | number = memory.load(++self.ip); 997 | if (self.zero || self.carry) { 998 | jump(number); 999 | } else { 1000 | self.ip++; 1001 | } 1002 | break; 1003 | case opcodes.PUSH_REG: 1004 | regFrom = checkGPR(memory.load(++self.ip)); 1005 | push(self.gpr[regFrom]); 1006 | self.ip++; 1007 | break; 1008 | case opcodes.PUSH_REGADDRESS: 1009 | regFrom = memory.load(++self.ip); 1010 | push(memory.load(indirectRegisterAddress(regFrom))); 1011 | self.ip++; 1012 | break; 1013 | case opcodes.PUSH_ADDRESS: 1014 | memFrom = memory.load(++self.ip); 1015 | push(memory.load(memFrom)); 1016 | self.ip++; 1017 | break; 1018 | case opcodes.PUSH_NUMBER: 1019 | number = memory.load(++self.ip); 1020 | push(number); 1021 | self.ip++; 1022 | break; 1023 | case opcodes.POP_REG: 1024 | regTo = checkGPR(memory.load(++self.ip)); 1025 | self.gpr[regTo] = pop(); 1026 | self.ip++; 1027 | break; 1028 | case opcodes.CALL_REGADDRESS: 1029 | regTo = checkGPR(memory.load(++self.ip)); 1030 | push(self.ip+1); 1031 | jump(self.gpr[regTo]); 1032 | break; 1033 | case opcodes.CALL_ADDRESS: 1034 | number = memory.load(++self.ip); 1035 | push(self.ip+1); 1036 | jump(number); 1037 | break; 1038 | case opcodes.RET: 1039 | jump(pop()); 1040 | break; 1041 | case opcodes.MUL_REG: // A = A * REG 1042 | regFrom = checkGPR(memory.load(++self.ip)); 1043 | self.gpr[0] = checkOperation(self.gpr[0] * self.gpr[regFrom]); 1044 | self.ip++; 1045 | break; 1046 | case opcodes.MUL_REGADDRESS: // A = A * [REG] 1047 | regFrom = memory.load(++self.ip); 1048 | self.gpr[0] = checkOperation(self.gpr[0] * memory.load(indirectRegisterAddress(regFrom))); 1049 | self.ip++; 1050 | break; 1051 | case opcodes.MUL_ADDRESS: // A = A * [NUMBER] 1052 | memFrom = memory.load(++self.ip); 1053 | self.gpr[0] = checkOperation(self.gpr[0] * memory.load(memFrom)); 1054 | self.ip++; 1055 | break; 1056 | case opcodes.MUL_NUMBER: // A = A * NUMBER 1057 | number = memory.load(++self.ip); 1058 | self.gpr[0] = checkOperation(self.gpr[0] * number); 1059 | self.ip++; 1060 | break; 1061 | case opcodes.DIV_REG: // A = A / REG 1062 | regFrom = checkGPR(memory.load(++self.ip)); 1063 | self.gpr[0] = checkOperation(division(self.gpr[regFrom])); 1064 | self.ip++; 1065 | break; 1066 | case opcodes.DIV_REGADDRESS: // A = A / [REG] 1067 | regFrom = memory.load(++self.ip); 1068 | self.gpr[0] = checkOperation(division(memory.load(indirectRegisterAddress(regFrom)))); 1069 | self.ip++; 1070 | break; 1071 | case opcodes.DIV_ADDRESS: // A = A / [NUMBER] 1072 | memFrom = memory.load(++self.ip); 1073 | self.gpr[0] = checkOperation(division(memory.load(memFrom))); 1074 | self.ip++; 1075 | break; 1076 | case opcodes.DIV_NUMBER: // A = A / NUMBER 1077 | number = memory.load(++self.ip); 1078 | self.gpr[0] = checkOperation(division(number)); 1079 | self.ip++; 1080 | break; 1081 | case opcodes.AND_REG_WITH_REG: 1082 | regTo = checkGPR(memory.load(++self.ip)); 1083 | regFrom = checkGPR(memory.load(++self.ip)); 1084 | self.gpr[regTo] = checkOperation(self.gpr[regTo] & self.gpr[regFrom]); 1085 | self.ip++; 1086 | break; 1087 | case opcodes.AND_REGADDRESS_WITH_REG: 1088 | regTo = checkGPR(memory.load(++self.ip)); 1089 | regFrom = memory.load(++self.ip); 1090 | self.gpr[regTo] = checkOperation(self.gpr[regTo] & memory.load(indirectRegisterAddress(regFrom))); 1091 | self.ip++; 1092 | break; 1093 | case opcodes.AND_ADDRESS_WITH_REG: 1094 | regTo = checkGPR(memory.load(++self.ip)); 1095 | memFrom = memory.load(++self.ip); 1096 | self.gpr[regTo] = checkOperation(self.gpr[regTo] & memory.load(memFrom)); 1097 | self.ip++; 1098 | break; 1099 | case opcodes.AND_NUMBER_WITH_REG: 1100 | regTo = checkGPR(memory.load(++self.ip)); 1101 | number = memory.load(++self.ip); 1102 | self.gpr[regTo] = checkOperation(self.gpr[regTo] & number); 1103 | self.ip++; 1104 | break; 1105 | case opcodes.OR_REG_WITH_REG: 1106 | regTo = checkGPR(memory.load(++self.ip)); 1107 | regFrom = checkGPR(memory.load(++self.ip)); 1108 | self.gpr[regTo] = checkOperation(self.gpr[regTo] | self.gpr[regFrom]); 1109 | self.ip++; 1110 | break; 1111 | case opcodes.OR_REGADDRESS_WITH_REG: 1112 | regTo = checkGPR(memory.load(++self.ip)); 1113 | regFrom = memory.load(++self.ip); 1114 | self.gpr[regTo] = checkOperation(self.gpr[regTo] | memory.load(indirectRegisterAddress(regFrom))); 1115 | self.ip++; 1116 | break; 1117 | case opcodes.OR_ADDRESS_WITH_REG: 1118 | regTo = checkGPR(memory.load(++self.ip)); 1119 | memFrom = memory.load(++self.ip); 1120 | self.gpr[regTo] = checkOperation(self.gpr[regTo] | memory.load(memFrom)); 1121 | self.ip++; 1122 | break; 1123 | case opcodes.OR_NUMBER_WITH_REG: 1124 | regTo = checkGPR(memory.load(++self.ip)); 1125 | number = memory.load(++self.ip); 1126 | self.gpr[regTo] = checkOperation(self.gpr[regTo] | number); 1127 | self.ip++; 1128 | break; 1129 | case opcodes.XOR_REG_WITH_REG: 1130 | regTo = checkGPR(memory.load(++self.ip)); 1131 | regFrom = checkGPR(memory.load(++self.ip)); 1132 | self.gpr[regTo] = checkOperation(self.gpr[regTo] ^ self.gpr[regFrom]); 1133 | self.ip++; 1134 | break; 1135 | case opcodes.XOR_REGADDRESS_WITH_REG: 1136 | regTo = checkGPR(memory.load(++self.ip)); 1137 | regFrom = memory.load(++self.ip); 1138 | self.gpr[regTo] = checkOperation(self.gpr[regTo] ^ memory.load(indirectRegisterAddress(regFrom))); 1139 | self.ip++; 1140 | break; 1141 | case opcodes.XOR_ADDRESS_WITH_REG: 1142 | regTo = checkGPR(memory.load(++self.ip)); 1143 | memFrom = memory.load(++self.ip); 1144 | self.gpr[regTo] = checkOperation(self.gpr[regTo] ^ memory.load(memFrom)); 1145 | self.ip++; 1146 | break; 1147 | case opcodes.XOR_NUMBER_WITH_REG: 1148 | regTo = checkGPR(memory.load(++self.ip)); 1149 | number = memory.load(++self.ip); 1150 | self.gpr[regTo] = checkOperation(self.gpr[regTo] ^ number); 1151 | self.ip++; 1152 | break; 1153 | case opcodes.NOT_REG: 1154 | regTo = checkGPR(memory.load(++self.ip)); 1155 | self.gpr[regTo] = checkOperation(~self.gpr[regTo]); 1156 | self.ip++; 1157 | break; 1158 | case opcodes.SHL_REG_WITH_REG: 1159 | regTo = checkGPR(memory.load(++self.ip)); 1160 | regFrom = checkGPR(memory.load(++self.ip)); 1161 | self.gpr[regTo] = checkOperation(self.gpr[regTo] << self.gpr[regFrom]); 1162 | self.ip++; 1163 | break; 1164 | case opcodes.SHL_REGADDRESS_WITH_REG: 1165 | regTo = checkGPR(memory.load(++self.ip)); 1166 | regFrom = memory.load(++self.ip); 1167 | self.gpr[regTo] = checkOperation(self.gpr[regTo] << memory.load(indirectRegisterAddress(regFrom))); 1168 | self.ip++; 1169 | break; 1170 | case opcodes.SHL_ADDRESS_WITH_REG: 1171 | regTo = checkGPR(memory.load(++self.ip)); 1172 | memFrom = memory.load(++self.ip); 1173 | self.gpr[regTo] = checkOperation(self.gpr[regTo] << memory.load(memFrom)); 1174 | self.ip++; 1175 | break; 1176 | case opcodes.SHL_NUMBER_WITH_REG: 1177 | regTo = checkGPR(memory.load(++self.ip)); 1178 | number = memory.load(++self.ip); 1179 | self.gpr[regTo] = checkOperation(self.gpr[regTo] << number); 1180 | self.ip++; 1181 | break; 1182 | case opcodes.SHR_REG_WITH_REG: 1183 | regTo = checkGPR(memory.load(++self.ip)); 1184 | regFrom = checkGPR(memory.load(++self.ip)); 1185 | self.gpr[regTo] = checkOperation(self.gpr[regTo] >>> self.gpr[regFrom]); 1186 | self.ip++; 1187 | break; 1188 | case opcodes.SHR_REGADDRESS_WITH_REG: 1189 | regTo = checkGPR(memory.load(++self.ip)); 1190 | regFrom = memory.load(++self.ip); 1191 | self.gpr[regTo] = checkOperation(self.gpr[regTo] >>> memory.load(indirectRegisterAddress(regFrom))); 1192 | self.ip++; 1193 | break; 1194 | case opcodes.SHR_ADDRESS_WITH_REG: 1195 | regTo = checkGPR(memory.load(++self.ip)); 1196 | memFrom = memory.load(++self.ip); 1197 | self.gpr[regTo] = checkOperation(self.gpr[regTo] >>> memory.load(memFrom)); 1198 | self.ip++; 1199 | break; 1200 | case opcodes.SHR_NUMBER_WITH_REG: 1201 | regTo = checkGPR(memory.load(++self.ip)); 1202 | number = memory.load(++self.ip); 1203 | self.gpr[regTo] = checkOperation(self.gpr[regTo] >>> number); 1204 | self.ip++; 1205 | break; 1206 | default: 1207 | throw "Invalid op code: " + instr; 1208 | } 1209 | 1210 | return true; 1211 | } catch(e) { 1212 | self.fault = true; 1213 | throw e; 1214 | } 1215 | }, 1216 | reset: function() { 1217 | var self = this; 1218 | self.maxSP = 231; 1219 | self.minSP = 0; 1220 | 1221 | self.gpr = [0, 0, 0, 0]; 1222 | self.sp = self.maxSP; 1223 | self.ip = 0; 1224 | self.zero = false; 1225 | self.carry = false; 1226 | self.fault = false; 1227 | } 1228 | }; 1229 | 1230 | cpu.reset(); 1231 | return cpu; 1232 | }]); 1233 | ;app.service('memory', [function () { 1234 | var memory = { 1235 | data: Array(256), 1236 | lastAccess: -1, 1237 | load: function (address) { 1238 | var self = this; 1239 | 1240 | if (address < 0 || address >= self.data.length) { 1241 | throw "Memory access violation at " + address; 1242 | } 1243 | 1244 | self.lastAccess = address; 1245 | return self.data[address]; 1246 | }, 1247 | store: function (address, value) { 1248 | var self = this; 1249 | 1250 | if (address < 0 || address >= self.data.length) { 1251 | throw "Memory access violation at " + address; 1252 | } 1253 | 1254 | self.lastAccess = address; 1255 | self.data[address] = value; 1256 | }, 1257 | reset: function () { 1258 | var self = this; 1259 | 1260 | self.lastAccess = -1; 1261 | for (var i = 0, l = self.data.length; i < l; i++) { 1262 | self.data[i] = 0; 1263 | } 1264 | } 1265 | }; 1266 | 1267 | memory.reset(); 1268 | return memory; 1269 | }]); 1270 | ;app.service('opcodes', [function() { 1271 | var opcodes = { 1272 | NONE: 0, 1273 | MOV_REG_TO_REG: 1, 1274 | MOV_ADDRESS_TO_REG: 2, 1275 | MOV_REGADDRESS_TO_REG: 3, 1276 | MOV_REG_TO_ADDRESS: 4, 1277 | MOV_REG_TO_REGADDRESS: 5, 1278 | MOV_NUMBER_TO_REG: 6, 1279 | MOV_NUMBER_TO_ADDRESS: 7, 1280 | MOV_NUMBER_TO_REGADDRESS: 8, 1281 | ADD_REG_TO_REG: 10, 1282 | ADD_REGADDRESS_TO_REG: 11, 1283 | ADD_ADDRESS_TO_REG: 12, 1284 | ADD_NUMBER_TO_REG: 13, 1285 | SUB_REG_FROM_REG: 14, 1286 | SUB_REGADDRESS_FROM_REG: 15, 1287 | SUB_ADDRESS_FROM_REG: 16, 1288 | SUB_NUMBER_FROM_REG: 17, 1289 | INC_REG: 18, 1290 | DEC_REG: 19, 1291 | CMP_REG_WITH_REG: 20, 1292 | CMP_REGADDRESS_WITH_REG: 21, 1293 | CMP_ADDRESS_WITH_REG: 22, 1294 | CMP_NUMBER_WITH_REG: 23, 1295 | JMP_REGADDRESS: 30, 1296 | JMP_ADDRESS: 31, 1297 | JC_REGADDRESS: 32, 1298 | JC_ADDRESS: 33, 1299 | JNC_REGADDRESS: 34, 1300 | JNC_ADDRESS: 35, 1301 | JZ_REGADDRESS: 36, 1302 | JZ_ADDRESS: 37, 1303 | JNZ_REGADDRESS: 38, 1304 | JNZ_ADDRESS: 39, 1305 | JA_REGADDRESS: 40, 1306 | JA_ADDRESS: 41, 1307 | JNA_REGADDRESS: 42, 1308 | JNA_ADDRESS: 43, 1309 | PUSH_REG: 50, 1310 | PUSH_REGADDRESS: 51, 1311 | PUSH_ADDRESS: 52, 1312 | PUSH_NUMBER: 53, 1313 | POP_REG: 54, 1314 | CALL_REGADDRESS: 55, 1315 | CALL_ADDRESS: 56, 1316 | RET: 57, 1317 | MUL_REG: 60, 1318 | MUL_REGADDRESS: 61, 1319 | MUL_ADDRESS: 62, 1320 | MUL_NUMBER: 63, 1321 | DIV_REG: 64, 1322 | DIV_REGADDRESS: 65, 1323 | DIV_ADDRESS: 66, 1324 | DIV_NUMBER: 67, 1325 | AND_REG_WITH_REG: 70, 1326 | AND_REGADDRESS_WITH_REG: 71, 1327 | AND_ADDRESS_WITH_REG: 72, 1328 | AND_NUMBER_WITH_REG: 73, 1329 | OR_REG_WITH_REG: 74, 1330 | OR_REGADDRESS_WITH_REG: 75, 1331 | OR_ADDRESS_WITH_REG: 76, 1332 | OR_NUMBER_WITH_REG: 77, 1333 | XOR_REG_WITH_REG: 78, 1334 | XOR_REGADDRESS_WITH_REG: 79, 1335 | XOR_ADDRESS_WITH_REG: 80, 1336 | XOR_NUMBER_WITH_REG: 81, 1337 | NOT_REG: 82, 1338 | SHL_REG_WITH_REG: 90, 1339 | SHL_REGADDRESS_WITH_REG: 91, 1340 | SHL_ADDRESS_WITH_REG: 92, 1341 | SHL_NUMBER_WITH_REG: 93, 1342 | SHR_REG_WITH_REG: 94, 1343 | SHR_REGADDRESS_WITH_REG: 95, 1344 | SHR_ADDRESS_WITH_REG: 96, 1345 | SHR_NUMBER_WITH_REG: 97 1346 | }; 1347 | 1348 | return opcodes; 1349 | }]); 1350 | ;app.controller('Ctrl', ['$document', '$scope', '$timeout', 'cpu', 'memory', 'assembler', function ($document, $scope, $timeout, cpu, memory, assembler) { 1351 | $scope.memory = memory; 1352 | $scope.cpu = cpu; 1353 | $scope.error = ''; 1354 | $scope.isRunning = false; 1355 | $scope.displayHex = true; 1356 | $scope.displayInstr = true; 1357 | $scope.displayA = false; 1358 | $scope.displayB = false; 1359 | $scope.displayC = false; 1360 | $scope.displayD = false; 1361 | $scope.speeds = [{speed: 1, desc: "1 HZ"}, 1362 | {speed: 4, desc: "4 HZ"}, 1363 | {speed: 8, desc: "8 HZ"}, 1364 | {speed: 16, desc: "16 HZ"}]; 1365 | $scope.speed = 4; 1366 | $scope.outputStartIndex = 232; 1367 | 1368 | $scope.code = "; Simple example\n; Writes Hello World to the output\n\n JMP start\nhello: DB \"Hello World!\" ; Variable\n DB 0 ; String terminator\n\nstart:\n MOV C, hello ; Point to var \n MOV D, 232 ; Point to output\n CALL print\n HLT ; Stop execution\n\nprint: ; print(C:*from, D:*to)\n PUSH A\n PUSH B\n MOV B, 0\n.loop:\n MOV A, [C] ; Get char from var\n MOV [D], A ; Write to output\n INC C\n INC D \n CMP B, [C] ; Check if end\n JNZ .loop ; jump if not\n\n POP B\n POP A\n RET"; 1369 | 1370 | $scope.reset = function () { 1371 | cpu.reset(); 1372 | memory.reset(); 1373 | $scope.error = ''; 1374 | $scope.selectedLine = -1; 1375 | }; 1376 | 1377 | $scope.executeStep = function () { 1378 | if (!$scope.checkPrgrmLoaded()) { 1379 | $scope.assemble(); 1380 | } 1381 | 1382 | try { 1383 | // Execute 1384 | var res = cpu.step(); 1385 | 1386 | // Mark in code 1387 | if (cpu.ip in $scope.mapping) { 1388 | $scope.selectedLine = $scope.mapping[cpu.ip]; 1389 | } 1390 | 1391 | return res; 1392 | } catch (e) { 1393 | $scope.error = e; 1394 | return false; 1395 | } 1396 | }; 1397 | 1398 | var runner; 1399 | $scope.run = function () { 1400 | if (!$scope.checkPrgrmLoaded()) { 1401 | $scope.assemble(); 1402 | } 1403 | 1404 | $scope.isRunning = true; 1405 | runner = $timeout(function () { 1406 | if ($scope.executeStep() === true) { 1407 | $scope.run(); 1408 | } else { 1409 | $scope.isRunning = false; 1410 | } 1411 | }, 1000 / $scope.speed); 1412 | }; 1413 | 1414 | $scope.stop = function () { 1415 | $timeout.cancel(runner); 1416 | $scope.isRunning = false; 1417 | }; 1418 | 1419 | $scope.checkPrgrmLoaded = function () { 1420 | for (var i = 0, l = memory.data.length; i < l; i++) { 1421 | if (memory.data[i] !== 0) { 1422 | return true; 1423 | } 1424 | } 1425 | 1426 | return false; 1427 | }; 1428 | 1429 | $scope.getChar = function (value) { 1430 | var text = String.fromCharCode(value); 1431 | 1432 | if (text.trim() === '') { 1433 | return '\u00A0\u00A0'; 1434 | } else { 1435 | return text; 1436 | } 1437 | }; 1438 | 1439 | $scope.assemble = function () { 1440 | try { 1441 | $scope.reset(); 1442 | 1443 | var assembly = assembler.go($scope.code); 1444 | $scope.mapping = assembly.mapping; 1445 | var binary = assembly.code; 1446 | $scope.labels = assembly.labels; 1447 | 1448 | if (binary.length > memory.data.length) 1449 | throw "Binary code does not fit into the memory. Max " + memory.data.length + " bytes are allowed"; 1450 | 1451 | for (var i = 0, l = binary.length; i < l; i++) { 1452 | memory.data[i] = binary[i]; 1453 | } 1454 | } catch (e) { 1455 | if (e.line !== undefined) { 1456 | $scope.error = e.line + " | " + e.error; 1457 | $scope.selectedLine = e.line; 1458 | } else { 1459 | $scope.error = e.error; 1460 | } 1461 | } 1462 | }; 1463 | 1464 | $scope.jumpToLine = function (index) { 1465 | $document[0].getElementById('sourceCode').scrollIntoView(); 1466 | $scope.selectedLine = $scope.mapping[index]; 1467 | }; 1468 | 1469 | 1470 | $scope.isInstruction = function (index) { 1471 | return $scope.mapping !== undefined && 1472 | $scope.mapping[index] !== undefined && 1473 | $scope.displayInstr; 1474 | }; 1475 | 1476 | $scope.getMemoryCellCss = function (index) { 1477 | if (index >= $scope.outputStartIndex) { 1478 | return 'output-bg'; 1479 | } else if ($scope.isInstruction(index)) { 1480 | return 'instr-bg'; 1481 | } else if (index > cpu.sp && index <= cpu.maxSP) { 1482 | return 'stack-bg'; 1483 | } else { 1484 | return ''; 1485 | } 1486 | }; 1487 | 1488 | $scope.getMemoryInnerCellCss = function (index) { 1489 | if (index === cpu.ip) { 1490 | return 'marker marker-ip'; 1491 | } else if (index === cpu.sp) { 1492 | return 'marker marker-sp'; 1493 | } else if (index === cpu.gpr[0] && $scope.displayA) { 1494 | return 'marker marker-a'; 1495 | } else if (index === cpu.gpr[1] && $scope.displayB) { 1496 | return 'marker marker-b'; 1497 | } else if (index === cpu.gpr[2] && $scope.displayC) { 1498 | return 'marker marker-c'; 1499 | } else if (index === cpu.gpr[3] && $scope.displayD) { 1500 | return 'marker marker-d'; 1501 | } else { 1502 | return ''; 1503 | } 1504 | }; 1505 | }]); 1506 | ;app.filter('flag', function() { 1507 | return function(input) { 1508 | return input.toString().toUpperCase(); 1509 | }; 1510 | }); 1511 | ;app.filter('number', function() { 1512 | return function(input, isHex) { 1513 | if (isHex) { 1514 | var hex = input.toString(16).toUpperCase(); 1515 | return hex.length == 1 ? "0" + hex: hex; 1516 | } else { 1517 | return input.toString(10); 1518 | } 1519 | }; 1520 | }); 1521 | ;// Source: http://lostsource.com/2012/11/30/selecting-textarea-line.html 1522 | app.directive('selectLine', [function () { 1523 | return { 1524 | restrict: 'A', 1525 | link: function (scope, element, attrs, controller) { 1526 | scope.$watch('selectedLine', function () { 1527 | if (scope.selectedLine >= 0) { 1528 | var lines = element[0].value.split("\n"); 1529 | 1530 | // Calculate start/end 1531 | var startPos = 0; 1532 | for (var x = 0; x < lines.length; x++) { 1533 | if (x == scope.selectedLine) { 1534 | break; 1535 | } 1536 | startPos += (lines[x].length + 1); 1537 | } 1538 | 1539 | var endPos = lines[scope.selectedLine].length + startPos; 1540 | 1541 | // Chrome / Firefox 1542 | if (typeof(element[0].selectionStart) != "undefined") { 1543 | element[0].focus(); 1544 | element[0].selectionStart = startPos; 1545 | element[0].selectionEnd = endPos; 1546 | } 1547 | 1548 | // IE 1549 | if (document.selection && document.selection.createRange) { 1550 | element[0].focus(); 1551 | element[0].select(); 1552 | var range = document.selection.createRange(); 1553 | range.collapse(true); 1554 | range.moveEnd("character", endPos); 1555 | range.moveStart("character", startPos); 1556 | range.select(); 1557 | } 1558 | } 1559 | }); 1560 | } 1561 | }; 1562 | }]); 1563 | ;app.filter('startFrom', function() { 1564 | return function(input, start) { 1565 | start = +start; //parse to int 1566 | return input.slice(start); 1567 | }; 1568 | }); 1569 | ;app.directive('tabSupport', [function () { 1570 | return { 1571 | restrict: 'A', 1572 | link: function (scope, element, attrs, controller) { 1573 | element.bind("keydown", function (e) { 1574 | if (e.keyCode === 9) { 1575 | var val = this.value; 1576 | var start = this.selectionStart; 1577 | var end = this.selectionEnd; 1578 | 1579 | this.value = val.substring(0, start) + '\t' + val.substring(end); 1580 | this.selectionStart = this.selectionEnd = start + 1; 1581 | 1582 | e.preventDefault(); 1583 | return false; 1584 | } 1585 | }); 1586 | } 1587 | }; 1588 | }]); 1589 | --------------------------------------------------------------------------------