Skip to content

Commit 84754be

Browse files
committed
JSON reader and utils edits
1 parent e0521f5 commit 84754be

File tree

5 files changed

+103
-55
lines changed

5 files changed

+103
-55
lines changed

convex-core/src/main/java/convex/core/json/JSONReader.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import convex.core.data.ACell;
1212
import convex.core.data.Cells;
1313
import convex.core.data.Maps;
14-
import convex.core.data.Strings;
1514
import convex.core.data.Vectors;
1615
import convex.core.data.prim.AInteger;
1716
import convex.core.data.prim.CVMBool;
@@ -26,6 +25,8 @@
2625
import convex.core.json.reader.antlr.JSONParser.NumberContext;
2726
import convex.core.json.reader.antlr.JSONParser.ObjContext;
2827
import convex.core.json.reader.antlr.JSONParser.StringContext;
28+
import convex.core.lang.reader.ConvexErrorListener;
29+
import convex.core.util.JSONUtils;
2930

3031
public class JSONReader {
3132

@@ -108,7 +109,7 @@ public void exitNumber(NumberContext ctx) {
108109
public void exitString(StringContext ctx) {
109110
String text=ctx.getText();
110111
String content=text.substring(1, text.length()-1);
111-
push(Strings.create(content));
112+
push(JSONUtils.unescape(content));
112113
}
113114

114115
@Override
@@ -132,19 +133,22 @@ public static ACell read(java.io.Reader r) throws IOException {
132133
return read(CharStreams.fromReader(r));
133134
}
134135

136+
private static final ConvexErrorListener ERROR_LISTENER=new ConvexErrorListener();
137+
138+
135139
static JSONParser getParser(CharStream cs, JSONListener listener) {
136140
// Create lexer and paser for the CharStream
137141
JSONLexer lexer=new JSONLexer(cs);
138142
lexer.removeErrorListeners();
139-
// lexer.addErrorListener(ERROR_LISTENER);
143+
lexer.addErrorListener(ERROR_LISTENER);
140144
CommonTokenStream tokens = new CommonTokenStream(lexer);
141145
JSONParser parser = new JSONParser(tokens);
142146

143147
// We don't need a parse tree, just want to visit everything in our listener
144148
parser.setBuildParseTree(false);
145149
parser.removeErrorListeners();
146150
parser.getInterpreter().setPredictionMode(PredictionMode.SLL); // Seems OK for our grammar?
147-
// parser.addErrorListener(ERROR_LISTENER);
151+
parser.addErrorListener(ERROR_LISTENER);
148152

149153
parser.addParseListener(listener);
150154
return parser;

convex-core/src/main/java/convex/core/lang/reader/ConvexErrorListener.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import convex.core.exceptions.ParseException;
1010

11-
class ConvexErrorListener extends BaseErrorListener {
11+
public class ConvexErrorListener extends BaseErrorListener {
1212

1313
@Override
1414
public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine,

convex-core/src/main/java/convex/core/util/JSONUtils.java

Lines changed: 66 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import convex.core.data.util.BlobBuilder;
2727
import convex.core.json.JSONReader;
2828
import convex.core.lang.RT;
29+
import convex.core.text.Text;
2930

3031
public class JSONUtils {
3132

@@ -100,7 +101,7 @@ public static String jsonKey(Object o) {
100101

101102
if (o instanceof String s) return s;
102103

103-
throw new IllegalArgumentException("Invalid yupe for JSON key: "+Utils.getClassName(o));
104+
throw new IllegalArgumentException("Invalid type for JSON key: "+Utils.getClassName(o));
104105
}
105106

106107
/**
@@ -126,10 +127,10 @@ public static HashMap<String, Object> jsonMap(AMap<?, ?> m) {
126127
* Convert any object to JSON
127128
*
128129
* @param value Value to convert to JSON, may be Java or CVM structure
129-
* @return JSON String
130+
* @return Java String containing valid JSON String
130131
*/
131132
public static String toString(Object value) {
132-
return toCVMString(value).toString();
133+
return toJSONString(value).toString();
133134
}
134135

135136
/**
@@ -147,20 +148,20 @@ public static ACell parse(String jsonString) {
147148
* @param value Value to convert to JSON, may be Java or CVM structure
148149
* @return CVM String containing valid JSON
149150
*/
150-
public static AString toCVMString(Object value) {
151+
public static AString toJSONString(Object value) {
151152
BlobBuilder bb = new BlobBuilder();
152-
appendCVMString(bb, value);
153+
appendJSON(bb, value);
153154
return Strings.create(bb.toBlob());
154155
}
155156

156-
private static void appendCVMString(BlobBuilder bb, Object value) {
157+
private static void appendJSON(BlobBuilder bb, Object value) {
157158
if (value == null) {
158159
bb.append(Strings.NULL);
159160
return;
160161
}
161162

162163
if (value instanceof ACell cell) {
163-
appendCVMString(bb,cell);
164+
appendJSON(bb,cell);
164165
return;
165166
}
166167

@@ -172,11 +173,11 @@ private static void appendCVMString(BlobBuilder bb, Object value) {
172173
Iterator<Map.Entry<Object,Object>> it = mv.entrySet().iterator();
173174
while (it.hasNext()) {
174175
Entry<Object, Object> me = it.next();
175-
if (i>0) bb.append("\n");
176-
appendCVMString(bb, jsonKey(me.getKey()));
176+
if (i>0) bb.append(",");
177+
appendJSON(bb, jsonKey(me.getKey()));
177178
bb.append(':');
178179
bb.append(' ');
179-
appendCVMString(bb, me.getValue());
180+
appendJSON(bb, me.getValue());
180181

181182
i += 1;
182183
}
@@ -190,52 +191,52 @@ private static void appendCVMString(BlobBuilder bb, Object value) {
190191
bb.append('[');
191192
int n = lv.size();
192193
for (int i = 0; i < n; i++) {
193-
if (i>0) bb.append(' ');
194-
appendCVMString(bb, lv.get(i));
194+
if (i>0) bb.append(',');
195+
appendJSON(bb, lv.get(i));
195196
}
196197
bb.append(']');
197198
return;
198199
}
199200

200-
if (value instanceof Boolean bv) {
201-
bb.append(bv ? Strings.TRUE : Strings.FALSE);
202-
return;
203-
}
204-
205-
if (value instanceof String cs) {
206-
bb.append('\"');
207-
appendCVMStringQuoted(bb, cs);
208-
bb.append('\"');
209-
return;
210-
}
211-
212-
if (value instanceof Number nv) {
213-
if (value instanceof Double dv) {
214-
if (Double.isFinite(dv)) {
215-
bb.append(nv.toString());
216-
return;
201+
if (value instanceof Boolean bv) {
202+
bb.append(bv ? Strings.TRUE : Strings.FALSE);
203+
return;
204+
}
205+
206+
if (value instanceof String cs) {
207+
bb.append('\"');
208+
appendCVMStringQuoted(bb, cs);
209+
bb.append('\"');
210+
return;
211+
}
212+
213+
if (value instanceof Number nv) {
214+
if (value instanceof Double dv) {
215+
if (Double.isFinite(dv)) {
216+
bb.append(nv.toString());
217+
return;
218+
} else {
219+
if (Double.isNaN(dv)) {
220+
bb.append(JS_NAN);
217221
} else {
218-
if (Double.isNaN(dv)) {
219-
bb.append(JS_NAN);
220-
} else {
221-
if (dv<0) {
222-
bb.append('-');
223-
}
224-
bb.append("Infinity");
222+
if (dv<0) {
223+
bb.append('-');
225224
}
225+
bb.append("Infinity");
226226
}
227-
return;
228227
}
229-
230-
bb.append(nv.toString());
231-
return;
228+
return;
232229
}
230+
231+
bb.append(nv.toString());
232+
return;
233+
}
233234

234235
throw new IllegalArgumentException("Can't print type as JSON: "+Utils.getClassName(value));
235236
}
236237

237238
// Specialised writing for CVM types
238-
private static void appendCVMString(BlobBuilder bb, ACell value) {
239+
private static void appendJSON(BlobBuilder bb, ACell value) {
239240
if (value == null) {
240241
bb.append(Strings.NULL);
241242
return;
@@ -250,7 +251,7 @@ private static void appendCVMString(BlobBuilder bb, ACell value) {
250251

251252
if (value instanceof ASymbolic cs) {
252253
// Print as the symbolic name string
253-
appendCVMString(bb, cs.getName());
254+
appendJSON(bb, cs.getName());
254255
return;
255256
}
256257

@@ -259,12 +260,12 @@ private static void appendCVMString(BlobBuilder bb, ACell value) {
259260
bb.append('{');
260261
long n = mv.size();
261262
for (long i = 0; i < n; i++) {
262-
if (i>0) bb.append(' ');
263+
if (i>0) bb.append(',');
263264
MapEntry<?,?> me=mv.entryAt(i);
264-
appendCVMString(bb, jsonKey(me.getKey()));
265+
appendJSON(bb, jsonKey(me.getKey()));
265266
bb.append(':');
266267
bb.append(' ');
267-
appendCVMString(bb, me.getValue());
268+
appendJSON(bb, me.getValue());
268269
}
269270
bb.append('}');
270271
return;
@@ -275,21 +276,21 @@ private static void appendCVMString(BlobBuilder bb, ACell value) {
275276
bb.append('[');
276277
long n = lv.count();
277278
for (long i = 0; i < n; i++) {
278-
if (i>0) bb.append(' ');
279-
appendCVMString(bb, lv.get(i));
279+
if (i>0) bb.append(',');
280+
appendJSON(bb, lv.get(i));
280281
}
281282
bb.append(']');
282283
return;
283284
}
284285

285286

286287
if (value instanceof CVMLong nv) {
287-
appendCVMString(bb,nv.longValue());
288+
appendJSON(bb,nv.longValue());
288289
return;
289290
}
290291

291292
if (value instanceof CVMDouble nv) {
292-
appendCVMString(bb,nv.doubleValue());
293+
appendJSON(bb,nv.doubleValue());
293294
return;
294295
}
295296

@@ -350,4 +351,20 @@ private static AString getReplacementString(char c) {
350351
return StringShort.create(Blob.wrap(new byte[] { '\\', 'u', '0', '0', (byte) Utils.toHexChar((c >> 4) & 0x000f), (byte) Utils.toHexChar(c & 0x000f) }));
351352
}
352353

354+
/**
355+
* Escape a string for inclusion in JSON
356+
* @param content
357+
* @return
358+
*/
359+
public static AString escape(String content) {
360+
BlobBuilder bb=new BlobBuilder();
361+
appendCVMStringQuoted(bb,content);
362+
return Strings.create(bb.toBlob());
363+
}
364+
365+
public static ACell unescape(String content) {
366+
String unes=Text.unescapeJava(content);
367+
return Strings.create(unes);
368+
}
369+
353370
}

convex-core/src/test/java/convex/core/data/StringsTest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,9 @@ public void doStringTest(AString a) {
238238
// JSON print
239239
JSONUtils.toString(a);
240240

241+
// JSON escape / unescape
242+
assertEquals(a,JSONUtils.unescape(JSONUtils.escape(js).toString()));
243+
241244
// fall back to bloblike tests
242245
BlobsTest.doBlobLikeTests(a);
243246
}

convex-core/src/test/java/convex/core/utils/JSONUtilsTest.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import static org.junit.jupiter.api.Assertions.assertEquals;
44
import static org.junit.jupiter.api.Assertions.assertNull;
55
import static org.junit.jupiter.api.Assertions.assertSame;
6+
import static org.junit.jupiter.api.Assertions.assertThrows;
67

78
import java.util.ArrayList;
89
import java.util.HashMap;
@@ -20,12 +21,14 @@
2021
import convex.core.data.Lists;
2122
import convex.core.data.Maps;
2223
import convex.core.data.Sets;
24+
import convex.core.data.StringShort;
2325
import convex.core.data.Strings;
2426
import convex.core.data.Vectors;
2527
import convex.core.data.prim.CVMBool;
2628
import convex.core.data.prim.CVMChar;
2729
import convex.core.data.prim.CVMDouble;
2830
import convex.core.data.prim.CVMLong;
31+
import convex.core.exceptions.ParseException;
2932
import convex.core.lang.RT;
3033
import convex.core.util.JSONUtils;
3134

@@ -40,13 +43,15 @@ public class JSONUtilsTest {
4043

4144
assertEquals("\"nil\"",JSONUtils.toString("nil"));
4245

43-
assertEquals("[1 2 null]",JSONUtils.toString(Vectors.of(1,2,null)));
46+
assertEquals("[1,2,null]",JSONUtils.toString(Vectors.of(1,2,null)));
4447

4548
assertEquals("true",JSONUtils.toString(true));
4649
assertEquals("false",JSONUtils.toString(false));
4750
assertEquals("true",JSONUtils.toString(CVMBool.TRUE));
4851
assertEquals("false",JSONUtils.toString(CVMBool.FALSE));
4952

53+
assertEquals("\"\\n\"",JSONUtils.toString("\n"));
54+
assertEquals("\"\\\"\"",JSONUtils.toString("\""));
5055

5156
assertEquals("\"foo\"",JSONUtils.toString(Symbols.FOO));
5257
assertEquals("\"foo\"",JSONUtils.toString(Keywords.FOO));
@@ -61,6 +66,7 @@ public void testParse() {
6166

6267
assertSame(Vectors.empty(),JSONUtils.parse("[]"));
6368
assertEquals(Vectors.of(true,null),JSONUtils.parse("[true,null]"));
69+
assertEquals(Vectors.of(1,2),JSONUtils.parse("[1,2]"));
6470

6571
assertSame(CVMLong.ONE,JSONUtils.parse("1"));
6672
assertEquals(CVMDouble.ONE,JSONUtils.parse("1.0"));
@@ -71,6 +77,21 @@ public void testParse() {
7177
assertEquals(Maps.of(Strings.NIL,1),JSONUtils.parse("{\"nil\": 1}"));
7278
assertEquals(Maps.of(Strings.EMPTY,Vectors.empty()),JSONUtils.parse("{\"\": []}"));
7379

80+
// Some errors
81+
assertThrows(ParseException.class,()->JSONUtils.parse("[1 2]"));
82+
assertThrows(ParseException.class,()->JSONUtils.parse("1,2"));
83+
assertThrows(ParseException.class,()->JSONUtils.parse("{"));
84+
assertThrows(ParseException.class,()->JSONUtils.parse("3]"));
85+
86+
// Special cases
87+
assertEquals(Strings.create("a\"b"),JSONUtils.parse("\"a\\\"b\""));
88+
89+
}
90+
91+
@Test
92+
public void testEscape() {
93+
assertEquals("\\n",JSONUtils.escape("\n").toString());
94+
assertEquals(StringShort.create(" \\\""),JSONUtils.escape(" \""));
7495
}
7596

7697
@Test
@@ -131,6 +152,9 @@ private void doJSONRoundTrip(Object o, ACell c) {
131152
String js1=JSONUtils.toString(o);
132153
String js2=JSONUtils.toString(c);
133154
assertEquals(js1.length(),js2.length()); // should be same length, orders might differ
155+
156+
assertEquals(c,JSONUtils.parse(js1),()->"JSON="+js1);
157+
assertEquals(c,JSONUtils.parse(js2));
134158
}
135159

136160
}

0 commit comments

Comments
 (0)