Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions bench/src/sjsonnet/bench/MaterializerBenchmark.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import org.openjdk.jmh.infra.*
import sjsonnet.*
import ujson.JsVisitor

import java.io.{StringWriter, Writer}
import java.io.Writer
import java.util.concurrent.TimeUnit

@BenchmarkMode(Array(Mode.AverageTime))
Expand Down Expand Up @@ -59,8 +59,8 @@ class MaterializerBenchmark {
new PrettyYamlRenderer(_, indent = 3, getCurrentPosition = () => null)
)

private def renderWith[T <: Writer](r: StringWriter => JsVisitor[T, T]): String = {
val writer = new StringWriter
private def renderWith[T <: Writer](r: StringBuilderWriter => JsVisitor[T, T]): String = {
val writer = new StringBuilderWriter
val renderer = r(writer)
interp.materialize(value, renderer)
writer.toString
Expand Down
6 changes: 2 additions & 4 deletions sjsonnet/src/sjsonnet/PrettyYamlRenderer.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package sjsonnet

import java.io.{StringWriter, Writer}
import java.io.Writer

import upickle.core.{ArrVisitor, ObjVisitor}

Expand All @@ -12,7 +12,7 @@ import scala.collection.mutable
* writes to a generic `Writer`.
*/
class PrettyYamlRenderer(
out: Writer = new java.io.StringWriter(),
out: Writer = new StringBuilderWriter(),
indentArrayInObject: Boolean = false,
indent: Int,
idealWidth: Int = 80,
Expand Down Expand Up @@ -78,8 +78,6 @@ class PrettyYamlRenderer(
// Strings which look like booleans/nulls/numbers/dates/etc.,
// or have leading/trailing spaces, are rendered single-quoted
else if (PrettyYamlRenderer.stringNeedsToBeQuoted(str)) {
val strWriter = new StringWriter
BaseRenderer.escape(strWriter, s, unicode = true)
val quotedStr = "'" + str.replace("'", "''") + "'"
PrettyYamlRenderer.writeWrappedString(
quotedStr,
Expand Down
11 changes: 9 additions & 2 deletions sjsonnet/src/sjsonnet/Renderer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ import upickle.core.{ArrVisitor, ObjVisitor}
final class StringBuilderWriter(initialCapacity: Int = 16) extends Writer {
private[this] val builder = new java.lang.StringBuilder(initialCapacity)

/**
* Exposes the underlying [[java.lang.StringBuilder]] for callers that need direct
* length/charAt/setLength operations (e.g. [[YamlRenderer]] trims trailing spaces).
* Single-threaded use only; the writer is not thread-safe.
*/
def getBuilder: java.lang.StringBuilder = builder

override def write(c: Int): Unit =
builder.append(c.toChar)

Expand Down Expand Up @@ -45,7 +52,7 @@ final class StringBuilderWriter(initialCapacity: Int = 16) extends Writer {
* - Custom printing of Doubles
* - Custom printing of empty dictionaries and arrays
*/
class Renderer(out: Writer = new java.io.StringWriter(), indent: Int = -1)
class Renderer(out: Writer = new StringBuilderWriter(), indent: Int = -1)
extends BaseCharRenderer(out, indent) {
var newlineBuffered = false
override def visitFloat64(d: Double, index: Int): Writer = {
Expand Down Expand Up @@ -153,7 +160,7 @@ class Renderer(out: Writer = new java.io.StringWriter(), indent: Int = -1)
}
}

class PythonRenderer(out: Writer = new java.io.StringWriter(), indent: Int = -1)
class PythonRenderer(out: Writer = new StringBuilderWriter(), indent: Int = -1)
extends BaseCharRenderer(out, indent) {

override def visitNull(index: Int): Writer = {
Expand Down
39 changes: 21 additions & 18 deletions sjsonnet/src/sjsonnet/YamlRenderer.scala
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package sjsonnet

import java.io.StringWriter
import upickle.core.{ArrVisitor, ObjVisitor, SimpleVisitor, Visitor}

class YamlRenderer(
_out: StringWriter = new java.io.StringWriter(),
_out: StringBuilderWriter = new StringBuilderWriter(),
indentArrayInObject: Boolean = false,
quoteKeys: Boolean = true,
indent: Int = 2)
Expand All @@ -13,11 +12,11 @@ class YamlRenderer(
var dashBuffered = false
var afterKey = false
private var topLevel = true
private val outBuffer = _out.getBuffer
private val outBuffer = _out.getBuilder

private val yamlKeyVisitor = new SimpleVisitor[StringWriter, StringWriter]() {
private val yamlKeyVisitor = new SimpleVisitor[StringBuilderWriter, StringBuilderWriter]() {
override def expectedMsg = "Expected a string key"
override def visitString(s: CharSequence, index: Int): StringWriter = {
override def visitString(s: CharSequence, index: Int): StringBuilderWriter = {
YamlRenderer.this.flushBuffer()
if (quoteKeys || !YamlRenderer.isSafeBareKey(s.toString)) {
upickle.core.RenderUtils.escapeChar(
Expand All @@ -39,7 +38,7 @@ class YamlRenderer(
elemBuilder.writeOutToIfLongerThan(_out, if (depth <= 0 || topLevel) 0 else 1000)
}

override def visitString(s: CharSequence, index: Int): StringWriter = {
override def visitString(s: CharSequence, index: Int): StringBuilderWriter = {
flushBuffer()
val len = s.length()
if (len == 0) {
Expand Down Expand Up @@ -69,7 +68,7 @@ class YamlRenderer(
_out
}

override def visitFloat64(d: Double, index: Int): StringWriter = {
override def visitFloat64(d: Double, index: Int): StringBuilderWriter = {
flushBuffer()
appendString(RenderUtils.renderDouble(d))
flushCharBuilder()
Expand Down Expand Up @@ -99,8 +98,10 @@ class YamlRenderer(
dashBuffered = false
}

override def visitArray(length: Int, index: Int): ArrVisitor[StringWriter, StringWriter] =
new ArrVisitor[StringWriter, StringWriter] {
override def visitArray(
length: Int,
index: Int): ArrVisitor[StringBuilderWriter, StringBuilderWriter] =
new ArrVisitor[StringBuilderWriter, StringBuilderWriter] {
var empty = true
flushBuffer()

Expand All @@ -115,14 +116,14 @@ class YamlRenderer(
if (dedentInObject) depth -= 1
dashBuffered = true

def subVisitor: Visitor[StringWriter, StringWriter] = YamlRenderer.this
def visitValue(v: StringWriter, index: Int): Unit = {
def subVisitor: Visitor[StringBuilderWriter, StringBuilderWriter] = YamlRenderer.this
def visitValue(v: StringBuilderWriter, index: Int): Unit = {
empty = false
flushBuffer()
newlineBuffered = true
dashBuffered = true
}
def visitEnd(index: Int): StringWriter = {
def visitEnd(index: Int): StringBuilderWriter = {
if (!dedentInObject) depth -= 1
if (empty) {
elemBuilder.ensureLength(2)
Expand All @@ -136,18 +137,20 @@ class YamlRenderer(
}
}

override def visitObject(length: Int, index: Int): ObjVisitor[StringWriter, StringWriter] =
new ObjVisitor[StringWriter, StringWriter] {
override def visitObject(
length: Int,
index: Int): ObjVisitor[StringBuilderWriter, StringBuilderWriter] =
new ObjVisitor[StringBuilderWriter, StringBuilderWriter] {
var empty = true
flushBuffer()
if (!topLevel) depth += 1
topLevel = false

if (afterKey) newlineBuffered = true

def subVisitor: Visitor[StringWriter, StringWriter] = YamlRenderer.this
def subVisitor: Visitor[StringBuilderWriter, StringBuilderWriter] = YamlRenderer.this

def visitKey(index: Int): Visitor[StringWriter, StringWriter] = yamlKeyVisitor
def visitKey(index: Int): Visitor[StringBuilderWriter, StringBuilderWriter] = yamlKeyVisitor

def visitKeyValue(s: Any): Unit = {
empty = false
Expand All @@ -160,12 +163,12 @@ class YamlRenderer(
newlineBuffered = false
}

def visitValue(v: StringWriter, index: Int): Unit = {
def visitValue(v: StringBuilderWriter, index: Int): Unit = {
newlineBuffered = true
afterKey = false
}

def visitEnd(index: Int): StringWriter = {
def visitEnd(index: Int): StringBuilderWriter = {
if (empty) {
elemBuilder.ensureLength(2)
elemBuilder.append('{')
Expand Down
23 changes: 14 additions & 9 deletions sjsonnet/src/sjsonnet/stdlib/StringModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1320,12 +1320,16 @@ object StringModule extends AbstractFunctionModule {
* quotes, escapes backslashes, and escapes unprintable characters.
*/
builtin("escapeStringJson", "str_") { (pos, ev, str: Val) =>
if (str.value.isInstanceOf[Val.Str]) {
Materializer.stringify(str)(ev)
} else {
val out = new java.io.StringWriter()
BaseRenderer.escape(out, Materializer.stringify(str)(ev), unicode = true)
out.toString
val v = str.value
v match {
case s: Val.Str =>
val out = new StringBuilderWriter(s.str.length + 16)
BaseRenderer.escape(out, s.str, unicode = true)
out.toString
case _ =>
val out = new StringBuilderWriter(64)
BaseRenderer.escape(out, Materializer.stringify(v)(ev), unicode = true)
out.toString
}
},
/**
Expand All @@ -1336,8 +1340,9 @@ object StringModule extends AbstractFunctionModule {
* Convert str to allow it to be embedded in Python. This is an alias for std.escapeStringJson.
*/
builtin("escapeStringPython", "str") { (pos, ev, str: Val) =>
val out = new java.io.StringWriter()
BaseRenderer.escape(out, stdToString(str)(ev), unicode = true)
val s = stdToString(str)(ev)
val out = new StringBuilderWriter(s.length + 16)
BaseRenderer.escape(out, s, unicode = true)
out.toString
},
/**
Expand All @@ -1350,7 +1355,7 @@ object StringModule extends AbstractFunctionModule {
*/
builtin("escapeStringXML", "str") { (_, ev, str: Val) =>
val string = stdToString(str)(ev)
val out = new java.io.StringWriter()
val out = new StringBuilderWriter(string.length + 16)
var i = 0
while (i < string.length) {
string.charAt(i) match {
Expand Down
Loading