Commit 1d92a91e authored by Roman Alifanov's avatar Roman Alifanov

Add json.unmarshal/marshal, reflect module, update telegram bot

- json.unmarshal(str, Class) deserializes JSON into class instance - json.marshal(obj) serializes class instance to JSON string - reflect module: fields, get, set, type, class_name, create - Class metadata generation for runtime introspection - Fix DCE namespace detection inside function bodies - Update telegram bot example with Message class, /json, /info commands
parent f11ced15
......@@ -878,6 +878,31 @@ count = json.get (items, ".items | length") # 3
Использует jq под капотом — поддерживает все jq-пути.
**json.unmarshal — JSON в объект класса:**
```
class User {
name: string = ""
age: int = 0
active: bool = false
}
user = json.unmarshal ('\{"name":"Alice","age":30,"active":true\}', User)
print (user.name) # Alice
print (user.age) # 30
```
Создаёт экземпляр указанного класса и заполняет поля из JSON. Второй аргумент — имя класса (не строка, а идентификатор). Работает только со скалярными полями (string, int, float, bool).
**json.marshal — объект класса в JSON:**
```
output = json.marshal (user)
print (output) # {"name":"Alice","age":30,"active":true}
```
Сериализует поля объекта в JSON-строку. Числовые и булевы типы выводятся без кавычек, строки — в кавычках.
**Пример: Telegram бот**
```
......@@ -891,6 +916,50 @@ http.get ("{base_url}/sendMessage?chat_id={chat_id}&text={encoded}")
**Примечание:** фигурные скобки в строках нужно экранировать как `\{` и `\}`.
### reflect
Модуль для runtime-интроспекции классов. Предназначен для авторов библиотек, создающих форматы сериализации, ORM и подобные инструменты.
```
class User {
name: string = ""
age: int = 0
}
user = new User ()
user.name = "Alice"
user.age = 30
# Получить список полей класса
fields = reflect.fields (user) # ["name", "age"]
# Получить/установить значение поля по имени
val = reflect.get (user, "name") # "Alice"
reflect.set (user, "name", "Bob")
# Получить тип поля
t = reflect.type (user, "name") # "string"
# Получить имя класса объекта
cls = reflect.class_name (user) # "User"
# Создать новый экземпляр класса по имени
obj = reflect.create ("User")
```
**Методы:**
| Метод | Описание |
|-------|----------|
| `reflect.fields (obj)` | Возвращает массив имён полей класса |
| `reflect.get (obj, field)` | Возвращает значение поля |
| `reflect.set (obj, field, value)` | Устанавливает значение поля |
| `reflect.type (obj, field)` | Возвращает тип поля (`"string"`, `"int"`, `"float"`, `"bool"`) |
| `reflect.class_name (obj)` | Возвращает имя класса объекта |
| `reflect.create (class_name)` | Создаёт новый экземпляр класса по строковому имени |
**Ограничения:** работает только со скалярными полями (string, int, float, bool). Массивы и вложенные объекты не поддерживаются.
### logger
```
......@@ -1318,7 +1387,8 @@ Error: Unknown method 'badMethod' for type 'fs'. Available: append, exists, list
- **Stdlib namespaces**:
- `fs``append`, `exists`, `list`, `mkdir`, `open`, `read`, `remove`, `write`
- `http``delete`, `get`, `post`, `put`
- `json``get`, `parse`, `stringify`
- `json``get`, `marshal`, `parse`, `stringify`, `unmarshal`
- `reflect``class_name`, `create`, `fields`, `get`, `set`, `type`
- `logger``debug`, `error`, `info`, `warn`
- `regex``extract`, `match`
- `args``count`, `get`
......
......@@ -208,6 +208,38 @@ squared = numbers.map (x => x * x) # [1, 4, 9, 16, 25]
evens = numbers.filter (x => x % 2 == 0) # [2, 4]
```
### JSON Marshal/Unmarshal
Go-style JSON serialization with classes:
```
class User {
name: string = ""
age: int = 0
}
# JSON → class instance
user = json.unmarshal ('\{"name":"Alice","age":30\}', User)
print (user.name) # Alice
# Class instance → JSON
output = json.marshal (user)
print (output) # {"name":"Alice","age":30}
```
### Reflect (Runtime Introspection)
For library authors building serialization, ORM, and similar tools:
```
fields = reflect.fields (user) # ["name", "age"]
val = reflect.get (user, "name") # "Alice"
reflect.set (user, "name", "Bob")
t = reflect.type (user, "name") # "string"
cls = reflect.class_name (user) # "User"
obj = reflect.create ("User") # new User instance
```
### File Handles & Context Managers
```
......@@ -272,7 +304,8 @@ try {
| **HTTP** | `http.get/post/put/delete` |
| **Filesystem** | `fs.read/write/append/exists/remove/mkdir/list`, `fs.open()` |
| **File handles** | `f.read()`, `f.readline()`, `f.write()`, `f.writeln()`, `f.close()` |
| **JSON** | `json.parse()` → dict, `json.stringify()` → string, `json.get(str, path)` → extract by jq path |
| **JSON** | `json.parse()` → dict, `json.stringify()` → string, `json.get(str, path)` → extract by jq path, `json.unmarshal(str, Class)` → class instance, `json.marshal(obj)` → JSON string |
| **Reflect** | `reflect.fields(obj)`, `reflect.get(obj, field)`, `reflect.set(obj, field, value)`, `reflect.type(obj, field)`, `reflect.class_name(obj)`, `reflect.create(name)` |
| **Strings** | `.len()`, `.upper()`, `.lower()`, `.trim()`, `.contains()`, `.replace()`, `.split()`, `.substr()`, `.urlencode()` |
| **Arrays** | `.push()`, `.pop()`, `.shift()`, `.len()`, `.get()`, `.set()`, `.join()`, `.slice()`, `.map()`, `.filter()` |
| **Dicts** | `.get()`, `.set()`, `.has()`, `.del()`, `.keys()` |
......@@ -436,8 +469,11 @@ python3 content run examples/telegram_echobot/telegram.ct examples/telegram_echo
```
Features:
- `Message` class for structured message handling
- User decorators for command registration (`@bot.command("start")`)
- Callbacks for message handlers
- `json.marshal()` for JSON logging of incoming messages
- `reflect` for runtime introspection (`/info` command)
- `json.get()` for parsing Telegram API responses
- `str.urlencode()` for UTF-8 URL encoding
......
......@@ -208,6 +208,38 @@ squared = numbers.map (x => x * x) # [1, 4, 9, 16, 25]
evens = numbers.filter (x => x % 2 == 0) # [2, 4]
```
### JSON Marshal/Unmarshal
Сериализация JSON в стиле Go через классы:
```
class User {
name: string = ""
age: int = 0
}
# JSON → экземпляр класса
user = json.unmarshal ('\{"name":"Alice","age":30\}', User)
print (user.name) # Alice
# Экземпляр класса → JSON
output = json.marshal (user)
print (output) # {"name":"Alice","age":30}
```
### Reflect (интроспекция)
Для авторов библиотек — сериализация, ORM и подобные инструменты:
```
fields = reflect.fields (user) # ["name", "age"]
val = reflect.get (user, "name") # "Alice"
reflect.set (user, "name", "Bob")
t = reflect.type (user, "name") # "string"
cls = reflect.class_name (user) # "User"
obj = reflect.create ("User") # новый экземпляр User
```
### Файловые дескрипторы и контекстные менеджеры
```
......@@ -272,7 +304,8 @@ try {
| **HTTP** | `http.get/post/put/delete` |
| **Файловая система** | `fs.read/write/append/exists/remove/mkdir/list`, `fs.open()` |
| **Файловые дескрипторы** | `f.read()`, `f.readline()`, `f.write()`, `f.writeln()`, `f.close()` |
| **JSON** | `json.parse()` → dict, `json.stringify()` → string, `json.get(str, path)` → извлечь по jq-пути |
| **JSON** | `json.parse()` → dict, `json.stringify()` → string, `json.get(str, path)` → извлечь по jq-пути, `json.unmarshal(str, Class)` → экземпляр класса, `json.marshal(obj)` → JSON-строка |
| **Reflect** | `reflect.fields(obj)`, `reflect.get(obj, field)`, `reflect.set(obj, field, value)`, `reflect.type(obj, field)`, `reflect.class_name(obj)`, `reflect.create(name)` |
| **Строки** | `.len()`, `.upper()`, `.lower()`, `.trim()`, `.contains()`, `.replace()`, `.split()`, `.substr()`, `.urlencode()` |
| **Массивы** | `.push()`, `.pop()`, `.shift()`, `.len()`, `.get()`, `.set()`, `.join()`, `.slice()`, `.map()`, `.filter()` |
| **Словари** | `.get()`, `.set()`, `.has()`, `.del()`, `.keys()` |
......@@ -428,8 +461,11 @@ python3 content run examples/telegram_echobot/telegram.ct examples/telegram_echo
```
Возможности:
- Класс `Message` для структурированной обработки сообщений
- Пользовательские декораторы для регистрации команд (`@bot.command("start")`)
- Колбеки для обработчиков сообщений
- `json.marshal()` для JSON-логирования входящих сообщений
- `reflect` для интроспекции в рантайме (команда `/info`)
- `json.get()` для парсинга ответов Telegram API
- `str.urlencode()` для UTF-8 URL-кодирования
......
......@@ -62,6 +62,7 @@ class ClassMixin:
self._check_inlineable_method(cls, method)
self._generate_class_constructor(cls)
self._generate_class_metadata(cls)
if cls.constructor:
self._generate_construct_method(cls)
......@@ -139,6 +140,27 @@ class ClassMixin:
self.emit("}")
self.emit()
def _generate_class_metadata(self, cls: ClassDecl):
field_names = []
field_types = {}
for field in cls.fields:
field_name, type_annotation, _ = self._get_field_info(field)
field_names.append(field_name)
ft = self.class_field_types.get((cls.name, field_name), "scalar")
if ft == "scalar" and type_annotation:
field_types[field_name] = type_annotation.name
elif ft == "scalar":
field_types[field_name] = "string"
else:
field_types[field_name] = ft
fields_str = " ".join([f'"{f}"' for f in field_names])
self.emit(f'declare -ga __ct_class_meta_{cls.name}_fields=({fields_str})')
types_pairs = " ".join([f'["{f}"]="{t}"' for f, t in field_types.items()])
self.emit(f'declare -gA __ct_class_meta_{cls.name}_types=({types_pairs})')
self.emit()
def _generate_construct_method(self, cls: ClassDecl):
"""Generate construct method."""
self.emit(f"__ct_class_{cls.name}_construct () {{")
......
......@@ -407,21 +407,18 @@ class UsageAnalyzer:
self.used.add('array')
elif ns in self.dict_variables:
self.used.add('dict')
elif hasattr(self, 'current_func_name') and self.current_func_name:
key = (self.current_func_name, ns)
if key in self.func_param_types:
for obj_class in self.func_param_types[key]:
if obj_class not in self.used_methods:
self.used_methods[obj_class] = set()
self.used_methods[obj_class].add(method)
else:
self._check_method(method)
elif ns == 'http':
self.used.add('http')
elif ns == 'fs':
self.used.add('fs')
elif ns == 'json':
self.used.add('json')
if method in ('unmarshal',) and len(expr.arguments) >= 2:
arg2 = expr.arguments[1]
if isinstance(arg2, Identifier) and arg2.name in self.defined_classes:
self.has_classes = True
self.used_classes.add(arg2.name)
self.used.add('object')
elif ns == 'logger':
self.used.add('logger')
elif ns == 'regex':
......@@ -432,11 +429,31 @@ class UsageAnalyzer:
self.used.add('time')
elif ns == 'args':
self.used.add('args')
elif ns == 'reflect':
self.used.add('reflect')
self.used.add('object')
if method == 'create' and len(expr.arguments) >= 1:
arg1 = expr.arguments[0]
cls_ref = None
if isinstance(arg1, Identifier) and arg1.name in self.defined_classes:
cls_ref = arg1.name
elif isinstance(arg1, StringLiteral) and arg1.value in self.defined_classes:
cls_ref = arg1.value
if cls_ref:
self.has_classes = True
self.used_classes.add(cls_ref)
elif ns == 'shell':
pass
elif hasattr(self, 'current_func_name') and self.current_func_name:
key = (self.current_func_name, ns)
if key in self.func_param_types:
for obj_class in self.func_param_types[key]:
if obj_class not in self.used_methods:
self.used_methods[obj_class] = set()
self.used_methods[obj_class].add(method)
else:
self._check_method(method)
else:
# Check if this could be a class method (conservative approach)
# Include method in all classes that define it
found_in_class = False
for cls_name, cls_decl in self.defined_classes.items():
for m in cls_decl.methods:
......
......@@ -250,6 +250,30 @@ class DispatchMixin:
self.emit(f'__ct_json_parse "{args[0]}" "{target}"')
self.dict_vars.add(target)
return
if isinstance(callee.object, Identifier) and callee.object.name == "json" and callee.member == "unmarshal":
args = [self.generate_expr(arg) for arg in stmt.value.arguments]
class_name = stmt.value.arguments[1].name if isinstance(stmt.value.arguments[1], Identifier) else args[1]
self.emit(f'__ct_json_unmarshal "{args[0]}" "{class_name}"')
self.emit_var_assign(target, '$__ct_last_instance')
self.object_vars.add(target)
self.instance_vars[target] = class_name
return
if isinstance(callee.object, Identifier) and callee.object.name == "reflect" and callee.member == "create":
args = [self.generate_expr(arg) for arg in stmt.value.arguments]
self.emit(f'__ct_reflect_create "{args[0]}"')
self.emit_var_assign(target, '$__ct_last_instance')
self.object_vars.add(target)
return
if isinstance(callee.object, Identifier) and callee.object.name == "reflect" and callee.member == "fields":
args = [self._generate_call_arg(arg) for arg in stmt.value.arguments]
self.emit(f'__ct_reflect_fields "{args[0]}"')
if self.in_function and target not in self.local_vars and target not in self.global_vars:
self.local_vars.add(target)
self.emit(f'local -a {target}=("${{{RET_ARR}[@]}}")')
else:
self.emit(f'{target}=("${{{RET_ARR}[@]}}")')
self.array_vars.add(target)
return
if self._generate_method_call_assignment(stmt, target):
return
......@@ -1145,7 +1169,11 @@ class DispatchMixin:
elif obj_name == "fs":
return f'__ct_fs_{method} {args_str}'
elif obj_name == "json":
if method == "marshal":
return f'__ct_json_marshal {args_str}'
return f'__ct_json_{method} {args_str}'
elif obj_name == "reflect":
return f'__ct_reflect_{method} {args_str}'
elif obj_name == "logger" and method in ("info", "warn", "error", "debug"):
return f'__ct_logger_{method} {args_str}'
elif obj_name == "regex":
......
......@@ -12,6 +12,7 @@ from .math import MathMethods
from .time import TimeMethods
from .args import ArgsMethods
from .core import CoreFunctions, AwkBuiltinFunctions
from .reflect import ReflectMethods
STRING_METHODS = collect_methods(StringMethods)
ARRAY_METHODS = collect_methods(ArrayMethods)
......@@ -27,6 +28,7 @@ TIME_METHODS = collect_methods(TimeMethods)
ARGS_METHODS = collect_methods(ArgsMethods)
CORE_FUNCTIONS = collect_methods(CoreFunctions)
AWK_BUILTIN_FUNCTIONS = collect_methods(AwkBuiltinFunctions)
REFLECT_METHODS = collect_methods(ReflectMethods)
NAMESPACE_REGISTRY = {
"fs": FS_METHODS,
......@@ -37,6 +39,7 @@ NAMESPACE_REGISTRY = {
"args": ARGS_METHODS,
"time": TIME_METHODS,
"math": MATH_METHODS,
"reflect": REFLECT_METHODS,
"shell": {"exec", "capture", "source"},
}
......
......@@ -20,3 +20,15 @@ class JsonMethods:
bash_impl='echo "$1" | jq -r "$2" 2>/dev/null',
min_args=2, max_args=2,
)
unmarshal = Method(
name="unmarshal",
bash_func="__ct_json_unmarshal",
bash_impl=None,
min_args=2, max_args=2,
)
marshal = Method(
name="marshal",
bash_func="__ct_json_marshal",
bash_impl=None,
min_args=1, max_args=1,
)
from .base import Method
class ReflectMethods:
fields = Method(
name="fields",
bash_func="__ct_reflect_fields",
bash_impl=None,
min_args=1, max_args=1,
returns_array=True,
)
get = Method(
name="get",
bash_func="__ct_reflect_get",
bash_impl=None,
min_args=2, max_args=2,
)
set = Method(
name="set",
bash_func="__ct_reflect_set",
bash_impl=None,
min_args=3, max_args=3,
)
type = Method(
name="type",
bash_func="__ct_reflect_type",
bash_impl=None,
min_args=2, max_args=2,
)
class_name = Method(
name="class_name",
bash_func="__ct_reflect_class_name",
bash_impl=None,
min_args=1, max_args=1,
)
create = Method(
name="create",
bash_func="__ct_reflect_create",
bash_impl=None,
min_args=1, max_args=1,
)
......@@ -65,6 +65,8 @@ class StdlibMixin:
self._emit_math()
if 'dict' in used_categories:
self._emit_dict()
if 'reflect' in used_categories:
self._emit_reflect()
if 'misc' in used_categories or 'time' in used_categories:
self._emit_misc()
if 'test' in used_categories:
......@@ -163,6 +165,52 @@ class StdlibMixin:
self.emit("}")
self.emit()
self.emit("__ct_json_unmarshal () {")
with self.indented():
self.emit('local __json="$1" __class="$2"')
self.emit('"$__class"')
self.emit('local __obj="$__ct_last_instance"')
self.emit('local __cls="${__ct_obj_class[$__obj]}"')
self.emit('local -n __fields="__ct_class_meta_${__cls}_fields"')
self.emit('local -n __types="__ct_class_meta_${__cls}_types"')
self.emit('for __f in "${__fields[@]}"; do')
with self.indented():
self.emit('local __val')
self.emit('__val="$(echo "$__json" | jq -r --arg f "$__f" \'.[$f] // empty\' 2>/dev/null)"')
self.emit('if [[ -n "$__val" ]]; then')
with self.indented():
self.emit('__CT_OBJ["$__obj.$__f"]="$__val"')
self.emit('fi')
self.emit('done')
self.emit("}")
self.emit()
self.emit("__ct_json_marshal () {")
with self.indented():
self.emit('local __obj="$1"')
self.emit('local __cls="${__ct_obj_class[$__obj]}"')
self.emit('local -n __fields="__ct_class_meta_${__cls}_fields"')
self.emit('local -n __types="__ct_class_meta_${__cls}_types"')
self.emit('local __first=1')
self.emit('printf "{"')
self.emit('for __f in "${__fields[@]}"; do')
with self.indented():
self.emit('[[ $__first -eq 1 ]] && __first=0 || printf ","')
self.emit('printf "\\"%s\\":" "$__f"')
self.emit('local __v="${__CT_OBJ["$__obj.$__f"]}"')
self.emit('local __t="${__types[$__f]}"')
self.emit('if [[ "$__t" == "int" || "$__t" == "float" || "$__t" == "bool" ]]; then')
with self.indented():
self.emit('printf "%s" "$__v"')
self.emit('else')
with self.indented():
self.emit('printf "\\"%s\\"" "$__v"')
self.emit('fi')
self.emit('done')
self.emit('printf "}\\n"')
self.emit("}")
self.emit()
self.emit("__ct_json_stringify () {")
with self.indented():
self.emit('local -n __d="$1"')
......@@ -325,6 +373,57 @@ class StdlibMixin:
self.emit(f"{method_def.bash_func} () {{ {method_def.bash_impl}; }}")
self.emit()
def _emit_reflect(self):
self.emit("# Reflect functions")
self.emit("__ct_reflect_fields () {")
with self.indented():
self.emit('local __obj="$1"')
self.emit('local __cls="${__ct_obj_class[$__obj]}"')
self.emit('local -n __fields="__ct_class_meta_${__cls}_fields"')
self.emit('__CT_RET_ARR=("${__fields[@]}")')
self.emit("}")
self.emit()
self.emit("__ct_reflect_type () {")
with self.indented():
self.emit('local __obj="$1" __field="$2"')
self.emit('local __cls="${__ct_obj_class[$__obj]}"')
self.emit('local -n __types="__ct_class_meta_${__cls}_types"')
self.emit('__CT_RET="${__types[$__field]}"')
self.emit('echo "${__types[$__field]}"')
self.emit("}")
self.emit()
self.emit("__ct_reflect_get () {")
with self.indented():
self.emit('local __obj="$1" __field="$2"')
self.emit('__CT_RET="${__CT_OBJ["$__obj.$__field"]}"')
self.emit('echo "${__CT_OBJ["$__obj.$__field"]}"')
self.emit("}")
self.emit()
self.emit("__ct_reflect_set () {")
with self.indented():
self.emit('local __obj="$1" __field="$2" __value="$3"')
self.emit('__CT_OBJ["$__obj.$__field"]="$__value"')
self.emit("}")
self.emit()
self.emit("__ct_reflect_class_name () {")
with self.indented():
self.emit('local __obj="$1"')
self.emit('__CT_RET="${__ct_obj_class[$__obj]}"')
self.emit('echo "${__ct_obj_class[$__obj]}"')
self.emit("}")
self.emit()
self.emit("__ct_reflect_create () {")
with self.indented():
self.emit('local __class="$1"; shift')
self.emit('"$__class" "$@"')
self.emit("}")
self.emit()
def _emit_dict(self):
"""Dict functions from DICT_METHODS."""
self.emit("# Dict functions")
......
......@@ -7,17 +7,20 @@ if token == "" {
bot = new TelegramBot (token)
@bot.command ("start")
func handle_start (chat_id, text, arg) {
bot.send (chat_id, "Welcome! Commands:\n/help - Show help\n/echo <text> - Echo text")
func handle_start (msg: Message, arg) {
chat_id = msg.chat_id
bot.send (chat_id, "Welcome! Commands:\n/help - Show help\n/echo <text> - Echo text\n/json - Message as JSON\n/info - Reflect message fields")
}
@bot.command ("help")
func handle_help (chat_id, text, arg) {
bot.send (chat_id, "Available commands:\n/start - Start bot\n/help - Show this help\n/echo <text> - Echo back text")
func handle_help (msg: Message, arg) {
chat_id = msg.chat_id
bot.send (chat_id, "Available commands:\n/start - Start bot\n/help - This help\n/echo <text> - Echo back text\n/json - Show message as JSON (json.marshal)\n/info - Show message fields (reflect)")
}
@bot.command ("echo")
func handle_echo (chat_id, text, arg) {
func handle_echo (msg: Message, arg) {
chat_id = msg.chat_id
if arg == "" {
bot.send (chat_id, "Usage: /echo <text>")
} else {
......@@ -25,8 +28,32 @@ func handle_echo (chat_id, text, arg) {
}
}
@bot.command ("json")
func handle_json (msg: Message, arg) {
chat_id = msg.chat_id
result = json.marshal (msg)
bot.send (chat_id, result)
}
@bot.command ("info")
func handle_info (msg: Message, arg) {
chat_id = msg.chat_id
cls = reflect.class_name (msg)
fields = reflect.fields (msg)
field_list = fields.join (", ")
response = "Class: {cls}\nFields: {field_list}"
foreach f in fields {
t = reflect.type (msg, f)
v = reflect.get (msg, f)
response = response .. "\n {f} ({t}) = {v}"
}
bot.send (chat_id, response)
}
@bot.on_message ()
func handle_message (chat_id, text, arg) {
func handle_message (msg: Message, arg) {
chat_id = msg.chat_id
text = msg.text
bot.send (chat_id, text)
}
......
class Message {
chat_id: string = ""
from_name: string = ""
text: string = ""
update_id: string = "0"
}
class TelegramBot {
token = ""
base_url = ""
......@@ -47,16 +54,20 @@ class TelegramBot {
if count != "0" {
i = 0
while i < count {
update_id = json.get (response, ".result[{i}].update_id")
chat_id = json.get (response, ".result[{i}].message.chat.id")
text = json.get (response, ".result[{i}].message.text")
from_name = json.get (response, ".result[{i}].message.from.first_name")
msg = new Message ()
msg.update_id = json.get (response, ".result[{i}].update_id")
msg.chat_id = json.get (response, ".result[{i}].message.chat.id")
msg.text = json.get (response, ".result[{i}].message.text")
msg.from_name = json.get (response, ".result[{i}].message.from.first_name")
update_id = msg.update_id
offset = update_id + 1
text = msg.text
if text != "null" {
print ("{from_name}: {text}")
this._dispatch (chat_id, text)
log = json.marshal (msg)
print (log)
this._dispatch (msg)
}
i = i + 1
......@@ -71,7 +82,10 @@ class TelegramBot {
return response
}
func _dispatch (chat_id, text) {
func _dispatch (msg: Message) {
text = msg.text
chat_id = msg.chat_id
if text.starts ("/") {
space_idx = text.index (" ")
if space_idx > 0 {
......@@ -84,17 +98,17 @@ class TelegramBot {
if this.commands.has (cmd) {
handler = this.commands.get (cmd)
this._invoke (handler, chat_id, text, arg)
this._invoke (handler, msg, arg)
return
}
}
if this.message_handler != "" {
this._invoke (this.message_handler, chat_id, text, "")
this._invoke (this.message_handler, msg, "")
}
}
func _invoke (handler, chat_id, text, arg) {
handler (chat_id, text, arg)
func _invoke (handler, msg, arg) {
handler (msg, arg)
}
}
......@@ -267,3 +267,169 @@ test ()
lines = stdout.strip().split('\n')
assert "work" in lines[0]
assert "cleanup" in lines[1]
class TestJsonMarshal:
def test_json_unmarshal_basic(self):
code, stdout, _ = run_ct(r'''
class User {
name: string = ""
age: int = 0
}
json_str = "\{\"name\":\"Alice\",\"age\":30\}"
user = json.unmarshal(json_str, User)
print(user.name)
print(user.age)
''')
assert code == 0
assert "Alice" in stdout
assert "30" in stdout
def test_json_marshal_basic(self):
code, stdout, _ = run_ct(r'''
class Config {
host: string = ""
port: int = 0
}
json_str = "\{\"host\":\"localhost\",\"port\":8080\}"
cfg = json.unmarshal(json_str, Config)
output = json.marshal(cfg)
print(output)
''')
assert code == 0
assert '"host"' in stdout
assert "localhost" in stdout
assert '"port"' in stdout
assert "8080" in stdout
def test_json_marshal_types(self):
code, stdout, _ = run_ct(r'''
class Item {
name: string = ""
count: int = 0
active: bool = false
}
json_str = "\{\"name\":\"widget\",\"count\":5,\"active\":true\}"
item = json.unmarshal(json_str, Item)
output = json.marshal(item)
print(output)
''')
assert code == 0
assert '"name":"widget"' in stdout
assert '"count":5' in stdout
assert '"active":true' in stdout
def test_json_roundtrip(self):
code, stdout, _ = run_ct(r'''
class Point {
x: int = 0
y: int = 0
}
json_str = "\{\"x\":10,\"y\":20\}"
p = json.unmarshal(json_str, Point)
p.x = 100
output = json.marshal(p)
print(output)
''')
assert code == 0
assert '"x":100' in stdout
assert '"y":20' in stdout
class TestReflect:
def test_reflect_fields(self):
code, stdout, _ = run_ct(r'''
class User {
name: string = ""
age: int = 0
}
user = new User()
fields = reflect.fields(user)
print(fields.join(","))
''')
assert code == 0
assert "name" in stdout
assert "age" in stdout
def test_reflect_get_set(self):
code, stdout, _ = run_ct(r'''
class User {
name: string = ""
}
user = new User()
reflect.set(user, "name", "Alice")
val = reflect.get(user, "name")
print(val)
''')
assert code == 0
assert "Alice" in stdout
def test_reflect_type(self):
code, stdout, _ = run_ct(r'''
class Config {
host: string = ""
port: int = 0
debug: bool = false
}
cfg = new Config()
print(reflect.type(cfg, "host"))
print(reflect.type(cfg, "port"))
print(reflect.type(cfg, "debug"))
''')
assert code == 0
lines = stdout.strip().split('\n')
assert "string" in lines[0]
assert "int" in lines[1]
assert "bool" in lines[2]
def test_reflect_class_name(self):
code, stdout, _ = run_ct(r'''
class MyClass {
x: int = 0
}
obj = new MyClass()
print(reflect.class_name(obj))
''')
assert code == 0
assert "MyClass" in stdout
def test_reflect_create(self):
code, stdout, _ = run_ct(r'''
class Item {
name: string = "default"
}
obj = reflect.create("Item")
print(obj.name)
''')
assert code == 0
assert "default" in stdout
def test_reflect_with_json(self):
code, stdout, _ = run_ct(r'''
class User {
name: string = ""
age: int = 0
}
json_str = "\{\"name\":\"Bob\",\"age\":25\}"
user = json.unmarshal(json_str, User)
fields = reflect.fields(user)
foreach f in fields {
val = reflect.get(user, f)
tp = reflect.type(user, f)
print("{f}:{tp}={val}")
}
''')
assert code == 0
assert "name:string=Bob" in stdout
assert "age:int=25" in stdout
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment