kokoscript 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env node
2
+
3
+ // KokoScript REPL (Read-Eval-Print Loop)
4
+
5
+ const readline = require('readline');
6
+ const { compile } = require('../compiler');
7
+
8
+ function startRepl() {
9
+ const rl = readline.createInterface({
10
+ input: process.stdin,
11
+ output: process.stdout,
12
+ prompt: 'KokoScript> '
13
+ });
14
+
15
+ console.log('KokoScript REPL v1.0.0');
16
+ console.log('終了するには .exit と入力してください');
17
+ console.log('');
18
+
19
+ let buffer = '';
20
+ let context = {};
21
+
22
+ rl.prompt();
23
+
24
+ rl.on('line', (line) => {
25
+ line = line.trim();
26
+
27
+ if (line === '.exit') {
28
+ console.log('さようなら!');
29
+ rl.close();
30
+ return;
31
+ }
32
+
33
+ if (line === '.clear') {
34
+ buffer = '';
35
+ context = {};
36
+ console.log('バッファとコンテキストをクリアしました');
37
+ rl.prompt();
38
+ return;
39
+ }
40
+
41
+ if (line === '.help') {
42
+ console.log('コマンド:');
43
+ console.log(' .exit - REPLを終了');
44
+ console.log(' .clear - バッファとコンテキストをクリア');
45
+ console.log(' .help - このヘルプを表示');
46
+ rl.prompt();
47
+ return;
48
+ }
49
+
50
+ if (line === '') {
51
+ rl.prompt();
52
+ return;
53
+ }
54
+
55
+ buffer += line + '\n';
56
+
57
+ // 「終わり」で終わる場合、または単一文の場合は実行
58
+ if (line.includes('終わり') ||
59
+ (!line.includes('ならば') && !line.includes('繰り返す') && !line.includes('定義する'))) {
60
+
61
+ const result = compile(buffer);
62
+
63
+ if (result.success) {
64
+ try {
65
+ // コンテキストを保持しながら実行
66
+ const wrappedCode = `
67
+ (function() {
68
+ with (context) {
69
+ ${result.code}
70
+ }
71
+ })();
72
+ `;
73
+ eval(wrappedCode);
74
+ } catch (error) {
75
+ console.error('実行エラー:', error.message);
76
+ }
77
+ } else {
78
+ console.error('コンパイルエラー:', result.error);
79
+ }
80
+
81
+ buffer = '';
82
+ }
83
+
84
+ rl.prompt();
85
+ }).on('close', () => {
86
+ console.log('\nまた起動してね。');
87
+ process.exit(0);
88
+ });
89
+ }
90
+
91
+ if (require.main === module) {
92
+ startRepl();
93
+ }
94
+
95
+ module.exports = { startRepl };
@@ -0,0 +1,188 @@
1
+ // コードジェネレーター(JavaScriptへの変換)
2
+
3
+ class CodeGenerator {
4
+ constructor(ast) {
5
+ this.ast = ast;
6
+ this.output = '';
7
+ this.indent = 0;
8
+ }
9
+
10
+ generate() {
11
+ this.visitProgram(this.ast);
12
+ return this.output;
13
+ }
14
+
15
+ visitProgram(node) {
16
+ for (const statement of node.statements) {
17
+ this.visitStatement(statement);
18
+ }
19
+ }
20
+
21
+ visitStatement(node) {
22
+ switch (node.type) {
23
+ case 'VariableDeclaration':
24
+ this.visitVariableDeclaration(node);
25
+ break;
26
+ case 'IfStatement':
27
+ this.visitIfStatement(node);
28
+ break;
29
+ case 'RepeatStatement':
30
+ this.visitRepeatStatement(node);
31
+ break;
32
+ case 'FunctionDeclaration':
33
+ this.visitFunctionDeclaration(node);
34
+ break;
35
+ case 'CallExpression':
36
+ this.visitCallExpression(node);
37
+ this.emit(';\n');
38
+ break;
39
+ case 'Assignment':
40
+ this.visitAssignment(node);
41
+ break;
42
+ case 'ReturnStatement':
43
+ this.visitReturnStatement(node);
44
+ break;
45
+ case 'DisplayStatement':
46
+ this.visitDisplayStatement(node);
47
+ break;
48
+ }
49
+ }
50
+
51
+ visitVariableDeclaration(node) {
52
+ this.emit('let ');
53
+ this.emit(node.name);
54
+ this.emit(' = ');
55
+ this.visitExpression(node.value);
56
+ this.emit(';\n');
57
+ }
58
+
59
+ visitIfStatement(node) {
60
+ this.emit('if (');
61
+ this.visitExpression(node.condition);
62
+ this.emit(') {\n');
63
+
64
+ this.indent++;
65
+ for (const stmt of node.consequent) {
66
+ this.emitIndent();
67
+ this.visitStatement(stmt);
68
+ }
69
+ this.indent--;
70
+
71
+ if (node.alternate && node.alternate.length > 0) {
72
+ this.emitIndent();
73
+ this.emit('} else {\n');
74
+ this.indent++;
75
+ for (const stmt of node.alternate) {
76
+ this.emitIndent();
77
+ this.visitStatement(stmt);
78
+ }
79
+ this.indent--;
80
+ }
81
+
82
+ this.emitIndent();
83
+ this.emit('}\n');
84
+ }
85
+
86
+ visitRepeatStatement(node) {
87
+ this.emit('for (let _i = 0; _i < ');
88
+ this.emit(node.count.toString());
89
+ this.emit('; _i++) {\n');
90
+
91
+ this.indent++;
92
+ for (const stmt of node.body) {
93
+ this.emitIndent();
94
+ this.visitStatement(stmt);
95
+ }
96
+ this.indent--;
97
+
98
+ this.emitIndent();
99
+ this.emit('}\n');
100
+ }
101
+
102
+ visitFunctionDeclaration(node) {
103
+ this.emit('function ');
104
+ this.emit(node.name);
105
+ this.emit('(');
106
+ this.emit(node.params.join(', '));
107
+ this.emit(') {\n');
108
+
109
+ this.indent++;
110
+ for (const stmt of node.body) {
111
+ this.emitIndent();
112
+ this.visitStatement(stmt);
113
+ }
114
+ this.indent--;
115
+
116
+ this.emitIndent();
117
+ this.emit('}\n');
118
+ }
119
+
120
+ visitCallExpression(node) {
121
+ this.emit(node.name);
122
+ this.emit('(');
123
+ for (let i = 0; i < node.args.length; i++) {
124
+ this.visitExpression(node.args[i]);
125
+ if (i < node.args.length - 1) {
126
+ this.emit(', ');
127
+ }
128
+ }
129
+ this.emit(')');
130
+ }
131
+
132
+ visitAssignment(node) {
133
+ this.emit(node.name);
134
+ this.emit(' = ');
135
+ this.visitExpression(node.value);
136
+ this.emit(';\n');
137
+ }
138
+
139
+ visitReturnStatement(node) {
140
+ this.emit('return ');
141
+ this.visitExpression(node.value);
142
+ this.emit(';\n');
143
+ }
144
+
145
+ visitDisplayStatement(node) {
146
+ this.emit('console.log(');
147
+ for (let i = 0; i < node.expressions.length; i++) {
148
+ this.visitExpression(node.expressions[i]);
149
+ if (i < node.expressions.length - 1) {
150
+ this.emit(' + ');
151
+ }
152
+ }
153
+ this.emit(');\n');
154
+ }
155
+
156
+ visitExpression(node) {
157
+ switch (node.type) {
158
+ case 'Literal':
159
+ if (typeof node.value === 'string') {
160
+ this.emit(`"${node.value}"`);
161
+ } else {
162
+ this.emit(String(node.value));
163
+ }
164
+ break;
165
+ case 'Identifier':
166
+ this.emit(node.name);
167
+ break;
168
+ case 'BinaryExpression':
169
+ this.visitExpression(node.left);
170
+ this.emit(' ' + node.operator + ' ');
171
+ this.visitExpression(node.right);
172
+ break;
173
+ case 'CallExpression':
174
+ this.visitCallExpression(node);
175
+ break;
176
+ }
177
+ }
178
+
179
+ emit(code) {
180
+ this.output += code;
181
+ }
182
+
183
+ emitIndent() {
184
+ this.output += ' '.repeat(this.indent);
185
+ }
186
+ }
187
+
188
+ module.exports = { CodeGenerator };
@@ -0,0 +1,27 @@
1
+ const { Lexer } = require('./lexer');
2
+ const { Parser } = require('./parser');
3
+ const { CodeGenerator } = require('./codegen');
4
+
5
+ function compile(source) {
6
+ try {
7
+ const lexer = new Lexer(source);
8
+ const tokens = lexer.tokenize();
9
+
10
+ const parser = new Parser(tokens);
11
+ const ast = parser.parse();
12
+
13
+ const codegen = new CodeGenerator(ast);
14
+ const jsCode = codegen.generate();
15
+
16
+ return { success: true, code: jsCode, ast };
17
+ } catch (error) {
18
+ return { success: false, error: error.message };
19
+ }
20
+ }
21
+
22
+ module.exports = {
23
+ compile,
24
+ Lexer,
25
+ Parser,
26
+ CodeGenerator
27
+ };
@@ -0,0 +1,306 @@
1
+ // レキサー(字句解析器) - 句読点ベース
2
+
3
+ class Token {
4
+ constructor(type, value, line, col) {
5
+ this.type = type;
6
+ this.value = value;
7
+ this.line = line;
8
+ this.col = col;
9
+ }
10
+ }
11
+
12
+ const TOKEN_TYPES = {
13
+ KEYWORD: 'KEYWORD',
14
+ IDENTIFIER: 'IDENTIFIER',
15
+ NUMBER: 'NUMBER',
16
+ STRING: 'STRING',
17
+ OPERATOR: 'OPERATOR',
18
+ COMMA: 'COMMA', // 、
19
+ PERIOD: 'PERIOD', // 。
20
+ NEWLINE: 'NEWLINE',
21
+ EOF: 'EOF'
22
+ };
23
+
24
+ // キーワード(句読点の前後で区切られる)
25
+ const KEYWORDS = new Set([
26
+ '変数', 'は', 'を', 'に', 'が', 'と', 'で', 'の', 'から',
27
+ 'もし', 'ならば', 'そうでなければ', '終わり',
28
+ '回', '繰り返す', '抜ける', '続ける',
29
+ '関数', '返す', '呼ぶ',
30
+ 'より', '大きい', '小さい', '等しい', '以上', '以下',
31
+ '表示', '渡す', '足す', '引く', '掛ける', '割る',
32
+ '真', '偽', '空'
33
+ ]);
34
+
35
+ class Lexer {
36
+ constructor(source) {
37
+ this.source = source;
38
+ this.pos = 0;
39
+ this.line = 1;
40
+ this.col = 1;
41
+ this.tokens = [];
42
+ }
43
+
44
+ tokenize() {
45
+ while (this.pos < this.source.length) {
46
+ this.skipWhitespace();
47
+
48
+ if (this.pos >= this.source.length) break;
49
+
50
+ const char = this.source[this.pos];
51
+
52
+ // 改行
53
+ if (char === '\n') {
54
+ this.advance();
55
+ this.line++;
56
+ this.col = 1;
57
+ continue;
58
+ }
59
+
60
+ // 読点(、)- キーワードの区切り
61
+ if (char === '、' || char === ',') {
62
+ this.tokens.push(new Token(TOKEN_TYPES.COMMA, '、', this.line, this.col));
63
+ this.advance();
64
+ continue;
65
+ }
66
+
67
+ // 句点(。)- 文の終わり
68
+ if (char === '。' || char === '.') {
69
+ this.tokens.push(new Token(TOKEN_TYPES.PERIOD, '。', this.line, this.col));
70
+ this.advance();
71
+ continue;
72
+ }
73
+
74
+ // コメント
75
+ if (char === '#' || (char === '/' && this.source[this.pos + 1] === '/')) {
76
+ this.skipComment();
77
+ continue;
78
+ }
79
+
80
+ // 文字列
81
+ if (char === '「' || char === '"' || char === "'") {
82
+ this.tokens.push(this.readString());
83
+ continue;
84
+ }
85
+
86
+ // 数値
87
+ if (this.isDigit(char)) {
88
+ this.tokens.push(this.readNumber());
89
+ continue;
90
+ }
91
+
92
+ // 演算子
93
+ if (this.isOperator(char)) {
94
+ this.tokens.push(this.readOperator());
95
+ continue;
96
+ }
97
+
98
+ // 識別子またはキーワード
99
+ if (this.isIdentifierStart(char)) {
100
+ this.tokens.push(this.readIdentifier());
101
+ continue;
102
+ }
103
+
104
+ // 不明な文字はスキップ
105
+ this.advance();
106
+ }
107
+
108
+ this.tokens.push(new Token(TOKEN_TYPES.EOF, null, this.line, this.col));
109
+ return this.tokens;
110
+ }
111
+
112
+ skipWhitespace() {
113
+ while (this.pos < this.source.length) {
114
+ const char = this.source[this.pos];
115
+ if (char === ' ' || char === '\t' || char === '\r') {
116
+ this.advance();
117
+ } else {
118
+ break;
119
+ }
120
+ }
121
+ }
122
+
123
+ skipComment() {
124
+ while (this.pos < this.source.length && this.source[this.pos] !== '\n') {
125
+ this.advance();
126
+ }
127
+ }
128
+
129
+ readString() {
130
+ const startLine = this.line;
131
+ const startCol = this.col;
132
+ const startChar = this.source[this.pos];
133
+ let endChar = startChar === '「' ? '」' : startChar;
134
+
135
+ this.advance();
136
+
137
+ let value = '';
138
+ while (this.pos < this.source.length && this.source[this.pos] !== endChar) {
139
+ if (this.source[this.pos] === '\\' && this.pos + 1 < this.source.length) {
140
+ this.advance();
141
+ const escapeChar = this.source[this.pos];
142
+ switch (escapeChar) {
143
+ case 'n': value += '\n'; break;
144
+ case 't': value += '\t'; break;
145
+ case '\\': value += '\\'; break;
146
+ default: value += escapeChar;
147
+ }
148
+ this.advance();
149
+ } else {
150
+ value += this.source[this.pos];
151
+ this.advance();
152
+ }
153
+ }
154
+
155
+ if (this.pos < this.source.length) {
156
+ this.advance();
157
+ }
158
+
159
+ return new Token(TOKEN_TYPES.STRING, value, startLine, startCol);
160
+ }
161
+
162
+ readNumber() {
163
+ const startLine = this.line;
164
+ const startCol = this.col;
165
+ let value = '';
166
+ let hasDot = false;
167
+
168
+ while (this.pos < this.source.length) {
169
+ const char = this.source[this.pos];
170
+ if (this.isDigit(char)) {
171
+ value += char;
172
+ this.advance();
173
+ } else if (char === '.' && !hasDot) {
174
+ hasDot = true;
175
+ value += char;
176
+ this.advance();
177
+ } else {
178
+ break;
179
+ }
180
+ }
181
+
182
+ return new Token(TOKEN_TYPES.NUMBER, parseFloat(value), startLine, startCol);
183
+ }
184
+
185
+ readIdentifier() {
186
+ const startLine = this.line;
187
+ const startCol = this.col;
188
+ let value = '';
189
+
190
+ // 一文字ずつ読みながら、キーワードをチェック
191
+ while (this.pos < this.source.length) {
192
+ const char = this.source[this.pos];
193
+
194
+ // 句読点で止まる
195
+ if (char === '、' || char === ',' || char === '。' || char === '.') {
196
+ break;
197
+ }
198
+
199
+ // スペースで止まる
200
+ if (char === ' ' || char === '\t' || char === '\r' || char === '\n') {
201
+ break;
202
+ }
203
+
204
+ // 数字で止まる(識別子と数字は別々にトークン化)
205
+ if (this.isDigit(char)) {
206
+ break;
207
+ }
208
+
209
+ // 演算子で止まる
210
+ if (this.isOperator(char)) {
211
+ break;
212
+ }
213
+
214
+ // 文字列開始で止まる
215
+ if (char === '「' || char === '"' || char === "'") {
216
+ break;
217
+ }
218
+
219
+ if (!this.isIdentifierChar(char)) {
220
+ break;
221
+ }
222
+
223
+ value += char;
224
+ this.advance();
225
+
226
+ // 読んだ部分の末尾がキーワードかチェック
227
+ // ただし、もっと長く読むとより長いキーワードになる可能性がある場合は続行
228
+ // 例: 「そうで」で止めず「そうでなければ」まで読む
229
+ // 最長マッチを優先:長いキーワードから短いキーワードへ
230
+ for (let i = value.length; i >= 1; i--) {
231
+ const suffix = value.substring(value.length - i);
232
+ if (KEYWORDS.has(suffix)) {
233
+ // もっと長いキーワードの可能性をチェック
234
+ // 次の文字を読んでもキーワードの一部になる可能性があるか?
235
+ let canExtend = false;
236
+ if (this.pos < this.source.length) {
237
+ const nextChar = this.source[this.pos];
238
+ if (this.isIdentifierChar(nextChar) &&
239
+ nextChar !== '、' && nextChar !== ',' &&
240
+ nextChar !== '。' && nextChar !== '.' &&
241
+ nextChar !== ' ' && nextChar !== '\t' &&
242
+ nextChar !== '\r' && nextChar !== '\n') {
243
+ // 次の文字を含めた文字列が、より長いキーワードの接頭辞になるか?
244
+ const extendedValue = value + nextChar;
245
+ for (const keyword of KEYWORDS) {
246
+ if (keyword.startsWith(extendedValue) && keyword.length > suffix.length) {
247
+ canExtend = true;
248
+ break;
249
+ }
250
+ }
251
+ }
252
+ }
253
+
254
+ if (!canExtend) {
255
+ const identifier = value.substring(0, value.length - i);
256
+
257
+ if (identifier.length > 0) {
258
+ // キーワードの前で止める
259
+ this.pos -= i;
260
+ this.col -= i;
261
+ return new Token(TOKEN_TYPES.IDENTIFIER, identifier, startLine, startCol);
262
+ } else {
263
+ // 全体がキーワード
264
+ return new Token(TOKEN_TYPES.KEYWORD, suffix, startLine, startCol);
265
+ }
266
+ }
267
+ }
268
+ }
269
+ }
270
+
271
+ // キーワードチェック
272
+ const type = KEYWORDS.has(value) ? TOKEN_TYPES.KEYWORD : TOKEN_TYPES.IDENTIFIER;
273
+ return new Token(type, value, startLine, startCol);
274
+ }
275
+
276
+ readOperator() {
277
+ const startLine = this.line;
278
+ const startCol = this.col;
279
+ const char = this.source[this.pos];
280
+ this.advance();
281
+ return new Token(TOKEN_TYPES.OPERATOR, char, startLine, startCol);
282
+ }
283
+
284
+ isDigit(char) {
285
+ return /[0-9]/.test(char);
286
+ }
287
+
288
+ isIdentifierStart(char) {
289
+ return /[a-zA-Z_あ-んア-ンー一-龯]/.test(char);
290
+ }
291
+
292
+ isIdentifierChar(char) {
293
+ return /[a-zA-Z0-9_あ-んア-ンー一-龯]/.test(char);
294
+ }
295
+
296
+ isOperator(char) {
297
+ return /[+\-*/%=<>!()]/.test(char);
298
+ }
299
+
300
+ advance() {
301
+ this.pos++;
302
+ this.col++;
303
+ }
304
+ }
305
+
306
+ module.exports = { Lexer, Token, TOKEN_TYPES };