Skip to content

Commit

Permalink
[j8] Raise encode errors, options for showing object cycles and non-data
Browse files Browse the repository at this point in the history
Still need to catch encode errors.
  • Loading branch information
Andy C committed Dec 28, 2023
1 parent 0a5afdd commit cc59573
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 25 deletions.
8 changes: 7 additions & 1 deletion core/error.py
Expand Up @@ -237,7 +237,13 @@ class Encode(Exception):
- binary data that can't be represented in JSON
- if using Unicode replacement char, then it won't fail
"""
pass
def __init__(self, msg):
# type: (str) -> None
self.msg = msg

def Message(self):
# type: () -> str
return self.msg


def e_usage(msg, location):
Expand Down
60 changes: 45 additions & 15 deletions data_lang/j8.py
Expand Up @@ -112,8 +112,8 @@ def Print(self, val, buf):
pass


SHOW_CYCLES = 1 # pretty-printing
SHOW_NON_DATA = 2 # non-data objects like Eggex can be <Eggex 0xff>
SHOW_CYCLES = 1 << 1 # show as [...] or {...} I think, with object ID
SHOW_NON_DATA = 1 << 2 # non-data objects like Eggex can be <Eggex 0xff>


class Printer(object):
Expand Down Expand Up @@ -142,23 +142,26 @@ def __init__(self):
self.spaces = {0: ''} # cache of strings with spaces

# Could be PrintMessage or PrintJsonMessage()
def _Print(self, val, buf, indent):
# type: (value_t, mylib.BufWriter, int) -> None
def _Print(self, val, buf, indent, options=0):
# type: (value_t, mylib.BufWriter, int, int) -> None
"""
Args:
indent: number of spaces to indent, or -1 for everything on one line
"""
p = InstancePrinter(buf, indent, self.options, self.spaces)
p = InstancePrinter(buf, indent, options, self.spaces)
p.Print(val)

def PrintMessage(self, val, buf, indent):
# type: (value_t, mylib.BufWriter, int) -> None
""" For j8 write (x) """
""" For j8 write (x) and toJ8() """

# TODO: handle error.Encode

self._Print(val, buf, indent)

def PrintJsonMessage(self, val, buf, indent):
# type: (value_t, mylib.BufWriter, int) -> None
""" For json write (x)
""" For json write (x) and toJson()
Doesn't decay to b"" strings
Either raise error.Decode() or use unicode replacement char
Expand All @@ -169,24 +172,30 @@ def DebugPrint(self, val, f):
# type: (value_t, mylib.Writer) -> None
"""
For = operator.
TODO: set options SHOW_CYCLES SHOW_NON_DATA
"""
# Note: I think error.Encode is impossible here - we show cycles and
# non-data

buf = mylib.BufWriter()
self._Print(val, buf, -1)
self._Print(val, buf, -1, options=SHOW_CYCLES | SHOW_NON_DATA)
f.write(buf.getvalue())
f.write('\n')

def PrintLine(self, val, f):
# type: (value_t, mylib.Writer) -> None
""" For pp line (x) """

buf = mylib.BufWriter()
self._Print(val, buf, -1)
self._Print(val, buf, -1, options=SHOW_CYCLES | SHOW_NON_DATA)
f.write(buf.getvalue())
f.write('\n')

def MaybeEncodeString(self, s):
# type: (str) -> str
""" For write --j8 $s
""" For write --j8 $s and compexport
Do we also have write --json or --json-string? That require handling
error.Encode()
Do we also want write (x) to use J8 notation? It's the default
serialization. But j8 write (x) is simple enough.
Expand Down Expand Up @@ -285,7 +294,12 @@ def Print(self, val, level=0):
# Cycle detection, only for containers that can be in cycles
heap_id = vm.HeapValueId(val)
if heap_id in self.seen:
raise AssertionError()
if self.options & SHOW_CYCLES:
self.buf.write('[ ...%s ]' % vm.ValueIdString(val))
return
else:
# node.js prints which index closes the cycle
raise error.Encode("JSON: Can't encode List in object cycle")
self.seen[heap_id] = True

self.buf.write('[')
Expand All @@ -308,7 +322,13 @@ def Print(self, val, level=0):
# Cycle detection, only for containers that can be in cycles
heap_id = vm.HeapValueId(val)
if heap_id in self.seen:
raise AssertionError()
if self.options & SHOW_CYCLES:
self.buf.write('{ ...%s }' % vm.ValueIdString(val))
return
else:
# node.js prints which key closes the cycle
raise error.Encode("JSON: Can't encode Dict in object cycle")

self.seen[heap_id] = True

self.buf.write('{')
Expand Down Expand Up @@ -389,8 +409,18 @@ def Print(self, val, level=0):
self.buf.write('}')

else:
# TODO: Option to use the <> format of = operator?
pass
pass # mycpp workaround
if self.options & SHOW_NON_DATA:
# Similar to = operator, ui.DebugPrint()
# TODO: that prints value.Range in a special way
from core import ui
ysh_type = ui.ValType(val)
id_str = vm.ValueIdString(val)
self.buf.write('<%s%s>' % (ysh_type, id_str))
else:
from core import ui # TODO: break dep
raise error.Encode("Can't serialize object of type %s" %
ui.ValType(val))


if mylib.PYTHON:
Expand Down
6 changes: 6 additions & 0 deletions data_lang/json-errors.sh
Expand Up @@ -60,6 +60,12 @@ test-lex-errors() {

test-encode() {
_error-case-X 1 'var d = {}; setvar d.k = d; json write (d)'

_error-case-X 1 'var L = []; call L->append(L); json write (L)'

# This should fail!
# But not pp line (L)
_error-case-X 1 'var L = []; call L->append(/d+/); json write (L)'
}

#
Expand Down
7 changes: 7 additions & 0 deletions data_lang/json-survey.sh
Expand Up @@ -60,11 +60,18 @@ obj-cycles() {
python3 -c 'import json; val = {}; val["k"] = val; print(json.dumps(val))' || true
echo

python3 -c 'import json; val = []; val.append(val); print(json.dumps(val))' || true
echo

# Better error message than Python!
# TypeError: Converting circular structure to JSON
# --> starting at object with constructor 'Object'
# --- property 'k' closes the circle
nodejs -e 'var val = {}; val["k"] = val; console.log(JSON.stringify(val))' || true
echo

nodejs -e 'var val = []; val.push(val); console.log(JSON.stringify(val))' || true
echo
}

"$@"
37 changes: 28 additions & 9 deletions spec/ysh-json.test.sh
@@ -1,4 +1,4 @@
## oils_failures_allowed: 1
## oils_failures_allowed: 2
## tags: dev-minimal

#### usage errors
Expand Down Expand Up @@ -167,29 +167,48 @@ status=0

# undefined var
json write (a)
echo status=$?
echo 'should have failed'

## status: 1
## STDOUT:
## END

#### json write of data structure with cycle
#### json write of List in cycle

var L = [1, 2, 3]
setvar L[0] = L

# TODO: I guess it should exit with status 1 or 3
shopt -s ysh:upgrade
fopen >tmp.txt {
pp line (L)
}
fgrep -n -o '[ ...' tmp.txt

json write (L)
echo 'should have failed'

## status: 1
## STDOUT:
1:[ ...
## END

var d = {k: 'v'}
setvar d.k1 = 'v2'
#### json write of Dict in cycle

# This makes it hang? But not interactively
#setvar d.k2 = d
var d = {}
setvar d.k = d

pp line (d)
shopt -s ysh:upgrade
fopen >tmp.txt {
pp line (d)
}
fgrep -n -o '{ ...' tmp.txt

json write (d)
echo 'should have failed'

## status: 1
## STDOUT:
1:{ ...
## END

#### j8 write
Expand Down

0 comments on commit cc59573

Please sign in to comment.