Commit 8e34932a authored by Roman Alifanov's avatar Roman Alifanov

Implement json.parse returning dict and json.stringify

- json.parse() now creates a dict from JSON string - json.stringify() converts dict back to JSON - Dict methods (.get, .set, .has, .del, .keys) work on parsed JSON - Added 4 integration tests for JSON functionality - Updated documentation
parent 9a9ffb41
......@@ -570,11 +570,22 @@ with input, output in fs.open (src), fs.open (dst, "w") {
### json
Парсинг JSON в dict и обратно:
```
obj = json.parse (string)
string = json.stringify (obj)
json_str = "\{\"name\": \"Alice\", \"age\": 30\}"
data = json.parse (json_str)
name = data.get ("name") # "Alice"
data.set ("city", "NYC")
data.has ("name") # true
result = json.stringify (data)
# {"name":"Alice","age":30,"city":"NYC"}
```
**Примечание:** фигурные скобки в строках нужно экранировать как `\{` и `\}`.
### logger
```
......
......@@ -199,7 +199,7 @@ 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/stringify` |
| **JSON** | `json.parse()` → dict, `json.stringify()` → string |
| **Strings** | `.len()`, `.upper()`, `.lower()`, `.trim()`, `.contains()`, `.replace()`, `.split()`, `.substr()` |
| **Arrays** | `.push()`, `.pop()`, `.shift()`, `.len()`, `.get()`, `.set()`, `.join()`, `.slice()`, `.map()`, `.filter()` |
| **Dicts** | `.get()`, `.set()`, `.has()`, `.del()`, `.keys()` |
......
......@@ -199,7 +199,7 @@ 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/stringify` |
| **JSON** | `json.parse()` → dict, `json.stringify()` → string |
| **Строки** | `.len()`, `.upper()`, `.lower()`, `.trim()`, `.contains()`, `.replace()`, `.split()`, `.substr()` |
| **Массивы** | `.push()`, `.pop()`, `.shift()`, `.len()`, `.get()`, `.set()`, `.join()`, `.slice()`, `.map()`, `.filter()` |
| **Словари** | `.get()`, `.set()`, `.has()`, `.del()`, `.keys()` |
......
......@@ -156,6 +156,14 @@ class DispatchMixin:
self.emit_var_assign(target, '$__CT_RET')
self.file_handle_vars.add(target)
return
if isinstance(callee.object, Identifier) and callee.object.name == "json" and callee.member == "parse":
args = [self.generate_expr(arg) for arg in stmt.value.arguments]
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}=()')
self.emit(f'__ct_json_parse "{args[0]}" "{target}"')
self.dict_vars.add(target)
return
if self._generate_method_call_assignment(stmt, target):
return
......@@ -640,9 +648,17 @@ class DispatchMixin:
def generate_call_statement(self, expr: CallExpr) -> str:
callee = expr.callee
location = getattr(expr, 'location', None) or getattr(callee, 'location', None)
if isinstance(callee, MemberAccess) and isinstance(callee.object, Identifier):
if callee.object.name == "json" and callee.member == "stringify":
if len(expr.arguments) == 1 and isinstance(expr.arguments[0], Identifier):
dict_name = expr.arguments[0].name
if dict_name in self.dict_vars:
return f'__ct_json_stringify "{dict_name}"'
args = [self.generate_expr(arg) for arg in expr.arguments]
args_str = " ".join([f'"{a}"' for a in args])
location = getattr(expr, 'location', None) or getattr(callee, 'location', None)
if isinstance(callee, Identifier):
return self._generate_builtin_call(callee.name, args, args_str)
......
......@@ -316,14 +316,46 @@ class StdlibMixin:
"""JSON functions: parse, stringify."""
self.emit ("__ct_json_parse () {")
self.indent_level += 1
self.emit ('echo "$1" | jq -r "."')
self.emit ('local __json="$1"')
self.emit ('local __dict_name="$2"')
self.emit ('eval "declare -gA $__dict_name=()"')
self.emit ('while IFS= read -r __line; do')
self.indent_level += 1
self.emit ('local __key="${__line%%=*}"')
self.emit ('local __val="${__line#*=}"')
self.emit ('[[ -n "$__key" ]] && eval "${__dict_name}[\\"$__key\\"]=\"\\$__val\""')
self.indent_level -= 1
self.emit ('done < <(echo "$__json" | jq -r \'to_entries[] | "\\(.key)=\\(.value)\"\' 2>/dev/null)')
self.indent_level -= 1
self.emit ("}")
self.emit ()
self.emit ("__ct_json_stringify () {")
self.indent_level += 1
self.emit ('echo "$1" | jq -c "."')
self.emit ('local -n __d="$1"')
self.emit ('local __first=1')
self.emit ('printf "{"')
self.emit ('for __k in "${!__d[@]}"; do')
self.indent_level += 1
self.emit ('[[ $__first -eq 1 ]] && __first=0 || printf ","')
self.emit ('printf "\\"%s\\":" "$__k"')
self.emit ('local __v="${__d[$__k]}"')
self.emit ('if [[ "$__v" =~ ^-?[0-9]+$ ]]; then')
self.indent_level += 1
self.emit ('printf "%s" "$__v"')
self.indent_level -= 1
self.emit ('elif [[ "$__v" == "true" || "$__v" == "false" || "$__v" == "null" ]]; then')
self.indent_level += 1
self.emit ('printf "%s" "$__v"')
self.indent_level -= 1
self.emit ('else')
self.indent_level += 1
self.emit ('printf "\\"%s\\"" "$__v"')
self.indent_level -= 1
self.emit ('fi')
self.indent_level -= 1
self.emit ('done')
self.emit ('printf "}\\n"')
self.indent_level -= 1
self.emit ("}")
self.emit ()
......
......@@ -516,6 +516,56 @@ foreach i in range (1, 4) {
assert "3" in stdout
class TestJson:
def test_json_parse(self):
code, stdout, _ = run_ct(r'''
json_str = "\{\"name\": \"Alice\", \"age\": 30\}"
data = json.parse (json_str)
print (data.get ("name"))
print (data.get ("age"))
''')
assert code == 0
assert "Alice" in stdout
assert "30" in stdout
def test_json_parse_set(self):
code, stdout, _ = run_ct(r'''
json_str = "\{\"x\": 1\}"
data = json.parse (json_str)
data.set ("y", 2)
print (data.get ("x"))
print (data.get ("y"))
''')
assert code == 0
assert "1" in stdout
assert "2" in stdout
def test_json_stringify(self):
code, stdout, _ = run_ct(r'''
json_str = "\{\"name\": \"Bob\"\}"
data = json.parse (json_str)
data.set ("age", 25)
result = json.stringify (data)
print (result)
''')
assert code == 0
assert "name" in stdout
assert "Bob" in stdout
assert "age" in stdout
assert "25" in stdout
def test_json_has(self):
code, stdout, _ = run_ct(r'''
json_str = "\{\"key\": \"value\"\}"
data = json.parse (json_str)
print (data.has ("key"))
print (data.has ("missing"))
''')
assert code == 0
assert "true" in stdout
assert "false" in stdout
class TestCompileOnly:
def test_compile_hello(self):
code, stdout, stderr = compile_ct('print ("hello")')
......
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