multilingualprogramming 0.2.0__py3-none-any.whl
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.
- multilingualprogramming/__init__.py +74 -0
- multilingualprogramming/__main__.py +194 -0
- multilingualprogramming/codegen/__init__.py +12 -0
- multilingualprogramming/codegen/executor.py +215 -0
- multilingualprogramming/codegen/python_generator.py +592 -0
- multilingualprogramming/codegen/repl.py +489 -0
- multilingualprogramming/codegen/runtime_builtins.py +308 -0
- multilingualprogramming/core/__init__.py +12 -0
- multilingualprogramming/core/ir.py +29 -0
- multilingualprogramming/core/lowering.py +24 -0
- multilingualprogramming/datetime/__init__.py +11 -0
- multilingualprogramming/datetime/date_parser.py +190 -0
- multilingualprogramming/datetime/mp_date.py +210 -0
- multilingualprogramming/datetime/mp_datetime.py +153 -0
- multilingualprogramming/datetime/mp_time.py +147 -0
- multilingualprogramming/datetime/resource_loader.py +18 -0
- multilingualprogramming/exceptions.py +158 -0
- multilingualprogramming/imports.py +150 -0
- multilingualprogramming/keyword/__init__.py +13 -0
- multilingualprogramming/keyword/keyword_registry.py +249 -0
- multilingualprogramming/keyword/keyword_validator.py +59 -0
- multilingualprogramming/keyword/language_pack_validator.py +110 -0
- multilingualprogramming/lexer/__init__.py +11 -0
- multilingualprogramming/lexer/lexer.py +570 -0
- multilingualprogramming/lexer/source_reader.py +91 -0
- multilingualprogramming/lexer/token.py +54 -0
- multilingualprogramming/lexer/token_types.py +38 -0
- multilingualprogramming/numeral/__init__.py +11 -0
- multilingualprogramming/numeral/abstract_numeral.py +232 -0
- multilingualprogramming/numeral/complex_numeral.py +190 -0
- multilingualprogramming/numeral/fraction_numeral.py +165 -0
- multilingualprogramming/numeral/mp_numeral.py +243 -0
- multilingualprogramming/numeral/numeral_converter.py +151 -0
- multilingualprogramming/numeral/roman_numeral.py +301 -0
- multilingualprogramming/numeral/unicode_numeral.py +292 -0
- multilingualprogramming/parser/__init__.py +28 -0
- multilingualprogramming/parser/ast_nodes.py +459 -0
- multilingualprogramming/parser/ast_printer.py +677 -0
- multilingualprogramming/parser/error_messages.py +75 -0
- multilingualprogramming/parser/parser.py +1796 -0
- multilingualprogramming/parser/semantic_analyzer.py +689 -0
- multilingualprogramming/parser/surface_normalizer.py +282 -0
- multilingualprogramming/resources/datetime/eras.json +23 -0
- multilingualprogramming/resources/datetime/formats.json +32 -0
- multilingualprogramming/resources/datetime/months.json +150 -0
- multilingualprogramming/resources/datetime/weekdays.json +90 -0
- multilingualprogramming/resources/parser/error_messages.json +310 -0
- multilingualprogramming/resources/repl/commands.json +636 -0
- multilingualprogramming/resources/usm/builtins_aliases.json +731 -0
- multilingualprogramming/resources/usm/keywords.json +1063 -0
- multilingualprogramming/resources/usm/operators.json +532 -0
- multilingualprogramming/resources/usm/schema.json +34 -0
- multilingualprogramming/resources/usm/surface_patterns.json +1523 -0
- multilingualprogramming/unicode_string.py +140 -0
- multilingualprogramming/version.py +9 -0
- multilingualprogramming-0.2.0.dist-info/METADATA +350 -0
- multilingualprogramming-0.2.0.dist-info/RECORD +61 -0
- multilingualprogramming-0.2.0.dist-info/WHEEL +5 -0
- multilingualprogramming-0.2.0.dist-info/entry_points.txt +3 -0
- multilingualprogramming-0.2.0.dist-info/licenses/LICENSE +674 -0
- multilingualprogramming-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
#
|
|
2
|
+
# SPDX-FileCopyrightText: 2024 John Samuel <johnsamuelwrites@gmail.com>
|
|
3
|
+
#
|
|
4
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
Interactive REPL (Read-Eval-Print Loop) for the multilingual programming
|
|
9
|
+
language.
|
|
10
|
+
|
|
11
|
+
Supports line-by-line and multi-line (block) input, persistent state
|
|
12
|
+
across interactions, expression auto-printing, bracket-aware continuation,
|
|
13
|
+
and REPL commands.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import io
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
import sys
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
from multilingualprogramming.lexer.lexer import Lexer
|
|
23
|
+
from multilingualprogramming.keyword.keyword_registry import KeywordRegistry
|
|
24
|
+
from multilingualprogramming.parser.parser import Parser
|
|
25
|
+
from multilingualprogramming.codegen.python_generator import PythonCodeGenerator
|
|
26
|
+
from multilingualprogramming.codegen.runtime_builtins import RuntimeBuiltins
|
|
27
|
+
from multilingualprogramming.imports import enable_multilingual_imports
|
|
28
|
+
from multilingualprogramming.exceptions import UnsupportedLanguageError
|
|
29
|
+
from multilingualprogramming.version import __version__
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class REPL:
|
|
33
|
+
"""
|
|
34
|
+
Interactive multilingual programming REPL.
|
|
35
|
+
|
|
36
|
+
Usage:
|
|
37
|
+
repl = REPL(language="fr")
|
|
38
|
+
repl.run() # starts interactive loop
|
|
39
|
+
|
|
40
|
+
Or programmatically:
|
|
41
|
+
repl = REPL(language="en")
|
|
42
|
+
output = repl.eval_line("let x = 42")
|
|
43
|
+
output = repl.eval_line("print(x)") # "42\\n"
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
_COMMAND_CATALOG = None
|
|
47
|
+
_OPERATOR_CATALOG = None
|
|
48
|
+
|
|
49
|
+
def __init__(self, language=None, show_python=False):
|
|
50
|
+
"""
|
|
51
|
+
Initialize the REPL.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
language: Source language code (e.g., "en", "fr").
|
|
55
|
+
If None, auto-detect from input.
|
|
56
|
+
show_python: If True, display generated Python before execution.
|
|
57
|
+
"""
|
|
58
|
+
self.language = language
|
|
59
|
+
self.show_python = show_python
|
|
60
|
+
self._globals = {}
|
|
61
|
+
self._init_globals()
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def _load_command_catalog(cls):
|
|
65
|
+
"""Load command aliases and help text from resources."""
|
|
66
|
+
if cls._COMMAND_CATALOG is not None:
|
|
67
|
+
return cls._COMMAND_CATALOG
|
|
68
|
+
|
|
69
|
+
path = (
|
|
70
|
+
Path(__file__).resolve().parent.parent
|
|
71
|
+
/ "resources" / "repl" / "commands.json"
|
|
72
|
+
)
|
|
73
|
+
with open(path, "r", encoding="utf-8-sig") as handle:
|
|
74
|
+
cls._COMMAND_CATALOG = json.load(handle)
|
|
75
|
+
return cls._COMMAND_CATALOG
|
|
76
|
+
|
|
77
|
+
def _language_code(self):
|
|
78
|
+
"""Return normalized active language code."""
|
|
79
|
+
return (self.language or "en").lower()
|
|
80
|
+
|
|
81
|
+
@classmethod
|
|
82
|
+
def _load_operator_catalog(cls):
|
|
83
|
+
"""Load operator and symbol metadata from resources."""
|
|
84
|
+
if cls._OPERATOR_CATALOG is not None:
|
|
85
|
+
return cls._OPERATOR_CATALOG
|
|
86
|
+
|
|
87
|
+
path = (
|
|
88
|
+
Path(__file__).resolve().parent.parent
|
|
89
|
+
/ "resources" / "usm" / "operators.json"
|
|
90
|
+
)
|
|
91
|
+
with open(path, "r", encoding="utf-8-sig") as handle:
|
|
92
|
+
cls._OPERATOR_CATALOG = json.load(handle)
|
|
93
|
+
return cls._OPERATOR_CATALOG
|
|
94
|
+
|
|
95
|
+
def _aliases_for(self, canonical, lang):
|
|
96
|
+
"""Return ordered aliases for a command in active language."""
|
|
97
|
+
catalog = self._load_command_catalog()
|
|
98
|
+
commands = catalog.get("commands", {})
|
|
99
|
+
default_lang = catalog.get("default_language", "en")
|
|
100
|
+
meta = commands.get(canonical, {})
|
|
101
|
+
aliases_by_lang = meta.get("aliases", {})
|
|
102
|
+
|
|
103
|
+
aliases = []
|
|
104
|
+
aliases.extend(aliases_by_lang.get(default_lang, []))
|
|
105
|
+
if lang != default_lang:
|
|
106
|
+
aliases.extend(aliases_by_lang.get(lang, []))
|
|
107
|
+
|
|
108
|
+
seen = set()
|
|
109
|
+
ordered = []
|
|
110
|
+
for alias in aliases:
|
|
111
|
+
key = alias.casefold()
|
|
112
|
+
if key not in seen:
|
|
113
|
+
seen.add(key)
|
|
114
|
+
ordered.append(alias)
|
|
115
|
+
return ordered
|
|
116
|
+
|
|
117
|
+
def _message(self, key, ui_lang, **kwargs):
|
|
118
|
+
"""Get localized REPL message template from command catalog."""
|
|
119
|
+
catalog = self._load_command_catalog()
|
|
120
|
+
default_lang = catalog.get("default_language", "en")
|
|
121
|
+
messages = catalog.get("messages", {})
|
|
122
|
+
template = messages.get(key, {}).get(
|
|
123
|
+
ui_lang,
|
|
124
|
+
messages.get(key, {}).get(default_lang, ""),
|
|
125
|
+
)
|
|
126
|
+
return template.format(**kwargs) if template else ""
|
|
127
|
+
|
|
128
|
+
def _command_alias_map(self, lang):
|
|
129
|
+
"""Map canonical command names to casefolded aliases."""
|
|
130
|
+
catalog = self._load_command_catalog()
|
|
131
|
+
commands = catalog.get("commands", {})
|
|
132
|
+
result = {}
|
|
133
|
+
for canonical in commands:
|
|
134
|
+
aliases = self._aliases_for(canonical, lang)
|
|
135
|
+
result[canonical] = {
|
|
136
|
+
canonical.casefold(), *(alias.casefold() for alias in aliases)
|
|
137
|
+
}
|
|
138
|
+
return result
|
|
139
|
+
|
|
140
|
+
def _print_help(self):
|
|
141
|
+
"""Print localized REPL help from command catalog."""
|
|
142
|
+
catalog = self._load_command_catalog()
|
|
143
|
+
lang = self._language_code()
|
|
144
|
+
default_lang = catalog.get("default_language", "en")
|
|
145
|
+
commands = catalog.get("commands", {})
|
|
146
|
+
|
|
147
|
+
title = catalog.get("help_titles", {}).get(
|
|
148
|
+
lang,
|
|
149
|
+
catalog.get("help_titles", {}).get(default_lang, "REPL Commands:"),
|
|
150
|
+
)
|
|
151
|
+
order = catalog.get("help_order", list(commands.keys()))
|
|
152
|
+
|
|
153
|
+
print(title)
|
|
154
|
+
for canonical in order:
|
|
155
|
+
meta = commands.get(canonical, {})
|
|
156
|
+
aliases = self._aliases_for(canonical, lang)
|
|
157
|
+
display = aliases[-1] if aliases else canonical
|
|
158
|
+
arg_hint = meta.get("arg_hint", "")
|
|
159
|
+
description = meta.get("descriptions", {}).get(
|
|
160
|
+
lang,
|
|
161
|
+
meta.get("descriptions", {}).get(default_lang, ""),
|
|
162
|
+
)
|
|
163
|
+
print(f" :{display}{arg_hint} {description}")
|
|
164
|
+
|
|
165
|
+
def _resolve_listing_language(self, arg):
|
|
166
|
+
"""Resolve and validate the language used for keyword/symbol listings."""
|
|
167
|
+
lang = arg.strip().lower() if arg.strip() else self._language_code()
|
|
168
|
+
registry = KeywordRegistry()
|
|
169
|
+
registry.check_language(lang)
|
|
170
|
+
return lang
|
|
171
|
+
|
|
172
|
+
def _print_keywords(self, arg):
|
|
173
|
+
"""Print available keywords for a given language."""
|
|
174
|
+
active_lang = self._language_code()
|
|
175
|
+
try:
|
|
176
|
+
lang = self._resolve_listing_language(arg)
|
|
177
|
+
except UnsupportedLanguageError:
|
|
178
|
+
label = arg.strip() or self._language_code()
|
|
179
|
+
print(self._message("unsupported_language", active_lang, lang=label))
|
|
180
|
+
return
|
|
181
|
+
|
|
182
|
+
keywords = KeywordRegistry().get_all_keywords(lang)
|
|
183
|
+
print(self._message("keywords_title", active_lang, lang=lang, count=len(keywords)))
|
|
184
|
+
for concept_id, keyword in sorted(
|
|
185
|
+
keywords.items(),
|
|
186
|
+
key=lambda item: (item[1].casefold(), item[0]),
|
|
187
|
+
):
|
|
188
|
+
print(f" {keyword} -> {concept_id}")
|
|
189
|
+
|
|
190
|
+
def _print_symbols(self, arg):
|
|
191
|
+
"""Print available operator and symbol mappings."""
|
|
192
|
+
active_lang = self._language_code()
|
|
193
|
+
try:
|
|
194
|
+
lang = self._resolve_listing_language(arg)
|
|
195
|
+
except UnsupportedLanguageError:
|
|
196
|
+
label = arg.strip() or self._language_code()
|
|
197
|
+
print(self._message("unsupported_language", active_lang, lang=label))
|
|
198
|
+
return
|
|
199
|
+
|
|
200
|
+
catalog = self._load_operator_catalog()
|
|
201
|
+
default_lang = catalog.get("default_language", "en")
|
|
202
|
+
print(self._message("symbols_title", active_lang, lang=lang))
|
|
203
|
+
for category, operators in catalog.items():
|
|
204
|
+
if not isinstance(operators, dict):
|
|
205
|
+
continue
|
|
206
|
+
print(f"{category}:")
|
|
207
|
+
for name, meta in operators.items():
|
|
208
|
+
if not isinstance(meta, dict):
|
|
209
|
+
continue
|
|
210
|
+
symbols = meta.get("symbols", [])
|
|
211
|
+
unicode_alt = meta.get("unicode_alt", [])
|
|
212
|
+
pairs = meta.get("pairs", [])
|
|
213
|
+
if pairs:
|
|
214
|
+
primary = f"{pairs[0]} {pairs[1]}"
|
|
215
|
+
elif symbols:
|
|
216
|
+
primary = ", ".join(symbols)
|
|
217
|
+
else:
|
|
218
|
+
primary = ""
|
|
219
|
+
|
|
220
|
+
line = f" {name}: {primary}"
|
|
221
|
+
if unicode_alt:
|
|
222
|
+
line += f" | alt: {', '.join(unicode_alt)}"
|
|
223
|
+
|
|
224
|
+
descriptions = meta.get("description", {})
|
|
225
|
+
if isinstance(descriptions, dict):
|
|
226
|
+
desc = descriptions.get(lang, descriptions.get(default_lang))
|
|
227
|
+
if desc:
|
|
228
|
+
line += f" ({desc})"
|
|
229
|
+
print(line)
|
|
230
|
+
|
|
231
|
+
def _init_globals(self):
|
|
232
|
+
"""Initialize the execution namespace with builtins."""
|
|
233
|
+
lang = self.language or "en"
|
|
234
|
+
builtins_ns = RuntimeBuiltins(lang).namespace()
|
|
235
|
+
self._globals.update(builtins_ns)
|
|
236
|
+
|
|
237
|
+
def eval_line(self, source):
|
|
238
|
+
"""
|
|
239
|
+
Evaluate a single line or block of multilingual source code.
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
source: Source code string.
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
Captured stdout output as a string, or error message.
|
|
246
|
+
"""
|
|
247
|
+
if not source.strip():
|
|
248
|
+
return ""
|
|
249
|
+
|
|
250
|
+
try:
|
|
251
|
+
lexer = Lexer(source, language=self.language)
|
|
252
|
+
tokens = lexer.tokenize()
|
|
253
|
+
detected_lang = lexer.language or self.language or "en"
|
|
254
|
+
|
|
255
|
+
parser = Parser(tokens, source_language=detected_lang)
|
|
256
|
+
program = parser.parse()
|
|
257
|
+
|
|
258
|
+
generator = PythonCodeGenerator()
|
|
259
|
+
python_source = generator.generate(program)
|
|
260
|
+
|
|
261
|
+
if self.show_python:
|
|
262
|
+
return f"[Python] {python_source.strip()}\n" + self._exec(
|
|
263
|
+
python_source
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
return self._exec(python_source)
|
|
267
|
+
|
|
268
|
+
except Exception as exc:
|
|
269
|
+
return f"Error: {exc}\n"
|
|
270
|
+
|
|
271
|
+
def _exec(self, python_source):
|
|
272
|
+
"""Execute generated Python and return captured output.
|
|
273
|
+
|
|
274
|
+
For single expressions, auto-prints the result (like Python REPL).
|
|
275
|
+
"""
|
|
276
|
+
enable_multilingual_imports()
|
|
277
|
+
|
|
278
|
+
captured = io.StringIO()
|
|
279
|
+
old_stdout = sys.stdout
|
|
280
|
+
try:
|
|
281
|
+
sys.stdout = captured
|
|
282
|
+
try:
|
|
283
|
+
code = compile(python_source, "<repl>", "eval")
|
|
284
|
+
result = eval(code, self._globals) # pylint: disable=eval-used
|
|
285
|
+
if result is not None:
|
|
286
|
+
print(repr(result))
|
|
287
|
+
except SyntaxError:
|
|
288
|
+
code = compile(python_source, "<repl>", "exec")
|
|
289
|
+
exec(code, self._globals) # pylint: disable=exec-used
|
|
290
|
+
except Exception as exc:
|
|
291
|
+
captured.write(f"Error: {exc}\n")
|
|
292
|
+
finally:
|
|
293
|
+
sys.stdout = old_stdout
|
|
294
|
+
return captured.getvalue()
|
|
295
|
+
|
|
296
|
+
def _continuation_state(self, text):
|
|
297
|
+
"""Return (open_brackets, has_unclosed_string) for continuation."""
|
|
298
|
+
count = 0
|
|
299
|
+
string_char = None
|
|
300
|
+
is_triple = False
|
|
301
|
+
i = 0
|
|
302
|
+
while i < len(text):
|
|
303
|
+
ch = text[i]
|
|
304
|
+
|
|
305
|
+
if string_char is not None:
|
|
306
|
+
if is_triple:
|
|
307
|
+
if text[i:i+3] == string_char * 3:
|
|
308
|
+
string_char = None
|
|
309
|
+
is_triple = False
|
|
310
|
+
i += 3
|
|
311
|
+
continue
|
|
312
|
+
else:
|
|
313
|
+
if ch == '\\':
|
|
314
|
+
i += 2
|
|
315
|
+
continue
|
|
316
|
+
if ch == string_char:
|
|
317
|
+
string_char = None
|
|
318
|
+
i += 1
|
|
319
|
+
continue
|
|
320
|
+
i += 1
|
|
321
|
+
continue
|
|
322
|
+
|
|
323
|
+
if ch == '#':
|
|
324
|
+
# Comment until end of line (outside strings)
|
|
325
|
+
while i < len(text) and text[i] != '\n':
|
|
326
|
+
i += 1
|
|
327
|
+
continue
|
|
328
|
+
|
|
329
|
+
if ch in ('"', "'"):
|
|
330
|
+
if text[i:i+3] == ch * 3:
|
|
331
|
+
string_char = ch
|
|
332
|
+
is_triple = True
|
|
333
|
+
i += 3
|
|
334
|
+
continue
|
|
335
|
+
string_char = ch
|
|
336
|
+
is_triple = False
|
|
337
|
+
i += 1
|
|
338
|
+
continue
|
|
339
|
+
|
|
340
|
+
if ch in ('(', '[', '{'):
|
|
341
|
+
count += 1
|
|
342
|
+
elif ch in (')', ']', '}'):
|
|
343
|
+
count -= 1
|
|
344
|
+
|
|
345
|
+
i += 1
|
|
346
|
+
|
|
347
|
+
return count, string_char is not None
|
|
348
|
+
|
|
349
|
+
def continuation_state(self, text):
|
|
350
|
+
"""Public wrapper for continuation-state inspection."""
|
|
351
|
+
return self._continuation_state(text)
|
|
352
|
+
|
|
353
|
+
def _count_open_brackets(self, text):
|
|
354
|
+
"""Count net open brackets in text."""
|
|
355
|
+
count, _has_unclosed_string = self._continuation_state(text)
|
|
356
|
+
return count
|
|
357
|
+
|
|
358
|
+
def _resolve_command(self, line):
|
|
359
|
+
"""Resolve REPL command aliases to canonical command names."""
|
|
360
|
+
text = line.strip()
|
|
361
|
+
if not text:
|
|
362
|
+
return None, ""
|
|
363
|
+
|
|
364
|
+
if text.startswith(":"):
|
|
365
|
+
text = text[1:].lstrip()
|
|
366
|
+
if not text:
|
|
367
|
+
return None, ""
|
|
368
|
+
|
|
369
|
+
parts = text.split(None, 1)
|
|
370
|
+
cmd = parts[0].casefold()
|
|
371
|
+
arg = parts[1] if len(parts) > 1 else ""
|
|
372
|
+
|
|
373
|
+
alias_map = self._command_alias_map(self._language_code())
|
|
374
|
+
for canonical, words in alias_map.items():
|
|
375
|
+
if cmd in words:
|
|
376
|
+
return canonical, arg
|
|
377
|
+
return None, ""
|
|
378
|
+
|
|
379
|
+
def _handle_command(self, line):
|
|
380
|
+
"""Handle REPL commands. Returns True if handled."""
|
|
381
|
+
cmd, arg = self._resolve_command(line)
|
|
382
|
+
if cmd is None:
|
|
383
|
+
return False
|
|
384
|
+
|
|
385
|
+
if cmd == "quit":
|
|
386
|
+
print("Bye!")
|
|
387
|
+
return "exit"
|
|
388
|
+
if cmd == "lang":
|
|
389
|
+
if arg:
|
|
390
|
+
self.language = arg.strip()
|
|
391
|
+
self._globals.clear()
|
|
392
|
+
self._init_globals()
|
|
393
|
+
print(f"Language switched to: {self.language}")
|
|
394
|
+
else:
|
|
395
|
+
print(f"Current language: {self.language or 'auto'}")
|
|
396
|
+
return True
|
|
397
|
+
if cmd == "python":
|
|
398
|
+
self.show_python = not self.show_python
|
|
399
|
+
state = "on" if self.show_python else "off"
|
|
400
|
+
print(f"Show Python: {state}")
|
|
401
|
+
return True
|
|
402
|
+
if cmd == "reset":
|
|
403
|
+
self._globals.clear()
|
|
404
|
+
self._init_globals()
|
|
405
|
+
print("State cleared.")
|
|
406
|
+
return True
|
|
407
|
+
if cmd == "help":
|
|
408
|
+
self._print_help()
|
|
409
|
+
return True
|
|
410
|
+
if cmd == "keywords":
|
|
411
|
+
self._print_keywords(arg)
|
|
412
|
+
return True
|
|
413
|
+
if cmd == "symbols":
|
|
414
|
+
self._print_symbols(arg)
|
|
415
|
+
return True
|
|
416
|
+
return False
|
|
417
|
+
|
|
418
|
+
def run(self):
|
|
419
|
+
"""
|
|
420
|
+
Start the interactive REPL loop.
|
|
421
|
+
|
|
422
|
+
Reads from stdin, supports multi-line blocks (lines ending with ':'),
|
|
423
|
+
bracket continuation, and REPL commands.
|
|
424
|
+
Exits on EOF (Ctrl+D on Unix, Ctrl+Z then Enter on Windows) or Ctrl+C.
|
|
425
|
+
"""
|
|
426
|
+
lang_label = self.language or "auto"
|
|
427
|
+
eof_hint = "Ctrl+Z then Enter" if os.name == "nt" else "Ctrl+D"
|
|
428
|
+
print(f"Multilingual Programming REPL v{__version__} "
|
|
429
|
+
f"[language={lang_label}]")
|
|
430
|
+
print(
|
|
431
|
+
f"Type ':help' for commands. Use ':quit' (or Ctrl+C) to exit. "
|
|
432
|
+
f"EOF key is terminal-dependent ({eof_hint}).\n"
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
while True:
|
|
436
|
+
try:
|
|
437
|
+
line = input(">>> ")
|
|
438
|
+
except (EOFError, KeyboardInterrupt):
|
|
439
|
+
print("\nBye!")
|
|
440
|
+
break
|
|
441
|
+
|
|
442
|
+
if line.strip() in ("exit", "quit", "\x04", "\x1a"):
|
|
443
|
+
print("Bye!")
|
|
444
|
+
break
|
|
445
|
+
|
|
446
|
+
result = self._handle_command(line)
|
|
447
|
+
if result == "exit":
|
|
448
|
+
break
|
|
449
|
+
if result:
|
|
450
|
+
continue
|
|
451
|
+
|
|
452
|
+
open_brackets, has_unclosed_string = self._continuation_state(line)
|
|
453
|
+
needs_block = line.rstrip().endswith(":")
|
|
454
|
+
|
|
455
|
+
if needs_block or open_brackets > 0 or has_unclosed_string:
|
|
456
|
+
block_lines = [line]
|
|
457
|
+
while True:
|
|
458
|
+
try:
|
|
459
|
+
cont = input("... ")
|
|
460
|
+
except (EOFError, KeyboardInterrupt):
|
|
461
|
+
print()
|
|
462
|
+
break
|
|
463
|
+
if cont.strip() in ("\x04", "\x1a"):
|
|
464
|
+
print("Bye!")
|
|
465
|
+
return
|
|
466
|
+
block_lines.append(cont)
|
|
467
|
+
full_text = "\n".join(block_lines)
|
|
468
|
+
open_brackets, has_unclosed_string = (
|
|
469
|
+
self._continuation_state(full_text)
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
if needs_block and cont.strip() == "" \
|
|
473
|
+
and open_brackets <= 0 and not has_unclosed_string:
|
|
474
|
+
break
|
|
475
|
+
if not needs_block and open_brackets <= 0 \
|
|
476
|
+
and not has_unclosed_string:
|
|
477
|
+
break
|
|
478
|
+
source = "\n".join(block_lines) + "\n"
|
|
479
|
+
else:
|
|
480
|
+
source = line + "\n"
|
|
481
|
+
|
|
482
|
+
output = self.eval_line(source)
|
|
483
|
+
if output:
|
|
484
|
+
sys.stdout.write(output)
|
|
485
|
+
|
|
486
|
+
def reset(self):
|
|
487
|
+
"""Clear the REPL state (variables, functions, etc.)."""
|
|
488
|
+
self._globals.clear()
|
|
489
|
+
self._init_globals()
|