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,210 @@
|
|
|
1
|
+
#
|
|
2
|
+
# SPDX-FileCopyrightText: 2024 John Samuel <johnsamuelwrites@gmail.com>
|
|
3
|
+
#
|
|
4
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
"""Multilingual date handling."""
|
|
8
|
+
# pylint: disable=unsubscriptable-object
|
|
9
|
+
|
|
10
|
+
import datetime as dt
|
|
11
|
+
from typing import Any, cast
|
|
12
|
+
from multilingualprogramming.datetime.date_parser import DateParser
|
|
13
|
+
from multilingualprogramming.datetime.resource_loader import load_datetime_resource
|
|
14
|
+
from multilingualprogramming.exceptions import InvalidDateError
|
|
15
|
+
from multilingualprogramming.unicode_string import get_unicode_character_string
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class MPDate:
|
|
19
|
+
"""
|
|
20
|
+
A date object with multilingual formatting and parsing.
|
|
21
|
+
|
|
22
|
+
Internally stores as Python datetime.date (Gregorian).
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
_months_data: dict[str, Any] | None = None
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def _load_months(cls):
|
|
29
|
+
"""Load month names from JSON."""
|
|
30
|
+
if cls._months_data is not None:
|
|
31
|
+
return
|
|
32
|
+
cls._months_data = load_datetime_resource("months.json")
|
|
33
|
+
|
|
34
|
+
def __init__(self, year=None, month=None, day=None, date=None):
|
|
35
|
+
"""
|
|
36
|
+
Create an MPDate.
|
|
37
|
+
|
|
38
|
+
Parameters:
|
|
39
|
+
year (int): Year
|
|
40
|
+
month (int): Month (1-12)
|
|
41
|
+
day (int): Day (1-31)
|
|
42
|
+
date (datetime.date): Python date object
|
|
43
|
+
"""
|
|
44
|
+
if date is not None:
|
|
45
|
+
self._date = date
|
|
46
|
+
elif year is not None and month is not None and day is not None:
|
|
47
|
+
try:
|
|
48
|
+
self._date = dt.date(year, month, day)
|
|
49
|
+
except ValueError as e:
|
|
50
|
+
raise InvalidDateError(str(e)) from e
|
|
51
|
+
else:
|
|
52
|
+
raise InvalidDateError(
|
|
53
|
+
"MPDate requires year/month/day or a date object"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
@classmethod
|
|
57
|
+
def from_string(cls, datestr, language=None):
|
|
58
|
+
"""
|
|
59
|
+
Parse a multilingual date string.
|
|
60
|
+
|
|
61
|
+
Parameters:
|
|
62
|
+
datestr (str): Date string in any supported language
|
|
63
|
+
language (str): Optional language hint
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
MPDate: Parsed date
|
|
67
|
+
"""
|
|
68
|
+
year, month, day, _ = DateParser.parse(datestr, language)
|
|
69
|
+
return cls(year=year, month=month, day=day)
|
|
70
|
+
|
|
71
|
+
def to_date(self):
|
|
72
|
+
"""
|
|
73
|
+
Convert to Python datetime.date.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
datetime.date: Python date object
|
|
77
|
+
"""
|
|
78
|
+
return self._date
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def year(self):
|
|
82
|
+
"""Return the year."""
|
|
83
|
+
return self._date.year
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def month(self):
|
|
87
|
+
"""Return the month."""
|
|
88
|
+
return self._date.month
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def day(self):
|
|
92
|
+
"""Return the day."""
|
|
93
|
+
return self._date.day
|
|
94
|
+
|
|
95
|
+
def _get_month_name(self, language, abbreviated=False):
|
|
96
|
+
"""Get the month name in a given language."""
|
|
97
|
+
self._load_months()
|
|
98
|
+
if self._months_data is None:
|
|
99
|
+
raise InvalidDateError("Month data not loaded")
|
|
100
|
+
months_data = cast(dict[str, Any], self._months_data)
|
|
101
|
+
month_keys = list(months_data["months"].keys())
|
|
102
|
+
month_key = month_keys[self._date.month - 1]
|
|
103
|
+
month_data = months_data["months"][month_key]
|
|
104
|
+
if language in month_data:
|
|
105
|
+
form = "abbr" if abbreviated else "full"
|
|
106
|
+
return month_data[language][form]
|
|
107
|
+
return month_data["en"]["full" if not abbreviated else "abbr"]
|
|
108
|
+
|
|
109
|
+
def _format_number(self, number, script=None):
|
|
110
|
+
"""Format a number in the given script."""
|
|
111
|
+
if script and script not in ("en", "DIGIT"):
|
|
112
|
+
# Map language codes to Unicode script names
|
|
113
|
+
script_map = {
|
|
114
|
+
"hi": "DEVANAGARI",
|
|
115
|
+
"ar": "ARABIC-INDIC",
|
|
116
|
+
"bn": "BENGALI",
|
|
117
|
+
"ta": "TAMIL",
|
|
118
|
+
}
|
|
119
|
+
unicode_script = script_map.get(script)
|
|
120
|
+
if unicode_script:
|
|
121
|
+
return get_unicode_character_string(unicode_script, number)
|
|
122
|
+
return str(number)
|
|
123
|
+
|
|
124
|
+
def to_string(self, language="en", fmt=None):
|
|
125
|
+
"""
|
|
126
|
+
Format date in a given language.
|
|
127
|
+
|
|
128
|
+
Parameters:
|
|
129
|
+
language (str): Language code (e.g., "en", "fr", "hi")
|
|
130
|
+
fmt (str): Optional format override
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
str: Formatted date string
|
|
134
|
+
"""
|
|
135
|
+
if fmt is not None:
|
|
136
|
+
return self._date.strftime(fmt)
|
|
137
|
+
|
|
138
|
+
month_name = self._get_month_name(language)
|
|
139
|
+
day_str = self._format_number(self._date.day, language)
|
|
140
|
+
year_str = self._format_number(self._date.year, language)
|
|
141
|
+
|
|
142
|
+
if language in ("zh", "ja"):
|
|
143
|
+
return f"{year_str}年{self._format_number(self._date.month, language)}月{day_str}日"
|
|
144
|
+
|
|
145
|
+
return f"{day_str}-{month_name}-{year_str}"
|
|
146
|
+
|
|
147
|
+
def __add__(self, days):
|
|
148
|
+
"""
|
|
149
|
+
Add days to the date.
|
|
150
|
+
|
|
151
|
+
Parameters:
|
|
152
|
+
days (int): Number of days to add
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
MPDate: New date
|
|
156
|
+
"""
|
|
157
|
+
if isinstance(days, int):
|
|
158
|
+
new_date = self._date + dt.timedelta(days=days)
|
|
159
|
+
return MPDate(date=new_date)
|
|
160
|
+
return NotImplemented
|
|
161
|
+
|
|
162
|
+
def __sub__(self, other):
|
|
163
|
+
"""
|
|
164
|
+
Subtract another MPDate or days.
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
int: Difference in days (if MPDate)
|
|
168
|
+
MPDate: New date (if int)
|
|
169
|
+
"""
|
|
170
|
+
if isinstance(other, MPDate):
|
|
171
|
+
delta = self._date - other._date
|
|
172
|
+
return delta.days
|
|
173
|
+
if isinstance(other, int):
|
|
174
|
+
new_date = self._date - dt.timedelta(days=other)
|
|
175
|
+
return MPDate(date=new_date)
|
|
176
|
+
return NotImplemented
|
|
177
|
+
|
|
178
|
+
def __eq__(self, other):
|
|
179
|
+
if isinstance(other, MPDate):
|
|
180
|
+
return self._date == other._date
|
|
181
|
+
return NotImplemented
|
|
182
|
+
|
|
183
|
+
def __lt__(self, other):
|
|
184
|
+
if isinstance(other, MPDate):
|
|
185
|
+
return self._date < other._date
|
|
186
|
+
return NotImplemented
|
|
187
|
+
|
|
188
|
+
def __le__(self, other):
|
|
189
|
+
if isinstance(other, MPDate):
|
|
190
|
+
return self._date <= other._date
|
|
191
|
+
return NotImplemented
|
|
192
|
+
|
|
193
|
+
def __gt__(self, other):
|
|
194
|
+
if isinstance(other, MPDate):
|
|
195
|
+
return self._date > other._date
|
|
196
|
+
return NotImplemented
|
|
197
|
+
|
|
198
|
+
def __ge__(self, other):
|
|
199
|
+
if isinstance(other, MPDate):
|
|
200
|
+
return self._date >= other._date
|
|
201
|
+
return NotImplemented
|
|
202
|
+
|
|
203
|
+
def __hash__(self):
|
|
204
|
+
return hash(self._date)
|
|
205
|
+
|
|
206
|
+
def __str__(self):
|
|
207
|
+
return self.to_string("en")
|
|
208
|
+
|
|
209
|
+
def __repr__(self):
|
|
210
|
+
return f"MPDate({self._date.year}, {self._date.month}, {self._date.day})"
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#
|
|
2
|
+
# SPDX-FileCopyrightText: 2024 John Samuel <johnsamuelwrites@gmail.com>
|
|
3
|
+
#
|
|
4
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
"""Multilingual datetime handling."""
|
|
8
|
+
|
|
9
|
+
import datetime as dt
|
|
10
|
+
from multilingualprogramming.datetime.mp_date import MPDate
|
|
11
|
+
from multilingualprogramming.datetime.mp_time import MPTime
|
|
12
|
+
from multilingualprogramming.datetime.date_parser import DateParser
|
|
13
|
+
from multilingualprogramming.exceptions import InvalidDateError
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class MPDatetime:
|
|
17
|
+
"""
|
|
18
|
+
A datetime object with multilingual formatting and parsing.
|
|
19
|
+
|
|
20
|
+
Combines MPDate and MPTime. Internally stores as Python datetime.datetime.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
# pylint: disable=too-many-arguments,too-many-positional-arguments
|
|
24
|
+
def __init__(self, year=None, month=None, day=None,
|
|
25
|
+
hour=0, minute=0, second=0, datetime_obj=None):
|
|
26
|
+
"""
|
|
27
|
+
Create an MPDatetime.
|
|
28
|
+
|
|
29
|
+
Parameters:
|
|
30
|
+
year (int): Year
|
|
31
|
+
month (int): Month (1-12)
|
|
32
|
+
day (int): Day (1-31)
|
|
33
|
+
hour (int): Hour (0-23)
|
|
34
|
+
minute (int): Minute (0-59)
|
|
35
|
+
second (int): Second (0-59)
|
|
36
|
+
datetime_obj (datetime.datetime): Python datetime object
|
|
37
|
+
"""
|
|
38
|
+
if datetime_obj is not None:
|
|
39
|
+
self._datetime = datetime_obj
|
|
40
|
+
elif year is not None and month is not None and day is not None:
|
|
41
|
+
try:
|
|
42
|
+
self._datetime = dt.datetime(
|
|
43
|
+
year, month, day, hour, minute, second
|
|
44
|
+
)
|
|
45
|
+
except ValueError as e:
|
|
46
|
+
raise InvalidDateError(str(e)) from e
|
|
47
|
+
else:
|
|
48
|
+
raise InvalidDateError(
|
|
49
|
+
"MPDatetime requires year/month/day or a datetime object"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
@classmethod
|
|
53
|
+
def from_string(cls, datestr, language=None):
|
|
54
|
+
"""
|
|
55
|
+
Parse a multilingual date string into an MPDatetime.
|
|
56
|
+
|
|
57
|
+
Parameters:
|
|
58
|
+
datestr (str): Date string in any supported language
|
|
59
|
+
language (str): Optional language hint
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
MPDatetime: Parsed datetime (time defaults to 00:00:00)
|
|
63
|
+
"""
|
|
64
|
+
year, month, day, _ = DateParser.parse(datestr, language)
|
|
65
|
+
return cls(year=year, month=month, day=day)
|
|
66
|
+
|
|
67
|
+
@classmethod
|
|
68
|
+
def now(cls):
|
|
69
|
+
"""Create an MPDatetime for the current date and time."""
|
|
70
|
+
return cls(datetime_obj=dt.datetime.now())
|
|
71
|
+
|
|
72
|
+
def to_datetime(self):
|
|
73
|
+
"""Convert to Python datetime.datetime."""
|
|
74
|
+
return self._datetime
|
|
75
|
+
|
|
76
|
+
def date(self):
|
|
77
|
+
"""Get the date part as MPDate."""
|
|
78
|
+
return MPDate(date=self._datetime.date())
|
|
79
|
+
|
|
80
|
+
def time(self):
|
|
81
|
+
"""Get the time part as MPTime."""
|
|
82
|
+
return MPTime(time=self._datetime.time())
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def year(self):
|
|
86
|
+
"""Return the year."""
|
|
87
|
+
return self._datetime.year
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def month(self):
|
|
91
|
+
"""Return the month."""
|
|
92
|
+
return self._datetime.month
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def day(self):
|
|
96
|
+
"""Return the day."""
|
|
97
|
+
return self._datetime.day
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def hour(self):
|
|
101
|
+
"""Return the hour."""
|
|
102
|
+
return self._datetime.hour
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def minute(self):
|
|
106
|
+
"""Return the minute."""
|
|
107
|
+
return self._datetime.minute
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def second(self):
|
|
111
|
+
"""Return the second."""
|
|
112
|
+
return self._datetime.second
|
|
113
|
+
|
|
114
|
+
def to_string(self, language="en", fmt=None):
|
|
115
|
+
"""
|
|
116
|
+
Format datetime in a given language.
|
|
117
|
+
|
|
118
|
+
Parameters:
|
|
119
|
+
language (str): Language code
|
|
120
|
+
fmt (str): Optional format override
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
str: Formatted datetime string
|
|
124
|
+
"""
|
|
125
|
+
if fmt is not None:
|
|
126
|
+
return self._datetime.strftime(fmt)
|
|
127
|
+
|
|
128
|
+
date_part = self.date().to_string(language)
|
|
129
|
+
time_part = self.time().to_string(language)
|
|
130
|
+
return f"{date_part} {time_part}"
|
|
131
|
+
|
|
132
|
+
def __eq__(self, other):
|
|
133
|
+
if isinstance(other, MPDatetime):
|
|
134
|
+
return self._datetime == other._datetime
|
|
135
|
+
return NotImplemented
|
|
136
|
+
|
|
137
|
+
def __lt__(self, other):
|
|
138
|
+
if isinstance(other, MPDatetime):
|
|
139
|
+
return self._datetime < other._datetime
|
|
140
|
+
return NotImplemented
|
|
141
|
+
|
|
142
|
+
def __hash__(self):
|
|
143
|
+
return hash(self._datetime)
|
|
144
|
+
|
|
145
|
+
def __str__(self):
|
|
146
|
+
return self.to_string("en")
|
|
147
|
+
|
|
148
|
+
def __repr__(self):
|
|
149
|
+
return (
|
|
150
|
+
f"MPDatetime({self._datetime.year}, {self._datetime.month}, "
|
|
151
|
+
f"{self._datetime.day}, {self._datetime.hour}, "
|
|
152
|
+
f"{self._datetime.minute}, {self._datetime.second})"
|
|
153
|
+
)
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
#
|
|
2
|
+
# SPDX-FileCopyrightText: 2024 John Samuel <johnsamuelwrites@gmail.com>
|
|
3
|
+
#
|
|
4
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
"""Multilingual time handling."""
|
|
8
|
+
|
|
9
|
+
import datetime as dt
|
|
10
|
+
from multilingualprogramming.exceptions import InvalidDateError
|
|
11
|
+
from multilingualprogramming.unicode_string import get_unicode_character_string
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MPTime:
|
|
15
|
+
"""
|
|
16
|
+
A time object with multilingual formatting.
|
|
17
|
+
|
|
18
|
+
Internally stores as Python datetime.time.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
# Script mapping for numeral conversion
|
|
22
|
+
SCRIPT_MAP = {
|
|
23
|
+
"hi": "DEVANAGARI",
|
|
24
|
+
"ar": "ARABIC-INDIC",
|
|
25
|
+
"bn": "BENGALI",
|
|
26
|
+
"ta": "TAMIL",
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
def __init__(self, hour=0, minute=0, second=0, time=None):
|
|
30
|
+
"""
|
|
31
|
+
Create an MPTime.
|
|
32
|
+
|
|
33
|
+
Parameters:
|
|
34
|
+
hour (int): Hour (0-23)
|
|
35
|
+
minute (int): Minute (0-59)
|
|
36
|
+
second (int): Second (0-59)
|
|
37
|
+
time (datetime.time): Python time object
|
|
38
|
+
"""
|
|
39
|
+
if time is not None:
|
|
40
|
+
self._time = time
|
|
41
|
+
else:
|
|
42
|
+
try:
|
|
43
|
+
self._time = dt.time(hour, minute, second)
|
|
44
|
+
except ValueError as e:
|
|
45
|
+
raise InvalidDateError(str(e)) from e
|
|
46
|
+
|
|
47
|
+
def to_time(self):
|
|
48
|
+
"""Convert to Python datetime.time."""
|
|
49
|
+
return self._time
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def hour(self):
|
|
53
|
+
"""Return the hour."""
|
|
54
|
+
return self._time.hour
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def minute(self):
|
|
58
|
+
"""Return the minute."""
|
|
59
|
+
return self._time.minute
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def second(self):
|
|
63
|
+
"""Return the second."""
|
|
64
|
+
return self._time.second
|
|
65
|
+
|
|
66
|
+
def _format_number(self, number, language, pad=True):
|
|
67
|
+
"""Format a number in the given script, zero-padded to 2 digits."""
|
|
68
|
+
unicode_script = self.SCRIPT_MAP.get(language)
|
|
69
|
+
if unicode_script:
|
|
70
|
+
numstr = get_unicode_character_string(unicode_script, number)
|
|
71
|
+
if pad and number < 10:
|
|
72
|
+
zero = get_unicode_character_string(unicode_script, 0)
|
|
73
|
+
numstr = zero + numstr
|
|
74
|
+
return numstr
|
|
75
|
+
if pad:
|
|
76
|
+
return f"{number:02d}"
|
|
77
|
+
return str(number)
|
|
78
|
+
|
|
79
|
+
def to_string(self, language="en", use_24h=True):
|
|
80
|
+
"""
|
|
81
|
+
Format time in a given language.
|
|
82
|
+
|
|
83
|
+
Parameters:
|
|
84
|
+
language (str): Language code
|
|
85
|
+
use_24h (bool): Use 24-hour format (default True)
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
str: Formatted time string
|
|
89
|
+
"""
|
|
90
|
+
if use_24h:
|
|
91
|
+
h = self._format_number(self._time.hour, language)
|
|
92
|
+
m = self._format_number(self._time.minute, language)
|
|
93
|
+
s = self._format_number(self._time.second, language)
|
|
94
|
+
return f"{h}:{m}:{s}"
|
|
95
|
+
|
|
96
|
+
# 12-hour format
|
|
97
|
+
hour_12 = self._time.hour % 12
|
|
98
|
+
if hour_12 == 0:
|
|
99
|
+
hour_12 = 12
|
|
100
|
+
is_pm = self._time.hour >= 12
|
|
101
|
+
|
|
102
|
+
h = self._format_number(hour_12, language)
|
|
103
|
+
m = self._format_number(self._time.minute, language)
|
|
104
|
+
s = self._format_number(self._time.second, language)
|
|
105
|
+
time_str = f"{h}:{m}:{s}"
|
|
106
|
+
|
|
107
|
+
am_pm_map = {
|
|
108
|
+
"en": ("AM", "PM"),
|
|
109
|
+
"fr": ("", ""),
|
|
110
|
+
"es": ("a.m.", "p.m."),
|
|
111
|
+
"de": ("", ""),
|
|
112
|
+
"hi": ("पूर्वाह्न", "अपराह्न"),
|
|
113
|
+
"ar": ("ص", "م"),
|
|
114
|
+
"bn": ("পূর্বাহ্ন", "অপরাহ্ন"),
|
|
115
|
+
"ta": ("முற்பகல்", "பிற்பகல்"),
|
|
116
|
+
"zh": ("上午", "下午"),
|
|
117
|
+
"ja": ("午前", "午後"),
|
|
118
|
+
}
|
|
119
|
+
am, pm = am_pm_map.get(language, ("AM", "PM"))
|
|
120
|
+
suffix = pm if is_pm else am
|
|
121
|
+
|
|
122
|
+
if language in ("zh", "ja"):
|
|
123
|
+
return f"{suffix} {time_str}"
|
|
124
|
+
if suffix:
|
|
125
|
+
return f"{time_str} {suffix}"
|
|
126
|
+
return time_str
|
|
127
|
+
|
|
128
|
+
def __eq__(self, other):
|
|
129
|
+
if isinstance(other, MPTime):
|
|
130
|
+
return self._time == other._time
|
|
131
|
+
return NotImplemented
|
|
132
|
+
|
|
133
|
+
def __lt__(self, other):
|
|
134
|
+
if isinstance(other, MPTime):
|
|
135
|
+
return self._time < other._time
|
|
136
|
+
return NotImplemented
|
|
137
|
+
|
|
138
|
+
def __hash__(self):
|
|
139
|
+
return hash(self._time)
|
|
140
|
+
|
|
141
|
+
def __str__(self):
|
|
142
|
+
return self.to_string("en")
|
|
143
|
+
|
|
144
|
+
def __repr__(self):
|
|
145
|
+
return (
|
|
146
|
+
f"MPTime({self._time.hour}, {self._time.minute}, {self._time.second})"
|
|
147
|
+
)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#
|
|
2
|
+
# SPDX-FileCopyrightText: 2024 John Samuel <johnsamuelwrites@gmail.com>
|
|
3
|
+
#
|
|
4
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
"""Shared helpers for datetime resource loading."""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def load_datetime_resource(filename):
|
|
14
|
+
"""Load a JSON resource from multilingual datetime resources."""
|
|
15
|
+
resources_dir = Path(__file__).parent.parent / "resources" / "datetime"
|
|
16
|
+
resource_path = resources_dir / filename
|
|
17
|
+
with open(resource_path, "r", encoding="utf-8") as resource_file:
|
|
18
|
+
return json.load(resource_file)
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
#
|
|
2
|
+
# SPDX-FileCopyrightText: 2022 John Samuel <johnsamuelwrites@gmail.com>
|
|
3
|
+
#
|
|
4
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
"""Exceptions"""
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class InvalidNumeralCharacterError(Exception):
|
|
11
|
+
"""
|
|
12
|
+
Exception raised when a character is not a valid digit
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, message):
|
|
16
|
+
message = "Invalid numeral: " + message
|
|
17
|
+
super().__init__(message)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class MultipleLanguageCharacterMixError(Exception):
|
|
21
|
+
"""
|
|
22
|
+
Exception raised when a numeral string contains mix of characters
|
|
23
|
+
from different languages
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, message):
|
|
27
|
+
message = "Mix of characters: " + message
|
|
28
|
+
super().__init__(message)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class DifferentNumeralTypeError(Exception):
|
|
32
|
+
"""
|
|
33
|
+
Exception raised when an operation is performed on different
|
|
34
|
+
types of numeral type
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, message):
|
|
38
|
+
message = "Invalid operation (different numeral type): " + message
|
|
39
|
+
super().__init__(message)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class UnknownKeywordError(Exception):
|
|
43
|
+
"""
|
|
44
|
+
Exception raised when a keyword string is not found in any language
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(self, message):
|
|
48
|
+
message = "Unknown keyword: " + message
|
|
49
|
+
super().__init__(message)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class AmbiguousKeywordError(Exception):
|
|
53
|
+
"""
|
|
54
|
+
Exception raised when a keyword matches multiple concepts
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(self, message):
|
|
58
|
+
message = "Ambiguous keyword: " + message
|
|
59
|
+
super().__init__(message)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class UnsupportedLanguageError(Exception):
|
|
63
|
+
"""
|
|
64
|
+
Exception raised when a requested language is not in the registry
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
def __init__(self, message):
|
|
68
|
+
message = "Unsupported language: " + message
|
|
69
|
+
super().__init__(message)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class InvalidDateError(Exception):
|
|
73
|
+
"""
|
|
74
|
+
Exception raised for malformed multilingual dates
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
def __init__(self, message):
|
|
78
|
+
message = "Invalid date: " + message
|
|
79
|
+
super().__init__(message)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class LexerError(Exception):
|
|
83
|
+
"""
|
|
84
|
+
Base exception for lexer errors
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
def __init__(self, message, line=None, column=None):
|
|
88
|
+
location = ""
|
|
89
|
+
if line is not None:
|
|
90
|
+
location = f" at line {line}"
|
|
91
|
+
if column is not None:
|
|
92
|
+
location += f", column {column}"
|
|
93
|
+
message = "Lexer error" + location + ": " + message
|
|
94
|
+
self.line = line
|
|
95
|
+
self.column = column
|
|
96
|
+
super().__init__(message)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class UnexpectedTokenError(LexerError):
|
|
100
|
+
"""
|
|
101
|
+
Exception raised for unexpected tokens during lexing
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
def __init__(self, message, line=None, column=None):
|
|
105
|
+
super().__init__("Unexpected token: " + message, line, column)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class ParseError(LexerError):
|
|
109
|
+
"""
|
|
110
|
+
Base exception for parser errors
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
def __init__(self, message, line=None, column=None):
|
|
114
|
+
super().__init__("Parse error: " + message, line, column)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class SemanticError(Exception):
|
|
118
|
+
"""
|
|
119
|
+
Exception for semantic analysis errors
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
def __init__(self, message, line=None, column=None):
|
|
123
|
+
location = ""
|
|
124
|
+
if line is not None:
|
|
125
|
+
location = f" at line {line}"
|
|
126
|
+
if column is not None:
|
|
127
|
+
location += f", column {column}"
|
|
128
|
+
self.line = line
|
|
129
|
+
self.column = column
|
|
130
|
+
message = "Semantic error" + location + ": " + message
|
|
131
|
+
super().__init__(message)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class CodeGenerationError(Exception):
|
|
135
|
+
"""
|
|
136
|
+
Exception for code generation errors
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
def __init__(self, message, line=None, column=None):
|
|
140
|
+
location = ""
|
|
141
|
+
if line is not None:
|
|
142
|
+
location = f" at line {line}"
|
|
143
|
+
if column is not None:
|
|
144
|
+
location += f", column {column}"
|
|
145
|
+
self.line = line
|
|
146
|
+
self.column = column
|
|
147
|
+
message = "Code generation error" + location + ": " + message
|
|
148
|
+
super().__init__(message)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class RuntimeExecutionError(Exception):
|
|
152
|
+
"""
|
|
153
|
+
Exception for runtime execution errors
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
def __init__(self, message):
|
|
157
|
+
message = "Runtime error: " + message
|
|
158
|
+
super().__init__(message)
|