├── .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