Skip to content

Commit

Permalink
Merge pull request #1978 from usethesource/json-lazy-parser-unparser
Browse files Browse the repository at this point in the history
json lazy parser unparser and support for reading NULL values.
  • Loading branch information
jurgenvinju authored Dec 18, 2024
2 parents 540416b + 9380bf8 commit d0cce09
Show file tree
Hide file tree
Showing 18 changed files with 1,519 additions and 745 deletions.
5 changes: 3 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,8 @@
<goal>package</goal>
</goals>
</execution>
<execution>
<!-- commented out for bootstrapping purposes;
<execution>
<id>default-cli</id>
<phase>compile</phase>
<goals>
Expand All @@ -190,7 +191,7 @@
<ignore>${project.basedir}/src/org/rascalmpl/library/lang/rascal</ignore>
</ignores>
</configuration>
</execution>
</execution> -->
</executions>
</plugin>
<plugin>
Expand Down
7 changes: 7 additions & 0 deletions src/org/rascalmpl/exceptions/RuntimeExceptionFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,13 @@ public static Throw noSuchKey(IValue v, AbstractAST ast, StackTrace trace) {
public static Throw parseError(ISourceLocation loc) {
return new Throw(VF.constructor(ParseError, loc));
}

public static Throw jsonParseError(ISourceLocation loc, String cause, String path) {
return new Throw(VF.constructor(ParseError, loc)
.asWithKeywordParameters().setParameter("reason", VF.string(cause))
.asWithKeywordParameters().setParameter("path", VF.string(path)));
}


public static Throw parseError(ISourceLocation loc, AbstractAST ast, StackTrace trace) {
return new Throw(VF.constructor(ParseError, loc), ast != null ? ast.getLocation() : null, trace);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ public Result<IValue> computeDefaultKeywordParameter(String label, IConstructor
}
else {
Expression def = getKeywordParameterDefaults().get(kwparam);
IValue res = def.interpret(eval).value;
IValue res = def.interpret(eval).getValue();

if (!res.getType().isSubtypeOf(kwType)) {
throw new UnexpectedKeywordArgumentType(kwparam, kwType, res.getType(), ctx.getCurrentAST());
Expand Down
22 changes: 13 additions & 9 deletions src/org/rascalmpl/library/Content.rsc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
@synopsis{Content provides access to the content server of the Rascal terminal for viewing interactive HTML output.}
module Content

import lang::json::IO;

@synopsis{Content wraps the HTTP Request/Response API to support interactive visualization types
on the terminal.}
Expand Down Expand Up @@ -80,30 +81,33 @@ which involves a handy, automatic, encoding of Rascal values into json values.
data Response
= response(Status status, str mimeType, map[str,str] header, str content)
| fileResponse(loc file, str mimeType, map[str,str] header)
| jsonResponse(Status status, map[str,str] header, value val, str dateTimeFormat = "yyyy-MM-dd\'T\'HH:mm:ss\'Z\'", bool explicitConstructorNames=false, bool explicitDataTypes=false)
| jsonResponse(Status status, map[str,str] header, value val, str dateTimeFormat = "yyyy-MM-dd\'T\'HH:mm:ss\'Z\'", JSONFormatter[value] formatter = str (value _) { fail; }, bool explicitConstructorNames=false, bool explicitDataTypes=false)
;


@synopsis{Utility to quickly render a string as HTML content}
Response response(str content, map[str,str] header = ()) = response(ok(), "text/html", header, content);


@synopsis{Utility to quickly report an HTTP error with a user-defined message}
Response response(Status status, str explanation, map[str,str] header = ()) = response(status, "text/plain", header, explanation);


@synopsis{Utility to quickly make a plaintext response.}
Response plain(str text) = response(ok(), "text/plain", (), text);


@synopsis{Utility to serve a file from any source location.}
Response response(loc f, map[str,str] header = ()) = fileResponse(f, mimeTypes[f.extension]?"text/plain", header);

@synopsis{Utility to quickly serve any rascal value as a json text.}
@benefits{
This comes in handy for asynchronous HTTP requests from Javascript clients. Rascal Values are
fully transformed to their respective JSON serialized form before being communicated over HTTP.
}
default Response response(value val, map[str,str] header = ()) = jsonResponse(ok(), header, val);

@synopsis{Utility to quickly serve any rascal value as a json text. This comes in handy for
asynchronous HTTP requests from Javascript.}
default Response response(value val, map[str,str] header = ()) = jsonResponse(ok(), header, val);

@synopsis{Utility to quickly serve any rascal value as a json text, formatting data-types on-the-fly using a `formatter` function}
@benefits{
Fast way of producing JSON strings for embedded DSLs on the Rascal side.
}
Response response(value val, JSONFormatter[value] formatter, map[str,str] header = ()) = jsonResponse(ok(), header, val, formatter=formatter);

@synopsis{Encoding of HTTP status}
data Status
Expand Down
3 changes: 3 additions & 0 deletions src/org/rascalmpl/library/analysis/graphs/Graph.rsc
Original file line number Diff line number Diff line change
Expand Up @@ -289,3 +289,6 @@ reduction's worst case complexity is in the same order as transitive closure its
* reduces cyclic sub-graphs to "empty"
}
Graph[&T] transitiveReduction(Graph[&T] g) = g - (g o g+);
@synopsis{Select the short-cut edges, the ones that transitively close at least two other edges.}
Graph[&T] transitiveEdges(Graph[&T] g) = g o g+;
6 changes: 1 addition & 5 deletions src/org/rascalmpl/library/lang/html5/DOM.rsc
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
module lang::html5::DOM

import List;
import Content;

@synopsis{Generic representation for all HTML tag types.}
@description{
Expand Down Expand Up @@ -459,7 +458,4 @@ str toString(HTML5Node x) {
attrs = { k | HTML5Attr k <- x.kids };
kids = [ k | value k <- x.kids, !(HTML5Attr _ := k) ];
return nodeToString(x.name, attrs, kids);
}

@synopsis{convenience function to render the HTML5Node dom tree in the browser}
public Content serve(HTML5Node x) = html(toString(x));
}
45 changes: 33 additions & 12 deletions src/org/rascalmpl/library/lang/json/IO.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,29 @@
import java.io.StringReader;
import java.io.StringWriter;
import java.nio.charset.Charset;

import java.util.Map;
import java.util.stream.Collectors;
import org.rascalmpl.debug.IRascalMonitor;
import org.rascalmpl.exceptions.RuntimeExceptionFactory;
import org.rascalmpl.library.lang.json.internal.IValueAdapter;
import org.rascalmpl.library.lang.json.internal.JSONReadingTypeVisitor;
import org.rascalmpl.library.lang.json.internal.JsonValueReader;
import org.rascalmpl.library.lang.json.internal.JsonValueWriter;
import org.rascalmpl.types.ReifiedType;
import org.rascalmpl.types.TypeReifier;
import org.rascalmpl.uri.URIResolverRegistry;
import org.rascalmpl.uri.URIUtil;
import org.rascalmpl.values.IRascalValueFactory;
import org.rascalmpl.values.functions.IFunction;

import io.usethesource.vallang.IBool;
import io.usethesource.vallang.IConstructor;
import io.usethesource.vallang.IInteger;
import io.usethesource.vallang.IMap;
import io.usethesource.vallang.ISourceLocation;
import io.usethesource.vallang.IString;
import io.usethesource.vallang.ITuple;
import io.usethesource.vallang.IValue;
import io.usethesource.vallang.IValueFactory;
import io.usethesource.vallang.type.Type;
import io.usethesource.vallang.type.TypeStore;

Expand All @@ -49,10 +54,10 @@
import com.ibm.icu.text.DateFormat;

public class IO {
private final IValueFactory values;
private final IRascalValueFactory values;
private final IRascalMonitor monitor;

public IO(IValueFactory values, IRascalMonitor monitor) {
public IO(IRascalValueFactory values, IRascalMonitor monitor) {
super();
this.values = values;
this.monitor = monitor;
Expand Down Expand Up @@ -99,15 +104,21 @@ public IValue fromJSON(IValue type, IString src) {
}
}


public IValue readJSON(IValue type, ISourceLocation loc, IString dateTimeFormat, IBool lenient, IBool trackOrigins, IBool explicitConstructorNames, IBool explicitDataTypes) {
public IValue readJSON(IValue type, ISourceLocation loc, IString dateTimeFormat, IBool lenient, IBool trackOrigins, IFunction parsers, IMap nulls, IBool explicitConstructorNames, IBool explicitDataTypes) {
TypeStore store = new TypeStore();
Type start = new TypeReifier(values).valueToType((IConstructor) type, store);

if (parsers.getType() instanceof ReifiedType && parsers.getType().getTypeParameters().getFieldType(0).isTop()) {
// ignore the default parser
parsers = null;
}

try (JsonReader in = new JsonReader(URIResolverRegistry.getInstance().getCharacterReader(loc))) {
in.setLenient(lenient.getValue());
return new JsonValueReader(values, store, monitor, trackOrigins.getValue() ? loc : null)
.setCalendarFormat(dateTimeFormat.getValue())
.setParsers(parsers)
.setNulls(unreify(nulls))
.setExplicitConstructorNames(explicitConstructorNames.getValue())
.setExplicitDataTypes(explicitDataTypes.getValue())
.read(in, start);
Expand All @@ -116,19 +127,27 @@ public IValue readJSON(IValue type, ISourceLocation loc, IString dateTimeFormat,
throw RuntimeExceptionFactory.io(values.string(e.getMessage()), null, null);
}
catch (NullPointerException e) {
e.printStackTrace();
throw RuntimeExceptionFactory.io(values.string("NPE"), null, null);
throw RuntimeExceptionFactory.io(values.string("NPE in error handling code"), null, null);
}
}

public IValue parseJSON(IValue type, IString src, IString dateTimeFormat, IBool lenient, IBool trackOrigins, IBool explicitConstructorNames, IBool explicitDataTypes) {
private Map<Type, IValue> unreify(IMap nulls) {
var tr = new TypeReifier(values);
return nulls.stream()
.map(t -> (ITuple) t)
.collect(Collectors.toMap(t -> tr.valueToType((IConstructor) t.get(0)), t -> t.get(1)));
}

public IValue parseJSON(IValue type, IString src, IString dateTimeFormat, IBool lenient, IBool trackOrigins, IFunction parsers, IMap nulls, IBool explicitConstructorNames, IBool explicitDataTypes) {
TypeStore store = new TypeStore();
Type start = new TypeReifier(values).valueToType((IConstructor) type, store);

try (JsonReader in = new JsonReader(new StringReader(src.getValue()))) {
in.setLenient(lenient.getValue());
return new JsonValueReader(values, store, monitor, trackOrigins.getValue() ? URIUtil.rootLocation("unknown") : null)
.setCalendarFormat(dateTimeFormat.getValue())
.setParsers(parsers)
.setNulls(unreify(nulls))
.setExplicitConstructorNames(explicitConstructorNames.getValue())
.setExplicitDataTypes(explicitDataTypes.getValue())
.read(in, start);
Expand All @@ -141,7 +160,7 @@ public IValue parseJSON(IValue type, IString src, IString dateTimeFormat, IBool
}
}

public void writeJSON(ISourceLocation loc, IValue value, IBool unpackedLocations, IString dateTimeFormat, IBool dateTimeAsInt, IInteger indent, IBool dropOrigins, IBool explicitConstructorNames, IBool explicitDataTypes) {
public void writeJSON(ISourceLocation loc, IValue value, IBool unpackedLocations, IString dateTimeFormat, IBool dateTimeAsInt, IInteger indent, IBool dropOrigins, IFunction formatter, IBool explicitConstructorNames, IBool explicitDataTypes) {
try (JsonWriter out = new JsonWriter(new OutputStreamWriter(URIResolverRegistry.getInstance().getOutputStream(loc, false), Charset.forName("UTF8")))) {
if (indent.intValue() > 0) {
out.setIndent(" ".substring(0, indent.intValue() % 9));
Expand All @@ -152,6 +171,7 @@ public void writeJSON(ISourceLocation loc, IValue value, IBool unpackedLocations
.setDatesAsInt(dateTimeAsInt.getValue())
.setUnpackedLocations(unpackedLocations.getValue())
.setDropOrigins(dropOrigins.getValue())
.setFormatters(formatter)
.setExplicitConstructorNames(explicitConstructorNames.getValue())
.setExplicitDataTypes(explicitDataTypes.getValue())
.write(out, value);
Expand All @@ -160,7 +180,7 @@ public void writeJSON(ISourceLocation loc, IValue value, IBool unpackedLocations
}
}

public IString asJSON(IValue value, IBool unpackedLocations, IString dateTimeFormat, IBool dateTimeAsInt, IInteger indent, IBool dropOrigins, IBool explicitConstructorNames, IBool explicitDataTypes) {
public IString asJSON(IValue value, IBool unpackedLocations, IString dateTimeFormat, IBool dateTimeAsInt, IInteger indent, IBool dropOrigins, IFunction formatter, IBool explicitConstructorNames, IBool explicitDataTypes) {
StringWriter string = new StringWriter();

try (JsonWriter out = new JsonWriter(string)) {
Expand All @@ -172,6 +192,7 @@ public IString asJSON(IValue value, IBool unpackedLocations, IString dateTimeFor
.setDatesAsInt(dateTimeAsInt.getValue())
.setUnpackedLocations(unpackedLocations.getValue())
.setDropOrigins(dropOrigins.getValue())
.setFormatters(formatter)
.setExplicitConstructorNames(explicitConstructorNames.getValue())
.setExplicitDataTypes(explicitDataTypes.getValue())
.write(out, value);
Expand Down
Loading

0 comments on commit d0cce09

Please sign in to comment.