├── .gitignore ├── app.zip ├── main.c ├── run.sh ├── MachineSystem.js ├── package.json ├── Machine.js ├── README.md ├── program.hdx ├── main.hdo ├── main.js ├── LICENSE ├── Program.js ├── common.js ├── main.html ├── ProgramStream.js ├── ProgramCompiler.js ├── ProgramCompilerTranslator.js ├── StringBuffer.js ├── MachineOperation.js ├── handy.js ├── MachineProcessor.js ├── MachineMemory.js ├── ProgramRunner.js ├── ProgramCompilerDeclaration.js ├── ProgramCompilerStatement.js ├── ProgramCompilerExpression.js └── ProgramLinker.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /app.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HDNua/JSCC/HEAD/app.zip -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | main() 2 | { 3 | int sum; 4 | int a, b; 5 | a = 10; 6 | b = 20; 7 | sum = a + b; 8 | } -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #script 2 | file="app.zip" 3 | 4 | if [ -f "$file" ] 5 | then 6 | node main.js 7 | else 8 | echo "cannot find $file" 9 | fi 10 | -------------------------------------------------------------------------------- /MachineSystem.js: -------------------------------------------------------------------------------- 1 | /** 2 | 가상 머신의 공통적 요소를 관리합니다. 3 | */ 4 | function initMachineSystem(machine) { 5 | var system = {}; 6 | 7 | machine.System = system; 8 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nw_demo", 3 | "version": "0.0.1", 4 | "main": "main.html", 5 | "window": { 6 | "width": 1280, 7 | "height": 768, 8 | "position": "center" 9 | } 10 | } -------------------------------------------------------------------------------- /Machine.js: -------------------------------------------------------------------------------- 1 | /** 2 | JSCC의 실행을 담당하는 가상 기계를 생성합니다. 3 | */ 4 | function initMachine() { 5 | var machine = {}; 6 | 7 | initMachineSystem(machine); 8 | initMachineMemory(machine); 9 | initMachineProcessor(machine); 10 | initMachineOperation(machine); 11 | 12 | window.Machine = machine; 13 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JSCC 2 | JSCC: Developing C Compiler Using JavaScript 3 | 4 | If you clicked the lecture post's link, you should go to here: 5 | https://github.com/HDNua/JSCC_Lecture. 6 | This repository is for open source developing. 7 | 8 | This is a project that develops the C programming langauge compiler using JavaScript. This project's license is MIT license so you can use and modify this code freely. 9 | I'll write how to use this module later. 10 | -------------------------------------------------------------------------------- /program.hdx: -------------------------------------------------------------------------------- 1 | .data 2 | 3 | .code 4 | push ebp 5 | mov ebp,esp 6 | sub esp,4 7 | sub esp,8 8 | mov eax,10 9 | push eax 10 | lea ebx,[ebp-8] 11 | mov eax,[ebx] 12 | pop eax 13 | mov [ebx],eax 14 | mov eax,20 15 | push eax 16 | lea ebx,[ebp-12] 17 | mov eax,[ebx] 18 | pop eax 19 | mov [ebx],eax 20 | lea ebx,[ebp-12] 21 | mov eax,[ebx] 22 | push eax 23 | lea ebx,[ebp-8] 24 | mov eax,[ebx] 25 | pop edx 26 | add eax,edx 27 | push eax 28 | lea ebx,[ebp-4] 29 | mov eax,[ebx] 30 | pop eax 31 | mov [ebx],eax 32 | mov esp,ebp 33 | pop ebp 34 | ret 35 | 36 | call 0x0004 37 | exit -------------------------------------------------------------------------------- /main.hdo: -------------------------------------------------------------------------------- 1 | .data 2 | 3 | .code 4 | global _main 5 | _main: 6 | push ebp 7 | mov ebp, esp 8 | sub esp, 4 9 | sub esp, 8 10 | mov eax, 10 11 | push eax 12 | lea ebx, [ebp-8] 13 | mov eax, [ebx] 14 | pop eax 15 | mov [ebx], eax 16 | mov eax, 20 17 | push eax 18 | lea ebx, [ebp-12] 19 | mov eax, [ebx] 20 | pop eax 21 | mov [ebx], eax 22 | lea ebx, [ebp-12] 23 | mov eax, [ebx] 24 | push eax 25 | lea ebx, [ebp-8] 26 | mov eax, [ebx] 27 | pop edx 28 | add eax, edx 29 | push eax 30 | lea ebx, [ebp-4] 31 | mov eax, [ebx] 32 | pop eax 33 | mov [ebx], eax 34 | mov esp, ebp 35 | pop ebp 36 | ret 37 | 38 | end _main -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | // get child_process module. 2 | var childproc = require('child_process'); 3 | 4 | // get os module. 5 | var os = require('os'); 6 | 7 | // get os.platform() 8 | var platform = os.platform(); 9 | console.log(platform); 10 | 11 | // set nwjs binary name by platform 12 | var execName = 'nw'; 13 | switch (platform) { 14 | case 'win32': // win32 or win64 15 | execName = 'nw'; 16 | break; 17 | 18 | case 'linux': 19 | execName = 'nw'; 20 | break; 21 | 22 | case 'darwin': // Mac OS X 23 | execName = 'nw'; 24 | break; 25 | } 26 | var execArgv = ['app.zip', __dirname, platform]; 27 | 28 | // start process using child_process 29 | // with current path string 30 | // '__dirname' would be not only path 31 | // of 'main.js' but also one of 'app.zip' 32 | // because 'main.js' and 'app.zip' have same directory 33 | childproc.exec(execName + ' ' + execArgv.join(' ')); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 HDNua 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Program.js: -------------------------------------------------------------------------------- 1 | /** 2 | JSCC 및 그 테스트 모듈을 관리하는 Program을 생성합니다. 3 | */ 4 | function initProgram() { 5 | var program = {}; 6 | initProgramStream(program); 7 | initProgramRunner(program); 8 | initProgramLinker(program); 9 | initProgramCompiler(program); 10 | 11 | // 필드 12 | program.status = []; 13 | program.statusIndex = 0; 14 | 15 | // 프로그램 메서드 16 | program.run = function() {} 17 | program.undo = function() { 18 | if (Program.statusIndex > 0) { 19 | --Program.statusIndex; 20 | Program.show(); 21 | } 22 | } 23 | program.redo = function() { 24 | if (Program.statusIndex < Program.status.length) { 25 | ++Program.statusIndex; 26 | Program.show(); 27 | } 28 | } 29 | program.show = function() { 30 | Program.Stream.mem.clear(); 31 | Program.Stream.exp.clear(); 32 | Program.Stream.reg.clear(); 33 | 34 | Program.Stream.reg.write(Program.status[Program.statusIndex].reg); 35 | Program.Stream.mem.write(Program.status[Program.statusIndex].mem); 36 | Program.Stream.exp.write(Program.status[Program.statusIndex].exp); 37 | } 38 | 39 | // 등록 40 | window.Program = program; 41 | } -------------------------------------------------------------------------------- /common.js: -------------------------------------------------------------------------------- 1 | /* common.js */ 2 | 3 | /** 4 | 문자가 숫자라면 참입니다. 5 | @param {string} ch 6 | @return {boolean} 7 | */ 8 | function is_digit(ch) { 9 | return ('0' <= ch && ch <= '9'); 10 | } 11 | 12 | /** 13 | 소문자라면 참입니다. 14 | @param {string} ch 15 | @return {boolean} 16 | */ 17 | function is_lower(ch) { 18 | return ('a' <= ch && ch <= 'z'); 19 | } 20 | 21 | /** 22 | 대문자라면 참입니다. 23 | @param {string} ch 24 | @return {boolean} 25 | */ 26 | function is_upper(ch) { 27 | return ('A' <= ch && ch <= 'Z'); 28 | } 29 | 30 | /** 31 | 알파벳이라면 참입니다. 32 | @param {string} ch 33 | @return {boolean} 34 | */ 35 | function is_alpha(ch) { 36 | return is_lower(ch) || is_upper(ch); 37 | } 38 | 39 | /** 40 | 알파벳 또는 숫자라면 참입니다. 41 | @param {string} ch 42 | @return {boolean} 43 | */ 44 | function is_alnum(ch) { 45 | return is_digit(ch) || is_alpha(ch); 46 | } 47 | 48 | /** 49 | 공백이라면 참입니다. 50 | @param {string} ch 51 | @return {boolean} 52 | */ 53 | function is_space(ch) { 54 | return (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'); 55 | } 56 | 57 | /** 58 | 식별자 문자라면 참입니다. 59 | @param {string} ch 60 | @return {boolean} 61 | */ 62 | function is_namch(ch) { 63 | return is_alnum(ch) || (ch == '_'); 64 | } 65 | /** 66 | 첫 식별자 문자라면 참입니다. 67 | @param {string} ch 68 | @return {boolean} 69 | */ 70 | function is_fnamch(ch) { 71 | return is_alpha(ch) || (ch == '_'); 72 | } -------------------------------------------------------------------------------- /main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 73 | 74 | 75 | 76 | 77 | 78 | 79 |
80 | 81 |
82 | 83 |
84 | 85 | 103 | 104 | -------------------------------------------------------------------------------- /ProgramStream.js: -------------------------------------------------------------------------------- 1 | /** 2 | 스트림을 관리하는 Stream 싱글톤 객체를 생성합니다. 3 | */ 4 | function initProgramStream(program) { 5 | var _stream = {}; // 빈 객체 생성 6 | 7 | /** 8 | 스트림의 상위 형식인 BaseStream을 정의합니다. 9 | @param {string} streamName 10 | */ 11 | function BaseStream(streamName) { 12 | this.streamName = streamName; 13 | } 14 | function stream_write(fmt) { 15 | var result; // 로그 스트림에 출력할 문자열을 보관합니다. 16 | var args = stream_write.arguments; 17 | if (args.length == 0) { // 인자의 수가 0이라면 18 | result = ''; // 개행만 합니다. 19 | } 20 | else if (args.length > 1) { // 인자의 수가 1보다 크면 format을 호출합니다. 21 | var params = Handy.toArray(args, 1); // 인자 목록 배열을 획득합니다. 22 | result = Handy.vformat(fmt, params); // 형식 문자열과 인자 목록을 같이 넘깁니다. 23 | } 24 | else { // 인자의 수가 1이면 그냥 출력합니다. 25 | result = fmt; 26 | } 27 | // 스트림에 출력합니다. 28 | var stream = document.getElementById(this.streamName); 29 | stream.value += (result); 30 | } 31 | function stream_writeln(fmt) { 32 | var result; // 로그 스트림에 출력할 문자열을 보관합니다. 33 | var args = stream_writeln.arguments; 34 | if (args.length == 0) { // 인자의 수가 0이라면 35 | result = ''; // 개행만 합니다. 36 | } 37 | else if (args.length > 1) { // 인자의 수가 1보다 크면 format을 호출합니다. 38 | var params = Handy.toArray(args, 1); // 인자 목록 배열을 획득합니다. 39 | result = Handy.vformat(fmt, params); // 형식 문자열과 인자 목록을 같이 넘깁니다. 40 | } 41 | else { // 인자의 수가 1이면 그냥 출력합니다. 42 | result = fmt; 43 | } 44 | // 스트림에 출력합니다. 45 | var stream = document.getElementById(this.streamName); 46 | stream.value += (result + NEWLINE); 47 | } 48 | function stream_clear() { 49 | // 스트림을 비웁니다. 50 | var stream = document.getElementById(this.streamName); 51 | stream.value = ''; 52 | } 53 | BaseStream.prototype.write = stream_write; 54 | BaseStream.prototype.writeln = stream_writeln; 55 | BaseStream.prototype.clear = stream_clear; 56 | 57 | /** 58 | 문자열을 스트림처럼 사용하기 위해 형식을 정의합니다. 59 | */ 60 | function StringStream() { 61 | this.str = ''; 62 | } 63 | function ss_write(fmt) { 64 | var result; // 로그 스트림에 출력할 문자열을 보관합니다. 65 | var args = ss_write.arguments; 66 | if (args.length == 0) { // 인자의 수가 0이라면 67 | result = ''; // 개행만 합니다. 68 | } 69 | else if (args.length > 1) { // 인자의 수가 1보다 크면 format을 호출합니다. 70 | var params = Handy.toArray(args, 1); // 인자 목록 배열을 획득합니다. 71 | result = Handy.vformat(fmt, params); // 형식 문자열과 인자 목록을 같이 넘깁니다. 72 | } 73 | else { // 인자의 수가 1이면 그냥 출력합니다. 74 | result = fmt; 75 | } 76 | // 스트림에 출력합니다. 77 | this.str += (result); 78 | } 79 | function ss_writeln(fmt) { 80 | var result; // 로그 스트림에 출력할 문자열을 보관합니다. 81 | var args = ss_writeln.arguments; 82 | if (args.length == 0) { // 인자의 수가 0이라면 83 | result = ''; // 개행만 합니다. 84 | } 85 | else if (args.length > 1) { // 인자의 수가 1보다 크면 format을 호출합니다. 86 | var params = Handy.toArray(args, 1); // 인자 목록 배열을 획득합니다. 87 | result = Handy.vformat(fmt, params); // 형식 문자열과 인자 목록을 같이 넘깁니다. 88 | } 89 | else { // 인자의 수가 1이면 그냥 출력합니다. 90 | result = fmt; 91 | } 92 | // 스트림에 출력합니다. 93 | this.str += (result + NEWLINE); 94 | } 95 | function ss_clear() { 96 | this.str = ''; 97 | } 98 | StringStream.prototype.write = ss_write; 99 | StringStream.prototype.writeln = ss_writeln; 100 | StringStream.prototype.clear = ss_clear; 101 | 102 | // Stream 객체에 등록합니다. 103 | _stream.out = new BaseStream('outputStream'); 104 | _stream.mem = new BaseStream('memoryStream'); 105 | _stream.reg = new BaseStream('registerStream'); 106 | _stream.exp = new BaseStream('expressionStream'); 107 | _stream.log = new BaseStream('HandyLogStream'); 108 | _stream.StringStream = StringStream; 109 | 110 | // 전역에 등록합니다. 111 | program.Stream = _stream; 112 | } -------------------------------------------------------------------------------- /ProgramCompiler.js: -------------------------------------------------------------------------------- 1 | /** 2 | C 프로그래밍 언어 컴파일러입니다. 3 | */ 4 | function initProgramCompiler(program) { 5 | var compiler = {}; 6 | 7 | // 속성 정의 이전에 정의해야 하는 내용 작성 8 | /** 9 | Compiler 모듈의 메서드를 수행할 때 발생하는 예외를 정의합니다. 10 | @param {string} msg 11 | */ 12 | function CompilerException(msg, data) { 13 | this.description = msg; 14 | this.data = data; 15 | } 16 | CompilerException.prototype = new Exception(); 17 | CompilerException.prototype.toString = function() { 18 | return 'Compiler' + Exception.prototype.toString.call(this); 19 | }; 20 | /** 21 | 버퍼로부터 C 특수 토큰을 획득합니다. 22 | @return {string} 23 | */ 24 | StringBuffer.prototype.get_extra = function() { 25 | if (this.is_empty()) 26 | return null; 27 | 28 | var ch = this.getc(); // 현재 문자를 획득하고 포인터를 이동합니다. 29 | var nextCh = this.peekc(); // 다음 문자를 획득합니다. 30 | var op = ch; 31 | switch (ch) { // 획득한 문자에 따라 분기합니다. 32 | case '+': // 획득한 토큰에 문자를 덧붙일 수 있는지 확인합니다. 33 | if ('+='.indexOf(nextCh) >= 0) // 다음 문자를 덧붙일 수 있다면, 34 | op += this.getc(); // 토큰을 덧붙입니다. 35 | break; 36 | case '-': 37 | if ('-=>'.indexOf(nextCh) >= 0) // [ - -- -= -> ] 38 | op += this.getc(); 39 | break; 40 | case '*': // [ * *= */ ] 41 | if ('=/'.indexOf(nextCh) >= 0) op += nextCh; 42 | break; 43 | case '/': // [ / /= // /* ] 44 | if ('=/*'.indexOf(nextCh) >= 0) 45 | op += this.getc(); 46 | break; 47 | case '.': // 가변 인자를 표현하는 "..." 토큰인지 검사합니다. 48 | if (String.prototype.substr(this.idx, 2) == '..') { 49 | this.idx += 2; 50 | op = '...'; 51 | } 52 | break; 53 | case '<': // [ < << <= <<= ] 54 | if (nextCh == '<') { // << 또는 <<= 55 | op += this.getc(); 56 | nextCh = this.peekc(); 57 | if (nextCh == '=') 58 | op += this.getc(); 59 | } 60 | else if ('<='.indexOf(nextCh) >= 0) 61 | op += this.getc(); 62 | break; 63 | case '>': // [ > >> >= >>= ] 64 | if (nextCh == '>') { // >> 또는 >>= 65 | op += this.getc(); 66 | nextCh = this.peekc(); 67 | if (nextCh == '=') 68 | op += this.getc(); 69 | } 70 | else if ('>='.indexOf(nextCh) >= 0) 71 | op += this.getc(); 72 | break; 73 | case '&': // [ & &= && ] 74 | if ('=&'.indexOf(nextCh) >= 0) op += nextCh; 75 | break; 76 | case '|': // [ | |= || ] 77 | if ('=|'.indexOf(nextCh) >= 0) op += nextCh; 78 | break; 79 | case '%': // [ % %= ] 80 | case '=': // [ = == ] 81 | case '!': // [ ! != ] 82 | case '^': // [ ^ ^= ] 83 | if (nextCh == '=') op += this.getc(); 84 | break; 85 | case '~': 86 | break; 87 | } 88 | 89 | return op; 90 | } 91 | /** 92 | 버퍼로부터 C 토큰을 획득합니다. 93 | @return {string} 94 | */ 95 | StringBuffer.prototype.get_ctoken = function() { 96 | this.trim(); // 공백 제거 97 | var ch = this.peekc(); 98 | var result = null; // 문자열 스트림 생성 99 | 100 | if (is_digit(ch)) // 정수를 발견했다면 정수 획득 101 | result = this.get_number(); // cout 출력 스트림처럼 사용하면 된다 102 | else if (is_fnamch(ch)) // 식별자 문자를 발견했다면 식별자 획득 103 | result = this.get_identifier(); 104 | else if (ch == '"' || ch == "'") { // 문자열 기호의 시작이라면 105 | result = ''; // 반환할 수 있도록 문자열을 초기화합니다. 106 | var quot = this.getc(); // 따옴표의 쌍을 맞출 수 있도록 따옴표를 보관합니다. 107 | 108 | while (this.is_empty() == false) { // 버퍼에 문자가 있는 동안 109 | var sch = this.peekc(); // 문자를 획득합니다. 110 | 111 | if (sch == '\\') { // '\' 특수기호라면 이스케이프 시퀀스를 처리합니다. 112 | this.getc(); // 이미 획득한 문자는 넘어갑니다. 113 | var next = this.getc(); // \ 다음의 문자를 획득합니다. 114 | var ech = null; // 획득할 이스케이프 시퀀스입니다. 115 | 116 | switch (next) { // 문자에 맞게 조건 분기합니다. 117 | case 'n': ech = '\n'; break; 118 | case 'r': ech = '\r'; break; 119 | case 't': ech = '\t'; break; 120 | case '0': ech = '\0'; break; 121 | case '\\': ech = '\\'; break; 122 | default: 123 | throw new StringBufferException 124 | ("invalid escape sequence"); 125 | } 126 | result += ech; // 획득한 이스케이프 시퀀스를 붙입니다. 127 | } 128 | else if (sch == quot) { // 같은 따옴표가 나왔다면 문자열 획득을 마칩니다. 129 | this.getc(); 130 | break; 131 | } 132 | else { // 나머지 문자는 result에 붙입니다. 133 | result += this.getc(); 134 | } 135 | } 136 | result = quot + result + quot; 137 | } 138 | else { // 그 외의 경우 특수 토큰을 획득합니다. 139 | result = this.get_extra(); 140 | } 141 | 142 | // 획득한 문자열이 없으면 null을 반환합니다. 143 | return (result != '' ? result : null); // 획득한 문자열을 반환한다 144 | } 145 | 146 | initProgramCompilerTranslator(compiler, CompilerException); 147 | initProgramCompilerDeclaration(compiler, CompilerException); 148 | initProgramCompilerExpression(compiler, CompilerException); 149 | initProgramCompilerStatement(compiler, CompilerException); 150 | 151 | // 메서드 작성 152 | /** 153 | C언어로 작성된 파일을 목적 파일로 변환합니다. 154 | @param {string} filename 155 | */ 156 | function compile(filename) { 157 | var Translator = Program.Compiler.Translator; 158 | 159 | // 파일 텍스트를 가져옵니다. 실패하면 예외 처리합니다. 160 | var filedata = HandyFileSystem.load(filename); 161 | if (filedata == null) 162 | throw new CompilerException('cannot open file ', filename); 163 | 164 | // 컴파일을 수행합니다. 165 | Translator.translate(filedata); 166 | 167 | // 컴파일한 결과를 파일에 출력합니다. 168 | var dataseg = Translator.segment.data; 169 | var codeseg = Translator.segment.code; 170 | var outputName = Handy.format 171 | ('%s.hdo', filename.substr(0, filename.length-2)); 172 | var output = new Program.Stream.StringStream(); 173 | output.writeln('.data'); // 데이터 세그먼트의 시작을 알립니다. 174 | output.writeln(dataseg.str); // 데이터 세그먼트의 정보를 기록합니다. 175 | output.writeln('.code'); // 코드 세그먼트의 시작을 알립니다. 176 | output.writeln(codeseg.str); // 코드 세그먼트의 정보를 기록합니다. 177 | output.write('end _main'); // _main 프로시저에서 시작함을 알립니다. 178 | HandyFileSystem.save(outputName, output.str); // 파일에 출력합니다. 179 | 180 | // 컴파일이 성공했음을 알립니다. 181 | log('compile complete'); 182 | return 0; // 반환하는 값이 0이 아니면 프로그램을 중단합니다. 183 | } 184 | 185 | compiler.compile = compile; 186 | program.Compiler = compiler; 187 | } -------------------------------------------------------------------------------- /ProgramCompilerTranslator.js: -------------------------------------------------------------------------------- 1 | /** 2 | 획득한 텍스트를 분석하고 어셈블리 언어로 번역하는 Translator 모듈을 초기화합니다. 3 | */ 4 | function initProgramCompilerTranslator(compiler, CompilerException) { 5 | var translator = {}; 6 | 7 | // 필드 정의 8 | translator.segment = null; 9 | 10 | // 메서드 정의 11 | /** 12 | 넘겨받은 코드 데이터를 분석하고 세그먼트 스트림에 번역 결과를 출력합니다. 13 | @param {string} filedata 14 | */ 15 | function translate(filedata) { 16 | // 가져온 텍스트를 바탕으로 새 StringBuffer 인스턴스를 생성합니다. 17 | var buffer = new StringBuffer(String(filedata)); 18 | 19 | // 세그먼트에 대한 문자열 스트림을 생성합니다. 20 | var dataseg = new Program.Stream.StringStream(); 21 | var codeseg = new Program.Stream.StringStream(); 22 | 23 | // 필드를 초기화합니다. 24 | Program.Compiler.Translator.segment = { 25 | data: dataseg, code: codeseg 26 | }; 27 | 28 | // 번역 단위를 획득하고 어셈블리 언어로 번역합니다. 29 | translationUnit(buffer); 30 | } 31 | 32 | /** 33 | 번역 단위를 획득하고 어셈블리 언어로의 변환을 수행합니다. 34 | @param {StringBuffer} buffer 35 | */ 36 | function translationUnit(buffer) { 37 | buffer.trim(); // 다음 토큰까지의 공백을 모두 제거합니다. 38 | while (buffer.is_empty() == false) { // 번역 단위가 남아있다면 39 | externalDeclaration(buffer); // 외부 정의를 획득합니다. 40 | buffer.trim(); // 다음 토큰까지의 공백을 모두 제거합니다. 41 | } 42 | } 43 | 44 | /** 45 | 현재 획득하려는 외부 정의가 함수 정의인지 판정합니다. 46 | 함수 정의라면 true, 아니라면 false를 반환합니다. 47 | @param {StringBuffer} buffer 48 | @return {boolean} 49 | */ 50 | function is_funcdef(buffer) { 51 | var Declaration = Program.Compiler.Declaration; 52 | 53 | // 토큰 획득 이전에 버퍼 포인터를 보관합니다. 54 | var originIndex = buffer.idx; 55 | 56 | // 선언 지정자 획득을 시도합니다. 57 | var declspec = Declaration.getDeclarationSpecifier(buffer); 58 | // 선언 지정자 획득에 실패하면 함수 정의로 간주합니다. 59 | if (declspec == null) { 60 | buffer.idx = originIndex; // 버퍼 포인터를 복구합니다. 61 | return true; 62 | } 63 | 64 | // 선언자 획득을 시도합니다. 65 | var declarator = Declaration.getDeclarator(buffer); 66 | // 선언자 획득에 실패하면 선언으로 간주합니다. 67 | if (declarator == null) { 68 | buffer.idx = originIndex; // 버퍼 포인터를 복구합니다. 69 | return false; 70 | } 71 | 72 | // 선언 획득을 시도합니다. 73 | var declInfo = Declaration.getDeclarationInfo(buffer); 74 | // 선언 획득에 성공하면 함수 정의로 간주합니다. 75 | if (declInfo != null) { 76 | buffer.idx = originIndex; // 버퍼 포인터를 복구합니다. 77 | return true; 78 | } 79 | 80 | buffer.trim(); // 버퍼 포인터를 다음 토큰의 시작 지점으로 옮깁니다. 81 | // 획득한 토큰이 여는 중괄호라면 함수 정의입니다. 82 | if (buffer.peekc() == '{') { 83 | buffer.idx = originIndex; // 버퍼 포인터를 복구합니다. 84 | return true; 85 | } 86 | // 그 외의 경우 함수 정의가 아닙니다. 87 | buffer.idx = originIndex; // 버퍼 포인터를 복구합니다. 88 | return false; 89 | } 90 | /** 91 | 외부 정의를 획득하고 어셈블리 언어로의 변환을 수행합니다. 92 | @param {StringBuffer} buffer 93 | */ 94 | function externalDeclaration(buffer) { 95 | if (is_funcdef(buffer)) // 함수 정의라면 96 | functionDefinition(buffer); // 함수 정의로 분석합니다. 97 | else // 함수 정의가 아니라면 98 | declaration(buffer); // 선언으로 간주하여 분석합니다. 99 | } 100 | /** 101 | 선언을 획득하고 어셈블리 언어로의 변환을 수행합니다. 102 | @param {StringBuffer} buffer 103 | */ 104 | function declaration(buffer) { 105 | var Declaration = Program.Compiler.Declaration; 106 | 107 | // 선언 지정자를 획득하고 정보를 출력합니다. 108 | var declspcf = Declaration.getDeclarationSpecifier(buffer); 109 | 110 | // 초기 선언자 리스트를 획득하고 정보를 출력합니다. 111 | var init_decl_list = Declaration.getInitDeclaratorList(buffer); 112 | for (var i=0, len=init_decl_list.length; i 0) { 65 | --this.idx; 66 | return true; 67 | } 68 | return false; 69 | } 70 | 71 | /** 72 | 버퍼의 끝에 문자열을 추가합니다. 73 | @param {string} s 74 | */ 75 | StringBuffer.prototype.add = function(s) { 76 | this.str += s; 77 | } 78 | 79 | /** 80 | 버퍼가 비어있다면 true, 값을 더 읽을 수 있다면 false를 반환합니다. 81 | @return {boolean} 82 | */ 83 | StringBuffer.prototype.is_empty = function() { 84 | return (this.idx >= this.str.length); 85 | } 86 | 87 | /** 88 | 버퍼로부터 정수를 획득합니다. 89 | @return {string} 90 | */ 91 | StringBuffer.prototype.get_number = function() { 92 | this.trim(); // 공백 제거 93 | if (this.is_empty()) 94 | return null; // 버퍼에 남은 문자가 없다면 null을 반환합니다. 95 | else if (is_digit(this.peekc()) == false) 96 | return null; // 첫 문자가 숫자가 아니면 null을 반환합니다. 97 | 98 | // 정수를 획득합니다. 99 | var value = ''; // 획득한 정수에 대한 문자열 객체입니다. 100 | 101 | // 0이 먼저 발견되었다면 16진수입니다. 8진수는 고려하지 않습니다. 102 | if (this.peekc() == '0') { 103 | this.getc(); // 수를 획득합니다. 104 | 105 | if (this.is_empty()) // 더 획득할 수가 없다면 0을 반환합니다. 106 | return '0'; 107 | 108 | // 다음에 나타난 문자가 'x'라면 16진수를 획득합니다. 109 | if (this.peekc() == 'x') { 110 | // 해석한 x는 지나갑니다. 111 | this.getc(); 112 | // 16진수를 해석합니다. 113 | while (this.is_empty() == false) { // 버퍼에 데이터가 남아있는 동안 114 | // 16진수의 자릿수 문자가 아니면 115 | if ('0123456789abcdefABCDEF'.indexOf(this.peekc()) < 0) 116 | break; // 탈출합니다. 117 | value += this.getc(); // 문자를 반환할 문자열에 추가합니다. 118 | } 119 | // 획득한 16진수 문자열을 반환합니다. 120 | return '0x' + value; 121 | } 122 | // 아니라면 원래 수를 획득합니다. 123 | else { 124 | // 획득했던 정수 0을 되돌립니다. 125 | this.ungetc(); 126 | } 127 | } 128 | 129 | // 10진수를 해석합니다. 130 | while (this.is_empty() == false) { // 버퍼에 값이 남아있는 동안 131 | if (is_digit(this.peekc()) == false) // 자릿수 문자가 아니면 132 | break; // 탈출합니다. 133 | value += this.getc(); // 문자를 반환할 문자열에 추가합니다. 134 | } 135 | return value; 136 | } 137 | 138 | /** 139 | 버퍼로부터 식별자를 획득합니다. 140 | @return {string} 141 | */ 142 | StringBuffer.prototype.get_identifier = function() { 143 | this.trim(); // 공백 제거 144 | if (this.is_empty()) // 버퍼에 남은 문자가 없다면 예외 145 | return null; 146 | else if (is_fnamch(this.peekc()) == false) 147 | return null; 148 | var identifier = ''; 149 | while (this.is_empty() == false) { 150 | if (is_namch(this.peekc()) == false) // 식별자 문자가 아니라면 탈출 151 | break; 152 | identifier += this.getc(); 153 | } 154 | return identifier; 155 | } 156 | 157 | /** 158 | 공백이 아닌 문자가 나올 때까지 포인터를 옮깁니다. 159 | */ 160 | StringBuffer.prototype.trim = function() { 161 | while (this.is_empty() == false) { // 버퍼에 문자가 남아있는 동안 162 | if (is_space(this.peekc()) == false) // 공백이 아닌 문자를 발견하면 163 | break; // 반복문을 탈출한다 164 | this.getc(); // 공백이면 다음 문자로 포인터를 넘긴다 165 | } 166 | } 167 | 168 | /** 169 | 현재 위치 다음에 존재하는 토큰을 획득합니다. 170 | 토큰 획득에 실패하면 null을 반환합니다. 171 | @return {string} 172 | */ 173 | StringBuffer.prototype.get_token = function() { 174 | this.trim(); // 공백 제거 175 | var ch = this.peekc(); 176 | var result = null; // 문자열 스트림 생성 177 | 178 | if (is_digit(ch)) // 정수를 발견했다면 정수 획득 179 | result = this.get_number(); // cout 출력 스트림처럼 사용하면 된다 180 | else if (is_fnamch(ch)) // 식별자 문자를 발견했다면 식별자 획득 181 | result = this.get_identifier(); 182 | else { 183 | if (ch == '"' || ch == "'") { // 문자열 기호의 시작이라면 184 | result = ''; // 반환할 수 있도록 문자열을 초기화합니다. 185 | var quot = this.getc(); // 따옴표의 쌍을 맞출 수 있도록 따옴표를 보관합니다. 186 | 187 | while (this.is_empty() == false) { // 버퍼에 문자가 있는 동안 188 | var sch = this.peekc(); // 문자를 획득합니다. 189 | 190 | if (sch == '\\') { // '\' 특수기호라면 이스케이프 시퀀스를 처리합니다. 191 | this.getc(); // 이미 획득한 문자는 넘어갑니다. 192 | var next = this.getc(); // \ 다음의 문자를 획득합니다. 193 | var ech = null; // 획득할 이스케이프 시퀀스입니다. 194 | 195 | switch (next) { // 문자에 맞게 조건 분기합니다. 196 | case 'n': ech = '\n'; break; 197 | case 'r': ech = '\r'; break; 198 | case 't': ech = '\t'; break; 199 | case '0': ech = '\0'; break; 200 | case '\\': ech = '\\'; break; 201 | default: 202 | throw new StringBufferException 203 | ("invalid escape sequence"); 204 | } 205 | result += ech; // 획득한 이스케이프 시퀀스를 붙입니다. 206 | } 207 | else if (sch == quot) { // 같은 따옴표가 나왔다면 문자열 획득을 마칩니다. 208 | this.getc(); 209 | break; 210 | } 211 | else { // 나머지 문자는 result에 붙입니다. 212 | result += this.getc(); 213 | } 214 | } 215 | result = quot + result + quot; 216 | } 217 | else if (ch == '[') { // 메모리의 시작이라면 218 | result = ''; // 반환할 수 있도록 문자열을 초기화합니다. 219 | this.getc(); // 이미 획득한 토큰이므로 넘어갑니다. 220 | while (this.is_empty() == false) { // 버퍼가 비어있는 동안 221 | if (this.peekc() == ']') // 닫는 대괄호가 나타났다면 탈출합니다. 222 | break; 223 | result += this.getc(); // 문자를 추가합니다. 224 | } 225 | this.getc(); // 추가된 문장: 마지막 닫는 대괄호는 사용했습니다. 226 | result = '[' + result + ']'; 227 | } 228 | else { // 아니라면 일단 획득합니다. 229 | result = this.getc(); // this.get_operator(); 230 | } 231 | } 232 | 233 | // 획득한 문자열이 없으면 null을 반환합니다. 234 | return (result != '' ? result : null); // 획득한 문자열을 반환한다 235 | } -------------------------------------------------------------------------------- /MachineOperation.js: -------------------------------------------------------------------------------- 1 | /** 2 | 프로세서의 행동이 정의된 보조 모듈입니다. 3 | */ 4 | function initMachineOperation(machine) { 5 | var operate = {}; 6 | 7 | /** 8 | 근원지로부터 값을 가져옵니다. 9 | @param {string} src 10 | @return {number} 11 | */ 12 | function get_value(src) { 13 | var Register = Machine.Processor.Register; 14 | var Memory = Machine.Memory; 15 | 16 | var value = null; 17 | if (Register.is_register(src)) { // 레지스터라면 18 | value = Register.get(src); // 레지스터의 값을 획득합니다. 19 | } 20 | else if (Memory.is_memory(src)) { // 메모리라면 21 | value = Memory.get_memory_value(src); // 메모리의 값을 획득합니다. 22 | } 23 | else { // 레지스터가 아니라면 정수로 간주하고, 정수로 변환한 값을 획득합니다. 24 | value = parseInt(src); 25 | } 26 | return value; // 획득한 값을 반환합니다. 27 | } 28 | /** 29 | 목적지에 맞게 값을 설정합니다. 30 | @param {string} dst 31 | @param {number} src 32 | */ 33 | function set_value(dst, src) { 34 | var Register = Machine.Processor.Register; 35 | var Memory = Machine.Memory; 36 | 37 | if (Register.is_register(dst)) { // 목적지가 레지스터라면 38 | Register.set(dst, src); // 레지스터에 값을 기록합니다. 39 | } 40 | else if (Memory.is_memory(dst)) { // 목적지가 메모리라면 41 | Memory.set_memory_value(dst, src); // 메모리에 값을 기록합니다. 42 | } 43 | else { // 그 외의 경우 예외 처리합니다. 44 | throw new ProcessorException('invalid left value'); 45 | } 46 | } 47 | 48 | function mov(left, right) { 49 | // right의 값을 획득하고 left에 기록합니다. 50 | set_value(left, get_value(right)); 51 | } 52 | function jmp(left) { 53 | var Register = Machine.Processor.Register; 54 | var Memory = Machine.Memory; 55 | var dest_addr = 0; // 점프할 목적지입니다. 56 | 57 | // 레지스터라면 해당 레지스터의 값으로 점프합니다. 58 | if (Register.is_register(left)) 59 | dest_addr = Register.get(left); // Register[left]; 60 | // 정수라면 해당 정수 값으로 점프합니다. 61 | else 62 | dest_addr = parseInt(left, 16); 63 | 64 | // 획득한 목적지로 바이트 포인터를 옮깁니다. 65 | Memory.bytePtr = dest_addr; 66 | } 67 | function push(left) { 68 | var Register = Machine.Processor.Register; 69 | var Memory = Machine.Memory; 70 | 71 | // esp의 값이 4만큼 줄었다. 72 | Register.set('esp', Register.get('esp') - 4); // Register.esp -= 4; 73 | 74 | var value; 75 | if (Register.is_register(left)) // 레지스터라면 레지스터의 값을 획득합니다. 76 | value = Register.get(left); // Register[left]; 77 | else // 레지스터가 아니라면 정수로 간주하고 값을 획득합니다. 78 | value = parseInt(left); 79 | 80 | // esp가 가리키는 위치의 메모리에서 4바이트만큼을 ebp로 채웠다. 81 | Memory.set_dword(value, Register.get('esp')); 82 | } 83 | function pop(left) { 84 | var Register = Machine.Processor.Register; 85 | var Memory = Machine.Memory; 86 | // esp가 가리키는 메모리의 dword 값을 획득합니다. 87 | var popVal = Memory.get_dword(Register.get('esp')); 88 | 89 | // esp를 레지스터 크기만큼 증가시킵니다. 90 | Register.set('esp', Register.get('esp') + 4); // Register.esp += 4; 91 | 92 | // 목적지 레지스터에 획득한 값을 대입합니다. 93 | Register.set(left, popVal); // Register[left] = popVal; 94 | } 95 | 96 | function jnz(left) { 97 | var Register = Machine.Processor.Register; 98 | var Memory = Machine.Memory; 99 | // 0이 아니면, 즉 영 플래그가 1이 아니면 점프합니다. 100 | if (Register.getZF() != 1) 101 | jmp(left); 102 | } 103 | function cmp(left, right) { 104 | var Register = Machine.Processor.Register; 105 | var Reg = Register; 106 | var Memory = Machine.Memory; 107 | 108 | var lval = null, rval = null; 109 | var isLeftMem = false, isRightMem = false; 110 | 111 | // left의 값을 획득합니다. 112 | if (Register.is_register(left)) 113 | lval = Reg.get(left); 114 | else if (Memory.is_memory(left)) { 115 | lval = Memory.get_memory_value(left); 116 | isLeftMem = true; 117 | } 118 | else 119 | lval = parseInt(left); 120 | 121 | // right의 값을 획득합니다. 122 | if (Register.is_register(right)) 123 | rval = Reg.get(right); 124 | else if (Memory.is_memory(right)) { 125 | rval = Memory.get_memory_value(right); 126 | isRightMem = true; 127 | } 128 | else 129 | rval = parseInt(right); 130 | 131 | // 둘 다 메모리라면 문법 위반입니다. 132 | if (isLeftMem && isRightMem) 133 | throw new ProcessorException 134 | ("syntax error; cannot refer 2 memory address simultaneously"); 135 | 136 | // 두 값이 같은지에 대한 결과를 반영합니다. 같다면 1, 아니면 0입니다. 137 | var value = (lval == rval) ? 1 : 0; 138 | Register.setZF(value); 139 | } 140 | 141 | function call(left) { 142 | var Register = Machine.Processor.Register; 143 | var Memory = Machine.Memory; 144 | push(Register.get('eip')); // 다음 명령의 위치를 푸시합니다. 145 | jmp(left); // 인자 값의 위치로 점프합니다. 146 | } 147 | function ret() { 148 | var Register = Machine.Processor.Register; 149 | var Memory = Machine.Memory; 150 | 151 | pop('eip'); // 복귀할 명령 주소를 팝합니다. 152 | Memory.bytePtr = Register.get('eip'); // 해당 주소로 명령 포인터를 맞춥니다. 153 | } 154 | function add(left, right) { 155 | // left의 값을 두 값의 합으로 갱신합니다. 156 | set_value(left, get_value(left) + get_value(right)); 157 | } 158 | function sub(left, right) { 159 | // left의 값을 두 값의 차로 갱신합니다. 160 | set_value(left, get_value(left) - get_value(right)); 161 | } 162 | 163 | // 메모리의 주소를 가져옵니다. 164 | function lea(left, right) { 165 | set_value(left, Machine.Memory.get_memory_address(right)); 166 | } 167 | 168 | operate.mov = mov; 169 | operate.jmp = jmp; 170 | operate.push = push; 171 | operate.pop = pop; 172 | operate.jnz = jnz; 173 | operate.cmp = cmp; 174 | 175 | operate.call = call; 176 | operate.ret = ret; 177 | 178 | operate.add = add; 179 | operate.sub = sub; 180 | 181 | operate.lea = lea; 182 | 183 | // handy 특수 니모닉을 처리합니다. 184 | function handy(left, right) { 185 | var Register = Machine.Processor.Register; 186 | var Memory = Machine.Memory; 187 | var Stream = Program.Stream; 188 | 189 | if (left == 'print_number') { 190 | var value; 191 | 192 | // 레지스터라면 레지스터의 값을 얻는다. 193 | if (Register.is_register(right)) { 194 | value = Register.get(right); // Register[right]; 195 | } 196 | // 레지스터가 아니라면 인자를 정수로 변환한다. 197 | else { 198 | value = parseInt(right); 199 | } 200 | 201 | // 획득한 값을 출력한다. 202 | Stream.out.write(value); 203 | } 204 | else if (left == 'print_letter') { 205 | var value; 206 | 207 | // 레지스터라면 레지스터의 값을 얻는다. 208 | if (Register.is_register(right)) { 209 | value = Register.get(right); // Register[right]; 210 | } 211 | // 레지스터가 아니라면 인자를 정수로 변환한다. 212 | else { 213 | value = parseInt(right); 214 | } 215 | 216 | // 획득한 값으로부터 문자를 획득하고 이를 출력한다. 217 | Stream.out.write(String.fromCharCode(value)); 218 | } 219 | else if (left == 'print_string') { 220 | Stream.out.write(right); 221 | } 222 | else if (left == 'puts') { 223 | var value = null; 224 | 225 | // 레지스터라면 레지스터 값 획득 226 | if (Register.is_register(right)) { 227 | value = Register.get(right); // Register[right]; 228 | } 229 | // 메모리라면 메모리 값 획득 230 | else if (Memory.is_memory(right)) { 231 | value = Memory.get_memory_value(right); 232 | } 233 | // 모두 아니라면 즉시 값 획득 234 | else { 235 | value = parseInt(right); 236 | } 237 | 238 | // 현재 바이트 포인터는 보존합니다. 239 | var prevBytePtr = Memory.bytePtr; 240 | 241 | // 문자열을 출력할 위치로 바이트 포인터를 맞춥니다. 242 | Memory.bytePtr = value; 243 | 244 | // 널이 아닐 때까지 문자를 출력합니다. 245 | var s = ''; 246 | while (true) { 247 | var byte = Memory.read_byte(); // 바이트를 획득합니다. 248 | if (byte == 0) // 널 문자가 나타났다면 종료합니다. 249 | break; 250 | s += String.fromCharCode(byte); // 바이트로부터 문자를 획득합니다. 251 | } 252 | 253 | // 획득한 문자열을 출력합니다. 254 | Stream.out.write(s); 255 | 256 | // 이전 바이트 포인터를 복구합니다. 257 | Memory.bytePtr = prevBytePtr; 258 | } 259 | else if (left == 'putn') { 260 | var value = null; 261 | 262 | // 레지스터라면 레지스터 값 획득 263 | if (Register.is_register(right)) { 264 | value = Register.get(right); // Register[right]; 265 | } 266 | // 메모리라면 메모리 값 획득 267 | else if (Memory.is_memory(right)) { 268 | value = Memory.get_memory_value(right); 269 | } 270 | // 모두 아니라면 즉시 값 획득 271 | else { 272 | value = parseInt(right); 273 | } 274 | 275 | Stream.out.write(value); 276 | } 277 | else { 278 | 279 | } 280 | } 281 | operate.handy = handy; 282 | 283 | machine.Operate = operate; 284 | } -------------------------------------------------------------------------------- /handy.js: -------------------------------------------------------------------------------- 1 | // common constant definitions 2 | function initHandyConstant() { 3 | var gui = require('nw.gui'); // get gui module 4 | var platform = gui.App.argv[1]; // get platform string 5 | 6 | switch (platform) { 7 | case 'win32': 8 | case 'win64': 9 | window.NEWLINE = '\r\n'; // new line character 10 | window.DIR_SEPERATOR = '\\'; 11 | break; 12 | 13 | case 'linux': 14 | window.NEWLINE = '\n'; 15 | window.DIR_SEPERATOR = '/'; 16 | break; 17 | 18 | case 'darwin': 19 | window.NEWLINE = '\n'; 20 | window.DIR_SEPERATOR = '/'; 21 | break; 22 | } 23 | } 24 | 25 | // method definitions 26 | /** 27 | @param {string} msg 28 | @param {object} data 29 | */ 30 | function Exception(msg, data) { 31 | this.description = msg; 32 | this.data = (data != undefined) ? data : null; 33 | } 34 | Exception.prototype.toString = function() { 35 | var type = 'Exception: '; 36 | var message = this.description; 37 | var data = (this.data != undefined) ? this.data.toString() : ''; 38 | return type + message + ' [' + data + ']'; 39 | } 40 | 41 | /** 42 | 로그 스트림에 문자열을 출력합니다. 43 | @param {string} fmt 44 | */ 45 | function log(fmt) { 46 | var result; // 로그 스트림에 출력할 문자열을 보관합니다. 47 | if (log.arguments.length == 0) { // 인자의 수가 0이라면 48 | result = ''; // 개행만 합니다. 49 | } 50 | else if (log.arguments.length > 1) { // 인자의 수가 1보다 크면 format을 호출합니다. 51 | var params = Handy.toArray(log.arguments, 1); // 인자 목록 배열을 획득합니다. 52 | result = Handy.vformat(fmt, params); // 형식 문자열과 인자 목록을 같이 넘깁니다. 53 | } 54 | else { // 인자의 수가 1이면 그냥 출력합니다. 55 | result = fmt; 56 | } 57 | // 로그 스트림에 출력합니다. 58 | var logStream = document.getElementById('HandyLogStream'); 59 | logStream.value += (result + NEWLINE); 60 | } 61 | 62 | /** 63 | value가 undefined라면 기본 값을, 아니면 그대로 반환합니다. 64 | */ 65 | function getValid(value, defaultValue) { 66 | return (value != undefined) ? value : defaultValue; 67 | } 68 | 69 | /** 70 | HandyFileSystem 싱글톤 객체를 생성하고 초기화합니다. 71 | */ 72 | function initHandyFileSystem() { 73 | 74 | // HandyFileSystem 싱글톤 객체를 정의합니다. 75 | // 빈 객체를 먼저 생성함에 주의합니다. 76 | var hfs = {}; 77 | 78 | // 파일 시스템 객체를 획득합니다. 79 | var fso = require('fs'); 80 | 81 | // 현재 작업중인 파일의 디렉터리를 획득합니다. 82 | var gui = require('nw.gui'); 83 | var dir = gui.App.argv[0]; 84 | 85 | /** 86 | // 파일에 기록된 텍스트를 모두 불러와 문자열로 반환합니다. 87 | // 성공하면 획득한 문자열, 실패하면 null을 반환합니다. 88 | @param {string} filename 89 | @return {string} 90 | */ 91 | function load(filename) { 92 | try { 93 | var filepath = this.dir + DIR_SEPERATOR + filename; 94 | return this.fso.readFileSync(filepath); 95 | } catch (ex) { 96 | return null; 97 | } 98 | } 99 | /** 100 | // 파일에 텍스트를 기록합니다. 101 | // 기록에 성공하면 true, 실패하면 false를 반환합니다. 102 | @param {string} filename 103 | @return {Boolean} 104 | */ 105 | function save(filename, data) { 106 | try { 107 | var filepath = this.dir + DIR_SEPERATOR + filename; 108 | this.fso.writeFileSync(filepath, data); 109 | return true; 110 | } catch (ex) { 111 | return false; 112 | } 113 | } 114 | /** 115 | // 파일이 디스크에 존재하는지 확인합니다. 116 | @param {string} filepath 117 | @return {Boolean} 118 | */ 119 | function exists(filepath) { 120 | return this.fso.exists(filepath); 121 | } 122 | 123 | // 정의한 속성을 HandyFileSystem 싱글톤 객체의 멤버로 정의합니다. 124 | hfs.fso = fso; 125 | hfs.dir = dir; 126 | hfs.load = load; 127 | hfs.save = save; 128 | hfs.exists = exists; 129 | 130 | // 전역 객체를 의미하는 window 객체에 HandyFileSystem 속성을 추가하고 131 | // 생성한 HandyFileSystem 싱글톤 객체를 대입합니다. 132 | window['HandyFileSystem'] = hfs; 133 | } 134 | 135 | /** 136 | Handy 라이브러리 기본 객체인 Handy 싱글톤 객체를 초기화합니다. 137 | */ 138 | function initHandy() { 139 | var handy = {}; // 빈 객체 생성 140 | 141 | initHandyConstant(); // initialize constant by platform 142 | 143 | /** 144 | 형식화된 문자열을 반환합니다. 145 | @param {string} fmt 146 | @param {Array} params 147 | @return {string} 148 | */ 149 | function vformat(fmt, params) { 150 | var value; 151 | var result = ''; // 반환할 문자열입니다. 152 | 153 | // fmt의 모든 문자에 대해 반복문을 구성합니다. 154 | // pi는 인자의 인덱스를 의미합니다. 155 | var i, pi, len; 156 | for (i=0, pi=0, len=fmt.length; i= 0) { 183 | // 길이를 획득합니다. get_number 구현을 참조하십시오. 184 | width = width * 10 + (nextChar - '0'); 185 | // 다음 문자를 다시 얻습니다. 186 | nextChar = fmt.charAt(++i); 187 | } 188 | 189 | // 인자를 획득하고 타당하게 만듭니다. 190 | var param = params[pi++]; 191 | if (param === undefined) 192 | param = 'undefined'; 193 | else if (param === null) 194 | param = 'null'; 195 | 196 | // 가져온 문자를 기준으로 조건 분기합니다. 197 | switch (nextChar) { 198 | case 'b': // 2진수를 출력합니다. 199 | value = param.toString(2); 200 | break; 201 | case 'x': // 16진수를 출력합니다. 202 | value = param.toString(16); 203 | break; 204 | case 'd': // 정수를 출력합니다. 205 | value = param.toString(); 206 | break; 207 | case 's': // 문자열을 출력합니다. 208 | value = param; 209 | break; 210 | case 'c': // 문자 코드에 해당하는 문자를 출력합니다. 211 | // 문자열이라면 첫 번째 글자만 획득합니다. 212 | if (typeof(param) == 'string') { 213 | value = param.charAt(0); 214 | } 215 | else { // 그 외의 경우 정수로 간주합니다. 216 | value = String.fromCharCode(param); 217 | } 218 | break; 219 | case '%': // 퍼센트를 출력합니다. 220 | --pi; // 인자를 사용하지 않았으므로 되돌립니다. 221 | value = '%'; 222 | break; 223 | } 224 | 225 | // 정상적으로 값을 획득하지 못한 경우 undefined를 출력합니다. 226 | if (value == undefined) { 227 | value = 'undefined'; 228 | } 229 | 230 | // 옵션을 문자열에 반영합니다. 231 | if (showLeft) { // 왼쪽 정렬 옵션입니다. 232 | var space = ''; // 여백 문자열입니다. 233 | // 주어진 크기만큼 여백을 확보합니다. 234 | for (var si=0, slen=width - value.length; si 0) { // 두 옵션 없이 크기만 주어진 경우입니다. 248 | var space = ''; // 여백 문자열입니다. 249 | // 주어진 크기만큼 여백을 확보합니다. 250 | for (var si=0, slen=width - value.length; si>= 8; // 다음에 기록할 바이트로 value 값을 옮깁니다. 77 | this.write_byte(value & 0xFF); // 두 번째 바이트를 기록합니다. 78 | } 79 | /** 80 | 바이트 배열에서 index 주소에 있는 워드를 획득합니다. 81 | @param {number} index 82 | @return {number} 83 | */ 84 | function get_word(index) { 85 | var byte1 = this.get_byte(index); // 첫 번째 바이트를 획득합니다. 86 | var byte2 = this.get_byte(index+1); // 두 번째 바이트를 획득합니다. 87 | return ((byte2 << 8) | byte1); // 워드를 반환합니다. 88 | } 89 | /** 90 | value를 워드로 변환한 값을 index 주소에 기록합니다. 91 | @param {number} value 92 | @param {number} index 93 | */ 94 | function set_word(value, index) { 95 | this.set_byte(value & 0xFF, index); // 첫 번째 바이트를 기록합니다. 96 | value >>= 8; // 다음에 기록할 바이트로 value 값을 옮깁니다. 97 | this.set_byte(value & 0xFF, index+1); // 두 번째 바이트를 기록합니다. 98 | } 99 | 100 | /** 101 | 더블 워드를 획득합니다. 102 | @return {number} 103 | */ 104 | function read_dword() { 105 | var byte1 = this.read_byte(); 106 | var byte2 = this.read_byte(); 107 | var byte3 = this.read_byte(); 108 | var byte4 = this.read_byte(); 109 | var dword = ( 110 | (byte4 << 24) 111 | | (byte3 << 16) 112 | | (byte2 << 8) 113 | | byte1); 114 | return dword; 115 | } 116 | /** 117 | 더블 워드를 기록합니다. 118 | @param {number} value 119 | */ 120 | function write_dword(value) { 121 | this.write_byte(value); value >>= 8; 122 | this.write_byte(value); value >>= 8; 123 | this.write_byte(value); value >>= 8; 124 | this.write_byte(value); 125 | } 126 | /** 127 | index에서 더블 워드를 획득합니다. 128 | @param {number} index 129 | @return {number} 130 | */ 131 | function get_dword(index) { 132 | var byte1 = this.get_byte(index); 133 | var byte2 = this.get_byte(index+1); 134 | var byte3 = this.get_byte(index+2); 135 | var byte4 = this.get_byte(index+3); 136 | var dword = ( 137 | (byte4 << 24) 138 | | (byte3 << 16) 139 | | (byte2 << 8) 140 | | byte1); 141 | return dword; 142 | } 143 | /** 144 | index에 더블 워드를 기록합니다. 145 | @param {number} value 146 | @param {number} index 147 | */ 148 | function set_dword(value, index) { 149 | this.set_byte(value, index); value >>= 8; 150 | this.set_byte(value, index+1); value >>= 8; 151 | this.set_byte(value, index+2); value >>= 8; 152 | this.set_byte(value, index+3); 153 | } 154 | 155 | /** 156 | 메모리의 전체 크기를 반환합니다. 157 | @return {number} 158 | */ 159 | function GetMaxMemorySize() { 160 | return MAX_MEMORY_SIZ; 161 | } 162 | 163 | /** 164 | 메모리를 출력합니다. 165 | */ 166 | function show() { 167 | var mem_str = ''; // 모든 메모리 텍스트를 보관하는 변수 168 | var mem_addr = ''; // 메모리 텍스트 줄을 보관하는 변수 169 | var mem_byte = ''; // 바이트 텍스트를 보관하는 변수 170 | var mem_char = ''; // 아스키 문자 텍스트를 보관하는 변수 171 | 172 | var addr_beg = 0; // 보여주기 시작하려는 메모리의 시작 주소 173 | var addr_end = Machine.Memory.MaxSize() - 1; // 메모리의 끝 주소 174 | 175 | // 모든 메모리의 바이트를 탐색합니다. 176 | for (var i=0; (addr_beg+i <= addr_end); ++i) { 177 | var addr = addr_beg + i; // 주소를 얻습니다. 178 | 179 | if (i % 16 == 0) { // 출력 중인 바이트의 인덱스가 16의 배수라면 180 | // 개행합니다. 181 | mem_str += Handy.format 182 | ('0x%04x | %-48s| %-16s \n', mem_addr, mem_byte, mem_char); 183 | 184 | // 표현하려는 주소 값을 16진수로 표시합니다. 185 | var addr_str = Number(addr).toString(16); 186 | 187 | // 다른 값을 초기화합니다. 188 | mem_addr = addr_str; 189 | mem_byte = ''; 190 | mem_char = ''; 191 | } 192 | 193 | // 획득한 주소의 바이트 값을 얻습니다. 194 | var byte = Machine.Memory.get_byte(addr); 195 | 196 | // 초기화되지 않은 메모리라면 0을 대입합니다. 197 | if (byte == undefined) 198 | byte = 0; 199 | 200 | // 바이트 값으로부터 문자를 획득합니다. 201 | var ascii = String.fromCharCode(byte); 202 | 203 | // 이스케이프 시퀀스인 경우에 대해 처리합니다. 204 | switch (ascii) { 205 | case '\0': ascii = '.'; break; 206 | case '\n': ascii = ' '; break; 207 | case '\t': ascii = ' '; break; 208 | case '\r': ascii = ' '; break; 209 | } 210 | 211 | // 각각의 문자열에 추가합니다. 212 | mem_byte += Handy.format('%02x ', byte); 213 | mem_char += ascii; 214 | } 215 | 216 | // 마지막 줄이 출력되지 않았다면 출력합니다. 217 | if (addr_beg + i == addr_end + 1) { 218 | mem_str += Handy.format 219 | ('0x%04x | %-48s| %-16s \n', mem_addr, mem_byte, mem_char); 220 | } 221 | // log(mem_str); 222 | Program.Stream.mem.writeln(mem_str); 223 | } 224 | 225 | /** 226 | 주어진 인자가 메모리인지 판정합니다. 227 | @param {string} param 228 | @return {boolean} 229 | */ 230 | function is_memory(param) { 231 | return ((param.charAt(0) == '[') 232 | && (param.charAt(param.length-1) == ']')); 233 | } 234 | /** 235 | 주어진 메모리로부터 4바이트 값을 획득합니다. 236 | @param {string} param 237 | @return {number} 238 | */ 239 | function get_memory_value(param) { 240 | var Register = Machine.Processor.Register; 241 | var addr; // 값을 가져올 메모리의 주소 값입니다. 242 | 243 | // 대괄호를 제외한 문자열을 획득하고, 이를 이용해 버퍼를 생성합니다. 244 | var buffer = new StringBuffer(param.slice(1, param.length-1)); 245 | 246 | // 가장 앞은 언제나 레지스터입니다. 247 | var reg = buffer.get_token(); 248 | 249 | // 획득한 레지스터가 보관하고 있는 값을 획득합니다. 250 | var regval = Register.get(reg); 251 | 252 | // 다음 토큰을 획득합니다. 253 | var op = buffer.get_token(); 254 | if (op == null) { // 다음 토큰이 존재하지 않는다면 [reg] 형식입니다. 255 | addr = regval; 256 | } 257 | else if (op == '+') { 258 | var imm = parseInt(buffer.get_token()); 259 | addr = regval + imm; 260 | } 261 | else if (op == '-') { 262 | var imm = parseInt(buffer.get_token()); 263 | addr = regval - imm; 264 | } 265 | else 266 | throw new MemoryException("invalid memory token"); 267 | 268 | // 획득한 위치에 존재하는 4바이트 값을 획득합니다. 269 | return Machine.Memory.get_dword(addr); 270 | } 271 | /** 272 | 메모리에 4바이트 값을 기록합니다. 273 | @param {string} dst 274 | @param {number} src 275 | */ 276 | function set_memory_value(dst, src) { 277 | var Register = Machine.Processor.Register; 278 | var addr; // 값을 설정할 메모리의 주소 값입니다. 279 | 280 | // 대괄호를 제외한 문자열을 획득하고, 이를 이용해 버퍼를 생성합니다. 281 | var buffer = new StringBuffer(dst.slice(1, dst.length-1)); 282 | 283 | // 가장 앞은 언제나 레지스터입니다. 284 | var reg = buffer.get_token(); 285 | 286 | // 획득한 레지스터가 보관하고 있는 값을 획득합니다. 287 | var regval = Register.get(reg); 288 | 289 | // 다음 토큰을 획득합니다. 290 | var op = buffer.get_token(); 291 | if (op == null) { // 다음 토큰이 존재하지 않는다면 [reg] 형식입니다. 292 | addr = regval; 293 | } 294 | else if (op == '+') { 295 | var imm = parseInt(buffer.get_token()); 296 | addr = regval + imm; 297 | } 298 | else if (op == '-') { 299 | var imm = parseInt(buffer.get_token()); 300 | addr = regval - imm; 301 | } 302 | else 303 | throw new MemoryException("invalid memory token"); 304 | 305 | // 획득한 위치에 4바이트 값을 설정합니다. 306 | return Machine.Memory.set_dword(src, addr); 307 | } 308 | 309 | /** 310 | 메모리 문자열이 가리키는 주소를 획득합니다. 311 | @param {string} param 312 | @return {number} 313 | */ 314 | function get_memory_address(param) { 315 | var Register = Machine.Processor.Register; 316 | var addr; // 값을 가져올 메모리의 주소 값입니다. 317 | 318 | // 대괄호를 제외한 문자열을 획득하고, 이를 이용해 버퍼를 생성합니다. 319 | var buffer = new StringBuffer(param.slice(1, param.length-1)); 320 | 321 | // 가장 앞은 언제나 레지스터입니다. 322 | var reg = buffer.get_token(); 323 | 324 | // 획득한 레지스터가 보관하고 있는 값을 획득합니다. 325 | var regval = Register.get(reg); 326 | 327 | // 다음 토큰을 획득합니다. 328 | var op = buffer.get_token(); 329 | if (op == null) { // 다음 토큰이 존재하지 않는다면 [reg] 형식입니다. 330 | addr = regval; 331 | } 332 | else if (op == '+') { 333 | var imm = parseInt(buffer.get_token()); 334 | addr = regval + imm; 335 | } 336 | else if (op == '-') { 337 | var imm = parseInt(buffer.get_token()); 338 | addr = regval - imm; 339 | } 340 | else 341 | throw new MemoryException("invalid memory token"); 342 | 343 | // 획득한 위치를 반환합니다. 344 | return addr; 345 | } 346 | 347 | // 싱글톤에 속성 등록 348 | memory.read_byte = read_byte; 349 | memory.write_byte = write_byte; 350 | memory.get_byte = get_byte; 351 | memory.set_byte = set_byte; 352 | memory.read_word = read_word; 353 | memory.write_word = write_word; 354 | memory.get_word = get_word; 355 | memory.set_word = set_word; 356 | memory.read_dword = read_dword; 357 | memory.write_dword = write_dword; 358 | memory.get_dword = get_dword; 359 | memory.set_dword = set_dword; 360 | 361 | memory.MaxSize = GetMaxMemorySize; 362 | memory.show = show; 363 | 364 | memory.is_memory = is_memory; 365 | memory.get_memory_value = get_memory_value; 366 | memory.set_memory_value = set_memory_value; 367 | 368 | memory.get_memory_address = get_memory_address; 369 | 370 | // 전역에 싱글톤 등록 371 | window.MemoryException = MemoryException; 372 | machine.Memory = memory; 373 | } -------------------------------------------------------------------------------- /ProgramRunner.js: -------------------------------------------------------------------------------- 1 | /** 2 | Machine에 프로그램 실행을 요청합니다. 3 | */ 4 | function initProgramRunner(program) { 5 | var runner = {}; 6 | 7 | // 속성 정의 이전에 정의해야 하는 내용 작성 8 | /** 9 | Runner 모듈의 메서드를 수행할 때 발생하는 예외를 정의합니다. 10 | @param {string} msg 11 | */ 12 | function RunnerException(msg, data) { 13 | this.description = msg; 14 | this.data = data; 15 | } 16 | RunnerException.prototype = new Exception(); 17 | RunnerException.prototype.toString = function() { 18 | return 'Runner' + Exception.prototype.toString.call(this); 19 | }; 20 | 21 | /** 22 | 레이블 정보를 표현하는 형식 LabelInfo를 정의합니다. 23 | @param {string} segmentName 24 | @param {string} name 25 | */ 26 | function LabelInfo(segmentName, name) { 27 | this.segmentName = segmentName; 28 | this.name = name; 29 | this.offset = 0; 30 | this.refered = []; 31 | } 32 | /** 33 | 인자가 레이블 이름인지 판정합니다. 34 | @param {string} param 35 | @return {boolean} 36 | */ 37 | function is_label_name(param) { 38 | return (param.charAt(0) == '_'); 39 | } 40 | 41 | /** 42 | 데이터 타입의 크기를 반환합니다. db이면 1을 반환하는 식입니다. 43 | @param {string} datatype 44 | @return {number} 45 | */ 46 | function getDataTypeSize(datatype) { 47 | switch (datatype.toLowerCase()) { // 소문자 문자열로 변경하고 확인합니다. 48 | case 'db': 49 | case 'byte': 50 | return 1; 51 | case 'dw': 52 | case 'word': 53 | return 2; 54 | case 'dd': 55 | case 'dword': 56 | return 4; 57 | } 58 | } 59 | 60 | /** 61 | 코드를 분석합니다. 62 | @param {string} line 63 | @return {InstructionInfo} 64 | */ 65 | function decode(line) { 66 | // StringBuffer 객체를 생성하고 line으로 초기화합니다. 67 | var buffer = new StringBuffer(line); 68 | 69 | // 가장 처음에 획득하는 단어는 반드시 니모닉입니다. 70 | var mne = buffer.get_token(); 71 | // 니모닉 획득에 실패한 경우 예외 처리합니다. 72 | if (is_fnamch(mne) == false) 73 | throw new RunnerException('invalid mnemonic'); 74 | 75 | // 다음 토큰 획득을 시도합니다. 76 | var left = buffer.get_token(); 77 | 78 | var right = null; 79 | if (left != null) { // 다음 토큰이 있는 경우의 처리 80 | // 피연산자가 두 개인지 확인하기 위해 토큰 획득을 시도합니다. 81 | right = buffer.get_token(); 82 | 83 | if (right != null) { // 다음 토큰이 있는 경우 84 | if (right != ',') // 반점이 아니라면 HASM 문법 위반입니다. 85 | throw new RunnerException 86 | ('syntax error; right operand must be after comma(,)'); 87 | 88 | // 오른쪽 피연산자 획득 89 | right = buffer.get_token(); 90 | if (right == null) // 획득 실패 시 문법을 위반한 것으로 간주합니다. 91 | throw new RunnerException 92 | ('syntax error; cannot find right operand'); 93 | } 94 | 95 | // 다음 토큰이 없다면 right는 null이고 96 | // 그렇지 않으면 다음 토큰 문자열이 된다 97 | } 98 | 99 | // 획득한 코드 정보를 담는 객체를 생성하고 반환합니다. 100 | var info = { mnemonic: mne, left: left, right: right }; 101 | return info; 102 | } 103 | 104 | // 필드 및 메서드 정의 105 | runner.imageBase = 4; 106 | runner.baseOfData = 0; 107 | runner.sizeOfData = 0; 108 | runner.baseOfCode = 0; 109 | runner.sizeOfCode = 0; 110 | runner.entrypoint = 0; 111 | 112 | /** 113 | 지정한 파일을 불러와 메모리에 올립니다. 114 | @param {string} filename 115 | */ 116 | function load(filename) { 117 | var Memory = Machine.Memory; 118 | 119 | // 파일로부터 텍스트를 획득합니다. 120 | var code = HandyFileSystem.load(filename); 121 | if (code == null) // 텍스트를 획득하지 못한 경우 예외를 발생합니다. 122 | throw new RunnerException('Cannot load file', filename); 123 | 124 | // 획득한 텍스트를 줄 단위로 나눈 배열을 획득합니다. 125 | var lines = String(code).split(NEWLINE); 126 | 127 | // 코드를 메모리에 기록하기 위해 바이트 포인터를 맞춥니다. 128 | Memory.bytePtr = 4; 129 | 130 | // 획득한 텍스트를 메모리에 기록합니다. 131 | var labelInfoDict = {}; // LabelInfo에 대한 딕셔너리 객체입니다. 132 | 133 | // 각각의 세그먼트에 들어갈 정보를 리스트 식으로 관리합니다. 134 | var segment = { data: [], code: [] }; // 순서대로 데이터나 코드를 추가합니다. 135 | var segment_target = null; // 현재 분석중인 세그먼트입니다. 136 | 137 | var baseOfData = 0; // 데이터 세그먼트의 시작 지점입니다. 138 | var sizeOfData = 0; // 데이터 세그먼트의 전체 크기입니다. 139 | var baseOfCode = 0; // 코드 세그먼트의 시작 지점입니다. 140 | var sizeOfCode = 0; // 코드 세그먼트의 전체 크기입니다. 141 | 142 | for (var i=0, len=lines.length; i 0) ? ' ' + typeqlif : ''), 24 | ((typespec.length > 0) ? ' ' + typespec : '')); 25 | } 26 | var StorageClassSpecifierDict = { 27 | auto: true, register: true, static: true, extern: true, typedef: true 28 | }; 29 | var TypeQualifierDict = { 30 | const: true, volatile: true, 31 | }; 32 | var TypeSpecifierDict = { 33 | void: true, char: true, short: true, int: true, 34 | long: true, float: true, double: true, signed: true, 35 | unsigned: true, struct: true, union: true, enum: true 36 | }; 37 | /** 38 | 초기 선언자 형식입니다. 39 | @param {Declarator} declarator 40 | @param {Expression} initializer 41 | */ 42 | function InitDeclarator(declarator, initializer) { 43 | this.declarator = declarator; 44 | this.initializer = initializer; 45 | } 46 | InitDeclarator.prototype.toString = function() { 47 | var declarator = getValid(this.declarator, ''); 48 | var initializer = getValid(this.initializer, ''); 49 | return Handy.format('[%s:%s]', declarator, initializer); 50 | } 51 | /** 52 | 선언자 형식입니다. 53 | @param {string} identifier 54 | @param {Array.} descriptor 55 | */ 56 | function Declarator(identifier, descriptor) { 57 | this.identifier = identifier; 58 | this.descriptor = descriptor; 59 | } 60 | Declarator.prototype.toString = function() { 61 | var identifier = getValid(this.identifier, ''); 62 | var descriptor = getValid(this.descriptor, ''); 63 | return Handy.format('[%s:%s]', identifier, descriptor); 64 | } 65 | /** 66 | 매개변수 선언 형식입니다. 67 | @param {DeclarationSpecifier} declspec 68 | @param {Declarator} paramdecl 69 | */ 70 | function ParameterDeclaration(declspec, paramdecl) { 71 | this.declarationSpecifier = declspec; 72 | this.parameterDeclarator = paramdecl; 73 | } 74 | ParameterDeclaration.prototype.toString = function() { 75 | var declspec = getValid(this.declarationSpecifier, ''); 76 | var paramdecl = getValid(this.parameterDeclarator, ''); 77 | return Handy.format('%s%s', paramdecl, declspec); 78 | } 79 | /** 80 | 선언 형식입니다. 81 | @param {DeclarationSpecifier} declspec 82 | @param {Array.} init_decl_list 83 | */ 84 | function DeclarationInfo(declspec, init_decl_list) { 85 | this.declarationSpecifier = declspec; 86 | this.initDeclaratorList = init_decl_list; 87 | } 88 | DeclarationInfo.prototype.toString = function() { 89 | return Handy.format 90 | ('[%s%s]', this.declarationSpecifier, this.initDeclaratorList); 91 | }; 92 | 93 | /** 94 | 매개변수 형식 리스트를 문자열로 반환합니다. 95 | */ 96 | function ParameterTypeList_toString() { 97 | return '(' + Array.prototype.toString.call(this) + ')'; 98 | } 99 | 100 | // 메서드 정의 101 | /** 102 | 선언 지정자를 획득합니다. 실패시 포인터를 복구하고 null을 반환합니다. 103 | @param {StringBuffer} buffer 104 | @return {DeclarationSpecifier} 105 | */ 106 | function getDeclarationSpecifier(buffer) { 107 | // 선언 지정자 획득에 실패하면 획득 이전으로 포인터를 복구합니다. 108 | var originIndex = buffer.idx; 109 | try { 110 | // 토큰을 획득하기 직전의 인덱스를 보존합니다. 111 | var prevIndex = buffer.idx; 112 | // 선언 지정자 정보를 담을 변수입니다. 113 | var sc_spec = null, type_spec = [], type_qlif = []; 114 | 115 | // 선언 지정자 정보를 획득하는 반복문입니다. 116 | while (buffer.is_empty() == false) { // 버퍼가 비어있는 동안 117 | prevIndex = buffer.idx; // 토큰 획득 이전의 위치를 보존합니다. 118 | var token = buffer.get_ctoken(); // 토큰을 획득합니다. 119 | 120 | // 기억 형태 지정자 토큰인 경우에 대해 처리합니다. 121 | if (isStorageClassSpecifierToken(token)) { 122 | if (sc_spec != null) { // 기억 형태 지정자는 두 개 이상 지정될 수 없습니다. 123 | throw new CompilerException 124 | ('cannot declare object with 2 or more storage class specifiers'); 125 | } 126 | // 획득한 기억 형태 지정자를 기억합니다. 127 | sc_spec = token; 128 | } 129 | // 형식 한정자 토큰인 경우에 대해 처리합니다. 130 | else if (isTypeQualifierToken(token)) { 131 | // 획득한 형식 한정자를 기억합니다. 132 | type_qlif.push(token); 133 | } 134 | // 형식 지정자 토큰인 경우에 대해 처리합니다. 135 | else if (isTypeSpecifierToken(token)) { 136 | // 획득한 형식 지정자를 기억합니다. 137 | type_spec.push(token); 138 | } 139 | // 그 외의 경우 선언 지정자가 아니므로 획득하면 안 됩니다. 140 | else { 141 | buffer.idx = prevIndex; // 토큰 위치를 복구합니다. 142 | break; // 선언 지정자 획득 반복문을 탈출합니다. 143 | } 144 | } 145 | 146 | // 획득한 토큰이 아무 것도 없으면 예외 처리합니다. 147 | if ((sc_spec == null) 148 | && (type_spec.length == 0) 149 | && (type_qlif.length == 0)) 150 | throw new CompilerException('undeclared identifier'); 151 | 152 | // 획득한 정보를 바탕으로 선언 지정자 정보 객체를 생성합니다. 153 | var declspcf = new DeclarationSpecifier 154 | (sc_spec, type_spec, type_qlif); 155 | // 생성한 선언 지정자 정보 객체를 반환합니다. 156 | return declspcf; 157 | 158 | } catch (ex) { 159 | // 선언 지정자 획득에 실패했으므로 획득 이전으로 포인터를 복구합니다. 160 | buffer.idx = originIndex; 161 | return null; 162 | } 163 | } 164 | /** 165 | 기억 형태 지정자 토큰이라면 true, 아니라면 false를 반환합니다. 166 | @param {string} token 167 | @return {boolean} 168 | */ 169 | function isStorageClassSpecifierToken(token) { 170 | return (StorageClassSpecifierDict[token] != undefined); 171 | } 172 | /** 173 | 형식 지정자 토큰이라면 true, 아니라면 false를 반환합니다. 174 | @param {string} token 175 | @return {boolean} 176 | */ 177 | function isTypeSpecifierToken(token) { 178 | return (TypeSpecifierDict[token] != undefined); 179 | } 180 | /** 181 | 형식 한정자 토큰이라면 true, 아니라면 false를 반환합니다. 182 | @param {string} token 183 | @return {boolean} 184 | */ 185 | function isTypeQualifierToken(token) { 186 | return (TypeQualifierDict[token] != undefined); 187 | } 188 | 189 | /** 190 | 초기 선언자 리스트를 획득합니다. 191 | @param {StringBuffer} buffer 192 | @return {Array.} 193 | */ 194 | function getInitDeclaratorList(buffer) { 195 | // 초기 선언자에 대한 리스트를 생성합니다. 196 | var init_decl_list = []; 197 | 198 | // 버퍼에 데이터가 남아있는 동안 199 | while (buffer.is_empty() == false) { 200 | // 초기 선언자를 먼저 획득합니다. 201 | var initDeclarator = getInitDeclarator(buffer); 202 | 203 | // 획득한 초기 선언자를 리스트에 추가합니다. 204 | init_decl_list.push(initDeclarator); 205 | 206 | // 다음 토큰의 시작점으로 버퍼 포인터를 맞춥니다. 207 | buffer.trim(); 208 | 209 | // 다음 토큰이 반점이 아니라면, 초기 선언자 리스트 분석을 종료합니다. 210 | if (buffer.peekc() != ',') 211 | break; 212 | 213 | // 초기 선언자 리스트 분석을 진행합니다. 반점은 지나갑니다. 214 | buffer.getc(); 215 | } 216 | 217 | // 획득한 초기 선언자의 리스트를 반환합니다. 218 | return init_decl_list; 219 | } 220 | /** 221 | 초기 선언자를 획득합니다. 222 | @param {StringBuffer} buffer 223 | @return {InitDeclarator} 224 | */ 225 | function getInitDeclarator(buffer) { 226 | // 반환할 초기 선언자에 대한 변수입니다. 227 | var initDeclarator = null; 228 | 229 | // 선언자를 획득합니다. 230 | var declarator = getDeclarator(buffer); 231 | 232 | // 다음 토큰의 시작점으로 버퍼 포인터를 맞춥니다. 233 | buffer.trim(); 234 | 235 | // 다음 토큰이 등호라면 초기 값을 획득합니다. 236 | if (buffer.peekc() == '=') { 237 | buffer.getc(); // 등호 토큰을 지나갑니다. 238 | 239 | var initializer; // 초기 값을 획득하기 위한 변수를 생성합니다. 240 | /* ... */ 241 | 242 | // 선언자와 초기 값으로 초기 선언자 객체를 생성합니다. 243 | initDeclarator = new InitDeclarator(declarator, initializer); 244 | } 245 | // 등호가 아니라면 초기 선언자 분석을 종료합니다. 246 | else { 247 | // 선언자로 초기 선언자 객체를 생성합니다. 248 | initDeclarator = new InitDeclarator(declarator); 249 | } 250 | 251 | // 초기 선언자 객체를 반환합니다. 252 | return initDeclarator; 253 | } 254 | /** 255 | 선언자를 획득합니다. 256 | @param {StringBuffer} buffer 257 | @return {Declarator} 258 | */ 259 | function getDeclarator(buffer) { 260 | // 토큰 획득 이전에 버퍼 포인터를 보관합니다. 261 | var originIndex = buffer.idx; 262 | 263 | // 선언자 토큰에 대한 빈 벡터를 생성합니다. 264 | var tokenArray = []; 265 | 266 | // 선언자 토큰을 생성한 벡터에 출력합니다. 267 | dcl(buffer, tokenArray); 268 | if (tokenArray.length == 0) { // 획득한 토큰이 없는 경우 269 | buffer.idx = originIndex; // 실패한 것으로 간주하고 null을 반환합니다. 270 | return null; 271 | } 272 | 273 | // 획득한 토큰 벡터를 바탕으로 선언자 객체를 생성합니다. 274 | var identifier = tokenArray[0]; 275 | var descriptor = Handy.toArray(tokenArray, 1); 276 | var declarator = new Declarator(identifier, descriptor); 277 | 278 | // 선언자 객체를 반환합니다. 279 | return declarator; 280 | } 281 | /** 282 | 토큰이 포인터라면 true, 아니면 false를 반환합니다. 283 | @param {string} token 284 | @return {boolean} 285 | */ 286 | function isPointerToken(token) { 287 | // 포인터라면 별표 기호거나 형식 한정자 토큰입니다. 288 | return (token == '*' || isTypeQualifierToken(token)); 289 | } 290 | 291 | /** 292 | 선언자를 분석한 결과를 토큰 배열에 저장합니다. 293 | @param {StringBuffer} bin 294 | @param {Array.} vout 295 | */ 296 | function dcl(bin, vout) { 297 | // 포인터에 대한 스택을 생성합니다. 298 | var pointerStack = new Array(); 299 | while (bin.is_empty() == false) { // 버퍼에 문자열이 남아있는 동안 300 | var prevIndex = bin.idx; // 토큰 획득 이전의 인덱스를 보존합니다. 301 | 302 | var token = bin.get_ctoken(); // 토큰을 획득합니다. 303 | if (isPointerToken(token) == false) { // 획득한 토큰이 포인터가 아니라면 304 | bin.idx = prevIndex; // 토큰 획득 이전의 인덱스를 복구합니다. 305 | break; // 반복문을 탈출합니다. 306 | } 307 | 308 | // 포인터 스택에 포인터를 푸시 합니다. 309 | pointerStack.push(token); 310 | 311 | // 다음 토큰의 시작 지점으로 버퍼 포인터를 옮깁니다. 312 | bin.trim(); 313 | } 314 | 315 | // 포인터 획득이 끝났으므로 직접 선언자를 분석합니다. 316 | dirdcl(bin, vout); 317 | 318 | // 스택에 존재하는 포인터를 토큰 배열에 저장합니다. 319 | while (pointerStack.length > 0) { 320 | var pointer = pointerStack.pop(); 321 | vout.push(pointer); 322 | } 323 | } 324 | /** 325 | 직접 선언자를 분석한 결과를 토큰 배열에 저장합니다. 326 | @param {StringBuffer} bin 327 | @param {Array} vout 328 | */ 329 | function dirdcl(bin, vout) { 330 | // 다음 토큰의 시작 지점으로 버퍼 포인터를 옮깁니다. 331 | bin.trim(); 332 | 333 | // 문자를 획득해본 다음 조건에 맞게 분기합니다. 334 | if (bin.peekc() == '(') { // 소괄호가 발견되었다면 335 | // 직접 선언자의 2번 정의 ( declarator )로 간주합니다. 336 | bin.getc(); // 발견한 여는 소괄호를 지나갑니다. 337 | dcl(bin, vout); // 선언자 분석을 시작합니다. 338 | 339 | // 선언자 분석 종료 후 닫는 소괄호가 발견되었는지 확인합니다. 340 | if (bin.peekc() != ')') { // 다음 토큰이 닫는 소괄호가 아니라면 341 | // 일단 함수를 탈출하고, 선언자 분석을 요청한 메서드가 342 | // 이 토큰을 처리하도록 책임을 넘깁니다. 343 | return; 344 | } 345 | 346 | // 닫는 소괄호는 사용했으므로 지나갑니다. 347 | bin.getc(); 348 | } 349 | else if (is_fnamch(bin.peekc())) { // 식별자의 시작이라면 350 | var identifier = bin.get_ctoken(); // 식별자를 획득합니다. 351 | vout.push(identifier); // 획득한 식별자를 토큰 벡터에 넣습니다. 352 | } 353 | bin.trim(); // 다음 토큰의 시작 지점으로 버퍼 포인터를 옮깁니다. 354 | 355 | // 배열과 함수에 대해 처리합니다. 356 | while (bin.peekc() == '(' || bin.peekc() == '[') { 357 | if (bin.peekc() == '(') { // 함수 기호의 시작이라면 358 | bin.getc(); // 함수의 시작으로서 분석한, 여는 소괄호는 생략합니다. 359 | 360 | // 매개변수 형식 리스트를 획득합니다. 361 | var paramTypeList = getParameterTypeList(bin); 362 | 363 | // 매개변수 형식 리스트를 벡터에 넣습니다. 364 | vout.push(paramTypeList); 365 | 366 | bin.trim(); // 다음 토큰의 시작 지점까지 버퍼 포인터를 옮깁니다. 367 | if (bin.peekc() != ')') // 다음 토큰이 닫는 소괄호가 아니라면 368 | break; // 토큰의 해석 책임을 dirdcl을 호출한 함수로 넘깁니다. 369 | 370 | bin.getc(); // 해석한 닫는 소괄호를 지나갑니다. 371 | } 372 | else { // 배열 기호의 시작이라면 373 | /* ... */ 374 | } 375 | } 376 | } 377 | 378 | /** 379 | 매개변수 형식 리스트를 획득합니다. 380 | @param {StringBuffer} buffer 381 | @return {Array.>} 382 | */ 383 | function getParameterTypeList(buffer) { 384 | // 매개변수 형식에 대한 리스트를 생성합니다. 385 | var param_type_list = []; 386 | 387 | while (buffer.is_empty() == false) { // 버퍼에 데이터가 남아있는 동안 388 | var prevIndex = buffer.idx; // 토큰 획득 이전의 위치를 보관합니다. 389 | var paramdecl = getParameterDeclaration(buffer); // 매개변수 선언을 획득합니다. 390 | 391 | if (paramdecl == null) { // 매개변수 선언 획득에 실패한 경우 392 | buffer.idx = prevIndex; // 토큰 획득 이전으로 버퍼 포인터를 복구합니다. 393 | var token = buffer.get_ctoken(); // 토큰 획득을 시도합니다. 394 | if (token != '...') { // 가변 인자 토큰이 아니라면 395 | buffer.idx = prevIndex; // 다시 버퍼 포인터를 복구합니다. 396 | } 397 | // 매개변수 선언 획득 반복문을 탈출합니다. 398 | break; 399 | } 400 | 401 | // 획득한 매개변수 선언을 매개변수 형식 리스트에 넣습니다. 402 | param_type_list.push(paramdecl); 403 | 404 | // 다음 토큰의 시작점으로 버퍼 포인터를 맞춥니다. 405 | buffer.trim(); 406 | // 다음 토큰이 반점이 아니라면, 초기 선언자 리스트 분석을 종료합니다. 407 | if (buffer.peekc() != ',') 408 | break; 409 | // 초기 선언자 리스트 분석을 진행합니다. 반점은 지나갑니다. 410 | buffer.getc(); 411 | } 412 | 413 | // 매개변수 형식 리스트의 toString을 오버라이딩합니다. 414 | param_type_list.toString = ParameterTypeList_toString; 415 | 416 | // 매개변수 형식 리스트를 반환합니다. 417 | return param_type_list; 418 | } 419 | /** 420 | 매개변수 선언을 획득합니다. 실패시 null을 반환합니다. 421 | @param {StringBuffer} buffer 422 | @return {ParameterDeclaration} 423 | */ 424 | function getParameterDeclaration(buffer) { 425 | // 매개변수 선언에 대한 변수를 생성합니다. 426 | var paramdecl = null; 427 | 428 | // 선언 지정자를 획득합니다. 429 | var declspec = getDeclarationSpecifier(buffer); 430 | 431 | // 추상 선언자 획득을 시도합니다. 432 | var declarator = getAbstractDeclarator(buffer); 433 | if (declarator == null) // 선언자 획득에 실패한 경우 434 | return null; // null을 반환합니다. 435 | 436 | // 획득한 정보를 바탕으로 매개변수 선언 객체를 생성합니다. 437 | paramdecl = new ParameterDeclaration(declspec, declarator); 438 | 439 | // 매개변수 선언 객체를 반환합니다. 440 | return paramdecl; 441 | } 442 | /** 443 | 추상 선언자를 획득합니다. 실패 시 null을 반환합니다. 444 | @param {StringBuffer} buffer 445 | @return {Declarator} 446 | */ 447 | function getAbstractDeclarator(buffer) { 448 | // 선언자 객체에 대한 변수를 생성합니다. 449 | var declarator = null; 450 | 451 | // 선언자 토큰에 대한 빈 벡터를 생성합니다. 452 | var tokenArray = []; 453 | 454 | // 가장 첫 원소를 식별자로 간주하므로, 첫 원소를 null로 채웁니다. 455 | tokenArray.push(null); 456 | 457 | // 선언자 토큰을 생성한 벡터에 출력합니다. 458 | absdcl(buffer, tokenArray); 459 | 460 | // 획득한 토큰 벡터를 바탕으로 선언자 객체를 생성합니다. 461 | var descriptor = Handy.toArray(tokenArray, 1); 462 | declarator = new Declarator(null, descriptor); 463 | 464 | // 선언자 객체를 반환합니다. 465 | return declarator; 466 | } 467 | /** 468 | 추상 선언자를 분석한 결과를 토큰 배열에 저장합니다. 469 | @param {StringBuffer} bin 470 | @param {Array.} vout 471 | */ 472 | function absdcl(bin, vout) { 473 | // 포인터에 대한 스택을 생성합니다. 474 | var pointerStack = new Array(); 475 | while (bin.is_empty() == false) { // 버퍼에 문자열이 남아있는 동안 476 | var prevIndex = bin.idx; // 토큰 획득 이전의 인덱스를 보존합니다. 477 | 478 | var token = bin.get_ctoken(); // 토큰을 획득합니다. 479 | if (isPointerToken(token) == false) { // 획득한 토큰이 포인터가 아니라면 480 | bin.idx = prevIndex; // 토큰 획득 이전의 인덱스를 복구합니다. 481 | break; // 반복문을 탈출합니다. 482 | } 483 | 484 | // 포인터 스택에 포인터를 푸시 합니다. 485 | pointerStack.push(token); 486 | 487 | // 다음 토큰의 시작 지점으로 버퍼 포인터를 옮깁니다. 488 | bin.trim(); 489 | } 490 | 491 | // 포인터 획득이 끝났으므로 직접 선언자를 분석합니다. 492 | absdirdcl(bin, vout); 493 | 494 | // 스택에 존재하는 포인터를 토큰 배열에 저장합니다. 495 | while (pointerStack.length > 0) { 496 | var pointer = pointerStack.pop(); 497 | vout.push(pointer); 498 | } 499 | } 500 | /** 501 | 추상 직접 선언자를 분석한 결과를 토큰 배열에 저장합니다. 502 | @param {StringBuffer} bin 503 | @param {Array} vout 504 | */ 505 | function absdirdcl(bin, vout) { 506 | // 다음 토큰의 시작 지점으로 버퍼 포인터를 옮깁니다. 507 | bin.trim(); 508 | 509 | // 문자를 획득해본 다음 조건에 맞게 분기합니다. 510 | if (bin.peekc() == '(') { // 소괄호가 발견되었다면 511 | // 직접 선언자의 2번 정의 ( declarator )로 간주합니다. 512 | bin.getc(); // 발견한 여는 소괄호를 지나갑니다. 513 | absdcl(bin, vout); // 선언자 분석을 시작합니다. 514 | 515 | // 선언자 분석 종료 후 닫는 소괄호가 발견되었는지 확인합니다. 516 | if (bin.peekc() != ')') { // 다음 토큰이 닫는 소괄호가 아니라면 517 | // 일단 함수를 탈출하고, 선언자 분석을 요청한 메서드가 518 | // 이 토큰을 처리하도록 책임을 넘깁니다. 519 | return; 520 | } 521 | 522 | // 닫는 소괄호는 사용했으므로 지나갑니다. 523 | bin.getc(); 524 | } 525 | else if (is_fnamch(bin.peekc())) { // 식별자의 시작이라면 526 | bin.get_ctoken(); // 식별자를 무시합니다. 527 | vout[0] = true; // 식별자가 발견된 경우 표시합니다. 528 | bin.trim(); // 다음 토큰의 시작 지점으로 버퍼 포인터를 옮깁니다. 529 | } 530 | 531 | // 배열과 함수에 대해 처리합니다. 532 | while (bin.peekc() == '(' || bin.peekc() == '[') { 533 | if (bin.peekc() == '(') { // 함수 기호의 시작이라면 534 | bin.getc(); // 함수의 시작으로서 분석한, 여는 소괄호는 생략합니다. 535 | 536 | // 매개변수 형식 리스트를 획득합니다. 537 | var paramTypeList = getParameterTypeList(bin); 538 | 539 | // 매개변수 형식 리스트를 벡터에 넣습니다. 540 | vout.push(paramTypeList); 541 | 542 | bin.trim(); // 다음 토큰의 시작 지점까지 버퍼 포인터를 옮깁니다. 543 | if (bin.peekc() != ')') // 다음 토큰이 닫는 소괄호가 아니라면 544 | break; // 토큰의 해석 책임을 dirdcl을 호출한 함수로 넘깁니다. 545 | 546 | bin.getc(); // 해석한 닫는 소괄호를 지나갑니다. 547 | } 548 | else { // 배열 기호의 시작이라면 549 | /* ... */ 550 | } 551 | } 552 | } 553 | 554 | /** 555 | 선언을 획득합니다. 실패시 포인터를 복구하고 null을 반환합니다. 556 | @param {StringBuffer} buffer 557 | @return {DeclarationInfo} 558 | */ 559 | function getDeclarationInfo(buffer) { 560 | // 선언 획득 이전에 버퍼 포인터를 보관합니다. 561 | var originIndex = buffer.idx; 562 | 563 | // 선언 지정자 획득을 시도합니다. 564 | var declspec = getDeclarationSpecifier(buffer); 565 | if (declspec == null) { 566 | buffer.idx = originIndex; // 버퍼 포인터를 복구합니다. 567 | return null; // null을 반환합니다. 568 | } 569 | 570 | // 초기 선언자 리스트를 획득합니다. 선언의 정의에 의해 생략될 수 있습니다. 571 | var init_decl_list = getInitDeclaratorList(buffer); 572 | 573 | // 다음 토큰의 시작 지점으로 버퍼 포인터를 옮깁니다. 574 | buffer.trim(); 575 | 576 | // 다음 토큰이 세미콜론이 아니라면 선언 획득 실패로 간주합니다. 577 | if (buffer.peekc() != ';') { 578 | buffer.idx = originIndex; // 버퍼 포인터를 복구합니다. 579 | return null; // null을 반환합니다. 580 | } 581 | 582 | // 세미콜론을 확인하는 용도로 사용했으므로 지나갑니다. 583 | buffer.getc(); 584 | 585 | // 획득한 정보를 바탕으로 선언 정보 객체를 생성합니다. 586 | var declInfo = new DeclarationInfo(declspec, init_decl_list); 587 | 588 | // 획득한 선언 정보 객체를 반환합니다. 589 | return declInfo; 590 | } 591 | 592 | _Declaration.getAbstractDeclarator = getAbstractDeclarator; 593 | 594 | _Declaration.getDeclarationSpecifier = getDeclarationSpecifier; 595 | _Declaration.getInitDeclaratorList = getInitDeclaratorList; 596 | _Declaration.getDeclarationInfo = getDeclarationInfo; 597 | _Declaration.getDeclarator = getDeclarator; 598 | compiler.Declaration = _Declaration; 599 | } -------------------------------------------------------------------------------- /ProgramCompilerStatement.js: -------------------------------------------------------------------------------- 1 | /** 2 | C의 문장을 분석합니다. 3 | */ 4 | function initProgramCompilerStatement(compiler, CompilerException) { 5 | var _Statement = {}; 6 | 7 | /** 8 | 문장을 정의합니다. 9 | */ 10 | function StatementInfo(stmt) { 11 | this.statement = stmt; 12 | } 13 | StatementInfo.prototype.toString = function() { 14 | return Handy.format('%s', this.statement); 15 | }; 16 | /** 17 | 수식문을 정의합니다. 18 | @param {ExpressionInfo} exprInfo 19 | */ 20 | function ExpressionStatementInfo(exprInfo) { 21 | this.expressionInfo = exprInfo; 22 | } 23 | ExpressionStatementInfo.prototype.toString = function() { 24 | return Handy.format('[%s]', this.expressionInfo); 25 | }; 26 | /** 27 | 복합문을 정의합니다. 28 | @param {Array.} decl_list 29 | @param {Array.} stmt_list 30 | */ 31 | function CompoundStatementInfo(decl_list, stmt_list) { 32 | this.declarationList = decl_list; 33 | this.statementList = stmt_list; 34 | } 35 | CompoundStatementInfo.prototype.toString = function() { 36 | return Handy.format('[%s | %s]', this.declarationList, this.statementList); 37 | }; 38 | /** 39 | 선택문을 정의합니다. 40 | @param {string} selectionType 41 | @param {ExpressionInfo} condition 42 | @param {StatementInfo} stmt 43 | @param {StatementInfo} elseStmt 44 | */ 45 | function SelectionStatementInfo(selectionType, condition, stmt, elseStmt) { 46 | this.selectionType = selectionType; 47 | this.condition = condition; 48 | this.statement = stmt; 49 | this.elseStatement = elseStmt; 50 | } 51 | SelectionStatementInfo.prototype.toString = function() { 52 | var selType = this.selectionType; 53 | var condExpr = this.condition; 54 | var stmt = this.statement; 55 | var elseStmt = this.elseStatement; 56 | var after = Handy.format 57 | ('%s%s', stmt, elseStmt ? (' else ' + elseStmt) : ''); 58 | return Handy.format('%s (%s) %s', selType, condExpr, after); 59 | }; 60 | /** 61 | 반복문을 정의합니다. 62 | @param {string} iterationType 63 | @param {ExpressionInfo} condition 64 | @param {StatementInfo} statement 65 | @param {ExpressionInfo} initializer 66 | @param {ExpressionInfo} iterator 67 | */ 68 | function IterationStatementInfo 69 | (iterationType, condition, statement, initializer, iterator) { 70 | this.iterationType = iterationType; 71 | this.condition = condition; 72 | this.statement = statement; 73 | this.initializer = initializer; 74 | this.iterator = iterator; 75 | } 76 | IterationStatementInfo.prototype.toString = function() { 77 | var iterType = this.iterationType; 78 | var initExpr = getValid(this.initializer, ''); 79 | var condExpr = this.condition; 80 | var iterExpr = getValid(this.iterator, ''); 81 | var statement = this.statement; 82 | return Handy.format 83 | ('%s (%s;%s;%s) %s', iterType, initExpr, condExpr, iterExpr, statement); 84 | }; 85 | /** 86 | 점프문을 정의합니다. 87 | @param {string} jumpType 88 | */ 89 | function JumpStatementInfo(jumpType, operand) { 90 | this.jumpType = jumpType; 91 | this.operand = operand; 92 | } 93 | JumpStatementInfo.prototype.toString = function() { 94 | var jumpType = this.jumpType; 95 | var operand = this.operand ? ' ' + this.operand : ''; 96 | return Handy.format('%s%s', jumpType, operand); 97 | }; 98 | /** 99 | 레이블문을 정의합니다. 100 | @param {string} labelType 101 | @param {object} value 102 | @param {StatementInfo} statement 103 | */ 104 | function LabeledStatementInfo(labelType, value, statement) { 105 | this.labelType = labelType; 106 | this.value = value; 107 | this.statement = statement; 108 | } 109 | LabeledStatementInfo.prototype.toString = function() { 110 | var labelType = getValid(this.labelType, ''); 111 | var value = getValid(this.value, ''); 112 | var label = Handy.format('%s %s', labelType, value); 113 | return Handy.format('[%s:%s]', label, this.statement); 114 | }; 115 | 116 | /** 117 | 문장을 분석합니다. 118 | @param {StringBuffer} buffer 119 | @return {StatementInfo} 120 | */ 121 | function getStatementInfo(buffer) { 122 | buffer.trim(); // 다음 토큰의 시작 지점으로 버퍼 포인터를 옮깁니다. 123 | var prevIndex = buffer.idx; // 버퍼 포인터를 보관합니다. 124 | var token = buffer.get_ctoken(); // 토큰을 획득합니다. 125 | buffer.idx = prevIndex; // 버퍼 포인터를 복구합니다. 126 | 127 | // 획득한 토큰을 기준으로 조건 분기합니다. 128 | var stmtInfo = null; // 문장 객체에 대한 변수입니다. 129 | switch (token) { 130 | // 복합문 토큰 131 | case '{': // 복합문을 분석하고 객체를 생성합니다. 132 | var compoundStmtInfo = getCompoundStatementInfo(buffer); 133 | stmtInfo = new StatementInfo(compoundStmtInfo); 134 | break; 135 | case '}': // 복합문의 마지막을 발견하면 null을 반환합니다. 136 | stmtInfo = null; 137 | break; 138 | 139 | // 선택문 토큰 140 | case 'if': 141 | case 'switch': 142 | var selectionStmtInfo = getSelectionStatementInfo(buffer); 143 | stmtInfo = new StatementInfo(selectionStmtInfo); 144 | break; 145 | 146 | // 반복문 토큰 147 | case 'while': 148 | case 'do': 149 | case 'for': 150 | var iterationStmtInfo = getIterationStatementInfo(buffer); 151 | stmtInfo = new StatementInfo(iterationStmtInfo); 152 | break; 153 | 154 | // 점프문 토큰 155 | case 'goto': 156 | case 'continue': 157 | case 'break': 158 | case 'return': 159 | var jumpStmtInfo = getJumpStatementInfo(buffer); 160 | stmtInfo = new StatementInfo(jumpStmtInfo); 161 | break; 162 | 163 | // 레이블문 토큰 164 | case 'case': 165 | case 'default': 166 | var labelStmtInfo = getLabeledStatementInfo(buffer); 167 | stmtInfo = new StatementInfo(labelStmtInfo); 168 | break; 169 | 170 | // 그 외의 경우 171 | default: 172 | buffer.get_ctoken(); 173 | if (buffer.get_ctoken() == ':') { // 레이블 문이라면 174 | buffer.idx = prevIndex; // 버퍼 포인터를 되돌린 후 분석합니다. 175 | var labelStmtInfo = getLabeledStatementInfo(buffer); 176 | stmtInfo = new StatementInfo(labelStmtInfo); 177 | } 178 | else { // 레이블 문이 아니라면 수식문으로 간주합니다. 179 | buffer.idx = prevIndex; // 버퍼 포인터를 되돌린 후 분석합니다. 180 | var exprStmtInfo = getExpressionStatementInfo(buffer); 181 | stmtInfo = new StatementInfo(exprStmtInfo); 182 | } 183 | } 184 | 185 | // 생성한 토큰을 반환합니다. 186 | return stmtInfo; 187 | } 188 | /** 189 | 수식문을 분석합니다. 190 | @param {StringBuffer} buffer 191 | */ 192 | function getExpressionStatementInfo(buffer) { 193 | var Expr = Program.Compiler.Expression; 194 | 195 | // 수식을 획득합니다. 196 | var exprInfo = Expr.getExpressionInfo(buffer); 197 | 198 | // 다음 토큰의 시작 지점으로 버퍼 포인터를 맞춥니다. 199 | buffer.trim(); 200 | // 세미콜론을 발견할 수 없으면 명백한 문법 위반입니다. 201 | if (buffer.peekc() != ';') 202 | throw new CompilerException('cannot find end of expression', buffer.peekc()); 203 | 204 | // 확인한 세미콜론을 지나갑니다. 205 | buffer.getc(); 206 | 207 | // 획득한 정보를 바탕으로 객체를 생성하고 반환합니다. 208 | var exprStmt = new ExpressionStatementInfo(exprInfo); 209 | return exprStmt; 210 | } 211 | /** 212 | 복합문을 분석합니다. 213 | @param {StringBuffer} buffer 214 | */ 215 | function getCompoundStatementInfo(buffer) { 216 | buffer.trim(); // 다음 토큰의 시작 지점으로 버퍼 포인터를 맞춥니다. 217 | if (buffer.peekc() != '{') 218 | throw new CompilerException 219 | ('cannot find start of compound statement', buffer.str); 220 | buffer.getc(); // 여는 중괄호를 지나갑니다. 221 | 222 | var Decl = Program.Compiler.Declaration; 223 | 224 | // 선언 리스트를 생성합니다. 225 | var decl_list = []; 226 | while (buffer.is_empty() == false) { // 버퍼에 데이터가 남아있는 동안 227 | var declInfo = Decl.getDeclarationInfo(buffer); // 선언 정보를 획득합니다. 228 | if (declInfo == null) // 선언 획득 실패 시 이후를 문장으로 간주합니다. 229 | break; 230 | decl_list.push(declInfo); // 선언 리스트에 선언 정보를 넣습니다. 231 | } 232 | 233 | // 문장 리스트를 생성합니다. 234 | var stmt_list = []; 235 | while (buffer.is_empty() == false) { // 버퍼에 데이터가 남아있는 동안 236 | var stmtInfo = getStatementInfo(buffer); // 문장 정보를 획득합니다. 237 | if (stmtInfo == null) // 문장 획득 실패 시 종료합니다. 238 | break; 239 | stmt_list.push(stmtInfo); // 문장 리스트에 문장 정보를 넣습니다. 240 | } 241 | 242 | buffer.trim(); // 다음 토큰의 시작 지점으로 버퍼 포인터를 맞춥니다. 243 | if (buffer.peekc() != '}') 244 | throw new CompilerException 245 | ('cannot find end of compound statement', buffer); 246 | buffer.getc(); // 닫는 중괄호를 지나갑니다. 247 | 248 | // 획득한 정보를 바탕으로 객체를 생성하고 반환합니다. 249 | var compoundInfo = new CompoundStatementInfo(decl_list, stmt_list); 250 | return compoundInfo; 251 | } 252 | /** 253 | 선택문을 분석합니다. 254 | @param {StringBuffer} buffer 255 | @return {SelectionStatementInfo} 256 | */ 257 | function getSelectionStatementInfo(buffer) { 258 | var Expr = Program.Compiler.Expression; 259 | 260 | var selectionStmt = null; // 선택문 객체에 대한 변수입니다. 261 | var token = buffer.get_ctoken(); // 첫 토큰을 획득합니다. 262 | if (token == 'if') { // if 문자열인 경우 263 | var selectionType = token; // 선택문의 형식은 if입니다. 264 | 265 | // 여는 소괄호를 발견하지 못하면 예외 처리합니다. 266 | if (buffer.get_ctoken() != '(') 267 | throw new CompilerException('cannot find start of conditional expression'); 268 | 269 | // 조건식을 획득합니다. 270 | var condExpr = Expr.getExpressionInfo(buffer); 271 | if (condExpr == null) 272 | throw new CompilerException('cannot find expression'); 273 | 274 | // 닫는 소괄호를 발견하지 못하면 예외 처리합니다. 275 | if (buffer.get_ctoken() != ')') 276 | throw new CompilerException('cannot find end of conditional expression'); 277 | 278 | // 조건식 이후에 나타나는 문장을 획득합니다. 279 | var trueStmt = getStatementInfo(buffer); 280 | if (trueStmt == null) 281 | throw new CompilerException('cannot find true case statement'); 282 | 283 | // else 구문이 존재하는지 확인합니다. 284 | var falseStmt = null; 285 | var prevIndex = buffer.idx; // 버퍼 포인터를 임시로 보관합니다. 286 | token = buffer.get_ctoken(); // 토큰 획득을 시도합니다. 287 | if (token == 'else') { // else인 경우의 처리입니다. 288 | // 거짓인 경우의 문장을 획득합니다. 289 | falseStmt = getStatementInfo(buffer); 290 | if (falseStmt == null) 291 | throw new CompilerException('cannot find false case statement'); 292 | } 293 | else { // else가 아니면 버퍼 포인터를 되돌립니다. 294 | buffer.idx = prevIndex; 295 | } 296 | 297 | // 획득한 정보를 바탕으로 객체를 생성합니다. 298 | selectionStmt = new SelectionStatementInfo 299 | (selectionType, condExpr, trueStmt, falseStmt); 300 | } 301 | else if (token == 'switch') { // switch 문자열인 경우 302 | var selectionType = token; // 선택문의 형식은 switch입니다. 303 | 304 | // 여는 소괄호를 발견하지 못하면 예외 처리합니다. 305 | if (buffer.get_ctoken() != '(') 306 | throw new CompilerException('cannot find start of conditional expression'); 307 | 308 | // 조건식을 획득합니다. 309 | var condExpr = Expr.getExpressionInfo(buffer); 310 | if (condExpr == null) 311 | throw new CompilerException('cannot find expression'); 312 | 313 | // 닫는 소괄호를 발견하지 못하면 예외 처리합니다. 314 | if (buffer.get_ctoken() != ')') 315 | throw new CompilerException('cannot find end of conditional expression'); 316 | 317 | // 조건식 이후에 나타나는 문장을 획득합니다. 318 | var trueStmt = getStatementInfo(buffer); 319 | if (trueStmt == null) 320 | throw new CompilerException('cannot find true case statement'); 321 | 322 | // 획득한 정보를 바탕으로 객체를 생성합니다. 323 | selectionStmt = new SelectionStatementInfo 324 | (selectionType, condExpr, trueStmt); 325 | } 326 | else { // 그 외의 경우 예외 처리합니다. 327 | throw new CompilerException 328 | ('invalid selection statement token', token); 329 | } 330 | 331 | // 생성한 객체를 반환합니다. 332 | return selectionStmt; 333 | } 334 | /** 335 | 반복문을 분석합니다. 336 | @param {StringBuffer} buffer 337 | @return {IterationStatementInfo} 338 | */ 339 | function getIterationStatementInfo(buffer) { 340 | var Expr = Program.Compiler.Expression; 341 | 342 | var iterStmt = null; // 반복문 객체에 대한 변수입니다. 343 | var token = buffer.get_ctoken(); // 첫 토큰을 획득합니다. 344 | if (token == 'while') { // while 문자열인 경우 345 | var iterationType = token; // 반복문의 형식은 while입니다. 346 | 347 | // 여는 소괄호를 발견하지 못하면 예외 처리합니다. 348 | if (buffer.get_ctoken() != '(') 349 | throw new CompilerException('cannot find start of conditional expression'); 350 | 351 | // 조건식을 획득합니다. 352 | var condExpr = Expr.getExpressionInfo(buffer); 353 | if (condExpr == null) 354 | throw new CompilerException('cannot find expression'); 355 | 356 | // 닫는 소괄호를 발견하지 못하면 예외 처리합니다. 357 | if (buffer.get_ctoken() != ')') 358 | throw new CompilerException('cannot find end of conditional expression'); 359 | 360 | // 조건식 이후에 나타나는 문장을 획득합니다. 361 | var trueStmt = getStatementInfo(buffer); 362 | if (trueStmt == null) 363 | throw new CompilerException('cannot find true case statement'); 364 | 365 | // 획득한 정보를 바탕으로 객체를 생성합니다. 366 | iterStmt = new IterationStatementInfo(iterationType, condExpr, trueStmt); 367 | } 368 | else if (token == 'do') { 369 | var iterationType = token; // 반복문의 형식은 do입니다. 370 | 371 | // 조건식 이후에 나타나는 문장을 획득합니다. 372 | var trueStmt = getStatementInfo(buffer); 373 | if (trueStmt == null) 374 | throw new CompilerException('cannot find true case statement'); 375 | 376 | // while 문자열을 발견하지 못하면 예외 처리합니다. 377 | if (buffer.get_ctoken() != 'while') 378 | throw new CompilerException 379 | ('cannot find keyword \'while\' in do-while statement'); 380 | 381 | // 여는 소괄호를 발견하지 못하면 예외 처리합니다. 382 | if (buffer.get_ctoken() != '(') 383 | throw new CompilerException('cannot find start of conditional expression'); 384 | 385 | // 조건식을 획득합니다. 386 | var condExpr = Expr.getExpressionInfo(buffer); 387 | if (condExpr == null) 388 | throw new CompilerException('cannot find expression'); 389 | 390 | // 닫는 소괄호를 발견하지 못하면 예외 처리합니다. 391 | if (buffer.get_ctoken() != ')') 392 | throw new CompilerException('cannot find end of conditional expression'); 393 | 394 | // 세미콜론을 발견하지 못하면 예외 처리합니다. 395 | // 닫는 소괄호를 발견하지 못하면 예외 처리합니다. 396 | if (buffer.get_ctoken() != ';') 397 | throw new CompilerException('cannot find end of do-while statement'); 398 | 399 | // 획득한 정보를 바탕으로 객체를 생성합니다. 400 | iterStmt = new IterationStatementInfo(iterationType, condExpr, trueStmt); 401 | } 402 | else if (token == 'for') { // 반복문의 형식은 for입니다. 403 | var iterationType = token; 404 | 405 | // 여는 소괄호를 발견하지 못하면 예외 처리합니다. 406 | if (buffer.get_ctoken() != '(') 407 | throw new CompilerException('cannot find start of conditional expression'); 408 | 409 | // 초기식을 획득합니다. 410 | var initExpr = Expr.getExpressionInfo(buffer); 411 | if (buffer.get_ctoken() != ';') // 세미콜론을 발견하지 못하면 예외 처리합니다. 412 | throw new CompilerException('cannot find end of initializer'); 413 | 414 | // 조건식을 획득합니다. 415 | var condExpr = Expr.getExpressionInfo(buffer); 416 | if (buffer.get_ctoken() != ';') // 세미콜론을 발견하지 못하면 예외 처리합니다. 417 | throw new CompilerException('cannot find end of condition'); 418 | 419 | // 증감식을 획득합니다. 420 | var iterExpr = Expr.getExpressionInfo(buffer); 421 | if (buffer.get_ctoken() != ')') // 닫는 소괄호를 발견하지 못하면 예외 처리합니다. 422 | throw new CompilerException('cannot find end of iterator'); 423 | 424 | // 조건식 이후에 나타나는 문장을 획득합니다. 425 | var trueStmt = getStatementInfo(buffer); 426 | if (trueStmt == null) 427 | throw new CompilerException('cannot find true case statement'); 428 | 429 | // 획득한 정보를 바탕으로 객체를 생성합니다. 430 | iterStmt = new IterationStatementInfo 431 | (iterationType, condExpr, trueStmt, initExpr, iterExpr); 432 | } 433 | else { // 그 외의 경우 예외 처리합니다. 434 | throw new CompilerException 435 | ('invalid iteration statement token', token); 436 | } 437 | 438 | return iterStmt; 439 | } 440 | /** 441 | 점프문을 분석합니다. 442 | @param {StringBuffer} buffer 443 | @return {JumpStatementInfo} 444 | */ 445 | function getJumpStatementInfo(buffer) { 446 | var Expr = Program.Compiler.Expression; 447 | 448 | var jumpStmt = null; // 반복문 객체에 대한 변수입니다. 449 | var token = buffer.get_ctoken(); // 첫 토큰을 획득합니다. 450 | if (token == 'goto') { // goto 문자열인 경우 451 | var jumpType = token; // 점프문의 형식은 goto입니다. 452 | var identifier = buffer.get_ctoken(); // 식별자를 획득합니다. 453 | if (identifier == null) // 식별자 획득에 실패한 경우 예외 처리합니다. 454 | throw new CompilerException 455 | ('cannot find identifier after goto'); 456 | 457 | // 세미콜론을 발견하지 못하면 예외 처리합니다. 458 | if (buffer.get_ctoken() != ';') 459 | throw new CompilerException('cannot find start of conditional expression'); 460 | 461 | // 획득한 정보를 바탕으로 객체를 생성합니다. 462 | jumpStmt = new JumpStatementInfo(jumpType, identifier); 463 | } 464 | else if (token == 'continue' || token == 'break') { 465 | var jumpType = token; // 점프문의 형식은 획득한 토큰입니다. 466 | if (buffer.get_ctoken() != ';') // 세미콜론을 발견하지 못하면 예외 처리합니다. 467 | throw new CompilerException('cannot find start of conditional expression'); 468 | // 획득한 정보를 바탕으로 객체를 생성합니다. 469 | jumpStmt = new JumpStatementInfo(jumpType); 470 | } 471 | else if (token == 'return') { 472 | var jumpType = token; // 점프문의 형식은 획득한 토큰입니다. 473 | // 수식을 획득합니다. 474 | var expression = Expr.getExpressionInfo(buffer); 475 | if (buffer.get_ctoken() != ';') // 세미콜론을 발견하지 못하면 예외 처리합니다. 476 | throw new CompilerException('cannot find start of conditional expression'); 477 | // 획득한 정보를 바탕으로 객체를 생성합니다. 478 | jumpStmt = new JumpStatementInfo(jumpType, expression); 479 | } 480 | // 생성한 점프문 객체를 반환합니다. 481 | return jumpStmt; 482 | } 483 | /** 484 | 레이블 문을 분석합니다. 485 | @param {StringBuffer} buffer 486 | @return {LabeledStatementInfo} 487 | */ 488 | function getLabeledStatementInfo(buffer) { 489 | var Expr = Program.Compiler.Expression; 490 | 491 | var labelStmt = null; // 반복문 객체에 대한 변수입니다. 492 | var token = buffer.get_ctoken(); // 첫 토큰을 획득합니다. 493 | if (token == 'case') { // case 문자열인 경우 494 | var labelType = token; // 점프문의 형식은 case입니다. 495 | 496 | // 수식을 획득합니다. 497 | var value = Expr.getExpressionInfo(buffer); 498 | if (value == null) 499 | throw new CompilerException 500 | ('cannot find expression after keyword case'); 501 | 502 | // 콜론을 발견하지 못하면 예외 처리합니다. 503 | if (buffer.get_ctoken() != ':') 504 | throw new CompilerException('cannot find start of conditional expression'); 505 | 506 | // 문장을 획득합니다. 507 | var statement = getStatementInfo(buffer); 508 | 509 | // 획득한 정보를 바탕으로 객체를 생성합니다. 510 | labelStmt = new LabeledStatementInfo(labelType, value, statement); 511 | } 512 | else if (token == 'default') { // default 문자열인 경우 513 | var labelType = token; // 점프문의 형식은 default입니다. 514 | 515 | // 콜론을 발견하지 못하면 예외 처리합니다. 516 | if (buffer.get_ctoken() != ':') 517 | throw new CompilerException('cannot find start of conditional expression'); 518 | 519 | // 문장을 획득합니다. 520 | var statement = getStatementInfo(buffer); 521 | 522 | // 획득한 정보를 바탕으로 객체를 생성합니다. 523 | labelStmt = new LabeledStatementInfo(labelType, null, statement); 524 | } 525 | else if (is_fnamch(token.charAt(0)) == false) { 526 | throw new CompilerException('invalid label found', token); 527 | } 528 | else { 529 | var labelName = token; // 획득한 토큰은 레이블의 이름입니다. 530 | 531 | // 콜론을 발견하지 못하면 예외 처리합니다. 532 | if (buffer.get_ctoken() != ':') 533 | throw new CompilerException('cannot find start of conditional expression'); 534 | 535 | var statement = getStatementInfo(buffer); // 문장을 획득합니다. 536 | 537 | // 획득한 정보를 바탕으로 객체를 생성합니다. 538 | labelStmt = new LabeledStatementInfo(null, labelName, statement); 539 | } 540 | // 생성한 객체를 반환합니다. 541 | return labelStmt; 542 | } 543 | 544 | // 등록 545 | _Statement.StatementInfo = StatementInfo; 546 | _Statement.ExpressionStatementInfo = ExpressionStatementInfo; 547 | _Statement.LabeledStatementInfo = LabeledStatementInfo; 548 | _Statement.IterationStatementInfo = IterationStatementInfo; 549 | _Statement.CompoundStatementInfo = CompoundStatementInfo; 550 | _Statement.JumpStatementInfo = JumpStatementInfo; 551 | 552 | _Statement.getCompoundStatementInfo = getCompoundStatementInfo; 553 | _Statement.getStatementInfo = getStatementInfo; 554 | compiler.Statement = _Statement; 555 | } -------------------------------------------------------------------------------- /ProgramCompilerExpression.js: -------------------------------------------------------------------------------- 1 | /** 2 | C의 수식을 분석합니다. 3 | */ 4 | function initProgramCompilerExpression(compiler, CompilerException) { 5 | var _Expression = {}; 6 | 7 | // 속성 정의 이전에 정의되어야 하는 내용을 작성합니다. 8 | var AssignmentOperatorDict = { 9 | "=": true, "+=": true, "-=": true, "*=":true, 10 | "/=":true, "%=": true, "&=": true, "^=": true, 11 | "|=": true, "<<=": true, ">>=": true 12 | }; 13 | var PrefixOperatorDict = { 14 | "sizeof": true, "++": true, "--": true, 15 | "+": true, "-": true, "*": true, 16 | "&": true, "~": true, "!": true 17 | }; 18 | var BinaryOperatorDict = { 19 | "*": 1, "/": 1, "%": 1, 20 | "+": 2, "-": 2, 21 | "<<": 3, ">>": 3, 22 | "<": 4, "<=": 4, ">": 4, ">=": 4, 23 | "==": 5, "!=": 5, 24 | "&": 6, 25 | "^": 7, 26 | "|": 8, 27 | "&&": 9, 28 | "||": 10 29 | }; 30 | 31 | // 형식 정의 32 | /** 33 | 수식을 정의합니다. 34 | @param {Array.} assignExprList 35 | */ 36 | function ExpressionInfo(assignExprList) { 37 | this.assignmentExprList = assignExprList; 38 | } 39 | ExpressionInfo.prototype.toString = function() { 40 | return Handy.format('%s', this.assignmentExprList); 41 | }; 42 | /** 43 | 할당식을 정의합니다. 44 | @param {Array} exprTokenList 45 | */ 46 | function AssignmentExpression(exprTokenList) { 47 | this.expressionTokenList = exprTokenList; 48 | } 49 | AssignmentExpression.prototype.toString = function() { 50 | return Handy.format('%s', this.expressionTokenList.join(' ')); 51 | }; 52 | /** 53 | 단항식을 정의합니다. 54 | @param {Array} exprTokenList 55 | */ 56 | function UnaryExpression(exprTokenList) { 57 | this.expressionTokenList = exprTokenList; 58 | } 59 | UnaryExpression.prototype.toString = function() { 60 | return Handy.format('%s', this.expressionTokenList); 61 | }; 62 | /** 63 | 캐스트를 정의합니다. 64 | @param {DeclarationSpecifier} declspec 65 | @param {AbstractDeclarator} absdecl 66 | */ 67 | function CastOperator(declspec, absdecl) { 68 | this.declarationSpecifier = declspec; 69 | this.abstractDeclarator = absdecl; 70 | } 71 | CastOperator.prototype.toString = function() { 72 | return Handy.format 73 | ('(%s%s)', this.declarationSpecifier, this.abstractDeclarator); 74 | }; 75 | /** 76 | 접미 수식을 정의합니다. 77 | @param {Array} exprTokenList 78 | */ 79 | function PostfixExpression(exprTokenList) { 80 | this.expressionTokenList = exprTokenList; 81 | } 82 | PostfixExpression.prototype.toString = function() { 83 | return Handy.format('%s', this.expressionTokenList); 84 | }; 85 | /** 86 | 기본 수식을 정의합니다. 87 | @param {object} value 88 | */ 89 | function PrimaryExpression(value) { 90 | this.value = value; 91 | } 92 | PrimaryExpression.prototype.toString = function() { 93 | return Handy.format('%s', this.value); 94 | }; 95 | /** 96 | 조건식을 정의합니다. 97 | @param {BinaryExpression} condExpr 98 | @param {Expression} trueExpr 99 | @param {ConditionalExpression} falseExpr 100 | */ 101 | function ConditionalExpression(condExpr, trueExpr, falseExpr) { 102 | this.conditionExpression = condExpr; 103 | this.trueExpression = trueExpr; 104 | this.falseExpression = falseExpr; 105 | } 106 | ConditionalExpression.prototype.toString = function() { 107 | var caseExpr = ''; 108 | if (this.trueExpression != undefined) { 109 | caseExpr = Handy.format('?%s:%s', this.trueExpression, this.falseExpression); 110 | } 111 | return Handy.format('%s%s', this.conditionExpression, caseExpr); 112 | }; 113 | /** 114 | 이항식을 정의합니다. 115 | @param {Array} exprTokenList 116 | */ 117 | function BinaryExpression(exprTokenList) { 118 | this.expressionTokenList = exprTokenList; 119 | } 120 | BinaryExpression.prototype.toString = function() { 121 | return Handy.format('%s', this.expressionTokenList.join(' ')); 122 | }; 123 | 124 | // 메서드 정의 125 | /** 126 | 수식을 획득합니다. 실패시 포인터를 복구하고 null을 반환합니다. 127 | @param {StringBuffer} buffer 128 | @return {ExpressionInfo} 129 | */ 130 | function getExpressionInfo(buffer) { 131 | // 수식 획득 전의 버퍼 포인터를 보관합니다. 132 | var originIndex = buffer.idx; 133 | 134 | // 반환할 수식 객체에 대한 변수를 생성합니다. 135 | var exprInfo = null; 136 | 137 | // 할당식 리스트를 획득합니다. 138 | var assignExprList = []; 139 | while (buffer.is_empty() == false) { // 버퍼에 데이터가 남아있는 동안 140 | // 할당식 객체를 획득합니다. 141 | var assignExprInfo = getAssignmentExpression(buffer); 142 | if (assignExprInfo == null) // 할당식 획득에 실패한 경우 143 | break; // 할당식 획득을 종료합니다. 144 | 145 | // 획득한 할당식을 리스트에 넣습니다. 146 | assignExprList.push(assignExprInfo); 147 | 148 | // 다음 토큰의 시작 지점으로 버퍼 포인터를 맞춥니다. 149 | buffer.trim(); 150 | // 다음 토큰이 반점이 아니라면 탈출합니다. 151 | if (buffer.peekc() != ',') 152 | break; 153 | // 사용한 반점 토큰은 지나갑니다. 154 | buffer.getc(); 155 | } 156 | // 획득한 할당식이 없으면 버퍼 포인터를 복구하고 null을 반환합니다. 157 | if (assignExprList.length == 0) { 158 | buffer.idx = originIndex; 159 | return null; 160 | } 161 | 162 | // 획득한 정보를 바탕으로 수식 객체를 생성합니다. 163 | exprInfo = new ExpressionInfo(assignExprList); 164 | // 수식 객체를 반환합니다. 165 | return exprInfo; 166 | } 167 | /** 168 | 할당식을 획득합니다. 169 | @param {StringBuffer} buffer 170 | @return {AssignmentExpression} 171 | */ 172 | function getAssignmentExpression(buffer) { 173 | var originIndex = buffer.idx; // 버퍼 포인터 위치를 보관합니다. 174 | var assignExpr = null; // 반환할 객체에 대한 변수를 생성합니다. 175 | 176 | // 할당식을 위한 수식 토큰 리스트를 만듭니다. 177 | var exprTokenList = []; 178 | 179 | while (buffer.is_empty() == false) { // 버퍼에 데이터가 남아있는 동안 180 | var prevIndex = buffer.idx; // 토큰 획득 이전의 버퍼 포인터를 보관합니다. 181 | var unaryExpr = getUnaryExpression(buffer); // 단항식 획득을 시도합니다. 182 | var assignOp = buffer.get_ctoken(buffer); // 다음 토큰을 획득합니다. 183 | if (unaryExpr && is_assign_op(assignOp)) { // 단항식과 할당 연산자 획득 성공 시 184 | // 획득한 요소를 리스트에 넣습니다. 185 | exprTokenList.push(unaryExpr); 186 | exprTokenList.push(assignOp); 187 | } 188 | else { // 실패한 경우 조건식을 획득하고 종료합니다. 189 | buffer.idx = prevIndex; // 단항식 획득 시도 전의 버퍼 포인터의 위치로 복구합니다. 190 | var condExpr = getConditionalExpression(buffer); // 조건식 획득을 시도합니다. 191 | if (condExpr == null) { // 조건식 획득에 실패했다면 192 | // 버퍼 포인터를 가장 처음의 위치로 복구하고 null을 반환합니다. 193 | buffer.idx = originIndex; 194 | return null; 195 | } 196 | exprTokenList.push(condExpr); // 조건식을 리스트에 넣습니다. 197 | break; // 반복문을 탈출합니다. 198 | } 199 | } 200 | 201 | // 획득한 정보를 바탕으로 객체를 생성하고 반환합니다. 202 | assignExpr = new AssignmentExpression(exprTokenList); 203 | return assignExpr; 204 | } 205 | /** 206 | 할당 연산자라면 true, 아니면 false를 반환합니다. 207 | @param {string} token 208 | @return {boolean} 209 | */ 210 | function is_assign_op(token) { 211 | return AssignmentOperatorDict[token] ? true : false; 212 | } 213 | /** 214 | 단항식을 획득합니다. 215 | @param {StringBuffer} buffer 216 | @return {UnaryExpression} 217 | */ 218 | function getUnaryExpression(buffer) { 219 | var originIndex = buffer.idx; // 최초 버퍼 포인터 위치를 보관합니다. 220 | var exprTokenList = []; // 단항식을 위한 수식 토큰 리스트를 만듭니다. 221 | 222 | while (buffer.is_empty() == false) { // 버퍼에 데이터가 남아있는 동안 223 | var prevIndex = buffer.idx; // 버퍼 포인터 위치를 보관합니다. 224 | var op = getPrefixOperator(buffer); // 전위 연산자 획득을 시도합니다. 225 | 226 | if (op == null) { // 토큰 획득 실패 시 접미 수식으로 간주합니다. 227 | buffer.idx = prevIndex; // 버퍼 포인터를 복구합니다. 228 | // 접미 수식을 획득합니다. 229 | var postExpr = getPostfixExpression(buffer); 230 | if (postExpr == null) { // 획득에 실패했다면 231 | // 버퍼 포인터를 최초 값으로 복구하고 null을 반환합니다. 232 | buffer.idx = originIndex; 233 | return null; 234 | } 235 | // 접미 수식을 수식 토큰 리스트에 넣고 종료합니다. 236 | exprTokenList.push(postExpr); 237 | break; 238 | } 239 | else if (op == 'sizeof') { // sizeof라면 240 | var cast = getCastOperator(buffer); // 캐스트 연산자 획득을 시도합니다. 241 | if (cast != null) { // 획득에 성공했다면 242 | // 정의에 의해 토큰 획득을 끝냅니다. 243 | exprTokenList.push(op); 244 | exprTokenList.push(cast); 245 | break; 246 | } 247 | // 단항식을 계속 획득합니다. 248 | exprTokenList.push(op); 249 | } 250 | else { // 그 외의 경우 전위 연산자로 처리합니다. 251 | exprTokenList.push(op); 252 | } 253 | } 254 | 255 | // 획득한 정보를 바탕으로 객체를 생성하고 반환합니다. 256 | var unaryExpr = new UnaryExpression(exprTokenList); 257 | return unaryExpr; 258 | } 259 | /** 260 | 전위 연산자를 획득합니다. 실패시 포인터를 복구하고 null을 반환합니다. 261 | @param {StringBuffer} buffer 262 | */ 263 | function getPrefixOperator(buffer) { 264 | var originIndex = buffer.idx; // 최초 버퍼 포인터를 보관합니다. 265 | var token = buffer.get_ctoken(); // 토큰을 획득합니다. 266 | 267 | // 일반 단항 연산자인 경우 그냥 반환합니다. 268 | if (is_prefix_op(token)) 269 | return token; 270 | else if (token == '(') { // 캐스트 연산의 시작인 경우 271 | var castOperator = getCastOperator(buffer); // 캐스트를 획득합니다. 272 | if (castOperator == null) { // 캐스트 연산자 획득에 실패한 경우 273 | // 버퍼 포인터를 복구하고 null을 반환합니다. 274 | buffer.idx = originIndex; 275 | return null; 276 | } 277 | 278 | // 다음 토큰이 닫는 소괄호라면 성공한 것으로 간주합니다. 279 | if (buffer.get_ctoken() == ')') 280 | return castOperator; 281 | } 282 | 283 | buffer.idx = originIndex; 284 | return null; 285 | } 286 | /** 287 | 전위 연산자라면 true, 아니면 false를 반환합니다. 288 | @param {string} token 289 | @return {boolean} 290 | */ 291 | function is_prefix_op(token) { 292 | return PrefixOperatorDict[token] ? true : false; 293 | } 294 | /** 295 | 캐스트 연산자를 획득합니다. 실패시 포인터를 복구하고 null을 반환합니다. 296 | @param {StringBuffer} buffer 297 | */ 298 | function getCastOperator(buffer) { 299 | var Declaration = Program.Compiler.Declaration; 300 | var originIndex = buffer.idx; // 최초 버퍼 포인터를 보관합니다. 301 | 302 | try { 303 | // 선언 지정자를 획득합니다. 304 | var declspec = Declaration.getDeclarationSpecifier(buffer); 305 | if (declspec == null) // 획득에 실패한 경우 null을 반환합니다. 306 | throw new CompilerException 307 | ('cannot find declaration specifiers'); 308 | // 캐스트 연산자에는 선언 지정자가 있을 수 없습니다. 309 | else if (declspec.storageClassSpecifier != null) 310 | throw new CompilerException 311 | ('storage class specifier found in cast operator'); 312 | 313 | // 추상 선언자를 획득합니다. 314 | var absdecl = Declaration.getAbstractDeclarator(buffer); 315 | // 캐스트 연산자에서는 식별자가 발견되면 안 됩니다. 316 | if (absdecl.identifier != null) 317 | throw new CompilerException 318 | ('identifier found in cast operator'); 319 | 320 | // 획득한 정보를 바탕으로 캐스트 연산자 객체를 생성하여 반환합니다. 321 | var castOperator = new CastOperator(declspec, absdecl); 322 | return castOperator; 323 | } catch (ex) { 324 | // 버퍼 포인터를 복구하고 null을 반환합니다. 325 | if (ex instanceof CompilerException) { 326 | buffer.idx = originIndex; 327 | return null; 328 | } 329 | throw ex; 330 | } 331 | } 332 | /** 333 | 접미 수식을 획득합니다. 실패시 포인터를 복구하고 null을 반환합니다. 334 | @param {StringBuffer} buffer 335 | */ 336 | function getPostfixExpression(buffer) { 337 | var originIndex = buffer.idx; // 최초 버퍼 포인터를 보관합니다. 338 | try { 339 | // 수식 토큰 리스트를 생성합니다. 340 | var exprTokenList = []; 341 | // 기본 수식을 획득합니다. 342 | var primaryExpr = getPrimaryExpression(buffer); 343 | if (primaryExpr == null) // 기본 수식 획득 실패 시 예외 처리합니다. 344 | throw new CompilerException 345 | ('cannot find primary expression'); 346 | 347 | // 수식 토큰 리스트에 기본 수식을 넣습니다. 348 | exprTokenList.push(primaryExpr); 349 | // 획득한 정보를 바탕으로 기본 수식 객체를 생성하고 반환합니다. 350 | var postExpr = new PostfixExpression(exprTokenList); 351 | return postExpr; 352 | } catch (ex) { // 실패시 포인터를 복구하고 null을 반환합니다. 353 | buffer.idx = originIndex; 354 | return null; 355 | } 356 | } 357 | /** 358 | 기본 수식을 획득합니다. 실패시 포인터를 복구하고 null을 반환합니다. 359 | @param {StringBuffer} buffer 360 | */ 361 | function getPrimaryExpression(buffer) { 362 | var originIndex = buffer.idx; // 최초 버퍼 포인터를 보관합니다. 363 | try { 364 | var primaryExpr = null; // 기본 수식에 대한 변수입니다. 365 | var token = buffer.get_ctoken(); // 토큰을 획득합니다. 366 | var ch = token.charAt(0); // 토큰의 첫 문자를 획득합니다. 367 | 368 | // 여는 소괄호라면 4번 정의에 해당합니다. 369 | if (token == '(') { 370 | var expr = getExpressionInfo(buffer); // 수식을 획득합니다. 371 | if (buffer.get_ctoken() != ')') // 닫는 소괄호가 없으면 372 | throw new CompilerException // 예외 처리합니다. 373 | ('cannot find small close bracket'); 374 | 375 | // 기본 수식 객체를 생성합니다. 376 | primaryExpr = new PrimaryExpression(expr); 377 | } 378 | // 기본 수식이라면 기본 수식 객체를 생성합니다. 379 | else if (is_fnamch(ch) || is_digit(ch) 380 | || ch == '\'' || ch == '\"') { 381 | primaryExpr = new PrimaryExpression(token); 382 | } 383 | else { 384 | throw new CompilerException 385 | ('invalid token found in primary expression'); 386 | } 387 | return primaryExpr; 388 | } catch (ex) { // 실패시 포인터를 복구하고 null을 반환합니다. 389 | if (ex instanceof CompilerException) { 390 | buffer.idx = originIndex; 391 | return null; 392 | } 393 | throw ex; 394 | } 395 | } 396 | /** 397 | 조건식을 획득합니다. 실패시 포인터를 복구하고 null을 반환합니다. 398 | @param {StringBuffer} buffer 399 | @return {ConditionalExpression} 400 | */ 401 | function getConditionalExpression(buffer) { 402 | var originIndex = buffer.idx; // 초기 버퍼 포인터를 보관합니다. 403 | 404 | try { 405 | // 이항 수식을 획득합니다. 406 | var binaryExpr = getBinaryExpression(buffer); 407 | 408 | // TODO: 409 | if (binaryExpr == null) 410 | throw new CompilerException 411 | ('cannot find binary expression in conditional'); 412 | 413 | buffer.trim(); // 다음 토큰의 시작 지점으로 버퍼 포인터를 옮깁니다. 414 | 415 | // 물음표 토큰이 존재한다면 식을 두 개 획득합니다. 416 | if (buffer.peekc() == '?') { 417 | buffer.getc(); // 분석한 물음표 기호를 지나갑니다. 418 | 419 | // 참일 경우의 식을 획득합니다. 420 | var trueExpr = getExpressionInfo(buffer); 421 | if (buffer.get_ctoken() != ':') // 조건식의 2번 정의에 맞지 않으면 422 | throw new CompilerException // 예외 처리합니다. 423 | ('cannot find colon of conditional expression'); 424 | 425 | // 거짓일 경우의 식을 획득합니다. 426 | var falseExpr = getConditionalExpression(buffer); 427 | 428 | // 획득한 식 중 하나라도 잘못된 식이라면 예외 처리합니다. 429 | if (trueExpr == null || falseExpr == null) 430 | throw new CompilerException 431 | ('empty expression found in conditional expression'); 432 | 433 | // 획득한 정보를 바탕으로 조건식을 생성합니다. 434 | var condExpr = new ConditionalExpression 435 | (binaryExpr, trueExpr, falseExpr); 436 | } 437 | else { 438 | // 획득한 정보를 바탕으로 조건식을 생성합니다. 439 | var condExpr = new ConditionalExpression(binaryExpr); 440 | } 441 | 442 | // 생성한 조건식 객체를 반환합니다. 443 | return condExpr; 444 | } catch (ex) { // 실패시 포인터를 복구하고 null을 반환한다. 445 | if (ex instanceof CompilerException) { 446 | buffer.idx = originIndex; 447 | return null; 448 | } 449 | throw ex; 450 | } 451 | } 452 | /** 453 | 이항 수식을 획득합니다. 실패시 포인터를 복구하고 null을 반환합니다. 454 | @param {StringBuffer} buffer 455 | @return {BinaryExpression} 456 | */ 457 | function getBinaryExpression(buffer) { 458 | var originIndex = buffer.idx; // 최초 버퍼 포인터를 보관합니다. 459 | try { 460 | // 수식 토큰 리스트를 생성합니다. 461 | var exprTokenList = []; 462 | 463 | // 버퍼에 데이터가 남아있는 동안 464 | while (buffer.is_empty() == false) { 465 | var prevIndex = buffer.idx; // 이전 버퍼 포인터를 보관합니다. 466 | 467 | // 단항식을 획득합니다. 468 | var unaryExpr = getUnaryExpression(buffer); 469 | if (unaryExpr == null) { // 단항식 획득에 실패한 경우 470 | // 버퍼 포인터를 복구하고 반복문을 탈출합니다. 471 | buffer.idx = prevIndex; 472 | break; 473 | } 474 | // 획득한 단항식을 수식 토큰 리스트에 넣습니다. 475 | exprTokenList.push(unaryExpr); 476 | 477 | // 버퍼 포인터를 보관하고 다음 토큰을 획득합니다. 478 | prevIndex = buffer.idx; 479 | var token = buffer.get_ctoken(); 480 | 481 | // 획득한 토큰이 이항 연산자가 아니라면 482 | if (is_binary_op(token) == false) { 483 | //버퍼 포인터를 복구하고 반복문을 탈출합니다. 484 | buffer.idx = prevIndex; 485 | break; 486 | } 487 | // 획득한 이항 연산자를 수식 토큰 리스트에 넣습니다. 488 | exprTokenList.push(token); 489 | } 490 | 491 | // 획득한 토큰이 없는 경우 예외 처리합니다. 492 | if (exprTokenList.length == 0) 493 | throw new CompilerException 494 | ('cannot find binary expression'); 495 | 496 | // 이항 연산자에 대한 스택을 생성합니다. 497 | var opStack = []; 498 | var postfixTokenList = []; 499 | for (var i=0, len=exprTokenList.length; i 0) { 510 | // 가장 최근에 추가한 연산자를 획득합니다. 511 | var prevOp = opStack[opStack.length-1]; 512 | // 새 연산자의 우선순위를 구합니다. 513 | var newPri = BinaryOperatorDict[token]; 514 | 515 | // 새 연산자의 우선순위가 더 낮다면 탈출합니다. 516 | if (newPri < BinaryOperatorDict[prevOp]) 517 | break; 518 | 519 | // 우선순위가 낮은 연산자를 빼서 수식 토큰 리스트에 넣습니다. 520 | postfixTokenList.push(opStack.pop()); 521 | } 522 | 523 | // 획득한 이항 연산자를 수식 토큰 리스트에 넣습니다. 524 | opStack.push(token); 525 | } 526 | } 527 | // 연산자 스택에 남은 연산자를 모두 출력합니다. 528 | while (opStack.length > 0) { 529 | postfixTokenList.push(opStack.pop()); 530 | } 531 | 532 | // 획득한 정보를 바탕으로 이항 수식 객체를 생성하고 반환합니다. 533 | var binaryExpr = new BinaryExpression(postfixTokenList); 534 | return binaryExpr; 535 | } catch (ex) { // 실패시 버퍼 퐁니터를 복구하고 null을 반환합니다. 536 | if (ex instanceof CompilerException) { 537 | buffer.idx = originIndex; 538 | return null; 539 | } 540 | throw ex; 541 | } 542 | } 543 | /** 544 | 이항 연산자라면 true, 아니면 false를 반환합니다. 545 | @param {string} token 546 | @return {boolean} 547 | */ 548 | function is_binary_op(token) { 549 | return BinaryOperatorDict[token] ? true : false; 550 | } 551 | // 수식 객체의 컴파일을 수행합니다. 552 | function ExpressionInfo_compile(dataseg, codeseg, identifierDict) { 553 | // 수식 객체는 할당식의 리스트입니다. 554 | for (var i=0, len=this.assignmentExprList.length; i 0) { 580 | // 할당 연산자를 획득합니다. 581 | var op = exprTokenList[--index]; 582 | 583 | // 피연산자를 획득하고 컴파일 합니다. 584 | var lExpr = exprTokenList[--index]; 585 | lExpr.compile(dataseg, codeseg, identifierDict); 586 | 587 | // 보관했던 우변식의 값을 꺼내어 계산에 사용합니다. 588 | codeseg.writeln('pop eax'); 589 | 590 | // 할당 연산자가 발견된 경우의 처리입니다. 591 | switch (op) { 592 | case '=': 593 | codeseg.writeln('mov [ebx], eax'); 594 | break; 595 | } 596 | } 597 | } 598 | AssignmentExpression.prototype.compile = AssignExpr_compile; 599 | // 조건식 객체의 컴파일을 수행합니다. 600 | function CondExpr_compile(dataseg, codeseg, identifierDict) { 601 | // conditionExpression은 ExpressionInfo 객체입니다. 602 | this.conditionExpression.compile(dataseg, codeseg, identifierDict); 603 | 604 | // 삼항 연산자인 경우입니다. 직접 구현해보십시오. 605 | if (this.trueExpression != null) { 606 | throw new CompilerException('not implemented'); 607 | } 608 | } 609 | ConditionalExpression.prototype.compile = CondExpr_compile; 610 | // 이항식 객체의 컴파일을 수행합니다. 611 | function BinaryExpr_compile(dataseg, codeseg, identifierDict) { 612 | var exprTokenList = this.expressionTokenList; 613 | 614 | // 이항식의 1번 정의에 해당한다면 그냥 컴파일 합니다. 615 | if (exprTokenList.length == 1) { 616 | var token = exprTokenList[0]; 617 | token.compile(dataseg, codeseg, identifierDict); 618 | } 619 | // 이항식의 2번 정의에 해당하는 경우입니다. 620 | else { // 1장에서 작성한 calculate_postfix의 코드와 비슷합니다. 621 | // 피연산자 스택을 생성합니다. 622 | var operandStack = []; 623 | 624 | // 수식 토큰 리스트의 모든 토큰을 분석합니다. 625 | for (var i=0, len=exprTokenList.length; i 0) ? '+' + offset : offset; 720 | } 721 | 722 | // 구한 오프셋을 이용하여 변수 위치를 획득하는 코드입니다. 723 | codeseg.writeln('lea ebx, [ebp%s]', offsetString); 724 | codeseg.writeln('mov eax, [ebx]'); 725 | } 726 | else if (is_digit(ch)) { // 상수라면 그냥 기록합니다. 727 | codeseg.writeln('mov eax, %s', this.value); 728 | } 729 | else if (ch == '\'' || ch == '\"') { // 직접 작성해보십시오. 730 | throw new CompilerException('not implemented'); 731 | } 732 | else { // 그 외의 경우 예외 처리합니다. 733 | throw new CompilerException 734 | ('invalid primary expression value'); 735 | } 736 | } 737 | PrimaryExpression.prototype.compile = PrimaryExpr_compile; 738 | 739 | // 등록 740 | _Expression.getExpressionInfo = getExpressionInfo; 741 | compiler.Expression = _Expression; 742 | } -------------------------------------------------------------------------------- /ProgramLinker.js: -------------------------------------------------------------------------------- 1 | /** 2 | 실행 가능한 목적 파일을 생성합니다. 3 | */ 4 | function initProgramLinker(program) { 5 | var linker = {}; 6 | 7 | // 속성 정의 이전에 정의해야 하는 내용 작성 8 | /** 9 | Linker 모듈의 메서드를 수행할 때 발생하는 예외를 정의합니다. 10 | @param {string} msg 11 | */ 12 | function LinkerException(msg, data) { 13 | this.description = msg; 14 | this.data = data; 15 | } 16 | LinkerException.prototype = new Exception(); 17 | LinkerException.prototype.toString = function() { 18 | return 'Linker' + Exception.prototype.toString.call(this); 19 | }; 20 | 21 | /** 22 | 목적 파일의 정보를 보관하는 객체 형식을 정의합니다. 23 | */ 24 | function ObjectInfo(segment, labelInfoDict, sizeOfData, sizeOfCode) { 25 | this.segment = segment; 26 | this.labelInfoDict = labelInfoDict; 27 | this.sizeOfData = sizeOfData; 28 | this.sizeOfCode = sizeOfCode; 29 | } 30 | 31 | /** 32 | 레이블 정보를 표현하는 형식 LabelInfo를 정의합니다. 33 | @param {string} segmentName 34 | @param {string} name 35 | */ 36 | function LabelInfo(segmentName, name) { 37 | this.segmentName = segmentName; 38 | this.name = name; 39 | this.offset = 0; 40 | this.refered = []; 41 | } 42 | /** 43 | 인자가 레이블 이름인지 판정합니다. 44 | @param {string} param 45 | @return {boolean} 46 | */ 47 | function is_label_name(param) { 48 | return (param.charAt(0) == '_'); 49 | } 50 | 51 | /** 52 | 전역 레이블 정보를 표현하는 형식 GlobalLabelInfo를 정의합니다. 53 | @param {number} objectIndex 54 | @param {string} segmentName 55 | @param {string} name 56 | */ 57 | function GlobalLabelInfo(objectIndex, segmentName, name) { 58 | this.segmentName = segmentName; 59 | this.name = name; 60 | this.index = objectIndex; // 레이블이 정의된 목적 파일의 인덱스를 보관합니다. 61 | this.offset = -1; // 목적 파일의 인덱스로부터 세그먼트의 시작 위치를 계산합니다. 62 | this.refered = []; // 이 레이블을 참조하는 목적 파일의 인덱스도 보관해야 합니다. 63 | } 64 | 65 | /** 66 | 데이터 타입의 크기를 반환합니다. db이면 1을 반환하는 식입니다. 67 | @param {string} datatype 68 | @return {number} 69 | */ 70 | function getDataTypeSize(datatype) { 71 | switch (datatype.toLowerCase()) { // 소문자 문자열로 변경하고 확인합니다. 72 | case 'db': 73 | case 'byte': 74 | return 1; 75 | case 'dw': 76 | case 'word': 77 | return 2; 78 | case 'dd': 79 | case 'dword': 80 | return 4; 81 | } 82 | } 83 | /** 84 | 코드를 분석합니다. 85 | @param {string} line 86 | @return {InstructionInfo} 87 | */ 88 | function decode(line) { 89 | // StringBuffer 객체를 생성하고 line으로 초기화합니다. 90 | var buffer = new StringBuffer(line); 91 | 92 | // 가장 처음에 획득하는 단어는 반드시 니모닉입니다. 93 | var mne = buffer.get_token(); 94 | // 니모닉 획득에 실패한 경우 예외 처리합니다. 95 | if (is_fnamch(mne) == false) 96 | throw new LinkerException('invalid mnemonic'); 97 | 98 | // 다음 토큰 획득을 시도합니다. 99 | var left = buffer.get_token(); 100 | 101 | var right = null; 102 | if (left != null) { // 다음 토큰이 있는 경우의 처리 103 | // 피연산자가 두 개인지 확인하기 위해 토큰 획득을 시도합니다. 104 | right = buffer.get_token(); 105 | 106 | if (right != null) { // 다음 토큰이 있는 경우 107 | if (right != ',') { // 반점이 아니라면 HASM 문법 위반입니다. 108 | log('decode.mne/left/right: [%s/%s/%s]', mne, left, right); 109 | throw new LinkerException 110 | ('syntax error; right operand must be after comma(,)'); 111 | } 112 | 113 | // 오른쪽 피연산자 획득 114 | right = buffer.get_token(); 115 | if (right == null) // 획득 실패 시 문법을 위반한 것으로 간주합니다. 116 | throw new LinkerException 117 | ('syntax error; cannot find right operand'); 118 | } 119 | 120 | // 다음 토큰이 없다면 right는 null이고 121 | // 그렇지 않으면 다음 토큰 문자열이 된다 122 | } 123 | 124 | // 획득한 코드 정보를 담는 객체를 생성하고 반환합니다. 125 | var info = { mnemonic: mne, left: left, right: right }; 126 | return info; 127 | } 128 | /** 129 | 전역 레이블이라면 true, 아니면 false입니다. 130 | @param {string} label 131 | @return {boolean} 132 | */ 133 | function is_public(label) { 134 | return (Program.Linker.GlobalLabelDict[label] != undefined); 135 | } 136 | 137 | /** 138 | 테스트 메서드입니다. 139 | */ 140 | function test(filename) { 141 | var Linker = Program.Linker; 142 | var objectList = Linker.ObjectInfoList; 143 | 144 | for (var j=0, objectCount=objectList.length; j local labels'); 153 | for (label in objectInfo.labelInfoDict) { 154 | var info = objectInfo.labelInfoDict[label]; 155 | ss.write('%s %s: %04x [ ', info.segmentName, info.name, info.offset); 156 | for (var i=0, len=info.refered.length; i data segment [%d(%04x)]', sizeOfData, sizeOfData); 165 | for (var i=0, len=objectInfo.segment.data.length; i code segment [%d(%04x)]', sizeOfCode, sizeOfCode); 171 | for (var i=0, len=objectInfo.segment.code.length; i= 0) { 584 | gLabelInfo.index = info.index; 585 | gLabelInfo.offset = info.offset; 586 | } 587 | 588 | // 참조 위치 배열에 참조 위치를 추가합니다. 589 | var ref = info.refered; 590 | for (var i=0, len=ref.length; i